merge main

This commit is contained in:
saml33 2023-07-23 21:06:29 +10:00
commit 8cf2da402c
231 changed files with 4603 additions and 3306 deletions

View File

@ -1,4 +1,5 @@
{
"semi": false,
"singleQuote": true
"singleQuote": true,
"trailingComma": "all"
}

View File

@ -110,7 +110,7 @@ export const calcPricePrecision = (num: number | string) => {
export const formatPrice = (
num: number,
precision?: number,
gr0 = true
gr0 = true,
): string => {
if (!num) {
return num.toString()
@ -132,7 +132,7 @@ export const formatPrice = (
const matchString: string = match[0].slice(1)
formated = formated.replace(
/\.0{4,15}/g,
`.0${SUBSCRIPT_NUMBER_MAP[matchString.length]}`
`.0${SUBSCRIPT_NUMBER_MAP[matchString.length]}`,
)
}

View File

@ -51,7 +51,7 @@ export function subscribeOnStream(
onRealtimeCallback: any,
subscriberUID: any,
onResetCacheNeededCallback: any,
lastBar: any
lastBar: any,
) {
subscriptionItem = {
resolution,

View File

@ -3,7 +3,7 @@ type CoingeckoOhlcv = [
open: number,
high: number,
low: number,
close: number
close: number,
][]
export type ChartDataItem = {
@ -16,16 +16,16 @@ export type ChartDataItem = {
export const fetchChartData = async (
baseTokenId: string | undefined,
quoteTokenId: string | undefined,
daysToShow: string
daysToShow: string,
): Promise<ChartDataItem[]> => {
if (!baseTokenId || !quoteTokenId) return []
try {
const [inputResponse, outputResponse] = await Promise.all([
fetch(
`https://api.coingecko.com/api/v3/coins/${baseTokenId}/ohlc?vs_currency=usd&days=${daysToShow}`
`https://api.coingecko.com/api/v3/coins/${baseTokenId}/ohlc?vs_currency=usd&days=${daysToShow}`,
),
fetch(
`https://api.coingecko.com/api/v3/coins/${quoteTokenId}/ohlc?vs_currency=usd&days=${daysToShow}`
`https://api.coingecko.com/api/v3/coins/${quoteTokenId}/ohlc?vs_currency=usd&days=${daysToShow}`,
),
])
@ -36,7 +36,7 @@ export const fetchChartData = async (
const parsedData: ChartDataItem[] = []
for (const inputTokenCandle of inputTokenData) {
const outputTokenCandle = outputTokenData.find(
(outputTokenCandle) => outputTokenCandle[0] === inputTokenCandle[0]
(outputTokenCandle) => outputTokenCandle[0] === inputTokenCandle[0],
)
if (outputTokenCandle) {
if (['usd-coin', 'tether'].includes(quoteTokenId)) {

View File

@ -72,11 +72,11 @@ const configurationData = {
const getTickerFromMktAddress = (
group: Group,
symbolAddress: string
symbolAddress: string,
): string | null => {
try {
const serumMkt = group.getSerum3MarketByExternalMarket(
new PublicKey(symbolAddress)
new PublicKey(symbolAddress),
)
if (serumMkt) {
@ -88,7 +88,7 @@ const getTickerFromMktAddress = (
const perpMarkets = Array.from(group.perpMarketsMapByMarketIndex.values())
const perpMkt = perpMarkets.find(
(perpMarket) => perpMarket.publicKey.toString() === symbolAddress
(perpMarket) => perpMarket.publicKey.toString() === symbolAddress,
)
if (perpMkt) {
@ -102,12 +102,12 @@ let marketType: 'spot' | 'perp'
export const queryPerpBars = async (
tokenAddress: string,
resolution: typeof SUPPORTED_RESOLUTIONS[number],
resolution: (typeof SUPPORTED_RESOLUTIONS)[number],
periodParams: {
firstDataRequest: boolean
from: number
to: number
}
},
): Promise<Bar[]> => {
const { from, to } = periodParams
@ -150,12 +150,12 @@ export const queryPerpBars = async (
export const queryBirdeyeBars = async (
tokenAddress: string,
resolution: typeof SUPPORTED_RESOLUTIONS[number],
resolution: (typeof SUPPORTED_RESOLUTIONS)[number],
periodParams: {
firstDataRequest: boolean
from: number
to: number
}
},
): Promise<Bar[]> => {
const { from, to } = periodParams
const urlParameters = {
@ -167,7 +167,7 @@ export const queryBirdeyeBars = async (
const query = Object.keys(urlParameters)
.map(
(name: string) =>
`${name}=${encodeURIComponent((urlParameters as any)[name])}`
`${name}=${encodeURIComponent((urlParameters as any)[name])}`,
)
.join('&')
@ -207,14 +207,14 @@ export default {
_userInput: string,
_exchange: string,
_symbolType: string,
_onResultReadyCallback: (items: SearchSymbolResultItem[]) => void
_onResultReadyCallback: (items: SearchSymbolResultItem[]) => void,
) => {
return
},
resolveSymbol: async (
symbolAddress: string,
onSymbolResolvedCallback: (symbolInfo: SymbolInfo) => void
onSymbolResolvedCallback: (symbolInfo: SymbolInfo) => void,
// _onResolveErrorCallback: any,
// _extension: any
) => {
@ -283,9 +283,9 @@ export default {
bars: Bar[],
t: {
noData: boolean
}
},
) => void,
onErrorCallback: (e: any) => void
onErrorCallback: (e: any) => void,
) => {
try {
const { firstDataRequest } = periodParams
@ -298,14 +298,14 @@ export default {
bars = await queryPerpBars(
symbolInfo.address,
resolution as any,
periodParams
periodParams,
)
} else {
marketType = 'spot'
bars = await queryBirdeyeBars(
symbolInfo.address,
resolution as any,
periodParams
periodParams,
)
}
if (!bars || bars.length === 0) {
@ -335,7 +335,7 @@ export default {
resolution: string,
onRealtimeCallback: (data: any) => void,
subscriberUID: string,
onResetCacheNeededCallback: () => void
onResetCacheNeededCallback: () => void,
) => {
subscriptionIds.set(subscriberUID, symbolInfo.address)
if (symbolInfo.description?.includes('PERP')) {
@ -345,7 +345,7 @@ export default {
onRealtimeCallback,
subscriberUID,
onResetCacheNeededCallback,
lastBarsCache.get(symbolInfo.address)
lastBarsCache.get(symbolInfo.address),
)
} else {
subscribeOnSpotStream(
@ -354,7 +354,7 @@ export default {
onRealtimeCallback,
subscriberUID,
onResetCacheNeededCallback,
lastBarsCache.get(symbolInfo.address)
lastBarsCache.get(symbolInfo.address),
)
}
},

View File

@ -66,12 +66,12 @@ const configurationData = {
export const queryBars = async (
tokenAddress: string,
resolution: typeof SUPPORTED_RESOLUTIONS[number],
resolution: (typeof SUPPORTED_RESOLUTIONS)[number],
periodParams: {
firstDataRequest: boolean
from: number
to: number
}
},
): Promise<Bar[]> => {
const { from, to } = periodParams
const urlParameters = {
@ -121,14 +121,14 @@ export default {
_userInput: string,
_exchange: string,
_symbolType: string,
_onResultReadyCallback: (items: SearchSymbolResultItem[]) => void
_onResultReadyCallback: (items: SearchSymbolResultItem[]) => void,
) => {
return
},
resolveSymbol: async (
symbolAddress: string,
onSymbolResolvedCallback: (symbolInfo: SymbolInfo) => void
onSymbolResolvedCallback: (symbolInfo: SymbolInfo) => void,
// _onResolveErrorCallback: any,
// _extension: any
) => {
@ -187,16 +187,16 @@ export default {
bars: Bar[],
t: {
noData: boolean
}
},
) => void,
onErrorCallback: (e: any) => void
onErrorCallback: (e: any) => void,
) => {
try {
const { firstDataRequest } = periodParams
const bars = await queryBars(
symbolInfo.address,
resolution as any,
periodParams
periodParams,
)
if (!bars || bars.length === 0) {
// "noData" should be set if there is no data in the requested period.
@ -226,7 +226,7 @@ export default {
resolution: string,
onRealtimeCallback: (data: any) => void,
subscriberUID: string,
onResetCacheNeededCallback: () => void
onResetCacheNeededCallback: () => void,
) => {
subscribeOnStream(
symbolInfo,
@ -234,7 +234,7 @@ export default {
onRealtimeCallback,
subscriberUID,
onResetCacheNeededCallback,
lastBarsCache.get(symbolInfo.address)
lastBarsCache.get(symbolInfo.address),
)
},

View File

@ -59,7 +59,7 @@ export function subscribeOnStream(
onRealtimeCallback: any,
subscriberUID: any,
onResetCacheNeededCallback: any,
lastBar: any
lastBar: any,
) {
subscriptionItem = {
resolution,

View File

@ -6,7 +6,7 @@ export type NotificationSettings = {
export const fetchNotificationSettings = async (
wallet: string,
token: string
token: string,
) => {
const data = await fetch(
`${NOTIFICATION_API}notifications/user/getSettings`,
@ -15,7 +15,7 @@ export const fetchNotificationSettings = async (
authorization: token,
publickey: wallet,
},
}
},
)
const body = await data.json()

View File

@ -35,7 +35,6 @@ import useMangoAccount from 'hooks/useMangoAccount'
import useMangoGroup from 'hooks/useMangoGroup'
import TokenVaultWarnings from '@components/shared/TokenVaultWarnings'
import { useWallet } from '@solana/wallet-adapter-react'
import { useEnhancedWallet } from './wallet/EnhancedWalletProvider'
import FormatNumericValue from './shared/FormatNumericValue'
import { floorToDecimal } from 'utils/numbers'
import BankAmountWithValue from './shared/BankAmountWithValue'
@ -58,13 +57,12 @@ function BorrowForm({ onSuccess, token }: BorrowFormProps) {
const [inputAmount, setInputAmount] = useState('')
const [submitting, setSubmitting] = useState(false)
const [selectedToken, setSelectedToken] = useState(
token || INPUT_TOKEN_DEFAULT
token || INPUT_TOKEN_DEFAULT,
)
const [showTokenList, setShowTokenList] = useState(false)
const [sizePercentage, setSizePercentage] = useState('')
const { mangoAccount } = useMangoAccount()
const { connected, publicKey } = useWallet()
const { handleConnect } = useEnhancedWallet()
const { connected, publicKey, connect } = useWallet()
const banks = useBanksWithBalances('maxBorrow')
const bank = useMemo(() => {
@ -93,11 +91,11 @@ function BorrowForm({ onSuccess, token }: BorrowFormProps) {
setSizePercentage(percentage)
const amount = floorToDecimal(
new Decimal(percentage).div(100).mul(tokenMax),
bank.mintDecimals
bank.mintDecimals,
)
setInputAmount(amount.toFixed())
},
[tokenMax, bank]
[tokenMax, bank],
)
const setMax = useCallback(() => {
@ -125,7 +123,7 @@ function BorrowForm({ onSuccess, token }: BorrowFormProps) {
mangoAccount,
bank!.mint,
Number(inputAmount),
true
true,
)
notify({
title: 'Transaction confirmed',
@ -329,7 +327,7 @@ function BorrowForm({ onSuccess, token }: BorrowFormProps) {
) : null}
</div>
<Button
onClick={connected ? handleBorrow : handleConnect}
onClick={connected ? handleBorrow : connect}
className="flex w-full items-center justify-center"
disabled={connected && (!inputAmount || showInsufficientBalance)}
size="large"

View File

@ -25,7 +25,6 @@ import MaxAmountButton from '@components/shared/MaxAmountButton'
import Tooltip from '@components/shared/Tooltip'
import HealthImpactTokenChange from '@components/HealthImpactTokenChange'
import SolBalanceWarnings from '@components/shared/SolBalanceWarnings'
import { useEnhancedWallet } from './wallet/EnhancedWalletProvider'
import useSolBalance from 'hooks/useSolBalance'
import FormatNumericValue from './shared/FormatNumericValue'
import Decimal from 'decimal.js'
@ -44,7 +43,7 @@ interface DepositFormProps {
export const walletBalanceForToken = (
walletTokens: TokenAccount[],
token: string
token: string,
): { maxAmount: number; maxDecimals: number } => {
const group = mangoStore.getState().group
const bank = group?.banksMapByName.get(token)?.[0]
@ -68,11 +67,11 @@ function DepositForm({ onSuccess, token }: DepositFormProps) {
const [inputAmount, setInputAmount] = useState('')
const [submitting, setSubmitting] = useState(false)
const [selectedToken, setSelectedToken] = useState(
token || INPUT_TOKEN_DEFAULT
token || INPUT_TOKEN_DEFAULT,
)
const [showTokenList, setShowTokenList] = useState(false)
const [sizePercentage, setSizePercentage] = useState('')
const { handleConnect } = useEnhancedWallet()
const { connect } = useWallet()
const { maxSolDeposit } = useSolBalance()
const banks = useBanksWithBalances('walletBalance')
@ -99,11 +98,11 @@ function DepositForm({ onSuccess, token }: DepositFormProps) {
setSizePercentage(percentage)
const amount = floorToDecimal(
new Decimal(tokenMax.maxAmount).mul(percentage).div(100),
tokenMax.maxDecimals
tokenMax.maxDecimals,
)
setInputAmount(amount.toFixed())
},
[tokenMax]
[tokenMax],
)
const handleSelectToken = (token: string) => {
@ -125,7 +124,7 @@ function DepositForm({ onSuccess, token }: DepositFormProps) {
group,
mangoAccount,
bank.mint,
parseFloat(inputAmount)
parseFloat(inputAmount),
)
notify({
title: 'Transaction confirmed',
@ -226,7 +225,7 @@ function DepositForm({ onSuccess, token }: DepositFormProps) {
value={inputAmount}
onValueChange={(e: NumberFormatValues) => {
setInputAmount(
!Number.isNaN(Number(e.value)) ? e.value : ''
!Number.isNaN(Number(e.value)) ? e.value : '',
)
}}
isAllowed={withValueLimit}
@ -272,7 +271,7 @@ function DepositForm({ onSuccess, token }: DepositFormProps) {
) : null}
</div>
<Button
onClick={connected ? handleDeposit : handleConnect}
onClick={connected ? handleDeposit : connect}
className="flex w-full items-center justify-center"
disabled={connected && (!inputAmount || showInsufficientBalance)}
size="large"

View File

@ -22,7 +22,7 @@ const HealthImpactTokenChange = ({
mangoAccount.simHealthRatioWithTokenPositionUiChanges(
group,
[{ mintPk, uiTokenAmount }],
HealthType.maint
HealthType.maint,
)
return projectedHealth! > 100

View File

@ -12,10 +12,13 @@ import { useViewport } from '../hooks/useViewport'
import { breakpoints, nftThemeMeta } from '../utils/theme'
import mangoStore from '@store/mangoStore'
import BottomBar from './mobile/BottomBar'
import BounceLoader from './shared/BounceLoader'
import TopBar from './TopBar'
import useLocalStorageState from '../hooks/useLocalStorageState'
import { ACCEPT_TERMS_KEY, SIDEBAR_COLLAPSE_KEY } from '../utils/constants'
import {
ACCEPT_TERMS_KEY,
SECONDS,
SIDEBAR_COLLAPSE_KEY,
} from '../utils/constants'
import { useWallet } from '@solana/wallet-adapter-react'
import SuccessParticles from './shared/SuccessParticles'
import { tsParticles } from 'tsparticles-engine'
@ -36,14 +39,14 @@ const Layout = ({ children }: { children: ReactNode }) => {
const { theme } = useTheme()
const [isCollapsed, setIsCollapsed] = useLocalStorageState(
SIDEBAR_COLLAPSE_KEY,
false
false,
)
const { width } = useViewport()
const { asPath } = useRouter()
useEffect(() => {
if (width < breakpoints.xl) {
if (width < breakpoints['2xl']) {
setIsCollapsed(true)
}
}, [width])
@ -52,9 +55,12 @@ const Layout = ({ children }: { children: ReactNode }) => {
const animationFrames = 15
for (let x = 1; x <= animationFrames; x++) {
setTimeout(() => {
window.dispatchEvent(new Event('resize'))
}, (sideBarAnimationDuration / animationFrames) * x)
setTimeout(
() => {
window.dispatchEvent(new Event('resize'))
},
(sideBarAnimationDuration / animationFrames) * x,
)
}
}, [isCollapsed])
@ -72,7 +78,7 @@ const Layout = ({ children }: { children: ReactNode }) => {
useEffect(() => {
const set = mangoStore.getState().set
if (nftThemeMeta[theme]) {
if (theme && nftThemeMeta[theme]) {
set((s) => {
s.themeData = nftThemeMeta[theme]
})
@ -90,7 +96,6 @@ const Layout = ({ children }: { children: ReactNode }) => {
<div className="fixed z-30">
<SuccessParticles />
</div>
<MangoAccountLoadingOverlay />
<div
className={`min-h-screen flex-grow ${
!themeData.useGradientBg
@ -104,7 +109,7 @@ const Layout = ({ children }: { children: ReactNode }) => {
<div className="fixed z-20 hidden h-screen md:block">
<button
className="absolute right-0 top-1/2 z-20 hidden h-8 w-3 -translate-y-1/2 rounded-none rounded-l bg-th-bkg-3 hover:bg-th-bkg-4 focus:outline-none focus-visible:bg-th-bkg-4 xl:block"
className="absolute right-0 top-1/2 z-20 hidden h-8 w-3 -translate-y-1/2 rounded-none rounded-l bg-th-bkg-3 hover:bg-th-bkg-4 focus:outline-none focus-visible:bg-th-bkg-4 2xl:block"
onClick={handleToggleSidebar}
>
<ChevronRightIcon
@ -140,26 +145,11 @@ const Layout = ({ children }: { children: ReactNode }) => {
export default Layout
const MangoAccountLoadingOverlay = () => {
const { connected } = useWallet()
const loadingMangoAccount = mangoStore((s) => s.mangoAccount.initialLoad)
return (
<>
{connected && loadingMangoAccount ? (
<div className="fixed z-30 flex h-screen w-full items-center justify-center bg-[rgba(0,0,0,0.7)]">
<BounceLoader />
</div>
) : null}
</>
)
}
const TermsOfUse = () => {
const { connected } = useWallet()
const [acceptTerms, setAcceptTerms] = useLocalStorageState(
ACCEPT_TERMS_KEY,
''
'',
)
const showTermsOfUse = useMemo(() => {
@ -190,7 +180,7 @@ function DeployRefreshManager(): JSX.Element | null {
// There's a new version deployed that we need to load
setNewBuildAvailable(true)
}
}, 300000)
}, 300 * SECONDS)
return (
<Transition

View File

@ -5,6 +5,10 @@ import { useRouter } from 'next/router'
import { MangoAccount } from '@blockworks-foundation/mango-v4'
import useMangoAccount from 'hooks/useMangoAccount'
import useInterval from './shared/useInterval'
import { SECONDS } from 'utils/constants'
import useNetworkSpeed from 'hooks/useNetworkSpeed'
import { useWallet } from '@solana/wallet-adapter-react'
import useLocalStorageState from 'hooks/useLocalStorageState'
const set = mangoStore.getState().set
const actions = mangoStore.getState().actions
@ -14,6 +18,16 @@ const HydrateStore = () => {
const { name: marketName } = router.query
const { mangoAccountPk, mangoAccountAddress } = useMangoAccount()
const connection = mangoStore((s) => s.connection)
const slowNetwork = useNetworkSpeed()
const { wallet } = useWallet()
const [, setLastWalletName] = useLocalStorageState('lastWalletName', '')
useEffect(() => {
if (wallet?.adapter) {
setLastWalletName(wallet?.adapter.name)
}
}, [wallet, setLastWalletName])
useEffect(() => {
if (marketName && typeof marketName === 'string') {
@ -24,17 +38,23 @@ const HydrateStore = () => {
actions.fetchGroup()
}, [marketName])
useInterval(() => {
actions.fetchGroup()
}, 25000)
useInterval(
() => {
actions.fetchGroup()
},
(slowNetwork ? 40 : 20) * SECONDS,
)
// refetches open orders every 30 seconds
// only the selected market's open orders are updated via websocket
useInterval(() => {
if (mangoAccountAddress) {
actions.fetchOpenOrders()
}
}, 30000)
useInterval(
() => {
if (mangoAccountAddress) {
actions.fetchOpenOrders()
}
},
(slowNetwork ? 60 : 30) * SECONDS,
)
// refetch trade history and activity feed when switching accounts
useEffect(() => {
@ -45,10 +65,13 @@ const HydrateStore = () => {
}, [mangoAccountAddress])
// reload and parse market fills from the event queue
useInterval(async () => {
const actions = mangoStore.getState().actions
actions.loadMarketFills()
}, 30000)
useInterval(
async () => {
const actions = mangoStore.getState().actions
actions.loadMarketFills()
},
(slowNetwork ? 60 : 20) * SECONDS,
)
// 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.
@ -78,11 +101,11 @@ const HydrateStore = () => {
if (context.slot > lastSeenSlot) {
const decodedMangoAccount = client.program.coder.accounts.decode(
'mangoAccount',
info?.data
info?.data,
)
const newMangoAccount = MangoAccount.from(
mangoAccount.publicKey,
decodedMangoAccount
decodedMangoAccount,
)
if (newMangoAccount.serum3Active().length > 0) {
await newMangoAccount.reloadSerum3OpenOrders(client)
@ -93,7 +116,7 @@ const HydrateStore = () => {
})
actions.fetchOpenOrders()
}
}
},
)
return () => {

View File

@ -45,7 +45,7 @@ function RepayForm({ onSuccess, token }: RepayFormProps) {
const [inputAmount, setInputAmount] = useState('')
const [submitting, setSubmitting] = useState(false)
const [selectedToken, setSelectedToken] = useState(
token || INPUT_TOKEN_DEFAULT
token || INPUT_TOKEN_DEFAULT,
)
const [showTokenList, setShowTokenList] = useState(false)
const [sizePercentage, setSizePercentage] = useState('')
@ -69,7 +69,7 @@ function RepayForm({ onSuccess, token }: RepayFormProps) {
const borrowAmount = useMemo(() => {
if (!mangoAccount || !bank) return new Decimal(0)
const amount = new Decimal(
mangoAccount.getTokenBorrowsUi(bank)
mangoAccount.getTokenBorrowsUi(bank),
).toDecimalPlaces(bank.mintDecimals, Decimal.ROUND_UP)
return amount
}, [bank, mangoAccount])
@ -78,7 +78,7 @@ function RepayForm({ onSuccess, token }: RepayFormProps) {
if (!bank) return
const amount = new Decimal(borrowAmount).toDecimalPlaces(
bank.mintDecimals,
Decimal.ROUND_UP
Decimal.ROUND_UP,
)
setInputAmount(amount.toFixed())
setSizePercentage('100')
@ -95,7 +95,7 @@ function RepayForm({ onSuccess, token }: RepayFormProps) {
setInputAmount(amount.toFixed())
},
[bank, borrowAmount]
[bank, borrowAmount],
)
const handleSelectToken = (token: string) => {
@ -130,7 +130,7 @@ function RepayForm({ onSuccess, token }: RepayFormProps) {
mangoAccount,
bank.mint,
actualAmount,
true
true,
)
notify({
title: 'Transaction confirmed',
@ -154,7 +154,7 @@ function RepayForm({ onSuccess, token }: RepayFormProps) {
})
}
},
[bank, publicKey?.toBase58(), sizePercentage]
[bank, publicKey?.toBase58(), sizePercentage],
)
useEffect(() => {
@ -236,7 +236,7 @@ function RepayForm({ onSuccess, token }: RepayFormProps) {
value={inputAmount}
onValueChange={(e: NumberFormatValues) => {
setInputAmount(
!Number.isNaN(Number(e.value)) ? e.value : ''
!Number.isNaN(Number(e.value)) ? e.value : '',
)
}}
isAllowed={withValueLimit}

View File

@ -10,7 +10,7 @@ const tpsWarningThreshold = 1300
const getRecentPerformance = async (
connection: Connection,
setTps: (x: number) => void
setTps: (x: number) => void,
) => {
try {
const samples = 2
@ -36,7 +36,7 @@ const SolanaTps = () => {
useInterval(() => {
getRecentPerformance(connection, setTps)
}, 45 * 1000)
}, 60 * 1000)
if (CLUSTER == 'mainnet-beta') {
return (

View File

@ -32,17 +32,19 @@ import useBanksWithBalances, {
import useUnownedAccount from 'hooks/useUnownedAccount'
import useLocalStorageState from 'hooks/useLocalStorageState'
import TokenLogo from './shared/TokenLogo'
import useHealthContributions from 'hooks/useHealthContributions'
const TokenList = () => {
const { t } = useTranslation(['common', 'token', 'trade'])
const [showZeroBalances, setShowZeroBalances] = useLocalStorageState(
SHOW_ZERO_BALANCES_KEY,
true
true,
)
const { mangoAccount, mangoAccountAddress } = useMangoAccount()
const { initContributions } = useHealthContributions()
const spotBalances = mangoStore((s) => s.mangoAccount.spotBalances)
const totalInterestData = mangoStore(
(s) => s.mangoAccount.interestTotals.data
(s) => s.mangoAccount.interestTotals.data,
)
const { width } = useViewport()
const showTableView = width ? width > breakpoints.md : false
@ -57,6 +59,18 @@ const TokenList = () => {
return []
}, [banks, mangoAccountAddress, showZeroBalances])
// const filteredBanks = useMemo(() => {
// if (!banks.length) return []
// if (showZeroBalances || !mangoAccountAddress) return banks
// const filtered = banks.filter((b) => {
// const contribution =
// initContributions.find((cont) => cont.asset === b.bank.name)
// ?.contribution || 0
// return Math.abs(contribution) > 0
// })
// return filtered
// }, [banks, mangoAccountAddress, showZeroBalances, initContributions])
return (
<ContentBox hideBorder hidePadding>
{mangoAccountAddress ? (
@ -66,141 +80,181 @@ const TokenList = () => {
disabled={!mangoAccount}
onChange={() => setShowZeroBalances(!showZeroBalances)}
>
{t('show-zero-balances')}
{t('account:zero-balances')}
</Switch>
</div>
) : null}
{showTableView ? (
<Table>
<thead>
<TrHead>
<Th className="text-left">{t('token')}</Th>
<Th>
<div className="flex justify-end">
<Tooltip content="A negative balance represents a borrow">
<span className="tooltip-underline">{t('balance')}</span>
<>
<Table>
<thead>
<TrHead>
<Th className="text-left">{t('token')}</Th>
<Th>
<div className="flex justify-end">
<Tooltip content="A negative balance represents a borrow">
<span className="tooltip-underline">{t('balance')}</span>
</Tooltip>
</div>
</Th>
<Th>
<div className="flex justify-end">
<Tooltip content={t('tooltip-collateral-value')}>
<span className="tooltip-underline">
{t('collateral-value')}
</span>
</Tooltip>
</div>
</Th>
<Th className="text-right">{t('trade:in-orders')}</Th>
<Th className="text-right">{t('trade:unsettled')}</Th>
<Th className="flex justify-end" id="account-step-nine">
<Tooltip content="The sum of interest earned and interest paid for each token">
<span className="tooltip-underline">
{t('interest-earned-paid')}
</span>
</Tooltip>
</div>
</Th>
<Th className="text-right">{t('trade:in-orders')}</Th>
<Th className="text-right">{t('trade:unsettled')}</Th>
<Th className="flex justify-end" id="account-step-nine">
<Tooltip content="The sum of interest earned and interest paid for each token">
<span className="tooltip-underline">
{t('interest-earned-paid')}
</span>
</Tooltip>
</Th>
<Th id="account-step-ten">
<div className="flex justify-end">
<Tooltip content="The interest rates for depositing (green/left) and borrowing (red/right)">
<span className="tooltip-underline">{t('rates')}</span>
</Tooltip>
</div>
</Th>
<Th className="text-right">
<span>{t('actions')}</span>
</Th>
</TrHead>
</thead>
<tbody>
{filteredBanks.map((b) => {
const bank = b.bank
</Th>
<Th id="account-step-ten">
<div className="flex justify-end">
<Tooltip content="The interest rates for depositing (green/left) and borrowing (red/right)">
<span className="tooltip-underline">{t('rates')}</span>
</Tooltip>
</div>
</Th>
<Th className="text-right">
<span>{t('actions')}</span>
</Th>
</TrHead>
</thead>
<tbody>
{filteredBanks.map((b) => {
const bank = b.bank
const tokenBalance = b.balance
const symbol = bank.name === 'MSOL' ? 'mSOL' : bank.name
const tokenBalance = b.balance
const symbol = bank.name === 'MSOL' ? 'mSOL' : bank.name
const hasInterestEarned = totalInterestData.find(
(d) =>
d.symbol.toLowerCase() === symbol.toLowerCase() ||
(symbol === 'ETH (Portal)' && d.symbol === 'ETH')
)
const hasInterestEarned = totalInterestData.find(
(d) =>
d.symbol.toLowerCase() === symbol.toLowerCase() ||
(symbol === 'ETH (Portal)' && d.symbol === 'ETH'),
)
const interestAmount = hasInterestEarned
? hasInterestEarned.borrow_interest * -1 +
hasInterestEarned.deposit_interest
: 0
const interestAmount = hasInterestEarned
? hasInterestEarned.borrow_interest * -1 +
hasInterestEarned.deposit_interest
: 0
const interestValue = hasInterestEarned
? hasInterestEarned.borrow_interest_usd * -1 +
hasInterestEarned.deposit_interest_usd
: 0.0
const interestValue = hasInterestEarned
? hasInterestEarned.borrow_interest_usd * -1 +
hasInterestEarned.deposit_interest_usd
: 0.0
const inOrders = spotBalances[bank.mint.toString()]?.inOrders || 0
const inOrders =
spotBalances[bank.mint.toString()]?.inOrders || 0
const unsettled =
spotBalances[bank.mint.toString()]?.unsettled || 0
const unsettled =
spotBalances[bank.mint.toString()]?.unsettled || 0
return (
<TrBody key={bank.name}>
<Td>
<div className="flex items-center">
<div className="mr-2.5 flex flex-shrink-0 items-center">
<TokenLogo bank={bank} />
const collateralValue =
initContributions.find((val) => val.asset === bank.name)
?.contribution || 0
const assetWeight = bank
.scaledInitAssetWeight(bank.price)
.toFixed(2)
const liabWeight = bank
.scaledInitLiabWeight(bank.price)
.toFixed(2)
return (
<TrBody key={bank.name}>
<Td>
<div className="flex items-center">
<div className="mr-2.5 flex flex-shrink-0 items-center">
<TokenLogo bank={bank} />
</div>
<p className="font-body">{symbol}</p>
</div>
<p className="font-body">{symbol}</p>
</div>
</Td>
<Td className="text-right">
<BankAmountWithValue
amount={tokenBalance}
bank={bank}
stacked
/>
</Td>
<Td className="text-right">
<BankAmountWithValue
amount={inOrders}
bank={bank}
stacked
/>
</Td>
<Td className="text-right">
<BankAmountWithValue
amount={unsettled}
bank={bank}
stacked
/>
</Td>
<Td>
<div className="flex flex-col text-right">
</Td>
<Td className="text-right">
<BankAmountWithValue
amount={interestAmount}
amount={tokenBalance}
bank={bank}
value={interestValue}
stacked
/>
</div>
</Td>
<Td>
<div className="flex justify-end space-x-1.5">
<p className="text-th-up">
</Td>
<Td className="text-right">
<p>
<FormatNumericValue
value={bank.getDepositRateUi()}
value={collateralValue}
decimals={2}
isUsd
/>
%
</p>
<span className="text-th-fgd-4">|</span>
<p className="text-th-down">
<p className="text-sm text-th-fgd-4">
<FormatNumericValue
value={bank.getBorrowRateUi()}
decimals={2}
value={
collateralValue <= -0.01 ? liabWeight : assetWeight
}
/>
%
x
</p>
</div>
</Td>
<Td>
<div className="flex items-center justify-end">
<ActionsMenu bank={bank} mangoAccount={mangoAccount} />
</div>
</Td>
</TrBody>
)
})}
</tbody>
</Table>
</Td>
<Td className="text-right">
<BankAmountWithValue
amount={inOrders}
bank={bank}
stacked
/>
</Td>
<Td className="text-right">
<BankAmountWithValue
amount={unsettled}
bank={bank}
stacked
/>
</Td>
<Td>
<div className="flex flex-col text-right">
<BankAmountWithValue
amount={interestAmount}
bank={bank}
value={interestValue}
stacked
/>
</div>
</Td>
<Td>
<div className="flex justify-end space-x-1.5">
<p className="text-th-up">
<FormatNumericValue
value={bank.getDepositRateUi()}
decimals={2}
/>
%
</p>
<span className="text-th-fgd-4">|</span>
<p className="text-th-down">
<FormatNumericValue
value={bank.getBorrowRateUi()}
decimals={2}
/>
%
</p>
</div>
</Td>
<Td>
<div className="flex items-center justify-end">
<ActionsMenu bank={bank} mangoAccount={mangoAccount} />
</div>
</Td>
</TrBody>
)
})}
</tbody>
</Table>
</>
) : (
<div className="border-b border-th-bkg-3">
{filteredBanks.map((b) => {
@ -218,8 +272,9 @@ const MobileTokenListItem = ({ bank }: { bank: BankWithBalance }) => {
const { t } = useTranslation(['common', 'token'])
const spotBalances = mangoStore((s) => s.mangoAccount.spotBalances)
const { mangoAccount } = useMangoAccount()
const { initContributions } = useHealthContributions()
const totalInterestData = mangoStore(
(s) => s.mangoAccount.interestTotals.data
(s) => s.mangoAccount.interestTotals.data,
)
const tokenBank = bank.bank
const mint = tokenBank.mint
@ -228,7 +283,7 @@ const MobileTokenListItem = ({ bank }: { bank: BankWithBalance }) => {
const hasInterestEarned = totalInterestData.find(
(d) =>
d.symbol.toLowerCase() === symbol.toLowerCase() ||
(symbol === 'ETH (Portal)' && d.symbol === 'ETH')
(symbol === 'ETH (Portal)' && d.symbol === 'ETH'),
)
const interestAmount = hasInterestEarned
@ -242,10 +297,17 @@ const MobileTokenListItem = ({ bank }: { bank: BankWithBalance }) => {
: 0
const tokenBalance = bank.balance
const unsettled = spotBalances[mint.toString()]?.unsettled || 0
const inOrders = spotBalances[mint.toString()]?.inOrders || 0
const unsettled = spotBalances[mint.toString()]?.unsettled || 0
const collateralValue =
initContributions.find((val) => val.asset === tokenBank.name)
?.contribution || 0
const assetWeight = tokenBank
.scaledInitAssetWeight(tokenBank.price)
.toFixed(2)
const liabWeight = tokenBank.scaledInitLiabWeight(tokenBank.price).toFixed(2)
return (
<Disclosure>
@ -296,6 +358,29 @@ const MobileTokenListItem = ({ bank }: { bank: BankWithBalance }) => {
>
<Disclosure.Panel>
<div className="mx-4 grid grid-cols-2 gap-4 border-t border-th-bkg-3 pt-4 pb-4">
<div className="col-span-1">
<Tooltip content={t('tooltip-collateral-value')}>
<p className="tooltip-underline text-xs text-th-fgd-3">
{t('collateral-value')}
</p>
</Tooltip>
<p className="font-mono text-th-fgd-2">
<FormatNumericValue
value={collateralValue}
decimals={2}
isUsd
/>
<span className="text-th-fgd-3">
{' '}
<FormatNumericValue
value={
collateralValue <= -0.01 ? liabWeight : assetWeight
}
/>
x
</span>
</p>
</div>
<div className="col-span-1">
<p className="text-xs text-th-fgd-3">
{t('trade:in-orders')}
@ -387,7 +472,7 @@ const ActionsMenu = ({
? setShowWithdrawModal(true)
: setShowRepayModal(true)
},
[]
[],
)
const balance = useMemo(() => {
@ -397,7 +482,7 @@ const ActionsMenu = ({
const handleSwap = useCallback(() => {
const tokenInfo = mangoTokens.find(
(t) => t.address === bank.mint.toString()
(t) => t.address === bank.mint.toString(),
)
const group = mangoStore.getState().group
if (balance && balance > 0) {
@ -416,7 +501,7 @@ const ActionsMenu = ({
} else {
if (tokenInfo?.symbol === 'USDC') {
const solTokenInfo = mangoTokens.find(
(t) => t.address === WRAPPED_SOL_MINT.toString()
(t) => t.address === WRAPPED_SOL_MINT.toString(),
)
const solBank = group?.getFirstBankByMint(WRAPPED_SOL_MINT)
set((s) => {
@ -466,7 +551,7 @@ const ActionsMenu = ({
leaveTo="opacity-0"
>
<Popover.Panel
className={`thin-scroll absolute bottom-12 left-0 z-40 max-h-60 w-32 space-y-2 overflow-auto rounded-md bg-th-bkg-2 p-4 pt-2 md:bottom-0 md:right-12 md:left-auto md:pt-4`}
className={`thin-scroll absolute bottom-12 left-0 z-20 max-h-60 w-32 space-y-2 overflow-auto rounded-md bg-th-bkg-2 p-4 pt-2 md:bottom-0 md:right-12 md:left-auto md:pt-4`}
>
<div className="hidden items-center justify-center border-b border-th-bkg-3 pb-2 md:flex">
<div className="mr-2 flex flex-shrink-0 items-center">

View File

@ -12,12 +12,9 @@ import { useTranslation } from 'next-i18next'
import WalletIcon from './icons/WalletIcon'
import Button from './shared/Button'
import ConnectedMenu from './wallet/ConnectedMenu'
import { ConnectWalletButton } from './wallet/ConnectWalletButton'
import { IS_ONBOARDED_KEY } from '../utils/constants'
import useLocalStorageState from '../hooks/useLocalStorageState'
import ConnectWalletButton from './wallet/ConnectWalletButton'
import CreateAccountModal from './modals/CreateAccountModal'
import { useRouter } from 'next/router'
import UserSetupModal from './modals/UserSetupModal'
import SolanaTps from './SolanaTps'
import useMangoAccount from 'hooks/useMangoAccount'
import useOnlineStatus from 'hooks/useOnlineStatus'
@ -31,33 +28,46 @@ import NotificationsButton from './notifications/NotificationsButton'
import Tooltip from './shared/Tooltip'
import { copyToClipboard } from 'utils'
import mangoStore from '@store/mangoStore'
import UserSetupModal from './modals/UserSetupModal'
import { IS_ONBOARDED_KEY } from 'utils/constants'
import useLocalStorageState from 'hooks/useLocalStorageState'
const set = mangoStore.getState().set
const TopBar = () => {
const { t } = useTranslation('common')
const { mangoAccount } = useMangoAccount()
const { connected } = useWallet()
const themeData = mangoStore((s) => s.themeData)
const [isOnboarded, setIsOnboarded] = useLocalStorageState(IS_ONBOARDED_KEY)
const [action, setAction] = useState<'deposit' | 'withdraw'>('deposit')
const [showUserSetup, setShowUserSetup] = useState(false)
const [copied, setCopied] = useState('')
const [showDepositWithdrawModal, setShowDepositWithdrawModal] =
useState(false)
const [showCreateAccountModal, setShowCreateAccountModal] = useState(false)
const isOnline = useOnlineStatus()
const router = useRouter()
const { query } = router
const { width } = useViewport()
const isMobile = width ? width < breakpoints.sm : false
const { isUnownedAccount } = useUnownedAccount()
const showUserSetup = mangoStore((s) => s.showUserSetup)
const [, setIsOnboarded] = useLocalStorageState(IS_ONBOARDED_KEY)
const handleCloseSetup = useCallback(() => {
setShowUserSetup(false)
set((s) => {
s.showUserSetup = false
})
setIsOnboarded(true)
}, [setShowUserSetup, setIsOnboarded])
}, [setIsOnboarded])
const handleShowSetup = useCallback(() => {
setShowUserSetup(true)
set((s) => {
s.showUserSetup = true
})
}, [])
const handleDepositWithdrawModal = (action: 'deposit' | 'withdraw') => {
@ -194,18 +204,8 @@ const TopBar = () => {
<AccountsButton />
<ConnectedMenu />
</div>
) : isOnboarded ? (
<ConnectWalletButton />
) : (
<button
className="relative h-16 rounded-none bg-th-bkg-2 bg-gradient-to-bl px-6 font-display text-base text-th-fgd-1 before:absolute before:inset-0 before:bg-gradient-to-r before:from-transparent before:via-th-bkg-4 before:to-transparent before:opacity-0 hover:cursor-pointer hover:overflow-hidden hover:before:-translate-x-full hover:before:animate-[shimmer_0.75s_normal] hover:before:opacity-100"
onClick={handleShowSetup}
>
<div className="relative z-10 flex items-center justify-center">
<WalletIcon className="mr-2 h-5 w-5 flex-shrink-0" />
{t('connect')}
</div>
</button>
<ConnectWalletButton handleShowSetup={handleShowSetup} />
)}
</div>
</div>

View File

@ -30,7 +30,6 @@ import useMangoAccount from 'hooks/useMangoAccount'
import useMangoGroup from 'hooks/useMangoGroup'
import TokenVaultWarnings from '@components/shared/TokenVaultWarnings'
import { useWallet } from '@solana/wallet-adapter-react'
import { useEnhancedWallet } from './wallet/EnhancedWalletProvider'
import { floorToDecimal } from 'utils/numbers'
import BankAmountWithValue from './shared/BankAmountWithValue'
import useBanksWithBalances from 'hooks/useBanksWithBalances'
@ -50,13 +49,12 @@ function WithdrawForm({ onSuccess, token }: WithdrawFormProps) {
const [inputAmount, setInputAmount] = useState('')
const [submitting, setSubmitting] = useState(false)
const [selectedToken, setSelectedToken] = useState(
token || INPUT_TOKEN_DEFAULT
token || INPUT_TOKEN_DEFAULT,
)
const [showTokenList, setShowTokenList] = useState(false)
const [sizePercentage, setSizePercentage] = useState('')
const { mangoAccount } = useMangoAccount()
const { connected } = useWallet()
const { handleConnect } = useEnhancedWallet()
const { connect, connected } = useWallet()
const banks = useBanksWithBalances('maxWithdraw')
const bank = useMemo(() => {
@ -84,17 +82,17 @@ function WithdrawForm({ onSuccess, token }: WithdrawFormProps) {
if (percentage !== '100') {
amount = floorToDecimal(
new Decimal(adjustedTokenMax).mul(percentage).div(100),
bank.mintDecimals
bank.mintDecimals,
)
} else {
amount = floorToDecimal(
new Decimal(adjustedTokenMax),
bank.mintDecimals
bank.mintDecimals,
)
}
setInputAmount(amount.toString())
},
[bank, adjustedTokenMax]
[bank, adjustedTokenMax],
)
const setMax = useCallback(() => {
@ -118,7 +116,7 @@ function WithdrawForm({ onSuccess, token }: WithdrawFormProps) {
mangoAccount,
bank.mint,
withdrawAmount,
false
false,
)
notify({
title: 'Transaction confirmed',
@ -229,7 +227,7 @@ function WithdrawForm({ onSuccess, token }: WithdrawFormProps) {
value={inputAmount}
onValueChange={(e: NumberFormatValues) =>
setInputAmount(
!Number.isNaN(Number(e.value)) ? e.value : ''
!Number.isNaN(Number(e.value)) ? e.value : '',
)
}
isAllowed={withValueLimit}
@ -259,7 +257,7 @@ function WithdrawForm({ onSuccess, token }: WithdrawFormProps) {
) : null}
</div>
<Button
onClick={connected ? handleWithdraw : handleConnect}
onClick={connected ? handleWithdraw : connect}
className="flex w-full items-center justify-center"
size="large"
disabled={

View File

@ -29,7 +29,7 @@ import { breakpoints } from 'utils/theme'
export const handleCopyAddress = (
mangoAccount: MangoAccount,
successMessage: string
successMessage: string,
) => {
copyToClipboard(mangoAccount.publicKey.toString())
notify({
@ -121,7 +121,7 @@ const AccountActions = () => {
mangoAccount!,
t('copy-address-success', {
pk: abbreviateAddress(mangoAccount!.publicKey),
})
}),
)
}
>

View File

@ -40,7 +40,7 @@ const AccountHeroStats = ({
useAccountHourlyVolumeStats()
const totalInterestData = mangoStore(
(s) => s.mangoAccount.interestTotals.data
(s) => s.mangoAccount.interestTotals.data,
)
useEffect(() => {
@ -63,7 +63,7 @@ const AccountHeroStats = ({
retry: 3,
refetchOnWindowFocus: false,
enabled: !!mangoAccountAddress,
}
},
)
const {
@ -79,7 +79,7 @@ const AccountHeroStats = ({
retry: 3,
refetchOnWindowFocus: false,
enabled: !!mangoAccountAddress,
}
},
)
const maintHealth = useMemo(() => {
@ -91,7 +91,7 @@ const AccountHeroStats = ({
const leverage = useMemo(() => {
if (!group || !mangoAccount) return 0
const assetsValue = toUiDecimalsForQuote(
mangoAccount.getAssetsValue(group).toNumber()
mangoAccount.getAssetsValue(group).toNumber(),
)
if (isNaN(assetsValue / accountValue)) {
@ -110,7 +110,7 @@ const AccountHeroStats = ({
if (totalInterestData.length) {
return totalInterestData.reduce(
(a, c) => a + (c.borrow_interest_usd * -1 + c.deposit_interest_usd),
0
0,
)
}
return 0.0
@ -120,7 +120,7 @@ const AccountHeroStats = ({
if (fundingData?.length && mangoAccountAddress) {
return fundingData.reduce(
(a, c) => a + c.long_funding + c.short_funding,
0
0,
)
}
return 0.0
@ -167,7 +167,7 @@ const AccountHeroStats = ({
return (
<>
<div className="grid grid-cols-6 border-b border-th-bkg-3">
<div className="col-span-6 border-t border-th-bkg-3 py-3 pl-6 pr-4 md:col-span-3 lg:col-span-2 lg:border-t-0 xl:col-span-1">
<div className="col-span-6 border-t border-th-bkg-3 py-3 pl-6 pr-4 md:col-span-3 lg:col-span-2 lg:border-t-0 2xl:col-span-1">
<div id="account-step-four">
<div className="flex justify-between">
<Tooltip
@ -214,9 +214,7 @@ const AccountHeroStats = ({
</div>
}
>
<p className="tooltip-underline text-sm font-normal text-th-fgd-3 xl:text-base">
{t('health')}
</p>
<p className="tooltip-underline">{t('health')}</p>
</Tooltip>
{mangoAccountAddress ? (
<Tooltip
@ -255,7 +253,7 @@ const AccountHeroStats = ({
</span>
</div>
</div>
<div className="col-span-6 flex border-t border-th-bkg-3 py-3 pl-6 md:col-span-3 md:border-l lg:col-span-2 lg:border-t-0 xl:col-span-1">
<div className="col-span-6 flex border-t border-th-bkg-3 py-3 pl-6 md:col-span-3 md:border-l lg:col-span-2 lg:border-t-0 2xl:col-span-1">
<div id="account-step-five">
<Tooltip
content={t('account:tooltip-free-collateral')}
@ -263,16 +261,14 @@ const AccountHeroStats = ({
placement="top-start"
delay={100}
>
<p className="tooltip-underline text-sm text-th-fgd-3 xl:text-base">
{t('free-collateral')}
</p>
<p className="tooltip-underline">{t('free-collateral')}</p>
</Tooltip>
<p className="mt-1 mb-0.5 text-2xl font-bold text-th-fgd-1 lg:text-xl xl:text-2xl">
<FormatNumericValue
value={
group && mangoAccount
? toUiDecimalsForQuote(
mangoAccount.getCollateralValue(group)
mangoAccount.getCollateralValue(group),
)
: 0
}
@ -293,7 +289,7 @@ const AccountHeroStats = ({
value={
group && mangoAccount
? toUiDecimalsForQuote(
mangoAccount.getAssetsValue(group, HealthType.init)
mangoAccount.getAssetsValue(group, HealthType.init),
)
: 0
}
@ -305,7 +301,7 @@ const AccountHeroStats = ({
</span>
</div>
</div>
<div className="col-span-6 flex border-t border-th-bkg-3 py-3 pl-6 pr-4 md:col-span-3 lg:col-span-2 lg:border-l lg:border-t-0 xl:col-span-1">
<div className="col-span-6 flex border-t border-th-bkg-3 py-3 pl-6 pr-4 md:col-span-3 lg:col-span-2 lg:border-l lg:border-t-0 2xl:col-span-1">
<div
id="account-step-seven"
className="flex w-full flex-col items-start"
@ -316,9 +312,7 @@ const AccountHeroStats = ({
placement="top-start"
delay={100}
>
<p className="tooltip-underline inline text-sm text-th-fgd-3 xl:text-base">
{t('pnl')}
</p>
<p className="tooltip-underline">{t('pnl')}</p>
</Tooltip>
{mangoAccountAddress ? (
<div className="flex items-center space-x-3">
@ -364,12 +358,10 @@ const AccountHeroStats = ({
</div>
</div>
</div>
<div className="col-span-6 border-t border-th-bkg-3 py-3 pl-6 pr-4 md:col-span-3 md:border-l lg:col-span-2 lg:border-l-0 xl:col-span-1 xl:border-l xl:border-t-0">
<div className="col-span-6 border-t border-th-bkg-3 py-3 pl-6 pr-4 md:col-span-3 md:border-l lg:col-span-2 lg:border-l-0 2xl:col-span-1 2xl:border-l 2xl:border-t-0">
<div id="account-step-six">
<div className="flex w-full items-center justify-between">
<p className="text-sm text-th-fgd-3 xl:text-base">
{t('account:lifetime-volume')}
</p>
<p>{t('account:lifetime-volume')}</p>
{mangoAccountAddress ? (
<Tooltip
className="hidden md:block"
@ -413,7 +405,7 @@ const AccountHeroStats = ({
</span>
</div>
</div>
<div className="col-span-6 border-t border-th-bkg-3 py-3 pl-6 pr-4 text-left md:col-span-3 lg:col-span-2 lg:border-l xl:col-span-1 xl:border-t-0">
<div className="col-span-6 border-t border-th-bkg-3 py-3 pl-6 pr-4 text-left md:col-span-3 lg:col-span-2 lg:border-l 2xl:col-span-1 2xl:border-t-0">
<div id="account-step-eight">
<div className="flex w-full items-center justify-between">
<Tooltip
@ -422,7 +414,7 @@ const AccountHeroStats = ({
placement="top-start"
delay={100}
>
<p className="tooltip-underline text-sm text-th-fgd-3 xl:text-base">
<p className="tooltip-underline">
{t('total-interest-earned')}
</p>
</Tooltip>
@ -457,7 +449,7 @@ const AccountHeroStats = ({
</div>
</div>
</div>
<div className="col-span-6 border-t border-th-bkg-3 py-3 pl-6 pr-4 text-left md:col-span-3 md:border-l lg:col-span-2 xl:col-span-1 xl:border-t-0">
<div className="col-span-6 border-t border-th-bkg-3 py-3 pl-6 pr-4 text-left md:col-span-3 md:border-l lg:col-span-2 2xl:col-span-1 2xl:border-t-0">
<div className="flex w-full items-center justify-between">
<Tooltip
content={t('account:tooltip-total-funding')}
@ -465,7 +457,7 @@ const AccountHeroStats = ({
placement="top-start"
delay={100}
>
<p className="tooltip-underline text-sm text-th-fgd-3 xl:text-base">
<p className="tooltip-underline">
{t('account:total-funding-earned')}
</p>
</Tooltip>

View File

@ -42,7 +42,7 @@ const AccountPage = () => {
const isMobile = width ? width < breakpoints.md : false
const [activeTab, setActiveTab] = useLocalStorageState(
'accountHeroKey-0.1',
'account-value'
'account-value',
)
const { performanceData, rollingDailyData } = useAccountPerformanceData()
const router = useRouter()
@ -53,7 +53,7 @@ const AccountPage = () => {
const query = { ...router.query, ['view']: view }
router.push({ pathname: router.pathname, query })
},
[router]
[router],
)
const handleCloseDailyPnlModal = () => {

View File

@ -8,7 +8,6 @@ import { Transition } from '@headlessui/react'
import SheenLoader from '../shared/SheenLoader'
import Change from '../shared/Change'
import FormatNumericValue from '@components/shared/FormatNumericValue'
import { useTheme } from 'next-themes'
import useLocalStorageState from 'hooks/useLocalStorageState'
import { ANIMATION_SETTINGS_KEY } from 'utils/constants'
import { INITIAL_ANIMATION_SETTINGS } from '@components/settings/AnimationSettings'
@ -21,6 +20,7 @@ import { useViewport } from 'hooks/useViewport'
import { breakpoints } from 'utils/theme'
import { ViewToShow } from './AccountPage'
import useAccountPerformanceData from 'hooks/useAccountPerformanceData'
import useThemeWrapper from 'hooks/useThemeWrapper'
const AccountValue = ({
accountValue,
@ -34,13 +34,13 @@ const AccountValue = ({
handleViewChange: (view: ViewToShow) => void
}) => {
const { t } = useTranslation('common')
const { theme } = useTheme()
const { theme } = useThemeWrapper()
const { group } = useMangoGroup()
const { mangoAccount, mangoAccountAddress } = useMangoAccount()
const [showExpandChart, setShowExpandChart] = useState<boolean>(false)
const [animationSettings] = useLocalStorageState(
ANIMATION_SETTINGS_KEY,
INITIAL_ANIMATION_SETTINGS
INITIAL_ANIMATION_SETTINGS,
)
const { width } = useViewport()
const { performanceLoading: loading } = useAccountPerformanceData()

View File

@ -48,7 +48,7 @@ export const getFee = (activity: any, mangoAccountAddress: string) => {
const value = price * quantity
fee = {
value: formatFee(
mangoAccountAddress === maker ? maker_fee * value : taker_fee * value
mangoAccountAddress === maker ? maker_fee * value : taker_fee * value,
),
symbol: 'USDC',
}
@ -63,14 +63,14 @@ export const getFee = (activity: any, mangoAccountAddress: string) => {
if (side === 'liqee') {
fee = {
value: formatNumericValue(
Math.abs(liab_amount) - Math.abs(asset_amount * asset_price)
Math.abs(liab_amount) - Math.abs(asset_amount * asset_price),
),
symbol: liab_symbol,
}
} else {
fee = {
value: formatNumericValue(
Math.abs(asset_amount * asset_price) - Math.abs(liab_amount)
Math.abs(asset_amount * asset_price) - Math.abs(liab_amount),
),
symbol: liab_symbol,
}
@ -81,14 +81,14 @@ export const getFee = (activity: any, mangoAccountAddress: string) => {
if (base_transfer > 0) {
fee = {
value: formatNumericValue(
Math.abs(base_transfer * price) - Math.abs(quote_transfer)
Math.abs(base_transfer * price) - Math.abs(quote_transfer),
),
symbol: 'USDC',
}
} else {
fee = {
value: formatNumericValue(
Math.abs(quote_transfer) - Math.abs(base_transfer * price)
Math.abs(quote_transfer) - Math.abs(base_transfer * price),
),
symbol: 'USDC',
}
@ -99,7 +99,7 @@ export const getFee = (activity: any, mangoAccountAddress: string) => {
export const getCreditAndDebit = (
activity: any,
mangoAccountAddress: string
mangoAccountAddress: string,
) => {
const { activity_type } = activity
let credit = { value: '0', symbol: '' }
@ -270,7 +270,7 @@ const ActivityFeedTable = () => {
const { connected } = useWallet()
const [preferredExplorer] = useLocalStorageState(
PREFERRED_EXPLORER_KEY,
EXPLORERS[0]
EXPLORERS[0],
)
const { width } = useViewport()
const showTableView = width ? width > breakpoints.md : false
@ -285,7 +285,7 @@ const ActivityFeedTable = () => {
actions.fetchActivityFeed(
mangoAccountAddress,
offset + PAGINATION_PAGE_LENGTH,
queryParams
queryParams,
)
}, [actions, offset, queryParams, mangoAccountAddress])
@ -376,7 +376,7 @@ const ActivityFeedTable = () => {
<div className="flex items-center justify-end">
<Tooltip
content={`View on ${t(
`settings:${preferredExplorer.name}`
`settings:${preferredExplorer.name}`,
)}`}
placement="top-end"
>
@ -515,7 +515,7 @@ const MobileActivityFeedItem = ({
const { t } = useTranslation(['common', 'activity'])
const [preferredExplorer] = useLocalStorageState(
PREFERRED_EXPLORER_KEY,
EXPLORERS[0]
EXPLORERS[0],
)
const { mangoAccountAddress } = useMangoAccount()
const { activity_type, block_datetime } = activity

View File

@ -50,7 +50,7 @@ const ActivityFilters = () => {
const actions = mangoStore((s) => s.actions)
const { mangoAccountAddress } = useMangoAccount()
const [advancedFilters, setAdvancedFilters] = useState<AdvancedFilters>(
DEFAULT_ADVANCED_FILTERS
DEFAULT_ADVANCED_FILTERS,
)
const [params, setParams] = useState<string[]>([])
const { t } = useTranslation(['common', 'activity'])
@ -64,7 +64,7 @@ const ActivityFilters = () => {
if (entry[1].length) {
// ETH should be renamed to ETH (Portal) in the database
const alignSymbolsToBackend = entry[1].map((e: string) =>
e === 'ETH (Portal)' ? 'ETH' : e
e === 'ETH (Portal)' ? 'ETH' : e,
)
advancedParams =
advancedParams + `&${entry[0]}=${alignSymbolsToBackend}`

View File

@ -2,7 +2,6 @@ import { toUiDecimalsForQuote } from '@blockworks-foundation/mango-v4'
import useMangoAccount from 'hooks/useMangoAccount'
import useMangoGroup from 'hooks/useMangoGroup'
import { useTranslation } from 'next-i18next'
import { useTheme } from 'next-themes'
import { useMemo } from 'react'
import { Cell, Pie, PieChart } from 'recharts'
import { COLORS } from 'styles/colors'
@ -11,15 +10,16 @@ import FlipNumbers from 'react-flip-numbers'
import useLocalStorageState from 'hooks/useLocalStorageState'
import { ANIMATION_SETTINGS_KEY } from 'utils/constants'
import { INITIAL_ANIMATION_SETTINGS } from '@components/settings/AnimationSettings'
import useThemeWrapper from 'hooks/useThemeWrapper'
const AssetsLiabilities = ({ isMobile }: { isMobile: boolean }) => {
const { t } = useTranslation('account')
const { group } = useMangoGroup()
const { mangoAccount } = useMangoAccount()
const { theme } = useTheme()
const { theme } = useThemeWrapper()
const [animationSettings] = useLocalStorageState(
ANIMATION_SETTINGS_KEY,
INITIAL_ANIMATION_SETTINGS
INITIAL_ANIMATION_SETTINGS,
)
const [assetsValue, assetsRatio, liabsValue, liabsRatio] = useMemo(() => {

View File

@ -54,16 +54,16 @@ const CreateAccountForm = ({
group,
newAccountNum,
name || `Account ${newAccountNum + 1}`,
16 // tokenCount
16, // tokenCount
)
if (tx) {
const pk = wallet.adapter.publicKey
const mangoAccounts = await client.getMangoAccountsForOwner(group, pk!)
const reloadedMangoAccounts = await Promise.all(
mangoAccounts.map((ma) => ma.reloadSerum3OpenOrders(client))
mangoAccounts.map((ma) => ma.reloadSerum3OpenOrders(client)),
)
const newAccount = mangoAccounts.find(
(acc) => acc.accountNum === newAccountNum
(acc) => acc.accountNum === newAccountNum,
)
if (newAccount) {
await newAccount.reloadSerum3OpenOrders(client)

View File

@ -21,7 +21,6 @@ import {
ReferenceLine,
ResponsiveContainer,
} from 'recharts'
import { useTheme } from 'next-themes'
import { COLORS } from 'styles/colors'
import { formatDateAxis } from '@components/shared/DetailedAreaOrBarChart'
import { formatYAxis } from 'utils/formatting'
@ -32,11 +31,12 @@ import { ArrowLeftIcon, NoSymbolIcon } from '@heroicons/react/20/solid'
import { FadeInFadeOut } from '@components/shared/Transitions'
import ContentBox from '@components/shared/ContentBox'
import SheenLoader from '@components/shared/SheenLoader'
import useThemeWrapper from 'hooks/useThemeWrapper'
const fetchHourlyFunding = async (mangoAccountPk: string) => {
try {
const data = await fetch(
`${MANGO_DATA_API_URL}/stats/funding-account-hourly?mango-account=${mangoAccountPk}`
`${MANGO_DATA_API_URL}/stats/funding-account-hourly?mango-account=${mangoAccountPk}`,
)
const res = await data.json()
if (res) {
@ -65,7 +65,7 @@ const FundingChart = ({ hideChart }: { hideChart: () => void }) => {
const { t } = useTranslation('common')
const { mangoAccountAddress } = useMangoAccount()
const [daysToShow, setDaysToShow] = useState('30')
const { theme } = useTheme()
const { theme } = useThemeWrapper()
const {
data: fundingData,
isLoading: loadingFunding,
@ -80,7 +80,7 @@ const FundingChart = ({ hideChart }: { hideChart: () => void }) => {
retry: 3,
refetchOnWindowFocus: false,
enabled: !!mangoAccountAddress,
}
},
)
useEffect(() => {
@ -100,7 +100,7 @@ const FundingChart = ({ hideChart }: { hideChart: () => void }) => {
}
const data = rawData.reduce((a: HourlyFundingChartData[], c) => {
const found: HourlyFundingChartData | undefined = a.find(
(item) => item['time'] === c.time
(item) => item['time'] === c.time,
)
const marketKey = Object.keys(c)[0]
const marketFunding = Object.values(c)[0]
@ -123,7 +123,7 @@ const FundingChart = ({ hideChart }: { hideChart: () => void }) => {
if (active && payload && payload.length) {
const load = payload[0].payload
const data: [string, any][] = Object.entries(load).filter(
(p) => p[0] !== 'time' && p[0] !== 'total'
(p) => p[0] !== 'time' && p[0] !== 'total',
)
return (
<div className="rounded-md bg-th-bkg-2 p-4">

View File

@ -2,7 +2,6 @@ import HealthContributionsChart from './HealthContributionsChart'
import useMangoGroup from 'hooks/useMangoGroup'
import useMangoAccount from 'hooks/useMangoAccount'
import { useMemo, useState } from 'react'
import { HealthType } from '@blockworks-foundation/mango-v4'
import {
ArrowLeftIcon,
NoSymbolIcon,
@ -16,33 +15,31 @@ import mangoStore from '@store/mangoStore'
import TokensHealthTable from './TokensHealthTable'
import MarketsHealthTable from './MarketsHealthTable'
import { HealthContribution, PerpMarketContribution } from 'types'
import useHealthContributions from 'hooks/useHealthContributions'
const HealthContributions = ({ hideView }: { hideView: () => void }) => {
const { t } = useTranslation(['common', 'account', 'trade'])
const { group } = useMangoGroup()
const { mangoAccount, mangoAccountAddress } = useMangoAccount()
const [initActiveIndex, setInitActiveIndex] = useState<number | undefined>(
undefined
undefined,
)
const [maintActiveIndex, setMaintActiveIndex] = useState<number | undefined>(
undefined
undefined,
)
const { initContributions, maintContributions } = useHealthContributions()
const [initHealthContributions, maintHealthContributions] = useMemo(() => {
if (!group || !mangoAccount) return [[], []]
const initAssets = mangoAccount.getHealthContributionPerAssetUi(
group,
HealthType.init
)
const initContributions = []
for (const item of initAssets) {
const initHealthContributions = []
for (const item of initContributions) {
const contribution = item.contribution
if (item.asset === 'USDC') {
const hasPerp =
!!item.contributionDetails?.perpMarketContributions.find(
(perp: PerpMarketContribution) => Math.abs(perp.contributionUi) > 0
(perp: PerpMarketContribution) => Math.abs(perp.contributionUi) > 0,
)
initContributions.push({
initHealthContributions.push({
...item,
contribution: Math.abs(contribution),
hasPerp: hasPerp,
@ -53,7 +50,7 @@ const HealthContributions = ({ hideView }: { hideView: () => void }) => {
.perpMarketContributions) {
const contribution = Math.abs(perpMarket.contributionUi)
if (contribution > 0) {
initContributions.push({
initHealthContributions.push({
asset: perpMarket.market,
contribution: contribution,
isAsset: perpMarket.contributionUi > 0 ? true : false,
@ -62,7 +59,7 @@ const HealthContributions = ({ hideView }: { hideView: () => void }) => {
}
}
} else {
initContributions.push({
initHealthContributions.push({
...item,
isAsset: contribution > 0 ? true : false,
contribution: Math.abs(contribution),
@ -70,19 +67,15 @@ const HealthContributions = ({ hideView }: { hideView: () => void }) => {
}
}
const maintAssets = mangoAccount.getHealthContributionPerAssetUi(
group,
HealthType.maint
)
const maintContributions = []
for (const item of maintAssets) {
const maintHealthContributions = []
for (const item of maintContributions) {
const contribution = item.contribution
if (item.asset === 'USDC') {
const hasPerp =
!!item.contributionDetails?.perpMarketContributions.find(
(perp: PerpMarketContribution) => Math.abs(perp.contributionUi) > 0
(perp: PerpMarketContribution) => Math.abs(perp.contributionUi) > 0,
)
maintContributions.push({
maintHealthContributions.push({
...item,
hasPerp: hasPerp,
isAsset: contribution > 0 ? true : false,
@ -93,7 +86,7 @@ const HealthContributions = ({ hideView }: { hideView: () => void }) => {
.perpMarketContributions) {
const contribution = Math.abs(perpMarket.contributionUi)
if (contribution > 0) {
maintContributions.push({
maintHealthContributions.push({
asset: perpMarket.market,
contribution: contribution,
isAsset: perpMarket.contributionUi > 0 ? true : false,
@ -102,7 +95,7 @@ const HealthContributions = ({ hideView }: { hideView: () => void }) => {
}
}
} else {
maintContributions.push({
maintHealthContributions.push({
...item,
isAsset: contribution > 0 ? true : false,
contribution: Math.abs(contribution),
@ -110,15 +103,15 @@ const HealthContributions = ({ hideView }: { hideView: () => void }) => {
}
}
return [initContributions, maintContributions]
}, [group, mangoAccount])
return [initHealthContributions, maintHealthContributions]
}, [group, mangoAccount, initContributions, maintContributions])
const [initHealthMarkets, initHealthTokens] = useMemo(() => {
if (!initHealthContributions.length) return [[], []]
const splitData = initHealthContributions.reduce(
(
acc: { market: HealthContribution[]; token: HealthContribution[] },
obj: HealthContribution
obj: HealthContribution,
) => {
const isPerp = obj.asset.includes('PERP')
const isSpotMarket = obj.asset.includes('/')
@ -130,7 +123,7 @@ const HealthContributions = ({ hideView }: { hideView: () => void }) => {
}
return acc
},
{ market: [], token: [] }
{ market: [], token: [] },
)
return [splitData.market, splitData.token]
}, [initHealthContributions])
@ -140,7 +133,7 @@ const HealthContributions = ({ hideView }: { hideView: () => void }) => {
const splitData = maintHealthContributions.reduce(
(
acc: { market: HealthContribution[]; token: HealthContribution[] },
obj: HealthContribution
obj: HealthContribution,
) => {
const isPerp = obj.asset.includes('PERP')
const isSpotMarket = obj.asset.includes('/')
@ -152,7 +145,7 @@ const HealthContributions = ({ hideView }: { hideView: () => void }) => {
}
return acc
},
{ market: [], token: [] }
{ market: [], token: [] },
)
const markets = splitData.market.filter((d) => d.contribution > 0)
const tokens = splitData.token

View File

@ -1,5 +1,4 @@
import { useTranslation } from 'next-i18next'
import { useTheme } from 'next-themes'
import {
Cell,
Pie,
@ -14,6 +13,7 @@ import { formatCurrencyValue } from 'utils/numbers'
import { useViewport } from 'hooks/useViewport'
import { breakpoints } from 'utils/theme'
import { HealthContribution } from 'types'
import useThemeWrapper from 'hooks/useThemeWrapper'
const HealthContributionsChart = ({
data,
@ -25,7 +25,7 @@ const HealthContributionsChart = ({
setActiveIndex: (i: number | undefined) => void
}) => {
const { t } = useTranslation(['common', 'account'])
const { theme } = useTheme()
const { theme } = useThemeWrapper()
const { width } = useViewport()
const isMobile = width ? width < breakpoints.sm : false

View File

@ -109,7 +109,7 @@ const HistoryTabs = () => {
paid: `${formatNumericValue(swap_in_amount, inDecimals)} ${inSymbol}`,
received: `${formatNumericValue(
swap_out_amount,
outDecimals
outDecimals,
)} ${outSymbol}`,
value: formatCurrencyValue(swap_out_price_usd * swap_out_amount),
borrow: `${borrowAmount} ${inSymbol}`,
@ -135,7 +135,7 @@ const HistoryTabs = () => {
let market
if ('market' in trade) {
market = group.getSerum3MarketByExternalMarket(
new PublicKey(trade.market)
new PublicKey(trade.market),
)
} else if ('perp_market' in trade) {
market = group.getPerpMarketByMarketIndex(trade.market_index)

View File

@ -22,7 +22,7 @@ const LiquidationDetails = ({
const { t } = useTranslation(['common', 'activity', 'settings'])
const [preferredExplorer] = useLocalStorageState(
PREFERRED_EXPLORER_KEY,
EXPLORERS[0]
EXPLORERS[0],
)
const getAssetLiquidatedReturned = (details: SpotOrPerpLiquidationItem) => {
@ -209,7 +209,7 @@ const LiquidationDetails = ({
>
<span className="mr-1.5">
{abbreviateAddress(
new PublicKey(activity.activity_details.counterparty)
new PublicKey(activity.activity_details.counterparty),
)}
</span>
<ArrowTopRightOnSquareIcon className="h-3 w-3" />

View File

@ -38,13 +38,13 @@ const MangoAccountSummary = () => {
const [accountPnl, accountValue, freeCollateral, health] = useMemo(() => {
if (!group || !mangoAccount) return [0, 0, 0, 0]
const accountPnl = toUiDecimalsForQuote(
mangoAccount.getPnl(group).toNumber()
mangoAccount.getPnl(group).toNumber(),
)
const accountValue = toUiDecimalsForQuote(
mangoAccount.getEquity(group).toNumber()
mangoAccount.getEquity(group).toNumber(),
)
const freeCollateral = toUiDecimalsForQuote(
mangoAccount.getCollateralValue(group).toNumber()
mangoAccount.getCollateralValue(group).toNumber(),
)
const health = mangoAccount.getHealthRatioUi(group, HealthType.maint)
return [accountPnl, accountValue, freeCollateral, health]
@ -53,7 +53,7 @@ const MangoAccountSummary = () => {
const leverage = useMemo(() => {
if (!group || !mangoAccount) return 0
const assetsValue = toUiDecimalsForQuote(
mangoAccount.getAssetsValue(group).toNumber()
mangoAccount.getAssetsValue(group).toNumber(),
)
if (isNaN(assetsValue / accountValue)) {

View File

@ -63,7 +63,7 @@ const MarketsHealthTable = ({
const { asset, contribution, isAsset } = cont
const market = group.getSerum3MarketByName(asset)
const bank = group.banksMapByTokenIndex.get(
market.baseTokenIndex
market.baseTokenIndex,
)?.[0]
let initAssetWeight = 0
@ -155,7 +155,7 @@ const MarketsHealthTable = ({
const { asset, contribution, isAsset } = cont
const market = group.getSerum3MarketByName(asset)
const bank = group.banksMapByTokenIndex.get(
market.baseTokenIndex
market.baseTokenIndex,
)?.[0]
let initAssetWeight = 0

View File

@ -19,7 +19,7 @@ const PerpTradeDetails = ({ activity }: { activity: PerpTradeActivity }) => {
const { mangoAccountAddress } = useMangoAccount()
const [preferredExplorer] = useLocalStorageState(
PREFERRED_EXPLORER_KEY,
EXPLORERS[0]
EXPLORERS[0],
)
const {
maker,

View File

@ -13,7 +13,6 @@ import {
ReferenceLine,
ResponsiveContainer,
} from 'recharts'
import { useTheme } from 'next-themes'
import { COLORS } from 'styles/colors'
import { formatDateAxis } from '@components/shared/DetailedAreaOrBarChart'
import { formatYAxis } from 'utils/formatting'
@ -27,6 +26,7 @@ import SheenLoader from '@components/shared/SheenLoader'
import useAccountHourlyVolumeStats from 'hooks/useAccountHourlyVolumeStats'
import useMangoAccount from 'hooks/useMangoAccount'
import { DAILY_MILLISECONDS } from 'utils/constants'
import useThemeWrapper from 'hooks/useThemeWrapper'
const VolumeChart = ({ hideChart }: { hideChart: () => void }) => {
const { t } = useTranslation(['account', 'common', 'stats'])
@ -34,7 +34,7 @@ const VolumeChart = ({ hideChart }: { hideChart: () => void }) => {
const { hourlyVolumeData: chartData, loadingHourlyVolume: loading } =
useAccountHourlyVolumeStats()
const [daysToShow, setDaysToShow] = useState('30')
const { theme } = useTheme()
const { theme } = useThemeWrapper()
const CustomTooltip = ({
active,

View File

@ -44,7 +44,7 @@ const BorrowPage = () => {
}
const [animationSettings] = useLocalStorageState(
ANIMATION_SETTINGS_KEY,
INITIAL_ANIMATION_SETTINGS
INITIAL_ANIMATION_SETTINGS,
)
const filteredBanks = useMemo(() => {
@ -58,7 +58,7 @@ const BorrowPage = () => {
if (!filteredBanks.length) return 0
return filteredBanks.reduce(
(a, c) => a + Math.abs(c.borrowedAmount) * c.bank.uiPrice,
0
0,
)
}, [filteredBanks])
@ -75,7 +75,7 @@ const BorrowPage = () => {
group,
usdcBank,
mangoAccount,
true
true,
).toNumber()
if (borrowValue) {

View File

@ -26,7 +26,7 @@ import TokenLogo from '@components/shared/TokenLogo'
export const getAvailableToBorrow = (
bankWithBal: BankWithBalance,
group: Group
group: Group,
) => {
const { balance, bank, maxBorrow } = bankWithBal
const { mint, mintDecimals, minVaultToDepositsRatio } = bankWithBal.bank
@ -40,7 +40,7 @@ export const getAvailableToBorrow = (
const available = Decimal.min(
availableAccountBorrow.toFixed(bank.mintDecimals),
Decimal.max(0, availableVaultBalance.toFixed(mintDecimals))
Decimal.max(0, availableVaultBalance.toFixed(mintDecimals)),
)
return available
}
@ -61,7 +61,7 @@ const YourBorrowsTable = ({ banks }: { banks: BankWithBalance[] }) => {
setSelectedToken(token)
action === 'borrow' ? setShowBorrowModal(true) : setShowRepayModal(true)
},
[]
[],
)
return (

View File

@ -17,7 +17,7 @@ const ChatForm = ({
const validateMessageText = useCallback(async (text: string) => {
try {
const response = await fetch(
`https://www.purgomalum.com/service/json?text=${text}&fill_char=*`
`https://www.purgomalum.com/service/json?text=${text}&fill_char=*`,
)
const profanityCheck = await response.json()
@ -45,7 +45,7 @@ const ChatForm = ({
setLatestMessages(newMessages)
setMessageText('')
},
[messageText, messages, publicKey, validateMessageText, setLatestMessages]
[messageText, messages, publicKey, validateMessageText, setLatestMessages],
)
const callbackRef = useCallback((inputElement: HTMLInputElement) => {

View File

@ -0,0 +1,70 @@
import { ChangeEvent, useEffect, useRef, useState } from 'react'
const Slider = ({
amount,
max,
min,
onChange,
step,
}: {
amount: number
max?: string
min?: string
onChange: (x: string) => void
step: number
}) => {
const [value, setValue] = useState(0)
const inputEl = useRef<HTMLInputElement>(null)
useEffect(() => {
if (inputEl.current) {
const target = inputEl.current
const min = parseFloat(target.min)
const max = parseFloat(target.max)
target.style.backgroundSize =
max - min === 0
? '0% 100%'
: ((value - min) * 100) / (max - min) + '% 100%'
}
}, [value])
useEffect(() => {
if (amount) {
setValue(amount)
} else {
setValue(0)
}
}, [amount])
const handleSliderChange = (e: ChangeEvent<HTMLInputElement>) => {
const target = e.target
const min = parseFloat(target.min)
const max = parseFloat(target.max)
const val = parseFloat(target.value)
target.style.backgroundSize = ((val - min) * 100) / (max - min) + '% 100%'
onChange(e.target.value)
setValue(parseFloat(e.target.value))
}
return (
<>
<label htmlFor="default-range" className="block text-sm"></label>
<input
ref={inputEl}
id="default-range"
type="range"
min={min || '0'}
max={max || '100'}
step={step}
className="w-full focus:outline-none"
onChange={handleSliderChange}
value={value}
></input>
</>
)
}
export default Slider

View File

@ -6,6 +6,7 @@ interface SwitchProps {
onChange: (x: boolean) => void
children?: ReactNode
disabled?: boolean
small?: boolean
}
const Switch: FunctionComponent<SwitchProps> = ({
@ -14,6 +15,7 @@ const Switch: FunctionComponent<SwitchProps> = ({
children,
onChange,
disabled,
small,
}) => {
const handleClick = () => {
onChange(!checked)
@ -26,7 +28,9 @@ const Switch: FunctionComponent<SwitchProps> = ({
type="button"
className={`${
checked ? 'bg-th-success' : 'bg-th-bkg-4'
} relative inline-flex h-5 w-10 flex-shrink-0 cursor-pointer rounded-full
} relative inline-flex ${
small ? 'h-4 w-8' : 'h-5 w-10'
} flex-shrink-0 cursor-pointer rounded-full
border-2 border-transparent transition-colors duration-200 ease-in-out focus-visible:outline-1 focus-visible:outline-offset-2 focus-visible:outline-th-fgd-4 ${
disabled ? 'opacity-60' : ''
}`}
@ -39,8 +43,14 @@ const Switch: FunctionComponent<SwitchProps> = ({
<span
aria-hidden="true"
className={`${
checked ? 'translate-x-5' : 'translate-x-0'
} pointer-events-none inline-block h-4 w-4 rounded-full
checked
? small
? 'translate-x-4'
: 'translate-x-5'
: 'translate-x-0'
} pointer-events-none inline-block ${
small ? 'h-3 w-3' : 'h-4 w-4'
} rounded-full
bg-white shadow ring-0 transition duration-200 ease-in-out`}
></span>
</button>

View File

@ -17,7 +17,7 @@ const GovernanceDelegate = () => {
const getCurrentVotingPower = GovernanceStore((s) => s.getCurrentVotingPower)
const [selectedDelegatePk, setSelectedDelegatePk] = useLocalStorageState(
`${publicKey?.toBase58()}${GOVERNANCE_DELEGATE_KEY}`
`${publicKey?.toBase58()}${GOVERNANCE_DELEGATE_KEY}`,
)
const currentDelegate = delegates
.find((x) => x.pubkey.toBase58() === selectedDelegatePk)

View File

@ -30,7 +30,6 @@ import ListingSuccess from '../ListingSuccess'
import { formatTokenSymbol } from 'utils/tokens'
import OnBoarding from '../OnBoarding'
import { calculateTradingParameters } from 'utils/governance/listingTools'
import { useEnhancedWallet } from '@components/wallet/EnhancedWalletProvider'
import { tryGetPubKey } from 'utils/governance/tools'
type FormErrors = Partial<Record<keyof ListMarketForm, string>>
@ -58,8 +57,8 @@ const defaultFormValues: ListMarketForm = {
}
const ListMarket = ({ goBack }: { goBack: () => void }) => {
//do not deconstruct wallet is used for anchor to sign
const wallet = useWallet()
const { handleConnect } = useEnhancedWallet()
const { t } = useTranslation(['governance', 'trade'])
const { group } = useMangoGroup()
const connection = mangoStore((s) => s.connection)
@ -91,7 +90,7 @@ const ListMarket = ({ goBack }: { goBack: () => void }) => {
const [baseTokens, quoteTokens] = useMemo(() => {
if (!group) return [[], []]
const allTokens = [...group.banksMapByName.keys()].sort((a, b) =>
a.localeCompare(b)
a.localeCompare(b),
)
return [
allTokens.filter((t) => t !== quoteToken),
@ -153,7 +152,7 @@ const ListMarket = ({ goBack }: { goBack: () => void }) => {
}
return invalidFields
},
[t]
[t],
)
const handlePropose = useCallback(async () => {
const invalidFields = isFormValid(advForm)
@ -190,7 +189,7 @@ const ListMarket = ({ goBack }: { goBack: () => void }) => {
advForm.proposalDescription,
index,
proposalTx,
vsrClient!
vsrClient!,
)
setProposalPk(proposalAddress.toBase58())
setCurrentView(VIEWS.SUCCESS)
@ -256,7 +255,7 @@ const ListMarket = ({ goBack }: { goBack: () => void }) => {
baseBank.uiPrice,
quoteBank.uiPrice,
baseBank.mintDecimals,
quoteBank.mintDecimals
quoteBank.mintDecimals,
)
}
return {
@ -290,7 +289,7 @@ const ListMarket = ({ goBack }: { goBack: () => void }) => {
{t('market-pair')}{' '}
{baseToken && quoteToken
? `- ${formatTokenSymbol(baseToken)}/${formatTokenSymbol(
quoteToken
quoteToken,
)}`
: null}
</h2>
@ -507,7 +506,7 @@ const ListMarket = ({ goBack }: { goBack: () => void }) => {
onChange={(e: ChangeEvent<HTMLInputElement>) =>
handleSetAdvForm(
'openBookMarketExternalPk',
e.target.value
e.target.value,
)
}
/>
@ -550,7 +549,7 @@ const ListMarket = ({ goBack }: { goBack: () => void }) => {
onChange={(e: ChangeEvent<HTMLInputElement>) =>
handleSetAdvForm(
'proposalDescription',
e.target.value
e.target.value,
)
}
/>
@ -592,7 +591,7 @@ const ListMarket = ({ goBack }: { goBack: () => void }) => {
)}
</Button>
) : (
<Button onClick={handleConnect} size="large">
<Button onClick={wallet.connect} size="large">
{t('connect-wallet')}
</Button>
)}

View File

@ -32,7 +32,6 @@ import Loading from '@components/shared/Loading'
import ListingSuccess from '../ListingSuccess'
import InlineNotification from '@components/shared/InlineNotification'
import { Disclosure } from '@headlessui/react'
import { useEnhancedWallet } from '@components/wallet/EnhancedWalletProvider'
import { abbreviateAddress } from 'utils/formatting'
import { formatNumericValue } from 'utils/numbers'
import useMangoGroup from 'hooks/useMangoGroup'
@ -86,12 +85,12 @@ const defaultTokenListFormValues: TokenListForm = {
const TWENTY_K_USDC_BASE = '20000000000'
const ListToken = ({ goBack }: { goBack: () => void }) => {
//do not deconstruct wallet is used for anchor to sign
const wallet = useWallet()
const { jupiterTokens } = useJupiterMints()
const connection = mangoStore((s) => s.connection)
const client = mangoStore((s) => s.client)
const { group } = useMangoGroup()
const { handleConnect } = useEnhancedWallet()
const voter = GovernanceStore((s) => s.voter)
const vsrClient = GovernanceStore((s) => s.vsrClient)
const governances = GovernanceStore((s) => s.governances)
@ -129,7 +128,7 @@ const ListToken = ({ goBack }: { goBack: () => void }) => {
? governances[MANGO_DAO_WALLET_GOVERNANCE.toBase58()].account.config
.minCommunityTokensToCreateProposal
: new BN(0),
[governances]
[governances],
) as BN
const mintVoterWeightNumber = governances
? fmtTokenAmount(minVoterWeight, MANGO_MINT_DECIMALS)
@ -140,7 +139,7 @@ const ListToken = ({ goBack }: { goBack: () => void }) => {
baseTokenPrice,
quoteBank.uiPrice,
currentTokenInfo.decimals,
quoteBank.mintDecimals
quoteBank.mintDecimals,
)
}
return {
@ -191,7 +190,7 @@ const ListToken = ({ goBack }: { goBack: () => void }) => {
new BN(index).toArrayLike(Buffer, 'le', 2),
new BN(bankNum).toArrayLike(Buffer, 'le', 4),
],
client.programId
client.programId,
)
setAdvForm({
...advForm,
@ -211,7 +210,7 @@ const ListToken = ({ goBack }: { goBack: () => void }) => {
})
setLoadingListingParams(false)
},
[advForm, client.programId, connection, group, mint, proposals]
[advForm, client.programId, connection, group, mint, proposals],
)
const handleGetRoutesWithFixedArgs = useCallback(
@ -230,10 +229,10 @@ const ListToken = ({ goBack }: { goBack: () => void }) => {
mode,
FEE,
walletForCheck,
'JUPITER'
'JUPITER',
)
},
[wallet.publicKey]
[wallet.publicKey],
)
const handleLiqudityCheck = useCallback(
@ -257,7 +256,7 @@ const ListToken = ({ goBack }: { goBack: () => void }) => {
(acc: { amount: string; priceImpactPct: number }[], val) => {
if (val.swapMode === 'ExactIn') {
const exactOutRoute = bestRoutesSwaps.find(
(x) => x.amount === val.amount && x.swapMode === 'ExactOut'
(x) => x.amount === val.amount && x.swapMode === 'ExactOut',
)
acc.push({
amount: val.amount.toString(),
@ -268,14 +267,14 @@ const ListToken = ({ goBack }: { goBack: () => void }) => {
}
return acc
},
[]
[],
)
const midTierCheck = averageSwaps.find(
(x) => x.amount === TWENTY_K_USDC_BASE
(x) => x.amount === TWENTY_K_USDC_BASE,
)
const indexForTierFromSwaps = averageSwaps.findIndex(
(x) => x?.priceImpactPct && x?.priceImpactPct * 100 < 1
(x) => x?.priceImpactPct && x?.priceImpactPct * 100 < 1,
)
const tier =
indexForTierFromSwaps > -1
@ -286,8 +285,8 @@ const ListToken = ({ goBack }: { goBack: () => void }) => {
handleGetPoolParams(
swaps.find(
(x) => x.bestRoute!.amount.toString() === TWENTY_K_USDC_BASE
)!.routes
(x) => x.bestRoute!.amount.toString() === TWENTY_K_USDC_BASE,
)!.routes,
)
return tier
} catch (e) {
@ -298,7 +297,7 @@ const ListToken = ({ goBack }: { goBack: () => void }) => {
})
}
},
[t, wallet.publicKey]
[t, handleGetRoutesWithFixedArgs],
)
const handleGetPoolParams = (routes: never[] | RouteInfo[]) => {
@ -379,7 +378,7 @@ const ListToken = ({ goBack }: { goBack: () => void }) => {
}
return invalidFields
},
[t]
[t],
)
const propose = useCallback(async () => {
@ -387,7 +386,7 @@ const ListToken = ({ goBack }: { goBack: () => void }) => {
if (Object.keys(invalidFields).length) {
return
}
if (!wallet?.publicKey || !vsrClient || !connectionContext) return
if (!wallet.publicKey || !vsrClient || !connectionContext) return
await getCurrentVotingPower(wallet.publicKey, vsrClient, connectionContext)
if (voter.voteWeight.cmp(minVoterWeight) === -1) {
@ -406,7 +405,7 @@ const ListToken = ({ goBack }: { goBack: () => void }) => {
group!.publicKey.toBuffer(),
new PublicKey(advForm.mintPk).toBuffer(),
],
client.programId
client.programId,
)
const proposalTx = []
@ -437,7 +436,7 @@ const ListToken = ({ goBack }: { goBack: () => void }) => {
Number(tierPreset.liquidationFee),
Number(tierPreset.minVaultToDepositsRatio),
new BN(tierPreset.netBorrowLimitWindowSizeTs),
new BN(tierPreset.netBorrowLimitPerWindowQuote)
new BN(tierPreset.netBorrowLimitPerWindowQuote),
)
.accounts({
admin: MANGO_DAO_WALLET,
@ -475,7 +474,7 @@ const ListToken = ({ goBack }: { goBack: () => void }) => {
false,
null,
null,
null
null,
)
.accounts({
oracle: new PublicKey(advForm.oraclePk),
@ -493,7 +492,7 @@ const ListToken = ({ goBack }: { goBack: () => void }) => {
.instruction()
proposalTx.push(editIx)
} else {
await client!.program.methods
const trustlessIx = await client!.program.methods
.tokenRegisterTrustless(Number(advForm.tokenIndex), advForm.name)
.accounts({
mint: new PublicKey(advForm.mintPk),
@ -504,6 +503,8 @@ const ListToken = ({ goBack }: { goBack: () => void }) => {
group: group!.publicKey,
})
.instruction()
proposalTx.push(trustlessIx)
}
const registerMarketix = await client!.program.methods
@ -532,7 +533,7 @@ const ListToken = ({ goBack }: { goBack: () => void }) => {
advForm.proposalDescription,
advForm.tokenIndex,
proposalTx,
vsrClient
vsrClient,
)
setProposalPk(proposalAddress.toBase58())
} catch (e) {
@ -649,7 +650,7 @@ const ListToken = ({ goBack }: { goBack: () => void }) => {
<p>{t('mint')}</p>
<p className="flex items-center">
{abbreviateAddress(
new PublicKey(currentTokenInfo?.address)
new PublicKey(currentTokenInfo?.address),
)}
</p>
</div>
@ -740,7 +741,7 @@ const ListToken = ({ goBack }: { goBack: () => void }) => {
onChange={(e: ChangeEvent<HTMLInputElement>) =>
handleSetAdvForm(
'openBookMarketExternalPk',
e.target.value
e.target.value,
)
}
/>
@ -802,7 +803,7 @@ const ListToken = ({ goBack }: { goBack: () => void }) => {
onChange={(e: ChangeEvent<HTMLInputElement>) =>
handleSetAdvForm(
'openBookProgram',
e.target.value
e.target.value,
)
}
/>
@ -843,7 +844,7 @@ const ListToken = ({ goBack }: { goBack: () => void }) => {
onChange={(e: ChangeEvent<HTMLInputElement>) =>
handleSetAdvForm(
'proposalTitle',
e.target.value
e.target.value,
)
}
/>
@ -867,7 +868,7 @@ const ListToken = ({ goBack }: { goBack: () => void }) => {
onChange={(e: ChangeEvent<HTMLInputElement>) =>
handleSetAdvForm(
'proposalDescription',
e.target.value
e.target.value,
)
}
/>
@ -982,7 +983,7 @@ const ListToken = ({ goBack }: { goBack: () => void }) => {
)}
</Button>
) : (
<Button onClick={handleConnect} size="large">
<Button onClick={wallet.connect} size="large">
{t('connect-wallet')}
</Button>
)}

View File

@ -23,7 +23,7 @@ const OnBoarding = ({ minVotes }: { minVotes?: BN }) => {
? new BN(
governances[
MANGO_DAO_WALLET_GOVERNANCE.toBase58()
].account.config.minCommunityTokensToCreateProposal.toString()
].account.config.minCommunityTokensToCreateProposal.toString(),
)
: new BN(0)
const mintVoterWeightNumber = governances
@ -50,7 +50,7 @@ const OnBoarding = ({ minVotes }: { minVotes?: BN }) => {
</>
}
desc={`${t('on-boarding-deposit-info')} ${t(
'on-boarding-description-1'
'on-boarding-description-1',
)}`}
/>
</div>

View File

@ -58,7 +58,7 @@ const ProposalCard = ({
const [voteType, setVoteType] = useState<VoteKind | undefined>(undefined)
const [voteRecordAddress, setVoteRecordAddress] = useState<PublicKey | null>(
null
null,
)
const [isVoteCast, setIsVoteCast] = useState(false)
const [description, setDescription] = useState('')
@ -73,7 +73,7 @@ const ProposalCard = ({
setProcessedVoteType(
voteType === VoteKind.Approve
? PROCESSED_VOTE_TYPE.APPROVE
: PROCESSED_VOTE_TYPE.DENY
: PROCESSED_VOTE_TYPE.DENY,
)
try {
await castVote(
@ -83,7 +83,7 @@ const ProposalCard = ({
voter.tokenOwnerRecord!,
voteType,
vsrClient!,
client
client,
)
await updateProposals(proposal.pubkey)
} catch (e) {
@ -106,7 +106,7 @@ const ProposalCard = ({
proposal,
voter.tokenOwnerRecord!,
client,
voteRecordAddress!
voteRecordAddress!,
)
await updateProposals(proposal.pubkey)
} catch (e) {
@ -140,14 +140,14 @@ const ProposalCard = ({
const voteRecordAddress = await getVoteRecordAddress(
MANGO_GOVERNANCE_PROGRAM,
proposal.pubkey,
voter.tokenOwnerRecord!.pubkey!
voter.tokenOwnerRecord!.pubkey!,
)
setVoteRecordAddress(voteRecordAddress)
try {
const governanceAccount = await getGovernanceAccount(
connection,
voteRecordAddress,
VoteRecord
VoteRecord,
)
setIsVoteCast(true)
setVoteType(governanceAccount.account.vote?.voteType)

View File

@ -89,7 +89,7 @@ const Vote = () => {
proposal={x}
mangoMint={mangoMint}
></ProposalCard>
)
),
)
) : (
<div className="flex h-56 items-center justify-center rounded-lg border border-th-bkg-3 p-6">

View File

@ -26,7 +26,7 @@ const QuorumProgress = ({ governance, proposal, communityMint }: Props) => {
realm &&
getMintMaxVoteWeight(
communityMint,
realm.account.config.communityMintMaxVoteWeightSource
realm.account.config.communityMintMaxVoteWeightSource,
)
const minimumYesVotes =
@ -35,7 +35,7 @@ const QuorumProgress = ({ governance, proposal, communityMint }: Props) => {
const yesVoteCount = fmtTokenAmount(
new BN(proposal.account.getYesVoteCount().toString()),
communityMint.decimals
communityMint.decimals,
)
const rawYesVotesRequired = minimumYesVotes - yesVoteCount

View File

@ -15,11 +15,11 @@ const VoteResults = ({ proposal, communityMint }: VoteResultsProps) => {
const yesVoteCount = fmtTokenAmount(
proposal.getYesVoteCount() as BN,
communityMint.decimals
communityMint.decimals,
)
const noVoteCount = fmtTokenAmount(
proposal.getNoVoteCount() as BN,
communityMint.decimals
communityMint.decimals,
)
const totalVoteCount = yesVoteCount + noVoteCount
const getRelativeVoteCount = (voteCount: number) =>

View File

@ -1,4 +1,4 @@
import { useTheme } from 'next-themes'
import useThemeWrapper from 'hooks/useThemeWrapper'
import { COLORS } from 'styles/colors'
const OrderbookIcon = ({
@ -8,7 +8,7 @@ const OrderbookIcon = ({
side: 'buy' | 'sell'
className?: string
}) => {
const { theme } = useTheme()
const { theme } = useThemeWrapper()
const buyColor = side === 'buy' ? COLORS.UP[theme] : COLORS.FGD4[theme]
const sellColor = side === 'sell' ? COLORS.DOWN[theme] : COLORS.FGD4[theme]
return (

View File

@ -23,7 +23,7 @@ export interface LeaderboardRes {
type DaysToShow = '1DAY' | '1WEEK' | 'ALLTIME'
const isLeaderboard = (
response: null | EmptyObject | LeaderboardRes[]
response: null | EmptyObject | LeaderboardRes[],
): response is LeaderboardRes[] => {
if (response && Array.isArray(response)) {
return true
@ -33,10 +33,10 @@ const isLeaderboard = (
const fetchLeaderboard = async (
daysToShow: DaysToShow,
offset = 0
offset = 0,
): Promise<Array<LeaderboardRes>> => {
const data = await fetch(
`${MANGO_DATA_API_URL}/leaderboard-pnl?over_period=${daysToShow}&offset=${offset}`
`${MANGO_DATA_API_URL}/leaderboard-pnl?over_period=${daysToShow}&offset=${offset}`,
)
const parsedData: null | EmptyObject | LeaderboardRes[] = await data.json()
@ -63,7 +63,7 @@ const LeaderboardPage = () => {
refetchOnWindowFocus: false,
keepPreviousData: true,
getNextPageParam: (_lastPage, pages) => pages.length * 20,
}
},
)
const leaderboardData = useMemo(() => {

View File

@ -27,11 +27,11 @@ const CloseAccountModal = ({ isOpen, onClose }: ModalProps) => {
const mangoAccount = mangoStore((s) => s.mangoAccount)
const perpPositions = mangoStore((s) => s.mangoAccount.perpPositions)
const openPerpPositions = Object.values(perpPositions).filter((p) =>
p.basePositionLots.toNumber()
p.basePositionLots.toNumber(),
)
const group = mangoStore.getState().group
const unsettledBalances = Object.values(mangoAccount.spotBalances).filter(
(x) => x.unsettled && x.unsettled > 0
(x) => x.unsettled && x.unsettled > 0,
)
const unsettledPerpPositions = useUnsettledPerpPositions()
const [hasBorrows, setHasBorrows] = useState(false)
@ -49,7 +49,7 @@ const CloseAccountModal = ({ isOpen, onClose }: ModalProps) => {
const tx = await client.emptyAndCloseMangoAccount(group, mangoAccount)
if (tx) {
const newMangoAccounts = mangoAccounts.filter(
(ma) => !ma.publicKey.equals(mangoAccount.publicKey)
(ma) => !ma.publicKey.equals(mangoAccount.publicKey),
)
let newCurrentAccount: MangoAccount
if (newMangoAccounts[0]) {
@ -99,7 +99,7 @@ const CloseAccountModal = ({ isOpen, onClose }: ModalProps) => {
.filter((token: TokenPosition) =>
token
.balance(group.getFirstBankByTokenIndex(token.tokenIndex))
.isNeg()
.isNeg(),
).length
) {
setHasBorrows(true)
@ -137,8 +137,8 @@ const CloseAccountModal = ({ isOpen, onClose }: ModalProps) => {
mangoAccount && group
? formatCurrencyValue(
toUiDecimalsForQuote(
mangoAccount!.current!.getEquity(group)
)
mangoAccount!.current!.getEquity(group),
),
)
: 0,
})}

View File

@ -13,7 +13,6 @@ import { LAMPORTS_PER_SOL, PublicKey, Transaction } from '@solana/web3.js'
import { MARKET_STATE_LAYOUT_V2 } from '@project-serum/serum'
import { notify } from 'utils/notifications'
import InlineNotification from '@components/shared/InlineNotification'
import { useEnhancedWallet } from '@components/wallet/EnhancedWalletProvider'
type CreateObMarketForm = {
programId: string
@ -63,8 +62,7 @@ const CreateOpenbookMarketModal = ({
}: ModalProps & CreateOpenbookMarketModalProps) => {
const { t } = useTranslation(['governance'])
const connection = mangoStore((s) => s.connection)
const wallet = useWallet()
const { handleConnect } = useEnhancedWallet()
const { connect, signAllTransactions, connected, publicKey } = useWallet()
const [form, setForm] = useState({ ...defaultFormValues })
const [formErrors, setFormErrors] = useState<FormErrors>({})
@ -77,7 +75,7 @@ const CreateOpenbookMarketModal = ({
}
const handleCreate = async () => {
if (!wallet || !wallet.signAllTransactions) {
if (!publicKey || !signAllTransactions) {
return
}
let sig = ''
@ -85,7 +83,7 @@ const CreateOpenbookMarketModal = ({
try {
const ixObj = await makeCreateOpenBookMarketInstructionSimple({
connection,
wallet: wallet.publicKey!,
wallet: publicKey,
baseInfo: {
mint: new PublicKey(baseMint),
decimals: baseDecimals,
@ -107,11 +105,11 @@ const CreateOpenbookMarketModal = ({
tx.add(...chunk.instructions)
tx.lastValidBlockHeight = latestBlockhash.lastValidBlockHeight
tx.recentBlockhash = latestBlockhash.blockhash
tx.feePayer = wallet.publicKey!
tx.feePayer = publicKey
tx.sign(...chunk.signers)
transactions.push(tx)
}
const signedTransactions = await wallet.signAllTransactions(transactions)
const signedTransactions = await signAllTransactions(transactions)
for (const tx of signedTransactions) {
const rawTransaction = tx.serialize()
@ -159,11 +157,11 @@ const CreateOpenbookMarketModal = ({
const getMinLamportsToCreateMarket = async () => {
const accountsSpace = 84522 + MARKET_STATE_LAYOUT_V2.span
const minLamports = await connection.getMinimumBalanceForRentExemption(
accountsSpace
accountsSpace,
)
setSolNeededToCreateMarket(
Math.round((minLamports / LAMPORTS_PER_SOL + Number.EPSILON) * 100) /
100
100,
)
}
getMinLamportsToCreateMarket()
@ -257,7 +255,7 @@ const CreateOpenbookMarketModal = ({
)}
</div>
</div>
{wallet.connected ? (
{connected ? (
<Button
className="mt-6 w-full"
onClick={handleCreate}
@ -267,7 +265,7 @@ const CreateOpenbookMarketModal = ({
{t('create')}
</Button>
) : (
<Button className="mt-6 w-full" onClick={handleConnect} size="large">
<Button className="mt-6 w-full" onClick={connect} size="large">
{t('connect-wallet')}
</Button>
)}

View File

@ -12,7 +12,7 @@ import Button from '@components/shared/Button'
import { MANGO_DAO_WALLET } from 'utils/governance/constants'
import { USDC_MINT } from 'utils/constants'
import { Transaction } from '@solana/web3.js'
import { chunk } from 'lodash'
import chunk from 'lodash/chunk'
import { useTranslation } from 'next-i18next'
import { notify } from 'utils/notifications'
import { isMangoError } from 'types'
@ -135,7 +135,7 @@ const CreateSwitchboardOracleModal = ({
},
},
],
})
}),
).finish(),
},
{
@ -201,7 +201,7 @@ const CreateSwitchboardOracleModal = ({
},
},
],
})
}),
).finish(),
},
],

View File

@ -22,7 +22,7 @@ const DelegateModal = ({ isOpen, onClose }: ModalProps) => {
const [delegateAddress, setDelegateAddress] = useState(
mangoAccount?.delegate?.toString() !== DEFAULT_DELEGATE
? mangoAccount!.delegate.toString()
: ''
: '',
)
const handleDelegateAccount = async (address: string) => {
@ -44,7 +44,7 @@ const DelegateModal = ({ isOpen, onClose }: ModalProps) => {
group,
mangoAccount,
undefined,
delegateAddress ? new PublicKey(address) : undefined
delegateAddress ? new PublicKey(address) : undefined,
)
onClose()
notify({
@ -82,7 +82,7 @@ const DelegateModal = ({ isOpen, onClose }: ModalProps) => {
<InlineNotification
type="info"
desc={`Account is delegated to ${abbreviateAddress(
mangoAccount.delegate
mangoAccount.delegate,
)}`}
/>
</div>

View File

@ -117,11 +117,11 @@ const MangoAccountsListModal = ({
acc = mangoAccount
}
const accountValue = toUiDecimalsForQuote(
Number(acc.getEquity(group!))
Number(acc.getEquity(group!)),
).toFixed(2)
const maintHealth = acc.getHealthRatioUi(
group!,
HealthType.maint
HealthType.maint,
)
return (
<div
@ -207,7 +207,7 @@ const MangoAccountsListModal = ({
acc,
t('copy-address-success', {
pk: abbreviateAddress(acc.publicKey),
})
}),
)
}
hideBg

View File

@ -51,12 +51,12 @@ const ModifyTvOrderModal = ({
const { t } = useTranslation(['common', 'trade'])
const [modifiedOrderPrice, setModifiedOrderPrice] = useState(price)
const [modifiedOrderSize, setModifiedOrderSize] = useState(
order.size.toString()
order.size.toString(),
)
const { baseSymbol, selectedMarket, serumOrPerpMarket } = useSelectedMarket()
const [savedCheckboxSettings] = useLocalStorageState(
TRADE_CHECKBOXES_KEY,
DEFAULT_CHECKBOX_SETTINGS
DEFAULT_CHECKBOX_SETTINGS,
)
const tickDecimals = useMemo(() => {
@ -99,13 +99,13 @@ const ModifyTvOrderModal = ({
Date.now(),
PerpOrderType.limit,
undefined,
undefined
undefined,
)
} else {
const marketPk = findSerum3MarketPkInOpenOrders(o)
if (!marketPk) return
const market = group.getSerum3MarketByExternalMarket(
new PublicKey(marketPk)
new PublicKey(marketPk),
)
tx = await client.modifySerum3Order(
group,
@ -118,7 +118,7 @@ const ModifyTvOrderModal = ({
Serum3SelfTradeBehavior.decrementTake,
Serum3OrderType.limit,
Date.now(),
10
10,
)
}
actions.fetchOpenOrders()
@ -139,7 +139,7 @@ const ModifyTvOrderModal = ({
})
}
},
[findSerum3MarketPkInOpenOrders, modifiedOrderPrice, modifiedOrderSize]
[findSerum3MarketPkInOpenOrders, modifiedOrderPrice, modifiedOrderSize],
)
return selectedMarket ? (

View File

@ -129,7 +129,7 @@ const SharePositionModal = ({
<p className="ml-2 font-mono text-base text-white">
{formatNumericValue(
entryPrice,
getDecimalCount(market.tickSize)
getDecimalCount(market.tickSize),
)}
</p>
</div>
@ -140,7 +140,7 @@ const SharePositionModal = ({
<p className="ml-2 font-mono text-base text-white">
{formatNumericValue(
market.uiPrice,
getDecimalCount(market.tickSize)
getDecimalCount(market.tickSize),
)}
</p>
</div>

View File

@ -26,11 +26,11 @@ const TradeVolumeAlertModal = ({ isOpen, onClose }: ModalProps) => {
const { t } = useTranslation(['common', 'trade'])
const [soundSettings, setSoundSettings] = useLocalStorageState(
SOUND_SETTINGS_KEY,
INITIAL_SOUND_SETTINGS
INITIAL_SOUND_SETTINGS,
)
const [alertSettings, setAlertSettings] = useLocalStorageState(
TRADE_VOLUME_ALERT_KEY,
DEFAULT_VOLUME_ALERT_SETTINGS
DEFAULT_VOLUME_ALERT_SETTINGS,
)
const [formValues, setFormValues] = useState(alertSettings)

View File

@ -3,6 +3,7 @@ import {
ArrowDownTrayIcon,
ArrowTopRightOnSquareIcon,
CheckCircleIcon,
ChevronDownIcon,
ExclamationCircleIcon,
PencilIcon,
} from '@heroicons/react/20/solid'
@ -26,7 +27,6 @@ import ActionTokenList from '../account/ActionTokenList'
import ButtonGroup from '../forms/ButtonGroup'
import Input from '../forms/Input'
import Label from '../forms/Label'
import WalletIcon from '../icons/WalletIcon'
// import ParticlesBackground from '../ParticlesBackground'
// import EditNftProfilePic from '../profile/EditNftProfilePic'
// import EditProfileForm from '../profile/EditProfileForm'
@ -35,7 +35,6 @@ import InlineNotification from '../shared/InlineNotification'
import Loading from '../shared/Loading'
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'
@ -46,6 +45,7 @@ import ColorBlur from '@components/ColorBlur'
import useLocalStorageState from 'hooks/useLocalStorageState'
import { ACCEPT_TERMS_KEY } from 'utils/constants'
import { ACCOUNT_ACTIONS_NUMBER_FORMAT_CLASSES } from '@components/BorrowForm'
import { WalletReadyState } from '@solana/wallet-adapter-base'
const UserSetupModal = ({
isOpen,
@ -55,7 +55,7 @@ const UserSetupModal = ({
onClose: () => void
}) => {
const { t } = useTranslation(['common', 'onboarding', 'swap'])
const { connected, select, wallet, wallets, publicKey } = useWallet()
const { connected, select, wallet, wallets, publicKey, connect } = useWallet()
const { mangoAccount } = useMangoAccount()
const mangoAccountLoading = mangoStore((s) => s.mangoAccount.initialLoad)
const [accountName, setAccountName] = useState('')
@ -66,10 +66,29 @@ const UserSetupModal = ({
const [submitDeposit, setSubmitDeposit] = useState(false)
const [sizePercentage, setSizePercentage] = useState('')
// const [showEditProfilePic, setShowEditProfilePic] = useState(false)
const { handleConnect } = useEnhancedWallet()
const { maxSolDeposit } = useSolBalance()
const banks = useBanksWithBalances('walletBalance')
const [, setAcceptTerms] = useLocalStorageState(ACCEPT_TERMS_KEY, '')
const [walletsToDisplay, setWalletstoDisplay] = useState<'default' | 'all'>(
'default',
)
const walletsDisplayed = useMemo(() => {
const firstFive = wallets.slice(0, 5)
const detectedWallets = wallets.filter(
(w) =>
w.readyState === WalletReadyState.Installed ||
w.readyState === WalletReadyState.Loadable,
)
if (walletsToDisplay === 'default') {
return detectedWallets.length > firstFive.length
? detectedWallets
: firstFive
} else {
return wallets
}
}, [walletsToDisplay, wallets])
useEffect(() => {
if (connected) {
@ -88,7 +107,7 @@ const UserSetupModal = ({
group,
0,
accountName || 'Account 1',
16 // tokenCount
16, // tokenCount
)
actions.fetchMangoAccounts(publicKey)
if (tx) {
@ -128,7 +147,7 @@ const UserSetupModal = ({
group,
mangoAccount,
bank.mint,
parseFloat(depositAmount)
parseFloat(depositAmount),
)
notify({
title: 'Transaction confirmed',
@ -178,7 +197,7 @@ const UserSetupModal = ({
const setMax = useCallback(() => {
const max = new Decimal(tokenMax.amount).toDecimalPlaces(
tokenMax.decimals,
Decimal.ROUND_FLOOR
Decimal.ROUND_FLOOR,
)
setDepositAmount(max.toString())
setSizePercentage('100')
@ -193,7 +212,7 @@ const UserSetupModal = ({
.toDecimalPlaces(tokenMax.decimals, Decimal.ROUND_FLOOR)
setDepositAmount(amount.toString())
},
[tokenMax]
[tokenMax],
)
const handleNextStep = () => {
@ -279,44 +298,49 @@ const UserSetupModal = ({
{t('onboarding:choose-wallet')}
</p>
<div className="space-y-2">
{wallets?.map((w) => (
{walletsDisplayed?.map((w) => (
<button
className={`col-span-1 w-full rounded-md border py-3 px-4 text-base font-normal focus:outline-none md:hover:cursor-pointer md:hover:border-th-fgd-4 ${
w.adapter.name === wallet?.adapter.name
? 'border-th-active text-th-fgd-1 md:hover:border-th-active'
: 'border-th-bkg-4 text-th-fgd-4'
: 'border-th-bkg-4 text-th-fgd-2'
}`}
onClick={() => {
if (wallet) {
connect()
}
select(w.adapter.name)
}}
key={w.adapter.name}
>
<div className="flex items-center">
<img
src={w.adapter.icon}
className="mr-2 h-5 w-5"
alt={`${w.adapter.name} icon`}
/>
{w.adapter.name}
<div className="flex items-center justify-between">
<div className="flex items-center">
<img
src={w.adapter.icon}
className="mr-2 h-5 w-5"
alt={`${w.adapter.name} icon`}
/>
<div className="ml-2">{w.adapter.name}</div>
</div>
{w.readyState === WalletReadyState.Installed ||
w.readyState === WalletReadyState.Loadable ? (
<div className="text-xs">Detected</div>
) : null}
</div>
</button>
))}
</div>
<Button
className="mt-10 flex items-center justify-center"
onClick={handleConnect}
size="large"
>
{connected && mangoAccountLoading ? (
<Loading />
) : (
<div className="flex items-center justify-center">
<WalletIcon className="mr-2 h-5 w-5" />
{t('onboarding:connect-wallet')}
{walletsToDisplay !== 'all' ? (
<button
className="mt-4 flex w-full items-center justify-center text-base text-th-fgd-3 hover:text-th-fgd-1"
onClick={() => setWalletstoDisplay('all')}
>
<div>More</div>
<div>
<ChevronDownIcon className={`h-5 w-5 flex-shrink-0`} />
</div>
)}
</Button>
</button>
) : null}
</div>
) : null}
</UserSetupTransition>
@ -435,7 +459,7 @@ const UserSetupModal = ({
value={depositAmount}
onValueChange={(e: NumberFormatValues) => {
setDepositAmount(
!Number.isNaN(Number(e.value)) ? e.value : ''
!Number.isNaN(Number(e.value)) ? e.value : '',
)
}}
isAllowed={withValueLimit}

View File

@ -22,7 +22,7 @@ import { notify } from 'utils/notifications'
export const createSolanaMessage = (
wallet: WalletContextState,
setCookie: (wallet: string, token: string) => void
setCookie: (wallet: string, token: string) => void,
) => {
const payload = new Payload()
payload.domain = window.location.host
@ -121,7 +121,7 @@ const NotificationsDrawer = ({
})
}
},
[NOTIFICATION_API, headers]
[NOTIFICATION_API, headers],
)
const remove = useCallback(
@ -136,7 +136,7 @@ const NotificationsDrawer = ({
body: JSON.stringify({
ids: ids,
}),
}
},
)
const body = await resp.json()
const error = body.error
@ -158,7 +158,7 @@ const NotificationsDrawer = ({
}
setIsRemoving(false)
},
[NOTIFICATION_API, headers]
[NOTIFICATION_API, headers],
)
// Mark all notifications as seen when the inbox is opened
@ -241,7 +241,7 @@ const NotificationsDrawer = ({
<CalendarIcon className="mr-1 h-3.5 w-3.5" />
<p className="text-xs">
{dayjs(notification.createdAt).format(
'DD MMM YYYY, h:mma'
'DD MMM YYYY, h:mma',
)}
</p>
</div>

View File

@ -22,6 +22,16 @@ import { EXPLORERS } from '@components/settings/PreferredExplorerSettings'
const setMangoStore = mangoStore.getState().set
function parseDescription(description: string | null | undefined) {
if (
description?.includes('{"err":{"InstructionError":[2,{"Custom":6001}]}}')
) {
return 'Your max slippage tolerance was exceeded'
}
return description
}
const TransactionNotificationList = () => {
const { t } = useTranslation()
const transactionNotifications = mangoStore((s) => s.transactionNotifications)
@ -29,7 +39,7 @@ const TransactionNotificationList = () => {
const notEnoughSoLMessage = t('deposit-more-sol')
const [notificationPosition] = useLocalStorageState(
NOTIFICATION_POSITION_KEY,
'bottom-left'
'bottom-left',
)
const [mounted, setMounted] = useState(false)
const { maxSolDeposit } = useSolBalance()
@ -39,10 +49,10 @@ const TransactionNotificationList = () => {
useEffect(() => {
if (transactionNotifications.length) {
const customErrorNotification = transactionNotifications.find(
(n) => n.description && n.description.includes('"Custom":1')
(n) => n.description && n.description.includes('"Custom":1'),
)
const notEnoughSolNotification = transactionNotifications.find(
(n) => n.title && n.title.includes(notEnoughSoLMessage)
(n) => n.title && n.title.includes(notEnoughSoLMessage),
)
if (
@ -115,11 +125,11 @@ const TransactionNotification = ({
}) => {
const [notificationPosition] = useLocalStorageState(
NOTIFICATION_POSITION_KEY,
'Bottom-Left'
'Bottom-Left',
)
const [preferredExplorer] = useLocalStorageState(
PREFERRED_EXPLORER_KEY,
EXPLORERS[0]
EXPLORERS[0],
)
const { type, title, description, txid, show, id } = notification
@ -134,54 +144,51 @@ const TransactionNotification = ({
}
}
let parsedDescription = description
if (
description?.includes('{"err":{"InstructionError":[2,{"Custom":6001}]}}')
) {
parsedDescription = 'Your max slippage tolerance was exceeded'
}
const parsedDescription = parseDescription(description)
// if the notification is a success, then hide the confirming tx notification with the same txid
useEffect(() => {
if ((type === 'error' || type === 'success') && txid) {
setMangoStore((s) => {
const newNotifications = s.transactionNotifications.map((n) =>
n.txid === txid && n.type === 'confirm' ? { ...n, show: false } : n
n.txid === txid && n.type === 'confirm' ? { ...n, show: false } : n,
)
s.transactionNotifications = newNotifications
})
}
}, [type, txid])
const hideNotification = () => {
const hideNotification = useCallback(() => {
setMangoStore((s) => {
const newNotifications = s.transactionNotifications.map((n) =>
n.id === id ? { ...n, show: false } : n
n.id === id ? { ...n, show: false } : n,
)
s.transactionNotifications = newNotifications
})
}
}, [id])
// auto hide a notification after 8 seconds unless it is a confirming or time out notification
// auto hide a notification
// if no status is provided for a tx notification after 90s, hide it
useEffect(() => {
const id = setTimeout(
() => {
if (show) {
hideNotification()
}
},
const timeoutInterval =
parsedTitle || type === 'confirm'
? CLIENT_TX_TIMEOUT
: type === 'error'
? 30000
: type === 'info'
? 4000
: 10000
)
const id = setTimeout(() => {
if (show) {
hideNotification()
}
}, timeoutInterval)
return () => {
clearInterval(id)
}
})
}, [hideNotification, parsedTitle, show, type])
const getTransformClasses = (position: string) => {
const fromLeft = {

View File

@ -73,7 +73,7 @@ const EditNftProfilePic = ({ onClose }: { onClose: () => void }) => {
}
const response = await fetch(
`${MANGO_DATA_API_URL}/user-data/profile-details`,
requestOptions
requestOptions,
)
if (response.status === 200) {
await actions.fetchProfileDetails(publicKey.toString())
@ -119,7 +119,7 @@ const EditNftProfilePic = ({ onClose }: { onClose: () => void }) => {
}
const response = await fetch(
`${MANGO_DATA_API_URL}/user-data/profile-details`,
requestOptions
requestOptions,
)
if (response.status === 200) {
await actions.fetchProfileDetails(publicKey.toString())

View File

@ -31,7 +31,7 @@ const EditProfileForm = ({
const profile = mangoStore((s) => s.profile.details)
const { publicKey, signMessage } = useWallet()
const [profileName, setProfileName] = useState(
startCase(profile?.profile_name) || ''
startCase(profile?.profile_name) || '',
)
const [inputError, setInputError] = useState('')
const [loadUniquenessCheck, setLoadUniquenessCheck] = useState(false)
@ -43,7 +43,7 @@ const EditProfileForm = ({
try {
setLoadUniquenessCheck(true)
const response = await fetch(
`${MANGO_DATA_API_URL}/user-data/check-profile-name-unique?profile-name=${name}`
`${MANGO_DATA_API_URL}/user-data/check-profile-name-unique?profile-name=${name}`,
)
const uniquenessCheck = await response.json()
@ -105,7 +105,7 @@ const EditProfileForm = ({
}
const response = await fetch(
`${MANGO_DATA_API_URL}/user-data/profile-details`,
requestOptions
requestOptions,
)
if (response.status === 200) {
setLoadUpdateProfile(false)

View File

@ -54,7 +54,7 @@ const Leaderboards = ({
staleTime: 1000 * 60,
retry: 3,
refetchOnWindowFocus: false,
}
},
)
const isLoading =
@ -96,7 +96,7 @@ const Leaderboards = ({
rewardsLeaderboardData.map(
(wallet: RewardsLeaderboardItem, i: number) => (
<LeaderboardCard rank={i + 1} key={i} wallet={wallet} />
)
),
)
) : (
<div className="flex justify-center rounded-lg border border-th-bkg-3 p-8">

View File

@ -66,7 +66,7 @@ export const tiers = ['seed', 'mango', 'whale', 'bot']
const fetchRewardsPoints = async (walletPk: string | undefined) => {
try {
const data = await fetch(
`${MANGO_DATA_API_URL}/user-data/campaign-total-points-wallet?wallet-pk=${walletPk}`
`${MANGO_DATA_API_URL}/user-data/campaign-total-points-wallet?wallet-pk=${walletPk}`,
)
const res = await data.json()
return res
@ -78,7 +78,7 @@ const fetchRewardsPoints = async (walletPk: string | undefined) => {
export const fetchLeaderboard = async (tier: string | undefined) => {
try {
const data = await fetch(
`${MANGO_DATA_API_URL}/user-data/campaign-leaderboard?tier=${tier}`
`${MANGO_DATA_API_URL}/user-data/campaign-leaderboard?tier=${tier}`,
)
const res = await data.json()
return res
@ -195,7 +195,7 @@ const Season = ({
retry: 3,
refetchOnWindowFocus: false,
enabled: !!wallet?.adapter,
}
},
)
const {
@ -210,7 +210,7 @@ const Season = ({
staleTime: 1000 * 60,
retry: 3,
refetchOnWindowFocus: false,
}
},
)
useEffect(() => {

View File

@ -41,7 +41,7 @@ const SearchPage = () => {
try {
setLoading(true)
const response = await fetch(
`${MANGO_DATA_API_URL}/user-data/profile-search?search-string=${searchString}&search-method=${searchType}`
`${MANGO_DATA_API_URL}/user-data/profile-search?search-string=${searchString}&search-method=${searchType}`,
)
const data = await response.json()
setSearchResults(data)
@ -115,7 +115,7 @@ const SearchPage = () => {
<MangoAccountItem item={r} type={searchType} />
) : (
<WalletItem item={r} />
)
),
)
) : showNoResults ? (
<div className="flex flex-col items-center rounded-md border border-th-bkg-3 p-4">

View File

@ -13,7 +13,7 @@ const AnimationSettings = () => {
const { t } = useTranslation(['common', 'settings'])
const [animationSettings, setAnimationSettings] = useLocalStorageState(
ANIMATION_SETTINGS_KEY,
INITIAL_ANIMATION_SETTINGS
INITIAL_ANIMATION_SETTINGS,
)
const handleToggleAnimationSetting = (settingName: string) => {

View File

@ -17,6 +17,7 @@ import { useTheme } from 'next-themes'
import { useCallback } from 'react'
import { useRouter } from 'next/router'
import {
AUTO_CONNECT_WALLET,
NOTIFICATION_POSITION_KEY,
SIZE_INPUT_UI_KEY,
TRADE_CHART_UI_KEY,
@ -24,6 +25,7 @@ import {
} from 'utils/constants'
import mangoStore from '@store/mangoStore'
import { useWallet } from '@solana/wallet-adapter-react'
import Switch from '@components/forms/Switch'
const NOTIFICATION_POSITIONS = [
'bottom-left',
@ -72,23 +74,27 @@ const DisplaySettings = () => {
const [savedLanguage, setSavedLanguage] = useLocalStorageState(
'language',
'en'
'en',
)
const router = useRouter()
const { pathname, asPath, query } = router
const [notificationPosition, setNotificationPosition] = useLocalStorageState(
NOTIFICATION_POSITION_KEY,
'bottom-left'
'bottom-left',
)
const [tradeFormUi, setTradeFormUi] = useLocalStorageState(
SIZE_INPUT_UI_KEY,
'slider'
'slider',
)
const [tradeChartUi, setTradeChartUi] = useLocalStorageState(
TRADE_CHART_UI_KEY,
'trading-view'
'trading-view',
)
const [, setTradeLayout] = useLocalStorageState(TRADE_LAYOUT_KEY, 'chartLeft')
const [autoConnect, setAutoConnect] = useLocalStorageState(
AUTO_CONNECT_WALLET,
true,
)
useEffect(() => {
if (connection && publicKey) {
@ -105,7 +111,7 @@ const DisplaySettings = () => {
router.push({ pathname, query }, asPath, { locale: l })
dayjs.locale(l == 'zh_tw' ? 'zh-tw' : l)
},
[router, pathname, query, asPath, setSavedLanguage]
[router, pathname, query, asPath, setSavedLanguage],
)
return (
@ -209,6 +215,13 @@ const DisplaySettings = () => {
/>
</div>
</div>
<div className="flex items-center justify-between border-t border-th-bkg-3 p-4">
<p className="">Auto Connect Wallet</p>
<Switch
checked={autoConnect}
onChange={() => setAutoConnect(!autoConnect)}
/>
</div>
</>
)
}

View File

@ -120,7 +120,7 @@ const HotKeysSettings = () => {
{Object.entries(options).map((e) => {
return e[1]
? `${e[0] !== 'margin' ? ', ' : ''}${t(
`trade:${e[0]}`
`trade:${e[0]}`,
)}`
: ''
})}

View File

@ -49,7 +49,7 @@ const NotificationSettings = () => {
onChange={() =>
handleSettingChange(
'fillsNotifications',
!data?.fillsNotifications
!data?.fillsNotifications,
)
}
/>

View File

@ -15,7 +15,7 @@ const PreferredExplorerSettings = () => {
const { t } = useTranslation('settings')
const [preferredExplorer, setPreferredExplorer] = useLocalStorageState(
PREFERRED_EXPLORER_KEY,
EXPLORERS[0]
EXPLORERS[0],
)
return (
<>

View File

@ -14,18 +14,18 @@ import {
} from 'utils/constants'
import Tooltip from '@components/shared/Tooltip'
export const TRITON_DEDICATED_URL = process.env.NEXT_PUBLIC_TRITON_TOKEN
? `https://mango.rpcpool.com/${process.env.NEXT_PUBLIC_TRITON_TOKEN}`
: 'https://mango.rpcpool.com/946ef7337da3f5b8d3e4a34e7f88'
const RPC_URLS = [
{
label: 'Triton Shared',
value:
process.env.NEXT_PUBLIC_ENDPOINT ||
'https://mango.rpcpool.com/946ef7337da3f5b8d3e4a34e7f88',
value: process.env.NEXT_PUBLIC_ENDPOINT || TRITON_DEDICATED_URL,
},
{
label: 'Triton Dedicated',
value: process.env.NEXT_PUBLIC_TRITON_TOKEN
? `https://mango.rpcpool.com/${process.env.NEXT_PUBLIC_TRITON_TOKEN}`
: 'https://mango.rpcpool.com/946ef7337da3f5b8d3e4a34e7f88',
value: TRITON_DEDICATED_URL,
},
// {
// label: 'Genesys Go',
@ -50,11 +50,11 @@ const RpcSettings = () => {
const [showCustomForm, setShowCustomForm] = useState(false)
const [rpcEndpointProvider, setRpcEndpointProvider] = useLocalStorageState(
RPC_PROVIDER_KEY,
RPC_URLS[0].value
RPC_URLS[0].value,
)
const [storedPriorityFee, setStoredPriorityFee] = useLocalStorageState(
PRIORITY_FEE_KEY,
DEFAULT_PRIORITY_FEE.value
DEFAULT_PRIORITY_FEE.value,
)
const [storedUseOrderbookFeed, setStoredUseOrderbookFeed] =
useLocalStorageState(USE_ORDERBOOK_FEED_KEY, true)
@ -77,7 +77,7 @@ const RpcSettings = () => {
const handleSetEndpointProvider = (provider: string) => {
const endpointProvider = RPC_URLS.find(
(node) => node.label === provider
(node) => node.label === provider,
) || { label: 'Custom', value: rpcEndpointProvider }
setRpcEndpointProvider(endpointProvider.value)
if (provider !== 'Custom') {
@ -96,7 +96,7 @@ const RpcSettings = () => {
}
}
},
[setStoredPriorityFee, actions, wallet]
[setStoredPriorityFee, actions, wallet],
)
useEffect(() => {

View File

@ -14,7 +14,7 @@ const SoundSettings = () => {
const { t } = useTranslation(['common', 'settings'])
const [soundSettings, setSoundSettings] = useLocalStorageState(
SOUND_SETTINGS_KEY,
INITIAL_SOUND_SETTINGS
INITIAL_SOUND_SETTINGS,
)
const handleToggleSoundSetting = (settingName: string) => {

View File

@ -22,18 +22,21 @@ import useBanksWithBalances, {
import useUnownedAccount from 'hooks/useUnownedAccount'
import { Disclosure, Transition } from '@headlessui/react'
import TokenLogo from './TokenLogo'
import useHealthContributions from 'hooks/useHealthContributions'
import Tooltip from './Tooltip'
import { PublicKey } from '@solana/web3.js'
import { USDC_MINT } from 'utils/constants'
import { WRAPPED_SOL_MINT } from '@project-serum/serum/lib/token-instructions'
const BalancesTable = () => {
const { t } = useTranslation(['common', 'trade'])
const { t } = useTranslation(['common', 'account', 'trade'])
const { mangoAccount, mangoAccountAddress } = useMangoAccount()
const spotBalances = mangoStore((s) => s.mangoAccount.spotBalances)
const { width } = useViewport()
const { connected } = useWallet()
const showTableView = width ? width > breakpoints.md : false
const banks = useBanksWithBalances('balance')
const { initContributions } = useHealthContributions()
const filteredBanks = useMemo(() => {
if (banks.length) {
@ -56,6 +59,15 @@ const BalancesTable = () => {
<TrHead>
<Th className="bg-th-bkg-1 text-left">{t('token')}</Th>
<Th className="bg-th-bkg-1 text-right">{t('balance')}</Th>
<Th>
<div className="flex justify-end">
<Tooltip content={t('account:tooltip-collateral-value')}>
<span className="tooltip-underline">
{t('collateral-value')}
</span>
</Tooltip>
</div>
</Th>
<Th className="bg-th-bkg-1 text-right">{t('trade:in-orders')}</Th>
<Th className="bg-th-bkg-1 text-right" id="trade-step-ten">
{t('trade:unsettled')}
@ -70,6 +82,15 @@ const BalancesTable = () => {
const inOrders = spotBalances[bank.mint.toString()]?.inOrders || 0
const unsettled = spotBalances[bank.mint.toString()]?.unsettled || 0
const collateralValue =
initContributions.find((val) => val.asset === bank.name)
?.contribution || 0
const assetWeight = bank
.scaledInitAssetWeight(bank.price)
.toFixed(2)
const liabWeight = bank.scaledInitLiabWeight(bank.price).toFixed(2)
return (
<TrBody key={bank.name} className="text-sm">
<Td>
@ -89,6 +110,23 @@ const BalancesTable = () => {
/>
</p>
</Td>
<Td className="text-right">
<p>
<FormatNumericValue
value={collateralValue}
decimals={2}
isUsd
/>
</p>
<p className="text-sm text-th-fgd-4">
<FormatNumericValue
value={
collateralValue <= -0.01 ? liabWeight : assetWeight
}
/>
x
</p>
</Td>
<Td className="text-right">
<BankAmountWithValue amount={inOrders} bank={bank} stacked />
</Td>
@ -109,6 +147,13 @@ const BalancesTable = () => {
const inOrders = spotBalances[bank.mint.toString()]?.inOrders || 0
const unsettled = spotBalances[bank.mint.toString()]?.unsettled || 0
const collateralValue =
initContributions.find((val) => val.asset === bank.name)
?.contribution || 0
const assetWeight = bank.scaledInitAssetWeight(bank.price).toFixed(2)
const liabWeight = bank.scaledInitLiabWeight(bank.price).toFixed(2)
return (
<Disclosure key={bank.name}>
{({ open }) => (
@ -152,6 +197,33 @@ const BalancesTable = () => {
>
<Disclosure.Panel>
<div className="mx-4 grid grid-cols-2 gap-4 border-t border-th-bkg-3 pt-4 pb-4">
<div className="col-span-1">
<Tooltip
content={t('account:tooltip-collateral-value')}
>
<p className="tooltip-underline text-xs text-th-fgd-3">
{t('collateral-value')}
</p>
</Tooltip>
<p className="font-mono text-th-fgd-2">
<FormatNumericValue
value={collateralValue}
decimals={2}
isUsd
/>
<span className="text-th-fgd-3">
{' '}
<FormatNumericValue
value={
collateralValue <= -0.01
? liabWeight
: assetWeight
}
/>
x
</span>
</p>
</div>
<div className="col-span-1">
<p className="text-xs text-th-fgd-3">
{t('trade:in-orders')}
@ -221,7 +293,7 @@ const Balance = ({ bank }: { bank: BankWithBalance }) => {
let tickDecimals: number
if (selectedMarket instanceof Serum3Market) {
const market = group.getSerum3ExternalMarket(
selectedMarket.serumMarketExternal
selectedMarket.serumMarketExternal,
)
minOrderDecimals = getDecimalCount(market.minOrderSize)
tickDecimals = getDecimalCount(market.tickSize)
@ -234,7 +306,7 @@ const Balance = ({ bank }: { bank: BankWithBalance }) => {
const floorBalance = floorToDecimal(balance, tickDecimals).toNumber()
const baseSize = floorToDecimal(
floorBalance / price,
minOrderDecimals
minOrderDecimals,
).toNumber()
const quoteSize = floorToDecimal(baseSize * price, tickDecimals)
set((s) => {
@ -250,7 +322,7 @@ const Balance = ({ bank }: { bank: BankWithBalance }) => {
})
}
},
[selectedMarket]
[selectedMarket],
)
const handleSwapFormBalanceClick = useCallback(
@ -284,7 +356,7 @@ const Balance = ({ bank }: { bank: BankWithBalance }) => {
})
}
},
[bank]
[bank],
)
const balance = bank.balance
@ -321,7 +393,7 @@ const Balance = ({ bank }: { bank: BankWithBalance }) => {
className="font-normal underline underline-offset-2 md:underline-offset-4 md:hover:no-underline"
onClick={() =>
handleSwapFormBalanceClick(
Number(floorToDecimal(balance, tokenBank.mintDecimals))
Number(floorToDecimal(balance, tokenBank.mintDecimals)),
)
}
>

View File

@ -1,19 +1,35 @@
import WalletIcon from '@components/icons/WalletIcon'
import { useEnhancedWallet } from '@components/wallet/EnhancedWalletProvider'
import { LinkIcon } from '@heroicons/react/20/solid'
import mangoStore from '@store/mangoStore'
import { useTranslation } from 'next-i18next'
import Button from './Button'
import { useWallet } from '@solana/wallet-adapter-react'
import mangoStore from '@store/mangoStore'
import { useCallback } from 'react'
import { AUTO_CONNECT_WALLET } from 'utils/constants'
import useLocalStorageState from 'hooks/useLocalStorageState'
const set = mangoStore.getState().set
const ConnectEmptyState = ({ text }: { text: string }) => {
const { t } = useTranslation('common')
const { handleConnect } = useEnhancedWallet()
const groupLoaded = mangoStore((s) => s.groupLoaded)
const { connect } = useWallet()
const [autoConnect] = useLocalStorageState(AUTO_CONNECT_WALLET, true)
const handleConnect = useCallback(() => {
if (!autoConnect) {
set((s) => {
s.showUserSetup = true
})
} else {
connect()
}
}, [autoConnect, connect])
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} disabled={!groupLoaded}>
<Button onClick={handleConnect}>
<div className="flex items-center">
<LinkIcon className="mr-2 h-5 w-5" />
{t('connect')}

View File

@ -18,7 +18,6 @@ import FlipNumbers from 'react-flip-numbers'
import ContentBox from './ContentBox'
import SheenLoader from './SheenLoader'
import { COLORS } from '../../styles/colors'
import { useTheme } from 'next-themes'
import { IconButton } from './Button'
import { ArrowLeftIcon, NoSymbolIcon } from '@heroicons/react/20/solid'
import { FadeInFadeOut } from './Transitions'
@ -33,6 +32,7 @@ import { useTranslation } from 'next-i18next'
import FormatNumericValue from './FormatNumericValue'
import { ContentType } from 'recharts/types/component/Tooltip'
import Tooltip from './Tooltip'
import useThemeWrapper from 'hooks/useThemeWrapper'
dayjs.extend(relativeTime)
@ -97,10 +97,10 @@ const DetailedAreaOrBarChart: FunctionComponent<
}) => {
const { t } = useTranslation('common')
const [mouseData, setMouseData] = useState<any>(null)
const { theme } = useTheme()
const { theme } = useThemeWrapper()
const [animationSettings] = useLocalStorageState(
ANIMATION_SETTINGS_KEY,
INITIAL_ANIMATION_SETTINGS
INITIAL_ANIMATION_SETTINGS,
)
const handleMouseMove = (coords: any) => {
@ -134,7 +134,7 @@ const DetailedAreaOrBarChart: FunctionComponent<
if (filteredData.length) {
if (mouseData) {
const index = filteredData.findIndex(
(d: any) => d[xKey] === mouseData[xKey]
(d: any) => d[xKey] === mouseData[xKey],
)
const change =
index >= 0 ? filteredData[index][yKey] - filteredData[0][yKey] : 0
@ -216,7 +216,7 @@ const DetailedAreaOrBarChart: FunctionComponent<
mouseData[yKey] < 0 ? '-' : ''
}${prefix}${formatNumericValue(
Math.abs(mouseData[yKey]),
yDecimals
yDecimals,
)}${suffix}`}
/>
) : (
@ -249,7 +249,7 @@ const DetailedAreaOrBarChart: FunctionComponent<
{dayjs(mouseData[xKey]).format(
tooltipDateFormat
? tooltipDateFormat
: 'DD MMM YY, h:mma'
: 'DD MMM YY, h:mma',
)}
</p>
</div>
@ -273,9 +273,9 @@ const DetailedAreaOrBarChart: FunctionComponent<
: ''
}${prefix}${formatNumericValue(
Math.abs(
filteredData[filteredData.length - 1][yKey]
filteredData[filteredData.length - 1][yKey],
),
yDecimals
yDecimals,
)}${suffix}`}
/>
) : (
@ -308,11 +308,11 @@ const DetailedAreaOrBarChart: FunctionComponent<
} text-th-fgd-4`}
>
{dayjs(
filteredData[filteredData.length - 1][xKey]
filteredData[filteredData.length - 1][xKey],
).format(
tooltipDateFormat
? tooltipDateFormat
: 'DD MMM YY, h:mma'
: 'DD MMM YY, h:mma',
)}
</p>
</div>
@ -341,7 +341,7 @@ const DetailedAreaOrBarChart: FunctionComponent<
<linearGradient
id={`gradientArea-${title?.replace(
/[^a-zA-Z]/g,
''
'',
)}`}
x1="0"
y1={flipGradientCoords ? '1' : '0'}
@ -380,7 +380,7 @@ const DetailedAreaOrBarChart: FunctionComponent<
strokeWidth={1.5}
fill={`url(#gradientArea-${title?.replace(
/[^a-zA-Z]/g,
''
'',
)})`}
/>
<XAxis

View File

@ -11,7 +11,7 @@ const FavoriteMarketButton = ({
}) => {
const [favoriteMarkets, setFavoriteMarkets] = useLocalStorageState<string[]>(
FAVORITE_MARKETS_KEY,
[]
[],
)
const addToFavorites = (marketName: string) => {
@ -24,7 +24,7 @@ const FavoriteMarketButton = ({
}
return favoriteMarkets.find(
(marketName: string) => marketName === market.name
(marketName: string) => marketName === market.name,
) ? (
<button
className="flex items-center justify-center text-th-active focus-visible:text-th-fgd-4 md:hover:text-th-fgd-3"

View File

@ -63,7 +63,7 @@ const SolBalanceWarnings = ({
<div className={className}>
<InlineNotification
type="info"
desc={`SOL deposits are restricted to leave ${MIN_SOL_BALANCE} SOL in your wallet for sending transactions`}
desc={`Max SOL deposits are reduced to leave ${MIN_SOL_BALANCE} SOL in your wallet for sending transactions`}
/>
</div>
) : null

View File

@ -18,7 +18,7 @@ const SuccessParticles = () => {
const set = mangoStore((s) => s.set)
const [animationSettings] = useLocalStorageState(
ANIMATION_SETTINGS_KEY,
INITIAL_ANIMATION_SETTINGS
INITIAL_ANIMATION_SETTINGS,
)
const { theme } = useTheme()
@ -77,7 +77,7 @@ const SuccessParticles = () => {
set((s) => {
s.successAnimation.swap = false
}),
8000
8000,
)
}
if (showForTheme) {
@ -86,7 +86,7 @@ const SuccessParticles = () => {
set((s) => {
s.successAnimation.theme = false
}),
6000
6000,
)
}
if (showForTrade) {
@ -95,7 +95,7 @@ const SuccessParticles = () => {
set((s) => {
s.successAnimation.trade = false
}),
8000
8000,
)
}
}, [showForSwap, showForTheme, showForTrade])

View File

@ -62,7 +62,7 @@ export const TrBody = forwardRef<HTMLTableRowElement, TrBodyProps>(
{children}
</tr>
)
}
},
)
TrBody.displayName = 'TrBody'

View File

@ -22,7 +22,7 @@ const TokenLogo = ({
let jupiterLogoURI
if (mangoTokens?.length) {
jupiterLogoURI = mangoTokens.find(
(t) => t.address === bank?.mint.toString()
(t) => t.address === bank?.mint.toString(),
)?.logoURI
}
return jupiterLogoURI

View File

@ -26,7 +26,7 @@ const TokenVaultWarnings = ({
const maxWithdraw = getMaxWithdrawForBank(group, bank, mangoAccount)
const maxBorrow = mangoAccount.getMaxWithdrawWithBorrowForTokenUi(
group,
bank.mint
bank.mint,
)
return [maxWithdraw, maxBorrow]

View File

@ -2,7 +2,7 @@ import { useEffect, useState } from 'react'
export default function useDebounce<T>(
value: T,
delay: number
delay: number,
): [T, (value: T) => void] {
// State and setters for debounced value
const [debouncedValue, setDebouncedValue] = useState(value)
@ -19,7 +19,7 @@ export default function useDebounce<T>(
clearTimeout(handler)
}
},
[value, delay] // Only re-call effect if value or delay changes
[value, delay], // Only re-call effect if value or delay changes
)
return [debouncedValue, setDebouncedValue]
}

View File

@ -3,7 +3,7 @@ import { useRef, useEffect } from 'react'
export default function useInterval(
// eslint-disable-next-line @typescript-eslint/no-explicit-any
callback: (...args: any[]) => any,
delay: number
delay: number,
) {
const savedCallback = useRef<() => void>()

View File

@ -20,7 +20,7 @@ const AverageFundingChart = ({
const groupByHourlyInterval = (
data: PerpStatsItem[],
intervalDurationHours: number
intervalDurationHours: number,
) => {
const intervalMillis = intervalDurationHours * 60 * 60 * 1000
const groupedData = []

View File

@ -3,6 +3,7 @@ import { useMemo, useState } from 'react'
import mangoStore from '@store/mangoStore'
import { PerpStatsItem } from 'types'
import DetailedAreaOrBarChart from '@components/shared/DetailedAreaOrBarChart'
import { formatYAxis } from 'utils/formatting'
interface OiValueItem {
date: string
@ -14,97 +15,143 @@ interface FeeValueItem {
feeValue: number
}
interface VolumeValueItem {
date: string
volume: number
}
interface PerpStatsData {
feeValues: FeeValueItem[]
openInterestValues: OiValueItem[]
volumeValues: VolumeValueItem[]
}
const MangoPerpStatsCharts = () => {
const { t } = useTranslation(['common', 'token', 'trade'])
const loadingPerpStats = mangoStore((s) => s.perpStats.loading)
const perpStats = mangoStore((s) => s.perpStats.data)
const [oiDaysToShow, setOiDaysToShow] = useState('30')
const [feesDaysToShow, setFeesDaysToShow] = useState('30')
// const perpMarkets = mangoStore((s) => s.perpMarkets)
const [oiDaysToShow, setOiDaysToShow] = useState('30')
const [volumeDaysToShow, setVolumeDaysToShow] = useState('30')
// const currentTotalOpenInterestValue = useMemo(() => {
// if (!perpMarkets.length) return 0
// return perpMarkets.reduce((a: number, c: PerpMarket) => {
// const value = a + c.openInterest.toNumber() * c.uiPrice
// return value
// }, 0)
// }, [perpMarkets])
const [feeValues, openInterestValues, volumeValues] = useMemo(() => {
if (!perpStats || !perpStats.length) return [[], [], []]
const data = perpStats.reduce(
(a: PerpStatsData, c: PerpStatsItem) => {
const hasDateFee = a.feeValues.find(
(d: FeeValueItem) => d.date === c.date_hour,
)
const totalFeeValues = useMemo(() => {
if (!perpStats || !perpStats.length) return []
const values = perpStats.reduce((a: FeeValueItem[], c: PerpStatsItem) => {
const hasDate = a.find((d: FeeValueItem) => d.date === c.date_hour)
if (!hasDate) {
a.push({
date: c.date_hour,
feeValue: c.total_fees,
})
} else {
hasDate.feeValue = hasDate.feeValue + c.total_fees
}
return a.sort(
(a, b) => new Date(a.date).getTime() - new Date(b.date).getTime()
)
}, [])
return values
}, [perpStats])
const hasDateOpenInterest = a.openInterestValues.find(
(d: OiValueItem) => d.date === c.date_hour,
)
const totalOpenInterestValues = useMemo(() => {
if (!perpStats || !perpStats.length) return []
const values = perpStats.reduce((a: OiValueItem[], c: PerpStatsItem) => {
const hasDate = a.find((d: OiValueItem) => d.date === c.date_hour)
if (!hasDate) {
a.push({
date: c.date_hour,
openInterest: Math.floor(c.open_interest * c.price),
})
} else {
hasDate.openInterest =
hasDate.openInterest + Math.floor(c.open_interest * c.price)
}
return a.sort(
(a, b) => new Date(a.date).getTime() - new Date(b.date).getTime()
)
}, [])
return values
const hasDateVolume = a.volumeValues.find(
(d: VolumeValueItem) => d.date === c.date_hour,
)
if (!hasDateFee) {
a.feeValues.push({
date: c.date_hour,
feeValue: c.total_fees,
})
} else {
hasDateFee.feeValue += c.total_fees
}
if (!hasDateOpenInterest) {
a.openInterestValues.push({
date: c.date_hour,
openInterest: Math.floor(c.open_interest * c.price),
})
} else {
hasDateOpenInterest.openInterest += Math.floor(
c.open_interest * c.price,
)
}
if (!hasDateVolume) {
a.volumeValues.push({
date: c.date_hour,
volume: c.cumulative_quote_volume,
})
} else {
hasDateVolume.volume += c.cumulative_quote_volume
}
return a
},
{ feeValues: [], openInterestValues: [], volumeValues: [] },
)
const { feeValues, openInterestValues, volumeValues } = data
const sortedFeeValues = feeValues.sort(
(a, b) => new Date(a.date).getTime() - new Date(b.date).getTime(),
)
const sortedOpenInterestValues = openInterestValues.sort(
(a, b) => new Date(a.date).getTime() - new Date(b.date).getTime(),
)
const sortedVolumeValues = volumeValues.sort(
(a, b) => new Date(a.date).getTime() - new Date(b.date).getTime(),
)
return [sortedFeeValues, sortedOpenInterestValues, sortedVolumeValues]
}, [perpStats])
return (
<>
{totalFeeValues.length ? (
{feeValues.length ? (
<div className="col-span-2 border-b border-th-bkg-3 py-4 px-6 md:col-span-1 md:pl-6">
<DetailedAreaOrBarChart
data={totalFeeValues}
data={feeValues}
daysToShow={feesDaysToShow}
setDaysToShow={setFeesDaysToShow}
heightClass="h-64"
loading={loadingPerpStats}
loaderHeightClass="h-[350px]"
prefix="$"
tickFormat={(x) => `$${x.toFixed(2)}`}
tickFormat={(x) => `$${formatYAxis(x)}`}
title="Perp Fees"
xKey="date"
yKey={'feeValue'}
/>
</div>
) : null}
{totalOpenInterestValues.length ? (
{openInterestValues.length ? (
<div className="col-span-2 border-b border-th-bkg-3 py-4 px-6 md:col-span-1 md:border-r">
<DetailedAreaOrBarChart
data={totalOpenInterestValues}
data={openInterestValues}
daysToShow={oiDaysToShow}
setDaysToShow={setOiDaysToShow}
heightClass="h-64"
loading={loadingPerpStats}
loaderHeightClass="h-[350px]"
prefix="$"
tickFormat={(x) => `$${Math.floor(x)}`}
tickFormat={(x) => `$${formatYAxis(x)}`}
title={t('trade:open-interest')}
xKey="date"
yKey={'openInterest'}
/>
</div>
) : null}
{volumeValues.length ? (
<div className="col-span-2 border-b border-th-bkg-3 py-4 px-6 md:col-span-1 md:pl-6">
<DetailedAreaOrBarChart
data={volumeValues}
daysToShow={volumeDaysToShow}
setDaysToShow={setVolumeDaysToShow}
heightClass="h-64"
loading={loadingPerpStats}
loaderHeightClass="h-[350px]"
prefix="$"
tickFormat={(x) => `$${formatYAxis(x)}`}
title={t('stats:perp-volume')}
xKey="date"
yKey={'volume'}
/>
</div>
) : null}
</>
)
}

View File

@ -1,6 +1,5 @@
import { I80F48, PerpMarket } from '@blockworks-foundation/mango-v4'
import { useTranslation } from 'next-i18next'
import { useTheme } from 'next-themes'
import { useViewport } from '../../hooks/useViewport'
import mangoStore from '@store/mangoStore'
import { COLORS } from '../../styles/colors'
@ -25,10 +24,11 @@ import { Disclosure, Transition } from '@headlessui/react'
import { LinkButton } from '@components/shared/Button'
import SoonBadge from '@components/shared/SoonBadge'
import { DAILY_SECONDS } from 'utils/constants'
import useThemeWrapper from 'hooks/useThemeWrapper'
export const getOneDayPerpStats = (
stats: PerpStatsItem[] | null,
marketName: string
marketName: string,
) => {
return stats
? stats
@ -46,7 +46,7 @@ export const getOneDayPerpStats = (
export const goToPerpMarketDetails = (
market: PerpMarket,
router: NextRouter
router: NextRouter,
) => {
const query = { ...router.query, ['market']: market.name }
router.push({ pathname: router.pathname, query })
@ -57,7 +57,7 @@ const PerpMarketsOverviewTable = () => {
const perpMarkets = mangoStore((s) => s.perpMarkets)
const loadingPerpStats = mangoStore((s) => s.perpStats.loading)
const perpStats = mangoStore((s) => s.perpStats.data)
const { theme } = useTheme()
const { theme } = useThemeWrapper()
const { width } = useViewport()
const showTableView = width ? width > breakpoints.md : false
const rate = usePerpFundingRate()
@ -107,14 +107,14 @@ const PerpMarketsOverviewTable = () => {
let fundingRateApr
if (rate.isSuccess) {
const marketRate = rate?.data?.find(
(r) => r.market_index === market.perpMarketIndex
(r) => r.market_index === market.perpMarketIndex,
)
if (marketRate) {
fundingRate = formatFunding.format(
marketRate.funding_rate_hourly
marketRate.funding_rate_hourly,
)
fundingRateApr = formatFunding.format(
marketRate.funding_rate_hourly * 8760
marketRate.funding_rate_hourly * 8760,
)
} else {
fundingRate = ''
@ -190,9 +190,9 @@ const PerpMarketsOverviewTable = () => {
<FormatNumericValue
value={group.toUiPrice(
I80F48.fromNumber(
market.stablePriceModel.stablePrice
market.stablePriceModel.stablePrice,
),
market.baseDecimals
market.baseDecimals,
)}
isUsd
/>
@ -260,7 +260,7 @@ const PerpMarketsOverviewTable = () => {
<p className="text-th-fgd-4">
$
{numberCompacter.format(
openInterest * market.uiPrice
openInterest * market.uiPrice,
)}
</p>
</>
@ -304,7 +304,7 @@ const MobilePerpMarketItem = ({ market }: { market: PerpMarket }) => {
const { t } = useTranslation('common')
const loadingPerpStats = mangoStore((s) => s.perpStats.loading)
const perpStats = mangoStore((s) => s.perpStats.data)
const { theme } = useTheme()
const { theme } = useThemeWrapper()
const router = useRouter()
const rate = usePerpFundingRate()
@ -328,12 +328,12 @@ const MobilePerpMarketItem = ({ market }: { market: PerpMarket }) => {
let fundingRateApr: string
if (rate.isSuccess) {
const marketRate = rate?.data?.find(
(r) => r.market_index === market.perpMarketIndex
(r) => r.market_index === market.perpMarketIndex,
)
if (marketRate) {
fundingRate = formatFunding.format(marketRate.funding_rate_hourly)
fundingRateApr = formatFunding.format(
marketRate.funding_rate_hourly * 8760
marketRate.funding_rate_hourly * 8760,
)
} else {
fundingRate = ''

View File

@ -41,7 +41,7 @@ const PerpPositionsStatsTable = ({
<tbody>
{positions.map(({ account, perpPosition, mangoAccount }, i) => {
const market = group.getPerpMarketByMarketIndex(
perpPosition.marketIndex
perpPosition.marketIndex,
)
const basePosition = perpPosition.getBasePositionUi(market)
@ -49,7 +49,7 @@ const PerpPositionsStatsTable = ({
const floorBasePosition = floorToDecimal(
basePosition,
getDecimalCount(market.minOrderSize)
getDecimalCount(market.minOrderSize),
).toNumber()
const isLong = basePosition > 0
@ -179,7 +179,7 @@ const PerpPositionsStatsTable = ({
<div className="border-b border-th-bkg-3">
{positions.map(({ account, perpPosition, mangoAccount }) => {
const market = group.getPerpMarketByMarketIndex(
perpPosition.marketIndex
perpPosition.marketIndex,
)
const basePosition = perpPosition.getBasePositionUi(market)
@ -187,7 +187,7 @@ const PerpPositionsStatsTable = ({
const floorBasePosition = floorToDecimal(
basePosition,
getDecimalCount(market.minOrderSize)
getDecimalCount(market.minOrderSize),
).toNumber()
const isLong = basePosition > 0

View File

@ -1,6 +1,5 @@
import { Serum3Market } from '@blockworks-foundation/mango-v4'
import { useTranslation } from 'next-i18next'
import { useTheme } from 'next-themes'
import { useMemo } from 'react'
import { useViewport } from '../../hooks/useViewport'
import mangoStore from '@store/mangoStore'
@ -21,12 +20,13 @@ import { TickerData } from 'types'
import { Disclosure, Transition } from '@headlessui/react'
import { ChevronDownIcon } from '@heroicons/react/20/solid'
import MarketChange from '@components/shared/MarketChange'
import useThemeWrapper from 'hooks/useThemeWrapper'
const SpotMarketsTable = () => {
const { t } = useTranslation('common')
const { group } = useMangoGroup()
const serumMarkets = mangoStore((s) => s.serumMarkets)
const { theme } = useTheme()
const { theme } = useThemeWrapper()
const { width } = useViewport()
const showTableView = width ? width > breakpoints.md : false
const { data: birdeyePrices, isLoading: loadingPrices } =
@ -40,7 +40,7 @@ const SpotMarketsTable = () => {
staleTime: 1000 * 60,
retry: 3,
refetchOnWindowFocus: false,
}
},
)
return (
@ -62,30 +62,30 @@ const SpotMarketsTable = () => {
.sort((a, b) => a.name.localeCompare(b.name))
.map((mkt) => {
const baseBank = group?.getFirstBankByTokenIndex(
mkt.baseTokenIndex
mkt.baseTokenIndex,
)
const quoteBank = group?.getFirstBankByTokenIndex(
mkt.quoteTokenIndex
mkt.quoteTokenIndex,
)
const market = group?.getSerum3ExternalMarket(
mkt.serumMarketExternal
mkt.serumMarketExternal,
)
let price
if (baseBank && market && quoteBank) {
price = floorToDecimal(
baseBank.uiPrice / quoteBank.uiPrice,
getDecimalCount(market.tickSize)
getDecimalCount(market.tickSize),
).toNumber()
}
let tickerData: TickerData | undefined
if (spotVolumeData && spotVolumeData.length) {
tickerData = spotVolumeData.find(
(m: TickerData) => m.ticker_id === mkt.name
(m: TickerData) => m.ticker_id === mkt.name,
)
}
const birdeyeData = birdeyePrices.find(
(m) => m.mint === mkt.serumMarketExternal.toString()
(m) => m.mint === mkt.serumMarketExternal.toString(),
)
const birdeyeChange =
@ -163,7 +163,7 @@ const SpotMarketsTable = () => {
{tickerData ? (
<span>
{numberCompacter.format(
parseFloat(tickerData.target_volume)
parseFloat(tickerData.target_volume),
)}{' '}
<span className="font-body text-th-fgd-4">
{quoteBank?.name}
@ -213,7 +213,7 @@ const MobileSpotMarketItem = ({
const { data: birdeyePrices, isLoading: loadingPrices } =
useBirdeyeMarketPrices()
const { group } = useMangoGroup()
const { theme } = useTheme()
const { theme } = useThemeWrapper()
const baseBank = group?.getFirstBankByTokenIndex(market.baseTokenIndex)
const quoteBank = group?.getFirstBankByTokenIndex(market.quoteTokenIndex)
const serumMarket = group?.getSerum3ExternalMarket(market.serumMarketExternal)
@ -222,14 +222,14 @@ const MobileSpotMarketItem = ({
if (!baseBank || !quoteBank || !serumMarket) return 0
return floorToDecimal(
baseBank.uiPrice / quoteBank.uiPrice,
getDecimalCount(serumMarket.tickSize)
getDecimalCount(serumMarket.tickSize),
).toNumber()
}, [baseBank, quoteBank, serumMarket])
const birdeyeData = useMemo(() => {
if (!loadingPrices) {
return birdeyePrices.find(
(m) => m.mint === market.serumMarketExternal.toString()
(m) => m.mint === market.serumMarketExternal.toString(),
)
}
return null
@ -254,7 +254,7 @@ const MobileSpotMarketItem = ({
let tickerData: TickerData | undefined
if (spotVolumeData && spotVolumeData.length) {
tickerData = spotVolumeData.find(
(m: TickerData) => m.ticker_id === market.name
(m: TickerData) => m.ticker_id === market.name,
)
}
@ -336,7 +336,7 @@ const MobileSpotMarketItem = ({
{tickerData ? (
<span>
{numberCompacter.format(
parseFloat(tickerData.target_volume)
parseFloat(tickerData.target_volume),
)}{' '}
<span className="font-body text-th-fgd-4">
{quoteBank?.name}

View File

@ -19,7 +19,7 @@ const StatsPage = () => {
const actions = mangoStore.getState().actions
const perpStats = mangoStore((s) => s.perpStats.data)
const perpPositionsStatsNotLoaded = mangoStore(
(s) => s.perpStats.positions.initialLoad
(s) => s.perpStats.positions.initialLoad,
)
const { group } = useMangoGroup()
const { width } = useViewport()

View File

@ -98,7 +98,7 @@ const TokenDetailsTable = () => {
const [oracleProvider, oracleLinkPath] = getOracleProvider(bank)
const mintInfo = group.mintInfosMapByMint.get(
bank.mint.toString()
bank.mint.toString(),
)
return (
@ -131,7 +131,7 @@ const TokenDetailsTable = () => {
<Td>
<p className="text-right">
{(100 * bank.loanOriginationFeeRate.toNumber()).toFixed(
2
2,
)}
%
</p>
@ -262,7 +262,7 @@ const TokenDetailsTable = () => {
</Tooltip>
<p className="font-mono text-th-fgd-1">
{(bank.liquidationFee.toNumber() * 100).toFixed(
2
2,
)}
%
</p>

View File

@ -84,11 +84,11 @@ const TokenOverviewTable = () => {
deposits * bank.minVaultToDepositsRatio
const available = Decimal.max(
0,
availableVaultBalance.toFixed(bank.mintDecimals)
availableVaultBalance.toFixed(bank.mintDecimals),
)
const feesEarned = toUiDecimals(
bank.collectedFeesNative,
bank.mintDecimals
bank.mintDecimals,
)
return (
@ -201,11 +201,11 @@ const TokenOverviewTable = () => {
deposits * bank.minVaultToDepositsRatio
const available = Decimal.max(
0,
availableVaultBalance.toFixed(bank.mintDecimals)
availableVaultBalance.toFixed(bank.mintDecimals),
)
const feesEarned = toUiDecimals(
bank.collectedFeesNative,
bank.mintDecimals
bank.mintDecimals,
)
return (
<Disclosure key={bank.name}>

View File

@ -39,7 +39,7 @@ const TokenStatsCharts = () => {
a +
c.bank.uiPrice *
toUiDecimals(c.bank.collectedFeesNative, c.bank.mintDecimals),
0
0,
),
]
}

View File

@ -81,7 +81,7 @@ const RoutesModal = ({
{
jupiterTokens.find(
(item) =>
item?.address === r?.outputMint?.toString()
item?.address === r?.outputMint?.toString(),
)?.symbol
}
</span>

View File

@ -15,7 +15,6 @@ import mangoStore from '@store/mangoStore'
import ContentBox from '../shared/ContentBox'
import SwapReviewRouteInfo from './SwapReviewRouteInfo'
import TokenSelect from './TokenSelect'
import useDebounce from '../shared/useDebounce'
import { useTranslation } from 'next-i18next'
import SwapFormTokenList from './SwapFormTokenList'
import { Transition } from '@headlessui/react'
@ -43,7 +42,6 @@ import TokenVaultWarnings from '@components/shared/TokenVaultWarnings'
import MaxSwapAmount from './MaxSwapAmount'
import PercentageSelectButtons from './PercentageSelectButtons'
import useIpAddress from 'hooks/useIpAddress'
import { useEnhancedWallet } from '@components/wallet/EnhancedWalletProvider'
import SwapSettings from './SwapSettings'
import InlineNotification from '@components/shared/InlineNotification'
import useUnownedAccount from 'hooks/useUnownedAccount'
@ -86,27 +84,25 @@ const SwapForm = () => {
amountOut: amountOutFormValue,
swapMode,
} = mangoStore((s) => s.swap)
const [debouncedAmountIn] = useDebounce(amountInFormValue, 300)
const [debouncedAmountOut] = useDebounce(amountOutFormValue, 300)
const { mangoAccount } = useMangoAccount()
const { connected, publicKey } = useWallet()
const amountInAsDecimal: Decimal | null = useMemo(() => {
return Number(debouncedAmountIn)
? new Decimal(debouncedAmountIn)
return Number(amountInFormValue)
? new Decimal(amountInFormValue)
: new Decimal(0)
}, [debouncedAmountIn])
}, [amountInFormValue])
const amountOutAsDecimal: Decimal | null = useMemo(() => {
return Number(debouncedAmountOut)
? new Decimal(debouncedAmountOut)
return Number(amountOutFormValue)
? new Decimal(amountOutFormValue)
: new Decimal(0)
}, [debouncedAmountOut])
}, [amountOutFormValue])
const { bestRoute, routes } = useQuoteRoutes({
inputMint: inputBank?.mint.toString() || USDC_MINT,
outputMint: outputBank?.mint.toString() || MANGO_MINT,
amount: swapMode === 'ExactIn' ? debouncedAmountIn : debouncedAmountOut,
amount: swapMode === 'ExactIn' ? amountInFormValue : amountOutFormValue,
slippage,
swapMode,
wallet: publicKey?.toBase58(),
@ -125,7 +121,14 @@ const SwapForm = () => {
}
})
},
[]
[],
)
const setAmountFromSlider = useCallback(
(amount: string) => {
setAmountInFormValue(amount, true)
},
[setAmountInFormValue],
)
const setAmountOutFormValue = useCallback((amountOut: string) => {
@ -146,7 +149,7 @@ const SwapForm = () => {
}
setAmountOutFormValue(borrowAmount.toString())
},
[setAmountOutFormValue]
[setAmountOutFormValue],
)
/*
@ -189,7 +192,7 @@ const SwapForm = () => {
}
setAmountInFormValue(e.value)
},
[swapMode, setAmountInFormValue]
[swapMode, setAmountInFormValue],
)
const handleAmountOutChange = useCallback(
@ -202,7 +205,7 @@ const SwapForm = () => {
}
setAmountOutFormValue(e.value)
},
[swapMode, setAmountOutFormValue]
[swapMode, setAmountOutFormValue],
)
const handleTokenInSelect = useCallback((mintAddress: string) => {
@ -227,20 +230,23 @@ const SwapForm = () => {
setShowTokenSelect(undefined)
}, [])
const handleSwitchTokens = useCallback(() => {
if (amountInAsDecimal?.gt(0) && amountOutAsDecimal.gte(0)) {
setAmountInFormValue(amountOutAsDecimal.toString())
}
const inputBank = mangoStore.getState().swap.inputBank
const outputBank = mangoStore.getState().swap.outputBank
set((s) => {
s.swap.inputBank = outputBank
s.swap.outputBank = inputBank
})
setAnimateSwitchArrow(
(prevanimateSwitchArrow) => prevanimateSwitchArrow + 1
)
}, [setAmountInFormValue, amountOutAsDecimal, amountInAsDecimal])
const handleSwitchTokens = useCallback(
(amountIn: Decimal, amountOut: Decimal) => {
if (amountIn?.gt(0) && amountOut.gte(0)) {
setAmountInFormValue(amountOut.toString())
}
const inputBank = mangoStore.getState().swap.inputBank
const outputBank = mangoStore.getState().swap.outputBank
set((s) => {
s.swap.inputBank = outputBank
s.swap.outputBank = inputBank
})
setAnimateSwitchArrow(
(prevanimateSwitchArrow) => prevanimateSwitchArrow + 1,
)
},
[setAmountInFormValue],
)
const maintProjectedHealth = useMemo(() => {
const group = mangoStore.getState().group
@ -266,7 +272,7 @@ const SwapForm = () => {
uiTokenAmount: amountOutAsDecimal.toNumber(),
},
],
HealthType.maint
HealthType.maint,
)
return simulatedHealthRatio > 100
? 100
@ -327,7 +333,7 @@ const SwapForm = () => {
/>
</Transition>
<EnterBottomExitBottom
className="thin-scroll absolute bottom-0 left-0 z-10 h-full w-full overflow-auto bg-th-bkg-1 p-6 pb-0"
className="thin-scroll absolute bottom-0 left-0 z-10 h-full w-full overflow-hidden bg-th-bkg-1 p-6 pb-0"
show={!!showTokenSelect}
>
<SwapFormTokenList
@ -395,7 +401,7 @@ const SwapForm = () => {
<span className="absolute right-3 bottom-1.5 text-xxs text-th-fgd-4">
{inputBank
? formatCurrencyValue(
inputBank.uiPrice * Number(amountInFormValue)
inputBank.uiPrice * Number(amountInFormValue),
)
: ''}
</span>
@ -404,7 +410,9 @@ const SwapForm = () => {
<div className="-mb-2 flex justify-center">
<button
className="rounded-full border border-th-bkg-4 p-1.5 text-th-fgd-3 focus-visible:border-th-fgd-4 md:hover:text-th-active"
onClick={handleSwitchTokens}
onClick={() =>
handleSwitchTokens(amountInAsDecimal, amountOutAsDecimal)
}
>
<ArrowDownIcon
className="h-5 w-5"
@ -426,8 +434,8 @@ const SwapForm = () => {
onClick={() =>
setBorrowAmountOut(
outputTokenBalanceBorrow.toFixed(
outputBank?.mintDecimals || 9
)
outputBank?.mintDecimals || 9,
),
)
}
value={outputTokenBalanceBorrow}
@ -468,7 +476,7 @@ const SwapForm = () => {
<span className="absolute right-3 bottom-1.5 text-xxs text-th-fgd-4">
{outputBank
? formatCurrencyValue(
outputBank.uiPrice * Number(amountOutFormValue)
outputBank.uiPrice * Number(amountOutFormValue),
)
: ''}
</span>
@ -480,13 +488,13 @@ const SwapForm = () => {
<SwapSlider
useMargin={useMargin}
amount={amountInAsDecimal.toNumber()}
onChange={(v) => setAmountInFormValue(v, true)}
onChange={setAmountFromSlider}
step={1 / 10 ** (inputBank?.mintDecimals || 6)}
/>
) : (
<PercentageSelectButtons
amountIn={amountInAsDecimal.toString()}
setAmountIn={(v) => setAmountInFormValue(v, true)}
setAmountIn={setAmountFromSlider}
useMargin={useMargin}
/>
)}
@ -554,6 +562,7 @@ const SwapForm = () => {
className="text-th-fgd-3"
checked={useMargin}
onChange={handleSetMargin}
small
/>
</div>
<div className="flex items-center justify-between">
@ -592,9 +601,8 @@ const SwapFormSubmitButton = ({
useMargin: boolean
}) => {
const { t } = useTranslation('common')
const { connected } = useWallet()
const { connected, connect } = useWallet()
const { amount: tokenMax, amountWithBorrow } = useTokenMax(useMargin)
const { handleConnect } = useEnhancedWallet()
const showInsufficientBalance = useMargin
? amountWithBorrow.lt(amountIn)
@ -607,7 +615,7 @@ const SwapFormSubmitButton = ({
!amountOut ||
!selectedRoute)
const onClick = connected ? () => setShowConfirm(true) : handleConnect
const onClick = connected ? () => setShowConfirm(true) : connect
return (
<>

View File

@ -1,8 +1,8 @@
import { memo, useMemo, useEffect, useRef } from 'react'
import { memo, useMemo, useEffect, useRef, useState, ChangeEvent } from 'react'
import { Token } from '../../types/jupiter'
import mangoStore from '@store/mangoStore'
import { IconButton } from '../shared/Button'
import { XMarkIcon } from '@heroicons/react/20/solid'
import { MagnifyingGlassIcon, XMarkIcon } from '@heroicons/react/20/solid'
import { useTranslation } from 'next-i18next'
import Decimal from 'decimal.js'
import { getTokenInMax } from './useTokenMax'
@ -13,32 +13,33 @@ import { PublicKey } from '@solana/web3.js'
import FormatNumericValue from '@components/shared/FormatNumericValue'
import { formatTokenSymbol } from 'utils/tokens'
import TokenLogo from '@components/shared/TokenLogo'
import Input from '@components/forms/Input'
// const generateSearchTerm = (item: Token, searchValue: string) => {
// const normalizedSearchValue = searchValue.toLowerCase()
// const values = `${item.symbol} ${item.name}`.toLowerCase()
const generateSearchTerm = (item: Token, searchValue: string) => {
const normalizedSearchValue = searchValue.toLowerCase()
const values = `${item.symbol} ${item.name}`.toLowerCase()
// const isMatchingWithSymbol =
// item.symbol.toLowerCase().indexOf(normalizedSearchValue) >= 0
// const matchingSymbolPercent = isMatchingWithSymbol
// ? normalizedSearchValue.length / item.symbol.length
// : 0
const isMatchingWithSymbol =
item.symbol.toLowerCase().indexOf(normalizedSearchValue) >= 0
const matchingSymbolPercent = isMatchingWithSymbol
? normalizedSearchValue.length / item.symbol.length
: 0
// return {
// token: item,
// matchingIdx: values.indexOf(normalizedSearchValue),
// matchingSymbolPercent,
// }
// }
return {
token: item,
matchingIdx: values.indexOf(normalizedSearchValue),
matchingSymbolPercent,
}
}
// const startSearch = (items: Token[], searchValue: string) => {
// return items
// .map((item) => generateSearchTerm(item, searchValue))
// .filter((item) => item.matchingIdx >= 0)
// .sort((i1, i2) => i1.matchingIdx - i2.matchingIdx)
// .sort((i1, i2) => i2.matchingSymbolPercent - i1.matchingSymbolPercent)
// .map((item) => item.token)
// }
const startSearch = (items: Token[], searchValue: string) => {
return items
.map((item) => generateSearchTerm(item, searchValue))
.filter((item) => item.matchingIdx >= 0)
.sort((i1, i2) => i1.matchingIdx - i2.matchingIdx)
.sort((i1, i2) => i2.matchingSymbolPercent - i1.matchingSymbolPercent)
.map((item) => item.token)
}
const TokenItem = ({
token,
@ -114,8 +115,6 @@ const TokenItem = ({
)
}
// const popularTokenSymbols = ['USDC', 'SOL', 'USDT', 'MNGO', 'BTC']
interface TokenInfoWithAmounts extends Token {
amount?: Decimal
amountWithBorrow?: Decimal
@ -132,22 +131,14 @@ const SwapFormTokenList = ({
type: 'input' | 'output' | undefined
useMargin: boolean
}) => {
const { t } = useTranslation(['common', 'swap'])
// const [search, setSearch] = useState('')
const { t } = useTranslation(['common', 'search', 'swap'])
const [search, setSearch] = useState('')
const { mangoTokens } = useJupiterMints()
const inputBank = mangoStore((s) => s.swap.inputBank)
const outputBank = mangoStore((s) => s.swap.outputBank)
const { group } = useMangoGroup()
const { mangoAccount } = useMangoAccount()
const focusRef = useRef<HTMLButtonElement>(null)
// const popularTokens = useMemo(() => {
// return tokens.filter((token) => {
// return !token?.name || !token?.symbol
// ? false
// : popularTokenSymbols.includes(token.symbol)
// })
// }, [tokens])
const focusRef = useRef<HTMLInputElement>(null)
useEffect(() => {
function onEscape(e: KeyboardEvent) {
@ -175,7 +166,7 @@ const SwapFormTokenList = ({
new PublicKey(token.address),
outputBank.mint,
group,
useMargin
useMargin,
)
return { ...token, ...max }
})
@ -183,7 +174,7 @@ const SwapFormTokenList = ({
.sort((a, b) =>
useMargin
? Number(b.amountWithBorrow) - Number(a.amountWithBorrow)
: Number(b.amount) - Number(a.amount)
: Number(b.amount) - Number(a.amount),
)
return filteredSortedTokens
@ -202,12 +193,11 @@ const SwapFormTokenList = ({
}
}, [mangoTokens, inputBank, outputBank, mangoAccount, group, useMargin, type])
// const handleUpdateSearch = (e: ChangeEvent<HTMLInputElement>) => {
// setSearch(e.target.value)
// }
const handleUpdateSearch = (e: ChangeEvent<HTMLInputElement>) => {
setSearch(e.target.value)
}
// const sortedTokens = search ? startSearch(tokenInfos, search) : tokenInfos
const sortedTokens = tokenInfos
const sortedTokens = search ? startSearch(tokenInfos, search) : tokenInfos
useEffect(() => {
if (focusRef?.current) {
@ -228,71 +218,43 @@ const SwapFormTokenList = ({
className="absolute top-2 right-2 text-th-fgd-3 hover:text-th-fgd-2"
onClick={onClose}
hideBg
ref={focusRef}
>
<XMarkIcon className="h-6 w-6" />
</IconButton>
{/* No need for search/popular tokens until we have more tokens */}
{/* <div className="flex items-center text-th-fgd-4">
<div className="relative mb-4">
<Input
className="pl-10"
type="text"
placeholder="Search by token or paste address"
prefix={<MagnifyingGlassIcon className="h-5 w-5" />}
autoFocus
value={search}
onChange={handleUpdateSearch}
ref={focusRef}
/>
<MagnifyingGlassIcon className="absolute left-3 top-3.5 h-5 w-5" />
</div>
{popularTokens.length ? (
<div className="mt-4 flex flex-wrap">
{popularTokens.map((token) => {
let logoURI
if (mangoTokens.length) {
logoURI = mangoTokens.find(
(t) => t.address === token.address
)!.logoURI
}
const disabled =
(type === 'input' && token.symbol === outputBank?.name) ||
(type === 'output' && token.symbol === inputBank?.name)
return (
<button
className={`${
disabled ? 'opacity-20' : 'hover:border-th-fgd-3'
} mx-1 mb-2 flex items-center rounded-md border border-th-bkg-4 py-1 px-3 focus:border-th-fgd-2`}
onClick={() => onTokenSelect(token.address)}
disabled={disabled}
key={token.address}
>
{logoURI ? (
<Image alt="" width="16" height="16" src={logoURI} />
) : (
<QuestionMarkCircleIcon className="h-5 w-5 text-th-fgd-3" />
)}
<span className="ml-1.5 text-th-fgd-1">{token.symbol}</span>
</button>
)
})}
</div>
) : null} */}
{/* <div className="my-2 border-t border-th-bkg-4"></div> */}
<div className="mb-2 flex justify-between rounded bg-th-bkg-2 p-2">
<div className="flex justify-between rounded bg-th-bkg-2 p-2">
<p className="text-xs text-th-fgd-4">{t('token')}</p>
{type === 'input' ? (
<p className="text-xs text-th-fgd-4">{t('max')}</p>
) : null}
</div>
<div className="overflow-auto pb-2">
{sortedTokens.map((token) => (
<TokenItem
key={token.address}
token={token}
onSubmit={onTokenSelect}
useMargin={useMargin}
type={type}
/>
))}
<div className="thin-scroll h-[calc(100%-128px)] overflow-auto py-2">
{sortedTokens?.length ? (
sortedTokens.map((token) => (
<TokenItem
key={token.address}
token={token}
onSubmit={onTokenSelect}
useMargin={useMargin}
type={type}
/>
))
) : (
<div className="mt-2 rounded-md border border-th-bkg-3 p-3">
<p className="text-center">{t('search:no-results')}</p>
</div>
)}
</div>
</>
)

View File

@ -36,7 +36,7 @@ const SwapHistoryTable = () => {
const showTableView = width ? width > breakpoints.md : false
const [preferredExplorer] = useLocalStorageState(
PREFERRED_EXPLORER_KEY,
EXPLORERS[0]
EXPLORERS[0],
)
useEffect(() => {
@ -56,7 +56,7 @@ const SwapHistoryTable = () => {
actions.fetchSwapHistory(
mangoAccountAddress,
0,
offset + PAGINATION_PAGE_LENGTH
offset + PAGINATION_PAGE_LENGTH,
)
}, [actions, offset, mangoAccountAddress])
@ -196,7 +196,7 @@ const SwapHistoryTable = () => {
<div className="flex items-center justify-end">
<Tooltip
content={`View on ${t(
`settings:${preferredExplorer.name}`
`settings:${preferredExplorer.name}`,
)}`}
placement="top-end"
>

View File

@ -19,7 +19,7 @@ const SwapPage = () => {
const actions = mangoStore((s) => s.actions)
const [showSwapIntro, setShowSwapIntro] = useLocalStorage(
SHOW_SWAP_INTRO_MODAL,
true
true,
)
useEffect(() => {

View File

@ -59,17 +59,17 @@ type JupiterRouteInfoProps = {
const deserializeJupiterIxAndAlt = async (
connection: Connection,
swapTransaction: string
swapTransaction: string,
): Promise<[TransactionInstruction[], AddressLookupTableAccount[]]> => {
const parsedSwapTransaction = VersionedTransaction.deserialize(
Buffer.from(swapTransaction, 'base64')
Buffer.from(swapTransaction, 'base64'),
)
const message = parsedSwapTransaction.message
// const lookups = message.addressTableLookups
const addressLookupTablesResponses = await Promise.all(
message.addressTableLookups.map((alt) =>
connection.getAddressLookupTable(alt.accountKey)
)
connection.getAddressLookupTable(alt.accountKey),
),
)
const addressLookupTables: AddressLookupTableAccount[] =
addressLookupTablesResponses
@ -87,7 +87,7 @@ const prepareMangoRouterInstructions = async (
selectedRoute: RouteInfo,
inputMint: PublicKey,
outputMint: PublicKey,
userPublicKey: PublicKey
userPublicKey: PublicKey,
): Promise<[TransactionInstruction[], AddressLookupTableAccount[]]> => {
if (!selectedRoute || !selectedRoute.mints || !selectedRoute.instructions) {
return [[], []]
@ -97,8 +97,8 @@ const prepareMangoRouterInstructions = async (
...selectedRoute.mints.filter(
(routeMint) =>
!mintsToFilterOut.find((filterOutMint) =>
filterOutMint.equals(routeMint)
)
filterOutMint.equals(routeMint),
),
),
]
const additionalInstructions = []
@ -106,7 +106,7 @@ const prepareMangoRouterInstructions = async (
const ix = await createAssociatedTokenAccountIdempotentInstruction(
userPublicKey,
userPublicKey,
mint
mint,
)
additionalInstructions.push(ix)
}
@ -117,13 +117,13 @@ const prepareMangoRouterInstructions = async (
return [instructions, []]
}
const fetchJupiterTransaction = async (
export const fetchJupiterTransaction = async (
connection: Connection,
selectedRoute: RouteInfo,
userPublicKey: PublicKey,
slippage: number,
inputMint: PublicKey,
outputMint: PublicKey
outputMint: PublicKey,
): Promise<[TransactionInstruction[], AddressLookupTableAccount[]]> => {
const transactions = await (
await fetch('https://quote-api.jup.ag/v4/swap', {
@ -148,7 +148,7 @@ const fetchJupiterTransaction = async (
const [ixs, alts] = await deserializeJupiterIxAndAlt(
connection,
swapTransaction
swapTransaction,
)
const isSetupIx = (pk: PublicKey): boolean =>
@ -202,14 +202,14 @@ const SwapReviewRouteInfo = ({
const outputBank = mangoStore((s) => s.swap.outputBank)
const [soundSettings] = useLocalStorageState(
SOUND_SETTINGS_KEY,
INITIAL_SOUND_SETTINGS
INITIAL_SOUND_SETTINGS,
)
const focusRef = useRef<HTMLButtonElement>(null)
const amountOut = useMemo(() => {
if (!selectedRoute || !outputTokenInfo) return
return new Decimal(selectedRoute.outAmount.toString()).div(
10 ** outputTokenInfo.decimals
10 ** outputTokenInfo.decimals,
)
}, [selectedRoute, outputTokenInfo])
@ -228,7 +228,7 @@ const SwapReviewRouteInfo = ({
if (inputId && outputId) {
try {
const results = await fetch(
`https://api.coingecko.com/api/v3/simple/price?ids=${inputId},${outputId}&vs_currencies=usd`
`https://api.coingecko.com/api/v3/simple/price?ids=${inputId},${outputId}&vs_currencies=usd`,
)
const json = await results.json()
if (json[inputId]?.usd && json[outputId]?.usd) {
@ -275,7 +275,7 @@ const SwapReviewRouteInfo = ({
selectedRoute,
inputBank.mint,
outputBank.mint,
mangoAccount.owner
mangoAccount.owner,
)
: await fetchJupiterTransaction(
connection,
@ -283,7 +283,7 @@ const SwapReviewRouteInfo = ({
wallet.publicKey,
slippage,
inputBank.mint,
outputBank.mint
outputBank.mint,
)
try {
@ -358,8 +358,8 @@ const SwapReviewRouteInfo = ({
.div(amountOut)
.minus(
new Decimal(coingeckoPrices?.outputCoingeckoPrice).div(
coingeckoPrices?.inputCoingeckoPrice
)
coingeckoPrices?.inputCoingeckoPrice,
),
)
.div(amountIn.div(amountOut))
.mul(100)
@ -540,7 +540,7 @@ const SwapReviewRouteInfo = ({
token: inputTokenInfo?.symbol,
rate: formatNumericValue(
inputBank!.getBorrowRateUi(),
2
2,
),
})
: t('swap:tooltip-borrow-no-balance', {
@ -548,7 +548,7 @@ const SwapReviewRouteInfo = ({
token: inputTokenInfo?.symbol,
rate: formatNumericValue(
inputBank!.getBorrowRateUi(),
2
2,
),
})
}
@ -667,7 +667,7 @@ const SwapReviewRouteInfo = ({
) : (
selectedRoute?.marketInfos.map((info, index) => {
const feeToken = jupiterTokens.find(
(item) => item?.address === info.lpFee?.mint
(item) => item?.address === info.lpFee?.mint,
)
return (
<div className="flex justify-between" key={index}>
@ -690,7 +690,7 @@ const SwapReviewRouteInfo = ({
undefined,
{
maximumSignificantDigits: 2,
}
},
)}
%)
</p>

View File

@ -1,5 +1,4 @@
import useMangoAccount from 'hooks/useMangoAccount'
import { useCallback } from 'react'
import LeverageSlider from '../shared/LeverageSlider'
import { useTokenMax } from './useTokenMax'
@ -17,10 +16,6 @@ const SwapSlider = ({
const { mangoAccount } = useMangoAccount()
const { amount: tokenMax, amountWithBorrow } = useTokenMax(useMargin)
const handleChange = useCallback((x: string) => {
onChange(x)
}, [])
return (
<>
{!mangoAccount ? (
@ -36,7 +31,7 @@ const SwapSlider = ({
leverageMax={
useMargin ? amountWithBorrow.toNumber() : tokenMax.toNumber()
}
onChange={handleChange}
onChange={onChange}
step={step}
/>
)}

Some files were not shown because too many files have changed in this diff Show More