User account page (#22)
* layout, overview, start on assets, borrows and open orders * trade history, sortable data hook for tables, borrow page * handle deposit and withdraw buttons * borrow modal ui and integration + settle borrow for individual assets * in orders balance to asset table and totals, responsive css, new connected wallet button + small tweaks * account switch/creation flow * accounts modal, update to usebalances hook * handle settle, deposit before settle, save last account * disable borrow/withdraw button when no account
This commit is contained in:
parent
bf0600ad6e
commit
3b5f22b815
|
@ -57,15 +57,13 @@ const AccountSelect = ({
|
|||
)
|
||||
|
||||
const missingTokens = symbols
|
||||
? Object.keys(symbols)
|
||||
.filter((sym) => !symbolsForAccounts.includes(sym))
|
||||
.join(', ')
|
||||
? Object.keys(symbols).filter((sym) => !symbolsForAccounts.includes(sym))
|
||||
: null
|
||||
|
||||
return (
|
||||
<div className={`relative inline-block w-full`}>
|
||||
<div className="flex justify-between pb-2">
|
||||
<div className="text-th-fgd-1">Token Account</div>
|
||||
<div className="text-th-fgd-1">Asset</div>
|
||||
{accounts.length < Object.keys(symbols).length ? (
|
||||
<button
|
||||
className="ml-2 text-th-fgd-1 hover:text-th-primary outline-none focus:outline-none"
|
||||
|
@ -121,7 +119,7 @@ const AccountSelect = ({
|
|||
</div>
|
||||
</div>
|
||||
) : (
|
||||
'Select a token address'
|
||||
'Select an asset'
|
||||
)}
|
||||
{open ? (
|
||||
<ChevronUpIcon className="h-5 w-5 ml-2 text-th-primary" />
|
||||
|
@ -134,12 +132,6 @@ const AccountSelect = ({
|
|||
<Listbox.Options
|
||||
className={`z-20 p-1 absolute right-0 top-13 bg-th-bkg-1 divide-y divide-th-bkg-3 shadow-lg outline-none rounded-md w-full max-h-60 overflow-auto`}
|
||||
>
|
||||
<div className="flex justify-between">
|
||||
<div className={`text-th-fgd-4 p-2`}>Accounts</div>
|
||||
{!hideAddress ? (
|
||||
<div className={`text-th-fgd-4 p-2`}>Balance</div>
|
||||
) : null}
|
||||
</div>
|
||||
{accounts.map((account) => {
|
||||
const symbolForAccount = getSymbolForTokenMintAddress(
|
||||
account?.account?.mint.toString()
|
||||
|
@ -183,13 +175,27 @@ const AccountSelect = ({
|
|||
</Listbox.Option>
|
||||
)
|
||||
})}
|
||||
{missingTokens && accounts.length !== Object.keys(symbols).length ? (
|
||||
<Listbox.Option value="">
|
||||
<div className="flex items-center justify-center text-th-fgd-1 p-2">
|
||||
Wallet token address not found for: {missingTokens}
|
||||
</div>
|
||||
</Listbox.Option>
|
||||
) : null}
|
||||
{missingTokens && accounts.length !== Object.keys(symbols).length
|
||||
? missingTokens.map((token) => (
|
||||
<Listbox.Option disabled key={token} value={token}>
|
||||
<div
|
||||
className={`opacity-50 p-2 hover:cursor-not-allowed`}
|
||||
>
|
||||
<div className={`flex items-center text-th-fgd-1`}>
|
||||
<img
|
||||
alt=""
|
||||
width="20"
|
||||
height="20"
|
||||
src={`/assets/icons/${token.toLowerCase()}.svg`}
|
||||
className="mr-2"
|
||||
/>
|
||||
<div className={`flex-grow text-left`}>{token}</div>
|
||||
<div className={`text-xs`}>No wallet address</div>
|
||||
</div>
|
||||
</div>
|
||||
</Listbox.Option>
|
||||
))
|
||||
: null}
|
||||
</Listbox.Options>
|
||||
</>
|
||||
)}
|
||||
|
|
|
@ -0,0 +1,193 @@
|
|||
import React, { FunctionComponent, useEffect, useState } from 'react'
|
||||
import { RadioGroup } from '@headlessui/react'
|
||||
import { CheckCircleIcon } from '@heroicons/react/solid'
|
||||
import {
|
||||
ChevronLeftIcon,
|
||||
CurrencyDollarIcon,
|
||||
PlusCircleIcon,
|
||||
} from '@heroicons/react/outline'
|
||||
import useMangoStore from '../stores/useMangoStore'
|
||||
import { MarginAccount } from '@blockworks-foundation/mango-client'
|
||||
import { abbreviateAddress } from '../utils'
|
||||
import useLocalStorageState from '../hooks/useLocalStorageState'
|
||||
import Modal from './Modal'
|
||||
import { ElementTitle } from './styles'
|
||||
import Button, { LinkButton } from './Button'
|
||||
import NewAccount from './NewAccount'
|
||||
|
||||
interface AccountsModalProps {
|
||||
onClose: () => void
|
||||
isOpen: boolean
|
||||
}
|
||||
|
||||
const AccountsModal: FunctionComponent<AccountsModalProps> = ({
|
||||
isOpen,
|
||||
onClose,
|
||||
}) => {
|
||||
const [showNewAccountForm, setShowNewAccountForm] = useState(false)
|
||||
const [newAccPublicKey, setNewAccPublicKey] = useState(null)
|
||||
const marginAccounts = useMangoStore((s) => s.marginAccounts)
|
||||
const selectedMarginAccount = useMangoStore(
|
||||
(s) => s.selectedMarginAccount.current
|
||||
)
|
||||
const selectedMangoGroup = useMangoStore((s) => s.selectedMangoGroup.current)
|
||||
const prices = useMangoStore((s) => s.selectedMangoGroup.prices)
|
||||
const setMangoStore = useMangoStore((s) => s.set)
|
||||
const [, setLastAccountViewed] = useLocalStorageState('lastAccountViewed')
|
||||
|
||||
const handleMarginAccountChange = (marginAccount: MarginAccount) => {
|
||||
setLastAccountViewed(marginAccount.publicKey.toString())
|
||||
setMangoStore((state) => {
|
||||
state.selectedMarginAccount.current = marginAccount
|
||||
})
|
||||
onClose()
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (newAccPublicKey) {
|
||||
setMangoStore((state) => {
|
||||
state.selectedMarginAccount.current = marginAccounts.find((ma) =>
|
||||
ma.publicKey.equals(newAccPublicKey)
|
||||
)
|
||||
})
|
||||
}
|
||||
}, [marginAccounts, newAccPublicKey])
|
||||
|
||||
const handleNewAccountCreation = (newAccPublicKey) => {
|
||||
if (newAccPublicKey) {
|
||||
setNewAccPublicKey(newAccPublicKey)
|
||||
}
|
||||
setShowNewAccountForm(false)
|
||||
}
|
||||
|
||||
const handleShowNewAccountForm = () => {
|
||||
setNewAccPublicKey(null)
|
||||
setShowNewAccountForm(true)
|
||||
}
|
||||
|
||||
const getAccountInfo = (acc) => {
|
||||
const accountEquity = acc
|
||||
.computeValue(selectedMangoGroup, prices)
|
||||
.toFixed(2)
|
||||
let leverage = accountEquity
|
||||
? (1 / (acc.getCollateralRatio(selectedMangoGroup, prices) - 1)).toFixed(
|
||||
2
|
||||
)
|
||||
: '∞'
|
||||
return (
|
||||
<div className="text-th-fgd-3 text-xs">
|
||||
${accountEquity}
|
||||
<span className="px-1.5 text-th-fgd-4">|</span>
|
||||
<span
|
||||
className={
|
||||
parseFloat(leverage) > 4
|
||||
? 'text-th-green'
|
||||
: parseFloat(leverage) > 2
|
||||
? 'text-th-orange'
|
||||
: 'text-th-green'
|
||||
}
|
||||
>
|
||||
{leverage}x
|
||||
</span>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<Modal isOpen={isOpen} onClose={onClose}>
|
||||
{marginAccounts.length > 0 ? (
|
||||
!showNewAccountForm ? (
|
||||
<>
|
||||
<Modal.Header>
|
||||
<ElementTitle noMarignBottom>Margin Accounts</ElementTitle>
|
||||
</Modal.Header>
|
||||
<div className="flex items-center justify-between pb-3 text-th-fgd-1">
|
||||
<div className="font-semibold">
|
||||
{marginAccounts.length > 1
|
||||
? 'Select an account'
|
||||
: 'Your Account'}
|
||||
</div>
|
||||
<Button
|
||||
className="text-xs flex items-center justify-center pt-0 pb-0 h-8 pl-3 pr-3"
|
||||
onClick={() => handleShowNewAccountForm()}
|
||||
>
|
||||
<div className="flex items-center">
|
||||
<PlusCircleIcon className="h-5 w-5 mr-1.5" />
|
||||
New
|
||||
</div>
|
||||
</Button>
|
||||
</div>
|
||||
<RadioGroup
|
||||
value={selectedMarginAccount}
|
||||
onChange={(acc) => handleMarginAccountChange(acc)}
|
||||
>
|
||||
<RadioGroup.Label className="sr-only">
|
||||
Select a Margin Account
|
||||
</RadioGroup.Label>
|
||||
<div className="space-y-2">
|
||||
{marginAccounts.map((account, i) => (
|
||||
<RadioGroup.Option
|
||||
key={account.publicKey.toString()}
|
||||
value={account}
|
||||
className={({ checked }) =>
|
||||
`${
|
||||
checked
|
||||
? 'bg-th-bkg-3 ring-1 ring-th-green ring-inset'
|
||||
: 'bg-th-bkg-1'
|
||||
}
|
||||
relative rounded-md w-full px-3 py-3 cursor-pointer default-transition flex hover:bg-th-bkg-3 focus:outline-none`
|
||||
}
|
||||
>
|
||||
{({ checked }) => (
|
||||
<>
|
||||
<div className="flex items-center justify-between w-full">
|
||||
<div className="flex items-center">
|
||||
<div className="text-sm">
|
||||
<RadioGroup.Label className="cursor-pointer flex items-center text-th-fgd-1">
|
||||
<CurrencyDollarIcon className="h-5 w-5 mr-2.5" />
|
||||
<div>
|
||||
<div className="pb-0.5">
|
||||
{abbreviateAddress(account.publicKey)}
|
||||
</div>
|
||||
{prices && selectedMangoGroup ? (
|
||||
<div className="text-th-fgd-3 text-xs">
|
||||
{getAccountInfo(account)}
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
</RadioGroup.Label>
|
||||
</div>
|
||||
</div>
|
||||
{checked && (
|
||||
<div className="flex-shrink-0 text-th-green">
|
||||
<CheckCircleIcon className="w-5 h-5" />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</RadioGroup.Option>
|
||||
))}
|
||||
</div>
|
||||
</RadioGroup>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<NewAccount onAccountCreation={handleNewAccountCreation} />
|
||||
<LinkButton
|
||||
className="flex items-center mt-4 text-th-fgd-3"
|
||||
onClick={() => setShowNewAccountForm(false)}
|
||||
>
|
||||
<ChevronLeftIcon className="h-5 w-5 mr-1" />
|
||||
Back
|
||||
</LinkButton>
|
||||
</>
|
||||
)
|
||||
) : (
|
||||
<NewAccount onAccountCreation={handleNewAccountCreation} />
|
||||
)}
|
||||
</Modal>
|
||||
)
|
||||
}
|
||||
|
||||
export default React.memo(AccountsModal)
|
|
@ -22,10 +22,8 @@ const AlertsList = () => {
|
|||
const triggeredAlerts = useAlertsStore((s) => s.triggeredAlerts)
|
||||
const loading = useAlertsStore((s) => s.loading)
|
||||
|
||||
const [
|
||||
triggeredAlertsLength,
|
||||
setTriggeredAlertsLength,
|
||||
] = useLocalStorageState('triggeredAlertsLength', null)
|
||||
const [triggeredAlertsLength, setTriggeredAlertsLength] =
|
||||
useLocalStorageState('triggeredAlertsLength', null)
|
||||
|
||||
const [alertsCount, setAlertsCount] = useLocalStorageState('alertsCount', 0)
|
||||
|
||||
|
@ -105,10 +103,7 @@ const AlertsList = () => {
|
|||
leaveFrom="opacity-100 translate-y-0"
|
||||
leaveTo="opacity-0 translate-y-1"
|
||||
>
|
||||
<Popover.Panel
|
||||
static
|
||||
className="absolute z-10 mt-4 right-0 md:transform md:-translate-x-1/2 md:left-1/2 w-64"
|
||||
>
|
||||
<Popover.Panel static className="absolute z-10 mt-4 right-0 w-64">
|
||||
<div className="bg-th-bkg-1 p-4 overflow-auto max-h-80 rounded-lg shadow-lg thin-scroll">
|
||||
{loading ? (
|
||||
<div className="flex items-center justify-center text-th-primary h-40">
|
||||
|
|
|
@ -5,8 +5,6 @@ import useConnection from '../hooks/useConnection'
|
|||
import Button from '../components/Button'
|
||||
import { notify } from '../utils/notifications'
|
||||
import { Table, Thead, Tbody, Tr, Th, Td } from 'react-super-responsive-table'
|
||||
import useMarket from '../hooks/useMarket'
|
||||
import { ElementTitle } from './styles'
|
||||
import { InformationCircleIcon } from '@heroicons/react/outline'
|
||||
import Tooltip from './Tooltip'
|
||||
import { sleep } from '../utils'
|
||||
|
@ -16,7 +14,6 @@ const BalancesTable = () => {
|
|||
const balances = useBalances()
|
||||
const { programId, connection } = useConnection()
|
||||
const actions = useMangoStore((s) => s.actions)
|
||||
const { marketName } = useMarket()
|
||||
|
||||
async function handleSettleAll() {
|
||||
const markets = Object.values(
|
||||
|
@ -56,15 +53,10 @@ const BalancesTable = () => {
|
|||
}
|
||||
|
||||
return (
|
||||
<div className={`flex flex-col py-6`}>
|
||||
<div className={`flex flex-col py-4`}>
|
||||
<div className={`-my-2 overflow-x-auto sm:-mx-6 lg:-mx-8`}>
|
||||
<div className={`align-middle inline-block min-w-full sm:px-6 lg:px-8`}>
|
||||
<ElementTitle>
|
||||
<div className="pr-1">{marketName.split('/')[0]}</div>
|
||||
<span className="text-th-fgd-4">/</span>
|
||||
<div className="pl-1">{marketName.split('/')[1]}</div>
|
||||
</ElementTitle>
|
||||
{balances.length &&
|
||||
{balances.length > 0 &&
|
||||
(balances.find(({ unsettled }) => unsettled > 0) ||
|
||||
balances.find(
|
||||
({ borrows, marginDeposits }) => borrows > 0 && marginDeposits > 0
|
||||
|
|
|
@ -0,0 +1,634 @@
|
|||
import React, { FunctionComponent, useEffect, useMemo, useState } from 'react'
|
||||
import Modal from './Modal'
|
||||
import Input from './Input'
|
||||
import { ElementTitle } from './styles'
|
||||
import useMangoStore from '../stores/useMangoStore'
|
||||
import useMarketList from '../hooks/useMarketList'
|
||||
import {
|
||||
DECIMALS,
|
||||
floorToDecimal,
|
||||
tokenPrecision,
|
||||
displayDepositsForMarginAccount,
|
||||
} from '../utils/index'
|
||||
import useConnection from '../hooks/useConnection'
|
||||
import { borrowAndWithdraw, withdraw } from '../utils/mango'
|
||||
import Loading from './Loading'
|
||||
import Slider from './Slider'
|
||||
import Button, { LinkButton } from './Button'
|
||||
import { notify } from '../utils/notifications'
|
||||
import Tooltip from './Tooltip'
|
||||
import {
|
||||
ExclamationCircleIcon,
|
||||
InformationCircleIcon,
|
||||
} from '@heroicons/react/outline'
|
||||
import {
|
||||
ChevronLeftIcon,
|
||||
ChevronDownIcon,
|
||||
ChevronUpIcon,
|
||||
} from '@heroicons/react/solid'
|
||||
import { Disclosure } from '@headlessui/react'
|
||||
import { PublicKey } from '@solana/web3.js'
|
||||
import { MarginAccount, uiToNative } from '@blockworks-foundation/mango-client'
|
||||
import Select from './Select'
|
||||
|
||||
interface BorrowModalProps {
|
||||
onClose: () => void
|
||||
isOpen: boolean
|
||||
tokenSymbol?: string
|
||||
}
|
||||
|
||||
const BorrowModal: FunctionComponent<BorrowModalProps> = ({
|
||||
isOpen,
|
||||
onClose,
|
||||
tokenSymbol = '',
|
||||
}) => {
|
||||
const [borrowTokenSymbol, setBorrowTokenSymbol] = useState(
|
||||
tokenSymbol || 'USDC'
|
||||
)
|
||||
const [borrowAssetDetails, setBorrowAssetDetails] = useState(null)
|
||||
const [inputAmount, setInputAmount] = useState(0)
|
||||
const [invalidAmountMessage, setInvalidAmountMessage] = useState('')
|
||||
const [maxAmount, setMaxAmount] = useState(0)
|
||||
const [submitting, setSubmitting] = useState(false)
|
||||
const [includeBorrow, setIncludeBorrow] = useState(false)
|
||||
const [simulation, setSimulation] = useState(null)
|
||||
const [showSimulation, setShowSimulation] = useState(false)
|
||||
const [sliderPercentage, setSliderPercentage] = useState(0)
|
||||
const [maxButtonTransition, setMaxButtonTransition] = useState(false)
|
||||
const { getTokenIndex, symbols } = useMarketList()
|
||||
const { connection, programId } = useConnection()
|
||||
const prices = useMangoStore((s) => s.selectedMangoGroup.prices)
|
||||
const selectedMangoGroup = useMangoStore((s) => s.selectedMangoGroup.current)
|
||||
const selectedMarginAccount = useMangoStore(
|
||||
(s) => s.selectedMarginAccount.current
|
||||
)
|
||||
const actions = useMangoStore((s) => s.actions)
|
||||
const tokenIndex = useMemo(
|
||||
() => getTokenIndex(symbols[borrowTokenSymbol]),
|
||||
[borrowTokenSymbol, getTokenIndex]
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
if (!selectedMangoGroup || !selectedMarginAccount || !borrowTokenSymbol)
|
||||
return
|
||||
|
||||
const mintDecimals = selectedMangoGroup.mintDecimals[tokenIndex]
|
||||
const groupIndex = selectedMangoGroup.indexes[tokenIndex]
|
||||
const deposits = selectedMarginAccount.getUiDeposit(
|
||||
selectedMangoGroup,
|
||||
tokenIndex
|
||||
)
|
||||
const borrows = selectedMarginAccount.getUiBorrow(
|
||||
selectedMangoGroup,
|
||||
tokenIndex
|
||||
)
|
||||
|
||||
const currentAssetsVal =
|
||||
selectedMarginAccount.getAssetsVal(selectedMangoGroup, prices) -
|
||||
getMaxForSelectedAsset() * prices[tokenIndex]
|
||||
const currentLiabs = selectedMarginAccount.getLiabsVal(
|
||||
selectedMangoGroup,
|
||||
prices
|
||||
)
|
||||
// multiply by 0.99 and subtract 0.01 to account for rounding issues
|
||||
const liabsAvail = (currentAssetsVal / 1.2 - currentLiabs) * 0.99 - 0.01
|
||||
|
||||
// calculate max withdraw amount
|
||||
const amountToWithdraw =
|
||||
liabsAvail / prices[tokenIndex] + getMaxForSelectedAsset()
|
||||
|
||||
if (amountToWithdraw > 0) {
|
||||
setMaxAmount(amountToWithdraw)
|
||||
} else {
|
||||
setMaxAmount(0)
|
||||
}
|
||||
|
||||
// simulate change to deposits & borrow based on input amount
|
||||
const newDeposit = Math.max(0, deposits - inputAmount)
|
||||
const newBorrows = borrows + Math.max(0, inputAmount - deposits)
|
||||
|
||||
// clone MarginAccount and arrays to not modify selectedMarginAccount
|
||||
const simulation = new MarginAccount(null, selectedMarginAccount)
|
||||
simulation.deposits = [...selectedMarginAccount.deposits]
|
||||
simulation.borrows = [...selectedMarginAccount.borrows]
|
||||
|
||||
// update with simulated values
|
||||
simulation.deposits[tokenIndex] =
|
||||
uiToNative(newDeposit, mintDecimals).toNumber() / groupIndex.deposit
|
||||
simulation.borrows[tokenIndex] =
|
||||
uiToNative(newBorrows, mintDecimals).toNumber() / groupIndex.borrow
|
||||
|
||||
const equity = simulation.computeValue(selectedMangoGroup, prices)
|
||||
const assetsVal = simulation.getAssetsVal(selectedMangoGroup, prices)
|
||||
const liabsVal = simulation.getLiabsVal(selectedMangoGroup, prices)
|
||||
const collateralRatio = simulation.getCollateralRatio(
|
||||
selectedMangoGroup,
|
||||
prices
|
||||
)
|
||||
const leverage = 1 / Math.max(0, collateralRatio - 1)
|
||||
|
||||
setSimulation({
|
||||
equity,
|
||||
assetsVal,
|
||||
liabsVal,
|
||||
collateralRatio,
|
||||
leverage,
|
||||
})
|
||||
}, [
|
||||
includeBorrow,
|
||||
inputAmount,
|
||||
prices,
|
||||
tokenIndex,
|
||||
selectedMarginAccount,
|
||||
selectedMangoGroup,
|
||||
])
|
||||
|
||||
const handleWithdraw = () => {
|
||||
setSubmitting(true)
|
||||
const marginAccount = useMangoStore.getState().selectedMarginAccount.current
|
||||
const mangoGroup = useMangoStore.getState().selectedMangoGroup.current
|
||||
const wallet = useMangoStore.getState().wallet.current
|
||||
if (!marginAccount || !mangoGroup) return
|
||||
|
||||
if (!includeBorrow) {
|
||||
withdraw(
|
||||
connection,
|
||||
new PublicKey(programId),
|
||||
mangoGroup,
|
||||
marginAccount,
|
||||
wallet,
|
||||
new PublicKey(symbols[borrowTokenSymbol]),
|
||||
Number(inputAmount)
|
||||
)
|
||||
.then((_transSig: string) => {
|
||||
setSubmitting(false)
|
||||
actions.fetchMangoGroup()
|
||||
actions.fetchMarginAccounts()
|
||||
actions.fetchWalletBalances()
|
||||
onClose()
|
||||
})
|
||||
.catch((err) => {
|
||||
setSubmitting(false)
|
||||
console.warn('Error withdrawing:', err)
|
||||
notify({
|
||||
message: 'Could not perform borrow and withdraw',
|
||||
txid: err.txid,
|
||||
type: 'error',
|
||||
})
|
||||
onClose()
|
||||
})
|
||||
} else {
|
||||
borrowAndWithdraw(
|
||||
connection,
|
||||
new PublicKey(programId),
|
||||
mangoGroup,
|
||||
marginAccount,
|
||||
wallet,
|
||||
new PublicKey(symbols[borrowTokenSymbol]),
|
||||
Number(inputAmount)
|
||||
)
|
||||
.then((_transSig: string) => {
|
||||
setSubmitting(false)
|
||||
actions.fetchMangoGroup()
|
||||
actions.fetchMarginAccounts()
|
||||
actions.fetchWalletBalances()
|
||||
onClose()
|
||||
})
|
||||
.catch((err) => {
|
||||
setSubmitting(false)
|
||||
console.warn('Error borrowing and withdrawing:', err)
|
||||
notify({
|
||||
message: 'Could not perform borrow and withdraw',
|
||||
description: `${err}`,
|
||||
txid: err.txid,
|
||||
type: 'error',
|
||||
})
|
||||
onClose()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const handleSetSelectedAsset = (symbol) => {
|
||||
setInputAmount(0)
|
||||
setSliderPercentage(0)
|
||||
setBorrowTokenSymbol(symbol)
|
||||
}
|
||||
|
||||
const getMaxForSelectedAsset = () => {
|
||||
return displayDepositsForMarginAccount(
|
||||
selectedMarginAccount,
|
||||
selectedMangoGroup,
|
||||
tokenIndex
|
||||
)
|
||||
}
|
||||
|
||||
const getBorrowAmount = () => {
|
||||
const tokenBalance = getMaxForSelectedAsset()
|
||||
const borrowAmount = inputAmount - tokenBalance
|
||||
return borrowAmount > 0 ? borrowAmount : 0
|
||||
}
|
||||
|
||||
const getAccountStatusColor = (
|
||||
collateralRatio: number,
|
||||
isRisk?: boolean,
|
||||
isStatus?: boolean
|
||||
) => {
|
||||
if (collateralRatio < 1.25) {
|
||||
return isRisk ? (
|
||||
<div className="text-th-red">High</div>
|
||||
) : isStatus ? (
|
||||
'bg-th-red'
|
||||
) : (
|
||||
'border-th-red text-th-red'
|
||||
)
|
||||
} else if (collateralRatio > 1.25 && collateralRatio < 1.5) {
|
||||
return isRisk ? (
|
||||
<div className="text-th-orange">Moderate</div>
|
||||
) : isStatus ? (
|
||||
'bg-th-orange'
|
||||
) : (
|
||||
'border-th-orange text-th-orange'
|
||||
)
|
||||
} else {
|
||||
return isRisk ? (
|
||||
<div className="text-th-green">Low</div>
|
||||
) : isStatus ? (
|
||||
'bg-th-green'
|
||||
) : (
|
||||
'border-th-green text-th-green'
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
const setMaxBorrowForSelectedAsset = async () => {
|
||||
setInputAmount(trimDecimals(maxAmount, DECIMALS[borrowTokenSymbol]))
|
||||
setSliderPercentage(100)
|
||||
setInvalidAmountMessage('')
|
||||
setMaxButtonTransition(true)
|
||||
}
|
||||
|
||||
const onChangeAmountInput = (amount) => {
|
||||
setInputAmount(amount)
|
||||
setSliderPercentage((amount / maxAmount) * 100)
|
||||
setInvalidAmountMessage('')
|
||||
}
|
||||
|
||||
const onChangeSlider = async (percentage) => {
|
||||
const amount = (percentage / 100) * maxAmount
|
||||
setInputAmount(trimDecimals(amount, DECIMALS[borrowTokenSymbol]))
|
||||
setSliderPercentage(percentage)
|
||||
setInvalidAmountMessage('')
|
||||
}
|
||||
|
||||
const validateAmountInput = (e) => {
|
||||
const amount = e.target.value
|
||||
if (Number(amount) <= 0) {
|
||||
setInvalidAmountMessage('Withdrawal amount must be greater than 0')
|
||||
}
|
||||
if (simulation.collateralRatio < 1.2) {
|
||||
setInvalidAmountMessage(
|
||||
'Leverage too high. Reduce the amount to withdraw'
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
const trimDecimals = (n, digits) => {
|
||||
const step = Math.pow(10, digits || 0)
|
||||
const temp = Math.trunc(step * n)
|
||||
|
||||
return temp / step
|
||||
}
|
||||
|
||||
const getTokenBalances = () =>
|
||||
Object.entries(symbols).map(([name], i) => {
|
||||
return {
|
||||
symbol: name,
|
||||
balance: floorToDecimal(
|
||||
selectedMarginAccount.getUiDeposit(selectedMangoGroup, i),
|
||||
tokenPrecision[name]
|
||||
),
|
||||
}
|
||||
})
|
||||
|
||||
// turn off slider transition for dragging slider handle interaction
|
||||
useEffect(() => {
|
||||
if (maxButtonTransition) {
|
||||
setMaxButtonTransition(false)
|
||||
}
|
||||
}, [maxButtonTransition])
|
||||
|
||||
useEffect(() => {
|
||||
const assetIndex = Object.keys(symbols).findIndex(
|
||||
(a) => a === borrowTokenSymbol
|
||||
)
|
||||
const totalDeposits = selectedMangoGroup.getUiTotalDeposit(assetIndex)
|
||||
const totalBorrows = selectedMangoGroup.getUiTotalBorrow(assetIndex)
|
||||
|
||||
setBorrowAssetDetails({
|
||||
interest: selectedMangoGroup.getBorrowRate(assetIndex) * 100,
|
||||
price: prices[assetIndex],
|
||||
totalDeposits,
|
||||
utilization: totalDeposits > 0.0 ? totalBorrows / totalDeposits : 0.0,
|
||||
})
|
||||
}, [borrowTokenSymbol])
|
||||
|
||||
if (!borrowTokenSymbol) return null
|
||||
|
||||
return (
|
||||
<Modal isOpen={isOpen} onClose={onClose}>
|
||||
<>
|
||||
{!showSimulation ? (
|
||||
<>
|
||||
<Modal.Header>
|
||||
<ElementTitle noMarignBottom>Borrow Funds</ElementTitle>
|
||||
</Modal.Header>
|
||||
<div className="pb-2 text-th-fgd-1">Asset</div>
|
||||
<Select
|
||||
value={
|
||||
borrowTokenSymbol && selectedMarginAccount ? (
|
||||
<div className="flex items-center justify-between w-full">
|
||||
<div className="flex items-center">
|
||||
<img
|
||||
alt=""
|
||||
width="20"
|
||||
height="20"
|
||||
src={`/assets/icons/${borrowTokenSymbol.toLowerCase()}.svg`}
|
||||
className={`mr-2.5`}
|
||||
/>
|
||||
{borrowTokenSymbol}
|
||||
</div>
|
||||
{floorToDecimal(
|
||||
selectedMarginAccount.getUiDeposit(
|
||||
selectedMangoGroup,
|
||||
tokenIndex
|
||||
),
|
||||
tokenPrecision[borrowTokenSymbol]
|
||||
)}
|
||||
</div>
|
||||
) : (
|
||||
<span className="text-th-fgd-4">Select an asset</span>
|
||||
)
|
||||
}
|
||||
onChange={(asset) => handleSetSelectedAsset(asset)}
|
||||
>
|
||||
{getTokenBalances().map(({ symbol, balance }) => (
|
||||
<Select.Option key={symbol} value={symbol}>
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center">
|
||||
<img
|
||||
alt=""
|
||||
width="20"
|
||||
height="20"
|
||||
src={`/assets/icons/${symbol.toLowerCase()}.svg`}
|
||||
className={`mr-2.5`}
|
||||
/>
|
||||
<span>{symbol}</span>
|
||||
</div>
|
||||
{balance}
|
||||
</div>
|
||||
</Select.Option>
|
||||
))}
|
||||
</Select>
|
||||
<div className="flex justify-between pb-2 pt-4">
|
||||
<div className="text-th-fgd-1">Amount</div>
|
||||
<div className="flex space-x-4">
|
||||
<div
|
||||
className="text-th-fgd-1 underline cursor-pointer default-transition hover:text-th-primary hover:no-underline"
|
||||
onClick={setMaxBorrowForSelectedAsset}
|
||||
>
|
||||
Max
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex">
|
||||
<Input
|
||||
disabled={!borrowTokenSymbol}
|
||||
type="number"
|
||||
min="0"
|
||||
className={`border border-th-fgd-4 flex-grow pr-11`}
|
||||
error={!!invalidAmountMessage}
|
||||
placeholder="0.00"
|
||||
value={inputAmount}
|
||||
onBlur={validateAmountInput}
|
||||
onChange={(e) => onChangeAmountInput(e.target.value)}
|
||||
suffix={borrowTokenSymbol}
|
||||
/>
|
||||
{simulation ? (
|
||||
<Tooltip content="Projected Leverage" className="py-1">
|
||||
<span
|
||||
className={`${getAccountStatusColor(
|
||||
simulation.collateralRatio
|
||||
)} bg-th-bkg-1 border flex font-semibold h-10 items-center justify-center ml-2 rounded text-th-fgd-1 w-14`}
|
||||
>
|
||||
{simulation.leverage < 5
|
||||
? simulation.leverage.toFixed(2)
|
||||
: '>5'}
|
||||
x
|
||||
</span>
|
||||
</Tooltip>
|
||||
) : null}
|
||||
</div>
|
||||
{invalidAmountMessage ? (
|
||||
<div className="flex items-center pt-1.5 text-th-red">
|
||||
<ExclamationCircleIcon className="h-4 w-4 mr-1.5" />
|
||||
{invalidAmountMessage}
|
||||
</div>
|
||||
) : null}
|
||||
<div className="pt-3 pb-4">
|
||||
<Slider
|
||||
disabled={!borrowTokenSymbol}
|
||||
value={sliderPercentage}
|
||||
onChange={(v) => onChangeSlider(v)}
|
||||
step={1}
|
||||
maxButtonTransition={maxButtonTransition}
|
||||
/>
|
||||
</div>
|
||||
<div className={`pt-8 flex justify-center`}>
|
||||
<Button
|
||||
onClick={() => setShowSimulation(true)}
|
||||
disabled={
|
||||
Number(inputAmount) <= 0 || simulation?.collateralRatio < 1.2
|
||||
}
|
||||
className="w-full"
|
||||
>
|
||||
Next
|
||||
</Button>
|
||||
</div>
|
||||
</>
|
||||
) : null}
|
||||
{showSimulation && simulation ? (
|
||||
<>
|
||||
<Modal.Header>
|
||||
<ElementTitle noMarignBottom>Confirm Borrow</ElementTitle>
|
||||
</Modal.Header>
|
||||
{simulation.collateralRatio < 1.2 ? (
|
||||
<div className="border border-th-red mb-4 p-2 rounded">
|
||||
<div className="flex items-center text-th-fgd-1">
|
||||
<ExclamationCircleIcon className="h-4 w-4 mr-1.5 flex-shrink-0 text-th-red" />
|
||||
Prices have changed and increased your leverage. Reduce the
|
||||
borrow amount.
|
||||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
<div className="bg-th-bkg-1 p-4 rounded-lg text-th-fgd-1 text-center">
|
||||
<div className="text-th-fgd-3 pb-1">You're about to withdraw</div>
|
||||
<div className="flex items-center justify-center">
|
||||
<div className="font-semibold relative text-xl">
|
||||
{inputAmount}
|
||||
<span className="absolute bottom-0.5 font-normal ml-1.5 text-xs text-th-fgd-4">
|
||||
{borrowTokenSymbol}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{getBorrowAmount() > 0 ? (
|
||||
<div className="bg-th-bkg-1 mt-2 p-4 rounded-lg text-th-fgd-1 text-center">
|
||||
<div className="flex justify-between pb-2">
|
||||
<div className="text-th-fgd-4">Borrow Amount</div>
|
||||
<div className="text-th-fgd-1">
|
||||
{trimDecimals(
|
||||
getBorrowAmount(),
|
||||
DECIMALS[borrowTokenSymbol]
|
||||
)}{' '}
|
||||
{borrowTokenSymbol}
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex justify-between pb-2">
|
||||
<div className="text-th-fgd-4">Interest APY</div>
|
||||
<div className="text-th-fgd-1">
|
||||
{borrowAssetDetails.interest.toFixed(2)}%
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex justify-between pb-2">
|
||||
<div className="text-th-fgd-4">Price</div>
|
||||
<div className="text-th-fgd-1">
|
||||
${borrowAssetDetails.price}
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex justify-between">
|
||||
<div className="text-th-fgd-4">Available Liquidity</div>
|
||||
<div className="text-th-fgd-1">
|
||||
{borrowAssetDetails.totalDeposits.toFixed(
|
||||
DECIMALS[borrowTokenSymbol]
|
||||
)}{' '}
|
||||
{borrowTokenSymbol}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
<Disclosure>
|
||||
{({ open }) => (
|
||||
<>
|
||||
<Disclosure.Button
|
||||
className={`border border-th-fgd-4 default-transition font-normal mt-4 pl-3 pr-2 py-2.5 ${
|
||||
open ? 'rounded-b-none' : 'rounded-md'
|
||||
} text-th-fgd-1 w-full hover:bg-th-bkg-3 focus:outline-none`}
|
||||
>
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center">
|
||||
<span className="flex h-2 w-2 mr-2.5 relative">
|
||||
<span
|
||||
className={`animate-ping absolute inline-flex h-full w-full rounded-full ${getAccountStatusColor(
|
||||
simulation.collateralRatio,
|
||||
false,
|
||||
true
|
||||
)} opacity-75`}
|
||||
></span>
|
||||
<span
|
||||
className={`relative inline-flex rounded-full h-2 w-2 ${getAccountStatusColor(
|
||||
simulation.collateralRatio,
|
||||
false,
|
||||
true
|
||||
)}`}
|
||||
></span>
|
||||
</span>
|
||||
Account Health Check
|
||||
<Tooltip content="The details of your account after this withdrawal.">
|
||||
<InformationCircleIcon
|
||||
className={`h-5 w-5 ml-2 text-th-fgd-3 cursor-help`}
|
||||
/>
|
||||
</Tooltip>
|
||||
</div>
|
||||
{open ? (
|
||||
<ChevronUpIcon className="h-5 w-5 mr-1" />
|
||||
) : (
|
||||
<ChevronDownIcon className="h-5 w-5 mr-1" />
|
||||
)}
|
||||
</div>
|
||||
</Disclosure.Button>
|
||||
<Disclosure.Panel
|
||||
className={`border border-th-fgd-4 border-t-0 p-4 rounded-b-md`}
|
||||
>
|
||||
<div>
|
||||
<div className="flex justify-between pb-2">
|
||||
<div className="text-th-fgd-4">Account Value</div>
|
||||
<div className="text-th-fgd-1">
|
||||
${simulation.assetsVal.toFixed(2)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex justify-between pb-2">
|
||||
<div className="text-th-fgd-4">Account Risk</div>
|
||||
<div className="text-th-fgd-1">
|
||||
{getAccountStatusColor(
|
||||
simulation.collateralRatio,
|
||||
true
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex justify-between pb-2">
|
||||
<div className="text-th-fgd-4">Leverage</div>
|
||||
<div className="text-th-fgd-1">
|
||||
{simulation.leverage.toFixed(2)}x
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex justify-between">
|
||||
<div className="text-th-fgd-4">Collateral Ratio</div>
|
||||
<div className="text-th-fgd-1">
|
||||
{simulation.collateralRatio * 100 < 200
|
||||
? Math.floor(simulation.collateralRatio * 100)
|
||||
: '>200'}
|
||||
%
|
||||
</div>
|
||||
</div>
|
||||
{simulation.liabsVal > 0.05 ? (
|
||||
<div className="flex justify-between pt-2">
|
||||
<div className="text-th-fgd-4">Borrow Value</div>
|
||||
<div className="text-th-fgd-1">
|
||||
${simulation.liabsVal.toFixed(2)}
|
||||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
</Disclosure.Panel>
|
||||
</>
|
||||
)}
|
||||
</Disclosure>
|
||||
<div className={`mt-5 flex justify-center`}>
|
||||
<Button
|
||||
onClick={handleWithdraw}
|
||||
disabled={
|
||||
Number(inputAmount) <= 0 || simulation.collateralRatio < 1.2
|
||||
}
|
||||
className="w-full"
|
||||
>
|
||||
<div className={`flex items-center justify-center`}>
|
||||
{submitting && <Loading className="-ml-1 mr-3" />}
|
||||
Confirm
|
||||
</div>
|
||||
</Button>
|
||||
</div>
|
||||
<LinkButton
|
||||
className="flex items-center mt-4 text-th-fgd-3"
|
||||
onClick={() => setShowSimulation(false)}
|
||||
>
|
||||
<ChevronLeftIcon className="h-5 w-5 mr-1" />
|
||||
Back
|
||||
</LinkButton>
|
||||
</>
|
||||
) : null}
|
||||
</>
|
||||
</Modal>
|
||||
)
|
||||
}
|
||||
|
||||
export default React.memo(BorrowModal)
|
|
@ -1,63 +1,33 @@
|
|||
import { useEffect, useState } from 'react'
|
||||
import { useCallback, useState } from 'react'
|
||||
import styled from '@emotion/styled'
|
||||
import useMangoStore from '../stores/useMangoStore'
|
||||
import { Menu } from '@headlessui/react'
|
||||
import { DuplicateIcon, LogoutIcon } from '@heroicons/react/outline'
|
||||
import { ChevronUpIcon, ChevronDownIcon } from '@heroicons/react/solid'
|
||||
import {
|
||||
CurrencyDollarIcon,
|
||||
DuplicateIcon,
|
||||
LogoutIcon,
|
||||
} from '@heroicons/react/outline'
|
||||
import { WALLET_PROVIDERS, DEFAULT_PROVIDER } from '../hooks/useWallet'
|
||||
import useLocalStorageState from '../hooks/useLocalStorageState'
|
||||
import { abbreviateAddress, copyToClipboard } from '../utils'
|
||||
import WalletSelect from './WalletSelect'
|
||||
import { WalletIcon } from './icons'
|
||||
import { WalletIcon, ProfileIcon } from './icons'
|
||||
import AccountsModal from './AccountsModal'
|
||||
|
||||
const StyledWalletTypeLabel = styled.div`
|
||||
font-size: 0.65rem;
|
||||
`
|
||||
|
||||
const StyledWalletButtonWrapper = styled.div`
|
||||
width: 196px;
|
||||
`
|
||||
|
||||
const Code = styled.code`
|
||||
border: 1px solid hsla(0, 0%, 39.2%, 0.2);
|
||||
border-radius: 3px;
|
||||
background: hsla(0, 0%, 58.8%, 0.1);
|
||||
font-size: 0.75rem;
|
||||
`
|
||||
|
||||
const WALLET_OPTIONS = [
|
||||
{ name: 'Copy address', icon: <DuplicateIcon /> },
|
||||
{ name: 'Disconnect', icon: <LogoutIcon /> },
|
||||
]
|
||||
|
||||
const ConnectWalletButton = () => {
|
||||
const wallet = useMangoStore((s) => s.wallet.current)
|
||||
const connected = useMangoStore((s) => s.wallet.connected)
|
||||
const set = useMangoStore((s) => s.set)
|
||||
const [isCopied, setIsCopied] = useState(false)
|
||||
const [showAccountsModal, setShowAccountsModal] = useState(false)
|
||||
const [savedProviderUrl] = useLocalStorageState(
|
||||
'walletProvider',
|
||||
DEFAULT_PROVIDER.url
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
if (isCopied) {
|
||||
const timer = setTimeout(() => {
|
||||
setIsCopied(false)
|
||||
}, 2000)
|
||||
return () => clearTimeout(timer)
|
||||
}
|
||||
}, [isCopied])
|
||||
|
||||
const handleWalletMenu = (option) => {
|
||||
if (option === 'Copy address') {
|
||||
copyToClipboard(wallet.publicKey)
|
||||
setIsCopied(true)
|
||||
} else {
|
||||
wallet.disconnect()
|
||||
}
|
||||
}
|
||||
|
||||
const handleWalletConect = () => {
|
||||
wallet.connect()
|
||||
set((state) => {
|
||||
|
@ -65,49 +35,56 @@ const ConnectWalletButton = () => {
|
|||
})
|
||||
}
|
||||
|
||||
const handleCloseAccounts = useCallback(() => {
|
||||
setShowAccountsModal(false)
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<StyledWalletButtonWrapper className="h-14">
|
||||
<>
|
||||
{connected && wallet?.publicKey ? (
|
||||
<Menu>
|
||||
{({ open }) => (
|
||||
<div className="relative h-full">
|
||||
<Menu.Button className="h-full w-full px-3 bg-th-bkg-1 rounded-none focus:outline-none text-th-primary hover:bg-th-bkg-3 hover:text-th-fgd-1">
|
||||
<div className="flex flex-row items-center justify-between">
|
||||
<div className="flex items-center">
|
||||
<WalletIcon className="w-4 h-4 mr-3 text-th-green fill-current" />
|
||||
<Code className="p-1 text-th-fgd-3 font-light">
|
||||
{isCopied
|
||||
? 'Copied!'
|
||||
: abbreviateAddress(wallet.publicKey)}
|
||||
</Code>
|
||||
<div className="relative h-full">
|
||||
<Menu.Button className="bg-th-fgd-4 flex items-center justify-center rounded-full w-9 h-9 text-th-fgd-2 focus:outline-none hover:bg-th-bkg-3 hover:text-th-fgd-3">
|
||||
<ProfileIcon className="fill-current h-5 w-5" />
|
||||
</Menu.Button>
|
||||
<Menu.Items className="bg-th-bkg-1 mt-2 p-1 absolute right-0 shadow-lg outline-none rounded-md w-48 z-20">
|
||||
<Menu.Item>
|
||||
<button
|
||||
className="flex flex-row font-normal items-center rounded-none w-full p-2 hover:bg-th-bkg-2 hover:cursor-pointer focus:outline-none"
|
||||
onClick={() => setShowAccountsModal(true)}
|
||||
>
|
||||
<CurrencyDollarIcon className="h-4 w-4" />
|
||||
<div className="pl-2 text-left">Accounts</div>
|
||||
</button>
|
||||
</Menu.Item>
|
||||
<Menu.Item>
|
||||
<button
|
||||
className="flex flex-row font-normal items-center rounded-none w-full p-2 hover:bg-th-bkg-2 hover:cursor-pointer focus:outline-none"
|
||||
onClick={() => copyToClipboard(wallet?.publicKey)}
|
||||
>
|
||||
<DuplicateIcon className="h-4 w-4" />
|
||||
<div className="pl-2 text-left">Copy address</div>
|
||||
</button>
|
||||
</Menu.Item>
|
||||
<Menu.Item>
|
||||
<button
|
||||
className="flex flex-row font-normal items-center rounded-none w-full p-2 hover:bg-th-bkg-2 hover:cursor-pointer focus:outline-none"
|
||||
onClick={() => wallet.disconnect()}
|
||||
>
|
||||
<LogoutIcon className="h-4 w-4" />
|
||||
<div className="pl-2 text-left">
|
||||
<div className="pb-0.5">Disconnect</div>
|
||||
<div className="text-th-fgd-4 text-xs">
|
||||
{abbreviateAddress(wallet?.publicKey)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="pl-2">
|
||||
{open ? (
|
||||
<ChevronUpIcon className="h-4 w-4" />
|
||||
) : (
|
||||
<ChevronDownIcon className="h-4 w-4" />
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</Menu.Button>
|
||||
<Menu.Items className="z-20 mt-1 p-1 absolute right-0 md:transform md:-translate-x-1/2 md:left-1/2 bg-th-bkg-1 divide-y divide-th-bkg-3 shadow-lg outline-none rounded-md w-48">
|
||||
{WALLET_OPTIONS.map(({ name, icon }) => (
|
||||
<Menu.Item key={name}>
|
||||
<button
|
||||
className="flex flex-row font-normal items-center rounded-none w-full p-2 hover:bg-th-bkg-2 hover:cursor-pointer focus:outline-none"
|
||||
onClick={() => handleWalletMenu(name)}
|
||||
>
|
||||
<div className="w-4 h-4 mr-2">{icon}</div>
|
||||
{name}
|
||||
</button>
|
||||
</Menu.Item>
|
||||
))}
|
||||
</Menu.Items>
|
||||
</div>
|
||||
)}
|
||||
</button>
|
||||
</Menu.Item>
|
||||
</Menu.Items>
|
||||
</div>
|
||||
</Menu>
|
||||
) : (
|
||||
<div className="bg-th-bkg-1 h-full flex divide-x divide-th-bkg-3 justify-between">
|
||||
<div className="bg-th-bkg-1 h-14 flex divide-x divide-th-bkg-3 justify-between">
|
||||
<button
|
||||
onClick={handleWalletConect}
|
||||
disabled={!wallet}
|
||||
|
@ -131,7 +108,13 @@ const ConnectWalletButton = () => {
|
|||
</div>
|
||||
</div>
|
||||
)}
|
||||
</StyledWalletButtonWrapper>
|
||||
{showAccountsModal ? (
|
||||
<AccountsModal
|
||||
onClose={handleCloseAccounts}
|
||||
isOpen={showAccountsModal}
|
||||
/>
|
||||
) : null}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -1,26 +1,60 @@
|
|||
import React, { useMemo, useState } from 'react'
|
||||
import React, { FunctionComponent, useEffect, useMemo, useState } from 'react'
|
||||
import { Disclosure } from '@headlessui/react'
|
||||
import {
|
||||
ExclamationCircleIcon,
|
||||
InformationCircleIcon,
|
||||
} from '@heroicons/react/outline'
|
||||
import {
|
||||
ChevronLeftIcon,
|
||||
ChevronDownIcon,
|
||||
ChevronUpIcon,
|
||||
} from '@heroicons/react/solid'
|
||||
import {
|
||||
nativeToUi,
|
||||
sleep,
|
||||
} from '@blockworks-foundation/mango-client/lib/utils'
|
||||
import { WRAPPED_SOL_MINT } from '@project-serum/serum/lib/token-instructions'
|
||||
import { MarginAccount, uiToNative } from '@blockworks-foundation/mango-client'
|
||||
import Modal from './Modal'
|
||||
import Input from './Input'
|
||||
import AccountSelect from './AccountSelect'
|
||||
import { ElementTitle } from './styles'
|
||||
import useMangoStore from '../stores/useMangoStore'
|
||||
import useMarketList from '../hooks/useMarketList'
|
||||
import { getSymbolForTokenMintAddress } from '../utils/index'
|
||||
import {
|
||||
getSymbolForTokenMintAddress,
|
||||
DECIMALS,
|
||||
trimDecimals,
|
||||
} from '../utils/index'
|
||||
import useConnection from '../hooks/useConnection'
|
||||
import { deposit, initMarginAccountAndDeposit } from '../utils/mango'
|
||||
import { PublicKey } from '@solana/web3.js'
|
||||
import Loading from './Loading'
|
||||
import Button from './Button'
|
||||
import Button, { LinkButton } from './Button'
|
||||
import Tooltip from './Tooltip'
|
||||
import Slider from './Slider'
|
||||
import InlineNotification from './InlineNotification'
|
||||
import { notify } from '../utils/notifications'
|
||||
|
||||
const DepositModal = ({ isOpen, onClose }) => {
|
||||
const [inputAmount, setInputAmount] = useState('')
|
||||
interface DepositModalProps {
|
||||
onClose: () => void
|
||||
isOpen: boolean
|
||||
settleDeficit?: number
|
||||
tokenSymbol?: string
|
||||
}
|
||||
|
||||
const DepositModal: FunctionComponent<DepositModalProps> = ({
|
||||
isOpen,
|
||||
onClose,
|
||||
settleDeficit,
|
||||
tokenSymbol = '',
|
||||
}) => {
|
||||
const [inputAmount, setInputAmount] = useState(settleDeficit || 0)
|
||||
const [submitting, setSubmitting] = useState(false)
|
||||
const [simulation, setSimulation] = useState(null)
|
||||
const [showSimulation, setShowSimulation] = useState(false)
|
||||
const [invalidAmountMessage, setInvalidAmountMessage] = useState('')
|
||||
const [sliderPercentage, setSliderPercentage] = useState(0)
|
||||
const [maxButtonTransition, setMaxButtonTransition] = useState(false)
|
||||
const { getTokenIndex, symbols } = useMarketList()
|
||||
const { connection, programId } = useConnection()
|
||||
const mintDecimals = useMangoStore((s) => s.selectedMangoGroup.mintDecimals)
|
||||
|
@ -35,24 +69,107 @@ const DepositModal = ({ isOpen, onClose }) => {
|
|||
)
|
||||
const [selectedAccount, setSelectedAccount] = useState(depositAccounts[0])
|
||||
|
||||
const prices = useMangoStore((s) => s.selectedMangoGroup.prices)
|
||||
const selectedMangoGroup = useMangoStore((s) => s.selectedMangoGroup.current)
|
||||
const selectedMarginAccount = useMangoStore(
|
||||
(s) => s.selectedMarginAccount.current
|
||||
)
|
||||
const mintAddress = useMemo(
|
||||
() => selectedAccount?.account.mint.toString(),
|
||||
[selectedAccount]
|
||||
)
|
||||
const tokenIndex = useMemo(
|
||||
() => getTokenIndex(mintAddress),
|
||||
[mintAddress, getTokenIndex]
|
||||
)
|
||||
const symbol = getSymbolForTokenMintAddress(
|
||||
selectedAccount?.account?.mint.toString()
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
if (tokenSymbol) {
|
||||
const symbolMint = symbols[tokenSymbol]
|
||||
const symbolAccount = walletAccounts.find(
|
||||
(a) => a.account.mint.toString() === symbolMint
|
||||
)
|
||||
if (symbolAccount) {
|
||||
setSelectedAccount(symbolAccount)
|
||||
} else {
|
||||
setSelectedAccount(null)
|
||||
}
|
||||
}
|
||||
}, [tokenSymbol])
|
||||
|
||||
useEffect(() => {
|
||||
if (!selectedMangoGroup || !selectedMarginAccount || !selectedAccount)
|
||||
return
|
||||
|
||||
const mintDecimals = selectedMangoGroup.mintDecimals[tokenIndex]
|
||||
const groupIndex = selectedMangoGroup.indexes[tokenIndex]
|
||||
const deposits = selectedMarginAccount.getUiDeposit(
|
||||
selectedMangoGroup,
|
||||
tokenIndex
|
||||
)
|
||||
|
||||
// simulate change to deposits based on input amount
|
||||
const newDeposit = Math.max(0, +inputAmount + +deposits)
|
||||
|
||||
// clone MarginAccount and arrays to not modify selectedMarginAccount
|
||||
const simulation = new MarginAccount(null, selectedMarginAccount)
|
||||
simulation.deposits = [...selectedMarginAccount.deposits]
|
||||
simulation.borrows = [...selectedMarginAccount.borrows]
|
||||
|
||||
// update with simulated values
|
||||
simulation.deposits[tokenIndex] =
|
||||
uiToNative(newDeposit, mintDecimals).toNumber() / groupIndex.deposit
|
||||
|
||||
const equity = simulation.computeValue(selectedMangoGroup, prices)
|
||||
const assetsVal = simulation.getAssetsVal(selectedMangoGroup, prices)
|
||||
const liabsVal = simulation.getLiabsVal(selectedMangoGroup, prices)
|
||||
const collateralRatio = simulation.getCollateralRatio(
|
||||
selectedMangoGroup,
|
||||
prices
|
||||
)
|
||||
const leverage = 1 / Math.max(0, collateralRatio - 1)
|
||||
setSimulation({
|
||||
equity,
|
||||
assetsVal,
|
||||
liabsVal,
|
||||
collateralRatio,
|
||||
leverage,
|
||||
})
|
||||
}, [
|
||||
inputAmount,
|
||||
prices,
|
||||
tokenIndex,
|
||||
selectedMarginAccount,
|
||||
selectedMangoGroup,
|
||||
])
|
||||
|
||||
const handleAccountSelect = (account) => {
|
||||
setInputAmount(0)
|
||||
setSliderPercentage(0)
|
||||
setInvalidAmountMessage('')
|
||||
setSelectedAccount(account)
|
||||
setInputAmount('')
|
||||
}
|
||||
|
||||
// TODO: remove duplication in AccountSelect
|
||||
const getBalanceForAccount = (account) => {
|
||||
const mintAddress = account?.account.mint.toString()
|
||||
const balance = nativeToUi(
|
||||
(account?.account.mint.equals(WRAPPED_SOL_MINT)) ? Math.max(account?.account?.amount - (0.05 * 1e9), 0) : account?.account?.amount,
|
||||
account?.account?.amount,
|
||||
mintDecimals[getTokenIndex(mintAddress)]
|
||||
)
|
||||
return balance.toString()
|
||||
|
||||
return balance
|
||||
}
|
||||
|
||||
const setMaxForSelectedAccount = () => {
|
||||
const max = getBalanceForAccount(selectedAccount)
|
||||
setInputAmount(max)
|
||||
setSliderPercentage(100)
|
||||
setInvalidAmountMessage('')
|
||||
setMaxButtonTransition(true)
|
||||
}
|
||||
|
||||
const handleDeposit = () => {
|
||||
|
@ -85,7 +202,6 @@ const DepositModal = ({ isOpen, onClose }) => {
|
|||
message:
|
||||
'Could not perform init margin account and deposit operation',
|
||||
type: 'error',
|
||||
txid: err.txid
|
||||
})
|
||||
onClose()
|
||||
})
|
||||
|
@ -113,62 +229,291 @@ const DepositModal = ({ isOpen, onClose }) => {
|
|||
notify({
|
||||
message: 'Could not perform deposit operation',
|
||||
type: 'error',
|
||||
txid: err.txid
|
||||
})
|
||||
onClose()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const renderAccountRiskStatus = (
|
||||
collateralRatio: number,
|
||||
isRiskLevel?: boolean,
|
||||
isStatusIcon?: boolean
|
||||
) => {
|
||||
if (collateralRatio < 1.25) {
|
||||
return isRiskLevel ? (
|
||||
<div className="text-th-red">High</div>
|
||||
) : isStatusIcon ? (
|
||||
'bg-th-red'
|
||||
) : (
|
||||
'border-th-red text-th-red'
|
||||
)
|
||||
} else if (collateralRatio > 1.25 && collateralRatio < 1.5) {
|
||||
return isRiskLevel ? (
|
||||
<div className="text-th-orange">Moderate</div>
|
||||
) : isStatusIcon ? (
|
||||
'bg-th-orange'
|
||||
) : (
|
||||
'border-th-orange text-th-orange'
|
||||
)
|
||||
} else {
|
||||
return isRiskLevel ? (
|
||||
<div className="text-th-green">Low</div>
|
||||
) : isStatusIcon ? (
|
||||
'bg-th-green'
|
||||
) : (
|
||||
'border-th-green text-th-green'
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
const validateAmountInput = (e) => {
|
||||
const amount = e.target.value
|
||||
if (Number(amount) <= 0) {
|
||||
setInvalidAmountMessage('Enter an amount to deposit')
|
||||
}
|
||||
if (Number(amount) > getBalanceForAccount(selectedAccount)) {
|
||||
setInvalidAmountMessage(
|
||||
'Insufficient balance. Reduce the amount to deposit'
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
const onChangeAmountInput = (amount) => {
|
||||
const max = getBalanceForAccount(selectedAccount)
|
||||
setInputAmount(amount)
|
||||
setSliderPercentage((amount / max) * 100)
|
||||
setInvalidAmountMessage('')
|
||||
}
|
||||
|
||||
const onChangeSlider = async (percentage) => {
|
||||
const max = getBalanceForAccount(selectedAccount)
|
||||
const amount = (percentage / 100) * max
|
||||
setInputAmount(trimDecimals(amount, DECIMALS[symbol]))
|
||||
setSliderPercentage(percentage)
|
||||
setInvalidAmountMessage('')
|
||||
}
|
||||
|
||||
// turn off slider transition for dragging slider handle interaction
|
||||
useEffect(() => {
|
||||
if (maxButtonTransition) {
|
||||
setMaxButtonTransition(false)
|
||||
}
|
||||
}, [maxButtonTransition])
|
||||
|
||||
return (
|
||||
<Modal isOpen={isOpen} onClose={onClose}>
|
||||
<Modal.Header>
|
||||
<div className={`text-th-fgd-3 flex-shrink invisible w-5`}>X</div>
|
||||
<ElementTitle noMarignBottom>Deposit Funds</ElementTitle>
|
||||
</Modal.Header>
|
||||
<>
|
||||
<AccountSelect
|
||||
symbols={symbols}
|
||||
accounts={depositAccounts}
|
||||
selectedAccount={selectedAccount}
|
||||
onSelectAccount={handleAccountSelect}
|
||||
/>
|
||||
<div className="flex justify-between pb-2 pt-4">
|
||||
<div className={`text-th-fgd-1`}>Amount</div>
|
||||
<div
|
||||
className="text-th-fgd-1 underline cursor-pointer default-transition hover:text-th-primary hover:no-underline"
|
||||
onClick={setMaxForSelectedAccount}
|
||||
>
|
||||
Max
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center">
|
||||
<Input
|
||||
type="number"
|
||||
min="0"
|
||||
className={`border border-th-fgd-4 flex-grow pr-11`}
|
||||
placeholder="0.00"
|
||||
value={inputAmount}
|
||||
onChange={(e) => setInputAmount(e.target.value)}
|
||||
suffix={getSymbolForTokenMintAddress(
|
||||
selectedAccount?.account?.mint.toString()
|
||||
)}
|
||||
{!showSimulation ? (
|
||||
<>
|
||||
<Modal.Header>
|
||||
<ElementTitle noMarignBottom>Deposit Funds</ElementTitle>
|
||||
</Modal.Header>
|
||||
{tokenSymbol && !selectedAccount ? (
|
||||
<InlineNotification
|
||||
desc={`Add ${tokenSymbol} to your wallet and fund it with ${tokenSymbol} to deposit.`}
|
||||
title={`No ${tokenSymbol} wallet address found`}
|
||||
type="error"
|
||||
/>
|
||||
) : null}
|
||||
{settleDeficit ? (
|
||||
<InlineNotification
|
||||
desc={`Deposit ${settleDeficit} ${tokenSymbol} before settling your borrow.`}
|
||||
title="Not enough balance to settle"
|
||||
type="error"
|
||||
/>
|
||||
) : null}
|
||||
<AccountSelect
|
||||
symbols={symbols}
|
||||
accounts={depositAccounts}
|
||||
selectedAccount={selectedAccount}
|
||||
onSelectAccount={handleAccountSelect}
|
||||
/>
|
||||
</div>
|
||||
<div className={`mt-5 flex justify-center`}>
|
||||
<Button onClick={handleDeposit} className="w-full">
|
||||
<div className={`flex items-center justify-center`}>
|
||||
{submitting && <Loading className="-ml-1 mr-3" />}
|
||||
{`Deposit ${
|
||||
inputAmount ? inputAmount : ''
|
||||
} ${getSymbolForTokenMintAddress(
|
||||
selectedAccount?.account?.mint.toString()
|
||||
)}
|
||||
`}
|
||||
<div className="flex justify-between pb-2 pt-4">
|
||||
<div className={`text-th-fgd-1`}>Amount</div>
|
||||
<div
|
||||
className="text-th-fgd-1 underline cursor-pointer default-transition hover:text-th-primary hover:no-underline"
|
||||
onClick={setMaxForSelectedAccount}
|
||||
>
|
||||
Max
|
||||
</div>
|
||||
</Button>
|
||||
</div>
|
||||
</>
|
||||
</div>
|
||||
<div className="flex">
|
||||
<Input
|
||||
type="number"
|
||||
min="0"
|
||||
className={`border border-th-fgd-4 flex-grow pr-11`}
|
||||
placeholder="0.00"
|
||||
error={!!invalidAmountMessage}
|
||||
onBlur={validateAmountInput}
|
||||
value={inputAmount}
|
||||
onChange={(e) => onChangeAmountInput(e.target.value)}
|
||||
suffix={symbol}
|
||||
/>
|
||||
{simulation ? (
|
||||
<Tooltip content="Projected Leverage" className="py-1">
|
||||
<span
|
||||
className={`${renderAccountRiskStatus(
|
||||
simulation?.collateralRatio
|
||||
)} bg-th-bkg-1 border flex font-semibold h-10 items-center justify-center ml-2 rounded text-th-fgd-1 w-14`}
|
||||
>
|
||||
{simulation?.leverage < 5
|
||||
? simulation?.leverage.toFixed(2)
|
||||
: '>5'}
|
||||
x
|
||||
</span>
|
||||
</Tooltip>
|
||||
) : null}
|
||||
</div>
|
||||
{invalidAmountMessage ? (
|
||||
<div className="flex items-center pt-1.5 text-th-red">
|
||||
<ExclamationCircleIcon className="h-4 w-4 mr-1.5" />
|
||||
{invalidAmountMessage}
|
||||
</div>
|
||||
) : null}
|
||||
<div className="pt-3 pb-4">
|
||||
<Slider
|
||||
disabled={null}
|
||||
value={sliderPercentage}
|
||||
onChange={(v) => onChangeSlider(v)}
|
||||
step={1}
|
||||
maxButtonTransition={maxButtonTransition}
|
||||
/>
|
||||
</div>
|
||||
<div className={`pt-8 flex justify-center`}>
|
||||
<Button
|
||||
onClick={() => setShowSimulation(true)}
|
||||
className="w-full"
|
||||
disabled={
|
||||
inputAmount <= 0 ||
|
||||
inputAmount > getBalanceForAccount(selectedAccount)
|
||||
}
|
||||
>
|
||||
Next
|
||||
</Button>
|
||||
</div>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Modal.Header>
|
||||
<ElementTitle noMarignBottom>Confirm Deposit</ElementTitle>
|
||||
</Modal.Header>
|
||||
<div className="bg-th-bkg-1 p-4 rounded-lg text-th-fgd-1 text-center">
|
||||
<div className="text-th-fgd-3 pb-1">{`You're about to deposit`}</div>
|
||||
<div className="flex items-center justify-center">
|
||||
<div className="font-semibold relative text-xl">
|
||||
{inputAmount}
|
||||
<span className="absolute bottom-0.5 font-normal ml-1.5 text-xs text-th-fgd-4">
|
||||
{symbol}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{simulation ? (
|
||||
<Disclosure>
|
||||
{({ open }) => (
|
||||
<>
|
||||
<Disclosure.Button
|
||||
className={`border border-th-fgd-4 default-transition font-normal mt-4 pl-3 pr-2 py-2.5 ${
|
||||
open ? 'rounded-b-none' : 'rounded-md'
|
||||
} text-th-fgd-1 w-full hover:bg-th-bkg-3 focus:outline-none`}
|
||||
>
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center">
|
||||
<span className="flex h-2 w-2 mr-2.5 relative">
|
||||
<span
|
||||
className={`animate-ping absolute inline-flex h-full w-full rounded-full ${renderAccountRiskStatus(
|
||||
simulation?.collateralRatio,
|
||||
false,
|
||||
true
|
||||
)} opacity-75`}
|
||||
></span>
|
||||
<span
|
||||
className={`relative inline-flex rounded-full h-2 w-2 ${renderAccountRiskStatus(
|
||||
simulation?.collateralRatio,
|
||||
false,
|
||||
true
|
||||
)}`}
|
||||
></span>
|
||||
</span>
|
||||
Account Health Check
|
||||
<Tooltip content="The details of your account after this deposit.">
|
||||
<InformationCircleIcon
|
||||
className={`h-5 w-5 ml-2 text-th-fgd-3 cursor-help`}
|
||||
/>
|
||||
</Tooltip>
|
||||
</div>
|
||||
{open ? (
|
||||
<ChevronUpIcon className="h-5 w-5 mr-1" />
|
||||
) : (
|
||||
<ChevronDownIcon className="h-5 w-5 mr-1" />
|
||||
)}
|
||||
</div>
|
||||
</Disclosure.Button>
|
||||
<Disclosure.Panel
|
||||
className={`border border-th-fgd-4 border-t-0 p-4 rounded-b-md`}
|
||||
>
|
||||
<div>
|
||||
<div className="flex justify-between pb-2">
|
||||
<div className="text-th-fgd-4">Account Value</div>
|
||||
<div className="text-th-fgd-1">
|
||||
${simulation?.assetsVal.toFixed(2)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex justify-between pb-2">
|
||||
<div className="text-th-fgd-4">Account Risk</div>
|
||||
<div className="text-th-fgd-1">
|
||||
{renderAccountRiskStatus(
|
||||
simulation?.collateralRatio,
|
||||
true
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex justify-between pb-2">
|
||||
<div className="text-th-fgd-4">Leverage</div>
|
||||
<div className="text-th-fgd-1">
|
||||
{simulation?.leverage.toFixed(2)}x
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex justify-between">
|
||||
<div className="text-th-fgd-4">Collateral Ratio</div>
|
||||
<div className="text-th-fgd-1">
|
||||
{simulation?.collateralRatio * 100 < 200
|
||||
? Math.floor(simulation?.collateralRatio * 100)
|
||||
: '>200'}
|
||||
%
|
||||
</div>
|
||||
</div>
|
||||
{simulation?.liabsVal > 0.05 ? (
|
||||
<div className="flex justify-between pt-2">
|
||||
<div className="text-th-fgd-4">Borrow Value</div>
|
||||
<div className="text-th-fgd-1">
|
||||
${simulation?.liabsVal.toFixed(2)}
|
||||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
</Disclosure.Panel>
|
||||
</>
|
||||
)}
|
||||
</Disclosure>
|
||||
) : null}
|
||||
<div className={`mt-5 flex justify-center`}>
|
||||
<Button onClick={handleDeposit} className="w-full">
|
||||
<div className={`flex items-center justify-center`}>
|
||||
{submitting && <Loading className="-ml-1 mr-3" />}
|
||||
Confirm
|
||||
</div>
|
||||
</Button>
|
||||
</div>
|
||||
<LinkButton
|
||||
className="flex items-center mt-4 text-th-fgd-3"
|
||||
onClick={() => setShowSimulation(false)}
|
||||
>
|
||||
<ChevronLeftIcon className="h-5 w-5 mr-1" />
|
||||
Back
|
||||
</LinkButton>
|
||||
</>
|
||||
)}
|
||||
</Modal>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@ import Tooltip from './Tooltip'
|
|||
type DropMenuProps = {
|
||||
button: ReactNode
|
||||
buttonClassName?: string
|
||||
disabled?: boolean
|
||||
onChange: (...args: any[]) => any
|
||||
options: Array<any>
|
||||
toolTipContent?: string
|
||||
|
@ -14,6 +15,7 @@ type DropMenuProps = {
|
|||
const DropMenu: FunctionComponent<DropMenuProps> = ({
|
||||
button,
|
||||
buttonClassName,
|
||||
disabled,
|
||||
value,
|
||||
onChange,
|
||||
options,
|
||||
|
@ -24,7 +26,10 @@ const DropMenu: FunctionComponent<DropMenuProps> = ({
|
|||
<Listbox value={value} onChange={onChange}>
|
||||
{({ open }) => (
|
||||
<>
|
||||
<Listbox.Button className={`${buttonClassName} default-transition`}>
|
||||
<Listbox.Button
|
||||
className={`${buttonClassName} default-transition`}
|
||||
disabled={disabled}
|
||||
>
|
||||
{toolTipContent && !open ? (
|
||||
<Tooltip content={toolTipContent} className="text-xs py-1">
|
||||
{button}
|
||||
|
@ -45,7 +50,7 @@ const DropMenu: FunctionComponent<DropMenuProps> = ({
|
|||
leaveTo="opacity-0 translate-y-1"
|
||||
>
|
||||
<Listbox.Options
|
||||
className={`absolute z-10 mt-4 p-1 right-0 md:transform md:-translate-x-1/2 md:left-1/2 w-24 bg-th-bkg-1 divide-y divide-th-bkg-3 shadow-lg outline-none rounded-md`}
|
||||
className={`absolute z-10 mt-4 p-1 right-0 w-24 bg-th-bkg-1 divide-y divide-th-bkg-3 shadow-lg outline-none rounded-md`}
|
||||
>
|
||||
{options.map((option) => (
|
||||
<Listbox.Option key={option.name} value={option.name}>
|
||||
|
|
|
@ -0,0 +1,33 @@
|
|||
import { FunctionComponent, ReactNode } from 'react'
|
||||
import Button from './Button'
|
||||
|
||||
interface EmptyStateProps {
|
||||
buttonText?: string
|
||||
icon: ReactNode
|
||||
onClickButton?: () => void
|
||||
desc?: string
|
||||
title: string
|
||||
}
|
||||
|
||||
const EmptyState: FunctionComponent<EmptyStateProps> = ({
|
||||
buttonText,
|
||||
icon,
|
||||
onClickButton,
|
||||
desc,
|
||||
title,
|
||||
}) => {
|
||||
return (
|
||||
<div className="flex flex-col items-center text-th-fgd-1 px-4 pb-2 rounded-lg">
|
||||
<div className="w-6 h-6 mb-1 text-th-primary">{icon}</div>
|
||||
<div className="font-bold text-lg pb-1">{title}</div>
|
||||
{desc ? <p className="mb-0 text-center">{desc}</p> : null}
|
||||
{buttonText && onClickButton ? (
|
||||
<Button className="mt-2" onClick={onClickButton}>
|
||||
{buttonText}
|
||||
</Button>
|
||||
) : null}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default EmptyState
|
|
@ -0,0 +1,44 @@
|
|||
import { FunctionComponent } from 'react'
|
||||
import {
|
||||
CheckCircleIcon,
|
||||
ExclamationCircleIcon,
|
||||
ExclamationIcon,
|
||||
} from '@heroicons/react/outline'
|
||||
|
||||
interface InlineNotificationProps {
|
||||
desc?: string
|
||||
title?: string
|
||||
type: string
|
||||
}
|
||||
|
||||
const InlineNotification: FunctionComponent<InlineNotificationProps> = ({
|
||||
desc,
|
||||
title,
|
||||
type,
|
||||
}) => (
|
||||
<div
|
||||
className={`border ${
|
||||
type === 'error'
|
||||
? 'border-th-red'
|
||||
: type === 'success'
|
||||
? 'border-th-green'
|
||||
: 'border-th-orange'
|
||||
} flex items-center mb-4 p-2.5 rounded-md`}
|
||||
>
|
||||
{type === 'error' ? (
|
||||
<ExclamationCircleIcon className="flex-shrink-0 h-5 w-5 mr-2 text-th-red" />
|
||||
) : null}
|
||||
{type === 'success' ? (
|
||||
<CheckCircleIcon className="flex-shrink-0 h-5 w-5 mr-2 text-th-green" />
|
||||
) : null}
|
||||
{type === 'warning' ? (
|
||||
<ExclamationIcon className="flex-shrink-0 h-5 w-5 mr-2 text-th-orange" />
|
||||
) : null}
|
||||
<div>
|
||||
<div className="pb-1 text-th-fgd-1">{title}</div>
|
||||
<div className="font-normal text-th-fgd-3 text-xs">{desc}</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
export default InlineNotification
|
|
@ -51,17 +51,11 @@ const MarginAccountSelect = ({
|
|||
className={className}
|
||||
>
|
||||
{marginAccounts.length ? (
|
||||
marginAccounts
|
||||
.slice()
|
||||
.sort(
|
||||
(a, b) =>
|
||||
(a.publicKey.toBase58() > b.publicKey.toBase58() && 1) || -1
|
||||
)
|
||||
.map((ma, index) => (
|
||||
<Select.Option key={index} value={ma.publicKey.toString()}>
|
||||
{abbreviateAddress(ma.publicKey)}
|
||||
</Select.Option>
|
||||
))
|
||||
marginAccounts.map((ma, index) => (
|
||||
<Select.Option key={index} value={ma.publicKey.toString()}>
|
||||
{abbreviateAddress(ma.publicKey)}
|
||||
</Select.Option>
|
||||
))
|
||||
) : (
|
||||
<Select.Option value className="text-th-fgd-4">
|
||||
No Margin Accounts Found
|
||||
|
|
|
@ -1,23 +1,24 @@
|
|||
import { useCallback, useState } from 'react'
|
||||
import {
|
||||
ExternalLinkIcon,
|
||||
InformationCircleIcon,
|
||||
} from '@heroicons/react/outline'
|
||||
import Link from 'next/link'
|
||||
import { Menu } from '@headlessui/react'
|
||||
import { DotsHorizontalIcon } from '@heroicons/react/outline'
|
||||
import FloatingElement from './FloatingElement'
|
||||
import { ElementTitle } from './styles'
|
||||
import useMangoStore from '../stores/useMangoStore'
|
||||
import useMarketList from '../hooks/useMarketList'
|
||||
import { floorToDecimal, tokenPrecision } from '../utils/index'
|
||||
import {
|
||||
abbreviateAddress,
|
||||
floorToDecimal,
|
||||
tokenPrecision,
|
||||
} from '../utils/index'
|
||||
import DepositModal from './DepositModal'
|
||||
import WithdrawModal from './WithdrawModal'
|
||||
import BorrowModal from './BorrowModal'
|
||||
import Button from './Button'
|
||||
import Tooltip from './Tooltip'
|
||||
import MarginAccountSelect from './MarginAccountSelect'
|
||||
import { MarginAccount } from '@blockworks-foundation/mango-client'
|
||||
import AccountsModal from './AccountsModal'
|
||||
|
||||
export default function MarginBalances() {
|
||||
const setMangoStore = useMangoStore((s) => s.set)
|
||||
const marginAccounts = useMangoStore((s) => s.marginAccounts)
|
||||
const selectedMangoGroup = useMangoStore((s) => s.selectedMangoGroup.current)
|
||||
const selectedMarginAccount = useMangoStore(
|
||||
(s) => s.selectedMarginAccount.current
|
||||
|
@ -30,6 +31,8 @@ export default function MarginBalances() {
|
|||
|
||||
const [showDepositModal, setShowDepositModal] = useState(false)
|
||||
const [showWithdrawModal, setShowWithdrawModal] = useState(false)
|
||||
const [showBorrowModal, setShowBorrowModal] = useState(false)
|
||||
const [showAccountsModal, setShowAccountsModal] = useState(false)
|
||||
|
||||
const handleCloseDeposit = useCallback(() => {
|
||||
setShowDepositModal(false)
|
||||
|
@ -39,39 +42,75 @@ export default function MarginBalances() {
|
|||
setShowWithdrawModal(false)
|
||||
}, [])
|
||||
|
||||
const handleMarginAccountChange = (marginAccount: MarginAccount) => {
|
||||
setMangoStore((state) => {
|
||||
state.selectedMarginAccount.current = marginAccount
|
||||
})
|
||||
}
|
||||
const handleCloseBorrow = useCallback(() => {
|
||||
setShowBorrowModal(false)
|
||||
}, [])
|
||||
|
||||
const handleCloseAccounts = useCallback(() => {
|
||||
setShowAccountsModal(false)
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<>
|
||||
<FloatingElement>
|
||||
<ElementTitle>
|
||||
Margin Account
|
||||
<Tooltip
|
||||
content={
|
||||
<AddressTooltip
|
||||
owner={selectedMarginAccount?.owner.toString()}
|
||||
marginAccount={selectedMarginAccount?.publicKey.toString()}
|
||||
/>
|
||||
}
|
||||
>
|
||||
<div>
|
||||
<InformationCircleIcon
|
||||
className={`h-5 w-5 ml-2 text-th-primary cursor-help`}
|
||||
/>
|
||||
<div className="flex justify-between pb-5">
|
||||
<div className="w-8 h-8" />
|
||||
<div className="flex flex-col items-center">
|
||||
<ElementTitle noMarignBottom>Margin Account</ElementTitle>
|
||||
{selectedMarginAccount ? (
|
||||
<Link href={'/account'}>
|
||||
<a className="pt-1 text-th-fgd-3 text-xs underline hover:no-underline">
|
||||
{abbreviateAddress(selectedMarginAccount?.publicKey)}
|
||||
</a>
|
||||
</Link>
|
||||
) : null}
|
||||
</div>
|
||||
<Menu>
|
||||
<div className="relative h-full">
|
||||
<Menu.Button
|
||||
className="flex items-center justify-center rounded-full bg-th-bkg-3 w-8 h-8 hover:text-th-primary focus:outline-none disabled:cursor-not-allowed disabled:opacity-50"
|
||||
disabled={!connected}
|
||||
>
|
||||
<DotsHorizontalIcon className="w-5 h-5" />
|
||||
</Menu.Button>
|
||||
<Menu.Items className="bg-th-bkg-1 mt-2 p-1 absolute right-0 shadow-lg outline-none rounded-md w-48 z-20">
|
||||
<Menu.Item>
|
||||
<button
|
||||
className="flex flex-row font-normal items-center rounded-none w-full p-2 hover:bg-th-bkg-2 hover:cursor-pointer focus:outline-none"
|
||||
onClick={() => setShowAccountsModal(true)}
|
||||
>
|
||||
<div className="pl-2 text-left">Change Account</div>
|
||||
</button>
|
||||
</Menu.Item>
|
||||
<Menu.Item>
|
||||
<button
|
||||
className="flex flex-row font-normal items-center rounded-none w-full p-2 hover:bg-th-bkg-2 hover:cursor-pointer focus:outline-none disabled:cursor-not-allowed disabled:opacity-50"
|
||||
disabled={!selectedMarginAccount}
|
||||
onClick={() => setShowBorrowModal(true)}
|
||||
>
|
||||
<div className="pl-2 text-left">Borrow</div>
|
||||
</button>
|
||||
</Menu.Item>
|
||||
<Menu.Item>
|
||||
<button
|
||||
className="flex flex-row font-normal items-center rounded-none w-full p-2 hover:bg-th-bkg-2 hover:cursor-pointer focus:outline-none"
|
||||
onClick={() => setShowDepositModal(true)}
|
||||
>
|
||||
<div className="pl-2 text-left">Deposit</div>
|
||||
</button>
|
||||
</Menu.Item>
|
||||
<Menu.Item>
|
||||
<button
|
||||
className="flex flex-row font-normal items-center rounded-none w-full p-2 hover:bg-th-bkg-2 hover:cursor-pointer focus:outline-none disabled:cursor-not-allowed disabled:opacity-50"
|
||||
disabled={!selectedMarginAccount}
|
||||
onClick={() => setShowWithdrawModal(true)}
|
||||
>
|
||||
<div className="pl-2 text-left">Withdraw</div>
|
||||
</button>
|
||||
</Menu.Item>
|
||||
</Menu.Items>
|
||||
</div>
|
||||
</Tooltip>
|
||||
</ElementTitle>
|
||||
<div>
|
||||
{marginAccounts.length > 1 ? (
|
||||
<MarginAccountSelect
|
||||
onChange={handleMarginAccountChange}
|
||||
className="mb-2"
|
||||
/>
|
||||
) : null}
|
||||
</Menu>
|
||||
</div>
|
||||
{selectedMangoGroup ? (
|
||||
<table className={`min-w-full`}>
|
||||
|
@ -96,7 +135,10 @@ export default function MarginBalances() {
|
|||
scope="col"
|
||||
className="flex-auto font-normal flex justify-end items-center"
|
||||
>
|
||||
<Tooltip content="Deposit APR and Borrow APY">
|
||||
<Tooltip
|
||||
className="text-xs py-1"
|
||||
content="Deposit APR and Borrow APY"
|
||||
>
|
||||
<div>Deposits / Borrows</div>
|
||||
</Tooltip>
|
||||
</th>
|
||||
|
@ -147,27 +189,6 @@ export default function MarginBalances() {
|
|||
</tbody>
|
||||
</table>
|
||||
) : null}
|
||||
<div className={`flex justify-center items-center mt-4`}>
|
||||
<Button
|
||||
onClick={() => setShowDepositModal(true)}
|
||||
className="w-1/2"
|
||||
disabled={!connected || loadingMarginAccount}
|
||||
>
|
||||
<span>Deposit</span>
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => setShowWithdrawModal(true)}
|
||||
className="ml-4 w-1/2"
|
||||
disabled={
|
||||
!connected || !selectedMarginAccount || loadingMarginAccount
|
||||
}
|
||||
>
|
||||
<span>Withdraw</span>
|
||||
</Button>
|
||||
</div>
|
||||
<div className={`text-center mt-5 text-th-fgd-3 text-xs`}>
|
||||
Settle funds in the Balances tab
|
||||
</div>
|
||||
</FloatingElement>
|
||||
{showDepositModal && (
|
||||
<DepositModal isOpen={showDepositModal} onClose={handleCloseDeposit} />
|
||||
|
@ -178,61 +199,15 @@ export default function MarginBalances() {
|
|||
onClose={handleCloseWithdraw}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
const AddressTooltip = ({
|
||||
owner,
|
||||
marginAccount,
|
||||
}: {
|
||||
owner?: string
|
||||
marginAccount?: string
|
||||
}) => {
|
||||
return (
|
||||
<>
|
||||
{owner && marginAccount ? (
|
||||
<>
|
||||
<div className={`flex flex-nowrap text-th-fgd-3`}>
|
||||
Margin Account:
|
||||
<a
|
||||
className="text-th-fgd-1 default-transition hover:text-th-primary"
|
||||
href={'https://explorer.solana.com/address/' + marginAccount}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<div className={`ml-2 flex items-center`}>
|
||||
<span className={`underline`}>
|
||||
{marginAccount.toString().substr(0, 5) +
|
||||
'...' +
|
||||
marginAccount.toString().substr(-5)}
|
||||
</span>
|
||||
<ExternalLinkIcon className={`h-4 w-4 ml-1`} />
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
<div className={`flex flex-nowrap text-th-fgd-3 pt-2`}>
|
||||
Account Owner:
|
||||
<a
|
||||
className="text-th-fgd-1 default-transition hover:text-th-primary"
|
||||
href={'https://explorer.solana.com/address/' + owner}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<div className={`ml-2 flex items-center`}>
|
||||
<span className={`underline`}>
|
||||
{owner.toString().substr(0, 5) +
|
||||
'...' +
|
||||
owner.toString().substr(-5)}
|
||||
</span>
|
||||
<ExternalLinkIcon className={`h-4 w-4 ml-1`} />
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
</>
|
||||
) : (
|
||||
'Connect a wallet and deposit funds to start trading'
|
||||
{showBorrowModal && (
|
||||
<BorrowModal isOpen={showBorrowModal} onClose={handleCloseBorrow} />
|
||||
)}
|
||||
{showAccountsModal ? (
|
||||
<AccountsModal
|
||||
onClose={handleCloseAccounts}
|
||||
isOpen={showAccountsModal}
|
||||
/>
|
||||
) : null}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -0,0 +1,209 @@
|
|||
import React, { FunctionComponent, useEffect, useMemo, useState } from 'react'
|
||||
import { ExclamationCircleIcon } from '@heroicons/react/outline'
|
||||
import {
|
||||
nativeToUi,
|
||||
sleep,
|
||||
} from '@blockworks-foundation/mango-client/lib/utils'
|
||||
import Input from './Input'
|
||||
import AccountSelect from './AccountSelect'
|
||||
import { ElementTitle } from './styles'
|
||||
import useMangoStore from '../stores/useMangoStore'
|
||||
import useMarketList from '../hooks/useMarketList'
|
||||
import {
|
||||
getSymbolForTokenMintAddress,
|
||||
DECIMALS,
|
||||
trimDecimals,
|
||||
} from '../utils/index'
|
||||
import useConnection from '../hooks/useConnection'
|
||||
import { initMarginAccountAndDeposit } from '../utils/mango'
|
||||
import { PublicKey } from '@solana/web3.js'
|
||||
import Loading from './Loading'
|
||||
import Button from './Button'
|
||||
import Slider from './Slider'
|
||||
import { notify } from '../utils/notifications'
|
||||
|
||||
interface NewAccountProps {
|
||||
onAccountCreation?: (x?) => void
|
||||
}
|
||||
|
||||
const NewAccount: FunctionComponent<NewAccountProps> = ({
|
||||
onAccountCreation,
|
||||
}) => {
|
||||
const [inputAmount, setInputAmount] = useState(0)
|
||||
const [submitting, setSubmitting] = useState(false)
|
||||
const [invalidAmountMessage, setInvalidAmountMessage] = useState('')
|
||||
const [sliderPercentage, setSliderPercentage] = useState(0)
|
||||
const [maxButtonTransition, setMaxButtonTransition] = useState(false)
|
||||
const { getTokenIndex, symbols } = useMarketList()
|
||||
const { connection, programId } = useConnection()
|
||||
const mintDecimals = useMangoStore((s) => s.selectedMangoGroup.mintDecimals)
|
||||
const walletAccounts = useMangoStore((s) => s.wallet.balances)
|
||||
const actions = useMangoStore((s) => s.actions)
|
||||
const depositAccounts = useMemo(
|
||||
() =>
|
||||
walletAccounts.filter((acc) =>
|
||||
Object.values(symbols).includes(acc.account.mint.toString())
|
||||
),
|
||||
[symbols, walletAccounts]
|
||||
)
|
||||
const [selectedAccount, setSelectedAccount] = useState(depositAccounts[0])
|
||||
|
||||
const symbol = getSymbolForTokenMintAddress(
|
||||
selectedAccount?.account?.mint.toString()
|
||||
)
|
||||
|
||||
const handleAccountSelect = (account) => {
|
||||
setInputAmount(0)
|
||||
setSliderPercentage(0)
|
||||
setInvalidAmountMessage('')
|
||||
setSelectedAccount(account)
|
||||
}
|
||||
|
||||
// TODO: remove duplication in AccountSelect
|
||||
const getBalanceForAccount = (account) => {
|
||||
const mintAddress = account?.account.mint.toString()
|
||||
const balance = nativeToUi(
|
||||
account?.account?.amount,
|
||||
mintDecimals[getTokenIndex(mintAddress)]
|
||||
)
|
||||
|
||||
return balance
|
||||
}
|
||||
|
||||
const setMaxForSelectedAccount = () => {
|
||||
const max = getBalanceForAccount(selectedAccount)
|
||||
setInputAmount(max)
|
||||
setSliderPercentage(100)
|
||||
setInvalidAmountMessage('')
|
||||
setMaxButtonTransition(true)
|
||||
}
|
||||
|
||||
const handleNewAccountDeposit = () => {
|
||||
setSubmitting(true)
|
||||
const mangoGroup = useMangoStore.getState().selectedMangoGroup.current
|
||||
const wallet = useMangoStore.getState().wallet.current
|
||||
|
||||
initMarginAccountAndDeposit(
|
||||
connection,
|
||||
new PublicKey(programId),
|
||||
mangoGroup,
|
||||
wallet,
|
||||
selectedAccount.account.mint,
|
||||
selectedAccount.publicKey,
|
||||
Number(inputAmount)
|
||||
)
|
||||
.then(async (_response: Array<any>) => {
|
||||
await sleep(1000)
|
||||
actions.fetchWalletBalances()
|
||||
actions.fetchMarginAccounts()
|
||||
setSubmitting(false)
|
||||
onAccountCreation(_response[0].publicKey)
|
||||
})
|
||||
.catch((err) => {
|
||||
setSubmitting(false)
|
||||
console.error(err)
|
||||
notify({
|
||||
message:
|
||||
'Could not perform init margin account and deposit operation',
|
||||
type: 'error',
|
||||
})
|
||||
onAccountCreation()
|
||||
})
|
||||
}
|
||||
|
||||
const validateAmountInput = (e) => {
|
||||
const amount = e.target.value
|
||||
if (Number(amount) <= 0) {
|
||||
setInvalidAmountMessage('Enter an amount to deposit')
|
||||
}
|
||||
if (Number(amount) > getBalanceForAccount(selectedAccount)) {
|
||||
setInvalidAmountMessage(
|
||||
'Insufficient balance. Reduce the amount to deposit'
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
const onChangeAmountInput = (amount) => {
|
||||
const max = getBalanceForAccount(selectedAccount)
|
||||
setInputAmount(amount)
|
||||
setSliderPercentage((amount / max) * 100)
|
||||
setInvalidAmountMessage('')
|
||||
}
|
||||
|
||||
const onChangeSlider = async (percentage) => {
|
||||
const max = getBalanceForAccount(selectedAccount)
|
||||
const amount = (percentage / 100) * max
|
||||
setInputAmount(trimDecimals(amount, DECIMALS[symbol]))
|
||||
setSliderPercentage(percentage)
|
||||
setInvalidAmountMessage('')
|
||||
}
|
||||
|
||||
// turn off slider transition for dragging slider handle interaction
|
||||
useEffect(() => {
|
||||
if (maxButtonTransition) {
|
||||
setMaxButtonTransition(false)
|
||||
}
|
||||
}, [maxButtonTransition])
|
||||
|
||||
return (
|
||||
<>
|
||||
<ElementTitle noMarignBottom>Create Margin Account</ElementTitle>
|
||||
<div className="text-th-fgd-3 text-center pb-4 pt-2">
|
||||
Make a deposit to initialize a new margin account
|
||||
</div>
|
||||
<AccountSelect
|
||||
symbols={symbols}
|
||||
accounts={depositAccounts}
|
||||
selectedAccount={selectedAccount}
|
||||
onSelectAccount={handleAccountSelect}
|
||||
/>
|
||||
<div className="flex justify-between pb-2 pt-4">
|
||||
<div className={`text-th-fgd-1`}>Amount</div>
|
||||
<div
|
||||
className="text-th-fgd-1 underline cursor-pointer default-transition hover:text-th-primary hover:no-underline"
|
||||
onClick={setMaxForSelectedAccount}
|
||||
>
|
||||
Max
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex">
|
||||
<Input
|
||||
type="number"
|
||||
min="0"
|
||||
className={`border border-th-fgd-4 flex-grow pr-11`}
|
||||
placeholder="0.00"
|
||||
error={!!invalidAmountMessage}
|
||||
onBlur={validateAmountInput}
|
||||
value={inputAmount}
|
||||
onChange={(e) => onChangeAmountInput(e.target.value)}
|
||||
suffix={symbol}
|
||||
/>
|
||||
</div>
|
||||
{invalidAmountMessage ? (
|
||||
<div className="flex items-center pt-1.5 text-th-red">
|
||||
<ExclamationCircleIcon className="h-4 w-4 mr-1.5" />
|
||||
{invalidAmountMessage}
|
||||
</div>
|
||||
) : null}
|
||||
<div className="pt-3 pb-4">
|
||||
<Slider
|
||||
disabled={null}
|
||||
value={sliderPercentage}
|
||||
onChange={(v) => onChangeSlider(v)}
|
||||
step={1}
|
||||
maxButtonTransition={maxButtonTransition}
|
||||
/>
|
||||
</div>
|
||||
<div className={`pt-8 flex justify-center`}>
|
||||
<Button onClick={handleNewAccountDeposit} className="w-full">
|
||||
<div className={`flex items-center justify-center`}>
|
||||
{submitting && <Loading className="-ml-1 mr-3" />}
|
||||
Create Account
|
||||
</div>
|
||||
</Button>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default NewAccount
|
|
@ -1,8 +1,10 @@
|
|||
import { useState } from 'react'
|
||||
import { TrashIcon } from '@heroicons/react/outline'
|
||||
import Link from 'next/link'
|
||||
import { ArrowSmDownIcon } from '@heroicons/react/solid'
|
||||
import { useRouter } from 'next/router'
|
||||
import { useOpenOrders } from '../hooks/useOpenOrders'
|
||||
import { cancelOrderAndSettle } from '../utils/mango'
|
||||
import Button from './Button'
|
||||
import Button, { LinkButton } from './Button'
|
||||
import Loading from './Loading'
|
||||
import { PublicKey } from '@solana/web3.js'
|
||||
import useConnection from '../hooks/useConnection'
|
||||
|
@ -10,19 +12,22 @@ import useMangoStore from '../stores/useMangoStore'
|
|||
import { notify } from '../utils/notifications'
|
||||
import { Table, Thead, Tbody, Tr, Th, Td } from 'react-super-responsive-table'
|
||||
import SideBadge from './SideBadge'
|
||||
import { useSortableData } from '../hooks/useSortableData'
|
||||
|
||||
const OpenOrdersTable = () => {
|
||||
const { asPath } = useRouter()
|
||||
const openOrders = useOpenOrders()
|
||||
const { items, requestSort, sortConfig } = useSortableData(openOrders)
|
||||
const [cancelId, setCancelId] = useState(null)
|
||||
const { connection, programId } = useConnection()
|
||||
const actions = useMangoStore((s) => s.actions)
|
||||
|
||||
const handleCancelOrder = async (order) => {
|
||||
const wallet = useMangoStore.getState().wallet.current
|
||||
const selectedMangoGroup = useMangoStore.getState().selectedMangoGroup
|
||||
.current
|
||||
const selectedMarginAccount = useMangoStore.getState().selectedMarginAccount
|
||||
.current
|
||||
const selectedMangoGroup =
|
||||
useMangoStore.getState().selectedMangoGroup.current
|
||||
const selectedMarginAccount =
|
||||
useMangoStore.getState().selectedMarginAccount.current
|
||||
setCancelId(order?.orderId)
|
||||
try {
|
||||
if (!selectedMangoGroup || !selectedMarginAccount) return
|
||||
|
@ -50,39 +55,93 @@ const OpenOrdersTable = () => {
|
|||
}
|
||||
|
||||
return (
|
||||
<div className={`flex flex-col py-6`}>
|
||||
<div className={`flex flex-col py-4`}>
|
||||
<div className={`-my-2 overflow-x-auto sm:-mx-6 lg:-mx-8`}>
|
||||
<div className={`align-middle inline-block min-w-full sm:px-6 lg:px-8`}>
|
||||
{openOrders && openOrders.length > 0 ? (
|
||||
<div
|
||||
className={`shadow overflow-hidden border-b border-th-bkg-2 sm:rounded-md`}
|
||||
>
|
||||
<div className={`shadow overflow-hidden border-b border-th-bkg-2`}>
|
||||
<Table className={`min-w-full divide-y divide-th-bkg-2`}>
|
||||
<Thead>
|
||||
<Tr className="text-th-fgd-3">
|
||||
<Tr className="text-th-fgd-3 text-xs">
|
||||
<Th
|
||||
scope="col"
|
||||
className={`px-6 py-3 text-left font-normal`}
|
||||
>
|
||||
Market
|
||||
<LinkButton
|
||||
className="flex items-center no-underline"
|
||||
onClick={() => requestSort('marketName')}
|
||||
>
|
||||
Market
|
||||
<ArrowSmDownIcon
|
||||
className={`default-transition flex-shrink-0 h-4 w-4 ml-1 ${
|
||||
sortConfig?.key === 'marketName'
|
||||
? sortConfig.direction === 'ascending'
|
||||
? 'transform rotate-180'
|
||||
: 'transform rotate-360'
|
||||
: null
|
||||
}`}
|
||||
/>
|
||||
</LinkButton>
|
||||
</Th>
|
||||
<Th
|
||||
scope="col"
|
||||
className={`px-6 py-3 text-left font-normal`}
|
||||
>
|
||||
Side
|
||||
<LinkButton
|
||||
className="flex items-center no-underline"
|
||||
onClick={() => requestSort('side')}
|
||||
>
|
||||
Side
|
||||
<ArrowSmDownIcon
|
||||
className={`default-transition flex-shrink-0 h-4 w-4 ml-1 ${
|
||||
sortConfig?.key === 'side'
|
||||
? sortConfig.direction === 'ascending'
|
||||
? 'transform rotate-180'
|
||||
: 'transform rotate-360'
|
||||
: null
|
||||
}`}
|
||||
/>
|
||||
</LinkButton>
|
||||
</Th>
|
||||
<Th
|
||||
scope="col"
|
||||
className={`px-6 py-3 text-left font-normal`}
|
||||
>
|
||||
Size
|
||||
<LinkButton
|
||||
className="flex items-center no-underline"
|
||||
onClick={() => requestSort('size')}
|
||||
>
|
||||
Size
|
||||
<ArrowSmDownIcon
|
||||
className={`default-transition flex-shrink-0 h-4 w-4 ml-1 ${
|
||||
sortConfig?.key === 'size'
|
||||
? sortConfig.direction === 'ascending'
|
||||
? 'transform rotate-180'
|
||||
: 'transform rotate-360'
|
||||
: null
|
||||
}`}
|
||||
/>
|
||||
</LinkButton>
|
||||
</Th>
|
||||
<Th
|
||||
scope="col"
|
||||
className={`px-6 py-3 text-left font-normal`}
|
||||
>
|
||||
Price
|
||||
<LinkButton
|
||||
className="flex items-center no-underline"
|
||||
onClick={() => requestSort('price')}
|
||||
>
|
||||
Price
|
||||
<ArrowSmDownIcon
|
||||
className={`default-transition flex-shrink-0 h-4 w-4 ml-1 ${
|
||||
sortConfig?.key === 'price'
|
||||
? sortConfig.direction === 'ascending'
|
||||
? 'transform rotate-180'
|
||||
: 'transform rotate-360'
|
||||
: null
|
||||
}`}
|
||||
/>
|
||||
</LinkButton>
|
||||
</Th>
|
||||
<Th scope="col" className={`relative px-6 py-3`}>
|
||||
<span className={`sr-only`}>Edit</span>
|
||||
|
@ -90,7 +149,7 @@ const OpenOrdersTable = () => {
|
|||
</Tr>
|
||||
</Thead>
|
||||
<Tbody>
|
||||
{openOrders.map((order, index) => (
|
||||
{items.map((order, index) => (
|
||||
<Tr
|
||||
key={`${order.orderId}${order.side}`}
|
||||
className={`border-b border-th-bkg-3
|
||||
|
@ -98,37 +157,58 @@ const OpenOrdersTable = () => {
|
|||
`}
|
||||
>
|
||||
<Td
|
||||
className={`px-6 py-4 whitespace-nowrap text-sm text-th-fgd-1`}
|
||||
className={`px-6 py-3 whitespace-nowrap text-sm text-th-fgd-1`}
|
||||
>
|
||||
{order.marketName}
|
||||
<div className="flex items-center">
|
||||
<img
|
||||
alt=""
|
||||
width="20"
|
||||
height="20"
|
||||
src={`/assets/icons/${order.marketName
|
||||
.split('/')[0]
|
||||
.toLowerCase()}.svg`}
|
||||
className={`mr-2.5`}
|
||||
/>
|
||||
<div>{order.marketName}</div>
|
||||
</div>
|
||||
</Td>
|
||||
<Td
|
||||
className={`px-6 py-4 whitespace-nowrap text-sm text-th-fgd-1`}
|
||||
className={`px-6 py-3 whitespace-nowrap text-sm text-th-fgd-1`}
|
||||
>
|
||||
<SideBadge side={order.side} />
|
||||
</Td>
|
||||
<Td
|
||||
className={`px-6 py-4 whitespace-nowrap text-sm text-th-fgd-1`}
|
||||
className={`px-6 py-3 whitespace-nowrap text-sm text-th-fgd-1`}
|
||||
>
|
||||
{order.size}
|
||||
</Td>
|
||||
<Td
|
||||
className={`px-6 py-4 whitespace-nowrap text-sm text-th-fgd-1`}
|
||||
className={`px-6 py-3 whitespace-nowrap text-sm text-th-fgd-1`}
|
||||
>
|
||||
{order.price}
|
||||
</Td>
|
||||
<Td className={`px-6 py-4 whitespace-nowrap text-left`}>
|
||||
<Button
|
||||
onClick={() => handleCancelOrder(order)}
|
||||
className={`flex items-center md:ml-auto px-2 py-1 text-xs`}
|
||||
>
|
||||
{cancelId + '' === order?.orderId + '' ? (
|
||||
<Loading className="-ml-1 mr-3" />
|
||||
) : (
|
||||
<TrashIcon className={`h-4 w-4 mr-1`} />
|
||||
)}
|
||||
<span>Cancel</span>
|
||||
</Button>
|
||||
<Td className={`px-6 py-3 whitespace-nowrap text-left`}>
|
||||
<div className={`flex justify-end`}>
|
||||
{/* Todo: support order modification */}
|
||||
{/* <Button
|
||||
onClick={() =>
|
||||
console.log('trigger modify order modal')
|
||||
}
|
||||
className={`text-xs pt-0 pb-0 h-8 pl-3 pr-3`}
|
||||
>
|
||||
Modify
|
||||
</Button> */}
|
||||
<Button
|
||||
onClick={() => handleCancelOrder(order)}
|
||||
className={`ml-3 text-xs pt-0 pb-0 h-8 pl-3 pr-3`}
|
||||
>
|
||||
{cancelId + '' === order?.orderId + '' ? (
|
||||
<Loading />
|
||||
) : (
|
||||
<span>Cancel</span>
|
||||
)}
|
||||
</Button>
|
||||
</div>
|
||||
</Td>
|
||||
</Tr>
|
||||
))}
|
||||
|
@ -139,7 +219,17 @@ const OpenOrdersTable = () => {
|
|||
<div
|
||||
className={`w-full text-center py-6 bg-th-bkg-1 text-th-fgd-3 rounded-md`}
|
||||
>
|
||||
No open orders
|
||||
No open orders.
|
||||
{asPath === '/account' ? (
|
||||
<Link href={'/'}>
|
||||
<a
|
||||
className={`inline-flex ml-2 py-0
|
||||
`}
|
||||
>
|
||||
Make a trade
|
||||
</a>
|
||||
</Link>
|
||||
) : null}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
const PageBodyContainer = ({ children }) => (
|
||||
<div className="min-h-screen grid grid-cols-12 gap-4 pb-10">
|
||||
<div className="col-span-12 px-6 md:col-start-2 md:col-span-10 lg:col-start-3 lg:col-span-8 2xl:col-start-5 2xl:col-span-6">
|
||||
<div className="col-span-12 px-6 lg:col-start-2 lg:col-span-10 2xl:col-start-5 2xl:col-span-6">
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -7,7 +7,7 @@ type SideBadgeProps = {
|
|||
const SideBadge: FunctionComponent<SideBadgeProps> = ({ side }) => {
|
||||
return (
|
||||
<div
|
||||
className={`rounded-md inline-block ${
|
||||
className={`rounded inline-block ${
|
||||
side === 'buy'
|
||||
? 'border border-th-green text-th-green'
|
||||
: 'border border-th-red text-th-red'
|
||||
|
|
|
@ -17,9 +17,7 @@ const ThemeSwitch = () => {
|
|||
// When mounted on client, now we can show the UI
|
||||
useEffect(() => setMounted(true), [])
|
||||
|
||||
if (!mounted) return null
|
||||
|
||||
return (
|
||||
return mounted ? (
|
||||
<DropMenu
|
||||
button={
|
||||
<div className="flex items-center justify-center rounded-full bg-th-bkg-3 w-8 h-8">
|
||||
|
@ -32,6 +30,8 @@ const ThemeSwitch = () => {
|
|||
options={THEMES}
|
||||
toolTipContent="Change Theme"
|
||||
/>
|
||||
) : (
|
||||
<div className="bg-th-bkg-3 rounded-full w-8 h-8" />
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -1,5 +1,9 @@
|
|||
import { useState } from 'react'
|
||||
import { MenuIcon, XIcon } from '@heroicons/react/outline'
|
||||
import { ChevronUpIcon, ChevronDownIcon } from '@heroicons/react/solid'
|
||||
import { Menu } from '@headlessui/react'
|
||||
import Link from 'next/link'
|
||||
import { useRouter } from 'next/router'
|
||||
import MenuItem from './MenuItem'
|
||||
import ThemeSwitch from './ThemeSwitch'
|
||||
import useMangoStore from '../stores/useMangoStore'
|
||||
|
@ -10,6 +14,7 @@ const TopBar = () => {
|
|||
const connected = useMangoStore((s) => s.wallet.connected)
|
||||
const wallet = useMangoStore((s) => s.wallet.current)
|
||||
const [showMenu, setShowMenu] = useState(false)
|
||||
const { asPath } = useRouter()
|
||||
|
||||
return (
|
||||
<>
|
||||
|
@ -24,11 +29,55 @@ const TopBar = () => {
|
|||
alt="next"
|
||||
/>
|
||||
</div>
|
||||
<div className={`hidden md:flex md:space-x-6 md:ml-4 py-2`}>
|
||||
<div
|
||||
className={`hidden md:flex md:items-center md:space-x-6 md:ml-4 py-2`}
|
||||
>
|
||||
<MenuItem href="/">Trade</MenuItem>
|
||||
<MenuItem href="/stats">Stats</MenuItem>
|
||||
<MenuItem href="/account">Account</MenuItem>
|
||||
<MenuItem href="/borrow">Borrow</MenuItem>
|
||||
<MenuItem href="/alerts">Alerts</MenuItem>
|
||||
<MenuItem href="https://docs.mango.markets/">Learn</MenuItem>
|
||||
<Menu>
|
||||
{({ open }) => (
|
||||
<div className="relative">
|
||||
<Menu.Button className="flex items-center hover:text-th-primary focus:outline-none">
|
||||
More
|
||||
<div className="pl-1">
|
||||
{open ? (
|
||||
<ChevronUpIcon className="h-5 w-5" />
|
||||
) : (
|
||||
<ChevronDownIcon className="h-5 w-5" />
|
||||
)}
|
||||
</div>
|
||||
</Menu.Button>
|
||||
<Menu.Items className="absolute z-10 mt-4 p-1 right-0 md:transform md:-translate-x-1/2 md:left-1/2 w-24 bg-th-bkg-1 divide-y divide-th-bkg-3 shadow-lg outline-none rounded-md">
|
||||
<Menu.Item>
|
||||
<Link href="/stats">
|
||||
<a
|
||||
className={`block text-th-fgd-1 font-bold items-center p-2 hover:text-th-primary hover:opacity-100
|
||||
${
|
||||
asPath === '/stats'
|
||||
? `text-th-primary`
|
||||
: `border-transparent hover:border-th-primary`
|
||||
}
|
||||
`}
|
||||
>
|
||||
Stats
|
||||
</a>
|
||||
</Link>
|
||||
</Menu.Item>
|
||||
<Menu.Item>
|
||||
<Link href="https://docs.mango.markets/">
|
||||
<a
|
||||
className={`block text-th-fgd-1 font-bold items-center p-2 hover:text-th-primary hover:opacity-100`}
|
||||
>
|
||||
Learn
|
||||
</a>
|
||||
</Link>
|
||||
</Menu.Item>
|
||||
</Menu.Items>
|
||||
</div>
|
||||
)}
|
||||
</Menu>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center">
|
||||
|
|
|
@ -1,12 +1,17 @@
|
|||
import { ArrowSmDownIcon } from '@heroicons/react/solid'
|
||||
import useTradeHistory from '../hooks/useTradeHistory'
|
||||
import Link from 'next/link'
|
||||
import { useRouter } from 'next/router'
|
||||
import { Table, Thead, Tbody, Tr, Th, Td } from 'react-super-responsive-table'
|
||||
import SideBadge from './SideBadge'
|
||||
// import useMangoStore from '../stores/useMangoStore'
|
||||
// import Loading from './Loading'
|
||||
import { LinkButton } from './Button'
|
||||
import { useSortableData } from '../hooks/useSortableData'
|
||||
|
||||
const TradeHistoryTable = () => {
|
||||
const { asPath } = useRouter()
|
||||
const tradeHistory = useTradeHistory()
|
||||
// const connected = useMangoStore((s) => s.wallet.connected)
|
||||
const { items, requestSort, sortConfig } = useSortableData(tradeHistory)
|
||||
|
||||
const renderTradeDateTime = (timestamp) => {
|
||||
const date = new Date(timestamp)
|
||||
return (
|
||||
|
@ -18,62 +23,178 @@ const TradeHistoryTable = () => {
|
|||
}
|
||||
|
||||
return (
|
||||
<div className={`flex flex-col py-6`}>
|
||||
<div className={`flex flex-col py-4`}>
|
||||
<div className={`-my-2 overflow-x-auto sm:-mx-6 lg:-mx-8`}>
|
||||
<div className={`align-middle inline-block min-w-full sm:px-6 lg:px-8`}>
|
||||
{tradeHistory && tradeHistory.length ? (
|
||||
<div
|
||||
className={`shadow overflow-hidden border-b border-th-bkg-2 sm:rounded-md`}
|
||||
>
|
||||
<div className={`shadow overflow-hidden border-b border-th-bkg-2`}>
|
||||
<Table className={`min-w-full divide-y divide-th-bkg-2`}>
|
||||
<Thead>
|
||||
<Tr>
|
||||
<Tr className="text-th-fgd-3 text-xs">
|
||||
<Th
|
||||
scope="col"
|
||||
className={`px-6 py-3 text-left font-normal`}
|
||||
>
|
||||
Market
|
||||
<LinkButton
|
||||
className="flex items-center no-underline"
|
||||
onClick={() => requestSort('market')}
|
||||
>
|
||||
Market
|
||||
<ArrowSmDownIcon
|
||||
className={`default-transition flex-shrink-0 h-4 w-4 ml-1 ${
|
||||
sortConfig?.key === 'market'
|
||||
? sortConfig.direction === 'ascending'
|
||||
? 'transform rotate-180'
|
||||
: 'transform rotate-360'
|
||||
: null
|
||||
}`}
|
||||
/>
|
||||
</LinkButton>
|
||||
</Th>
|
||||
<Th
|
||||
scope="col"
|
||||
className={`px-6 py-3 text-left font-normal`}
|
||||
>
|
||||
Side
|
||||
<LinkButton
|
||||
className="flex items-center no-underline"
|
||||
onClick={() => requestSort('side')}
|
||||
>
|
||||
Side
|
||||
<ArrowSmDownIcon
|
||||
className={`default-transition flex-shrink-0 h-4 w-4 ml-1 ${
|
||||
sortConfig?.key === 'side'
|
||||
? sortConfig.direction === 'ascending'
|
||||
? 'transform rotate-180'
|
||||
: 'transform rotate-360'
|
||||
: null
|
||||
}`}
|
||||
/>
|
||||
</LinkButton>
|
||||
</Th>
|
||||
<Th
|
||||
scope="col"
|
||||
className={`px-6 py-3 text-left font-normal`}
|
||||
>
|
||||
Size
|
||||
<LinkButton
|
||||
className="flex items-center no-underline"
|
||||
onClick={() => requestSort('size')}
|
||||
>
|
||||
Size
|
||||
<ArrowSmDownIcon
|
||||
className={`default-transition flex-shrink-0 h-4 w-4 ml-1 ${
|
||||
sortConfig?.key === 'size'
|
||||
? sortConfig.direction === 'ascending'
|
||||
? 'transform rotate-180'
|
||||
: 'transform rotate-360'
|
||||
: null
|
||||
}`}
|
||||
/>
|
||||
</LinkButton>
|
||||
</Th>
|
||||
<Th
|
||||
scope="col"
|
||||
className={`px-6 py-3 text-left font-normal`}
|
||||
>
|
||||
Price
|
||||
<LinkButton
|
||||
className="flex items-center no-underline"
|
||||
onClick={() => requestSort('price')}
|
||||
>
|
||||
Price
|
||||
<ArrowSmDownIcon
|
||||
className={`default-transition flex-shrink-0 h-4 w-4 ml-1 ${
|
||||
sortConfig?.key === 'price'
|
||||
? sortConfig.direction === 'ascending'
|
||||
? 'transform rotate-180'
|
||||
: 'transform rotate-360'
|
||||
: null
|
||||
}`}
|
||||
/>
|
||||
</LinkButton>
|
||||
</Th>
|
||||
<Th
|
||||
scope="col"
|
||||
className={`px-6 py-3 text-left font-normal`}
|
||||
>
|
||||
Liquidity
|
||||
<LinkButton
|
||||
className="flex items-center no-underline"
|
||||
onClick={() => requestSort('value')}
|
||||
>
|
||||
Value
|
||||
<ArrowSmDownIcon
|
||||
className={`default-transition flex-shrink-0 h-4 w-4 ml-1 ${
|
||||
sortConfig?.key === 'value'
|
||||
? sortConfig.direction === 'ascending'
|
||||
? 'transform rotate-180'
|
||||
: 'transform rotate-360'
|
||||
: null
|
||||
}`}
|
||||
/>
|
||||
</LinkButton>
|
||||
</Th>
|
||||
<Th
|
||||
scope="col"
|
||||
className={`px-6 py-3 text-left font-normal`}
|
||||
>
|
||||
Fees
|
||||
<LinkButton
|
||||
className="flex items-center no-underline"
|
||||
onClick={() => requestSort('liquidity')}
|
||||
>
|
||||
Liquidity
|
||||
<ArrowSmDownIcon
|
||||
className={`default-transition flex-shrink-0 h-4 w-4 ml-1 ${
|
||||
sortConfig?.key === 'liquidity'
|
||||
? sortConfig.direction === 'ascending'
|
||||
? 'transform rotate-180'
|
||||
: 'transform rotate-360'
|
||||
: null
|
||||
}`}
|
||||
/>
|
||||
</LinkButton>
|
||||
</Th>
|
||||
<Th
|
||||
scope="col"
|
||||
className={`px-6 py-3 text-left font-normal`}
|
||||
>
|
||||
Approx Date/Time
|
||||
<LinkButton
|
||||
className="flex items-center no-underline"
|
||||
onClick={() => requestSort('feeCost')}
|
||||
>
|
||||
Fee
|
||||
<ArrowSmDownIcon
|
||||
className={`default-transition flex-shrink-0 h-4 w-4 ml-1 ${
|
||||
sortConfig?.key === 'feeCost'
|
||||
? sortConfig.direction === 'ascending'
|
||||
? 'transform rotate-180'
|
||||
: 'transform rotate-360'
|
||||
: null
|
||||
}`}
|
||||
/>
|
||||
</LinkButton>
|
||||
</Th>
|
||||
<Th
|
||||
scope="col"
|
||||
className={`px-6 py-3 text-left font-normal`}
|
||||
>
|
||||
<LinkButton
|
||||
className="flex items-center no-underline"
|
||||
onClick={() => requestSort('loadTimestamp')}
|
||||
>
|
||||
Approx Date
|
||||
<ArrowSmDownIcon
|
||||
className={`default-transition flex-shrink-0 h-4 w-4 ml-1 ${
|
||||
sortConfig?.key === 'loadTimestamp'
|
||||
? sortConfig.direction === 'ascending'
|
||||
? 'transform rotate-180'
|
||||
: 'transform rotate-360'
|
||||
: null
|
||||
}`}
|
||||
/>
|
||||
</LinkButton>
|
||||
</Th>
|
||||
</Tr>
|
||||
</Thead>
|
||||
<Tbody>
|
||||
{tradeHistory.map((trade, index) => (
|
||||
{items.map((trade, index) => (
|
||||
<Tr
|
||||
key={`${trade.orderId}${trade.side}${trade.uuid}`}
|
||||
className={`border-b border-th-bkg-3
|
||||
|
@ -83,7 +204,18 @@ const TradeHistoryTable = () => {
|
|||
<Td
|
||||
className={`px-6 py-4 whitespace-nowrap text-sm text-th-fgd-1`}
|
||||
>
|
||||
{trade.marketName}
|
||||
<div className="flex items-center">
|
||||
<img
|
||||
alt=""
|
||||
width="20"
|
||||
height="20"
|
||||
src={`/assets/icons/${trade.marketName
|
||||
.split('/')[0]
|
||||
.toLowerCase()}.svg`}
|
||||
className={`mr-2.5`}
|
||||
/>
|
||||
<div>{trade.marketName}</div>
|
||||
</div>
|
||||
</Td>
|
||||
<Td
|
||||
className={`px-6 py-4 whitespace-nowrap text-sm text-th-fgd-1`}
|
||||
|
@ -100,6 +232,11 @@ const TradeHistoryTable = () => {
|
|||
>
|
||||
{trade.price}
|
||||
</Td>
|
||||
<Td
|
||||
className={`px-6 py-4 whitespace-nowrap text-sm text-th-fgd-1`}
|
||||
>
|
||||
${(trade.price * trade.size).toFixed(2)}
|
||||
</Td>
|
||||
<Td
|
||||
className={`px-6 py-4 whitespace-nowrap text-sm text-th-fgd-1`}
|
||||
>
|
||||
|
@ -126,7 +263,17 @@ const TradeHistoryTable = () => {
|
|||
<div
|
||||
className={`w-full text-center py-6 bg-th-bkg-1 text-th-fgd-3 rounded-md`}
|
||||
>
|
||||
No trade history
|
||||
No trade history.
|
||||
{asPath === '/account' ? (
|
||||
<Link href={'/'}>
|
||||
<a
|
||||
className={`inline-flex ml-2 py-0
|
||||
`}
|
||||
>
|
||||
Make a trade
|
||||
</a>
|
||||
</Link>
|
||||
) : null}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
|
|
@ -24,13 +24,13 @@ export const defaultLayouts = {
|
|||
{ i: 'orderbook', x: 6, y: 0, w: 3, h: 17 },
|
||||
{ i: 'tradeForm', x: 9, y: 0, w: 3, h: 12 },
|
||||
{ i: 'marketTrades', x: 6, y: 1, w: 3, h: 13 },
|
||||
{ i: 'balanceInfo', x: 9, y: 1, w: 3, h: 15 },
|
||||
{ i: 'balanceInfo', x: 9, y: 1, w: 3, h: 13 },
|
||||
{ i: 'userInfo', x: 0, y: 2, w: 9, h: 17 },
|
||||
{ i: 'marginInfo', x: 9, y: 2, w: 3, h: 13 },
|
||||
],
|
||||
lg: [
|
||||
{ i: 'tvChart', x: 0, y: 0, w: 8, h: 28, minW: 2 },
|
||||
{ i: 'balanceInfo', x: 8, y: 0, w: 4, h: 15, minW: 2 },
|
||||
{ i: 'tvChart', x: 0, y: 0, w: 8, h: 26, minW: 2 },
|
||||
{ i: 'balanceInfo', x: 8, y: 0, w: 4, h: 13, minW: 2 },
|
||||
{ i: 'marginInfo', x: 8, y: 1, w: 4, h: 13, minW: 2 },
|
||||
{ i: 'orderbook', x: 0, y: 2, w: 4, h: 17, minW: 2 },
|
||||
{ i: 'tradeForm', x: 4, y: 2, w: 4, h: 17, minW: 3 },
|
||||
|
@ -38,8 +38,8 @@ export const defaultLayouts = {
|
|||
{ i: 'userInfo', x: 0, y: 3, w: 12, h: 17, minW: 6 },
|
||||
],
|
||||
md: [
|
||||
{ i: 'tvChart', x: 0, y: 0, w: 8, h: 28, minW: 2 },
|
||||
{ i: 'balanceInfo', x: 8, y: 0, w: 4, h: 15, minW: 2 },
|
||||
{ i: 'tvChart', x: 0, y: 0, w: 8, h: 26, minW: 2 },
|
||||
{ i: 'balanceInfo', x: 8, y: 0, w: 4, h: 13, minW: 2 },
|
||||
{ i: 'marginInfo', x: 8, y: 1, w: 4, h: 13, minW: 2 },
|
||||
{ i: 'orderbook', x: 0, y: 2, w: 4, h: 17, minW: 2 },
|
||||
{ i: 'tradeForm', x: 4, y: 2, w: 4, h: 17, minW: 3 },
|
||||
|
@ -48,8 +48,8 @@ export const defaultLayouts = {
|
|||
],
|
||||
sm: [
|
||||
{ i: 'tvChart', x: 0, y: 0, w: 12, h: 25, minW: 6 },
|
||||
{ i: 'balanceInfo', x: 0, y: 1, w: 6, h: 15, minW: 2 },
|
||||
{ i: 'marginInfo', x: 6, y: 1, w: 6, h: 15, minW: 2 },
|
||||
{ i: 'balanceInfo', x: 0, y: 1, w: 6, h: 13, minW: 2 },
|
||||
{ i: 'marginInfo', x: 6, y: 1, w: 6, h: 13, minW: 2 },
|
||||
{ i: 'tradeForm', x: 0, y: 2, w: 12, h: 13, minW: 3 },
|
||||
{ i: 'orderbook', x: 0, y: 3, w: 6, h: 17, minW: 3 },
|
||||
{ i: 'marketTrades', x: 6, y: 3, w: 6, h: 17, minW: 2 },
|
||||
|
@ -57,7 +57,7 @@ export const defaultLayouts = {
|
|||
],
|
||||
xs: [
|
||||
{ i: 'tvChart', x: 0, y: 0, w: 0, h: 0, minW: 6 },
|
||||
{ i: 'balanceInfo', x: 0, y: 1, w: 6, h: 15, minW: 2 },
|
||||
{ i: 'balanceInfo', x: 0, y: 1, w: 6, h: 13, minW: 2 },
|
||||
{ i: 'marginInfo', x: 0, y: 2, w: 6, h: 13, minW: 2 },
|
||||
{ i: 'tradeForm', x: 0, y: 3, w: 12, h: 13, minW: 3 },
|
||||
{ i: 'orderbook', x: 0, y: 4, w: 6, h: 17, minW: 3 },
|
||||
|
|
|
@ -1,10 +1,11 @@
|
|||
import React, { useEffect, useMemo, useState } from 'react'
|
||||
import React, { FunctionComponent, useEffect, useMemo, useState } from 'react'
|
||||
import Modal from './Modal'
|
||||
import Input from './Input'
|
||||
import { ElementTitle } from './styles'
|
||||
import useMangoStore from '../stores/useMangoStore'
|
||||
import useMarketList from '../hooks/useMarketList'
|
||||
import {
|
||||
DECIMALS,
|
||||
floorToDecimal,
|
||||
tokenPrecision,
|
||||
displayDepositsForMarginAccount,
|
||||
|
@ -31,8 +32,20 @@ import { PublicKey } from '@solana/web3.js'
|
|||
import { MarginAccount, uiToNative } from '@blockworks-foundation/mango-client'
|
||||
import Select from './Select'
|
||||
|
||||
const WithdrawModal = ({ isOpen, onClose }) => {
|
||||
const [withdrawTokenSymbol, setWithdrawTokenSymbol] = useState('USDC')
|
||||
interface WithdrawModalProps {
|
||||
onClose: () => void
|
||||
isOpen: boolean
|
||||
tokenSymbol?: string
|
||||
}
|
||||
|
||||
const WithdrawModal: FunctionComponent<WithdrawModalProps> = ({
|
||||
isOpen,
|
||||
onClose,
|
||||
tokenSymbol = '',
|
||||
}) => {
|
||||
const [withdrawTokenSymbol, setWithdrawTokenSymbol] = useState(
|
||||
tokenSymbol || 'USDC'
|
||||
)
|
||||
const [inputAmount, setInputAmount] = useState(0)
|
||||
const [invalidAmountMessage, setInvalidAmountMessage] = useState('')
|
||||
const [maxAmount, setMaxAmount] = useState(0)
|
||||
|
@ -54,13 +67,6 @@ const WithdrawModal = ({ isOpen, onClose }) => {
|
|||
() => getTokenIndex(symbols[withdrawTokenSymbol]),
|
||||
[withdrawTokenSymbol, getTokenIndex]
|
||||
)
|
||||
const DECIMALS = {
|
||||
BTC: 6,
|
||||
ETH: 5,
|
||||
SOL: 3,
|
||||
SRM: 2,
|
||||
USDC: 2,
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (!selectedMangoGroup || !selectedMarginAccount || !withdrawTokenSymbol)
|
||||
|
@ -346,7 +352,7 @@ const WithdrawModal = ({ isOpen, onClose }) => {
|
|||
<div className="pb-2 text-th-fgd-1">Asset</div>
|
||||
<Select
|
||||
value={
|
||||
withdrawTokenSymbol ? (
|
||||
withdrawTokenSymbol && selectedMarginAccount ? (
|
||||
<div className="flex items-center justify-between w-full">
|
||||
<div className="flex items-center">
|
||||
<img
|
||||
|
@ -463,7 +469,7 @@ const WithdrawModal = ({ isOpen, onClose }) => {
|
|||
maxButtonTransition={maxButtonTransition}
|
||||
/>
|
||||
</div>
|
||||
<div className={`mt-5 flex justify-center`}>
|
||||
<div className={`pt-8 flex justify-center`}>
|
||||
<Button
|
||||
onClick={() => setShowSimulation(true)}
|
||||
disabled={
|
||||
|
|
|
@ -0,0 +1,281 @@
|
|||
import { useCallback, useState } from 'react'
|
||||
import { Table, Thead, Tbody, Tr, Th, Td } from 'react-super-responsive-table'
|
||||
import { InformationCircleIcon } from '@heroicons/react/outline'
|
||||
import useMangoStore from '../../stores/useMangoStore'
|
||||
import { settleAllTrades } from '../../utils/mango'
|
||||
import { useBalances } from '../../hooks/useBalances'
|
||||
import useConnection from '../../hooks/useConnection'
|
||||
import { tokenPrecision } from '../../utils/index'
|
||||
import { notify } from '../../utils/notifications'
|
||||
import { sleep } from '../../utils'
|
||||
import { PublicKey } from '@solana/web3.js'
|
||||
import DepositModal from '../DepositModal'
|
||||
import WithdrawModal from '../WithdrawModal'
|
||||
import Button from '../Button'
|
||||
import Tooltip from '../Tooltip'
|
||||
|
||||
export default function AccountAssets() {
|
||||
const balances = useBalances()
|
||||
const { programId, connection } = useConnection()
|
||||
const actions = useMangoStore((s) => s.actions)
|
||||
const selectedMangoGroup = useMangoStore((s) => s.selectedMangoGroup.current)
|
||||
const selectedMarginAccount = useMangoStore(
|
||||
(s) => s.selectedMarginAccount.current
|
||||
)
|
||||
const loadingMarginAccount = useMangoStore(
|
||||
(s) => s.selectedMarginAccount.initialLoad
|
||||
)
|
||||
const connected = useMangoStore((s) => s.wallet.connected)
|
||||
|
||||
const prices = useMangoStore((s) => s.selectedMangoGroup.prices)
|
||||
|
||||
const [showDepositModal, setShowDepositModal] = useState(false)
|
||||
const [showWithdrawModal, setShowWithdrawModal] = useState(false)
|
||||
const [withdrawSymbol, setWithdrawSymbol] = useState('')
|
||||
const [depositSymbol, setDepositSymbol] = useState('')
|
||||
|
||||
const handleCloseDeposit = useCallback(() => {
|
||||
setShowDepositModal(false)
|
||||
}, [])
|
||||
|
||||
const handleCloseWithdraw = useCallback(() => {
|
||||
setShowWithdrawModal(false)
|
||||
}, [])
|
||||
|
||||
const handleShowWithdraw = (symbol) => {
|
||||
setWithdrawSymbol(symbol)
|
||||
setShowWithdrawModal(true)
|
||||
}
|
||||
|
||||
const handleShowDeposit = (symbol) => {
|
||||
setDepositSymbol(symbol)
|
||||
setShowDepositModal(true)
|
||||
}
|
||||
|
||||
async function handleSettleAllTrades() {
|
||||
const markets = Object.values(
|
||||
useMangoStore.getState().selectedMangoGroup.markets
|
||||
)
|
||||
const marginAccount = useMangoStore.getState().selectedMarginAccount.current
|
||||
const mangoGroup = useMangoStore.getState().selectedMangoGroup.current
|
||||
const wallet = useMangoStore.getState().wallet.current
|
||||
|
||||
try {
|
||||
await settleAllTrades(
|
||||
connection,
|
||||
new PublicKey(programId),
|
||||
mangoGroup,
|
||||
marginAccount,
|
||||
markets,
|
||||
wallet
|
||||
)
|
||||
await sleep(250)
|
||||
actions.fetchMarginAccounts()
|
||||
} catch (e) {
|
||||
console.warn('Error settling all:', e)
|
||||
if (e.message === 'No unsettled funds') {
|
||||
notify({
|
||||
message: 'There are no unsettled funds',
|
||||
type: 'error',
|
||||
})
|
||||
} else {
|
||||
notify({
|
||||
message: 'Error settling funds',
|
||||
description: e.message,
|
||||
txid: e.txid,
|
||||
type: 'error',
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return selectedMarginAccount ? (
|
||||
<>
|
||||
<div className="sm:flex sm:items-center sm:justify-between pb-2">
|
||||
<div className="pb-2 sm:pb-0 text-th-fgd-1 text-lg">Your Assets</div>
|
||||
{balances.length > 0 ? (
|
||||
<div className="border border-th-green flex items-center justify-between p-2 rounded">
|
||||
<div className="pr-4 text-xs text-th-fgd-3">Total Asset Value:</div>
|
||||
<span>
|
||||
$
|
||||
{balances
|
||||
.reduce(
|
||||
(acc, d, i) =>
|
||||
acc +
|
||||
(d.marginDeposits + d.orders + d.unsettled) * prices[i],
|
||||
0
|
||||
)
|
||||
.toFixed(2)}
|
||||
</span>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
{balances.length > 0 &&
|
||||
balances.find(({ unsettled }) => unsettled > 0) ? (
|
||||
<div
|
||||
className={`flex items-center justify-between px-6 py-4 my-2 rounded-md bg-th-bkg-1`}
|
||||
>
|
||||
<div className="flex items-center text-fgd-1 font-semibold pr-4">
|
||||
You have unsettled funds
|
||||
<Tooltip content="Use the Settle All button to move unsettled funds to your deposits.">
|
||||
<div>
|
||||
<InformationCircleIcon
|
||||
className={`h-5 w-5 ml-2 text-th-primary cursor-help`}
|
||||
/>
|
||||
</div>
|
||||
</Tooltip>
|
||||
</div>
|
||||
<Button onClick={handleSettleAllTrades}>Settle All</Button>
|
||||
</div>
|
||||
) : null}
|
||||
{selectedMangoGroup && balances.length > 0 ? (
|
||||
<div className={`flex flex-col py-4`}>
|
||||
<div className={`-my-2 overflow-x-auto sm:-mx-6 lg:-mx-8`}>
|
||||
<div
|
||||
className={`align-middle inline-block min-w-full sm:px-6 lg:px-8`}
|
||||
>
|
||||
<Table className="min-w-full divide-y divide-th-bkg-2">
|
||||
<Thead>
|
||||
<Tr className="text-th-fgd-3 text-xs">
|
||||
<Th
|
||||
scope="col"
|
||||
className={`px-6 py-3 text-left font-normal`}
|
||||
>
|
||||
Asset
|
||||
</Th>
|
||||
<Th
|
||||
scope="col"
|
||||
className={`px-6 py-3 text-left font-normal`}
|
||||
>
|
||||
Available
|
||||
</Th>
|
||||
<Th
|
||||
scope="col"
|
||||
className={`px-6 py-3 text-left font-normal`}
|
||||
>
|
||||
In Orders
|
||||
</Th>
|
||||
<Th
|
||||
scope="col"
|
||||
className={`px-6 py-3 text-left font-normal`}
|
||||
>
|
||||
Unsettled
|
||||
</Th>
|
||||
<Th
|
||||
scope="col"
|
||||
className={`px-6 py-3 text-left font-normal`}
|
||||
>
|
||||
Value
|
||||
</Th>
|
||||
<Th scope="col" className="px-6 py-3 text-left font-normal">
|
||||
Interest APR
|
||||
</Th>
|
||||
</Tr>
|
||||
</Thead>
|
||||
<Tbody>
|
||||
{balances.map((bal, i) => (
|
||||
<Tr
|
||||
key={`${i}`}
|
||||
className={`border-b border-th-bkg-3
|
||||
${i % 2 === 0 ? `bg-th-bkg-3` : `bg-th-bkg-2`}
|
||||
`}
|
||||
>
|
||||
<Td
|
||||
className={`px-6 py-3 whitespace-nowrap text-sm text-th-fgd-1`}
|
||||
>
|
||||
<div className="flex items-center">
|
||||
<img
|
||||
alt=""
|
||||
width="20"
|
||||
height="20"
|
||||
src={`/assets/icons/${bal.coin.toLowerCase()}.svg`}
|
||||
className={`mr-2.5`}
|
||||
/>
|
||||
<div>{bal.coin}</div>
|
||||
</div>
|
||||
</Td>
|
||||
<Td
|
||||
className={`px-6 py-3 whitespace-nowrap text-sm text-th-fgd-1`}
|
||||
>
|
||||
{bal.marginDeposits.toFixed(tokenPrecision[bal.coin])}
|
||||
</Td>
|
||||
<Td
|
||||
className={`px-6 py-3 whitespace-nowrap text-sm text-th-fgd-1`}
|
||||
>
|
||||
{bal.orders.toFixed(tokenPrecision[bal.coin])}
|
||||
</Td>
|
||||
<Td
|
||||
className={`px-6 py-3 whitespace-nowrap text-sm text-th-fgd-1`}
|
||||
>
|
||||
{bal.unsettled.toFixed(tokenPrecision[bal.coin])}
|
||||
</Td>
|
||||
<Td
|
||||
className={`px-6 py-3 whitespace-nowrap text-sm text-th-fgd-1`}
|
||||
>
|
||||
$
|
||||
{(
|
||||
(bal.marginDeposits + bal.orders + bal.unsettled) *
|
||||
prices[i]
|
||||
).toFixed(2)}
|
||||
</Td>
|
||||
<Td
|
||||
className={`px-6 py-3 whitespace-nowrap text-sm text-th-fgd-1`}
|
||||
>
|
||||
<span className={`text-th-green`}>
|
||||
{(selectedMangoGroup.getDepositRate(i) * 100).toFixed(
|
||||
2
|
||||
)}
|
||||
%
|
||||
</span>
|
||||
</Td>
|
||||
<Td
|
||||
className={`px-6 py-3 whitespace-nowrap text-sm text-th-fgd-1`}
|
||||
>
|
||||
<div className={`flex justify-end`}>
|
||||
<Button
|
||||
onClick={() => handleShowDeposit(bal.coin)}
|
||||
className="text-xs pt-0 pb-0 h-8 pl-3 pr-3"
|
||||
disabled={!connected || loadingMarginAccount}
|
||||
>
|
||||
<span>Deposit</span>
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => handleShowWithdraw(bal.coin)}
|
||||
className="ml-3 text-xs pt-0 pb-0 h-8 pl-3 pr-3"
|
||||
disabled={!connected || loadingMarginAccount}
|
||||
>
|
||||
<span>Withdraw</span>
|
||||
</Button>
|
||||
</div>
|
||||
</Td>
|
||||
</Tr>
|
||||
))}
|
||||
</Tbody>
|
||||
</Table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div
|
||||
className={`w-full text-center py-6 bg-th-bkg-1 text-th-fgd-3 rounded-md`}
|
||||
>
|
||||
No assets found.
|
||||
</div>
|
||||
)}
|
||||
{showDepositModal && (
|
||||
<DepositModal
|
||||
isOpen={showDepositModal}
|
||||
onClose={handleCloseDeposit}
|
||||
tokenSymbol={depositSymbol}
|
||||
/>
|
||||
)}
|
||||
{showWithdrawModal && (
|
||||
<WithdrawModal
|
||||
isOpen={showWithdrawModal}
|
||||
onClose={handleCloseWithdraw}
|
||||
tokenSymbol={withdrawSymbol}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
) : null
|
||||
}
|
|
@ -0,0 +1,323 @@
|
|||
import { useCallback, useState } from 'react'
|
||||
import { Table, Thead, Tbody, Tr, Th, Td } from 'react-super-responsive-table'
|
||||
import useConnection from '../../hooks/useConnection'
|
||||
import useMangoStore from '../../stores/useMangoStore'
|
||||
import useMarketList from '../../hooks/useMarketList'
|
||||
import { useBalances } from '../../hooks/useBalances'
|
||||
import { notify } from '../../utils/notifications'
|
||||
import { sleep } from '../../utils'
|
||||
import { PublicKey } from '@solana/web3.js'
|
||||
import { floorToDecimal, tokenPrecision } from '../../utils/index'
|
||||
import { settleBorrow } from '../../utils/mango'
|
||||
import BorrowModal from '../BorrowModal'
|
||||
import Button from '../Button'
|
||||
import DepositModal from '../DepositModal'
|
||||
|
||||
export default function AccountBorrows() {
|
||||
const balances = useBalances()
|
||||
const { programId, connection } = useConnection()
|
||||
const actions = useMangoStore((s) => s.actions)
|
||||
const selectedMangoGroup = useMangoStore((s) => s.selectedMangoGroup.current)
|
||||
const selectedMarginAccount = useMangoStore(
|
||||
(s) => s.selectedMarginAccount.current
|
||||
)
|
||||
const loadingMarginAccount = useMangoStore(
|
||||
(s) => s.selectedMarginAccount.initialLoad
|
||||
)
|
||||
const connected = useMangoStore((s) => s.wallet.connected)
|
||||
const { symbols } = useMarketList()
|
||||
|
||||
const prices = useMangoStore((s) => s.selectedMangoGroup.prices)
|
||||
|
||||
const [borrowSymbol, setBorrowSymbol] = useState('')
|
||||
const [depositToSettle, setDepositToSettle] = useState(null)
|
||||
const [showBorrowModal, setShowBorrowModal] = useState(false)
|
||||
const [showDepositModal, setShowDepositModal] = useState(false)
|
||||
|
||||
async function handleSettleBorrow(token, borrowQuantity, depositBalance) {
|
||||
const marginAccount = useMangoStore.getState().selectedMarginAccount.current
|
||||
const mangoGroup = useMangoStore.getState().selectedMangoGroup.current
|
||||
const wallet = useMangoStore.getState().wallet.current
|
||||
|
||||
if (borrowQuantity > depositBalance) {
|
||||
const deficit = borrowQuantity - depositBalance
|
||||
handleShowDeposit(token, deficit)
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
await settleBorrow(
|
||||
connection,
|
||||
new PublicKey(programId),
|
||||
mangoGroup,
|
||||
marginAccount,
|
||||
wallet,
|
||||
new PublicKey(symbols[token]),
|
||||
Number(borrowQuantity)
|
||||
)
|
||||
await sleep(250)
|
||||
actions.fetchMarginAccounts()
|
||||
} catch (e) {
|
||||
console.warn('Error settling all:', e)
|
||||
if (e.message === 'No unsettled borrows') {
|
||||
notify({
|
||||
message: 'There are no unsettled borrows',
|
||||
type: 'error',
|
||||
})
|
||||
} else {
|
||||
notify({
|
||||
message: 'Error settling borrows',
|
||||
description: e.message,
|
||||
txid: e.txid,
|
||||
type: 'error',
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const handleCloseWithdraw = useCallback(() => {
|
||||
setShowBorrowModal(false)
|
||||
}, [])
|
||||
|
||||
const handleCloseDeposit = useCallback(() => {
|
||||
setDepositToSettle(null)
|
||||
setShowDepositModal(false)
|
||||
}, [])
|
||||
|
||||
const handleShowBorrow = (symbol) => {
|
||||
setBorrowSymbol(symbol)
|
||||
setShowBorrowModal(true)
|
||||
}
|
||||
|
||||
const handleShowDeposit = (symbol, deficit) => {
|
||||
setDepositToSettle({ symbol, deficit })
|
||||
setShowDepositModal(true)
|
||||
}
|
||||
|
||||
console.log(depositToSettle)
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="sm:flex sm:items-center sm:justify-between pb-4">
|
||||
<div className="pb-2 sm:pb-0 text-th-fgd-1 text-lg">Your Borrows</div>
|
||||
<div className="border border-th-red flex items-center justify-between p-2 rounded">
|
||||
<div className="pr-4 text-xs text-th-fgd-3">Total Borrow Value:</div>
|
||||
<span>
|
||||
$
|
||||
{balances
|
||||
.reduce((acc, d, i) => acc + d.borrows * prices[i], 0)
|
||||
.toFixed(2)}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
{selectedMangoGroup ? (
|
||||
balances.find((b) => b.borrows > 0) ? (
|
||||
<Table className="min-w-full divide-y divide-th-bkg-2">
|
||||
<Thead>
|
||||
<Tr className="text-th-fgd-3 text-xs">
|
||||
<Th scope="col" className={`px-6 py-3 text-left font-normal`}>
|
||||
Asset
|
||||
</Th>
|
||||
<Th scope="col" className={`px-6 py-3 text-left font-normal`}>
|
||||
Balance
|
||||
</Th>
|
||||
<Th scope="col" className={`px-6 py-3 text-left font-normal`}>
|
||||
Value
|
||||
</Th>
|
||||
<Th scope="col" className="px-6 py-3 text-left font-normal">
|
||||
Interest APY
|
||||
</Th>
|
||||
</Tr>
|
||||
</Thead>
|
||||
<Tbody>
|
||||
{balances
|
||||
.filter((assets) => assets.borrows > 0)
|
||||
.map((asset, i) => (
|
||||
<Tr
|
||||
key={`${i}`}
|
||||
className={`border-b border-th-bkg-3
|
||||
${i % 2 === 0 ? `bg-th-bkg-3` : `bg-th-bkg-2`}
|
||||
`}
|
||||
>
|
||||
<Td
|
||||
className={`px-6 py-3 whitespace-nowrap text-sm text-th-fgd-1`}
|
||||
>
|
||||
<div className="flex items-center">
|
||||
<img
|
||||
alt=""
|
||||
width="20"
|
||||
height="20"
|
||||
src={`/assets/icons/${asset.coin.toLowerCase()}.svg`}
|
||||
className={`mr-2.5`}
|
||||
/>
|
||||
<div>{asset.coin}</div>
|
||||
</div>
|
||||
</Td>
|
||||
<Td
|
||||
className={`px-6 py-3 whitespace-nowrap text-sm text-th-fgd-1`}
|
||||
>
|
||||
{asset.borrows.toFixed(tokenPrecision[asset.coin])}
|
||||
</Td>
|
||||
<Td
|
||||
className={`px-6 py-3 whitespace-nowrap text-sm text-th-fgd-1`}
|
||||
>
|
||||
$
|
||||
{(
|
||||
asset.borrows *
|
||||
prices[
|
||||
Object.keys(symbols).findIndex(
|
||||
(key) => key === asset.coin
|
||||
)
|
||||
]
|
||||
).toFixed(tokenPrecision[asset.coin])}
|
||||
</Td>
|
||||
<Td
|
||||
className={`px-6 py-3 whitespace-nowrap text-sm text-th-fgd-1`}
|
||||
>
|
||||
<span className={`text-th-green`}>
|
||||
{(
|
||||
selectedMangoGroup.getBorrowRate(
|
||||
Object.keys(symbols).findIndex(
|
||||
(key) => key === asset.coin
|
||||
)
|
||||
) * 100
|
||||
).toFixed(2)}
|
||||
%
|
||||
</span>
|
||||
</Td>
|
||||
<Td
|
||||
className={`px-6 py-3 whitespace-nowrap text-sm text-th-fgd-1`}
|
||||
>
|
||||
<div className={`flex justify-end`}>
|
||||
<Button
|
||||
onClick={() =>
|
||||
handleSettleBorrow(
|
||||
asset.coin,
|
||||
asset.borrows,
|
||||
asset.marginDeposits
|
||||
)
|
||||
}
|
||||
className="text-xs pt-0 pb-0 h-8 pl-3 pr-3"
|
||||
disabled={
|
||||
!connected ||
|
||||
!selectedMarginAccount ||
|
||||
loadingMarginAccount
|
||||
}
|
||||
>
|
||||
Settle
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => handleShowBorrow(asset.coin)}
|
||||
className="ml-3 text-xs pt-0 pb-0 h-8 pl-3 pr-3"
|
||||
disabled={!connected || loadingMarginAccount}
|
||||
>
|
||||
Borrow
|
||||
</Button>
|
||||
</div>
|
||||
</Td>
|
||||
</Tr>
|
||||
))}
|
||||
</Tbody>
|
||||
</Table>
|
||||
) : (
|
||||
<div
|
||||
className={`w-full text-center py-6 bg-th-bkg-1 text-th-fgd-3 rounded-md`}
|
||||
>
|
||||
No borrows found.
|
||||
</div>
|
||||
)
|
||||
) : null}
|
||||
<div className="pb-2 pt-8 text-th-fgd-1 text-lg">Available Assets</div>
|
||||
<Table className="min-w-full divide-y divide-th-bkg-2">
|
||||
<Thead>
|
||||
<Tr className="text-th-fgd-3 text-xs">
|
||||
<Th scope="col" className={`px-6 py-3 text-left font-normal`}>
|
||||
Asset
|
||||
</Th>
|
||||
<Th scope="col" className={`px-6 py-3 text-left font-normal`}>
|
||||
Price
|
||||
</Th>
|
||||
<Th scope="col" className="px-6 py-3 text-left font-normal">
|
||||
Interest APY
|
||||
</Th>
|
||||
<Th scope="col" className="px-6 py-3 text-left font-normal">
|
||||
Available Liquidity
|
||||
</Th>
|
||||
</Tr>
|
||||
</Thead>
|
||||
<Tbody>
|
||||
{Object.entries(symbols).map(([asset], i) => (
|
||||
<Tr
|
||||
key={`${i}`}
|
||||
className={`border-b border-th-bkg-3
|
||||
${i % 2 === 0 ? `bg-th-bkg-3` : `bg-th-bkg-2`}
|
||||
`}
|
||||
>
|
||||
<Td
|
||||
className={`px-6 py-3 whitespace-nowrap text-sm text-th-fgd-1`}
|
||||
>
|
||||
<div className="flex items-center">
|
||||
<img
|
||||
alt=""
|
||||
width="20"
|
||||
height="20"
|
||||
src={`/assets/icons/${asset.toLowerCase()}.svg`}
|
||||
className={`mr-2.5`}
|
||||
/>
|
||||
<div>{asset}</div>
|
||||
</div>
|
||||
</Td>
|
||||
<Td
|
||||
className={`px-6 py-3 whitespace-nowrap text-sm text-th-fgd-1`}
|
||||
>
|
||||
${prices[i]}
|
||||
</Td>
|
||||
<Td
|
||||
className={`px-6 py-3 whitespace-nowrap text-sm text-th-fgd-1`}
|
||||
>
|
||||
<span className={`text-th-green`}>
|
||||
{(selectedMangoGroup.getBorrowRate(i) * 100).toFixed(2)}%
|
||||
</span>
|
||||
</Td>
|
||||
<Td
|
||||
className={`px-6 py-3 whitespace-nowrap text-sm text-th-fgd-1`}
|
||||
>
|
||||
{selectedMangoGroup
|
||||
.getUiTotalDeposit(i)
|
||||
.toFixed(tokenPrecision[asset])}
|
||||
</Td>
|
||||
<Td
|
||||
className={`px-6 py-3 whitespace-nowrap text-sm text-th-fgd-1`}
|
||||
>
|
||||
<div className={`flex justify-end`}>
|
||||
<Button
|
||||
onClick={() => handleShowBorrow(asset)}
|
||||
className="text-xs pt-0 pb-0 h-8 pl-3 pr-3"
|
||||
disabled={!connected || loadingMarginAccount}
|
||||
>
|
||||
Borrow
|
||||
</Button>
|
||||
</div>
|
||||
</Td>
|
||||
</Tr>
|
||||
))}
|
||||
</Tbody>
|
||||
</Table>
|
||||
{showBorrowModal && (
|
||||
<BorrowModal
|
||||
isOpen={showBorrowModal}
|
||||
onClose={handleCloseWithdraw}
|
||||
tokenSymbol={borrowSymbol}
|
||||
/>
|
||||
)}
|
||||
{showDepositModal && (
|
||||
<DepositModal
|
||||
isOpen={showDepositModal}
|
||||
onClose={handleCloseDeposit}
|
||||
settleDeficit={depositToSettle.deficit}
|
||||
tokenSymbol={depositToSettle.symbol}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
|
@ -0,0 +1,49 @@
|
|||
import { useState } from 'react'
|
||||
import TradeHistoryTable from '../TradeHistoryTable'
|
||||
|
||||
const historyViews = ['Trades', 'Deposits', 'Withdrawals', 'Liquidations']
|
||||
|
||||
export default function AccountHistory() {
|
||||
const [view, setView] = useState('Trades')
|
||||
return (
|
||||
<>
|
||||
<div className="flex items-center justify-between pb-3.5 sm:pt-1">
|
||||
<div className="text-th-fgd-1 text-lg">{view.slice(0, -1)} History</div>
|
||||
{/* Todo: add this back when the data is available */}
|
||||
{/* <div className="flex">
|
||||
{historyViews.map((section) => (
|
||||
<div
|
||||
className={`border px-3 py-1.5 mr-2 rounded cursor-pointer default-transition
|
||||
${
|
||||
view === section
|
||||
? `bg-th-bkg-3 border-th-bkg-3 text-th-primary`
|
||||
: `border-th-fgd-4 text-th-fgd-1 opacity-80 hover:opacity-100`
|
||||
}
|
||||
`}
|
||||
onClick={() => setView(section)}
|
||||
key={section as string}
|
||||
>
|
||||
{section}
|
||||
</div>
|
||||
))}
|
||||
</div> */}
|
||||
</div>
|
||||
<ViewContent view={view} />
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
const ViewContent = ({ view }) => {
|
||||
switch (view) {
|
||||
case 'Trades':
|
||||
return <TradeHistoryTable />
|
||||
case 'Deposits':
|
||||
return <div>Deposits</div>
|
||||
case 'Withdrawals':
|
||||
return <div>Withdrawals</div>
|
||||
case 'Liquidations':
|
||||
return <div>Liquidations</div>
|
||||
default:
|
||||
return <TradeHistoryTable />
|
||||
}
|
||||
}
|
|
@ -0,0 +1,60 @@
|
|||
import { useState } from 'react'
|
||||
import { useOpenOrders } from '../../hooks/useOpenOrders'
|
||||
import { cancelOrderAndSettle } from '../../utils/mango'
|
||||
import Button from '../Button'
|
||||
import Loading from '../Loading'
|
||||
import { PublicKey } from '@solana/web3.js'
|
||||
import useConnection from '../../hooks/useConnection'
|
||||
import useMangoStore from '../../stores/useMangoStore'
|
||||
import { notify } from '../../utils/notifications'
|
||||
import { Table, Thead, Tbody, Tr, Th, Td } from 'react-super-responsive-table'
|
||||
import SideBadge from '../SideBadge'
|
||||
import OpenOrdersTable from '../OpenOrdersTable'
|
||||
|
||||
const AccountOrders = () => {
|
||||
const openOrders = useOpenOrders()
|
||||
const [cancelId, setCancelId] = useState(null)
|
||||
const { connection, programId } = useConnection()
|
||||
const actions = useMangoStore((s) => s.actions)
|
||||
|
||||
const handleCancelOrder = async (order) => {
|
||||
const wallet = useMangoStore.getState().wallet.current
|
||||
const selectedMangoGroup =
|
||||
useMangoStore.getState().selectedMangoGroup.current
|
||||
const selectedMarginAccount =
|
||||
useMangoStore.getState().selectedMarginAccount.current
|
||||
setCancelId(order?.orderId)
|
||||
try {
|
||||
if (!selectedMangoGroup || !selectedMarginAccount) return
|
||||
await cancelOrderAndSettle(
|
||||
connection,
|
||||
new PublicKey(programId),
|
||||
selectedMangoGroup,
|
||||
selectedMarginAccount,
|
||||
wallet,
|
||||
order.market,
|
||||
order
|
||||
)
|
||||
actions.fetchMarginAccounts()
|
||||
} catch (e) {
|
||||
notify({
|
||||
message: 'Error cancelling order',
|
||||
description: e.message,
|
||||
txid: e.txid,
|
||||
type: 'error',
|
||||
})
|
||||
return
|
||||
} finally {
|
||||
setCancelId(null)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="pb-3.5 sm:pt-1 text-th-fgd-1 text-lg">Open Orders</div>
|
||||
<OpenOrdersTable />
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default AccountOrders
|
|
@ -120,3 +120,24 @@ export const TelegramIcon = ({ className }) => {
|
|||
</svg>
|
||||
)
|
||||
}
|
||||
|
||||
export const ProfileIcon = ({ className }) => {
|
||||
return (
|
||||
<svg
|
||||
className={`${className}`}
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 45.532 45.532"
|
||||
fill="currentColor"
|
||||
>
|
||||
<g>
|
||||
<path
|
||||
d="M22.766,0.001C10.194,0.001,0,10.193,0,22.766s10.193,22.765,22.766,22.765c12.574,0,22.766-10.192,22.766-22.765
|
||||
S35.34,0.001,22.766,0.001z M22.766,6.808c4.16,0,7.531,3.372,7.531,7.53c0,4.159-3.371,7.53-7.531,7.53
|
||||
c-4.158,0-7.529-3.371-7.529-7.53C15.237,10.18,18.608,6.808,22.766,6.808z M22.761,39.579c-4.149,0-7.949-1.511-10.88-4.012
|
||||
c-0.714-0.609-1.126-1.502-1.126-2.439c0-4.217,3.413-7.592,7.631-7.592h8.762c4.219,0,7.619,3.375,7.619,7.592
|
||||
c0,0.938-0.41,1.829-1.125,2.438C30.712,38.068,26.911,39.579,22.761,39.579z"
|
||||
/>
|
||||
</g>
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -0,0 +1,39 @@
|
|||
import useConnection from './useConnection'
|
||||
import { IDS } from '@blockworks-foundation/mango-client'
|
||||
import useMangoStore from '../stores/useMangoStore'
|
||||
import { formatTokenMints } from './useMarket'
|
||||
|
||||
const useAllMarkets = () => {
|
||||
const markets = useMangoStore((s) => s.selectedMangoGroup.markets)
|
||||
const { cluster, programId } = useConnection()
|
||||
const TOKEN_MINTS = formatTokenMints(IDS[cluster].symbols)
|
||||
|
||||
return Object.keys(markets).map(function (marketIndex) {
|
||||
const market = markets[marketIndex]
|
||||
const marketAddress = market ? market.publicKey.toString() : null
|
||||
|
||||
const baseCurrency =
|
||||
(market?.baseMintAddress &&
|
||||
TOKEN_MINTS.find((token) =>
|
||||
token.address.equals(market.baseMintAddress)
|
||||
)?.name) ||
|
||||
'...'
|
||||
|
||||
const quoteCurrency =
|
||||
(market?.quoteMintAddress &&
|
||||
TOKEN_MINTS.find((token) =>
|
||||
token.address.equals(market.quoteMintAddress)
|
||||
)?.name) ||
|
||||
'...'
|
||||
|
||||
return {
|
||||
market,
|
||||
marketAddress,
|
||||
programId,
|
||||
baseCurrency,
|
||||
quoteCurrency,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
export default useAllMarkets
|
|
@ -1,4 +1,3 @@
|
|||
import useMarket from './useMarket'
|
||||
import { Balances } from '../@types/types'
|
||||
import { nativeToUi } from '@blockworks-foundation/mango-client'
|
||||
import useMarketList from './useMarketList'
|
||||
|
@ -8,108 +7,123 @@ import {
|
|||
displayDepositsForMarginAccount,
|
||||
floorToDecimal,
|
||||
} from '../utils'
|
||||
import useAllMarkets from './useAllMarkets'
|
||||
|
||||
export function useBalances(): Balances[] {
|
||||
const { baseCurrency, quoteCurrency, market } = useMarket()
|
||||
let balances = []
|
||||
const markets = useAllMarkets()
|
||||
const mangoGroup = useMangoStore((s) => s.selectedMangoGroup.current)
|
||||
const marginAccount = useMangoStore((s) => s.selectedMarginAccount.current)
|
||||
const { symbols } = useMarketList()
|
||||
|
||||
if (!marginAccount || !mangoGroup || !market) {
|
||||
return []
|
||||
}
|
||||
for (const { market, baseCurrency, quoteCurrency } of markets) {
|
||||
if (!marginAccount || !mangoGroup || !market) {
|
||||
return []
|
||||
}
|
||||
|
||||
const marketIndex = mangoGroup.getMarketIndex(market)
|
||||
const openOrders: any = marginAccount.openOrdersAccounts[marketIndex]
|
||||
const baseCurrencyIndex = Object.entries(symbols).findIndex(
|
||||
(x) => x[0] === baseCurrency
|
||||
)
|
||||
const quoteCurrencyIndex = Object.entries(symbols).findIndex(
|
||||
(x) => x[0] === quoteCurrency
|
||||
)
|
||||
|
||||
if (
|
||||
baseCurrency === 'UNKNOWN' ||
|
||||
quoteCurrency === 'UNKNOWN' ||
|
||||
!baseCurrency ||
|
||||
!quoteCurrency
|
||||
) {
|
||||
return []
|
||||
}
|
||||
|
||||
const nativeBaseFree = openOrders?.baseTokenFree || 0
|
||||
const nativeQuoteFree = openOrders?.quoteTokenFree || 0
|
||||
|
||||
const nativeBaseLocked = openOrders
|
||||
? openOrders.baseTokenTotal - nativeBaseFree
|
||||
: 0
|
||||
const nativeQuoteLocked = openOrders
|
||||
? openOrders?.quoteTokenTotal - nativeQuoteFree
|
||||
: 0
|
||||
|
||||
const nativeBaseUnsettled = openOrders?.baseTokenFree || 0
|
||||
const nativeQuoteUnsettled = openOrders?.quoteTokenFree || 0
|
||||
const tokenIndex = marketIndex
|
||||
|
||||
const net = (borrows, currencyIndex) => {
|
||||
const amount =
|
||||
marginAccount.getNativeDeposit(mangoGroup, currencyIndex) +
|
||||
borrows -
|
||||
marginAccount.getNativeBorrow(mangoGroup, currencyIndex)
|
||||
|
||||
return floorToDecimal(
|
||||
nativeToUi(amount, mangoGroup.mintDecimals[currencyIndex]),
|
||||
mangoGroup.mintDecimals[currencyIndex]
|
||||
const marketIndex = mangoGroup.getMarketIndex(market)
|
||||
const openOrders: any = marginAccount.openOrdersAccounts[marketIndex]
|
||||
const baseCurrencyIndex = Object.entries(symbols).findIndex(
|
||||
(x) => x[0] === baseCurrency
|
||||
)
|
||||
const quoteCurrencyIndex = Object.entries(symbols).findIndex(
|
||||
(x) => x[0] === quoteCurrency
|
||||
)
|
||||
|
||||
if (
|
||||
baseCurrency === 'UNKNOWN' ||
|
||||
quoteCurrency === 'UNKNOWN' ||
|
||||
!baseCurrency ||
|
||||
!quoteCurrency
|
||||
) {
|
||||
return []
|
||||
}
|
||||
|
||||
const nativeBaseFree = openOrders?.baseTokenFree || 0
|
||||
const nativeQuoteFree = openOrders?.quoteTokenFree || 0
|
||||
|
||||
const nativeBaseLocked = openOrders
|
||||
? openOrders.baseTokenTotal - nativeBaseFree
|
||||
: 0
|
||||
const nativeQuoteLocked = openOrders
|
||||
? openOrders?.quoteTokenTotal - nativeQuoteFree
|
||||
: 0
|
||||
|
||||
const nativeBaseUnsettled = openOrders?.baseTokenFree || 0
|
||||
const nativeQuoteUnsettled = openOrders?.quoteTokenFree || 0
|
||||
const tokenIndex = marketIndex
|
||||
|
||||
const net = (borrows, currencyIndex) => {
|
||||
const amount =
|
||||
marginAccount.getNativeDeposit(mangoGroup, currencyIndex) +
|
||||
borrows -
|
||||
marginAccount.getNativeBorrow(mangoGroup, currencyIndex)
|
||||
|
||||
return floorToDecimal(
|
||||
nativeToUi(amount, mangoGroup.mintDecimals[currencyIndex]),
|
||||
mangoGroup.mintDecimals[currencyIndex]
|
||||
)
|
||||
}
|
||||
|
||||
const marketPair = [
|
||||
{
|
||||
market,
|
||||
key: `${baseCurrency}${quoteCurrency}${baseCurrency}`,
|
||||
coin: baseCurrency,
|
||||
marginDeposits: displayDepositsForMarginAccount(
|
||||
marginAccount,
|
||||
mangoGroup,
|
||||
baseCurrencyIndex
|
||||
),
|
||||
borrows: displayBorrowsForMarginAccount(
|
||||
marginAccount,
|
||||
mangoGroup,
|
||||
baseCurrencyIndex
|
||||
),
|
||||
orders: nativeToUi(
|
||||
nativeBaseLocked,
|
||||
mangoGroup.mintDecimals[tokenIndex]
|
||||
),
|
||||
openOrders,
|
||||
unsettled: nativeToUi(
|
||||
nativeBaseUnsettled,
|
||||
mangoGroup.mintDecimals[tokenIndex]
|
||||
),
|
||||
net: net(nativeBaseLocked, tokenIndex),
|
||||
},
|
||||
{
|
||||
market,
|
||||
key: `${quoteCurrency}${baseCurrency}${quoteCurrency}`,
|
||||
coin: quoteCurrency,
|
||||
marginDeposits: displayDepositsForMarginAccount(
|
||||
marginAccount,
|
||||
mangoGroup,
|
||||
quoteCurrencyIndex
|
||||
),
|
||||
borrows: displayBorrowsForMarginAccount(
|
||||
marginAccount,
|
||||
mangoGroup,
|
||||
quoteCurrencyIndex
|
||||
),
|
||||
openOrders,
|
||||
orders: nativeToUi(
|
||||
nativeQuoteLocked,
|
||||
mangoGroup.mintDecimals[quoteCurrencyIndex]
|
||||
),
|
||||
unsettled: nativeToUi(
|
||||
nativeQuoteUnsettled,
|
||||
mangoGroup.mintDecimals[quoteCurrencyIndex]
|
||||
),
|
||||
net: net(nativeQuoteLocked, quoteCurrencyIndex),
|
||||
},
|
||||
]
|
||||
balances = balances.concat(marketPair)
|
||||
}
|
||||
|
||||
return [
|
||||
{
|
||||
market,
|
||||
key: `${baseCurrency}${quoteCurrency}${baseCurrency}`,
|
||||
coin: baseCurrency,
|
||||
marginDeposits: displayDepositsForMarginAccount(
|
||||
marginAccount,
|
||||
mangoGroup,
|
||||
baseCurrencyIndex
|
||||
),
|
||||
borrows: displayBorrowsForMarginAccount(
|
||||
marginAccount,
|
||||
mangoGroup,
|
||||
baseCurrencyIndex
|
||||
),
|
||||
orders: nativeToUi(nativeBaseLocked, mangoGroup.mintDecimals[tokenIndex]),
|
||||
openOrders,
|
||||
unsettled: nativeToUi(
|
||||
nativeBaseUnsettled,
|
||||
mangoGroup.mintDecimals[tokenIndex]
|
||||
),
|
||||
net: net(nativeBaseLocked, tokenIndex),
|
||||
},
|
||||
{
|
||||
market,
|
||||
key: `${quoteCurrency}${baseCurrency}${quoteCurrency}`,
|
||||
coin: quoteCurrency,
|
||||
marginDeposits: displayDepositsForMarginAccount(
|
||||
marginAccount,
|
||||
mangoGroup,
|
||||
quoteCurrencyIndex
|
||||
),
|
||||
borrows: displayBorrowsForMarginAccount(
|
||||
marginAccount,
|
||||
mangoGroup,
|
||||
quoteCurrencyIndex
|
||||
),
|
||||
openOrders,
|
||||
orders: nativeToUi(
|
||||
nativeQuoteLocked,
|
||||
mangoGroup.mintDecimals[quoteCurrencyIndex]
|
||||
),
|
||||
unsettled: nativeToUi(
|
||||
nativeQuoteUnsettled,
|
||||
mangoGroup.mintDecimals[quoteCurrencyIndex]
|
||||
),
|
||||
net: net(nativeQuoteLocked, quoteCurrencyIndex),
|
||||
},
|
||||
]
|
||||
balances.sort((a, b) => (a.coin > b.coin ? 1 : -1))
|
||||
balances = balances.filter(function (elem, index, self) {
|
||||
return index === self.map((a) => a.coin).indexOf(elem.coin)
|
||||
})
|
||||
|
||||
return balances
|
||||
}
|
||||
|
|
|
@ -0,0 +1,159 @@
|
|||
import { ReactNode, useEffect, useMemo, useState } from 'react'
|
||||
import {
|
||||
CurrencyDollarIcon,
|
||||
ChartBarIcon,
|
||||
ChartPieIcon,
|
||||
ScaleIcon,
|
||||
} from '@heroicons/react/outline'
|
||||
import { nativeToUi } from '@blockworks-foundation/mango-client/lib/utils'
|
||||
import { groupBy } from '../utils'
|
||||
import useMangoStore from '../stores/useMangoStore'
|
||||
import useTradeHistory from '../hooks/useTradeHistory'
|
||||
|
||||
const calculatePNL = (tradeHistory, prices, mangoGroup) => {
|
||||
if (!tradeHistory.length) return '0.00'
|
||||
const profitAndLoss = {}
|
||||
const groupedTrades = groupBy(tradeHistory, (trade) => trade.marketName)
|
||||
if (!prices.length) return '-'
|
||||
|
||||
const assetIndex = {
|
||||
'BTC/USDT': 0,
|
||||
'BTC/WUSDT': 0,
|
||||
'ETH/USDT': 1,
|
||||
'ETH/WUSDT': 1,
|
||||
'SOL/USDT': 2,
|
||||
'SOL/WUSDT': 2,
|
||||
'SRM/USDT': 3,
|
||||
'SRM/WUSDT': 3,
|
||||
USDT: 2,
|
||||
WUSDT: 2,
|
||||
}
|
||||
|
||||
groupedTrades.forEach((val, key) => {
|
||||
profitAndLoss[key] = val.reduce(
|
||||
(acc, current) =>
|
||||
(current.side === 'sell' ? current.size * -1 : current.size) + acc,
|
||||
0
|
||||
)
|
||||
})
|
||||
|
||||
const totalNativeUsdt = tradeHistory.reduce((acc, current) => {
|
||||
const usdtAmount =
|
||||
current.side === 'sell'
|
||||
? parseInt(current.nativeQuantityReleased)
|
||||
: parseInt(current.nativeQuantityPaid) * -1
|
||||
|
||||
return usdtAmount + acc
|
||||
}, 0)
|
||||
|
||||
profitAndLoss['USDT'] = nativeToUi(
|
||||
totalNativeUsdt,
|
||||
mangoGroup.mintDecimals[2]
|
||||
)
|
||||
|
||||
let total = 0
|
||||
for (const assetName in profitAndLoss) {
|
||||
total = total + profitAndLoss[assetName] * prices[assetIndex[assetName]]
|
||||
}
|
||||
|
||||
return isNaN(total) ? 0 : total.toFixed(2)
|
||||
}
|
||||
|
||||
const useMarginInfo = () => {
|
||||
const connection = useMangoStore((s) => s.connection.current)
|
||||
const selectedMangoGroup = useMangoStore((s) => s.selectedMangoGroup.current)
|
||||
const selectedMarginAccount = useMangoStore(
|
||||
(s) => s.selectedMarginAccount.current
|
||||
)
|
||||
const tradeHistory = useTradeHistory()
|
||||
const tradeHistoryLength = useMemo(() => tradeHistory.length, [tradeHistory])
|
||||
const [mAccountInfo, setMAccountInfo] =
|
||||
useState<
|
||||
| {
|
||||
label: string
|
||||
value: string
|
||||
unit: string
|
||||
desc: string
|
||||
currency: string
|
||||
icon: ReactNode
|
||||
}[]
|
||||
| null
|
||||
>(null)
|
||||
useEffect(() => {
|
||||
if (selectedMangoGroup) {
|
||||
selectedMangoGroup.getPrices(connection).then((prices) => {
|
||||
const collateralRatio = selectedMarginAccount
|
||||
? selectedMarginAccount.getCollateralRatio(selectedMangoGroup, prices)
|
||||
: 200
|
||||
|
||||
const accountEquity = selectedMarginAccount
|
||||
? selectedMarginAccount.computeValue(selectedMangoGroup, prices)
|
||||
: 0
|
||||
let leverage
|
||||
if (selectedMarginAccount) {
|
||||
leverage = accountEquity
|
||||
? (
|
||||
1 /
|
||||
(selectedMarginAccount.getCollateralRatio(
|
||||
selectedMangoGroup,
|
||||
prices
|
||||
) -
|
||||
1)
|
||||
).toFixed(2)
|
||||
: '∞'
|
||||
} else {
|
||||
leverage = '0'
|
||||
}
|
||||
|
||||
setMAccountInfo([
|
||||
{
|
||||
label: 'Account Value',
|
||||
value: accountEquity.toFixed(2),
|
||||
unit: '',
|
||||
currency: '$',
|
||||
desc: 'The value of the account',
|
||||
icon: (
|
||||
<CurrencyDollarIcon className="flex-shrink-0 h-5 w-5 mr-2 text-th-primary" />
|
||||
),
|
||||
},
|
||||
{
|
||||
label: 'Total PNL',
|
||||
value: calculatePNL(tradeHistory, prices, selectedMangoGroup),
|
||||
unit: '',
|
||||
currency: '$',
|
||||
desc: 'Total PNL reflects trades placed after March 15th 2021 04:00 AM UTC. Visit the Learn link in the top menu for more information.',
|
||||
icon: (
|
||||
<ChartBarIcon className="flex-shrink-0 h-5 w-5 mr-2 text-th-primary" />
|
||||
),
|
||||
},
|
||||
{
|
||||
label: 'Leverage',
|
||||
value: leverage,
|
||||
unit: 'x',
|
||||
currency: '',
|
||||
desc: 'Total position size divided by account value',
|
||||
icon: (
|
||||
<ScaleIcon className="flex-shrink-0 h-5 w-5 mr-2 text-th-primary" />
|
||||
),
|
||||
},
|
||||
{
|
||||
// TODO: Get collaterization ratio
|
||||
label: 'Collateral Ratio',
|
||||
value:
|
||||
collateralRatio > 2 ? '>200' : (100 * collateralRatio).toFixed(0),
|
||||
unit: '%',
|
||||
currency: '',
|
||||
desc: 'The current collateral ratio',
|
||||
icon: (
|
||||
<ChartPieIcon className="flex-shrink-0 h-5 w-5 mr-2 text-th-primary" />
|
||||
),
|
||||
},
|
||||
])
|
||||
})
|
||||
}
|
||||
}, [selectedMarginAccount, selectedMangoGroup, tradeHistoryLength])
|
||||
|
||||
return mAccountInfo
|
||||
}
|
||||
|
||||
export default useMarginInfo
|
|
@ -4,7 +4,7 @@ import { PublicKey } from '@solana/web3.js'
|
|||
import useMangoStore from '../stores/useMangoStore'
|
||||
import { IDS } from '@blockworks-foundation/mango-client'
|
||||
|
||||
const formatTokenMints = (symbols: { [name: string]: string }) => {
|
||||
export const formatTokenMints = (symbols: { [name: string]: string }) => {
|
||||
return Object.entries(symbols).map(([name, address]) => {
|
||||
return {
|
||||
address: new PublicKey(address),
|
||||
|
@ -23,9 +23,10 @@ const useMarket = () => {
|
|||
[market]
|
||||
)
|
||||
|
||||
const TOKEN_MINTS = useMemo(() => formatTokenMints(IDS[cluster].symbols), [
|
||||
cluster,
|
||||
])
|
||||
const TOKEN_MINTS = useMemo(
|
||||
() => formatTokenMints(IDS[cluster].symbols),
|
||||
[cluster]
|
||||
)
|
||||
|
||||
const baseCurrency = useMemo(
|
||||
() =>
|
||||
|
|
|
@ -0,0 +1,40 @@
|
|||
import { useMemo, useState } from 'react'
|
||||
|
||||
export const useSortableData = (items, config = null) => {
|
||||
const [sortConfig, setSortConfig] = useState(config)
|
||||
|
||||
const sortedItems = useMemo(() => {
|
||||
let sortableItems = items ? [...items] : []
|
||||
if (sortConfig !== null) {
|
||||
sortableItems.sort((a, b) => {
|
||||
if (!isNaN(a[sortConfig.key])) {
|
||||
return sortConfig.direction === 'ascending'
|
||||
? a[sortConfig.key] - b[sortConfig.key]
|
||||
: b[sortConfig.key] - a[sortConfig.key]
|
||||
}
|
||||
if (a[sortConfig.key] < b[sortConfig.key]) {
|
||||
return sortConfig.direction === 'ascending' ? -1 : 1
|
||||
}
|
||||
if (a[sortConfig.key] > b[sortConfig.key]) {
|
||||
return sortConfig.direction === 'ascending' ? 1 : -1
|
||||
}
|
||||
return 0
|
||||
})
|
||||
}
|
||||
return sortableItems
|
||||
}, [items, sortConfig])
|
||||
|
||||
const requestSort = (key) => {
|
||||
let direction = 'ascending'
|
||||
if (
|
||||
sortConfig &&
|
||||
sortConfig.key === key &&
|
||||
sortConfig.direction === 'ascending'
|
||||
) {
|
||||
direction = 'descending'
|
||||
}
|
||||
setSortConfig({ key, direction })
|
||||
}
|
||||
|
||||
return { items: sortedItems, requestSort, sortConfig }
|
||||
}
|
|
@ -20,6 +20,7 @@ const formatTradeHistory = (newTradeHistory) => {
|
|||
: `${trade.baseCurrency}/${trade.quoteCurrency}`,
|
||||
key: `${trade.orderId}-${trade.uuid}`,
|
||||
liquidity: trade.maker || trade?.eventFlags?.maker ? 'Maker' : 'Taker',
|
||||
value: trade.price * trade.size,
|
||||
}
|
||||
})
|
||||
.sort(byTimestamp)
|
||||
|
|
|
@ -0,0 +1,167 @@
|
|||
import { useCallback, useState } from 'react'
|
||||
import {
|
||||
CurrencyDollarIcon,
|
||||
ExternalLinkIcon,
|
||||
LinkIcon,
|
||||
} from '@heroicons/react/outline'
|
||||
import useMangoStore from '../stores/useMangoStore'
|
||||
import { abbreviateAddress } from '../utils'
|
||||
import useMarginInfo from '../hooks/useMarginInfo'
|
||||
import PageBodyContainer from '../components/PageBodyContainer'
|
||||
import TopBar from '../components/TopBar'
|
||||
import AccountAssets from '../components/account-page/AccountAssets'
|
||||
import AccountBorrows from '../components/account-page/AccountBorrows'
|
||||
import AccountOrders from '../components/account-page/AccountOrders'
|
||||
import AccountHistory from '../components/account-page/AccountHistory'
|
||||
import AccountsModal from '../components/AccountsModal'
|
||||
import EmptyState from '../components/EmptyState'
|
||||
|
||||
const TABS = [
|
||||
'Assets',
|
||||
'Borrows',
|
||||
// 'Stats',
|
||||
// 'Positions',
|
||||
'Orders',
|
||||
'History',
|
||||
]
|
||||
|
||||
export default function Account() {
|
||||
const [activeTab, setActiveTab] = useState(TABS[0])
|
||||
const [showAccountsModal, setShowAccountsModal] = useState(false)
|
||||
const accountMarginInfo = useMarginInfo()
|
||||
const connected = useMangoStore((s) => s.wallet.connected)
|
||||
const selectedMarginAccount = useMangoStore(
|
||||
(s) => s.selectedMarginAccount.current
|
||||
)
|
||||
|
||||
const handleTabChange = (tabName) => {
|
||||
setActiveTab(tabName)
|
||||
}
|
||||
const handleCloseAccounts = useCallback(() => {
|
||||
setShowAccountsModal(false)
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<div className={`bg-th-bkg-1 text-th-fgd-1 transition-all`}>
|
||||
<TopBar />
|
||||
<PageBodyContainer>
|
||||
<div className="flex flex-col sm:flex-row items-center justify-between pt-8 pb-3 sm:pb-6 md:pt-10">
|
||||
<h1 className={`text-th-fgd-1 text-2xl font-semibold`}>Account</h1>
|
||||
{selectedMarginAccount ? (
|
||||
<div className="divide-x divide-th-fgd-4 flex justify-center w-full pt-4 sm:pt-0 sm:justify-end">
|
||||
<div className="pr-4 text-xs text-th-fgd-1">
|
||||
<div className="pb-0.5 text-2xs text-th-fgd-3">Acc Owner</div>
|
||||
<a
|
||||
className="default-transition flex items-center text-th-fgd-2"
|
||||
href={`https://explorer.solana.com/address/${selectedMarginAccount?.owner}`}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<span>{abbreviateAddress(selectedMarginAccount?.owner)}</span>
|
||||
<ExternalLinkIcon className={`h-3 w-3 ml-1`} />
|
||||
</a>
|
||||
</div>
|
||||
<div className="pl-4 text-xs text-th-fgd-1">
|
||||
<div className="pb-0.5 text-2xs text-th-fgd-3">Acc Address</div>
|
||||
<a
|
||||
className="default-transition flex items-center text-th-fgd-2"
|
||||
href={`https://explorer.solana.com/address/${selectedMarginAccount?.publicKey}`}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<span>
|
||||
{abbreviateAddress(selectedMarginAccount?.publicKey)}
|
||||
</span>
|
||||
<ExternalLinkIcon className={`h-3 w-3 ml-1`} />
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
<div className="bg-th-bkg-2 overflow-auto p-6 rounded-lg">
|
||||
{selectedMarginAccount ? (
|
||||
<>
|
||||
<div className="pb-4 text-th-fgd-1 text-lg">Overview</div>
|
||||
{accountMarginInfo ? (
|
||||
<div className="grid grid-flow-col grid-cols-1 grid-rows-4 sm:grid-cols-2 sm:grid-rows-2 md:grid-cols-4 md:grid-rows-1 gap-4 pb-10">
|
||||
{accountMarginInfo.map((info) => (
|
||||
<div
|
||||
className="bg-th-bkg-3 p-3 rounded-md"
|
||||
key={info.label}
|
||||
>
|
||||
<div className="pb-0.5 text-xs text-th-fgd-3">
|
||||
{info.label}
|
||||
</div>
|
||||
<div className="flex items-center">
|
||||
{info.icon}
|
||||
<div className="text-lg text-th-fgd-1">{`${info.currency}${info.value}${info.unit}`}</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
) : null}
|
||||
<div className="border-b border-th-fgd-4 mb-4">
|
||||
<nav className={`-mb-px flex space-x-6`} aria-label="Tabs">
|
||||
{TABS.map((tabName) => (
|
||||
<a
|
||||
key={tabName}
|
||||
onClick={() => handleTabChange(tabName)}
|
||||
className={`whitespace-nowrap pb-4 px-1 border-b-2 font-semibold cursor-pointer default-transition hover:opacity-100
|
||||
${
|
||||
activeTab === tabName
|
||||
? `border-th-primary text-th-primary`
|
||||
: `border-transparent text-th-fgd-4 hover:text-th-primary`
|
||||
}
|
||||
`}
|
||||
>
|
||||
{tabName}
|
||||
</a>
|
||||
))}
|
||||
</nav>
|
||||
</div>
|
||||
<TabContent activeTab={activeTab} />
|
||||
</>
|
||||
) : connected ? (
|
||||
<EmptyState
|
||||
buttonText="Create Account"
|
||||
icon={<CurrencyDollarIcon />}
|
||||
onClickButton={() => setShowAccountsModal(true)}
|
||||
title="No Account Found"
|
||||
/>
|
||||
) : (
|
||||
<EmptyState
|
||||
desc="Connect a wallet to view your account"
|
||||
icon={<LinkIcon />}
|
||||
title="Connect Wallet"
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</PageBodyContainer>
|
||||
{showAccountsModal ? (
|
||||
<AccountsModal
|
||||
onClose={handleCloseAccounts}
|
||||
isOpen={showAccountsModal}
|
||||
/>
|
||||
) : null}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const TabContent = ({ activeTab }) => {
|
||||
switch (activeTab) {
|
||||
case 'Assets':
|
||||
return <AccountAssets />
|
||||
case 'Borrows':
|
||||
return <AccountBorrows />
|
||||
case 'Stats':
|
||||
return <div>Stats</div>
|
||||
case 'Positions':
|
||||
return <div>Positions</div>
|
||||
case 'Orders':
|
||||
return <AccountOrders />
|
||||
case 'History':
|
||||
return <AccountHistory />
|
||||
default:
|
||||
return <AccountAssets />
|
||||
}
|
||||
}
|
|
@ -17,6 +17,7 @@ import Button, { LinkButton } from '../components/Button'
|
|||
import AlertsModal from '../components/AlertsModal'
|
||||
import AlertItem from '../components/AlertItem'
|
||||
import PageBodyContainer from '../components/PageBodyContainer'
|
||||
import EmptyState from '../components/EmptyState'
|
||||
import { abbreviateAddress } from '../utils'
|
||||
|
||||
const relativeTime = require('dayjs/plugin/relativeTime')
|
||||
|
@ -103,34 +104,27 @@ export default function Alerts() {
|
|||
</button>
|
||||
)}
|
||||
</RadioGroup.Option>
|
||||
{marginAccounts
|
||||
.slice()
|
||||
.sort(
|
||||
(a, b) =>
|
||||
(a.publicKey.toBase58() > b.publicKey.toBase58() && 1) ||
|
||||
-1
|
||||
)
|
||||
.map((acc, i) => (
|
||||
<RadioGroup.Option
|
||||
value={acc.publicKey.toString()}
|
||||
className="focus:outline-none flex-1"
|
||||
key={i}
|
||||
>
|
||||
{({ checked }) => (
|
||||
<button
|
||||
className={`${
|
||||
checked ? 'bg-th-bkg-3' : ''
|
||||
} font-normal text-th-fgd-1 text-center py-1.5 h-full w-full rounded-none ${
|
||||
i === marginAccounts.length - 1
|
||||
? 'rounded-r-md'
|
||||
: null
|
||||
} border-l border-th-fgd-4 hover:bg-th-bkg-3 focus:outline-none`}
|
||||
>
|
||||
{abbreviateAddress(acc.publicKey)}
|
||||
</button>
|
||||
)}
|
||||
</RadioGroup.Option>
|
||||
))}
|
||||
{marginAccounts.map((acc, i) => (
|
||||
<RadioGroup.Option
|
||||
value={acc.publicKey.toString()}
|
||||
className="focus:outline-none flex-1"
|
||||
key={i}
|
||||
>
|
||||
{({ checked }) => (
|
||||
<button
|
||||
className={`${
|
||||
checked ? 'bg-th-bkg-3' : ''
|
||||
} font-normal text-th-fgd-1 text-center py-1.5 h-full w-full rounded-none ${
|
||||
i === marginAccounts.length - 1
|
||||
? 'rounded-r-md'
|
||||
: null
|
||||
} border-l border-th-fgd-4 hover:bg-th-bkg-3 focus:outline-none`}
|
||||
>
|
||||
{abbreviateAddress(acc.publicKey)}
|
||||
</button>
|
||||
)}
|
||||
</RadioGroup.Option>
|
||||
))}
|
||||
</RadioGroup>
|
||||
) : null}
|
||||
<Button
|
||||
|
@ -189,13 +183,11 @@ export default function Alerts() {
|
|||
/>
|
||||
</>
|
||||
) : (
|
||||
<div className="flex flex-col items-center text-th-fgd-1 px-4 pb-2 rounded-lg">
|
||||
<LinkIcon className="w-6 h-6 mb-1 text-th-primary" />
|
||||
<div className="font-bold text-lg pb-1">Connect Wallet</div>
|
||||
<p className="mb-0 text-center">
|
||||
Connect your wallet to view and create liquidation alerts.
|
||||
</p>
|
||||
</div>
|
||||
<EmptyState
|
||||
desc="Connect a wallet to view and create liquidation alerts"
|
||||
icon={<LinkIcon />}
|
||||
title="Connect Wallet"
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</PageBodyContainer>
|
||||
|
|
|
@ -0,0 +1,55 @@
|
|||
import { useCallback, useState } from 'react'
|
||||
import { CurrencyDollarIcon, LinkIcon } from '@heroicons/react/outline'
|
||||
import useMangoStore from '../stores/useMangoStore'
|
||||
import PageBodyContainer from '../components/PageBodyContainer'
|
||||
import TopBar from '../components/TopBar'
|
||||
import EmptyState from '../components/EmptyState'
|
||||
import AccountsModal from '../components/AccountsModal'
|
||||
import AccountBorrows from '../components/account-page/AccountBorrows'
|
||||
|
||||
export default function Borrow() {
|
||||
const [showAccountsModal, setShowAccountsModal] = useState(false)
|
||||
const connected = useMangoStore((s) => s.wallet.connected)
|
||||
const selectedMarginAccount = useMangoStore(
|
||||
(s) => s.selectedMarginAccount.current
|
||||
)
|
||||
const handleCloseAccounts = useCallback(() => {
|
||||
setShowAccountsModal(false)
|
||||
}, [])
|
||||
return (
|
||||
<div className={`bg-th-bkg-1 text-th-fgd-1 transition-all`}>
|
||||
<TopBar />
|
||||
<PageBodyContainer>
|
||||
<div className="flex flex-col sm:flex-row pt-8 pb-3 sm:pb-6 md:pt-10">
|
||||
<h1 className={`text-th-fgd-1 text-2xl font-semibold`}>
|
||||
Borrow Funds
|
||||
</h1>
|
||||
</div>
|
||||
<div className="p-6 rounded-lg bg-th-bkg-2">
|
||||
{selectedMarginAccount ? (
|
||||
<AccountBorrows />
|
||||
) : connected ? (
|
||||
<EmptyState
|
||||
buttonText="Create Account"
|
||||
icon={<CurrencyDollarIcon />}
|
||||
onClickButton={() => setShowAccountsModal(true)}
|
||||
title="No Account Found"
|
||||
/>
|
||||
) : (
|
||||
<EmptyState
|
||||
desc="Connect a wallet to view and create borrows"
|
||||
icon={<LinkIcon />}
|
||||
title="Connect Wallet"
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</PageBodyContainer>
|
||||
{showAccountsModal ? (
|
||||
<AccountsModal
|
||||
onClose={handleCloseAccounts}
|
||||
isOpen={showAccountsModal}
|
||||
/>
|
||||
) : null}
|
||||
</div>
|
||||
)
|
||||
}
|
|
@ -0,0 +1,661 @@
|
|||
|
||||
{
|
||||
"header": {
|
||||
"reportVersion": 1,
|
||||
"event": "Allocation failed - JavaScript heap out of memory",
|
||||
"trigger": "FatalError",
|
||||
"filename": "report.20210602.231048.64561.0.001.json",
|
||||
"dumpEventTime": "2021-06-02T23:10:48Z",
|
||||
"dumpEventTimeStamp": "1622639448044",
|
||||
"processId": 64561,
|
||||
"cwd": "/Users/samluke/Desktop/Projects/mango/v3/mango-ui-v2",
|
||||
"commandLine": [
|
||||
"node",
|
||||
"/Users/samluke/Desktop/Projects/mango/v3/mango-ui-v2/node_modules/.bin/next",
|
||||
"dev"
|
||||
],
|
||||
"nodejsVersion": "v12.14.0",
|
||||
"wordSize": 64,
|
||||
"arch": "x64",
|
||||
"platform": "darwin",
|
||||
"componentVersions": {
|
||||
"node": "12.14.0",
|
||||
"v8": "7.7.299.13-node.16",
|
||||
"uv": "1.33.1",
|
||||
"zlib": "1.2.11",
|
||||
"brotli": "1.0.7",
|
||||
"ares": "1.15.0",
|
||||
"modules": "72",
|
||||
"nghttp2": "1.39.2",
|
||||
"napi": "5",
|
||||
"llhttp": "1.1.4",
|
||||
"http_parser": "2.8.0",
|
||||
"openssl": "1.1.1d",
|
||||
"cldr": "35.1",
|
||||
"icu": "64.2",
|
||||
"tz": "2019c",
|
||||
"unicode": "12.1"
|
||||
},
|
||||
"release": {
|
||||
"name": "node",
|
||||
"lts": "Erbium",
|
||||
"headersUrl": "https://nodejs.org/download/release/v12.14.0/node-v12.14.0-headers.tar.gz",
|
||||
"sourceUrl": "https://nodejs.org/download/release/v12.14.0/node-v12.14.0.tar.gz"
|
||||
},
|
||||
"osName": "Darwin",
|
||||
"osRelease": "16.4.0",
|
||||
"osVersion": "Darwin Kernel Version 16.4.0: Thu Dec 22 22:53:21 PST 2016; root:xnu-3789.41.3~3/RELEASE_X86_64",
|
||||
"osMachine": "x86_64",
|
||||
"cpus": [
|
||||
{
|
||||
"model": "Intel(R) Core(TM) i5-5257U CPU @ 2.70GHz",
|
||||
"speed": 2700,
|
||||
"user": 91006670,
|
||||
"nice": 0,
|
||||
"sys": 68670550,
|
||||
"idle": 289634560,
|
||||
"irq": 0
|
||||
},
|
||||
{
|
||||
"model": "Intel(R) Core(TM) i5-5257U CPU @ 2.70GHz",
|
||||
"speed": 2700,
|
||||
"user": 59652590,
|
||||
"nice": 0,
|
||||
"sys": 23759920,
|
||||
"idle": 365882090,
|
||||
"irq": 0
|
||||
},
|
||||
{
|
||||
"model": "Intel(R) Core(TM) i5-5257U CPU @ 2.70GHz",
|
||||
"speed": 2700,
|
||||
"user": 89189830,
|
||||
"nice": 0,
|
||||
"sys": 48334440,
|
||||
"idle": 311770570,
|
||||
"irq": 0
|
||||
},
|
||||
{
|
||||
"model": "Intel(R) Core(TM) i5-5257U CPU @ 2.70GHz",
|
||||
"speed": 2700,
|
||||
"user": 61353640,
|
||||
"nice": 0,
|
||||
"sys": 24618750,
|
||||
"idle": 363322020,
|
||||
"irq": 0
|
||||
}
|
||||
],
|
||||
"networkInterfaces": [
|
||||
{
|
||||
"name": "lo0",
|
||||
"internal": true,
|
||||
"mac": "00:00:00:00:00:00",
|
||||
"address": "127.0.0.1",
|
||||
"netmask": "255.0.0.0",
|
||||
"family": "IPv4"
|
||||
},
|
||||
{
|
||||
"name": "lo0",
|
||||
"internal": true,
|
||||
"mac": "00:00:00:00:00:00",
|
||||
"address": "::1",
|
||||
"netmask": "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff",
|
||||
"family": "IPv6",
|
||||
"scopeid": 0
|
||||
},
|
||||
{
|
||||
"name": "lo0",
|
||||
"internal": true,
|
||||
"mac": "00:00:00:00:00:00",
|
||||
"address": "fe80::1",
|
||||
"netmask": "ffff:ffff:ffff:ffff::",
|
||||
"family": "IPv6",
|
||||
"scopeid": 1
|
||||
},
|
||||
{
|
||||
"name": "en0",
|
||||
"internal": false,
|
||||
"mac": "d0:a6:37:ec:7f:ad",
|
||||
"address": "fe80::181c:d777:5cef:a110",
|
||||
"netmask": "ffff:ffff:ffff:ffff::",
|
||||
"family": "IPv6",
|
||||
"scopeid": 4
|
||||
},
|
||||
{
|
||||
"name": "en0",
|
||||
"internal": false,
|
||||
"mac": "d0:a6:37:ec:7f:ad",
|
||||
"address": "192.168.1.104",
|
||||
"netmask": "255.255.255.0",
|
||||
"family": "IPv4"
|
||||
},
|
||||
{
|
||||
"name": "awdl0",
|
||||
"internal": false,
|
||||
"mac": "f6:d2:ae:16:b8:ba",
|
||||
"address": "fe80::f4d2:aeff:fe16:b8ba",
|
||||
"netmask": "ffff:ffff:ffff:ffff::",
|
||||
"family": "IPv6",
|
||||
"scopeid": 9
|
||||
},
|
||||
{
|
||||
"name": "utun0",
|
||||
"internal": false,
|
||||
"mac": "00:00:00:00:00:00",
|
||||
"address": "fe80::e2b9:f89c:9a5c:241b",
|
||||
"netmask": "ffff:ffff:ffff:ffff::",
|
||||
"family": "IPv6",
|
||||
"scopeid": 10
|
||||
}
|
||||
],
|
||||
"host": "Sams-MacBook-Pro.local"
|
||||
},
|
||||
"javascriptStack": {
|
||||
"message": "No stack.",
|
||||
"stack": [
|
||||
"Unavailable."
|
||||
]
|
||||
},
|
||||
"nativeStack": [
|
||||
{
|
||||
"pc": "0x000000010014db86",
|
||||
"symbol": "report::TriggerNodeReport(v8::Isolate*, node::Environment*, char const*, char const*, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, v8::Local<v8::String>) [/Users/samluke/.nvm/versions/node/v12.14.0/bin/node]"
|
||||
},
|
||||
{
|
||||
"pc": "0x000000010007eb13",
|
||||
"symbol": "node::OnFatalError(char const*, char const*) [/Users/samluke/.nvm/versions/node/v12.14.0/bin/node]"
|
||||
},
|
||||
{
|
||||
"pc": "0x0000000100176337",
|
||||
"symbol": "v8::Utils::ReportOOMFailure(v8::internal::Isolate*, char const*, bool) [/Users/samluke/.nvm/versions/node/v12.14.0/bin/node]"
|
||||
},
|
||||
{
|
||||
"pc": "0x00000001001762d3",
|
||||
"symbol": "v8::internal::V8::FatalProcessOutOfMemory(v8::internal::Isolate*, char const*, bool) [/Users/samluke/.nvm/versions/node/v12.14.0/bin/node]"
|
||||
},
|
||||
{
|
||||
"pc": "0x00000001002fa485",
|
||||
"symbol": "v8::internal::Heap::FatalProcessOutOfMemory(char const*) [/Users/samluke/.nvm/versions/node/v12.14.0/bin/node]"
|
||||
},
|
||||
{
|
||||
"pc": "0x00000001002fbb54",
|
||||
"symbol": "v8::internal::Heap::RecomputeLimits(v8::internal::GarbageCollector) [/Users/samluke/.nvm/versions/node/v12.14.0/bin/node]"
|
||||
},
|
||||
{
|
||||
"pc": "0x00000001002f8a27",
|
||||
"symbol": "v8::internal::Heap::PerformGarbageCollection(v8::internal::GarbageCollector, v8::GCCallbackFlags) [/Users/samluke/.nvm/versions/node/v12.14.0/bin/node]"
|
||||
},
|
||||
{
|
||||
"pc": "0x00000001002f6a0d",
|
||||
"symbol": "v8::internal::Heap::CollectGarbage(v8::internal::AllocationSpace, v8::internal::GarbageCollectionReason, v8::GCCallbackFlags) [/Users/samluke/.nvm/versions/node/v12.14.0/bin/node]"
|
||||
},
|
||||
{
|
||||
"pc": "0x0000000100302124",
|
||||
"symbol": "v8::internal::Heap::AllocateRawWithLightRetry(int, v8::internal::AllocationType, v8::internal::AllocationAlignment) [/Users/samluke/.nvm/versions/node/v12.14.0/bin/node]"
|
||||
},
|
||||
{
|
||||
"pc": "0x000000010030219f",
|
||||
"symbol": "v8::internal::Heap::AllocateRawWithRetryOrFail(int, v8::internal::AllocationType, v8::internal::AllocationAlignment) [/Users/samluke/.nvm/versions/node/v12.14.0/bin/node]"
|
||||
},
|
||||
{
|
||||
"pc": "0x00000001002ced97",
|
||||
"symbol": "v8::internal::Factory::NewFillerObject(int, bool, v8::internal::AllocationType) [/Users/samluke/.nvm/versions/node/v12.14.0/bin/node]"
|
||||
},
|
||||
{
|
||||
"pc": "0x00000001005f83e5",
|
||||
"symbol": "v8::internal::Runtime_AllocateInYoungGeneration(int, unsigned long*, v8::internal::Isolate*) [/Users/samluke/.nvm/versions/node/v12.14.0/bin/node]"
|
||||
},
|
||||
{
|
||||
"pc": "0x0000000100930c99",
|
||||
"symbol": "Builtins_CEntry_Return1_DontSaveFPRegs_ArgvOnStack_NoBuiltinExit [/Users/samluke/.nvm/versions/node/v12.14.0/bin/node]"
|
||||
},
|
||||
{
|
||||
"pc": "0x000034df0a3ca47f",
|
||||
"symbol": ""
|
||||
},
|
||||
{
|
||||
"pc": "0x00000001008aa97c",
|
||||
"symbol": "Builtins_ArgumentsAdaptorTrampoline [/Users/samluke/.nvm/versions/node/v12.14.0/bin/node]"
|
||||
},
|
||||
{
|
||||
"pc": "0x00000001008b12e4",
|
||||
"symbol": "Builtins_InterpreterEntryTrampoline [/Users/samluke/.nvm/versions/node/v12.14.0/bin/node]"
|
||||
},
|
||||
{
|
||||
"pc": "0x00000001008b12e4",
|
||||
"symbol": "Builtins_InterpreterEntryTrampoline [/Users/samluke/.nvm/versions/node/v12.14.0/bin/node]"
|
||||
},
|
||||
{
|
||||
"pc": "0x00000001008aa97c",
|
||||
"symbol": "Builtins_ArgumentsAdaptorTrampoline [/Users/samluke/.nvm/versions/node/v12.14.0/bin/node]"
|
||||
},
|
||||
{
|
||||
"pc": "0x0000000100943908",
|
||||
"symbol": "Builtins_ArrayForEach [/Users/samluke/.nvm/versions/node/v12.14.0/bin/node]"
|
||||
},
|
||||
{
|
||||
"pc": "0x00000001008b12e4",
|
||||
"symbol": "Builtins_InterpreterEntryTrampoline [/Users/samluke/.nvm/versions/node/v12.14.0/bin/node]"
|
||||
}
|
||||
],
|
||||
"javascriptHeap": {
|
||||
"totalMemory": 2150522880,
|
||||
"totalCommittedMemory": 2148776200,
|
||||
"usedMemory": 2136440104,
|
||||
"availableMemory": 50232728,
|
||||
"memoryLimit": 2197815296,
|
||||
"heapSpaces": {
|
||||
"read_only_space": {
|
||||
"memorySize": 262144,
|
||||
"committedMemory": 32568,
|
||||
"capacity": 261872,
|
||||
"used": 32296,
|
||||
"available": 229576
|
||||
},
|
||||
"new_space": {
|
||||
"memorySize": 2097152,
|
||||
"committedMemory": 1069536,
|
||||
"capacity": 1047488,
|
||||
"used": 19968,
|
||||
"available": 1027520
|
||||
},
|
||||
"old_space": {
|
||||
"memorySize": 394375168,
|
||||
"committedMemory": 394201344,
|
||||
"capacity": 387202864,
|
||||
"used": 386567136,
|
||||
"available": 635728
|
||||
},
|
||||
"code_space": {
|
||||
"memorySize": 4620288,
|
||||
"committedMemory": 4466240,
|
||||
"capacity": 3824672,
|
||||
"used": 3824672,
|
||||
"available": 0
|
||||
},
|
||||
"map_space": {
|
||||
"memorySize": 6819840,
|
||||
"committedMemory": 6658224,
|
||||
"capacity": 4320400,
|
||||
"used": 4320400,
|
||||
"available": 0
|
||||
},
|
||||
"large_object_space": {
|
||||
"memorySize": 1741152256,
|
||||
"committedMemory": 1741152256,
|
||||
"capacity": 1740577872,
|
||||
"used": 1740577872,
|
||||
"available": 0
|
||||
},
|
||||
"code_large_object_space": {
|
||||
"memorySize": 1196032,
|
||||
"committedMemory": 1196032,
|
||||
"capacity": 1097760,
|
||||
"used": 1097760,
|
||||
"available": 0
|
||||
},
|
||||
"new_large_object_space": {
|
||||
"memorySize": 0,
|
||||
"committedMemory": 0,
|
||||
"capacity": 1047488,
|
||||
"used": 0,
|
||||
"available": 1047488
|
||||
}
|
||||
}
|
||||
},
|
||||
"resourceUsage": {
|
||||
"userCpuSeconds": 805.165,
|
||||
"kernelCpuSeconds": 198.771,
|
||||
"cpuConsumptionPercent": 1.70023,
|
||||
"maxRss": 736058408960,
|
||||
"pageFaults": {
|
||||
"IORequired": 2846,
|
||||
"IONotRequired": 21268316
|
||||
},
|
||||
"fsActivity": {
|
||||
"reads": 39477,
|
||||
"writes": 6047
|
||||
}
|
||||
},
|
||||
"libuv": [
|
||||
],
|
||||
"environmentVariables": {
|
||||
"npm_package_devDependencies_lint_staged": "^10.0.10",
|
||||
"npm_package_devDependencies_identity_obj_proxy": "^3.0.0",
|
||||
"npm_package_devDependencies_prettier": "^2.0.2",
|
||||
"TERM_PROGRAM": "Apple_Terminal",
|
||||
"NODE": "/Users/samluke/.nvm/versions/node/v12.14.0/bin/node",
|
||||
"npm_config_version_git_tag": "true",
|
||||
"npm_package_devDependencies_typescript": "^4.1.3",
|
||||
"NVM_CD_FLAGS": "",
|
||||
"npm_package_dependencies_react_grid_layout": "^1.2.4",
|
||||
"npm_package_devDependencies_jest": "^26.6.3",
|
||||
"TERM": "xterm-256color",
|
||||
"SHELL": "/bin/bash",
|
||||
"npm_package_dependencies__emotion_styled": "^11.1.5",
|
||||
"npm_package_dependencies__project_serum_serum": "^0.13.20",
|
||||
"TMPDIR": "/var/folders/4s/95t514691qd938qmf1cxcj8m0000gn/T/",
|
||||
"npm_config_init_license": "MIT",
|
||||
"npm_package_scripts_lint": "eslint . --ext ts --ext tsx --ext js --quiet",
|
||||
"CONDA_SHLVL": "1",
|
||||
"Apple_PubSub_Socket_Render": "/private/tmp/com.apple.launchd.ealgMWYqCt/Render",
|
||||
"CONDA_PROMPT_MODIFIER": "(base) ",
|
||||
"TERM_PROGRAM_VERSION": "388",
|
||||
"npm_package_scripts_dev": "next dev",
|
||||
"npm_package_husky_hooks_pre_push": "",
|
||||
"TERM_SESSION_ID": "DC2550B6-7F01-4BE8-B540-87BAA015E047",
|
||||
"npm_config_registry": "https://registry.yarnpkg.com",
|
||||
"npm_package_dependencies__headlessui_react": "^1.2.0",
|
||||
"npm_package_dependencies__project_serum_sol_wallet_adapter": "^0.1.8",
|
||||
"npm_package_dependencies__tippyjs_react": "^4.2.5",
|
||||
"npm_package_dependencies_react_dom": "^17.0.1",
|
||||
"npm_package_lint_staged_____ts_tsx__1": "yarn format",
|
||||
"npm_package_dependencies_dayjs": "^1.10.4",
|
||||
"npm_package_readmeFilename": "README.md",
|
||||
"npm_config_python": "/usr/bin/python",
|
||||
"npm_package_lint_staged_____ts_tsx__0": "yarn lint",
|
||||
"npm_package_devDependencies__testing_library_react": "^11.2.5",
|
||||
"npm_package_description": "Uses:",
|
||||
"NVM_DIR": "/Users/samluke/.nvm",
|
||||
"USER": "samluke",
|
||||
"npm_package_license": "MIT",
|
||||
"npm_package_devDependencies__types_react": "^17.0.1",
|
||||
"npm_package_dependencies_bs58": "^4.0.1",
|
||||
"npm_package_devDependencies__emotion_babel_plugin": "^11.2.0",
|
||||
"npm_package_dependencies__solana_web3_js": "^0.90.5",
|
||||
"CONDA_EXE": "/opt/anaconda3/bin/conda",
|
||||
"npm_package_devDependencies__babel_core": "^7.13.10",
|
||||
"npm_package_devDependencies_babel_jest": "^26.6.3",
|
||||
"npm_package_dependencies_zustand": "^3.3.3",
|
||||
"SSH_AUTH_SOCK": "/private/tmp/com.apple.launchd.ShYHBkXDSQ/Listeners",
|
||||
"npm_package_devDependencies__types_jest": "^26.0.20",
|
||||
"npm_package_devDependencies_babel_plugin_styled_components": "^1.12.0",
|
||||
"npm_package_devDependencies_eslint": "^7.19.0",
|
||||
"npm_package_devDependencies_postcss": "^8.2.8",
|
||||
"__CF_USER_TEXT_ENCODING": "0x1F5:0x0:0xF",
|
||||
"npm_package_husky_hooks_pre_commit": "lint-staged",
|
||||
"npm_package_dependencies_buffer_layout": "^1.2.0",
|
||||
"npm_package_dependencies_rc_slider": "^9.7.2",
|
||||
"npm_package_devDependencies__typescript_eslint_eslint_plugin": "^4.14.2",
|
||||
"npm_execpath": "/usr/local/Cellar/yarn/1.6.0/libexec/bin/yarn.js",
|
||||
"npm_package_author_name": "@erikdstock",
|
||||
"npm_package_dependencies_react_cool_dimensions": "^2.0.1",
|
||||
"npm_package_scripts_type_check": "tsc --pretty --noEmit",
|
||||
"npm_package_devDependencies__svgr_webpack": "^5.5.0",
|
||||
"npm_package_devDependencies_twin_macro": "^2.4.1",
|
||||
"_CE_CONDA": "",
|
||||
"npm_package_dependencies__blockworks_foundation_mango_client": "https://github.com/blockworks-foundation/mango-client-ts#5_tokens",
|
||||
"npm_package_devDependencies__typescript_eslint_parser": "^4.14.2",
|
||||
"npm_package_dependencies_immer": "^9.0.1",
|
||||
"npm_config_argv": "{\"remain\":[],\"cooked\":[\"run\",\"dev\"],\"original\":[\"dev\"]}",
|
||||
"PATH": "/Users/samluke/Desktop/Projects/mango/v3/mango-ui-v2/node_modules/.bin:/Users/samluke/.config/yarn/link/node_modules/.bin:/Users/samluke/Desktop/Projects/mango/v3/mango-ui-v2/node_modules/.bin:/Users/samluke/.config/yarn/link/node_modules/.bin:/Users/samluke/.nvm/versions/node/v12.14.0/libexec/lib/node_modules/npm/bin/node-gyp-bin:/Users/samluke/.nvm/versions/node/v12.14.0/lib/node_modules/npm/bin/node-gyp-bin:/Users/samluke/.nvm/versions/node/v12.14.0/bin/node_modules/npm/bin/node-gyp-bin:/opt/anaconda3/bin:/opt/anaconda3/condabin:/Library/Frameworks/Python.framework/Versions/3.9/bin:/Users/samluke/mongodb-macos-x86_64-4.2.3/bin:/Users/samluke/.nvm/versions/node/v12.14.0/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin",
|
||||
"npm_package_dependencies_react_super_responsive_table": "^5.2.0",
|
||||
"npm_package_devDependencies_babel_plugin_macros": "^3.1.0",
|
||||
"_": "/Users/samluke/Desktop/Projects/mango/v3/mango-ui-v2/node_modules/.bin/next",
|
||||
"npm_package_devDependencies__next_bundle_analyzer": "^10.1.3",
|
||||
"CONDA_PREFIX": "/opt/anaconda3",
|
||||
"npm_package_devDependencies_tailwindcss": "^2.1.2",
|
||||
"PWD": "/Users/samluke/desktop/projects/mango/v3/mango-ui-v2",
|
||||
"npm_package_devDependencies_eslint_plugin_react_hooks": "^4.2.0",
|
||||
"npm_lifecycle_event": "dev",
|
||||
"npm_package_name": "with-typescript-eslint-jest",
|
||||
"npm_package_dependencies_immutable_tuple": "^0.4.10",
|
||||
"LANG": "en_AU.UTF-8",
|
||||
"npm_package_dependencies_react_phone_input_2": "^2.14.0",
|
||||
"npm_package_scripts_build": "next build",
|
||||
"npm_package_scripts_start": "next start",
|
||||
"XPC_FLAGS": "0x0",
|
||||
"npm_package_dependencies_next": "^10.1.3",
|
||||
"npm_package_devDependencies_eslint_config_prettier": "^7.2.0",
|
||||
"npm_package_version": "1.0.0",
|
||||
"npm_package_dependencies__emotion_react": "^11.1.5",
|
||||
"_CE_M": "",
|
||||
"XPC_SERVICE_NAME": "0",
|
||||
"npm_package_babelMacros_twin_preset": "styled-components",
|
||||
"HOME": "/Users/samluke",
|
||||
"SHLVL": "2",
|
||||
"npm_package_scripts_test": "jest",
|
||||
"npm_package_dependencies_postcss_preset_env": "^6.7.0",
|
||||
"npm_config_strict_ssl": "true",
|
||||
"npm_config_save_prefix": "^",
|
||||
"npm_config_version_git_message": "v%s",
|
||||
"npm_package_devDependencies_husky": "^4.2.3",
|
||||
"npm_package_dependencies_bn_js": "^5.2.0",
|
||||
"NPM_CONFIG_PYTHON": "/usr/bin/python",
|
||||
"npm_package_dependencies__heroicons_react": "^1.0.0",
|
||||
"npm_package_scripts_format": "prettier --check .",
|
||||
"YARN_WRAP_OUTPUT": "false",
|
||||
"CONDA_PYTHON_EXE": "/opt/anaconda3/bin/python",
|
||||
"LOGNAME": "samluke",
|
||||
"npm_package_devDependencies_react_is": "^17.0.2",
|
||||
"npm_lifecycle_script": "next dev",
|
||||
"PREFIX": "/usr/local",
|
||||
"npm_package_dependencies_react": "^17.0.1",
|
||||
"CONDA_DEFAULT_ENV": "base",
|
||||
"NVM_BIN": "/Users/samluke/.nvm/versions/node/v12.14.0/bin",
|
||||
"npm_config_user_agent": "yarn/1.6.0 npm/? node/v12.14.0 darwin x64",
|
||||
"npm_config_ignore_scripts": "",
|
||||
"npm_config_version_git_sign": "",
|
||||
"npm_package_devDependencies_jest_watch_typeahead": "^0.6.1",
|
||||
"npm_package_dependencies_babel_plugin_import": "^1.13.3",
|
||||
"npm_package_dependencies_recharts": "^2.0.9",
|
||||
"npm_package_devDependencies__types_node": "^14.14.25",
|
||||
"npm_config_ignore_optional": "",
|
||||
"npm_config_init_version": "1.0.0",
|
||||
"SECURITYSESSIONID": "186a6",
|
||||
"npm_package_scripts_test_all": "yarn lint && yarn type-check && yarn test",
|
||||
"npm_config_version_tag_prefix": "v",
|
||||
"npm_package_dependencies_next_themes": "^0.0.14",
|
||||
"npm_package_dependencies_react_portal": "^4.2.1",
|
||||
"npm_package_devDependencies_eslint_plugin_react": "^7.19.0",
|
||||
"npm_node_execpath": "/Users/samluke/.nvm/versions/node/v12.14.0/bin/node",
|
||||
"NODE_ENV": "development",
|
||||
"TRACE_ID": "525e9cbb0be52e77"
|
||||
},
|
||||
"userLimits": {
|
||||
"core_file_size_blocks": {
|
||||
"soft": 0,
|
||||
"hard": "unlimited"
|
||||
},
|
||||
"data_seg_size_kbytes": {
|
||||
"soft": "unlimited",
|
||||
"hard": "unlimited"
|
||||
},
|
||||
"file_size_blocks": {
|
||||
"soft": "unlimited",
|
||||
"hard": "unlimited"
|
||||
},
|
||||
"max_locked_memory_bytes": {
|
||||
"soft": "unlimited",
|
||||
"hard": "unlimited"
|
||||
},
|
||||
"max_memory_size_kbytes": {
|
||||
"soft": "unlimited",
|
||||
"hard": "unlimited"
|
||||
},
|
||||
"open_files": {
|
||||
"soft": 10240,
|
||||
"hard": "unlimited"
|
||||
},
|
||||
"stack_size_bytes": {
|
||||
"soft": 8388608,
|
||||
"hard": 67104768
|
||||
},
|
||||
"cpu_time_seconds": {
|
||||
"soft": "unlimited",
|
||||
"hard": "unlimited"
|
||||
},
|
||||
"max_user_processes": {
|
||||
"soft": 709,
|
||||
"hard": 1064
|
||||
},
|
||||
"virtual_memory_kbytes": {
|
||||
"soft": "unlimited",
|
||||
"hard": "unlimited"
|
||||
}
|
||||
},
|
||||
"sharedObjects": [
|
||||
"/Users/samluke/.nvm/versions/node/v12.14.0/bin/node",
|
||||
"/System/Library/Frameworks/CoreFoundation.framework/Versions/A/CoreFoundation",
|
||||
"/usr/lib/libSystem.B.dylib",
|
||||
"/usr/lib/libc++.1.dylib",
|
||||
"/usr/lib/libDiagnosticMessagesClient.dylib",
|
||||
"/usr/lib/libicucore.A.dylib",
|
||||
"/usr/lib/libobjc.A.dylib",
|
||||
"/usr/lib/libz.1.dylib",
|
||||
"/usr/lib/system/libcache.dylib",
|
||||
"/usr/lib/system/libcommonCrypto.dylib",
|
||||
"/usr/lib/system/libcompiler_rt.dylib",
|
||||
"/usr/lib/system/libcopyfile.dylib",
|
||||
"/usr/lib/system/libcorecrypto.dylib",
|
||||
"/usr/lib/system/libdispatch.dylib",
|
||||
"/usr/lib/system/libdyld.dylib",
|
||||
"/usr/lib/system/libkeymgr.dylib",
|
||||
"/usr/lib/system/liblaunch.dylib",
|
||||
"/usr/lib/system/libmacho.dylib",
|
||||
"/usr/lib/system/libquarantine.dylib",
|
||||
"/usr/lib/system/libremovefile.dylib",
|
||||
"/usr/lib/system/libsystem_asl.dylib",
|
||||
"/usr/lib/system/libsystem_blocks.dylib",
|
||||
"/usr/lib/system/libsystem_c.dylib",
|
||||
"/usr/lib/system/libsystem_configuration.dylib",
|
||||
"/usr/lib/system/libsystem_coreservices.dylib",
|
||||
"/usr/lib/system/libsystem_coretls.dylib",
|
||||
"/usr/lib/system/libsystem_dnssd.dylib",
|
||||
"/usr/lib/system/libsystem_info.dylib",
|
||||
"/usr/lib/system/libsystem_kernel.dylib",
|
||||
"/usr/lib/system/libsystem_m.dylib",
|
||||
"/usr/lib/system/libsystem_malloc.dylib",
|
||||
"/usr/lib/system/libsystem_network.dylib",
|
||||
"/usr/lib/system/libsystem_networkextension.dylib",
|
||||
"/usr/lib/system/libsystem_notify.dylib",
|
||||
"/usr/lib/system/libsystem_platform.dylib",
|
||||
"/usr/lib/system/libsystem_pthread.dylib",
|
||||
"/usr/lib/system/libsystem_sandbox.dylib",
|
||||
"/usr/lib/system/libsystem_secinit.dylib",
|
||||
"/usr/lib/system/libsystem_symptoms.dylib",
|
||||
"/usr/lib/system/libsystem_trace.dylib",
|
||||
"/usr/lib/system/libunwind.dylib",
|
||||
"/usr/lib/system/libxpc.dylib",
|
||||
"/usr/lib/libauto.dylib",
|
||||
"/usr/lib/libc++abi.dylib",
|
||||
"/System/Library/Frameworks/ApplicationServices.framework/Versions/A/ApplicationServices",
|
||||
"/System/Library/Frameworks/CoreGraphics.framework/Versions/A/CoreGraphics",
|
||||
"/System/Library/Frameworks/CoreText.framework/Versions/A/CoreText",
|
||||
"/System/Library/Frameworks/ImageIO.framework/Versions/A/ImageIO",
|
||||
"/System/Library/Frameworks/ApplicationServices.framework/Versions/A/Frameworks/ATS.framework/Versions/A/ATS",
|
||||
"/System/Library/Frameworks/ApplicationServices.framework/Versions/A/Frameworks/ColorSync.framework/Versions/A/ColorSync",
|
||||
"/System/Library/Frameworks/CoreServices.framework/Versions/A/CoreServices",
|
||||
"/System/Library/Frameworks/ApplicationServices.framework/Versions/A/Frameworks/HIServices.framework/Versions/A/HIServices",
|
||||
"/System/Library/Frameworks/ApplicationServices.framework/Versions/A/Frameworks/LangAnalysis.framework/Versions/A/LangAnalysis",
|
||||
"/System/Library/Frameworks/ApplicationServices.framework/Versions/A/Frameworks/PrintCore.framework/Versions/A/PrintCore",
|
||||
"/System/Library/Frameworks/ApplicationServices.framework/Versions/A/Frameworks/QD.framework/Versions/A/QD",
|
||||
"/System/Library/Frameworks/ApplicationServices.framework/Versions/A/Frameworks/SpeechSynthesis.framework/Versions/A/SpeechSynthesis",
|
||||
"/System/Library/Frameworks/CFNetwork.framework/Versions/A/CFNetwork",
|
||||
"/System/Library/Frameworks/IOKit.framework/Versions/A/IOKit",
|
||||
"/System/Library/Frameworks/Accelerate.framework/Versions/A/Accelerate",
|
||||
"/System/Library/Frameworks/Foundation.framework/Versions/C/Foundation",
|
||||
"/usr/lib/libbsm.0.dylib",
|
||||
"/System/Library/Frameworks/SystemConfiguration.framework/Versions/A/SystemConfiguration",
|
||||
"/System/Library/Frameworks/Security.framework/Versions/A/Security",
|
||||
"/usr/lib/libsqlite3.dylib",
|
||||
"/usr/lib/libxml2.2.dylib",
|
||||
"/usr/lib/libnetwork.dylib",
|
||||
"/usr/lib/libenergytrace.dylib",
|
||||
"/usr/lib/system/libkxld.dylib",
|
||||
"/usr/lib/libpcap.A.dylib",
|
||||
"/usr/lib/libcoretls.dylib",
|
||||
"/usr/lib/libcoretls_cfhelpers.dylib",
|
||||
"/usr/lib/libxar.1.dylib",
|
||||
"/usr/lib/libpam.2.dylib",
|
||||
"/usr/lib/libOpenScriptingUtil.dylib",
|
||||
"/usr/lib/libbz2.1.0.dylib",
|
||||
"/usr/lib/liblzma.5.dylib",
|
||||
"/System/Library/Frameworks/Accelerate.framework/Versions/A/Frameworks/vImage.framework/Versions/A/vImage",
|
||||
"/System/Library/Frameworks/Accelerate.framework/Versions/A/Frameworks/vecLib.framework/Versions/A/vecLib",
|
||||
"/System/Library/Frameworks/Accelerate.framework/Versions/A/Frameworks/vecLib.framework/Versions/A/libvDSP.dylib",
|
||||
"/System/Library/Frameworks/Accelerate.framework/Versions/A/Frameworks/vecLib.framework/Versions/A/libBNNS.dylib",
|
||||
"/System/Library/Frameworks/Accelerate.framework/Versions/A/Frameworks/vecLib.framework/Versions/A/libQuadrature.dylib",
|
||||
"/System/Library/Frameworks/Accelerate.framework/Versions/A/Frameworks/vecLib.framework/Versions/A/libvMisc.dylib",
|
||||
"/System/Library/Frameworks/Accelerate.framework/Versions/A/Frameworks/vecLib.framework/Versions/A/libLAPACK.dylib",
|
||||
"/System/Library/Frameworks/Accelerate.framework/Versions/A/Frameworks/vecLib.framework/Versions/A/libBLAS.dylib",
|
||||
"/System/Library/Frameworks/Accelerate.framework/Versions/A/Frameworks/vecLib.framework/Versions/A/libLinearAlgebra.dylib",
|
||||
"/System/Library/Frameworks/Accelerate.framework/Versions/A/Frameworks/vecLib.framework/Versions/A/libSparseBLAS.dylib",
|
||||
"/System/Library/Frameworks/CoreServices.framework/Versions/A/Frameworks/FSEvents.framework/Versions/A/FSEvents",
|
||||
"/System/Library/Frameworks/CoreServices.framework/Versions/A/Frameworks/CarbonCore.framework/Versions/A/CarbonCore",
|
||||
"/System/Library/Frameworks/CoreServices.framework/Versions/A/Frameworks/Metadata.framework/Versions/A/Metadata",
|
||||
"/System/Library/Frameworks/CoreServices.framework/Versions/A/Frameworks/OSServices.framework/Versions/A/OSServices",
|
||||
"/System/Library/Frameworks/CoreServices.framework/Versions/A/Frameworks/SearchKit.framework/Versions/A/SearchKit",
|
||||
"/System/Library/Frameworks/CoreServices.framework/Versions/A/Frameworks/AE.framework/Versions/A/AE",
|
||||
"/System/Library/Frameworks/CoreServices.framework/Versions/A/Frameworks/LaunchServices.framework/Versions/A/LaunchServices",
|
||||
"/System/Library/Frameworks/CoreServices.framework/Versions/A/Frameworks/DictionaryServices.framework/Versions/A/DictionaryServices",
|
||||
"/System/Library/Frameworks/CoreServices.framework/Versions/A/Frameworks/SharedFileList.framework/Versions/A/SharedFileList",
|
||||
"/System/Library/Frameworks/DiskArbitration.framework/Versions/A/DiskArbitration",
|
||||
"/System/Library/Frameworks/NetFS.framework/Versions/A/NetFS",
|
||||
"/System/Library/PrivateFrameworks/NetAuth.framework/Versions/A/NetAuth",
|
||||
"/System/Library/PrivateFrameworks/login.framework/Versions/A/Frameworks/loginsupport.framework/Versions/A/loginsupport",
|
||||
"/usr/lib/libarchive.2.dylib",
|
||||
"/usr/lib/liblangid.dylib",
|
||||
"/usr/lib/libCRFSuite.dylib",
|
||||
"/System/Library/PrivateFrameworks/TCC.framework/Versions/A/TCC",
|
||||
"/usr/lib/libmecabra.dylib",
|
||||
"/System/Library/PrivateFrameworks/LanguageModeling.framework/Versions/A/LanguageModeling",
|
||||
"/usr/lib/libmarisa.dylib",
|
||||
"/usr/lib/libChineseTokenizer.dylib",
|
||||
"/usr/lib/libcmph.dylib",
|
||||
"/usr/lib/libiconv.2.dylib",
|
||||
"/System/Library/Frameworks/CoreData.framework/Versions/A/CoreData",
|
||||
"/System/Library/PrivateFrameworks/CoreEmoji.framework/Versions/A/CoreEmoji",
|
||||
"/usr/lib/libcompression.dylib",
|
||||
"/System/Library/Frameworks/OpenDirectory.framework/Versions/A/Frameworks/CFOpenDirectory.framework/Versions/A/CFOpenDirectory",
|
||||
"/System/Library/Frameworks/ServiceManagement.framework/Versions/A/ServiceManagement",
|
||||
"/usr/lib/libxslt.1.dylib",
|
||||
"/System/Library/Frameworks/ApplicationServices.framework/Versions/A/Frameworks/ATS.framework/Versions/A/Resources/libFontParser.dylib",
|
||||
"/System/Library/Frameworks/ApplicationServices.framework/Versions/A/Frameworks/ATS.framework/Versions/A/Resources/libFontRegistry.dylib",
|
||||
"/System/Library/Frameworks/ImageIO.framework/Versions/A/Resources/libJPEG.dylib",
|
||||
"/System/Library/Frameworks/ImageIO.framework/Versions/A/Resources/libTIFF.dylib",
|
||||
"/System/Library/Frameworks/ImageIO.framework/Versions/A/Resources/libPng.dylib",
|
||||
"/System/Library/Frameworks/ImageIO.framework/Versions/A/Resources/libGIF.dylib",
|
||||
"/System/Library/Frameworks/ImageIO.framework/Versions/A/Resources/libJP2.dylib",
|
||||
"/System/Library/Frameworks/ImageIO.framework/Versions/A/Resources/libRadiance.dylib",
|
||||
"/System/Library/PrivateFrameworks/AppleJPEG.framework/Versions/A/AppleJPEG",
|
||||
"/System/Library/Frameworks/IOSurface.framework/Versions/A/IOSurface",
|
||||
"/System/Library/PrivateFrameworks/MultitouchSupport.framework/Versions/A/MultitouchSupport",
|
||||
"/usr/lib/libcups.2.dylib",
|
||||
"/System/Library/Frameworks/Kerberos.framework/Versions/A/Kerberos",
|
||||
"/System/Library/Frameworks/GSS.framework/Versions/A/GSS",
|
||||
"/usr/lib/libresolv.9.dylib",
|
||||
"/System/Library/PrivateFrameworks/Heimdal.framework/Versions/A/Heimdal",
|
||||
"/usr/lib/libheimdal-asn1.dylib",
|
||||
"/System/Library/Frameworks/OpenDirectory.framework/Versions/A/OpenDirectory",
|
||||
"/System/Library/PrivateFrameworks/CommonAuth.framework/Versions/A/CommonAuth",
|
||||
"/System/Library/Frameworks/SecurityFoundation.framework/Versions/A/SecurityFoundation",
|
||||
"/System/Library/Frameworks/CoreAudio.framework/Versions/A/CoreAudio",
|
||||
"/System/Library/Frameworks/AudioToolbox.framework/Versions/A/AudioToolbox",
|
||||
"/System/Library/PrivateFrameworks/SkyLight.framework/Versions/A/SkyLight",
|
||||
"/System/Library/Frameworks/Metal.framework/Versions/A/Metal",
|
||||
"/System/Library/Frameworks/CoreDisplay.framework/Versions/A/CoreDisplay",
|
||||
"/System/Library/Frameworks/QuartzCore.framework/Versions/A/QuartzCore",
|
||||
"/System/Library/PrivateFrameworks/GPUCompiler.framework/libmetal_timestamp.dylib",
|
||||
"/System/Library/Frameworks/OpenGL.framework/Versions/A/Libraries/libCoreFSCache.dylib",
|
||||
"/System/Library/PrivateFrameworks/IOAccelerator.framework/Versions/A/IOAccelerator",
|
||||
"/System/Library/PrivateFrameworks/IOPresentment.framework/Versions/A/IOPresentment",
|
||||
"/System/Library/Frameworks/CoreImage.framework/Versions/A/CoreImage",
|
||||
"/System/Library/Frameworks/CoreVideo.framework/Versions/A/CoreVideo",
|
||||
"/System/Library/Frameworks/OpenGL.framework/Versions/A/OpenGL",
|
||||
"/usr/lib/libFosl_dynamic.dylib",
|
||||
"/System/Library/PrivateFrameworks/MetalPerformanceShaders.framework/Versions/A/MetalPerformanceShaders",
|
||||
"/System/Library/PrivateFrameworks/FaceCore.framework/Versions/A/FaceCore",
|
||||
"/System/Library/Frameworks/OpenCL.framework/Versions/A/OpenCL",
|
||||
"/System/Library/Frameworks/OpenGL.framework/Versions/A/Libraries/libGLU.dylib",
|
||||
"/System/Library/Frameworks/OpenGL.framework/Versions/A/Libraries/libGFXShared.dylib",
|
||||
"/System/Library/Frameworks/OpenGL.framework/Versions/A/Libraries/libGL.dylib",
|
||||
"/System/Library/Frameworks/OpenGL.framework/Versions/A/Libraries/libGLImage.dylib",
|
||||
"/System/Library/Frameworks/OpenGL.framework/Versions/A/Libraries/libCVMSPluginSupport.dylib",
|
||||
"/System/Library/Frameworks/OpenGL.framework/Versions/A/Libraries/libCoreVMClient.dylib",
|
||||
"/Users/samluke/Desktop/Projects/mango/v3/mango-ui-v2/node_modules/fsevents/fsevents.node",
|
||||
"/Users/samluke/Desktop/Projects/mango/v3/mango-ui-v2/node_modules/bufferutil/prebuilds/darwin-x64/node.napi.node",
|
||||
"/Users/samluke/Desktop/Projects/mango/v3/mango-ui-v2/node_modules/utf-8-validate/prebuilds/darwin-x64/node.napi.node",
|
||||
"/Users/samluke/Desktop/Projects/mango/v3/mango-ui-v2/node_modules/secp256k1/prebuilds/darwin-x64/node.napi.node",
|
||||
"/Users/samluke/Desktop/Projects/mango/v3/mango-ui-v2/node_modules/keccak/prebuilds/darwin-x64/node.napi.node"
|
||||
]
|
||||
}
|
|
@ -32,8 +32,7 @@ export const ENDPOINTS: EndpointInfo[] = [
|
|||
|
||||
type ClusterType = 'mainnet-beta' | 'devnet'
|
||||
|
||||
const CLUSTER =
|
||||
(process.env.NEXT_PUBLIC_CLUSTER as ClusterType) || 'mainnet-beta'
|
||||
const CLUSTER = (process.env.NEXT_PUBLIC_CLUSTER as ClusterType) || 'devnet'
|
||||
const ENDPOINT = ENDPOINTS.find((e) => e.name === CLUSTER)
|
||||
const DEFAULT_CONNECTION = new Connection(ENDPOINT.url, 'recent')
|
||||
const WEBSOCKET_CONNECTION = new Connection(ENDPOINT.websocket, 'recent')
|
||||
|
@ -250,8 +249,14 @@ const useMangoStore = create<MangoStore>((set, get) => ({
|
|||
)
|
||||
.then((marginAccounts) => {
|
||||
if (marginAccounts.length > 0) {
|
||||
const sortedAccounts = marginAccounts
|
||||
.slice()
|
||||
.sort(
|
||||
(a, b) =>
|
||||
(a.publicKey.toBase58() > b.publicKey.toBase58() && 1) || -1
|
||||
)
|
||||
set((state) => {
|
||||
state.marginAccounts = marginAccounts
|
||||
state.marginAccounts = sortedAccounts
|
||||
if (state.selectedMarginAccount.current) {
|
||||
state.selectedMarginAccount.current = marginAccounts.find(
|
||||
(ma) =>
|
||||
|
@ -260,7 +265,14 @@ const useMangoStore = create<MangoStore>((set, get) => ({
|
|||
)
|
||||
)
|
||||
} else {
|
||||
state.selectedMarginAccount.current = marginAccounts[0]
|
||||
const lastAccount = localStorage.getItem('lastAccountViewed')
|
||||
console.log(JSON.parse(lastAccount))
|
||||
state.selectedMarginAccount.current = lastAccount
|
||||
? marginAccounts.find(
|
||||
(ma) =>
|
||||
ma.publicKey.toString() === JSON.parse(lastAccount)
|
||||
)
|
||||
: sortedAccounts[0]
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
@ -5,12 +5,13 @@
|
|||
/* Theme */
|
||||
|
||||
:root {
|
||||
--primary: theme('colors.light-theme.orange');
|
||||
--primary: theme('colors.light-theme.orange.DEFAULT');
|
||||
--primary-dark: theme('colors.light-theme.orange.dark');
|
||||
--red: theme('colors.light-theme.red.DEFAULT');
|
||||
--red-dark: theme('colors.light-theme.red.dark');
|
||||
--green: theme('colors.light-theme.green.DEFAULT');
|
||||
--green-dark: theme('colors.light-theme.green.dark');
|
||||
--orange: theme('colors.light-theme.orange');
|
||||
--orange: theme('colors.light-theme.orange.DEFAULT');
|
||||
--bkg-1: theme('colors.light-theme["bkg-1"]');
|
||||
--bkg-2: theme('colors.light-theme["bkg-2"]');
|
||||
--bkg-3: theme('colors.light-theme["bkg-3"]');
|
||||
|
@ -21,7 +22,8 @@
|
|||
}
|
||||
|
||||
[data-theme='Dark'] {
|
||||
--primary: theme('colors.dark-theme.yellow');
|
||||
--primary: theme('colors.dark-theme.yellow.DEFAULT');
|
||||
--primary-dark: theme('colors.dark-theme.yellow.dark');
|
||||
--red: theme('colors.dark-theme.red.DEFAULT');
|
||||
--red-dark: theme('colors.dark-theme.red.dark');
|
||||
--green: theme('colors.dark-theme.green.DEFAULT');
|
||||
|
@ -37,7 +39,8 @@
|
|||
}
|
||||
|
||||
[data-theme='Mango'] {
|
||||
--primary: theme('colors.mango-theme.yellow');
|
||||
--primary: theme('colors.mango-theme.yellow.DEFAULT');
|
||||
--primary-dark: theme('colors.mango-theme.yellow.dark');
|
||||
--red: theme('colors.mango-theme.red.DEFAULT');
|
||||
--red-dark: theme('colors.mango-theme.red.dark');
|
||||
--green: theme('colors.mango-theme.green.DEFAULT');
|
||||
|
@ -63,7 +66,7 @@ p {
|
|||
}
|
||||
|
||||
a {
|
||||
@apply text-th-primary transition-all duration-300 hover:opacity-70;
|
||||
@apply text-th-primary transition-all duration-300 hover:text-th-primary-dark;
|
||||
}
|
||||
|
||||
button {
|
||||
|
|
|
@ -49,7 +49,10 @@ module.exports = {
|
|||
darkest: '#061f23',
|
||||
},
|
||||
'light-theme': {
|
||||
orange: '#FF9C24',
|
||||
orange: {
|
||||
DEFAULT: '#FF9C24',
|
||||
dark: '#F58700',
|
||||
},
|
||||
red: { DEFAULT: '#CC2929', dark: '#AA2222' },
|
||||
green: { DEFAULT: '#5EBF4D', dark: '#4BA53B' },
|
||||
'bkg-1': '#f7f7f7',
|
||||
|
@ -61,7 +64,10 @@ module.exports = {
|
|||
'fgd-4': '#B0B0B0',
|
||||
},
|
||||
'dark-theme': {
|
||||
yellow: '#F2C94C',
|
||||
yellow: {
|
||||
DEFAULT: '#F2C94C',
|
||||
dark: '#E4AF11',
|
||||
},
|
||||
red: { DEFAULT: '#CC2929', dark: '#AA2222' },
|
||||
green: { DEFAULT: '#5EBF4D', dark: '#4BA53B' },
|
||||
orange: { DEFAULT: '#FF9C24' },
|
||||
|
@ -74,7 +80,10 @@ module.exports = {
|
|||
'fgd-4': '#878787',
|
||||
},
|
||||
'mango-theme': {
|
||||
yellow: '#F2C94C',
|
||||
yellow: {
|
||||
DEFAULT: '#F2C94C',
|
||||
dark: '#E4AF11',
|
||||
},
|
||||
red: {
|
||||
DEFAULT: '#E54033',
|
||||
dark: '#C7251A',
|
||||
|
@ -100,6 +109,7 @@ module.exports = {
|
|||
'th-fgd-3': 'var(--fgd-3)',
|
||||
'th-fgd-4': 'var(--fgd-4)',
|
||||
'th-primary': 'var(--primary)',
|
||||
'th-primary-dark': 'var(--primary-dark)',
|
||||
'th-red': 'var(--red)',
|
||||
'th-red-dark': 'var(--red-dark)',
|
||||
'th-green': 'var(--green)',
|
||||
|
|
|
@ -176,6 +176,17 @@ export const tokenPrecision = {
|
|||
WUSDT: 2,
|
||||
}
|
||||
|
||||
// Precision for depositing/withdrawing
|
||||
export const DECIMALS = {
|
||||
BTC: 5,
|
||||
ETH: 4,
|
||||
SOL: 2,
|
||||
SRM: 2,
|
||||
USDC: 2,
|
||||
USDT: 2,
|
||||
WUSDT: 2,
|
||||
}
|
||||
|
||||
export const getSymbolForTokenMintAddress = (address: string): string => {
|
||||
if (address && address.length) {
|
||||
return TOKEN_MINTS.find((m) => m.address.toString() === address)?.name || ''
|
||||
|
@ -197,3 +208,11 @@ export const copyToClipboard = (copyThis) => {
|
|||
document.execCommand('copy')
|
||||
document.body.removeChild(el)
|
||||
}
|
||||
|
||||
// Truncate decimals without rounding
|
||||
export const trimDecimals = (n, digits) => {
|
||||
var step = Math.pow(10, digits || 0)
|
||||
var temp = Math.trunc(step * n)
|
||||
|
||||
return temp / step
|
||||
}
|
||||
|
|
|
@ -1603,3 +1603,93 @@ async function packageAndSend(
|
|||
successMessage,
|
||||
})
|
||||
}
|
||||
|
||||
export async function settleAllTrades(
|
||||
connection: Connection,
|
||||
programId: PublicKey,
|
||||
mangoGroup: MangoGroup,
|
||||
marginAccount: MarginAccount,
|
||||
markets: Market[],
|
||||
wallet: Wallet
|
||||
): Promise<TransactionSignature> {
|
||||
const transaction = new Transaction()
|
||||
|
||||
const assetGains: number[] = new Array(NUM_TOKENS).fill(0)
|
||||
|
||||
for (let i = 0; i < NUM_MARKETS; i++) {
|
||||
const openOrdersAccount = marginAccount.openOrdersAccounts[i]
|
||||
if (openOrdersAccount === undefined) {
|
||||
continue
|
||||
} else if (
|
||||
openOrdersAccount.quoteTokenFree.toNumber() === 0 &&
|
||||
openOrdersAccount.baseTokenFree.toNumber() === 0
|
||||
) {
|
||||
continue
|
||||
}
|
||||
|
||||
assetGains[i] += openOrdersAccount.baseTokenFree.toNumber()
|
||||
assetGains[NUM_TOKENS - 1] += openOrdersAccount.quoteTokenFree.toNumber()
|
||||
|
||||
const spotMarket = markets[i]
|
||||
const dexSigner = await PublicKey.createProgramAddress(
|
||||
[
|
||||
spotMarket.publicKey.toBuffer(),
|
||||
spotMarket['_decoded'].vaultSignerNonce.toArrayLike(Buffer, 'le', 8),
|
||||
],
|
||||
spotMarket.programId
|
||||
)
|
||||
|
||||
const keys = [
|
||||
{ isSigner: false, isWritable: true, pubkey: mangoGroup.publicKey },
|
||||
{ isSigner: true, isWritable: false, pubkey: wallet.publicKey },
|
||||
{ isSigner: false, isWritable: true, pubkey: marginAccount.publicKey },
|
||||
{ isSigner: false, isWritable: false, pubkey: SYSVAR_CLOCK_PUBKEY },
|
||||
{ isSigner: false, isWritable: false, pubkey: spotMarket.programId },
|
||||
{ isSigner: false, isWritable: true, pubkey: spotMarket.publicKey },
|
||||
{
|
||||
isSigner: false,
|
||||
isWritable: true,
|
||||
pubkey: marginAccount.openOrders[i],
|
||||
},
|
||||
{ isSigner: false, isWritable: false, pubkey: mangoGroup.signerKey },
|
||||
{
|
||||
isSigner: false,
|
||||
isWritable: true,
|
||||
pubkey: spotMarket['_decoded'].baseVault,
|
||||
},
|
||||
{
|
||||
isSigner: false,
|
||||
isWritable: true,
|
||||
pubkey: spotMarket['_decoded'].quoteVault,
|
||||
},
|
||||
{ isSigner: false, isWritable: true, pubkey: mangoGroup.vaults[i] },
|
||||
{
|
||||
isSigner: false,
|
||||
isWritable: true,
|
||||
pubkey: mangoGroup.vaults[mangoGroup.vaults.length - 1],
|
||||
},
|
||||
{ isSigner: false, isWritable: false, pubkey: dexSigner },
|
||||
{ isSigner: false, isWritable: false, pubkey: TOKEN_PROGRAM_ID },
|
||||
]
|
||||
const data = encodeMangoInstruction({ SettleFunds: {} })
|
||||
|
||||
const settleFundsInstruction = new TransactionInstruction({
|
||||
keys,
|
||||
data,
|
||||
programId,
|
||||
})
|
||||
transaction.add(settleFundsInstruction)
|
||||
}
|
||||
|
||||
if (transaction.instructions.length === 0) {
|
||||
throw new Error('No unsettled funds')
|
||||
}
|
||||
|
||||
return await packageAndSend(
|
||||
transaction,
|
||||
connection,
|
||||
wallet,
|
||||
[],
|
||||
'Settle All Trades'
|
||||
)
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue