merge main

This commit is contained in:
saml33 2023-01-18 21:56:06 +11:00
commit 7fab02c1ba
73 changed files with 1560 additions and 889 deletions

View File

@ -33,12 +33,14 @@ export const fetchChartData = async (
const found = a.find((price: any) => price.time === c[0])
if (found) {
if (['usd-coin', 'tether'].includes(quoteTokenId)) {
found.price = found.inputPrice / c[4]
found.price = found.p1 / c[4]
found.p2 = c[4]
} else {
found.price = c[4] / found.inputPrice
found.price = c[4] / found.p1
found.p2 = c[4]
}
} else {
a.push({ time: c[0], inputPrice: c[4] })
a.push({ time: c[0], p1: c[4] })
}
return a
}, [])

View File

@ -10,7 +10,6 @@ import {
import Decimal from 'decimal.js'
import { useTranslation } from 'next-i18next'
import Image from 'next/legacy/image'
import { Wallet } from '@project-serum/anchor'
import React, { useCallback, useMemo, useState } from 'react'
import NumberFormat, {
NumberFormatValues,
@ -64,7 +63,7 @@ function BorrowForm({ onSuccess, token }: BorrowFormProps) {
const [sizePercentage, setSizePercentage] = useState('')
const { mangoTokens } = useJupiterMints()
const { mangoAccount } = useMangoAccount()
const { connected, wallet } = useWallet()
const { connected, publicKey } = useWallet()
const { handleConnect } = useEnhancedWallet()
const bank = useMemo(() => {
@ -130,7 +129,7 @@ function BorrowForm({ onSuccess, token }: BorrowFormProps) {
const group = mangoStore.getState().group
const mangoAccount = mangoStore.getState().mangoAccount.current
const actions = mangoStore.getState().actions
if (!mangoAccount || !group) return
if (!mangoAccount || !group || !publicKey) return
setSubmitting(true)
try {
const tx = await client.tokenWithdraw(
@ -146,7 +145,7 @@ function BorrowForm({ onSuccess, token }: BorrowFormProps) {
txid: tx,
})
await actions.reloadMangoAccount()
actions.fetchWalletTokens(wallet!.adapter as unknown as Wallet)
actions.fetchWalletTokens(publicKey)
setSubmitting(false)
onSuccess()
} catch (e: any) {
@ -159,7 +158,7 @@ function BorrowForm({ onSuccess, token }: BorrowFormProps) {
})
setSubmitting(false)
}
}, [bank, inputAmount, onSuccess, wallet])
}, [bank, inputAmount, onSuccess, publicKey])
const banks = useMemo(() => {
if (mangoAccount) {

View File

@ -6,7 +6,6 @@ import {
ExclamationCircleIcon,
LinkIcon,
} from '@heroicons/react/20/solid'
import { Wallet } from '@project-serum/anchor'
import { useWallet } from '@solana/wallet-adapter-react'
import Decimal from 'decimal.js'
import { useTranslation } from 'next-i18next'
@ -121,7 +120,7 @@ function DepositForm({ onSuccess, token }: DepositFormProps) {
return logoURI
}, [bank?.mint, mangoTokens])
const { connected, wallet } = useWallet()
const { connected, publicKey } = useWallet()
const walletTokens = mangoStore((s) => s.wallet.tokens)
const tokenMax = useMemo(() => {
@ -158,7 +157,7 @@ function DepositForm({ onSuccess, token }: DepositFormProps) {
const actions = mangoStore.getState().actions
const mangoAccount = mangoStore.getState().mangoAccount.current
if (!mangoAccount || !group || !bank) return
if (!mangoAccount || !group || !bank || !publicKey) return
setSubmitting(true)
try {
@ -175,7 +174,7 @@ function DepositForm({ onSuccess, token }: DepositFormProps) {
})
await actions.reloadMangoAccount()
actions.fetchWalletTokens(wallet!.adapter as unknown as Wallet)
actions.fetchWalletTokens(publicKey)
setSubmitting(false)
onSuccess()
} catch (e: any) {
@ -188,7 +187,7 @@ function DepositForm({ onSuccess, token }: DepositFormProps) {
console.error('Error depositing:', e)
setSubmitting(false)
}
}, [bank, wallet, inputAmount])
}, [bank, publicKey, inputAmount])
// TODO extract into a shared hook for UserSetup.tsx
const banks = useMemo(() => {

View File

@ -5,7 +5,6 @@ import {
ExclamationCircleIcon,
QuestionMarkCircleIcon,
} from '@heroicons/react/20/solid'
import { Wallet } from '@project-serum/anchor'
import { useWallet } from '@solana/wallet-adapter-react'
import Decimal from 'decimal.js'
import { useTranslation } from 'next-i18next'
@ -74,7 +73,7 @@ function RepayForm({ onSuccess, token }: RepayFormProps) {
return logoURI
}, [bank, mangoTokens])
const { connected, wallet } = useWallet()
const { connected, publicKey } = useWallet()
const walletTokens = mangoStore((s) => s.wallet.tokens)
const walletBalance = useMemo(() => {
@ -119,13 +118,23 @@ function RepayForm({ onSuccess, token }: RepayFormProps) {
const handleDeposit = useCallback(
async (amount: string) => {
//to not leave some dust on account we round amount by this number
//with reduce only set to true we take only what is needed to be
//deposited in need to repay borrow
const mangoAccount = mangoStore.getState().mangoAccount.current
const client = mangoStore.getState().client
const group = mangoStore.getState().group
const actions = mangoStore.getState().actions
const mangoAccount = mangoStore.getState().mangoAccount.current
if (!mangoAccount || !group || !bank || !wallet) return
console.log('inputAmount: ', amount)
if (!mangoAccount || !group || !bank || !publicKey) return
//we don't want to left negative dust in account if someone wants to repay full amount
const actualAmount =
sizePercentage === '100'
? mangoAccount.getTokenBorrowsUi(bank) < parseFloat(amount)
? parseFloat(amount)
: mangoAccount.getTokenBorrowsUi(bank)
: parseFloat(amount)
setSubmitting(true)
try {
@ -133,7 +142,8 @@ function RepayForm({ onSuccess, token }: RepayFormProps) {
group,
mangoAccount,
bank.mint,
parseFloat(amount)
actualAmount,
true
)
notify({
title: 'Transaction confirmed',
@ -142,7 +152,7 @@ function RepayForm({ onSuccess, token }: RepayFormProps) {
})
await actions.reloadMangoAccount()
actions.fetchWalletTokens(wallet.adapter as unknown as Wallet)
actions.fetchWalletTokens(publicKey)
setSubmitting(false)
onSuccess()
} catch (e: any) {
@ -156,7 +166,7 @@ function RepayForm({ onSuccess, token }: RepayFormProps) {
setSubmitting(false)
}
},
[bank, wallet]
[bank, publicKey?.toBase58(), sizePercentage]
)
const banks = useMemo(() => {

View File

@ -221,7 +221,7 @@ const MenuItem = ({
<Link
href={pagePath}
shallow={true}
className={`default-transition flex cursor-pointer px-4 focus:text-th-active focus:outline-none md:hover:text-th-active ${
className={`default-transition flex cursor-pointer pl-4 focus:text-th-active focus:outline-none md:hover:text-th-active ${
active
? 'text-th-active'
: theme === 'Light'
@ -256,7 +256,7 @@ const MenuItem = ({
</Transition>
</div>
{isExternal ? (
<ArrowTopRightOnSquareIcon className="h-4 w-4" />
<ArrowTopRightOnSquareIcon className="mr-4 h-4 w-4" />
) : null}
</div>
</Link>
@ -295,7 +295,7 @@ export const ExpandableMenuItem = ({
return collapsed ? (
<Popover>
<div
className={`relative z-30 ${alignBottom ? '' : 'px-4 py-2'}`}
className={`relative z-30 ${alignBottom ? '' : 'py-2 pl-4'}`}
role="button"
>
<Popover.Button

View File

@ -190,7 +190,7 @@ const TokenList = () => {
<QuestionMarkCircleIcon className="h-6 w-6 text-th-fgd-3" />
)}
</div>
<p className="font-body tracking-wider">{bank.name}</p>
<p className="font-body">{bank.name}</p>
</div>
</Td>
<Td className="text-right">
@ -442,7 +442,7 @@ const MobileTokenListItem = ({ bank }: { bank: Bank }) => {
onClick={() => goToTokenPage(bank)}
>
{t('token:token-details')}
<ChevronRightIcon className="ml-2 h-5 w-5" />
<ChevronRightIcon className="ml-1.5 h-5 w-5" />
</LinkButton>
</div>
</div>
@ -565,9 +565,7 @@ const ActionsMenu = ({
<div className="mr-2 flex flex-shrink-0 items-center">
<Image alt="" width="20" height="20" src={logoURI || ''} />
</div>
<p className="font-body tracking-wider">
{formatTokenSymbol(bank.name)}
</p>
<p className="font-body">{formatTokenSymbol(bank.name)}</p>
</div>
<ActionsLinkButton
mangoAccount={mangoAccount!}

View File

@ -3,6 +3,7 @@ import { useTranslation } from 'next-i18next'
import { useMemo, useState } from 'react'
import mangoStore from '@store/mangoStore'
import dynamic from 'next/dynamic'
import { numberCompacter } from 'utils/numbers'
const DetailedAreaChart = dynamic(
() => import('@components/shared/DetailedAreaChart'),
{ ssr: false }
@ -57,7 +58,7 @@ const AccountChart = ({
loading={loading}
prefix="$"
setDaysToShow={handleDaysToShow}
tickFormat={(x) => `$${x.toFixed(2)}`}
tickFormat={(x) => `$${numberCompacter.format(x)}`}
title={t(chartToShow)}
xKey="time"
yKey={yKey}

View File

@ -39,6 +39,7 @@ import dayjs from 'dayjs'
import { INITIAL_ANIMATION_SETTINGS } from '@components/settings/AnimationSettings'
import { useViewport } from 'hooks/useViewport'
import { breakpoints } from 'utils/theme'
import useMangoGroup from 'hooks/useMangoGroup'
export async function getStaticProps({ locale }: { locale: string }) {
return {
@ -55,7 +56,7 @@ export async function getStaticProps({ locale }: { locale: string }) {
const AccountPage = () => {
const { t } = useTranslation('common')
// const { connected } = useWallet()
const group = mangoStore.getState().group
const { group } = useMangoGroup()
const { mangoAccount, mangoAccountAddress } = useMangoAccount()
const actions = mangoStore.getState().actions
const loadPerformanceData = mangoStore(

View File

@ -170,7 +170,7 @@ const ActivityFilters = ({
<Disclosure>
<div className="relative">
{hasFilters ? (
<div className="absolute right-14 top-3">
<div className="absolute right-14 top-2">
<Tooltip content={t('activity:reset-filters')}>
<IconButton
className={`${loadActivityFeed ? 'animate-spin' : ''}`}
@ -429,7 +429,7 @@ const ActivityDetails = ({
<p className="mb-0.5 text-sm">{t('activity:asset-liquidated')}</p>
<p className="font-mono text-th-fgd-1">
{formatDecimal(asset_amount)}{' '}
<span className="font-body tracking-wider">{asset_symbol}</span>
<span className="font-body">{asset_symbol}</span>
<span className="ml-2 font-body text-th-fgd-3">at</span>{' '}
{formatFixedDecimals(asset_price, true)}
</p>
@ -441,7 +441,7 @@ const ActivityDetails = ({
<p className="mb-0.5 text-sm">{t('activity:asset-returned')}</p>
<p className="font-mono text-th-fgd-1">
{formatDecimal(liab_amount)}{' '}
<span className="font-body tracking-wider">{liab_symbol}</span>
<span className="font-body">{liab_symbol}</span>
<span className="ml-2 font-body text-th-fgd-3">at</span>{' '}
{formatFixedDecimals(liab_price, true)}
</p>

View File

@ -200,7 +200,7 @@ const ActivityFeedTable = ({
</TrHead>
</thead>
<tbody>
{activityFeed.map((activity: any) => {
{activityFeed.map((activity: any, index: number) => {
const { activity_type, block_datetime } = activity
const { signature } = activity.activity_details
const isLiquidation =
@ -211,7 +211,7 @@ const ActivityFeedTable = ({
const fee = getFee(activity)
return (
<TrBody
key={signature}
key={signature + index}
className={`default-transition text-sm hover:bg-th-bkg-2 ${
isLiquidation ? 'cursor-pointer' : ''
}`}
@ -222,7 +222,7 @@ const ActivityFeedTable = ({
}
>
<Td>
<p className="font-body tracking-wider">
<p className="font-body">
{dayjs(block_datetime).format('ddd D MMM')}
</p>
<p className="text-xs text-th-fgd-3">
@ -302,11 +302,11 @@ const ActivityFeedTable = ({
</Table>
) : (
<div>
{activityFeed.map((activity: any) => (
{activityFeed.map((activity: any, index: number) => (
<MobileActivityFeedItem
activity={activity}
getValue={getValue}
key={activity.activity_details.signature}
key={activity?.activity_details?.signature + index}
/>
))}
</div>
@ -322,7 +322,7 @@ const ActivityFeedTable = ({
) : null}
{activityFeed.length &&
activityFeed.length % PAGINATION_PAGE_LENGTH === 0 ? (
<div className="flex justify-center pt-6">
<div className="flex justify-center py-6">
<LinkButton onClick={handleShowMore}>Show More</LinkButton>
</div>
) : null}
@ -473,7 +473,7 @@ const MobileActivityFeedItem = ({
<p className="mb-0.5 text-sm">{t('activity:asset-liquidated')}</p>
<p className="font-mono text-sm text-th-fgd-1">
{formatDecimal(activity.activity_details.asset_amount)}{' '}
<span className="font-body tracking-wider">
<span className="font-body">
{activity.activity_details.asset_symbol}
</span>
<span className="ml-2 font-body text-th-fgd-3">at</span>{' '}
@ -491,7 +491,7 @@ const MobileActivityFeedItem = ({
<p className="mb-0.5 text-sm">{t('activity:asset-returned')}</p>
<p className="font-mono text-sm text-th-fgd-1">
{formatDecimal(activity.activity_details.liab_amount)}{' '}
<span className="font-body tracking-wider">
<span className="font-body">
{activity.activity_details.liab_symbol}
</span>
<span className="ml-2 font-body text-th-fgd-3">at</span>{' '}

View File

@ -27,7 +27,7 @@ const MultiSelectDropdown = ({
>
<div className={`flex items-center justify-between`}>
{selected.length ? (
<span>
<span className="text-left">
{selected
.map((v) => t(v))
.toString()

View File

@ -4,25 +4,53 @@ import mangoStore from '@store/mangoStore'
import { notify } from '../../utils/notifications'
import Button from '../shared/Button'
import { useTranslation } from 'next-i18next'
import { useState } from 'react'
import { useCallback, useEffect, useState } from 'react'
import BounceLoader from '../shared/BounceLoader'
import { MangoAccount } from '@blockworks-foundation/mango-v4'
import { TrashIcon } from '@heroicons/react/20/solid'
import {
MangoAccount,
TokenPosition,
toUiDecimalsForQuote,
} from '@blockworks-foundation/mango-v4'
import {
CheckCircleIcon,
ExclamationCircleIcon,
TrashIcon,
} from '@heroicons/react/20/solid'
import useUnsettledPerpPositions from 'hooks/useUnsettledPerpPositions'
import { getMultipleAccounts } from '@project-serum/anchor/dist/cjs/utils/rpc'
import { formatFixedDecimals } from 'utils/numbers'
const CloseAccountModal = ({ isOpen, onClose }: ModalProps) => {
const { t } = useTranslation('common')
const { t } = useTranslation(['close-account'])
const [loading, setLoading] = useState(false)
const set = mangoStore((s) => s.set)
const openOrders = Object.values(mangoStore((s) => s.mangoAccount.openOrders))
const connection = mangoStore.getState().connection
const hasOpenOrders =
openOrders.length && openOrders.filter((x) => x.length).length > 0
const mangoAccount = mangoStore((s) => s.mangoAccount)
const perpPositions = mangoStore((s) => s.mangoAccount.perpPositions)
const openPerpPositions = Object.values(perpPositions).filter((p) =>
p.basePositionLots.toNumber()
)
const group = mangoStore.getState().group
const unsettledBalances = Object.values(mangoAccount.spotBalances).filter(
(x) => x.unsettled && x.unsettled > 0
)
const unsettledPerpPositions = useUnsettledPerpPositions()
const [hasBorrows, setHasBorrows] = useState(false)
const [hasOpenPositions, setHasOpenPositions] = useState(false)
const [totalAccountSOL, setTotalAccountSOL] = useState(0)
const handleCloseMangoAccount = async () => {
const client = mangoStore.getState().client
const mangoAccount = mangoStore.getState().mangoAccount.current
const mangoAccounts = mangoStore.getState().mangoAccounts
const group = mangoStore.getState().group
if (!mangoAccount || !group) return
setLoading(true)
try {
const tx = await client.closeMangoAccount(group, mangoAccount)
const tx = await client.emptyAndCloseMangoAccount(group, mangoAccount)
if (tx) {
const newMangoAccounts = mangoAccounts.filter(
(ma) => !ma.publicKey.equals(mangoAccount.publicKey)
@ -50,19 +78,126 @@ const CloseAccountModal = ({ isOpen, onClose }: ModalProps) => {
}
}
const fetchTotalAccountSOL = useCallback(async () => {
if (!mangoAccount) {
return
}
const accountKeys = [
mangoAccount.current!.publicKey,
...mangoAccount.openOrderAccounts.map((x) => x.address),
]
const accounts = await getMultipleAccounts(connection, accountKeys)
const lamports =
accounts.reduce((total, account) => {
return total + (account?.account.lamports || 0)
}, 0) * 0.000000001
setTotalAccountSOL(lamports)
}, [mangoAccount])
useEffect(() => {
if (mangoAccount && group) {
if (
mangoAccount.current
?.tokensActive()
.filter((token: TokenPosition) =>
token
.balance(group.getFirstBankByTokenIndex(token.tokenIndex))
.isNeg()
).length
) {
setHasBorrows(true)
}
if (openPerpPositions.length || unsettledPerpPositions.length) {
setHasOpenPositions(true)
}
fetchTotalAccountSOL()
}
}, [mangoAccount, group])
const isDisabled =
hasOpenOrders ||
hasBorrows ||
hasOpenPositions ||
!!unsettledBalances.length
return (
<Modal isOpen={isOpen} onClose={onClose}>
<div className="h-56">
<div className="h-[550px]">
{loading ? (
<BounceLoader loadingMessage={t('closing-account')} />
) : (
<div className="flex h-full flex-col justify-between">
<div className="pb-6">
<div className="space-y-4">
<h2 className="mb-1">{t('close-account')}</h2>
<p>{t('close-account-desc')}</p>
<p>{t('description')}</p>
<p>{t('you-must')}:</p>
<div className="overflow-none space-y-2 rounded-md bg-th-bkg-4 p-2 sm:p-4">
<div className="flex items-center text-th-fgd-2">
{hasBorrows ? (
<ExclamationCircleIcon className="mr-1.5 h-4 w-4 text-th-down" />
) : (
<CheckCircleIcon className="mr-1.5 h-4 w-4 text-th-success"></CheckCircleIcon>
)}
{t('close-all-borrows')}
</div>
<div className="flex items-center text-th-fgd-2">
{hasOpenPositions ? (
<ExclamationCircleIcon className="mr-1.5 h-4 w-4 text-th-down" />
) : (
<CheckCircleIcon className="mr-1.5 h-4 w-4 text-th-success"></CheckCircleIcon>
)}
{t('close-perp-positions')}
</div>
<div className="flex items-center text-th-fgd-2">
{hasOpenOrders ? (
<ExclamationCircleIcon className="mr-1.5 h-4 w-4 text-th-down" />
) : (
<CheckCircleIcon className="mr-1.5 h-4 w-4 text-th-success"></CheckCircleIcon>
)}
{t('close-open-orders')}
</div>
<div className="flex items-center text-th-fgd-2">
{unsettledBalances.length ? (
<ExclamationCircleIcon className="mr-1.5 h-4 w-4 text-th-down" />
) : (
<CheckCircleIcon className="mr-1.5 h-4 w-4 text-th-success"></CheckCircleIcon>
)}
{t('settle-balances')}
</div>
</div>
<p>By closing your account you will:</p>
<div className="overflow-none space-y-2 rounded-md bg-th-bkg-4 p-2 sm:p-4">
<div className="flex items-center text-th-fgd-2">
<CheckCircleIcon className="mr-1.5 h-4 w-4 text-th-success"></CheckCircleIcon>
{t('delete-your-account')}
</div>
<div className="flex items-center text-th-fgd-2">
<CheckCircleIcon className="mr-1.5 h-4 w-4 text-th-success"></CheckCircleIcon>
{t('withdraw-assets-worth', {
value:
mangoAccount && group
? formatFixedDecimals(
toUiDecimalsForQuote(
mangoAccount!.current!.getEquity(group).toNumber()
),
false,
true
)
: 0,
})}
</div>
<div className="flex items-center text-th-fgd-2">
<CheckCircleIcon className="mr-1.5 h-4 w-4 text-th-success"></CheckCircleIcon>
{t('recover-x-sol', {
amount: totalAccountSOL.toFixed(3),
})}
</div>
</div>
</div>
<Button
className="w-full"
disabled={isDisabled}
onClick={handleCloseMangoAccount}
size="large"
>

View File

@ -96,6 +96,12 @@ const MangoAccountsListModal = ({
) : mangoAccounts.length ? (
<div className="thin-scroll mt-4 max-h-[280px] space-y-2 overflow-y-auto">
{mangoAccounts.map((acc) => {
if (
mangoAccount &&
acc.publicKey.equals(mangoAccount.publicKey)
) {
acc = mangoAccount
}
const accountValue = toUiDecimalsForQuote(
Number(acc.getEquity(group!))
).toFixed(2)

View File

@ -7,7 +7,6 @@ import {
FireIcon,
PencilIcon,
} from '@heroicons/react/20/solid'
import { Wallet } from '@project-serum/anchor'
import { useWallet } from '@solana/wallet-adapter-react'
import mangoStore from '@store/mangoStore'
import Decimal from 'decimal.js'
@ -59,7 +58,7 @@ const UserSetupModal = ({
}) => {
const { t } = useTranslation(['common', 'onboarding', 'swap'])
const { group } = useMangoGroup()
const { connected, select, wallet, wallets } = useWallet()
const { connected, select, wallet, wallets, publicKey } = useWallet()
const { mangoAccount } = useMangoAccount()
const mangoAccountLoading = mangoStore((s) => s.mangoAccount.initialLoad)
const [accountName, setAccountName] = useState('')
@ -84,7 +83,7 @@ const UserSetupModal = ({
const client = mangoStore.getState().client
const group = mangoStore.getState().group
const actions = mangoStore.getState().actions
if (!group || !wallet) return
if (!group || !publicKey) return
setLoadingAccount(true)
try {
const tx = await client.createMangoAccount(
@ -96,9 +95,9 @@ const UserSetupModal = ({
8, // perpCount
8 // perpOoCount
)
actions.fetchMangoAccounts(wallet!.adapter as unknown as Wallet)
actions.fetchMangoAccounts(publicKey)
if (tx) {
actions.fetchWalletTokens(wallet!.adapter as unknown as Wallet) // need to update sol balance after account rent
actions.fetchWalletTokens(publicKey) // need to update sol balance after account rent
setShowSetupStep(3)
notify({
title: t('new-account-success'),
@ -116,7 +115,7 @@ const UserSetupModal = ({
} finally {
setLoadingAccount(false)
}
}, [accountName, wallet, t])
}, [accountName, publicKey, t])
const handleDeposit = useCallback(async () => {
const client = mangoStore.getState().client

View File

@ -29,8 +29,14 @@ const SearchPage = () => {
const [searchResults, setSearchResults] = useState([])
const [searchType, setSearchType] = useState('mango-account')
const [showNoResults, setShowNoResults] = useState(false)
const [isAccountSearch, setIsAccountSearch] = useState(true)
const handleSearch = async () => {
if (searchType === 'mango-account' || searchType === 'mango-account-name') {
setIsAccountSearch(true)
} else {
setIsAccountSearch(false)
}
try {
setLoading(true)
const response = await fetch(
@ -51,9 +57,6 @@ const SearchPage = () => {
}
}
const isAccountSearch =
searchType === 'mango-account' || searchType === 'mango-account-name'
return (
<div className="grid grid-cols-12 p-8 pb-20 md:pb-16 lg:p-10">
<div className="col-span-12 lg:col-span-8 lg:col-start-3">
@ -140,7 +143,7 @@ const MangoAccountItem = ({
type,
}: {
item: MangoAccountItem
type: 'mango-account' | 'mango-account-name'
type: string
}) => {
const { mango_account_name, mango_account_pk, profile_name } = item
return (

View File

@ -1,6 +1,6 @@
import { Bank, Serum3Market } from '@blockworks-foundation/mango-v4'
import useJupiterMints from 'hooks/useJupiterMints'
import { QuestionMarkCircleIcon } from '@heroicons/react/20/solid'
import { NoSymbolIcon, QuestionMarkCircleIcon } from '@heroicons/react/20/solid'
import mangoStore from '@store/mangoStore'
import useMangoAccount from 'hooks/useMangoAccount'
import { useViewport } from 'hooks/useViewport'
@ -33,24 +33,24 @@ const BalancesTable = () => {
const showTableView = width ? width > breakpoints.md : false
const banks = useMemo(() => {
if (!group) return []
if (!group || !mangoAccount) return []
const rawBanks = Array.from(group?.banksMapByName, ([key, value]) => ({
key,
value,
balance: mangoAccount?.getTokenBalanceUi(value[0]),
balance: mangoAccount.getTokenBalanceUi(value[0]),
}))
const sortedBanks = mangoAccount
? rawBanks
.sort(
(a, b) =>
Math.abs(b.balance! * b.value[0].uiPrice) -
Math.abs(a.balance! * a.value[0].uiPrice)
Math.abs(b.balance * b.value[0].uiPrice) -
Math.abs(a.balance * a.value[0].uiPrice)
)
.filter((c) => {
return (
Math.abs(
floorToDecimal(c.balance!, c.value[0].mintDecimals).toNumber()
floorToDecimal(c.balance, c.value[0].mintDecimals).toNumber()
) > 0 ||
spotBalances[c.value[0].mint.toString()]?.unsettled > 0 ||
spotBalances[c.value[0].mint.toString()]?.inOrders > 0
@ -59,7 +59,7 @@ const BalancesTable = () => {
: rawBanks
return sortedBanks
}, [group, mangoAccount])
}, [group, mangoAccount, spotBalances])
return banks?.length ? (
showTableView ? (
@ -212,6 +212,7 @@ const BalancesTable = () => {
)
) : (
<div className="flex flex-col items-center p-8">
<NoSymbolIcon className="mb-2 h-6 w-6 text-th-fgd-4" />
<p>{t('trade:no-balances')}</p>
</div>
)
@ -320,7 +321,9 @@ const Balance = ({ bank }: { bank: Bank }) => {
{asPath.includes('/trade') && isBaseOrQuote ? (
<LinkButton
className="font-normal underline-offset-4"
onClick={() => handleTradeFormBalanceClick(balance, isBaseOrQuote)}
onClick={() =>
handleTradeFormBalanceClick(Math.abs(balance), isBaseOrQuote)
}
>
{formatDecimal(balance, bank.mintDecimals)}
</LinkButton>

View File

@ -121,7 +121,11 @@ const DetailedAreaChart: FunctionComponent<DetailedAreaChartProps> = ({
<div className="flex items-start justify-between">
<div className="flex flex-col md:flex-row md:items-start md:space-x-6">
{hideChart ? (
<IconButton className="mb-6" onClick={hideChart}>
<IconButton
className="mb-6"
onClick={hideChart}
size="medium"
>
<ArrowLeftIcon className="h-5 w-5" />
</IconButton>
) : null}

View File

@ -9,16 +9,19 @@ type SideBadgeProps = {
const SideBadge: FunctionComponent<SideBadgeProps> = ({ side }) => {
const { t } = useTranslation('common')
const isBid =
typeof side === 'string' ? ['buy', 'long'].includes(side) : 'bid' in side
return (
<div
className={`inline-block rounded uppercase ${
side === 'buy' || side === 'long' || side === 0
isBid
? 'text-th-up md:border md:border-th-up'
: 'text-th-down md:border md:border-th-down'
}
uppercase md:-my-0.5 md:px-1.5 md:py-0.5 md:text-xs`}
>
{typeof side === 'string' ? t(side) : side === 0 ? 'Buy' : 'Sell'}
{typeof side === 'string' ? t(side) : 'bid' in side ? 'Long' : 'Short'}
</div>
)
}

View File

@ -1,30 +1,15 @@
import SheenLoader from '@components/shared/SheenLoader'
import mangoStore, { TokenStatsItem } from '@store/mangoStore'
import useMangoGroup from 'hooks/useMangoGroup'
import { useTranslation } from 'next-i18next'
import dynamic from 'next/dynamic'
import { useMemo } from 'react'
import dayjs from 'dayjs'
import mangoStore from '@store/mangoStore'
import TotalDepositBorrowCharts from './TotalDepositBorrowCharts'
// import { useTranslation } from 'next-i18next'
// import { PerpMarket } from '@blockworks-foundation/mango-v4'
const DetailedAreaChart = dynamic(
() => import('@components/shared/DetailedAreaChart'),
{ ssr: false }
)
interface TotalValueItem {
date: string
borrowValue: number
depositValue: number
}
const MangoStats = () => {
const { t } = useTranslation(['common', 'token', 'trade'])
// const { t } = useTranslation(['common', 'token', 'trade'])
const tokenStats = mangoStore((s) => s.tokenStats.data)
const loadingStats = mangoStore((s) => s.tokenStats.loading)
// const perpStats = mangoStore((s) => s.perpStats.data)
// const loadingPerpStats = mangoStore((s) => s.perpStats.loading)
// const perpMarkets = mangoStore((s) => s.perpMarkets)
const { group } = useMangoGroup()
// const totalFeeValues = useMemo(() => {
// if (!perpStats.length) return []
@ -71,111 +56,12 @@ const MangoStats = () => {
// }, 0)
// }, [perpMarkets])
const totalDepositBorrowValues = useMemo(() => {
if (!tokenStats) return []
const values: TotalValueItem[] = tokenStats.reduce(
(a: TotalValueItem[], c: TokenStatsItem) => {
const hasDate = a.find((d: TotalValueItem) => d.date === c.date_hour)
if (!hasDate) {
a.push({
date: c.date_hour,
depositValue: Math.floor(c.total_deposits * c.price),
borrowValue: Math.floor(c.total_borrows * c.price),
})
} else {
hasDate.depositValue =
hasDate.depositValue + Math.floor(c.total_deposits * c.price)
hasDate.borrowValue =
hasDate.borrowValue + Math.floor(c.total_borrows * c.price)
}
return a
},
[]
)
return values.reverse()
}, [tokenStats])
const banks = useMemo(() => {
if (group) {
const rawBanks = Array.from(group?.banksMapByName, ([key, value]) => ({
key,
value,
}))
return rawBanks
}
return []
}, [group])
const [currentTotalDepositValue, currentTotalBorrowValue] = useMemo(() => {
if (banks.length) {
return [
banks.reduce(
(a, c) => a + c.value[0].uiPrice * c.value[0].uiDeposits(),
0
),
banks.reduce(
(a, c) => a + c.value[0].uiPrice * c.value[0].uiBorrows(),
0
),
]
}
return [0, 0]
}, [banks])
return (
<div className="grid grid-cols-2">
{loadingStats ? (
<div className="col-span-2 border-b border-th-bkg-3 py-4 px-6 md:col-span-1">
<SheenLoader className="flex flex-1">
<div className="h-96 w-full rounded-lg bg-th-bkg-2" />
</SheenLoader>
</div>
) : totalDepositBorrowValues.length ? (
<div className="col-span-2 border-b border-th-bkg-3 py-4 px-6 md:col-span-1">
<DetailedAreaChart
data={totalDepositBorrowValues.concat([
{
date: dayjs().toISOString(),
depositValue: Math.floor(currentTotalDepositValue),
borrowValue: Math.floor(currentTotalBorrowValue),
},
])}
daysToShow={'999'}
heightClass="h-64"
prefix="$"
tickFormat={(x) => `$${x.toFixed(2)}`}
title={t('total-deposit-value')}
xKey="date"
yKey={'depositValue'}
/>
</div>
) : null}
{loadingStats ? (
<div className="col-span-2 border-b border-th-bkg-3 py-4 px-6 md:col-span-1 md:border-l md:pl-6">
<SheenLoader className="flex flex-1">
<div className="h-96 w-full rounded-lg bg-th-bkg-2" />
</SheenLoader>
</div>
) : totalDepositBorrowValues.length ? (
<div className="col-span-2 border-b border-th-bkg-3 py-4 px-6 md:col-span-1 md:border-l md:pl-6">
<DetailedAreaChart
data={totalDepositBorrowValues.concat([
{
date: dayjs().toISOString(),
borrowValue: Math.floor(currentTotalBorrowValue),
depositValue: Math.floor(currentTotalDepositValue),
},
])}
daysToShow={'999'}
heightClass="h-64"
prefix="$"
tickFormat={(x) => `$${x.toFixed(2)}`}
title={t('total-borrow-value')}
xKey="date"
yKey={'borrowValue'}
/>
</div>
) : null}
<TotalDepositBorrowCharts
tokenStats={tokenStats}
loadingStats={loadingStats}
/>
{/* uncomment below when perps launch */}
{/* {loadingPerpStats ? (

View File

@ -85,7 +85,7 @@ const PerpMarketsTable = ({
<Td>
<div className="flex items-center">
<MarketLogos market={market} />
<p className="font-body tracking-wider">{market.name}</p>
<p className="font-body">{market.name}</p>
</div>
</Td>
<Td>

View File

@ -67,7 +67,7 @@ const SpotMarketsTable = () => {
<Td>
<div className="flex items-center">
<MarketLogos market={market} />
<p className="font-body tracking-wider">{market.name}</p>
<p className="font-body">{market.name}</p>
</div>
</Td>
<Td>

View File

@ -125,7 +125,7 @@ const TokenStats = () => {
<QuestionMarkCircleIcon className="h-6 w-6 text-th-fgd-3" />
)}
</div>
<p className="font-body tracking-wider">{bank.name}</p>
<p className="font-body">{bank.name}</p>
</div>
</Td>
<Td>

View File

@ -0,0 +1,170 @@
import SheenLoader from '@components/shared/SheenLoader'
import { TokenStatsItem } from '@store/mangoStore'
import useMangoGroup from 'hooks/useMangoGroup'
import { useTranslation } from 'next-i18next'
import dynamic from 'next/dynamic'
import { useMemo, useState } from 'react'
import dayjs from 'dayjs'
import { numberCompacter } from 'utils/numbers'
const DetailedAreaChart = dynamic(
() => import('@components/shared/DetailedAreaChart'),
{ ssr: false }
)
interface TotalValueItem {
date: string
borrowValue: number
depositValue: number
}
const TotalDepositBorrowCharts = ({
tokenStats,
loadingStats,
}: {
tokenStats: TokenStatsItem[] | null
loadingStats: boolean
}) => {
const { t } = useTranslation(['common', 'token', 'trade'])
const [borrowDaysToShow, setBorrowDaysToShow] = useState('30')
const [depositDaysToShow, setDepositDaysToShow] = useState('30')
const { group } = useMangoGroup()
const totalDepositBorrowValues = useMemo(() => {
if (!tokenStats) return []
const values: TotalValueItem[] = tokenStats.reduce(
(a: TotalValueItem[], c: TokenStatsItem) => {
const hasDate = a.find((d: TotalValueItem) => d.date === c.date_hour)
if (!hasDate) {
a.push({
date: c.date_hour,
depositValue: Math.floor(c.total_deposits * c.price),
borrowValue: Math.floor(c.total_borrows * c.price),
})
} else {
hasDate.depositValue =
hasDate.depositValue + Math.floor(c.total_deposits * c.price)
hasDate.borrowValue =
hasDate.borrowValue + Math.floor(c.total_borrows * c.price)
}
return a
},
[]
)
return values.reverse()
}, [tokenStats])
const filteredBorrowValues = useMemo(() => {
if (!totalDepositBorrowValues) return []
if (borrowDaysToShow !== '30') {
const seconds = Number(borrowDaysToShow) * 86400
const data = totalDepositBorrowValues.filter((d) => {
const dataTime = new Date(d.date).getTime() / 1000
const now = new Date().getTime() / 1000
const limit = now - seconds
return dataTime >= limit
})
return data
}
return totalDepositBorrowValues
}, [totalDepositBorrowValues, borrowDaysToShow])
const filteredDepositValues = useMemo(() => {
if (!totalDepositBorrowValues) return []
if (depositDaysToShow !== '30') {
const seconds = Number(depositDaysToShow) * 86400
const data = totalDepositBorrowValues.filter((d) => {
const dataTime = new Date(d.date).getTime() / 1000
const now = new Date().getTime() / 1000
const limit = now - seconds
return dataTime >= limit
})
return data
}
return totalDepositBorrowValues
}, [totalDepositBorrowValues, depositDaysToShow])
const banks = useMemo(() => {
if (group) {
const rawBanks = Array.from(group?.banksMapByName, ([key, value]) => ({
key,
value,
}))
return rawBanks
}
return []
}, [group])
const [currentTotalDepositValue, currentTotalBorrowValue] = useMemo(() => {
if (banks.length) {
return [
banks.reduce(
(a, c) => a + c.value[0].uiPrice * c.value[0].uiDeposits(),
0
),
banks.reduce(
(a, c) => a + c.value[0].uiPrice * c.value[0].uiBorrows(),
0
),
]
}
return [0, 0]
}, [banks])
return loadingStats ? (
<>
<div className="col-span-2 border-b border-th-bkg-3 py-4 px-6 md:col-span-1">
<SheenLoader className="flex flex-1">
<div className="h-96 w-full rounded-lg bg-th-bkg-2" />
</SheenLoader>
</div>
<div className="col-span-2 border-b border-th-bkg-3 py-4 px-6 md:col-span-1 md:border-l md:pl-6">
<SheenLoader className="flex flex-1">
<div className="h-96 w-full rounded-lg bg-th-bkg-2" />
</SheenLoader>
</div>
</>
) : totalDepositBorrowValues.length ? (
<>
<div className="col-span-2 border-b border-th-bkg-3 py-4 px-6 md:col-span-1">
<DetailedAreaChart
data={filteredDepositValues.concat([
{
date: dayjs().toISOString(),
depositValue: Math.floor(currentTotalDepositValue),
borrowValue: Math.floor(currentTotalBorrowValue),
},
])}
daysToShow={depositDaysToShow}
setDaysToShow={setDepositDaysToShow}
heightClass="h-64"
prefix="$"
tickFormat={(x) => `$${numberCompacter.format(x)}`}
title={t('total-deposit-value')}
xKey="date"
yKey={'depositValue'}
/>
</div>
<div className="col-span-2 border-b border-th-bkg-3 py-4 px-6 md:col-span-1 md:border-l md:pl-6">
<DetailedAreaChart
data={filteredBorrowValues.concat([
{
date: dayjs().toISOString(),
borrowValue: Math.floor(currentTotalBorrowValue),
depositValue: Math.floor(currentTotalDepositValue),
},
])}
daysToShow={borrowDaysToShow}
setDaysToShow={setBorrowDaysToShow}
heightClass="h-64"
prefix="$"
tickFormat={(x) => `$${numberCompacter.format(x)}`}
title={t('total-borrow-value')}
xKey="date"
yKey={'borrowValue'}
/>
</div>
</>
) : null
}
export default TotalDepositBorrowCharts

View File

@ -23,7 +23,7 @@ import { Transition } from '@headlessui/react'
import Button, { IconButton } from '../shared/Button'
import Loading from '../shared/Loading'
import { EnterBottomExitBottom } from '../shared/Transitions'
import useJupiterRoutes from './useJupiterRoutes'
import useQuoteRoutes from './useQuoteRoutes'
import SheenLoader from '../shared/SheenLoader'
import { HealthType } from '@blockworks-foundation/mango-v4'
import {
@ -80,7 +80,7 @@ const SwapForm = () => {
const [debouncedAmountIn] = useDebounce(amountInFormValue, 300)
const [debouncedAmountOut] = useDebounce(amountOutFormValue, 300)
const { mangoAccount } = useMangoAccount()
const { connected } = useWallet()
const { connected, publicKey } = useWallet()
const amountInAsDecimal: Decimal | null = useMemo(() => {
return Number(debouncedAmountIn)
@ -94,12 +94,13 @@ const SwapForm = () => {
: new Decimal(0)
}, [debouncedAmountOut])
const { bestRoute, routes } = useJupiterRoutes({
const { bestRoute, routes } = useQuoteRoutes({
inputMint: inputBank?.mint.toString() || USDC_MINT,
outputMint: outputBank?.mint.toString() || MANGO_MINT,
amount: swapMode === 'ExactIn' ? debouncedAmountIn : debouncedAmountOut,
slippage,
swapMode,
wallet: publicKey?.toBase58(),
})
const setAmountInFormValue = useCallback(

View File

@ -42,6 +42,7 @@ import Tooltip from '@components/shared/Tooltip'
import { Disclosure } from '@headlessui/react'
import RoutesModal from './RoutesModal'
import useMangoAccount from 'hooks/useMangoAccount'
import { createAssociatedTokenAccountIdempotentInstruction } from '@blockworks-foundation/mango-v4'
type JupiterRouteInfoProps = {
amountIn: Decimal
@ -78,6 +79,40 @@ const deserializeJupiterIxAndAlt = async (
return [decompiledMessage.instructions, addressLookupTables]
}
const prepareMangoRouterInstructions = async (
selectedRoute: RouteInfo,
inputMint: PublicKey,
outputMint: PublicKey,
userPublicKey: PublicKey
): Promise<[TransactionInstruction[], AddressLookupTableAccount[]]> => {
if (!selectedRoute || !selectedRoute.mints || !selectedRoute.instructions) {
return [[], []]
}
const mintsToFilterOut = [inputMint, outputMint]
const filteredOutMints = [
...selectedRoute.mints.filter(
(routeMint) =>
!mintsToFilterOut.find((filterOutMint) =>
filterOutMint.equals(routeMint)
)
),
]
const additionalInstructions = []
for (const mint of filteredOutMints) {
const ix = await createAssociatedTokenAccountIdempotentInstruction(
userPublicKey,
userPublicKey,
mint
)
additionalInstructions.push(ix)
}
const instructions = [
...additionalInstructions,
...selectedRoute.instructions,
]
return [instructions, []]
}
const fetchJupiterTransaction = async (
connection: Connection,
selectedRoute: RouteInfo,
@ -217,14 +252,22 @@ const SwapReviewRouteInfo = ({
if (!mangoAccount || !group || !inputBank || !outputBank) return
setSubmitting(true)
const [ixs, alts] = await fetchJupiterTransaction(
connection,
selectedRoute,
mangoAccount.owner,
slippage,
inputBank.mint,
outputBank.mint
)
const [ixs, alts] =
selectedRoute.routerName === 'Mango'
? await prepareMangoRouterInstructions(
selectedRoute,
inputBank.mint,
outputBank.mint,
mangoAccount.owner
)
: await fetchJupiterTransaction(
connection,
selectedRoute,
mangoAccount.owner,
slippage,
inputBank.mint,
outputBank.mint
)
try {
const tx = await client.marginTrade({
@ -346,22 +389,22 @@ const SwapReviewRouteInfo = ({
{swapRate ? (
<>
1{' '}
<span className="font-body tracking-wider">
{inputTokenInfo?.name} {' '}
<span className="font-body text-th-fgd-3">
{inputTokenInfo?.symbol} {' '}
</span>
{formatFixedDecimals(amountOut.div(amountIn).toNumber())}{' '}
<span className="font-body tracking-wider">
<span className="font-body text-th-fgd-3">
{outputTokenInfo?.symbol}
</span>
</>
) : (
<>
1{' '}
<span className="font-body tracking-wider">
<span className="font-body text-th-fgd-3">
{outputTokenInfo?.symbol} {' '}
</span>
{formatFixedDecimals(amountIn.div(amountOut).toNumber())}{' '}
<span className="font-body tracking-wider">
<span className="font-body text-th-fgd-3">
{inputTokenInfo?.symbol}
</span>
</>
@ -397,20 +440,42 @@ const SwapReviewRouteInfo = ({
<p className="text-sm text-th-fgd-3">
{t('swap:minimum-received')}
</p>
{outputTokenInfo?.decimals &&
selectedRoute?.otherAmountThreshold ? (
{outputTokenInfo?.decimals && selectedRoute ? (
<p className="text-right font-mono text-sm text-th-fgd-2">
{formatDecimal(
selectedRoute.otherAmountThreshold /
10 ** outputTokenInfo.decimals || 1,
outputTokenInfo.decimals
)}{' '}
<span className="font-body tracking-wider">
{selectedRoute.swapMode === 'ExactIn'
? formatDecimal(
selectedRoute.otherAmountThreshold /
10 ** outputTokenInfo.decimals || 1,
outputTokenInfo.decimals
)
: formatDecimal(
selectedRoute.outAmount /
10 ** outputTokenInfo.decimals || 1,
outputTokenInfo.decimals
)}{' '}
<span className="font-body text-th-fgd-3">
{outputTokenInfo?.symbol}
</span>
</p>
) : null}
</div>
{selectedRoute?.swapMode === 'ExactOut' ? (
<div className="flex justify-between">
<p className="text-sm text-th-fgd-3">{t('swap:maximum-cost')}</p>
{inputTokenInfo?.decimals && selectedRoute ? (
<p className="text-right font-mono text-sm text-th-fgd-2">
{formatDecimal(
selectedRoute.otherAmountThreshold /
10 ** inputTokenInfo.decimals || 1,
inputTokenInfo.decimals
)}{' '}
<span className="font-body text-th-fgd-3">
{inputTokenInfo?.symbol}
</span>
</p>
) : null}
</div>
) : null}
<div className="flex justify-between">
<p className="text-sm text-th-fgd-3">{t('swap:price-impact')}</p>
<p className="text-right font-mono text-sm text-th-fgd-2">
@ -448,9 +513,7 @@ const SwapReviewRouteInfo = ({
</Tooltip>
<p className="text-right font-mono text-sm text-th-fgd-2">
~{formatFixedDecimals(borrowAmount)}{' '}
<span className="font-body tracking-wider">
{inputTokenInfo?.symbol}
</span>
<span className="font-body">{inputTokenInfo?.symbol}</span>
</p>
</div>
) : null}
@ -533,10 +596,7 @@ const SwapReviewRouteInfo = ({
.mul(inputBank!.loanOriginationFeeRate.toFixed())
.toNumber()
)}{' '}
<span className="font-body tracking-wider">
{inputBank!.name}
</span>{' '}
(
<span className="font-body">{inputBank!.name}</span> (
{formatFixedDecimals(
inputBank!.loanOriginationFeeRate.toNumber() * 100
)}
@ -571,7 +631,7 @@ const SwapReviewRouteInfo = ({
info.lpFee?.amount /
Math.pow(10, feeToken.decimals)
).toFixed(6)}{' '}
<span className="font-body tracking-wider">
<span className="font-body">
{feeToken?.symbol}
</span>{' '}
(

View File

@ -28,10 +28,17 @@ import useLocalStorageState from 'hooks/useLocalStorageState'
import { ANIMATION_SETTINGS_KEY } from 'utils/constants'
import { INITIAL_ANIMATION_SETTINGS } from '@components/settings/AnimationSettings'
import { useTranslation } from 'next-i18next'
import { NoSymbolIcon } from '@heroicons/react/20/solid'
import { ArrowsRightLeftIcon, NoSymbolIcon } from '@heroicons/react/20/solid'
dayjs.extend(relativeTime)
interface ChartDataItem {
p1: number
p2: number
price: number
time: number
}
const CustomizedLabel = ({
chartData,
x,
@ -79,12 +86,14 @@ const CustomizedLabel = ({
const SwapTokenChart = () => {
const { t } = useTranslation('common')
const { inputBank, outputBank } = mangoStore((s) => s.swap)
const inputBank = mangoStore((s) => s.swap.inputBank)
const outputBank = mangoStore((s) => s.swap.outputBank)
const { inputCoingeckoId, outputCoingeckoId } = useJupiterSwapData()
const [baseTokenId, setBaseTokenId] = useState(inputCoingeckoId)
const [quoteTokenId, setQuoteTokenId] = useState(outputCoingeckoId)
const [mouseData, setMouseData] = useState<any>(null)
const [daysToShow, setDaysToShow] = useState('1')
const [flipPrices, setFlipPrices] = useState(false)
const { theme } = useTheme()
const [animationSettings] = useLocalStorageState(
ANIMATION_SETTINGS_KEY,
@ -100,7 +109,18 @@ const SwapTokenChart = () => {
enabled: !!baseTokenId && !!quoteTokenId,
}
)
const chartData = chartDataQuery.data
const chartData = useMemo(() => {
if (!chartDataQuery?.data?.length) return []
if (!flipPrices) {
return chartDataQuery.data
} else {
return chartDataQuery.data.map((d: ChartDataItem) => {
const price = d.p1 / d.p2 === d.price ? d.p2 / d.p1 : d.p1 / d.p2
return { ...d, price: price }
})
}
}, [flipPrices, chartDataQuery])
const handleMouseMove = (coords: any) => {
if (coords.activePayload) {
@ -149,6 +169,19 @@ const SwapTokenChart = () => {
return 0
}
const swapMarketName = useMemo(() => {
if (!inputBank || !outputBank) return ''
const inputSymbol = formatTokenSymbol(inputBank.name?.toUpperCase())
const outputSymbol = formatTokenSymbol(outputBank.name?.toUpperCase())
return ['usd-coin', 'tether'].includes(inputCoingeckoId || '')
? !flipPrices
? `${outputSymbol}/${inputSymbol}`
: `${inputSymbol}/${outputSymbol}`
: !flipPrices
? `${inputSymbol}/${outputSymbol}`
: `${outputSymbol}/${inputSymbol}`
}, [flipPrices, inputBank, inputCoingeckoId, outputBank])
return (
<ContentBox hideBorder hidePadding className="h-full px-6 py-3">
{chartDataQuery?.isLoading || chartDataQuery.isFetching ? (
@ -169,23 +202,13 @@ const SwapTokenChart = () => {
<div>
{inputBank && outputBank ? (
<div className="mb-0.5 flex items-center">
<p className="text-base text-th-fgd-3">
{['usd-coin', 'tether'].includes(inputCoingeckoId || '')
? `${formatTokenSymbol(
outputBank?.name?.toUpperCase()
)}/${inputBank?.name?.toUpperCase()}`
: `${formatTokenSymbol(
inputBank?.name?.toUpperCase()
)}/${formatTokenSymbol(
outputBank?.name?.toUpperCase()
)}`}
</p>
{/* <div
<p className="text-base text-th-fgd-3">{swapMarketName}</p>
<div
className="px-2 hover:cursor-pointer hover:text-th-active"
onClick={handleFlipChart}
onClick={() => setFlipPrices(!flipPrices)}
>
<SwitchHorizontalIcon className="h-4 w-4" />
</div> */}
<ArrowsRightLeftIcon className="h-4 w-4" />
</div>
</div>
) : null}
{mouseData ? (

View File

@ -1,95 +0,0 @@
import { useQuery } from '@tanstack/react-query'
import Decimal from 'decimal.js'
import { RouteInfo } from 'types/jupiter'
import useJupiterSwapData from './useJupiterSwapData'
type useJupiterPropTypes = {
inputMint: string
outputMint: string
amount: string
slippage: number
swapMode: string
}
const fetchJupiterRoutes = async (
inputMint = 'So11111111111111111111111111111111111111112',
outputMint = 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v',
amount = 0,
slippage = 50,
swapMode = 'ExactIn',
feeBps = 0
) => {
{
const paramsString = new URLSearchParams({
inputMint: inputMint.toString(),
outputMint: outputMint.toString(),
amount: amount.toString(),
slippageBps: Math.ceil(slippage * 100).toString(),
feeBps: feeBps.toString(),
swapMode,
}).toString()
const response = await fetch(
`https://quote-api.jup.ag/v4/quote?${paramsString}`
)
const res = await response.json()
const data = res.data
return {
routes: res.data,
bestRoute: data[0],
}
}
}
const useJupiterRoutes = ({
inputMint,
outputMint,
amount,
slippage,
swapMode,
}: useJupiterPropTypes) => {
const { inputTokenInfo, outputTokenInfo } = useJupiterSwapData()
const decimals =
swapMode === 'ExactIn'
? inputTokenInfo?.decimals || 6
: outputTokenInfo?.decimals || 6
const nativeAmount =
amount && !Number.isNaN(+amount)
? new Decimal(amount).mul(10 ** decimals)
: new Decimal(0)
const res = useQuery<{ routes: RouteInfo[]; bestRoute: RouteInfo }, Error>(
['swap-routes', inputMint, outputMint, amount, slippage, swapMode],
async () =>
fetchJupiterRoutes(
inputMint,
outputMint,
nativeAmount.toNumber(),
slippage,
swapMode
),
{
enabled: amount ? true : false,
}
)
return amount
? {
...(res.data ?? {
routes: [],
bestRoute: undefined,
}),
isLoading: res.isLoading,
}
: {
routes: [],
bestRoute: undefined,
isLoading: false,
}
}
export default useJupiterRoutes

View File

@ -0,0 +1,207 @@
import { PublicKey } from '@solana/web3.js'
import { useQuery } from '@tanstack/react-query'
import Decimal from 'decimal.js'
import { RouteInfo } from 'types/jupiter'
import { MANGO_ROUTER_API_URL } from 'utils/constants'
import useJupiterSwapData from './useJupiterSwapData'
type useQuoteRoutesPropTypes = {
inputMint: string
outputMint: string
amount: string
slippage: number
swapMode: string
wallet: string | undefined | null
}
const fetchJupiterRoutes = async (
inputMint = 'So11111111111111111111111111111111111111112',
outputMint = 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v',
amount = 0,
slippage = 50,
swapMode = 'ExactIn',
feeBps = 0
) => {
{
const paramsString = new URLSearchParams({
inputMint: inputMint.toString(),
outputMint: outputMint.toString(),
amount: amount.toString(),
slippageBps: Math.ceil(slippage * 100).toString(),
feeBps: feeBps.toString(),
swapMode,
}).toString()
const response = await fetch(
`https://quote-api.jup.ag/v4/quote?${paramsString}`
)
const res = await response.json()
const data = res.data
return {
routes: res.data as RouteInfo[],
bestRoute: (data.length ? data[0] : null) as RouteInfo | null,
}
}
}
const fetchMangoRoutes = async (
inputMint = 'So11111111111111111111111111111111111111112',
outputMint = 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v',
amount = 0,
slippage = 50,
swapMode = 'ExactIn',
feeBps = 0,
wallet = PublicKey.default.toBase58()
) => {
{
const defaultOtherAmount =
swapMode === 'ExactIn' ? 0 : Number.MAX_SAFE_INTEGER
const paramsString = new URLSearchParams({
inputMint: inputMint.toString(),
outputMint: outputMint.toString(),
amount: amount.toString(),
slippage: ((slippage * 1) / 100).toString(),
feeBps: feeBps.toString(),
mode: swapMode,
wallet: wallet,
otherAmountThreshold: defaultOtherAmount.toString(),
}).toString()
const response = await fetch(`${MANGO_ROUTER_API_URL}/swap?${paramsString}`)
const res = await response.json()
const data: RouteInfo[] = res.map((route: any) => ({
...route,
priceImpactPct: route.priceImpact,
slippageBps: slippage,
marketInfos: route.marketInfos.map((mInfo: any) => ({
...mInfo,
lpFee: {
...mInfo.fee,
pct: mInfo.fee.rate,
},
})),
mints: route.mints.map((x: string) => new PublicKey(x)),
instructions: route.instructions.map((ix: any) => ({
...ix,
programId: new PublicKey(ix.programId),
data: Buffer.from(ix.data, 'base64'),
keys: ix.keys.map((key: any) => ({
...key,
pubkey: new PublicKey(key.pubkey),
})),
})),
routerName: 'Mango',
}))
return {
routes: data,
bestRoute: (data.length ? data[0] : null) as RouteInfo | null,
}
}
}
const handleGetRoutes = async (
inputMint = 'So11111111111111111111111111111111111111112',
outputMint = 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v',
amount = 0,
slippage = 50,
swapMode = 'ExactIn',
feeBps = 0,
wallet: string | undefined | null
) => {
wallet ||= PublicKey.default.toBase58()
const results = await Promise.allSettled([
fetchMangoRoutes(
inputMint,
outputMint,
amount,
slippage,
swapMode,
feeBps,
wallet
),
fetchJupiterRoutes(
inputMint,
outputMint,
amount,
slippage,
swapMode,
feeBps
),
])
const responses = results
.filter((x) => x.status === 'fulfilled' && x.value.bestRoute !== null)
.map((x) => (x as any).value)
const sortedByBiggestOutAmount = (
responses as {
routes: RouteInfo[]
bestRoute: RouteInfo
}[]
).sort(
(a, b) => Number(b.bestRoute.outAmount) - Number(a.bestRoute.outAmount)
)
return {
routes: sortedByBiggestOutAmount[0].routes,
bestRoute: sortedByBiggestOutAmount[0].bestRoute,
}
}
const useQuoteRoutes = ({
inputMint,
outputMint,
amount,
slippage,
swapMode,
wallet,
}: useQuoteRoutesPropTypes) => {
const { inputTokenInfo, outputTokenInfo } = useJupiterSwapData()
const decimals =
swapMode === 'ExactIn'
? inputTokenInfo?.decimals || 6
: outputTokenInfo?.decimals || 6
const nativeAmount =
amount && !Number.isNaN(+amount)
? new Decimal(amount).mul(10 ** decimals)
: new Decimal(0)
const res = useQuery<{ routes: RouteInfo[]; bestRoute: RouteInfo }, Error>(
['swap-routes', inputMint, outputMint, amount, slippage, swapMode, wallet],
async () =>
handleGetRoutes(
inputMint,
outputMint,
nativeAmount.toNumber(),
slippage,
swapMode,
0,
wallet
),
{
enabled: amount ? true : false,
}
)
return amount
? {
...(res.data ?? {
routes: [],
bestRoute: undefined,
}),
isLoading: res.isLoading,
}
: {
routes: [],
bestRoute: undefined,
isLoading: false,
}
}
export default useQuoteRoutes

View File

@ -1,35 +1,34 @@
import { PerpMarket } from '@blockworks-foundation/mango-v4'
import Change from '@components/shared/Change'
import { useCoingecko } from 'hooks/useCoingecko'
import useOraclePrice from 'hooks/useOraclePrice'
import useSelectedMarket from 'hooks/useSelectedMarket'
import { useTranslation } from 'next-i18next'
import { useMemo } from 'react'
import { getDecimalCount } from 'utils/numbers'
import MarketSelectDropdown from './MarketSelectDropdown'
import PerpFundingRate from './PerpFundingRate'
const AdvancedMarketHeader = () => {
const { t } = useTranslation(['common', 'trade'])
const { selectedMarket } = useSelectedMarket()
const { serumOrPerpMarket, baseSymbol, price } = useSelectedMarket()
const { data: tokenPrices } = useCoingecko()
const oraclePrice = useOraclePrice()
const baseSymbol = useMemo(() => {
return selectedMarket?.name.split('/')[0]
}, [selectedMarket])
const coingeckoData = useMemo(() => {
return tokenPrices.find((asset) =>
baseSymbol === 'soETH'
? asset.symbol === 'ETH'
: asset.symbol === baseSymbol
)
}, [baseSymbol, tokenPrices])
const coingeckoData = tokenPrices.find((asset) =>
baseSymbol === 'soETH'
? asset.symbol === 'ETH'
: asset.symbol === baseSymbol
)
const change = coingeckoData
? ((coingeckoData.prices[coingeckoData.prices.length - 1][1] -
coingeckoData.prices[0][1]) /
coingeckoData.prices[0][1]) *
100
: 0
const change = useMemo(() => {
return coingeckoData
? ((coingeckoData.prices[coingeckoData.prices.length - 1][1] -
coingeckoData.prices[0][1]) /
coingeckoData.prices[0][1]) *
100
: 0
}, [coingeckoData])
return (
<div className="flex flex-col bg-th-bkg-1 md:h-12 md:flex-row md:items-center">
@ -40,8 +39,10 @@ const AdvancedMarketHeader = () => {
<div id="trade-step-two" className="flex-col md:ml-6">
<div className="text-xs text-th-fgd-4">{t('trade:oracle-price')}</div>
<div className="font-mono text-xs text-th-fgd-2">
{oraclePrice ? (
`$${oraclePrice}`
{price ? (
`$${price.toFixed(
getDecimalCount(serumOrPerpMarket?.tickSize || 0.01)
)}`
) : (
<span className="text-th-fgd-4"></span>
)}
@ -51,7 +52,7 @@ const AdvancedMarketHeader = () => {
<div className="text-xs text-th-fgd-4">{t('rolling-change')}</div>
<Change change={change} size="small" suffix="%" />
</div>
{selectedMarket instanceof PerpMarket ? (
{serumOrPerpMarket instanceof PerpMarket ? (
<div className="ml-6 flex-col">
<div className="text-xs text-th-fgd-4">
{t('trade:funding-rate')}

View File

@ -13,7 +13,7 @@ import Tooltip from '@components/shared/Tooltip'
import mangoStore from '@store/mangoStore'
import Decimal from 'decimal.js'
import { useTranslation } from 'next-i18next'
import { useCallback, useEffect, useMemo, useState } from 'react'
import { ChangeEvent, useCallback, useEffect, useMemo, useState } from 'react'
import NumberFormat, {
NumberFormatValues,
SourceInfo,
@ -31,7 +31,6 @@ import { SIZE_INPUT_UI_KEY, SOUND_SETTINGS_KEY } from 'utils/constants'
import SpotButtonGroup from './SpotButtonGroup'
import PerpButtonGroup from './PerpButtonGroup'
import SolBalanceWarnings from '@components/shared/SolBalanceWarnings'
import useJupiterMints from 'hooks/useJupiterMints'
import useSelectedMarket from 'hooks/useSelectedMarket'
import { getDecimalCount } from 'utils/numbers'
import LogoWithFallback from '@components/shared/LogoWithFallback'
@ -56,8 +55,6 @@ const AdvancedTradeForm = () => {
const { t } = useTranslation(['common', 'trade'])
const { mangoAccount } = useMangoAccount()
const tradeForm = mangoStore((s) => s.tradeForm)
const { mangoTokens } = useJupiterMints()
const { selectedMarket, price: oraclePrice } = useSelectedMarket()
const [useMargin, setUseMargin] = useState(true)
const [placingOrder, setPlacingOrder] = useState(false)
const [tradeFormSizeUi] = useLocalStorageState(SIZE_INPUT_UI_KEY, 'Slider')
@ -68,44 +65,14 @@ const AdvancedTradeForm = () => {
)
const { connected } = useWallet()
const { handleConnect } = useEnhancedWallet()
const baseSymbol = useMemo(() => {
return selectedMarket?.name.split(/-|\//)[0]
}, [selectedMarket])
const baseLogoURI = useMemo(() => {
if (!baseSymbol || !mangoTokens.length) return ''
const token =
mangoTokens.find((t) => t.symbol === baseSymbol) ||
mangoTokens.find((t) => t.symbol?.includes(baseSymbol))
if (token) {
return token.logoURI
}
return ''
}, [baseSymbol, mangoTokens])
const quoteBank = useMemo(() => {
const group = mangoStore.getState().group
if (!group || !selectedMarket) return
const tokenIdx =
selectedMarket instanceof Serum3Market
? selectedMarket.quoteTokenIndex
: selectedMarket?.settleTokenIndex
return group?.getFirstBankByTokenIndex(tokenIdx)
}, [selectedMarket])
const quoteSymbol = useMemo(() => {
return quoteBank?.name
}, [quoteBank])
const quoteLogoURI = useMemo(() => {
if (!quoteSymbol || !mangoTokens.length) return ''
const token = mangoTokens.find((t) => t.symbol === quoteSymbol)
if (token) {
return token.logoURI
}
return ''
}, [quoteSymbol, mangoTokens])
const {
selectedMarket,
price: oraclePrice,
baseLogoURI,
baseSymbol,
quoteLogoURI,
quoteSymbol,
} = useSelectedMarket()
const setTradeType = useCallback((tradeType: 'Limit' | 'Market') => {
set((s) => {
@ -186,12 +153,28 @@ const AdvancedTradeForm = () => {
})
}, [])
const handleReduceOnlyChange = useCallback((reduceOnly: boolean) => {
set((s) => {
s.tradeForm.reduceOnly = reduceOnly
})
}, [])
const handleSetSide = useCallback((side: 'buy' | 'sell') => {
set((s) => {
s.tradeForm.side = side
})
}, [])
const handleSetMargin = useCallback((e: ChangeEvent<HTMLInputElement>) => {
if (!e.target.checked) {
set((s) => {
s.tradeForm.quoteSize = ''
s.tradeForm.baseSize = ''
})
}
setUseMargin(e.target.checked)
}, [])
const [tickDecimals, tickSize] = useMemo(() => {
const group = mangoStore.getState().group
if (!group || !selectedMarket) return [1, 0.1]
@ -319,6 +302,7 @@ const AdvancedTradeForm = () => {
undefined, // maxQuoteQuantity
Date.now(),
perpOrderType,
selectedMarket.reduceOnly || tradeForm.reduceOnly,
undefined,
undefined
)
@ -417,6 +401,7 @@ const AdvancedTradeForm = () => {
<MaxSizeButton
minOrderDecimals={minOrderDecimals}
tickDecimals={tickDecimals}
useMargin={useMargin}
/>
<div className="flex flex-col">
<div className="default-transition flex items-center rounded-md rounded-b-none border border-th-input-border bg-th-input-bkg p-2 text-sm font-bold text-th-fgd-1 md:hover:z-10 md:hover:border-th-input-border-hover lg:text-base">
@ -485,11 +470,13 @@ const AdvancedTradeForm = () => {
minOrderDecimals={minOrderDecimals}
tickDecimals={tickDecimals}
step={tradeForm.side === 'buy' ? tickSize : minOrderSize}
useMargin={useMargin}
/>
) : (
<SpotButtonGroup
minOrderDecimals={minOrderDecimals}
tickDecimals={tickDecimals}
useMargin={useMargin}
/>
)
) : tradeFormSizeUi === 'slider' ? (
@ -548,15 +535,32 @@ const AdvancedTradeForm = () => {
placement="left"
content={t('trade:tooltip-enable-margin')}
>
<Checkbox
checked={useMargin}
onChange={(e) => setUseMargin(e.target.checked)}
>
<Checkbox checked={useMargin} onChange={handleSetMargin}>
{t('trade:margin')}
</Checkbox>
</Tooltip>
</div>
) : null}
) : (
<div className="mr-3 mt-4">
<Tooltip
className="hidden md:block"
delay={250}
placement="left"
content={
'Reduce will only decrease the size of an open position. This is often used for closing a position.'
}
>
<div className="flex items-center text-xs text-th-fgd-3">
<Checkbox
checked={tradeForm.reduceOnly}
onChange={(e) => handleReduceOnlyChange(e.target.checked)}
>
Reduce Only
</Checkbox>
</div>
</Tooltip>
</div>
)}
</div>
<div className="mt-6 mb-4 flex px-3 md:px-4">
{ipAllowed ? (

View File

@ -10,8 +10,8 @@ import { useMemo, useState } from 'react'
import { DEFAULT_MARKET_NAME } from 'utils/constants'
import MarketLogos from './MarketLogos'
const TAB_VALUES =
process.env.NEXT_PUBLIC_SHOW_PERPS === 'true' ? ['spot', 'perp'] : ['spot']
const isTesting = process.env.NEXT_PUBLIC_SHOW_PERPS === 'true'
const TAB_VALUES = isTesting ? ['spot', 'perp'] : ['spot']
const MarketSelectDropdown = () => {
const { selectedMarket } = useSelectedMarket()
@ -114,36 +114,38 @@ const MarketSelectDropdown = () => {
) : null}
{activeTab === 'perp'
? perpMarkets?.length
? perpMarkets.map((m) => {
return (
<div
className="flex items-center justify-between py-2 px-4"
key={m.publicKey.toString()}
>
<Link
href={{
pathname: '/trade',
query: { name: m.name },
}}
shallow={true}
? perpMarkets
.filter((m) => m.name !== 'MNGO-PERP' || isTesting)
.map((m) => {
return (
<div
className="flex items-center justify-between py-2 px-4"
key={m.publicKey.toString()}
>
<div className="default-transition flex items-center hover:cursor-pointer hover:bg-th-bkg-2">
<MarketLogos market={m} />
<span
className={
m.name === selectedMarket?.name
? 'text-th-active'
: ''
}
>
{m.name}
</span>
</div>
</Link>
<FavoriteMarketButton market={m} />
</div>
)
})
<Link
href={{
pathname: '/trade',
query: { name: m.name },
}}
shallow={true}
>
<div className="default-transition flex items-center hover:cursor-pointer hover:bg-th-bkg-2">
<MarketLogos market={m} />
<span
className={
m.name === selectedMarket?.name
? 'text-th-active'
: ''
}
>
{m.name}
</span>
</div>
</Link>
<FavoriteMarketButton market={m} />
</div>
)
})
: null
: null}
</Popover.Panel>

View File

@ -1,4 +1,4 @@
import { Serum3Market } from '@blockworks-foundation/mango-v4'
import { PerpMarket, Serum3Market } from '@blockworks-foundation/mango-v4'
import MaxAmountButton from '@components/shared/MaxAmountButton'
import { FadeInFadeOut } from '@components/shared/Transitions'
import mangoStore from '@store/mangoStore'
@ -7,37 +7,33 @@ import useSelectedMarket from 'hooks/useSelectedMarket'
import { useTranslation } from 'next-i18next'
import { useCallback, useMemo } from 'react'
import { floorToDecimal } from 'utils/numbers'
import { useSpotMarketMax } from './SpotSlider'
const MaxSizeButton = ({
minOrderDecimals,
tickDecimals,
useMargin,
}: {
minOrderDecimals: number
tickDecimals: number
useMargin: boolean
}) => {
const { t } = useTranslation(['common', 'trade'])
const { mangoAccount } = useMangoAccount()
const { selectedMarket, price: oraclePrice } = useSelectedMarket()
const { price, side, tradeType } = mangoStore((s) => s.tradeForm)
const spotMax = useSpotMarketMax(
mangoAccount,
selectedMarket,
side,
useMargin
)
const leverageMax = useMemo(() => {
const perpMax = useMemo(() => {
const group = mangoStore.getState().group
if (!mangoAccount || !group || !selectedMarket) return 0
try {
if (selectedMarket instanceof Serum3Market) {
if (side === 'buy') {
return mangoAccount.getMaxQuoteForSerum3BidUi(
group,
selectedMarket.serumMarketExternal
)
} else {
return mangoAccount.getMaxBaseForSerum3AskUi(
group,
selectedMarket.serumMarketExternal
)
}
} else {
if (selectedMarket instanceof PerpMarket) {
try {
if (side === 'buy') {
return mangoAccount.getMaxQuoteForPerpBidUi(
group,
@ -49,60 +45,101 @@ const MaxSizeButton = ({
selectedMarket.perpMarketIndex
)
}
} catch (e) {
console.error('Error calculating max leverage: spot btn group: ', e)
return 0
}
} catch (e) {
console.error('Error calculating max leverage: spot btn group: ', e)
return 0
}
}, [mangoAccount, side, selectedMarket])
// const leverageMax = useMemo(() => {
// const group = mangoStore.getState().group
// if (!mangoAccount || !group || !selectedMarket) return 0
// try {
// if (selectedMarket instanceof Serum3Market) {
// if (side === 'buy') {
// return mangoAccount.getMaxQuoteForSerum3BidUi(
// group,
// selectedMarket.serumMarketExternal
// )
// } else {
// return mangoAccount.getMaxBaseForSerum3AskUi(
// group,
// selectedMarket.serumMarketExternal
// )
// }
// } else {
// if (side === 'buy') {
// return mangoAccount.getMaxQuoteForPerpBidUi(
// group,
// selectedMarket.perpMarketIndex
// )
// } else {
// return mangoAccount.getMaxBaseForPerpAskUi(
// group,
// selectedMarket.perpMarketIndex
// )
// }
// }
// } catch (e) {
// console.error('Error calculating max leverage: spot btn group: ', e)
// return 0
// }
// }, [mangoAccount, side, selectedMarket])
const handleMax = useCallback(() => {
const max = selectedMarket instanceof Serum3Market ? spotMax : perpMax || 0
const set = mangoStore.getState().set
set((state) => {
if (side === 'buy') {
state.tradeForm.quoteSize = floorToDecimal(
leverageMax,
tickDecimals
).toFixed()
state.tradeForm.quoteSize = floorToDecimal(max, tickDecimals).toFixed()
if (tradeType === 'Market' || !price) {
state.tradeForm.baseSize = floorToDecimal(
leverageMax / oraclePrice,
max / oraclePrice,
minOrderDecimals
).toFixed()
} else {
state.tradeForm.baseSize = floorToDecimal(
leverageMax / parseFloat(price),
max / parseFloat(price),
minOrderDecimals
).toFixed()
}
} else {
state.tradeForm.baseSize = floorToDecimal(
leverageMax,
tickDecimals
).toFixed()
state.tradeForm.baseSize = floorToDecimal(max, tickDecimals).toFixed()
if (tradeType === 'Market' || !price) {
state.tradeForm.quoteSize = floorToDecimal(
leverageMax * oraclePrice,
max * oraclePrice,
minOrderDecimals
).toFixed()
} else {
state.tradeForm.quoteSize = floorToDecimal(
leverageMax * parseFloat(price),
max * parseFloat(price),
minOrderDecimals
).toFixed()
}
}
})
}, [leverageMax, price, side, tradeType])
}, [perpMax, spotMax, price, side, tradeType, selectedMarket])
const maxAmount = useMemo(() => {
const max = selectedMarket instanceof Serum3Market ? spotMax : perpMax || 0
const tradePrice = tradeType === 'Market' ? oraclePrice : Number(price)
if (side === 'buy') {
return floorToDecimal(leverageMax / tradePrice, tickDecimals).toFixed()
return floorToDecimal(max / tradePrice, tickDecimals).toFixed()
} else {
return floorToDecimal(leverageMax, minOrderDecimals).toFixed()
return floorToDecimal(max, minOrderDecimals).toFixed()
}
}, [leverageMax, minOrderDecimals, tickDecimals, price, side, tradeType])
}, [
perpMax,
spotMax,
selectedMarket,
minOrderDecimals,
tickDecimals,
price,
side,
tradeType,
])
return (
<div className="mb-2 mt-3 flex items-center justify-between">

View File

@ -182,7 +182,12 @@ const depth = 40
const Orderbook = () => {
const { t } = useTranslation(['common', 'trade'])
const { selectedMarket, serumOrPerpMarket: market } = useSelectedMarket()
const {
selectedMarket,
serumOrPerpMarket: market,
baseSymbol,
quoteSymbol,
} = useSelectedMarket()
const [isScrolled, setIsScrolled] = useState(false)
const [orderbookData, setOrderbookData] = useState<any | null>(null)
@ -199,7 +204,7 @@ const Orderbook = () => {
const isMobile = width ? width < breakpoints.md : false
const depthArray = useMemo(() => {
const bookDepth = !isMobile ? depth : 7
const bookDepth = !isMobile ? depth : 9
return Array(bookDepth).fill(0)
}, [isMobile])
@ -483,8 +488,12 @@ const Orderbook = () => {
) : null}
</div>
<div className="grid grid-cols-2 px-4 pt-2 pb-1 text-xxs text-th-fgd-4">
<div className="col-span-1 text-right">{t('trade:size')}</div>
<div className="col-span-1 text-right">{t('price')}</div>
<div className="col-span-1 text-right">
{t('trade:size')} ({baseSymbol})
</div>
<div className="col-span-1 text-right">
{t('price')} ({quoteSymbol})
</div>
</div>
<div
className="hide-scroll relative h-full overflow-y-scroll"
@ -595,7 +604,6 @@ const OrderbookRow = ({
minOrderSize: number
tickSize: number
}) => {
const tradeForm = mangoStore((s) => s.tradeForm)
const element = useRef<HTMLDivElement>(null)
const [animationSettings] = useLocalStorageState(
ANIMATION_SETTINGS_KEY,
@ -632,29 +640,29 @@ const OrderbookRow = ({
const set = mangoStore.getState().set
set((state) => {
state.tradeForm.price = formattedPrice.toFixed()
if (tradeForm.baseSize && tradeForm.tradeType === 'Limit') {
if (state.tradeForm.baseSize && state.tradeForm.tradeType === 'Limit') {
const quoteSize = floorToDecimal(
formattedPrice.mul(new Decimal(tradeForm.baseSize)),
formattedPrice.mul(new Decimal(state.tradeForm.baseSize)),
getDecimalCount(tickSize)
)
state.tradeForm.quoteSize = quoteSize.toFixed()
}
})
}, [formattedPrice, tradeForm])
}, [formattedPrice, tickSize])
const handleSizeClick = useCallback(() => {
const set = mangoStore.getState().set
set((state) => {
state.tradeForm.baseSize = formattedSize.toString()
if (formattedSize && tradeForm.price) {
if (formattedSize && state.tradeForm.price) {
const quoteSize = floorToDecimal(
formattedSize.mul(new Decimal(tradeForm.price)),
formattedSize.mul(new Decimal(state.tradeForm.price)),
getDecimalCount(tickSize)
)
state.tradeForm.quoteSize = quoteSize.toString()
}
})
}, [formattedSize, tradeForm])
}, [formattedSize, tickSize])
const groupingDecimalCount = useMemo(
() => getDecimalCount(grouping),

View File

@ -8,6 +8,7 @@ import useMangoGroup from 'hooks/useMangoGroup'
import useSelectedMarket from 'hooks/useSelectedMarket'
import { useTranslation } from 'next-i18next'
import {
formatDecimal,
formatFixedDecimals,
getDecimalCount,
numberFormat,
@ -66,8 +67,8 @@ const PerpPositions = () => {
<Th className="text-right">{t('trade:size')}</Th>
<Th className="text-right">{t('trade:notional')}</Th>
<Th className="text-right">{t('trade:entry-price')}</Th>
<Th className="text-right">Redeemable PnL</Th>
<Th className="text-right">Realized PnL</Th>
<Th className="text-right">Unsettled PnL</Th>
<Th className="text-right">PnL</Th>
</TrHead>
</thead>
<tbody>
@ -86,7 +87,11 @@ const PerpPositions = () => {
if (!basePosition) return null
const unsettledPnl = position.getEquityUi(group, market)
const unsettledPnl = position.getUnsettledPnlUi(group, market)
const cummulativePnl = position.cumulativePnlOverPositionLifetimeUi(
group,
market
)
return (
<TrBody key={`${position.marketIndex}`} className="my-1 p-2">
@ -96,7 +101,7 @@ const PerpPositions = () => {
<Td className="text-right">
<PerpSideBadge basePosition={basePosition} />
</Td>
<Td className="text-right">
<Td className="text-right font-mono">
<p className="flex justify-end">
{isSelectedMarket ? (
<LinkButton
@ -123,20 +128,15 @@ const PerpPositions = () => {
)}
</div>
</Td>
<Td className={`text-right font-mono`}>
<div>{formatDecimal(unsettledPnl, market.baseDecimals)}</div>
</Td>
<Td
className={`text-right font-mono ${
unsettledPnl > 0 ? 'text-th-up' : 'text-th-down'
cummulativePnl > 0 ? 'text-th-up' : 'text-th-down'
}`}
>
<div>{formatFixedDecimals(unsettledPnl, true)}</div>
</Td>
<Td className="text-right">
<div>
$
{/* {numberFormat.format(
position.perpSpotTransfers.toNumber()
)} */}
</div>
<div>{formatFixedDecimals(cummulativePnl, true)}</div>
</Td>
</TrBody>
)

View File

@ -1,13 +1,15 @@
import SideBadge from '@components/shared/SideBadge'
const PerpSideBadge = ({ basePosition }: { basePosition: number }) => (
<>
{basePosition !== 0 ? (
<SideBadge side={basePosition > 0 ? 'long' : 'short'} />
) : (
'--'
)}
</>
)
const PerpSideBadge = ({ basePosition }: { basePosition: number }) => {
return (
<>
{basePosition !== 0 ? (
<SideBadge side={basePosition > 0 ? 'long' : 'short'} />
) : (
'--'
)}
</>
)
}
export default PerpSideBadge

View File

@ -50,15 +50,12 @@ const RecentTrades = () => {
}
}, [fills, previousFills, soundSettings])
const { selectedMarket, serumOrPerpMarket: market } = useSelectedMarket()
const baseSymbol = useMemo(() => {
return selectedMarket?.name.split(/-|\//)[0]
}, [selectedMarket])
const quoteSymbol = useMemo(() => {
return selectedMarket?.name.split(/-|\//)[1]
}, [selectedMarket])
const {
selectedMarket,
serumOrPerpMarket: market,
baseSymbol,
quoteSymbol,
} = useSelectedMarket()
// const fetchRecentTrades = useCallback(async () => {
// if (!market) return

View File

@ -45,7 +45,7 @@ const Slippage = () => {
: 'text-th-error'
}`}
>
{slippage === 100_000 ? 'Unavailable' : `${slippage.toFixed(2)}&`}
{slippage === 100_000 ? 'Unavailable' : `${slippage.toFixed(2)}%`}
</p>
</div>
) : null

View File

@ -1,51 +1,31 @@
import { Serum3Market } from '@blockworks-foundation/mango-v4'
import ButtonGroup from '@components/forms/ButtonGroup'
import mangoStore from '@store/mangoStore'
import useMangoAccount from 'hooks/useMangoAccount'
import useSelectedMarket from 'hooks/useSelectedMarket'
import { useCallback, useMemo, useState } from 'react'
import { useCallback, useState } from 'react'
import { trimDecimals } from 'utils/numbers'
import { useSpotMarketMax } from './SpotSlider'
const SpotButtonGroup = ({
minOrderDecimals,
tickDecimals,
useMargin,
}: {
minOrderDecimals: number
tickDecimals: number
useMargin: boolean
}) => {
const side = mangoStore((s) => s.tradeForm.side)
const { selectedMarket } = useSelectedMarket()
const { mangoAccount } = useMangoAccount()
const [sizePercentage, setSizePercentage] = useState('')
const leverageMax = useMemo(() => {
const group = mangoStore.getState().group
if (!mangoAccount || !group || !selectedMarket) return 100
if (!(selectedMarket instanceof Serum3Market)) return 100
try {
if (side === 'buy') {
return mangoAccount.getMaxQuoteForSerum3BidUi(
group,
selectedMarket.serumMarketExternal
)
} else {
return mangoAccount.getMaxBaseForSerum3AskUi(
group,
selectedMarket.serumMarketExternal
)
}
} catch (e) {
console.error('Error calculating max leverage: spot btn group: ', e)
return 0
}
}, [side, selectedMarket, mangoAccount])
const max = useSpotMarketMax(mangoAccount, selectedMarket, side, useMargin)
const handleSizePercentage = useCallback(
(percentage: string) => {
const set = mangoStore.getState().set
setSizePercentage(percentage)
const size = leverageMax * (Number(percentage) / 100)
const size = max * (Number(percentage) / 100)
set((s) => {
if (s.tradeForm.side === 'buy') {
@ -75,7 +55,7 @@ const SpotButtonGroup = ({
}
})
},
[side, selectedMarket, mangoAccount, minOrderDecimals, tickDecimals]
[minOrderDecimals, tickDecimals, max]
)
return (

View File

@ -1,47 +1,69 @@
import { Serum3Market } from '@blockworks-foundation/mango-v4'
import { MangoAccount, Serum3Market } from '@blockworks-foundation/mango-v4'
import LeverageSlider from '@components/shared/LeverageSlider'
import mangoStore from '@store/mangoStore'
import useMangoAccount from 'hooks/useMangoAccount'
import useSelectedMarket from 'hooks/useSelectedMarket'
import { useCallback, useMemo } from 'react'
import { GenericMarket } from 'types'
import { trimDecimals } from 'utils/numbers'
export const useSpotMarketMax = (
mangoAccount: MangoAccount | undefined,
selectedMarket: GenericMarket | undefined,
side: string,
useMargin: boolean
) => {
const max = useMemo(() => {
const group = mangoStore.getState().group
if (!mangoAccount || !group || !selectedMarket) return 100
if (!(selectedMarket instanceof Serum3Market)) return 100
let leverageMax = 0
let spotMax = 0
try {
if (side === 'buy') {
leverageMax = mangoAccount.getMaxQuoteForSerum3BidUi(
group,
selectedMarket.serumMarketExternal
)
spotMax = mangoAccount.getTokenBalanceUi(
group.getFirstBankByTokenIndex(selectedMarket.quoteTokenIndex)
)
} else {
leverageMax = mangoAccount.getMaxBaseForSerum3AskUi(
group,
selectedMarket.serumMarketExternal
)
spotMax = mangoAccount.getTokenBalanceUi(
group.getFirstBankByTokenIndex(selectedMarket.baseTokenIndex)
)
}
return useMargin ? leverageMax : Math.max(spotMax, 0)
} catch (e) {
console.error('Error calculating max leverage: spot btn group: ', e)
return 0
}
}, [side, selectedMarket, mangoAccount, useMargin])
return max
}
const SpotSlider = ({
minOrderDecimals,
tickDecimals,
step,
useMargin,
}: {
minOrderDecimals: number
tickDecimals: number
step: number
useMargin: boolean
}) => {
const side = mangoStore((s) => s.tradeForm.side)
const { selectedMarket, price: marketPrice } = useSelectedMarket()
const { mangoAccount } = useMangoAccount()
const tradeForm = mangoStore((s) => s.tradeForm)
const leverageMax = useMemo(() => {
const group = mangoStore.getState().group
if (!mangoAccount || !group || !selectedMarket) return 100
if (!(selectedMarket instanceof Serum3Market)) return 100
try {
if (side === 'buy') {
return mangoAccount.getMaxQuoteForSerum3BidUi(
group,
selectedMarket.serumMarketExternal
)
} else {
return mangoAccount.getMaxBaseForSerum3AskUi(
group,
selectedMarket.serumMarketExternal
)
}
} catch (e) {
console.error('Error calculating max leverage for spot slider: ', e)
return 0
}
}, [side, selectedMarket, mangoAccount])
const max = useSpotMarketMax(mangoAccount, selectedMarket, side, useMargin)
const handleSlide = useCallback(
(val: string) => {
@ -85,7 +107,7 @@ const SpotSlider = ({
? parseFloat(tradeForm.quoteSize)
: parseFloat(tradeForm.baseSize)
}
leverageMax={leverageMax}
leverageMax={max}
onChange={handleSlide}
step={step}
/>

View File

@ -5,15 +5,16 @@ import MarketLogos from './MarketLogos'
const TableMarketName = ({ market }: { market: PerpMarket | Serum3Market }) => {
const { selectedMarket } = useSelectedMarket()
return selectedMarket?.name === market.name ? (
<div className="flex items-center">
<MarketLogos market={market!} />
<MarketLogos market={market} />
<span className="whitespace-nowrap">{market.name}</span>
</div>
) : (
<Link href={`/trade?name=${market.name}`}>
<div className="default-transition flex items-center underline md:hover:text-th-fgd-3 md:hover:no-underline">
<MarketLogos market={market!} />
<MarketLogos market={market} />
<span className="whitespace-nowrap">{market.name}</span>
</div>
</Link>

View File

@ -271,7 +271,7 @@ const TradeHistory = () => {
</div>
)
) : (
<div className="flex flex-col items-center justify-center px-6 pb-8 pt-4">
<div className="flex flex-col items-center p-8">
<NoSymbolIcon className="mb-2 h-6 w-6 text-th-fgd-4" />
<p>No trade history</p>
</div>

View File

@ -204,7 +204,7 @@ const UnsettledTrades = ({
</Td>
<Td className="text-right font-mono">
{formatDecimal(
position.getEquityUi(group, market),
position.getUnsettledPnlUi(group, market),
market.baseDecimals
)}
</Td>

View File

@ -15,7 +15,6 @@ import { useViewport } from 'hooks/useViewport'
import { breakpoints } from '../../utils/theme'
import EditProfileModal from '@components/modals/EditProfileModal'
import MangoAccountsListModal from '@components/modals/MangoAccountsListModal'
import { Wallet as AnchorWallet } from '@project-serum/anchor'
const ConnectedMenu = () => {
const { t } = useTranslation('common')
@ -33,10 +32,10 @@ const ConnectedMenu = () => {
const isMobile = width ? width < breakpoints.md : false
const onConnectFetchAccountData = async (wallet: Wallet) => {
if (!wallet) return
await actions.fetchMangoAccounts(wallet.adapter as unknown as AnchorWallet)
if (!wallet.adapter.publicKey) return
await actions.fetchMangoAccounts(wallet.adapter.publicKey)
actions.fetchTourSettings(wallet.adapter.publicKey?.toString() as string)
actions.fetchWalletTokens(wallet.adapter as unknown as AnchorWallet)
actions.fetchWalletTokens(wallet.adapter.publicKey)
actions.fetchTradeHistory()
}

View File

@ -1,24 +0,0 @@
import { Serum3Market } from '@blockworks-foundation/mango-v4'
import { getDecimalCount } from 'utils/numbers'
import useMangoGroup from './useMangoGroup'
import useSelectedMarket from './useSelectedMarket'
export default function useOraclePrice() {
const { group } = useMangoGroup()
const { selectedMarket } = useSelectedMarket()
if (!group || !selectedMarket) return false
let price
let market
if (selectedMarket instanceof Serum3Market) {
price = group.getFirstBankByTokenIndex(
selectedMarket?.baseTokenIndex
).uiPrice
market = group.getSerum3ExternalMarket(selectedMarket.serumMarketExternal)
} else {
price = selectedMarket.uiPrice
market = selectedMarket
}
return price && market ? price.toFixed(getDecimalCount(market.tickSize)) : ''
}

View File

@ -1,11 +1,13 @@
import { Serum3Market } from '@blockworks-foundation/mango-v4'
import mangoStore from '@store/mangoStore'
import { useMemo } from 'react'
import useJupiterMints from './useJupiterMints'
import useMangoGroup from './useMangoGroup'
export default function useSelectedMarket() {
const { group } = useMangoGroup()
const selectedMarket = mangoStore((s) => s.selectedMarket.current)
const { mangoTokens } = useJupiterMints()
const price: number = useMemo(() => {
if (!group) return 0
@ -31,5 +33,52 @@ export default function useSelectedMarket() {
}
}, [selectedMarket])
return { selectedMarket, price, serumOrPerpMarket }
const baseSymbol = useMemo(() => {
return selectedMarket?.name.split(/-|\//)[0]
}, [selectedMarket])
const baseLogoURI = useMemo(() => {
if (!baseSymbol || !mangoTokens.length) return ''
const token =
mangoTokens.find((t) => t.symbol === baseSymbol) ||
mangoTokens.find((t) => t.symbol?.includes(baseSymbol))
if (token) {
return token.logoURI
}
return ''
}, [baseSymbol, mangoTokens])
const quoteBank = useMemo(() => {
const group = mangoStore.getState().group
if (!group || !selectedMarket) return
const tokenIdx =
selectedMarket instanceof Serum3Market
? selectedMarket.quoteTokenIndex
: selectedMarket?.settleTokenIndex
return group?.getFirstBankByTokenIndex(tokenIdx)
}, [selectedMarket])
const quoteSymbol = useMemo(() => {
return quoteBank?.name
}, [quoteBank])
const quoteLogoURI = useMemo(() => {
if (!quoteSymbol || !mangoTokens.length) return ''
const token = mangoTokens.find((t) => t.symbol === quoteSymbol)
if (token) {
return token.logoURI
}
return ''
}, [quoteSymbol, mangoTokens])
return {
selectedMarket,
price,
serumOrPerpMarket,
baseSymbol,
quoteBank,
quoteSymbol,
baseLogoURI,
quoteLogoURI,
}
}

View File

@ -7,8 +7,8 @@ const useUnsettledPerpPositions = () => {
return perpPositions.filter((p) => {
const market = group?.getPerpMarketByMarketIndex(p.marketIndex)
if (!market) return false
return p.getPnl(market).toNumber() > 0
if (!market || !group) return false
return p.getUnsettledPnlUi(group, market) !== 0
})
}

View File

@ -14,6 +14,7 @@ import {
} from '@heroicons/react/20/solid'
import { Disclosure } from '@headlessui/react'
import MarketLogos from '@components/trade/MarketLogos'
import Button from '@components/shared/Button'
export async function getStaticProps({ locale }: { locale: string }) {
return {
@ -55,248 +56,284 @@ const Dashboard: NextPage = () => {
address={group?.publicKey.toString()}
anchorData
></ExplorerLink>
<div className="mt-4 flex space-x-4">
<Button
secondary
size="small"
onClick={() => {
const panels = [
...document.querySelectorAll(
'[aria-expanded=false][aria-label=panel]'
),
]
panels.map((panel) => (panel as HTMLElement).click())
}}
>
Expand All
</Button>
<Button
secondary
size="small"
onClick={() => {
const panels = [
...document.querySelectorAll(
'[aria-expanded=true][aria-label=panel]'
),
]
panels.map((panel) => (panel as HTMLElement).click())
}}
>
Collpase All
</Button>
</div>
<h3 className="mt-6 mb-3 text-base text-th-fgd-3">Banks</h3>
<div className="border-b border-th-bkg-3">
{Array.from(group.banksMapByMint).map(([mintAddress, banks]) =>
banks.map((bank) => {
const logoUri = mangoTokens.length
? mangoTokens.find((t) => t.address === mintAddress)
?.logoURI
: ''
return (
<Disclosure key={bank.publicKey.toString()}>
{({ open }) => (
<>
<Disclosure.Button
className={`default-transition flex w-full items-center justify-between border-t border-th-bkg-3 p-4 md:hover:bg-th-bkg-2 ${
open ? 'bg-th-bkg-2' : ''
}`}
>
<div className="flex items-center">
{logoUri ? (
<Image
alt=""
width="20"
height="20"
src={logoUri}
/>
) : (
<QuestionMarkCircleIcon className="h-6 w-6 text-th-fgd-3" />
)}
<p className="ml-2 text-th-fgd-2">
{bank.name} Bank
</p>
</div>
<ChevronDownIcon
className={`${
open ? 'rotate-180' : 'rotate-360'
} h-5 w-5 text-th-fgd-3`}
/>
</Disclosure.Button>
<Disclosure.Panel>
<KeyValuePair
label="Mint"
value={<ExplorerLink address={mintAddress} />}
/>
<KeyValuePair
label="Bank"
value={
<ExplorerLink
address={bank.publicKey.toString()}
anchorData
/>
}
/>
<KeyValuePair
label="Vault"
value={
<ExplorerLink
address={bank.vault.toString()}
anchorData
/>
}
/>
<KeyValuePair
label="Oracle"
value={
<ExplorerLink
address={bank.oracle.toString()}
/>
}
/>
<KeyValuePair
label="Token Index"
value={bank.tokenIndex}
/>
<KeyValuePair
label="Oracle Price"
value={`$${bank.uiPrice}`}
/>
<KeyValuePair
label="Stable Price"
value={`$${group.toUiPrice(
I80F48.fromNumber(
bank.stablePriceModel.stablePrice
),
bank.mintDecimals
)}`}
/>
<VaultData bank={bank} />
<KeyValuePair
label="Loan Fee Rate"
value={`${(
10000 * bank.loanFeeRate.toNumber()
).toFixed(2)} bps`}
/>
<KeyValuePair
label="Loan origination fee rate"
value={`${(
10000 * bank.loanOriginationFeeRate.toNumber()
).toFixed(2)} bps`}
/>
<KeyValuePair
label="Collected fees native"
value={bank.collectedFeesNative.toNumber()}
/>
<KeyValuePair
label="Liquidation fee"
value={`${(
10000 * bank.liquidationFee.toNumber()
).toFixed(2)} bps`}
/>
<KeyValuePair
label="Dust"
value={bank.dust.toNumber()}
/>
<KeyValuePair
label="Deposits"
value={toUiDecimals(
bank.indexedDeposits
.mul(bank.depositIndex)
.toNumber(),
bank.mintDecimals
)}
/>
<KeyValuePair
label="Borrows"
value={toUiDecimals(
bank.indexedBorrows
.mul(bank.borrowIndex)
.toNumber(),
bank.mintDecimals
)}
/>
<KeyValuePair
label="Avg Utilization"
value={`${
bank.avgUtilization.toNumber() * 100
}%`}
/>
<KeyValuePair
label="Maint Asset/Liab Weight"
value={`${bank.maintAssetWeight.toFixed(2)}/
{Array.from(group.banksMapByMint)
.sort((a, b) => a[0].localeCompare(b[0]))
.map(([mintAddress, banks]) =>
banks.map((bank) => {
const logoUri = mangoTokens.length
? mangoTokens.find((t) => t.address === mintAddress)
?.logoURI
: ''
return (
<Disclosure key={bank.publicKey.toString()}>
{({ open }) => (
<>
<Disclosure.Button
aria-label="panel"
className={`default-transition flex w-full items-center justify-between border-t border-th-bkg-3 p-4 md:hover:bg-th-bkg-2 ${
open ? 'bg-th-bkg-2' : ''
}`}
>
<div className="flex items-center">
{logoUri ? (
<Image
alt=""
width="20"
height="20"
src={logoUri}
/>
) : (
<QuestionMarkCircleIcon className="h-6 w-6 text-th-fgd-3" />
)}
<p className="ml-2 text-th-fgd-2">
{bank.name} Bank
</p>
</div>
<ChevronDownIcon
className={`${
open ? 'rotate-180' : 'rotate-360'
} h-5 w-5 text-th-fgd-3`}
/>
</Disclosure.Button>
<Disclosure.Panel>
<KeyValuePair
label="Mint"
value={<ExplorerLink address={mintAddress} />}
/>
<KeyValuePair
label="Bank"
value={
<ExplorerLink
address={bank.publicKey.toString()}
anchorData
/>
}
/>
<KeyValuePair
label="Vault"
value={
<ExplorerLink
address={bank.vault.toString()}
anchorData
/>
}
/>
<KeyValuePair
label="Oracle"
value={
<ExplorerLink
address={bank.oracle.toString()}
/>
}
/>
<KeyValuePair
label="Token Index"
value={bank.tokenIndex}
/>
<KeyValuePair
label="Oracle Price"
value={`$${bank.uiPrice}`}
/>
<KeyValuePair
label="Stable Price"
value={`$${group.toUiPrice(
I80F48.fromNumber(
bank.stablePriceModel.stablePrice
),
bank.mintDecimals
)}`}
/>
<VaultData bank={bank} />
<KeyValuePair
label="Loan Fee Rate"
value={`${(
10000 * bank.loanFeeRate.toNumber()
).toFixed(2)} bps`}
/>
<KeyValuePair
label="Loan origination fee rate"
value={`${(
10000 *
bank.loanOriginationFeeRate.toNumber()
).toFixed(2)} bps`}
/>
<KeyValuePair
label="Collected fees native"
value={bank.collectedFeesNative.toNumber()}
/>
<KeyValuePair
label="Liquidation fee"
value={`${(
10000 * bank.liquidationFee.toNumber()
).toFixed(2)} bps`}
/>
<KeyValuePair
label="Dust"
value={bank.dust.toNumber()}
/>
<KeyValuePair
label="Deposits"
value={toUiDecimals(
bank.indexedDeposits
.mul(bank.depositIndex)
.toNumber(),
bank.mintDecimals
)}
/>
<KeyValuePair
label="Borrows"
value={toUiDecimals(
bank.indexedBorrows
.mul(bank.borrowIndex)
.toNumber(),
bank.mintDecimals
)}
/>
<KeyValuePair
label="Avg Utilization"
value={`${
bank.avgUtilization.toNumber() * 100
}%`}
/>
<KeyValuePair
label="Maint Asset/Liab Weight"
value={`${bank.maintAssetWeight.toFixed(2)}/
${bank.maintLiabWeight.toFixed(2)}`}
/>
<KeyValuePair
label="Init Asset/Liab Weight"
value={`${bank.initAssetWeight.toFixed(2)}/
/>
<KeyValuePair
label="Init Asset/Liab Weight"
value={`${bank.initAssetWeight.toFixed(2)}/
${bank.initLiabWeight.toFixed(2)}`}
/>
<KeyValuePair
label="Scaled Init Asset/Liab Weight"
value={`${bank
.scaledInitAssetWeight()
.toFixed(4)}/
/>
<KeyValuePair
label="Scaled Init Asset/Liab Weight"
value={`${bank
.scaledInitAssetWeight()
.toFixed(4)}/
${bank.scaledInitLiabWeight().toFixed(4)}`}
/>
<KeyValuePair
label="Deposit weight scale start quote"
value={bank.depositWeightScaleStartQuote}
/>
<KeyValuePair
label="Borrow weight scale start quote"
value={bank.borrowWeightScaleStartQuote}
/>
<KeyValuePair
label="Rate params"
value={
<span className="text-right">
{`${(100 * bank.rate0.toNumber()).toFixed(
2
)}% @ ${(
100 * bank.util0.toNumber()
).toFixed()}% util, `}
{`${(100 * bank.rate1.toNumber()).toFixed(
2
)}% @ ${(
100 * bank.util1.toNumber()
).toFixed()}% util, `}
{`${(100 * bank.maxRate.toNumber()).toFixed(
2
)}% @ 100% util`}
</span>
}
/>
<KeyValuePair
label="Deposit rate"
value={`${bank.getDepositRateUi()}%`}
/>
<KeyValuePair
label="Borrow rate"
value={`${bank.getBorrowRateUi()}%`}
/>
<KeyValuePair
label="Last index update"
value={new Date(
1000 * bank.indexLastUpdated.toNumber()
).toUTCString()}
/>
<KeyValuePair
label="Last rates updated"
value={new Date(
1000 * bank.bankRateLastUpdated.toNumber()
).toUTCString()}
/>
<KeyValuePair
label="Last stable price updated"
value={new Date(
1000 *
bank.stablePriceModel.lastUpdateTimestamp.toNumber()
).toUTCString()}
/>
<KeyValuePair
label="Stable Price: delay interval"
value={`${bank.stablePriceModel.delayIntervalSeconds}s`}
/>
<KeyValuePair
label="Stable Price: growth limits"
value={`${(
100 * bank.stablePriceModel.delayGrowthLimit
).toFixed(2)}% delay / ${(
100 * bank.stablePriceModel.stableGrowthLimit
).toFixed(2)}% stable`}
/>
<KeyValuePair
label="Oracle: Conf Filter"
value={`${(
100 * bank.oracleConfig.confFilter.toNumber()
).toFixed(2)}%`}
/>
<KeyValuePair
label="Oracle: Max Staleness"
value={`${bank.oracleConfig.maxStalenessSlots} slots`}
/>
<KeyValuePair
label="netBorrowsInWindow / netBorrowLimitPerWindowQuote"
value={`${bank.netBorrowsInWindow.toNumber()} / ${bank.netBorrowLimitPerWindowQuote.toNumber()}`}
/>
</Disclosure.Panel>
</>
)}
</Disclosure>
)
})
)}
/>
<KeyValuePair
label="Deposit weight scale start quote"
value={bank.depositWeightScaleStartQuote}
/>
<KeyValuePair
label="Borrow weight scale start quote"
value={bank.borrowWeightScaleStartQuote}
/>
<KeyValuePair
label="Rate params"
value={
<span className="text-right">
{`${(100 * bank.rate0.toNumber()).toFixed(
2
)}% @ ${(
100 * bank.util0.toNumber()
).toFixed()}% util, `}
{`${(100 * bank.rate1.toNumber()).toFixed(
2
)}% @ ${(
100 * bank.util1.toNumber()
).toFixed()}% util, `}
{`${(
100 * bank.maxRate.toNumber()
).toFixed(2)}% @ 100% util`}
</span>
}
/>
<KeyValuePair
label="Deposit rate"
value={`${bank.getDepositRateUi()}%`}
/>
<KeyValuePair
label="Borrow rate"
value={`${bank.getBorrowRateUi()}%`}
/>
<KeyValuePair
label="Last index update"
value={new Date(
1000 * bank.indexLastUpdated.toNumber()
).toUTCString()}
/>
<KeyValuePair
label="Last rates updated"
value={new Date(
1000 * bank.bankRateLastUpdated.toNumber()
).toUTCString()}
/>
<KeyValuePair
label="Last stable price updated"
value={new Date(
1000 *
bank.stablePriceModel.lastUpdateTimestamp.toNumber()
).toUTCString()}
/>
<KeyValuePair
label="Stable Price: delay interval"
value={`${bank.stablePriceModel.delayIntervalSeconds}s`}
/>
<KeyValuePair
label="Stable Price: growth limits"
value={`${(
100 * bank.stablePriceModel.delayGrowthLimit
).toFixed(2)}% delay / ${(
100 *
bank.stablePriceModel.stableGrowthLimit
).toFixed(2)}% stable`}
/>
<KeyValuePair
label="Oracle: Conf Filter"
value={`${(
100 *
bank.oracleConfig.confFilter.toNumber()
).toFixed(2)}%`}
/>
<KeyValuePair
label="Oracle: Max Staleness"
value={`${bank.oracleConfig.maxStalenessSlots} slots`}
/>
<KeyValuePair
label="netBorrowsInWindow / netBorrowLimitPerWindowQuote"
value={`${bank.netBorrowsInWindow.toNumber()} / ${bank.netBorrowLimitPerWindowQuote.toNumber()}`}
/>
</Disclosure.Panel>
</>
)}
</Disclosure>
)
})
)}
</div>
<h3 className="mt-6 mb-3 text-base text-th-fgd-3">
@ -310,6 +347,7 @@ const Dashboard: NextPage = () => {
{({ open }) => (
<>
<Disclosure.Button
aria-label="panel"
className={`default-transition flex w-full items-center justify-between border-t border-th-bkg-3 p-4 md:hover:bg-th-bkg-2 ${
open ? 'bg-th-bkg-2' : ''
}`}
@ -375,6 +413,10 @@ const Dashboard: NextPage = () => {
label="Perp Market Index"
value={perpMarket.perpMarketIndex}
/>
<KeyValuePair
label="Reduce Only"
value={perpMarket.reduceOnly ? 'True' : 'False'}
/>
<KeyValuePair
label="Oracle Price"
value={`$${perpMarket.uiPrice}`}
@ -399,17 +441,29 @@ const Dashboard: NextPage = () => {
/>
<KeyValuePair
label="Maint Asset/Liab Weight"
value={`${perpMarket.maintAssetWeight.toFixed(
value={`${perpMarket.maintBaseAssetWeight.toFixed(
4
)}/
${perpMarket.maintLiabWeight.toFixed(4)}`}
${perpMarket.maintBaseLiabWeight.toFixed(4)}`}
/>
<KeyValuePair
label="Init Asset/Liab Weight"
value={`${perpMarket.initAssetWeight.toFixed(
value={`${perpMarket.initBaseAssetWeight.toFixed(
4
)}/
${perpMarket.initLiabWeight.toFixed(4)}`}
${perpMarket.initBaseLiabWeight.toFixed(4)}`}
/>
<KeyValuePair
label="Maint PNL Asset weight"
value={`${perpMarket.maintPnlAssetWeight.toFixed(
4
)}`}
/>
<KeyValuePair
label="Init PNL Asset weight"
value={`${perpMarket.initPnlAssetWeight.toFixed(
4
)}`}
/>
<KeyValuePair
label="Liquidation Fee"
@ -458,10 +512,6 @@ const Dashboard: NextPage = () => {
label="Oracle: Max Staleness"
value={`${perpMarket.oracleConfig.maxStalenessSlots} slots`}
/>
<KeyValuePair
label="Trusted Market"
value={`${perpMarket.trustedMarket}`}
/>
<KeyValuePair
label="Group Insurance Fund"
value={`${perpMarket.groupInsuranceFund}`}
@ -492,7 +542,7 @@ const KeyValuePair = ({
value: number | ReactNode | string
}) => {
return (
<div className="flex justify-between border-t border-th-bkg-3 p-4 xl:py-1.5">
<div className="flex justify-between border-t border-th-bkg-3 p-4 xl:py-3">
<span className="mr-4 whitespace-nowrap text-th-fgd-3">{label}</span>
{value}
</div>

View File

@ -6,6 +6,7 @@ export async function getStaticProps({ locale }: { locale: string }) {
return {
props: {
...(await serverSideTranslations(locale, [
'activity',
'common',
'onboarding',
'onboarding-tours',
@ -13,8 +14,9 @@ export async function getStaticProps({ locale }: { locale: string }) {
'search',
'settings',
'swap',
'token',
'trade',
'activity',
'close-account'
])),
},
}

View File

@ -15,6 +15,7 @@ export async function getStaticProps({ locale }: { locale: string }) {
'swap',
'settings',
'trade',
'close-account'
])),
},
}

View File

@ -22,6 +22,7 @@ export async function getStaticProps({ locale }: { locale: string }) {
'search',
'settings',
'trade',
'close-account',
])),
},
}

View File

@ -0,0 +1,19 @@
{
"description": "You can close your Mango account and recover the small amount amount of SOL used to cover rent exemption.",
"you-must": "To close account you must",
"are-you-sure": "Are you sure?",
"before-you-continue": "Before you can continue",
"close-all-borrows": "Close all borrows",
"close-open-orders": "Close all open orders",
"close-perp-positions": "Close and settle all futures positons",
"closing-account-will": "Closing your Mango Account will:",
"delete-your-account": "Delete your Mango Account",
"error-deleting-account": "Error deleting your Mango Account",
"goodbye": "Until next time 👋",
"recover-x-sol": "Recover {{amount}} SOL (rent for your account)",
"settle-balances": "Settle all balances",
"transaction-confirmed": "Transaction Confirmed",
"withdraw-assets-worth": "Withdraw assets worth {{value}}",
"close-account": "Close Account",
"closing-account": "Closing your account..."
}

View File

@ -2,7 +2,7 @@
"mango-account": "Account Address",
"mango-account-name": "Account Name",
"no-results": "No Results",
"no-results-desc": "Try another search. Searches are case-sensitive execpt for profile names which are lowercase",
"no-results-desc": "Try another search. Searches are case-sensitive",
"profile-name": "Profile Name",
"results": "Results",
"search": "Search",

View File

@ -8,6 +8,7 @@
"insufficient-balance": "Insufficient {{symbol}} Balance",
"insufficient-collateral": "Insufficient Collateral",
"max-slippage": "Max Slippage",
"maximum-cost": "Maximum Cost",
"minimum-received": "Minimum Received",
"no-history": "No swap history",
"pay": "You Pay",

View File

@ -0,0 +1,19 @@
{
"description": "You can close your Mango account and recover the small amount amount of SOL used to cover rent exemption.",
"you-must": "To close account you must",
"are-you-sure": "Are you sure?",
"before-you-continue": "Before you can continue",
"close-all-borrows": "Close all borrows",
"close-open-orders": "Close all open orders",
"close-perp-positions": "Close and settle all futures positons",
"closing-account-will": "Closing your Mango Account will:",
"delete-your-account": "Delete your Mango Account",
"error-deleting-account": "Error deleting your Mango Account",
"goodbye": "Until next time 👋",
"recover-x-sol": "Recover {{amount}} SOL (rent for your account)",
"settle-balances": "Settle all balances",
"transaction-confirmed": "Transaction Confirmed",
"withdraw-assets-worth": "Withdraw assets worth {{value}}",
"close-account": "Close Account",
"closing-account": "Closing your account..."
}

View File

@ -2,7 +2,7 @@
"mango-account": "Account Address",
"mango-account-name": "Account Name",
"no-results": "No Results",
"no-results-desc": "Try another search. Searches are case-sensitive execpt for profile names which are lowercase",
"no-results-desc": "Try another search. Searches are case-sensitive",
"profile-name": "Profile Name",
"results": "Results",
"search": "Search",

View File

@ -8,6 +8,7 @@
"insufficient-balance": "Insufficient {{symbol}} Balance",
"insufficient-collateral": "Insufficient Collateral",
"max-slippage": "Max Slippage",
"maximum-cost": "Maximum Cost",
"minimum-received": "Minimum Received",
"no-history": "No swap history",
"pay": "You Pay",

View File

@ -0,0 +1,19 @@
{
"description": "You can close your Mango account and recover the small amount amount of SOL used to cover rent exemption.",
"you-must": "To close account you must",
"are-you-sure": "Are you sure?",
"before-you-continue": "Before you can continue",
"close-all-borrows": "Close all borrows",
"close-open-orders": "Close all open orders",
"close-perp-positions": "Close and settle all futures positons",
"closing-account-will": "Closing your Mango Account will:",
"delete-your-account": "Delete your Mango Account",
"error-deleting-account": "Error deleting your Mango Account",
"goodbye": "Until next time 👋",
"recover-x-sol": "Recover {{amount}} SOL (rent for your account)",
"settle-balances": "Settle all balances",
"transaction-confirmed": "Transaction Confirmed",
"withdraw-assets-worth": "Withdraw assets worth {{value}}",
"close-account": "Close Account",
"closing-account": "Closing your account..."
}

View File

@ -2,7 +2,7 @@
"mango-account": "Account Address",
"mango-account-name": "Account Name",
"no-results": "No Results",
"no-results-desc": "Try another search. Searches are case-sensitive execpt for profile names which are lowercase",
"no-results-desc": "Try another search. Searches are case-sensitive",
"profile-name": "Profile Name",
"results": "Results",
"search": "Search",

View File

@ -8,6 +8,7 @@
"insufficient-balance": "Insufficient {{symbol}} Balance",
"insufficient-collateral": "Insufficient Collateral",
"max-slippage": "Max Slippage",
"maximum-cost": "Maximum Cost",
"minimum-received": "Minimum Received",
"no-history": "No swap history",
"pay": "You Pay",

View File

@ -0,0 +1,19 @@
{
"description": "You can close your Mango account and recover the small amount amount of SOL used to cover rent exemption.",
"you-must": "To close account you must",
"are-you-sure": "Are you sure?",
"before-you-continue": "Before you can continue",
"close-all-borrows": "Close all borrows",
"close-open-orders": "Close all open orders",
"close-perp-positions": "Close and settle all futures positons",
"closing-account-will": "Closing your Mango Account will:",
"delete-your-account": "Delete your Mango Account",
"error-deleting-account": "Error deleting your Mango Account",
"goodbye": "Until next time 👋",
"recover-x-sol": "Recover {{amount}} SOL (rent for your account)",
"settle-balances": "Settle all balances",
"transaction-confirmed": "Transaction Confirmed",
"withdraw-assets-worth": "Withdraw assets worth {{value}}",
"close-account": "Close Account",
"closing-account": "Closing your account..."
}

View File

@ -2,7 +2,7 @@
"mango-account": "Account Address",
"mango-account-name": "Account Name",
"no-results": "No Results",
"no-results-desc": "Try another search. Searches are case-sensitive execpt for profile names which are lowercase",
"no-results-desc": "Try another search. Searches are case-sensitive",
"profile-name": "Profile Name",
"results": "Results",
"search": "Search",

View File

@ -8,6 +8,7 @@
"insufficient-balance": "Insufficient {{symbol}} Balance",
"insufficient-collateral": "Insufficient Collateral",
"max-slippage": "Max Slippage",
"maximum-cost": "Maximum Cost",
"minimum-received": "Minimum Received",
"no-history": "No swap history",
"pay": "You Pay",

View File

@ -0,0 +1,19 @@
{
"description": "You can close your Mango account and recover the small amount amount of SOL used to cover rent exemption.",
"you-must": "To close account you must",
"are-you-sure": "Are you sure?",
"before-you-continue": "Before you can continue",
"close-all-borrows": "Close all borrows",
"close-open-orders": "Close all open orders",
"close-perp-positions": "Close and settle all futures positons",
"closing-account-will": "Closing your Mango Account will:",
"delete-your-account": "Delete your Mango Account",
"error-deleting-account": "Error deleting your Mango Account",
"goodbye": "Until next time 👋",
"recover-x-sol": "Recover {{amount}} SOL (rent for your account)",
"settle-balances": "Settle all balances",
"transaction-confirmed": "Transaction Confirmed",
"withdraw-assets-worth": "Withdraw assets worth {{value}}",
"close-account": "Close Account",
"closing-account": "Closing your account..."
}

View File

@ -2,7 +2,7 @@
"mango-account": "Account Address",
"mango-account-name": "Account Name",
"no-results": "No Results",
"no-results-desc": "Try another search. Searches are case-sensitive execpt for profile names which are lowercase",
"no-results-desc": "Try another search. Searches are case-sensitive",
"profile-name": "Profile Name",
"results": "Results",
"search": "Search",

View File

@ -8,6 +8,7 @@
"insufficient-balance": "Insufficient {{symbol}} Balance",
"insufficient-collateral": "Insufficient Collateral",
"max-slippage": "Max Slippage",
"maximum-cost": "Maximum Cost",
"minimum-received": "Minimum Received",
"no-history": "No swap history",
"pay": "You Pay",

View File

@ -208,6 +208,7 @@ interface TradeForm {
tradeType: 'Market' | 'Limit'
postOnly: boolean
ioc: boolean
reduceOnly: boolean
}
export const DEFAULT_TRADE_FORM: TradeForm = {
@ -218,6 +219,7 @@ export const DEFAULT_TRADE_FORM: TradeForm = {
tradeType: 'Limit',
postOnly: false,
ioc: false,
reduceOnly: false,
}
export type MangoStore = {
@ -319,7 +321,7 @@ export type MangoStore = {
) => Promise<void>
fetchGroup: () => Promise<void>
reloadMangoAccount: () => Promise<void>
fetchMangoAccounts: (wallet: Wallet) => Promise<void>
fetchMangoAccounts: (ownerPk: PublicKey) => Promise<void>
fetchNfts: (connection: Connection, walletPk: PublicKey) => void
fetchOpenOrders: (ma?: MangoAccount) => Promise<void>
fetchPerpStats: () => void
@ -332,7 +334,7 @@ export type MangoStore = {
fetchTokenStats: () => void
fetchTourSettings: (walletPk: string) => void
fetchTradeHistory: () => Promise<void>
fetchWalletTokens: (wallet: Wallet) => Promise<void>
fetchWalletTokens: (walletPk: PublicKey) => Promise<void>
connectMangoClientWithWallet: (wallet: WalletAdapter) => Promise<void>
loadMarketFills: () => Promise<void>
updateConnection: (url: string) => void
@ -658,7 +660,7 @@ const mangoStore = create<MangoStore>()(
})
}
},
fetchMangoAccounts: async (wallet) => {
fetchMangoAccounts: async (ownerPk: PublicKey) => {
const set = get().set
const actions = get().actions
try {
@ -670,7 +672,7 @@ const mangoStore = create<MangoStore>()(
const mangoAccounts = await client.getMangoAccountsForOwner(
group,
wallet.publicKey
ownerPk
)
const selectedAccountIsNotInAccountsList = mangoAccounts.find(
(x) =>
@ -685,8 +687,6 @@ const mangoStore = create<MangoStore>()(
return
}
mangoAccounts.forEach((ma) => ma.reloadAccountData(client))
let newSelectedMangoAccount = selectedMangoAccount
if (!selectedMangoAccount || !selectedAccountIsNotInAccountsList) {
const lastAccount = localStorage.getItem(LAST_ACCOUNT_KEY)
@ -701,12 +701,20 @@ const mangoStore = create<MangoStore>()(
}
if (newSelectedMangoAccount) {
await actions.fetchOpenOrders(newSelectedMangoAccount)
await newSelectedMangoAccount.reloadAccountData(client)
set((state) => {
state.mangoAccount.current = newSelectedMangoAccount
state.mangoAccount.initialLoad = false
})
actions.fetchOpenOrders(newSelectedMangoAccount)
}
await Promise.all(
mangoAccounts.map((ma) => ma.reloadAccountData(client))
)
set((state) => {
state.mangoAccounts = mangoAccounts
state.mangoAccount.current = newSelectedMangoAccount
})
} catch (e) {
console.error('Error fetching mango accts', e)
@ -893,14 +901,14 @@ const mangoStore = create<MangoStore>()(
})
}
},
fetchWalletTokens: async (wallet: Wallet) => {
fetchWalletTokens: async (walletPk: PublicKey) => {
const set = get().set
const connection = get().connection
if (wallet.publicKey) {
if (walletPk) {
const token = await getTokenAccountsByOwnerWithWrappedSol(
connection,
wallet.publicKey
walletPk
)
set((state) => {

View File

@ -60,7 +60,7 @@ export const COLORS: any = {
Olive: '#acaa8b',
},
UP: {
'Mango Classic': '#A6CD03',
'Mango Classic': '#89B92A',
Dark: '#4aa13a',
Light: '#60bf4f',
'High Contrast': '#50f434',

View File

@ -35,17 +35,17 @@ module.exports = {
},
link: { DEFAULT: 'hsl(33, 100%, 57%)', hover: 'hsl(33, 100%, 52%)' },
down: {
DEFAULT: 'hsl(4, 93%, 60%)',
DEFAULT: 'hsl(4, 63%, 55%)',
dark: 'hsl(4, 93%, 55%)',
muted: 'hsl(4, 53%, 55%)',
muted: 'hsl(4, 43%, 38%)',
},
up: {
DEFAULT: 'hsl(72, 97%, 41%)',
dark: 'hsl(72, 97%, 36%)',
muted: 'hsl(72, 57%, 36%)',
DEFAULT: 'hsl(77, 63%, 40%)',
dark: 'hsl(85, 50%, 36%)',
muted: 'hsl(84, 40%, 32%)',
},
error: 'hsl(4, 93%, 60%)',
success: 'hsl(72, 97%, 41%)',
success: 'hsl(82, 97%, 41%)',
warning: 'hsl(33, 100%, 57%)',
'bkg-1': 'hsl(256, 18%, 12%)',
'bkg-2': 'hsl(256, 18%, 17%)',
@ -109,14 +109,14 @@ module.exports = {
},
link: { DEFAULT: 'hsl(45, 86%, 62%)', hover: 'hsl(45, 86%, 57%)' },
down: {
DEFAULT: 'hsl(0, 59%, 58%)',
dark: 'hsl(0, 59%, 53%)',
muted: 'hsl(0, 19%, 53%)',
DEFAULT: 'hsl(358, 55%, 50%)',
dark: 'hsl(0, 45%, 26%)',
muted: 'hsl(0, 45%, 30%)',
},
up: {
DEFAULT: 'hsl(111, 47%, 43%)',
dark: 'hsl(111, 47%, 38%)',
muted: 'hsl(111, 7%, 38%)',
muted: 'hsl(130, 34%, 26%)',
},
error: 'hsl(0, 59%, 58%)',
success: 'hsl(111, 47%, 43%)',

View File

@ -1,6 +1,6 @@
{
"compilerOptions": {
"target": "es5",
"target": "es2017",
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"skipLibCheck": true,

View File

@ -1,3 +1,4 @@
import { PerpMarket, Serum3Market } from '@blockworks-foundation/mango-v4'
import { BN } from '@project-serum/anchor'
export interface ChartTradeType {
@ -45,3 +46,5 @@ export interface SpotTradeHistory {
base_symbol: string
quote_symbol: string
}
export type GenericMarket = Serum3Market | PerpMarket

View File

@ -1,13 +1,14 @@
import { AccountMeta } from '@solana/web3.js'
import { AccountInfo } from '@solana/web3.js'
import { AccountInfo, PublicKey, TransactionInstruction } from '@solana/web3.js'
import Decimal from 'decimal.js'
export declare type SideType = typeof Side.Ask | typeof Side.Bid
export declare const Side: {
Bid: {
// eslint-disable-next-line @typescript-eslint/ban-types
bid: {}
}
Ask: {
// eslint-disable-next-line @typescript-eslint/ban-types
ask: {}
}
}
@ -57,7 +58,7 @@ export interface ExactOutSwapParams extends SwapParams {
}
export declare type AccountInfoMap = Map<string, AccountInfo<Buffer> | null>
declare type AmmLabel =
export declare type AmmLabel =
| 'Aldrin'
| 'Crema'
| 'Cropper'
@ -127,6 +128,9 @@ export interface RouteInfo {
priceImpactPct: number
slippageBps: number
swapMode: SwapMode
instructions?: TransactionInstruction[]
mints?: PublicKey[]
routerName?: 'Mango'
}
export type Routes = {

View File

@ -58,6 +58,8 @@ export const PROFILE_CATEGORIES = [
export const CHART_DATA_FEED = `https://dry-ravine-67635.herokuapp.com/tv`
export const MANGO_ROUTER_API_URL = 'https://api.mngo.cloud/router/v1'
export const DEFAULT_MARKET_NAME = 'SOL/USDC'
export const MIN_SOL_BALANCE = 0.001

View File

@ -52,7 +52,7 @@
"@blockworks-foundation/mango-v4@https://github.com/blockworks-foundation/mango-v4.git#ts-client":
version "0.0.1-beta.6"
resolved "https://github.com/blockworks-foundation/mango-v4.git#59ef05ebe95ed5ca947ab5cfecf97c4c9fc99740"
resolved "https://github.com/blockworks-foundation/mango-v4.git#adc9140ad2a3c2e9c3b425b8804d9415f2a05067"
dependencies:
"@project-serum/anchor" "^0.25.0"
"@project-serum/serum" "^0.13.65"
@ -4844,7 +4844,14 @@ node-addon-api@^2.0.0:
resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-2.0.2.tgz#432cfa82962ce494b132e9d72a15b29f71ff5d32"
integrity sha512-Ntyt4AIXyaLIuMHF6IOoTakB3K+RWxwtsHNRxllEoA6vPwP9o4866g6YWDLUdnucilZhmkxiHwHr11gAENw+QA==
node-fetch@2, node-fetch@2.6.7, node-fetch@^2.6.1:
node-fetch@2, node-fetch@^2.6.1:
version "2.6.8"
resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.8.tgz#a68d30b162bc1d8fd71a367e81b997e1f4d4937e"
integrity sha512-RZ6dBYuj8dRSfxpUSu+NsdF1dpPpluJxwOp+6IoDp/sH2QNDSvurYsAa+F1WxY2RjA1iP93xhcsUoYbF2XBqVg==
dependencies:
whatwg-url "^5.0.0"
node-fetch@2.6.7:
version "2.6.7"
resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.7.tgz#24de9fba827e3b4ae44dc8b20256a379160052ad"
integrity sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==