Merge branch 'main' into trade-success-animation
This commit is contained in:
commit
c3f107820e
|
@ -1,22 +1,52 @@
|
|||
import { makeApiRequest, parseResolution } from './helpers'
|
||||
import { subscribeOnStream, unsubscribeFromStream } from './streaming'
|
||||
import mangoStore from '@store/mangoStore'
|
||||
import {
|
||||
DatafeedConfiguration,
|
||||
LibrarySymbolInfo,
|
||||
ResolutionString,
|
||||
SearchSymbolResultItem,
|
||||
} from '@public/charting_library/charting_library'
|
||||
|
||||
export const SUPPORTED_RESOLUTIONS = [
|
||||
'1',
|
||||
'3',
|
||||
'5',
|
||||
'15',
|
||||
'30',
|
||||
'60',
|
||||
'120',
|
||||
'240',
|
||||
'1D',
|
||||
'1W',
|
||||
] as const
|
||||
|
||||
type BaseBar = {
|
||||
low: number
|
||||
high: number
|
||||
open: number
|
||||
close: number
|
||||
}
|
||||
|
||||
type KlineBar = BaseBar & {
|
||||
volume: number
|
||||
timestamp: number
|
||||
}
|
||||
|
||||
type TradingViewBar = BaseBar & {
|
||||
time: number
|
||||
}
|
||||
|
||||
type Bar = KlineBar & TradingViewBar
|
||||
|
||||
type SymbolInfo = LibrarySymbolInfo & {
|
||||
address: string
|
||||
}
|
||||
|
||||
const lastBarsCache = new Map()
|
||||
|
||||
const configurationData = {
|
||||
supported_resolutions: [
|
||||
'1',
|
||||
'3',
|
||||
'5',
|
||||
'15',
|
||||
'30',
|
||||
'60',
|
||||
'120',
|
||||
'240',
|
||||
'1D',
|
||||
'1W',
|
||||
],
|
||||
supported_resolutions: SUPPORTED_RESOLUTIONS,
|
||||
intraday_multipliers: ['1', '3', '5', '15', '30', '60', '120', '240'],
|
||||
exchanges: [],
|
||||
}
|
||||
|
@ -29,42 +59,101 @@ const configurationData = {
|
|||
// return data.data.tokens
|
||||
// }
|
||||
|
||||
export const queryBars = async (
|
||||
tokenAddress: string,
|
||||
resolution: typeof SUPPORTED_RESOLUTIONS[number],
|
||||
periodParams: {
|
||||
firstDataRequest: boolean
|
||||
from: number
|
||||
to: number
|
||||
}
|
||||
): Promise<Bar[]> => {
|
||||
const { from, to } = periodParams
|
||||
const urlParameters = {
|
||||
address: tokenAddress,
|
||||
type: parseResolution(resolution),
|
||||
time_from: from,
|
||||
time_to: to,
|
||||
}
|
||||
const query = Object.keys(urlParameters)
|
||||
.map(
|
||||
(name: string) =>
|
||||
`${name}=${encodeURIComponent((urlParameters as any)[name])}`
|
||||
)
|
||||
.join('&')
|
||||
|
||||
const data = await makeApiRequest(`defi/ohlcv/pair?${query}`)
|
||||
if (!data.success || data.data.items.length === 0) {
|
||||
return []
|
||||
}
|
||||
let bars: Bar[] = []
|
||||
for (const bar of data.data.items) {
|
||||
if (bar.unixTime >= from && bar.unixTime < to) {
|
||||
const timestamp = bar.unixTime * 1000
|
||||
bars = [
|
||||
...bars,
|
||||
{
|
||||
time: timestamp,
|
||||
low: bar.l,
|
||||
high: bar.h,
|
||||
open: bar.o,
|
||||
close: bar.c,
|
||||
volume: bar.v,
|
||||
timestamp,
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
return bars
|
||||
}
|
||||
|
||||
export default {
|
||||
onReady: (callback: any) => {
|
||||
console.log('[onReady]: Method call')
|
||||
setTimeout(() => callback(configurationData))
|
||||
onReady: (callback: (configuration: DatafeedConfiguration) => void) => {
|
||||
// console.log('[onReady]: Method call')
|
||||
setTimeout(() => callback(configurationData as any))
|
||||
},
|
||||
|
||||
searchSymbols: async (
|
||||
_userInput: any,
|
||||
_exchange: any,
|
||||
_symbolType: any,
|
||||
_onResultReadyCallback: any
|
||||
_userInput: string,
|
||||
_exchange: string,
|
||||
_symbolType: string,
|
||||
_onResultReadyCallback: (items: SearchSymbolResultItem[]) => void
|
||||
) => {
|
||||
return
|
||||
},
|
||||
|
||||
resolveSymbol: async (
|
||||
symbolAddress: any,
|
||||
onSymbolResolvedCallback: any,
|
||||
_onResolveErrorCallback: any,
|
||||
_extension: any
|
||||
symbolAddress: string,
|
||||
onSymbolResolvedCallback: (symbolInfo: SymbolInfo) => void
|
||||
// _onResolveErrorCallback: any,
|
||||
// _extension: any
|
||||
) => {
|
||||
console.log('[resolveSymbol]: Method call', symbolAddress)
|
||||
// console.log(
|
||||
// '[resolveSymbol]: Method call',
|
||||
// symbolAddress,
|
||||
// onSymbolResolvedCallback
|
||||
// )
|
||||
// const symbols = await getAllSymbols()
|
||||
// let symbolItem = symbols.find((item: any) => item.address === symbolAddress)
|
||||
// console.log('========symbols:', symbolItem, symbols)
|
||||
let symbolItem: any
|
||||
let symbolItem:
|
||||
| {
|
||||
address: string
|
||||
type: string
|
||||
symbol: string
|
||||
}
|
||||
| undefined
|
||||
|
||||
if (!symbolItem) {
|
||||
symbolItem = {
|
||||
address: symbolAddress,
|
||||
type: 'pair',
|
||||
symbol: '',
|
||||
}
|
||||
}
|
||||
const ticker = mangoStore.getState().selectedMarket.name
|
||||
|
||||
const symbolInfo = {
|
||||
const symbolInfo: SymbolInfo = {
|
||||
address: symbolItem.address,
|
||||
ticker: symbolItem.address,
|
||||
name: symbolItem.symbol || symbolItem.address,
|
||||
|
@ -75,89 +164,78 @@ export default {
|
|||
minmov: 1,
|
||||
pricescale: 100,
|
||||
has_intraday: true,
|
||||
has_no_volume: true,
|
||||
has_weekly_and_monthly: false,
|
||||
supported_resolutions: configurationData.supported_resolutions,
|
||||
supported_resolutions: configurationData.supported_resolutions as any,
|
||||
intraday_multipliers: configurationData.intraday_multipliers,
|
||||
volume_precision: 2,
|
||||
data_status: 'streaming',
|
||||
full_name: '',
|
||||
exchange: '',
|
||||
listed_exchange: '',
|
||||
format: 'price',
|
||||
}
|
||||
|
||||
console.log('[resolveSymbol]: Symbol resolved', symbolAddress)
|
||||
// console.log('[resolveSymbol]: Symbol resolved', symbolAddress)
|
||||
onSymbolResolvedCallback(symbolInfo)
|
||||
},
|
||||
|
||||
getBars: async (
|
||||
symbolInfo: any,
|
||||
resolution: any,
|
||||
periodParams: any,
|
||||
onHistoryCallback: any,
|
||||
onErrorCallback: any
|
||||
symbolInfo: SymbolInfo,
|
||||
resolution: ResolutionString,
|
||||
periodParams: {
|
||||
countBack: number
|
||||
firstDataRequest: boolean
|
||||
from: number
|
||||
to: number
|
||||
},
|
||||
onHistoryCallback: (
|
||||
bars: Bar[],
|
||||
t: {
|
||||
noData: boolean
|
||||
}
|
||||
) => void,
|
||||
onErrorCallback: (e: any) => void
|
||||
) => {
|
||||
const { from, to, firstDataRequest } = periodParams
|
||||
console.log('[getBars]: Method call', symbolInfo, resolution, from, to)
|
||||
const urlParameters = {
|
||||
address: symbolInfo.address,
|
||||
type: parseResolution(resolution),
|
||||
time_from: from,
|
||||
time_to: to,
|
||||
}
|
||||
const query = Object.keys(urlParameters)
|
||||
.map(
|
||||
(name: any) =>
|
||||
`${name}=${encodeURIComponent((urlParameters as any)[name])}`
|
||||
)
|
||||
.join('&')
|
||||
try {
|
||||
const data = await makeApiRequest(`defi/ohlcv/pair?${query}`)
|
||||
if (!data.success || data.data.items.length === 0) {
|
||||
const { firstDataRequest } = periodParams
|
||||
const bars = await queryBars(
|
||||
symbolInfo.address,
|
||||
resolution as any,
|
||||
periodParams
|
||||
)
|
||||
if (!bars || bars.length === 0) {
|
||||
// "noData" should be set if there is no data in the requested period.
|
||||
onHistoryCallback([], {
|
||||
noData: true,
|
||||
})
|
||||
return
|
||||
}
|
||||
let bars: any = []
|
||||
data.data.items.forEach((bar: any) => {
|
||||
if (bar.unixTime >= from && bar.unixTime < to) {
|
||||
bars = [
|
||||
...bars,
|
||||
{
|
||||
time: bar.unixTime * 1000,
|
||||
low: bar.l,
|
||||
high: bar.h,
|
||||
open: bar.o,
|
||||
close: bar.c,
|
||||
},
|
||||
]
|
||||
}
|
||||
})
|
||||
|
||||
if (firstDataRequest) {
|
||||
lastBarsCache.set(symbolInfo.address, {
|
||||
...bars[bars.length - 1],
|
||||
})
|
||||
}
|
||||
console.log(`[getBars]: returned ${bars.length} bar(s)`)
|
||||
// console.log(`[getBars]: returned ${bars.length} bar(s)`)
|
||||
onHistoryCallback(bars, {
|
||||
noData: false,
|
||||
})
|
||||
} catch (error) {
|
||||
console.log('[getBars]: Get error', error)
|
||||
console.warn('[getBars]: Get error', error)
|
||||
onErrorCallback(error)
|
||||
}
|
||||
},
|
||||
|
||||
subscribeBars: (
|
||||
symbolInfo: any,
|
||||
resolution: any,
|
||||
onRealtimeCallback: any,
|
||||
subscriberUID: any,
|
||||
onResetCacheNeededCallback: any
|
||||
symbolInfo: SymbolInfo,
|
||||
resolution: string,
|
||||
onRealtimeCallback: (data: any) => void,
|
||||
subscriberUID: string,
|
||||
onResetCacheNeededCallback: () => void
|
||||
) => {
|
||||
console.log(
|
||||
'[subscribeBars]: Method call with subscriberUID:',
|
||||
subscriberUID
|
||||
)
|
||||
// console.log(
|
||||
// '[subscribeBars]: Method call with subscriberUID:',
|
||||
// subscriberUID
|
||||
// )
|
||||
subscribeOnStream(
|
||||
symbolInfo,
|
||||
resolution,
|
||||
|
@ -169,7 +247,7 @@ export default {
|
|||
},
|
||||
|
||||
unsubscribeBars: () => {
|
||||
console.log('[unsubscribeBars]')
|
||||
console.warn('[unsubscribeBars]')
|
||||
unsubscribeFromStream()
|
||||
},
|
||||
}
|
||||
|
|
|
@ -38,7 +38,7 @@ socket.addEventListener('message', (msg) => {
|
|||
close: data.data.c,
|
||||
volume: data.data.v,
|
||||
}
|
||||
console.log('[socket] Generate new bar')
|
||||
// console.log('[socket] Generate new bar')
|
||||
} else {
|
||||
bar = {
|
||||
...lastBar,
|
||||
|
@ -47,7 +47,7 @@ socket.addEventListener('message', (msg) => {
|
|||
close: data.data.c,
|
||||
volume: data.data.v,
|
||||
}
|
||||
console.log('[socket] Update the latest bar by price')
|
||||
// console.log('[socket] Update the latest bar by price')
|
||||
}
|
||||
|
||||
subscriptionItem.lastBar = bar
|
||||
|
|
|
@ -21,7 +21,11 @@ import {
|
|||
INPUT_TOKEN_DEFAULT,
|
||||
} from './../utils/constants'
|
||||
import { notify } from './../utils/notifications'
|
||||
import { floorToDecimal, formatFixedDecimals } from './../utils/numbers'
|
||||
import {
|
||||
floorToDecimal,
|
||||
formatDecimal,
|
||||
formatFixedDecimals,
|
||||
} from './../utils/numbers'
|
||||
import ActionTokenList from './account/ActionTokenList'
|
||||
import ButtonGroup from './forms/ButtonGroup'
|
||||
import Label from './forms/Label'
|
||||
|
@ -39,6 +43,8 @@ import useJupiterMints from 'hooks/useJupiterMints'
|
|||
import useMangoGroup from 'hooks/useMangoGroup'
|
||||
import TokenVaultWarnings from '@components/shared/TokenVaultWarnings'
|
||||
import { useWallet } from '@solana/wallet-adapter-react'
|
||||
import { useEnhancedWallet } from './wallet/EnhancedWalletProvider'
|
||||
import AmountWithValue from './shared/AmountWithValue'
|
||||
|
||||
interface BorrowFormProps {
|
||||
onSuccess: () => void
|
||||
|
@ -58,6 +64,7 @@ function BorrowForm({ onSuccess, token }: BorrowFormProps) {
|
|||
const { mangoTokens } = useJupiterMints()
|
||||
const { mangoAccount } = useMangoAccount()
|
||||
const { connected } = useWallet()
|
||||
const { handleConnect } = useEnhancedWallet()
|
||||
|
||||
const bank = useMemo(() => {
|
||||
const group = mangoStore.getState().group
|
||||
|
@ -85,10 +92,11 @@ function BorrowForm({ onSuccess, token }: BorrowFormProps) {
|
|||
|
||||
const tokenBalance = useMemo(() => {
|
||||
if (!bank || !mangoAccount) return new Decimal(0)
|
||||
return floorToDecimal(
|
||||
const balance = floorToDecimal(
|
||||
mangoAccount.getTokenBalanceUi(bank),
|
||||
bank.mintDecimals
|
||||
)
|
||||
return balance.gt(0) ? balance : new Decimal(0)
|
||||
}, [bank, mangoAccount])
|
||||
|
||||
const isBorrow = parseFloat(inputAmount) > tokenBalance.toNumber()
|
||||
|
@ -104,16 +112,19 @@ function BorrowForm({ onSuccess, token }: BorrowFormProps) {
|
|||
)
|
||||
|
||||
const setMax = useCallback(() => {
|
||||
setInputAmount(tokenMax.toFixed())
|
||||
if (!bank) return
|
||||
setInputAmount(
|
||||
floorToDecimal(Number(tokenMax), bank.mintDecimals).toFixed()
|
||||
)
|
||||
handleSizePercentage('100')
|
||||
}, [tokenMax, handleSizePercentage])
|
||||
}, [bank, tokenMax, handleSizePercentage])
|
||||
|
||||
const handleSelectToken = (token: string) => {
|
||||
setSelectedToken(token)
|
||||
setShowTokenList(false)
|
||||
}
|
||||
|
||||
const handleWithdraw = async () => {
|
||||
const handleBorrow = async () => {
|
||||
const client = mangoStore.getState().client
|
||||
const group = mangoStore.getState().group
|
||||
const mangoAccount = mangoStore.getState().mangoAccount.current
|
||||
|
@ -203,14 +214,14 @@ function BorrowForm({ onSuccess, token }: BorrowFormProps) {
|
|||
<ArrowLeftIcon className={`h-6 w-6`} />
|
||||
</button>
|
||||
<h2 className="mb-4 text-center text-lg">{t('select-borrow-token')}</h2>
|
||||
<div className="grid auto-cols-fr grid-flow-col px-4 pb-2">
|
||||
<div className="">
|
||||
<div className="flex items-center px-4 pb-2">
|
||||
<div className="w-1/4">
|
||||
<p className="text-xs">{t('token')}</p>
|
||||
</div>
|
||||
<div className="text-right">
|
||||
<div className="w-1/4 text-right">
|
||||
<p className="text-xs">{t('borrow-rate')}</p>
|
||||
</div>
|
||||
<div className="text-right">
|
||||
<div className="w-1/2 text-right">
|
||||
<p className="whitespace-nowrap text-xs">{t('max-borrow')}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -236,16 +247,21 @@ function BorrowForm({ onSuccess, token }: BorrowFormProps) {
|
|||
/>
|
||||
</div>
|
||||
) : null}
|
||||
{bank ? <TokenVaultWarnings bank={bank} /> : null}
|
||||
{bank ? <TokenVaultWarnings bank={bank} type="borrow" /> : null}
|
||||
<div className="grid grid-cols-2">
|
||||
<div className="col-span-2 flex justify-between">
|
||||
<Label text={`${t('borrow')} ${t('token')}`} />
|
||||
<MaxAmountButton
|
||||
className="mb-2"
|
||||
label={t('max')}
|
||||
onClick={setMax}
|
||||
value={tokenMax.toFixed()}
|
||||
/>
|
||||
{bank ? (
|
||||
<MaxAmountButton
|
||||
className="mb-2"
|
||||
label={t('max')}
|
||||
onClick={setMax}
|
||||
value={floorToDecimal(
|
||||
Number(tokenMax),
|
||||
bank.mintDecimals
|
||||
).toFixed()}
|
||||
/>
|
||||
) : null}
|
||||
</div>
|
||||
<div className="col-span-1 rounded-lg rounded-r-none border border-r-0 border-th-input-border bg-th-input-bkg">
|
||||
<button
|
||||
|
@ -277,7 +293,7 @@ function BorrowForm({ onSuccess, token }: BorrowFormProps) {
|
|||
allowNegative={false}
|
||||
isNumericString={true}
|
||||
decimalScale={bank?.mintDecimals || 6}
|
||||
className="w-full rounded-lg rounded-l-none border border-th-input-border bg-th-input-bkg p-3 text-right font-mono text-xl tracking-wider text-th-fgd-1 focus:border-th-input-border-hover focus:outline-none md:hover:border-th-input-border-hover"
|
||||
className="w-full rounded-lg rounded-l-none border border-th-input-border bg-th-input-bkg p-3 text-right font-mono text-xl text-th-fgd-1 focus:border-th-input-border-hover focus:outline-none md:hover:border-th-input-border-hover"
|
||||
placeholder="0.00"
|
||||
value={inputAmount}
|
||||
onValueChange={handleInputChange}
|
||||
|
@ -306,70 +322,56 @@ function BorrowForm({ onSuccess, token }: BorrowFormProps) {
|
|||
</div> */}
|
||||
</div>
|
||||
{bank ? (
|
||||
<div className="my-6 space-y-2 border-y border-th-bkg-3 px-2 py-4">
|
||||
<div className="my-6 space-y-1.5 border-y border-th-bkg-3 px-2 py-4">
|
||||
<HealthImpactTokenChange
|
||||
mintPk={bank.mint}
|
||||
uiAmount={Number(inputAmount)}
|
||||
/>
|
||||
{tokenBalance ? (
|
||||
<div className="flex justify-between">
|
||||
<p>{t('withdraw-amount')}</p>
|
||||
<p className="font-mono text-th-fgd-2">
|
||||
{isBorrow ? (
|
||||
<>
|
||||
{tokenBalance.toNumber()}{' '}
|
||||
<span className="text-xs text-th-fgd-3">
|
||||
(
|
||||
{formatFixedDecimals(
|
||||
bank.uiPrice * tokenBalance.toNumber(),
|
||||
true
|
||||
)}
|
||||
</span>
|
||||
</>
|
||||
) : inputAmount ? (
|
||||
<>
|
||||
{inputAmount}{' '}
|
||||
<span className="text-xs text-th-fgd-3">
|
||||
(
|
||||
{formatFixedDecimals(
|
||||
bank.uiPrice * parseFloat(inputAmount),
|
||||
true
|
||||
)}
|
||||
)
|
||||
</span>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
0{' '}
|
||||
<span className="text-xs text-th-fgd-3">($0.00)</span>
|
||||
</>
|
||||
<div className="flex justify-between">
|
||||
<p>{t('withdraw-amount')}</p>
|
||||
{isBorrow ? (
|
||||
<AmountWithValue
|
||||
amount={formatDecimal(
|
||||
Number(tokenBalance),
|
||||
bank.mintDecimals
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
) : null}
|
||||
value={formatFixedDecimals(
|
||||
bank.uiPrice * tokenBalance.toNumber(),
|
||||
true
|
||||
)}
|
||||
/>
|
||||
) : inputAmount ? (
|
||||
<AmountWithValue
|
||||
amount={formatDecimal(
|
||||
Number(inputAmount),
|
||||
bank.mintDecimals
|
||||
)}
|
||||
value={formatFixedDecimals(
|
||||
bank.uiPrice * parseFloat(inputAmount),
|
||||
true
|
||||
)}
|
||||
/>
|
||||
) : (
|
||||
<AmountWithValue amount="0" value="$0.00" />
|
||||
)}
|
||||
</div>
|
||||
<div className="flex justify-between">
|
||||
<p>{t('borrow-amount')}</p>
|
||||
<p className="font-mono text-th-fgd-2">
|
||||
{isBorrow ? (
|
||||
<>
|
||||
{parseFloat(inputAmount) - tokenBalance.toNumber()}{' '}
|
||||
<span className="text-xs text-th-fgd-3">
|
||||
(
|
||||
{formatFixedDecimals(
|
||||
bank.uiPrice *
|
||||
(parseFloat(inputAmount) -
|
||||
tokenBalance.toNumber()),
|
||||
true
|
||||
)}
|
||||
)
|
||||
</span>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
0 <span className="text-xs text-th-fgd-3">($0.00)</span>
|
||||
</>
|
||||
)}
|
||||
</p>
|
||||
{isBorrow ? (
|
||||
<AmountWithValue
|
||||
amount={formatDecimal(
|
||||
Number(inputAmount) - Number(tokenBalance),
|
||||
bank.mintDecimals
|
||||
)}
|
||||
value={formatFixedDecimals(
|
||||
bank.uiPrice *
|
||||
(parseFloat(inputAmount) - tokenBalance.toNumber()),
|
||||
true
|
||||
)}
|
||||
/>
|
||||
) : (
|
||||
<AmountWithValue amount="0" value="$0.00" />
|
||||
)}
|
||||
</div>
|
||||
<div className="flex justify-between">
|
||||
<Tooltip content={t('loan-origination-fee-tooltip')}>
|
||||
|
@ -378,23 +380,29 @@ function BorrowForm({ onSuccess, token }: BorrowFormProps) {
|
|||
</p>
|
||||
</Tooltip>
|
||||
<p className="font-mono text-th-fgd-2">
|
||||
{isBorrow
|
||||
? formatFixedDecimals(
|
||||
bank.uiPrice *
|
||||
bank.loanOriginationFeeRate.toNumber() *
|
||||
{isBorrow ? (
|
||||
<>
|
||||
{formatDecimal(
|
||||
bank.loanOriginationFeeRate.toNumber() *
|
||||
(parseFloat(inputAmount) - tokenBalance.toNumber()),
|
||||
true
|
||||
)
|
||||
: '$0.00'}
|
||||
bank.mintDecimals
|
||||
)}{' '}
|
||||
<span className="font-body text-th-fgd-4">
|
||||
{bank.name}
|
||||
</span>
|
||||
</>
|
||||
) : (
|
||||
'N/A'
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
<Button
|
||||
onClick={handleWithdraw}
|
||||
onClick={connected ? handleBorrow : handleConnect}
|
||||
className="flex w-full items-center justify-center"
|
||||
disabled={!inputAmount || showInsufficientBalance || !connected}
|
||||
disabled={connected && (!inputAmount || showInsufficientBalance)}
|
||||
size="large"
|
||||
>
|
||||
{!connected ? (
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { toUiDecimalsForQuote } from '@blockworks-foundation/mango-v4'
|
||||
import { Bank, toUiDecimalsForQuote } from '@blockworks-foundation/mango-v4'
|
||||
import {
|
||||
ArrowDownTrayIcon,
|
||||
ArrowLeftIcon,
|
||||
|
@ -20,7 +20,11 @@ import {
|
|||
INPUT_TOKEN_DEFAULT,
|
||||
} from './../utils/constants'
|
||||
import { notify } from './../utils/notifications'
|
||||
import { floorToDecimal, formatFixedDecimals } from './../utils/numbers'
|
||||
import {
|
||||
floorToDecimal,
|
||||
formatDecimal,
|
||||
formatFixedDecimals,
|
||||
} from './../utils/numbers'
|
||||
import { TokenAccount } from './../utils/tokens'
|
||||
import ActionTokenList from './account/ActionTokenList'
|
||||
import ButtonGroup from './forms/ButtonGroup'
|
||||
|
@ -36,6 +40,9 @@ import HealthImpactTokenChange from '@components/HealthImpactTokenChange'
|
|||
import SolBalanceWarnings from '@components/shared/SolBalanceWarnings'
|
||||
import useJupiterMints from 'hooks/useJupiterMints'
|
||||
import useMangoGroup from 'hooks/useMangoGroup'
|
||||
import { useEnhancedWallet } from './wallet/EnhancedWalletProvider'
|
||||
import useSolBalance from 'hooks/useSolBalance'
|
||||
import AmountWithValue from './shared/AmountWithValue'
|
||||
|
||||
interface DepositFormProps {
|
||||
onSuccess: () => void
|
||||
|
@ -63,6 +70,27 @@ export const walletBalanceForToken = (
|
|||
}
|
||||
}
|
||||
|
||||
export const useAlphaMax = (inputAmount: string, bank: Bank | undefined) => {
|
||||
const exceedsAlphaMax = useMemo(() => {
|
||||
const mangoAccount = mangoStore.getState().mangoAccount.current
|
||||
const group = mangoStore.getState().group
|
||||
if (!group || !mangoAccount) return
|
||||
if (
|
||||
mangoAccount.owner.toString() ===
|
||||
'8SSLjXBEVk9nesbhi9UMCA32uijbVBUqWoKPPQPTekzt'
|
||||
)
|
||||
return false
|
||||
const accountValue = toUiDecimalsForQuote(
|
||||
mangoAccount.getEquity(group).toNumber()
|
||||
)
|
||||
return (
|
||||
parseFloat(inputAmount) * (bank?.uiPrice || 1) + accountValue >
|
||||
ALPHA_DEPOSIT_LIMIT || accountValue > ALPHA_DEPOSIT_LIMIT
|
||||
)
|
||||
}, [inputAmount, bank])
|
||||
return exceedsAlphaMax
|
||||
}
|
||||
|
||||
function DepositForm({ onSuccess, token }: DepositFormProps) {
|
||||
const { t } = useTranslation('common')
|
||||
const { group } = useMangoGroup()
|
||||
|
@ -74,11 +102,14 @@ function DepositForm({ onSuccess, token }: DepositFormProps) {
|
|||
const [showTokenList, setShowTokenList] = useState(false)
|
||||
const [sizePercentage, setSizePercentage] = useState('')
|
||||
const { mangoTokens } = useJupiterMints()
|
||||
const { handleConnect } = useEnhancedWallet()
|
||||
const { maxSolDeposit } = useSolBalance()
|
||||
|
||||
const bank = useMemo(() => {
|
||||
const group = mangoStore.getState().group
|
||||
return group?.banksMapByName.get(selectedToken)?.[0]
|
||||
}, [selectedToken])
|
||||
const exceedsAlphaMax = useAlphaMax(inputAmount, bank)
|
||||
|
||||
const logoUri = useMemo(() => {
|
||||
let logoURI
|
||||
|
@ -98,7 +129,9 @@ function DepositForm({ onSuccess, token }: DepositFormProps) {
|
|||
}, [walletTokens, selectedToken])
|
||||
|
||||
const setMax = useCallback(() => {
|
||||
setInputAmount(tokenMax.maxAmount.toString())
|
||||
setInputAmount(
|
||||
floorToDecimal(tokenMax.maxAmount, tokenMax.maxDecimals).toFixed()
|
||||
)
|
||||
setSizePercentage('100')
|
||||
}, [tokenMax])
|
||||
|
||||
|
@ -176,25 +209,9 @@ function DepositForm({ onSuccess, token }: DepositFormProps) {
|
|||
return banks
|
||||
}, [group?.banksMapByName, walletTokens])
|
||||
|
||||
const exceedsAlphaMax = useMemo(() => {
|
||||
const mangoAccount = mangoStore.getState().mangoAccount.current
|
||||
const group = mangoStore.getState().group
|
||||
if (!group || !mangoAccount) return
|
||||
if (
|
||||
mangoAccount.owner.toString() ===
|
||||
'8SSLjXBEVk9nesbhi9UMCA32uijbVBUqWoKPPQPTekzt'
|
||||
)
|
||||
return false
|
||||
const accountValue = toUiDecimalsForQuote(
|
||||
mangoAccount.getEquity(group).toNumber()
|
||||
)
|
||||
return (
|
||||
parseFloat(inputAmount) * (bank?.uiPrice || 1) + accountValue >
|
||||
ALPHA_DEPOSIT_LIMIT || accountValue > ALPHA_DEPOSIT_LIMIT
|
||||
)
|
||||
}, [inputAmount, bank])
|
||||
|
||||
const showInsufficientBalance = tokenMax.maxAmount < Number(inputAmount)
|
||||
const showInsufficientBalance =
|
||||
tokenMax.maxAmount < Number(inputAmount) ||
|
||||
(selectedToken === 'SOL' && maxSolDeposit <= 0)
|
||||
|
||||
return (
|
||||
<>
|
||||
|
@ -211,14 +228,14 @@ function DepositForm({ onSuccess, token }: DepositFormProps) {
|
|||
<h2 className="mb-4 text-center text-lg">
|
||||
{t('select-deposit-token')}
|
||||
</h2>
|
||||
<div className="grid auto-cols-fr grid-flow-col px-4 pb-2">
|
||||
<div className="text-left">
|
||||
<div className="flex items-center px-4 pb-2">
|
||||
<div className="w-1/4 text-left">
|
||||
<p className="text-xs">{t('token')}</p>
|
||||
</div>
|
||||
<div className="text-right">
|
||||
<div className="w-1/4 text-right">
|
||||
<p className="text-xs">{t('deposit-rate')}</p>
|
||||
</div>
|
||||
<div className="text-right">
|
||||
<div className="w-1/2 text-right">
|
||||
<p className="whitespace-nowrap text-xs">{t('wallet-balance')}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -243,6 +260,7 @@ function DepositForm({ onSuccess, token }: DepositFormProps) {
|
|||
/>
|
||||
<SolBalanceWarnings
|
||||
amount={inputAmount}
|
||||
className="mt-2"
|
||||
setAmount={setInputAmount}
|
||||
selectedToken={selectedToken}
|
||||
/>
|
||||
|
@ -289,7 +307,7 @@ function DepositForm({ onSuccess, token }: DepositFormProps) {
|
|||
allowNegative={false}
|
||||
isNumericString={true}
|
||||
decimalScale={bank?.mintDecimals || 6}
|
||||
className="w-full rounded-lg rounded-l-none border border-th-input-border bg-th-input-bkg p-3 text-right font-mono text-xl tracking-wider text-th-fgd-1 focus:border-th-input-border-hover focus:outline-none md:hover:border-th-input-border-hover"
|
||||
className="w-full rounded-lg rounded-l-none border border-th-input-border bg-th-input-bkg p-3 text-right font-mono text-xl text-th-fgd-1 focus:border-th-input-border-hover focus:outline-none md:hover:border-th-input-border-hover"
|
||||
placeholder="0.00"
|
||||
value={inputAmount}
|
||||
onValueChange={(e: NumberFormatValues) => {
|
||||
|
@ -310,35 +328,31 @@ function DepositForm({ onSuccess, token }: DepositFormProps) {
|
|||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="my-6 space-y-1.5 border-y border-th-bkg-3 px-2 py-4 text-sm ">
|
||||
<HealthImpactTokenChange
|
||||
mintPk={bank!.mint}
|
||||
uiAmount={Number(inputAmount)}
|
||||
isDeposit
|
||||
/>
|
||||
<div className="flex justify-between">
|
||||
<p>{t('deposit-amount')}</p>
|
||||
<p className="font-mono text-th-fgd-2">
|
||||
{bank?.uiPrice && inputAmount ? (
|
||||
<>
|
||||
{inputAmount}{' '}
|
||||
<span className="text-xs text-th-fgd-3">
|
||||
(
|
||||
{formatFixedDecimals(
|
||||
bank.uiPrice * Number(inputAmount),
|
||||
true
|
||||
)}
|
||||
)
|
||||
</span>
|
||||
</>
|
||||
{bank ? (
|
||||
<div className="my-6 space-y-1.5 border-y border-th-bkg-3 px-2 py-4 text-sm ">
|
||||
<HealthImpactTokenChange
|
||||
mintPk={bank.mint}
|
||||
uiAmount={Number(inputAmount)}
|
||||
isDeposit
|
||||
/>
|
||||
<div className="flex justify-between">
|
||||
<p>{t('deposit-amount')}</p>
|
||||
{inputAmount ? (
|
||||
<AmountWithValue
|
||||
amount={formatDecimal(
|
||||
Number(inputAmount),
|
||||
bank.mintDecimals
|
||||
)}
|
||||
value={formatFixedDecimals(
|
||||
bank.uiPrice * Number(inputAmount),
|
||||
true
|
||||
)}
|
||||
/>
|
||||
) : (
|
||||
<>
|
||||
0 <span className="text-xs text-th-fgd-3">($0.00)</span>
|
||||
</>
|
||||
<AmountWithValue amount="0" value="$0.00" />
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
{/* <div className="flex justify-between">
|
||||
</div>
|
||||
{/* <div className="flex justify-between">
|
||||
<div className="flex items-center">
|
||||
<Tooltip content={t('asset-weight-desc')}>
|
||||
<p className="tooltip-underline">{t('asset-weight')}</p>
|
||||
|
@ -346,29 +360,28 @@ function DepositForm({ onSuccess, token }: DepositFormProps) {
|
|||
</div>
|
||||
<p className="font-mono">{bank!.initAssetWeight.toFixed(2)}x</p>
|
||||
</div> */}
|
||||
<div className="flex justify-between">
|
||||
<Tooltip content={t('tooltip-collateral-value')}>
|
||||
<p className="tooltip-underline">{t('collateral-value')}</p>
|
||||
</Tooltip>
|
||||
<p className="font-mono text-th-fgd-2">
|
||||
{formatFixedDecimals(
|
||||
bank!.uiPrice! *
|
||||
Number(inputAmount) *
|
||||
Number(bank!.initAssetWeight),
|
||||
true
|
||||
)}
|
||||
</p>
|
||||
<div className="flex justify-between">
|
||||
<Tooltip content={t('tooltip-collateral-value')}>
|
||||
<p className="tooltip-underline">{t('collateral-value')}</p>
|
||||
</Tooltip>
|
||||
<p className="font-mono text-th-fgd-2">
|
||||
{formatFixedDecimals(
|
||||
bank.uiPrice *
|
||||
Number(inputAmount) *
|
||||
Number(bank.initAssetWeight),
|
||||
true
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
<Button
|
||||
onClick={handleDeposit}
|
||||
onClick={connected ? handleDeposit : handleConnect}
|
||||
className="flex w-full items-center justify-center"
|
||||
disabled={
|
||||
!inputAmount ||
|
||||
exceedsAlphaMax ||
|
||||
showInsufficientBalance ||
|
||||
!connected
|
||||
connected &&
|
||||
(!inputAmount || exceedsAlphaMax || showInsufficientBalance)
|
||||
}
|
||||
size="large"
|
||||
>
|
||||
|
|
|
@ -79,7 +79,11 @@ const Layout = ({ children }: { children: ReactNode }) => {
|
|||
}`}
|
||||
/>
|
||||
</button>
|
||||
<div className={`h-full ${!isCollapsed ? 'overflow-y-auto' : ''}`}>
|
||||
<div
|
||||
className={`thin-scroll h-full ${
|
||||
!isCollapsed ? 'overflow-y-auto' : ''
|
||||
}`}
|
||||
>
|
||||
<SideNav collapsed={isCollapsed} />
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -1,129 +0,0 @@
|
|||
import { Fragment, useState } from 'react'
|
||||
import {
|
||||
CheckCircleIcon,
|
||||
ChevronDownIcon,
|
||||
PlusCircleIcon,
|
||||
} from '@heroicons/react/20/solid'
|
||||
import { Popover, Transition } from '@headlessui/react'
|
||||
import { MangoAccount } from '@blockworks-foundation/mango-v4'
|
||||
import mangoStore from '@store/mangoStore'
|
||||
import { LinkButton } from './shared/Button'
|
||||
import CreateAccountModal from './modals/CreateAccountModal'
|
||||
import { useLocalStorageStringState } from '../hooks/useLocalStorageState'
|
||||
import { LAST_ACCOUNT_KEY } from '../utils/constants'
|
||||
import { useTranslation } from 'next-i18next'
|
||||
import { retryFn } from '../utils'
|
||||
import Loading from './shared/Loading'
|
||||
|
||||
const MangoAccountsList = ({
|
||||
mangoAccount,
|
||||
}: {
|
||||
mangoAccount: MangoAccount | undefined
|
||||
}) => {
|
||||
const { t } = useTranslation('common')
|
||||
const mangoAccounts = mangoStore((s) => s.mangoAccounts)
|
||||
const actions = mangoStore((s) => s.actions)
|
||||
const loading = mangoStore((s) => s.mangoAccount.initialLoad)
|
||||
const [showNewAccountModal, setShowNewAccountModal] = useState(false)
|
||||
const [, setLastAccountViewed] = useLocalStorageStringState(LAST_ACCOUNT_KEY)
|
||||
|
||||
const handleSelectMangoAccount = async (acc: MangoAccount) => {
|
||||
const set = mangoStore.getState().set
|
||||
const client = mangoStore.getState().client
|
||||
const group = mangoStore.getState().group
|
||||
if (!group) return
|
||||
set((s) => {
|
||||
s.activityFeed.feed = []
|
||||
s.activityFeed.loading = true
|
||||
})
|
||||
try {
|
||||
const reloadedMangoAccount = await retryFn(() => acc.reload(client))
|
||||
set((s) => {
|
||||
s.mangoAccount.current = reloadedMangoAccount
|
||||
})
|
||||
setLastAccountViewed(acc.publicKey.toString())
|
||||
actions.fetchOpenOrders(acc)
|
||||
} catch (e) {
|
||||
console.warn('Error selecting account', e)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div id="account-step-two">
|
||||
<Popover>
|
||||
{({ open }) => (
|
||||
<>
|
||||
<Popover.Button className="flex w-full items-center rounded-none text-th-fgd-1 hover:text-th-active">
|
||||
<div className="mr-2">
|
||||
<p className="text-right text-xs">{t('accounts')}</p>
|
||||
<p className="text-left text-sm font-bold text-th-fgd-1">
|
||||
{mangoAccount ? mangoAccount.name : 'No Accounts'}
|
||||
</p>
|
||||
</div>
|
||||
<ChevronDownIcon
|
||||
className={`${
|
||||
open ? 'rotate-180' : 'rotate-360'
|
||||
} mt-0.5 h-6 w-6 flex-shrink-0 text-th-fgd-3`}
|
||||
/>
|
||||
</Popover.Button>
|
||||
<div className="relative">
|
||||
<Transition
|
||||
appear={true}
|
||||
show={open}
|
||||
as={Fragment}
|
||||
enter="transition-all ease-in duration-200"
|
||||
enterFrom="opacity-0 scale-75"
|
||||
enterTo="opacity-100 scale-100"
|
||||
leave="transition ease-out duration-200"
|
||||
leaveFrom="opacity-100"
|
||||
leaveTo="opacity-0"
|
||||
>
|
||||
<Popover.Panel className="absolute top-[13.5px] -right-7 z-20 mr-4 w-56 rounded-md rounded-t-none border border-th-bkg-2 bg-th-bkg-2 p-4 text-th-fgd-3">
|
||||
{loading ? (
|
||||
<Loading />
|
||||
) : mangoAccounts.length ? (
|
||||
mangoAccounts.map((acc) => (
|
||||
<div key={acc.publicKey.toString()}>
|
||||
<button
|
||||
onClick={() => handleSelectMangoAccount(acc)}
|
||||
className="default-transition mb-3 flex w-full items-center justify-between border-b border-th-bkg-4 pb-3 hover:text-th-fgd-1"
|
||||
>
|
||||
{acc.name}
|
||||
{acc.publicKey.toString() ===
|
||||
mangoAccount?.publicKey.toString() ? (
|
||||
<CheckCircleIcon className="h-5 w-5 text-th-up" />
|
||||
) : null}
|
||||
</button>
|
||||
</div>
|
||||
))
|
||||
) : (
|
||||
<p className="mb-4 text-center text-sm">
|
||||
Create your first account 😎
|
||||
</p>
|
||||
)}
|
||||
<div>
|
||||
<LinkButton
|
||||
className="w-full justify-center"
|
||||
onClick={() => setShowNewAccountModal(true)}
|
||||
>
|
||||
<PlusCircleIcon className="h-5 w-5" />
|
||||
<span className="ml-2">New subaccount</span>
|
||||
</LinkButton>
|
||||
</div>
|
||||
</Popover.Panel>
|
||||
</Transition>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</Popover>
|
||||
{showNewAccountModal ? (
|
||||
<CreateAccountModal
|
||||
isOpen={showNewAccountModal}
|
||||
onClose={() => setShowNewAccountModal(false)}
|
||||
/>
|
||||
) : null}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default MangoAccountsList
|
|
@ -1,6 +1,6 @@
|
|||
import { useCallback, useEffect } from 'react'
|
||||
import mangoStore from '@store/mangoStore'
|
||||
import { PublicKey } from '@solana/web3.js'
|
||||
import { Keypair, PublicKey } from '@solana/web3.js'
|
||||
import { useRouter } from 'next/router'
|
||||
import { MangoAccount } from '@blockworks-foundation/mango-v4'
|
||||
import useMangoAccount from 'hooks/useMangoAccount'
|
||||
|
@ -12,7 +12,8 @@ const actions = mangoStore.getState().actions
|
|||
const HydrateStore = () => {
|
||||
const router = useRouter()
|
||||
const { name: marketName } = router.query
|
||||
const { mangoAccount } = useMangoAccount()
|
||||
const { mangoAccountPk, mangoAccountAddress } = useMangoAccount()
|
||||
const connection = mangoStore((s) => s.connection)
|
||||
|
||||
const fetchData = useCallback(async () => {
|
||||
await actions.fetchGroup()
|
||||
|
@ -31,15 +32,32 @@ const HydrateStore = () => {
|
|||
fetchData()
|
||||
}, 15000)
|
||||
|
||||
useInterval(() => {
|
||||
if (mangoAccountAddress) {
|
||||
actions.fetchOpenOrders()
|
||||
}
|
||||
}, 30000)
|
||||
|
||||
// The websocket library solana/web3.js uses closes its websocket connection when the subscription list
|
||||
// is empty after opening its first time, preventing subsequent subscriptions from receiving responses.
|
||||
// This is a hack to prevent the list from every getting empty
|
||||
useEffect(() => {
|
||||
const id = connection.onAccountChange(new Keypair().publicKey, () => {
|
||||
return
|
||||
})
|
||||
return () => {
|
||||
connection.removeAccountChangeListener(id)
|
||||
}
|
||||
}, [connection])
|
||||
|
||||
// watch selected Mango Account for changes
|
||||
useEffect(() => {
|
||||
const connection = mangoStore.getState().connection
|
||||
const client = mangoStore.getState().client
|
||||
|
||||
if (!mangoAccount) return
|
||||
if (!mangoAccountPk) return
|
||||
|
||||
const subscriptionId = connection.onAccountChange(
|
||||
mangoAccount.publicKey,
|
||||
mangoAccountPk,
|
||||
async (info, context) => {
|
||||
if (info?.lamports === 0) return
|
||||
|
||||
|
@ -80,7 +98,7 @@ const HydrateStore = () => {
|
|||
return () => {
|
||||
connection.removeAccountChangeListener(subscriptionId)
|
||||
}
|
||||
}, [mangoAccount])
|
||||
}, [connection, mangoAccountPk])
|
||||
|
||||
return null
|
||||
}
|
||||
|
@ -103,10 +121,12 @@ const ReadOnlyMangoAccount = () => {
|
|||
const pk = new PublicKey(ma)
|
||||
const readOnlyMangoAccount = await client.getMangoAccount(pk)
|
||||
await readOnlyMangoAccount.reloadAccountData(client)
|
||||
await actions.fetchOpenOrders(readOnlyMangoAccount)
|
||||
set((state) => {
|
||||
state.mangoAccount.current = readOnlyMangoAccount
|
||||
state.mangoAccount.initialLoad = false
|
||||
})
|
||||
actions.fetchTradeHistory()
|
||||
} catch (error) {
|
||||
console.error('error', error)
|
||||
}
|
||||
|
|
|
@ -3,7 +3,6 @@ import {
|
|||
ArrowLeftIcon,
|
||||
ChevronDownIcon,
|
||||
ExclamationCircleIcon,
|
||||
LinkIcon,
|
||||
QuestionMarkCircleIcon,
|
||||
} from '@heroicons/react/20/solid'
|
||||
import { Wallet } from '@project-serum/anchor'
|
||||
|
@ -15,7 +14,11 @@ import React, { useCallback, useEffect, useMemo, useState } from 'react'
|
|||
import NumberFormat, { NumberFormatValues } from 'react-number-format'
|
||||
import mangoStore from '@store/mangoStore'
|
||||
import { notify } from './../utils/notifications'
|
||||
import { floorToDecimal, formatFixedDecimals } from './../utils/numbers'
|
||||
import {
|
||||
floorToDecimal,
|
||||
formatDecimal,
|
||||
formatFixedDecimals,
|
||||
} from './../utils/numbers'
|
||||
import ActionTokenList from './account/ActionTokenList'
|
||||
import ButtonGroup from './forms/ButtonGroup'
|
||||
import Label from './forms/Label'
|
||||
|
@ -25,12 +28,17 @@ import { EnterBottomExitBottom, FadeInFadeOut } from './shared/Transitions'
|
|||
import { withValueLimit } from './swap/SwapForm'
|
||||
import MaxAmountButton from '@components/shared/MaxAmountButton'
|
||||
import HealthImpactTokenChange from '@components/HealthImpactTokenChange'
|
||||
import { walletBalanceForToken } from './DepositForm'
|
||||
import { useAlphaMax, walletBalanceForToken } from './DepositForm'
|
||||
import SolBalanceWarnings from '@components/shared/SolBalanceWarnings'
|
||||
import useMangoAccount from 'hooks/useMangoAccount'
|
||||
import useJupiterMints from 'hooks/useJupiterMints'
|
||||
import useMangoGroup from 'hooks/useMangoGroup'
|
||||
import { ACCOUNT_ACTION_MODAL_INNER_HEIGHT } from 'utils/constants'
|
||||
import {
|
||||
ACCOUNT_ACTION_MODAL_INNER_HEIGHT,
|
||||
INPUT_TOKEN_DEFAULT,
|
||||
} from 'utils/constants'
|
||||
import ConnectEmptyState from './shared/ConnectEmptyState'
|
||||
import AmountWithValue from './shared/AmountWithValue'
|
||||
|
||||
interface RepayFormProps {
|
||||
onSuccess: () => void
|
||||
|
@ -43,7 +51,9 @@ function RepayForm({ onSuccess, token }: RepayFormProps) {
|
|||
const { mangoAccount } = useMangoAccount()
|
||||
const [inputAmount, setInputAmount] = useState('')
|
||||
const [submitting, setSubmitting] = useState(false)
|
||||
const [selectedToken, setSelectedToken] = useState(token)
|
||||
const [selectedToken, setSelectedToken] = useState(
|
||||
token || INPUT_TOKEN_DEFAULT
|
||||
)
|
||||
const [showTokenList, setShowTokenList] = useState(false)
|
||||
const [sizePercentage, setSizePercentage] = useState('')
|
||||
const { mangoTokens } = useJupiterMints()
|
||||
|
@ -51,7 +61,7 @@ function RepayForm({ onSuccess, token }: RepayFormProps) {
|
|||
|
||||
const bank = useMemo(() => {
|
||||
const group = mangoStore.getState().group
|
||||
return selectedToken ? group?.banksMapByName.get(selectedToken)?.[0] : null
|
||||
return group?.banksMapByName.get(selectedToken)?.[0]
|
||||
}, [selectedToken])
|
||||
|
||||
const logoUri = useMemo(() => {
|
||||
|
@ -82,20 +92,22 @@ function RepayForm({ onSuccess, token }: RepayFormProps) {
|
|||
}, [bank, mangoAccount])
|
||||
|
||||
const setMax = useCallback(() => {
|
||||
setInputAmount(borrowAmount.toFixed(bank?.mintDecimals))
|
||||
if (!bank) return
|
||||
setInputAmount(floorToDecimal(borrowAmount, bank.mintDecimals).toFixed())
|
||||
setSizePercentage('100')
|
||||
}, [bank, borrowAmount])
|
||||
|
||||
const handleSizePercentage = useCallback(
|
||||
(percentage: string) => {
|
||||
if (!bank) return
|
||||
setSizePercentage(percentage)
|
||||
|
||||
let amount: Decimal | number = new Decimal(borrowAmount)
|
||||
.mul(percentage)
|
||||
.div(100)
|
||||
amount = floorToDecimal(amount, bank!.mintDecimals).toNumber()
|
||||
amount = floorToDecimal(amount, bank.mintDecimals).toNumber()
|
||||
|
||||
setInputAmount(amount.toFixed(bank?.mintDecimals))
|
||||
setInputAmount(amount.toFixed(bank.mintDecimals))
|
||||
},
|
||||
[bank, borrowAmount]
|
||||
)
|
||||
|
@ -172,6 +184,8 @@ function RepayForm({ onSuccess, token }: RepayFormProps) {
|
|||
}
|
||||
}, [token, banks])
|
||||
|
||||
const exceedsAlphaMax = useAlphaMax(inputAmount, bank)
|
||||
|
||||
const showInsufficientBalance = walletBalance.maxAmount < Number(inputAmount)
|
||||
|
||||
return banks.length ? (
|
||||
|
@ -187,11 +201,11 @@ function RepayForm({ onSuccess, token }: RepayFormProps) {
|
|||
<ArrowLeftIcon className={`h-6 w-6`} />
|
||||
</button>
|
||||
<h2 className="mb-4 text-center text-lg">{t('select-repay-token')}</h2>
|
||||
<div className="grid auto-cols-fr grid-flow-col px-4 pb-2">
|
||||
<div className="text-left">
|
||||
<div className="flex items-center px-4 pb-2">
|
||||
<div className="w-1/2 text-left">
|
||||
<p className="text-xs">{t('token')}</p>
|
||||
</div>
|
||||
<div className="text-right">
|
||||
<div className="w-1/2 text-right">
|
||||
<p className="whitespace-nowrap text-xs">{t('amount-owed')}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -208,13 +222,12 @@ function RepayForm({ onSuccess, token }: RepayFormProps) {
|
|||
style={{ height: ACCOUNT_ACTION_MODAL_INNER_HEIGHT }}
|
||||
>
|
||||
<div>
|
||||
<div className="-mt-2 mb-2">
|
||||
<SolBalanceWarnings
|
||||
amount={inputAmount}
|
||||
setAmount={setInputAmount}
|
||||
selectedToken={selectedToken}
|
||||
/>
|
||||
</div>
|
||||
<SolBalanceWarnings
|
||||
amount={inputAmount}
|
||||
className="mb-4"
|
||||
setAmount={setInputAmount}
|
||||
selectedToken={selectedToken}
|
||||
/>
|
||||
<div className="grid grid-cols-2">
|
||||
<div className="col-span-2 flex justify-between">
|
||||
<Label text={`${t('repay')} ${t('token')}`} />
|
||||
|
@ -255,7 +268,7 @@ function RepayForm({ onSuccess, token }: RepayFormProps) {
|
|||
allowNegative={false}
|
||||
isNumericString={true}
|
||||
decimalScale={bank?.mintDecimals || 6}
|
||||
className="w-full rounded-lg rounded-l-none border border-th-input-border bg-th-input-bkg p-3 text-right font-mono text-xl tracking-wider text-th-fgd-1 focus:border-th-input-border-hover focus:outline-none md:hover:border-th-input-border-hover"
|
||||
className="w-full rounded-lg rounded-l-none border border-th-input-border bg-th-input-bkg p-3 text-right font-mono text-xl text-th-fgd-1 focus:border-th-input-border-hover focus:outline-none md:hover:border-th-input-border-hover"
|
||||
placeholder="0.00"
|
||||
value={inputAmount}
|
||||
onValueChange={(e: NumberFormatValues) => {
|
||||
|
@ -276,56 +289,53 @@ function RepayForm({ onSuccess, token }: RepayFormProps) {
|
|||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="my-6 space-y-1.5 border-y border-th-bkg-3 px-2 py-4 text-sm ">
|
||||
{bank ? (
|
||||
{bank ? (
|
||||
<div className="my-6 space-y-1.5 border-y border-th-bkg-3 px-2 py-4 text-sm ">
|
||||
<HealthImpactTokenChange
|
||||
mintPk={bank.mint}
|
||||
uiAmount={Number(inputAmount)}
|
||||
isDeposit
|
||||
/>
|
||||
) : null}
|
||||
<div className="flex justify-between">
|
||||
<p>{t('repayment-amount')}</p>
|
||||
<p className="font-mono text-th-fgd-2">
|
||||
{bank?.uiPrice && inputAmount ? (
|
||||
<>
|
||||
{inputAmount}{' '}
|
||||
<span className="text-xs text-th-fgd-3">
|
||||
(
|
||||
{formatFixedDecimals(
|
||||
bank.uiPrice * Number(inputAmount),
|
||||
true
|
||||
)}
|
||||
)
|
||||
</span>
|
||||
</>
|
||||
<div className="flex justify-between">
|
||||
<p>{t('repayment-amount')}</p>
|
||||
{inputAmount ? (
|
||||
<AmountWithValue
|
||||
amount={formatDecimal(
|
||||
Number(inputAmount),
|
||||
bank.mintDecimals
|
||||
)}
|
||||
value={formatFixedDecimals(
|
||||
bank.uiPrice * Number(inputAmount),
|
||||
true
|
||||
)}
|
||||
/>
|
||||
) : (
|
||||
<>
|
||||
0 <span className="text-xs text-th-fgd-3">($0.00)</span>
|
||||
</>
|
||||
<AmountWithValue amount="0" value="$0.00" />
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex justify-between">
|
||||
<div className="flex items-center">
|
||||
<p>{t('outstanding-balance')}</p>
|
||||
</div>
|
||||
<p className="font-mono text-th-fgd-2">
|
||||
{floorToDecimal(
|
||||
borrowAmount - Number(inputAmount),
|
||||
walletBalance.maxDecimals
|
||||
).toNumber()}{' '}
|
||||
<span className="font-body text-th-fgd-4">
|
||||
{selectedToken}
|
||||
</span>
|
||||
</p>
|
||||
<div className="flex justify-between">
|
||||
<div className="flex items-center">
|
||||
<p>{t('outstanding-balance')}</p>
|
||||
</div>
|
||||
<p className="font-mono text-th-fgd-2">
|
||||
{floorToDecimal(
|
||||
borrowAmount - Number(inputAmount),
|
||||
walletBalance.maxDecimals
|
||||
).toFixed()}{' '}
|
||||
<span className="font-body text-th-fgd-4">
|
||||
{selectedToken}
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
<Button
|
||||
onClick={() => handleDeposit(inputAmount)}
|
||||
className="flex w-full items-center justify-center"
|
||||
disabled={!inputAmount || showInsufficientBalance}
|
||||
disabled={
|
||||
!inputAmount || showInsufficientBalance || exceedsAlphaMax
|
||||
}
|
||||
size="large"
|
||||
>
|
||||
{submitting ? (
|
||||
|
@ -349,8 +359,7 @@ function RepayForm({ onSuccess, token }: RepayFormProps) {
|
|||
</>
|
||||
) : !connected ? (
|
||||
<div className="flex h-[356px] flex-col items-center justify-center">
|
||||
<LinkIcon className="mb-2 h-6 w-6 text-th-fgd-4" />
|
||||
<p>Connect to repay your borrows</p>
|
||||
<ConnectEmptyState text="Connect to repay your borrows" />
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex h-[356px] flex-col items-center justify-center">
|
||||
|
|
|
@ -10,10 +10,11 @@ import {
|
|||
Cog8ToothIcon,
|
||||
ArrowsRightLeftIcon,
|
||||
ArrowTrendingUpIcon,
|
||||
XMarkIcon,
|
||||
} from '@heroicons/react/20/solid'
|
||||
import { useRouter } from 'next/router'
|
||||
import { useTranslation } from 'next-i18next'
|
||||
import { Fragment, ReactNode, useEffect, useState } from 'react'
|
||||
import { Fragment, ReactNode, useState } from 'react'
|
||||
import { Disclosure, Popover, Transition } from '@headlessui/react'
|
||||
import MangoAccountSummary from './account/MangoAccountSummary'
|
||||
import Tooltip from './shared/Tooltip'
|
||||
|
@ -23,6 +24,7 @@ import mangoStore from '@store/mangoStore'
|
|||
import HealthHeart from './account/HealthHeart'
|
||||
import useMangoAccount from 'hooks/useMangoAccount'
|
||||
import { useTheme } from 'next-themes'
|
||||
import { IconButton } from './shared/Button'
|
||||
|
||||
const SideNav = ({ collapsed }: { collapsed: boolean }) => {
|
||||
const { t } = useTranslation('common')
|
||||
|
@ -152,9 +154,13 @@ const SideNav = ({ collapsed }: { collapsed: boolean }) => {
|
|||
size={32}
|
||||
/>
|
||||
}
|
||||
isOpen
|
||||
panelTitle={`${mangoAccount?.name || ''} ${t('account')}`}
|
||||
title={
|
||||
<div className="w-24 text-left">
|
||||
<p className="mb-0.5 whitespace-nowrap text-xs">Health Check</p>
|
||||
<p className="mb-0.5 whitespace-nowrap text-xs">
|
||||
{t('account')}
|
||||
</p>
|
||||
<p className="truncate whitespace-nowrap text-sm font-bold text-th-fgd-1">
|
||||
{mangoAccount
|
||||
? mangoAccount.name
|
||||
|
@ -166,6 +172,7 @@ const SideNav = ({ collapsed }: { collapsed: boolean }) => {
|
|||
}
|
||||
alignBottom
|
||||
hideIconBg
|
||||
showClose
|
||||
>
|
||||
<div className="px-4 py-2">
|
||||
<MangoAccountSummary />
|
||||
|
@ -253,6 +260,9 @@ export const ExpandableMenuItem = ({
|
|||
collapsed,
|
||||
hideIconBg,
|
||||
icon,
|
||||
panelTitle,
|
||||
isOpen,
|
||||
showClose,
|
||||
title,
|
||||
}: {
|
||||
alignBottom?: boolean
|
||||
|
@ -260,26 +270,14 @@ export const ExpandableMenuItem = ({
|
|||
collapsed: boolean
|
||||
hideIconBg?: boolean
|
||||
icon: ReactNode
|
||||
panelTitle?: string
|
||||
isOpen?: boolean
|
||||
showClose?: boolean
|
||||
title: string | ReactNode
|
||||
}) => {
|
||||
const [showMenu, setShowMenu] = useState(false)
|
||||
const [showMenu, setShowMenu] = useState(isOpen || false)
|
||||
const { theme } = useTheme()
|
||||
|
||||
const onHoverMenu = (open: boolean, action: string) => {
|
||||
if (
|
||||
(!open && action === 'onMouseEnter') ||
|
||||
(open && action === 'onMouseLeave')
|
||||
) {
|
||||
setShowMenu(!open)
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (collapsed) {
|
||||
setShowMenu(false)
|
||||
}
|
||||
}, [collapsed])
|
||||
|
||||
const toggleMenu = () => {
|
||||
setShowMenu(!showMenu)
|
||||
}
|
||||
|
@ -287,12 +285,6 @@ export const ExpandableMenuItem = ({
|
|||
return collapsed ? (
|
||||
<Popover>
|
||||
<div
|
||||
onMouseEnter={
|
||||
!alignBottom ? () => onHoverMenu(showMenu, 'onMouseEnter') : undefined
|
||||
}
|
||||
onMouseLeave={
|
||||
!alignBottom ? () => onHoverMenu(showMenu, 'onMouseLeave') : undefined
|
||||
}
|
||||
className={`relative z-30 ${alignBottom ? '' : 'px-4 py-2'}`}
|
||||
role="button"
|
||||
>
|
||||
|
@ -329,6 +321,20 @@ export const ExpandableMenuItem = ({
|
|||
}`}
|
||||
>
|
||||
<div className="rounded-md rounded-l-none bg-th-bkg-2 py-2">
|
||||
<div className="flex items-center justify-between pl-4 pr-2">
|
||||
{panelTitle ? (
|
||||
<h3 className="text-sm font-bold">{panelTitle}</h3>
|
||||
) : null}
|
||||
{showClose ? (
|
||||
<IconButton
|
||||
onClick={() => setShowMenu(false)}
|
||||
hideBg
|
||||
size="small"
|
||||
>
|
||||
<XMarkIcon className="h-5 w-5" />
|
||||
</IconButton>
|
||||
) : null}
|
||||
</div>
|
||||
{children}
|
||||
</div>
|
||||
</Popover.Panel>
|
||||
|
|
|
@ -31,6 +31,11 @@ import { Table, Td, Th, TrBody, TrHead } from './shared/TableElements'
|
|||
import useMangoGroup from 'hooks/useMangoGroup'
|
||||
import DepositWithdrawModal from './modals/DepositWithdrawModal'
|
||||
import BorrowRepayModal from './modals/BorrowRepayModal'
|
||||
import { WRAPPED_SOL_MINT } from '@project-serum/serum/lib/token-instructions'
|
||||
import { USDC_MINT } from 'utils/constants'
|
||||
import { PublicKey } from '@solana/web3.js'
|
||||
import ActionsLinkButton from './account/ActionsLinkButton'
|
||||
import AmountWithValue from './shared/AmountWithValue'
|
||||
|
||||
const TokenList = () => {
|
||||
const { t } = useTranslation(['common', 'token', 'trade'])
|
||||
|
@ -54,18 +59,23 @@ const TokenList = () => {
|
|||
value,
|
||||
}))
|
||||
const sortedBanks = mangoAccount
|
||||
? rawBanks.sort(
|
||||
(a, b) =>
|
||||
Math.abs(
|
||||
mangoAccount?.getTokenBalanceUi(b.value[0]) *
|
||||
b.value[0].uiPrice!
|
||||
) -
|
||||
Math.abs(
|
||||
mangoAccount?.getTokenBalanceUi(a.value[0]) *
|
||||
a.value[0].uiPrice!
|
||||
)
|
||||
)
|
||||
: rawBanks
|
||||
? rawBanks.sort((a, b) => {
|
||||
const aBalance = Math.abs(
|
||||
mangoAccount.getTokenBalanceUi(a.value[0]) * a.value[0].uiPrice
|
||||
)
|
||||
const bBalance = Math.abs(
|
||||
mangoAccount.getTokenBalanceUi(b.value[0]) * b.value[0].uiPrice
|
||||
)
|
||||
if (aBalance > bBalance) return -1
|
||||
if (aBalance < bBalance) return 1
|
||||
|
||||
const aName = a.value[0].name
|
||||
const bName = b.value[0].name
|
||||
if (aName > bName) return 1
|
||||
if (aName < bName) return -1
|
||||
return 1
|
||||
})
|
||||
: rawBanks.sort((a, b) => a.key.localeCompare(b.key))
|
||||
|
||||
return mangoAccount && !showZeroBalances
|
||||
? sortedBanks.filter(
|
||||
|
@ -155,12 +165,12 @@ const TokenList = () => {
|
|||
)
|
||||
|
||||
const interestAmount = hasInterestEarned
|
||||
? hasInterestEarned.borrow_interest +
|
||||
? hasInterestEarned.borrow_interest * -1 +
|
||||
hasInterestEarned.deposit_interest
|
||||
: 0
|
||||
|
||||
const interestValue = hasInterestEarned
|
||||
? hasInterestEarned.borrow_interest_usd +
|
||||
? hasInterestEarned.borrow_interest_usd * -1 +
|
||||
hasInterestEarned.deposit_interest_usd
|
||||
: 0.0
|
||||
|
||||
|
@ -180,48 +190,69 @@ const TokenList = () => {
|
|||
<QuestionMarkCircleIcon className="h-6 w-6 text-th-fgd-3" />
|
||||
)}
|
||||
</div>
|
||||
<p className="font-body tracking-wide">{bank.name}</p>
|
||||
<p className="font-body tracking-wider">{bank.name}</p>
|
||||
</div>
|
||||
</Td>
|
||||
<Td className="text-right">
|
||||
<p>{tokenBalance}</p>
|
||||
<p className="text-sm text-th-fgd-4">
|
||||
{tokenBalance
|
||||
? `${formatFixedDecimals(
|
||||
tokenBalance * oraclePrice!,
|
||||
true
|
||||
)}`
|
||||
: '$0.00'}
|
||||
</p>
|
||||
{tokenBalance ? (
|
||||
<AmountWithValue
|
||||
amount={formatDecimal(tokenBalance, bank.mintDecimals)}
|
||||
value={formatFixedDecimals(
|
||||
tokenBalance * oraclePrice,
|
||||
true,
|
||||
true
|
||||
)}
|
||||
stacked
|
||||
/>
|
||||
) : (
|
||||
<AmountWithValue amount="0" value="$0.00" stacked />
|
||||
)}
|
||||
</Td>
|
||||
<Td className="text-right">
|
||||
<p>{inOrders}</p>
|
||||
<p className="text-sm text-th-fgd-4">
|
||||
{formatFixedDecimals(inOrders * oraclePrice!, true)}
|
||||
</p>
|
||||
{inOrders ? (
|
||||
<AmountWithValue
|
||||
amount={formatDecimal(inOrders, bank.mintDecimals)}
|
||||
value={formatFixedDecimals(
|
||||
inOrders * oraclePrice,
|
||||
true,
|
||||
true
|
||||
)}
|
||||
stacked
|
||||
/>
|
||||
) : (
|
||||
<AmountWithValue amount="0" value="$0.00" stacked />
|
||||
)}
|
||||
</Td>
|
||||
<Td className="text-right">
|
||||
<p>
|
||||
{unsettled ? unsettled.toFixed(bank.mintDecimals) : 0}
|
||||
</p>
|
||||
<p className="text-sm text-th-fgd-4">
|
||||
{formatFixedDecimals(unsettled * oraclePrice!, true)}
|
||||
</p>
|
||||
{unsettled ? (
|
||||
<AmountWithValue
|
||||
amount={formatDecimal(unsettled, bank.mintDecimals)}
|
||||
value={formatFixedDecimals(
|
||||
unsettled * oraclePrice,
|
||||
true,
|
||||
true
|
||||
)}
|
||||
stacked
|
||||
/>
|
||||
) : (
|
||||
<AmountWithValue amount="0" value="$0.00" stacked />
|
||||
)}
|
||||
</Td>
|
||||
<Td>
|
||||
<div className="flex flex-col text-right">
|
||||
<p>
|
||||
{interestAmount
|
||||
? interestAmount.toFixed(bank.mintDecimals)
|
||||
: 0}
|
||||
</p>
|
||||
<p className="text-sm text-th-fgd-4">
|
||||
{formatFixedDecimals(interestValue, true)}
|
||||
</p>
|
||||
<AmountWithValue
|
||||
amount={
|
||||
interestAmount
|
||||
? formatDecimal(interestAmount, bank.mintDecimals)
|
||||
: '0'
|
||||
}
|
||||
value={formatFixedDecimals(interestValue, true, true)}
|
||||
stacked
|
||||
/>
|
||||
</div>
|
||||
</Td>
|
||||
<Td>
|
||||
<div className="flex justify-end space-x-2">
|
||||
<div className="flex justify-end space-x-1.5">
|
||||
<p className="text-th-up">
|
||||
{formatDecimal(bank.getDepositRateUi(), 2, {
|
||||
fixed: true,
|
||||
|
@ -291,11 +322,12 @@ const MobileTokenListItem = ({ bank }: { bank: Bank }) => {
|
|||
)
|
||||
|
||||
const interestAmount = hasInterestEarned
|
||||
? hasInterestEarned.borrow_interest + hasInterestEarned.deposit_interest
|
||||
? hasInterestEarned.borrow_interest * -1 +
|
||||
hasInterestEarned.deposit_interest
|
||||
: 0
|
||||
|
||||
const interestValue = hasInterestEarned
|
||||
? hasInterestEarned.borrow_interest_usd +
|
||||
? hasInterestEarned.borrow_interest_usd * -1 +
|
||||
hasInterestEarned.deposit_interest_usd
|
||||
: 0.0
|
||||
|
||||
|
@ -331,7 +363,9 @@ const MobileTokenListItem = ({ bank }: { bank: Bank }) => {
|
|||
<span className="mr-1 font-body text-th-fgd-4">
|
||||
{t('balance')}:
|
||||
</span>
|
||||
{tokenBalance}
|
||||
{tokenBalance
|
||||
? formatDecimal(tokenBalance, bank.mintDecimals)
|
||||
: '0'}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -363,34 +397,32 @@ const MobileTokenListItem = ({ bank }: { bank: Bank }) => {
|
|||
<div className="mt-4 grid grid-cols-2 gap-4 border-t border-th-bkg-3 pt-4">
|
||||
<div className="col-span-1">
|
||||
<p className="text-xs text-th-fgd-3">{t('trade:in-orders')}</p>
|
||||
<div className="flex font-mono">
|
||||
<p className="text-th-fgd-2">{inOrders}</p>
|
||||
<p className="ml-1 text-th-fgd-4">
|
||||
({formatFixedDecimals(inOrders * oraclePrice!, true)})
|
||||
</p>
|
||||
</div>
|
||||
<AmountWithValue
|
||||
amount={
|
||||
inOrders ? formatDecimal(inOrders, bank.mintDecimals) : '0'
|
||||
}
|
||||
value={formatFixedDecimals(inOrders * oraclePrice, true, true)}
|
||||
/>
|
||||
</div>
|
||||
<div className="col-span-1">
|
||||
<p className="text-xs text-th-fgd-3">{t('trade:unsettled')}</p>
|
||||
<div className="flex font-mono">
|
||||
<p className="text-th-fgd-2">
|
||||
{unsettled ? unsettled.toFixed(bank.mintDecimals) : 0}
|
||||
</p>
|
||||
<p className="ml-1 text-th-fgd-4">
|
||||
({formatFixedDecimals(unsettled * oraclePrice!, true)})
|
||||
</p>
|
||||
</div>
|
||||
<AmountWithValue
|
||||
amount={
|
||||
unsettled ? formatDecimal(unsettled, bank.mintDecimals) : '0'
|
||||
}
|
||||
value={formatFixedDecimals(unsettled * oraclePrice, true, true)}
|
||||
/>
|
||||
</div>
|
||||
<div className="col-span-1">
|
||||
<p className="text-xs text-th-fgd-3">{t('interest-earned-paid')}</p>
|
||||
<div className="flex font-mono">
|
||||
<p className="text-th-fgd-2">
|
||||
{floorToDecimal(interestAmount, bank.mintDecimals).toNumber()}
|
||||
</p>
|
||||
<p className="ml-1 text-th-fgd-4">
|
||||
({formatFixedDecimals(interestValue, true)})
|
||||
</p>
|
||||
</div>
|
||||
<AmountWithValue
|
||||
amount={
|
||||
interestAmount
|
||||
? formatDecimal(interestAmount, bank.mintDecimals)
|
||||
: '0'
|
||||
}
|
||||
value={formatFixedDecimals(interestValue, true, true)}
|
||||
/>
|
||||
</div>
|
||||
<div className="col-span-1">
|
||||
<p className="text-xs text-th-fgd-3">{t('rates')}</p>
|
||||
|
@ -432,10 +464,18 @@ const ActionsMenu = ({
|
|||
const [showBorrowModal, setShowBorrowModal] = useState(false)
|
||||
const [showRepayModal, setShowRepayModal] = useState(false)
|
||||
const [selectedToken, setSelectedToken] = useState('')
|
||||
// const set = mangoStore.getState().set
|
||||
// const router = useRouter()
|
||||
// const { asPath } = router
|
||||
const set = mangoStore.getState().set
|
||||
const router = useRouter()
|
||||
const { mangoTokens } = useJupiterMints()
|
||||
const spotMarkets = mangoStore((s) => s.serumMarkets)
|
||||
const { connected } = useWallet()
|
||||
|
||||
const spotMarket = useMemo(() => {
|
||||
return spotMarkets.find((m) => {
|
||||
const base = m.name.split('/')[0]
|
||||
return base.toUpperCase() === bank.name.toUpperCase()
|
||||
})
|
||||
}, [spotMarkets])
|
||||
|
||||
const handleShowActionModals = useCallback(
|
||||
(token: string, action: 'borrow' | 'deposit' | 'withdraw' | 'repay') => {
|
||||
|
@ -451,31 +491,53 @@ const ActionsMenu = ({
|
|||
[]
|
||||
)
|
||||
|
||||
// const handleBuy = useCallback(() => {
|
||||
// const outputTokenInfo = mangoTokens.find(
|
||||
// (t: any) => t.address === bank.mint.toString()
|
||||
// )
|
||||
// set((s) => {
|
||||
// s.swap.outputBank = bank
|
||||
// s.swap.outputTokenInfo = outputTokenInfo
|
||||
// })
|
||||
// if (asPath === '/') {
|
||||
// router.push('/swap', undefined, { shallow: true })
|
||||
// }
|
||||
// }, [bank, router, asPath, set, mangoTokens])
|
||||
const balance = useMemo(() => {
|
||||
if (!mangoAccount || !bank) return 0
|
||||
return mangoAccount.getTokenBalanceUi(bank)
|
||||
}, [bank, mangoAccount])
|
||||
|
||||
// const handleSell = useCallback(() => {
|
||||
// const inputTokenInfo = mangoTokens.find(
|
||||
// (t: any) => t.address === bank.mint.toString()
|
||||
// )
|
||||
// set((s) => {
|
||||
// s.swap.inputBank = bank
|
||||
// s.swap.inputTokenInfo = inputTokenInfo
|
||||
// })
|
||||
// if (asPath === '/') {
|
||||
// router.push('/swap', undefined, { shallow: true })
|
||||
// }
|
||||
// }, [router, asPath, set, bank, mangoTokens])
|
||||
const handleSwap = useCallback(() => {
|
||||
const tokenInfo = mangoTokens.find(
|
||||
(t: any) => t.address === bank.mint.toString()
|
||||
)
|
||||
const group = mangoStore.getState().group
|
||||
if (balance && balance > 0) {
|
||||
if (tokenInfo?.symbol === 'SOL') {
|
||||
const usdcTokenInfo = mangoTokens.find(
|
||||
(t: any) => t.address === USDC_MINT
|
||||
)
|
||||
const usdcBank = group?.getFirstBankByMint(new PublicKey(USDC_MINT))
|
||||
set((s) => {
|
||||
s.swap.inputBank = usdcBank
|
||||
s.swap.inputTokenInfo = usdcTokenInfo
|
||||
})
|
||||
}
|
||||
set((s) => {
|
||||
s.swap.inputBank = bank
|
||||
s.swap.inputTokenInfo = tokenInfo
|
||||
})
|
||||
} else {
|
||||
if (tokenInfo?.symbol === 'USDC') {
|
||||
const solTokenInfo = mangoTokens.find(
|
||||
(t: any) => t.address === WRAPPED_SOL_MINT.toString()
|
||||
)
|
||||
const solBank = group?.getFirstBankByMint(WRAPPED_SOL_MINT)
|
||||
set((s) => {
|
||||
s.swap.inputBank = solBank
|
||||
s.swap.inputTokenInfo = solTokenInfo
|
||||
})
|
||||
}
|
||||
set((s) => {
|
||||
s.swap.outputBank = bank
|
||||
s.swap.outputTokenInfo = tokenInfo
|
||||
})
|
||||
}
|
||||
router.push('/swap', undefined, { shallow: true })
|
||||
}, [bank, router, set, mangoTokens, mangoAccount])
|
||||
|
||||
const handleTrade = useCallback(() => {
|
||||
router.push(`/trade?name=${spotMarket?.name}`, undefined, { shallow: true })
|
||||
}, [spotMarket, router])
|
||||
|
||||
const logoURI = useMemo(() => {
|
||||
if (!bank || !mangoTokens?.length) return ''
|
||||
|
@ -494,63 +556,60 @@ const ActionsMenu = ({
|
|||
|
||||
return (
|
||||
<>
|
||||
<IconDropMenu
|
||||
icon={<EllipsisHorizontalIcon className="h-5 w-5" />}
|
||||
postion="leftBottom"
|
||||
>
|
||||
<div className="flex items-center justify-center border-b border-th-bkg-3 pb-2">
|
||||
<div className="mr-2 flex flex-shrink-0 items-center">
|
||||
<Image alt="" width="20" height="20" src={logoURI || ''} />
|
||||
{mangoAccount && !connected ? null : (
|
||||
<IconDropMenu
|
||||
icon={<EllipsisHorizontalIcon className="h-5 w-5" />}
|
||||
postion="leftBottom"
|
||||
>
|
||||
<div className="flex items-center justify-center border-b border-th-bkg-3 pb-2">
|
||||
<div className="mr-2 flex flex-shrink-0 items-center">
|
||||
<Image alt="" width="20" height="20" src={logoURI || ''} />
|
||||
</div>
|
||||
<p className="font-body tracking-wider">
|
||||
{formatTokenSymbol(bank.name)}
|
||||
</p>
|
||||
</div>
|
||||
<p className="font-body tracking-wide">
|
||||
{formatTokenSymbol(bank.name)}
|
||||
</p>
|
||||
</div>
|
||||
<LinkButton
|
||||
className="w-full text-left font-normal no-underline md:hover:text-th-fgd-1"
|
||||
disabled={!mangoAccount}
|
||||
onClick={() => handleShowActionModals(bank.name, 'deposit')}
|
||||
>
|
||||
{t('deposit')}
|
||||
</LinkButton>
|
||||
{hasBorrow ? (
|
||||
<LinkButton
|
||||
className="w-full text-left font-normal no-underline md:hover:text-th-fgd-1"
|
||||
disabled={!mangoAccount}
|
||||
onClick={() => handleShowActionModals(bank.name, 'repay')}
|
||||
<ActionsLinkButton
|
||||
mangoAccount={mangoAccount!}
|
||||
onClick={() => handleShowActionModals(bank.name, 'deposit')}
|
||||
>
|
||||
{t('repay')}
|
||||
</LinkButton>
|
||||
) : null}
|
||||
<LinkButton
|
||||
className="w-full text-left font-normal no-underline md:hover:text-th-fgd-1"
|
||||
disabled={!mangoAccount}
|
||||
onClick={() => handleShowActionModals(bank.name, 'withdraw')}
|
||||
>
|
||||
{t('withdraw')}
|
||||
</LinkButton>
|
||||
<LinkButton
|
||||
className="w-full text-left font-normal no-underline md:hover:text-th-fgd-1"
|
||||
disabled={!mangoAccount}
|
||||
onClick={() => handleShowActionModals(bank.name, 'borrow')}
|
||||
>
|
||||
{t('borrow')}
|
||||
</LinkButton>
|
||||
{/* <LinkButton
|
||||
className="w-full text-left"
|
||||
disabled={!mangoAccount}
|
||||
onClick={handleBuy}
|
||||
>
|
||||
{t('buy')}
|
||||
</LinkButton>
|
||||
<LinkButton
|
||||
className="w-full text-left"
|
||||
disabled={!mangoAccount}
|
||||
onClick={handleSell}
|
||||
>
|
||||
{t('sell')}
|
||||
</LinkButton> */}
|
||||
</IconDropMenu>
|
||||
{t('deposit')}
|
||||
</ActionsLinkButton>
|
||||
{hasBorrow ? (
|
||||
<ActionsLinkButton
|
||||
mangoAccount={mangoAccount!}
|
||||
onClick={() => handleShowActionModals(bank.name, 'repay')}
|
||||
>
|
||||
{t('repay')}
|
||||
</ActionsLinkButton>
|
||||
) : null}
|
||||
{balance && balance > 0 ? (
|
||||
<ActionsLinkButton
|
||||
mangoAccount={mangoAccount!}
|
||||
onClick={() => handleShowActionModals(bank.name, 'withdraw')}
|
||||
>
|
||||
{t('withdraw')}
|
||||
</ActionsLinkButton>
|
||||
) : null}
|
||||
<ActionsLinkButton
|
||||
mangoAccount={mangoAccount!}
|
||||
onClick={() => handleShowActionModals(bank.name, 'borrow')}
|
||||
>
|
||||
{t('borrow')}
|
||||
</ActionsLinkButton>
|
||||
<ActionsLinkButton mangoAccount={mangoAccount!} onClick={handleSwap}>
|
||||
{t('swap')}
|
||||
</ActionsLinkButton>
|
||||
{spotMarket ? (
|
||||
<ActionsLinkButton
|
||||
mangoAccount={mangoAccount!}
|
||||
onClick={handleTrade}
|
||||
>
|
||||
{t('trade')}
|
||||
</ActionsLinkButton>
|
||||
) : null}
|
||||
</IconDropMenu>
|
||||
)}
|
||||
{showDepositModal ? (
|
||||
<DepositWithdrawModal
|
||||
action="deposit"
|
||||
|
|
|
@ -3,6 +3,7 @@ import {
|
|||
ArrowLeftIcon,
|
||||
ArrowRightIcon,
|
||||
ExclamationTriangleIcon,
|
||||
EyeIcon,
|
||||
UsersIcon,
|
||||
} from '@heroicons/react/20/solid'
|
||||
import { useWallet } from '@solana/wallet-adapter-react'
|
||||
|
@ -93,11 +94,22 @@ const TopBar = () => {
|
|||
alt="next"
|
||||
/>
|
||||
{!connected ? (
|
||||
<span className="hidden items-center md:flex">
|
||||
<WalletIcon className="h-5 w-5 text-th-fgd-3" />
|
||||
<span className="ml-2">{t('connect-helper')}</span>
|
||||
<ArrowRightIcon className="sideways-bounce ml-2 h-5 w-5 text-th-fgd-1" />
|
||||
</span>
|
||||
mangoAccount ? (
|
||||
<span className="hidden items-center md:flex">
|
||||
<EyeIcon className="h-5 w-5 text-th-fgd-3" />
|
||||
<span className="ml-2">
|
||||
{t('unowned-helper', {
|
||||
accountPk: abbreviateAddress(mangoAccount.publicKey),
|
||||
})}
|
||||
</span>
|
||||
</span>
|
||||
) : (
|
||||
<span className="hidden items-center md:flex">
|
||||
<WalletIcon className="h-5 w-5 text-th-fgd-3" />
|
||||
<span className="ml-2">{t('connect-helper')}</span>
|
||||
<ArrowRightIcon className="sideways-bounce ml-2 h-5 w-5 text-th-fgd-1" />
|
||||
</span>
|
||||
)
|
||||
) : null}
|
||||
</span>
|
||||
{!isOnline ? (
|
||||
|
@ -112,7 +124,7 @@ const TopBar = () => {
|
|||
{/* <div className="px-3 md:px-4">
|
||||
<ThemeSwitcher />
|
||||
</div> */}
|
||||
{!connected && isMobile ? null : (
|
||||
{(mangoAccount && !connected) || (!connected && isMobile) ? null : (
|
||||
<Button
|
||||
onClick={() => handleDepositWithdrawModal('deposit')}
|
||||
secondary
|
||||
|
|
|
@ -18,7 +18,11 @@ import {
|
|||
INPUT_TOKEN_DEFAULT,
|
||||
} from './../utils/constants'
|
||||
import { notify } from './../utils/notifications'
|
||||
import { floorToDecimal, formatFixedDecimals } from './../utils/numbers'
|
||||
import {
|
||||
floorToDecimal,
|
||||
formatDecimal,
|
||||
formatFixedDecimals,
|
||||
} from './../utils/numbers'
|
||||
import ActionTokenList from './account/ActionTokenList'
|
||||
import ButtonGroup from './forms/ButtonGroup'
|
||||
import Label from './forms/Label'
|
||||
|
@ -35,6 +39,8 @@ import useJupiterMints from 'hooks/useJupiterMints'
|
|||
import useMangoGroup from 'hooks/useMangoGroup'
|
||||
import TokenVaultWarnings from '@components/shared/TokenVaultWarnings'
|
||||
import { useWallet } from '@solana/wallet-adapter-react'
|
||||
import { useEnhancedWallet } from './wallet/EnhancedWalletProvider'
|
||||
import AmountWithValue from './shared/AmountWithValue'
|
||||
|
||||
interface WithdrawFormProps {
|
||||
onSuccess: () => void
|
||||
|
@ -54,6 +60,7 @@ function WithdrawForm({ onSuccess, token }: WithdrawFormProps) {
|
|||
const { mangoTokens } = useJupiterMints()
|
||||
const { mangoAccount } = useMangoAccount()
|
||||
const { connected } = useWallet()
|
||||
const { handleConnect } = useEnhancedWallet()
|
||||
|
||||
const bank = useMemo(() => {
|
||||
const group = mangoStore.getState().group
|
||||
|
@ -81,11 +88,12 @@ function WithdrawForm({ onSuccess, token }: WithdrawFormProps) {
|
|||
|
||||
const handleSizePercentage = useCallback(
|
||||
(percentage: string) => {
|
||||
if (!bank) return
|
||||
setSizePercentage(percentage)
|
||||
const amount = tokenMax.mul(Number(percentage) / 100)
|
||||
setInputAmount(amount.toFixed())
|
||||
setInputAmount(floorToDecimal(amount, bank.mintDecimals).toFixed())
|
||||
},
|
||||
[tokenMax]
|
||||
[bank, tokenMax]
|
||||
)
|
||||
|
||||
const handleWithdraw = useCallback(async () => {
|
||||
|
@ -181,11 +189,11 @@ function WithdrawForm({ onSuccess, token }: WithdrawFormProps) {
|
|||
<h2 className="mb-4 text-center text-lg">
|
||||
{t('select-withdraw-token')}
|
||||
</h2>
|
||||
<div className="grid auto-cols-fr grid-flow-col px-4 pb-2">
|
||||
<div className="text-left">
|
||||
<div className="flex items-center px-4 pb-2">
|
||||
<div className="w-1/2 text-left">
|
||||
<p className="text-xs">{t('token')}</p>
|
||||
</div>
|
||||
<div className="flex justify-end">
|
||||
<div className="w-1/2 text-right">
|
||||
<p className="text-xs">{t('available-balance')}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -210,16 +218,21 @@ function WithdrawForm({ onSuccess, token }: WithdrawFormProps) {
|
|||
/>
|
||||
</div>
|
||||
) : null}
|
||||
{bank ? <TokenVaultWarnings bank={bank} /> : null}
|
||||
{bank ? <TokenVaultWarnings bank={bank} type="withdraw" /> : null}
|
||||
<div className="grid grid-cols-2">
|
||||
<div className="col-span-2 flex justify-between">
|
||||
<Label text={`${t('withdraw')} ${t('token')}`} />
|
||||
<MaxAmountButton
|
||||
className="mb-2"
|
||||
label={t('max')}
|
||||
onClick={() => handleSizePercentage('100')}
|
||||
value={tokenMax.toString()}
|
||||
/>
|
||||
{bank ? (
|
||||
<MaxAmountButton
|
||||
className="mb-2"
|
||||
label={t('max')}
|
||||
onClick={() => handleSizePercentage('100')}
|
||||
value={floorToDecimal(
|
||||
Number(tokenMax),
|
||||
bank.mintDecimals
|
||||
).toFixed()}
|
||||
/>
|
||||
) : null}
|
||||
</div>
|
||||
<div className="col-span-1 rounded-lg rounded-r-none border border-r-0 border-th-input-border bg-th-input-bkg">
|
||||
<button
|
||||
|
@ -251,7 +264,7 @@ function WithdrawForm({ onSuccess, token }: WithdrawFormProps) {
|
|||
allowNegative={false}
|
||||
isNumericString={true}
|
||||
decimalScale={bank?.mintDecimals || 6}
|
||||
className="w-full rounded-lg rounded-l-none border border-th-input-border bg-th-input-bkg p-3 text-right font-mono text-xl tracking-wider text-th-fgd-1 focus:border-th-input-border-hover focus:outline-none md:hover:border-th-input-border-hover"
|
||||
className="w-full rounded-lg rounded-l-none border border-th-input-border bg-th-input-bkg p-3 text-right font-mono text-xl text-th-fgd-1 focus:border-th-input-border-hover focus:outline-none md:hover:border-th-input-border-hover"
|
||||
placeholder="0.00"
|
||||
value={inputAmount}
|
||||
onValueChange={(e: NumberFormatValues) =>
|
||||
|
@ -272,44 +285,39 @@ function WithdrawForm({ onSuccess, token }: WithdrawFormProps) {
|
|||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="my-6 space-y-2 border-y border-th-bkg-3 px-2 py-4">
|
||||
<HealthImpactTokenChange
|
||||
mintPk={bank!.mint}
|
||||
uiAmount={Number(inputAmount)}
|
||||
/>
|
||||
<div className="flex justify-between">
|
||||
<p>{t('withdraw-amount')}</p>
|
||||
<p className="font-mono text-th-fgd-2">
|
||||
{bank?.uiPrice && inputAmount ? (
|
||||
<>
|
||||
{inputAmount}{' '}
|
||||
<span className="text-xs text-th-fgd-3">
|
||||
(
|
||||
{formatFixedDecimals(
|
||||
bank.uiPrice * Number(inputAmount),
|
||||
true
|
||||
)}
|
||||
)
|
||||
</span>
|
||||
</>
|
||||
{bank ? (
|
||||
<div className="my-6 space-y-1.5 border-y border-th-bkg-3 px-2 py-4">
|
||||
<HealthImpactTokenChange
|
||||
mintPk={bank.mint}
|
||||
uiAmount={Number(inputAmount)}
|
||||
/>
|
||||
<div className="flex justify-between">
|
||||
<p>{t('withdraw-amount')}</p>
|
||||
{inputAmount ? (
|
||||
<AmountWithValue
|
||||
amount={formatDecimal(
|
||||
Number(inputAmount),
|
||||
bank.mintDecimals
|
||||
)}
|
||||
value={formatFixedDecimals(
|
||||
bank.uiPrice * Number(inputAmount),
|
||||
true
|
||||
)}
|
||||
/>
|
||||
) : (
|
||||
<>
|
||||
0 <span className="text-xs text-th-fgd-3">($0.00)</span>
|
||||
</>
|
||||
<AmountWithValue amount="0" value="$0.00" />
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
<Button
|
||||
onClick={handleWithdraw}
|
||||
onClick={connected ? handleWithdraw : handleConnect}
|
||||
className="flex w-full items-center justify-center"
|
||||
size="large"
|
||||
disabled={
|
||||
!inputAmount ||
|
||||
showInsufficientBalance ||
|
||||
initHealth <= 0 ||
|
||||
!connected
|
||||
connected &&
|
||||
(!inputAmount || showInsufficientBalance || initHealth <= 0)
|
||||
}
|
||||
>
|
||||
{!connected ? (
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { Fragment, ReactNode, useState } from 'react'
|
||||
import Button, { LinkButton } from '../shared/Button'
|
||||
import { Fragment, useState } from 'react'
|
||||
import Button from '../shared/Button'
|
||||
import {
|
||||
ArrowDownRightIcon,
|
||||
ArrowUpLeftIcon,
|
||||
|
@ -22,6 +22,7 @@ import BorrowRepayModal from '@components/modals/BorrowRepayModal'
|
|||
import { useWallet } from '@solana/wallet-adapter-react'
|
||||
import CreateAccountModal from '@components/modals/CreateAccountModal'
|
||||
import { Menu, Transition } from '@headlessui/react'
|
||||
import ActionsLinkButton from './ActionsLinkButton'
|
||||
|
||||
export const handleCopyAddress = (
|
||||
mangoAccount: MangoAccount,
|
||||
|
@ -45,17 +46,6 @@ const AccountActions = () => {
|
|||
const [showCreateAccountModal, setShowCreateAccountModal] = useState(false)
|
||||
const { connected } = useWallet()
|
||||
|
||||
// this doesn't work for detecting spot borrows as it includes perp liabs. was only using it to make the repay button have primary styles so could delete
|
||||
|
||||
// const hasBorrows = useMemo(() => {
|
||||
// if (!mangoAccount || !group) return false
|
||||
// return (
|
||||
// toUiDecimalsForQuote(
|
||||
// mangoAccount.getLiabsValue(group, HealthType.init).toNumber()
|
||||
// ) >= 1
|
||||
// )
|
||||
// }, [mangoAccount, group])
|
||||
|
||||
const handleBorrowModal = () => {
|
||||
if (!connected || mangoAccount) {
|
||||
setShowBorrowModal(true)
|
||||
|
@ -66,100 +56,102 @@ const AccountActions = () => {
|
|||
|
||||
return (
|
||||
<>
|
||||
<div className="flex items-center space-x-2">
|
||||
<Button
|
||||
className="flex w-1/3 items-center justify-center sm:w-auto"
|
||||
disabled={!mangoAccount}
|
||||
onClick={() => setShowRepayModal(true)}
|
||||
// secondary={!hasBorrows}
|
||||
secondary
|
||||
>
|
||||
<ArrowDownRightIcon className="mr-2 h-5 w-5" />
|
||||
{t('repay')}
|
||||
</Button>
|
||||
<Button
|
||||
className="flex w-1/3 items-center justify-center sm:w-auto"
|
||||
onClick={handleBorrowModal}
|
||||
secondary
|
||||
>
|
||||
<ArrowUpLeftIcon className="mr-2 h-5 w-5" />
|
||||
{t('borrow')}
|
||||
</Button>
|
||||
<Menu>
|
||||
{({ open }) => (
|
||||
<div className="relative w-1/3 sm:w-auto">
|
||||
<Menu.Button
|
||||
className={`default-transition w-full focus:outline-none`}
|
||||
>
|
||||
<Button
|
||||
className="flex w-full items-center justify-center"
|
||||
secondary
|
||||
{mangoAccount && !connected ? null : (
|
||||
<div className="flex items-center space-x-2">
|
||||
<Button
|
||||
className="flex w-1/3 items-center justify-center md:w-auto"
|
||||
disabled={!mangoAccount}
|
||||
onClick={() => setShowRepayModal(true)}
|
||||
secondary
|
||||
>
|
||||
<ArrowDownRightIcon className="mr-2 h-5 w-5" />
|
||||
{t('repay')}
|
||||
</Button>
|
||||
<Button
|
||||
className="flex w-1/3 items-center justify-center md:w-auto"
|
||||
onClick={handleBorrowModal}
|
||||
secondary
|
||||
>
|
||||
<ArrowUpLeftIcon className="mr-2 h-5 w-5" />
|
||||
{t('borrow')}
|
||||
</Button>
|
||||
<Menu>
|
||||
{({ open }) => (
|
||||
<div className="relative w-1/3 md:w-auto">
|
||||
<Menu.Button
|
||||
className={`default-transition w-full focus:outline-none`}
|
||||
as="div"
|
||||
>
|
||||
<WrenchIcon className="mr-2 h-4 w-4" />
|
||||
{t('actions')}
|
||||
</Button>
|
||||
</Menu.Button>
|
||||
<Transition
|
||||
appear={true}
|
||||
show={open}
|
||||
as={Fragment}
|
||||
enter="transition ease-in duration-200"
|
||||
enterFrom="opacity-0 scale-75"
|
||||
enterTo="opacity-100 scale-100"
|
||||
leave="transition ease-out duration-200"
|
||||
leaveFrom="opacity-100"
|
||||
leaveTo="opacity-0"
|
||||
>
|
||||
<Menu.Items className="absolute right-0 top-10 mt-1 space-y-1.5 rounded-md bg-th-bkg-2 px-4 py-2.5">
|
||||
<Menu.Item>
|
||||
<ActionsButton
|
||||
mangoAccount={mangoAccount!}
|
||||
onClick={() =>
|
||||
handleCopyAddress(
|
||||
mangoAccount!,
|
||||
t('copy-address-success', {
|
||||
pk: abbreviateAddress(mangoAccount!.publicKey),
|
||||
})
|
||||
)
|
||||
}
|
||||
>
|
||||
<DocumentDuplicateIcon className="h-4 w-4" />
|
||||
<span className="ml-2">{t('copy-address')}</span>
|
||||
</ActionsButton>
|
||||
</Menu.Item>
|
||||
<Menu.Item>
|
||||
<ActionsButton
|
||||
mangoAccount={mangoAccount!}
|
||||
onClick={() => setShowEditAccountModal(true)}
|
||||
>
|
||||
<PencilIcon className="h-4 w-4" />
|
||||
<span className="ml-2">{t('edit-account')}</span>
|
||||
</ActionsButton>
|
||||
</Menu.Item>
|
||||
<Menu.Item>
|
||||
<ActionsButton
|
||||
mangoAccount={mangoAccount!}
|
||||
onClick={() => setShowDelegateModal(true)}
|
||||
>
|
||||
<UsersIcon className="h-4 w-4" />
|
||||
<span className="ml-2">{t('delegate-account')}</span>
|
||||
</ActionsButton>
|
||||
</Menu.Item>
|
||||
<Menu.Item>
|
||||
<ActionsButton
|
||||
mangoAccount={mangoAccount!}
|
||||
onClick={() => setShowCloseAccountModal(true)}
|
||||
>
|
||||
<TrashIcon className="h-4 w-4" />
|
||||
<span className="ml-2">{t('close-account')}</span>
|
||||
</ActionsButton>
|
||||
</Menu.Item>
|
||||
</Menu.Items>
|
||||
</Transition>
|
||||
</div>
|
||||
)}
|
||||
</Menu>
|
||||
</div>
|
||||
<Button
|
||||
className="flex w-full items-center justify-center"
|
||||
secondary
|
||||
>
|
||||
<WrenchIcon className="mr-2 h-4 w-4" />
|
||||
{t('actions')}
|
||||
</Button>
|
||||
</Menu.Button>
|
||||
<Transition
|
||||
appear={true}
|
||||
show={open}
|
||||
as={Fragment}
|
||||
enter="transition ease-in duration-200"
|
||||
enterFrom="opacity-0 scale-75"
|
||||
enterTo="opacity-100 scale-100"
|
||||
leave="transition ease-out duration-200"
|
||||
leaveFrom="opacity-100"
|
||||
leaveTo="opacity-0"
|
||||
>
|
||||
<Menu.Items className="absolute right-0 top-10 mt-1 space-y-1.5 rounded-md bg-th-bkg-2 px-4 py-2.5">
|
||||
<Menu.Item>
|
||||
<ActionsLinkButton
|
||||
mangoAccount={mangoAccount!}
|
||||
onClick={() =>
|
||||
handleCopyAddress(
|
||||
mangoAccount!,
|
||||
t('copy-address-success', {
|
||||
pk: abbreviateAddress(mangoAccount!.publicKey),
|
||||
})
|
||||
)
|
||||
}
|
||||
>
|
||||
<DocumentDuplicateIcon className="h-4 w-4" />
|
||||
<span className="ml-2">{t('copy-address')}</span>
|
||||
</ActionsLinkButton>
|
||||
</Menu.Item>
|
||||
<Menu.Item>
|
||||
<ActionsLinkButton
|
||||
mangoAccount={mangoAccount!}
|
||||
onClick={() => setShowEditAccountModal(true)}
|
||||
>
|
||||
<PencilIcon className="h-4 w-4" />
|
||||
<span className="ml-2">{t('edit-account')}</span>
|
||||
</ActionsLinkButton>
|
||||
</Menu.Item>
|
||||
<Menu.Item>
|
||||
<ActionsLinkButton
|
||||
mangoAccount={mangoAccount!}
|
||||
onClick={() => setShowDelegateModal(true)}
|
||||
>
|
||||
<UsersIcon className="h-4 w-4" />
|
||||
<span className="ml-2">{t('delegate-account')}</span>
|
||||
</ActionsLinkButton>
|
||||
</Menu.Item>
|
||||
<Menu.Item>
|
||||
<ActionsLinkButton
|
||||
mangoAccount={mangoAccount!}
|
||||
onClick={() => setShowCloseAccountModal(true)}
|
||||
>
|
||||
<TrashIcon className="h-4 w-4" />
|
||||
<span className="ml-2">{t('close-account')}</span>
|
||||
</ActionsLinkButton>
|
||||
</Menu.Item>
|
||||
</Menu.Items>
|
||||
</Transition>
|
||||
</div>
|
||||
)}
|
||||
</Menu>
|
||||
</div>
|
||||
)}
|
||||
{showCloseAccountModal ? (
|
||||
<CloseAccountModal
|
||||
isOpen={showCloseAccountModal}
|
||||
|
@ -203,23 +195,3 @@ const AccountActions = () => {
|
|||
}
|
||||
|
||||
export default AccountActions
|
||||
|
||||
const ActionsButton = ({
|
||||
children,
|
||||
mangoAccount,
|
||||
onClick,
|
||||
}: {
|
||||
children: ReactNode
|
||||
mangoAccount: MangoAccount
|
||||
onClick: () => void
|
||||
}) => {
|
||||
return (
|
||||
<LinkButton
|
||||
className="whitespace-nowrap font-normal no-underline md:hover:text-th-fgd-1"
|
||||
disabled={!mangoAccount}
|
||||
onClick={onClick}
|
||||
>
|
||||
{children}
|
||||
</LinkButton>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -1,7 +1,4 @@
|
|||
import {
|
||||
MangoAccount,
|
||||
toUiDecimalsForQuote,
|
||||
} from '@blockworks-foundation/mango-v4'
|
||||
import { toUiDecimalsForQuote } from '@blockworks-foundation/mango-v4'
|
||||
import { useTranslation } from 'next-i18next'
|
||||
import { useMemo, useState } from 'react'
|
||||
import mangoStore from '@store/mangoStore'
|
||||
|
@ -15,39 +12,41 @@ const AccountChart = ({
|
|||
chartToShow,
|
||||
data,
|
||||
hideChart,
|
||||
mangoAccount,
|
||||
yKey,
|
||||
}: {
|
||||
chartToShow: string
|
||||
data: Array<any>
|
||||
hideChart: () => void
|
||||
mangoAccount: MangoAccount
|
||||
yKey: string
|
||||
}) => {
|
||||
const { t } = useTranslation('common')
|
||||
const actions = mangoStore((s) => s.actions)
|
||||
const actions = mangoStore.getState().actions
|
||||
const [daysToShow, setDaysToShow] = useState<string>('1')
|
||||
const loading = mangoStore((s) => s.mangoAccount.stats.performance.loading)
|
||||
|
||||
const handleDaysToShow = async (days: string) => {
|
||||
await actions.fetchAccountPerformance(
|
||||
mangoAccount.publicKey.toString(),
|
||||
parseInt(days)
|
||||
)
|
||||
setDaysToShow(days)
|
||||
const mangoAccount = mangoStore.getState().mangoAccount.current
|
||||
if (mangoAccount) {
|
||||
await actions.fetchAccountPerformance(
|
||||
mangoAccount.publicKey.toString(),
|
||||
parseInt(days)
|
||||
)
|
||||
setDaysToShow(days)
|
||||
}
|
||||
}
|
||||
|
||||
const currentValue = useMemo(() => {
|
||||
if (chartToShow === 'account-value') {
|
||||
const group = mangoStore.getState().group
|
||||
const mangoAccount = mangoStore.getState().mangoAccount.current
|
||||
const group = mangoStore.getState().group
|
||||
if (group && mangoAccount && chartToShow === 'account-value') {
|
||||
const currentAccountValue = toUiDecimalsForQuote(
|
||||
mangoAccount.getEquity(group!)!.toNumber()
|
||||
mangoAccount.getEquity(group).toNumber()
|
||||
)
|
||||
const time = Date.now()
|
||||
return [{ account_equity: currentAccountValue, time: time }]
|
||||
}
|
||||
return []
|
||||
}, [chartToShow, mangoAccount])
|
||||
}, [chartToShow])
|
||||
|
||||
return (
|
||||
<DetailedAreaChart
|
||||
|
|
|
@ -56,8 +56,8 @@ const AccountPage = () => {
|
|||
const { t } = useTranslation('common')
|
||||
// const { connected } = useWallet()
|
||||
const group = mangoStore.getState().group
|
||||
const { mangoAccount } = useMangoAccount()
|
||||
const actions = mangoStore((s) => s.actions)
|
||||
const { mangoAccount, mangoAccountAddress } = useMangoAccount()
|
||||
const actions = mangoStore.getState().actions
|
||||
const loadPerformanceData = mangoStore(
|
||||
(s) => s.mangoAccount.stats.performance.loading
|
||||
)
|
||||
|
@ -76,7 +76,7 @@ const AccountPage = () => {
|
|||
const [showExpandChart, setShowExpandChart] = useState<boolean>(false)
|
||||
const { theme } = useTheme()
|
||||
const { width } = useViewport()
|
||||
const isMobile = width ? width < breakpoints.sm : false
|
||||
const isMobile = width ? width < breakpoints.md : false
|
||||
// const tourSettings = mangoStore((s) => s.settings.tours)
|
||||
// const [isOnBoarded] = useLocalStorageState(IS_ONBOARDED_KEY)
|
||||
const [animationSettings] = useLocalStorageState(
|
||||
|
@ -85,12 +85,11 @@ const AccountPage = () => {
|
|||
)
|
||||
|
||||
useEffect(() => {
|
||||
if (mangoAccount) {
|
||||
const pubKey = mangoAccount.publicKey.toString()
|
||||
actions.fetchAccountPerformance(pubKey, 1)
|
||||
actions.fetchAccountInterestTotals(pubKey)
|
||||
if (mangoAccountAddress) {
|
||||
actions.fetchAccountPerformance(mangoAccountAddress, 1)
|
||||
actions.fetchAccountInterestTotals(mangoAccountAddress)
|
||||
}
|
||||
}, [actions, mangoAccount])
|
||||
}, [actions, mangoAccountAddress])
|
||||
|
||||
useEffect(() => {
|
||||
if (mangoAccount && performanceData.length && !chartToShow) {
|
||||
|
@ -222,7 +221,7 @@ const AccountPage = () => {
|
|||
return !chartToShow ? (
|
||||
<>
|
||||
<div className="flex flex-col border-b-0 border-th-bkg-3 px-6 py-3 lg:flex-row lg:items-center lg:justify-between lg:border-b">
|
||||
<div className="flex flex-col sm:flex-row sm:items-center sm:space-x-6">
|
||||
<div className="flex flex-col md:flex-row md:items-center md:space-x-6">
|
||||
<div id="account-step-three">
|
||||
<Tooltip
|
||||
maxWidth="20rem"
|
||||
|
@ -243,7 +242,7 @@ const AccountPage = () => {
|
|||
play
|
||||
delay={0.05}
|
||||
duration={1}
|
||||
numbers={formatFixedDecimals(accountValue, true)}
|
||||
numbers={formatFixedDecimals(accountValue, true, true)}
|
||||
/>
|
||||
) : (
|
||||
<FlipNumbers
|
||||
|
@ -256,7 +255,7 @@ const AccountPage = () => {
|
|||
/>
|
||||
)
|
||||
) : (
|
||||
<span>{formatFixedDecimals(accountValue, true)}</span>
|
||||
<span>{formatFixedDecimals(accountValue, true, true)}</span>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex items-center space-x-1.5">
|
||||
|
@ -267,7 +266,7 @@ const AccountPage = () => {
|
|||
{!loadPerformanceData ? (
|
||||
mangoAccount && performanceData.length ? (
|
||||
<div
|
||||
className="relative mt-4 flex h-44 items-end sm:mt-0 sm:h-24 sm:w-48"
|
||||
className="relative mt-4 flex h-44 items-end md:mt-0 md:h-24 md:w-48"
|
||||
onMouseEnter={() =>
|
||||
onHoverMenu(showExpandChart, 'onMouseEnter')
|
||||
}
|
||||
|
@ -308,8 +307,8 @@ const AccountPage = () => {
|
|||
</div>
|
||||
) : null
|
||||
) : (
|
||||
<SheenLoader className="mt-4 flex flex-1 sm:mt-0">
|
||||
<div className="h-40 w-full rounded-md bg-th-bkg-2 sm:h-24 sm:w-48" />
|
||||
<SheenLoader className="mt-4 flex flex-1 md:mt-0">
|
||||
<div className="h-40 w-full rounded-md bg-th-bkg-2 md:h-24 md:w-48" />
|
||||
</SheenLoader>
|
||||
)}
|
||||
</div>
|
||||
|
@ -318,7 +317,7 @@ const AccountPage = () => {
|
|||
</div>
|
||||
</div>
|
||||
<div className="grid grid-cols-5 border-b border-th-bkg-3">
|
||||
<div className="col-span-4 flex border-t border-th-bkg-3 py-3 pl-6 lg:col-span-1 lg:border-t-0">
|
||||
<div className="col-span-5 flex border-t border-th-bkg-3 py-3 pl-6 lg:col-span-1 lg:border-t-0">
|
||||
<div id="account-step-four">
|
||||
<Tooltip
|
||||
maxWidth="20rem"
|
||||
|
@ -364,7 +363,7 @@ const AccountPage = () => {
|
|||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="col-span-4 flex border-t border-th-bkg-3 py-3 pl-6 lg:col-span-1 lg:border-l lg:border-t-0">
|
||||
<div className="col-span-5 flex border-t border-th-bkg-3 py-3 pl-6 lg:col-span-1 lg:border-l lg:border-t-0">
|
||||
<div id="account-step-five">
|
||||
<Tooltip
|
||||
content="The amount of capital you have to use for trades and loans. When your free collateral reaches $0 you won't be able to trade, borrow or withdraw."
|
||||
|
@ -382,6 +381,7 @@ const AccountPage = () => {
|
|||
toUiDecimalsForQuote(
|
||||
mangoAccount.getCollateralValue(group).toNumber()
|
||||
),
|
||||
false,
|
||||
true
|
||||
)
|
||||
: `$${(0).toFixed(2)}`}
|
||||
|
@ -402,6 +402,7 @@ const AccountPage = () => {
|
|||
.getAssetsValue(group, HealthType.init)
|
||||
.toNumber()
|
||||
),
|
||||
false,
|
||||
true
|
||||
)
|
||||
: `$${(0).toFixed(2)}`}
|
||||
|
@ -413,7 +414,7 @@ const AccountPage = () => {
|
|||
<div className="col-span-5 flex border-t border-th-bkg-3 py-3 pl-6 lg:col-span-1 lg:border-l lg:border-t-0">
|
||||
<div id="account-step-six">
|
||||
<Tooltip
|
||||
content="Total position size divided by total collateral."
|
||||
content="Total assets value divided by account equity value."
|
||||
maxWidth="20rem"
|
||||
placement="bottom"
|
||||
delay={250}
|
||||
|
@ -428,7 +429,7 @@ const AccountPage = () => {
|
|||
</div>
|
||||
</div>
|
||||
<button
|
||||
className={`col-span-4 flex items-center justify-between border-t border-th-bkg-3 py-3 pl-6 pr-4 lg:col-span-1 lg:border-l lg:border-t-0 ${
|
||||
className={`col-span-5 flex items-center justify-between border-t border-th-bkg-3 py-3 pl-6 pr-4 lg:col-span-1 lg:border-l lg:border-t-0 ${
|
||||
performanceData.length > 4
|
||||
? 'default-transition cursor-pointer md:hover:bg-th-bkg-2'
|
||||
: 'cursor-default'
|
||||
|
@ -446,7 +447,7 @@ const AccountPage = () => {
|
|||
</p>
|
||||
</Tooltip>
|
||||
<p className="mt-1 mb-0.5 text-left text-2xl font-bold text-th-fgd-1 lg:text-xl xl:text-2xl">
|
||||
{formatFixedDecimals(accountPnl, true)}
|
||||
{formatFixedDecimals(accountPnl, true, true)}
|
||||
</p>
|
||||
<div className="flex space-x-1">
|
||||
<Change change={oneDayPnlChange} prefix="$" size="small" />
|
||||
|
@ -458,7 +459,7 @@ const AccountPage = () => {
|
|||
) : null}
|
||||
</button>
|
||||
<button
|
||||
className={`col-span-4 flex items-center justify-between border-t border-th-bkg-3 py-3 pl-6 pr-4 text-left lg:col-span-1 lg:border-l lg:border-t-0 ${
|
||||
className={`col-span-5 flex items-center justify-between border-t border-th-bkg-3 py-3 pl-6 pr-4 text-left lg:col-span-1 lg:border-l lg:border-t-0 ${
|
||||
interestTotalValue > 1 || interestTotalValue < -1
|
||||
? 'default-transition cursor-pointer md:hover:bg-th-bkg-2'
|
||||
: 'cursor-default'
|
||||
|
@ -477,7 +478,7 @@ const AccountPage = () => {
|
|||
</p>
|
||||
</Tooltip>
|
||||
<p className="mt-1 mb-0.5 text-2xl font-bold text-th-fgd-1 lg:text-xl xl:text-2xl">
|
||||
{formatFixedDecimals(interestTotalValue, true)}
|
||||
{formatFixedDecimals(interestTotalValue, true, true)}
|
||||
</p>
|
||||
<div className="flex space-x-1">
|
||||
<Change change={oneDayInterestChange} prefix="$" size="small" />
|
||||
|
@ -501,7 +502,6 @@ const AccountPage = () => {
|
|||
chartToShow="account-value"
|
||||
data={performanceData}
|
||||
hideChart={handleHideChart}
|
||||
mangoAccount={mangoAccount!}
|
||||
yKey="account_equity"
|
||||
/>
|
||||
) : chartToShow === 'pnl' ? (
|
||||
|
@ -509,7 +509,6 @@ const AccountPage = () => {
|
|||
chartToShow="pnl"
|
||||
data={performanceData}
|
||||
hideChart={handleHideChart}
|
||||
mangoAccount={mangoAccount!}
|
||||
yKey="pnl"
|
||||
/>
|
||||
) : (
|
||||
|
@ -522,7 +521,6 @@ const AccountPage = () => {
|
|||
time: d.time,
|
||||
}))}
|
||||
hideChart={handleHideChart}
|
||||
mangoAccount={mangoAccount!}
|
||||
yKey="interest_value"
|
||||
/>
|
||||
)}
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import { useMemo, useState } from 'react'
|
||||
import mangoStore from '@store/mangoStore'
|
||||
import TabButtons from '../shared/TabButtons'
|
||||
import TokenList from '../TokenList'
|
||||
import SwapHistoryTable from '../swap/SwapHistoryTable'
|
||||
|
@ -43,8 +42,6 @@ const AccountTabs = () => {
|
|||
}
|
||||
|
||||
const TabContent = ({ activeTab }: { activeTab: string }) => {
|
||||
const swapHistory = mangoStore((s) => s.mangoAccount.stats.swapHistory.data)
|
||||
const loading = mangoStore((s) => s.mangoAccount.stats.swapHistory.loading)
|
||||
const unsettledSpotBalances = useUnsettledSpotBalances()
|
||||
const unsettledPerpPositions = useUnsettledPerpPositions()
|
||||
switch (activeTab) {
|
||||
|
@ -53,7 +50,7 @@ const TabContent = ({ activeTab }: { activeTab: string }) => {
|
|||
case 'activity:activity':
|
||||
return <ActivityFeed />
|
||||
case 'swap:swap-history':
|
||||
return <SwapHistoryTable swapHistory={swapHistory} loading={loading} />
|
||||
return <SwapHistoryTable />
|
||||
case 'trade:unsettled':
|
||||
return (
|
||||
<UnsettledTrades
|
||||
|
|
|
@ -30,11 +30,15 @@ const ActionTokenItem = ({
|
|||
|
||||
return (
|
||||
<button
|
||||
className="default-transition flex grid w-full auto-cols-fr grid-flow-col items-center rounded-md border border-th-bkg-4 bg-th-bkg-1 px-4 py-3 disabled:cursor-not-allowed disabled:opacity-30 md:hover:border-th-fgd-4 md:disabled:hover:border-th-bkg-4"
|
||||
className="default-transition flex w-full items-center rounded-md border border-th-bkg-4 bg-th-bkg-1 px-4 py-3 disabled:cursor-not-allowed disabled:opacity-30 md:hover:border-th-fgd-4 md:disabled:hover:border-th-bkg-4"
|
||||
onClick={() => onSelect(name)}
|
||||
disabled={customValue <= 0}
|
||||
>
|
||||
<div className="flex items-center">
|
||||
<div
|
||||
className={`flex ${
|
||||
!showBorrowRates && !showDepositRates ? 'w-1/2' : 'w-1/4'
|
||||
} items-center`}
|
||||
>
|
||||
<div className="mr-2.5 flex flex-shrink-0 items-center">
|
||||
<Image
|
||||
alt=""
|
||||
|
@ -46,21 +50,23 @@ const ActionTokenItem = ({
|
|||
<p className="text-th-fgd-1">{name}</p>
|
||||
</div>
|
||||
{showDepositRates ? (
|
||||
<div className="text-right font-mono">
|
||||
<div className="w-1/4 text-right font-mono">
|
||||
<p className="text-th-up">
|
||||
{formatDecimal(bank.getDepositRate().toNumber(), 2)}%
|
||||
</p>
|
||||
</div>
|
||||
) : null}
|
||||
{showBorrowRates ? (
|
||||
<div className="text-right font-mono">
|
||||
<div className="w-1/4 text-right font-mono">
|
||||
<p className="text-th-down">
|
||||
{formatDecimal(bank.getBorrowRate().toNumber(), 2)}%
|
||||
</p>
|
||||
</div>
|
||||
) : null}
|
||||
<div className="text-right">
|
||||
<p className="font-mono text-th-fgd-1">{formatDecimal(customValue)}</p>
|
||||
<div className="w-1/2 pl-3 text-right">
|
||||
<p className="truncate font-mono text-th-fgd-1">
|
||||
{formatDecimal(customValue)}
|
||||
</p>
|
||||
</div>
|
||||
</button>
|
||||
)
|
||||
|
|
|
@ -0,0 +1,25 @@
|
|||
import { MangoAccount } from '@blockworks-foundation/mango-v4'
|
||||
import { LinkButton } from '@components/shared/Button'
|
||||
import { ReactNode } from 'react'
|
||||
|
||||
const ActionsLinkButton = ({
|
||||
children,
|
||||
mangoAccount,
|
||||
onClick,
|
||||
}: {
|
||||
children: ReactNode
|
||||
mangoAccount: MangoAccount
|
||||
onClick: () => void
|
||||
}) => {
|
||||
return (
|
||||
<LinkButton
|
||||
className="w-full whitespace-nowrap text-left font-normal no-underline md:hover:text-th-fgd-1"
|
||||
disabled={!mangoAccount}
|
||||
onClick={onClick}
|
||||
>
|
||||
{children}
|
||||
</LinkButton>
|
||||
)
|
||||
}
|
||||
|
||||
export default ActionsLinkButton
|
|
@ -1,4 +1,3 @@
|
|||
import Checkbox from '@components/forms/Checkbox'
|
||||
import MangoDateRangePicker from '@components/forms/DateRangePicker'
|
||||
import Input from '@components/forms/Input'
|
||||
import Label from '@components/forms/Label'
|
||||
|
@ -26,14 +25,6 @@ import { PREFERRED_EXPLORER_KEY } from 'utils/constants'
|
|||
import { formatDecimal, formatFixedDecimals } from 'utils/numbers'
|
||||
import ActivityFeedTable from './ActivityFeedTable'
|
||||
|
||||
interface Filters {
|
||||
deposit: boolean
|
||||
liquidate_token_with_token: boolean
|
||||
perp_trade: boolean
|
||||
swap: boolean
|
||||
withdraw: boolean
|
||||
}
|
||||
|
||||
interface AdvancedFilters {
|
||||
symbol: string[]
|
||||
'start-date': string
|
||||
|
@ -42,14 +33,6 @@ interface AdvancedFilters {
|
|||
'usd-upper': string
|
||||
}
|
||||
|
||||
const DEFAULT_FILTERS = {
|
||||
deposit: true,
|
||||
liquidate_token_with_token: true,
|
||||
perp_trade: true,
|
||||
swap: true,
|
||||
withdraw: true,
|
||||
}
|
||||
|
||||
const DEFAULT_ADVANCED_FILTERS = {
|
||||
symbol: [],
|
||||
'start-date': '',
|
||||
|
@ -60,47 +43,33 @@ const DEFAULT_ADVANCED_FILTERS = {
|
|||
|
||||
const DEFAULT_PARAMS = [
|
||||
'deposit',
|
||||
'liquidate_token_with_token',
|
||||
'swap',
|
||||
'perp_trade',
|
||||
'liquidate_token_with_token',
|
||||
'openbook_trade',
|
||||
'swap',
|
||||
'withdraw',
|
||||
]
|
||||
|
||||
const ActivityFeed = () => {
|
||||
const activityFeed = mangoStore((s) => s.activityFeed.feed)
|
||||
const actions = mangoStore((s) => s.actions)
|
||||
const { mangoAccount } = useMangoAccount()
|
||||
const actions = mangoStore.getState().actions
|
||||
const { mangoAccountAddress } = useMangoAccount()
|
||||
const [showActivityDetail, setShowActivityDetail] = useState(null)
|
||||
const [filters, setFilters] = useState<Filters>(DEFAULT_FILTERS)
|
||||
const [advancedFilters, setAdvancedFilters] = useState<AdvancedFilters>(
|
||||
DEFAULT_ADVANCED_FILTERS
|
||||
)
|
||||
const [params, setParams] = useState<string[]>(DEFAULT_PARAMS)
|
||||
|
||||
useEffect(() => {
|
||||
if (mangoAccount) {
|
||||
const pubKey = mangoAccount.publicKey.toString()
|
||||
actions.fetchActivityFeed(pubKey)
|
||||
if (mangoAccountAddress) {
|
||||
actions.fetchActivityFeed(mangoAccountAddress)
|
||||
}
|
||||
}, [actions, mangoAccount])
|
||||
}, [actions, mangoAccountAddress])
|
||||
|
||||
const handleShowActivityDetails = (activity: any) => {
|
||||
setShowActivityDetail(activity)
|
||||
}
|
||||
|
||||
const updateFilters = (e: ChangeEvent<HTMLInputElement>, filter: string) => {
|
||||
setFilters({ ...filters, [filter]: e.target.checked })
|
||||
|
||||
let newParams: string[] = DEFAULT_PARAMS
|
||||
|
||||
if (params.includes(filter)) {
|
||||
newParams = params.filter((p) => p !== filter)
|
||||
} else {
|
||||
newParams = [...params, filter]
|
||||
}
|
||||
setParams(newParams)
|
||||
}
|
||||
|
||||
const advancedParamsString = useMemo(() => {
|
||||
let advancedParams = ''
|
||||
Object.entries(advancedFilters).map((entry) => {
|
||||
|
@ -112,7 +81,7 @@ const ActivityFeed = () => {
|
|||
}, [advancedFilters])
|
||||
|
||||
const queryParams = useMemo(() => {
|
||||
return params.length === 5
|
||||
return !params.length || params.length === 6
|
||||
? advancedParamsString
|
||||
: `&activity-type=${params.toString()}${advancedParamsString}`
|
||||
}, [advancedParamsString, params])
|
||||
|
@ -120,11 +89,9 @@ const ActivityFeed = () => {
|
|||
return !showActivityDetail ? (
|
||||
<>
|
||||
<ActivityFilters
|
||||
filters={filters}
|
||||
setFilters={setFilters}
|
||||
updateFilters={updateFilters}
|
||||
filters={params}
|
||||
setFilters={setParams}
|
||||
params={queryParams}
|
||||
setParams={setParams}
|
||||
advancedFilters={advancedFilters}
|
||||
setAdvancedFilters={setAdvancedFilters}
|
||||
/>
|
||||
|
@ -147,29 +114,24 @@ export default ActivityFeed
|
|||
const ActivityFilters = ({
|
||||
filters,
|
||||
setFilters,
|
||||
updateFilters,
|
||||
params,
|
||||
setParams,
|
||||
advancedFilters,
|
||||
setAdvancedFilters,
|
||||
}: {
|
||||
filters: Filters
|
||||
setFilters: (x: Filters) => void
|
||||
updateFilters: (e: ChangeEvent<HTMLInputElement>, filter: string) => void
|
||||
filters: string[]
|
||||
setFilters: (x: string[]) => void
|
||||
params: string
|
||||
setParams: (x: string[]) => void
|
||||
advancedFilters: AdvancedFilters
|
||||
setAdvancedFilters: (x: AdvancedFilters) => void
|
||||
}) => {
|
||||
const { t } = useTranslation(['common', 'activity'])
|
||||
const actions = mangoStore((s) => s.actions)
|
||||
const actions = mangoStore.getState().actions
|
||||
const loadActivityFeed = mangoStore((s) => s.activityFeed.loading)
|
||||
const { mangoAccount } = useMangoAccount()
|
||||
const { mangoAccountAddress } = useMangoAccount()
|
||||
const [showMobileFilters, setShowMobileFilters] = useState(false)
|
||||
const [hasFilters, setHasFilters] = useState(false)
|
||||
|
||||
const handleUpdateResults = useCallback(() => {
|
||||
const mangoAccount = mangoStore.getState().mangoAccount.current
|
||||
const set = mangoStore.getState().set
|
||||
if (params) {
|
||||
setHasFilters(true)
|
||||
|
@ -180,33 +142,31 @@ const ActivityFilters = ({
|
|||
s.activityFeed.feed = []
|
||||
s.activityFeed.loading = true
|
||||
})
|
||||
if (mangoAccount) {
|
||||
actions.fetchActivityFeed(mangoAccount.publicKey.toString(), 0, params)
|
||||
if (mangoAccountAddress) {
|
||||
actions.fetchActivityFeed(mangoAccountAddress, 0, params)
|
||||
}
|
||||
}, [actions, params])
|
||||
}, [actions, params, mangoAccountAddress])
|
||||
|
||||
const handleResetFilters = useCallback(async () => {
|
||||
const mangoAccount = mangoStore.getState().mangoAccount.current
|
||||
const set = mangoStore.getState().set
|
||||
setHasFilters(false)
|
||||
set((s) => {
|
||||
s.activityFeed.feed = []
|
||||
s.activityFeed.loading = true
|
||||
})
|
||||
if (mangoAccount) {
|
||||
await actions.fetchActivityFeed(mangoAccount.publicKey.toString())
|
||||
if (mangoAccountAddress) {
|
||||
await actions.fetchActivityFeed(mangoAccountAddress)
|
||||
setAdvancedFilters(DEFAULT_ADVANCED_FILTERS)
|
||||
setFilters(DEFAULT_FILTERS)
|
||||
setParams(DEFAULT_PARAMS)
|
||||
setFilters(DEFAULT_PARAMS)
|
||||
}
|
||||
}, [actions])
|
||||
}, [actions, mangoAccountAddress])
|
||||
|
||||
const handleUpdateMobileResults = () => {
|
||||
handleUpdateResults()
|
||||
setShowMobileFilters(false)
|
||||
}
|
||||
|
||||
return mangoAccount ? (
|
||||
return mangoAccountAddress ? (
|
||||
<Disclosure>
|
||||
<div className="relative">
|
||||
{hasFilters ? (
|
||||
|
@ -225,7 +185,7 @@ const ActivityFilters = ({
|
|||
<div
|
||||
onClick={() => setShowMobileFilters(!showMobileFilters)}
|
||||
role="button"
|
||||
className={`default-transition w-full bg-th-bkg-2 p-4 hover:bg-th-bkg-3 md:px-6`}
|
||||
className={`default-transition h-12 w-full bg-th-bkg-2 px-4 hover:bg-th-bkg-3 md:px-6`}
|
||||
>
|
||||
<Disclosure.Button
|
||||
className={`flex h-full w-full items-center justify-between rounded-none`}
|
||||
|
@ -245,16 +205,11 @@ const ActivityFilters = ({
|
|||
</div>
|
||||
</div>
|
||||
<Disclosure.Panel className="bg-th-bkg-2 px-6 pb-6">
|
||||
<div className="py-4">
|
||||
<Label text={t('activity:activity-type')} />
|
||||
<ActivityTypeFiltersForm
|
||||
filters={filters}
|
||||
updateFilters={updateFilters}
|
||||
/>
|
||||
</div>
|
||||
<AdvancedFiltersForm
|
||||
<FiltersForm
|
||||
advancedFilters={advancedFilters}
|
||||
setAdvancedFilters={setAdvancedFilters}
|
||||
filters={filters}
|
||||
setFilters={setFilters}
|
||||
/>
|
||||
<Button
|
||||
className="w-full md:w-auto"
|
||||
|
@ -268,69 +223,19 @@ const ActivityFilters = ({
|
|||
) : null
|
||||
}
|
||||
|
||||
const ActivityTypeFiltersForm = ({
|
||||
filters,
|
||||
updateFilters,
|
||||
}: {
|
||||
filters: Filters
|
||||
updateFilters: (e: ChangeEvent<HTMLInputElement>, filter: string) => void
|
||||
}) => {
|
||||
const { t } = useTranslation('activity')
|
||||
return (
|
||||
<div className="flex w-full flex-col space-y-3 md:flex-row md:space-y-0">
|
||||
<div className="flex h-8 flex-1 items-center lg:h-12 lg:border-l lg:border-th-bkg-4 lg:p-4">
|
||||
<Checkbox
|
||||
checked={filters.deposit}
|
||||
onChange={(e) => updateFilters(e, 'deposit')}
|
||||
>
|
||||
<span className="text-sm">{t('deposits')}</span>
|
||||
</Checkbox>
|
||||
</div>
|
||||
<div className="flex h-8 flex-1 items-center lg:h-12 lg:border-l lg:border-th-bkg-4 lg:p-4">
|
||||
<Checkbox
|
||||
checked={filters.withdraw}
|
||||
onChange={(e) => updateFilters(e, 'withdraw')}
|
||||
>
|
||||
<span className="text-sm">{t('withdrawals')}</span>
|
||||
</Checkbox>
|
||||
</div>
|
||||
<div className="flex h-8 flex-1 items-center lg:h-12 lg:border-l lg:border-th-bkg-4 lg:p-4">
|
||||
<Checkbox
|
||||
checked={filters.swap}
|
||||
onChange={(e) => updateFilters(e, 'swap')}
|
||||
>
|
||||
<span className="text-sm">{t('swaps')}</span>
|
||||
</Checkbox>
|
||||
</div>
|
||||
<div className="flex h-8 flex-1 items-center lg:h-12 lg:border-l lg:border-th-bkg-4 lg:p-4">
|
||||
<Checkbox
|
||||
checked={filters.perp_trade}
|
||||
onChange={(e) => updateFilters(e, 'perp_trade')}
|
||||
>
|
||||
<span className="text-sm">{t('perps')}</span>
|
||||
</Checkbox>
|
||||
</div>
|
||||
<div className="flex h-8 flex-1 items-center lg:h-12 lg:border-l lg:border-th-bkg-4 lg:p-4">
|
||||
<Checkbox
|
||||
checked={filters.liquidate_token_with_token}
|
||||
onChange={(e) => updateFilters(e, 'liquidate_token_with_token')}
|
||||
>
|
||||
<span className="text-sm">{t('liquidations')}</span>
|
||||
</Checkbox>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
interface AdvancedFiltersFormProps {
|
||||
interface FiltersFormProps {
|
||||
advancedFilters: any
|
||||
setAdvancedFilters: (x: any) => void
|
||||
filters: string[]
|
||||
setFilters: (x: string[]) => void
|
||||
}
|
||||
|
||||
const AdvancedFiltersForm = ({
|
||||
const FiltersForm = ({
|
||||
advancedFilters,
|
||||
setAdvancedFilters,
|
||||
}: AdvancedFiltersFormProps) => {
|
||||
filters,
|
||||
setFilters,
|
||||
}: FiltersFormProps) => {
|
||||
const { t } = useTranslation(['common', 'activity'])
|
||||
const { group } = useMangoGroup()
|
||||
const [dateFrom, setDateFrom] = useState<Date | null>(null)
|
||||
|
@ -352,13 +257,21 @@ const AdvancedFiltersForm = ({
|
|||
}
|
||||
}, [])
|
||||
|
||||
const toggleOption = (option: string) => {
|
||||
const toggleTypeOption = (option: string) => {
|
||||
if (filters.includes(option)) {
|
||||
setFilters(filters.filter((opt) => opt !== option))
|
||||
} else {
|
||||
setFilters(filters.concat(option))
|
||||
}
|
||||
}
|
||||
|
||||
const toggleSymbolOption = (option: string) => {
|
||||
setAdvancedFilters((prevSelected: any) => {
|
||||
const newSelections = prevSelected.symbol ? [...prevSelected.symbol] : []
|
||||
if (newSelections.includes(option)) {
|
||||
return {
|
||||
...prevSelected,
|
||||
symbol: newSelections.filter((item) => item != option),
|
||||
symbol: newSelections.filter((item) => item !== option),
|
||||
}
|
||||
} else {
|
||||
newSelections.push(option)
|
||||
|
@ -390,8 +303,8 @@ const AdvancedFiltersForm = ({
|
|||
if (valueFrom && valueTo) {
|
||||
setAdvancedFilters({
|
||||
...advancedFilters,
|
||||
'usd-lower': valueFrom,
|
||||
'usd-upper': valueTo,
|
||||
'usd-lower': Math.floor(valueFrom),
|
||||
'usd-upper': Math.ceil(valueTo),
|
||||
})
|
||||
} else {
|
||||
setAdvancedFilters({
|
||||
|
@ -404,13 +317,23 @@ const AdvancedFiltersForm = ({
|
|||
|
||||
return (
|
||||
<>
|
||||
<Label text={t('tokens')} />
|
||||
<div className="w-full lg:w-1/2 lg:pr-4">
|
||||
<MultiSelectDropdown
|
||||
options={symbols}
|
||||
selected={advancedFilters.symbol || []}
|
||||
toggleOption={toggleOption}
|
||||
/>
|
||||
<div className="grid grid-cols-2 gap-x-8 pt-4">
|
||||
<div className="col-span-2 lg:col-span-1">
|
||||
<Label text={t('activity:activity-type')} />
|
||||
<MultiSelectDropdown
|
||||
options={DEFAULT_PARAMS}
|
||||
selected={filters}
|
||||
toggleOption={toggleTypeOption}
|
||||
/>
|
||||
</div>
|
||||
<div className="col-span-2 lg:col-span-1">
|
||||
<Label text={t('tokens')} />
|
||||
<MultiSelectDropdown
|
||||
options={symbols}
|
||||
selected={advancedFilters.symbol || []}
|
||||
toggleOption={toggleSymbolOption}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="my-4 w-full">
|
||||
<MangoDateRangePicker
|
||||
|
@ -424,7 +347,7 @@ const AdvancedFiltersForm = ({
|
|||
<div className="w-full">
|
||||
<Label text={t('activity:value-from')} />
|
||||
<Input
|
||||
type="text"
|
||||
type="number"
|
||||
placeholder="0.00"
|
||||
value={valueFrom}
|
||||
onChange={(e: ChangeEvent<HTMLInputElement>) =>
|
||||
|
@ -438,7 +361,7 @@ const AdvancedFiltersForm = ({
|
|||
<div className="w-full">
|
||||
<Label text={t('activity:value-to')} />
|
||||
<Input
|
||||
type="text"
|
||||
type="number"
|
||||
placeholder="0.00"
|
||||
value={valueTo || ''}
|
||||
onChange={(e: ChangeEvent<HTMLInputElement>) =>
|
||||
|
@ -506,10 +429,8 @@ const ActivityDetails = ({
|
|||
<p className="mb-0.5 text-sm">{t('activity:asset-liquidated')}</p>
|
||||
<p className="font-mono text-th-fgd-1">
|
||||
{formatDecimal(asset_amount)}{' '}
|
||||
<span className="font-body tracking-wide">{asset_symbol}</span>
|
||||
<span className="ml-2 font-body tracking-wide text-th-fgd-3">
|
||||
at
|
||||
</span>{' '}
|
||||
<span className="font-body tracking-wider">{asset_symbol}</span>
|
||||
<span className="ml-2 font-body text-th-fgd-3">at</span>{' '}
|
||||
{formatFixedDecimals(asset_price, true)}
|
||||
</p>
|
||||
<p className="font-mono text-xs text-th-fgd-3">
|
||||
|
@ -520,10 +441,8 @@ const ActivityDetails = ({
|
|||
<p className="mb-0.5 text-sm">{t('activity:asset-returned')}</p>
|
||||
<p className="font-mono text-th-fgd-1">
|
||||
{formatDecimal(liab_amount)}{' '}
|
||||
<span className="font-body tracking-wide">{liab_symbol}</span>
|
||||
<span className="ml-2 font-body tracking-wide text-th-fgd-3">
|
||||
at
|
||||
</span>{' '}
|
||||
<span className="font-body tracking-wider">{liab_symbol}</span>
|
||||
<span className="ml-2 font-body text-th-fgd-3">at</span>{' '}
|
||||
{formatFixedDecimals(liab_price, true)}
|
||||
</p>
|
||||
<p className="font-mono text-xs text-th-fgd-3">
|
||||
|
|
|
@ -7,7 +7,6 @@ import { Transition } from '@headlessui/react'
|
|||
import {
|
||||
ChevronDownIcon,
|
||||
ChevronRightIcon,
|
||||
LinkIcon,
|
||||
NoSymbolIcon,
|
||||
} from '@heroicons/react/20/solid'
|
||||
import mangoStore, { LiquidationFeedItem } from '@store/mangoStore'
|
||||
|
@ -22,6 +21,13 @@ import { PREFERRED_EXPLORER_KEY } from 'utils/constants'
|
|||
import { formatDecimal, formatFixedDecimals } from 'utils/numbers'
|
||||
import { breakpoints } from 'utils/theme'
|
||||
|
||||
const formatFee = (value: number) => {
|
||||
return value.toLocaleString(undefined, {
|
||||
minimumSignificantDigits: 1,
|
||||
maximumSignificantDigits: 1,
|
||||
})
|
||||
}
|
||||
|
||||
const ActivityFeedTable = ({
|
||||
activityFeed,
|
||||
handleShowActivityDetails,
|
||||
|
@ -32,8 +38,8 @@ const ActivityFeedTable = ({
|
|||
params: string
|
||||
}) => {
|
||||
const { t } = useTranslation(['common', 'activity'])
|
||||
const { mangoAccount } = useMangoAccount()
|
||||
const actions = mangoStore((s) => s.actions)
|
||||
const { mangoAccountAddress } = useMangoAccount()
|
||||
const actions = mangoStore.getState().actions
|
||||
const loadActivityFeed = mangoStore((s) => s.activityFeed.loading)
|
||||
const [offset, setOffset] = useState(0)
|
||||
const [preferredExplorer] = useLocalStorageState(
|
||||
|
@ -44,19 +50,14 @@ const ActivityFeedTable = ({
|
|||
const showTableView = width ? width > breakpoints.md : false
|
||||
|
||||
const handleShowMore = useCallback(() => {
|
||||
const mangoAccount = mangoStore.getState().mangoAccount.current
|
||||
const set = mangoStore.getState().set
|
||||
set((s) => {
|
||||
s.activityFeed.loading = true
|
||||
})
|
||||
if (!mangoAccount) return
|
||||
if (!mangoAccountAddress) return
|
||||
setOffset(offset + 25)
|
||||
actions.fetchActivityFeed(
|
||||
mangoAccount.publicKey.toString(),
|
||||
offset + 25,
|
||||
params
|
||||
)
|
||||
}, [actions, offset, params])
|
||||
actions.fetchActivityFeed(mangoAccountAddress, offset + 25, params)
|
||||
}, [actions, offset, params, mangoAccountAddress])
|
||||
|
||||
const getCreditAndDebit = (activity: any) => {
|
||||
const { activity_type } = activity
|
||||
|
@ -107,6 +108,18 @@ const ActivityFeedTable = ({
|
|||
credit = { value: quantity, symbol: perp_market }
|
||||
debit = { value: formatDecimal(quantity * price * -1), symbol: 'USDC' }
|
||||
}
|
||||
if (activity_type === 'openbook_trade') {
|
||||
const { side, price, size, base_symbol, quote_symbol } =
|
||||
activity.activity_details
|
||||
credit =
|
||||
side === 'buy'
|
||||
? { value: formatDecimal(size), symbol: base_symbol }
|
||||
: { value: formatDecimal(size * price), symbol: quote_symbol }
|
||||
debit =
|
||||
side === 'buy'
|
||||
? { value: formatDecimal(size * price * -1), symbol: quote_symbol }
|
||||
: { value: formatDecimal(size * -1), symbol: base_symbol }
|
||||
}
|
||||
return { credit, debit }
|
||||
}
|
||||
|
||||
|
@ -136,166 +149,183 @@ const ActivityFeedTable = ({
|
|||
activity.activity_details
|
||||
value = quantity * price + maker_fee + taker_fee
|
||||
}
|
||||
if (activity_type === 'openbook_trade') {
|
||||
const { price, size } = activity.activity_details
|
||||
value = price * size
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
return mangoAccount ? (
|
||||
activityFeed.length || loadActivityFeed ? (
|
||||
<>
|
||||
{showTableView ? (
|
||||
<Table className="min-w-full">
|
||||
<thead className="sticky top-0 z-10">
|
||||
<TrHead>
|
||||
<Th className="bg-th-bkg-1 text-left">{t('date')}</Th>
|
||||
<Th className="bg-th-bkg-1 text-right">
|
||||
{t('activity:activity')}
|
||||
</Th>
|
||||
<Th className="bg-th-bkg-1 text-right">
|
||||
{t('activity:credit')}
|
||||
</Th>
|
||||
<Th className="bg-th-bkg-1 text-right">
|
||||
{t('activity:debit')}
|
||||
</Th>
|
||||
<Th className="bg-th-bkg-1 text-right">
|
||||
{t('activity:activity-value')}
|
||||
</Th>
|
||||
<Th className="bg-th-bkg-1 text-right">{t('explorer')}</Th>
|
||||
</TrHead>
|
||||
</thead>
|
||||
<tbody>
|
||||
{activityFeed.map((activity: any) => {
|
||||
const { activity_type, block_datetime } = activity
|
||||
const { signature } = activity.activity_details
|
||||
const isLiquidation =
|
||||
activity_type === 'liquidate_token_with_token'
|
||||
const activityName = isLiquidation
|
||||
? 'spot-liquidation'
|
||||
: activity_type
|
||||
const amounts = getCreditAndDebit(activity)
|
||||
const value = getValue(activity)
|
||||
return (
|
||||
<TrBody
|
||||
key={signature}
|
||||
className={`default-transition text-sm hover:bg-th-bkg-2 ${
|
||||
isLiquidation ? 'cursor-pointer' : ''
|
||||
const getFee = (activity: any) => {
|
||||
const { activity_type } = activity
|
||||
let fee = '0'
|
||||
if (activity_type === 'swap') {
|
||||
const { loan_origination_fee, swap_in_symbol } = activity.activity_details
|
||||
fee = loan_origination_fee
|
||||
? `${formatFee(loan_origination_fee)} ${swap_in_symbol}`
|
||||
: '0'
|
||||
}
|
||||
if (activity_type === 'perp_trade') {
|
||||
const { maker_fee, taker_fee } = activity.activity_details
|
||||
fee = `${formatFee(maker_fee + taker_fee)} USDC`
|
||||
}
|
||||
if (activity_type === 'openbook_trade') {
|
||||
const { fee_cost, quote_symbol } = activity.activity_details
|
||||
fee = `${formatFee(fee_cost)} ${quote_symbol}`
|
||||
}
|
||||
return fee
|
||||
}
|
||||
|
||||
return mangoAccountAddress && (activityFeed.length || loadActivityFeed) ? (
|
||||
<>
|
||||
{showTableView ? (
|
||||
<Table className="min-w-full">
|
||||
<thead className="sticky top-0 z-10">
|
||||
<TrHead>
|
||||
<Th className="bg-th-bkg-1 text-left">{t('date')}</Th>
|
||||
<Th className="bg-th-bkg-1 text-right">
|
||||
{t('activity:activity')}
|
||||
</Th>
|
||||
<Th className="bg-th-bkg-1 text-right">{t('activity:credit')}</Th>
|
||||
<Th className="bg-th-bkg-1 text-right">{t('activity:debit')}</Th>
|
||||
<Th className="bg-th-bkg-1 text-right">{t('fee')}</Th>
|
||||
<Th className="bg-th-bkg-1 text-right">
|
||||
{t('activity:activity-value')}
|
||||
</Th>
|
||||
<Th className="bg-th-bkg-1 text-right">{t('explorer')}</Th>
|
||||
</TrHead>
|
||||
</thead>
|
||||
<tbody>
|
||||
{activityFeed.map((activity: any) => {
|
||||
const { activity_type, block_datetime } = activity
|
||||
const { signature } = activity.activity_details
|
||||
const isLiquidation =
|
||||
activity_type === 'liquidate_token_with_token'
|
||||
const isOpenbook = activity_type === 'openbook_trade'
|
||||
const amounts = getCreditAndDebit(activity)
|
||||
const value = getValue(activity)
|
||||
const fee = getFee(activity)
|
||||
return (
|
||||
<TrBody
|
||||
key={signature}
|
||||
className={`default-transition text-sm hover:bg-th-bkg-2 ${
|
||||
isLiquidation ? 'cursor-pointer' : ''
|
||||
}`}
|
||||
onClick={
|
||||
isLiquidation
|
||||
? () => handleShowActivityDetails(activity)
|
||||
: undefined
|
||||
}
|
||||
>
|
||||
<Td>
|
||||
<p className="font-body tracking-wider">
|
||||
{dayjs(block_datetime).format('ddd D MMM')}
|
||||
</p>
|
||||
<p className="text-xs text-th-fgd-3">
|
||||
{dayjs(block_datetime).format('h:mma')}
|
||||
</p>
|
||||
</Td>
|
||||
<Td className="text-right">
|
||||
{t(`activity:${activity_type}`)}
|
||||
</Td>
|
||||
<Td className="text-right font-mono">
|
||||
{amounts.credit.value}{' '}
|
||||
<span className="font-body text-th-fgd-3">
|
||||
{amounts.credit.symbol}
|
||||
</span>
|
||||
</Td>
|
||||
<Td className="text-right font-mono">
|
||||
{amounts.debit.value}{' '}
|
||||
<span className="font-body text-th-fgd-3">
|
||||
{amounts.debit.symbol}
|
||||
</span>
|
||||
</Td>
|
||||
<Td className="text-right font-mono">{fee}</Td>
|
||||
<Td
|
||||
className={`text-right font-mono ${
|
||||
activity_type === 'swap' ||
|
||||
activity_type === 'perp_trade' ||
|
||||
isOpenbook
|
||||
? 'text-th-fgd-2'
|
||||
: value >= 0
|
||||
? 'text-th-up'
|
||||
: 'text-th-down'
|
||||
}`}
|
||||
onClick={
|
||||
isLiquidation
|
||||
? () => handleShowActivityDetails(activity)
|
||||
: undefined
|
||||
}
|
||||
>
|
||||
<Td>
|
||||
<p className="font-body tracking-wide">
|
||||
{dayjs(block_datetime).format('ddd D MMM')}
|
||||
</p>
|
||||
<p className="text-xs text-th-fgd-3">
|
||||
{dayjs(block_datetime).format('h:mma')}
|
||||
</p>
|
||||
</Td>
|
||||
<Td className="text-right">
|
||||
{t(`activity:${activityName}`)}
|
||||
</Td>
|
||||
<Td className="text-right font-mono">
|
||||
{amounts.credit.value}{' '}
|
||||
<span className="font-body tracking-wide text-th-fgd-3">
|
||||
{amounts.credit.symbol}
|
||||
</span>
|
||||
</Td>
|
||||
<Td className="text-right font-mono">
|
||||
{amounts.debit.value}{' '}
|
||||
<span className="font-body tracking-wide text-th-fgd-3">
|
||||
{amounts.debit.symbol}
|
||||
</span>
|
||||
</Td>
|
||||
<Td
|
||||
className={`text-right font-mono ${
|
||||
activityName === 'swap' || activityName === 'perp_trade'
|
||||
? 'text-th-fgd-2'
|
||||
: value >= 0
|
||||
? 'text-th-up'
|
||||
: 'text-th-down'
|
||||
}`}
|
||||
>
|
||||
{value > 0 &&
|
||||
activityName !== 'swap' &&
|
||||
activityName !== 'perp_trade'
|
||||
? '+'
|
||||
: ''}
|
||||
{formatFixedDecimals(value, true)}
|
||||
</Td>
|
||||
<Td>
|
||||
{activity_type !== 'liquidate_token_with_token' ? (
|
||||
<div className="flex items-center justify-end">
|
||||
<Tooltip
|
||||
content={`View on ${t(
|
||||
`settings:${preferredExplorer.name}`
|
||||
)}`}
|
||||
placement="top-end"
|
||||
{value > 0 &&
|
||||
activity_type !== 'swap' &&
|
||||
activity_type !== 'perp_trade' &&
|
||||
!isOpenbook
|
||||
? '+'
|
||||
: ''}
|
||||
{formatFixedDecimals(value, true)}
|
||||
</Td>
|
||||
<Td>
|
||||
{activity_type !== 'liquidate_token_with_token' ? (
|
||||
<div className="flex items-center justify-end">
|
||||
<Tooltip
|
||||
content={`View on ${t(
|
||||
`settings:${preferredExplorer.name}`
|
||||
)}`}
|
||||
placement="top-end"
|
||||
>
|
||||
<a
|
||||
href={`${preferredExplorer.url}${signature}`}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<a
|
||||
href={`${preferredExplorer.url}${signature}`}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<div className="h-6 w-6">
|
||||
<Image
|
||||
alt=""
|
||||
width="24"
|
||||
height="24"
|
||||
src={`/explorer-logos/${preferredExplorer.name}.png`}
|
||||
/>
|
||||
</div>
|
||||
</a>
|
||||
</Tooltip>
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex items-center justify-end">
|
||||
<ChevronRightIcon className="h-6 w-6 text-th-fgd-3" />
|
||||
</div>
|
||||
)}
|
||||
</Td>
|
||||
</TrBody>
|
||||
)
|
||||
})}
|
||||
</tbody>
|
||||
</Table>
|
||||
) : (
|
||||
<div>
|
||||
{activityFeed.map((activity: any) => (
|
||||
<MobileActivityFeedItem
|
||||
activity={activity}
|
||||
getValue={getValue}
|
||||
key={activity.activity_details.signature}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
{loadActivityFeed ? (
|
||||
<div className="mt-4 space-y-1.5">
|
||||
{[...Array(4)].map((x, i) => (
|
||||
<SheenLoader className="mx-4 flex flex-1 md:mx-6" key={i}>
|
||||
<div className="h-16 w-full bg-th-bkg-2" />
|
||||
</SheenLoader>
|
||||
))}
|
||||
</div>
|
||||
) : null}
|
||||
{activityFeed.length && activityFeed.length % 25 === 0 ? (
|
||||
<div className="flex justify-center pt-6">
|
||||
<LinkButton onClick={handleShowMore}>Show More</LinkButton>
|
||||
</div>
|
||||
) : null}
|
||||
</>
|
||||
) : (
|
||||
<div className="flex flex-col items-center p-8">
|
||||
<NoSymbolIcon className="mb-1 h-6 w-6 text-th-fgd-4" />
|
||||
<p>{t('activity:no-activity')}</p>
|
||||
</div>
|
||||
)
|
||||
<div className="h-6 w-6">
|
||||
<Image
|
||||
alt=""
|
||||
width="24"
|
||||
height="24"
|
||||
src={`/explorer-logos/${preferredExplorer.name}.png`}
|
||||
/>
|
||||
</div>
|
||||
</a>
|
||||
</Tooltip>
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex items-center justify-end">
|
||||
<ChevronRightIcon className="h-6 w-6 text-th-fgd-3" />
|
||||
</div>
|
||||
)}
|
||||
</Td>
|
||||
</TrBody>
|
||||
)
|
||||
})}
|
||||
</tbody>
|
||||
</Table>
|
||||
) : (
|
||||
<div>
|
||||
{activityFeed.map((activity: any) => (
|
||||
<MobileActivityFeedItem
|
||||
activity={activity}
|
||||
getValue={getValue}
|
||||
key={activity.activity_details.signature}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
{loadActivityFeed ? (
|
||||
<div className="mt-4 space-y-1.5">
|
||||
{[...Array(4)].map((x, i) => (
|
||||
<SheenLoader className="mx-4 flex flex-1 md:mx-6" key={i}>
|
||||
<div className="h-16 w-full bg-th-bkg-2" />
|
||||
</SheenLoader>
|
||||
))}
|
||||
</div>
|
||||
) : null}
|
||||
{activityFeed.length && activityFeed.length % 25 === 0 ? (
|
||||
<div className="flex justify-center pt-6">
|
||||
<LinkButton onClick={handleShowMore}>Show More</LinkButton>
|
||||
</div>
|
||||
) : null}
|
||||
</>
|
||||
) : (
|
||||
<div className="flex flex-col items-center p-8">
|
||||
<LinkIcon className="mb-2 h-6 w-6 text-th-fgd-4" />
|
||||
<p>{t('activity:connect-activity')}</p>
|
||||
<NoSymbolIcon className="mb-2 h-6 w-6 text-th-fgd-4" />
|
||||
<p>{t('activity:no-activity')}</p>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
@ -320,7 +350,6 @@ const MobileActivityFeedItem = ({
|
|||
const isLiquidation = activity_type === 'liquidate_token_with_token'
|
||||
const isSwap = activity_type === 'swap'
|
||||
const isPerp = activity_type === 'perp_trade'
|
||||
const activityName = isLiquidation ? 'spot-liquidation' : activity_type
|
||||
const value = getValue(activity)
|
||||
return (
|
||||
<div key={signature} className="border-b border-th-bkg-3 p-4">
|
||||
|
@ -336,7 +365,7 @@ const MobileActivityFeedItem = ({
|
|||
<div className="flex items-center space-x-4">
|
||||
<div>
|
||||
<p className="text-right text-xs">
|
||||
{t(`activity:${activityName}`)}
|
||||
{t(`activity:${activity_type}`)}
|
||||
</p>
|
||||
<p className="text-right font-mono text-sm text-th-fgd-1">
|
||||
{isLiquidation ? (
|
||||
|
@ -349,7 +378,7 @@ const MobileActivityFeedItem = ({
|
|||
{ maximumFractionDigits: 6 }
|
||||
)}
|
||||
</span>
|
||||
<span className="font-body tracking-wide text-th-fgd-3">
|
||||
<span className="font-body text-th-fgd-3">
|
||||
{activity.activity_details.swap_in_symbol}
|
||||
</span>
|
||||
<span className="mx-1 font-body text-th-fgd-3">for</span>
|
||||
|
@ -359,7 +388,7 @@ const MobileActivityFeedItem = ({
|
|||
{ maximumFractionDigits: 6 }
|
||||
)}
|
||||
</span>
|
||||
<span className="font-body tracking-wide text-th-fgd-3">
|
||||
<span className="font-body text-th-fgd-3">
|
||||
{activity.activity_details.swap_out_symbol}
|
||||
</span>
|
||||
</>
|
||||
|
@ -388,7 +417,7 @@ const MobileActivityFeedItem = ({
|
|||
<span className="mr-1">
|
||||
{activity.activity_details.quantity}
|
||||
</span>
|
||||
<span className="font-body tracking-wide text-th-fgd-3">
|
||||
<span className="font-body text-th-fgd-3">
|
||||
{activity.activity_details.symbol}
|
||||
</span>
|
||||
</>
|
||||
|
@ -439,12 +468,10 @@ const MobileActivityFeedItem = ({
|
|||
<p className="mb-0.5 text-sm">{t('activity:asset-liquidated')}</p>
|
||||
<p className="font-mono text-sm text-th-fgd-1">
|
||||
{formatDecimal(activity.activity_details.asset_amount)}{' '}
|
||||
<span className="font-body tracking-wide">
|
||||
<span className="font-body tracking-wider">
|
||||
{activity.activity_details.asset_symbol}
|
||||
</span>
|
||||
<span className="ml-2 font-body tracking-wide text-th-fgd-3">
|
||||
at
|
||||
</span>{' '}
|
||||
<span className="ml-2 font-body text-th-fgd-3">at</span>{' '}
|
||||
{formatFixedDecimals(activity.activity_details.asset_price, true)}
|
||||
</p>
|
||||
<p className="font-mono text-xs text-th-fgd-3">
|
||||
|
@ -459,12 +486,10 @@ const MobileActivityFeedItem = ({
|
|||
<p className="mb-0.5 text-sm">{t('activity:asset-returned')}</p>
|
||||
<p className="font-mono text-sm text-th-fgd-1">
|
||||
{formatDecimal(activity.activity_details.liab_amount)}{' '}
|
||||
<span className="font-body tracking-wide">
|
||||
<span className="font-body tracking-wider">
|
||||
{activity.activity_details.liab_symbol}
|
||||
</span>
|
||||
<span className="ml-2 font-body tracking-wide text-th-fgd-3">
|
||||
at
|
||||
</span>{' '}
|
||||
<span className="ml-2 font-body text-th-fgd-3">at</span>{' '}
|
||||
{formatFixedDecimals(activity.activity_details.liab_price, true)}
|
||||
</p>
|
||||
<p className="font-mono text-xs text-th-fgd-3">
|
||||
|
|
|
@ -59,7 +59,7 @@ const CreateAccountForm = ({
|
|||
8 // perpOoCount
|
||||
)
|
||||
if (tx) {
|
||||
const pk = wallet!.adapter.publicKey
|
||||
const pk = wallet.adapter.publicKey
|
||||
const mangoAccounts = await client.getMangoAccountsForOwner(group, pk!)
|
||||
const reloadedMangoAccounts = await Promise.all(
|
||||
mangoAccounts.map((ma) => ma.reloadAccountData(client))
|
||||
|
|
|
@ -3,7 +3,7 @@ import {
|
|||
HealthType,
|
||||
toUiDecimalsForQuote,
|
||||
} from '@blockworks-foundation/mango-v4'
|
||||
import { formatDecimal, formatFixedDecimals } from '../../utils/numbers'
|
||||
import { formatFixedDecimals } from '../../utils/numbers'
|
||||
import { useTranslation } from 'next-i18next'
|
||||
import useMangoAccount from 'hooks/useMangoAccount'
|
||||
|
||||
|
@ -40,15 +40,15 @@ const MangoAccountSummary = () => {
|
|||
<div>
|
||||
<p className="text-sm text-th-fgd-3">{t('account-value')}</p>
|
||||
<p className="font-mono text-sm text-th-fgd-1">
|
||||
$
|
||||
{group && mangoAccount
|
||||
? formatDecimal(
|
||||
? formatFixedDecimals(
|
||||
toUiDecimalsForQuote(
|
||||
mangoAccount.getEquity(group).toNumber()
|
||||
),
|
||||
2
|
||||
false,
|
||||
true
|
||||
)
|
||||
: (0).toFixed(2)}
|
||||
: '$0.00'}
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
|
@ -59,9 +59,10 @@ const MangoAccountSummary = () => {
|
|||
toUiDecimalsForQuote(
|
||||
mangoAccount.getCollateralValue(group).toNumber()
|
||||
),
|
||||
false,
|
||||
true
|
||||
)
|
||||
: `$${(0).toFixed(2)}`}
|
||||
: '$0.00'}
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
|
@ -74,9 +75,10 @@ const MangoAccountSummary = () => {
|
|||
.getAssetsValue(group, HealthType.init)
|
||||
.toNumber()
|
||||
),
|
||||
false,
|
||||
true
|
||||
)
|
||||
: `$${(0).toFixed(2)}`}
|
||||
: '$0.00'}
|
||||
</p>
|
||||
</div>
|
||||
{/* <div>
|
||||
|
|
|
@ -39,15 +39,15 @@ const Checkbox = ({
|
|||
/>
|
||||
<div
|
||||
className={`${
|
||||
checked && !disabled && !halfState ? 'bg-th-fgd-4' : 'bg-th-bkg-3'
|
||||
checked && !disabled && !halfState ? 'bg-th-active' : 'bg-th-bkg-4'
|
||||
} default-transition flex h-4 w-4 flex-shrink-0 cursor-pointer items-center justify-center rounded`}
|
||||
>
|
||||
{halfState ? (
|
||||
<div className="mb-0.5 font-bold">–</div>
|
||||
<div className="mb-0.5 font-bold text-th-bkg-1">–</div>
|
||||
) : (
|
||||
<CheckIcon
|
||||
className={`${checked ? 'block' : 'hidden'} h-4 w-4 ${
|
||||
disabled ? 'text-th-bkg-4' : 'text-th-bkg-1'
|
||||
disabled ? 'text-th-fgd-4' : 'text-th-bkg-1'
|
||||
}`}
|
||||
/>
|
||||
)}
|
||||
|
|
|
@ -8,12 +8,14 @@ const MultiSelectDropdown = ({
|
|||
options,
|
||||
selected,
|
||||
toggleOption,
|
||||
placeholder,
|
||||
}: {
|
||||
options: string[]
|
||||
selected: string[]
|
||||
toggleOption: (x: string) => void
|
||||
placeholder?: string
|
||||
}) => {
|
||||
const { t } = useTranslation('common')
|
||||
const { t } = useTranslation(['activity', 'common'])
|
||||
return (
|
||||
<Popover className="relative w-full min-w-[120px]">
|
||||
{({ open }) => (
|
||||
|
@ -25,10 +27,15 @@ const MultiSelectDropdown = ({
|
|||
>
|
||||
<div className={`flex items-center justify-between`}>
|
||||
{selected.length ? (
|
||||
<span>{selected.toString().replace(/,/g, ', ')}</span>
|
||||
<span>
|
||||
{selected
|
||||
.map((v) => t(v))
|
||||
.toString()
|
||||
.replace(/,/g, ', ')}
|
||||
</span>
|
||||
) : (
|
||||
<span className="text-th-fgd-4">
|
||||
{t('activity:select-tokens')}
|
||||
{placeholder || t('common:select')}
|
||||
</span>
|
||||
)}
|
||||
<ChevronDownIcon
|
||||
|
@ -61,7 +68,7 @@ const MultiSelectDropdown = ({
|
|||
key={option}
|
||||
onChange={() => toggleOption(option)}
|
||||
>
|
||||
{option}
|
||||
{t(option)}
|
||||
</Checkbox>
|
||||
)
|
||||
})}
|
||||
|
|
|
@ -19,7 +19,6 @@ import { useTranslation } from 'next-i18next'
|
|||
import { retryFn } from '../../utils'
|
||||
import Loading from '../shared/Loading'
|
||||
import Modal from '@components/shared/Modal'
|
||||
import { formatFixedDecimals } from 'utils/numbers'
|
||||
import CreateAccountForm from '@components/account/CreateAccountForm'
|
||||
import { EnterRightExitLeft } from '@components/shared/Transitions'
|
||||
import { useRouter } from 'next/router'
|
||||
|
@ -47,6 +46,7 @@ const MangoAccountsListModal = ({
|
|||
const [, setLastAccountViewed] = useLocalStorageStringState(LAST_ACCOUNT_KEY)
|
||||
const router = useRouter()
|
||||
const { asPath } = useRouter()
|
||||
const [submitting, setSubmitting] = useState('')
|
||||
|
||||
const handleSelectMangoAccount = async (acc: MangoAccount) => {
|
||||
const set = mangoStore.getState().set
|
||||
|
@ -56,13 +56,14 @@ const MangoAccountsListModal = ({
|
|||
s.activityFeed.feed = []
|
||||
s.activityFeed.loading = true
|
||||
})
|
||||
setSubmitting(acc.publicKey.toString())
|
||||
try {
|
||||
const reloadedMangoAccount = await retryFn(() => acc.reload(client))
|
||||
actions.fetchOpenOrders(reloadedMangoAccount)
|
||||
|
||||
set((s) => {
|
||||
s.mangoAccount.current = reloadedMangoAccount
|
||||
})
|
||||
actions.fetchTradeHistory()
|
||||
setLastAccountViewed(acc.publicKey.toString())
|
||||
} catch (e) {
|
||||
console.warn('Error selecting account', e)
|
||||
|
@ -73,6 +74,7 @@ const MangoAccountsListModal = ({
|
|||
})
|
||||
} finally {
|
||||
onClose()
|
||||
setSubmitting('')
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -94,10 +96,9 @@ const MangoAccountsListModal = ({
|
|||
) : mangoAccounts.length ? (
|
||||
<div className="thin-scroll mt-4 max-h-[280px] space-y-2 overflow-y-auto">
|
||||
{mangoAccounts.map((acc) => {
|
||||
const accountValue = formatFixedDecimals(
|
||||
toUiDecimalsForQuote(Number(acc.getEquity(group!))),
|
||||
true
|
||||
)
|
||||
const accountValue = toUiDecimalsForQuote(
|
||||
Number(acc.getEquity(group!))
|
||||
).toFixed(2)
|
||||
const maintHealth = acc.getHealthRatioUi(
|
||||
group!,
|
||||
HealthType.maint
|
||||
|
@ -113,8 +114,10 @@ const MangoAccountsListModal = ({
|
|||
>
|
||||
<div className="flex w-full items-center justify-between">
|
||||
<div className="flex items-center space-x-2.5">
|
||||
{acc.publicKey.toString() ===
|
||||
mangoAccount?.publicKey.toString() ? (
|
||||
{submitting === acc.publicKey.toString() ? (
|
||||
<Loading className="h-4 w-4" />
|
||||
) : acc.publicKey.toString() ===
|
||||
mangoAccount?.publicKey.toString() ? (
|
||||
<div className="flex h-5 w-5 items-center justify-center rounded-full bg-th-success">
|
||||
<CheckIcon className="h-4 w-4 text-th-bkg-1" />
|
||||
</div>
|
||||
|
@ -150,7 +153,7 @@ const MangoAccountsListModal = ({
|
|||
</div>
|
||||
<div className="flex">
|
||||
<span className="text-sm text-th-fgd-2">
|
||||
{accountValue}
|
||||
${accountValue}
|
||||
</span>
|
||||
<span className="mx-2 text-th-fgd-4">|</span>
|
||||
<div
|
||||
|
|
|
@ -26,7 +26,11 @@ import {
|
|||
} from 'react'
|
||||
import { ALPHA_DEPOSIT_LIMIT } from 'utils/constants'
|
||||
import { notify } from 'utils/notifications'
|
||||
import { floorToDecimal, formatFixedDecimals } from 'utils/numbers'
|
||||
import {
|
||||
floorToDecimal,
|
||||
formatDecimal,
|
||||
formatFixedDecimals,
|
||||
} from 'utils/numbers'
|
||||
import ActionTokenList from '../account/ActionTokenList'
|
||||
import ButtonGroup from '../forms/ButtonGroup'
|
||||
import Input from '../forms/Input'
|
||||
|
@ -34,8 +38,8 @@ import Label from '../forms/Label'
|
|||
import WalletIcon from '../icons/WalletIcon'
|
||||
import { walletBalanceForToken } from '../DepositForm'
|
||||
import ParticlesBackground from '../ParticlesBackground'
|
||||
import EditNftProfilePic from '../profile/EditNftProfilePic'
|
||||
import EditProfileForm from '../profile/EditProfileForm'
|
||||
// import EditNftProfilePic from '../profile/EditNftProfilePic'
|
||||
// import EditProfileForm from '../profile/EditProfileForm'
|
||||
import Button, { LinkButton } from '../shared/Button'
|
||||
import InlineNotification from '../shared/InlineNotification'
|
||||
import Loading from '../shared/Loading'
|
||||
|
@ -43,6 +47,8 @@ import MaxAmountButton from '../shared/MaxAmountButton'
|
|||
import SolBalanceWarnings from '../shared/SolBalanceWarnings'
|
||||
import { useEnhancedWallet } from '../wallet/EnhancedWalletProvider'
|
||||
import Modal from '../shared/Modal'
|
||||
import NumberFormat, { NumberFormatValues } from 'react-number-format'
|
||||
import { withValueLimit } from '@components/swap/SwapForm'
|
||||
|
||||
const UserSetupModal = ({
|
||||
isOpen,
|
||||
|
@ -63,7 +69,7 @@ const UserSetupModal = ({
|
|||
const [depositAmount, setDepositAmount] = useState('')
|
||||
const [submitDeposit, setSubmitDeposit] = useState(false)
|
||||
const [sizePercentage, setSizePercentage] = useState('')
|
||||
const [showEditProfilePic, setShowEditProfilePic] = useState(false)
|
||||
// const [showEditProfilePic, setShowEditProfilePic] = useState(false)
|
||||
const walletTokens = mangoStore((s) => s.wallet.tokens)
|
||||
const { handleConnect } = useEnhancedWallet()
|
||||
const { maxSolDeposit } = useSolBalance()
|
||||
|
@ -135,8 +141,9 @@ const UserSetupModal = ({
|
|||
})
|
||||
|
||||
await actions.reloadMangoAccount()
|
||||
setShowSetupStep(4)
|
||||
setSubmitDeposit(false)
|
||||
onClose()
|
||||
// setShowSetupStep(4)
|
||||
} catch (e: any) {
|
||||
notify({
|
||||
title: 'Transaction failed',
|
||||
|
@ -203,7 +210,9 @@ const UserSetupModal = ({
|
|||
return { amount: 0, decimals: 0 }
|
||||
}, [banks, depositToken])
|
||||
|
||||
const showInsufficientBalance = tokenMax.amount < Number(depositAmount)
|
||||
const showInsufficientBalance =
|
||||
tokenMax.amount < Number(depositAmount) ||
|
||||
(depositToken === 'SOL' && maxSolDeposit <= 0)
|
||||
|
||||
const handleSizePercentage = useCallback(
|
||||
(percentage: string) => {
|
||||
|
@ -223,11 +232,14 @@ const UserSetupModal = ({
|
|||
}
|
||||
|
||||
return (
|
||||
<Modal isOpen={isOpen} onClose={onClose} fullScreen>
|
||||
<Modal isOpen={isOpen} onClose={onClose} fullScreen disableOutsideClose>
|
||||
<div className="radial-gradient-bg grid h-screen overflow-auto text-left lg:grid-cols-2">
|
||||
<img
|
||||
className="absolute -bottom-6 right-0 hidden h-auto w-[53%] lg:block xl:w-[57%]"
|
||||
src="/images/trade.png"
|
||||
src="/images/trade@0.75x.png"
|
||||
srcSet="/images/trade@0.75x.png 1157w, /images/trade@1x.png 1542w,
|
||||
/images/trade@2x.png 3084w"
|
||||
sizes="(max-width: 1600px) 1157px, (max-width: 2500px) 1542px, 3084px"
|
||||
alt="next"
|
||||
/>
|
||||
<img
|
||||
|
@ -238,7 +250,7 @@ const UserSetupModal = ({
|
|||
<div className="absolute top-0 left-0 z-10 flex h-1.5 w-full flex-grow bg-th-bkg-3">
|
||||
<div
|
||||
style={{
|
||||
width: `${(showSetupStep / 4) * 100}%`,
|
||||
width: `${(showSetupStep / 3) * 100}%`,
|
||||
}}
|
||||
className="flex bg-th-active transition-all duration-700 ease-out"
|
||||
/>
|
||||
|
@ -254,10 +266,6 @@ const UserSetupModal = ({
|
|||
<CheckCircleIcon className="h-5 w-5 text-th-success" />
|
||||
<p className="text-base">{t('onboarding:bullet-1')}</p>
|
||||
</div>
|
||||
{/* <div className="flex items-center space-x-2">
|
||||
<CheckCircleIcon className="h-5 w-5 text-th-success" />
|
||||
<p className="text-base">Deeply liquid markets</p>
|
||||
</div> */}
|
||||
<div className="flex items-center space-x-2">
|
||||
<CheckCircleIcon className="h-5 w-5 text-th-success" />
|
||||
<p className="text-base">{t('onboarding:bullet-2')}</p>
|
||||
|
@ -316,7 +324,7 @@ const UserSetupModal = ({
|
|||
))}
|
||||
</div>
|
||||
<Button
|
||||
className="mt-10 flex w-44 items-center justify-center"
|
||||
className="mt-10 flex items-center justify-center"
|
||||
onClick={handleConnect}
|
||||
size="large"
|
||||
>
|
||||
|
@ -347,7 +355,7 @@ const UserSetupModal = ({
|
|||
{t('onboarding:create-account-desc')}
|
||||
</p>
|
||||
</div>
|
||||
<div className="pb-4">
|
||||
<div className="mb-4">
|
||||
<Label text={t('account-name')} optional />
|
||||
<Input
|
||||
type="text"
|
||||
|
@ -361,14 +369,15 @@ const UserSetupModal = ({
|
|||
charLimit={30}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<SolBalanceWarnings className="mt-4" />
|
||||
<div className="mt-2">
|
||||
<InlineNotification
|
||||
type="info"
|
||||
desc={t('insufficient-sol')}
|
||||
/>
|
||||
<div className="mt-10">
|
||||
<Button
|
||||
className="mb-6 flex w-44 items-center justify-center"
|
||||
className="mb-6 flex items-center justify-center"
|
||||
disabled={maxSolDeposit <= 0}
|
||||
onClick={handleCreateAccount}
|
||||
size="large"
|
||||
|
@ -381,7 +390,6 @@ const UserSetupModal = ({
|
|||
</div>
|
||||
)}
|
||||
</Button>
|
||||
<SolBalanceWarnings />
|
||||
<LinkButton onClick={onClose}>
|
||||
<span className="default-transition text-th-fgd-4 underline md:hover:text-th-fgd-3 md:hover:no-underline">
|
||||
{t('onboarding:skip')}
|
||||
|
@ -406,6 +414,7 @@ const UserSetupModal = ({
|
|||
/>
|
||||
<SolBalanceWarnings
|
||||
amount={depositAmount}
|
||||
className="mt-4"
|
||||
setAmount={setDepositAmount}
|
||||
selectedToken={depositToken}
|
||||
/>
|
||||
|
@ -414,16 +423,16 @@ const UserSetupModal = ({
|
|||
<Label text={t('amount')} />
|
||||
<MaxAmountButton
|
||||
className="mb-2"
|
||||
disabled={depositToken === 'SOL' && maxSolDeposit <= 0}
|
||||
label="Wallet Max"
|
||||
onClick={() =>
|
||||
label="Max"
|
||||
onClick={() => {
|
||||
setDepositAmount(
|
||||
floorToDecimal(
|
||||
tokenMax.amount,
|
||||
tokenMax.decimals
|
||||
).toFixed()
|
||||
)
|
||||
}
|
||||
setSizePercentage('100')
|
||||
}}
|
||||
value={floorToDecimal(
|
||||
tokenMax.amount,
|
||||
tokenMax.decimals
|
||||
|
@ -432,7 +441,7 @@ const UserSetupModal = ({
|
|||
</div>
|
||||
<div className="mb-6 grid grid-cols-2">
|
||||
<button
|
||||
className="col-span-1 flex items-center rounded-lg rounded-r-none border border-r-0 border-th-bkg-4 bg-transparent px-4 hover:bg-transparent"
|
||||
className="col-span-1 flex items-center rounded-lg rounded-r-none border border-r-0 border-th-input-border bg-th-input-bkg px-4"
|
||||
onClick={() => setDepositToken('')}
|
||||
>
|
||||
<div className="flex w-full items-center justify-between">
|
||||
|
@ -450,56 +459,67 @@ const UserSetupModal = ({
|
|||
<PencilIcon className="ml-2 h-5 w-5 text-th-fgd-3" />
|
||||
</div>
|
||||
</button>
|
||||
<Input
|
||||
className="col-span-1 w-full rounded-lg rounded-l-none border border-th-bkg-4 bg-transparent p-3 text-right text-xl font-bold tracking-wider text-th-fgd-1 focus:outline-none"
|
||||
type="text"
|
||||
name="deposit"
|
||||
id="deposit"
|
||||
<NumberFormat
|
||||
name="amountIn"
|
||||
id="amountIn"
|
||||
inputMode="decimal"
|
||||
thousandSeparator=","
|
||||
allowNegative={false}
|
||||
isNumericString={true}
|
||||
decimalScale={tokenMax.decimals || 6}
|
||||
className="w-full rounded-lg rounded-l-none border border-th-input-border bg-th-input-bkg p-3 text-right font-mono text-xl text-th-fgd-1 focus:border-th-input-border-hover focus:outline-none md:hover:border-th-input-border-hover"
|
||||
placeholder="0.00"
|
||||
value={depositAmount}
|
||||
onChange={(e: ChangeEvent<HTMLInputElement>) =>
|
||||
setDepositAmount(e.target.value)
|
||||
}
|
||||
onValueChange={(e: NumberFormatValues) => {
|
||||
setDepositAmount(
|
||||
!Number.isNaN(Number(e.value)) ? e.value : ''
|
||||
)
|
||||
}}
|
||||
isAllowed={withValueLimit}
|
||||
/>
|
||||
<div className="col-span-2 mt-2">
|
||||
<ButtonGroup
|
||||
activeValue={sizePercentage}
|
||||
disabled={depositToken === 'SOL' && maxSolDeposit <= 0}
|
||||
onChange={(p) => handleSizePercentage(p)}
|
||||
values={['10', '25', '50', '75', '100']}
|
||||
unit="%"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="mb-10 border-y border-th-bkg-3">
|
||||
<div className="flex justify-between px-2 py-4">
|
||||
<p>{t('deposit-amount')}</p>
|
||||
<p className="font-mono text-th-fgd-2">
|
||||
{depositBank?.uiPrice && depositAmount ? (
|
||||
<>
|
||||
{depositAmount}{' '}
|
||||
<span className="text-xs text-th-fgd-3">
|
||||
(
|
||||
{formatFixedDecimals(
|
||||
depositBank.uiPrice * Number(depositAmount),
|
||||
true
|
||||
)}
|
||||
)
|
||||
</span>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
0{' '}
|
||||
<span className="text-xs text-th-fgd-3">
|
||||
($0.00)
|
||||
</span>
|
||||
</>
|
||||
)}
|
||||
</p>
|
||||
{depositBank ? (
|
||||
<div className="border-y border-th-bkg-3">
|
||||
<div className="flex justify-between px-2 py-4">
|
||||
<p>{t('deposit-amount')}</p>
|
||||
<p className="font-mono text-th-fgd-2">
|
||||
{depositAmount ? (
|
||||
<>
|
||||
{formatDecimal(
|
||||
Number(depositAmount),
|
||||
depositBank.mintDecimals
|
||||
)}{' '}
|
||||
<span className="text-xs text-th-fgd-3">
|
||||
(
|
||||
{formatFixedDecimals(
|
||||
depositBank.uiPrice * Number(depositAmount),
|
||||
true
|
||||
)}
|
||||
)
|
||||
</span>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
0{' '}
|
||||
<span className="text-xs text-th-fgd-3">
|
||||
($0.00)
|
||||
</span>
|
||||
</>
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
<Button
|
||||
className="mb-6 flex w-44 items-center justify-center"
|
||||
className="mb-6 mt-10 flex items-center justify-center"
|
||||
disabled={
|
||||
!depositAmount ||
|
||||
!depositToken ||
|
||||
|
@ -525,7 +545,7 @@ const UserSetupModal = ({
|
|||
</div>
|
||||
)}
|
||||
</Button>
|
||||
<LinkButton onClick={() => setShowSetupStep(4)}>
|
||||
<LinkButton onClick={onClose}>
|
||||
<span className="default-transition text-th-fgd-4 underline md:hover:text-th-fgd-3 md:hover:no-underline">
|
||||
{t('onboarding:skip')}
|
||||
</span>
|
||||
|
@ -536,14 +556,14 @@ const UserSetupModal = ({
|
|||
className="thin-scroll absolute top-36 w-full overflow-auto"
|
||||
style={{ height: 'calc(100vh - 380px)' }}
|
||||
>
|
||||
<div className="grid auto-cols-fr grid-flow-col px-4 pb-2">
|
||||
<div className="">
|
||||
<div className="flex items-center px-4 pb-2">
|
||||
<div className="w-1/4">
|
||||
<p className="text-xs">{t('token')}</p>
|
||||
</div>
|
||||
<div className="text-right">
|
||||
<div className="w-1/4 text-right">
|
||||
<p className="text-xs">{t('deposit-rate')}</p>
|
||||
</div>
|
||||
<div className="text-right">
|
||||
<div className="w-1/2 text-right">
|
||||
<p className="whitespace-nowrap text-xs">
|
||||
{t('wallet-balance')}
|
||||
</p>
|
||||
|
@ -561,7 +581,7 @@ const UserSetupModal = ({
|
|||
</div>
|
||||
) : null}
|
||||
</UserSetupTransition>
|
||||
<UserSetupTransition delay show={showSetupStep === 4}>
|
||||
{/* <UserSetupTransition delay show={showSetupStep === 4}>
|
||||
{showSetupStep === 4 ? (
|
||||
<div className="relative">
|
||||
<h2 className="mb-4 font-display text-3xl tracking-normal md:text-5xl lg:text-6xl">
|
||||
|
@ -594,7 +614,7 @@ const UserSetupModal = ({
|
|||
</UserSetupTransition>
|
||||
</div>
|
||||
) : null}
|
||||
</UserSetupTransition>
|
||||
</UserSetupTransition> */}
|
||||
</div>
|
||||
<div className="col-span-1 hidden h-screen lg:block">
|
||||
<ParticlesBackground />
|
||||
|
|
|
@ -26,7 +26,7 @@ const EditNftProfilePic = ({ onClose }: { onClose: () => void }) => {
|
|||
const nftsLoading = mangoStore((s) => s.wallet.nfts.loading)
|
||||
const connection = mangoStore((s) => s.connection)
|
||||
const [selectedProfile, setSelectedProfile] = useState<string>('')
|
||||
const actions = mangoStore((s) => s.actions)
|
||||
const actions = mangoStore.getState().actions
|
||||
const profile = mangoStore((s) => s.profile.details)
|
||||
|
||||
useEffect(() => {
|
||||
|
|
|
@ -36,7 +36,7 @@ const EditProfileForm = ({
|
|||
const [loadUniquenessCheck, setLoadUniquenessCheck] = useState(false)
|
||||
const [loadUpdateProfile, setLoadUpdateProfile] = useState(false)
|
||||
const [updateError, setUpdateError] = useState('')
|
||||
const actions = mangoStore((s) => s.actions)
|
||||
const actions = mangoStore.getState().actions
|
||||
|
||||
const validateProfileNameUniqueness = async (name: string) => {
|
||||
try {
|
||||
|
|
|
@ -109,13 +109,20 @@ const DisplaySettings = () => {
|
|||
</div> */}
|
||||
<div className="flex flex-col border-t border-th-bkg-3 py-4 md:flex-row md:items-center md:justify-between md:px-4">
|
||||
<p className="mb-2 md:mb-0">{t('settings:notification-position')}</p>
|
||||
<div className="w-full min-w-[330px] md:w-[480px] md:pl-4">
|
||||
<ButtonGroup
|
||||
activeValue={notificationPosition}
|
||||
<div className="w-full min-w-[140px] md:w-auto">
|
||||
<Select
|
||||
value={t(`settings:${notificationPosition}`)}
|
||||
onChange={(p) => setNotificationPosition(p)}
|
||||
values={NOTIFICATION_POSITIONS}
|
||||
names={NOTIFICATION_POSITIONS.map((val) => t(`settings:${val}`))}
|
||||
/>
|
||||
className="w-full"
|
||||
>
|
||||
{NOTIFICATION_POSITIONS.map((val) => (
|
||||
<Select.Option key={val} value={t(`settings:${val}`)}>
|
||||
<div className="flex w-full items-center justify-between">
|
||||
{t(`settings:${val}`)}
|
||||
</div>
|
||||
</Select.Option>
|
||||
))}
|
||||
</Select>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col border-t border-th-bkg-3 py-4 md:flex-row md:items-center md:justify-between md:px-4">
|
||||
|
|
|
@ -21,7 +21,7 @@ const RPC_URLS = [
|
|||
|
||||
const RpcSettings = () => {
|
||||
const { t } = useTranslation('settings')
|
||||
const actions = mangoStore((s) => s.actions)
|
||||
const actions = mangoStore.getState().actions
|
||||
const [customUrl, setCustomUrl] = useState('')
|
||||
const [showCustomForm, setShowCustomForm] = useState(false)
|
||||
const [rpcEndpointProvider, setRpcEndpointProvider] = useLocalStorageState(
|
||||
|
@ -67,7 +67,7 @@ const RpcSettings = () => {
|
|||
<h2 className="mb-4 text-base">{t('rpc')}</h2>
|
||||
<div className="flex flex-col border-t border-th-bkg-3 py-4 md:flex-row md:items-center md:justify-between md:px-4">
|
||||
<p className="mb-2 md:mb-0">{t('rpc-provider')}</p>
|
||||
<div className="w-full min-w-[240px] md:w-[340px] md:pl-4">
|
||||
<div className="w-full min-w-[160px] md:w-auto md:pl-4">
|
||||
<ButtonGroup
|
||||
activeValue={rpcEndpoint.label}
|
||||
onChange={(v) => handleSetEndpointProvider(v)}
|
||||
|
|
|
@ -0,0 +1,24 @@
|
|||
import Decimal from 'decimal.js'
|
||||
|
||||
const AmountWithValue = ({
|
||||
amount,
|
||||
value,
|
||||
stacked,
|
||||
}: {
|
||||
amount: Decimal | number | string
|
||||
value: string
|
||||
stacked?: boolean
|
||||
}) => {
|
||||
return (
|
||||
<p className={`font-mono text-th-fgd-2 ${stacked ? 'text-right' : ''}`}>
|
||||
<>
|
||||
{amount}{' '}
|
||||
<span className={`text-th-fgd-4 ${stacked ? 'block' : ''}`}>
|
||||
{value}
|
||||
</span>
|
||||
</>
|
||||
</p>
|
||||
)
|
||||
}
|
||||
|
||||
export default AmountWithValue
|
|
@ -21,6 +21,7 @@ import { LinkButton } from './Button'
|
|||
import { Table, Td, Th, TrBody, TrHead } from './TableElements'
|
||||
import useSelectedMarket from 'hooks/useSelectedMarket'
|
||||
import useMangoGroup from 'hooks/useMangoGroup'
|
||||
import AmountWithValue from './AmountWithValue'
|
||||
|
||||
const BalancesTable = () => {
|
||||
const { t } = useTranslation(['common', 'trade'])
|
||||
|
@ -46,12 +47,15 @@ const BalancesTable = () => {
|
|||
Math.abs(b.balance! * b.value[0].uiPrice) -
|
||||
Math.abs(a.balance! * a.value[0].uiPrice)
|
||||
)
|
||||
.filter(
|
||||
(c) =>
|
||||
.filter((c) => {
|
||||
return (
|
||||
Math.abs(
|
||||
floorToDecimal(c.balance!, c.value[0].mintDecimals).toNumber()
|
||||
) > 0
|
||||
)
|
||||
) > 0 ||
|
||||
spotBalances[c.value[0].mint.toString()]?.unsettled > 0 ||
|
||||
spotBalances[c.value[0].mint.toString()]?.inOrders > 0
|
||||
)
|
||||
})
|
||||
: rawBanks
|
||||
|
||||
return sortedBanks
|
||||
|
@ -104,22 +108,41 @@ const BalancesTable = () => {
|
|||
{mangoAccount
|
||||
? `${formatFixedDecimals(
|
||||
mangoAccount.getTokenBalanceUi(bank) * bank.uiPrice,
|
||||
false,
|
||||
true
|
||||
)}`
|
||||
: '$0.00'}
|
||||
</p>
|
||||
</Td>
|
||||
<Td className="text-right font-mono">
|
||||
<p>{inOrders}</p>
|
||||
<p className="text-sm text-th-fgd-4">
|
||||
{formatFixedDecimals(inOrders * bank.uiPrice, true)}
|
||||
</p>
|
||||
<Td className="text-right">
|
||||
<AmountWithValue
|
||||
amount={
|
||||
inOrders
|
||||
? formatDecimal(Number(inOrders), bank.mintDecimals)
|
||||
: '0'
|
||||
}
|
||||
value={formatFixedDecimals(
|
||||
inOrders * bank.uiPrice,
|
||||
true,
|
||||
true
|
||||
)}
|
||||
stacked
|
||||
/>
|
||||
</Td>
|
||||
<Td className="text-right font-mono">
|
||||
<p>{unsettled ? unsettled.toFixed(bank.mintDecimals) : 0}</p>
|
||||
<p className="text-sm text-th-fgd-4">
|
||||
{formatFixedDecimals(unsettled * bank.uiPrice, true)}
|
||||
</p>
|
||||
<Td className="text-right">
|
||||
<AmountWithValue
|
||||
amount={
|
||||
unsettled
|
||||
? formatDecimal(Number(unsettled), bank.mintDecimals)
|
||||
: '0'
|
||||
}
|
||||
value={formatFixedDecimals(
|
||||
unsettled * bank.uiPrice,
|
||||
true,
|
||||
true
|
||||
)}
|
||||
stacked
|
||||
/>
|
||||
</Td>
|
||||
</TrBody>
|
||||
)
|
||||
|
@ -160,14 +183,13 @@ const BalancesTable = () => {
|
|||
<div className="mb-0.5 flex justify-end space-x-1.5">
|
||||
<Balance bank={bank} />
|
||||
<span className="text-sm text-th-fgd-4">
|
||||
(
|
||||
{mangoAccount
|
||||
? `${formatFixedDecimals(
|
||||
mangoAccount.getTokenBalanceUi(bank) * bank.uiPrice,
|
||||
false,
|
||||
true
|
||||
)}`
|
||||
: '$0.00'}
|
||||
)
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex space-x-2">
|
||||
|
@ -298,12 +320,7 @@ const Balance = ({ bank }: { bank: Bank }) => {
|
|||
{asPath.includes('/trade') && isBaseOrQuote ? (
|
||||
<LinkButton
|
||||
className="font-normal underline-offset-4"
|
||||
onClick={() =>
|
||||
handleTradeFormBalanceClick(
|
||||
parseFloat(formatDecimal(balance, bank.mintDecimals)),
|
||||
isBaseOrQuote
|
||||
)
|
||||
}
|
||||
onClick={() => handleTradeFormBalanceClick(balance, isBaseOrQuote)}
|
||||
>
|
||||
{formatDecimal(balance, bank.mintDecimals)}
|
||||
</LinkButton>
|
||||
|
@ -312,7 +329,7 @@ const Balance = ({ bank }: { bank: Bank }) => {
|
|||
className="font-normal underline-offset-4"
|
||||
onClick={() =>
|
||||
handleSwapFormBalanceClick(
|
||||
parseFloat(formatDecimal(balance, bank.mintDecimals))
|
||||
floorToDecimal(balance, bank.mintDecimals).toNumber()
|
||||
)
|
||||
}
|
||||
>
|
||||
|
|
|
@ -29,7 +29,7 @@ const Button: FunctionComponent<ButtonCombinedProps> = ({
|
|||
<button
|
||||
onClick={onClick}
|
||||
disabled={disabled}
|
||||
className={`whitespace-nowrap rounded-md ${
|
||||
className={`rounded-md ${
|
||||
secondary
|
||||
? 'border border-th-button md:hover:border-th-button-hover'
|
||||
: 'bg-th-button md:hover:bg-th-button-hover'
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { MinusSmallIcon } from '@heroicons/react/20/solid'
|
||||
import { formatFixedDecimals } from 'utils/numbers'
|
||||
import { formatDecimal } from 'utils/numbers'
|
||||
import { DownTriangle, UpTriangle } from './DirectionTriangles'
|
||||
|
||||
const Change = ({
|
||||
|
@ -42,9 +42,7 @@ const Change = ({
|
|||
}`}
|
||||
>
|
||||
{prefix ? prefix : ''}
|
||||
{isNaN(change)
|
||||
? '0.00'
|
||||
: formatFixedDecimals(Math.abs(change), false, true)}
|
||||
{isNaN(change) ? '0.00' : formatDecimal(Math.abs(change), 2)}
|
||||
{suffix ? suffix : ''}
|
||||
</p>
|
||||
</div>
|
||||
|
|
|
@ -0,0 +1,24 @@
|
|||
import WalletIcon from '@components/icons/WalletIcon'
|
||||
import { useEnhancedWallet } from '@components/wallet/EnhancedWalletProvider'
|
||||
import { LinkIcon } from '@heroicons/react/20/solid'
|
||||
import { useTranslation } from 'next-i18next'
|
||||
import Button from './Button'
|
||||
|
||||
const ConnectEmptyState = ({ text }: { text: string }) => {
|
||||
const { t } = useTranslation('common')
|
||||
const { handleConnect } = useEnhancedWallet()
|
||||
return (
|
||||
<div className="flex flex-col items-center">
|
||||
<WalletIcon className="mb-2 h-6 w-6 text-th-fgd-4" />
|
||||
<p className="mb-4">{text}</p>
|
||||
<Button onClick={handleConnect}>
|
||||
<div className="flex items-center">
|
||||
<LinkIcon className="mr-2 h-5 w-5" />
|
||||
{t('connect')}
|
||||
</div>
|
||||
</Button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default ConnectEmptyState
|
|
@ -102,7 +102,7 @@ const DetailedAreaChart: FunctionComponent<DetailedAreaChartProps> = ({
|
|||
|
||||
const flipGradientCoords = useMemo(() => {
|
||||
if (!data.length) return
|
||||
return data[0][yKey] <= 0 && data[data.length - 1][yKey] < data[0][yKey]
|
||||
return data[0][yKey] <= 0 && data[data.length - 1][yKey] <= 0
|
||||
}, [data])
|
||||
|
||||
return (
|
||||
|
@ -147,17 +147,19 @@ const DetailedAreaChart: FunctionComponent<DetailedAreaChartProps> = ({
|
|||
height={small ? 24 : 40}
|
||||
width={small ? 17 : 30}
|
||||
play
|
||||
numbers={
|
||||
prefix +
|
||||
formatFixedDecimals(mouseData[yKey] ?? 0) +
|
||||
suffix
|
||||
}
|
||||
numbers={`${
|
||||
mouseData[yKey] < 0 ? '-' : ''
|
||||
}${prefix}${formatFixedDecimals(
|
||||
Math.abs(mouseData[yKey])
|
||||
)}${suffix}`}
|
||||
/>
|
||||
) : (
|
||||
<span>
|
||||
{prefix +
|
||||
formatFixedDecimals(mouseData[yKey] ?? 0) +
|
||||
suffix}
|
||||
{`${
|
||||
mouseData[yKey] < 0 ? '-' : ''
|
||||
}${prefix}${formatFixedDecimals(
|
||||
Math.abs(mouseData[yKey])
|
||||
)}${suffix}`}
|
||||
</span>
|
||||
)}
|
||||
{!hideChange ? (
|
||||
|
@ -192,17 +194,19 @@ const DetailedAreaChart: FunctionComponent<DetailedAreaChartProps> = ({
|
|||
height={small ? 24 : 40}
|
||||
width={small ? 17 : 30}
|
||||
play
|
||||
numbers={
|
||||
prefix +
|
||||
formatFixedDecimals(data[data.length - 1][yKey]) +
|
||||
suffix
|
||||
}
|
||||
numbers={`${
|
||||
data[data.length - 1][yKey] < 0 ? '-' : ''
|
||||
}${prefix}${formatFixedDecimals(
|
||||
Math.abs(data[data.length - 1][yKey])
|
||||
)}${suffix}`}
|
||||
/>
|
||||
) : (
|
||||
<span>
|
||||
{prefix +
|
||||
formatFixedDecimals(data[data.length - 1][yKey]) +
|
||||
suffix}
|
||||
{`${
|
||||
data[data.length - 1][yKey] < 0 ? '-' : ''
|
||||
}${prefix}${formatFixedDecimals(
|
||||
Math.abs(data[data.length - 1][yKey])
|
||||
)}${suffix}`}
|
||||
</span>
|
||||
)}
|
||||
{!hideChange ? (
|
||||
|
|
|
@ -18,14 +18,19 @@ function Modal({
|
|||
onClose,
|
||||
hideClose,
|
||||
}: ModalProps) {
|
||||
const handleClose = () => {
|
||||
if (disableOutsideClose) return
|
||||
onClose()
|
||||
}
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
open={isOpen}
|
||||
onClose={onClose}
|
||||
onClose={handleClose}
|
||||
className="relative z-40 overflow-y-auto"
|
||||
>
|
||||
<div
|
||||
className={`fixed inset-0 backdrop-brightness-50 ${
|
||||
className={`fixed inset-0 backdrop-brightness-[0.3] ${
|
||||
disableOutsideClose ? 'pointer-events-none' : ''
|
||||
}`}
|
||||
aria-hidden="true"
|
||||
|
|
|
@ -123,13 +123,20 @@ const Notification = ({ notification }: { notification: Notification }) => {
|
|||
let parsedTitle: string | undefined
|
||||
if (description) {
|
||||
if (
|
||||
description?.includes('Timed out awaiting') ||
|
||||
description?.includes('was not confirmed')
|
||||
description.includes('Timed out awaiting') ||
|
||||
description.includes('was not confirmed')
|
||||
) {
|
||||
parsedTitle = 'Transaction status unknown'
|
||||
}
|
||||
}
|
||||
|
||||
let parsedDescription = description
|
||||
if (
|
||||
description?.includes('{"err":{"InstructionError":[2,{"Custom":6001}]}}')
|
||||
) {
|
||||
parsedDescription = 'Your max slippage tolerance was exceeded'
|
||||
}
|
||||
|
||||
// if the notification is a success, then hide the confirming tx notification with the same txid
|
||||
useEffect(() => {
|
||||
if ((type === 'error' || type === 'success') && txid) {
|
||||
|
@ -247,11 +254,11 @@ const Notification = ({ notification }: { notification: Notification }) => {
|
|||
</div>
|
||||
<div className={`ml-2 flex-1`}>
|
||||
<p className={`text-th-fgd-1`}>{parsedTitle || title}</p>
|
||||
{description ? (
|
||||
{parsedDescription ? (
|
||||
<p
|
||||
className={`mb-0 mt-0.5 break-all text-sm leading-tight text-th-fgd-4`}
|
||||
>
|
||||
{description}
|
||||
{parsedDescription}
|
||||
</p>
|
||||
) : null}
|
||||
{txid ? (
|
||||
|
|
|
@ -12,17 +12,13 @@ const SideBadge: FunctionComponent<SideBadgeProps> = ({ side }) => {
|
|||
return (
|
||||
<div
|
||||
className={`inline-block rounded uppercase ${
|
||||
side === 'buy' || side === 'long' || side === PerpOrderSide.bid
|
||||
side === 'buy' || side === 'long' || side === 0
|
||||
? 'text-th-up md:border md:border-th-up'
|
||||
: 'text-th-down md:border md:border-th-down'
|
||||
}
|
||||
uppercase md:-my-0.5 md:px-1.5 md:py-0.5 md:text-xs`}
|
||||
>
|
||||
{typeof side === 'string'
|
||||
? t(side)
|
||||
: side === PerpOrderSide.bid
|
||||
? 'Buy'
|
||||
: 'Sell'}
|
||||
{typeof side === 'string' ? t(side) : side === 0 ? 'Buy' : 'Sell'}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -14,10 +14,10 @@ const SimpleAreaChart = ({
|
|||
xKey: string
|
||||
yKey: string
|
||||
}) => {
|
||||
const flipGradientCoords = useMemo(
|
||||
() => data[0][yKey] <= 0 && data[data.length - 1][yKey] < data[0][yKey],
|
||||
[data]
|
||||
)
|
||||
const flipGradientCoords = useMemo(() => {
|
||||
if (!data.length) return
|
||||
return data[0][yKey] <= 0 && data[data.length - 1][yKey] <= 0
|
||||
}, [data])
|
||||
|
||||
return (
|
||||
<ResponsiveContainer width="100%" height="100%">
|
||||
|
@ -35,7 +35,6 @@ const SimpleAreaChart = ({
|
|||
</linearGradient>
|
||||
</defs>
|
||||
<Area
|
||||
isAnimationActive={false}
|
||||
type="monotone"
|
||||
dataKey={yKey}
|
||||
stroke={color}
|
||||
|
|
|
@ -6,10 +6,12 @@ import InlineNotification from './InlineNotification'
|
|||
|
||||
const SolBalanceWarnings = ({
|
||||
amount,
|
||||
className,
|
||||
setAmount,
|
||||
selectedToken,
|
||||
}: {
|
||||
amount?: string
|
||||
className?: string
|
||||
setAmount?: (a: string) => void
|
||||
selectedToken?: string
|
||||
}) => {
|
||||
|
@ -54,11 +56,11 @@ const SolBalanceWarnings = ({
|
|||
}, [maxSolDeposit])
|
||||
|
||||
return showLowSolWarning ? (
|
||||
<div className="mt-2">
|
||||
<div className={className}>
|
||||
<InlineNotification type="warning" desc={t('deposit-more-sol')} />
|
||||
</div>
|
||||
) : showMaxSolWarning ? (
|
||||
<div className="mt-2">
|
||||
<div className={className}>
|
||||
<InlineNotification
|
||||
type="info"
|
||||
desc={`SOL deposits are restricted to leave ${MIN_SOL_BALANCE} SOL in your wallet for sending transactions`}
|
||||
|
|
|
@ -65,7 +65,9 @@ export const TableDateDisplay = ({
|
|||
showSeconds?: boolean
|
||||
}) => (
|
||||
<>
|
||||
<p className="text-th-fgd-2">{dayjs(date).format('DD MMM YYYY')}</p>
|
||||
<p className="tracking-normal text-th-fgd-2">
|
||||
{dayjs(date).format('DD MMM YYYY')}
|
||||
</p>
|
||||
<p className="text-xs text-th-fgd-4">
|
||||
{dayjs(date).format(showSeconds ? 'h:mm:ssa' : 'h:mma')}
|
||||
</p>
|
||||
|
|
|
@ -1,48 +1,59 @@
|
|||
import { Bank } from '@blockworks-foundation/mango-v4'
|
||||
import {
|
||||
getMaxWithdrawForBank,
|
||||
useTokenMax,
|
||||
} from '@components/swap/useTokenMax'
|
||||
import useMangoAccount from 'hooks/useMangoAccount'
|
||||
import useMangoGroup from 'hooks/useMangoGroup'
|
||||
import Link from 'next/link'
|
||||
import { useMemo } from 'react'
|
||||
import { floorToDecimal } from 'utils/numbers'
|
||||
import InlineNotification from './InlineNotification'
|
||||
|
||||
const TokenVaultWarnings = ({ bank }: { bank: Bank }) => {
|
||||
const TokenVaultWarnings = ({
|
||||
bank,
|
||||
type,
|
||||
}: {
|
||||
bank: Bank
|
||||
type: 'borrow' | 'swap' | 'withdraw'
|
||||
}) => {
|
||||
const { mangoAccount } = useMangoAccount()
|
||||
const { group } = useMangoGroup()
|
||||
|
||||
const balance = useMemo(() => {
|
||||
if (!mangoAccount || !group) return 0
|
||||
const { amountWithBorrow: swapBorrowMax } = useTokenMax()
|
||||
|
||||
const [maxWithdraw, maxBorrow] = useMemo(() => {
|
||||
if (!mangoAccount || !group) return [0, 0]
|
||||
const maxWithdraw = getMaxWithdrawForBank(group, bank, mangoAccount)
|
||||
const maxBorrow = mangoAccount.getMaxWithdrawWithBorrowForTokenUi(
|
||||
group,
|
||||
bank.mint
|
||||
)
|
||||
console.log('xyx', maxBorrow / bank.minVaultToDepositsRatio)
|
||||
|
||||
return maxBorrow
|
||||
return [maxWithdraw, maxBorrow]
|
||||
}, [bank, mangoAccount, group])
|
||||
|
||||
const vaultBalance = useMemo(() => {
|
||||
if (!group) return 0
|
||||
return floorToDecimal(
|
||||
group.getTokenVaultBalanceByMintUi(bank.mint),
|
||||
bank.mintDecimals
|
||||
).toNumber()
|
||||
const availableVaultBalance = useMemo(() => {
|
||||
if (!bank || !group) return 0
|
||||
const vaultBalance = group.getTokenVaultBalanceByMintUi(bank.mint)
|
||||
const vaultDeposits = bank.uiDeposits()
|
||||
const available =
|
||||
vaultBalance - vaultDeposits * bank.minVaultToDepositsRatio
|
||||
return available
|
||||
}, [bank, group])
|
||||
|
||||
// return !vaultBalance ? (
|
||||
// <InlineNotification
|
||||
// type="warning"
|
||||
// desc={`${bank.name} vault is too low or fully utilized`}
|
||||
// />
|
||||
// ) : mangoAccount && balance! > vaultBalance ? (
|
||||
// <InlineNotification
|
||||
// type="warning"
|
||||
// desc={`Available ${bank.name} vault balance is lower than your balance`}
|
||||
// />
|
||||
// ) : null
|
||||
const showWarning = useMemo(() => {
|
||||
if (!bank || !group) return false
|
||||
if (
|
||||
(type === 'borrow' && maxBorrow > availableVaultBalance) ||
|
||||
(type === 'swap' && swapBorrowMax.toNumber() > availableVaultBalance) ||
|
||||
(type === 'withdraw' && maxWithdraw > availableVaultBalance)
|
||||
) {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}, [bank, group, type])
|
||||
|
||||
return mangoAccount &&
|
||||
balance / bank.minVaultToDepositsRatio > vaultBalance ? (
|
||||
return showWarning ? (
|
||||
<div className="mb-4">
|
||||
<InlineNotification
|
||||
type="warning"
|
||||
|
@ -52,7 +63,7 @@ const TokenVaultWarnings = ({ bank }: { bank: Bank }) => {
|
|||
<Link href="/stats" className="underline hover:no-underline">
|
||||
vault balance
|
||||
</Link>{' '}
|
||||
is low and impacting the maximum amount you can withdraw/borrow.
|
||||
is low and impacting the maximum amount you can <span>{type}</span>
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
|
|
|
@ -85,7 +85,7 @@ const PerpMarketsTable = ({
|
|||
<Td>
|
||||
<div className="flex items-center">
|
||||
<MarketLogos market={market} />
|
||||
<p className="font-body tracking-wide">{market.name}</p>
|
||||
<p className="font-body tracking-wider">{market.name}</p>
|
||||
</div>
|
||||
</Td>
|
||||
<Td>
|
||||
|
|
|
@ -50,7 +50,7 @@ const SpotMarketsTable = () => {
|
|||
const coingeckoData = coingeckoPrices.find((asset) =>
|
||||
bank?.name === 'soETH'
|
||||
? asset.symbol === 'ETH'
|
||||
: asset.symbol === bank?.name
|
||||
: asset.symbol.toUpperCase() === bank?.name.toUpperCase()
|
||||
)
|
||||
|
||||
const change = coingeckoData
|
||||
|
@ -67,12 +67,16 @@ const SpotMarketsTable = () => {
|
|||
<Td>
|
||||
<div className="flex items-center">
|
||||
<MarketLogos market={market} />
|
||||
<p className="font-body tracking-wide">{market.name}</p>
|
||||
<p className="font-body tracking-wider">{market.name}</p>
|
||||
</div>
|
||||
</Td>
|
||||
<Td>
|
||||
<div className="flex flex-col text-right">
|
||||
<p>{formatFixedDecimals(oraclePrice!, true)}</p>
|
||||
<p>
|
||||
{oraclePrice
|
||||
? formatFixedDecimals(oraclePrice, true)
|
||||
: '–'}
|
||||
</p>
|
||||
</div>
|
||||
</Td>
|
||||
<Td>
|
||||
|
|
|
@ -15,7 +15,7 @@ const TABS =
|
|||
|
||||
const StatsPage = () => {
|
||||
const [activeTab, setActiveTab] = useState('tokens')
|
||||
const actions = mangoStore((s) => s.actions)
|
||||
const actions = mangoStore.getState().actions
|
||||
const { group } = useMangoGroup()
|
||||
|
||||
useEffect(() => {
|
||||
|
|
|
@ -19,10 +19,11 @@ import useJupiterMints from 'hooks/useJupiterMints'
|
|||
import { Table, Td, Th, TrBody, TrHead } from '@components/shared/TableElements'
|
||||
import useMangoGroup from 'hooks/useMangoGroup'
|
||||
import mangoStore from '@store/mangoStore'
|
||||
import AmountWithValue from '@components/shared/AmountWithValue'
|
||||
|
||||
const TokenStats = () => {
|
||||
const { t } = useTranslation(['common', 'token'])
|
||||
const actions = mangoStore((s) => s.actions)
|
||||
const actions = mangoStore.getState().actions
|
||||
const initialStatsLoad = mangoStore((s) => s.tokenStats.initialLoad)
|
||||
const [showTokenDetails, setShowTokenDetails] = useState('')
|
||||
const { group } = useMangoGroup()
|
||||
|
@ -43,7 +44,7 @@ const TokenStats = () => {
|
|||
key,
|
||||
value,
|
||||
}))
|
||||
return rawBanks
|
||||
return rawBanks.sort((a, b) => a.key.localeCompare(b.key))
|
||||
}
|
||||
return []
|
||||
}, [group])
|
||||
|
@ -65,6 +66,11 @@ const TokenStats = () => {
|
|||
<Th className="text-left">{t('token')}</Th>
|
||||
<Th className="text-right">{t('total-deposits')}</Th>
|
||||
<Th className="text-right">{t('total-borrows')}</Th>
|
||||
<Th className="text-right">
|
||||
<Tooltip content="The amount available to borrow">
|
||||
<span className="tooltip-underline">{t('available')}</span>
|
||||
</Tooltip>
|
||||
</Th>
|
||||
<Th>
|
||||
<div className="flex justify-end">
|
||||
<Tooltip content="The deposit rate (green) will automatically be paid on positive balances and the borrow rate (red) will automatically be charged on negative balances.">
|
||||
|
@ -83,18 +89,13 @@ const TokenStats = () => {
|
|||
</Th>
|
||||
<Th>
|
||||
<div className="flex justify-end text-right">
|
||||
<Tooltip content={t('asset-weight-desc')}>
|
||||
<Tooltip content={t('asset-liability-weight-desc')}>
|
||||
<span className="tooltip-underline">
|
||||
{t('asset-weight')}
|
||||
{t('asset-liability-weight')}
|
||||
</span>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</Th>
|
||||
<Th>
|
||||
<div className="flex items-center justify-end">
|
||||
<span className="text-right">{t('liability-weight')}</span>
|
||||
</div>
|
||||
</Th>
|
||||
</TrHead>
|
||||
</thead>
|
||||
<tbody>
|
||||
|
@ -107,6 +108,11 @@ const TokenStats = () => {
|
|||
(t) => t.address === bank.mint.toString()
|
||||
)?.logoURI
|
||||
}
|
||||
const deposits = bank.uiDeposits()
|
||||
const borrows = bank.uiBorrows()
|
||||
const price = bank.uiPrice
|
||||
const available =
|
||||
deposits - deposits * bank.minVaultToDepositsRatio - borrows
|
||||
|
||||
return (
|
||||
<TrBody key={key}>
|
||||
|
@ -119,21 +125,39 @@ const TokenStats = () => {
|
|||
<QuestionMarkCircleIcon className="h-6 w-6 text-th-fgd-3" />
|
||||
)}
|
||||
</div>
|
||||
<p className="font-body tracking-wide">{bank.name}</p>
|
||||
<p className="font-body tracking-wider">{bank.name}</p>
|
||||
</div>
|
||||
</Td>
|
||||
<Td>
|
||||
<div className="flex flex-col text-right">
|
||||
<p>{formatFixedDecimals(bank.uiDeposits())}</p>
|
||||
<p>{formatFixedDecimals(deposits)}</p>
|
||||
<p className="text-th-fgd-4">
|
||||
{formatFixedDecimals(deposits * price, true, true)}
|
||||
</p>
|
||||
</div>
|
||||
</Td>
|
||||
<Td>
|
||||
<div className="flex flex-col text-right">
|
||||
<p>{formatFixedDecimals(bank.uiBorrows())}</p>
|
||||
<p>{formatFixedDecimals(borrows)}</p>
|
||||
<p className="text-th-fgd-4">
|
||||
{formatFixedDecimals(borrows * price, true, true)}
|
||||
</p>
|
||||
</div>
|
||||
</Td>
|
||||
<Td>
|
||||
<div className="flex justify-end space-x-2">
|
||||
<div className="flex flex-col text-right">
|
||||
<p>
|
||||
{available > 0 ? formatFixedDecimals(available) : '0'}
|
||||
</p>
|
||||
<p className="text-th-fgd-4">
|
||||
{available > 0
|
||||
? formatFixedDecimals(available * price, false, true)
|
||||
: '$0.00'}
|
||||
</p>
|
||||
</div>
|
||||
</Td>
|
||||
<Td>
|
||||
<div className="flex justify-end space-x-1.5">
|
||||
<p className="text-th-up">
|
||||
{formatDecimal(bank.getDepositRateUi(), 2, {
|
||||
fixed: true,
|
||||
|
@ -164,12 +188,9 @@ const TokenStats = () => {
|
|||
</div>
|
||||
</Td>
|
||||
<Td>
|
||||
<div className="text-right">
|
||||
<div className="flex justify-end space-x-1.5 text-right">
|
||||
<p>{bank.initAssetWeight.toFixed(2)}</p>
|
||||
</div>
|
||||
</Td>
|
||||
<Td>
|
||||
<div className="text-right">
|
||||
<span className="text-th-fgd-4">|</span>
|
||||
<p>{bank.initLiabWeight.toFixed(2)}</p>
|
||||
</div>
|
||||
</Td>
|
||||
|
@ -198,6 +219,11 @@ const TokenStats = () => {
|
|||
(t) => t.address === bank.mint.toString()
|
||||
)?.logoURI
|
||||
}
|
||||
const deposits = bank.uiDeposits()
|
||||
const borrows = bank.uiBorrows()
|
||||
const price = bank.uiPrice
|
||||
const available =
|
||||
deposits - deposits * bank.minVaultToDepositsRatio - borrows
|
||||
return (
|
||||
<div key={key} className="border-b border-th-bkg-3 px-6 py-4">
|
||||
<div className="flex items-center justify-between">
|
||||
|
@ -213,18 +239,28 @@ const TokenStats = () => {
|
|||
</div>
|
||||
<div className="flex items-center space-x-4">
|
||||
<div>
|
||||
<p className="text-right text-xs">
|
||||
<p className="mb-0.5 text-right text-xs">
|
||||
{t('total-deposits')}
|
||||
</p>
|
||||
<p className="text-right font-mono text-th-fgd-1">
|
||||
{formatFixedDecimals(bank.uiDeposits())}
|
||||
</p>
|
||||
<AmountWithValue
|
||||
amount={formatFixedDecimals(deposits)}
|
||||
value={formatFixedDecimals(
|
||||
deposits * price,
|
||||
true,
|
||||
true
|
||||
)}
|
||||
stacked
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-right text-xs">{t('total-borrows')}</p>
|
||||
<p className="text-right font-mono text-th-fgd-1">
|
||||
{formatFixedDecimals(bank.uiBorrows())}
|
||||
<p className="mb-0.5 text-right text-xs">
|
||||
{t('total-borrows')}
|
||||
</p>
|
||||
<AmountWithValue
|
||||
amount={formatFixedDecimals(borrows)}
|
||||
value={formatFixedDecimals(borrows * price, true, true)}
|
||||
stacked
|
||||
/>
|
||||
</div>
|
||||
<IconButton
|
||||
onClick={() => handleShowTokenDetails(bank.name)}
|
||||
|
@ -279,20 +315,41 @@ const TokenStats = () => {
|
|||
</p>
|
||||
</div>
|
||||
<div className="col-span-1">
|
||||
<p className="text-xs text-th-fgd-3">
|
||||
{t('asset-weight')}
|
||||
</p>
|
||||
<p className="font-mono text-th-fgd-1">
|
||||
{bank.initAssetWeight.toFixed(2)}
|
||||
<Tooltip content="The amount available to borrow">
|
||||
<p className="tooltip-underline text-xs text-th-fgd-3">
|
||||
{t('available')}
|
||||
</p>
|
||||
</Tooltip>
|
||||
<p className="text-th-fgd-1">
|
||||
{available > 0 ? formatFixedDecimals(available) : '0'}{' '}
|
||||
<span className="text-th-fgd-4">
|
||||
(
|
||||
{available > 0
|
||||
? formatFixedDecimals(
|
||||
available * price,
|
||||
false,
|
||||
true
|
||||
)
|
||||
: '$0.00'}
|
||||
)
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
<div className="col-span-1">
|
||||
<p className="text-xs text-th-fgd-3">
|
||||
{t('liability-weight')}
|
||||
</p>
|
||||
<p className="font-mono text-th-fgd-1">
|
||||
{bank.initLiabWeight.toFixed(2)}
|
||||
</p>
|
||||
<Tooltip content={t('asset-liability-weight-desc')}>
|
||||
<p className="tooltip-underline text-xs text-th-fgd-3">
|
||||
{t('asset-liability-weight')}
|
||||
</p>
|
||||
</Tooltip>
|
||||
<div className="flex space-x-1.5 text-right font-mono">
|
||||
<p className="text-th-fgd-1">
|
||||
{bank.initAssetWeight.toFixed(2)}
|
||||
</p>
|
||||
<span className="text-th-fgd-4">|</span>
|
||||
<p className="text-th-fgd-1">
|
||||
{bank.initLiabWeight.toFixed(2)}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="col-span-1">
|
||||
<LinkButton
|
||||
|
|
|
@ -1,65 +0,0 @@
|
|||
import Tooltip from '@components/shared/Tooltip'
|
||||
import mangoStore from '@store/mangoStore'
|
||||
import { useTranslation } from 'next-i18next'
|
||||
import { formatDecimal, formatFixedDecimals } from 'utils/numbers'
|
||||
import { useTokenMax } from './useTokenMax'
|
||||
|
||||
const BorrowInfo = ({
|
||||
amount,
|
||||
useMargin,
|
||||
}: {
|
||||
amount: number
|
||||
useMargin: boolean
|
||||
}) => {
|
||||
const { t } = useTranslation('common')
|
||||
const { amount: tokenMax } = useTokenMax(useMargin)
|
||||
const inputBank = mangoStore((s) => s.swap.inputBank)
|
||||
|
||||
const tokenMaxAsNumber = tokenMax.toNumber()
|
||||
const borrowAmount = amount - tokenMaxAsNumber
|
||||
|
||||
return amount > tokenMaxAsNumber && inputBank ? (
|
||||
<div className="space-y-2">
|
||||
<div className="flex justify-between">
|
||||
<Tooltip
|
||||
content={
|
||||
tokenMaxAsNumber
|
||||
? t('swap:tooltip-borrow-balance', {
|
||||
balance: formatFixedDecimals(tokenMaxAsNumber),
|
||||
borrowAmount: formatFixedDecimals(borrowAmount),
|
||||
token: inputBank.name,
|
||||
})
|
||||
: t('swap:tooltip-borrow-no-balance', {
|
||||
borrowAmount: formatFixedDecimals(borrowAmount),
|
||||
token: inputBank.name,
|
||||
})
|
||||
}
|
||||
delay={250}
|
||||
>
|
||||
<p className="tooltip-underline text-sm text-th-fgd-3">
|
||||
{t('borrow-amount')}
|
||||
</p>
|
||||
</Tooltip>
|
||||
<p className="text-right font-mono text-sm text-th-fgd-3">
|
||||
{formatFixedDecimals(borrowAmount)}
|
||||
<span className="font-body"> {inputBank.name}</span>
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex justify-between">
|
||||
<Tooltip content={t('tooltip-borrow-rate')} delay={250}>
|
||||
<p className="tooltip-underline text-sm text-th-fgd-3">
|
||||
{t('borrow-rate')}
|
||||
</p>
|
||||
</Tooltip>
|
||||
<p className="text-right font-mono text-sm text-th-down">
|
||||
{formatDecimal(inputBank.getBorrowRateUi(), 2, {
|
||||
fixed: true,
|
||||
})}
|
||||
%
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
) : null
|
||||
}
|
||||
|
||||
export default BorrowInfo
|
|
@ -1,6 +1,7 @@
|
|||
import MaxAmountButton from '@components/shared/MaxAmountButton'
|
||||
import mangoStore from '@store/mangoStore'
|
||||
import { useTranslation } from 'next-i18next'
|
||||
import { floorToDecimal } from 'utils/numbers'
|
||||
import { useTokenMax } from './useTokenMax'
|
||||
|
||||
const MaxSwapAmount = ({
|
||||
|
@ -20,20 +21,29 @@ const MaxSwapAmount = ({
|
|||
|
||||
if (mangoAccountLoading) return null
|
||||
|
||||
const maxBalanceValue = floorToDecimal(
|
||||
tokenMax.toNumber(),
|
||||
decimals
|
||||
).toFixed()
|
||||
const maxBorrowValue = floorToDecimal(
|
||||
amountWithBorrow.toNumber(),
|
||||
decimals
|
||||
).toFixed()
|
||||
|
||||
return (
|
||||
<div className="flex flex-wrap justify-end pl-6 text-xs">
|
||||
<MaxAmountButton
|
||||
className="mb-0.5"
|
||||
label="Bal"
|
||||
onClick={() => setAmountIn(tokenMax.toFixed(decimals))}
|
||||
value={tokenMax.toFixed()}
|
||||
onClick={() => setAmountIn(maxBalanceValue)}
|
||||
value={maxBalanceValue}
|
||||
/>
|
||||
{useMargin ? (
|
||||
<MaxAmountButton
|
||||
className="mb-0.5 ml-2"
|
||||
label={t('max')}
|
||||
onClick={() => setAmountIn(amountWithBorrow.toFixed(decimals))}
|
||||
value={amountWithBorrow.toFixed()}
|
||||
onClick={() => setAmountIn(maxBorrowValue)}
|
||||
value={maxBorrowValue}
|
||||
/>
|
||||
) : null}
|
||||
</div>
|
||||
|
|
|
@ -1,10 +1,11 @@
|
|||
import { useState, useCallback, useEffect, useMemo } from 'react'
|
||||
import { PublicKey } from '@solana/web3.js'
|
||||
import {
|
||||
AdjustmentsHorizontalIcon,
|
||||
ArrowDownIcon,
|
||||
Cog8ToothIcon,
|
||||
ExclamationCircleIcon,
|
||||
LinkIcon,
|
||||
PencilIcon,
|
||||
} from '@heroicons/react/20/solid'
|
||||
import NumberFormat, {
|
||||
NumberFormatValues,
|
||||
|
@ -19,11 +20,10 @@ import useDebounce from '../shared/useDebounce'
|
|||
import { useTranslation } from 'next-i18next'
|
||||
import SwapFormTokenList from './SwapFormTokenList'
|
||||
import { Transition } from '@headlessui/react'
|
||||
import Button from '../shared/Button'
|
||||
import Button, { IconButton } from '../shared/Button'
|
||||
import Loading from '../shared/Loading'
|
||||
import { EnterBottomExitBottom } from '../shared/Transitions'
|
||||
import useJupiterRoutes from './useJupiterRoutes'
|
||||
import SlippageSettings from './SlippageSettings'
|
||||
import SheenLoader from '../shared/SheenLoader'
|
||||
import { HealthType } from '@blockworks-foundation/mango-v4'
|
||||
import {
|
||||
|
@ -45,7 +45,8 @@ import TokenVaultWarnings from '@components/shared/TokenVaultWarnings'
|
|||
import MaxSwapAmount from './MaxSwapAmount'
|
||||
import PercentageSelectButtons from './PercentageSelectButtons'
|
||||
import useIpAddress from 'hooks/useIpAddress'
|
||||
import Checkbox from '@components/forms/Checkbox'
|
||||
import { useEnhancedWallet } from '@components/wallet/EnhancedWalletProvider'
|
||||
import SwapSettings from './SwapSettings'
|
||||
|
||||
const MAX_DIGITS = 11
|
||||
export const withValueLimit = (values: NumberFormatValues): boolean => {
|
||||
|
@ -261,19 +262,12 @@ const SwapForm = () => {
|
|||
return !!amountInAsDecimal.toNumber() && connected && !selectedRoute
|
||||
}, [amountInAsDecimal, connected, selectedRoute])
|
||||
|
||||
const handleSetMargin = () => {
|
||||
set((s) => {
|
||||
s.swap.margin = !s.swap.margin
|
||||
})
|
||||
}
|
||||
|
||||
return (
|
||||
<ContentBox
|
||||
hidePadding
|
||||
// showBackground
|
||||
className="relative overflow-hidden border-x-0 md:border-l md:border-r-0 md:border-t-0 md:border-b-0"
|
||||
>
|
||||
<div className="">
|
||||
<div>
|
||||
<Transition
|
||||
className="absolute top-0 left-0 z-10 h-full w-full bg-th-bkg-1 pb-0"
|
||||
show={showConfirm}
|
||||
|
@ -312,9 +306,19 @@ const SwapForm = () => {
|
|||
className="thin-scroll absolute bottom-0 left-0 z-10 h-full w-full overflow-auto bg-th-bkg-1 p-6 pb-0"
|
||||
show={showSettings}
|
||||
>
|
||||
<SlippageSettings onClose={() => setShowSettings(false)} />
|
||||
<SwapSettings onClose={() => setShowSettings(false)} />
|
||||
</EnterBottomExitBottom>
|
||||
<div className="p-6">
|
||||
<div className="relative p-6 pt-10">
|
||||
<div className="absolute right-2 top-2">
|
||||
<IconButton
|
||||
className="text-th-fgd-3"
|
||||
hideBg
|
||||
onClick={() => setShowSettings(true)}
|
||||
size="small"
|
||||
>
|
||||
<Cog8ToothIcon className="h-5 w-5" />
|
||||
</IconButton>
|
||||
</div>
|
||||
<div className="mb-2 flex items-end justify-between">
|
||||
<p className="text-th-fgd-2 lg:text-base">{t('swap:pay')}</p>
|
||||
<MaxSwapAmount
|
||||
|
@ -427,19 +431,19 @@ const SwapForm = () => {
|
|||
}
|
||||
/>
|
||||
) : (
|
||||
<div className="mt-6 mb-4 flex-grow">
|
||||
<div className="flex">
|
||||
<Button disabled className="flex-grow">
|
||||
<span>
|
||||
{t('country-not-allowed', {
|
||||
country: ipCountry ? `(${ipCountry})` : '(Unknown)',
|
||||
})}
|
||||
</span>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<Button
|
||||
disabled
|
||||
className="mt-6 mb-4 w-full leading-tight"
|
||||
size="large"
|
||||
>
|
||||
{t('country-not-allowed', {
|
||||
country: ipCountry ? `(${ipCountry})` : '',
|
||||
})}
|
||||
</Button>
|
||||
)}
|
||||
{group && inputBank ? <TokenVaultWarnings bank={inputBank} /> : null}
|
||||
{group && inputBank ? (
|
||||
<TokenVaultWarnings bank={inputBank} type="swap" />
|
||||
) : null}
|
||||
<div className="space-y-2">
|
||||
<div id="swap-step-four">
|
||||
<HealthImpact maintProjectedHealth={maintProjectedHealth} />
|
||||
|
@ -454,22 +458,21 @@ const SwapForm = () => {
|
|||
: '–'}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center justify-between border-t border-th-bkg-3 px-6 py-4">
|
||||
<p>{`${t('swap')} ${t('settings')}`}</p>
|
||||
<div className="flex items-center space-x-5">
|
||||
<Checkbox checked={useMargin} onChange={handleSetMargin}>
|
||||
<span className="text-xs text-th-fgd-3">{t('trade:margin')}</span>
|
||||
</Checkbox>
|
||||
<div id="swap-step-one">
|
||||
<button
|
||||
className="default-transition flex items-center text-th-fgd-3 focus:outline-none md:hover:text-th-active"
|
||||
onClick={() => setShowSettings(true)}
|
||||
>
|
||||
<AdjustmentsHorizontalIcon className="mr-1.5 h-4 w-4" />
|
||||
<span className="text-xs">{slippage}%</span>
|
||||
</button>
|
||||
<div className="flex justify-between">
|
||||
<p className="text-sm text-th-fgd-3">{t('swap:max-slippage')}</p>
|
||||
<div className="flex items-center space-x-1">
|
||||
<p className="text-right font-mono text-sm text-th-fgd-2">
|
||||
{slippage}%
|
||||
</p>
|
||||
<IconButton
|
||||
className="text-th-fgd-3"
|
||||
hideBg
|
||||
onClick={() => setShowSettings(true)}
|
||||
size="small"
|
||||
>
|
||||
<PencilIcon className="h-4 w-4" />
|
||||
</IconButton>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -498,17 +501,20 @@ const SwapFormSubmitButton = ({
|
|||
const { t } = useTranslation('common')
|
||||
const { connected } = useWallet()
|
||||
const { amount: tokenMax, amountWithBorrow } = useTokenMax(useMargin)
|
||||
const { handleConnect } = useEnhancedWallet()
|
||||
|
||||
const showInsufficientBalance = useMargin
|
||||
? amountWithBorrow.lt(amountIn)
|
||||
: tokenMax.lt(amountIn)
|
||||
|
||||
const disabled =
|
||||
!amountIn.toNumber() || !connected || showInsufficientBalance || !amountOut
|
||||
connected && (!amountIn.toNumber() || showInsufficientBalance || !amountOut)
|
||||
|
||||
const onClick = connected ? () => setShowConfirm(true) : handleConnect
|
||||
|
||||
return (
|
||||
<Button
|
||||
onClick={() => setShowConfirm(true)}
|
||||
onClick={onClick}
|
||||
className="mt-6 mb-4 flex w-full items-center justify-center text-base"
|
||||
disabled={disabled}
|
||||
size="large"
|
||||
|
|
|
@ -10,6 +10,7 @@ import useMangoAccount from 'hooks/useMangoAccount'
|
|||
import useJupiterMints from 'hooks/useJupiterMints'
|
||||
import useMangoGroup from 'hooks/useMangoGroup'
|
||||
import { PublicKey } from '@solana/web3.js'
|
||||
import { formatDecimal } from 'utils/numbers'
|
||||
|
||||
// const generateSearchTerm = (item: Token, searchValue: string) => {
|
||||
// const normalizedSearchValue = searchValue.toLowerCase()
|
||||
|
@ -69,11 +70,14 @@ const TokenItem = ({
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{type === 'input' ? (
|
||||
<p className="text-sm text-th-fgd-2">
|
||||
{type === 'input' &&
|
||||
token.amount &&
|
||||
token.amountWithBorrow &&
|
||||
token.decimals ? (
|
||||
<p className="font-mono text-sm text-th-fgd-2">
|
||||
{useMargin
|
||||
? token.amountWithBorrow?.toString()
|
||||
: token.amount?.toString()}
|
||||
? formatDecimal(token.amountWithBorrow.toNumber(), token.decimals)
|
||||
: formatDecimal(token.amount.toNumber(), token.decimals)}
|
||||
</p>
|
||||
) : null}
|
||||
</button>
|
||||
|
@ -156,6 +160,7 @@ const SwapFormTokenList = ({
|
|||
amountWithBorrow: new Decimal(0),
|
||||
}))
|
||||
.filter((token) => (token.symbol === inputBank?.name ? false : true))
|
||||
.sort((a, b) => a.symbol.localeCompare(b.symbol))
|
||||
return filteredTokens
|
||||
} else {
|
||||
return []
|
||||
|
|
|
@ -2,7 +2,6 @@ import { Fragment, useEffect, useState } from 'react'
|
|||
import {
|
||||
ArrowRightIcon,
|
||||
ChevronDownIcon,
|
||||
LinkIcon,
|
||||
NoSymbolIcon,
|
||||
} from '@heroicons/react/20/solid'
|
||||
import dayjs from 'dayjs'
|
||||
|
@ -13,9 +12,10 @@ import { useViewport } from '../../hooks/useViewport'
|
|||
import { IconButton } from '../shared/Button'
|
||||
import { Transition } from '@headlessui/react'
|
||||
import SheenLoader from '../shared/SheenLoader'
|
||||
import mangoStore, { SwapHistoryItem } from '@store/mangoStore'
|
||||
import mangoStore from '@store/mangoStore'
|
||||
import {
|
||||
countLeadingZeros,
|
||||
formatDecimal,
|
||||
formatFixedDecimals,
|
||||
trimDecimals,
|
||||
} from '../../utils/numbers'
|
||||
|
@ -25,23 +25,19 @@ import Tooltip from '@components/shared/Tooltip'
|
|||
import { formatTokenSymbol } from 'utils/tokens'
|
||||
import useJupiterMints from 'hooks/useJupiterMints'
|
||||
import { Table, Td, Th, TrBody, TrHead } from '@components/shared/TableElements'
|
||||
import { useWallet } from '@solana/wallet-adapter-react'
|
||||
import { EXPLORERS } from '@components/settings/PreferredExplorerSettings'
|
||||
import useMangoAccount from 'hooks/useMangoAccount'
|
||||
|
||||
const SwapHistoryTable = ({
|
||||
swapHistory,
|
||||
loading,
|
||||
}: {
|
||||
swapHistory: SwapHistoryItem[]
|
||||
loading: boolean
|
||||
}) => {
|
||||
const SwapHistoryTable = () => {
|
||||
const { t } = useTranslation(['common', 'settings', 'swap'])
|
||||
const { connected } = useWallet()
|
||||
const swapHistory = mangoStore((s) => s.mangoAccount.stats.swapHistory.data)
|
||||
const initialLoad = mangoStore(
|
||||
(s) => s.mangoAccount.stats.swapHistory.initialLoad
|
||||
)
|
||||
const { mangoTokens } = useJupiterMints()
|
||||
const [showSwapDetails, setSwapDetails] = useState('')
|
||||
const actions = mangoStore((s) => s.actions)
|
||||
const { mangoAccount } = useMangoAccount()
|
||||
const actions = mangoStore.getState().actions
|
||||
const { mangoAccountAddress } = useMangoAccount()
|
||||
const { width } = useViewport()
|
||||
const showTableView = width ? width > breakpoints.md : false
|
||||
const [preferredExplorer] = useLocalStorageState(
|
||||
|
@ -50,191 +46,30 @@ const SwapHistoryTable = ({
|
|||
)
|
||||
|
||||
useEffect(() => {
|
||||
if (mangoAccount) {
|
||||
actions.fetchSwapHistory(mangoAccount.publicKey.toString())
|
||||
if (mangoAccountAddress) {
|
||||
actions.fetchSwapHistory(mangoAccountAddress)
|
||||
}
|
||||
}, [actions, mangoAccount])
|
||||
}, [actions, mangoAccountAddress])
|
||||
|
||||
const handleShowSwapDetails = (signature: string) => {
|
||||
showSwapDetails ? setSwapDetails('') : setSwapDetails(signature)
|
||||
}
|
||||
|
||||
return connected ? (
|
||||
!loading ? (
|
||||
swapHistory.length ? (
|
||||
showTableView ? (
|
||||
<Table>
|
||||
<thead>
|
||||
<TrHead>
|
||||
<Th className="text-left">{t('date')}</Th>
|
||||
<Th className="w-1/3 text-left">{t('swap')}</Th>
|
||||
<Th className="text-right">{t('value')}</Th>
|
||||
<Th className="text-right">{t('borrow')}</Th>
|
||||
<Th className="text-right">{t('borrow-fee')}</Th>
|
||||
<Th />
|
||||
</TrHead>
|
||||
</thead>
|
||||
<tbody>
|
||||
{swapHistory.map((h) => {
|
||||
const {
|
||||
block_datetime,
|
||||
signature,
|
||||
swap_in_amount,
|
||||
swap_in_loan_origination_fee,
|
||||
swap_in_price_usd,
|
||||
swap_in_symbol,
|
||||
swap_out_amount,
|
||||
loan,
|
||||
loan_origination_fee,
|
||||
swap_out_price_usd,
|
||||
swap_out_symbol,
|
||||
} = h
|
||||
const borrowAmount =
|
||||
loan > 0
|
||||
? `${trimDecimals(loan, countLeadingZeros(loan) + 2)}`
|
||||
: 0
|
||||
const borrowFee =
|
||||
swap_in_loan_origination_fee > 0
|
||||
? swap_in_loan_origination_fee.toFixed(4)
|
||||
: loan_origination_fee > 0
|
||||
? loan_origination_fee.toFixed(4)
|
||||
: 0
|
||||
|
||||
let baseLogoURI
|
||||
let quoteLogoURI
|
||||
|
||||
const inSymbol = formatTokenSymbol(swap_in_symbol)
|
||||
const outSymbol = formatTokenSymbol(swap_out_symbol)
|
||||
|
||||
if (mangoTokens.length) {
|
||||
baseLogoURI = mangoTokens.find(
|
||||
(t) => t.symbol === inSymbol
|
||||
)?.logoURI
|
||||
quoteLogoURI = mangoTokens.find(
|
||||
(t) => t.symbol === outSymbol
|
||||
)?.logoURI
|
||||
}
|
||||
|
||||
const inDecimals = countLeadingZeros(swap_in_amount)
|
||||
const outDecimals = countLeadingZeros(swap_out_amount) + 2
|
||||
return (
|
||||
<TrBody key={signature}>
|
||||
<Td>
|
||||
<p className="font-body tracking-wide">
|
||||
{dayjs(block_datetime).format('ddd D MMM')}
|
||||
</p>
|
||||
<p className="font-body text-xs tracking-wide text-th-fgd-3">
|
||||
{dayjs(block_datetime).format('h:mma')}
|
||||
</p>
|
||||
</Td>
|
||||
<Td className="w-1/3">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex w-1/2 items-center">
|
||||
<div className="mr-2 flex flex-shrink-0 items-center">
|
||||
<Image
|
||||
alt=""
|
||||
width="24"
|
||||
height="24"
|
||||
src={baseLogoURI || ''}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<p className="whitespace-nowrap">
|
||||
{`${swap_in_amount.toFixed(inDecimals)}`}
|
||||
<span className="ml-1 font-body tracking-wide text-th-fgd-3">
|
||||
{inSymbol}
|
||||
</span>
|
||||
</p>
|
||||
<p className="text-xs text-th-fgd-3">
|
||||
<span className="font-body text-th-fgd-4">
|
||||
{t('price')}:
|
||||
</span>{' '}
|
||||
{formatFixedDecimals(swap_in_price_usd, true)}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<ArrowRightIcon className="mx-4 h-4 w-4 flex-shrink-0 text-th-fgd-4" />
|
||||
<div className="flex w-1/2 items-center">
|
||||
<div className="mr-2 flex flex-shrink-0 items-center">
|
||||
<Image
|
||||
alt=""
|
||||
width="24"
|
||||
height="24"
|
||||
src={quoteLogoURI || ''}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<p className="whitespace-nowrap">
|
||||
{`${trimDecimals(swap_out_amount, outDecimals)}`}
|
||||
<span className="ml-1 font-body tracking-wide text-th-fgd-3">
|
||||
{outSymbol}
|
||||
</span>
|
||||
</p>
|
||||
<p className="text-xs text-th-fgd-3">
|
||||
<span className="font-body text-th-fgd-4">
|
||||
{t('price')}:
|
||||
</span>{' '}
|
||||
{formatFixedDecimals(swap_out_price_usd, true)}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Td>
|
||||
<Td>
|
||||
<p className="text-right">
|
||||
{formatFixedDecimals(
|
||||
swap_out_price_usd * swap_out_amount,
|
||||
true
|
||||
)}
|
||||
</p>
|
||||
</Td>
|
||||
<Td>
|
||||
<div className="flex flex-col text-right">
|
||||
<p>
|
||||
{borrowAmount}
|
||||
<span className="ml-1 font-body tracking-wide text-th-fgd-3">
|
||||
{inSymbol}
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
</Td>
|
||||
<Td>
|
||||
<div className="flex flex-col text-right">
|
||||
<p>${borrowFee}</p>
|
||||
</div>
|
||||
</Td>
|
||||
<Td>
|
||||
<div className="flex items-center justify-end">
|
||||
<Tooltip
|
||||
content={`View on ${t(
|
||||
`settings:${preferredExplorer.name}`
|
||||
)}`}
|
||||
placement="top-end"
|
||||
>
|
||||
<a
|
||||
href={`${preferredExplorer.url}${signature}`}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<div className="h-6 w-6">
|
||||
<Image
|
||||
alt=""
|
||||
width="24"
|
||||
height="24"
|
||||
src={`/explorer-logos/${preferredExplorer.name}.png`}
|
||||
/>
|
||||
</div>
|
||||
</a>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</Td>
|
||||
</TrBody>
|
||||
)
|
||||
})}
|
||||
</tbody>
|
||||
</Table>
|
||||
) : (
|
||||
<div>
|
||||
return initialLoad ? (
|
||||
mangoAccountAddress && swapHistory.length ? (
|
||||
showTableView ? (
|
||||
<Table>
|
||||
<thead>
|
||||
<TrHead>
|
||||
<Th className="text-left">{t('date')}</Th>
|
||||
<Th className="w-1/3 text-left">{t('swap')}</Th>
|
||||
<Th className="text-right">{t('value')}</Th>
|
||||
<Th className="text-right">{t('borrow')}</Th>
|
||||
<Th className="text-right">{t('borrow-fee')}</Th>
|
||||
<Th />
|
||||
</TrHead>
|
||||
</thead>
|
||||
<tbody>
|
||||
{swapHistory.map((h) => {
|
||||
const {
|
||||
block_datetime,
|
||||
|
@ -249,7 +84,6 @@ const SwapHistoryTable = ({
|
|||
swap_out_price_usd,
|
||||
swap_out_symbol,
|
||||
} = h
|
||||
|
||||
const borrowAmount =
|
||||
loan > 0
|
||||
? `${trimDecimals(loan, countLeadingZeros(loan) + 2)}`
|
||||
|
@ -266,6 +100,7 @@ const SwapHistoryTable = ({
|
|||
|
||||
const inSymbol = formatTokenSymbol(swap_in_symbol)
|
||||
const outSymbol = formatTokenSymbol(swap_out_symbol)
|
||||
console.log('mangoTokens', mangoTokens)
|
||||
|
||||
if (mangoTokens.length) {
|
||||
baseLogoURI = mangoTokens.find(
|
||||
|
@ -276,11 +111,21 @@ const SwapHistoryTable = ({
|
|||
)?.logoURI
|
||||
}
|
||||
|
||||
const inDecimals = countLeadingZeros(swap_in_amount)
|
||||
const outDecimals = countLeadingZeros(swap_out_amount) + 2
|
||||
return (
|
||||
<div key={signature} className="border-b border-th-bkg-3 p-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<TrBody key={signature}>
|
||||
<Td>
|
||||
<p className="font-body tracking-wider">
|
||||
{dayjs(block_datetime).format('ddd D MMM')}
|
||||
</p>
|
||||
<p className="font-body text-xs text-th-fgd-3">
|
||||
{dayjs(block_datetime).format('h:mma')}
|
||||
</p>
|
||||
</Td>
|
||||
<Td className="w-1/3">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center">
|
||||
<div className="flex w-1/2 items-center">
|
||||
<div className="mr-2 flex flex-shrink-0 items-center">
|
||||
<Image
|
||||
alt=""
|
||||
|
@ -290,13 +135,13 @@ const SwapHistoryTable = ({
|
|||
/>
|
||||
</div>
|
||||
<div>
|
||||
<p className="whitespace-nowrap font-mono text-th-fgd-1">
|
||||
{swap_in_amount.toFixed(2)}{' '}
|
||||
<span className="font-body text-th-fgd-3">
|
||||
<p className="whitespace-nowrap">
|
||||
{`${formatDecimal(swap_in_amount, inDecimals)}`}
|
||||
<span className="ml-1 font-body text-th-fgd-3">
|
||||
{inSymbol}
|
||||
</span>
|
||||
</p>
|
||||
<p className="font-mono text-xs text-th-fgd-3">
|
||||
<p className="text-xs text-th-fgd-3">
|
||||
<span className="font-body text-th-fgd-4">
|
||||
{t('price')}:
|
||||
</span>{' '}
|
||||
|
@ -305,7 +150,7 @@ const SwapHistoryTable = ({
|
|||
</div>
|
||||
</div>
|
||||
<ArrowRightIcon className="mx-4 h-4 w-4 flex-shrink-0 text-th-fgd-4" />
|
||||
<div className="flex items-center">
|
||||
<div className="flex w-1/2 items-center">
|
||||
<div className="mr-2 flex flex-shrink-0 items-center">
|
||||
<Image
|
||||
alt=""
|
||||
|
@ -315,116 +160,261 @@ const SwapHistoryTable = ({
|
|||
/>
|
||||
</div>
|
||||
<div>
|
||||
<p className="whitespace-nowrap font-mono text-th-fgd-1">
|
||||
{swap_out_amount.toFixed(2)}{' '}
|
||||
<span className="font-body text-th-fgd-3">
|
||||
<p className="whitespace-nowrap">
|
||||
{`${formatDecimal(swap_out_amount, outDecimals)}`}
|
||||
<span className="ml-1 font-body text-th-fgd-3">
|
||||
{outSymbol}
|
||||
</span>
|
||||
</p>
|
||||
<p className="font-mono text-xs text-th-fgd-3">
|
||||
<span className="font-body text-th-fgd-4">
|
||||
{t('price')}:
|
||||
</span>{' '}
|
||||
<p className="text-xs text-th-fgd-4">
|
||||
<span className="font-body">{t('price')}:</span>{' '}
|
||||
{formatFixedDecimals(swap_out_price_usd, true)}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<IconButton
|
||||
onClick={() => handleShowSwapDetails(signature)}
|
||||
>
|
||||
<ChevronDownIcon
|
||||
className={`${
|
||||
showSwapDetails === signature
|
||||
? 'rotate-180'
|
||||
: 'rotate-360'
|
||||
} h-6 w-6 flex-shrink-0 text-th-fgd-1`}
|
||||
/>
|
||||
</IconButton>
|
||||
</div>
|
||||
<Transition
|
||||
appear={true}
|
||||
show={showSwapDetails === signature}
|
||||
as={Fragment}
|
||||
enter="transition ease-in duration-200"
|
||||
enterFrom="opacity-0"
|
||||
enterTo="opacity-100"
|
||||
leave="transition ease-out"
|
||||
leaveFrom="opacity-100"
|
||||
leaveTo="opacity-0"
|
||||
>
|
||||
<div className="mt-4 grid grid-cols-2 gap-4 border-t border-th-bkg-3 pt-4">
|
||||
<div className="col-span-1">
|
||||
<p className="text-xs text-th-fgd-3">{t('date')}</p>
|
||||
<p className="text-th-fgd-1">
|
||||
{dayjs(block_datetime).format('ddd D MMM')}
|
||||
</p>
|
||||
<p className="text-xs text-th-fgd-3">
|
||||
{dayjs(block_datetime).format('h:mma')}
|
||||
</p>
|
||||
</div>
|
||||
<div className="col-span-1">
|
||||
<p className="text-xs text-th-fgd-3">{t('borrow')}</p>
|
||||
<p className="font-mono text-th-fgd-1">
|
||||
{borrowAmount}{' '}
|
||||
<span className="font-body text-th-fgd-3">
|
||||
{inSymbol}
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
<div className="col-span-1">
|
||||
<p className="text-xs text-th-fgd-3">
|
||||
{t('borrow-fee')}
|
||||
</p>
|
||||
<p className="font-mono text-th-fgd-1">${borrowFee}</p>
|
||||
</div>
|
||||
<div className="col-span-1">
|
||||
<p className="mb-0.5 text-xs text-th-fgd-3">
|
||||
{t('transaction')}
|
||||
</p>
|
||||
</Td>
|
||||
<Td>
|
||||
<p className="text-right">
|
||||
{formatFixedDecimals(
|
||||
swap_out_price_usd * swap_out_amount,
|
||||
false,
|
||||
true
|
||||
)}
|
||||
</p>
|
||||
</Td>
|
||||
<Td>
|
||||
<div className="flex flex-col text-right">
|
||||
<p>
|
||||
{borrowAmount}
|
||||
<span className="ml-1 font-body text-th-fgd-3">
|
||||
{inSymbol}
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
</Td>
|
||||
<Td>
|
||||
<div className="flex flex-col text-right">
|
||||
<p>
|
||||
{borrowFee}
|
||||
<span className="ml-1 font-body text-th-fgd-3">
|
||||
{inSymbol}
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
</Td>
|
||||
<Td>
|
||||
<div className="flex items-center justify-end">
|
||||
<Tooltip
|
||||
content={`View on ${t(
|
||||
`settings:${preferredExplorer.name}`
|
||||
)}`}
|
||||
placement="top-end"
|
||||
>
|
||||
<a
|
||||
className="default-transition flex items-center text-th-fgd-1 hover:text-th-fgd-3"
|
||||
href={`${preferredExplorer.url}${signature}`}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<Image
|
||||
alt=""
|
||||
width="20"
|
||||
height="20"
|
||||
src={`/explorer-logos/${preferredExplorer.name}.png`}
|
||||
/>
|
||||
<span className="ml-1.5">{`View on ${t(
|
||||
`settings:${preferredExplorer.name}`
|
||||
)}`}</span>
|
||||
<div className="h-6 w-6">
|
||||
<Image
|
||||
alt=""
|
||||
width="24"
|
||||
height="24"
|
||||
src={`/explorer-logos/${preferredExplorer.name}.png`}
|
||||
/>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</Transition>
|
||||
</div>
|
||||
</Td>
|
||||
</TrBody>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
)
|
||||
</tbody>
|
||||
</Table>
|
||||
) : (
|
||||
<div className="flex flex-col items-center p-8">
|
||||
<NoSymbolIcon className="mb-2 h-6 w-6 text-th-fgd-4" />
|
||||
<p>{t('swap:no-history')}</p>
|
||||
<div>
|
||||
{swapHistory.map((h) => {
|
||||
const {
|
||||
block_datetime,
|
||||
signature,
|
||||
swap_in_amount,
|
||||
swap_in_loan_origination_fee,
|
||||
swap_in_price_usd,
|
||||
swap_in_symbol,
|
||||
swap_out_amount,
|
||||
loan,
|
||||
loan_origination_fee,
|
||||
swap_out_price_usd,
|
||||
swap_out_symbol,
|
||||
} = h
|
||||
|
||||
const borrowAmount =
|
||||
loan > 0
|
||||
? `${trimDecimals(loan, countLeadingZeros(loan) + 2)}`
|
||||
: 0
|
||||
const borrowFee =
|
||||
swap_in_loan_origination_fee > 0
|
||||
? swap_in_loan_origination_fee.toFixed(4)
|
||||
: loan_origination_fee > 0
|
||||
? loan_origination_fee.toFixed(4)
|
||||
: 0
|
||||
|
||||
let baseLogoURI
|
||||
let quoteLogoURI
|
||||
|
||||
const inSymbol = formatTokenSymbol(swap_in_symbol)
|
||||
const outSymbol = formatTokenSymbol(swap_out_symbol)
|
||||
|
||||
if (mangoTokens.length) {
|
||||
baseLogoURI = mangoTokens.find(
|
||||
(t) => t.symbol === inSymbol
|
||||
)?.logoURI
|
||||
quoteLogoURI = mangoTokens.find(
|
||||
(t) => t.symbol === outSymbol
|
||||
)?.logoURI
|
||||
}
|
||||
|
||||
return (
|
||||
<div key={signature} className="border-b border-th-bkg-3 p-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center">
|
||||
<div className="mr-2 flex flex-shrink-0 items-center">
|
||||
<Image
|
||||
alt=""
|
||||
width="24"
|
||||
height="24"
|
||||
src={baseLogoURI || ''}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<p className="whitespace-nowrap font-mono text-th-fgd-1">
|
||||
{swap_in_amount.toFixed(2)}{' '}
|
||||
<span className="font-body text-th-fgd-3">
|
||||
{inSymbol}
|
||||
</span>
|
||||
</p>
|
||||
<p className="font-mono text-xs text-th-fgd-3">
|
||||
<span className="font-body text-th-fgd-4">
|
||||
{t('price')}:
|
||||
</span>{' '}
|
||||
{formatFixedDecimals(swap_in_price_usd, true)}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<ArrowRightIcon className="mx-4 h-4 w-4 flex-shrink-0 text-th-fgd-4" />
|
||||
<div className="flex items-center">
|
||||
<div className="mr-2 flex flex-shrink-0 items-center">
|
||||
<Image
|
||||
alt=""
|
||||
width="24"
|
||||
height="24"
|
||||
src={quoteLogoURI || ''}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<p className="whitespace-nowrap font-mono text-th-fgd-1">
|
||||
{swap_out_amount.toFixed(2)}{' '}
|
||||
<span className="font-body text-th-fgd-3">
|
||||
{outSymbol}
|
||||
</span>
|
||||
</p>
|
||||
<p className="font-mono text-xs text-th-fgd-3">
|
||||
<span className="font-body text-th-fgd-4">
|
||||
{t('price')}:
|
||||
</span>{' '}
|
||||
{formatFixedDecimals(swap_out_price_usd, true)}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<IconButton onClick={() => handleShowSwapDetails(signature)}>
|
||||
<ChevronDownIcon
|
||||
className={`${
|
||||
showSwapDetails === signature
|
||||
? 'rotate-180'
|
||||
: 'rotate-360'
|
||||
} h-6 w-6 flex-shrink-0 text-th-fgd-1`}
|
||||
/>
|
||||
</IconButton>
|
||||
</div>
|
||||
<Transition
|
||||
appear={true}
|
||||
show={showSwapDetails === signature}
|
||||
as={Fragment}
|
||||
enter="transition ease-in duration-200"
|
||||
enterFrom="opacity-0"
|
||||
enterTo="opacity-100"
|
||||
leave="transition ease-out"
|
||||
leaveFrom="opacity-100"
|
||||
leaveTo="opacity-0"
|
||||
>
|
||||
<div className="mt-4 grid grid-cols-2 gap-4 border-t border-th-bkg-3 pt-4">
|
||||
<div className="col-span-1">
|
||||
<p className="text-xs text-th-fgd-3">{t('date')}</p>
|
||||
<p className="text-th-fgd-1">
|
||||
{dayjs(block_datetime).format('ddd D MMM')}
|
||||
</p>
|
||||
<p className="text-xs text-th-fgd-3">
|
||||
{dayjs(block_datetime).format('h:mma')}
|
||||
</p>
|
||||
</div>
|
||||
<div className="col-span-1">
|
||||
<p className="text-xs text-th-fgd-3">{t('borrow')}</p>
|
||||
<p className="font-mono text-th-fgd-1">
|
||||
{borrowAmount}{' '}
|
||||
<span className="font-body text-th-fgd-3">
|
||||
{inSymbol}
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
<div className="col-span-1">
|
||||
<p className="text-xs text-th-fgd-3">{t('borrow-fee')}</p>
|
||||
<p className="font-mono text-th-fgd-1">${borrowFee}</p>
|
||||
</div>
|
||||
<div className="col-span-1">
|
||||
<p className="mb-0.5 text-xs text-th-fgd-3">
|
||||
{t('transaction')}
|
||||
</p>
|
||||
<a
|
||||
className="default-transition flex items-center text-th-fgd-1 hover:text-th-fgd-3"
|
||||
href={`${preferredExplorer.url}${signature}`}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<Image
|
||||
alt=""
|
||||
width="20"
|
||||
height="20"
|
||||
src={`/explorer-logos/${preferredExplorer.name}.png`}
|
||||
/>
|
||||
<span className="ml-1.5">{`View on ${t(
|
||||
`settings:${preferredExplorer.name}`
|
||||
)}`}</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</Transition>
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
)
|
||||
) : (
|
||||
<div className="mt-4 space-y-1.5">
|
||||
{[...Array(4)].map((x, i) => (
|
||||
<SheenLoader className="mx-4 flex flex-1 md:mx-6" key={i}>
|
||||
<div className="h-16 w-full bg-th-bkg-2" />
|
||||
</SheenLoader>
|
||||
))}
|
||||
<div className="flex flex-col items-center p-8">
|
||||
<NoSymbolIcon className="mb-2 h-6 w-6 text-th-fgd-4" />
|
||||
<p>{t('swap:no-history')}</p>
|
||||
</div>
|
||||
)
|
||||
) : (
|
||||
<div className="flex flex-col items-center p-8">
|
||||
<LinkIcon className="mb-2 h-6 w-6 text-th-fgd-4" />
|
||||
<p>{t('swap:connect-swap')}</p>
|
||||
<div className="mt-4 space-y-1.5">
|
||||
{[...Array(4)].map((x, i) => (
|
||||
<SheenLoader className="mx-4 flex flex-1 md:mx-6" key={i}>
|
||||
<div className="h-16 w-full bg-th-bkg-2" />
|
||||
</SheenLoader>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -9,8 +9,6 @@ const SwapInfoTabs = () => {
|
|||
const [selectedTab, setSelectedTab] = useState('balances')
|
||||
const openOrders = mangoStore((s) => s.mangoAccount.openOrders)
|
||||
const { mangoAccount } = useMangoAccount()
|
||||
const swapHistory = mangoStore((s) => s.mangoAccount.stats.swapHistory.data)
|
||||
const loading = mangoStore((s) => s.mangoAccount.stats.swapHistory.loading)
|
||||
|
||||
const tabsWithCount: [string, number][] = useMemo(() => {
|
||||
return [
|
||||
|
@ -30,9 +28,7 @@ const SwapInfoTabs = () => {
|
|||
/>
|
||||
</div>
|
||||
{selectedTab === 'balances' ? <SwapTradeBalances /> : null}
|
||||
{selectedTab === 'swap:swap-history' ? (
|
||||
<SwapHistoryTable swapHistory={swapHistory} loading={loading} />
|
||||
) : null}
|
||||
{selectedTab === 'swap:swap-history' ? <SwapHistoryTable /> : null}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -214,7 +214,7 @@ const SwapReviewRouteInfo = ({
|
|||
const connection = mangoStore.getState().connection
|
||||
|
||||
if (!mangoAccount || !group || !inputBank || !outputBank) return
|
||||
|
||||
setSubmitting(true)
|
||||
const [ixs, alts] = await fetchJupiterTransaction(
|
||||
connection,
|
||||
selectedRoute,
|
||||
|
@ -225,7 +225,6 @@ const SwapReviewRouteInfo = ({
|
|||
)
|
||||
|
||||
try {
|
||||
setSubmitting(true)
|
||||
const tx = await client.marginTrade({
|
||||
group,
|
||||
mangoAccount,
|
||||
|
@ -295,7 +294,11 @@ const SwapReviewRouteInfo = ({
|
|||
: new Decimal(0)
|
||||
}, [coingeckoPrices, amountIn, amountOut])
|
||||
|
||||
return routes?.length && selectedRoute && outputTokenInfo && amountOut ? (
|
||||
return routes?.length &&
|
||||
selectedRoute &&
|
||||
inputTokenInfo &&
|
||||
outputTokenInfo &&
|
||||
amountOut ? (
|
||||
<div className="thin-scroll flex h-full flex-col justify-between overflow-y-auto">
|
||||
<div>
|
||||
<IconButton
|
||||
|
@ -315,7 +318,7 @@ const SwapReviewRouteInfo = ({
|
|||
alt=""
|
||||
width="40"
|
||||
height="40"
|
||||
src={outputTokenInfo.logoURI}
|
||||
src={outputTokenInfo?.logoURI}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -323,12 +326,12 @@ const SwapReviewRouteInfo = ({
|
|||
<span className="mr-1 font-mono text-th-fgd-1">{`${formatFixedDecimals(
|
||||
amountIn.toNumber()
|
||||
)}`}</span>{' '}
|
||||
{inputTokenInfo!.symbol}
|
||||
{inputTokenInfo?.symbol}
|
||||
<ArrowRightIcon className="mx-2 h-5 w-5 text-th-fgd-4" />
|
||||
<span className="mr-1 font-mono text-th-fgd-1">{`${formatFixedDecimals(
|
||||
amountOut.toNumber()
|
||||
)}`}</span>{' '}
|
||||
{`${outputTokenInfo.symbol}`}
|
||||
{`${outputTokenInfo?.symbol}`}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -341,23 +344,23 @@ const SwapReviewRouteInfo = ({
|
|||
{swapRate ? (
|
||||
<>
|
||||
1{' '}
|
||||
<span className="font-body tracking-wide">
|
||||
{inputTokenInfo!.name} ≈{' '}
|
||||
<span className="font-body tracking-wider">
|
||||
{inputTokenInfo?.name} ≈{' '}
|
||||
</span>
|
||||
{formatFixedDecimals(amountOut.div(amountIn).toNumber())}{' '}
|
||||
<span className="font-body tracking-wide">
|
||||
<span className="font-body tracking-wider">
|
||||
{outputTokenInfo?.symbol}
|
||||
</span>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
1{' '}
|
||||
<span className="font-body tracking-wide">
|
||||
<span className="font-body tracking-wider">
|
||||
{outputTokenInfo?.symbol} ≈{' '}
|
||||
</span>
|
||||
{formatFixedDecimals(amountIn.div(amountOut).toNumber())}{' '}
|
||||
<span className="font-body tracking-wide">
|
||||
{inputTokenInfo!.symbol}
|
||||
<span className="font-body tracking-wider">
|
||||
{inputTokenInfo?.symbol}
|
||||
</span>
|
||||
</>
|
||||
)}
|
||||
|
@ -378,7 +381,7 @@ const SwapReviewRouteInfo = ({
|
|||
}`}
|
||||
>
|
||||
{Decimal.abs(coinGeckoPriceDifference).toFixed(1)}%{' '}
|
||||
<span className="font-body tracking-wide text-th-fgd-3">{`${
|
||||
<span className="font-body text-th-fgd-3">{`${
|
||||
coinGeckoPriceDifference.lte(0)
|
||||
? 'cheaper'
|
||||
: 'more expensive'
|
||||
|
@ -392,14 +395,15 @@ const SwapReviewRouteInfo = ({
|
|||
<p className="text-sm text-th-fgd-3">
|
||||
{t('swap:minimum-received')}
|
||||
</p>
|
||||
{outputTokenInfo?.decimals ? (
|
||||
{outputTokenInfo?.decimals &&
|
||||
selectedRoute?.otherAmountThreshold ? (
|
||||
<p className="text-right font-mono text-sm text-th-fgd-2">
|
||||
{formatDecimal(
|
||||
selectedRoute?.otherAmountThreshold /
|
||||
selectedRoute.otherAmountThreshold /
|
||||
10 ** outputTokenInfo.decimals || 1,
|
||||
outputTokenInfo.decimals
|
||||
)}{' '}
|
||||
<span className="font-body tracking-wide">
|
||||
<span className="font-body tracking-wider">
|
||||
{outputTokenInfo?.symbol}
|
||||
</span>
|
||||
</p>
|
||||
|
@ -442,7 +446,7 @@ const SwapReviewRouteInfo = ({
|
|||
</Tooltip>
|
||||
<p className="text-right font-mono text-sm text-th-fgd-2">
|
||||
~{formatFixedDecimals(borrowAmount)}{' '}
|
||||
<span className="font-body tracking-wide">
|
||||
<span className="font-body tracking-wider">
|
||||
{inputTokenInfo?.symbol}
|
||||
</span>
|
||||
</p>
|
||||
|
@ -523,7 +527,7 @@ const SwapReviewRouteInfo = ({
|
|||
.mul(inputBank!.loanOriginationFeeRate.toFixed())
|
||||
.toNumber()
|
||||
)}{' '}
|
||||
<span className="font-body tracking-wide">
|
||||
<span className="font-body tracking-wider">
|
||||
{inputBank!.name}
|
||||
</span>{' '}
|
||||
(
|
||||
|
@ -561,7 +565,7 @@ const SwapReviewRouteInfo = ({
|
|||
info.lpFee?.amount /
|
||||
Math.pow(10, feeToken.decimals)
|
||||
).toFixed(6)}{' '}
|
||||
<span className="font-body tracking-wide">
|
||||
<span className="font-body tracking-wider">
|
||||
{feeToken?.symbol}
|
||||
</span>{' '}
|
||||
(
|
||||
|
@ -591,7 +595,7 @@ const SwapReviewRouteInfo = ({
|
|||
setSelectedRoute={setSelectedRoute}
|
||||
selectedRoute={selectedRoute}
|
||||
routes={routes}
|
||||
inputTokenSymbol={inputTokenInfo!.name}
|
||||
inputTokenSymbol={inputTokenInfo?.name}
|
||||
outputTokenInfo={outputTokenInfo}
|
||||
/>
|
||||
) : null}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { XMarkIcon } from '@heroicons/react/20/solid'
|
||||
import { useTranslation } from 'next-i18next'
|
||||
import { useState } from 'react'
|
||||
import { useEffect, useState } from 'react'
|
||||
import mangoStore from '@store/mangoStore'
|
||||
import ButtonGroup from '../forms/ButtonGroup'
|
||||
import Input from '../forms/Input'
|
||||
|
@ -10,13 +10,19 @@ import Button, { IconButton, LinkButton } from '../shared/Button'
|
|||
const slippagePresets = ['0.1', '0.5', '1', '2']
|
||||
|
||||
const SwapSettings = ({ onClose }: { onClose: () => void }) => {
|
||||
const { t } = useTranslation('common')
|
||||
const { t } = useTranslation(['common', 'settings', 'swap'])
|
||||
const margin = mangoStore((s) => s.swap.margin)
|
||||
const slippage = mangoStore((s) => s.swap.slippage)
|
||||
const set = mangoStore((s) => s.set)
|
||||
|
||||
const [showCustomSlippageForm, setShowCustomSlippageForm] = useState(false)
|
||||
const [inputValue, setInputValue] = useState('')
|
||||
const [inputValue, setInputValue] = useState(slippage.toString())
|
||||
|
||||
useEffect(() => {
|
||||
if (!slippagePresets.includes(slippage.toString())) {
|
||||
setShowCustomSlippageForm(true)
|
||||
}
|
||||
}, [])
|
||||
|
||||
const handleSetMargin = () => {
|
||||
set((s) => {
|
||||
|
@ -39,11 +45,11 @@ const SwapSettings = ({ onClose }: { onClose: () => void }) => {
|
|||
|
||||
<div className="mt-4">
|
||||
<div className="mb-2 flex justify-between">
|
||||
<p className="text-th-fgd-2">{`${t('max')} ${t('swap:slippage')}`}</p>
|
||||
<p className="text-th-fgd-2">{t('swap:max-slippage')}</p>
|
||||
<LinkButton
|
||||
onClick={() => setShowCustomSlippageForm(!showCustomSlippageForm)}
|
||||
>
|
||||
{showCustomSlippageForm ? 'Preset' : t('settings:custom')}
|
||||
{showCustomSlippageForm ? t('swap:preset') : t('settings:custom')}
|
||||
</LinkButton>
|
||||
</div>
|
||||
{showCustomSlippageForm ? (
|
||||
|
|
|
@ -37,11 +37,13 @@ const CustomizedLabel = ({
|
|||
x,
|
||||
y,
|
||||
value,
|
||||
index,
|
||||
}: {
|
||||
chartData: any[]
|
||||
x?: number
|
||||
y?: string | number
|
||||
value?: number
|
||||
index?: number
|
||||
}) => {
|
||||
const { width } = useViewport()
|
||||
const [min, max] = useMemo(() => {
|
||||
|
@ -52,7 +54,13 @@ const CustomizedLabel = ({
|
|||
return ['', '']
|
||||
}, [chartData])
|
||||
|
||||
if (value === min || value === max) {
|
||||
const [minIndex, maxIndex] = useMemo(() => {
|
||||
const minIndex = chartData.findIndex((d) => d.price === min)
|
||||
const maxIndex = chartData.findIndex((d) => d.price === max)
|
||||
return [minIndex, maxIndex]
|
||||
}, [min, max, chartData])
|
||||
|
||||
if (value && (minIndex === index || maxIndex === index)) {
|
||||
return (
|
||||
<Text
|
||||
x={x}
|
||||
|
|
|
@ -22,11 +22,21 @@ const useJupiterSwapData = () => {
|
|||
}
|
||||
}, [inputBank, outputBank, mangoTokens])
|
||||
|
||||
let inputCoingeckoId = inputTokenInfo?.extensions?.coingeckoId
|
||||
let outputCoingeckoId = outputTokenInfo?.extensions?.coingeckoId
|
||||
|
||||
if (inputBank?.name.toLocaleLowerCase() === 'dai') {
|
||||
inputCoingeckoId = 'dai'
|
||||
}
|
||||
if (outputBank?.name.toLocaleLowerCase() === 'dai') {
|
||||
outputCoingeckoId = 'dai'
|
||||
}
|
||||
|
||||
return {
|
||||
inputTokenInfo,
|
||||
inputCoingeckoId: inputTokenInfo?.extensions?.coingeckoId,
|
||||
inputCoingeckoId,
|
||||
outputTokenInfo,
|
||||
outputCoingeckoId: outputTokenInfo?.extensions?.coingeckoId,
|
||||
outputCoingeckoId,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -21,6 +21,8 @@ export const getMaxWithdrawForBank = (
|
|||
)
|
||||
const maxWithdraw = allowBorrow
|
||||
? Decimal.min(vaultBalance, maxBorrow)
|
||||
: bank.initAssetWeight.toNumber() === 0
|
||||
? Decimal.min(accountBalance, vaultBalance)
|
||||
: Decimal.min(accountBalance, vaultBalance, maxBorrow)
|
||||
return Decimal.max(0, maxWithdraw)
|
||||
}
|
||||
|
@ -43,10 +45,10 @@ export const getTokenInMax = (
|
|||
}
|
||||
}
|
||||
|
||||
const inputTokenBalance = floorToDecimal(
|
||||
mangoAccount.getTokenBalanceUi(inputBank),
|
||||
inputBank.mintDecimals
|
||||
const inputTokenBalance = new Decimal(
|
||||
mangoAccount.getTokenBalanceUi(inputBank)
|
||||
)
|
||||
|
||||
const maxAmountWithoutMargin = inputTokenBalance.gt(0)
|
||||
? inputTokenBalance
|
||||
: new Decimal(0)
|
||||
|
|
|
@ -4,6 +4,7 @@ import useMangoGroup from 'hooks/useMangoGroup'
|
|||
import { useTranslation } from 'next-i18next'
|
||||
import dynamic from 'next/dynamic'
|
||||
import { useEffect, useMemo, useState } from 'react'
|
||||
import { numberCompacter } from 'utils/numbers'
|
||||
const DetailedAreaChart = dynamic(
|
||||
() => import('@components/shared/DetailedAreaChart'),
|
||||
{ ssr: false }
|
||||
|
@ -16,7 +17,7 @@ const ChartTabs = ({ token }: { token: string }) => {
|
|||
const tokenStats = mangoStore((s) => s.tokenStats.data)
|
||||
const initialStatsLoad = mangoStore((s) => s.tokenStats.initialLoad)
|
||||
const loadingTokenStats = mangoStore((s) => s.tokenStats.loading)
|
||||
const actions = mangoStore((s) => s.actions)
|
||||
const actions = mangoStore.getState().actions
|
||||
const { group } = useMangoGroup()
|
||||
|
||||
useEffect(() => {
|
||||
|
@ -63,7 +64,7 @@ const ChartTabs = ({ token }: { token: string }) => {
|
|||
// domain={[0, 'dataMax']}
|
||||
loading={loadingTokenStats}
|
||||
small
|
||||
tickFormat={(x) => x.toFixed(2)}
|
||||
tickFormat={(x) => numberCompacter.format(x)}
|
||||
title={`${token} ${t('token:deposits')}`}
|
||||
xKey="date_hour"
|
||||
yKey={'total_deposits'}
|
||||
|
@ -107,7 +108,7 @@ const ChartTabs = ({ token }: { token: string }) => {
|
|||
// domain={[0, 'dataMax']}
|
||||
loading={loadingTokenStats}
|
||||
small
|
||||
tickFormat={(x) => x.toFixed(2)}
|
||||
tickFormat={(x) => numberCompacter.format(x)}
|
||||
title={`${token} ${t('token:borrows')}`}
|
||||
xKey="date_hour"
|
||||
yKey={'total_borrows'}
|
||||
|
|
|
@ -111,32 +111,34 @@ const CoingeckoStats = ({
|
|||
|
||||
return (
|
||||
<>
|
||||
<div className="border-b border-th-bkg-3 py-4 px-6">
|
||||
<h2 className="mb-1 text-xl">About {bank.name}</h2>
|
||||
<div className="flex items-end">
|
||||
<p
|
||||
className={`${
|
||||
showFullDesc ? 'h-full' : 'h-5'
|
||||
} max-w-[720px] overflow-hidden`}
|
||||
ref={descWidthRef}
|
||||
>
|
||||
{parse(coingeckoData.description.en)}
|
||||
</p>
|
||||
{width === 720 ? (
|
||||
<span
|
||||
className="default-transition ml-4 flex cursor-pointer items-end font-normal underline hover:text-th-fgd-2 md:hover:no-underline"
|
||||
onClick={() => setShowFullDesc(!showFullDesc)}
|
||||
{coingeckoData?.description?.en?.length ? (
|
||||
<div className="border-b border-th-bkg-3 py-4 px-6">
|
||||
<h2 className="mb-1 text-xl">About {bank.name}</h2>
|
||||
<div className="flex items-end">
|
||||
<p
|
||||
className={`${
|
||||
showFullDesc ? 'h-full' : 'h-5'
|
||||
} max-w-[720px] overflow-hidden`}
|
||||
ref={descWidthRef}
|
||||
>
|
||||
{showFullDesc ? 'Less' : 'More'}
|
||||
<ArrowSmallUpIcon
|
||||
className={`h-5 w-5 ${
|
||||
showFullDesc ? 'rotate-360' : 'rotate-180'
|
||||
} default-transition`}
|
||||
/>
|
||||
</span>
|
||||
) : null}
|
||||
{parse(coingeckoData.description.en)}
|
||||
</p>
|
||||
{width === 720 ? (
|
||||
<span
|
||||
className="default-transition ml-4 flex cursor-pointer items-end font-normal underline hover:text-th-fgd-2 md:hover:no-underline"
|
||||
onClick={() => setShowFullDesc(!showFullDesc)}
|
||||
>
|
||||
{showFullDesc ? 'Less' : 'More'}
|
||||
<ArrowSmallUpIcon
|
||||
className={`h-5 w-5 ${
|
||||
showFullDesc ? 'rotate-360' : 'rotate-180'
|
||||
} default-transition`}
|
||||
/>
|
||||
</span>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
{!loadingChart ? (
|
||||
coingeckoTokenPrices.length ? (
|
||||
<>
|
||||
|
|
|
@ -3,6 +3,7 @@ import { useTheme } from 'next-themes'
|
|||
import { useMemo } from 'react'
|
||||
import { Area, AreaChart, ResponsiveContainer, XAxis, YAxis } from 'recharts'
|
||||
import { COLORS } from 'styles/colors'
|
||||
import { formatFixedDecimals } from 'utils/numbers'
|
||||
|
||||
const PriceChart = ({
|
||||
prices,
|
||||
|
@ -70,7 +71,7 @@ const PriceChart = ({
|
|||
fill: 'var(--fgd-4)',
|
||||
fontSize: 10,
|
||||
}}
|
||||
tickFormatter={(x) => `$${x.toFixed(2)}`}
|
||||
tickFormatter={(x) => formatFixedDecimals(x, true)}
|
||||
tickLine={false}
|
||||
/>
|
||||
</AreaChart>
|
||||
|
|
|
@ -90,8 +90,8 @@ const AccountOnboardingTour = () => {
|
|||
},
|
||||
{
|
||||
selector: '#account-step-eleven',
|
||||
title: t('health-check'),
|
||||
description: t('health-check-desc'),
|
||||
title: t('account-summary'),
|
||||
description: t('account-summary-desc'),
|
||||
orientationPreferences: [CardinalOrientation.EASTSOUTH],
|
||||
movingTarget: true,
|
||||
},
|
||||
|
|
|
@ -17,7 +17,7 @@ const CustomTooltip = ({
|
|||
const { title, description } = tourLogic!.stepContent
|
||||
const { next, prev, close, allSteps, stepIndex } = tourLogic!
|
||||
const { publicKey } = useWallet()
|
||||
const actions = mangoStore((s) => s.actions)
|
||||
const actions = mangoStore.getState().actions
|
||||
const tourSettings = mangoStore((s) => s.settings.tours)
|
||||
const [loading, setLoading] = useState(false)
|
||||
|
||||
|
|
|
@ -22,7 +22,7 @@ import { notify } from 'utils/notifications'
|
|||
import SpotSlider from './SpotSlider'
|
||||
import { calculateLimitPriceForMarketOrder } from 'utils/tradeForm'
|
||||
import Image from 'next/legacy/image'
|
||||
import { QuestionMarkCircleIcon } from '@heroicons/react/20/solid'
|
||||
import { LinkIcon, QuestionMarkCircleIcon } from '@heroicons/react/20/solid'
|
||||
import Loading from '@components/shared/Loading'
|
||||
import TabUnderline from '@components/shared/TabUnderline'
|
||||
import PerpSlider from './PerpSlider'
|
||||
|
@ -42,6 +42,8 @@ import useMangoAccount from 'hooks/useMangoAccount'
|
|||
import MaxSizeButton from './MaxSizeButton'
|
||||
import { INITIAL_SOUND_SETTINGS } from '@components/settings/SoundSettings'
|
||||
import { Howl } from 'howler'
|
||||
import { useWallet } from '@solana/wallet-adapter-react'
|
||||
import { useEnhancedWallet } from '@components/wallet/EnhancedWalletProvider'
|
||||
|
||||
const set = mangoStore.getState().set
|
||||
|
||||
|
@ -64,6 +66,8 @@ const AdvancedTradeForm = () => {
|
|||
SOUND_SETTINGS_KEY,
|
||||
INITIAL_SOUND_SETTINGS
|
||||
)
|
||||
const { connected } = useWallet()
|
||||
const { handleConnect } = useEnhancedWallet()
|
||||
|
||||
const baseSymbol = useMemo(() => {
|
||||
return selectedMarket?.name.split(/-|\//)[0]
|
||||
|
@ -188,9 +192,9 @@ const AdvancedTradeForm = () => {
|
|||
})
|
||||
}, [])
|
||||
|
||||
const tickDecimals = useMemo(() => {
|
||||
const [tickDecimals, tickSize] = useMemo(() => {
|
||||
const group = mangoStore.getState().group
|
||||
if (!group || !selectedMarket) return 1
|
||||
if (!group || !selectedMarket) return [1, 0.1]
|
||||
let tickSize: number
|
||||
if (selectedMarket instanceof Serum3Market) {
|
||||
const market = group.getSerum3ExternalMarket(
|
||||
|
@ -200,7 +204,8 @@ const AdvancedTradeForm = () => {
|
|||
} else {
|
||||
tickSize = selectedMarket.tickSize
|
||||
}
|
||||
return getDecimalCount(tickSize)
|
||||
const tickDecimals = getDecimalCount(tickSize)
|
||||
return [tickDecimals, tickSize]
|
||||
}, [selectedMarket])
|
||||
|
||||
/*
|
||||
|
@ -337,19 +342,20 @@ const AdvancedTradeForm = () => {
|
|||
}
|
||||
}, [])
|
||||
|
||||
const minOrderDecimals = useMemo(() => {
|
||||
const [minOrderDecimals, minOrderSize] = useMemo(() => {
|
||||
const group = mangoStore.getState().group
|
||||
if (!group || !selectedMarket) return 1
|
||||
let minOrderDecimals = 1
|
||||
if (!group || !selectedMarket) return [1, 0.1]
|
||||
let minOrderSize: number
|
||||
if (selectedMarket instanceof Serum3Market) {
|
||||
const market = group.getSerum3ExternalMarket(
|
||||
selectedMarket.serumMarketExternal
|
||||
)
|
||||
minOrderDecimals = getDecimalCount(market.minOrderSize)
|
||||
minOrderSize = market.minOrderSize
|
||||
} else {
|
||||
minOrderDecimals = getDecimalCount(selectedMarket.minOrderSize)
|
||||
minOrderSize = selectedMarket.minOrderSize
|
||||
}
|
||||
return minOrderDecimals
|
||||
const minOrderDecimals = getDecimalCount(minOrderSize)
|
||||
return [minOrderDecimals, minOrderSize]
|
||||
}, [selectedMarket])
|
||||
|
||||
return (
|
||||
|
@ -363,7 +369,7 @@ const AdvancedTradeForm = () => {
|
|||
/>
|
||||
</div>
|
||||
<div className="px-3 md:px-4">
|
||||
<SolBalanceWarnings />
|
||||
<SolBalanceWarnings className="mt-4" />
|
||||
</div>
|
||||
<div className="mt-1 px-2 md:mt-3 md:px-4">
|
||||
<p className="mb-2 text-xs">{t('trade:order-type')}</p>
|
||||
|
@ -478,6 +484,7 @@ const AdvancedTradeForm = () => {
|
|||
<SpotSlider
|
||||
minOrderDecimals={minOrderDecimals}
|
||||
tickDecimals={tickDecimals}
|
||||
step={tradeForm.side === 'buy' ? tickSize : minOrderSize}
|
||||
/>
|
||||
) : (
|
||||
<SpotButtonGroup
|
||||
|
@ -554,16 +561,23 @@ const AdvancedTradeForm = () => {
|
|||
<div className="mt-6 mb-4 flex px-3 md:px-4">
|
||||
{ipAllowed ? (
|
||||
<Button
|
||||
onClick={handlePlaceOrder}
|
||||
className={`flex w-full items-center justify-center text-white ${
|
||||
tradeForm.side === 'buy'
|
||||
? 'bg-th-up-dark md:hover:bg-th-up'
|
||||
: 'bg-th-down-dark md:hover:bg-th-down'
|
||||
onClick={connected ? handlePlaceOrder : handleConnect}
|
||||
className={`flex w-full items-center justify-center ${
|
||||
!connected
|
||||
? ''
|
||||
: tradeForm.side === 'buy'
|
||||
? 'bg-th-up-dark text-white md:hover:bg-th-up'
|
||||
: 'bg-th-down-dark text-white md:hover:bg-th-down'
|
||||
}`}
|
||||
disabled={!tradeForm.baseSize}
|
||||
disabled={connected && !tradeForm.baseSize}
|
||||
size="large"
|
||||
>
|
||||
{!placingOrder ? (
|
||||
{!connected ? (
|
||||
<div className="flex items-center">
|
||||
<LinkIcon className="mr-2 h-5 w-5" />
|
||||
{t('connect')}
|
||||
</div>
|
||||
) : !placingOrder ? (
|
||||
<span className="capitalize">
|
||||
{t('trade:place-order', { side: tradeForm.side })}
|
||||
</span>
|
||||
|
@ -575,17 +589,11 @@ const AdvancedTradeForm = () => {
|
|||
)}
|
||||
</Button>
|
||||
) : (
|
||||
<div className="flex-grow">
|
||||
<div className="flex">
|
||||
<Button disabled className="flex-grow">
|
||||
<span>
|
||||
{t('country-not-allowed', {
|
||||
country: ipCountry ? `(${ipCountry})` : '(Unknown)',
|
||||
})}
|
||||
</span>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<Button disabled className="w-full leading-tight" size="large">
|
||||
{t('country-not-allowed', {
|
||||
country: ipCountry ? `(${ipCountry})` : '',
|
||||
})}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
<TradeSummary mangoAccount={mangoAccount} />
|
||||
|
|
|
@ -6,7 +6,7 @@ import useMangoAccount from 'hooks/useMangoAccount'
|
|||
import useSelectedMarket from 'hooks/useSelectedMarket'
|
||||
import { useTranslation } from 'next-i18next'
|
||||
import { useCallback, useMemo } from 'react'
|
||||
import { trimDecimals } from 'utils/numbers'
|
||||
import { floorToDecimal } from 'utils/numbers'
|
||||
|
||||
const MaxSizeButton = ({
|
||||
minOrderDecimals,
|
||||
|
@ -18,7 +18,7 @@ const MaxSizeButton = ({
|
|||
const { t } = useTranslation(['common', 'trade'])
|
||||
const { mangoAccount } = useMangoAccount()
|
||||
const { selectedMarket, price: oraclePrice } = useSelectedMarket()
|
||||
const tradeForm = mangoStore((s) => s.tradeForm)
|
||||
const { price, side, tradeType } = mangoStore((s) => s.tradeForm)
|
||||
|
||||
const leverageMax = useMemo(() => {
|
||||
const group = mangoStore.getState().group
|
||||
|
@ -26,7 +26,7 @@ const MaxSizeButton = ({
|
|||
|
||||
try {
|
||||
if (selectedMarket instanceof Serum3Market) {
|
||||
if (tradeForm.side === 'buy') {
|
||||
if (side === 'buy') {
|
||||
return mangoAccount.getMaxQuoteForSerum3BidUi(
|
||||
group,
|
||||
selectedMarket.serumMarketExternal
|
||||
|
@ -38,7 +38,7 @@ const MaxSizeButton = ({
|
|||
)
|
||||
}
|
||||
} else {
|
||||
if (tradeForm.side === 'buy') {
|
||||
if (side === 'buy') {
|
||||
return mangoAccount.getMaxQuoteForPerpBidUi(
|
||||
group,
|
||||
selectedMarket.perpMarketIndex
|
||||
|
@ -54,65 +54,60 @@ const MaxSizeButton = ({
|
|||
console.error('Error calculating max leverage: spot btn group: ', e)
|
||||
return 0
|
||||
}
|
||||
}, [mangoAccount, tradeForm.side, selectedMarket])
|
||||
}, [mangoAccount, side, selectedMarket])
|
||||
|
||||
const handleMax = useCallback(() => {
|
||||
const set = mangoStore.getState().set
|
||||
set((state) => {
|
||||
if (tradeForm.side === 'buy') {
|
||||
state.tradeForm.quoteSize = trimDecimals(
|
||||
if (side === 'buy') {
|
||||
state.tradeForm.quoteSize = floorToDecimal(
|
||||
leverageMax,
|
||||
tickDecimals
|
||||
).toFixed(tickDecimals)
|
||||
if (tradeForm.tradeType === 'Market' || !tradeForm.price) {
|
||||
state.tradeForm.baseSize = trimDecimals(
|
||||
).toFixed()
|
||||
if (tradeType === 'Market' || !price) {
|
||||
state.tradeForm.baseSize = floorToDecimal(
|
||||
leverageMax / oraclePrice,
|
||||
minOrderDecimals
|
||||
).toFixed(minOrderDecimals)
|
||||
).toFixed()
|
||||
} else {
|
||||
state.tradeForm.baseSize = trimDecimals(
|
||||
leverageMax / parseFloat(tradeForm.price),
|
||||
state.tradeForm.baseSize = floorToDecimal(
|
||||
leverageMax / parseFloat(price),
|
||||
minOrderDecimals
|
||||
).toFixed(minOrderDecimals)
|
||||
).toFixed()
|
||||
}
|
||||
} else {
|
||||
state.tradeForm.baseSize = trimDecimals(
|
||||
state.tradeForm.baseSize = floorToDecimal(
|
||||
leverageMax,
|
||||
tickDecimals
|
||||
).toFixed(tickDecimals)
|
||||
if (tradeForm.tradeType === 'Market' || !tradeForm.price) {
|
||||
state.tradeForm.quoteSize = trimDecimals(
|
||||
).toFixed()
|
||||
if (tradeType === 'Market' || !price) {
|
||||
state.tradeForm.quoteSize = floorToDecimal(
|
||||
leverageMax * oraclePrice,
|
||||
minOrderDecimals
|
||||
).toFixed(minOrderDecimals)
|
||||
).toFixed()
|
||||
} else {
|
||||
state.tradeForm.quoteSize = trimDecimals(
|
||||
leverageMax * parseFloat(tradeForm.price),
|
||||
state.tradeForm.quoteSize = floorToDecimal(
|
||||
leverageMax * parseFloat(price),
|
||||
minOrderDecimals
|
||||
).toFixed(minOrderDecimals)
|
||||
).toFixed()
|
||||
}
|
||||
}
|
||||
})
|
||||
}, [leverageMax, tradeForm])
|
||||
}, [leverageMax, price, side, tradeType])
|
||||
|
||||
const maxAmount = useMemo(() => {
|
||||
if (!tradeForm.price) return '0'
|
||||
if (tradeForm.side === 'buy') {
|
||||
return trimDecimals(
|
||||
leverageMax / parseFloat(tradeForm.price),
|
||||
tickDecimals
|
||||
).toFixed(tickDecimals)
|
||||
const tradePrice = tradeType === 'Market' ? oraclePrice : Number(price)
|
||||
if (side === 'buy') {
|
||||
return floorToDecimal(leverageMax / tradePrice, tickDecimals).toFixed()
|
||||
} else {
|
||||
return trimDecimals(leverageMax, minOrderDecimals).toFixed(
|
||||
minOrderDecimals
|
||||
)
|
||||
return floorToDecimal(leverageMax, minOrderDecimals).toFixed()
|
||||
}
|
||||
}, [leverageMax, minOrderDecimals, tickDecimals, tradeForm])
|
||||
}, [leverageMax, minOrderDecimals, tickDecimals, price, side, tradeType])
|
||||
|
||||
return (
|
||||
<div className="mb-2 mt-3 flex items-center justify-between">
|
||||
<p className="text-xs text-th-fgd-3">{t('trade:size')}</p>
|
||||
<FadeInFadeOut show={!!tradeForm.price}>
|
||||
<FadeInFadeOut show={!!price}>
|
||||
<MaxAmountButton
|
||||
className="text-xs"
|
||||
label={t('max')}
|
||||
|
|
|
@ -15,7 +15,6 @@ import { Table, Td, Th, TrBody, TrHead } from '@components/shared/TableElements'
|
|||
import Tooltip from '@components/shared/Tooltip'
|
||||
import {
|
||||
CheckIcon,
|
||||
LinkIcon,
|
||||
NoSymbolIcon,
|
||||
PencilIcon,
|
||||
TrashIcon,
|
||||
|
@ -25,6 +24,7 @@ import { Order } from '@project-serum/serum/lib/market'
|
|||
import { useWallet } from '@solana/wallet-adapter-react'
|
||||
import { PublicKey } from '@solana/web3.js'
|
||||
import mangoStore from '@store/mangoStore'
|
||||
import useMangoAccount from 'hooks/useMangoAccount'
|
||||
import { useViewport } from 'hooks/useViewport'
|
||||
import { useTranslation } from 'next-i18next'
|
||||
import { ChangeEvent, useCallback, useState } from 'react'
|
||||
|
@ -35,7 +35,6 @@ import TableMarketName from './TableMarketName'
|
|||
|
||||
const OpenOrders = () => {
|
||||
const { t } = useTranslation(['common', 'trade'])
|
||||
const { connected } = useWallet()
|
||||
const openOrders = mangoStore((s) => s.mangoAccount.openOrders)
|
||||
const [cancelId, setCancelId] = useState<string>('')
|
||||
const [modifyOrderId, setModifyOrderId] = useState<string | undefined>(
|
||||
|
@ -46,6 +45,8 @@ const OpenOrders = () => {
|
|||
const [modifiedOrderPrice, setModifiedOrderPrice] = useState('')
|
||||
const { width } = useViewport()
|
||||
const showTableView = width ? width > breakpoints.md : false
|
||||
const { mangoAccount } = useMangoAccount()
|
||||
const { connected } = useWallet()
|
||||
|
||||
const findSerum3MarketPkInOpenOrders = (o: Order): string | undefined => {
|
||||
const openOrders = mangoStore.getState().mangoAccount.openOrders
|
||||
|
@ -224,110 +225,108 @@ const OpenOrders = () => {
|
|||
setModifiedOrderPrice('')
|
||||
}
|
||||
|
||||
return connected ? (
|
||||
Object.values(openOrders).flat().length ? (
|
||||
showTableView ? (
|
||||
<Table>
|
||||
<thead>
|
||||
<TrHead>
|
||||
<Th className="w-[16.67%] text-left">{t('market')}</Th>
|
||||
<Th className="w-[16.67%] text-right">{t('trade:side')}</Th>
|
||||
<Th className="w-[16.67%] text-right">{t('trade:size')}</Th>
|
||||
<Th className="w-[16.67%] text-right">{t('price')}</Th>
|
||||
<Th className="w-[16.67%] text-right">{t('value')}</Th>
|
||||
<Th className="w-[16.67%] text-right"></Th>
|
||||
</TrHead>
|
||||
</thead>
|
||||
<tbody>
|
||||
{Object.entries(openOrders)
|
||||
.map(([marketPk, orders]) => {
|
||||
return orders.map((o) => {
|
||||
const group = mangoStore.getState().group!
|
||||
let market: PerpMarket | Serum3Market
|
||||
let tickSize: number
|
||||
let minOrderSize: number
|
||||
let quoteSymbol
|
||||
if (o instanceof PerpOrder) {
|
||||
market = group.getPerpMarketByMarketIndex(o.perpMarketIndex)
|
||||
quoteSymbol = group.getFirstBankByTokenIndex(
|
||||
market.settleTokenIndex
|
||||
).name
|
||||
tickSize = market.tickSize
|
||||
minOrderSize = market.minOrderSize
|
||||
} else {
|
||||
market = group.getSerum3MarketByExternalMarket(
|
||||
new PublicKey(marketPk)
|
||||
)
|
||||
quoteSymbol = group.getFirstBankByTokenIndex(
|
||||
market!.quoteTokenIndex
|
||||
).name
|
||||
const serumMarket = group.getSerum3ExternalMarket(
|
||||
market.serumMarketExternal
|
||||
)
|
||||
tickSize = serumMarket.tickSize
|
||||
minOrderSize = serumMarket.minOrderSize
|
||||
}
|
||||
return (
|
||||
<TrBody
|
||||
key={`${o.side}${o.size}${o.price}`}
|
||||
className="my-1 p-2"
|
||||
>
|
||||
<Td className="w-[16.67%]">
|
||||
<TableMarketName market={market} />
|
||||
</Td>
|
||||
<Td className="w-[16.67%] text-right">
|
||||
<SideBadge side={o.side} />
|
||||
</Td>
|
||||
{modifyOrderId !== o.orderId.toString() ? (
|
||||
<>
|
||||
<Td className="w-[16.67%] text-right font-mono">
|
||||
{o.size.toLocaleString(undefined, {
|
||||
maximumFractionDigits:
|
||||
getDecimalCount(minOrderSize),
|
||||
})}
|
||||
</Td>
|
||||
<Td className="w-[16.67%] whitespace-nowrap text-right">
|
||||
<span className="font-mono">
|
||||
{o.price.toLocaleString(undefined, {
|
||||
minimumFractionDigits:
|
||||
getDecimalCount(tickSize),
|
||||
maximumFractionDigits:
|
||||
getDecimalCount(tickSize),
|
||||
})}{' '}
|
||||
<span className="font-body tracking-wide text-th-fgd-4">
|
||||
{quoteSymbol}
|
||||
</span>
|
||||
return mangoAccount && Object.values(openOrders).flat().length ? (
|
||||
showTableView ? (
|
||||
<Table>
|
||||
<thead>
|
||||
<TrHead>
|
||||
<Th className="w-[16.67%] text-left">{t('market')}</Th>
|
||||
<Th className="w-[16.67%] text-right">{t('trade:side')}</Th>
|
||||
<Th className="w-[16.67%] text-right">{t('trade:size')}</Th>
|
||||
<Th className="w-[16.67%] text-right">{t('price')}</Th>
|
||||
<Th className="w-[16.67%] text-right">{t('value')}</Th>
|
||||
{connected ? <Th className="w-[16.67%] text-right"></Th> : null}
|
||||
</TrHead>
|
||||
</thead>
|
||||
<tbody>
|
||||
{Object.entries(openOrders)
|
||||
.map(([marketPk, orders]) => {
|
||||
return orders.map((o) => {
|
||||
const group = mangoStore.getState().group!
|
||||
let market: PerpMarket | Serum3Market
|
||||
let tickSize: number
|
||||
let minOrderSize: number
|
||||
let quoteSymbol
|
||||
if (o instanceof PerpOrder) {
|
||||
market = group.getPerpMarketByMarketIndex(o.perpMarketIndex)
|
||||
quoteSymbol = group.getFirstBankByTokenIndex(
|
||||
market.settleTokenIndex
|
||||
).name
|
||||
tickSize = market.tickSize
|
||||
minOrderSize = market.minOrderSize
|
||||
} else {
|
||||
market = group.getSerum3MarketByExternalMarket(
|
||||
new PublicKey(marketPk)
|
||||
)
|
||||
quoteSymbol = group.getFirstBankByTokenIndex(
|
||||
market!.quoteTokenIndex
|
||||
).name
|
||||
const serumMarket = group.getSerum3ExternalMarket(
|
||||
market.serumMarketExternal
|
||||
)
|
||||
tickSize = serumMarket.tickSize
|
||||
minOrderSize = serumMarket.minOrderSize
|
||||
}
|
||||
return (
|
||||
<TrBody
|
||||
key={`${o.side}${o.size}${o.price}`}
|
||||
className="my-1 p-2"
|
||||
>
|
||||
<Td className="w-[16.67%]">
|
||||
<TableMarketName market={market} />
|
||||
</Td>
|
||||
<Td className="w-[16.67%] text-right">
|
||||
<SideBadge side={o.side} />
|
||||
</Td>
|
||||
{modifyOrderId !== o.orderId.toString() ? (
|
||||
<>
|
||||
<Td className="w-[16.67%] text-right font-mono">
|
||||
{o.size.toLocaleString(undefined, {
|
||||
maximumFractionDigits:
|
||||
getDecimalCount(minOrderSize),
|
||||
})}
|
||||
</Td>
|
||||
<Td className="w-[16.67%] whitespace-nowrap text-right">
|
||||
<span className="font-mono">
|
||||
{o.price.toLocaleString(undefined, {
|
||||
minimumFractionDigits: getDecimalCount(tickSize),
|
||||
maximumFractionDigits: getDecimalCount(tickSize),
|
||||
})}{' '}
|
||||
<span className="font-body text-th-fgd-4">
|
||||
{quoteSymbol}
|
||||
</span>
|
||||
</Td>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Td className="w-[16.67%]">
|
||||
<Input
|
||||
className="default-transition h-7 w-full rounded-none border-b-2 border-l-0 border-r-0 border-t-0 border-th-bkg-4 bg-transparent px-0 text-right font-mono hover:border-th-fgd-3 focus:border-th-active focus:outline-none"
|
||||
type="text"
|
||||
value={modifiedOrderSize}
|
||||
onChange={(e: ChangeEvent<HTMLInputElement>) =>
|
||||
setModifiedOrderSize(e.target.value)
|
||||
}
|
||||
/>
|
||||
</Td>
|
||||
<Td className="w-[16.67%]">
|
||||
<Input
|
||||
autoFocus
|
||||
className="default-transition h-7 w-full rounded-none border-b-2 border-l-0 border-r-0 border-t-0 border-th-bkg-4 bg-transparent px-0 text-right font-mono hover:border-th-fgd-3 focus:border-th-active focus:outline-none"
|
||||
type="text"
|
||||
value={modifiedOrderPrice}
|
||||
onChange={(e: ChangeEvent<HTMLInputElement>) =>
|
||||
setModifiedOrderPrice(e.target.value)
|
||||
}
|
||||
/>
|
||||
</Td>
|
||||
</>
|
||||
)}
|
||||
<Td className="w-[16.67%] text-right">
|
||||
{formatFixedDecimals(o.size * o.price, true)}
|
||||
</Td>
|
||||
</span>
|
||||
</Td>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Td className="w-[16.67%]">
|
||||
<Input
|
||||
className="default-transition h-7 w-full rounded-none border-b-2 border-l-0 border-r-0 border-t-0 border-th-bkg-4 bg-transparent px-0 text-right font-mono hover:border-th-fgd-3 focus:border-th-active focus:outline-none"
|
||||
type="text"
|
||||
value={modifiedOrderSize}
|
||||
onChange={(e: ChangeEvent<HTMLInputElement>) =>
|
||||
setModifiedOrderSize(e.target.value)
|
||||
}
|
||||
/>
|
||||
</Td>
|
||||
<Td className="w-[16.67%]">
|
||||
<Input
|
||||
autoFocus
|
||||
className="default-transition h-7 w-full rounded-none border-b-2 border-l-0 border-r-0 border-t-0 border-th-bkg-4 bg-transparent px-0 text-right font-mono hover:border-th-fgd-3 focus:border-th-active focus:outline-none"
|
||||
type="text"
|
||||
value={modifiedOrderPrice}
|
||||
onChange={(e: ChangeEvent<HTMLInputElement>) =>
|
||||
setModifiedOrderPrice(e.target.value)
|
||||
}
|
||||
/>
|
||||
</Td>
|
||||
</>
|
||||
)}
|
||||
<Td className="w-[16.67%] text-right">
|
||||
{formatFixedDecimals(o.size * o.price, true, true)}
|
||||
</Td>
|
||||
{connected ? (
|
||||
<Td className="w-[16.67%]">
|
||||
<div className="flex justify-end space-x-2">
|
||||
{modifyOrderId !== o.orderId.toString() ? (
|
||||
|
@ -378,101 +377,103 @@ const OpenOrders = () => {
|
|||
)}
|
||||
</div>
|
||||
</Td>
|
||||
</TrBody>
|
||||
)
|
||||
})
|
||||
) : null}
|
||||
</TrBody>
|
||||
)
|
||||
})
|
||||
.flat()}
|
||||
</tbody>
|
||||
</Table>
|
||||
) : (
|
||||
<div>
|
||||
{Object.entries(openOrders).map(([marketPk, orders]) => {
|
||||
return orders.map((o) => {
|
||||
const group = mangoStore.getState().group!
|
||||
let market: PerpMarket | Serum3Market
|
||||
let tickSize: number
|
||||
let minOrderSize: number
|
||||
let quoteSymbol: string
|
||||
let baseSymbol: string
|
||||
if (o instanceof PerpOrder) {
|
||||
market = group.getPerpMarketByMarketIndex(o.perpMarketIndex)
|
||||
baseSymbol = market.name.split('-')[0]
|
||||
quoteSymbol = group.getFirstBankByTokenIndex(
|
||||
market.settleTokenIndex
|
||||
).name
|
||||
tickSize = market.tickSize
|
||||
minOrderSize = market.minOrderSize
|
||||
} else {
|
||||
market = group.getSerum3MarketByExternalMarket(
|
||||
new PublicKey(marketPk)
|
||||
)
|
||||
baseSymbol = market.name.split('/')[0]
|
||||
quoteSymbol = group.getFirstBankByTokenIndex(
|
||||
market!.quoteTokenIndex
|
||||
).name
|
||||
const serumMarket = group.getSerum3ExternalMarket(
|
||||
market.serumMarketExternal
|
||||
)
|
||||
tickSize = serumMarket.tickSize
|
||||
minOrderSize = serumMarket.minOrderSize
|
||||
}
|
||||
return (
|
||||
<div
|
||||
className="flex items-center justify-between border-b border-th-bkg-3 p-4"
|
||||
key={`${o.side}${o.size}${o.price}`}
|
||||
>
|
||||
<div>
|
||||
<TableMarketName market={market} />
|
||||
{modifyOrderId !== o.orderId.toString() ? (
|
||||
<div className="mt-1 flex items-center space-x-1">
|
||||
<SideBadge side={o.side} />
|
||||
<p className="text-th-fgd-4">
|
||||
<span className="font-mono text-th-fgd-3">
|
||||
{o.size.toLocaleString(undefined, {
|
||||
maximumFractionDigits:
|
||||
getDecimalCount(minOrderSize),
|
||||
})}
|
||||
</span>{' '}
|
||||
{baseSymbol}
|
||||
{' for '}
|
||||
<span className="font-mono text-th-fgd-3">
|
||||
{o.price.toLocaleString(undefined, {
|
||||
minimumFractionDigits: getDecimalCount(tickSize),
|
||||
maximumFractionDigits: getDecimalCount(tickSize),
|
||||
})}
|
||||
</span>{' '}
|
||||
{quoteSymbol}
|
||||
</p>
|
||||
})
|
||||
.flat()}
|
||||
</tbody>
|
||||
</Table>
|
||||
) : (
|
||||
<div>
|
||||
{Object.entries(openOrders).map(([marketPk, orders]) => {
|
||||
return orders.map((o) => {
|
||||
const group = mangoStore.getState().group!
|
||||
let market: PerpMarket | Serum3Market
|
||||
let tickSize: number
|
||||
let minOrderSize: number
|
||||
let quoteSymbol: string
|
||||
let baseSymbol: string
|
||||
if (o instanceof PerpOrder) {
|
||||
market = group.getPerpMarketByMarketIndex(o.perpMarketIndex)
|
||||
baseSymbol = market.name.split('-')[0]
|
||||
quoteSymbol = group.getFirstBankByTokenIndex(
|
||||
market.settleTokenIndex
|
||||
).name
|
||||
tickSize = market.tickSize
|
||||
minOrderSize = market.minOrderSize
|
||||
} else {
|
||||
market = group.getSerum3MarketByExternalMarket(
|
||||
new PublicKey(marketPk)
|
||||
)
|
||||
baseSymbol = market.name.split('/')[0]
|
||||
quoteSymbol = group.getFirstBankByTokenIndex(
|
||||
market!.quoteTokenIndex
|
||||
).name
|
||||
const serumMarket = group.getSerum3ExternalMarket(
|
||||
market.serumMarketExternal
|
||||
)
|
||||
tickSize = serumMarket.tickSize
|
||||
minOrderSize = serumMarket.minOrderSize
|
||||
}
|
||||
return (
|
||||
<div
|
||||
className="flex items-center justify-between border-b border-th-bkg-3 p-4"
|
||||
key={`${o.side}${o.size}${o.price}`}
|
||||
>
|
||||
<div>
|
||||
<TableMarketName market={market} />
|
||||
{modifyOrderId !== o.orderId.toString() ? (
|
||||
<div className="mt-1 flex items-center space-x-1">
|
||||
<SideBadge side={o.side} />
|
||||
<p className="text-th-fgd-4">
|
||||
<span className="font-mono text-th-fgd-3">
|
||||
{o.size.toLocaleString(undefined, {
|
||||
maximumFractionDigits:
|
||||
getDecimalCount(minOrderSize),
|
||||
})}
|
||||
</span>{' '}
|
||||
{baseSymbol}
|
||||
{' for '}
|
||||
<span className="font-mono text-th-fgd-3">
|
||||
{o.price.toLocaleString(undefined, {
|
||||
minimumFractionDigits: getDecimalCount(tickSize),
|
||||
maximumFractionDigits: getDecimalCount(tickSize),
|
||||
})}
|
||||
</span>{' '}
|
||||
{quoteSymbol}
|
||||
</p>
|
||||
</div>
|
||||
) : (
|
||||
<div className="mt-2 flex space-x-4">
|
||||
<div>
|
||||
<p className="text-xs">{t('trade:size')}</p>
|
||||
<Input
|
||||
className="default-transition h-7 w-full rounded-none border-b-2 border-l-0 border-r-0 border-t-0 border-th-bkg-4 bg-transparent px-0 text-right font-mono hover:border-th-fgd-3 focus:border-th-active focus:outline-none"
|
||||
type="text"
|
||||
value={modifiedOrderSize}
|
||||
onChange={(e: ChangeEvent<HTMLInputElement>) =>
|
||||
setModifiedOrderSize(e.target.value)
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<div className="mt-2 flex space-x-4">
|
||||
<div>
|
||||
<p className="text-xs">{t('trade:size')}</p>
|
||||
<Input
|
||||
className="default-transition h-7 w-full rounded-none border-b-2 border-l-0 border-r-0 border-t-0 border-th-bkg-4 bg-transparent px-0 text-right font-mono hover:border-th-fgd-3 focus:border-th-active focus:outline-none"
|
||||
type="text"
|
||||
value={modifiedOrderSize}
|
||||
onChange={(e: ChangeEvent<HTMLInputElement>) =>
|
||||
setModifiedOrderSize(e.target.value)
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-xs">{t('price')}</p>
|
||||
<Input
|
||||
autoFocus
|
||||
className="default-transition h-7 w-full rounded-none border-b-2 border-l-0 border-r-0 border-t-0 border-th-bkg-4 bg-transparent px-0 text-right font-mono hover:border-th-fgd-3 focus:border-th-active focus:outline-none"
|
||||
type="text"
|
||||
value={modifiedOrderPrice}
|
||||
onChange={(e: ChangeEvent<HTMLInputElement>) =>
|
||||
setModifiedOrderPrice(e.target.value)
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-xs">{t('price')}</p>
|
||||
<Input
|
||||
autoFocus
|
||||
className="default-transition h-7 w-full rounded-none border-b-2 border-l-0 border-r-0 border-t-0 border-th-bkg-4 bg-transparent px-0 text-right font-mono hover:border-th-fgd-3 focus:border-th-active focus:outline-none"
|
||||
type="text"
|
||||
value={modifiedOrderPrice}
|
||||
onChange={(e: ChangeEvent<HTMLInputElement>) =>
|
||||
setModifiedOrderPrice(e.target.value)
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{connected ? (
|
||||
<div className="flex items-center space-x-3 pl-8">
|
||||
<div className="flex items-center space-x-2">
|
||||
{modifyOrderId !== o.orderId.toString() ? (
|
||||
|
@ -513,22 +514,17 @@ const OpenOrders = () => {
|
|||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
})
|
||||
})}
|
||||
</div>
|
||||
)
|
||||
) : (
|
||||
<div className="flex flex-col items-center p-8">
|
||||
<NoSymbolIcon className="mb-2 h-6 w-6 text-th-fgd-4" />
|
||||
<p>{t('trade:no-orders')}</p>
|
||||
) : null}
|
||||
</div>
|
||||
)
|
||||
})
|
||||
})}
|
||||
</div>
|
||||
)
|
||||
) : (
|
||||
<div className="flex flex-col items-center p-8">
|
||||
<LinkIcon className="mb-2 h-6 w-6 text-th-fgd-4" />
|
||||
<p>{t('trade:connect-orders')}</p>
|
||||
<NoSymbolIcon className="mb-2 h-6 w-6 text-th-fgd-4" />
|
||||
<p>{t('trade:no-orders')}</p>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -24,6 +24,8 @@ import {
|
|||
} from '@blockworks-foundation/mango-v4'
|
||||
import useSelectedMarket from 'hooks/useSelectedMarket'
|
||||
import { INITIAL_ANIMATION_SETTINGS } from '@components/settings/AnimationSettings'
|
||||
import { ArrowPathIcon } from '@heroicons/react/20/solid'
|
||||
import { sleep } from 'utils'
|
||||
|
||||
export const decodeBookL2 = (book: SpotOrderBook | BookSide): number[][] => {
|
||||
const depth = 40
|
||||
|
@ -406,6 +408,13 @@ const Orderbook = () => {
|
|||
// return () => clearTimeout(id)
|
||||
}, [verticallyCenterOrderbook])
|
||||
|
||||
const resetOrderbook = useCallback(async () => {
|
||||
setShowBuys(true)
|
||||
setShowSells(true)
|
||||
await sleep(300)
|
||||
verticallyCenterOrderbook()
|
||||
}, [])
|
||||
|
||||
const onGroupSizeChange = useCallback((groupSize: number) => {
|
||||
setGrouping(groupSize)
|
||||
}, [])
|
||||
|
@ -417,10 +426,10 @@ const Orderbook = () => {
|
|||
return (
|
||||
<div className="flex h-full flex-col">
|
||||
<div className="flex items-center justify-between border-b border-th-bkg-3 px-4 py-2">
|
||||
<div id="trade-step-three" className="flex items-center space-x-2">
|
||||
<div id="trade-step-three" className="flex items-center space-x-1.5">
|
||||
<Tooltip
|
||||
content={showBuys ? t('trade:hide-bids') : t('trade:show-bids')}
|
||||
placement="top"
|
||||
placement="bottom"
|
||||
>
|
||||
<button
|
||||
className={`rounded ${
|
||||
|
@ -434,7 +443,7 @@ const Orderbook = () => {
|
|||
</Tooltip>
|
||||
<Tooltip
|
||||
content={showSells ? t('trade:hide-asks') : t('trade:show-asks')}
|
||||
placement="top"
|
||||
placement="bottom"
|
||||
>
|
||||
<button
|
||||
className={`rounded ${
|
||||
|
@ -446,10 +455,24 @@ const Orderbook = () => {
|
|||
<OrderbookIcon className="h-4 w-4" side="sell" />
|
||||
</button>
|
||||
</Tooltip>
|
||||
<Tooltip content={'Reset and center orderbook'} placement="bottom">
|
||||
<button
|
||||
className={`rounded ${
|
||||
showSells ? 'bg-th-bkg-3' : 'bg-th-bkg-2'
|
||||
} default-transition flex h-6 w-6 items-center justify-center hover:border-th-fgd-4 focus:outline-none disabled:cursor-not-allowed`}
|
||||
onClick={resetOrderbook}
|
||||
>
|
||||
<ArrowPathIcon className="h-4 w-4" />
|
||||
</button>
|
||||
</Tooltip>
|
||||
</div>
|
||||
{market ? (
|
||||
<div id="trade-step-four">
|
||||
<Tooltip content={t('trade:grouping')} placement="top" delay={250}>
|
||||
<Tooltip
|
||||
content={t('trade:grouping')}
|
||||
placement="bottom"
|
||||
delay={250}
|
||||
>
|
||||
<GroupSize
|
||||
tickSize={market.tickSize}
|
||||
onChange={onGroupSizeChange}
|
||||
|
|
|
@ -80,7 +80,7 @@ const PerpButtonGroup = ({
|
|||
)
|
||||
|
||||
return (
|
||||
<div className="w-full px-4">
|
||||
<div className="w-full px-3 md:px-4">
|
||||
<ButtonGroup
|
||||
activeValue={sizePercentage}
|
||||
onChange={(p) => handleSizePercentage(p)}
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
import { PerpMarket } from '@blockworks-foundation/mango-v4'
|
||||
import { LinkButton } from '@components/shared/Button'
|
||||
import { Table, Td, Th, TrBody, TrHead } from '@components/shared/TableElements'
|
||||
import { LinkIcon, NoSymbolIcon } from '@heroicons/react/20/solid'
|
||||
import { useWallet } from '@solana/wallet-adapter-react'
|
||||
import { NoSymbolIcon } from '@heroicons/react/20/solid'
|
||||
import mangoStore from '@store/mangoStore'
|
||||
import useMangoAccount from 'hooks/useMangoAccount'
|
||||
import useMangoGroup from 'hooks/useMangoGroup'
|
||||
import useSelectedMarket from 'hooks/useSelectedMarket'
|
||||
import { useTranslation } from 'next-i18next'
|
||||
|
@ -19,10 +19,10 @@ import TableMarketName from './TableMarketName'
|
|||
|
||||
const PerpPositions = () => {
|
||||
const { t } = useTranslation(['common', 'trade'])
|
||||
const { connected } = useWallet()
|
||||
const { group } = useMangoGroup()
|
||||
const perpPositions = mangoStore((s) => s.mangoAccount.perpPositions)
|
||||
const { selectedMarket } = useSelectedMarket()
|
||||
const { mangoAccount } = useMangoAccount()
|
||||
|
||||
const handlePositionClick = (positionSize: number) => {
|
||||
const tradeForm = mangoStore.getState().tradeForm
|
||||
|
@ -56,109 +56,98 @@ const PerpPositions = () => {
|
|||
p.basePositionLots.toNumber()
|
||||
)
|
||||
|
||||
return connected ? (
|
||||
openPerpPositions.length ? (
|
||||
<div>
|
||||
<Table>
|
||||
<thead>
|
||||
<TrHead>
|
||||
<Th className="text-left">{t('market')}</Th>
|
||||
<Th className="text-right">{t('trade:side')}</Th>
|
||||
<Th className="text-right">{t('trade:size')}</Th>
|
||||
<Th className="text-right">{t('notional')}</Th>
|
||||
<Th className="text-right">{t('trade:entry-price')}</Th>
|
||||
<Th className="text-right">Redeemable PnL</Th>
|
||||
<Th className="text-right">Realized PnL</Th>
|
||||
</TrHead>
|
||||
</thead>
|
||||
<tbody>
|
||||
{openPerpPositions.map((position) => {
|
||||
const market = group.getPerpMarketByMarketIndex(
|
||||
position.marketIndex
|
||||
)
|
||||
const basePosition = position.getBasePositionUi(market)
|
||||
const trimmedBasePosition = trimDecimals(
|
||||
basePosition,
|
||||
getDecimalCount(market.minOrderSize)
|
||||
)
|
||||
const isSelectedMarket =
|
||||
selectedMarket instanceof PerpMarket &&
|
||||
selectedMarket.perpMarketIndex === position.marketIndex
|
||||
return mangoAccount && openPerpPositions.length ? (
|
||||
<div>
|
||||
<Table>
|
||||
<thead>
|
||||
<TrHead>
|
||||
<Th className="text-left">{t('market')}</Th>
|
||||
<Th className="text-right">{t('trade:side')}</Th>
|
||||
<Th className="text-right">{t('trade:size')}</Th>
|
||||
<Th className="text-right">{t('trade:notional')}</Th>
|
||||
<Th className="text-right">{t('trade:entry-price')}</Th>
|
||||
<Th className="text-right">Redeemable PnL</Th>
|
||||
<Th className="text-right">Realized PnL</Th>
|
||||
</TrHead>
|
||||
</thead>
|
||||
<tbody>
|
||||
{openPerpPositions.map((position) => {
|
||||
const market = group.getPerpMarketByMarketIndex(
|
||||
position.marketIndex
|
||||
)
|
||||
const basePosition = position.getBasePositionUi(market)
|
||||
const trimmedBasePosition = trimDecimals(
|
||||
basePosition,
|
||||
getDecimalCount(market.minOrderSize)
|
||||
)
|
||||
const isSelectedMarket =
|
||||
selectedMarket instanceof PerpMarket &&
|
||||
selectedMarket.perpMarketIndex === position.marketIndex
|
||||
|
||||
if (!basePosition) return null
|
||||
if (!basePosition) return null
|
||||
|
||||
const unsettledPnl = position.getEquityUi(group, market)
|
||||
const unsettledPnl = position.getEquityUi(group, market)
|
||||
|
||||
return (
|
||||
<TrBody key={`${position.marketIndex}`} className="my-1 p-2">
|
||||
<Td>
|
||||
<TableMarketName market={market} />
|
||||
</Td>
|
||||
<Td className="text-right">
|
||||
<PerpSideBadge basePosition={basePosition} />
|
||||
</Td>
|
||||
<Td className="text-right">
|
||||
<p className="flex justify-end">
|
||||
{isSelectedMarket ? (
|
||||
<LinkButton
|
||||
onClick={() =>
|
||||
handlePositionClick(trimmedBasePosition)
|
||||
}
|
||||
>
|
||||
{Math.abs(trimmedBasePosition)}
|
||||
</LinkButton>
|
||||
) : (
|
||||
Math.abs(trimmedBasePosition)
|
||||
)}
|
||||
</p>
|
||||
</Td>
|
||||
<Td className="text-right font-mono">
|
||||
<div>
|
||||
$
|
||||
{Math.abs(trimmedBasePosition * market._uiPrice).toFixed(
|
||||
2
|
||||
)}
|
||||
</div>
|
||||
</Td>
|
||||
<Td className="text-right font-mono">
|
||||
<div>
|
||||
$
|
||||
{numberFormat.format(
|
||||
position.getAverageEntryPriceUi(market)
|
||||
)}
|
||||
</div>
|
||||
</Td>
|
||||
<Td
|
||||
className={`text-right font-mono ${
|
||||
unsettledPnl > 0 ? 'text-th-up' : 'text-th-down'
|
||||
}`}
|
||||
>
|
||||
<div>${formatFixedDecimals(unsettledPnl)}</div>
|
||||
</Td>
|
||||
<Td className="text-right">
|
||||
<div>
|
||||
$
|
||||
{/* {numberFormat.format(
|
||||
return (
|
||||
<TrBody key={`${position.marketIndex}`} className="my-1 p-2">
|
||||
<Td>
|
||||
<TableMarketName market={market} />
|
||||
</Td>
|
||||
<Td className="text-right">
|
||||
<PerpSideBadge basePosition={basePosition} />
|
||||
</Td>
|
||||
<Td className="text-right">
|
||||
<p className="flex justify-end">
|
||||
{isSelectedMarket ? (
|
||||
<LinkButton
|
||||
onClick={() => handlePositionClick(trimmedBasePosition)}
|
||||
>
|
||||
{Math.abs(trimmedBasePosition)}
|
||||
</LinkButton>
|
||||
) : (
|
||||
Math.abs(trimmedBasePosition)
|
||||
)}
|
||||
</p>
|
||||
</Td>
|
||||
<Td className="text-right font-mono">
|
||||
<div>
|
||||
$
|
||||
{Math.abs(trimmedBasePosition * market._uiPrice).toFixed(2)}
|
||||
</div>
|
||||
</Td>
|
||||
<Td className="text-right font-mono">
|
||||
<div>
|
||||
$
|
||||
{numberFormat.format(
|
||||
position.getAverageEntryPriceUi(market)
|
||||
)}
|
||||
</div>
|
||||
</Td>
|
||||
<Td
|
||||
className={`text-right font-mono ${
|
||||
unsettledPnl > 0 ? 'text-th-up' : 'text-th-down'
|
||||
}`}
|
||||
>
|
||||
<div>{formatFixedDecimals(unsettledPnl, true)}</div>
|
||||
</Td>
|
||||
<Td className="text-right">
|
||||
<div>
|
||||
$
|
||||
{/* {numberFormat.format(
|
||||
position.perpSpotTransfers.toNumber()
|
||||
)} */}
|
||||
</div>
|
||||
</Td>
|
||||
</TrBody>
|
||||
)
|
||||
})}
|
||||
</tbody>
|
||||
</Table>
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex flex-col items-center p-8">
|
||||
<NoSymbolIcon className="mb-2 h-6 w-6 text-th-fgd-4" />
|
||||
<p>{t('trade:no-positions')}</p>
|
||||
</div>
|
||||
)
|
||||
</div>
|
||||
</Td>
|
||||
</TrBody>
|
||||
)
|
||||
})}
|
||||
</tbody>
|
||||
</Table>
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex flex-col items-center p-8">
|
||||
<LinkIcon className="mb-2 h-6 w-6 text-th-fgd-4" />
|
||||
<p>{t('trade:connect-positions')}</p>
|
||||
<NoSymbolIcon className="mb-2 h-6 w-6 text-th-fgd-4" />
|
||||
<p>{t('trade:no-positions')}</p>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -83,7 +83,7 @@ const PerpSlider = ({
|
|||
)
|
||||
|
||||
return (
|
||||
<div className="w-full px-4">
|
||||
<div className="w-full px-3 md:px-4">
|
||||
<LeverageSlider
|
||||
amount={
|
||||
tradeForm.side === 'buy'
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
import useInterval from '@components/shared/useInterval'
|
||||
import mangoStore from '@store/mangoStore'
|
||||
import { useEffect, useMemo, useRef } from 'react'
|
||||
import isEqual from 'lodash/isEqual'
|
||||
import { useEffect, useMemo } from 'react'
|
||||
import { floorToDecimal, getDecimalCount } from 'utils/numbers'
|
||||
import Decimal from 'decimal.js'
|
||||
import { ChartTradeType } from 'types'
|
||||
|
@ -14,6 +13,7 @@ import { SOUND_SETTINGS_KEY } from 'utils/constants'
|
|||
import { SpeakerWaveIcon, SpeakerXMarkIcon } from '@heroicons/react/20/solid'
|
||||
import Tooltip from '@components/shared/Tooltip'
|
||||
import { INITIAL_SOUND_SETTINGS } from '@components/settings/SoundSettings'
|
||||
import usePrevious from '@components/shared/usePrevious'
|
||||
|
||||
const buySound = new Howl({
|
||||
src: ['/sounds/trade-buy.mp3'],
|
||||
|
@ -25,15 +25,30 @@ const sellSound = new Howl({
|
|||
})
|
||||
|
||||
const RecentTrades = () => {
|
||||
// const [trades, setTrades] = useState<any[]>([])
|
||||
const { t } = useTranslation(['common', 'trade'])
|
||||
const fills = mangoStore((s) => s.selectedMarket.fills)
|
||||
const [soundSettings, setSoundSettings] = useLocalStorageState(
|
||||
SOUND_SETTINGS_KEY,
|
||||
INITIAL_SOUND_SETTINGS
|
||||
)
|
||||
const currentFillsData = useRef<any>([])
|
||||
const nextFillsData = useRef<any>([])
|
||||
const previousFills = usePrevious(fills)
|
||||
|
||||
useEffect(() => {
|
||||
if (!soundSettings['recent-trades']) return
|
||||
if (fills.length && previousFills && previousFills.length) {
|
||||
const latestFill: ChartTradeType = fills[0]
|
||||
const previousFill: ChartTradeType = previousFills[0]
|
||||
if (previousFill.orderId.toString() !== latestFill.orderId.toString()) {
|
||||
const side =
|
||||
latestFill.side || (latestFill.takerSide === 1 ? 'bid' : 'ask')
|
||||
if (['buy', 'bid'].includes(side)) {
|
||||
buySound.play()
|
||||
} else {
|
||||
sellSound.play()
|
||||
}
|
||||
}
|
||||
}
|
||||
}, [fills, previousFills, soundSettings])
|
||||
|
||||
const { selectedMarket, serumOrPerpMarket: market } = useSelectedMarket()
|
||||
|
||||
|
@ -45,33 +60,6 @@ const RecentTrades = () => {
|
|||
return selectedMarket?.name.split(/-|\//)[1]
|
||||
}, [selectedMarket])
|
||||
|
||||
// needs more testing
|
||||
useEffect(() => {
|
||||
if (soundSettings['recent-trades']) {
|
||||
mangoStore.subscribe(
|
||||
(state) => [state.selectedMarket.fills],
|
||||
(fills) => (nextFillsData.current = fills[0])
|
||||
)
|
||||
}
|
||||
}, [soundSettings['recent-trades']])
|
||||
|
||||
// needs more testing
|
||||
useInterval(() => {
|
||||
if (soundSettings['recent-trades']) {
|
||||
if (fills.length) {
|
||||
currentFillsData.current = fills
|
||||
}
|
||||
if (
|
||||
nextFillsData.current.length &&
|
||||
!isEqual(currentFillsData.current, nextFillsData.current)
|
||||
) {
|
||||
nextFillsData.current[0].side === 'buy'
|
||||
? buySound.play()
|
||||
: sellSound.play()
|
||||
}
|
||||
}
|
||||
}, 1000)
|
||||
|
||||
// const fetchRecentTrades = useCallback(async () => {
|
||||
// if (!market) return
|
||||
|
||||
|
@ -109,9 +97,27 @@ const RecentTrades = () => {
|
|||
actions.loadMarketFills()
|
||||
}, 5000)
|
||||
|
||||
const [buyRatio, sellRatio] = useMemo(() => {
|
||||
if (!fills.length) return [0, 0]
|
||||
|
||||
const vol = fills.reduce(
|
||||
(a: { buys: number; sells: number }, c: any) => {
|
||||
if (c.side === 'buy' || c.takerSide === 1) {
|
||||
a.buys = a.buys + c.size
|
||||
} else {
|
||||
a.sells = a.sells + c.size
|
||||
}
|
||||
return a
|
||||
},
|
||||
{ buys: 0, sells: 0 }
|
||||
)
|
||||
const totalVol = vol.buys + vol.sells
|
||||
return [vol.buys / totalVol, vol.sells / totalVol]
|
||||
}, [fills])
|
||||
|
||||
return (
|
||||
<div className="thin-scroll h-full overflow-y-scroll">
|
||||
<div className="flex justify-end border-b border-th-bkg-3 px-2 py-1">
|
||||
<div className="flex items-center justify-between border-b border-th-bkg-3 py-1 px-2">
|
||||
<Tooltip content={t('trade:trade-sounds-tooltip')} delay={250}>
|
||||
<IconButton
|
||||
onClick={() =>
|
||||
|
@ -130,6 +136,17 @@ const RecentTrades = () => {
|
|||
)}
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
<span className="text-xs text-th-fgd-4">
|
||||
{t('trade:buys')}:{' '}
|
||||
<span className="font-mono text-th-up">
|
||||
{(buyRatio * 100).toFixed(1)}%
|
||||
</span>
|
||||
<span className="px-2">|</span>
|
||||
{t('trade:sells')}:{' '}
|
||||
<span className="font-mono text-th-down">
|
||||
{(sellRatio * 100).toFixed(1)}%
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
<div className="px-2">
|
||||
<table className="min-w-full">
|
||||
|
@ -147,7 +164,8 @@ const RecentTrades = () => {
|
|||
<tbody>
|
||||
{!!fills.length &&
|
||||
fills.map((trade: ChartTradeType, i: number) => {
|
||||
const side = trade.side || trade.takerSide === 1 ? 'bid' : 'ask'
|
||||
const side =
|
||||
trade.side || (trade.takerSide === 1 ? 'bid' : 'ask')
|
||||
|
||||
// const price =
|
||||
// typeof trade.price === 'number'
|
||||
|
@ -174,8 +192,8 @@ const RecentTrades = () => {
|
|||
<td
|
||||
className={`pb-1.5 text-right ${
|
||||
['buy', 'bid'].includes(side)
|
||||
? `text-th-up`
|
||||
: `text-th-down`
|
||||
? 'text-th-up'
|
||||
: 'text-th-down'
|
||||
}`}
|
||||
>
|
||||
{formattedPrice.toFixed()}
|
||||
|
|
|
@ -4,7 +4,7 @@ import useMarkPrice from 'hooks/useMarkPrice'
|
|||
import useSelectedMarket from 'hooks/useSelectedMarket'
|
||||
import { useTranslation } from 'next-i18next'
|
||||
import { useMemo } from 'react'
|
||||
import { notify } from 'utils/notifications'
|
||||
// import { notify } from 'utils/notifications'
|
||||
import { calculateSlippage } from 'utils/tradeForm'
|
||||
|
||||
const Slippage = () => {
|
||||
|
@ -25,7 +25,8 @@ const Slippage = () => {
|
|||
)
|
||||
}
|
||||
} catch (e) {
|
||||
notify({ type: 'info', title: 'Unable to calculate slippage' })
|
||||
console.error({ type: 'info', title: 'Unable to calculate slippage' })
|
||||
return 100_000
|
||||
}
|
||||
return 0
|
||||
}, [tradeForm, markPrice, selectedMarket])
|
||||
|
@ -44,7 +45,7 @@ const Slippage = () => {
|
|||
: 'text-th-error'
|
||||
}`}
|
||||
>
|
||||
{slippage.toFixed(2)}%
|
||||
{slippage === 100_000 ? 'Unavailable' : `${slippage.toFixed(2)}&`}
|
||||
</p>
|
||||
</div>
|
||||
) : null
|
||||
|
|
|
@ -79,7 +79,7 @@ const SpotButtonGroup = ({
|
|||
)
|
||||
|
||||
return (
|
||||
<div className="w-full px-4">
|
||||
<div className="w-full px-3 md:px-4">
|
||||
<ButtonGroup
|
||||
activeValue={sizePercentage}
|
||||
onChange={(p) => handleSizePercentage(p)}
|
||||
|
|
|
@ -9,9 +9,11 @@ import { trimDecimals } from 'utils/numbers'
|
|||
const SpotSlider = ({
|
||||
minOrderDecimals,
|
||||
tickDecimals,
|
||||
step,
|
||||
}: {
|
||||
minOrderDecimals: number
|
||||
tickDecimals: number
|
||||
step: number
|
||||
}) => {
|
||||
const side = mangoStore((s) => s.tradeForm.side)
|
||||
const { selectedMarket, price: marketPrice } = useSelectedMarket()
|
||||
|
@ -76,7 +78,7 @@ const SpotSlider = ({
|
|||
)
|
||||
|
||||
return (
|
||||
<div className="w-full px-4">
|
||||
<div className="w-full px-3 md:px-4">
|
||||
<LeverageSlider
|
||||
amount={
|
||||
tradeForm.side === 'buy'
|
||||
|
@ -85,7 +87,7 @@ const SpotSlider = ({
|
|||
}
|
||||
leverageMax={leverageMax}
|
||||
onChange={handleSlide}
|
||||
step={0.01}
|
||||
step={step}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import { I80F48, PerpMarket } from '@blockworks-foundation/mango-v4'
|
||||
import InlineNotification from '@components/shared/InlineNotification'
|
||||
import SideBadge from '@components/shared/SideBadge'
|
||||
import {
|
||||
Table,
|
||||
|
@ -9,15 +8,14 @@ import {
|
|||
TrBody,
|
||||
TrHead,
|
||||
} from '@components/shared/TableElements'
|
||||
import { LinkIcon, NoSymbolIcon } from '@heroicons/react/20/solid'
|
||||
import { useWallet } from '@solana/wallet-adapter-react'
|
||||
import { NoSymbolIcon } from '@heroicons/react/20/solid'
|
||||
import { PublicKey } from '@solana/web3.js'
|
||||
import mangoStore from '@store/mangoStore'
|
||||
import useMangoAccount from 'hooks/useMangoAccount'
|
||||
import useSelectedMarket from 'hooks/useSelectedMarket'
|
||||
import { useViewport } from 'hooks/useViewport'
|
||||
import { useMemo } from 'react'
|
||||
import { formatDecimal } from 'utils/numbers'
|
||||
import { formatDecimal, formatFixedDecimals } from 'utils/numbers'
|
||||
import { breakpoints } from 'utils/theme'
|
||||
import TableMarketName from './TableMarketName'
|
||||
|
||||
|
@ -30,8 +28,8 @@ const byTimestamp = (a: any, b: any) => {
|
|||
|
||||
const reverseSide = (side: string) => (side === 'buy' ? 'sell' : 'buy')
|
||||
|
||||
const parsedPerpEvent = (mangoAccountPk: PublicKey, event: any) => {
|
||||
const maker = event.maker.toString() === mangoAccountPk.toString()
|
||||
const parsedPerpEvent = (mangoAccountAddress: string, event: any) => {
|
||||
const maker = event.maker.toString() === mangoAccountAddress
|
||||
const orderId = maker ? event.makerOrderId : event.takerOrderId
|
||||
const value = event.quantity * event.price
|
||||
const feeRate = maker
|
||||
|
@ -69,14 +67,17 @@ const parsedSerumEvent = (event: any) => {
|
|||
}
|
||||
}
|
||||
|
||||
const formatTradeHistory = (mangoAccountPk: PublicKey, tradeHistory: any[]) => {
|
||||
const formatTradeHistory = (
|
||||
mangoAccountAddress: string,
|
||||
tradeHistory: any[]
|
||||
) => {
|
||||
return tradeHistory
|
||||
.flat()
|
||||
.map((event) => {
|
||||
if (event.eventFlags || event.nativeQuantityPaid) {
|
||||
return parsedSerumEvent(event)
|
||||
} else if (event.maker) {
|
||||
return parsedPerpEvent(mangoAccountPk, event)
|
||||
return parsedPerpEvent(mangoAccountAddress, event)
|
||||
} else {
|
||||
return event
|
||||
}
|
||||
|
@ -85,29 +86,33 @@ const formatTradeHistory = (mangoAccountPk: PublicKey, tradeHistory: any[]) => {
|
|||
}
|
||||
|
||||
const TradeHistory = () => {
|
||||
const { connected } = useWallet()
|
||||
const group = mangoStore.getState().group
|
||||
const { selectedMarket } = useSelectedMarket()
|
||||
const { mangoAccount } = useMangoAccount()
|
||||
const { mangoAccount, mangoAccountAddress } = useMangoAccount()
|
||||
const fills = mangoStore((s) => s.selectedMarket.fills)
|
||||
const tradeHistory = mangoStore((s) => s.mangoAccount.tradeHistory)
|
||||
const { width } = useViewport()
|
||||
const showTableView = width ? width > breakpoints.md : false
|
||||
|
||||
const openOrderOwner = useMemo(() => {
|
||||
if (!mangoAccount || !selectedMarket) return
|
||||
try {
|
||||
if (selectedMarket instanceof PerpMarket) {
|
||||
return mangoAccount.publicKey
|
||||
} else {
|
||||
if (selectedMarket instanceof PerpMarket) {
|
||||
return mangoAccount.publicKey
|
||||
} else {
|
||||
try {
|
||||
return mangoAccount.getSerum3OoAccount(selectedMarket.marketIndex)
|
||||
.address
|
||||
} catch {
|
||||
console.warn(
|
||||
'Unable to find OO account for mkt index',
|
||||
selectedMarket.marketIndex
|
||||
)
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('Error loading open order account for trade history: ', e)
|
||||
}
|
||||
}, [mangoAccount, selectedMarket])
|
||||
|
||||
const tradeHistoryFromEventQueue = useMemo(() => {
|
||||
if (!mangoAccount || !selectedMarket) return []
|
||||
const eventQueueFillsForAccount = useMemo(() => {
|
||||
if (!mangoAccountAddress || !selectedMarket) return []
|
||||
|
||||
const mangoAccountFills = fills
|
||||
.filter((fill: any) => {
|
||||
|
@ -124,117 +129,151 @@ const TradeHistory = () => {
|
|||
})
|
||||
.map((fill: any) => ({ ...fill, marketName: selectedMarket.name }))
|
||||
|
||||
return formatTradeHistory(mangoAccount.publicKey, mangoAccountFills)
|
||||
}, [selectedMarket, mangoAccount, openOrderOwner])
|
||||
return formatTradeHistory(mangoAccountAddress, mangoAccountFills)
|
||||
}, [selectedMarket, mangoAccountAddress, openOrderOwner, fills])
|
||||
|
||||
if (!selectedMarket) return null
|
||||
const combinedTradeHistory = useMemo(() => {
|
||||
let newFills = []
|
||||
if (eventQueueFillsForAccount?.length) {
|
||||
console.log('eventQueueFillsForAccount', eventQueueFillsForAccount)
|
||||
|
||||
return connected ? (
|
||||
tradeHistoryFromEventQueue.length ? (
|
||||
showTableView ? (
|
||||
<div>
|
||||
<Table>
|
||||
<thead>
|
||||
<TrHead>
|
||||
<Th className="text-left">Market</Th>
|
||||
<Th className="text-right">Side</Th>
|
||||
<Th className="text-right">Size</Th>
|
||||
<Th className="text-right">Price</Th>
|
||||
<Th className="text-right">Value</Th>
|
||||
<Th className="text-right">Fee</Th>
|
||||
{selectedMarket instanceof PerpMarket ? (
|
||||
<Th className="text-right">Time</Th>
|
||||
) : null}
|
||||
</TrHead>
|
||||
</thead>
|
||||
<tbody>
|
||||
{tradeHistoryFromEventQueue.map((trade: any) => {
|
||||
return (
|
||||
<TrBody key={`${trade.marketIndex}`} className="my-1 p-2">
|
||||
<Td className="">
|
||||
<TableMarketName market={selectedMarket} />
|
||||
</Td>
|
||||
<Td className="text-right">
|
||||
<SideBadge side={trade.side} />
|
||||
</Td>
|
||||
<Td className="text-right font-mono">{trade.size}</Td>
|
||||
<Td className="text-right font-mono">
|
||||
{formatDecimal(trade.price)}
|
||||
</Td>
|
||||
<Td className="text-right font-mono">
|
||||
${trade.value.toFixed(2)}
|
||||
</Td>
|
||||
<Td className="text-right">
|
||||
<span className="font-mono">{trade.feeCost}</span>
|
||||
<p className="font-body text-xs text-th-fgd-4">{`${
|
||||
trade.liquidity ? trade.liquidity : ''
|
||||
}`}</p>
|
||||
</Td>
|
||||
{selectedMarket instanceof PerpMarket ? (
|
||||
<Td className="whitespace-nowrap text-right font-mono">
|
||||
<TableDateDisplay
|
||||
date={trade.timestamp.toNumber() * 1000}
|
||||
showSeconds
|
||||
/>
|
||||
</Td>
|
||||
) : null}
|
||||
</TrBody>
|
||||
newFills = eventQueueFillsForAccount.filter((fill) => {
|
||||
return !tradeHistory.find((t) => {
|
||||
if (t.order_id) {
|
||||
return t.order_id === fill.orderId?.toString()
|
||||
}
|
||||
// else {
|
||||
// return t.seq_num === fill.seqNum?.toString()
|
||||
// }
|
||||
})
|
||||
})
|
||||
}
|
||||
return [...newFills, ...tradeHistory]
|
||||
}, [eventQueueFillsForAccount, tradeHistory])
|
||||
|
||||
console.log('trade history', tradeHistory)
|
||||
|
||||
if (!selectedMarket || !group) return null
|
||||
|
||||
return mangoAccount && combinedTradeHistory.length ? (
|
||||
showTableView ? (
|
||||
<div>
|
||||
<Table>
|
||||
<thead>
|
||||
<TrHead>
|
||||
<Th className="text-left">Market</Th>
|
||||
<Th className="text-right">Side</Th>
|
||||
<Th className="text-right">Size</Th>
|
||||
<Th className="text-right">Price</Th>
|
||||
<Th className="text-right">Value</Th>
|
||||
<Th className="text-right">Fee</Th>
|
||||
<Th className="text-right">Time</Th>
|
||||
</TrHead>
|
||||
</thead>
|
||||
<tbody>
|
||||
{combinedTradeHistory.map((trade: any) => {
|
||||
let market
|
||||
if ('market' in trade) {
|
||||
market = group.getSerum3MarketByExternalMarket(
|
||||
new PublicKey(trade.market)
|
||||
)
|
||||
})}
|
||||
</tbody>
|
||||
</Table>
|
||||
<div className="px-6 py-4">
|
||||
<InlineNotification
|
||||
type="info"
|
||||
desc="During the Mango V4 alpha, only your recent Openbook trades will be displayed here. Full trade history will be available shortly."
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div>
|
||||
{tradeHistoryFromEventQueue.map((trade: any) => {
|
||||
return (
|
||||
<div
|
||||
className="flex items-center justify-between border-b border-th-bkg-3 p-4"
|
||||
key={`${trade.marketIndex}`}
|
||||
>
|
||||
<div>
|
||||
<TableMarketName market={selectedMarket} />
|
||||
<div className="mt-1 flex items-center space-x-1">
|
||||
} else if ('perp_market' in trade) {
|
||||
market = group.getPerpMarketByName(trade.perp_market)
|
||||
} else {
|
||||
market = selectedMarket
|
||||
}
|
||||
let makerTaker = trade.liquidity
|
||||
if ('maker' in trade) {
|
||||
makerTaker = trade.maker ? 'Maker' : 'Taker'
|
||||
if (trade.taker === mangoAccount.publicKey.toString()) {
|
||||
makerTaker = 'Taker'
|
||||
}
|
||||
}
|
||||
const size = trade.size || trade.quantity
|
||||
let fee
|
||||
if (trade.fee_cost || trade.feeCost) {
|
||||
fee = trade.fee_cost || trade.feeCost
|
||||
} else {
|
||||
fee =
|
||||
trade.maker === mangoAccount.publicKey.toString()
|
||||
? trade.maker_fee
|
||||
: trade.taker_fee
|
||||
}
|
||||
|
||||
return (
|
||||
<TrBody
|
||||
key={`${trade.signature || trade.marketIndex}${size}`}
|
||||
className="my-1 p-2"
|
||||
>
|
||||
<Td className="">
|
||||
<TableMarketName market={market} />
|
||||
</Td>
|
||||
<Td className="text-right">
|
||||
<SideBadge side={trade.side} />
|
||||
<p className="text-th-fgd-4">
|
||||
<span className="font-mono text-th-fgd-3">
|
||||
{trade.size}
|
||||
</span>
|
||||
{' for '}
|
||||
<span className="font-mono text-th-fgd-3">
|
||||
{formatDecimal(trade.price)}
|
||||
</span>
|
||||
</Td>
|
||||
<Td className="text-right font-mono">{size}</Td>
|
||||
<Td className="text-right font-mono">
|
||||
{formatDecimal(trade.price)}
|
||||
</Td>
|
||||
<Td className="text-right font-mono">
|
||||
{formatFixedDecimals(trade.price * size)}
|
||||
</Td>
|
||||
<Td className="text-right">
|
||||
<span className="font-mono">{formatDecimal(fee)}</span>
|
||||
<p className="font-body text-xs text-th-fgd-4">
|
||||
{makerTaker}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<p className="font-mono">${trade.value.toFixed(2)}</p>
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
)
|
||||
</Td>
|
||||
|
||||
<Td className="whitespace-nowrap text-right">
|
||||
{trade.block_datetime ? (
|
||||
<TableDateDisplay
|
||||
date={trade.block_datetime}
|
||||
showSeconds
|
||||
/>
|
||||
) : (
|
||||
'Recent'
|
||||
)}
|
||||
</Td>
|
||||
</TrBody>
|
||||
)
|
||||
})}
|
||||
</tbody>
|
||||
</Table>
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex flex-col items-center justify-center px-6 pb-8 pt-4">
|
||||
<div className="mb-8 w-full">
|
||||
<InlineNotification
|
||||
type="info"
|
||||
desc="During the Mango V4 alpha, only your recent Openbook trades will be displayed here. Full trade history will be available shortly."
|
||||
/>
|
||||
</div>
|
||||
<NoSymbolIcon className="mb-2 h-6 w-6 text-th-fgd-4" />
|
||||
<p>No trade history for {selectedMarket?.name}</p>
|
||||
<div>
|
||||
{eventQueueFillsForAccount.map((trade: any) => {
|
||||
return (
|
||||
<div
|
||||
className="flex items-center justify-between border-b border-th-bkg-3 p-4"
|
||||
key={`${trade.marketIndex}`}
|
||||
>
|
||||
<div>
|
||||
<TableMarketName market={selectedMarket} />
|
||||
<div className="mt-1 flex items-center space-x-1">
|
||||
<SideBadge side={trade.side} />
|
||||
<p className="text-th-fgd-4">
|
||||
<span className="font-mono text-th-fgd-3">
|
||||
{trade.size}
|
||||
</span>
|
||||
{' for '}
|
||||
<span className="font-mono text-th-fgd-3">
|
||||
{formatDecimal(trade.price)}
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<p className="font-mono">${trade.value.toFixed(2)}</p>
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
)
|
||||
) : (
|
||||
<div className="flex flex-col items-center p-8">
|
||||
<LinkIcon className="mb-2 h-6 w-6 text-th-fgd-4" />
|
||||
<p>Connect to view your trade history</p>
|
||||
<div className="flex flex-col items-center justify-center px-6 pb-8 pt-4">
|
||||
<NoSymbolIcon className="mb-2 h-6 w-6 text-th-fgd-4" />
|
||||
<p>No trade history</p>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -18,7 +18,7 @@ const TradeInfoTabs = () => {
|
|||
const unsettledSpotBalances = useUnsettledSpotBalances()
|
||||
const unsettledPerpPositions = useUnsettledPerpPositions()
|
||||
const { width } = useViewport()
|
||||
const isMobile = width ? width < breakpoints.lg : false
|
||||
const isMobile = width ? width < breakpoints['2xl'] : false
|
||||
|
||||
const tabsWithCount: [string, number][] = useMemo(() => {
|
||||
const unsettledTradeCount =
|
||||
|
@ -38,7 +38,7 @@ const TradeInfoTabs = () => {
|
|||
|
||||
return (
|
||||
<div className="hide-scroll h-full overflow-y-scroll pb-5">
|
||||
<div className="hide-scroll sticky top-0 z-10 overflow-x-auto border-b border-th-bkg-3">
|
||||
<div className="hide-scroll sticky top-0 z-20 overflow-x-auto border-b border-th-bkg-3">
|
||||
<TabButtons
|
||||
activeValue={selectedTab}
|
||||
onChange={(tab: string) => setSelectedTab(tab)}
|
||||
|
|
|
@ -69,7 +69,7 @@ const TradeSummary = ({
|
|||
: simulatedHealthRatio < 0
|
||||
? 0
|
||||
: Math.trunc(simulatedHealthRatio)
|
||||
}, [group, mangoAccount, selectedMarket, tradeForm.baseSize, tradeForm.side])
|
||||
}, [group, mangoAccount, selectedMarket, tradeForm])
|
||||
|
||||
return (
|
||||
<div className="space-y-2 px-3 md:px-4">
|
||||
|
@ -79,6 +79,7 @@ const TradeSummary = ({
|
|||
{tradeForm.price && tradeForm.baseSize
|
||||
? formatFixedDecimals(
|
||||
parseFloat(tradeForm.price) * parseFloat(tradeForm.baseSize),
|
||||
false,
|
||||
true
|
||||
)
|
||||
: '0.00'}
|
||||
|
@ -93,8 +94,9 @@ const TradeSummary = ({
|
|||
{group && mangoAccount
|
||||
? formatFixedDecimals(
|
||||
toUiDecimalsForQuote(
|
||||
mangoAccount.getCollateralValue(group)!.toNumber()
|
||||
mangoAccount.getCollateralValue(group).toNumber()
|
||||
),
|
||||
false,
|
||||
true
|
||||
)
|
||||
: '–'}
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
import { Dispatch, SetStateAction, useEffect, useRef, useState } from 'react'
|
||||
import mangoStore from '@store/mangoStore'
|
||||
import klinecharts, { init, dispose } from 'klinecharts'
|
||||
import axios from 'axios'
|
||||
import { useViewport } from 'hooks/useViewport'
|
||||
import usePrevious from '@components/shared/usePrevious'
|
||||
import Modal from '@components/shared/Modal'
|
||||
|
@ -11,7 +10,6 @@ import {
|
|||
CHART_QUERY,
|
||||
DEFAULT_MAIN_INDICATORS,
|
||||
DEFAULT_SUB_INDICATOR,
|
||||
HISTORY,
|
||||
mainTechnicalIndicatorTypes,
|
||||
MAIN_INDICATOR_CLASS,
|
||||
ONE_DAY_SECONDS,
|
||||
|
@ -20,11 +18,11 @@ import {
|
|||
} from 'utils/kLineChart'
|
||||
import Loading from '@components/shared/Loading'
|
||||
import clsx from 'clsx'
|
||||
import { API_URL, BE_API_KEY } from 'apis/birdeye/helpers'
|
||||
import { useTheme } from 'next-themes'
|
||||
import { COLORS } from 'styles/colors'
|
||||
import { IconButton } from '@components/shared/Button'
|
||||
import { ArrowsPointingOutIcon, XMarkIcon } from '@heroicons/react/20/solid'
|
||||
import { queryBars } from 'apis/birdeye/datafeed'
|
||||
|
||||
const UPDATE_INTERVAL = 10000
|
||||
|
||||
|
@ -60,24 +58,17 @@ const TradingViewChartKline = ({ setIsFullView, isFullView }: Props) => {
|
|||
...baseQuery,
|
||||
time_from: from,
|
||||
}
|
||||
const response = await axios.get(`${API_URL}defi/ohlcv/pair`, {
|
||||
params: query,
|
||||
headers: {
|
||||
'X-API-KEY': BE_API_KEY,
|
||||
},
|
||||
const response = await queryBars(query.address, query.type, {
|
||||
firstDataRequest: false,
|
||||
from: query.time_from,
|
||||
to: query.time_to,
|
||||
})
|
||||
const newData = response.data.data.items as HISTORY[]
|
||||
const dataSize = newData.length
|
||||
const dataSize = response.length
|
||||
const dataList = []
|
||||
for (let i = 0; i < dataSize; i++) {
|
||||
const row = newData[i]
|
||||
const row = response[i]
|
||||
const kLineModel = {
|
||||
open: row.o,
|
||||
low: row.l,
|
||||
high: row.h,
|
||||
close: row.c,
|
||||
volume: row.v,
|
||||
timestamp: row.unixTime * 1000,
|
||||
...row,
|
||||
}
|
||||
dataList.push(kLineModel)
|
||||
}
|
||||
|
|
|
@ -4,7 +4,7 @@ import { useCallback, useState } from 'react'
|
|||
import { PublicKey } from '@solana/web3.js'
|
||||
import { IconButton } from '@components/shared/Button'
|
||||
import { notify } from 'utils/notifications'
|
||||
import { CheckIcon, LinkIcon, NoSymbolIcon } from '@heroicons/react/20/solid'
|
||||
import { CheckIcon, NoSymbolIcon } from '@heroicons/react/20/solid'
|
||||
import Tooltip from '@components/shared/Tooltip'
|
||||
import Loading from '@components/shared/Loading'
|
||||
import { useViewport } from 'hooks/useViewport'
|
||||
|
@ -12,8 +12,10 @@ import { breakpoints } from 'utils/theme'
|
|||
import { Table, Td, Th, TrBody, TrHead } from '@components/shared/TableElements'
|
||||
import useMangoGroup from 'hooks/useMangoGroup'
|
||||
import { PerpMarket, PerpPosition } from '@blockworks-foundation/mango-v4'
|
||||
import { useWallet } from '@solana/wallet-adapter-react'
|
||||
import TableMarketName from './TableMarketName'
|
||||
import useMangoAccount from 'hooks/useMangoAccount'
|
||||
import { useWallet } from '@solana/wallet-adapter-react'
|
||||
import { formatDecimal } from 'utils/numbers'
|
||||
|
||||
const UnsettledTrades = ({
|
||||
unsettledSpotBalances,
|
||||
|
@ -23,11 +25,12 @@ const UnsettledTrades = ({
|
|||
unsettledPerpPositions: PerpPosition[]
|
||||
}) => {
|
||||
const { t } = useTranslation(['common', 'trade'])
|
||||
const { connected } = useWallet()
|
||||
const { group } = useMangoGroup()
|
||||
const [settleMktAddress, setSettleMktAddress] = useState<string>('')
|
||||
const { width } = useViewport()
|
||||
const showTableView = width ? width > breakpoints.md : false
|
||||
const { mangoAccount } = useMangoAccount()
|
||||
const { connected } = useWallet()
|
||||
|
||||
const handleSettleSerumFunds = useCallback(async (mktAddress: string) => {
|
||||
const client = mangoStore.getState().client
|
||||
|
@ -90,13 +93,6 @@ const UnsettledTrades = ({
|
|||
?.getEquityUi(group, market) || 0,
|
||||
}))
|
||||
.sort((a, b) => sign * (a.pnl - b.pnl))
|
||||
console.log(
|
||||
'pnl',
|
||||
filteredAccounts.map((m) => [
|
||||
m.mangoAccount.publicKey.toString(),
|
||||
m.pnl,
|
||||
])
|
||||
)
|
||||
|
||||
const profitableAccount =
|
||||
mangoAccountPnl >= 0 ? mangoAccount : filteredAccounts[0].mangoAccount
|
||||
|
@ -135,51 +131,48 @@ const UnsettledTrades = ({
|
|||
|
||||
if (!group) return null
|
||||
|
||||
return connected ? (
|
||||
return mangoAccount &&
|
||||
Object.values(unsettledSpotBalances).flat().concat(unsettledPerpPositions)
|
||||
.length ? (
|
||||
showTableView ? (
|
||||
<Table>
|
||||
<thead>
|
||||
<TrHead>
|
||||
<Th className="bg-th-bkg-1 text-left">{t('market')}</Th>
|
||||
<Th className="bg-th-bkg-1 text-right">{t('trade:amount')}</Th>
|
||||
<Th className="bg-th-bkg-1 text-right" />
|
||||
</TrHead>
|
||||
</thead>
|
||||
<tbody>
|
||||
{Object.entries(unsettledSpotBalances).map(([mktAddress]) => {
|
||||
const market = group.getSerum3MarketByExternalMarket(
|
||||
new PublicKey(mktAddress)
|
||||
)
|
||||
const base = market?.name.split('/')[0]
|
||||
const quote = market?.name.split('/')[1]
|
||||
showTableView ? (
|
||||
<Table>
|
||||
<thead>
|
||||
<TrHead>
|
||||
<Th className="bg-th-bkg-1 text-left">{t('market')}</Th>
|
||||
<Th className="bg-th-bkg-1 text-right">{t('trade:amount')}</Th>
|
||||
{connected ? <Th className="bg-th-bkg-1 text-right" /> : null}
|
||||
</TrHead>
|
||||
</thead>
|
||||
<tbody>
|
||||
{Object.entries(unsettledSpotBalances).map(([mktAddress]) => {
|
||||
const market = group.getSerum3MarketByExternalMarket(
|
||||
new PublicKey(mktAddress)
|
||||
)
|
||||
const base = market?.name.split('/')[0]
|
||||
const quote = market?.name.split('/')[1]
|
||||
|
||||
return (
|
||||
<TrBody key={mktAddress} className="text-sm">
|
||||
<Td>
|
||||
<TableMarketName market={market} />
|
||||
</Td>
|
||||
<Td className="text-right font-mono">
|
||||
<div className="flex justify-end">
|
||||
{unsettledSpotBalances[mktAddress].base ? (
|
||||
<div>
|
||||
{unsettledSpotBalances[mktAddress].base}{' '}
|
||||
<span className="font-body tracking-wide text-th-fgd-4">
|
||||
{base}
|
||||
</span>
|
||||
</div>
|
||||
) : null}
|
||||
{unsettledSpotBalances[mktAddress].quote ? (
|
||||
<div className="ml-4">
|
||||
{unsettledSpotBalances[mktAddress].quote}{' '}
|
||||
<span className="font-body tracking-wide text-th-fgd-4">
|
||||
{quote}
|
||||
</span>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
</Td>
|
||||
return (
|
||||
<TrBody key={mktAddress} className="text-sm">
|
||||
<Td>
|
||||
<TableMarketName market={market} />
|
||||
</Td>
|
||||
<Td className="text-right font-mono">
|
||||
<div className="flex justify-end">
|
||||
{unsettledSpotBalances[mktAddress].base ? (
|
||||
<div>
|
||||
{unsettledSpotBalances[mktAddress].base}{' '}
|
||||
<span className="font-body text-th-fgd-4">{base}</span>
|
||||
</div>
|
||||
) : null}
|
||||
{unsettledSpotBalances[mktAddress].quote ? (
|
||||
<div className="ml-4">
|
||||
{unsettledSpotBalances[mktAddress].quote}{' '}
|
||||
<span className="font-body text-th-fgd-4">{quote}</span>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
</Td>
|
||||
{connected ? (
|
||||
<Td>
|
||||
<div className="flex justify-end">
|
||||
<Tooltip content={t('trade:settle-funds')}>
|
||||
|
@ -196,74 +189,75 @@ const UnsettledTrades = ({
|
|||
</Tooltip>
|
||||
</div>
|
||||
</Td>
|
||||
</TrBody>
|
||||
)
|
||||
})}
|
||||
{unsettledPerpPositions.map((position) => {
|
||||
const market = group.getPerpMarketByMarketIndex(
|
||||
position.marketIndex
|
||||
)
|
||||
return (
|
||||
<TrBody key={position.marketIndex} className="text-sm">
|
||||
<Td>
|
||||
<TableMarketName market={market} />
|
||||
</Td>
|
||||
<Td className="text-right font-mono">
|
||||
{position.getEquityUi(group, market)}
|
||||
</Td>
|
||||
<Td>
|
||||
<div className="flex justify-end">
|
||||
<Tooltip content={t('trade:settle-funds')}>
|
||||
<IconButton
|
||||
onClick={() => handleSettlePerpFunds(market)}
|
||||
size="small"
|
||||
>
|
||||
{settleMktAddress === market.publicKey.toString() ? (
|
||||
<Loading className="h-4 w-4" />
|
||||
) : (
|
||||
<CheckIcon className="h-4 w-4" />
|
||||
)}
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</Td>
|
||||
</TrBody>
|
||||
)
|
||||
})}
|
||||
</tbody>
|
||||
</Table>
|
||||
) : (
|
||||
<div className="pb-20">
|
||||
{Object.entries(unsettledSpotBalances).map(([mktAddress]) => {
|
||||
const market = group.getSerum3MarketByExternalMarket(
|
||||
new PublicKey(mktAddress)
|
||||
) : null}
|
||||
</TrBody>
|
||||
)
|
||||
})}
|
||||
{unsettledPerpPositions.map((position) => {
|
||||
const market = group.getPerpMarketByMarketIndex(
|
||||
position.marketIndex
|
||||
)
|
||||
const base = market?.name.split('/')[0]
|
||||
const quote = market?.name.split('/')[1]
|
||||
|
||||
return (
|
||||
<div
|
||||
key={mktAddress}
|
||||
className="flex items-center justify-between border-b border-th-bkg-3 p-4"
|
||||
>
|
||||
<TableMarketName market={market} />
|
||||
<div className="flex items-center space-x-3">
|
||||
{unsettledSpotBalances[mktAddress].base ? (
|
||||
<span className="font-mono text-sm">
|
||||
{unsettledSpotBalances[mktAddress].base}{' '}
|
||||
<span className="font-body tracking-wide text-th-fgd-4">
|
||||
{base}
|
||||
</span>
|
||||
</span>
|
||||
) : null}
|
||||
{unsettledSpotBalances[mktAddress].quote ? (
|
||||
<span className="font-mono text-sm">
|
||||
{unsettledSpotBalances[mktAddress].quote}{' '}
|
||||
<span className="font-body tracking-wide text-th-fgd-4">
|
||||
{quote}
|
||||
</span>
|
||||
</span>
|
||||
) : null}
|
||||
<TrBody key={position.marketIndex} className="text-sm">
|
||||
<Td>
|
||||
<TableMarketName market={market} />
|
||||
</Td>
|
||||
<Td className="text-right font-mono">
|
||||
{formatDecimal(
|
||||
position.getEquityUi(group, market),
|
||||
market.baseDecimals
|
||||
)}
|
||||
</Td>
|
||||
<Td>
|
||||
<div className="flex justify-end">
|
||||
<Tooltip content={t('trade:settle-funds')}>
|
||||
<IconButton
|
||||
onClick={() => handleSettlePerpFunds(market)}
|
||||
size="small"
|
||||
>
|
||||
{settleMktAddress === market.publicKey.toString() ? (
|
||||
<Loading className="h-4 w-4" />
|
||||
) : (
|
||||
<CheckIcon className="h-4 w-4" />
|
||||
)}
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</Td>
|
||||
</TrBody>
|
||||
)
|
||||
})}
|
||||
</tbody>
|
||||
</Table>
|
||||
) : (
|
||||
<div className="pb-20">
|
||||
{Object.entries(unsettledSpotBalances).map(([mktAddress]) => {
|
||||
const market = group.getSerum3MarketByExternalMarket(
|
||||
new PublicKey(mktAddress)
|
||||
)
|
||||
const base = market?.name.split('/')[0]
|
||||
const quote = market?.name.split('/')[1]
|
||||
|
||||
return (
|
||||
<div
|
||||
key={mktAddress}
|
||||
className="flex items-center justify-between border-b border-th-bkg-3 p-4"
|
||||
>
|
||||
<TableMarketName market={market} />
|
||||
<div className="flex items-center space-x-3">
|
||||
{unsettledSpotBalances[mktAddress].base ? (
|
||||
<span className="font-mono text-sm">
|
||||
{unsettledSpotBalances[mktAddress].base}{' '}
|
||||
<span className="font-body text-th-fgd-4">{base}</span>
|
||||
</span>
|
||||
) : null}
|
||||
{unsettledSpotBalances[mktAddress].quote ? (
|
||||
<span className="font-mono text-sm">
|
||||
{unsettledSpotBalances[mktAddress].quote}{' '}
|
||||
<span className="font-body text-th-fgd-4">{quote}</span>
|
||||
</span>
|
||||
) : null}
|
||||
{connected ? (
|
||||
<IconButton
|
||||
onClick={() => handleSettleSerumFunds(mktAddress)}
|
||||
>
|
||||
|
@ -273,22 +267,17 @@ const UnsettledTrades = ({
|
|||
<CheckIcon className="h-4 w-4" />
|
||||
)}
|
||||
</IconButton>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
)
|
||||
) : (
|
||||
<div className="flex flex-col items-center p-8">
|
||||
<NoSymbolIcon className="mb-2 h-6 w-6 text-th-fgd-4" />
|
||||
<p>{t('trade:no-unsettled')}</p>
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
)
|
||||
) : (
|
||||
<div className="flex flex-col items-center p-8">
|
||||
<LinkIcon className="mb-2 h-6 w-6 text-th-fgd-4" />
|
||||
<p>{t('trade:connect-unsettled')}</p>
|
||||
<NoSymbolIcon className="mb-2 h-6 w-6 text-th-fgd-4" />
|
||||
<p>{t('trade:no-unsettled')}</p>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -49,7 +49,7 @@ export const ConnectWalletButton: React.FC = () => {
|
|||
{connecting ? <Loading className="h-4 w-4" /> : t('connect')}
|
||||
</div>
|
||||
|
||||
<div className="text-xxs font-normal leading-3 tracking-wider text-th-fgd-3">
|
||||
<div className="text-xxs font-normal leading-3 text-th-fgd-3">
|
||||
{preselectedWalletName}
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -26,18 +26,18 @@ const ConnectedMenu = () => {
|
|||
const [showMangoAccountsModal, setShowMangoAccountsModal] = useState(false)
|
||||
|
||||
const set = mangoStore((s) => s.set)
|
||||
const actions = mangoStore((s) => s.actions)
|
||||
const profileDetails = mangoStore((s) => s.profile.details)
|
||||
const actions = mangoStore.getState().actions
|
||||
// const profileDetails = mangoStore((s) => s.profile.details)
|
||||
const loadProfileDetails = mangoStore((s) => s.profile.loadDetails)
|
||||
|
||||
const isMobile = width ? width < breakpoints.md : false
|
||||
|
||||
const onConnectFetchAccountData = async (wallet: Wallet) => {
|
||||
if (!wallet) return
|
||||
const actions = mangoStore.getState().actions
|
||||
await actions.fetchMangoAccounts(wallet.adapter as unknown as AnchorWallet)
|
||||
actions.fetchTourSettings(wallet.adapter.publicKey?.toString() as string)
|
||||
actions.fetchWalletTokens(wallet.adapter as unknown as AnchorWallet)
|
||||
actions.fetchTradeHistory()
|
||||
}
|
||||
|
||||
const handleDisconnect = useCallback(() => {
|
||||
|
@ -89,14 +89,17 @@ const ConnectedMenu = () => {
|
|||
/>
|
||||
{!loadProfileDetails && !isMobile ? (
|
||||
<div className="ml-2.5 overflow-hidden text-left">
|
||||
<p className="font-mono text-xs text-th-fgd-3">
|
||||
<p className="text-xs text-th-fgd-3">
|
||||
{wallet?.adapter.name}
|
||||
</p>
|
||||
<p className="truncate pr-2 text-sm font-bold text-th-fgd-1">
|
||||
{publicKey ? abbreviateAddress(publicKey) : ''}
|
||||
</p>
|
||||
<p className="truncate pr-2 text-sm font-bold capitalize text-th-fgd-1">
|
||||
{/* <p className="truncate pr-2 text-sm font-bold capitalize text-th-fgd-1">
|
||||
{profileDetails?.profile_name
|
||||
? profileDetails.profile_name
|
||||
: 'Profile Unavailabe'}
|
||||
</p>
|
||||
</p> */}
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
|
@ -120,7 +123,7 @@ const ConnectedMenu = () => {
|
|||
>
|
||||
<UserCircleIcon className="h-4 w-4" />
|
||||
<div className="pl-2 text-left">
|
||||
{t('profile:edit-profile')}
|
||||
{t('profile:edit-profile-pic')}
|
||||
</div>
|
||||
</button>
|
||||
</Menu.Item>
|
||||
|
|
|
@ -47,7 +47,9 @@ const useJupiterMints = (): {
|
|||
(t) => t.address === WRAPPED_SOL_MINT.toString()
|
||||
)
|
||||
if (findSol) {
|
||||
findSol.logoURI = '/icons/sol.svg'
|
||||
if (findSol.logoURI !== '/icons/sol.svg') {
|
||||
findSol.logoURI = '/icons/sol.svg'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,12 +1,24 @@
|
|||
import { MangoAccount } from '@blockworks-foundation/mango-v4'
|
||||
import { PublicKey } from '@solana/web3.js'
|
||||
import mangoStore from '@store/mangoStore'
|
||||
import { useMemo } from 'react'
|
||||
|
||||
export default function useMangoAccount(): {
|
||||
mangoAccount: MangoAccount | undefined
|
||||
initialLoad: boolean
|
||||
mangoAccountPk: PublicKey | undefined
|
||||
mangoAccountAddress: string
|
||||
} {
|
||||
const mangoAccount = mangoStore((s) => s.mangoAccount.current)
|
||||
const initialLoad = mangoStore((s) => s.mangoAccount.initialLoad)
|
||||
|
||||
return { mangoAccount, initialLoad }
|
||||
const mangoAccountPk = useMemo(() => {
|
||||
return mangoAccount?.publicKey
|
||||
}, [mangoAccount?.publicKey])
|
||||
|
||||
const mangoAccountAddress = useMemo(() => {
|
||||
return mangoAccountPk?.toString() || ''
|
||||
}, [mangoAccountPk])
|
||||
|
||||
return { mangoAccount, initialLoad, mangoAccountAddress, mangoAccountPk }
|
||||
}
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
import mangoStore from '@store/mangoStore'
|
||||
import { useEffect } from 'react'
|
||||
|
||||
const set = mangoStore.getState().set
|
||||
|
||||
export default function useMarkPrice() {
|
||||
const set = mangoStore((s) => s.set)
|
||||
const markPrice = mangoStore((s) => s.selectedMarket.markPrice)
|
||||
const orderbook = mangoStore((s) => s.selectedMarket.orderbook)
|
||||
const fills = mangoStore((s) => s.selectedMarket.fills)
|
||||
|
@ -28,7 +29,7 @@ export default function useMarkPrice() {
|
|||
state.selectedMarket.markPrice = newMarkPrice
|
||||
})
|
||||
}
|
||||
}, [orderbook, trades])
|
||||
}, [orderbook, trades, markPrice])
|
||||
|
||||
return markPrice
|
||||
}
|
||||
|
|
|
@ -7,7 +7,7 @@ const nextConfig = {
|
|||
BROWSER: true,
|
||||
},
|
||||
images: {
|
||||
domains: ['raw.githubusercontent.com'],
|
||||
domains: ['raw.githubusercontent.com', 'arweave.net'],
|
||||
},
|
||||
reactStrictMode: true,
|
||||
webpack: (config, opts) => {
|
||||
|
|
|
@ -12,7 +12,7 @@
|
|||
"postinstall": "tar -xzC public -f vendor/charting_library.tgz;tar -xzC public -f vendor/datafeeds.tgz"
|
||||
},
|
||||
"dependencies": {
|
||||
"@blockworks-foundation/mango-v4": "https://github.com/blockworks-foundation/mango-v4.git#main",
|
||||
"@blockworks-foundation/mango-v4": "https://github.com/blockworks-foundation/mango-v4.git#ts-client",
|
||||
"@headlessui/react": "^1.6.6",
|
||||
"@heroicons/react": "^2.0.10",
|
||||
"@project-serum/anchor": "0.25.0",
|
||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 164 KiB |
Binary file not shown.
After Width: | Height: | Size: 95 KiB |
Binary file not shown.
After Width: | Height: | Size: 151 KiB |
Binary file not shown.
After Width: | Height: | Size: 426 KiB |
|
@ -15,12 +15,14 @@
|
|||
"liquidation-type": "Liquidation Type",
|
||||
"liquidations": "Liquidations",
|
||||
"liquidation-details": "Liquidation Details",
|
||||
"liquidate_token_with_token": "Spot Liquidation",
|
||||
"no-activity": "No account activity",
|
||||
"openbook_trade": "Spot Trade",
|
||||
"perps": "Perps",
|
||||
"perp_trade": "Perp",
|
||||
"perp_trade": "Perp Trade",
|
||||
"reset-filters": "Reset Filters",
|
||||
"select-tokens": "Select Tokens",
|
||||
"spot-liquidation": "Spot Liquidation",
|
||||
"spot-trade": "Spot Trade",
|
||||
"swap": "Swap",
|
||||
"swaps": "Swaps",
|
||||
"update": "Update",
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
"account-balance": "Account Balance",
|
||||
"account-name": "Account Name",
|
||||
"account-name-desc": "Organize your accounts by giving them useful names",
|
||||
"account-settings": "Account Settings",
|
||||
"account-update-failed": "Failed to update account",
|
||||
"account-update-success": "Account updated successfully",
|
||||
"account-value": "Account Value",
|
||||
|
@ -14,8 +15,10 @@
|
|||
"add-new-account": "Add New Account",
|
||||
"amount": "Amount",
|
||||
"amount-owed": "Amount Owed",
|
||||
"asset-liability-weight": "Asset/Liability Weights",
|
||||
"asset-liability-weight-desc": "Asset weight applies a haircut to the value of the collateral in your account health calculation. The lower the asset weight, the less the asset counts towards collateral. Liability weight does the opposite (adds to the value of the liability in your health calculation).",
|
||||
"asset-weight": "Asset Weight",
|
||||
"asset-weight-desc": "The asset weight applies a haircut to the value of the collateral in your account health calculation. The lower the asset weight, the less the asset counts towards collateral.",
|
||||
"asset-weight-desc": "Asset weight applies a haircut to the value of the collateral in your account health calculation. The lower the asset weight, the less the asset counts towards collateral.",
|
||||
"available": "Available",
|
||||
"available-balance": "Available Balance",
|
||||
"balance": "Balance",
|
||||
|
@ -24,7 +27,7 @@
|
|||
"borrow-amount": "Borrow Amount",
|
||||
"borrow-fee": "Borrow Fee",
|
||||
"borrow-funds": "Borrow Funds",
|
||||
"borrow-rate": "Borrow Rate (APR)",
|
||||
"borrow-rate": "Borrow APR",
|
||||
"buy": "Buy",
|
||||
"cancel": "Cancel",
|
||||
"chart-unavailable": "Chart Unavailable",
|
||||
|
@ -53,10 +56,10 @@
|
|||
"delegate-placeholder": "Enter a wallet address to delegate to",
|
||||
"deposit": "Deposit",
|
||||
"deposit-amount": "Deposit Amount",
|
||||
"deposit-more-sol": "Your SOL wallet balance is low. Add more to pay for transactions",
|
||||
"deposit-rate": "Deposit Rate (APR)",
|
||||
"deposit-more-sol": "Your SOL wallet balance is too low. Add more to pay for transactions",
|
||||
"deposit-rate": "Deposit APR",
|
||||
"disconnect": "Disconnect",
|
||||
"edit-account": "Edit Account",
|
||||
"edit-account": "Edit Account Name",
|
||||
"edit-profile-image": "Edit Profile Image",
|
||||
"explorer": "Explorer",
|
||||
"fee": "Fee",
|
||||
|
@ -98,6 +101,7 @@
|
|||
"repayment-amount": "Repayment Amount",
|
||||
"rolling-change": "24h Change",
|
||||
"save": "Save",
|
||||
"select": "Select",
|
||||
"select-borrow-token": "Select Borrow Token",
|
||||
"select-deposit-token": "Select Deposit Token",
|
||||
"select-repay-token": "Select Repay Token",
|
||||
|
@ -127,6 +131,7 @@
|
|||
"trade-history": "Trade History",
|
||||
"transaction": "Transaction",
|
||||
"unavailable": "Unavailable",
|
||||
"unowned-helper": "Currently viewing account {{accountPk}}",
|
||||
"update": "Update",
|
||||
"update-delegate": "Update Delegate",
|
||||
"updating-account-name": "Updating Account Name...",
|
||||
|
@ -137,7 +142,6 @@
|
|||
"wallet-balance": "Wallet Balance",
|
||||
"wallet-disconnected": "Disconnected from wallet",
|
||||
"withdraw": "Withdraw",
|
||||
"withdraw-amount": "Withdraw Amount",
|
||||
"account-settings": "Account Settings"
|
||||
"withdraw-amount": "Withdraw Amount"
|
||||
}
|
||||
|
|
@ -7,8 +7,8 @@
|
|||
"free-collateral-desc": "The amount of capital you have to trade or borrow against. When your free collateral reaches $0 you won't be able to make withdrawals.",
|
||||
"health": "Health",
|
||||
"health-desc": "If your account health reaches 0% your account will be liquidated. You can increase the health of your account by making a deposit.",
|
||||
"health-check": "Health Check",
|
||||
"health-check-desc": "Check the health of your account from any screen in the app. A green heart represents good health, orange okay and red poor.",
|
||||
"account-summary": "Account Summary",
|
||||
"account-summary-desc": "Check your key account information from any screen in the app.",
|
||||
"health-impact": "Health Impact",
|
||||
"health-impact-desc": "Projects the health of your account before you make a swap. The first value is your current account health and the second, your projected account health.",
|
||||
"interest-earned": "Interest Earned",
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"bullet-1": "Fully permissionless",
|
||||
"bullet-2": "Up to 20x leverage",
|
||||
"bullet-2": "Up to 10x leverage",
|
||||
"bullet-3": "Automatically earn interest on your deposits",
|
||||
"bullet-4": "Borrow tokens with many collateral options",
|
||||
"choose-wallet": "Choose Wallet",
|
||||
|
|
|
@ -7,9 +7,11 @@
|
|||
"hide-fees": "Hide Fees",
|
||||
"insufficient-balance": "Insufficient {{symbol}} Balance",
|
||||
"insufficient-collateral": "Insufficient Collateral",
|
||||
"max-slippage": "Max Slippage",
|
||||
"minimum-received": "Minimum Received",
|
||||
"no-history": "No swap history",
|
||||
"pay": "You Pay",
|
||||
"preset": "Preset",
|
||||
"price-impact": "Price Impact",
|
||||
"rate": "Rate",
|
||||
"receive": "You Receive",
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
"amount": "Amount",
|
||||
"base": "Base",
|
||||
"book": "Book",
|
||||
"buys": "Buys",
|
||||
"cancel-order-error": "Failed to cancel order",
|
||||
"connect-orders": "Connect to view your open orders",
|
||||
"connect-positions": "Connect to view your perp positions",
|
||||
|
@ -21,6 +22,7 @@
|
|||
"no-orders": "No open orders",
|
||||
"no-positions": "No perp positions",
|
||||
"no-unsettled": "No unsettled funds",
|
||||
"notional": "Notional",
|
||||
"open-interest": "Open Interest",
|
||||
"oracle-price": "Oracle Price",
|
||||
"order-error": "Failed to place order",
|
||||
|
@ -31,6 +33,7 @@
|
|||
"place-order": "Place {{side}} Order",
|
||||
"placing-order": "Placing Order",
|
||||
"quote": "Quote",
|
||||
"sells": "Sells",
|
||||
"settle-funds": "Settle Funds",
|
||||
"settle-funds-error": "Failed to settle funds",
|
||||
"show-asks": "Show Asks",
|
||||
|
|
|
@ -15,12 +15,14 @@
|
|||
"liquidation-type": "Liquidation Type",
|
||||
"liquidations": "Liquidations",
|
||||
"liquidation-details": "Liquidation Details",
|
||||
"liquidate_token_with_token": "Spot Liquidation",
|
||||
"no-activity": "No account activity",
|
||||
"openbook_trade": "Spot Trade",
|
||||
"perps": "Perps",
|
||||
"perp_trade": "Perp",
|
||||
"perp_trade": "Perp Trade",
|
||||
"reset-filters": "Reset Filters",
|
||||
"select-tokens": "Select Tokens",
|
||||
"spot-liquidation": "Spot Liquidation",
|
||||
"spot-trade": "Spot Trade",
|
||||
"swap": "Swap",
|
||||
"swaps": "Swaps",
|
||||
"update": "Update",
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
"account-balance": "Account Balance",
|
||||
"account-name": "Account Name",
|
||||
"account-name-desc": "Organize your accounts by giving them useful names",
|
||||
"account-settings": "Account Settings",
|
||||
"account-update-failed": "Failed to update account",
|
||||
"account-update-success": "Account updated successfully",
|
||||
"account-value": "Account Value",
|
||||
|
@ -14,8 +15,10 @@
|
|||
"add-new-account": "Add New Account",
|
||||
"amount": "Amount",
|
||||
"amount-owed": "Amount Owed",
|
||||
"asset-liability-weight": "Asset/Liability Weights",
|
||||
"asset-liability-weight-desc": "Asset weight applies a haircut to the value of the collateral in your account health calculation. The lower the asset weight, the less the asset counts towards collateral. Liability weight does the opposite (adds to the value of the liability in your health calculation).",
|
||||
"asset-weight": "Asset Weight",
|
||||
"asset-weight-desc": "The asset weight applies a haircut to the value of the collateral in your account health calculation. The lower the asset weight, the less the asset counts towards collateral.",
|
||||
"asset-weight-desc": "Asset weight applies a haircut to the value of the collateral in your account health calculation. The lower the asset weight, the less the asset counts towards collateral.",
|
||||
"available": "Available",
|
||||
"available-balance": "Available Balance",
|
||||
"balance": "Balance",
|
||||
|
@ -24,7 +27,7 @@
|
|||
"borrow-amount": "Borrow Amount",
|
||||
"borrow-fee": "Borrow Fee",
|
||||
"borrow-funds": "Borrow Funds",
|
||||
"borrow-rate": "Borrow Rate (APR)",
|
||||
"borrow-rate": "Borrow APR",
|
||||
"buy": "Buy",
|
||||
"cancel": "Cancel",
|
||||
"chart-unavailable": "Chart Unavailable",
|
||||
|
@ -53,10 +56,10 @@
|
|||
"delegate-placeholder": "Enter a wallet address to delegate to",
|
||||
"deposit": "Deposit",
|
||||
"deposit-amount": "Deposit Amount",
|
||||
"deposit-more-sol": "Your SOL wallet balance is low. Add more to pay for transactions",
|
||||
"deposit-rate": "Deposit Rate (APR)",
|
||||
"deposit-more-sol": "Your SOL wallet balance is too low. Add more to pay for transactions",
|
||||
"deposit-rate": "Deposit APR",
|
||||
"disconnect": "Disconnect",
|
||||
"edit-account": "Edit Account",
|
||||
"edit-account": "Edit Account Name",
|
||||
"edit-profile-image": "Edit Profile Image",
|
||||
"explorer": "Explorer",
|
||||
"fee": "Fee",
|
||||
|
@ -98,6 +101,7 @@
|
|||
"repayment-amount": "Repayment Amount",
|
||||
"rolling-change": "24h Change",
|
||||
"save": "Save",
|
||||
"select": "Select",
|
||||
"select-borrow-token": "Select Borrow Token",
|
||||
"select-deposit-token": "Select Deposit Token",
|
||||
"select-repay-token": "Select Repay Token",
|
||||
|
@ -127,6 +131,7 @@
|
|||
"trade-history": "Trade History",
|
||||
"transaction": "Transaction",
|
||||
"unavailable": "Unavailable",
|
||||
"unowned-helper": "Currently viewing account {{accountPk}}",
|
||||
"update": "Update",
|
||||
"update-delegate": "Update Delegate",
|
||||
"updating-account-name": "Updating Account Name...",
|
||||
|
@ -137,7 +142,6 @@
|
|||
"wallet-balance": "Wallet Balance",
|
||||
"wallet-disconnected": "Disconnected from wallet",
|
||||
"withdraw": "Withdraw",
|
||||
"withdraw-amount": "Withdraw Amount",
|
||||
"account-settings": "Account Settings"
|
||||
"withdraw-amount": "Withdraw Amount"
|
||||
}
|
||||
|
|
@ -7,8 +7,8 @@
|
|||
"free-collateral-desc": "The amount of capital you have to trade or borrow against. When your free collateral reaches $0 you won't be able to make withdrawals.",
|
||||
"health": "Health",
|
||||
"health-desc": "If your account health reaches 0% your account will be liquidated. You can increase the health of your account by making a deposit.",
|
||||
"health-check": "Health Check",
|
||||
"health-check-desc": "Check the health of your account from any screen in the app. A green heart represents good health, orange okay and red poor.",
|
||||
"account-summary": "Account Summary",
|
||||
"account-summary-desc": "Check your key account information from any screen in the app.",
|
||||
"health-impact": "Health Impact",
|
||||
"health-impact-desc": "Projects the health of your account before you make a swap. The first value is your current account health and the second, your projected account health.",
|
||||
"interest-earned": "Interest Earned",
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"bullet-1": "Fully permissionless",
|
||||
"bullet-2": "Up to 20x leverage",
|
||||
"bullet-2": "Up to 10x leverage",
|
||||
"bullet-3": "Automatically earn interest on your deposits",
|
||||
"bullet-4": "Borrow tokens with many collateral options",
|
||||
"choose-wallet": "Choose Wallet",
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue