Merges main
This commit is contained in:
commit
9718bf5d38
|
@ -0,0 +1,113 @@
|
|||
name: 'Next.js Bundle Analysis'
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
push:
|
||||
branches:
|
||||
- main # change this if your default branch is named differently
|
||||
workflow_dispatch:
|
||||
|
||||
defaults:
|
||||
run:
|
||||
# change this if your nextjs app does not live at the root of the repo
|
||||
working-directory: ./
|
||||
|
||||
jobs:
|
||||
analyze:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- name: Set up node
|
||||
uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: '14.x'
|
||||
|
||||
- name: Install dependencies
|
||||
uses: bahmutov/npm-install@v1
|
||||
|
||||
- name: Restore next build
|
||||
uses: actions/cache@v2
|
||||
id: restore-build-cache
|
||||
env:
|
||||
cache-name: cache-next-build
|
||||
with:
|
||||
# if you use a custom build directory, replace all instances of `.next` in this file with your build directory
|
||||
# ex: if your app builds to `dist`, replace `.next` with `dist`
|
||||
path: .next/cache
|
||||
# change this if you prefer a more strict cache
|
||||
key: ${{ runner.os }}-build-${{ env.cache-name }}
|
||||
|
||||
- name: Build next.js app
|
||||
# change this if your site requires a custom build command
|
||||
run: ./node_modules/.bin/next build
|
||||
|
||||
# Here's the first place where next-bundle-analysis' own script is used
|
||||
# This step pulls the raw bundle stats for the current bundle
|
||||
- name: Analyze bundle
|
||||
run: npx -p nextjs-bundle-analysis report
|
||||
|
||||
- name: Upload bundle
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: bundle
|
||||
path: .next/analyze/__bundle_analysis.json
|
||||
|
||||
- name: Download base branch bundle stats
|
||||
uses: dawidd6/action-download-artifact@v2
|
||||
if: success() && github.event.number
|
||||
with:
|
||||
workflow: nextjs_bundle_analysis.yml
|
||||
branch: ${{ github.event.pull_request.base.ref }}
|
||||
path: .next/analyze/base
|
||||
|
||||
# And here's the second place - this runs after we have both the current and
|
||||
# base branch bundle stats, and will compare them to determine what changed.
|
||||
# There are two configurable arguments that come from package.json:
|
||||
#
|
||||
# - budget: optional, set a budget (bytes) against which size changes are measured
|
||||
# it's set to 350kb here by default, as informed by the following piece:
|
||||
# https://infrequently.org/2021/03/the-performance-inequality-gap/
|
||||
#
|
||||
# - red-status-percentage: sets the percent size increase where you get a red
|
||||
# status indicator, defaults to 20%
|
||||
#
|
||||
# Either of these arguments can be changed or removed by editing the `nextBundleAnalysis`
|
||||
# entry in your package.json file.
|
||||
- name: Compare with base branch bundle
|
||||
if: success() && github.event.number
|
||||
run: ls -laR .next/analyze/base && npx -p nextjs-bundle-analysis compare
|
||||
|
||||
- name: Get comment body
|
||||
id: get-comment-body
|
||||
if: success() && github.event.number
|
||||
run: |
|
||||
body=$(cat .next/analyze/__bundle_analysis_comment.txt)
|
||||
body="${body//'%'/'%25'}"
|
||||
body="${body//$'\n'/'%0A'}"
|
||||
body="${body//$'\r'/'%0D'}"
|
||||
echo ::set-output name=body::$body
|
||||
|
||||
- name: Find Comment
|
||||
uses: peter-evans/find-comment@v1
|
||||
if: success() && github.event.number
|
||||
id: fc
|
||||
with:
|
||||
issue-number: ${{ github.event.number }}
|
||||
body-includes: '<!-- __NEXTJS_BUNDLE -->'
|
||||
|
||||
- name: Create Comment
|
||||
uses: peter-evans/create-or-update-comment@v1.4.4
|
||||
if: success() && github.event.number && steps.fc.outputs.comment-id == 0
|
||||
with:
|
||||
issue-number: ${{ github.event.number }}
|
||||
body: ${{ steps.get-comment-body.outputs.body }}
|
||||
|
||||
- name: Update Comment
|
||||
uses: peter-evans/create-or-update-comment@v1.4.4
|
||||
if: success() && github.event.number && steps.fc.outputs.comment-id != 0
|
||||
with:
|
||||
issue-number: ${{ github.event.number }}
|
||||
body: ${{ steps.get-comment-body.outputs.body }}
|
||||
comment-id: ${{ steps.fc.outputs.comment-id }}
|
||||
edit-mode: replace
|
|
@ -5,13 +5,12 @@ name: Node.js CI
|
|||
|
||||
on:
|
||||
push:
|
||||
branches: [ main ]
|
||||
branches: [main]
|
||||
pull_request:
|
||||
branches: [ main ]
|
||||
branches: [main]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
strategy:
|
||||
|
@ -20,12 +19,12 @@ jobs:
|
|||
# See supported Node.js release schedule at https://nodejs.org/en/about/releases/
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Use Node.js ${{ matrix.node-version }}
|
||||
uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
cache: 'npm'
|
||||
- run: yarn install --frozen-lockfile
|
||||
- run: yarn type-check
|
||||
- run: yarn lint
|
||||
- uses: actions/checkout@v2
|
||||
- name: Use Node.js ${{ matrix.node-version }}
|
||||
uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
- run: yarn install --frozen-lockfile
|
||||
- run: yarn type-check
|
||||
- run: yarn lint
|
||||
- run: yarn prettier --check .
|
||||
|
|
|
@ -25,6 +25,7 @@ yarn-debug.log*
|
|||
yarn-error.log*
|
||||
|
||||
# local env files
|
||||
.env
|
||||
.env.local
|
||||
.env.development.local
|
||||
.env.test.local
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
#!/bin/sh
|
||||
. "$(dirname "$0")/_/husky.sh"
|
||||
|
||||
yarn lint-staged
|
||||
yarn lint-all
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { AccountInfo, PublicKey, Transaction } from '@solana/web3.js'
|
||||
import { AccountInfo, PublicKey } from '@solana/web3.js'
|
||||
import { Market, OpenOrders } from '@project-serum/serum'
|
||||
import { Event } from '@project-serum/serum/lib/queue'
|
||||
import { I80F48 } from '@blockworks-foundation/mango-client'
|
||||
|
@ -122,17 +122,6 @@ export const DEFAULT_PUBLIC_KEY = new PublicKey(
|
|||
'11111111111111111111111111111111'
|
||||
)
|
||||
|
||||
export interface WalletAdapter {
|
||||
publicKey: PublicKey
|
||||
autoApprove: boolean
|
||||
connected: boolean
|
||||
signTransaction: (transaction: Transaction) => Promise<Transaction>
|
||||
signAllTransactions: (transaction: Transaction[]) => Promise<Transaction[]>
|
||||
connect: () => any
|
||||
disconnect: () => any
|
||||
on(event: string, fn: () => void): this
|
||||
}
|
||||
|
||||
export interface PerpTriggerOrder {
|
||||
orderId: number
|
||||
marketIndex: number
|
||||
|
|
|
@ -29,17 +29,17 @@ import { useTranslation } from 'next-i18next'
|
|||
import useMangoAccount from '../hooks/useMangoAccount'
|
||||
import Loading from './Loading'
|
||||
import CreateAlertModal from './CreateAlertModal'
|
||||
import { useWallet } from '@solana/wallet-adapter-react'
|
||||
|
||||
const I80F48_100 = I80F48.fromString('100')
|
||||
|
||||
export default function AccountInfo() {
|
||||
const { t } = useTranslation('common')
|
||||
const connected = useMangoStore((s) => s.wallet.connected)
|
||||
const { publicKey, wallet, connected } = useWallet()
|
||||
const mangoGroup = useMangoStore((s) => s.selectedMangoGroup.current)
|
||||
const mangoCache = useMangoStore((s) => s.selectedMangoGroup.cache)
|
||||
const { mangoAccount, initialLoad } = useMangoAccount()
|
||||
const marketConfig = useMangoStore((s) => s.selectedMarket.config)
|
||||
const wallet = useMangoStore((s) => s.wallet.current)
|
||||
const actions = useMangoStore((s) => s.actions)
|
||||
const { width } = useViewport()
|
||||
const isMobile = width ? width < breakpoints.sm : false
|
||||
|
@ -50,8 +50,8 @@ export default function AccountInfo() {
|
|||
const [showAlertsModal, setShowAlertsModal] = useState(false)
|
||||
|
||||
const canWithdraw =
|
||||
mangoAccount?.owner && wallet?.publicKey
|
||||
? mangoAccount?.owner?.equals(wallet?.publicKey)
|
||||
mangoAccount?.owner && publicKey
|
||||
? mangoAccount?.owner?.equals(publicKey)
|
||||
: false
|
||||
|
||||
const handleCloseDeposit = useCallback(() => {
|
||||
|
@ -78,7 +78,6 @@ export default function AccountInfo() {
|
|||
// console.log('rerendering account info', mangoAccount, mngoAccrued.toNumber())
|
||||
|
||||
const handleRedeemMngo = async () => {
|
||||
const wallet = useMangoStore.getState().wallet.current
|
||||
const mangoClient = useMangoStore.getState().connection.client
|
||||
const mngoNodeBank =
|
||||
mangoGroup.rootBankAccounts[MNGO_INDEX].nodeBankAccounts[0]
|
||||
|
@ -88,7 +87,7 @@ export default function AccountInfo() {
|
|||
const txid = await mangoClient.redeemAllMngo(
|
||||
mangoGroup,
|
||||
mangoAccount,
|
||||
wallet,
|
||||
wallet?.adapter,
|
||||
mangoGroup.tokens[MNGO_INDEX].rootBank,
|
||||
mngoNodeBank.publicKey,
|
||||
mngoNodeBank.vault
|
||||
|
|
|
@ -11,6 +11,7 @@ import { ElementTitle } from './styles'
|
|||
import Tooltip from './Tooltip'
|
||||
import { notify } from '../utils/notifications'
|
||||
import { useTranslation } from 'next-i18next'
|
||||
import { useWallet } from '@solana/wallet-adapter-react'
|
||||
|
||||
interface AccountNameModalProps {
|
||||
accountName?: string
|
||||
|
@ -24,6 +25,7 @@ const AccountNameModal: FunctionComponent<AccountNameModalProps> = ({
|
|||
onClose,
|
||||
}) => {
|
||||
const { t } = useTranslation('common')
|
||||
const { wallet } = useWallet()
|
||||
const [name, setName] = useState(accountName || '')
|
||||
const [invalidNameMessage, setInvalidNameMessage] = useState('')
|
||||
const mangoGroup = useMangoStore((s) => s.selectedMangoGroup.current)
|
||||
|
@ -32,16 +34,15 @@ const AccountNameModal: FunctionComponent<AccountNameModalProps> = ({
|
|||
|
||||
const submitName = async () => {
|
||||
const mangoClient = useMangoStore.getState().connection.client
|
||||
const wallet = useMangoStore.getState().wallet.current
|
||||
|
||||
try {
|
||||
const txid = await mangoClient.addMangoAccountInfo(
|
||||
mangoGroup,
|
||||
mangoAccount,
|
||||
wallet,
|
||||
wallet?.adapter,
|
||||
name
|
||||
)
|
||||
actions.fetchAllMangoAccounts()
|
||||
actions.fetchAllMangoAccounts(wallet)
|
||||
actions.reloadMangoAccount()
|
||||
onClose()
|
||||
notify({
|
||||
|
|
|
@ -12,6 +12,7 @@ import Button, { LinkButton } from './Button'
|
|||
import NewAccount from './NewAccount'
|
||||
import { useTranslation } from 'next-i18next'
|
||||
import Tooltip from './Tooltip'
|
||||
import { useWallet } from '@solana/wallet-adapter-react'
|
||||
|
||||
export const LAST_ACCOUNT_KEY = 'lastAccountViewed-3.0'
|
||||
|
||||
|
@ -25,6 +26,7 @@ const AccountsModal: FunctionComponent<AccountsModalProps> = ({
|
|||
onClose,
|
||||
}) => {
|
||||
const { t } = useTranslation('common')
|
||||
const { publicKey } = useWallet()
|
||||
const [showNewAccountForm, setShowNewAccountForm] = useState(false)
|
||||
const [newAccPublicKey, setNewAccPublicKey] = useState(null)
|
||||
const mangoAccounts = useMangoStore((s) => s.mangoAccounts)
|
||||
|
@ -35,7 +37,6 @@ const AccountsModal: FunctionComponent<AccountsModalProps> = ({
|
|||
const setMangoStore = useMangoStore((s) => s.set)
|
||||
const actions = useMangoStore((s) => s.actions)
|
||||
const [, setLastAccountViewed] = useLocalStorageState(LAST_ACCOUNT_KEY)
|
||||
const wallet = useMangoStore.getState().wallet.current
|
||||
|
||||
const handleMangoAccountChange = (mangoAccount: MangoAccount) => {
|
||||
setLastAccountViewed(mangoAccount.publicKey.toString())
|
||||
|
@ -124,9 +125,7 @@ const AccountsModal: FunctionComponent<AccountsModalProps> = ({
|
|||
<div className="flex items-center pb-0.5">
|
||||
{account?.name ||
|
||||
abbreviateAddress(account.publicKey)}
|
||||
{!account?.owner.equals(
|
||||
wallet?.publicKey
|
||||
) ? (
|
||||
{!account?.owner.equals(publicKey) ? (
|
||||
<Tooltip
|
||||
content={t(
|
||||
'delegate:delegated-account'
|
||||
|
@ -138,14 +137,14 @@ const AccountsModal: FunctionComponent<AccountsModalProps> = ({
|
|||
''
|
||||
)}
|
||||
</div>
|
||||
{mangoGroup ? (
|
||||
{mangoGroup && (
|
||||
<div className="text-xs text-th-fgd-3">
|
||||
<AccountInfo
|
||||
mangoGroup={mangoGroup}
|
||||
mangoAccount={account}
|
||||
/>
|
||||
</div>
|
||||
) : null}
|
||||
)}
|
||||
</div>
|
||||
</RadioGroup.Label>
|
||||
</div>
|
||||
|
|
|
@ -9,7 +9,7 @@ import { SHOW_TOUR_KEY } from './IntroTips'
|
|||
import { useViewport } from '../hooks/useViewport'
|
||||
import { breakpoints } from './TradePageGrid'
|
||||
import { useRouter } from 'next/router'
|
||||
import { LANGS } from './LanguageSwitch'
|
||||
import { LANGS } from './SettingsModal'
|
||||
import { RadioGroup } from '@headlessui/react'
|
||||
|
||||
export const ALPHA_MODAL_KEY = 'mangoAlphaAccepted-3.06'
|
||||
|
|
|
@ -19,6 +19,7 @@ import { useTranslation } from 'next-i18next'
|
|||
import { TransactionSignature } from '@solana/web3.js'
|
||||
import Link from 'next/link'
|
||||
import { useRouter } from 'next/router'
|
||||
import { useWallet } from '@solana/wallet-adapter-react'
|
||||
|
||||
const BalancesTable = ({
|
||||
showZeroBalances = false,
|
||||
|
@ -54,10 +55,8 @@ const BalancesTable = ({
|
|||
const [submitting, setSubmitting] = useState(false)
|
||||
const isMobile = width ? width < breakpoints.md : false
|
||||
const mangoAccount = useMangoStore((s) => s.selectedMangoAccount.current)
|
||||
const wallet = useMangoStore((s) => s.wallet.current)
|
||||
const canWithdraw = wallet?.publicKey
|
||||
? mangoAccount?.owner.equals(wallet.publicKey)
|
||||
: true
|
||||
const { wallet, publicKey } = useWallet()
|
||||
const canWithdraw = publicKey ? mangoAccount?.owner.equals(publicKey) : true
|
||||
const { asPath } = useRouter()
|
||||
|
||||
const handleSizeClick = (size, symbol) => {
|
||||
|
@ -118,7 +117,7 @@ const BalancesTable = ({
|
|||
mangoGroup,
|
||||
mangoAccount,
|
||||
spotMarkets,
|
||||
wallet
|
||||
wallet?.adapter
|
||||
)
|
||||
|
||||
for (const txid of txids) {
|
||||
|
@ -148,6 +147,24 @@ const BalancesTable = ({
|
|||
|
||||
const unsettledBalances = balances.filter((bal) => bal.unsettled > 0)
|
||||
|
||||
const trimDecimals = useCallback((num: string) => {
|
||||
if (parseFloat(num) === 0) {
|
||||
return '0'
|
||||
}
|
||||
// Trim the decimals depending on the length of the whole number
|
||||
const splitNum = num.split('.')
|
||||
if (splitNum.length > 1) {
|
||||
const wholeNum = splitNum[0]
|
||||
const decimals = splitNum[1]
|
||||
if (wholeNum.length > 8) {
|
||||
return `${wholeNum}.${decimals.substring(0, 2)}`
|
||||
} else if (wholeNum.length > 3) {
|
||||
return `${wholeNum}.${decimals.substring(0, 3)}`
|
||||
}
|
||||
}
|
||||
return num
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<div className={`flex flex-col pb-2 sm:pb-4`}>
|
||||
{unsettledBalances.length > 0 ? (
|
||||
|
@ -360,101 +377,116 @@ const BalancesTable = ({
|
|||
</TrHead>
|
||||
</thead>
|
||||
<tbody>
|
||||
{items.map((balance, index) => (
|
||||
<TrBody key={`${balance.symbol}${index}`}>
|
||||
<Td>
|
||||
<div className="flex items-center">
|
||||
<img
|
||||
alt=""
|
||||
width="20"
|
||||
height="20"
|
||||
src={`/assets/icons/${balance.symbol.toLowerCase()}.svg`}
|
||||
className={`mr-2.5`}
|
||||
/>
|
||||
|
||||
{balance.symbol === 'USDC' ||
|
||||
decodeURIComponent(asPath).includes(
|
||||
`${balance.symbol}/USDC`
|
||||
) ? (
|
||||
<span>{balance.symbol}</span>
|
||||
) : (
|
||||
<Link
|
||||
href={{
|
||||
pathname: '/',
|
||||
query: { name: `${balance.symbol}/USDC` },
|
||||
}}
|
||||
shallow={true}
|
||||
>
|
||||
<a className="text-th-fgd-1 underline hover:text-th-fgd-1 hover:no-underline">
|
||||
{balance.symbol}
|
||||
</a>
|
||||
</Link>
|
||||
)}
|
||||
</div>
|
||||
</Td>
|
||||
<Td>{balance.deposits.toFormat(balance.decimals)}</Td>
|
||||
<Td>{balance.borrows.toFormat(balance.decimals)}</Td>
|
||||
<Td>{balance.orders}</Td>
|
||||
<Td>{balance.unsettled}</Td>
|
||||
<Td>
|
||||
{marketConfig.kind === 'spot' &&
|
||||
marketConfig.name.includes(balance.symbol) &&
|
||||
selectedMarket &&
|
||||
clickToPopulateTradeForm ? (
|
||||
<span
|
||||
className={
|
||||
balance.net.toNumber() != 0
|
||||
? 'cursor-pointer underline hover:no-underline'
|
||||
: ''
|
||||
}
|
||||
onClick={() =>
|
||||
handleSizeClick(balance.net, balance.symbol)
|
||||
}
|
||||
>
|
||||
{balance.net.toFormat(balance.decimals)}
|
||||
</span>
|
||||
) : (
|
||||
balance.net.toFormat(balance.decimals)
|
||||
)}
|
||||
</Td>
|
||||
<Td>{formatUsdValue(balance.value.toNumber())}</Td>
|
||||
<Td>
|
||||
<span className="text-th-green">
|
||||
{balance.depositRate.toFixed(2)}%
|
||||
</span>
|
||||
</Td>
|
||||
<Td>
|
||||
<span className="text-th-red">
|
||||
{balance.borrowRate.toFixed(2)}%
|
||||
</span>
|
||||
</Td>
|
||||
{showDepositWithdraw ? (
|
||||
{items.map((balance, index) => {
|
||||
if (!balance) {
|
||||
return null
|
||||
}
|
||||
return (
|
||||
<TrBody key={`${balance.symbol}${index}`}>
|
||||
<Td>
|
||||
<div className="flex justify-end">
|
||||
<Button
|
||||
className="h-7 pt-0 pb-0 pl-3 pr-3 text-xs"
|
||||
onClick={() =>
|
||||
handleOpenDepositModal(balance.symbol)
|
||||
}
|
||||
>
|
||||
{balance.borrows.toNumber() > 0
|
||||
? t('repay')
|
||||
: t('deposit')}
|
||||
</Button>
|
||||
<Button
|
||||
className="ml-4 h-7 pt-0 pb-0 pl-3 pr-3 text-xs"
|
||||
onClick={() =>
|
||||
handleOpenWithdrawModal(balance.symbol)
|
||||
}
|
||||
disabled={!canWithdraw}
|
||||
>
|
||||
{t('withdraw')}
|
||||
</Button>
|
||||
<div className="flex items-center">
|
||||
<img
|
||||
alt=""
|
||||
width="20"
|
||||
height="20"
|
||||
src={`/assets/icons/${balance.symbol.toLowerCase()}.svg`}
|
||||
className={`mr-2.5`}
|
||||
/>
|
||||
|
||||
{balance.symbol === 'USDC' ||
|
||||
decodeURIComponent(asPath).includes(
|
||||
`${balance.symbol}/USDC`
|
||||
) ? (
|
||||
<span>{balance.symbol}</span>
|
||||
) : (
|
||||
<Link
|
||||
href={{
|
||||
pathname: '/',
|
||||
query: { name: `${balance.symbol}/USDC` },
|
||||
}}
|
||||
shallow={true}
|
||||
>
|
||||
<a className="text-th-fgd-1 underline hover:text-th-fgd-1 hover:no-underline">
|
||||
{balance.symbol}
|
||||
</a>
|
||||
</Link>
|
||||
)}
|
||||
</div>
|
||||
</Td>
|
||||
) : null}
|
||||
</TrBody>
|
||||
))}
|
||||
<Td>
|
||||
{trimDecimals(
|
||||
balance.deposits.toFormat(balance.decimals)
|
||||
)}
|
||||
</Td>
|
||||
<Td>
|
||||
{trimDecimals(
|
||||
balance.borrows.toFormat(balance.decimals)
|
||||
)}
|
||||
</Td>
|
||||
<Td>{balance.orders}</Td>
|
||||
<Td>{balance.unsettled}</Td>
|
||||
<Td>
|
||||
{marketConfig.kind === 'spot' &&
|
||||
marketConfig.name.includes(balance.symbol) &&
|
||||
selectedMarket &&
|
||||
clickToPopulateTradeForm ? (
|
||||
<span
|
||||
className={
|
||||
balance.net.toNumber() != 0
|
||||
? 'cursor-pointer underline hover:no-underline'
|
||||
: ''
|
||||
}
|
||||
onClick={() =>
|
||||
handleSizeClick(balance.net, balance.symbol)
|
||||
}
|
||||
>
|
||||
{trimDecimals(
|
||||
balance.net.toFormat(balance.decimals)
|
||||
)}
|
||||
</span>
|
||||
) : (
|
||||
trimDecimals(balance.net.toFormat(balance.decimals))
|
||||
)}
|
||||
</Td>
|
||||
<Td>{formatUsdValue(balance.value.toNumber())}</Td>
|
||||
<Td>
|
||||
<span className="text-th-green">
|
||||
{balance.depositRate.toFixed(2)}%
|
||||
</span>
|
||||
</Td>
|
||||
<Td>
|
||||
<span className="text-th-red">
|
||||
{balance.borrowRate.toFixed(2)}%
|
||||
</span>
|
||||
</Td>
|
||||
{showDepositWithdraw ? (
|
||||
<Td>
|
||||
<div className="flex justify-end">
|
||||
<Button
|
||||
className="h-7 pt-0 pb-0 pl-3 pr-3 text-xs"
|
||||
onClick={() =>
|
||||
handleOpenDepositModal(balance.symbol)
|
||||
}
|
||||
>
|
||||
{balance.borrows.toNumber() > 0
|
||||
? t('repay')
|
||||
: t('deposit')}
|
||||
</Button>
|
||||
<Button
|
||||
className="ml-4 h-7 pt-0 pb-0 pl-3 pr-3 text-xs"
|
||||
onClick={() =>
|
||||
handleOpenWithdrawModal(balance.symbol)
|
||||
}
|
||||
disabled={!canWithdraw}
|
||||
>
|
||||
{t('withdraw')}
|
||||
</Button>
|
||||
</div>
|
||||
</Td>
|
||||
) : null}
|
||||
</TrBody>
|
||||
)
|
||||
})}
|
||||
</tbody>
|
||||
{showDepositModal && (
|
||||
<DepositModal
|
||||
|
|
|
@ -42,7 +42,7 @@ export const LinkButton: FunctionComponent<ButtonProps> = ({
|
|||
disabled={disabled}
|
||||
className={`border-0 font-bold ${
|
||||
primary ? 'text-th-primary' : 'text-th-fgd-2'
|
||||
} underline hover:no-underline hover:opacity-60 focus:outline-none ${className}`}
|
||||
} underline hover:no-underline hover:opacity-60 focus:outline-none disabled:cursor-not-allowed disabled:underline disabled:opacity-60 ${className}`}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
|
|
|
@ -5,7 +5,7 @@ interface ButtonGroupProps {
|
|||
className?: string
|
||||
onChange: (x) => void
|
||||
unit?: string
|
||||
values: Array<string>
|
||||
values: Array<any>
|
||||
names?: Array<string>
|
||||
}
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@ import React from 'react'
|
|||
import { CheckIcon } from '@heroicons/react/solid'
|
||||
|
||||
const Checkbox = ({ checked, children, disabled = false, ...props }) => (
|
||||
<label className="flex cursor-pointer items-center text-th-fgd-3">
|
||||
<label className="default-transition flex cursor-pointer items-center text-th-fgd-3 hover:text-th-fgd-2">
|
||||
<input
|
||||
checked={checked}
|
||||
{...props}
|
||||
|
@ -32,7 +32,11 @@ const Checkbox = ({ checked, children, disabled = false, ...props }) => (
|
|||
}`}
|
||||
/>
|
||||
</div>
|
||||
<span className="ml-2 text-xs">{children}</span>
|
||||
<span
|
||||
className={`ml-2 text-xs ${checked && !disabled ? 'text-th-fgd-2' : ''}`}
|
||||
>
|
||||
{children}
|
||||
</span>
|
||||
</label>
|
||||
)
|
||||
|
||||
|
|
|
@ -23,6 +23,7 @@ import {
|
|||
ZERO_I80F48,
|
||||
} from '@blockworks-foundation/mango-client'
|
||||
import { formatUsdValue } from '../utils'
|
||||
import { useWallet } from '@solana/wallet-adapter-react'
|
||||
|
||||
interface CloseAccountModalProps {
|
||||
lamports?: number
|
||||
|
@ -35,6 +36,7 @@ const CloseAccountModal: FunctionComponent<CloseAccountModalProps> = ({
|
|||
onClose,
|
||||
}) => {
|
||||
const { t } = useTranslation(['common', 'close-account'])
|
||||
const { wallet } = useWallet()
|
||||
const mangoGroup = useMangoStore((s) => s.selectedMangoGroup.current)
|
||||
const mangoAccount = useMangoStore((s) => s.selectedMangoAccount.current)
|
||||
const mangoCache = useMangoStore((s) => s.selectedMangoGroup.cache)
|
||||
|
@ -94,7 +96,6 @@ const CloseAccountModal: FunctionComponent<CloseAccountModalProps> = ({
|
|||
}, [mangoAccount])
|
||||
|
||||
const closeAccount = async () => {
|
||||
const wallet = useMangoStore.getState().wallet.current
|
||||
const mangoClient = useMangoStore.getState().connection.client
|
||||
|
||||
try {
|
||||
|
@ -103,10 +104,10 @@ const CloseAccountModal: FunctionComponent<CloseAccountModalProps> = ({
|
|||
mangoAccount,
|
||||
mangoCache,
|
||||
MNGO_INDEX,
|
||||
wallet
|
||||
wallet?.adapter
|
||||
)
|
||||
|
||||
await actions.fetchAllMangoAccounts()
|
||||
await actions.fetchAllMangoAccounts(wallet)
|
||||
const mangoAccounts = useMangoStore.getState().mangoAccounts
|
||||
|
||||
setMangoStore((state) => {
|
||||
|
|
|
@ -1,54 +1,107 @@
|
|||
import { Fragment, useCallback, useState } from 'react'
|
||||
import useMangoStore from '../stores/useMangoStore'
|
||||
import React, {
|
||||
Fragment,
|
||||
useCallback,
|
||||
useState,
|
||||
useMemo,
|
||||
useEffect,
|
||||
} from 'react'
|
||||
import { Menu, Transition } from '@headlessui/react'
|
||||
import { useWallet, Wallet } from '@solana/wallet-adapter-react'
|
||||
import { WalletReadyState } from '@solana/wallet-adapter-base'
|
||||
import {
|
||||
CurrencyDollarIcon,
|
||||
DuplicateIcon,
|
||||
LogoutIcon,
|
||||
} from '@heroicons/react/outline'
|
||||
import { PROVIDER_LOCAL_STORAGE_KEY } from '../hooks/useWallet'
|
||||
import useLocalStorageState from '../hooks/useLocalStorageState'
|
||||
import { abbreviateAddress, copyToClipboard } from '../utils'
|
||||
import WalletSelect from './WalletSelect'
|
||||
import { WalletIcon, ProfileIcon } from './icons'
|
||||
import AccountsModal from './AccountsModal'
|
||||
import { useEffect } from 'react'
|
||||
import { notify } from 'utils/notifications'
|
||||
import { abbreviateAddress, copyToClipboard } from 'utils'
|
||||
import useMangoStore from 'stores/useMangoStore'
|
||||
import { ProfileIcon, WalletIcon } from './icons'
|
||||
import { useTranslation } from 'next-i18next'
|
||||
import { DEFAULT_PROVIDER, WALLET_PROVIDERS } from '../utils/wallet-adapters'
|
||||
import { WalletSelect } from 'components/WalletSelect'
|
||||
import AccountsModal from './AccountsModal'
|
||||
import { uniqBy } from 'lodash'
|
||||
|
||||
const ConnectWalletButton = () => {
|
||||
const { t } = useTranslation('common')
|
||||
const wallet = useMangoStore((s) => s.wallet.current)
|
||||
const mangoGroup = useMangoStore((s) => s.selectedMangoGroup.current)
|
||||
const pfp = useMangoStore((s) => s.wallet.pfp)
|
||||
const connected = useMangoStore((s) => s.wallet.connected)
|
||||
const set = useMangoStore((s) => s.set)
|
||||
const [showAccountsModal, setShowAccountsModal] = useState(false)
|
||||
const [selectedWallet, setSelectedWallet] = useState(DEFAULT_PROVIDER.url)
|
||||
const [savedProviderUrl] = useLocalStorageState(
|
||||
PROVIDER_LOCAL_STORAGE_KEY,
|
||||
DEFAULT_PROVIDER.url
|
||||
)
|
||||
|
||||
// update in useEffect to prevent SRR error from next.js
|
||||
useEffect(() => {
|
||||
setSelectedWallet(savedProviderUrl)
|
||||
}, [savedProviderUrl])
|
||||
|
||||
const handleWalletConect = () => {
|
||||
wallet.connect()
|
||||
set((state) => {
|
||||
state.selectedMangoAccount.initialLoad = true
|
||||
export const handleWalletConnect = (wallet: Wallet) => {
|
||||
if (!wallet) {
|
||||
return
|
||||
}
|
||||
if (wallet.readyState === WalletReadyState.NotDetected) {
|
||||
window.open(wallet.adapter.url, '_blank')
|
||||
} else {
|
||||
wallet?.adapter?.connect().catch((e) => {
|
||||
if (e.name.includes('WalletLoadError')) {
|
||||
notify({
|
||||
title: `${wallet.adapter.name} Error`,
|
||||
type: 'error',
|
||||
description: `Please install ${wallet.adapter.name} and then reload this page.`,
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export const ConnectWalletButton: React.FC = () => {
|
||||
const { connected, publicKey, wallet, wallets, select } = useWallet()
|
||||
const { t } = useTranslation('common')
|
||||
const pfp = useMangoStore((s) => s.wallet.pfp)
|
||||
const set = useMangoStore((s) => s.set)
|
||||
const mangoGroup = useMangoStore((s) => s.selectedMangoGroup.current)
|
||||
const [showAccountsModal, setShowAccountsModal] = useState(false)
|
||||
|
||||
const installedWallets = useMemo(() => {
|
||||
const installed: Wallet[] = []
|
||||
|
||||
for (const wallet of wallets) {
|
||||
if (wallet.readyState === WalletReadyState.Installed) {
|
||||
installed.push(wallet)
|
||||
}
|
||||
}
|
||||
|
||||
return installed
|
||||
}, [wallets])
|
||||
|
||||
const displayedWallets = useMemo(() => {
|
||||
return uniqBy([...installedWallets, ...wallets], (w) => {
|
||||
return w.adapter.name
|
||||
})
|
||||
}, [wallets, installedWallets])
|
||||
|
||||
const handleConnect = useCallback(() => {
|
||||
handleWalletConnect(wallet)
|
||||
}, [wallet])
|
||||
|
||||
const handleCloseAccounts = useCallback(() => {
|
||||
setShowAccountsModal(false)
|
||||
}, [])
|
||||
|
||||
const handleDisconnect = useCallback(() => {
|
||||
wallet?.adapter?.disconnect()
|
||||
set((state) => {
|
||||
state.mangoAccounts = []
|
||||
state.selectedMangoAccount.current = null
|
||||
state.tradeHistory = {
|
||||
spot: [],
|
||||
perp: [],
|
||||
parsed: [],
|
||||
initialLoad: false,
|
||||
}
|
||||
})
|
||||
notify({
|
||||
type: 'info',
|
||||
title: t('wallet-disconnected'),
|
||||
})
|
||||
}, [wallet, set, t])
|
||||
|
||||
useEffect(() => {
|
||||
if (!wallet && displayedWallets?.length) {
|
||||
select(displayedWallets[0].adapter.name)
|
||||
}
|
||||
}, [wallet, displayedWallets, select])
|
||||
|
||||
return (
|
||||
<>
|
||||
{connected && wallet?.publicKey ? (
|
||||
{connected && publicKey ? (
|
||||
<Menu>
|
||||
{({ open }) => (
|
||||
<div className="relative" id="profile-menu-tip">
|
||||
|
@ -83,7 +136,7 @@ const ConnectWalletButton = () => {
|
|||
<Menu.Item>
|
||||
<button
|
||||
className="flex w-full flex-row items-center rounded-none py-0.5 font-normal hover:cursor-pointer hover:text-th-primary focus:outline-none"
|
||||
onClick={() => copyToClipboard(wallet?.publicKey)}
|
||||
onClick={() => copyToClipboard(publicKey)}
|
||||
>
|
||||
<DuplicateIcon className="h-4 w-4" />
|
||||
<div className="pl-2 text-left">{t('copy-address')}</div>
|
||||
|
@ -92,13 +145,13 @@ const ConnectWalletButton = () => {
|
|||
<Menu.Item>
|
||||
<button
|
||||
className="flex w-full flex-row items-center rounded-none py-0.5 font-normal hover:cursor-pointer hover:text-th-primary focus:outline-none"
|
||||
onClick={() => wallet.disconnect()}
|
||||
onClick={handleDisconnect}
|
||||
>
|
||||
<LogoutIcon className="h-4 w-4" />
|
||||
<div className="pl-2 text-left">
|
||||
<div className="pb-0.5">{t('disconnect')}</div>
|
||||
<div className="text-xs text-th-fgd-4">
|
||||
{abbreviateAddress(wallet?.publicKey)}
|
||||
{abbreviateAddress(publicKey)}
|
||||
</div>
|
||||
</div>
|
||||
</button>
|
||||
|
@ -114,8 +167,8 @@ const ConnectWalletButton = () => {
|
|||
id="connect-wallet-tip"
|
||||
>
|
||||
<button
|
||||
onClick={handleWalletConect}
|
||||
disabled={!wallet || !mangoGroup}
|
||||
onClick={handleConnect}
|
||||
disabled={!mangoGroup}
|
||||
className="rounded-none bg-th-primary-dark text-th-bkg-1 hover:brightness-[1.1] focus:outline-none disabled:cursor-wait disabled:text-th-bkg-2"
|
||||
>
|
||||
<div className="default-transition flex h-full flex-row items-center justify-center px-3">
|
||||
|
@ -124,25 +177,25 @@ const ConnectWalletButton = () => {
|
|||
<div className="mb-0.5 whitespace-nowrap font-bold">
|
||||
{t('connect')}
|
||||
</div>
|
||||
<div className="text-xxs font-normal leading-3 tracking-wider text-th-bkg-2">
|
||||
{WALLET_PROVIDERS.find((p) => p.url === selectedWallet)?.name}
|
||||
</div>
|
||||
{wallet?.adapter?.name && (
|
||||
<div className="text-xxs font-normal leading-3 tracking-wider text-th-bkg-2">
|
||||
{wallet.adapter.name}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</button>
|
||||
<div className="relative">
|
||||
<WalletSelect />
|
||||
<WalletSelect wallets={displayedWallets} />
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{showAccountsModal ? (
|
||||
{showAccountsModal && (
|
||||
<AccountsModal
|
||||
onClose={handleCloseAccounts}
|
||||
isOpen={showAccountsModal}
|
||||
/>
|
||||
) : null}
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default ConnectWalletButton
|
||||
|
|
|
@ -0,0 +1,97 @@
|
|||
import DatePicker from 'react-datepicker/dist/react-datepicker'
|
||||
import { ChevronLeftIcon, ChevronRightIcon } from '@heroicons/react/solid'
|
||||
import Select from './Select'
|
||||
|
||||
const months = [
|
||||
'January',
|
||||
'February',
|
||||
'March',
|
||||
'April',
|
||||
'May',
|
||||
'June',
|
||||
'July',
|
||||
'August',
|
||||
'September',
|
||||
'October',
|
||||
'November',
|
||||
'December',
|
||||
]
|
||||
|
||||
const MangoDatePicker = ({ date, setDate, ...props }) => {
|
||||
const generateArrayOfYears = () => {
|
||||
const max = new Date().getFullYear()
|
||||
const min = max - (max - 2020)
|
||||
const years = []
|
||||
|
||||
for (let i = max; i >= min; i--) {
|
||||
years.push(i)
|
||||
}
|
||||
return years
|
||||
}
|
||||
|
||||
const years = generateArrayOfYears()
|
||||
|
||||
return (
|
||||
<DatePicker
|
||||
renderCustomHeader={({
|
||||
date,
|
||||
changeYear,
|
||||
changeMonth,
|
||||
decreaseMonth,
|
||||
increaseMonth,
|
||||
prevMonthButtonDisabled,
|
||||
nextMonthButtonDisabled,
|
||||
}) => (
|
||||
<div className="flex items-center justify-between px-1">
|
||||
<button
|
||||
className="default-transition mr-1 text-th-fgd-3 hover:text-th-fgd-1"
|
||||
onClick={decreaseMonth}
|
||||
disabled={prevMonthButtonDisabled}
|
||||
>
|
||||
<ChevronLeftIcon className="h-6 w-6" />
|
||||
</button>
|
||||
<div className="flex space-x-2">
|
||||
<Select
|
||||
className="w-28"
|
||||
dropdownPanelClassName="text-left"
|
||||
value={months[date.getMonth()]}
|
||||
onChange={(value) => changeMonth(months.indexOf(value))}
|
||||
>
|
||||
{months.map((option) => (
|
||||
<Select.Option key={option} value={option}>
|
||||
{option}
|
||||
</Select.Option>
|
||||
))}
|
||||
</Select>
|
||||
<Select
|
||||
dropdownPanelClassName="text-left"
|
||||
value={date.getFullYear()}
|
||||
onChange={(value) => changeYear(value)}
|
||||
>
|
||||
{years.map((option) => (
|
||||
<Select.Option key={option} value={option}>
|
||||
{option}
|
||||
</Select.Option>
|
||||
))}
|
||||
</Select>
|
||||
</div>
|
||||
<button
|
||||
className="default-transition ml-1 text-th-fgd-3 hover:text-th-fgd-1"
|
||||
onClick={increaseMonth}
|
||||
disabled={nextMonthButtonDisabled}
|
||||
>
|
||||
<ChevronRightIcon className="h-6 w-6" />
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
placeholderText="dd/mm/yyyy"
|
||||
dateFormat="dd/MM/yyyy"
|
||||
selected={date}
|
||||
onChange={(date: Date) => setDate(date)}
|
||||
className="default-transition h-10 w-full cursor-pointer rounded-md border border-th-bkg-4 bg-th-bkg-1 px-2 text-th-fgd-1 hover:border-th-fgd-4 focus:border-th-fgd-4 focus:outline-none"
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export default MangoDatePicker
|
|
@ -13,6 +13,7 @@ import { ElementTitle } from './styles'
|
|||
import { notify } from '../utils/notifications'
|
||||
import { useTranslation } from 'next-i18next'
|
||||
import { PublicKey } from '@solana/web3.js'
|
||||
import { useWallet } from '@solana/wallet-adapter-react'
|
||||
|
||||
interface DelegateModalProps {
|
||||
delegate?: PublicKey
|
||||
|
@ -26,6 +27,7 @@ const DelegateModal: FunctionComponent<DelegateModalProps> = ({
|
|||
onClose,
|
||||
}) => {
|
||||
const { t } = useTranslation(['common', 'delegate'])
|
||||
const { wallet } = useWallet()
|
||||
|
||||
const [keyBase58, setKeyBase58] = useState(
|
||||
delegate.equals(PublicKey.default) ? '' : delegate.toBase58()
|
||||
|
@ -36,7 +38,6 @@ const DelegateModal: FunctionComponent<DelegateModalProps> = ({
|
|||
const actions = useMangoStore((s) => s.actions)
|
||||
|
||||
const setDelegate = async () => {
|
||||
const wallet = useMangoStore.getState().wallet.current
|
||||
const mangoClient = useMangoStore.getState().connection.client
|
||||
|
||||
try {
|
||||
|
@ -46,7 +47,7 @@ const DelegateModal: FunctionComponent<DelegateModalProps> = ({
|
|||
const txid = await mangoClient.setDelegate(
|
||||
mangoGroup,
|
||||
mangoAccount,
|
||||
wallet,
|
||||
wallet?.adapter,
|
||||
key
|
||||
)
|
||||
actions.reloadMangoAccount()
|
||||
|
@ -70,7 +71,7 @@ const DelegateModal: FunctionComponent<DelegateModalProps> = ({
|
|||
if (isKeyValid()) {
|
||||
setInvalidKeyMessage('')
|
||||
} else {
|
||||
setInvalidKeyMessage(t('delegate:invalid-key'))
|
||||
setInvalidKeyMessage(t('invalid-address'))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -13,6 +13,7 @@ import { notify } from '../utils/notifications'
|
|||
import { sleep, trimDecimals } from '../utils'
|
||||
import { useTranslation } from 'next-i18next'
|
||||
import ButtonGroup from './ButtonGroup'
|
||||
import { useWallet } from '@solana/wallet-adapter-react'
|
||||
|
||||
interface DepositModalProps {
|
||||
onClose: () => void
|
||||
|
@ -28,6 +29,7 @@ const DepositModal: FunctionComponent<DepositModalProps> = ({
|
|||
tokenSymbol = '',
|
||||
}) => {
|
||||
const { t } = useTranslation('common')
|
||||
const { wallet } = useWallet()
|
||||
const [inputAmount, setInputAmount] = useState<string>(repayAmount || '')
|
||||
const [submitting, setSubmitting] = useState(false)
|
||||
const [invalidAmountMessage, setInvalidAmountMessage] = useState('')
|
||||
|
@ -65,6 +67,7 @@ const DepositModal: FunctionComponent<DepositModalProps> = ({
|
|||
amount: parseFloat(inputAmount),
|
||||
fromTokenAcc: selectedAccount.account,
|
||||
mangoAccount,
|
||||
wallet,
|
||||
})
|
||||
.then((response) => {
|
||||
notify({
|
||||
|
@ -77,7 +80,7 @@ const DepositModal: FunctionComponent<DepositModalProps> = ({
|
|||
sleep(500).then(() => {
|
||||
mangoAccount
|
||||
? actions.reloadMangoAccount()
|
||||
: actions.fetchAllMangoAccounts()
|
||||
: actions.fetchAllMangoAccounts(wallet)
|
||||
actions.fetchWalletTokens()
|
||||
})
|
||||
})
|
||||
|
|
|
@ -6,6 +6,7 @@ import Loading from './Loading'
|
|||
import Modal from './Modal'
|
||||
import { msrmMints } from '@blockworks-foundation/mango-client'
|
||||
import { useTranslation } from 'next-i18next'
|
||||
import { useWallet } from '@solana/wallet-adapter-react'
|
||||
|
||||
const DepositMsrmModal = ({ onClose, isOpen }) => {
|
||||
const { t } = useTranslation('common')
|
||||
|
@ -13,7 +14,7 @@ const DepositMsrmModal = ({ onClose, isOpen }) => {
|
|||
const actions = useMangoStore((s) => s.actions)
|
||||
const mangoAccount = useMangoStore((s) => s.selectedMangoAccount.current)
|
||||
const mangoGroup = useMangoStore((s) => s.selectedMangoGroup.current)
|
||||
const wallet = useMangoStore((s) => s.wallet.current)
|
||||
const { wallet } = useWallet()
|
||||
const walletTokens = useMangoStore((s) => s.wallet.tokens)
|
||||
const cluster = useMangoStore.getState().connection.cluster
|
||||
|
||||
|
@ -27,7 +28,7 @@ const DepositMsrmModal = ({ onClose, isOpen }) => {
|
|||
const txid = await mangoClient.depositMsrm(
|
||||
mangoGroup,
|
||||
mangoAccount,
|
||||
wallet,
|
||||
wallet?.adapter,
|
||||
ownerMsrmAccount?.account?.publicKey,
|
||||
1
|
||||
)
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import { FunctionComponent, ReactNode } from 'react'
|
||||
import useMangoStore from '../stores/useMangoStore'
|
||||
import Button from './Button'
|
||||
|
||||
interface EmptyStateProps {
|
||||
|
@ -8,6 +7,7 @@ interface EmptyStateProps {
|
|||
onClickButton?: () => void
|
||||
desc?: string
|
||||
title: string
|
||||
disabled?: boolean
|
||||
}
|
||||
|
||||
const EmptyState: FunctionComponent<EmptyStateProps> = ({
|
||||
|
@ -16,10 +16,8 @@ const EmptyState: FunctionComponent<EmptyStateProps> = ({
|
|||
onClickButton,
|
||||
desc,
|
||||
title,
|
||||
disabled = false,
|
||||
}) => {
|
||||
const wallet = useMangoStore((s) => s.wallet.current)
|
||||
const mangoGroup = useMangoStore((s) => s.selectedMangoGroup.current)
|
||||
|
||||
return (
|
||||
<div className="flex flex-col items-center rounded-lg px-4 pb-2 text-th-fgd-1">
|
||||
<div className="mb-1 h-6 w-6 text-th-primary">{icon}</div>
|
||||
|
@ -34,11 +32,7 @@ const EmptyState: FunctionComponent<EmptyStateProps> = ({
|
|||
</p>
|
||||
) : null}
|
||||
{buttonText && onClickButton ? (
|
||||
<Button
|
||||
className="mt-2"
|
||||
onClick={onClickButton}
|
||||
disabled={!wallet || !mangoGroup}
|
||||
>
|
||||
<Button className="mt-2" onClick={onClickButton} disabled={disabled}>
|
||||
{buttonText}
|
||||
</Button>
|
||||
) : null}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import React from 'react'
|
||||
import { FiveOhFive } from './FiveOhFive'
|
||||
|
||||
class ErrorBoundary extends React.Component<
|
||||
any,
|
||||
|
@ -39,16 +40,7 @@ class ErrorBoundary extends React.Component<
|
|||
render() {
|
||||
if (this.state.hasError) {
|
||||
// You can render any custom fallback UI
|
||||
return (
|
||||
<div className="pt-1 text-center text-th-fgd-2">
|
||||
<div>Something went wrong.</div>
|
||||
<div className="text-th-red">{this.state.error.message}</div>
|
||||
<button className="mt-2" onClick={() => location.reload()}>
|
||||
Refresh and try again
|
||||
</button>
|
||||
<div className="mx-8 mt-4 px-8">{this.state.error.stack}</div>
|
||||
</div>
|
||||
)
|
||||
return <FiveOhFive error={this.state.error} />
|
||||
}
|
||||
|
||||
return this.props.children
|
||||
|
|
|
@ -1,121 +0,0 @@
|
|||
import { percentFormat } from '../utils/index'
|
||||
import useSrmAccount from '../hooks/useSrmAccount'
|
||||
import {
|
||||
MSRM_DECIMALS,
|
||||
SRM_DECIMALS,
|
||||
} from '@project-serum/serum/lib/token-instructions'
|
||||
import Tooltip from './Tooltip'
|
||||
import { InformationCircleIcon } from '@heroicons/react/outline'
|
||||
import { useTranslation } from 'next-i18next'
|
||||
import Button from './Button'
|
||||
import useMangoStore from '../stores/useMangoStore'
|
||||
import { msrmMints, ZERO_BN } from '@blockworks-foundation/mango-client'
|
||||
import DepositMsrmModal from './DepositMsrmModal'
|
||||
import WithdrawMsrmModal from './WithdrawMsrmModal'
|
||||
import { useState } from 'react'
|
||||
|
||||
const FeeDiscountsTable = () => {
|
||||
const { t } = useTranslation('common')
|
||||
const mangoAccount = useMangoStore((s) => s.selectedMangoAccount.current)
|
||||
const connected = useMangoStore((s) => s.wallet.connected)
|
||||
const walletTokens = useMangoStore((s) => s.wallet.tokens)
|
||||
const { totalSrm, totalMsrm, rates } = useSrmAccount()
|
||||
const [showDeposit, setShowDeposit] = useState(false)
|
||||
const [showWithdraw, setShowWithdraw] = useState(false)
|
||||
const cluster = useMangoStore.getState().connection.cluster
|
||||
|
||||
const ownerMsrmAccount = walletTokens.find((t) =>
|
||||
t.account.mint.equals(msrmMints[cluster])
|
||||
)
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`flex justify-center divide-x divide-gray-500 rounded-md bg-th-bkg-1 py-6`}
|
||||
>
|
||||
<div className="pr-10">
|
||||
<div className="text-center text-lg">{t('serum-fees')}</div>
|
||||
<div
|
||||
className={`mt-4 flex flex-col justify-between text-center text-th-fgd-4 sm:flex-row`}
|
||||
>
|
||||
<div className="px-4">
|
||||
<div>
|
||||
{totalMsrm > 0 ? 'MSRM' : 'SRM'} {t('deposits')}
|
||||
</div>
|
||||
<div className="text-normal text-th-fgd-3">
|
||||
{totalMsrm > 0
|
||||
? totalMsrm.toLocaleString(undefined, {
|
||||
maximumFractionDigits: MSRM_DECIMALS,
|
||||
})
|
||||
: totalSrm.toLocaleString(undefined, {
|
||||
maximumFractionDigits: SRM_DECIMALS,
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-4 px-4 sm:mt-0">
|
||||
<div>{t('maker-fee')}</div>
|
||||
<div className="text-normal text-th-fgd-3">
|
||||
{rates ? percentFormat.format(rates.maker) : null}
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-4 px-4 sm:mt-0">
|
||||
<div className="flex items-center">
|
||||
<div>{t('taker-fee')}</div>
|
||||
<div className="flex items-center">
|
||||
<Tooltip
|
||||
content={t('tooltip-serum-rebate', {
|
||||
taker_percent: percentFormat.format(rates.taker),
|
||||
})}
|
||||
>
|
||||
<div>
|
||||
<InformationCircleIcon
|
||||
className={`ml-2 h-5 w-5 cursor-help text-th-fgd-4`}
|
||||
/>
|
||||
</div>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</div>
|
||||
<div className="text-normal text-th-fgd-3">
|
||||
{rates
|
||||
? new Intl.NumberFormat(undefined, {
|
||||
style: 'percent',
|
||||
minimumFractionDigits: 2,
|
||||
maximumFractionDigits: 3,
|
||||
}).format(rates.takerWithRebate)
|
||||
: null}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{connected && mangoAccount ? (
|
||||
<div className="mt-6 flex justify-center">
|
||||
<Button
|
||||
onClick={() => setShowDeposit(true)}
|
||||
disabled={!ownerMsrmAccount}
|
||||
>
|
||||
{t('deposit')} MSRM
|
||||
</Button>
|
||||
{mangoAccount.msrmAmount.gt(ZERO_BN) ? (
|
||||
<Button onClick={() => setShowWithdraw(true)} className="ml-2">
|
||||
{t('withdraw')} MSRM
|
||||
</Button>
|
||||
) : null}
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
{showDeposit ? (
|
||||
<DepositMsrmModal
|
||||
isOpen={showDeposit}
|
||||
onClose={() => setShowDeposit(false)}
|
||||
/>
|
||||
) : null}
|
||||
|
||||
{showWithdraw ? (
|
||||
<WithdrawMsrmModal
|
||||
isOpen={showWithdraw}
|
||||
onClose={() => setShowWithdraw(false)}
|
||||
/>
|
||||
) : null}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default FeeDiscountsTable
|
|
@ -0,0 +1,130 @@
|
|||
import { useState } from 'react'
|
||||
import { ChevronRightIcon, ChevronDownIcon } from '@heroicons/react/solid'
|
||||
import GradientText from './GradientText'
|
||||
import cls from 'classnames'
|
||||
|
||||
const social = [
|
||||
{
|
||||
name: 'Twitter',
|
||||
href: 'https://twitter.com/mangomarkets',
|
||||
icon: (props) => (
|
||||
<svg fill="currentColor" viewBox="0 0 24 24" {...props}>
|
||||
<path d="M8.29 20.251c7.547 0 11.675-6.253 11.675-11.675 0-.178 0-.355-.012-.53A8.348 8.348 0 0022 5.92a8.19 8.19 0 01-2.357.646 4.118 4.118 0 001.804-2.27 8.224 8.224 0 01-2.605.996 4.107 4.107 0 00-6.993 3.743 11.65 11.65 0 01-8.457-4.287 4.106 4.106 0 001.27 5.477A4.072 4.072 0 012.8 9.713v.052a4.105 4.105 0 003.292 4.022 4.095 4.095 0 01-1.853.07 4.108 4.108 0 003.834 2.85A8.233 8.233 0 012 18.407a11.616 11.616 0 006.29 1.84" />
|
||||
</svg>
|
||||
),
|
||||
},
|
||||
{
|
||||
name: 'GitHub',
|
||||
href: 'https://github.com/blockworks-foundation',
|
||||
icon: (props) => (
|
||||
<svg fill="currentColor" viewBox="0 0 24 24" {...props}>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
d="M12 2C6.477 2 2 6.484 2 12.017c0 4.425 2.865 8.18 6.839 9.504.5.092.682-.217.682-.483 0-.237-.008-.868-.013-1.703-2.782.605-3.369-1.343-3.369-1.343-.454-1.158-1.11-1.466-1.11-1.466-.908-.62.069-.608.069-.608 1.003.07 1.531 1.032 1.531 1.032.892 1.53 2.341 1.088 2.91.832.092-.647.35-1.088.636-1.338-2.22-.253-4.555-1.113-4.555-4.951 0-1.093.39-1.988 1.029-2.688-.103-.253-.446-1.272.098-2.65 0 0 .84-.27 2.75 1.026A9.564 9.564 0 0112 6.844c.85.004 1.705.115 2.504.337 1.909-1.296 2.747-1.027 2.747-1.027.546 1.379.202 2.398.1 2.651.64.7 1.028 1.595 1.028 2.688 0 3.848-2.339 4.695-4.566 4.943.359.309.678.92.678 1.855 0 1.338-.012 2.419-.012 2.747 0 .268.18.58.688.482A10.019 10.019 0 0022 12.017C22 6.484 17.522 2 12 2z"
|
||||
clipRule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
),
|
||||
},
|
||||
]
|
||||
|
||||
export const FiveOhFive = ({ error }) => {
|
||||
const stack = error.stack.split('\n').slice(0, 5).join('\n')
|
||||
const [showDetails, toggleDetails] = useState(false)
|
||||
|
||||
const Icon = showDetails ? ChevronDownIcon : ChevronRightIcon
|
||||
|
||||
return (
|
||||
<div className="bg-bg-texture flex min-h-screen flex-col bg-cover bg-bottom bg-no-repeat">
|
||||
<div className="h-2 w-screen bg-gradient-to-r from-mango-theme-green via-mango-theme-yellow-dark to-mango-theme-red-dark"></div>
|
||||
<main className="my-[-2] mx-auto w-full max-w-7xl flex-grow px-4 sm:px-6 lg:px-8">
|
||||
<div className="flex-shrink-0 pt-16">
|
||||
<img
|
||||
className="mx-auto h-12 w-auto"
|
||||
src="/assets/logotext.svg"
|
||||
alt="Workflow"
|
||||
/>
|
||||
</div>
|
||||
<div className="mx-auto max-w-xl py-16 sm:py-24">
|
||||
<div className="text-center">
|
||||
<p className="text-sm font-semibold uppercase tracking-wide">
|
||||
<GradientText>505 error</GradientText>
|
||||
</p>
|
||||
<h1 className="mt-2 text-4xl font-extrabold tracking-tight text-white sm:text-5xl">
|
||||
Something went wrong
|
||||
</h1>
|
||||
<p className="mt-2 text-lg text-gray-500">
|
||||
The page you are looking for could not be loaded.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="my-10 text-center">
|
||||
<div className="font-mono mt-8 rounded-lg bg-th-bkg-2 p-8 text-left text-th-fgd-1">
|
||||
<div className="flex">
|
||||
<div className="text-mango-theme-fgd-2">{error.message}</div>
|
||||
<div className="flex-grow"></div>
|
||||
<div className="flex-shrink-0 self-center">
|
||||
<Icon
|
||||
className="text-mango-yellow h-5 w-5"
|
||||
aria-hidden="true"
|
||||
onClick={() => toggleDetails(!showDetails)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
style={{
|
||||
maxHeight: showDetails ? '500px' : 0,
|
||||
opacity: showDetails ? 1 : 0,
|
||||
}}
|
||||
className={cls('overflow-hidden transition-all')}
|
||||
>
|
||||
<div className="mt-6">{stack}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col items-center">
|
||||
<div className="mt-10 flex flex-row">
|
||||
<button
|
||||
className="mx-2 whitespace-nowrap rounded-full bg-th-bkg-button px-6 py-2 font-bold text-th-fgd-1 hover:brightness-[1.1] focus:outline-none disabled:cursor-not-allowed disabled:bg-th-bkg-4 disabled:text-th-fgd-4 disabled:hover:brightness-100"
|
||||
onClick={() => location.reload()}
|
||||
>
|
||||
Refresh and try again
|
||||
</button>
|
||||
|
||||
<a
|
||||
className="whitespace-nowrap rounded-full bg-mango-theme-bkg-3 px-6 py-2 font-bold text-th-fgd-1 hover:brightness-[1.1] focus:outline-none disabled:cursor-not-allowed disabled:bg-th-bkg-4 disabled:text-th-fgd-4 disabled:hover:brightness-100"
|
||||
href="https://discord.gg/mangomarkets"
|
||||
>
|
||||
<div className="flex">
|
||||
<img
|
||||
className="mr-2 h-[20px] w-[20px]"
|
||||
src="/assets/icons/discord.svg"
|
||||
/>
|
||||
Join Discord
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
<footer className="mx-auto w-full max-w-7xl px-4 sm:px-6 lg:px-8">
|
||||
<div className="border-t border-gray-200 py-10 text-center md:flex md:justify-between">
|
||||
<div className="mt-6 flex justify-center space-x-8 md:mt-0">
|
||||
{social.map((item, itemIdx) => (
|
||||
<a
|
||||
key={itemIdx}
|
||||
href={item.href}
|
||||
className="inline-flex text-gray-400 hover:text-gray-500"
|
||||
>
|
||||
<span className="sr-only">{item.name}</span>
|
||||
<item.icon className="h-6 w-6" aria-hidden="true" />
|
||||
</a>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
<div className="h-2 w-screen bg-gradient-to-r from-mango-theme-green via-mango-theme-yellow-dark to-mango-theme-red-dark"></div>
|
||||
</div>
|
||||
)
|
||||
}
|
|
@ -1,31 +1,11 @@
|
|||
import React, { FunctionComponent } from 'react'
|
||||
// import styled from '@emotion/styled'
|
||||
import React, { FunctionComponent, useCallback } from 'react'
|
||||
import { LinkIcon } from '@heroicons/react/outline'
|
||||
import useMangoStore from '../stores/useMangoStore'
|
||||
import { MoveIcon } from './icons'
|
||||
import EmptyState from './EmptyState'
|
||||
import { useTranslation } from 'next-i18next'
|
||||
|
||||
// const StyledDragWrapperContent = styled.div`
|
||||
// transition: all 0.25s ease-in;
|
||||
// opacity: 0;
|
||||
// `
|
||||
|
||||
// const StyledDragBkg = styled.div`
|
||||
// transition: all 0.25s ease-in;
|
||||
// opacity: 0;
|
||||
// `
|
||||
|
||||
// const StyledDragWrapper = styled.div`
|
||||
// :hover {
|
||||
// ${StyledDragWrapperContent} {
|
||||
// opacity: 1;
|
||||
// }
|
||||
// ${StyledDragBkg} {
|
||||
// opacity: 0.9;
|
||||
// }
|
||||
// }
|
||||
// `
|
||||
import { handleWalletConnect } from 'components/ConnectWalletButton'
|
||||
import { useWallet } from '@solana/wallet-adapter-react'
|
||||
|
||||
interface FloatingElementProps {
|
||||
className?: string
|
||||
|
@ -38,9 +18,14 @@ const FloatingElement: FunctionComponent<FloatingElementProps> = ({
|
|||
showConnect,
|
||||
}) => {
|
||||
const { t } = useTranslation('common')
|
||||
const { wallet, connected } = useWallet()
|
||||
const { uiLocked } = useMangoStore((s) => s.settings)
|
||||
const connected = useMangoStore((s) => s.wallet.connected)
|
||||
const wallet = useMangoStore((s) => s.wallet.current)
|
||||
const mangoGroup = useMangoStore((s) => s.selectedMangoGroup.current)
|
||||
|
||||
const handleConnect = useCallback(() => {
|
||||
handleWalletConnect(wallet)
|
||||
}, [wallet])
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`thin-scroll relative overflow-auto overflow-x-hidden rounded-lg bg-th-bkg-2 p-2.5 md:p-4 ${className}`}
|
||||
|
@ -49,9 +34,10 @@ const FloatingElement: FunctionComponent<FloatingElementProps> = ({
|
|||
<div className="absolute top-0 left-0 z-10 h-full w-full">
|
||||
<div className="relative z-10 flex h-full flex-col items-center justify-center">
|
||||
<EmptyState
|
||||
disabled={!wallet || !mangoGroup}
|
||||
buttonText={t('connect')}
|
||||
icon={<LinkIcon />}
|
||||
onClickButton={() => (wallet ? wallet.connect() : null)}
|
||||
onClickButton={handleConnect}
|
||||
title={t('connect-wallet')}
|
||||
/>
|
||||
</div>
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
const GradientText = (props) => (
|
||||
<span className="bg-gradient-to-bl from-mango-theme-green via-mango-theme-yellow-dark to-mango-theme-red-dark bg-clip-text text-transparent">
|
||||
{props.children}
|
||||
</span>
|
||||
)
|
||||
|
||||
export default GradientText
|
|
@ -52,30 +52,6 @@ class IntroTips extends Component<Props, State> {
|
|||
highlightClass: 'intro-highlight',
|
||||
disableInteraction: true,
|
||||
},
|
||||
{
|
||||
element: '#themes-tip',
|
||||
intro: (
|
||||
<div>
|
||||
<h4>{this.props.t('themes-tip-title')}</h4>
|
||||
<p>{this.props.t('themes-tip-desc')}</p>
|
||||
</div>
|
||||
),
|
||||
tooltipClass: 'intro-tooltip',
|
||||
highlightClass: 'intro-highlight',
|
||||
disableInteraction: true,
|
||||
},
|
||||
{
|
||||
element: '#languages-tip',
|
||||
intro: (
|
||||
<div>
|
||||
<h4>{this.props.t('languages-tip-title')}</h4>
|
||||
<p>{this.props.t('languages-tip-desc')}</p>
|
||||
</div>
|
||||
),
|
||||
tooltipClass: 'intro-tooltip',
|
||||
highlightClass: 'intro-highlight',
|
||||
disableInteraction: true,
|
||||
},
|
||||
{
|
||||
element: '#data-refresh-tip',
|
||||
intro: (
|
||||
|
|
|
@ -9,11 +9,7 @@ import { useJupiter, RouteInfo } from '@jup-ag/react-hook'
|
|||
import { TOKEN_LIST_URL } from '@jup-ag/core'
|
||||
import { PublicKey } from '@solana/web3.js'
|
||||
import useMangoStore from '../stores/useMangoStore'
|
||||
import {
|
||||
connectionSelector,
|
||||
walletConnectedSelector,
|
||||
walletSelector,
|
||||
} from '../stores/selectors'
|
||||
import { connectionSelector } from '../stores/selectors'
|
||||
import { sortBy, sum } from 'lodash'
|
||||
import {
|
||||
CogIcon,
|
||||
|
@ -46,6 +42,8 @@ import { numberFormatter } from './SwapTokenInfo'
|
|||
import { useTranslation } from 'next-i18next'
|
||||
import Tabs from './Tabs'
|
||||
import SwapTokenInsights from './SwapTokenInsights'
|
||||
import { useWallet } from '@solana/wallet-adapter-react'
|
||||
import { handleWalletConnect } from 'components/ConnectWalletButton'
|
||||
|
||||
const TABS = ['Market Data', 'Performance Insights']
|
||||
|
||||
|
@ -53,10 +51,9 @@ type UseJupiterProps = Parameters<typeof useJupiter>[0]
|
|||
|
||||
const JupiterForm: FunctionComponent = () => {
|
||||
const { t } = useTranslation(['common', 'swap'])
|
||||
const wallet = useMangoStore(walletSelector)
|
||||
const { wallet, publicKey, connected, signAllTransactions, signTransaction } =
|
||||
useWallet()
|
||||
const connection = useMangoStore(connectionSelector)
|
||||
const connected = useMangoStore(walletConnectedSelector)
|
||||
|
||||
const [showSettings, setShowSettings] = useState(false)
|
||||
const [depositAndFee, setDepositAndFee] = useState(null)
|
||||
const [selectedRoute, setSelectedRoute] = useState<RouteInfo>(null)
|
||||
|
@ -90,10 +87,13 @@ const JupiterForm: FunctionComponent = () => {
|
|||
}
|
||||
|
||||
const fetchWalletTokens = useCallback(async () => {
|
||||
if (!publicKey) {
|
||||
return
|
||||
}
|
||||
const ownedTokens = []
|
||||
const ownedTokenAccounts = await getTokenAccountsByOwnerWithWrappedSol(
|
||||
connection,
|
||||
wallet.publicKey
|
||||
publicKey
|
||||
)
|
||||
|
||||
ownedTokenAccounts.forEach((account) => {
|
||||
|
@ -104,9 +104,8 @@ const JupiterForm: FunctionComponent = () => {
|
|||
const uiBalance = nativeToUi(account.amount, decimals || 6)
|
||||
ownedTokens.push({ account, uiBalance })
|
||||
})
|
||||
console.log('ownedToknes', ownedTokens)
|
||||
setWalletTokens(ownedTokens)
|
||||
}, [wallet, connection, tokens])
|
||||
}, [publicKey, connection, tokens])
|
||||
|
||||
// @ts-ignore
|
||||
const [inputTokenInfo, outputTokenInfo] = useMemo(() => {
|
||||
|
@ -237,6 +236,10 @@ const JupiterForm: FunctionComponent = () => {
|
|||
}
|
||||
}, [routeMap, tokens, formValue.inputMint])
|
||||
|
||||
const handleConnect = useCallback(() => {
|
||||
handleWalletConnect(wallet)
|
||||
}, [wallet])
|
||||
|
||||
const inputWalletBalance = () => {
|
||||
if (walletTokens.length) {
|
||||
const walletToken = walletTokens.filter((t) => {
|
||||
|
@ -352,7 +355,7 @@ const JupiterForm: FunctionComponent = () => {
|
|||
<div className="col-span-12 xl:col-span-10 xl:col-start-2 ">
|
||||
<div className="flex flex-col md:flex-row md:space-x-6">
|
||||
<div className="w-full md:w-1/2 lg:w-1/3">
|
||||
<div className="relative z-10">
|
||||
<div className="relative">
|
||||
{connected &&
|
||||
walletTokensWithInfos.length &&
|
||||
walletTokenPrices &&
|
||||
|
@ -373,11 +376,11 @@ const JupiterForm: FunctionComponent = () => {
|
|||
</div>
|
||||
<a
|
||||
className="flex items-center text-xs text-th-fgd-3 hover:text-th-fgd-2"
|
||||
href={`https://explorer.solana.com/address/${wallet?.publicKey}`}
|
||||
href={`https://explorer.solana.com/address/${publicKey}`}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
{abbreviateAddress(wallet.publicKey)}
|
||||
{abbreviateAddress(publicKey)}
|
||||
<ExternalLinkIcon className="ml-0.5 -mt-0.5 h-3.5 w-3.5" />
|
||||
</a>
|
||||
</div>
|
||||
|
@ -945,16 +948,21 @@ const JupiterForm: FunctionComponent = () => {
|
|||
<Button
|
||||
disabled={swapDisabled}
|
||||
onClick={async () => {
|
||||
if (!connected && zeroKey !== wallet?.publicKey) {
|
||||
wallet.connect()
|
||||
if (!connected && zeroKey !== publicKey) {
|
||||
handleConnect()
|
||||
} else if (!loading && selectedRoute && connected) {
|
||||
setSwapping(true)
|
||||
let txCount = 1
|
||||
let errorTxid
|
||||
const swapResult = await exchange({
|
||||
wallet: wallet as any,
|
||||
route: selectedRoute,
|
||||
confirmationWaiterFactory: async (txid, totalTxs) => {
|
||||
wallet: {
|
||||
sendTransaction: wallet?.adapter?.sendTransaction,
|
||||
publicKey: wallet?.adapter?.publicKey,
|
||||
signAllTransactions,
|
||||
signTransaction,
|
||||
},
|
||||
routeInfo: selectedRoute,
|
||||
onTransaction: async (txid, totalTxs) => {
|
||||
console.log('txid, totalTxs', txid, totalTxs)
|
||||
if (txCount === totalTxs) {
|
||||
errorTxid = txid
|
||||
|
|
|
@ -1,59 +0,0 @@
|
|||
import { useEffect, useState } from 'react'
|
||||
import { TranslateIcon } from '@heroicons/react/outline'
|
||||
import DropMenu from './DropMenu'
|
||||
import { useRouter } from 'next/router'
|
||||
import dayjs from 'dayjs'
|
||||
import useLocalStorageState from '../hooks/useLocalStorageState'
|
||||
|
||||
require('dayjs/locale/en')
|
||||
require('dayjs/locale/es')
|
||||
require('dayjs/locale/zh')
|
||||
require('dayjs/locale/zh-tw')
|
||||
|
||||
export const LANGS = [
|
||||
{ locale: 'en', name: 'english', description: 'english' },
|
||||
{ locale: 'es', name: 'spanish', description: 'spanish' },
|
||||
{
|
||||
locale: 'zh_tw',
|
||||
name: 'chinese-traditional',
|
||||
description: 'traditional chinese',
|
||||
},
|
||||
{ locale: 'zh', name: 'chinese', description: 'simplified chinese' },
|
||||
]
|
||||
|
||||
const LanguageSwitch = () => {
|
||||
const router = useRouter()
|
||||
const { pathname, asPath, query } = router
|
||||
const [mounted, setMounted] = useState(false)
|
||||
const [savedLanguage, setSavedLanguage] = useLocalStorageState('language', '')
|
||||
|
||||
// When mounted on client, now we can show the UI
|
||||
useEffect(() => setMounted(true), [])
|
||||
|
||||
const handleLangChange = (e) => {
|
||||
setSavedLanguage(e)
|
||||
router.push({ pathname, query }, asPath, { locale: e })
|
||||
dayjs.locale(e == 'zh_tw' ? 'zh-tw' : e)
|
||||
}
|
||||
|
||||
return (
|
||||
<div id="languages-tip">
|
||||
{mounted ? (
|
||||
<DropMenu
|
||||
button={
|
||||
<div className="default-transition flex h-8 w-8 items-center justify-center rounded-full bg-th-bkg-4 text-th-fgd-1 hover:text-th-primary focus:outline-none">
|
||||
<TranslateIcon className="h-4 w-4" />
|
||||
</div>
|
||||
}
|
||||
value={savedLanguage}
|
||||
onChange={(lang) => handleLangChange(lang)}
|
||||
options={LANGS}
|
||||
/>
|
||||
) : (
|
||||
<div className="h-8 w-8 rounded-full bg-th-bkg-3" />
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default LanguageSwitch
|
|
@ -6,9 +6,11 @@ import { nativeI80F48ToUi } from '@blockworks-foundation/mango-client'
|
|||
import { useViewport } from '../hooks/useViewport'
|
||||
import { breakpoints } from './TradePageGrid'
|
||||
import { useTranslation } from 'next-i18next'
|
||||
import { useWallet } from '@solana/wallet-adapter-react'
|
||||
|
||||
export default function MarketBalances() {
|
||||
const { t } = useTranslation('common')
|
||||
const { connected } = useWallet()
|
||||
const mangoGroup = useMangoStore((s) => s.selectedMangoGroup.current)
|
||||
const mangoGroupConfig = useMangoStore((s) => s.selectedMangoGroup.config)
|
||||
const mangoGroupCache = useMangoStore((s) => s.selectedMangoGroup.cache)
|
||||
|
@ -17,7 +19,6 @@ export default function MarketBalances() {
|
|||
const marketConfig = useMangoStore((s) => s.selectedMarket.config)
|
||||
const setMangoStore = useMangoStore((s) => s.set)
|
||||
const price = useMangoStore((s) => s.tradeForm.price)
|
||||
const connected = useMangoStore((s) => s.wallet.connected)
|
||||
const isLoading = useMangoStore((s) => s.selectedMangoAccount.initialLoad)
|
||||
const baseSymbol = marketConfig.baseSymbol
|
||||
const { width } = useViewport()
|
||||
|
|
|
@ -7,6 +7,7 @@ import Loading from './Loading'
|
|||
import { sleep } from '../utils'
|
||||
import Modal from './Modal'
|
||||
import { useTranslation } from 'next-i18next'
|
||||
import { useWallet } from '@solana/wallet-adapter-react'
|
||||
|
||||
interface MarketCloseModalProps {
|
||||
onClose: () => void
|
||||
|
@ -23,6 +24,7 @@ const MarketCloseModal: FunctionComponent<MarketCloseModalProps> = ({
|
|||
}) => {
|
||||
const { t } = useTranslation('common')
|
||||
const [submitting, setSubmitting] = useState(false)
|
||||
const { wallet } = useWallet()
|
||||
const actions = useMangoStore((s) => s.actions)
|
||||
const config = useMangoStore.getState().selectedMarket.config
|
||||
|
||||
|
@ -35,7 +37,6 @@ const MarketCloseModal: FunctionComponent<MarketCloseModalProps> = ({
|
|||
useMangoStore.getState().accountInfos[marketConfig.asksKey.toString()]
|
||||
const bidInfo =
|
||||
useMangoStore.getState().accountInfos[marketConfig.bidsKey.toString()]
|
||||
const wallet = useMangoStore.getState().wallet.current
|
||||
|
||||
const orderbook = useMangoStore.getState().selectedMarket.orderBook
|
||||
const markPrice = useMangoStore.getState().selectedMarket.markPrice
|
||||
|
@ -63,7 +64,7 @@ const MarketCloseModal: FunctionComponent<MarketCloseModalProps> = ({
|
|||
mangoGroup,
|
||||
mangoAccount,
|
||||
market,
|
||||
wallet,
|
||||
wallet?.adapter,
|
||||
side,
|
||||
referencePrice * (1 + (side === 'buy' ? 1 : -1) * maxSlippage),
|
||||
size,
|
||||
|
|
|
@ -14,6 +14,7 @@ import { breakpoints } from './TradePageGrid'
|
|||
import { useTranslation } from 'next-i18next'
|
||||
import SwitchMarketDropdown from './SwitchMarketDropdown'
|
||||
import Tooltip from './Tooltip'
|
||||
import { useWallet } from '@solana/wallet-adapter-react'
|
||||
|
||||
const OraclePrice = () => {
|
||||
const oraclePrice = useOraclePrice()
|
||||
|
@ -38,12 +39,12 @@ const OraclePrice = () => {
|
|||
|
||||
const MarketDetails = () => {
|
||||
const { t } = useTranslation('common')
|
||||
const { connected } = useWallet()
|
||||
const marketConfig = useMangoStore((s) => s.selectedMarket.config)
|
||||
const baseSymbol = marketConfig.baseSymbol
|
||||
const selectedMarketName = marketConfig.name
|
||||
const isPerpMarket = marketConfig.kind === 'perp'
|
||||
|
||||
const connected = useMangoStore((s) => s.wallet.connected)
|
||||
const { width } = useViewport()
|
||||
const isMobile = width ? width < breakpoints.sm : false
|
||||
|
||||
|
@ -105,11 +106,11 @@ const MarketDetails = () => {
|
|||
{t('average-funding')}
|
||||
</div>
|
||||
<div className="text-th-fgd-1 md:text-xs">
|
||||
{`${market?.funding1h.toLocaleString(undefined, {
|
||||
maximumSignificantDigits: 3,
|
||||
})}% (${(market?.funding1h * 24 * 365).toFixed(
|
||||
2
|
||||
)}% APR)`}
|
||||
{`${market?.funding1h.toFixed(4)}% (${(
|
||||
market?.funding1h *
|
||||
24 *
|
||||
365
|
||||
).toFixed(2)}% APR)`}
|
||||
</div>
|
||||
</div>
|
||||
</Tooltip>
|
||||
|
|
|
@ -38,6 +38,12 @@ const MarketNavItem: FunctionComponent<MarketNavItemProps> = ({
|
|||
mangoGroupConfig,
|
||||
market.baseSymbol
|
||||
)
|
||||
|
||||
// The following if statement is for markets not on devnet
|
||||
if (!mangoGroup.spotMarkets[marketIndex]) {
|
||||
return 1
|
||||
}
|
||||
|
||||
const ws = getWeights(mangoGroup, marketIndex, 'Init')
|
||||
const w = market.name.includes('PERP')
|
||||
? ws.perpAssetWeight
|
||||
|
@ -85,7 +91,7 @@ const MarketNavItem: FunctionComponent<MarketNavItemProps> = ({
|
|||
</div>
|
||||
) : null}
|
||||
</button>
|
||||
<div className="ml-2">
|
||||
<div className="ml-1">
|
||||
<FavoriteMarketButton market={market} />
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -20,17 +20,62 @@ import { useViewport } from '../hooks/useViewport'
|
|||
import { breakpoints } from './TradePageGrid'
|
||||
import { useTranslation } from 'next-i18next'
|
||||
import useMangoAccount from '../hooks/useMangoAccount'
|
||||
import { useWallet, Wallet } from '@solana/wallet-adapter-react'
|
||||
|
||||
export const settlePosPnl = async (
|
||||
perpMarkets: PerpMarket[],
|
||||
perpAccount: PerpAccount,
|
||||
t,
|
||||
mangoAccounts: MangoAccount[] | undefined,
|
||||
wallet: Wallet
|
||||
) => {
|
||||
const mangoAccount = useMangoStore.getState().selectedMangoAccount.current
|
||||
const mangoGroup = useMangoStore.getState().selectedMangoGroup.current
|
||||
const mangoCache = useMangoStore.getState().selectedMangoGroup.cache
|
||||
const actions = useMangoStore.getState().actions
|
||||
const mangoClient = useMangoStore.getState().connection.client
|
||||
|
||||
try {
|
||||
const txids = await mangoClient.settlePosPnl(
|
||||
mangoGroup,
|
||||
mangoCache,
|
||||
mangoAccount,
|
||||
perpMarkets,
|
||||
mangoGroup.rootBankAccounts[QUOTE_INDEX],
|
||||
wallet?.adapter,
|
||||
mangoAccounts
|
||||
)
|
||||
actions.reloadMangoAccount()
|
||||
for (const txid of txids) {
|
||||
if (txid) {
|
||||
notify({
|
||||
title: t('pnl-success'),
|
||||
description: '',
|
||||
txid,
|
||||
})
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.log('Error settling PNL: ', `${e}`, `${perpAccount}`)
|
||||
notify({
|
||||
title: t('pnl-error'),
|
||||
description: e.message,
|
||||
txid: e.txid,
|
||||
type: 'error',
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export const settlePnl = async (
|
||||
perpMarket: PerpMarket,
|
||||
perpAccount: PerpAccount,
|
||||
t,
|
||||
mangoAccounts: MangoAccount[] | undefined
|
||||
mangoAccounts: MangoAccount[] | undefined,
|
||||
wallet: Wallet
|
||||
) => {
|
||||
const mangoAccount = useMangoStore.getState().selectedMangoAccount.current
|
||||
const mangoGroup = useMangoStore.getState().selectedMangoGroup.current
|
||||
const mangoCache = useMangoStore.getState().selectedMangoGroup.cache
|
||||
const wallet = useMangoStore.getState().wallet.current
|
||||
const actions = useMangoStore.getState().actions
|
||||
const marketIndex = mangoGroup.getPerpMarketIndex(perpMarket.publicKey)
|
||||
const mangoClient = useMangoStore.getState().connection.client
|
||||
|
@ -43,7 +88,7 @@ export const settlePnl = async (
|
|||
perpMarket,
|
||||
mangoGroup.rootBankAccounts[QUOTE_INDEX],
|
||||
mangoCache.priceCache[marketIndex].price,
|
||||
wallet,
|
||||
wallet?.adapter,
|
||||
mangoAccounts
|
||||
)
|
||||
actions.reloadMangoAccount()
|
||||
|
@ -65,13 +110,13 @@ export const settlePnl = async (
|
|||
|
||||
export default function MarketPosition() {
|
||||
const { t } = useTranslation('common')
|
||||
const { wallet, connected } = useWallet()
|
||||
const mangoGroup = useMangoStore((s) => s.selectedMangoGroup.current)
|
||||
const mangoGroupConfig = useMangoStore((s) => s.selectedMangoGroup.config)
|
||||
const mangoCache = useMangoStore((s) => s.selectedMangoGroup.cache)
|
||||
const { mangoAccount, initialLoad } = useMangoAccount()
|
||||
const selectedMarket = useMangoStore((s) => s.selectedMarket.current)
|
||||
const marketConfig = useMangoStore((s) => s.selectedMarket.config)
|
||||
const connected = useMangoStore((s) => s.wallet.connected)
|
||||
const setMangoStore = useMangoStore((s) => s.set)
|
||||
const price = useMangoStore((s) => s.tradeForm.price)
|
||||
const perpAccounts = useMangoStore((s) => s.selectedMangoAccount.perpAccounts)
|
||||
|
@ -112,7 +157,7 @@ export default function MarketPosition() {
|
|||
|
||||
const handleSettlePnl = (perpMarket, perpAccount) => {
|
||||
setSettling(true)
|
||||
settlePnl(perpMarket, perpAccount, t, undefined).then(() => {
|
||||
settlePnl(perpMarket, perpAccount, t, undefined, wallet).then(() => {
|
||||
setSettling(false)
|
||||
})
|
||||
}
|
||||
|
@ -248,23 +293,23 @@ export default function MarketPosition() {
|
|||
)}
|
||||
</div>
|
||||
</div>
|
||||
{basePosition ? (
|
||||
{basePosition && (
|
||||
<Button
|
||||
onClick={() => setShowMarketCloseModal(true)}
|
||||
className="mt-2.5 w-full"
|
||||
>
|
||||
<span>{t('market-close')}</span>
|
||||
</Button>
|
||||
) : null}
|
||||
)}
|
||||
</div>
|
||||
{showMarketCloseModal ? (
|
||||
{showMarketCloseModal && (
|
||||
<MarketCloseModal
|
||||
isOpen={showMarketCloseModal}
|
||||
onClose={handleCloseWarning}
|
||||
market={selectedMarket}
|
||||
marketIndex={marketIndex}
|
||||
/>
|
||||
) : null}
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -21,6 +21,7 @@ import { useTranslation } from 'next-i18next'
|
|||
import ButtonGroup from './ButtonGroup'
|
||||
import InlineNotification from './InlineNotification'
|
||||
import Modal from './Modal'
|
||||
import { useWallet } from '@solana/wallet-adapter-react'
|
||||
|
||||
interface NewAccountProps {
|
||||
onAccountCreation?: (x?) => void
|
||||
|
@ -36,6 +37,7 @@ const NewAccount: FunctionComponent<NewAccountProps> = ({
|
|||
const [depositPercentage, setDepositPercentage] = useState('')
|
||||
const [invalidNameMessage, setInvalidNameMessage] = useState('')
|
||||
const [name, setName] = useState('')
|
||||
const { wallet } = useWallet()
|
||||
const walletTokens = useMangoStore((s) => s.wallet.tokens)
|
||||
const actions = useMangoStore((s) => s.actions)
|
||||
|
||||
|
@ -58,11 +60,12 @@ const NewAccount: FunctionComponent<NewAccountProps> = ({
|
|||
amount: parseFloat(inputAmount),
|
||||
fromTokenAcc: selectedAccount.account,
|
||||
accountName: name,
|
||||
wallet,
|
||||
})
|
||||
.then(async (response) => {
|
||||
await sleep(1000)
|
||||
actions.fetchWalletTokens()
|
||||
actions.fetchAllMangoAccounts()
|
||||
actions.fetchWalletTokens(wallet)
|
||||
actions.fetchAllMangoAccounts(wallet)
|
||||
setSubmitting(false)
|
||||
onAccountCreation(response[0])
|
||||
notify({
|
||||
|
|
|
@ -21,6 +21,7 @@ import { Row } from './TableElements'
|
|||
import { PerpTriggerOrder } from '../@types/types'
|
||||
import { useTranslation } from 'next-i18next'
|
||||
import Input, { Label } from './Input'
|
||||
import { useWallet, Wallet } from '@solana/wallet-adapter-react'
|
||||
|
||||
const DesktopTable = ({
|
||||
cancelledOrderId,
|
||||
|
@ -33,6 +34,7 @@ const DesktopTable = ({
|
|||
}) => {
|
||||
const { t } = useTranslation('common')
|
||||
const { asPath } = useRouter()
|
||||
const { wallet } = useWallet()
|
||||
const [modifiedOrderSize, setModifiedOrderSize] = useState('')
|
||||
const [modifiedOrderPrice, setModifiedOrderPrice] = useState('')
|
||||
|
||||
|
@ -170,7 +172,8 @@ const DesktopTable = ({
|
|||
order,
|
||||
market.account,
|
||||
modifiedOrderPrice || order.price,
|
||||
modifiedOrderSize || order.size
|
||||
modifiedOrderSize || order.size,
|
||||
wallet
|
||||
)
|
||||
}
|
||||
>
|
||||
|
@ -208,6 +211,7 @@ const MobileTable = ({
|
|||
setEditOrderIndex,
|
||||
}) => {
|
||||
const { t } = useTranslation('common')
|
||||
const { wallet } = useWallet()
|
||||
const [modifiedOrderSize, setModifiedOrderSize] = useState('')
|
||||
const [modifiedOrderPrice, setModifiedOrderPrice] = useState('')
|
||||
|
||||
|
@ -313,7 +317,8 @@ const MobileTable = ({
|
|||
order,
|
||||
market.account,
|
||||
modifiedOrderPrice || order.price,
|
||||
modifiedOrderSize || order.size
|
||||
modifiedOrderSize || order.size,
|
||||
wallet
|
||||
)
|
||||
}
|
||||
>
|
||||
|
@ -335,6 +340,7 @@ const MobileTable = ({
|
|||
const OpenOrdersTable = () => {
|
||||
const { t } = useTranslation('common')
|
||||
const { asPath } = useRouter()
|
||||
const { wallet } = useWallet()
|
||||
const openOrders = useMangoStore((s) => s.selectedMangoAccount.openOrders)
|
||||
const [cancelId, setCancelId] = useState(null)
|
||||
const [modifyId, setModifyId] = useState(null)
|
||||
|
@ -347,7 +353,6 @@ const OpenOrdersTable = () => {
|
|||
order: Order | PerpOrder | PerpTriggerOrder,
|
||||
market: Market | PerpMarket
|
||||
) => {
|
||||
const wallet = useMangoStore.getState().wallet.current
|
||||
const selectedMangoGroup =
|
||||
useMangoStore.getState().selectedMangoGroup.current
|
||||
const selectedMangoAccount =
|
||||
|
@ -361,7 +366,7 @@ const OpenOrdersTable = () => {
|
|||
txid = await mangoClient.cancelSpotOrder(
|
||||
selectedMangoGroup,
|
||||
selectedMangoAccount,
|
||||
wallet,
|
||||
wallet?.adapter,
|
||||
market,
|
||||
order as Order
|
||||
)
|
||||
|
@ -372,7 +377,7 @@ const OpenOrdersTable = () => {
|
|||
txid = await mangoClient.removeAdvancedOrder(
|
||||
selectedMangoGroup,
|
||||
selectedMangoAccount,
|
||||
wallet,
|
||||
wallet?.adapter,
|
||||
(order as PerpTriggerOrder).orderId
|
||||
)
|
||||
actions.reloadOrders()
|
||||
|
@ -380,7 +385,7 @@ const OpenOrdersTable = () => {
|
|||
txid = await mangoClient.cancelPerpOrder(
|
||||
selectedMangoGroup,
|
||||
selectedMangoAccount,
|
||||
wallet,
|
||||
wallet?.adapter,
|
||||
market,
|
||||
order as PerpOrder,
|
||||
false
|
||||
|
@ -407,7 +412,8 @@ const OpenOrdersTable = () => {
|
|||
order: Order | PerpOrder,
|
||||
market: Market | PerpMarket,
|
||||
price: number,
|
||||
size: number
|
||||
size: number,
|
||||
wallet: Wallet
|
||||
) => {
|
||||
const mangoAccount = useMangoStore.getState().selectedMangoAccount.current
|
||||
const mangoGroup = useMangoStore.getState().selectedMangoGroup.current
|
||||
|
@ -417,7 +423,6 @@ const OpenOrdersTable = () => {
|
|||
useMangoStore.getState().accountInfos[marketConfig.asksKey.toString()]
|
||||
const bidInfo =
|
||||
useMangoStore.getState().accountInfos[marketConfig.bidsKey.toString()]
|
||||
const wallet = useMangoStore.getState().wallet.current
|
||||
const referrerPk = useMangoStore.getState().referrerPk
|
||||
|
||||
if (!wallet || !mangoGroup || !mangoAccount || !market) return
|
||||
|
@ -440,7 +445,7 @@ const OpenOrdersTable = () => {
|
|||
mangoAccount,
|
||||
mangoGroup.mangoCache,
|
||||
market,
|
||||
wallet,
|
||||
wallet?.adapter,
|
||||
order as Order,
|
||||
order.side,
|
||||
orderPrice,
|
||||
|
@ -453,7 +458,7 @@ const OpenOrdersTable = () => {
|
|||
mangoAccount,
|
||||
mangoGroup.mangoCache,
|
||||
market,
|
||||
wallet,
|
||||
wallet?.adapter,
|
||||
order as PerpOrder,
|
||||
order.side,
|
||||
orderPrice,
|
||||
|
|
|
@ -0,0 +1,123 @@
|
|||
import { ChevronDownIcon } from '@heroicons/react/solid'
|
||||
import React, { Fragment, useState } from 'react'
|
||||
import { settlePnl, settlePosPnl } from 'components/MarketPosition'
|
||||
import Button from 'components/Button'
|
||||
import { Transition } from '@headlessui/react'
|
||||
import { useTranslation } from 'next-i18next'
|
||||
import Loading from 'components/Loading'
|
||||
import useMangoStore from 'stores/useMangoStore'
|
||||
import { useWallet } from '@solana/wallet-adapter-react'
|
||||
|
||||
const MenuButton: React.FC<{
|
||||
onClick: () => void
|
||||
text: string
|
||||
disabled?: boolean
|
||||
}> = ({ onClick, text, disabled }) => {
|
||||
return (
|
||||
<div
|
||||
className={`default-transition flex items-center justify-end whitespace-nowrap pb-2.5 text-xs tracking-wider hover:cursor-pointer hover:text-th-primary ${
|
||||
disabled ? 'pointer-events-none text-th-fgd-4' : 'text-th-fgd-1'
|
||||
}`}
|
||||
onClick={disabled ? null : onClick}
|
||||
>
|
||||
{text}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export const RedeemDropdown: React.FC = () => {
|
||||
const { t } = useTranslation('common')
|
||||
const { reloadMangoAccount } = useMangoStore((s) => s.actions)
|
||||
const [settling, setSettling] = useState(false)
|
||||
const { wallet } = useWallet()
|
||||
const [settlingPosPnl, setSettlingPosPnl] = useState(false)
|
||||
const [open, setOpen] = useState(false)
|
||||
const unsettledPositions =
|
||||
useMangoStore.getState().selectedMangoAccount.unsettledPerpPositions
|
||||
const unsettledPositivePositions = useMangoStore
|
||||
.getState()
|
||||
.selectedMangoAccount.unsettledPerpPositions?.filter(
|
||||
(p) => p.unsettledPnl > 0
|
||||
)
|
||||
|
||||
const loading = settling || settlingPosPnl
|
||||
|
||||
const handleSettleAll = async () => {
|
||||
setOpen(false)
|
||||
setSettling(true)
|
||||
for (const p of unsettledPositions) {
|
||||
await settlePnl(p.perpMarket, p.perpAccount, t, undefined, wallet)
|
||||
}
|
||||
|
||||
reloadMangoAccount()
|
||||
setSettling(false)
|
||||
}
|
||||
|
||||
const handleSettlePosPnl = async () => {
|
||||
setOpen(false)
|
||||
setSettlingPosPnl(true)
|
||||
for (const p of unsettledPositivePositions) {
|
||||
await settlePosPnl([p.perpMarket], p.perpAccount, t, null, wallet)
|
||||
}
|
||||
setSettlingPosPnl(false)
|
||||
}
|
||||
|
||||
const buttons = [
|
||||
{ onClick: handleSettleAll, disabled: false, text: t('redeem-all') },
|
||||
{
|
||||
onClick: handleSettlePosPnl,
|
||||
disabled: !unsettledPositivePositions?.length,
|
||||
text: t('redeem-positive'),
|
||||
},
|
||||
]
|
||||
|
||||
return (
|
||||
<div
|
||||
className="relative"
|
||||
onMouseOver={() => setOpen(true)}
|
||||
onMouseOut={() => setOpen(false)}
|
||||
>
|
||||
<Button
|
||||
className="flex h-8 w-full items-center justify-center rounded-full bg-th-bkg-button pt-0 pb-0 pl-3 pr-2 text-xs font-bold hover:brightness-[1.1] hover:filter sm:w-auto"
|
||||
disabled={!unsettledPositions?.length}
|
||||
>
|
||||
{loading ? (
|
||||
<Loading />
|
||||
) : (
|
||||
<>
|
||||
{t('redeem-pnl')}
|
||||
<ChevronDownIcon
|
||||
className={`default-transition h-5 w-5 ${
|
||||
open ? 'rotate-180 transform' : 'rotate-360 transform'
|
||||
}`}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</Button>
|
||||
<Transition
|
||||
appear={true}
|
||||
show={open}
|
||||
as={Fragment}
|
||||
enter="transition-all ease-in duration-200"
|
||||
enterFrom="opacity-0 transform scale-75"
|
||||
enterTo="opacity-100 transform scale-100"
|
||||
leave="transition ease-out duration-200"
|
||||
leaveFrom="opacity-100"
|
||||
leaveTo="opacity-0"
|
||||
>
|
||||
<div className="absolute right-0 rounded-md bg-th-bkg-3 px-4 pt-2.5">
|
||||
{buttons.map((b) => {
|
||||
return (
|
||||
<MenuButton
|
||||
key={b.text}
|
||||
onClick={b.onClick}
|
||||
text={b.text}
|
||||
disabled={b.disabled}
|
||||
/>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</Transition>
|
||||
</div>
|
||||
)
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
export * from './RedeemDropdown'
|
|
@ -1,11 +1,11 @@
|
|||
import { useCallback, useState } from 'react'
|
||||
import React, { useCallback, useState } from 'react'
|
||||
import { useRouter } from 'next/router'
|
||||
import Link from 'next/link'
|
||||
import { useTranslation } from 'next-i18next'
|
||||
import { ExclamationIcon } from '@heroicons/react/outline'
|
||||
|
||||
import useMangoStore from '../stores/useMangoStore'
|
||||
import Button, { LinkButton } from '../components/Button'
|
||||
import { LinkButton } from '../components/Button'
|
||||
import { useViewport } from '../hooks/useViewport'
|
||||
import { breakpoints } from './TradePageGrid'
|
||||
import { ExpandableRow, Table, Td, Th, TrBody, TrHead } from './TableElements'
|
||||
|
@ -19,17 +19,18 @@ import MobileTableHeader from './mobile/MobileTableHeader'
|
|||
import ShareModal from './ShareModal'
|
||||
import { TwitterIcon } from './icons'
|
||||
import { marketSelector } from '../stores/selectors'
|
||||
import { useWallet } from '@solana/wallet-adapter-react'
|
||||
import { RedeemDropdown } from 'components/PerpPositions'
|
||||
|
||||
const PositionsTable = () => {
|
||||
const PositionsTable: React.FC = () => {
|
||||
const { t } = useTranslation('common')
|
||||
const { reloadMangoAccount } = useMangoStore((s) => s.actions)
|
||||
const [settling, setSettling] = useState(false)
|
||||
const [showShareModal, setShowShareModal] = useState(false)
|
||||
const [showMarketCloseModal, setShowMarketCloseModal] = useState(false)
|
||||
const [positionToShare, setPositionToShare] = useState(null)
|
||||
const [settleSinglePos, setSettleSinglePos] = useState(null)
|
||||
|
||||
const market = useMangoStore(marketSelector)
|
||||
const { wallet } = useWallet()
|
||||
const price = useMangoStore((s) => s.tradeForm.price)
|
||||
const setMangoStore = useMangoStore((s) => s.set)
|
||||
const openPositions = useMangoStore(
|
||||
|
@ -57,16 +58,6 @@ const PositionsTable = () => {
|
|||
})
|
||||
}
|
||||
|
||||
const handleSettleAll = async () => {
|
||||
setSettling(true)
|
||||
for (const p of unsettledPositions) {
|
||||
await settlePnl(p.perpMarket, p.perpAccount, t, undefined)
|
||||
}
|
||||
|
||||
reloadMangoAccount()
|
||||
setSettling(false)
|
||||
}
|
||||
|
||||
const handleCloseShare = useCallback(() => {
|
||||
setShowShareModal(false)
|
||||
}, [])
|
||||
|
@ -78,7 +69,7 @@ const PositionsTable = () => {
|
|||
|
||||
const handleSettlePnl = async (perpMarket, perpAccount, index) => {
|
||||
setSettleSinglePos(index)
|
||||
await settlePnl(perpMarket, perpAccount, t, undefined)
|
||||
await settlePnl(perpMarket, perpAccount, t, undefined, wallet)
|
||||
setSettleSinglePos(null)
|
||||
}
|
||||
|
||||
|
@ -92,12 +83,7 @@ const PositionsTable = () => {
|
|||
<h3>{t('unsettled-positions')}</h3>
|
||||
</div>
|
||||
|
||||
<Button
|
||||
className="h-8 whitespace-nowrap pt-0 pb-0 pl-3 pr-3 text-xs"
|
||||
onClick={handleSettleAll}
|
||||
>
|
||||
{settling ? <Loading /> : t('redeem-all')}
|
||||
</Button>
|
||||
<RedeemDropdown />
|
||||
</div>
|
||||
<div className="grid grid-flow-row grid-cols-1 gap-4 md:grid-cols-2 xl:grid-cols-3">
|
||||
{unsettledPositions.map((p, index) => {
|
||||
|
|
|
@ -6,6 +6,7 @@ const Select = ({
|
|||
onChange,
|
||||
children,
|
||||
className = '',
|
||||
dropdownPanelClassName = '',
|
||||
placeholder = '',
|
||||
disabled = false,
|
||||
}) => {
|
||||
|
@ -32,7 +33,7 @@ const Select = ({
|
|||
{open ? (
|
||||
<Listbox.Options
|
||||
static
|
||||
className={`thin-scroll absolute left-0 z-20 mt-1 max-h-60 w-full origin-top-left overflow-auto rounded-md bg-th-bkg-3 p-2 text-th-fgd-1 outline-none`}
|
||||
className={`thin-scroll absolute left-0 z-20 mt-1 max-h-60 w-full origin-top-left overflow-auto rounded-md bg-th-bkg-3 p-2 text-th-fgd-1 outline-none ${dropdownPanelClassName}`}
|
||||
>
|
||||
{children}
|
||||
</Listbox.Options>
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import React, { useState } from 'react'
|
||||
import React, { useMemo, useState } from 'react'
|
||||
import { ChevronLeftIcon, ChevronRightIcon } from '@heroicons/react/solid'
|
||||
import useMangoGroupConfig from '../hooks/useMangoGroupConfig'
|
||||
import Modal from './Modal'
|
||||
|
@ -11,6 +11,15 @@ import Select from './Select'
|
|||
import { useTranslation } from 'next-i18next'
|
||||
import Switch from './Switch'
|
||||
import { MarketKind } from '@blockworks-foundation/mango-client'
|
||||
import { useTheme } from 'next-themes'
|
||||
import { useRouter } from 'next/router'
|
||||
import ButtonGroup from './ButtonGroup'
|
||||
import dayjs from 'dayjs'
|
||||
|
||||
require('dayjs/locale/en')
|
||||
require('dayjs/locale/es')
|
||||
require('dayjs/locale/zh')
|
||||
require('dayjs/locale/zh-tw')
|
||||
|
||||
const NODE_URLS = [
|
||||
{ label: 'Triton (RPC Pool)', value: 'https://mango.rpcpool.com' },
|
||||
|
@ -18,9 +27,26 @@ const NODE_URLS = [
|
|||
label: 'Genesys Go',
|
||||
value: 'https://mango.genesysgo.net/',
|
||||
},
|
||||
{
|
||||
label: 'Project Serum',
|
||||
value: 'https://solana-api.projectserum.com/',
|
||||
},
|
||||
{ label: 'Custom', value: '' },
|
||||
]
|
||||
|
||||
const THEMES = ['Light', 'Dark', 'Mango']
|
||||
|
||||
export const LANGS = [
|
||||
{ locale: 'en', name: 'english', description: 'english' },
|
||||
{ locale: 'es', name: 'spanish', description: 'spanish' },
|
||||
{
|
||||
locale: 'zh_tw',
|
||||
name: 'chinese-traditional',
|
||||
description: 'traditional chinese',
|
||||
},
|
||||
{ locale: 'zh', name: 'chinese', description: 'simplified chinese' },
|
||||
]
|
||||
|
||||
const CUSTOM_NODE = NODE_URLS.find((n) => n.label === 'Custom')
|
||||
|
||||
export const NODE_URL_KEY = 'node-url-key-0.6'
|
||||
|
@ -37,6 +63,8 @@ export const initialMarket = {
|
|||
const SettingsModal = ({ isOpen, onClose }) => {
|
||||
const { t } = useTranslation('common')
|
||||
const [settingsView, setSettingsView] = useState('')
|
||||
const { theme } = useTheme()
|
||||
const [savedLanguage] = useLocalStorageState('language', '')
|
||||
const [rpcEndpointUrl] = useLocalStorageState(
|
||||
NODE_URL_KEY,
|
||||
NODE_URLS[0].value
|
||||
|
@ -58,6 +86,12 @@ const SettingsModal = ({ isOpen, onClose }) => {
|
|||
|
||||
const rpcEndpoint =
|
||||
NODE_URLS.find((node) => node.value === rpcEndpointUrl) || CUSTOM_NODE
|
||||
|
||||
const savedLanguageName = useMemo(
|
||||
() => LANGS.find((l) => l.locale === savedLanguage).name,
|
||||
[savedLanguage]
|
||||
)
|
||||
|
||||
return (
|
||||
<Modal isOpen={isOpen} onClose={onClose}>
|
||||
{settingsView !== '' ? (
|
||||
|
@ -75,7 +109,7 @@ const SettingsModal = ({ isOpen, onClose }) => {
|
|||
{!settingsView ? (
|
||||
<div className="border-b border-th-bkg-4">
|
||||
<button
|
||||
className="default-transition flex w-full items-center justify-between border-t border-th-bkg-4 py-3 font-normal text-th-fgd-1 hover:text-th-primary focus:outline-none"
|
||||
className="default-transition flex w-full items-center justify-between rounded-none border-t border-th-bkg-4 py-3 font-normal text-th-fgd-1 hover:text-th-primary focus:outline-none"
|
||||
onClick={() => setSettingsView('Default Market')}
|
||||
>
|
||||
<span>{t('default-market')}</span>
|
||||
|
@ -85,7 +119,27 @@ const SettingsModal = ({ isOpen, onClose }) => {
|
|||
</div>
|
||||
</button>
|
||||
<button
|
||||
className="default-transition flex w-full items-center justify-between border-t border-th-bkg-4 py-3 font-normal text-th-fgd-1 hover:text-th-primary focus:outline-none"
|
||||
className="default-transition flex w-full items-center justify-between rounded-none border-t border-th-bkg-4 py-3 font-normal text-th-fgd-1 hover:text-th-primary focus:outline-none"
|
||||
onClick={() => setSettingsView('Theme')}
|
||||
>
|
||||
<span>{t('theme')}</span>
|
||||
<div className="flex items-center text-xs text-th-fgd-3">
|
||||
{theme}
|
||||
<ChevronRightIcon className="ml-1 h-5 w-5 text-th-fgd-1" />
|
||||
</div>
|
||||
</button>
|
||||
<button
|
||||
className="default-transition flex w-full items-center justify-between rounded-none border-t border-th-bkg-4 py-3 font-normal text-th-fgd-1 hover:text-th-primary focus:outline-none"
|
||||
onClick={() => setSettingsView('Language')}
|
||||
>
|
||||
<span>{t('language')}</span>
|
||||
<div className="flex items-center text-xs text-th-fgd-3">
|
||||
{t(savedLanguageName)}
|
||||
<ChevronRightIcon className="ml-1 h-5 w-5 text-th-fgd-1" />
|
||||
</div>
|
||||
</button>
|
||||
<button
|
||||
className="default-transition flex w-full items-center justify-between rounded-none border-t border-th-bkg-4 py-3 font-normal text-th-fgd-1 hover:text-th-primary focus:outline-none"
|
||||
onClick={() => setSettingsView('RPC Endpoint')}
|
||||
>
|
||||
<span>{t('rpc-endpoint')}</span>
|
||||
|
@ -132,6 +186,10 @@ const SettingsContent = ({ settingsView, setSettingsView }) => {
|
|||
return <DefaultMarketSettings setSettingsView={setSettingsView} />
|
||||
case 'RPC Endpoint':
|
||||
return <RpcEndpointSettings setSettingsView={setSettingsView} />
|
||||
case 'Theme':
|
||||
return <ThemeSettings setSettingsView={setSettingsView} />
|
||||
case 'Language':
|
||||
return <LanguageSettings />
|
||||
case '':
|
||||
return null
|
||||
}
|
||||
|
@ -240,3 +298,49 @@ const RpcEndpointSettings = ({ setSettingsView }) => {
|
|||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const ThemeSettings = ({ setSettingsView }) => {
|
||||
const { theme, setTheme } = useTheme()
|
||||
const { t } = useTranslation('common')
|
||||
|
||||
return (
|
||||
<>
|
||||
<Label>{t('theme')}</Label>
|
||||
<ButtonGroup
|
||||
activeValue={theme}
|
||||
onChange={(t) => setTheme(t)}
|
||||
values={THEMES}
|
||||
/>
|
||||
<Button onClick={() => setSettingsView('')} className="mt-6 w-full">
|
||||
<div className={`flex items-center justify-center`}>{t('save')}</div>
|
||||
</Button>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
const LanguageSettings = () => {
|
||||
const [savedLanguage, setSavedLanguage] = useLocalStorageState('language', '')
|
||||
const router = useRouter()
|
||||
const { pathname, asPath, query } = router
|
||||
const { t } = useTranslation('common')
|
||||
|
||||
const handleLangChange = () => {
|
||||
router.push({ pathname, query }, asPath, { locale: savedLanguage })
|
||||
dayjs.locale(savedLanguage == 'zh_tw' ? 'zh-tw' : savedLanguage)
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Label>{t('language')}</Label>
|
||||
<ButtonGroup
|
||||
activeValue={savedLanguage}
|
||||
onChange={(l) => setSavedLanguage(l)}
|
||||
values={LANGS.map((val) => val.locale)}
|
||||
names={LANGS.map((val) => t(val.name))}
|
||||
/>
|
||||
<Button onClick={() => handleLangChange()} className="mt-6 w-full">
|
||||
<div className={`flex items-center justify-center`}>{t('save')}</div>
|
||||
</Button>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -99,7 +99,7 @@ export const Row = ({ children }: RowProps) => {
|
|||
)
|
||||
}
|
||||
|
||||
export const TableDateDisplay = ({ date }: { date: string }) => (
|
||||
export const TableDateDisplay = ({ date }: { date: string | number }) => (
|
||||
<>
|
||||
<p className="mb-0 text-th-fgd-2">{dayjs(date).format('DD MMM YYYY')}</p>
|
||||
<p className="mb-0 text-xs">{dayjs(date).format('h:mma')}</p>
|
||||
|
|
|
@ -1,40 +0,0 @@
|
|||
import { useEffect, useState } from 'react'
|
||||
import { useTheme } from 'next-themes'
|
||||
import { MoonIcon, SunIcon } from '@heroicons/react/outline'
|
||||
import DropMenu from './DropMenu'
|
||||
import { MangoIcon } from './icons'
|
||||
|
||||
const THEMES = [
|
||||
{ name: 'Light', icon: <SunIcon className="h-4 w-4" /> },
|
||||
{ name: 'Dark', icon: <MoonIcon className="h-4 w-4" /> },
|
||||
{ name: 'Mango', icon: <MangoIcon className="h-4 w-4 stroke-current" /> },
|
||||
]
|
||||
|
||||
const ThemeSwitch = () => {
|
||||
const [mounted, setMounted] = useState(false)
|
||||
const { theme, setTheme } = useTheme()
|
||||
|
||||
// When mounted on client, now we can show the UI
|
||||
useEffect(() => setMounted(true), [])
|
||||
|
||||
return (
|
||||
<div id="themes-tip">
|
||||
{mounted ? (
|
||||
<DropMenu
|
||||
button={
|
||||
<div className="default-transition flex h-8 w-8 items-center justify-center rounded-full bg-th-bkg-4 text-th-fgd-1 hover:text-th-primary focus:outline-none">
|
||||
{THEMES.find((t) => t.name === theme).icon}
|
||||
</div>
|
||||
}
|
||||
value={theme}
|
||||
onChange={(theme) => setTheme(theme)}
|
||||
options={THEMES}
|
||||
/>
|
||||
) : (
|
||||
<div className="h-8 w-8 rounded-full bg-th-bkg-3" />
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default ThemeSwitch
|
|
@ -3,22 +3,22 @@ import Link from 'next/link'
|
|||
import { abbreviateAddress } from '../utils/index'
|
||||
import useLocalStorageState from '../hooks/useLocalStorageState'
|
||||
import MenuItem from './MenuItem'
|
||||
import ThemeSwitch from './ThemeSwitch'
|
||||
import useMangoStore from '../stores/useMangoStore'
|
||||
import ConnectWalletButton from './ConnectWalletButton'
|
||||
import { ConnectWalletButton } from 'components'
|
||||
import NavDropMenu from './NavDropMenu'
|
||||
import AccountsModal from './AccountsModal'
|
||||
import LanguageSwitch from './LanguageSwitch'
|
||||
import { DEFAULT_MARKET_KEY, initialMarket } from './SettingsModal'
|
||||
import { useTranslation } from 'next-i18next'
|
||||
import Settings from './Settings'
|
||||
import TradeNavMenu from './TradeNavMenu'
|
||||
import {
|
||||
CalculatorIcon,
|
||||
CurrencyDollarIcon,
|
||||
LightBulbIcon,
|
||||
UserAddIcon,
|
||||
} from '@heroicons/react/outline'
|
||||
import { MangoIcon } from './icons'
|
||||
import { useWallet } from '@solana/wallet-adapter-react'
|
||||
|
||||
const StyledNewLabel = ({ children, ...props }) => (
|
||||
<div style={{ fontSize: '0.5rem', marginLeft: '1px' }} {...props}>
|
||||
|
@ -28,8 +28,8 @@ const StyledNewLabel = ({ children, ...props }) => (
|
|||
|
||||
const TopBar = () => {
|
||||
const { t } = useTranslation('common')
|
||||
const { publicKey } = useWallet()
|
||||
const mangoAccount = useMangoStore((s) => s.selectedMangoAccount.current)
|
||||
const wallet = useMangoStore((s) => s.wallet.current)
|
||||
const [showAccountsModal, setShowAccountsModal] = useState(false)
|
||||
const [defaultMarket] = useLocalStorageState(
|
||||
DEFAULT_MARKET_KEY,
|
||||
|
@ -83,7 +83,7 @@ const TopBar = () => {
|
|||
t('referrals'),
|
||||
'/referral',
|
||||
false,
|
||||
<UserAddIcon className="h-4 w-4" key="calculator" />,
|
||||
<UserAddIcon className="h-4 w-4" key="referrals" />,
|
||||
],
|
||||
[
|
||||
t('calculator'),
|
||||
|
@ -91,6 +91,12 @@ const TopBar = () => {
|
|||
false,
|
||||
<CalculatorIcon className="h-4 w-4" key="calculator" />,
|
||||
],
|
||||
[
|
||||
t('fees'),
|
||||
'/fees',
|
||||
false,
|
||||
<CurrencyDollarIcon className="h-4 w-4" key="fees" />,
|
||||
],
|
||||
[
|
||||
t('learn'),
|
||||
'https://docs.mango.markets/',
|
||||
|
@ -119,48 +125,35 @@ const TopBar = () => {
|
|||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center">
|
||||
<div className={`pl-2`}>
|
||||
<LanguageSwitch />
|
||||
</div>
|
||||
<div className={`pl-2`}>
|
||||
<ThemeSwitch />
|
||||
</div>
|
||||
<div className="flex items-center space-x-2.5">
|
||||
<div className="pl-2">
|
||||
<Settings />
|
||||
</div>
|
||||
{mangoAccount &&
|
||||
mangoAccount.owner.toBase58() ===
|
||||
wallet?.publicKey?.toBase58() ? (
|
||||
<div className="pl-2">
|
||||
<button
|
||||
className="rounded border border-th-bkg-4 py-1 px-2 text-xs hover:border-th-fgd-4 focus:outline-none"
|
||||
onClick={() => setShowAccountsModal(true)}
|
||||
>
|
||||
<div className="text-xs font-normal text-th-primary">
|
||||
{t('account')}
|
||||
</div>
|
||||
{mangoAccount.name
|
||||
? mangoAccount.name
|
||||
: abbreviateAddress(mangoAccount.publicKey)}
|
||||
</button>
|
||||
</div>
|
||||
mangoAccount.owner.toBase58() === publicKey?.toBase58() ? (
|
||||
<button
|
||||
className="rounded border border-th-bkg-4 py-1 px-2 text-xs hover:border-th-fgd-4 focus:outline-none"
|
||||
onClick={() => setShowAccountsModal(true)}
|
||||
>
|
||||
<div className="text-xs font-normal text-th-primary">
|
||||
{t('account')}
|
||||
</div>
|
||||
{mangoAccount.name
|
||||
? mangoAccount.name
|
||||
: abbreviateAddress(mangoAccount.publicKey)}
|
||||
</button>
|
||||
) : null}
|
||||
<div className="flex">
|
||||
<div className="pl-4">
|
||||
<ConnectWalletButton />
|
||||
</div>
|
||||
</div>
|
||||
<ConnectWalletButton />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
{showAccountsModal ? (
|
||||
{showAccountsModal && (
|
||||
<AccountsModal
|
||||
onClose={handleCloseAccounts}
|
||||
isOpen={showAccountsModal}
|
||||
/>
|
||||
) : null}
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -0,0 +1,369 @@
|
|||
import {
|
||||
Fragment,
|
||||
FunctionComponent,
|
||||
useEffect,
|
||||
useMemo,
|
||||
useState,
|
||||
} from 'react'
|
||||
import { RefreshIcon } from '@heroicons/react/outline'
|
||||
import { ChevronDownIcon } from '@heroicons/react/solid'
|
||||
import Input, { Label } from './Input'
|
||||
import Button, { LinkButton } from './Button'
|
||||
import Modal from './Modal'
|
||||
import { ElementTitle } from './styles'
|
||||
import { useTranslation } from 'next-i18next'
|
||||
import useMangoGroupConfig from '../hooks/useMangoGroupConfig'
|
||||
import { Popover, Transition } from '@headlessui/react'
|
||||
import Checkbox from './Checkbox'
|
||||
import dayjs from 'dayjs'
|
||||
import DatePicker from './DatePicker'
|
||||
|
||||
import 'react-datepicker/dist/react-datepicker.css'
|
||||
|
||||
interface TradeHistoryFilterModalProps {
|
||||
filters: any
|
||||
setFilters: any
|
||||
isOpen: boolean
|
||||
onClose: () => void
|
||||
}
|
||||
|
||||
const TradeHistoryFilterModal: FunctionComponent<
|
||||
TradeHistoryFilterModalProps
|
||||
> = ({ filters, setFilters, isOpen, onClose }) => {
|
||||
const { t } = useTranslation('common')
|
||||
const [newFilters, setNewFilters] = useState({ ...filters })
|
||||
const [dateFrom, setDateFrom] = useState(null)
|
||||
const [dateTo, setDateTo] = useState(null)
|
||||
const [sizeFrom, setSizeFrom] = useState(filters?.size?.values?.from || '')
|
||||
const [sizeTo, setSizeTo] = useState(filters?.size?.values?.to || '')
|
||||
const [valueFrom, setValueFrom] = useState(filters?.value?.values?.from || '')
|
||||
const [valueTo, setValueTo] = useState(filters?.value?.values?.to || '')
|
||||
const groupConfig = useMangoGroupConfig()
|
||||
const markets = useMemo(
|
||||
() =>
|
||||
[...groupConfig.perpMarkets, ...groupConfig.spotMarkets].sort((a, b) =>
|
||||
a.name.localeCompare(b.name)
|
||||
),
|
||||
[groupConfig]
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
if (filters?.loadTimestamp?.values?.from) {
|
||||
setDateFrom(new Date(filters?.loadTimestamp?.values?.from))
|
||||
}
|
||||
|
||||
if (filters?.loadTimestamp?.values?.to) {
|
||||
setDateTo(new Date(filters?.loadTimestamp?.values?.to))
|
||||
}
|
||||
}, [])
|
||||
|
||||
const handleUpdateFilterButtons = (key: string, value: any) => {
|
||||
const updatedFilters = { ...newFilters }
|
||||
if (Object.prototype.hasOwnProperty.call(updatedFilters, key)) {
|
||||
updatedFilters[key].includes(value)
|
||||
? (updatedFilters[key] = updatedFilters[key].filter((v) => v !== value))
|
||||
: updatedFilters[key].push(value)
|
||||
} else {
|
||||
updatedFilters[key] = [value]
|
||||
}
|
||||
setNewFilters(updatedFilters)
|
||||
}
|
||||
|
||||
const toggleOption = ({ id }) => {
|
||||
setNewFilters((prevSelected) => {
|
||||
const newSelections = prevSelected.marketName
|
||||
? [...prevSelected.marketName]
|
||||
: []
|
||||
if (newSelections.includes(id)) {
|
||||
return {
|
||||
...prevSelected,
|
||||
marketName: newSelections.filter((item) => item != id),
|
||||
}
|
||||
} else {
|
||||
newSelections.push(id)
|
||||
return { ...prevSelected, marketName: newSelections }
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (sizeFrom && sizeTo) {
|
||||
// filter should still work if users get from/to backwards
|
||||
const from = sizeFrom < sizeTo ? sizeFrom : sizeTo
|
||||
const to = sizeTo > sizeFrom ? sizeTo : sizeFrom
|
||||
setNewFilters((prevSelected) => {
|
||||
return {
|
||||
...prevSelected,
|
||||
size: {
|
||||
condition: (size) =>
|
||||
parseFloat(size) >= from && parseFloat(size) <= to,
|
||||
values: { from: from, to: to },
|
||||
},
|
||||
}
|
||||
})
|
||||
}
|
||||
}, [sizeFrom, sizeTo])
|
||||
|
||||
useEffect(() => {
|
||||
if (valueFrom && valueTo) {
|
||||
// filter should still work if users get from/to backwards
|
||||
const from = valueFrom < valueTo ? valueFrom : valueTo
|
||||
const to = valueTo > valueFrom ? valueTo : valueFrom
|
||||
setNewFilters((prevSelected) => {
|
||||
return {
|
||||
...prevSelected,
|
||||
value: {
|
||||
condition: (value) => value >= from && value <= to,
|
||||
values: { from: from, to: to },
|
||||
},
|
||||
}
|
||||
})
|
||||
}
|
||||
}, [valueFrom, valueTo])
|
||||
|
||||
useEffect(() => {
|
||||
if (dateFrom && dateTo) {
|
||||
const dateFromTimestamp = dayjs(dateFrom).unix() * 1000
|
||||
const dateToTimestamp = dayjs(dateTo).unix() * 1000
|
||||
// filter should still work if users get from/to backwards
|
||||
const from =
|
||||
dateFromTimestamp < dateToTimestamp
|
||||
? dateFromTimestamp
|
||||
: dateToTimestamp
|
||||
const to =
|
||||
dateToTimestamp > dateFromTimestamp
|
||||
? dateToTimestamp
|
||||
: dateFromTimestamp
|
||||
setNewFilters((prevSelected) => {
|
||||
return {
|
||||
...prevSelected,
|
||||
loadTimestamp: {
|
||||
condition: (date) => {
|
||||
const timestamp = dayjs(date).unix() * 1000
|
||||
return timestamp >= from && timestamp <= to
|
||||
},
|
||||
values: { from: from, to: to },
|
||||
},
|
||||
}
|
||||
})
|
||||
}
|
||||
}, [dateFrom, dateTo])
|
||||
|
||||
const handleResetFilters = () => {
|
||||
setFilters({})
|
||||
setNewFilters({})
|
||||
setDateFrom('')
|
||||
setDateTo('')
|
||||
setSizeFrom('')
|
||||
setSizeTo('')
|
||||
setValueFrom('')
|
||||
setValueTo('')
|
||||
}
|
||||
|
||||
const updateFilters = (filters) => {
|
||||
setFilters(filters)
|
||||
onClose()
|
||||
}
|
||||
|
||||
return (
|
||||
<Modal onClose={onClose} isOpen={isOpen}>
|
||||
<Modal.Header>
|
||||
<div className="flex w-full items-start justify-between pt-2">
|
||||
<ElementTitle noMarginBottom>
|
||||
{t('filter-trade-history')}
|
||||
</ElementTitle>
|
||||
<LinkButton
|
||||
className="flex items-center text-th-primary"
|
||||
onClick={() => handleResetFilters()}
|
||||
>
|
||||
<RefreshIcon className="mr-1.5 h-4 w-4" />
|
||||
{t('reset')}
|
||||
</LinkButton>
|
||||
</div>
|
||||
</Modal.Header>
|
||||
<div className="pb-4">
|
||||
<p className="font-bold text-th-fgd-1">{t('date')}</p>
|
||||
<div className="flex items-center space-x-2">
|
||||
<div className="w-1/2">
|
||||
<Label>{t('from')}</Label>
|
||||
<DatePicker date={dateFrom} setDate={setDateFrom} />
|
||||
</div>
|
||||
<div className="w-1/2">
|
||||
<Label>{t('to')}</Label>
|
||||
<DatePicker date={dateTo} setDate={setDateTo} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="pb-4">
|
||||
<Label>{t('markets')}</Label>
|
||||
<MultiSelectDropdown
|
||||
options={markets}
|
||||
selected={newFilters.marketName || []}
|
||||
toggleOption={toggleOption}
|
||||
/>
|
||||
</div>
|
||||
<div className="mb-4 flex items-center justify-between border-y border-th-bkg-4 py-3">
|
||||
<p className="mb-0 font-bold text-th-fgd-1">{t('side')}</p>
|
||||
<div className="flex space-x-2">
|
||||
<FilterButton
|
||||
filters={newFilters}
|
||||
filterKey="side"
|
||||
onClick={() => handleUpdateFilterButtons('side', 'buy')}
|
||||
value="buy"
|
||||
/>
|
||||
<FilterButton
|
||||
filters={newFilters}
|
||||
filterKey="side"
|
||||
onClick={() => handleUpdateFilterButtons('side', 'sell')}
|
||||
value="sell"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="pb-4">
|
||||
<p className="font-bold text-th-fgd-1">{t('size')}</p>
|
||||
<div className="flex items-center space-x-2">
|
||||
<div className="w-1/2">
|
||||
<Label>{t('from')}</Label>
|
||||
<Input
|
||||
type="number"
|
||||
min="0"
|
||||
placeholder="0.00"
|
||||
value={sizeFrom || ''}
|
||||
onChange={(e) => setSizeFrom(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<div className="w-1/2">
|
||||
<Label>{t('to')}</Label>
|
||||
<Input
|
||||
type="number"
|
||||
min="0"
|
||||
placeholder="0.00"
|
||||
value={sizeTo || ''}
|
||||
onChange={(e) => setSizeTo(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="pb-4">
|
||||
<p className="font-bold text-th-fgd-1">{t('value')}</p>
|
||||
<div className="flex items-center space-x-2">
|
||||
<div className="w-1/2">
|
||||
<Label>{t('from')}</Label>
|
||||
<Input
|
||||
type="number"
|
||||
min="0"
|
||||
placeholder="0.00"
|
||||
value={valueFrom || ''}
|
||||
onChange={(e) => setValueFrom(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<div className="w-1/2">
|
||||
<Label>{t('to')}</Label>
|
||||
<Input
|
||||
type="number"
|
||||
min="0"
|
||||
placeholder="0.00"
|
||||
value={valueTo || ''}
|
||||
onChange={(e) => setValueTo(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="mb-6 flex items-center justify-between border-y border-th-bkg-4 py-3">
|
||||
<p className="mb-0 font-bold text-th-fgd-1">{t('liquidity')}</p>
|
||||
<div className="flex space-x-2">
|
||||
<FilterButton
|
||||
filters={newFilters}
|
||||
filterKey="liquidity"
|
||||
onClick={() => handleUpdateFilterButtons('liquidity', 'Maker')}
|
||||
value="Maker"
|
||||
/>
|
||||
<FilterButton
|
||||
filters={newFilters}
|
||||
filterKey="liquidity"
|
||||
onClick={() => handleUpdateFilterButtons('liquidity', 'Taker')}
|
||||
value="Taker"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<Button className="w-full" onClick={() => updateFilters(newFilters)}>
|
||||
{Object.keys(filters).length > 0 ? t('update-filters') : t('filter')}
|
||||
</Button>
|
||||
</Modal>
|
||||
)
|
||||
}
|
||||
|
||||
const FilterButton = ({ filters, filterKey, value, onClick }) => {
|
||||
const { t } = useTranslation('common')
|
||||
return (
|
||||
<button
|
||||
className={`default-transitions rounded-full border border-th-fgd-3 px-3 py-1 text-xs text-th-fgd-1 ${
|
||||
filters[filterKey]?.includes(value) &&
|
||||
'border-th-primary bg-th-primary text-th-bkg-1 hover:text-th-bkg-1'
|
||||
} hover:border-th-primary hover:text-th-primary`}
|
||||
onClick={onClick}
|
||||
>
|
||||
{t(value.toLowerCase())}
|
||||
</button>
|
||||
)
|
||||
}
|
||||
|
||||
const MultiSelectDropdown = ({ options, selected, toggleOption }) => {
|
||||
const { t } = useTranslation('common')
|
||||
return (
|
||||
<Popover className="relative">
|
||||
{({ open }) => (
|
||||
<div className="flex flex-col">
|
||||
<Popover.Button
|
||||
className={`default-transition rounded-md border bg-th-bkg-1 p-3 text-th-fgd-1 hover:border-th-fgd-4 ${
|
||||
open ? 'border-th-fgd-4' : 'border-th-bkg-4'
|
||||
}`}
|
||||
>
|
||||
<div className={`flex items-center justify-between`}>
|
||||
<span>
|
||||
{t('filters-selected', { selectedFilters: selected.length })}
|
||||
</span>
|
||||
<ChevronDownIcon
|
||||
className={`default-transition ml-0.5 h-5 w-5 ${
|
||||
open ? 'rotate-180 transform' : 'rotate-360 transform'
|
||||
}`}
|
||||
aria-hidden="true"
|
||||
/>
|
||||
</div>
|
||||
</Popover.Button>
|
||||
<Transition
|
||||
appear={true}
|
||||
show={open}
|
||||
as={Fragment}
|
||||
enter="transition-all ease-in duration-200"
|
||||
enterFrom="opacity-0 transform scale-75"
|
||||
enterTo="opacity-100 transform scale-100"
|
||||
leave="transition ease-out duration-200"
|
||||
leaveFrom="opacity-100"
|
||||
leaveTo="opacity-0"
|
||||
>
|
||||
<Popover.Panel className="absolute top-12 z-10 h-72 w-full overflow-y-auto">
|
||||
<div className="relative space-y-2.5 rounded-md bg-th-bkg-3 p-3">
|
||||
{options.map((option) => {
|
||||
const isSelected = selected.includes(option.name)
|
||||
return (
|
||||
<Checkbox
|
||||
checked={isSelected}
|
||||
className="mr-2"
|
||||
key={option.name}
|
||||
onChange={() => toggleOption({ id: option.name })}
|
||||
>
|
||||
{option.name}
|
||||
</Checkbox>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</Popover.Panel>
|
||||
</Transition>
|
||||
</div>
|
||||
)}
|
||||
</Popover>
|
||||
)
|
||||
}
|
||||
|
||||
export default TradeHistoryFilterModal
|
|
@ -3,7 +3,7 @@ import BN from 'bn.js'
|
|||
import Link from 'next/link'
|
||||
import { useRouter } from 'next/router'
|
||||
import SideBadge from './SideBadge'
|
||||
import { LinkButton } from './Button'
|
||||
import Button, { LinkButton } from './Button'
|
||||
import { useSortableData } from '../hooks/useSortableData'
|
||||
import { useViewport } from '../hooks/useViewport'
|
||||
import { breakpoints } from './TradePageGrid'
|
||||
|
@ -20,29 +20,41 @@ import { formatUsdValue } from '../utils'
|
|||
import { useTranslation } from 'next-i18next'
|
||||
import Pagination from './Pagination'
|
||||
import usePagination from '../hooks/usePagination'
|
||||
import { useEffect } from 'react'
|
||||
import { useEffect, useMemo, useState } from 'react'
|
||||
import { useFilteredData } from '../hooks/useFilteredData'
|
||||
import TradeHistoryFilterModal from './TradeHistoryFilterModal'
|
||||
import {
|
||||
FilterIcon,
|
||||
InformationCircleIcon,
|
||||
RefreshIcon,
|
||||
SaveIcon,
|
||||
} from '@heroicons/react/outline'
|
||||
import { fetchHourlyPerformanceStats } from './account_page/AccountOverview'
|
||||
import useMangoStore from '../stores/useMangoStore'
|
||||
import Loading from './Loading'
|
||||
import { exportDataToCSV } from '../utils/export'
|
||||
import Tooltip from './Tooltip'
|
||||
import { useWallet } from '@solana/wallet-adapter-react'
|
||||
|
||||
const renderTradeDateTime = (timestamp: BN | string) => {
|
||||
let date
|
||||
const formatTradeDateTime = (timestamp: BN | string) => {
|
||||
// don't compare to BN because of npm maddness
|
||||
// prototypes can be different due to multiple versions being imported
|
||||
if (typeof timestamp === 'string') {
|
||||
date = new Date(timestamp)
|
||||
return timestamp
|
||||
} else {
|
||||
date = new Date(timestamp.toNumber() * 1000)
|
||||
return timestamp.toNumber() * 1000
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<div>{date.toLocaleDateString()}</div>
|
||||
<div className="text-xs text-th-fgd-3">{date.toLocaleTimeString()}</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
const TradeHistoryTable = ({ numTrades }: { numTrades?: number }) => {
|
||||
const TradeHistoryTable = ({
|
||||
numTrades,
|
||||
showExportPnl,
|
||||
}: {
|
||||
numTrades?: number
|
||||
showExportPnl?: boolean
|
||||
}) => {
|
||||
const { t } = useTranslation('common')
|
||||
const mangoAccount = useMangoStore((s) => s.selectedMangoAccount.current)
|
||||
const { asPath } = useRouter()
|
||||
const { width } = useViewport()
|
||||
const tradeHistoryAndLiquidations = useMangoStore(
|
||||
|
@ -52,6 +64,13 @@ const TradeHistoryTable = ({ numTrades }: { numTrades?: number }) => {
|
|||
(t) => !('liqor' in t)
|
||||
)
|
||||
const isMobile = width ? width < breakpoints.md : false
|
||||
const [filters, setFilters] = useState({})
|
||||
const [showFiltersModal, setShowFiltersModal] = useState(false)
|
||||
const [loadExportData, setLoadExportData] = useState(false)
|
||||
|
||||
const filteredData = useFilteredData(tradeHistory, filters)
|
||||
const initialLoad = useMangoStore((s) => s.tradeHistory.initialLoad)
|
||||
const { publicKey } = useWallet()
|
||||
|
||||
const {
|
||||
paginatedData,
|
||||
|
@ -63,14 +82,14 @@ const TradeHistoryTable = ({ numTrades }: { numTrades?: number }) => {
|
|||
lastPage,
|
||||
setData,
|
||||
data,
|
||||
} = usePagination(tradeHistory || [], { perPage: 100 })
|
||||
} = usePagination(filteredData, { perPage: 100 })
|
||||
const { items, requestSort, sortConfig } = useSortableData(paginatedData)
|
||||
|
||||
useEffect(() => {
|
||||
if (tradeHistory?.length && data?.length !== tradeHistory?.length) {
|
||||
setData(tradeHistory)
|
||||
if (data?.length !== filteredData?.length) {
|
||||
setData(filteredData)
|
||||
}
|
||||
}, [tradeHistory])
|
||||
}, [filteredData])
|
||||
|
||||
const renderMarketName = (trade: any) => {
|
||||
if (
|
||||
|
@ -94,329 +113,477 @@ const TradeHistoryTable = ({ numTrades }: { numTrades?: number }) => {
|
|||
}
|
||||
}
|
||||
|
||||
const exportPerformanceDataToCSV = async () => {
|
||||
setLoadExportData(true)
|
||||
const exportData = await fetchHourlyPerformanceStats(
|
||||
mangoAccount.publicKey.toString(),
|
||||
10000
|
||||
)
|
||||
const dataToExport = exportData.map((row) => {
|
||||
const timestamp = new Date(row.time)
|
||||
return {
|
||||
timestamp: `${timestamp.toLocaleDateString()} ${timestamp.toLocaleTimeString()}`,
|
||||
account_equity: row.account_equity,
|
||||
pnl: row.pnl,
|
||||
}
|
||||
})
|
||||
|
||||
const title = `${
|
||||
mangoAccount.name || mangoAccount.publicKey
|
||||
}-Performance-${new Date().toLocaleDateString()}`
|
||||
const headers = ['Timestamp', 'Account Equity', 'PNL']
|
||||
|
||||
exportDataToCSV(dataToExport, title, headers, t)
|
||||
setLoadExportData(false)
|
||||
}
|
||||
|
||||
const hasActiveFilter = useMemo(() => {
|
||||
return tradeHistory.length !== filteredData.length
|
||||
}, [data, filteredData])
|
||||
|
||||
const mangoAccountPk = useMemo(() => {
|
||||
console.log('new mango account')
|
||||
|
||||
return mangoAccount.publicKey.toString()
|
||||
}, [mangoAccount])
|
||||
|
||||
const canWithdraw =
|
||||
mangoAccount && publicKey ? mangoAccount.owner.equals(publicKey) : false
|
||||
|
||||
return (
|
||||
<div className={`flex flex-col sm:pb-4`}>
|
||||
<div className={`overflow-x-auto sm:-mx-6 lg:-mx-8`}>
|
||||
<div className={`inline-block min-w-full align-middle sm:px-6 lg:px-8`}>
|
||||
{tradeHistory && tradeHistory.length ? (
|
||||
!isMobile ? (
|
||||
<>
|
||||
<Table>
|
||||
<thead>
|
||||
<TrHead>
|
||||
<Th>
|
||||
<LinkButton
|
||||
className="flex items-center no-underline"
|
||||
onClick={() => requestSort('marketName')}
|
||||
>
|
||||
<span className="font-normal">{t('market')}</span>
|
||||
<ArrowSmDownIcon
|
||||
className={`default-transition ml-1 h-4 w-4 flex-shrink-0 ${
|
||||
sortConfig?.key === 'marketName'
|
||||
? sortConfig.direction === 'ascending'
|
||||
? 'rotate-180 transform'
|
||||
: 'rotate-360 transform'
|
||||
: null
|
||||
}`}
|
||||
/>
|
||||
</LinkButton>
|
||||
</Th>
|
||||
<Th>
|
||||
<LinkButton
|
||||
className="flex items-center no-underline"
|
||||
onClick={() => requestSort('side')}
|
||||
>
|
||||
<span className="font-normal">{t('side')}</span>
|
||||
<ArrowSmDownIcon
|
||||
className={`default-transition ml-1 h-4 w-4 flex-shrink-0 ${
|
||||
sortConfig?.key === 'side'
|
||||
? sortConfig.direction === 'ascending'
|
||||
? 'rotate-180 transform'
|
||||
: 'rotate-360 transform'
|
||||
: null
|
||||
}`}
|
||||
/>
|
||||
</LinkButton>
|
||||
</Th>
|
||||
<Th>
|
||||
<LinkButton
|
||||
className="flex items-center no-underline"
|
||||
onClick={() => requestSort('size')}
|
||||
>
|
||||
<span className="font-normal">{t('size')}</span>
|
||||
<ArrowSmDownIcon
|
||||
className={`default-transition ml-1 h-4 w-4 flex-shrink-0 ${
|
||||
sortConfig?.key === 'size'
|
||||
? sortConfig.direction === 'ascending'
|
||||
? 'rotate-180 transform'
|
||||
: 'rotate-360 transform'
|
||||
: null
|
||||
}`}
|
||||
/>
|
||||
</LinkButton>
|
||||
</Th>
|
||||
<Th>
|
||||
<LinkButton
|
||||
className="flex items-center no-underline"
|
||||
onClick={() => requestSort('price')}
|
||||
>
|
||||
<span className="font-normal">{t('price')}</span>
|
||||
<ArrowSmDownIcon
|
||||
className={`default-transition ml-1 h-4 w-4 flex-shrink-0 ${
|
||||
sortConfig?.key === 'price'
|
||||
? sortConfig.direction === 'ascending'
|
||||
? 'rotate-180 transform'
|
||||
: 'rotate-360 transform'
|
||||
: null
|
||||
}`}
|
||||
/>
|
||||
</LinkButton>
|
||||
</Th>
|
||||
<Th>
|
||||
<LinkButton
|
||||
className="flex items-center no-underline"
|
||||
onClick={() => requestSort('value')}
|
||||
>
|
||||
<span className="font-normal">{t('value')}</span>
|
||||
<ArrowSmDownIcon
|
||||
className={`default-transition ml-1 h-4 w-4 flex-shrink-0 ${
|
||||
sortConfig?.key === 'value'
|
||||
? sortConfig.direction === 'ascending'
|
||||
? 'rotate-180 transform'
|
||||
: 'rotate-360 transform'
|
||||
: null
|
||||
}`}
|
||||
/>
|
||||
</LinkButton>
|
||||
</Th>
|
||||
<Th>
|
||||
<LinkButton
|
||||
className="flex items-center no-underline"
|
||||
onClick={() => requestSort('liquidity')}
|
||||
>
|
||||
<span className="font-normal">{t('liquidity')}</span>
|
||||
<ArrowSmDownIcon
|
||||
className={`default-transition ml-1 h-4 w-4 flex-shrink-0 ${
|
||||
sortConfig?.key === 'liquidity'
|
||||
? sortConfig.direction === 'ascending'
|
||||
? 'rotate-180 transform'
|
||||
: 'rotate-360 transform'
|
||||
: null
|
||||
}`}
|
||||
/>
|
||||
</LinkButton>
|
||||
</Th>
|
||||
<Th>
|
||||
<LinkButton
|
||||
className="flex items-center no-underline"
|
||||
onClick={() => requestSort('feeCost')}
|
||||
>
|
||||
<span className="font-normal">{t('fee')}</span>
|
||||
<ArrowSmDownIcon
|
||||
className={`default-transition ml-1 h-4 w-4 flex-shrink-0 ${
|
||||
sortConfig?.key === 'feeCost'
|
||||
? sortConfig.direction === 'ascending'
|
||||
? 'rotate-180 transform'
|
||||
: 'rotate-360 transform'
|
||||
: null
|
||||
}`}
|
||||
/>
|
||||
</LinkButton>
|
||||
</Th>
|
||||
<Th>
|
||||
<LinkButton
|
||||
className="flex items-center no-underline"
|
||||
onClick={() => requestSort('loadTimestamp')}
|
||||
>
|
||||
<span className="font-normal">{t('date')}</span>
|
||||
<ArrowSmDownIcon
|
||||
className={`default-transition ml-1 h-4 w-4 flex-shrink-0 ${
|
||||
sortConfig?.key === 'loadTimestamp'
|
||||
? sortConfig.direction === 'ascending'
|
||||
? 'rotate-180 transform'
|
||||
: 'rotate-360 transform'
|
||||
: null
|
||||
}`}
|
||||
/>
|
||||
</LinkButton>
|
||||
</Th>
|
||||
</TrHead>
|
||||
</thead>
|
||||
<tbody>
|
||||
{items.map((trade: any) => {
|
||||
return (
|
||||
<TrBody key={`${trade.seqNum}${trade.marketName}`}>
|
||||
<Td className="!py-2 ">
|
||||
<div className="flex items-center">
|
||||
<img
|
||||
alt=""
|
||||
width="20"
|
||||
height="20"
|
||||
src={`/assets/icons/${trade.marketName
|
||||
.split(/-|\//)[0]
|
||||
.toLowerCase()}.svg`}
|
||||
className={`mr-2.5`}
|
||||
/>
|
||||
{renderMarketName(trade)}
|
||||
</div>
|
||||
</Td>
|
||||
<Td className="!py-2 ">
|
||||
<SideBadge side={trade.side} />
|
||||
</Td>
|
||||
<Td className="!py-2 ">{trade.size}</Td>
|
||||
<Td className="!py-2 ">
|
||||
$
|
||||
{new Intl.NumberFormat('en-US').format(trade.price)}
|
||||
</Td>
|
||||
<Td className="!py-2 ">
|
||||
{formatUsdValue(trade.value)}
|
||||
</Td>
|
||||
<Td className="!py-2 ">
|
||||
{t(trade.liquidity.toLowerCase())}
|
||||
</Td>
|
||||
<Td className="!py-2 ">
|
||||
{formatUsdValue(trade.feeCost)}
|
||||
</Td>
|
||||
<Td className="!py-2">
|
||||
<>
|
||||
<div className="flex flex-col pb-3 sm:flex-row sm:items-center sm:justify-between">
|
||||
<div className="flex items-center">
|
||||
<h4 className="mb-0 flex items-center text-th-fgd-1">
|
||||
{!initialLoad ? <Loading className="mr-2" /> : data.length}{' '}
|
||||
{data.length === 1 ? 'Trade' : 'Trades'}
|
||||
</h4>
|
||||
<Tooltip
|
||||
content={
|
||||
<div className="mr-4 text-xs text-th-fgd-3">
|
||||
{t('delay-displaying-recent')} {t('use-explorer-one')}
|
||||
<a
|
||||
href={`https://explorer.solana.com/address/${mangoAccount.publicKey.toString()}`}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
{t('use-explorer-two')}
|
||||
</a>
|
||||
{t('use-explorer-three')}
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<InformationCircleIcon className="ml-1.5 h-5 w-5 cursor-pointer text-th-fgd-3" />
|
||||
</Tooltip>
|
||||
</div>
|
||||
<div className="flex flex-col space-y-3 pl-2 sm:flex-row sm:items-center sm:space-y-0 sm:space-x-3">
|
||||
{hasActiveFilter ? (
|
||||
<LinkButton
|
||||
className="order-4 mt-3 flex items-center justify-end whitespace-nowrap text-xs sm:order-first sm:mt-0"
|
||||
onClick={() => setFilters({})}
|
||||
>
|
||||
<RefreshIcon className="mr-1.5 h-4 w-4 flex-shrink-0" />
|
||||
{t('reset-filters')}
|
||||
</LinkButton>
|
||||
) : null}
|
||||
{tradeHistory.length >= 15 &&
|
||||
tradeHistory.length <= 10000 &&
|
||||
initialLoad ? (
|
||||
<Button
|
||||
className="order-3 mt-3 flex h-8 items-center justify-center whitespace-nowrap pt-0 pb-0 pl-3 pr-3 text-xs sm:order-first sm:mt-0"
|
||||
onClick={() => setShowFiltersModal(true)}
|
||||
>
|
||||
<FilterIcon className="mr-1.5 h-4 w-4" />
|
||||
{t('filter')}
|
||||
</Button>
|
||||
) : null}
|
||||
{canWithdraw && showExportPnl ? (
|
||||
<Button
|
||||
className={`flex h-8 items-center justify-center whitespace-nowrap pt-0 pb-0 pl-3 pr-3 text-xs`}
|
||||
onClick={exportPerformanceDataToCSV}
|
||||
>
|
||||
{loadExportData ? (
|
||||
<Loading />
|
||||
) : (
|
||||
<div className={`flex items-center`}>
|
||||
<SaveIcon className={`mr-1.5 h-4 w-4`} />
|
||||
{t('export-pnl-csv')}
|
||||
</div>
|
||||
)}
|
||||
</Button>
|
||||
) : null}
|
||||
{canWithdraw ? (
|
||||
<div className={`flex items-center`}>
|
||||
<a
|
||||
className={`default-transition flex h-8 w-full items-center justify-center whitespace-nowrap rounded-full bg-th-bkg-button pt-0 pb-0 pl-3 pr-3 text-xs font-bold text-th-fgd-1 hover:text-th-fgd-1 hover:brightness-[1.1]`}
|
||||
href={`https://event-history-api.herokuapp.com/all_trades_csv?mango_account=${mangoAccountPk}&open_orders=${mangoAccount.spotOpenOrders
|
||||
.filter(
|
||||
(e) => e.toString() !== '11111111111111111111111111111111'
|
||||
)
|
||||
.join(',')}`}
|
||||
download
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<SaveIcon className={`mr-1.5 h-4 w-4`} />
|
||||
{t('export-trades-csv')}
|
||||
<Tooltip content={t('trade-export-disclaimer')}>
|
||||
<InformationCircleIcon className="ml-1.5 h-5 w-5 cursor-help text-th-fgd-3" />
|
||||
</Tooltip>
|
||||
</a>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
<div className={`flex flex-col sm:pb-4`}>
|
||||
<div className={`overflow-x-auto sm:-mx-6 lg:-mx-8`}>
|
||||
<div
|
||||
className={`inline-block min-w-full align-middle sm:px-6 lg:px-8`}
|
||||
>
|
||||
{tradeHistory && paginatedData.length > 0 ? (
|
||||
!isMobile ? (
|
||||
<>
|
||||
<Table>
|
||||
<thead>
|
||||
<TrHead>
|
||||
<Th>
|
||||
<LinkButton
|
||||
className="flex items-center no-underline"
|
||||
onClick={() => requestSort('marketName')}
|
||||
>
|
||||
<span className="font-normal">{t('market')}</span>
|
||||
<ArrowSmDownIcon
|
||||
className={`default-transition ml-1 h-4 w-4 flex-shrink-0 ${
|
||||
sortConfig?.key === 'marketName'
|
||||
? sortConfig.direction === 'ascending'
|
||||
? 'rotate-180 transform'
|
||||
: 'rotate-360 transform'
|
||||
: null
|
||||
}`}
|
||||
/>
|
||||
</LinkButton>
|
||||
</Th>
|
||||
<Th>
|
||||
<LinkButton
|
||||
className="flex items-center no-underline"
|
||||
onClick={() => requestSort('side')}
|
||||
>
|
||||
<span className="font-normal">{t('side')}</span>
|
||||
<ArrowSmDownIcon
|
||||
className={`default-transition ml-1 h-4 w-4 flex-shrink-0 ${
|
||||
sortConfig?.key === 'side'
|
||||
? sortConfig.direction === 'ascending'
|
||||
? 'rotate-180 transform'
|
||||
: 'rotate-360 transform'
|
||||
: null
|
||||
}`}
|
||||
/>
|
||||
</LinkButton>
|
||||
</Th>
|
||||
<Th>
|
||||
<LinkButton
|
||||
className="flex items-center no-underline"
|
||||
onClick={() => requestSort('size')}
|
||||
>
|
||||
<span className="font-normal">{t('size')}</span>
|
||||
<ArrowSmDownIcon
|
||||
className={`default-transition ml-1 h-4 w-4 flex-shrink-0 ${
|
||||
sortConfig?.key === 'size'
|
||||
? sortConfig.direction === 'ascending'
|
||||
? 'rotate-180 transform'
|
||||
: 'rotate-360 transform'
|
||||
: null
|
||||
}`}
|
||||
/>
|
||||
</LinkButton>
|
||||
</Th>
|
||||
<Th>
|
||||
<LinkButton
|
||||
className="flex items-center no-underline"
|
||||
onClick={() => requestSort('price')}
|
||||
>
|
||||
<span className="font-normal">{t('price')}</span>
|
||||
<ArrowSmDownIcon
|
||||
className={`default-transition ml-1 h-4 w-4 flex-shrink-0 ${
|
||||
sortConfig?.key === 'price'
|
||||
? sortConfig.direction === 'ascending'
|
||||
? 'rotate-180 transform'
|
||||
: 'rotate-360 transform'
|
||||
: null
|
||||
}`}
|
||||
/>
|
||||
</LinkButton>
|
||||
</Th>
|
||||
<Th>
|
||||
<LinkButton
|
||||
className="flex items-center no-underline"
|
||||
onClick={() => requestSort('value')}
|
||||
>
|
||||
<span className="font-normal">{t('value')}</span>
|
||||
<ArrowSmDownIcon
|
||||
className={`default-transition ml-1 h-4 w-4 flex-shrink-0 ${
|
||||
sortConfig?.key === 'value'
|
||||
? sortConfig.direction === 'ascending'
|
||||
? 'rotate-180 transform'
|
||||
: 'rotate-360 transform'
|
||||
: null
|
||||
}`}
|
||||
/>
|
||||
</LinkButton>
|
||||
</Th>
|
||||
<Th>
|
||||
<LinkButton
|
||||
className="flex items-center no-underline"
|
||||
onClick={() => requestSort('liquidity')}
|
||||
>
|
||||
<span className="font-normal">
|
||||
{t('liquidity')}
|
||||
</span>
|
||||
<ArrowSmDownIcon
|
||||
className={`default-transition ml-1 h-4 w-4 flex-shrink-0 ${
|
||||
sortConfig?.key === 'liquidity'
|
||||
? sortConfig.direction === 'ascending'
|
||||
? 'rotate-180 transform'
|
||||
: 'rotate-360 transform'
|
||||
: null
|
||||
}`}
|
||||
/>
|
||||
</LinkButton>
|
||||
</Th>
|
||||
<Th>
|
||||
<LinkButton
|
||||
className="flex items-center no-underline"
|
||||
onClick={() => requestSort('feeCost')}
|
||||
>
|
||||
<span className="font-normal">{t('fee')}</span>
|
||||
<ArrowSmDownIcon
|
||||
className={`default-transition ml-1 h-4 w-4 flex-shrink-0 ${
|
||||
sortConfig?.key === 'feeCost'
|
||||
? sortConfig.direction === 'ascending'
|
||||
? 'rotate-180 transform'
|
||||
: 'rotate-360 transform'
|
||||
: null
|
||||
}`}
|
||||
/>
|
||||
</LinkButton>
|
||||
</Th>
|
||||
<Th>
|
||||
<LinkButton
|
||||
className="flex items-center no-underline"
|
||||
onClick={() => requestSort('loadTimestamp')}
|
||||
>
|
||||
<span className="font-normal">
|
||||
{t('approximate-time')}
|
||||
</span>
|
||||
<ArrowSmDownIcon
|
||||
className={`default-transition ml-1 h-4 w-4 flex-shrink-0 ${
|
||||
sortConfig?.key === 'loadTimestamp'
|
||||
? sortConfig.direction === 'ascending'
|
||||
? 'rotate-180 transform'
|
||||
: 'rotate-360 transform'
|
||||
: null
|
||||
}`}
|
||||
/>
|
||||
</LinkButton>
|
||||
</Th>
|
||||
</TrHead>
|
||||
</thead>
|
||||
<tbody>
|
||||
{items.map((trade: any) => {
|
||||
return (
|
||||
<TrBody key={`${trade.seqNum}${trade.marketName}`}>
|
||||
<Td className="!py-2 ">
|
||||
<div className="flex items-center">
|
||||
<img
|
||||
alt=""
|
||||
width="20"
|
||||
height="20"
|
||||
src={`/assets/icons/${trade.marketName
|
||||
.split(/-|\//)[0]
|
||||
.toLowerCase()}.svg`}
|
||||
className={`mr-2.5`}
|
||||
/>
|
||||
{renderMarketName(trade)}
|
||||
</div>
|
||||
</Td>
|
||||
<Td className="!py-2 ">
|
||||
<SideBadge side={trade.side} />
|
||||
</Td>
|
||||
<Td className="!py-2 ">{trade.size}</Td>
|
||||
<Td className="!py-2 ">
|
||||
$
|
||||
{new Intl.NumberFormat('en-US').format(
|
||||
trade.price
|
||||
)}
|
||||
</Td>
|
||||
<Td className="!py-2 ">
|
||||
{formatUsdValue(trade.value)}
|
||||
</Td>
|
||||
<Td className="!py-2 ">
|
||||
{t(trade.liquidity.toLowerCase())}
|
||||
</Td>
|
||||
<Td className="!py-2 ">
|
||||
{formatUsdValue(trade.feeCost)}
|
||||
</Td>
|
||||
<Td className="!py-2">
|
||||
{trade.loadTimestamp || trade.timestamp ? (
|
||||
<TableDateDisplay
|
||||
date={formatTradeDateTime(
|
||||
trade.loadTimestamp || trade.timestamp
|
||||
)}
|
||||
/>
|
||||
) : (
|
||||
t('recent')
|
||||
)}
|
||||
</Td>
|
||||
<Td className="keep-break w-[0.1%] !py-2">
|
||||
{trade.marketName.includes('PERP') ? (
|
||||
<a
|
||||
className="text-xs text-th-fgd-4 underline underline-offset-4"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
href={`/account?pubkey=${
|
||||
trade.liquidity === 'Taker'
|
||||
? trade.maker
|
||||
: trade.taker
|
||||
}`}
|
||||
>
|
||||
{t('view-counterparty')}
|
||||
</a>
|
||||
) : null}
|
||||
</Td>
|
||||
</TrBody>
|
||||
)
|
||||
})}
|
||||
</tbody>
|
||||
</Table>
|
||||
{numTrades && items.length > numTrades ? (
|
||||
<div className="mt-4 flex items-center justify-center">
|
||||
<Link href="/account" shallow={true}>
|
||||
{t('view-all-trades')}
|
||||
</Link>
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex items-center justify-end">
|
||||
<Pagination
|
||||
page={page}
|
||||
totalPages={totalPages}
|
||||
nextPage={nextPage}
|
||||
lastPage={lastPage}
|
||||
firstPage={firstPage}
|
||||
previousPage={previousPage}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
) : (
|
||||
paginatedData.map((trade: any, index) => (
|
||||
<ExpandableRow
|
||||
buttonTemplate={
|
||||
<>
|
||||
<div className="text-fgd-1 flex w-full items-center justify-between">
|
||||
<div className="text-left">
|
||||
{trade.loadTimestamp || trade.timestamp ? (
|
||||
<TableDateDisplay
|
||||
date={trade.loadTimestamp || trade.timestamp}
|
||||
date={formatTradeDateTime(
|
||||
trade.loadTimestamp || trade.timestamp
|
||||
)}
|
||||
/>
|
||||
) : (
|
||||
t('recent')
|
||||
)}
|
||||
</Td>
|
||||
<Td className="keep-break w-[0.1%] !py-2">
|
||||
{trade.marketName.includes('PERP') ? (
|
||||
<a
|
||||
className="text-xs text-th-fgd-4 underline underline-offset-4"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
href={`/account?pubkey=${
|
||||
trade.liquidity === 'Taker'
|
||||
? trade.maker
|
||||
: trade.taker
|
||||
}`}
|
||||
>
|
||||
{t('view-counterparty')}
|
||||
</a>
|
||||
) : null}
|
||||
</Td>
|
||||
</TrBody>
|
||||
)
|
||||
})}
|
||||
</tbody>
|
||||
</Table>
|
||||
{numTrades && items.length > numTrades ? (
|
||||
<div className="mt-4 flex items-center justify-center">
|
||||
<Link href="/account" shallow={true}>
|
||||
{t('view-all-trades')}
|
||||
</Link>
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex items-center justify-end">
|
||||
<Pagination
|
||||
page={page}
|
||||
totalPages={totalPages}
|
||||
nextPage={nextPage}
|
||||
lastPage={lastPage}
|
||||
firstPage={firstPage}
|
||||
previousPage={previousPage}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
) : (
|
||||
paginatedData.map((trade: any, index) => (
|
||||
<ExpandableRow
|
||||
buttonTemplate={
|
||||
<>
|
||||
<div className="text-fgd-1 flex w-full items-center justify-between">
|
||||
<div className="text-left">
|
||||
{trade.loadTimestamp || trade.timestamp
|
||||
? renderTradeDateTime(
|
||||
trade.loadTimestamp || trade.timestamp
|
||||
)
|
||||
: t('recent')}
|
||||
</div>
|
||||
<div>
|
||||
<div className="text-right">
|
||||
<div className="mb-0.5 flex items-center text-left">
|
||||
<img
|
||||
alt=""
|
||||
width="16"
|
||||
height="16"
|
||||
src={`/assets/icons/${trade.marketName
|
||||
.split(/-|\//)[0]
|
||||
.toLowerCase()}.svg`}
|
||||
className={`mr-1.5`}
|
||||
/>
|
||||
{trade.marketName}
|
||||
</div>
|
||||
<div className="text-xs text-th-fgd-3">
|
||||
<span
|
||||
className={`mr-1
|
||||
</div>
|
||||
<div>
|
||||
<div className="text-right">
|
||||
<div className="mb-0.5 flex items-center text-left">
|
||||
<img
|
||||
alt=""
|
||||
width="16"
|
||||
height="16"
|
||||
src={`/assets/icons/${trade.marketName
|
||||
.split(/-|\//)[0]
|
||||
.toLowerCase()}.svg`}
|
||||
className={`mr-1.5`}
|
||||
/>
|
||||
{trade.marketName}
|
||||
</div>
|
||||
<div className="text-xs text-th-fgd-3">
|
||||
<span
|
||||
className={`mr-1
|
||||
${
|
||||
trade.side === 'buy' || trade.side === 'long'
|
||||
? 'text-th-green'
|
||||
: 'text-th-red'
|
||||
}
|
||||
`}
|
||||
>
|
||||
{trade.side.toUpperCase()}
|
||||
</span>
|
||||
{trade.size}
|
||||
>
|
||||
{trade.side.toUpperCase()}
|
||||
</span>
|
||||
{trade.size}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
}
|
||||
key={`${index}`}
|
||||
panelTemplate={
|
||||
<div className="grid grid-flow-row grid-cols-2 gap-4">
|
||||
<div className="text-left">
|
||||
<div className="pb-0.5 text-xs text-th-fgd-3">
|
||||
{t('price')}
|
||||
</>
|
||||
}
|
||||
key={`${index}`}
|
||||
panelTemplate={
|
||||
<div className="grid grid-flow-row grid-cols-2 gap-4">
|
||||
<div className="text-left">
|
||||
<div className="pb-0.5 text-xs text-th-fgd-3">
|
||||
{t('price')}
|
||||
</div>
|
||||
{formatUsdValue(trade.price)}
|
||||
</div>
|
||||
{formatUsdValue(trade.price)}
|
||||
</div>
|
||||
<div className="text-left">
|
||||
<div className="pb-0.5 text-xs text-th-fgd-3">
|
||||
{t('value')}
|
||||
<div className="text-left">
|
||||
<div className="pb-0.5 text-xs text-th-fgd-3">
|
||||
{t('value')}
|
||||
</div>
|
||||
{formatUsdValue(trade.value)}
|
||||
</div>
|
||||
{formatUsdValue(trade.value)}
|
||||
</div>
|
||||
<div className="text-left">
|
||||
<div className="pb-0.5 text-xs text-th-fgd-3">
|
||||
{t('liquidity')}
|
||||
<div className="text-left">
|
||||
<div className="pb-0.5 text-xs text-th-fgd-3">
|
||||
{t('liquidity')}
|
||||
</div>
|
||||
{trade.liquidity}
|
||||
</div>
|
||||
{trade.liquidity}
|
||||
</div>
|
||||
<div className="text-left">
|
||||
<div className="pb-0.5 text-xs text-th-fgd-3">
|
||||
{t('fee')}
|
||||
<div className="text-left">
|
||||
<div className="pb-0.5 text-xs text-th-fgd-3">
|
||||
{t('fee')}
|
||||
</div>
|
||||
{formatUsdValue(trade.feeCost)}
|
||||
</div>
|
||||
{formatUsdValue(trade.feeCost)}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
))
|
||||
)
|
||||
) : (
|
||||
<div className="w-full rounded-md bg-th-bkg-1 py-6 text-center text-th-fgd-3">
|
||||
{t('no-history')}
|
||||
{asPath === '/account' ? (
|
||||
<Link href={'/'} shallow={true}>
|
||||
<a className="ml-2 inline-flex py-0">{t('make-trade')}</a>
|
||||
</Link>
|
||||
) : null}
|
||||
</div>
|
||||
)}
|
||||
}
|
||||
/>
|
||||
))
|
||||
)
|
||||
) : hasActiveFilter ? (
|
||||
<div className="w-full rounded-md bg-th-bkg-1 py-6 text-center text-th-fgd-3">
|
||||
{t('no-trades-found')}
|
||||
</div>
|
||||
) : (
|
||||
<div className="w-full rounded-md bg-th-bkg-1 py-6 text-center text-th-fgd-3">
|
||||
{t('no-history')}
|
||||
{asPath === '/account' ? (
|
||||
<Link href={'/'} shallow={true}>
|
||||
<a className="ml-2 inline-flex py-0">{t('make-trade')}</a>
|
||||
</Link>
|
||||
) : null}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{showFiltersModal ? (
|
||||
<TradeHistoryFilterModal
|
||||
filters={filters}
|
||||
setFilters={setFilters}
|
||||
isOpen={showFiltersModal}
|
||||
onClose={() => setShowFiltersModal(false)}
|
||||
/>
|
||||
) : null}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -240,14 +240,14 @@ export const FavoriteMarketButton = ({ market }) => {
|
|||
|
||||
return favoriteMarkets.find((mkt) => mkt.name === market.name) ? (
|
||||
<button
|
||||
className="default-transition text-th-primary hover:text-th-fgd-3"
|
||||
className="default-transition flex items-center justify-center text-th-primary hover:text-th-fgd-3"
|
||||
onClick={() => removeFromFavorites(market)}
|
||||
>
|
||||
<FilledStarIcon className="h-5 w-5" />
|
||||
</button>
|
||||
) : (
|
||||
<button
|
||||
className="default-transition text-th-fgd-4 hover:text-th-primary"
|
||||
className="default-transition flex items-center justify-center text-th-fgd-4 hover:text-th-primary"
|
||||
onClick={() => addToFavorites(market)}
|
||||
>
|
||||
<StarIcon className="h-5 w-5" />
|
||||
|
|
|
@ -17,6 +17,7 @@ import { sleep, formatUsdValue, usdFormatter, roundPerpSize } from '../../utils'
|
|||
import { PerpTriggerOrder } from '../../@types/types'
|
||||
import { useTranslation } from 'next-i18next'
|
||||
import useLocalStorageState from '../../hooks/useLocalStorageState'
|
||||
import { useWallet, Wallet } from '@solana/wallet-adapter-react'
|
||||
|
||||
export interface ChartContainerProps {
|
||||
symbol: ChartingLibraryWidgetOptions['symbol']
|
||||
|
@ -40,6 +41,7 @@ const TVChartContainer = () => {
|
|||
const { t } = useTranslation(['common', 'tv-chart'])
|
||||
const { theme } = useTheme()
|
||||
const { width } = useViewport()
|
||||
const { wallet } = useWallet()
|
||||
const [chartReady, setChartReady] = useState(false)
|
||||
const [showOrderLinesLocalStorage, toggleShowOrderLinesLocalStorage] =
|
||||
useLocalStorageState(SHOW_ORDER_LINES_KEY, true)
|
||||
|
@ -51,7 +53,6 @@ const TVChartContainer = () => {
|
|||
const selectedMarketConfig = useMangoStore((s) => s.selectedMarket.config)
|
||||
const openOrders = useMangoStore((s) => s.selectedMangoAccount.openOrders)
|
||||
const actions = useMangoStore((s) => s.actions)
|
||||
const selectedMarketPrice = useMangoStore((s) => s.selectedMarket.markPrice)
|
||||
|
||||
const isMobile = width ? width < breakpoints.sm : false
|
||||
const mangoClient = useMangoStore.getState().connection.client
|
||||
|
@ -216,9 +217,9 @@ const TVChartContainer = () => {
|
|||
|
||||
const handleCancelOrder = async (
|
||||
order: Order | PerpOrder | PerpTriggerOrder,
|
||||
market: Market | PerpMarket
|
||||
market: Market | PerpMarket,
|
||||
wallet: Wallet
|
||||
) => {
|
||||
const wallet = useMangoStore.getState().wallet.current
|
||||
const selectedMangoGroup =
|
||||
useMangoStore.getState().selectedMangoGroup.current
|
||||
const selectedMangoAccount =
|
||||
|
@ -231,7 +232,7 @@ const TVChartContainer = () => {
|
|||
txid = await mangoClient.cancelSpotOrder(
|
||||
selectedMangoGroup,
|
||||
selectedMangoAccount,
|
||||
wallet,
|
||||
wallet?.adapter,
|
||||
// @ts-ignore
|
||||
market,
|
||||
order as Order
|
||||
|
@ -241,14 +242,14 @@ const TVChartContainer = () => {
|
|||
txid = await mangoClient.removeAdvancedOrder(
|
||||
selectedMangoGroup,
|
||||
selectedMangoAccount,
|
||||
wallet,
|
||||
wallet?.adapter,
|
||||
(order as PerpTriggerOrder).orderId
|
||||
)
|
||||
} else {
|
||||
txid = await mangoClient.cancelPerpOrder(
|
||||
selectedMangoGroup,
|
||||
selectedMangoAccount,
|
||||
wallet,
|
||||
wallet?.adapter,
|
||||
market,
|
||||
order as PerpOrder,
|
||||
false
|
||||
|
@ -273,7 +274,8 @@ const TVChartContainer = () => {
|
|||
const handleModifyOrder = async (
|
||||
order: Order | PerpOrder,
|
||||
market: Market | PerpMarket,
|
||||
price: number
|
||||
price: number,
|
||||
wallet: Wallet
|
||||
) => {
|
||||
const mangoAccount = useMangoStore.getState().selectedMangoAccount.current
|
||||
const mangoGroup = useMangoStore.getState().selectedMangoGroup.current
|
||||
|
@ -282,7 +284,6 @@ const TVChartContainer = () => {
|
|||
useMangoStore.getState().accountInfos[marketConfig.asksKey.toString()]
|
||||
const bidInfo =
|
||||
useMangoStore.getState().accountInfos[marketConfig.bidsKey.toString()]
|
||||
const wallet = useMangoStore.getState().wallet.current
|
||||
const referrerPk = useMangoStore.getState().referrerPk
|
||||
|
||||
if (!wallet || !mangoGroup || !mangoAccount || !market) return
|
||||
|
@ -306,7 +307,7 @@ const TVChartContainer = () => {
|
|||
mangoGroup.mangoCache,
|
||||
// @ts-ignore
|
||||
market,
|
||||
wallet,
|
||||
wallet?.adapter,
|
||||
order as Order,
|
||||
order.side,
|
||||
orderPrice,
|
||||
|
@ -319,7 +320,7 @@ const TVChartContainer = () => {
|
|||
mangoAccount,
|
||||
mangoGroup.mangoCache,
|
||||
market,
|
||||
wallet,
|
||||
wallet?.adapter,
|
||||
order as PerpOrder,
|
||||
order.side,
|
||||
orderPrice,
|
||||
|
@ -351,12 +352,15 @@ const TVChartContainer = () => {
|
|||
function drawLine(order, market) {
|
||||
const orderSizeUi = roundPerpSize(order.size, market.config.baseSymbol)
|
||||
if (!tvWidgetRef.current.chart()) return
|
||||
|
||||
return tvWidgetRef.current
|
||||
.chart()
|
||||
.createOrderLine({ disableUndo: false })
|
||||
.onMove(function () {
|
||||
const currentOrderPrice = order.price
|
||||
const updatedOrderPrice = this.getPrice()
|
||||
const selectedMarketPrice =
|
||||
useMangoStore.getState().selectedMarket.markPrice
|
||||
if (!order.perpTrigger?.clientOrderId) {
|
||||
if (
|
||||
(order.side === 'buy' &&
|
||||
|
@ -390,7 +394,12 @@ const TVChartContainer = () => {
|
|||
}),
|
||||
callback: (res) => {
|
||||
if (res) {
|
||||
handleModifyOrder(order, market.account, updatedOrderPrice)
|
||||
handleModifyOrder(
|
||||
order,
|
||||
market.account,
|
||||
updatedOrderPrice,
|
||||
wallet
|
||||
)
|
||||
} else {
|
||||
this.setPrice(currentOrderPrice)
|
||||
}
|
||||
|
@ -418,7 +427,7 @@ const TVChartContainer = () => {
|
|||
}),
|
||||
callback: (res) => {
|
||||
if (res) {
|
||||
handleCancelOrder(order, market.account)
|
||||
handleCancelOrder(order, market.account, wallet)
|
||||
}
|
||||
},
|
||||
})
|
||||
|
|
|
@ -6,16 +6,9 @@ import PositionsTable from './PerpPositionsTable'
|
|||
import TradeHistoryTable from './TradeHistoryTable'
|
||||
import ManualRefresh from './ManualRefresh'
|
||||
import Tabs from './Tabs'
|
||||
import FeeDiscountsTable from './FeeDiscountsTable'
|
||||
import { marketConfigSelector } from '../stores/selectors'
|
||||
|
||||
const TABS = [
|
||||
'Balances',
|
||||
'Orders',
|
||||
'Positions',
|
||||
'Trade History',
|
||||
'Fee Discount',
|
||||
]
|
||||
const TABS = ['Balances', 'Orders', 'Positions', 'Trade History']
|
||||
|
||||
const UserInfoTabs = ({ activeTab, setActiveTab }) => {
|
||||
const totalOpenOrders = useMangoStore(
|
||||
|
@ -60,8 +53,6 @@ const TabContent = ({ activeTab }) => {
|
|||
return <TradeHistoryTable numTrades={100} />
|
||||
case 'Positions':
|
||||
return <PositionsTable />
|
||||
case 'Fee Discount':
|
||||
return <FeeDiscountsTable />
|
||||
default:
|
||||
return <BalancesTable clickToPopulateTradeForm />
|
||||
}
|
||||
|
|
|
@ -0,0 +1,39 @@
|
|||
import React, { useEffect } from 'react'
|
||||
import useMangoStore from 'stores/useMangoStore'
|
||||
import { useWallet } from '@solana/wallet-adapter-react'
|
||||
|
||||
/*
|
||||
* This component listens for when the Solana Wallet Adapter connects to a wallet.
|
||||
* When a wallet is connected we stitch the Solana Wallet Adapter wallet to our Mango Store's wallet.
|
||||
* Eventually we can remove this listener when we move to only use one Wallet, preferably the Wallet Adapter Wallet.
|
||||
*/
|
||||
export const WalletListener: React.FC = () => {
|
||||
const set = useMangoStore((s) => s.set)
|
||||
|
||||
const actions = useMangoStore((s) => s.actions)
|
||||
|
||||
const { wallet } = useWallet()
|
||||
|
||||
const connecting = wallet?.adapter?.connecting
|
||||
|
||||
useEffect(() => {
|
||||
const onConnect = async () => {
|
||||
set((state) => {
|
||||
state.selectedMangoAccount.initialLoad = true
|
||||
})
|
||||
|
||||
await actions.fetchAllMangoAccounts(wallet)
|
||||
actions.fetchProfilePicture(wallet)
|
||||
|
||||
actions.reloadOrders()
|
||||
actions.fetchTradeHistory()
|
||||
actions.fetchWalletTokens()
|
||||
}
|
||||
|
||||
if (connecting) {
|
||||
onConnect()
|
||||
}
|
||||
}, [connecting, set, actions, wallet])
|
||||
|
||||
return null
|
||||
}
|
|
@ -0,0 +1,330 @@
|
|||
import {
|
||||
Adapter,
|
||||
SendTransactionOptions,
|
||||
WalletError,
|
||||
WalletNotConnectedError,
|
||||
WalletNotReadyError,
|
||||
WalletReadyState,
|
||||
} from '@solana/wallet-adapter-base'
|
||||
import { Connection, PublicKey, Transaction } from '@solana/web3.js'
|
||||
import { useLocalStorageStringState } from 'hooks/useLocalStorageState'
|
||||
import React, {
|
||||
FC,
|
||||
ReactNode,
|
||||
useCallback,
|
||||
useEffect,
|
||||
useMemo,
|
||||
useRef,
|
||||
useState,
|
||||
} from 'react'
|
||||
import { Wallet, WalletContext } from '@solana/wallet-adapter-react'
|
||||
|
||||
export interface WalletProviderProps {
|
||||
children: ReactNode
|
||||
wallets: Adapter[]
|
||||
autoConnect?: boolean
|
||||
onError?: (error: WalletError) => void
|
||||
localStorageKey?: string
|
||||
}
|
||||
|
||||
class WalletNotSelectedError extends WalletError {
|
||||
name = 'WalletNotSelectedError'
|
||||
}
|
||||
|
||||
const initialState: {
|
||||
wallet: Wallet | null
|
||||
adapter: Adapter | null
|
||||
publicKey: PublicKey | null
|
||||
connected: boolean
|
||||
} = {
|
||||
wallet: null,
|
||||
adapter: null,
|
||||
publicKey: null,
|
||||
connected: false,
|
||||
}
|
||||
|
||||
/*
|
||||
* This is a refactored version of the Solana Labs wallet.
|
||||
* For Mango's use case we needed to remove the clearing of the wallet after disconnect and errors.
|
||||
* Original: https://github.com/solana-labs/wallet-adapter/blob/master/packages/core/react/src/WalletProvider.tsx
|
||||
*/
|
||||
export const WalletProvider: FC<WalletProviderProps> = ({
|
||||
children,
|
||||
wallets: adapters,
|
||||
autoConnect = false,
|
||||
onError,
|
||||
localStorageKey = 'walletName',
|
||||
}) => {
|
||||
const [name, setName] = useLocalStorageStringState(localStorageKey, null)
|
||||
const [{ wallet, adapter, publicKey, connected }, setState] =
|
||||
useState(initialState)
|
||||
const readyState = adapter?.readyState || WalletReadyState.Unsupported
|
||||
const [connecting, setConnecting] = useState(false)
|
||||
const [disconnecting, setDisconnecting] = useState(false)
|
||||
const isConnecting = useRef(false)
|
||||
const isDisconnecting = useRef(false)
|
||||
const isUnloading = useRef(false)
|
||||
|
||||
// Wrap adapters to conform to the `Wallet` interface
|
||||
const [wallets, setWallets] = useState(() =>
|
||||
adapters.map((adapter) => ({
|
||||
adapter,
|
||||
readyState: adapter.readyState,
|
||||
}))
|
||||
)
|
||||
|
||||
// When the wallets change, start to listen for changes to their `readyState`
|
||||
useEffect(() => {
|
||||
function handleReadyStateChange(
|
||||
this: Adapter,
|
||||
readyState: WalletReadyState
|
||||
) {
|
||||
setWallets((prevWallets) => {
|
||||
const walletIndex = prevWallets.findIndex(
|
||||
({ adapter }) => adapter.name === this.name
|
||||
)
|
||||
if (walletIndex === -1) return prevWallets
|
||||
|
||||
return [
|
||||
...prevWallets.slice(0, walletIndex),
|
||||
{ ...prevWallets[walletIndex], readyState },
|
||||
...prevWallets.slice(walletIndex + 1),
|
||||
]
|
||||
})
|
||||
}
|
||||
for (const adapter of adapters) {
|
||||
adapter.on('readyStateChange', handleReadyStateChange, adapter)
|
||||
}
|
||||
return () => {
|
||||
for (const adapter of adapters) {
|
||||
adapter.off('readyStateChange', handleReadyStateChange, adapter)
|
||||
}
|
||||
}
|
||||
}, [adapters])
|
||||
|
||||
// When the selected wallet changes, initialize the state
|
||||
useEffect(() => {
|
||||
const wallet = wallets.find(({ adapter }) => adapter.name === name)
|
||||
if (wallet) {
|
||||
setState({
|
||||
wallet,
|
||||
adapter: wallet.adapter,
|
||||
connected: wallet.adapter.connected,
|
||||
publicKey: wallet.adapter.publicKey,
|
||||
})
|
||||
} else {
|
||||
setState(initialState)
|
||||
}
|
||||
}, [name, wallets])
|
||||
|
||||
// If autoConnect is enabled, try to connect when the adapter changes and is ready
|
||||
useEffect(() => {
|
||||
if (
|
||||
isConnecting.current ||
|
||||
connecting ||
|
||||
connected ||
|
||||
!autoConnect ||
|
||||
!adapter ||
|
||||
!(
|
||||
readyState === WalletReadyState.Installed ||
|
||||
readyState === WalletReadyState.Loadable
|
||||
)
|
||||
)
|
||||
return
|
||||
;(async function () {
|
||||
isConnecting.current = true
|
||||
setConnecting(true)
|
||||
try {
|
||||
await adapter.connect()
|
||||
} catch (error: any) {
|
||||
// Clear the selected wallet
|
||||
// Don't throw error, but handleError will still be called
|
||||
} finally {
|
||||
setConnecting(false)
|
||||
isConnecting.current = false
|
||||
}
|
||||
})()
|
||||
}, [isConnecting, connecting, connected, autoConnect, adapter, readyState])
|
||||
|
||||
// If the window is closing or reloading, ignore disconnect and error events from the adapter
|
||||
useEffect(() => {
|
||||
function listener() {
|
||||
isUnloading.current = true
|
||||
}
|
||||
|
||||
window.addEventListener('beforeunload', listener)
|
||||
return () => window.removeEventListener('beforeunload', listener)
|
||||
}, [isUnloading])
|
||||
|
||||
// Handle the adapter's connect event
|
||||
const handleConnect = useCallback(() => {
|
||||
if (!adapter) return
|
||||
setState((state) => ({
|
||||
...state,
|
||||
connected: adapter.connected,
|
||||
publicKey: adapter.publicKey,
|
||||
}))
|
||||
}, [adapter])
|
||||
|
||||
// Handle the adapter's disconnect event
|
||||
const handleDisconnect = useCallback(() => {
|
||||
setState((state) => ({
|
||||
...state,
|
||||
connected: adapter.connected,
|
||||
publicKey: null,
|
||||
}))
|
||||
}, [adapter])
|
||||
|
||||
// Handle the adapter's error event, and local errors
|
||||
const handleError = useCallback(
|
||||
(error: WalletError) => {
|
||||
// Call onError unless the window is unloading
|
||||
if (!isUnloading.current) (onError || console.error)(error)
|
||||
return error
|
||||
},
|
||||
[isUnloading, onError]
|
||||
)
|
||||
|
||||
// Setup and teardown event listeners when the adapter changes
|
||||
useEffect(() => {
|
||||
if (adapter) {
|
||||
adapter.on('connect', handleConnect)
|
||||
adapter.on('disconnect', handleDisconnect)
|
||||
adapter.on('error', handleError)
|
||||
return () => {
|
||||
adapter.off('connect', handleConnect)
|
||||
adapter.off('disconnect', handleDisconnect)
|
||||
adapter.off('error', handleError)
|
||||
}
|
||||
}
|
||||
}, [adapter, handleConnect, handleDisconnect, handleError])
|
||||
|
||||
// When the adapter changes, disconnect the old one
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
adapter?.disconnect()
|
||||
}
|
||||
}, [adapter])
|
||||
|
||||
// Connect the adapter to the wallet
|
||||
const connect = useCallback(async () => {
|
||||
if (isConnecting.current || connecting || disconnecting || connected) return
|
||||
if (!adapter) throw handleError(new WalletNotSelectedError())
|
||||
|
||||
if (
|
||||
!(
|
||||
readyState === WalletReadyState.Installed ||
|
||||
readyState === WalletReadyState.Loadable
|
||||
)
|
||||
) {
|
||||
if (typeof window !== 'undefined') {
|
||||
window.open(adapter.url, '_blank')
|
||||
}
|
||||
|
||||
throw handleError(new WalletNotReadyError())
|
||||
}
|
||||
|
||||
isConnecting.current = true
|
||||
setConnecting(true)
|
||||
await adapter.connect()
|
||||
setConnecting(false)
|
||||
isConnecting.current = false
|
||||
}, [
|
||||
isConnecting,
|
||||
connecting,
|
||||
disconnecting,
|
||||
connected,
|
||||
adapter,
|
||||
readyState,
|
||||
handleError,
|
||||
])
|
||||
|
||||
// Disconnect the adapter from the wallet
|
||||
const disconnect = useCallback(async () => {
|
||||
if (isDisconnecting.current || disconnecting) return
|
||||
if (!adapter) return
|
||||
|
||||
isDisconnecting.current = true
|
||||
setDisconnecting(true)
|
||||
try {
|
||||
await adapter.disconnect()
|
||||
} catch (error: any) {
|
||||
setDisconnecting(false)
|
||||
isDisconnecting.current = false
|
||||
throw error
|
||||
}
|
||||
}, [isDisconnecting, disconnecting, adapter])
|
||||
|
||||
// Send a transaction using the provided connection
|
||||
const sendTransaction = useCallback(
|
||||
async (
|
||||
transaction: Transaction,
|
||||
connection: Connection,
|
||||
options?: SendTransactionOptions
|
||||
) => {
|
||||
if (!adapter) throw handleError(new WalletNotSelectedError())
|
||||
if (!connected) throw handleError(new WalletNotConnectedError())
|
||||
return await adapter.sendTransaction(transaction, connection, options)
|
||||
},
|
||||
[adapter, handleError, connected]
|
||||
)
|
||||
|
||||
// Sign a transaction if the wallet supports it
|
||||
const signTransaction = useMemo(
|
||||
() =>
|
||||
adapter && 'signTransaction' in adapter
|
||||
? async (transaction: Transaction): Promise<Transaction> => {
|
||||
if (!connected) throw handleError(new WalletNotConnectedError())
|
||||
return await adapter.signTransaction(transaction)
|
||||
}
|
||||
: undefined,
|
||||
[adapter, handleError, connected]
|
||||
)
|
||||
|
||||
// Sign multiple transactions if the wallet supports it
|
||||
const signAllTransactions = useMemo(
|
||||
() =>
|
||||
adapter && 'signAllTransactions' in adapter
|
||||
? async (transactions: Transaction[]): Promise<Transaction[]> => {
|
||||
if (!connected) throw handleError(new WalletNotConnectedError())
|
||||
return await adapter.signAllTransactions(transactions)
|
||||
}
|
||||
: undefined,
|
||||
[adapter, handleError, connected]
|
||||
)
|
||||
|
||||
// Sign an arbitrary message if the wallet supports it
|
||||
const signMessage = useMemo(
|
||||
() =>
|
||||
adapter && 'signMessage' in adapter
|
||||
? async (message: Uint8Array): Promise<Uint8Array> => {
|
||||
if (!connected) throw handleError(new WalletNotConnectedError())
|
||||
return await adapter.signMessage(message)
|
||||
}
|
||||
: undefined,
|
||||
[adapter, handleError, connected]
|
||||
)
|
||||
|
||||
return (
|
||||
<WalletContext.Provider
|
||||
value={{
|
||||
autoConnect,
|
||||
wallets,
|
||||
wallet,
|
||||
publicKey,
|
||||
connected,
|
||||
connecting,
|
||||
disconnecting,
|
||||
select: setName,
|
||||
connect,
|
||||
disconnect,
|
||||
sendTransaction,
|
||||
signTransaction,
|
||||
signAllTransactions,
|
||||
signMessage,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</WalletContext.Provider>
|
||||
)
|
||||
}
|
|
@ -0,0 +1,2 @@
|
|||
export * from './WalletListener'
|
||||
export * from './WalletProvider'
|
|
@ -1,16 +1,13 @@
|
|||
import { Fragment } from 'react'
|
||||
import React, { Fragment } from 'react'
|
||||
import { Menu, Transition } from '@headlessui/react'
|
||||
import { ChevronDownIcon } from '@heroicons/react/solid'
|
||||
import useMangoStore from '../stores/useMangoStore'
|
||||
import { WALLET_PROVIDERS } from '../utils/wallet-adapters'
|
||||
import { useWallet, Wallet } from '@solana/wallet-adapter-react'
|
||||
|
||||
export default function WalletSelect() {
|
||||
const setMangoStore = useMangoStore((s) => s.set)
|
||||
export const WalletSelect: React.FC<{ wallets: Wallet[] }> = ({ wallets }) => {
|
||||
const { select } = useWallet()
|
||||
|
||||
const handleSelectProvider = (url) => {
|
||||
setMangoStore((state) => {
|
||||
state.wallet.providerUrl = url
|
||||
})
|
||||
if (!wallets?.length) {
|
||||
return null
|
||||
}
|
||||
|
||||
return (
|
||||
|
@ -38,15 +35,21 @@ export default function WalletSelect() {
|
|||
leaveTo="opacity-0"
|
||||
>
|
||||
<Menu.Items className="absolute right-0 z-20 w-44 rounded-b-md bg-th-bkg-3 px-4 py-2.5 outline-none">
|
||||
{WALLET_PROVIDERS.map(({ name, url, icon }) => (
|
||||
<Menu.Item key={name}>
|
||||
{wallets?.map((wallet, index) => (
|
||||
<Menu.Item key={index}>
|
||||
<button
|
||||
className="flex w-full flex-row items-center justify-between rounded-none py-1.5 font-normal hover:cursor-pointer hover:text-th-primary focus:outline-none"
|
||||
onClick={() => handleSelectProvider(url)}
|
||||
onClick={() => {
|
||||
select(wallet.adapter.name)
|
||||
}}
|
||||
>
|
||||
<div className="flex items-center">
|
||||
<img src={icon} className="mr-2 h-4 w-4" />
|
||||
{name}
|
||||
<img
|
||||
src={wallet.adapter.icon}
|
||||
className="mr-2 h-4 w-4"
|
||||
alt={`${wallet.adapter.name} icon`}
|
||||
/>
|
||||
{wallet.adapter.name}
|
||||
</div>
|
||||
</button>
|
||||
</Menu.Item>
|
||||
|
|
|
@ -23,6 +23,7 @@ import {
|
|||
import { notify } from '../utils/notifications'
|
||||
import { useTranslation } from 'next-i18next'
|
||||
import { ExpandableRow } from './TableElements'
|
||||
import { useWallet } from '@solana/wallet-adapter-react'
|
||||
|
||||
interface WithdrawModalProps {
|
||||
onClose: () => void
|
||||
|
@ -50,7 +51,7 @@ const WithdrawModal: FunctionComponent<WithdrawModalProps> = ({
|
|||
const [includeBorrow, setIncludeBorrow] = useState(borrow)
|
||||
const [simulation, setSimulation] = useState(null)
|
||||
const [showSimulation, setShowSimulation] = useState(false)
|
||||
|
||||
const { wallet } = useWallet()
|
||||
const actions = useMangoStore((s) => s.actions)
|
||||
const mangoGroup = useMangoStore((s) => s.selectedMangoGroup.current)
|
||||
const mangoAccount = useMangoStore((s) => s.selectedMangoAccount.current)
|
||||
|
@ -161,6 +162,7 @@ const WithdrawModal: FunctionComponent<WithdrawModalProps> = ({
|
|||
amount: Number(inputAmount),
|
||||
token: mangoGroup.tokens[tokenIndex].mint,
|
||||
allowBorrow: includeBorrow,
|
||||
wallet,
|
||||
})
|
||||
.then((txid: string) => {
|
||||
setSubmitting(false)
|
||||
|
|
|
@ -6,6 +6,7 @@ import Loading from './Loading'
|
|||
import Modal from './Modal'
|
||||
import { msrmMints } from '@blockworks-foundation/mango-client'
|
||||
import { useTranslation } from 'next-i18next'
|
||||
import { useWallet } from '@solana/wallet-adapter-react'
|
||||
|
||||
const WithdrawMsrmModal = ({ onClose, isOpen }) => {
|
||||
const { t } = useTranslation('common')
|
||||
|
@ -13,8 +14,8 @@ const WithdrawMsrmModal = ({ onClose, isOpen }) => {
|
|||
const actions = useMangoStore((s) => s.actions)
|
||||
const mangoAccount = useMangoStore((s) => s.selectedMangoAccount.current)
|
||||
const mangoGroup = useMangoStore((s) => s.selectedMangoGroup.current)
|
||||
const wallet = useMangoStore((s) => s.wallet.current)
|
||||
const walletTokens = useMangoStore((s) => s.wallet.tokens)
|
||||
const { wallet } = useWallet()
|
||||
const cluster = useMangoStore.getState().connection.cluster
|
||||
|
||||
const handleMsrmWithdraw = async () => {
|
||||
|
@ -27,7 +28,7 @@ const WithdrawMsrmModal = ({ onClose, isOpen }) => {
|
|||
const txid = await mangoClient.withdrawMsrm(
|
||||
mangoGroup,
|
||||
mangoAccount,
|
||||
wallet,
|
||||
wallet?.adapter,
|
||||
ownerMsrmAccount.account.publicKey,
|
||||
1
|
||||
)
|
||||
|
|
|
@ -20,8 +20,8 @@ import { Table, Td, Th, TrBody, TrHead } from '../TableElements'
|
|||
import { ExpandableRow } from '../TableElements'
|
||||
import MobileTableHeader from '../mobile/MobileTableHeader'
|
||||
import { useTranslation } from 'next-i18next'
|
||||
import { walletSelector } from '../../stores/selectors'
|
||||
import Tooltip from '../Tooltip'
|
||||
import { useWallet } from '@solana/wallet-adapter-react'
|
||||
|
||||
export default function AccountBorrows() {
|
||||
const { t } = useTranslation('common')
|
||||
|
@ -30,11 +30,10 @@ export default function AccountBorrows() {
|
|||
const mangoCache = useMangoStore((s) => s.selectedMangoGroup.cache)
|
||||
const mangoConfig = useMangoStore((s) => s.selectedMangoGroup.config)
|
||||
const mangoAccount = useMangoStore((s) => s.selectedMangoAccount.current)
|
||||
const wallet = useMangoStore(walletSelector)
|
||||
const { publicKey, connected } = useWallet()
|
||||
const loadingMangoAccount = useMangoStore(
|
||||
(s) => s.selectedMangoAccount.initialLoad
|
||||
)
|
||||
const connected = useMangoStore((s) => s.wallet.connected)
|
||||
|
||||
const [borrowSymbol, setBorrowSymbol] = useState('')
|
||||
const [depositToSettle, setDepositToSettle] = useState(null)
|
||||
|
@ -42,7 +41,7 @@ export default function AccountBorrows() {
|
|||
const [showDepositModal, setShowDepositModal] = useState(false)
|
||||
const { width } = useViewport()
|
||||
const isMobile = width ? width < breakpoints.sm : false
|
||||
const canWithdraw = mangoAccount?.owner.equals(wallet.publicKey)
|
||||
const canWithdraw = publicKey && mangoAccount?.owner.equals(publicKey)
|
||||
|
||||
const handleCloseWithdraw = useCallback(() => {
|
||||
setShowBorrowModal(false)
|
||||
|
|
|
@ -1,7 +1,11 @@
|
|||
import { useState, useEffect, useMemo } from 'react'
|
||||
import { useTranslation } from 'next-i18next'
|
||||
import { ArrowSmDownIcon, ExternalLinkIcon } from '@heroicons/react/outline'
|
||||
import { SaveIcon } from '@heroicons/react/outline'
|
||||
import {
|
||||
ArrowSmDownIcon,
|
||||
ExternalLinkIcon,
|
||||
InformationCircleIcon,
|
||||
SaveIcon,
|
||||
} from '@heroicons/react/outline'
|
||||
import {
|
||||
getMarketByBaseSymbolAndKind,
|
||||
PerpMarket,
|
||||
|
@ -20,13 +24,10 @@ import {
|
|||
import { LinkButton } from '../Button'
|
||||
import { useSortableData } from '../../hooks/useSortableData'
|
||||
import { formatUsdValue } from '../../utils'
|
||||
import Tooltip from '../Tooltip'
|
||||
import { exportDataToCSV } from '../../utils/export'
|
||||
import { notify } from '../../utils/notifications'
|
||||
import Button from '../Button'
|
||||
import Loading from '../Loading'
|
||||
import { fetchHourlyPerformanceStats } from './AccountOverview'
|
||||
import Tooltip from '../Tooltip'
|
||||
import { InformationCircleIcon } from '@heroicons/react/outline'
|
||||
|
||||
const historyViews = [
|
||||
{ label: 'Trades', key: 'Trades' },
|
||||
|
@ -39,9 +40,6 @@ export default function AccountHistory() {
|
|||
const { t } = useTranslation('common')
|
||||
const [view, setView] = useState('Trades')
|
||||
const [history, setHistory] = useState(null)
|
||||
const [loadExportData, setLoadExportData] = useState(false)
|
||||
|
||||
const wallet = useMangoStore((s) => s.wallet.current)
|
||||
const mangoAccount = useMangoStore((s) => s.selectedMangoAccount.current)
|
||||
|
||||
const mangoAccountPk = useMemo(() => {
|
||||
|
@ -64,68 +62,6 @@ export default function AccountHistory() {
|
|||
}
|
||||
}, [mangoAccountPk])
|
||||
|
||||
const exportPerformanceDataToCSV = async () => {
|
||||
setLoadExportData(true)
|
||||
const exportData = await fetchHourlyPerformanceStats(
|
||||
mangoAccount.publicKey.toString(),
|
||||
10000
|
||||
)
|
||||
const dataToExport = exportData.map((row) => {
|
||||
const timestamp = new Date(row.time)
|
||||
return {
|
||||
timestamp: `${timestamp.toLocaleDateString()} ${timestamp.toLocaleTimeString()}`,
|
||||
account_equity: row.account_equity,
|
||||
pnl: row.pnl,
|
||||
}
|
||||
})
|
||||
|
||||
const title = `${
|
||||
mangoAccount.name || mangoAccount.publicKey
|
||||
}-Performance-${new Date().toLocaleDateString()}`
|
||||
const headers = ['Timestamp', 'Account Equity', 'PNL']
|
||||
|
||||
exportDataToCSV(dataToExport, title, headers, t)
|
||||
setLoadExportData(false)
|
||||
}
|
||||
|
||||
const exportHistoryToCSV = () => {
|
||||
const dataToExport = history
|
||||
.filter((val) => val.activity_type == view)
|
||||
.map((row) => {
|
||||
row = row.activity_details
|
||||
const timestamp = new Date(row.block_datetime)
|
||||
|
||||
return {
|
||||
date: `${timestamp.toLocaleDateString()} ${timestamp.toLocaleTimeString()}`,
|
||||
asset: row.symbol,
|
||||
quantity: row.quantity,
|
||||
value: row.usd_equivalent,
|
||||
}
|
||||
})
|
||||
const headers = ['Timestamp', 'Asset', 'Quantity', 'Value']
|
||||
|
||||
if (dataToExport.length == 0) {
|
||||
notify({
|
||||
title: t('export-data-empty'),
|
||||
description: '',
|
||||
type: 'info',
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
const tab = historyViews.filter((v) => v.key == view)[0].label
|
||||
const title = `${
|
||||
mangoAccount.name || mangoAccount.publicKey
|
||||
}-${tab}-${new Date().toLocaleDateString()}`
|
||||
|
||||
exportDataToCSV(dataToExport, title, headers, t)
|
||||
}
|
||||
|
||||
const canWithdraw =
|
||||
mangoAccount && wallet?.publicKey
|
||||
? mangoAccount.owner.equals(wallet.publicKey)
|
||||
: false
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="mb-4 flex rounded-md bg-th-bkg-3 px-3 py-2 md:mb-6 md:px-4">
|
||||
|
@ -147,70 +83,6 @@ export default function AccountHistory() {
|
|||
</div>
|
||||
))}
|
||||
</div>
|
||||
<div className="flex flex-col pb-6 sm:flex-row sm:items-end sm:justify-between">
|
||||
<div className="pb-4 sm:pb-0">
|
||||
<h2 className="mb-1">{t(`${view.toLowerCase()}-history`)}</h2>
|
||||
<div className="mr-4 text-xs text-th-fgd-3">
|
||||
{t('delay-displaying-recent')} {t('use-explorer-one')}
|
||||
<a
|
||||
href={`https://explorer.solana.com/address/${mangoAccountPk}`}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
{t('use-explorer-two')}
|
||||
</a>
|
||||
{t('use-explorer-three')}
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex space-x-3">
|
||||
{view === 'Trades' && canWithdraw ? (
|
||||
<Button
|
||||
className={`flex h-8 items-center justify-center whitespace-nowrap pt-0 pb-0 pl-3 pr-3 text-xs`}
|
||||
onClick={exportPerformanceDataToCSV}
|
||||
>
|
||||
{loadExportData ? (
|
||||
<Loading />
|
||||
) : (
|
||||
<div className={`flex items-center`}>
|
||||
<SaveIcon className={`mr-1.5 h-4 w-4`} />
|
||||
{t('export-pnl-csv')}
|
||||
</div>
|
||||
)}
|
||||
</Button>
|
||||
) : null}
|
||||
{view !== 'Trades' ? (
|
||||
<Button
|
||||
className={`flex h-8 items-center justify-center whitespace-nowrap pt-0 pb-0 pl-3 pr-3 text-xs`}
|
||||
onClick={exportHistoryToCSV}
|
||||
>
|
||||
<div className={`flex items-center`}>
|
||||
<SaveIcon className={`mr-1.5 h-4 w-4`} />
|
||||
{t('export-data')}
|
||||
</div>
|
||||
</Button>
|
||||
) : canWithdraw ? (
|
||||
<div className={`flex items-center`}>
|
||||
<a
|
||||
className={`default-transition flex h-8 items-center justify-center whitespace-nowrap rounded-full bg-th-bkg-button pt-0 pb-0 pl-3 pr-3 text-xs font-bold text-th-fgd-1 hover:text-th-fgd-1 hover:brightness-[1.1]`}
|
||||
href={`https://event-history-api.herokuapp.com/all_trades_csv?mango_account=${mangoAccountPk}&open_orders=${mangoAccount.spotOpenOrders
|
||||
.filter(
|
||||
(e) => e.toString() !== '11111111111111111111111111111111'
|
||||
)
|
||||
.join(',')}`}
|
||||
download
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<SaveIcon className={`mr-1.5 h-4 w-4`} />
|
||||
Export Trades CSV
|
||||
</a>
|
||||
<Tooltip content={t('trade-export-disclaimer')}>
|
||||
<InformationCircleIcon className="ml-1.5 h-5 w-5 cursor-help text-th-fgd-3" />
|
||||
</Tooltip>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
<ViewContent view={view} history={history} />
|
||||
</>
|
||||
)
|
||||
|
@ -219,7 +91,7 @@ export default function AccountHistory() {
|
|||
const ViewContent = ({ view, history }) => {
|
||||
switch (view) {
|
||||
case 'Trades':
|
||||
return <TradeHistoryTable />
|
||||
return <TradeHistoryTable showExportPnl />
|
||||
case 'Deposit':
|
||||
return <HistoryTable history={history} view={view} />
|
||||
case 'Withdraw':
|
||||
|
@ -227,7 +99,7 @@ const ViewContent = ({ view, history }) => {
|
|||
case 'Liquidation':
|
||||
return <LiquidationHistoryTable history={history} view={view} />
|
||||
default:
|
||||
return <TradeHistoryTable />
|
||||
return <TradeHistoryTable showExportPnl />
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -291,6 +163,7 @@ const parseActivityDetails = (activity_details, activity_type, perpMarket) => {
|
|||
|
||||
const LiquidationHistoryTable = ({ history, view }) => {
|
||||
const { t } = useTranslation('common')
|
||||
const mangoAccount = useMangoStore((s) => s.selectedMangoAccount.current)
|
||||
const markets = useMangoStore((s) => s.selectedMangoGroup.markets)
|
||||
const groupConfig = useMangoStore((s) => s.selectedMangoGroup.config)
|
||||
const filteredHistory = useMemo(() => {
|
||||
|
@ -300,8 +173,75 @@ const LiquidationHistoryTable = ({ history, view }) => {
|
|||
}, [history, view])
|
||||
const { items, requestSort, sortConfig } = useSortableData(filteredHistory)
|
||||
|
||||
const exportHistoryToCSV = () => {
|
||||
const dataToExport = history
|
||||
.filter((val) => val.activity_type == view)
|
||||
.map((row) => {
|
||||
row = row.activity_details
|
||||
const timestamp = new Date(row.block_datetime)
|
||||
|
||||
return {
|
||||
date: `${timestamp.toLocaleDateString()} ${timestamp.toLocaleTimeString()}`,
|
||||
asset: row.symbol,
|
||||
quantity: row.quantity,
|
||||
value: row.usd_equivalent,
|
||||
}
|
||||
})
|
||||
const headers = ['Timestamp', 'Asset', 'Quantity', 'Value']
|
||||
|
||||
if (dataToExport.length == 0) {
|
||||
notify({
|
||||
title: t('export-data-empty'),
|
||||
description: '',
|
||||
type: 'info',
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
const tab = historyViews.filter((v) => v.key == view)[0].label
|
||||
const title = `${
|
||||
mangoAccount.name || mangoAccount.publicKey
|
||||
}-${tab}-${new Date().toLocaleDateString()}`
|
||||
|
||||
exportDataToCSV(dataToExport, title, headers, t)
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="flex items-center justify-between pb-3">
|
||||
<div className="flex items-center">
|
||||
<h4 className="mb-0 text-th-fgd-1">
|
||||
{filteredHistory.length}{' '}
|
||||
{filteredHistory.length === 1 ? view : `${view}s`}
|
||||
</h4>
|
||||
<Tooltip
|
||||
content={
|
||||
<div className="mr-4 text-xs text-th-fgd-3">
|
||||
{t('delay-displaying-recent')} {t('use-explorer-one')}
|
||||
<a
|
||||
href={`https://explorer.solana.com/address/${mangoAccount.publicKey.toString()}`}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
{t('use-explorer-two')}
|
||||
</a>
|
||||
{t('use-explorer-three')}
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<InformationCircleIcon className="ml-1.5 h-5 w-5 cursor-pointer text-th-fgd-3" />
|
||||
</Tooltip>
|
||||
</div>
|
||||
<Button
|
||||
className={`flex h-8 items-center justify-center whitespace-nowrap pt-0 pb-0 pl-3 pr-3 text-xs`}
|
||||
onClick={exportHistoryToCSV}
|
||||
>
|
||||
<div className={`flex items-center`}>
|
||||
<SaveIcon className={`mr-1.5 h-4 w-4`} />
|
||||
{t('export-data')}
|
||||
</div>
|
||||
</Button>
|
||||
</div>
|
||||
{items.length ? (
|
||||
<>
|
||||
<Table>
|
||||
|
@ -312,7 +252,7 @@ const LiquidationHistoryTable = ({ history, view }) => {
|
|||
className="flex items-center font-normal no-underline"
|
||||
onClick={() => requestSort('block_datetime')}
|
||||
>
|
||||
{t('date')}
|
||||
<span className="font-normal">{t('date')}</span>
|
||||
<ArrowSmDownIcon
|
||||
className={`default-transition ml-1 h-4 w-4 flex-shrink-0 ${
|
||||
sortConfig?.key === 'block_datetime'
|
||||
|
@ -330,7 +270,7 @@ const LiquidationHistoryTable = ({ history, view }) => {
|
|||
className="flex items-center font-normal no-underline"
|
||||
onClick={() => requestSort('asset_amount')}
|
||||
>
|
||||
Asset Lost
|
||||
<span className="font-normal">Asset Lost</span>
|
||||
<ArrowSmDownIcon
|
||||
className={`default-transition ml-1 h-4 w-4 flex-shrink-0 ${
|
||||
sortConfig?.key === 'asset_amount'
|
||||
|
@ -347,7 +287,7 @@ const LiquidationHistoryTable = ({ history, view }) => {
|
|||
className="flex items-center font-normal no-underline"
|
||||
onClick={() => requestSort('asset_price')}
|
||||
>
|
||||
Price
|
||||
<span className="font-normal">Price</span>
|
||||
<ArrowSmDownIcon
|
||||
className={`default-transition ml-1 h-4 w-4 flex-shrink-0 ${
|
||||
sortConfig?.key === 'asset_price'
|
||||
|
@ -365,7 +305,7 @@ const LiquidationHistoryTable = ({ history, view }) => {
|
|||
className="flex items-center font-normal no-underline"
|
||||
onClick={() => requestSort('liab_amount')}
|
||||
>
|
||||
Asset Gained
|
||||
<span className="font-normal">Asset Gained</span>
|
||||
<ArrowSmDownIcon
|
||||
className={`default-transition ml-1 h-4 w-4 flex-shrink-0 ${
|
||||
sortConfig?.key === 'liab_amount'
|
||||
|
@ -382,7 +322,7 @@ const LiquidationHistoryTable = ({ history, view }) => {
|
|||
className="flex items-center font-normal no-underline"
|
||||
onClick={() => requestSort('liab_price')}
|
||||
>
|
||||
Price
|
||||
<span className="font-normal">Price</span>
|
||||
<ArrowSmDownIcon
|
||||
className={`default-transition ml-1 h-4 w-4 flex-shrink-0 ${
|
||||
sortConfig?.key === 'liab_price'
|
||||
|
@ -487,6 +427,7 @@ const LiquidationHistoryTable = ({ history, view }) => {
|
|||
|
||||
const HistoryTable = ({ history, view }) => {
|
||||
const { t } = useTranslation('common')
|
||||
const mangoAccount = useMangoStore((s) => s.selectedMangoAccount.current)
|
||||
const filteredHistory = useMemo(() => {
|
||||
return history?.length
|
||||
? history
|
||||
|
@ -496,8 +437,81 @@ const HistoryTable = ({ history, view }) => {
|
|||
}, [history, view])
|
||||
const { items, requestSort, sortConfig } = useSortableData(filteredHistory)
|
||||
|
||||
const exportHistoryToCSV = () => {
|
||||
const dataToExport = history
|
||||
.filter((val) => val.activity_type == view)
|
||||
.map((row) => {
|
||||
row = row.activity_details
|
||||
const timestamp = new Date(row.block_datetime)
|
||||
|
||||
return {
|
||||
date: `${timestamp.toLocaleDateString()} ${timestamp.toLocaleTimeString()}`,
|
||||
asset: row.symbol,
|
||||
quantity: row.quantity,
|
||||
value: row.usd_equivalent,
|
||||
}
|
||||
})
|
||||
const headers = ['Timestamp', 'Asset', 'Quantity', 'Value']
|
||||
|
||||
if (dataToExport.length == 0) {
|
||||
notify({
|
||||
title: t('export-data-empty'),
|
||||
description: '',
|
||||
type: 'info',
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
const tab = historyViews.filter((v) => v.key == view)[0].label
|
||||
const title = `${
|
||||
mangoAccount.name || mangoAccount.publicKey
|
||||
}-${tab}-${new Date().toLocaleDateString()}`
|
||||
|
||||
exportDataToCSV(dataToExport, title, headers, t)
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="flex items-center justify-between pb-3">
|
||||
<div className="flex items-center">
|
||||
<h4 className="mb-0 text-th-fgd-1">
|
||||
{filteredHistory.length}{' '}
|
||||
{filteredHistory.length === 1
|
||||
? view === 'Withdraw'
|
||||
? 'Withdrawal'
|
||||
: view
|
||||
: view === 'Withdraw'
|
||||
? 'Withdrawals'
|
||||
: `${view}s`}
|
||||
</h4>
|
||||
<Tooltip
|
||||
content={
|
||||
<div className="mr-4 text-xs text-th-fgd-3">
|
||||
{t('delay-displaying-recent')} {t('use-explorer-one')}
|
||||
<a
|
||||
href={`https://explorer.solana.com/address/${mangoAccount.publicKey.toString()}`}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
{t('use-explorer-two')}
|
||||
</a>
|
||||
{t('use-explorer-three')}
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<InformationCircleIcon className="ml-1.5 h-5 w-5 cursor-pointer text-th-fgd-3" />
|
||||
</Tooltip>
|
||||
</div>
|
||||
<Button
|
||||
className={`flex h-8 items-center justify-center whitespace-nowrap pt-0 pb-0 pl-3 pr-3 text-xs`}
|
||||
onClick={exportHistoryToCSV}
|
||||
>
|
||||
<div className={`flex items-center`}>
|
||||
<SaveIcon className={`mr-1.5 h-4 w-4`} />
|
||||
{t('export-data')}
|
||||
</div>
|
||||
</Button>
|
||||
</div>
|
||||
{items.length ? (
|
||||
<>
|
||||
<Table>
|
||||
|
@ -508,7 +522,7 @@ const HistoryTable = ({ history, view }) => {
|
|||
className="flex items-center font-normal no-underline"
|
||||
onClick={() => requestSort('block_datetime')}
|
||||
>
|
||||
{t('date')}
|
||||
<span className="font-normal">{t('date')}</span>
|
||||
<ArrowSmDownIcon
|
||||
className={`default-transition ml-1 h-4 w-4 flex-shrink-0 ${
|
||||
sortConfig?.key === 'block_datetime'
|
||||
|
@ -525,7 +539,7 @@ const HistoryTable = ({ history, view }) => {
|
|||
className="flex items-center font-normal no-underline"
|
||||
onClick={() => requestSort('symbol')}
|
||||
>
|
||||
{t('asset')}
|
||||
<span className="font-normal">{t('asset')}</span>
|
||||
<ArrowSmDownIcon
|
||||
className={`default-transition ml-1 h-4 w-4 flex-shrink-0 ${
|
||||
sortConfig?.key === 'symbol'
|
||||
|
@ -542,7 +556,7 @@ const HistoryTable = ({ history, view }) => {
|
|||
className="flex items-center font-normal no-underline"
|
||||
onClick={() => requestSort('quantity')}
|
||||
>
|
||||
{t('quantity')}
|
||||
<span className="font-normal">{t('quantity')}</span>
|
||||
<ArrowSmDownIcon
|
||||
className={`default-transition ml-1 h-4 w-4 flex-shrink-0 ${
|
||||
sortConfig?.key === 'quantity'
|
||||
|
@ -559,7 +573,7 @@ const HistoryTable = ({ history, view }) => {
|
|||
className="flex items-center font-normal no-underline"
|
||||
onClick={() => requestSort('usd_equivalent')}
|
||||
>
|
||||
{t('value')}
|
||||
<span className="font-normal">{t('value')}</span>
|
||||
<ArrowSmDownIcon
|
||||
className={`default-transition ml-1 h-4 w-4 flex-shrink-0 ${
|
||||
sortConfig?.key === 'usd_equivalent'
|
||||
|
|
|
@ -35,14 +35,15 @@ export const fetchHourlyPerformanceStats = async (
|
|||
.format('YYYY-MM-DD')}`
|
||||
)
|
||||
const parsedResponse = await response.json()
|
||||
const entries: any = Object.entries(parsedResponse)
|
||||
const entries: any = Object.entries(parsedResponse).sort((a, b) =>
|
||||
b[0].localeCompare(a[0])
|
||||
)
|
||||
|
||||
const stats = entries
|
||||
.map(([key, value]) => {
|
||||
return { ...value, time: key }
|
||||
})
|
||||
.filter((x) => x)
|
||||
.reverse()
|
||||
|
||||
return stats
|
||||
}
|
||||
|
@ -183,7 +184,7 @@ export default function AccountOverview() {
|
|||
<h2 className="mb-4">{t('assets-liabilities')}</h2>
|
||||
|
||||
<div className="grid grid-flow-col grid-cols-1 grid-rows-2 pb-8 md:grid-cols-2 md:grid-rows-1 md:gap-4 md:pb-12">
|
||||
<div className="rounded-md border-t border-th-bkg-4 p-3 sm:rounded-lg sm:p-4 md:border-b">
|
||||
<div className="border-t border-th-bkg-4 p-3 sm:p-4 md:border-b">
|
||||
<div className="pb-0.5 text-th-fgd-3">{t('total-assets')}</div>
|
||||
<div className="flex items-center">
|
||||
<div className="text-xl font-bold text-th-fgd-1 md:text-2xl">
|
||||
|
@ -193,7 +194,7 @@ export default function AccountOverview() {
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="rounded-md border-b border-t border-th-bkg-4 p-3 sm:rounded-lg sm:p-4">
|
||||
<div className="border-b border-t border-th-bkg-4 p-3 sm:p-4">
|
||||
<div className="pb-0.5 text-th-fgd-3">{t('total-liabilities')}</div>
|
||||
<div className="flex items-center">
|
||||
<div className="text-xl font-bold text-th-fgd-1 md:text-2xl">
|
||||
|
|
|
@ -74,14 +74,15 @@ const AccountPerformance = () => {
|
|||
`https://mango-transaction-log.herokuapp.com/v3/stats/account-performance?mango-account=${mangoAccountPk}`
|
||||
)
|
||||
const parsedResponse = await response.json()
|
||||
const entries: any = Object.entries(parsedResponse)
|
||||
const entries: any = Object.entries(parsedResponse).sort((a, b) =>
|
||||
b[0].localeCompare(a[0])
|
||||
)
|
||||
|
||||
const stats = entries
|
||||
.map(([key, value]) => {
|
||||
return { ...value, time: key }
|
||||
})
|
||||
.filter((x) => x)
|
||||
.reverse()
|
||||
|
||||
setLoading(false)
|
||||
setHourlyPerformanceStats(stats)
|
||||
|
|
|
@ -0,0 +1,60 @@
|
|||
import { PublicKey } from '@solana/web3.js'
|
||||
import Button from 'components/Button'
|
||||
import Input from 'components/Input'
|
||||
import { useRouter } from 'next/router'
|
||||
import React, { useState } from 'react'
|
||||
import { ExclamationCircleIcon } from '@heroicons/react/outline'
|
||||
import { useTranslation } from 'next-i18next'
|
||||
|
||||
export const MangoAccountLookup = () => {
|
||||
const { t } = useTranslation('common')
|
||||
const router = useRouter()
|
||||
const [value, setValue] = useState('')
|
||||
const [isInvalid, setIsInvalid] = useState(false)
|
||||
|
||||
const validatePubKey = (key: string) => {
|
||||
try {
|
||||
const pubkey = new PublicKey(key)
|
||||
return PublicKey.isOnCurve(pubkey.toBuffer())
|
||||
} catch (e) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
const onClickSearch = () => {
|
||||
const isValid = validatePubKey(value)
|
||||
|
||||
if (isValid) {
|
||||
const route = `/account?pubkey=${value}`
|
||||
setValue('')
|
||||
router.push(route)
|
||||
} else {
|
||||
setIsInvalid(true)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex flex-col items-center rounded-lg px-4 text-th-fgd-1">
|
||||
<h2 className="mb-1 text-base">{t('mango-account-lookup-title')}</h2>
|
||||
<p className="mb-2 text-center">{t('mango-account-lookup-desc')}</p>
|
||||
<div className="w-[350px] p-1 md:w-[400px]">
|
||||
<Input
|
||||
type="text"
|
||||
error={isInvalid}
|
||||
placeholder="Address"
|
||||
value={value}
|
||||
onChange={(e) => setValue(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
{isInvalid && (
|
||||
<div className="flex items-center pt-1.5 text-th-red">
|
||||
<ExclamationCircleIcon className="mr-1.5 h-4 w-4" />
|
||||
{t('invalid-address')}
|
||||
</div>
|
||||
)}
|
||||
<div className="pt-3 pb-2">
|
||||
<Button onClick={onClickSearch}>{t('view')}</Button>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
|
@ -0,0 +1,81 @@
|
|||
export * from './AccountInfo'
|
||||
export * from './AccountNameModal'
|
||||
export * from './AccountSelect'
|
||||
export * from './AccountsModal'
|
||||
export * from './AlphaModal'
|
||||
export * from './BalancesTable'
|
||||
export * from './Button'
|
||||
export * from './ButtonGroup'
|
||||
export * from './Chart'
|
||||
export * from './Checkbox'
|
||||
export * from './CloseAccountModal'
|
||||
export * from './ConnectWalletButton'
|
||||
export * from './CreateAlertModal'
|
||||
export * from './DayHighLow'
|
||||
export * from './DelegateModal'
|
||||
export * from './DepositModal'
|
||||
export * from './DepositMsrmModal'
|
||||
export * from './DropMenu'
|
||||
export * from './EmptyState'
|
||||
export * from './ErrorBoundary'
|
||||
export * from './FavoritesShortcutBar'
|
||||
export * from './FlipCard'
|
||||
export * from './FloatingElement'
|
||||
export * from './GlobalNotification'
|
||||
export * from './icons'
|
||||
export * from './ImageWithFallback'
|
||||
export * from './InlineNotification'
|
||||
export * from './Input'
|
||||
export * from './IntroTips'
|
||||
export * from './JupiterForm'
|
||||
export * from './LeverageSlider'
|
||||
export * from './Loading'
|
||||
export * from './MangoAccountSelect'
|
||||
export * from './MangoIcon'
|
||||
export * from './ManualRefresh'
|
||||
export * from './MarketBalances'
|
||||
export * from './MarketCloseModal'
|
||||
export * from './MarketDetails'
|
||||
export * from './MarketFee'
|
||||
export * from './MarketMenuItem'
|
||||
export * from './MarketNavItem'
|
||||
export * from './MarketSelect'
|
||||
export * from './MarketsModal'
|
||||
export * from './MenuItem'
|
||||
export * from './Modal'
|
||||
export * from './NavDropMenu'
|
||||
export * from './NewAccount'
|
||||
export * from './Notification'
|
||||
export * from './OpenOrdersTable'
|
||||
export * from './Orderbook'
|
||||
export * from './PageBodyContainer'
|
||||
export * from './Pagination'
|
||||
export * from './PerpPositionsTable'
|
||||
export * from './PerpSideBadge'
|
||||
export * from './PnlText'
|
||||
export * from './RecentMarketTrades'
|
||||
export * from './ResetLayout'
|
||||
export * from './Select'
|
||||
export * from './Settings'
|
||||
export * from './SettingsModal'
|
||||
export * from './SideBadge'
|
||||
export * from './Slider'
|
||||
export * from './styles'
|
||||
export * from './SwapSettingsModal'
|
||||
export * from './SwapTokenInfo'
|
||||
export * from './SwapTokenInsights'
|
||||
export * from './SwapTokenSelect'
|
||||
export * from './Switch'
|
||||
export * from './SwitchMarketDropdown'
|
||||
export * from './TableElements'
|
||||
export * from './Tabs'
|
||||
export * from './Tooltip'
|
||||
export * from './TopBar'
|
||||
export * from './TradeHistoryTable'
|
||||
export * from './TradeNavMenu'
|
||||
export * from './TradePageGrid'
|
||||
export * from './UiLock'
|
||||
export * from './UserInfo'
|
||||
export * from './UserMarketInfo'
|
||||
export * from './WithdrawModal'
|
||||
export * from './WithdrawMsrmModal'
|
|
@ -1,8 +1,24 @@
|
|||
import { ReactNode, useState } from 'react'
|
||||
import Link from 'next/link'
|
||||
import { useRouter } from 'next/router'
|
||||
import { ChartBarIcon, CurrencyDollarIcon } from '@heroicons/react/solid'
|
||||
import {
|
||||
ChartBarIcon,
|
||||
CurrencyDollarIcon,
|
||||
MenuIcon,
|
||||
XIcon,
|
||||
} from '@heroicons/react/solid'
|
||||
import { BtcMonoIcon, TradeIcon } from '../icons'
|
||||
import { useTranslation } from 'next-i18next'
|
||||
import { IconButton } from '../Button'
|
||||
import {
|
||||
CalculatorIcon,
|
||||
CashIcon,
|
||||
ChevronRightIcon,
|
||||
CurrencyDollarIcon as FeesIcon,
|
||||
LightBulbIcon,
|
||||
SwitchHorizontalIcon,
|
||||
UserAddIcon,
|
||||
} from '@heroicons/react/outline'
|
||||
|
||||
const StyledBarItemLabel = ({ children, ...props }) => (
|
||||
<div style={{ fontSize: '0.6rem', lineHeight: 1 }} {...props}>
|
||||
|
@ -13,10 +29,11 @@ const StyledBarItemLabel = ({ children, ...props }) => (
|
|||
const BottomBar = () => {
|
||||
const { t } = useTranslation('common')
|
||||
const { asPath } = useRouter()
|
||||
const [showPanel, setShowPanel] = useState(false)
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="default-transition grid grid-cols-4 grid-rows-1 bg-th-bkg-3 py-2.5">
|
||||
<div className="default-transition grid grid-cols-5 grid-rows-1 bg-th-bkg-3 py-2.5">
|
||||
<Link
|
||||
href={{
|
||||
pathname: '/select',
|
||||
|
@ -69,9 +86,114 @@ const BottomBar = () => {
|
|||
<StyledBarItemLabel>{t('stats')}</StyledBarItemLabel>
|
||||
</div>
|
||||
</Link>
|
||||
<div
|
||||
className={`${
|
||||
showPanel ? 'text-th-primary' : 'text-th-fgd-3'
|
||||
} default-transition col-span-1 flex cursor-pointer flex-col items-center hover:text-th-primary`}
|
||||
onClick={() => setShowPanel(!showPanel)}
|
||||
>
|
||||
<MenuIcon className="mb-1 h-4 w-4" />
|
||||
<StyledBarItemLabel>{t('more')}</StyledBarItemLabel>
|
||||
</div>
|
||||
</div>
|
||||
<MoreMenuPanel showPanel={showPanel} setShowPanel={setShowPanel} />
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default BottomBar
|
||||
|
||||
const MoreMenuPanel = ({
|
||||
showPanel,
|
||||
setShowPanel,
|
||||
}: {
|
||||
showPanel: boolean
|
||||
setShowPanel: (showPanel: boolean) => void
|
||||
}) => {
|
||||
const { t } = useTranslation('common')
|
||||
return (
|
||||
<div
|
||||
className={`fixed bottom-0 z-30 h-96 w-full transform overflow-hidden bg-th-bkg-4 px-4 transition-all duration-700 ease-in-out ${
|
||||
showPanel ? 'translate-y-0' : 'translate-y-full'
|
||||
}`}
|
||||
>
|
||||
<div className="flex justify-end py-4">
|
||||
<IconButton className="" onClick={() => setShowPanel(false)}>
|
||||
<XIcon className="h-5 w-5" />
|
||||
</IconButton>
|
||||
</div>
|
||||
<div
|
||||
className="border-b border-th-fgd-4"
|
||||
onClick={() => setShowPanel(false)}
|
||||
>
|
||||
<MoreMenuItem
|
||||
title={t('borrow')}
|
||||
path="/borrow"
|
||||
icon={<CashIcon className="h-5 w-5" />}
|
||||
/>
|
||||
<MoreMenuItem
|
||||
title={t('calculator')}
|
||||
path="/risk-calculator"
|
||||
icon={<CalculatorIcon className="h-5 w-5" />}
|
||||
/>
|
||||
<MoreMenuItem
|
||||
title={t('swap')}
|
||||
path="/swap"
|
||||
icon={<SwitchHorizontalIcon className="h-5 w-5" />}
|
||||
/>
|
||||
<MoreMenuItem
|
||||
title={t('referrals')}
|
||||
path="/referral"
|
||||
icon={<UserAddIcon className="h-5 w-5" />}
|
||||
/>
|
||||
<MoreMenuItem
|
||||
title={t('fees')}
|
||||
path="/fees"
|
||||
icon={<FeesIcon className="h-5 w-5" />}
|
||||
/>
|
||||
<MoreMenuItem
|
||||
title={t('learn')}
|
||||
path="https://docs.mango.markets/"
|
||||
icon={<LightBulbIcon className="h-5 w-5" />}
|
||||
isExternal
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const MoreMenuItem = ({
|
||||
title,
|
||||
path,
|
||||
icon,
|
||||
isExternal,
|
||||
}: {
|
||||
title: string
|
||||
path: string
|
||||
icon: ReactNode
|
||||
isExternal?: boolean
|
||||
}) =>
|
||||
isExternal ? (
|
||||
<a
|
||||
className="default-transition flex w-full items-center justify-between border-t border-th-fgd-4 px-2 py-3 text-th-fgd-2 hover:text-th-fgd-1"
|
||||
href={path}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<div className="flex items-center">
|
||||
{icon}
|
||||
<span className="ml-1.5">{title}</span>
|
||||
</div>
|
||||
<ChevronRightIcon className="h-5 w-5" />
|
||||
</a>
|
||||
) : (
|
||||
<Link href={path} shallow={true}>
|
||||
<a className="default-transition flex w-full items-center justify-between border-t border-th-fgd-4 px-2 py-3 text-th-fgd-2 hover:text-th-fgd-1">
|
||||
<div className="flex items-center">
|
||||
{icon}
|
||||
<span className="ml-1.5">{title}</span>
|
||||
</div>
|
||||
<ChevronRightIcon className="h-5 w-5" />
|
||||
</a>
|
||||
</Link>
|
||||
)
|
||||
|
|
|
@ -17,6 +17,7 @@ import FloatingElement from '../FloatingElement'
|
|||
import Swipeable from './Swipeable'
|
||||
import { useTranslation } from 'next-i18next'
|
||||
import Link from 'next/link'
|
||||
import { useWallet } from '@solana/wallet-adapter-react'
|
||||
|
||||
const TVChartContainer = dynamic(
|
||||
() => import('../../components/TradingView/index'),
|
||||
|
@ -26,10 +27,10 @@ const TVChartContainer = dynamic(
|
|||
const MobileTradePage = () => {
|
||||
const { t } = useTranslation('common')
|
||||
const [viewIndex, setViewIndex] = useState(0)
|
||||
const { connected } = useWallet()
|
||||
const selectedMarket = useMangoStore((s) => s.selectedMarket.current)
|
||||
const marketConfig = useMangoStore((s) => s.selectedMarket.config)
|
||||
const mangoGroup = useMangoStore((s) => s.selectedMangoGroup.current)
|
||||
const connected = useMangoStore((s) => s.wallet.connected)
|
||||
const groupConfig = useMangoStore((s) => s.selectedMangoGroup.config)
|
||||
const baseSymbol = marketConfig.baseSymbol
|
||||
const isPerpMarket = marketConfig.kind === 'perp'
|
||||
|
|
|
@ -15,29 +15,32 @@ function formatNumberString(x: number, decimals): string {
|
|||
}
|
||||
|
||||
const getAverageStats = (
|
||||
stats,
|
||||
stats: any[],
|
||||
daysAgo: number,
|
||||
symbol: string,
|
||||
type: string
|
||||
) => {
|
||||
const priorDate = new Date(Date.now() - daysAgo * 24 * 60 * 60 * 1000)
|
||||
const selectedStatsData = stats.filter((s) => s.name === symbol)
|
||||
const timeFilteredStats = selectedStatsData.filter(
|
||||
(d) => new Date(d.time).getTime() >= priorDate.getTime()
|
||||
)
|
||||
): string => {
|
||||
if (stats?.length) {
|
||||
const priorDate = new Date(Date.now() - daysAgo * 24 * 60 * 60 * 1000)
|
||||
const selectedStatsData = stats.filter((s) => s.name === symbol)
|
||||
const timeFilteredStats = selectedStatsData.filter(
|
||||
(d) => new Date(d.time).getTime() >= priorDate.getTime()
|
||||
)
|
||||
|
||||
const oldestStat = timeFilteredStats[0]
|
||||
const latestStat = timeFilteredStats[timeFilteredStats.length - 1]
|
||||
const avg =
|
||||
Math.pow(latestStat[type] / oldestStat[type], 365 / daysAgo) * 100 - 100
|
||||
const oldestStat = timeFilteredStats[0]
|
||||
const latestStat = timeFilteredStats[timeFilteredStats.length - 1]
|
||||
const avg =
|
||||
Math.pow(latestStat[type] / oldestStat[type], 365 / daysAgo) * 100 - 100
|
||||
|
||||
priorDate.setHours(priorDate.getHours() + 1)
|
||||
priorDate.setHours(priorDate.getHours() + 1)
|
||||
|
||||
if (new Date(oldestStat.hourly).getDate() > priorDate.getDate()) {
|
||||
return '-'
|
||||
} else {
|
||||
return `${avg.toFixed(4)}%`
|
||||
if (new Date(oldestStat.hourly).getDate() > priorDate.getDate()) {
|
||||
return '-'
|
||||
} else {
|
||||
return `${avg.toFixed(4)}%`
|
||||
}
|
||||
}
|
||||
return '-'
|
||||
}
|
||||
|
||||
export default function StatsTotals({ latestStats, stats }) {
|
||||
|
|
|
@ -42,6 +42,7 @@ import useLocalStorageState, {
|
|||
} from '../../hooks/useLocalStorageState'
|
||||
import InlineNotification from '../InlineNotification'
|
||||
import { DEFAULT_SPOT_MARGIN_KEY } from '../SettingsModal'
|
||||
import { useWallet } from '@solana/wallet-adapter-react'
|
||||
|
||||
const MAX_SLIPPAGE_KEY = 'maxSlippage'
|
||||
|
||||
|
@ -61,7 +62,7 @@ export default function AdvancedTradeForm({
|
|||
const { t } = useTranslation('common')
|
||||
const set = useMangoStore((s) => s.set)
|
||||
const { ipAllowed, spotAllowed } = useIpAddress()
|
||||
const connected = useMangoStore((s) => s.wallet.connected)
|
||||
const { wallet, connected } = useWallet()
|
||||
const actions = useMangoStore((s) => s.actions)
|
||||
const groupConfig = useMangoStore((s) => s.selectedMangoGroup.config)
|
||||
const marketConfig = useMangoStore((s) => s.selectedMarket.config)
|
||||
|
@ -113,10 +114,7 @@ export default function AdvancedTradeForm({
|
|||
|
||||
const isTriggerOrder = TRIGGER_ORDER_TYPES.includes(tradeType)
|
||||
|
||||
// TODO saml - create a tick box on the UI; Only available on perps
|
||||
// eslint-disable-next-line
|
||||
const [postOnlySlide, setPostOnlySlide] = useState(false)
|
||||
|
||||
const [postOnly, setPostOnly] = useState(false)
|
||||
const [ioc, setIoc] = useState(false)
|
||||
|
||||
|
@ -405,8 +403,6 @@ export default function AdvancedTradeForm({
|
|||
}
|
||||
}
|
||||
|
||||
// TODO saml - use
|
||||
// eslint-disable-next-line
|
||||
const postOnlySlideOnChange = (checked) => {
|
||||
if (checked) {
|
||||
setIoc(false)
|
||||
|
@ -414,7 +410,6 @@ export default function AdvancedTradeForm({
|
|||
}
|
||||
setPostOnlySlide(checked)
|
||||
}
|
||||
|
||||
const postOnChange = (checked) => {
|
||||
if (checked) {
|
||||
setIoc(false)
|
||||
|
@ -575,7 +570,6 @@ export default function AdvancedTradeForm({
|
|||
useMangoStore.getState().accountInfos[marketConfig.asksKey.toString()]
|
||||
const bidInfo =
|
||||
useMangoStore.getState().accountInfos[marketConfig.bidsKey.toString()]
|
||||
const wallet = useMangoStore.getState().wallet.current
|
||||
const referrerPk = useMangoStore.getState().referrerPk
|
||||
|
||||
if (!wallet || !mangoGroup || !mangoAccount || !market) return
|
||||
|
@ -618,7 +612,7 @@ export default function AdvancedTradeForm({
|
|||
mangoGroup,
|
||||
mangoAccount,
|
||||
market,
|
||||
wallet,
|
||||
wallet?.adapter,
|
||||
side,
|
||||
orderPrice,
|
||||
baseSize,
|
||||
|
@ -632,7 +626,9 @@ export default function AdvancedTradeForm({
|
|||
let perpOrderPrice: number = orderPrice
|
||||
|
||||
if (isMarketOrder) {
|
||||
if (tradeType === 'Market' && maxSlippage !== undefined) {
|
||||
if (postOnlySlide) {
|
||||
perpOrderType = 'postOnlySlide'
|
||||
} else if (tradeType === 'Market' && maxSlippage !== undefined) {
|
||||
perpOrderType = 'ioc'
|
||||
if (side === 'buy') {
|
||||
perpOrderPrice = markPrice * (1 + parseFloat(maxSlippage))
|
||||
|
@ -652,7 +648,7 @@ export default function AdvancedTradeForm({
|
|||
mangoGroup,
|
||||
mangoAccount,
|
||||
market,
|
||||
wallet,
|
||||
wallet?.adapter,
|
||||
perpOrderType,
|
||||
side,
|
||||
perpOrderPrice,
|
||||
|
@ -667,7 +663,7 @@ export default function AdvancedTradeForm({
|
|||
mangoGroup,
|
||||
mangoAccount,
|
||||
market,
|
||||
wallet,
|
||||
wallet?.adapter,
|
||||
side,
|
||||
perpOrderPrice,
|
||||
baseSize,
|
||||
|
@ -793,15 +789,19 @@ export default function AdvancedTradeForm({
|
|||
min="0"
|
||||
step={tickSize}
|
||||
onChange={(e) => onSetPrice(e.target.value)}
|
||||
value={price}
|
||||
disabled={isMarketOrder}
|
||||
value={postOnlySlide ? '' : price}
|
||||
disabled={isMarketOrder || postOnlySlide}
|
||||
placeholder={tradeType === 'Market' ? markPrice : null}
|
||||
prefix={
|
||||
<img
|
||||
src={`/assets/icons/${groupConfig.quoteSymbol.toLowerCase()}.svg`}
|
||||
width="16"
|
||||
height="16"
|
||||
/>
|
||||
<>
|
||||
{!postOnlySlide && (
|
||||
<img
|
||||
src={`/assets/icons/${groupConfig.quoteSymbol.toLowerCase()}.svg`}
|
||||
width="16"
|
||||
height="16"
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
}
|
||||
/>
|
||||
</>
|
||||
|
@ -908,7 +908,7 @@ export default function AdvancedTradeForm({
|
|||
</div>
|
||||
) : null
|
||||
) : null}
|
||||
<div className="sm:flex">
|
||||
<div className="flex-wrap sm:flex">
|
||||
{isLimitOrder ? (
|
||||
<div className="flex">
|
||||
<div className="mr-4 mt-3">
|
||||
|
@ -951,7 +951,7 @@ export default function AdvancedTradeForm({
|
|||
&& showReduceOnly(perpAccount?.basePosition.toNumber())
|
||||
*/}
|
||||
{marketConfig.kind === 'perp' ? (
|
||||
<div className="mt-3">
|
||||
<div className="mr-4 mt-3">
|
||||
<Tooltip
|
||||
className="hidden md:block"
|
||||
delay={250}
|
||||
|
@ -968,6 +968,24 @@ export default function AdvancedTradeForm({
|
|||
</Tooltip>
|
||||
</div>
|
||||
) : null}
|
||||
{marketConfig.kind === 'perp' ? (
|
||||
<div className="mt-3">
|
||||
<Tooltip
|
||||
className="hidden md:block"
|
||||
delay={250}
|
||||
placement="left"
|
||||
content={t('tooltip-post-and-slide')}
|
||||
>
|
||||
<Checkbox
|
||||
checked={postOnlySlide}
|
||||
onChange={(e) => postOnlySlideOnChange(e.target.checked)}
|
||||
disabled={isTriggerOrder}
|
||||
>
|
||||
Post & Slide
|
||||
</Checkbox>
|
||||
</Tooltip>
|
||||
</div>
|
||||
) : null}
|
||||
{marketConfig.kind === 'spot' ? (
|
||||
<div className="mt-3">
|
||||
<Tooltip
|
||||
|
|
|
@ -23,12 +23,13 @@ import OrderSideTabs from './OrderSideTabs'
|
|||
import Tooltip from '../Tooltip'
|
||||
import EstPriceImpact from './EstPriceImpact'
|
||||
import { useTranslation } from 'next-i18next'
|
||||
import { useWallet } from '@solana/wallet-adapter-react'
|
||||
|
||||
export default function SimpleTradeForm({ initLeverage }) {
|
||||
const { t } = useTranslation('common')
|
||||
const set = useMangoStore((s) => s.set)
|
||||
const { ipAllowed, spotAllowed } = useIpAddress()
|
||||
const connected = useMangoStore((s) => s.wallet.connected)
|
||||
const { wallet, connected } = useWallet()
|
||||
const actions = useMangoStore((s) => s.actions)
|
||||
const groupConfig = useMangoStore((s) => s.selectedMangoGroup.config)
|
||||
const marketConfig = useMangoStore((s) => s.selectedMarket.config)
|
||||
|
@ -277,9 +278,8 @@ export default function SimpleTradeForm({ initLeverage }) {
|
|||
useMangoStore.getState().accountInfos[marketConfig.asksKey.toString()]
|
||||
const bidInfo =
|
||||
useMangoStore.getState().accountInfos[marketConfig.bidsKey.toString()]
|
||||
const wallet = useMangoStore.getState().wallet.current
|
||||
|
||||
if (!wallet || !mangoGroup || !mangoAccount || !market) return
|
||||
if (!wallet?.adapter || !mangoGroup || !mangoAccount || !market) return
|
||||
setSubmitting(true)
|
||||
|
||||
try {
|
||||
|
@ -308,7 +308,7 @@ export default function SimpleTradeForm({ initLeverage }) {
|
|||
mangoAccount,
|
||||
mangoGroup.mangoCache,
|
||||
market,
|
||||
wallet,
|
||||
wallet?.adapter,
|
||||
side,
|
||||
orderPrice,
|
||||
baseSize,
|
||||
|
@ -319,7 +319,7 @@ export default function SimpleTradeForm({ initLeverage }) {
|
|||
mangoGroup,
|
||||
mangoAccount,
|
||||
market,
|
||||
wallet,
|
||||
wallet?.adapter,
|
||||
side,
|
||||
orderPrice,
|
||||
baseSize,
|
||||
|
|
|
@ -11,12 +11,13 @@ import {
|
|||
FlipCardInner,
|
||||
} from '../FlipCard'
|
||||
import FloatingElement from '../FloatingElement'
|
||||
import { useWallet } from '@solana/wallet-adapter-react'
|
||||
|
||||
export default function TradeForm() {
|
||||
const [showAdvancedForm, setShowAdvancedForm] = useState(true)
|
||||
const { connected } = useWallet()
|
||||
const marketConfig = useMangoStore((s) => s.selectedMarket.config)
|
||||
const mangoGroup = useMangoStore((s) => s.selectedMangoGroup.current)
|
||||
const connected = useMangoStore((s) => s.wallet.connected)
|
||||
|
||||
const handleFormChange = () => {
|
||||
setShowAdvancedForm(!showAdvancedForm)
|
||||
|
|
|
@ -0,0 +1,33 @@
|
|||
import { useMemo } from 'react'
|
||||
|
||||
interface Filters {
|
||||
[key: string]: any
|
||||
value?: any
|
||||
}
|
||||
|
||||
export function useFilteredData<T>(items: T[], filters: Filters) {
|
||||
const filteredItems = useMemo(() => {
|
||||
const filtered = items.filter((item) => {
|
||||
let validItem = true
|
||||
|
||||
for (const [key, value] of Object.entries(filters)) {
|
||||
if (Array.isArray(value)) {
|
||||
if (!value.includes(item[key]) && value.length > 0) {
|
||||
validItem = false
|
||||
}
|
||||
} else if (Object.prototype.hasOwnProperty.call(value, 'condition')) {
|
||||
if (!value.condition(item[key])) {
|
||||
validItem = false
|
||||
}
|
||||
} else {
|
||||
if (value !== item[key]) validItem = false
|
||||
}
|
||||
}
|
||||
|
||||
return validItem
|
||||
})
|
||||
return filtered
|
||||
}, [items])
|
||||
|
||||
return filteredItems
|
||||
}
|
|
@ -39,19 +39,23 @@ export const collectPerpPosition = (
|
|||
|
||||
let avgEntryPrice = 0,
|
||||
breakEvenPrice = 0
|
||||
const perpTradeHistory = tradeHistory.filter(
|
||||
(t) => t.marketName === marketConfig.name
|
||||
)
|
||||
try {
|
||||
const perpTradeHistory = tradeHistory.filter(
|
||||
(t) => t.marketName === marketConfig.name
|
||||
)
|
||||
|
||||
avgEntryPrice = perpAccount
|
||||
.getAverageOpenPrice(mangoAccount, perpMarket, perpTradeHistory)
|
||||
.toNumber()
|
||||
} catch (e) {
|
||||
console.error(marketConfig.name, e)
|
||||
}
|
||||
|
||||
try {
|
||||
breakEvenPrice = perpAccount
|
||||
.getBreakEvenPrice(mangoAccount, perpMarket, perpTradeHistory)
|
||||
.toNumber()
|
||||
} catch (e) {
|
||||
// console.error(e)
|
||||
console.error(marketConfig.name, e)
|
||||
}
|
||||
|
||||
const basePosition = perpMarket?.baseLotsToNumber(perpAccount.basePosition)
|
||||
|
@ -91,7 +95,7 @@ const usePerpPositions = () => {
|
|||
const mangoGroup = useMangoStore(mangoGroupSelector)
|
||||
const mangoCache = useMangoStore(mangoCacheSelector)
|
||||
const allMarkets = useMangoStore(marketsSelector)
|
||||
const tradeHistory = useMangoStore.getState().tradeHistory.parsed
|
||||
const tradeHistory = useMangoStore((s) => s.tradeHistory.parsed)
|
||||
|
||||
useEffect(() => {
|
||||
if (
|
||||
|
|
|
@ -1,103 +0,0 @@
|
|||
import { useEffect, useMemo } from 'react'
|
||||
import Wallet from '@project-serum/sol-wallet-adapter'
|
||||
import useLocalStorageState from './useLocalStorageState'
|
||||
import useMangoStore from '../stores/useMangoStore'
|
||||
import { notify } from '../utils/notifications'
|
||||
import { WalletAdapter } from '../@types/types'
|
||||
import { useTranslation } from 'next-i18next'
|
||||
import { DEFAULT_PROVIDER, WALLET_PROVIDERS } from '../utils/wallet-adapters'
|
||||
|
||||
export const PROVIDER_LOCAL_STORAGE_KEY = 'walletProvider-0.1'
|
||||
|
||||
export default function useWallet() {
|
||||
const { t } = useTranslation('common')
|
||||
const setMangoStore = useMangoStore((state) => state.set)
|
||||
const {
|
||||
current: wallet,
|
||||
connected,
|
||||
providerUrl: selectedProviderUrl,
|
||||
} = useMangoStore((state) => state.wallet)
|
||||
const endpoint = useMangoStore((state) => state.connection.endpoint)
|
||||
const actions = useMangoStore((s) => s.actions)
|
||||
const [savedProviderUrl, setSavedProviderUrl] = useLocalStorageState(
|
||||
PROVIDER_LOCAL_STORAGE_KEY,
|
||||
DEFAULT_PROVIDER.url
|
||||
)
|
||||
const provider = useMemo(
|
||||
() => WALLET_PROVIDERS.find(({ url }) => url === savedProviderUrl),
|
||||
[savedProviderUrl]
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
if (selectedProviderUrl) {
|
||||
setSavedProviderUrl(selectedProviderUrl)
|
||||
}
|
||||
}, [selectedProviderUrl])
|
||||
|
||||
useEffect(() => {
|
||||
if (provider) {
|
||||
const updateWallet = () => {
|
||||
// hack to also update wallet synchronously in case it disconnects
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
const wallet = new (provider.adapter || Wallet)(
|
||||
savedProviderUrl,
|
||||
endpoint
|
||||
) as WalletAdapter
|
||||
setMangoStore((state) => {
|
||||
state.wallet.current = wallet
|
||||
})
|
||||
}
|
||||
|
||||
if (document.readyState !== 'complete') {
|
||||
// wait to ensure that browser extensions are loaded
|
||||
const listener = () => {
|
||||
updateWallet()
|
||||
window.removeEventListener('load', listener)
|
||||
}
|
||||
window.addEventListener('load', listener)
|
||||
return () => window.removeEventListener('load', listener)
|
||||
} else {
|
||||
updateWallet()
|
||||
}
|
||||
}
|
||||
}, [provider, savedProviderUrl, endpoint])
|
||||
|
||||
useEffect(() => {
|
||||
if (!wallet) return
|
||||
wallet.on('connect', async () => {
|
||||
setMangoStore((state) => {
|
||||
state.wallet.connected = true
|
||||
})
|
||||
// set connected before fetching data
|
||||
await actions.fetchAllMangoAccounts()
|
||||
actions.fetchProfilePicture()
|
||||
actions.reloadOrders()
|
||||
actions.fetchTradeHistory()
|
||||
actions.fetchWalletTokens()
|
||||
|
||||
// notify({
|
||||
// title: t('wallet-connected'),
|
||||
// description:
|
||||
// t('connected-to') +
|
||||
// wallet.publicKey.toString().substr(0, 5) +
|
||||
// '...' +
|
||||
// wallet.publicKey.toString().substr(-5),
|
||||
// })
|
||||
})
|
||||
wallet.on('disconnect', () => {
|
||||
console.log('disconnecting wallet')
|
||||
setMangoStore((state) => {
|
||||
state.wallet.connected = false
|
||||
state.mangoAccounts = []
|
||||
state.selectedMangoAccount.current = null
|
||||
state.tradeHistory = { spot: [], perp: [], parsed: [] }
|
||||
})
|
||||
notify({
|
||||
type: 'info',
|
||||
title: t('wallet-disconnected'),
|
||||
})
|
||||
})
|
||||
}, [wallet, setMangoStore])
|
||||
|
||||
return { connected, wallet }
|
||||
}
|
|
@ -1,6 +0,0 @@
|
|||
[build]
|
||||
command = "yarn build"
|
||||
publish = ".next"
|
||||
|
||||
[functions]
|
||||
included_files = ["public/locales/*/*.json", "next-i18next.config.js"]
|
|
@ -21,34 +21,29 @@ const moduleExports = {
|
|||
},
|
||||
]
|
||||
},
|
||||
webpack: (config, { isServer }) => {
|
||||
webpack: (config, options) => {
|
||||
// Important: return the modified config
|
||||
if (!isServer) {
|
||||
if (!options.isServer) {
|
||||
config.resolve.fallback.fs = false
|
||||
}
|
||||
|
||||
config.module.rules.push({
|
||||
test: /\.svg?$/,
|
||||
oneOf: [
|
||||
{
|
||||
use: [
|
||||
{
|
||||
loader: '@svgr/webpack',
|
||||
options: {
|
||||
prettier: false,
|
||||
svgo: true,
|
||||
svgoConfig: {
|
||||
plugins: [{ removeViewBox: false }],
|
||||
},
|
||||
titleProp: true,
|
||||
},
|
||||
},
|
||||
],
|
||||
issuer: {
|
||||
and: [/\.(ts|tsx|js|jsx|md|mdx)$/],
|
||||
},
|
||||
},
|
||||
],
|
||||
test: /\.svg$/,
|
||||
use: ['@svgr/webpack'],
|
||||
})
|
||||
|
||||
if (process.env.ANALYZE === 'true') {
|
||||
const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer')
|
||||
config.plugins.push(
|
||||
new BundleAnalyzerPlugin({
|
||||
analyzerMode: 'static',
|
||||
reportFilename: options.isServer
|
||||
? './analyze/server.html'
|
||||
: './analyze/client.html',
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
return config
|
||||
},
|
||||
}
|
||||
|
|
29
package.json
29
package.json
|
@ -13,17 +13,27 @@
|
|||
"format": "prettier --write .",
|
||||
"lint": "eslint . --ext ts --ext tsx --ext js --quiet",
|
||||
"lint-staged": "lint-staged --config lint-staged.js",
|
||||
"test": "jest",
|
||||
"test-all": "yarn lint && yarn type-check && yarn test"
|
||||
"lint-all": "yarn lint && yarn type-check",
|
||||
"analyze": "ANALYZE=true yarn build"
|
||||
},
|
||||
"dependencies": {
|
||||
"@blockworks-foundation/mango-client": "^3.3.25",
|
||||
"@blockworks-foundation/mango-client": "^3.3.27",
|
||||
"@headlessui/react": "^0.0.0-insiders.2dbc38c",
|
||||
"@heroicons/react": "^1.0.0",
|
||||
"@jup-ag/react-hook": "^1.0.0-beta.16",
|
||||
"@next/bundle-analyzer": "^12.1.0",
|
||||
"@project-serum/serum": "0.13.55",
|
||||
"@project-serum/sol-wallet-adapter": "0.2.0",
|
||||
"@solana/web3.js": "^1.31.0",
|
||||
"@solana/wallet-adapter-base": "^0.9.5",
|
||||
"@solana/wallet-adapter-bitpie": "^0.5.3",
|
||||
"@solana/wallet-adapter-glow": "^0.1.1",
|
||||
"@solana/wallet-adapter-huobi": "^0.1.0",
|
||||
"@solana/wallet-adapter-phantom": "^0.9.3",
|
||||
"@solana/wallet-adapter-react": "^0.15.4",
|
||||
"@solana/wallet-adapter-slope": "^0.5.4",
|
||||
"@solana/wallet-adapter-solflare": "^0.6.6",
|
||||
"@solana/wallet-adapter-sollet": "^0.11.1",
|
||||
"@solana/web3.js": "^1.36.0",
|
||||
"@solflare-wallet/pfp": "^0.0.6",
|
||||
"@solflare-wallet/sdk": "^1.0.10",
|
||||
"@tippyjs/react": "^4.2.5",
|
||||
|
@ -31,7 +41,6 @@
|
|||
"bignumber.js": "^9.0.2",
|
||||
"bn.js": "^5.1.0",
|
||||
"bs58": "^4.0.1",
|
||||
"buffer-layout": "^1.2.0",
|
||||
"dayjs": "^1.10.4",
|
||||
"export-to-csv": "^0.2.1",
|
||||
"html2canvas": "^1.4.1",
|
||||
|
@ -45,6 +54,7 @@
|
|||
"rc-slider": "^9.7.5",
|
||||
"react": "^17.0.2",
|
||||
"react-cool-dimensions": "^2.0.7",
|
||||
"react-datepicker": "^4.7.0",
|
||||
"react-dom": "^17.0.2",
|
||||
"react-grid-layout": "^1.3.3",
|
||||
"react-portal": "^4.2.1",
|
||||
|
@ -57,10 +67,8 @@
|
|||
"zustand": "^3.7.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@netlify/plugin-nextjs": "^4.1.0",
|
||||
"@svgr/webpack": "^6.1.2",
|
||||
"@testing-library/react": "^11.2.5",
|
||||
"@types/jest": "^26.0.20",
|
||||
"@types/node": "^14.14.25",
|
||||
"@types/react": "^17.0.1",
|
||||
"@typescript-eslint/eslint-plugin": "^4.33.0",
|
||||
|
@ -72,8 +80,6 @@
|
|||
"eslint-plugin-react-hooks": "^4.3.0",
|
||||
"husky": "^7.0.4",
|
||||
"identity-obj-proxy": "^3.0.0",
|
||||
"jest": "^27.4.7",
|
||||
"jest-watch-typeahead": "^0.6.1",
|
||||
"lint-staged": "^12.3.6",
|
||||
"postcss": "^8.4.7",
|
||||
"prettier": "^2.0.2",
|
||||
|
@ -88,5 +94,10 @@
|
|||
"bn.js": "5.1.3",
|
||||
"@solana/buffer-layout": "3.0.0",
|
||||
"@types/bn.js": "5.1.0"
|
||||
},
|
||||
"nextBundleAnalysis": {
|
||||
"budget": 512000,
|
||||
"budgetPercentIncreaseRed": 20,
|
||||
"showDetails": true
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,9 +2,10 @@ import Head from 'next/head'
|
|||
import { ThemeProvider } from 'next-themes'
|
||||
import '../node_modules/react-grid-layout/css/styles.css'
|
||||
import '../node_modules/react-resizable/css/styles.css'
|
||||
import '../node_modules/react-datepicker/dist/react-datepicker.css'
|
||||
import 'intro.js/introjs.css'
|
||||
import '../styles/index.css'
|
||||
import useWallet from '../hooks/useWallet'
|
||||
import '../styles/datepicker.css'
|
||||
import useHydrateStore from '../hooks/useHydrateStore'
|
||||
import Notifications from '../components/Notification'
|
||||
import useMangoStore from '../stores/useMangoStore'
|
||||
|
@ -18,7 +19,7 @@ import ErrorBoundary from '../components/ErrorBoundary'
|
|||
import GlobalNotification from '../components/GlobalNotification'
|
||||
import { useOpenOrders } from '../hooks/useOpenOrders'
|
||||
import usePerpPositions from '../hooks/usePerpPositions'
|
||||
import { useEffect } from 'react'
|
||||
import { useEffect, useMemo } from 'react'
|
||||
import { PublicKey } from '@solana/web3.js'
|
||||
import { connectionSelector, mangoGroupSelector } from '../stores/selectors'
|
||||
import {
|
||||
|
@ -26,17 +27,20 @@ import {
|
|||
ReferrerIdRecord,
|
||||
} from '@blockworks-foundation/mango-client'
|
||||
import useTradeHistory from '../hooks/useTradeHistory'
|
||||
import { WalletProvider, WalletListener } from 'components/WalletAdapter'
|
||||
import { PhantomWalletAdapter } from '@solana/wallet-adapter-phantom'
|
||||
import { SolflareWalletAdapter } from '@solana/wallet-adapter-solflare'
|
||||
import { SolletWalletAdapter } from '@solana/wallet-adapter-sollet'
|
||||
import { SlopeWalletAdapter } from '@solana/wallet-adapter-slope'
|
||||
import { BitpieWalletAdapter } from '@solana/wallet-adapter-bitpie'
|
||||
import { HuobiWalletAdapter } from '@solana/wallet-adapter-huobi'
|
||||
import { GlowWalletAdapter } from '@solana/wallet-adapter-glow'
|
||||
|
||||
const MangoStoreUpdater = () => {
|
||||
useHydrateStore()
|
||||
return null
|
||||
}
|
||||
|
||||
const WalletStoreUpdater = () => {
|
||||
useWallet()
|
||||
return null
|
||||
}
|
||||
|
||||
const OpenOrdersStoreUpdater = () => {
|
||||
useOpenOrders()
|
||||
return null
|
||||
|
@ -54,9 +58,9 @@ const TradeHistoryStoreUpdater = () => {
|
|||
|
||||
const FetchReferrer = () => {
|
||||
const setMangoStore = useMangoStore((s) => s.set)
|
||||
const router = useRouter()
|
||||
const mangoGroup = useMangoStore(mangoGroupSelector)
|
||||
const connection = useMangoStore(connectionSelector)
|
||||
const router = useRouter()
|
||||
const { query } = router
|
||||
|
||||
useEffect(() => {
|
||||
|
@ -120,6 +124,19 @@ const PageTitle = () => {
|
|||
}
|
||||
|
||||
function App({ Component, pageProps }) {
|
||||
const wallets = useMemo(
|
||||
() => [
|
||||
new PhantomWalletAdapter(),
|
||||
new SolflareWalletAdapter(),
|
||||
new SolletWalletAdapter(),
|
||||
new SlopeWalletAdapter(),
|
||||
new BitpieWalletAdapter(),
|
||||
new HuobiWalletAdapter(),
|
||||
new GlowWalletAdapter(),
|
||||
],
|
||||
[]
|
||||
)
|
||||
|
||||
return (
|
||||
<>
|
||||
<Head>
|
||||
|
@ -159,7 +176,6 @@ function App({ Component, pageProps }) {
|
|||
<ErrorBoundary>
|
||||
<PageTitle />
|
||||
<MangoStoreUpdater />
|
||||
<WalletStoreUpdater />
|
||||
<OpenOrdersStoreUpdater />
|
||||
<PerpPositionsStoreUpdater />
|
||||
<TradeHistoryStoreUpdater />
|
||||
|
@ -167,21 +183,24 @@ function App({ Component, pageProps }) {
|
|||
</ErrorBoundary>
|
||||
|
||||
<ThemeProvider defaultTheme="Mango">
|
||||
<ViewportProvider>
|
||||
<div className="min-h-screen bg-th-bkg-1">
|
||||
<ErrorBoundary>
|
||||
<GlobalNotification />
|
||||
<Component {...pageProps} />
|
||||
</ErrorBoundary>
|
||||
</div>
|
||||
<div className="fixed bottom-0 left-0 z-20 w-full md:hidden">
|
||||
<ErrorBoundary>
|
||||
<BottomBar />
|
||||
</ErrorBoundary>
|
||||
</div>
|
||||
<WalletProvider wallets={wallets}>
|
||||
<WalletListener />
|
||||
<ViewportProvider>
|
||||
<div className="min-h-screen bg-th-bkg-1">
|
||||
<ErrorBoundary>
|
||||
<GlobalNotification />
|
||||
<Component {...pageProps} />
|
||||
</ErrorBoundary>
|
||||
</div>
|
||||
<div className="fixed bottom-0 left-0 z-20 w-full md:hidden">
|
||||
<ErrorBoundary>
|
||||
<BottomBar />
|
||||
</ErrorBoundary>
|
||||
</div>
|
||||
|
||||
<Notifications />
|
||||
</ViewportProvider>
|
||||
<Notifications />
|
||||
</ViewportProvider>
|
||||
</WalletProvider>
|
||||
</ThemeProvider>
|
||||
</ErrorBoundary>
|
||||
</>
|
||||
|
|
|
@ -19,42 +19,41 @@ import {
|
|||
} from '@heroicons/react/outline'
|
||||
import { ChevronDownIcon } from '@heroicons/react/solid'
|
||||
import { nativeToUi, ZERO_BN } from '@blockworks-foundation/mango-client'
|
||||
import useMangoStore, {
|
||||
serumProgramId,
|
||||
MNGO_INDEX,
|
||||
} from '../stores/useMangoStore'
|
||||
import PageBodyContainer from '../components/PageBodyContainer'
|
||||
import TopBar from '../components/TopBar'
|
||||
import AccountOrders from '../components/account_page/AccountOrders'
|
||||
import AccountHistory from '../components/account_page/AccountHistory'
|
||||
import AccountsModal from '../components/AccountsModal'
|
||||
import AccountOverview from '../components/account_page/AccountOverview'
|
||||
import AccountInterest from '../components/account_page/AccountInterest'
|
||||
import AccountFunding from '../components/account_page/AccountFunding'
|
||||
import AccountNameModal from '../components/AccountNameModal'
|
||||
import { IconButton, LinkButton } from '../components/Button'
|
||||
import EmptyState from '../components/EmptyState'
|
||||
import Loading from '../components/Loading'
|
||||
import Swipeable from '../components/mobile/Swipeable'
|
||||
import Tabs from '../components/Tabs'
|
||||
import { useViewport } from '../hooks/useViewport'
|
||||
import { breakpoints } from '../components/TradePageGrid'
|
||||
import useMangoStore, { serumProgramId, MNGO_INDEX } from 'stores/useMangoStore'
|
||||
import PageBodyContainer from 'components/PageBodyContainer'
|
||||
import TopBar from 'components/TopBar'
|
||||
import AccountOrders from 'components/account_page/AccountOrders'
|
||||
import AccountHistory from 'components/account_page/AccountHistory'
|
||||
import AccountsModal from 'components/AccountsModal'
|
||||
import AccountOverview from 'components/account_page/AccountOverview'
|
||||
import AccountInterest from 'components/account_page/AccountInterest'
|
||||
import AccountFunding from 'components/account_page/AccountFunding'
|
||||
import AccountNameModal from 'components/AccountNameModal'
|
||||
import { IconButton, LinkButton } from 'components/Button'
|
||||
import EmptyState from 'components/EmptyState'
|
||||
import Loading from 'components/Loading'
|
||||
import Swipeable from 'components/mobile/Swipeable'
|
||||
import Tabs from 'components/Tabs'
|
||||
import { useViewport } from 'hooks/useViewport'
|
||||
import { breakpoints } from 'components/TradePageGrid'
|
||||
import { serverSideTranslations } from 'next-i18next/serverSideTranslations'
|
||||
import { useTranslation } from 'next-i18next'
|
||||
import { useRouter } from 'next/router'
|
||||
import { PublicKey } from '@solana/web3.js'
|
||||
import CloseAccountModal from '../components/CloseAccountModal'
|
||||
import { notify } from '../utils/notifications'
|
||||
import CloseAccountModal from 'components/CloseAccountModal'
|
||||
import { notify } from 'utils/notifications'
|
||||
import {
|
||||
actionsSelector,
|
||||
mangoAccountSelector,
|
||||
mangoGroupSelector,
|
||||
walletConnectedSelector,
|
||||
} from '../stores/selectors'
|
||||
import CreateAlertModal from '../components/CreateAlertModal'
|
||||
import { copyToClipboard } from '../utils'
|
||||
import DelegateModal from '../components/DelegateModal'
|
||||
} from 'stores/selectors'
|
||||
import CreateAlertModal from 'components/CreateAlertModal'
|
||||
import { copyToClipboard } from 'utils'
|
||||
import DelegateModal from 'components/DelegateModal'
|
||||
import { Menu, Transition } from '@headlessui/react'
|
||||
import { useWallet } from '@solana/wallet-adapter-react'
|
||||
import { handleWalletConnect } from 'components/ConnectWalletButton'
|
||||
import { MangoAccountLookup } from 'components/account_page/MangoAccountLookup'
|
||||
|
||||
export async function getStaticProps({ locale }) {
|
||||
return {
|
||||
|
@ -76,15 +75,12 @@ export default function Account() {
|
|||
const { t } = useTranslation(['common', 'close-account', 'delegate'])
|
||||
const { width } = useViewport()
|
||||
const router = useRouter()
|
||||
|
||||
const connected = useMangoStore(walletConnectedSelector)
|
||||
const { connected, wallet, publicKey } = useWallet()
|
||||
const isLoading = useMangoStore((s) => s.selectedMangoAccount.initialLoad)
|
||||
const mangoAccount = useMangoStore(mangoAccountSelector)
|
||||
const mangoGroup = useMangoStore(mangoGroupSelector)
|
||||
const wallet = useMangoStore((s) => s.wallet.current)
|
||||
const isLoading = useMangoStore((s) => s.selectedMangoAccount.initialLoad)
|
||||
const actions = useMangoStore(actionsSelector)
|
||||
const setMangoStore = useMangoStore((s) => s.set)
|
||||
|
||||
const [showAccountsModal, setShowAccountsModal] = useState(false)
|
||||
const [showNameModal, setShowNameModal] = useState(false)
|
||||
const [showCloseAccountModal, setShowCloseAccountModal] = useState(false)
|
||||
|
@ -96,10 +92,11 @@ export default function Account() {
|
|||
const [viewIndex, setViewIndex] = useState(0)
|
||||
const [activeTab, setActiveTab] = useState(TABS[0])
|
||||
|
||||
const connecting = wallet?.adapter?.connecting
|
||||
const isMobile = width ? width < breakpoints.sm : false
|
||||
const { pubkey } = router.query
|
||||
const isDelegatedAccount = wallet?.publicKey
|
||||
? !mangoAccount?.owner?.equals(wallet?.publicKey)
|
||||
const isDelegatedAccount = publicKey
|
||||
? !mangoAccount?.owner?.equals(publicKey)
|
||||
: false
|
||||
|
||||
const handleCloseAlertModal = useCallback(() => {
|
||||
|
@ -122,6 +119,10 @@ export default function Account() {
|
|||
setShowDelegateModal(false)
|
||||
}, [])
|
||||
|
||||
const handleConnect = useCallback(() => {
|
||||
handleWalletConnect(wallet)
|
||||
}, [wallet])
|
||||
|
||||
useEffect(() => {
|
||||
async function loadUnownedMangoAccount() {
|
||||
try {
|
||||
|
@ -140,6 +141,7 @@ export default function Account() {
|
|||
setResetOnLeave(true)
|
||||
}
|
||||
} catch (error) {
|
||||
console.log('error', error)
|
||||
router.push('/account')
|
||||
}
|
||||
}
|
||||
|
@ -152,12 +154,6 @@ export default function Account() {
|
|||
}
|
||||
}, [pubkey, mangoGroup])
|
||||
|
||||
useEffect(() => {
|
||||
if (connected) {
|
||||
router.push('/account')
|
||||
}
|
||||
}, [connected])
|
||||
|
||||
useEffect(() => {
|
||||
const handleRouteChange = () => {
|
||||
if (resetOnLeave) {
|
||||
|
@ -205,8 +201,13 @@ export default function Account() {
|
|||
)
|
||||
}, [mangoAccount])
|
||||
|
||||
useEffect(() => {
|
||||
if (connecting) {
|
||||
router.push('/account')
|
||||
}
|
||||
}, [connecting, router])
|
||||
|
||||
const handleRedeemMngo = async () => {
|
||||
const wallet = useMangoStore.getState().wallet.current
|
||||
const mangoClient = useMangoStore.getState().connection.client
|
||||
const mngoNodeBank =
|
||||
mangoGroup.rootBankAccounts[MNGO_INDEX].nodeBankAccounts[0]
|
||||
|
@ -215,7 +216,7 @@ export default function Account() {
|
|||
const txid = await mangoClient.redeemAllMngo(
|
||||
mangoGroup,
|
||||
mangoAccount,
|
||||
wallet,
|
||||
wallet?.adapter,
|
||||
mangoGroup.tokens[MNGO_INDEX].rootBank,
|
||||
mngoNodeBank.publicKey,
|
||||
mngoNodeBank.vault
|
||||
|
@ -432,18 +433,25 @@ export default function Account() {
|
|||
icon={<CurrencyDollarIcon />}
|
||||
onClickButton={() => setShowAccountsModal(true)}
|
||||
title={t('no-account-found')}
|
||||
disabled={!wallet || !mangoGroup}
|
||||
/>
|
||||
)
|
||||
) : (
|
||||
<EmptyState
|
||||
buttonText={t('connect')}
|
||||
desc={t('connect-view')}
|
||||
disabled={!wallet || !mangoGroup}
|
||||
icon={<LinkIcon />}
|
||||
onClickButton={() => wallet.connect()}
|
||||
onClickButton={handleConnect}
|
||||
title={t('connect-wallet')}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
{!connected && (
|
||||
<div className="mt-6 md:mt-3 md:rounded-lg md:bg-th-bkg-2 md:p-6">
|
||||
<MangoAccountLookup />
|
||||
</div>
|
||||
)}
|
||||
</PageBodyContainer>
|
||||
{showAccountsModal ? (
|
||||
<AccountsModal
|
||||
|
|
|
@ -0,0 +1,218 @@
|
|||
import { useTranslation } from 'next-i18next'
|
||||
import { serverSideTranslations } from 'next-i18next/serverSideTranslations'
|
||||
import TopBar from '../components/TopBar'
|
||||
import PageBodyContainer from '../components/PageBodyContainer'
|
||||
import useSrmAccount from '../hooks/useSrmAccount'
|
||||
import {
|
||||
MSRM_DECIMALS,
|
||||
SRM_DECIMALS,
|
||||
} from '@project-serum/serum/lib/token-instructions'
|
||||
import { percentFormat } from '../utils/index'
|
||||
import Tooltip from '../components/Tooltip'
|
||||
import { InformationCircleIcon } from '@heroicons/react/outline'
|
||||
import DepositMsrmModal from '../components/DepositMsrmModal'
|
||||
import WithdrawMsrmModal from '../components/WithdrawMsrmModal'
|
||||
import { useState } from 'react'
|
||||
import { LinkButton } from '../components/Button'
|
||||
import useMangoStore from '../stores/useMangoStore'
|
||||
import { msrmMints, ZERO_BN } from '@blockworks-foundation/mango-client'
|
||||
import useFees from '../hooks/useFees'
|
||||
import { useWallet } from '@solana/wallet-adapter-react'
|
||||
|
||||
export async function getStaticProps({ locale }) {
|
||||
return {
|
||||
props: {
|
||||
...(await serverSideTranslations(locale, ['common'])),
|
||||
// Will be passed to the page component as props
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
export default function Fees() {
|
||||
const { t } = useTranslation('common')
|
||||
const { totalSrm, totalMsrm, rates } = useSrmAccount()
|
||||
const { takerFee, makerFee } = useFees()
|
||||
const { connected } = useWallet()
|
||||
const [showDeposit, setShowDeposit] = useState(false)
|
||||
const [showWithdraw, setShowWithdraw] = useState(false)
|
||||
const mangoAccount = useMangoStore((s) => s.selectedMangoAccount.current)
|
||||
const walletTokens = useMangoStore((s) => s.wallet.tokens)
|
||||
const cluster = useMangoStore.getState().connection.cluster
|
||||
const ownerMsrmAccount = walletTokens.find((t) =>
|
||||
t.account.mint.equals(msrmMints[cluster])
|
||||
)
|
||||
return (
|
||||
<div className={`bg-th-bkg-1 text-th-fgd-1 transition-all`}>
|
||||
<TopBar />
|
||||
<PageBodyContainer>
|
||||
<div className="flex flex-col py-4 sm:flex-row md:pb-4 md:pt-10">
|
||||
<h1>{t('fees')}</h1>
|
||||
</div>
|
||||
<div className="md:rounded-lg md:bg-th-bkg-2 md:p-6">
|
||||
<h2 className="mb-4">{t('futures')}</h2>
|
||||
|
||||
<div className="grid grid-cols-1 grid-rows-2 pb-8 md:grid-cols-2 md:grid-rows-1 md:gap-4">
|
||||
<div className="border-t border-th-bkg-4 p-3 sm:p-4 md:border-b">
|
||||
<div className="pb-0.5 text-th-fgd-3">{t('maker-fee')}</div>
|
||||
<div className="flex items-center">
|
||||
<div className="text-xl font-bold text-th-fgd-1 md:text-2xl">
|
||||
{percentFormat.format(makerFee)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center">
|
||||
<p className="mb-0">
|
||||
{t('if-referred', {
|
||||
fee: percentFormat.format(
|
||||
makerFee < 0
|
||||
? makerFee + makerFee * 0.04
|
||||
: makerFee - makerFee * 0.04
|
||||
),
|
||||
})}
|
||||
</p>
|
||||
|
||||
<Tooltip content={t('if-referred-tooltip')}>
|
||||
<div>
|
||||
<InformationCircleIcon
|
||||
className={`ml-1.5 h-5 w-5 cursor-help text-th-fgd-3`}
|
||||
/>
|
||||
</div>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</div>
|
||||
<div className="border-b border-t border-th-bkg-4 p-3 sm:p-4">
|
||||
<div className="pb-0.5 text-th-fgd-3">{t('taker-fee')}</div>
|
||||
<div className="flex items-center">
|
||||
<div className="text-xl font-bold text-th-fgd-1 md:text-2xl">
|
||||
{percentFormat.format(takerFee)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center">
|
||||
<p className="mb-0">
|
||||
{t('if-referred', {
|
||||
fee: percentFormat.format(
|
||||
takerFee < 0
|
||||
? takerFee + takerFee * 0.04
|
||||
: takerFee - takerFee * 0.04
|
||||
),
|
||||
})}
|
||||
</p>
|
||||
|
||||
<Tooltip content={t('if-referred-tooltip')}>
|
||||
<div>
|
||||
<InformationCircleIcon
|
||||
className={`ml-1.5 h-5 w-5 cursor-help text-th-fgd-3`}
|
||||
/>
|
||||
</div>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<h2 className="mb-4">{t('serum-fees')}</h2>
|
||||
|
||||
<div className="grid grid-cols-1 grid-rows-2 pb-8 md:grid-cols-3 md:grid-rows-1 md:gap-4">
|
||||
<div className="border-t border-th-bkg-4 p-3 sm:p-4 md:border-b">
|
||||
<div className="pb-0.5 text-th-fgd-3">{t('maker-fee')}</div>
|
||||
<div className="flex items-center">
|
||||
<div className="text-xl font-bold text-th-fgd-1 md:text-2xl">
|
||||
{rates ? percentFormat.format(rates.maker) : null}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="border-t border-th-bkg-4 p-3 sm:p-4 md:border-b">
|
||||
<div className="flex items-center pb-0.5 text-th-fgd-3">
|
||||
{t('taker-fee')}
|
||||
<Tooltip
|
||||
content={t('tooltip-serum-rebate', {
|
||||
taker_percent: percentFormat.format(rates.taker),
|
||||
})}
|
||||
>
|
||||
<div>
|
||||
<InformationCircleIcon
|
||||
className={`ml-1.5 h-5 w-5 cursor-help text-th-fgd-3`}
|
||||
/>
|
||||
</div>
|
||||
</Tooltip>
|
||||
</div>
|
||||
<div className="flex items-center">
|
||||
<div className="text-xl font-bold text-th-fgd-1 md:text-2xl">
|
||||
{rates
|
||||
? new Intl.NumberFormat(undefined, {
|
||||
style: 'percent',
|
||||
minimumFractionDigits: 2,
|
||||
maximumFractionDigits: 3,
|
||||
}).format(rates.takerWithRebate)
|
||||
: null}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="border-b border-t border-th-bkg-4 p-3 sm:p-4">
|
||||
<div className="flex items-center justify-between pb-0.5 text-th-fgd-3">
|
||||
{totalMsrm > 0 ? 'MSRM' : 'SRM'} {t('deposits')}
|
||||
{connected && mangoAccount ? (
|
||||
<div className="flex justify-center space-x-3 pl-2">
|
||||
<LinkButton
|
||||
onClick={() => setShowDeposit(true)}
|
||||
disabled={!ownerMsrmAccount}
|
||||
>
|
||||
{t('deposit')}
|
||||
</LinkButton>
|
||||
{mangoAccount.msrmAmount.gt(ZERO_BN) ? (
|
||||
<LinkButton onClick={() => setShowWithdraw(true)}>
|
||||
{t('withdraw')}
|
||||
</LinkButton>
|
||||
) : null}
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
<div className="flex items-center">
|
||||
<div className="text-xl font-bold text-th-fgd-1 md:text-2xl">
|
||||
{totalMsrm > 0
|
||||
? totalMsrm.toLocaleString(undefined, {
|
||||
maximumFractionDigits: MSRM_DECIMALS,
|
||||
})
|
||||
: totalSrm.toLocaleString(undefined, {
|
||||
maximumFractionDigits: SRM_DECIMALS,
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<h2 className="mb-4">{t('other')}</h2>
|
||||
<div className="grid grid-cols-1 grid-rows-3 pb-6 md:grid-cols-3 md:grid-rows-1 md:gap-4">
|
||||
<div className="border-t border-th-bkg-4 p-3 sm:p-4 md:border-b">
|
||||
<div className="pb-0.5 text-th-fgd-3">{t('withdraw')}</div>
|
||||
<div className="text-xl font-bold text-th-fgd-1 md:text-2xl">
|
||||
0%
|
||||
</div>
|
||||
</div>
|
||||
<div className="border-t border-th-bkg-4 p-3 sm:p-4 md:border-b">
|
||||
<div className="pb-0.5 text-th-fgd-3">{t('borrow')}</div>
|
||||
<div className="text-xl font-bold text-th-fgd-1 md:text-2xl">
|
||||
0%
|
||||
</div>
|
||||
</div>
|
||||
<div className="border-b border-t border-th-bkg-4 p-3 sm:p-4">
|
||||
<div className="pb-0.5 text-th-fgd-3">{t('lend')}</div>
|
||||
<div className="text-xl font-bold text-th-fgd-1 md:text-2xl">
|
||||
0%
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</PageBodyContainer>
|
||||
{showDeposit ? (
|
||||
<DepositMsrmModal
|
||||
isOpen={showDeposit}
|
||||
onClose={() => setShowDeposit(false)}
|
||||
/>
|
||||
) : null}
|
||||
|
||||
{showWithdraw ? (
|
||||
<WithdrawMsrmModal
|
||||
isOpen={showWithdraw}
|
||||
onClose={() => setShowWithdraw(false)}
|
||||
/>
|
||||
) : null}
|
||||
</div>
|
||||
)
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
import { useEffect } from 'react'
|
||||
import React, { useEffect } from 'react'
|
||||
import { useRouter } from 'next/router'
|
||||
import useMangoGroupConfig from '../hooks/useMangoGroupConfig'
|
||||
import useMangoStore, { serumProgramId } from '../stores/useMangoStore'
|
||||
|
@ -19,10 +19,10 @@ import {
|
|||
actionsSelector,
|
||||
mangoAccountSelector,
|
||||
marketConfigSelector,
|
||||
walletConnectedSelector,
|
||||
} from '../stores/selectors'
|
||||
import { PublicKey } from '@solana/web3.js'
|
||||
import FavoritesShortcutBar from '../components/FavoritesShortcutBar'
|
||||
import { useWallet } from '@solana/wallet-adapter-react'
|
||||
|
||||
export async function getStaticProps({ locale }) {
|
||||
return {
|
||||
|
@ -37,12 +37,12 @@ export async function getStaticProps({ locale }) {
|
|||
}
|
||||
}
|
||||
|
||||
const PerpMarket = () => {
|
||||
const PerpMarket: React.FC = () => {
|
||||
const [alphaAccepted] = useLocalStorageState(ALPHA_MODAL_KEY, false)
|
||||
const [showTour] = useLocalStorageState(SHOW_TOUR_KEY, false)
|
||||
const { connected } = useWallet()
|
||||
const groupConfig = useMangoGroupConfig()
|
||||
const setMangoStore = useMangoStore((s) => s.set)
|
||||
const connected = useMangoStore(walletConnectedSelector)
|
||||
const mangoAccount = useMangoStore(mangoAccountSelector)
|
||||
const mangoGroup = useMangoStore((s) => s.selectedMangoGroup.current)
|
||||
const marketConfig = useMangoStore(marketConfigSelector)
|
||||
|
@ -62,12 +62,10 @@ const PerpMarket = () => {
|
|||
unownedMangoAccountPubkey,
|
||||
serumProgramId
|
||||
)
|
||||
console.log('unOwnedMangoAccount: ', unOwnedMangoAccount)
|
||||
|
||||
setMangoStore((state) => {
|
||||
state.selectedMangoAccount.current = unOwnedMangoAccount
|
||||
state.selectedMangoAccount.initialLoad = false
|
||||
state.wallet.connected = true
|
||||
})
|
||||
actions.fetchTradeHistory()
|
||||
actions.reloadOrders()
|
||||
|
|
|
@ -7,7 +7,6 @@ import {
|
|||
mangoCacheSelector,
|
||||
mangoGroupConfigSelector,
|
||||
mangoGroupSelector,
|
||||
walletSelector,
|
||||
} from '../stores/selectors'
|
||||
import Button, { IconButton } from '../components/Button'
|
||||
import {
|
||||
|
@ -47,6 +46,8 @@ import MobileTableHeader from '../components/mobile/MobileTableHeader'
|
|||
import Input, { Label } from '../components/Input'
|
||||
import InlineNotification from '../components/InlineNotification'
|
||||
import useMangoAccount from '../hooks/useMangoAccount'
|
||||
import { handleWalletConnect } from 'components/ConnectWalletButton'
|
||||
import { useWallet } from '@solana/wallet-adapter-react'
|
||||
|
||||
export async function getStaticProps({ locale }) {
|
||||
return {
|
||||
|
@ -78,9 +79,8 @@ export default function Referral() {
|
|||
const mangoCache = useMangoStore(mangoCacheSelector)
|
||||
const { mangoAccount } = useMangoAccount()
|
||||
const groupConfig = useMangoStore(mangoGroupConfigSelector)
|
||||
const wallet = useMangoStore(walletSelector)
|
||||
const connected = useMangoStore((s) => s.wallet.connected)
|
||||
const actions = useMangoStore((s) => s.actions)
|
||||
const { wallet, connected } = useWallet()
|
||||
|
||||
const referralHistory = useMangoStore((s) => s.referrals.history)
|
||||
const referralTotalAmount = useMangoStore((s) => s.referrals.total)
|
||||
|
@ -92,6 +92,8 @@ export default function Referral() {
|
|||
>([])
|
||||
const [hasCopied, setHasCopied] = useState(null)
|
||||
const [showAccountsModal, setShowAccountsModal] = useState(false)
|
||||
|
||||
// const [hasReferrals] = useState(false) // Placeholder to show/hide users referral stats
|
||||
const [loading, setLoading] = useState(false)
|
||||
const [inputError, setInputError] = useState('')
|
||||
const { width } = useViewport()
|
||||
|
@ -149,6 +151,10 @@ export default function Referral() {
|
|||
}
|
||||
}
|
||||
|
||||
const handleConnect = useCallback(() => {
|
||||
handleWalletConnect(wallet)
|
||||
}, [wallet])
|
||||
|
||||
const submitRefLink = async () => {
|
||||
let encodedRefLink: string
|
||||
try {
|
||||
|
@ -166,7 +172,7 @@ export default function Referral() {
|
|||
const txid = await mangoClient.registerReferrerId(
|
||||
mangoGroup,
|
||||
mangoAccount,
|
||||
wallet,
|
||||
wallet?.adapter,
|
||||
encodedRefLink
|
||||
)
|
||||
notify({
|
||||
|
@ -517,6 +523,7 @@ export default function Referral() {
|
|||
icon={<CurrencyDollarIcon />}
|
||||
onClickButton={() => setShowAccountsModal(true)}
|
||||
title={t('no-account-found')}
|
||||
disabled={!wallet || !mangoGroup}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
|
@ -529,8 +536,9 @@ export default function Referral() {
|
|||
<div className="col-span-12 flex items-center justify-center rounded-md bg-th-bkg-3 p-6 lg:col-span-8">
|
||||
<EmptyState
|
||||
buttonText={t('connect')}
|
||||
disabled={!wallet || !mangoGroup}
|
||||
icon={<LinkIcon />}
|
||||
onClickButton={() => wallet.connect()}
|
||||
onClickButton={handleConnect}
|
||||
title={t('connect-wallet')}
|
||||
/>
|
||||
</div>
|
||||
|
|
|
@ -29,6 +29,7 @@ import { serverSideTranslations } from 'next-i18next/serverSideTranslations'
|
|||
import { useTranslation } from 'next-i18next'
|
||||
import { useRouter } from 'next/router'
|
||||
import { PublicKey } from '@solana/web3.js'
|
||||
import { useWallet } from '@solana/wallet-adapter-react'
|
||||
|
||||
export async function getStaticProps({ locale }) {
|
||||
return {
|
||||
|
@ -98,9 +99,9 @@ export default function RiskCalculator() {
|
|||
const mangoCache = useMangoStore((s) => s.selectedMangoGroup.cache)
|
||||
const mangoConfig = useMangoStore((s) => s.selectedMangoGroup.config)
|
||||
const mangoAccount = useMangoStore((s) => s.selectedMangoAccount.current)
|
||||
const connected = useMangoStore((s) => s.wallet.connected)
|
||||
const setMangoStore = useMangoStore((s) => s.set)
|
||||
const router = useRouter()
|
||||
const { connected } = useWallet()
|
||||
const { pubkey } = router.query
|
||||
|
||||
// Set default state variables
|
||||
|
@ -121,7 +122,7 @@ export default function RiskCalculator() {
|
|||
router.push('/risk-calculator')
|
||||
setScenarioInitialized(false)
|
||||
}
|
||||
}, [connected])
|
||||
}, [connected, router])
|
||||
|
||||
useEffect(() => {
|
||||
async function loadUnownedMangoAccount() {
|
||||
|
@ -147,7 +148,7 @@ export default function RiskCalculator() {
|
|||
if (pubkey) {
|
||||
loadUnownedMangoAccount()
|
||||
}
|
||||
}, [pubkey, mangoGroup])
|
||||
}, [pubkey, mangoGroup, router, setMangoStore])
|
||||
|
||||
useEffect(() => {
|
||||
const handleRouteChange = () => {
|
||||
|
@ -161,7 +162,7 @@ export default function RiskCalculator() {
|
|||
return () => {
|
||||
router.events.off('routeChangeStart', handleRouteChange)
|
||||
}
|
||||
}, [resetOnLeave])
|
||||
}, [resetOnLeave, setMangoStore])
|
||||
|
||||
// Set rules for updating the scenario
|
||||
useEffect(() => {
|
||||
|
|
|
@ -4,15 +4,11 @@ import useMangoStore from '../stores/useMangoStore'
|
|||
import PageBodyContainer from '../components/PageBodyContainer'
|
||||
import TopBar from '../components/TopBar'
|
||||
import { serverSideTranslations } from 'next-i18next/serverSideTranslations'
|
||||
import {
|
||||
actionsSelector,
|
||||
connectionSelector,
|
||||
walletConnectedSelector,
|
||||
walletSelector,
|
||||
} from '../stores/selectors'
|
||||
import { actionsSelector, connectionSelector } from '../stores/selectors'
|
||||
import JupiterForm from '../components/JupiterForm'
|
||||
import { zeroKey } from '@blockworks-foundation/mango-client'
|
||||
import { useTranslation } from 'next-i18next'
|
||||
import { useWallet } from '@solana/wallet-adapter-react'
|
||||
|
||||
export async function getStaticProps({ locale }) {
|
||||
return {
|
||||
|
@ -26,8 +22,7 @@ export async function getStaticProps({ locale }) {
|
|||
export default function Swap() {
|
||||
const { t } = useTranslation(['common', 'swap'])
|
||||
const connection = useMangoStore(connectionSelector)
|
||||
const connected = useMangoStore(walletConnectedSelector)
|
||||
const wallet = useMangoStore(walletSelector)
|
||||
const { connected, publicKey } = useWallet()
|
||||
const actions = useMangoStore(actionsSelector)
|
||||
|
||||
useEffect(() => {
|
||||
|
@ -39,9 +34,7 @@ export default function Swap() {
|
|||
if (!connection) return null
|
||||
|
||||
const userPublicKey =
|
||||
wallet?.publicKey && !zeroKey.equals(wallet.publicKey)
|
||||
? wallet.publicKey
|
||||
: null
|
||||
publicKey && !zeroKey.equals(publicKey) ? publicKey : null
|
||||
|
||||
return (
|
||||
<JupiterProvider
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
<svg fill="#ffffff" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 50 50" width="50px" height="50px"><path d="M 41.625 10.769531 C 37.644531 7.566406 31.347656 7.023438 31.078125 7.003906 C 30.660156 6.96875 30.261719 7.203125 30.089844 7.589844 C 30.074219 7.613281 29.9375 7.929688 29.785156 8.421875 C 32.417969 8.867188 35.652344 9.761719 38.578125 11.578125 C 39.046875 11.867188 39.191406 12.484375 38.902344 12.953125 C 38.710938 13.261719 38.386719 13.429688 38.050781 13.429688 C 37.871094 13.429688 37.6875 13.378906 37.523438 13.277344 C 32.492188 10.15625 26.210938 10 25 10 C 23.789063 10 17.503906 10.15625 12.476563 13.277344 C 12.007813 13.570313 11.390625 13.425781 11.101563 12.957031 C 10.808594 12.484375 10.953125 11.871094 11.421875 11.578125 C 14.347656 9.765625 17.582031 8.867188 20.214844 8.425781 C 20.0625 7.929688 19.925781 7.617188 19.914063 7.589844 C 19.738281 7.203125 19.34375 6.960938 18.921875 7.003906 C 18.652344 7.023438 12.355469 7.566406 8.320313 10.8125 C 6.214844 12.761719 2 24.152344 2 34 C 2 34.175781 2.046875 34.34375 2.132813 34.496094 C 5.039063 39.605469 12.972656 40.941406 14.78125 41 C 14.789063 41 14.800781 41 14.8125 41 C 15.132813 41 15.433594 40.847656 15.621094 40.589844 L 17.449219 38.074219 C 12.515625 36.800781 9.996094 34.636719 9.851563 34.507813 C 9.4375 34.144531 9.398438 33.511719 9.765625 33.097656 C 10.128906 32.683594 10.761719 32.644531 11.175781 33.007813 C 11.234375 33.0625 15.875 37 25 37 C 34.140625 37 38.78125 33.046875 38.828125 33.007813 C 39.242188 32.648438 39.871094 32.683594 40.238281 33.101563 C 40.601563 33.515625 40.5625 34.144531 40.148438 34.507813 C 40.003906 34.636719 37.484375 36.800781 32.550781 38.074219 L 34.378906 40.589844 C 34.566406 40.847656 34.867188 41 35.1875 41 C 35.199219 41 35.210938 41 35.21875 41 C 37.027344 40.941406 44.960938 39.605469 47.867188 34.496094 C 47.953125 34.34375 48 34.175781 48 34 C 48 24.152344 43.785156 12.761719 41.625 10.769531 Z M 18.5 30 C 16.566406 30 15 28.210938 15 26 C 15 23.789063 16.566406 22 18.5 22 C 20.433594 22 22 23.789063 22 26 C 22 28.210938 20.433594 30 18.5 30 Z M 31.5 30 C 29.566406 30 28 28.210938 28 26 C 28 23.789063 29.566406 22 31.5 22 C 33.433594 22 35 23.789063 35 26 C 35 28.210938 33.433594 30 31.5 30 Z"/></svg>
|
After Width: | Height: | Size: 2.2 KiB |
|
@ -0,0 +1,56 @@
|
|||
<svg width="2963" height="864" viewBox="0 0 2963 864" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M1274.13 488.608C1277.75 495.431 1281.05 502.575 1284.04 510.039C1287.24 517.289 1290.33 524.646 1293.31 532.11C1296.29 524.433 1299.39 516.863 1302.58 509.399C1305.78 501.935 1309.19 494.792 1312.81 487.968L1449.66 231.754C1451.37 228.556 1453.07 225.997 1454.78 224.077C1456.69 222.158 1458.72 220.772 1460.85 219.919C1463.2 219.066 1465.75 218.533 1468.52 218.32C1471.3 218.107 1474.6 218 1478.44 218H1543.34V680.528H1467.57V381.772C1467.57 376.228 1467.67 370.15 1467.89 363.54C1468.31 356.929 1468.84 350.212 1469.48 343.388L1329.76 605.679C1326.56 611.65 1322.41 616.341 1317.29 619.753C1312.17 622.952 1306.21 624.551 1299.39 624.551H1287.56C1280.73 624.551 1274.77 622.952 1269.65 619.753C1264.53 616.341 1260.38 611.65 1257.18 605.679L1115.54 342.428C1116.39 349.465 1116.92 356.396 1117.14 363.22C1117.56 369.83 1117.78 376.014 1117.78 381.772V680.528H1042V218H1106.91C1110.74 218 1114.05 218.107 1116.82 218.32C1119.59 218.533 1122.04 219.066 1124.17 219.919C1126.52 220.772 1128.65 222.158 1130.57 224.077C1132.48 225.997 1134.3 228.556 1136 231.754L1274.13 488.608Z" fill="white"/>
|
||||
<path d="M1889.09 680.528H1853.6C1846.14 680.528 1840.28 679.462 1836.02 677.329C1831.76 674.984 1828.56 670.399 1826.43 663.575L1819.39 640.225C1811.08 647.688 1802.87 654.299 1794.77 660.056C1786.89 665.601 1778.68 670.292 1770.15 674.131C1761.63 677.969 1752.57 680.848 1742.98 682.767C1733.38 684.686 1722.73 685.646 1711 685.646C1697.15 685.646 1684.36 683.833 1672.63 680.208C1660.91 676.37 1650.79 670.719 1642.26 663.255C1633.95 655.792 1627.45 646.515 1622.76 635.427C1618.07 624.338 1615.72 611.437 1615.72 596.723C1615.72 584.355 1618.92 572.2 1625.31 560.258C1631.92 548.103 1642.79 537.227 1657.93 527.631C1673.06 517.822 1693.2 509.719 1718.36 503.322C1743.51 496.924 1774.74 493.299 1812.04 492.446V473.254C1812.04 451.29 1807.35 435.083 1797.97 424.634C1788.8 413.972 1775.38 408.641 1757.68 408.641C1744.89 408.641 1734.24 410.134 1725.71 413.119C1717.18 416.104 1709.72 419.516 1703.33 423.355C1697.15 426.98 1691.39 430.285 1686.06 433.271C1680.73 436.256 1674.87 437.749 1668.48 437.749C1663.15 437.749 1658.57 436.363 1654.73 433.59C1650.89 430.818 1647.8 427.406 1645.46 423.355L1631.07 398.085C1668.8 363.54 1714.31 346.267 1767.6 346.267C1786.78 346.267 1803.83 349.465 1818.75 355.863C1833.89 362.047 1846.68 370.79 1857.12 382.092C1867.57 393.181 1875.45 406.508 1880.78 422.075C1886.32 437.642 1889.09 454.702 1889.09 473.254V680.528ZM1735.62 631.268C1743.72 631.268 1751.18 630.522 1758 629.029C1764.82 627.537 1771.22 625.298 1777.19 622.312C1783.37 619.327 1789.23 615.702 1794.77 611.437C1800.53 606.958 1806.28 601.734 1812.04 595.763V540.426C1789.02 541.492 1769.73 543.518 1754.17 546.504C1738.82 549.276 1726.46 552.901 1717.08 557.379C1707.7 561.857 1700.98 567.082 1696.93 573.053C1693.1 579.023 1691.18 585.527 1691.18 592.564C1691.18 606.425 1695.23 616.341 1703.33 622.312C1711.64 628.283 1722.41 631.268 1735.62 631.268Z" fill="white"/>
|
||||
<path d="M1966.75 680.528V352.344H2015.03C2025.26 352.344 2031.98 357.142 2035.17 366.738L2040.61 392.648C2047.22 385.824 2054.14 379.64 2061.39 374.095C2068.85 368.551 2076.63 363.753 2084.73 359.701C2093.04 355.65 2101.89 352.557 2111.27 350.425C2120.65 348.293 2130.88 347.226 2141.96 347.226C2159.87 347.226 2175.75 350.318 2189.6 356.503C2203.46 362.473 2214.97 371.003 2224.14 382.092C2233.51 392.967 2240.55 406.082 2245.24 421.436C2250.14 436.576 2252.59 453.316 2252.59 471.655V680.528H2173.62V471.655C2173.62 451.61 2168.93 436.149 2159.55 425.274C2150.38 414.185 2136.53 408.641 2117.98 408.641C2104.34 408.641 2091.55 411.733 2079.62 417.917C2067.68 424.101 2056.38 432.524 2045.72 443.187V680.528H1966.75Z" fill="white"/>
|
||||
<path d="M2443.7 346.587C2457.77 346.587 2470.99 348.079 2483.35 351.065C2495.71 353.837 2507.01 357.995 2517.24 363.54H2611.56V392.967C2611.56 397.872 2610.28 401.71 2607.73 404.483C2605.17 407.255 2600.8 409.174 2594.62 410.24L2565.2 415.678C2567.33 421.222 2568.93 427.087 2570 433.271C2571.28 439.455 2571.92 445.959 2571.92 452.783C2571.92 468.989 2568.61 483.703 2562 496.924C2555.61 509.932 2546.66 521.021 2535.15 530.19C2523.85 539.36 2510.31 546.504 2494.54 551.621C2478.98 556.526 2462.03 558.978 2443.7 558.978C2431.34 558.978 2419.3 557.806 2407.57 555.46C2397.34 561.644 2392.23 568.574 2392.23 576.251C2392.23 582.862 2395.21 587.766 2401.18 590.965C2407.36 593.951 2415.35 596.083 2425.16 597.362C2435.18 598.642 2446.47 599.495 2459.05 599.921C2471.63 600.135 2484.52 600.774 2497.74 601.841C2510.95 602.907 2523.85 604.826 2536.43 607.598C2549 610.157 2560.19 614.315 2570 620.073C2580.02 625.831 2588.01 633.721 2593.98 643.743C2600.16 653.552 2603.25 666.241 2603.25 681.807C2603.25 696.308 2599.63 710.382 2592.38 724.03C2585.34 737.678 2575.01 749.833 2561.36 760.495C2547.94 771.157 2531.42 779.687 2511.81 786.084C2492.2 792.695 2469.81 796 2444.66 796C2419.72 796 2398.09 793.548 2379.76 788.643C2361.42 783.952 2346.18 777.554 2334.03 769.451C2322.1 761.561 2313.14 752.391 2307.18 741.942C2301.21 731.493 2298.22 720.618 2298.22 709.316C2298.22 693.962 2302.91 681.061 2312.29 670.612C2321.67 660.163 2334.67 651.847 2351.3 645.662C2343.2 641.184 2336.7 635.213 2331.8 627.75C2326.89 620.286 2324.44 610.584 2324.44 598.642C2324.44 593.737 2325.29 588.726 2327 583.608C2328.71 578.277 2331.26 573.053 2334.67 567.935C2338.3 562.817 2342.77 558.019 2348.1 553.541C2353.43 548.849 2359.72 544.691 2366.97 541.066C2350.34 532.11 2337.23 520.168 2327.64 505.241C2318.26 490.314 2313.57 472.828 2313.57 452.783C2313.57 436.576 2316.77 421.969 2323.16 408.961C2329.77 395.74 2338.83 384.544 2350.34 375.375C2362.06 365.992 2375.81 358.848 2391.59 353.944C2407.57 349.039 2424.94 346.587 2443.7 346.587ZM2530.35 695.242C2530.35 688.845 2528.43 683.62 2524.6 679.568C2520.76 675.517 2515.54 672.425 2508.93 670.292C2502.32 667.947 2494.54 666.241 2485.59 665.174C2476.85 664.108 2467.47 663.362 2457.45 662.935C2447.65 662.296 2437.41 661.762 2426.76 661.336C2416.31 660.696 2406.19 659.737 2396.38 658.457C2387.43 663.362 2380.18 669.226 2374.64 676.05C2369.31 682.66 2366.65 690.337 2366.65 699.08C2366.65 704.838 2368.03 710.169 2370.8 715.074C2373.79 720.191 2378.37 724.563 2384.55 728.188C2390.95 731.813 2399.15 734.586 2409.17 736.505C2419.19 738.637 2431.45 739.703 2445.94 739.703C2460.65 739.703 2473.33 738.531 2483.99 736.185C2494.65 734.052 2503.39 730.96 2510.21 726.909C2517.24 723.07 2522.36 718.379 2525.55 712.834C2528.75 707.503 2530.35 701.639 2530.35 695.242ZM2443.7 508.439C2453.51 508.439 2462.03 507.16 2469.28 504.601C2476.53 501.829 2482.5 498.097 2487.19 493.406C2492.09 488.714 2495.71 483.063 2498.06 476.453C2500.62 469.842 2501.89 462.592 2501.89 454.702C2501.89 438.495 2496.99 425.7 2487.19 416.318C2477.59 406.722 2463.1 401.924 2443.7 401.924C2424.31 401.924 2409.7 406.722 2399.9 416.318C2390.31 425.7 2385.51 438.495 2385.51 454.702C2385.51 462.379 2386.68 469.522 2389.03 476.133C2391.59 482.743 2395.21 488.501 2399.9 493.406C2404.8 498.097 2410.88 501.829 2418.12 504.601C2425.58 507.16 2434.11 508.439 2443.7 508.439Z" fill="white"/>
|
||||
<path d="M2801.21 347.226C2825.73 347.226 2847.9 351.171 2867.72 359.061C2887.76 366.952 2904.81 378.147 2918.88 392.648C2932.95 407.148 2943.82 424.847 2951.49 445.745C2959.16 466.643 2963 489.994 2963 515.796C2963 541.812 2959.16 565.269 2951.49 586.167C2943.82 607.065 2932.95 624.871 2918.88 639.585C2904.81 654.299 2887.76 665.601 2867.72 673.491C2847.9 681.381 2825.73 685.326 2801.21 685.326C2776.7 685.326 2754.43 681.381 2734.39 673.491C2714.35 665.601 2697.19 654.299 2682.91 639.585C2668.85 624.871 2657.87 607.065 2649.98 586.167C2642.31 565.269 2638.47 541.812 2638.47 515.796C2638.47 489.994 2642.31 466.643 2649.98 445.745C2657.87 424.847 2668.85 407.148 2682.91 392.648C2697.19 378.147 2714.35 366.952 2734.39 359.061C2754.43 351.171 2776.7 347.226 2801.21 347.226ZM2801.21 624.551C2828.5 624.551 2848.64 615.382 2861.64 597.043C2874.86 578.704 2881.47 551.835 2881.47 516.436C2881.47 481.037 2874.86 454.062 2861.64 435.51C2848.64 416.957 2828.5 407.681 2801.21 407.681C2773.5 407.681 2753.04 417.064 2739.83 435.83C2726.61 454.382 2720 481.251 2720 516.436C2720 551.621 2726.61 578.49 2739.83 597.043C2753.04 615.382 2773.5 624.551 2801.21 624.551Z" fill="white"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M337.746 201.289C337.751 201.283 337.755 201.277 337.76 201.27C374.05 219.89 413.76 226.27 451.76 226.16C473.84 246.44 492.21 270.26 509.47 294.72C516.627 304.937 523.082 315.628 528.79 326.72C541.989 352.136 551.696 379.028 561.518 406.237C564.398 414.217 567.289 422.225 570.28 430.23C571.217 433.656 572.196 437.084 573.217 440.514L573.27 440.5C588.44 492.53 610.59 548.11 640.27 594L640.241 594.012C650.058 609.087 661.055 623.361 673.13 636.7C675.506 639.27 677.936 641.816 680.369 644.365L680.37 644.366L680.371 644.367L680.374 644.37C691.742 656.28 703.19 668.274 709.68 683.28C717.76 701.98 717.14 723.49 712.28 743.28C689.35 836.56 599.52 861.07 513.67 863.33L513.711 863.223C481.522 863.864 449.556 861.465 421.21 858.56C421.21 858.56 284.5 844.41 168.62 759.69L164.88 756.91C164.88 756.91 164.88 756.91 164.881 756.909C151.355 746.83 138.451 735.941 126.24 724.3C93.76 693.3 64.86 658.14 42.76 619.54C42.9078 619.392 43.0553 619.243 43.2026 619.095C40.587 614.388 38.0795 609.634 35.68 604.83C14.3 562.04 1.27 515.46 0.679999 465.95C-0.325631 382.873 28.0953 297.795 82.1176 236.113C82.0984 236.062 82.0792 236.011 82.06 235.96C111.31 203.92 147.87 178.75 191.15 164.42C218.283 155.354 246.768 151.001 275.37 151.55C292.775 171.987 313.954 188.874 337.746 201.289ZM271.153 744.85C290.711 737.711 309.24 728.11 326.518 716.279C309.131 728.03 290.575 737.637 271.153 744.85Z" fill="url(#paint0_linear)"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M621.498 133.628C621.512 133.628 621.526 133.628 621.54 133.627L622.72 132.867C470.3 -127.133 271 77.5671 271 77.5671L271.285 78.0685C271.273 78.0714 271.262 78.0742 271.25 78.0771C385.856 279.01 603.664 145.087 621.498 133.628Z" fill="url(#paint1_linear)"/>
|
||||
<path d="M432.56 581.44C390.56 681.84 309.92 748.32 212.22 758.39C210.12 758.67 183.38 760.78 168.62 759.69C284.5 844.41 421.21 858.56 421.21 858.56C450.48 861.56 483.61 864.02 516.86 863.15C528.57 832.58 535.16 797.58 533.27 757.87C528.88 665.64 582.36 618.29 640.27 594C610.59 548.11 588.44 492.53 573.27 440.5C528.05 452.53 470.62 490.36 432.56 581.44Z" fill="url(#paint2_linear)"/>
|
||||
<path d="M531.44 757.22C533.34 796.93 525.38 832.76 513.67 863.33C599.52 861.07 689.35 836.56 712.28 743.28C717.14 723.49 717.76 701.98 709.68 683.28C701.8 665.06 686.61 651.28 673.13 636.7C660.519 622.769 649.084 607.818 638.94 592C581.08 616.3 527.05 665 531.44 757.22Z" fill="url(#paint3_linear)"/>
|
||||
<path d="M570.28 430.23C557.09 394.93 545.86 359.59 528.79 326.72C523.082 315.628 516.627 304.937 509.47 294.72C492.21 270.26 473.84 246.44 451.76 226.16C413.76 226.27 374.05 219.89 337.76 201.27C301 253.41 258.94 341.86 297.44 442.82C354.29 591.92 238.92 693.82 164.88 756.91L168.62 759.69C182.502 760.773 196.455 760.573 210.3 759.09C307.99 749.01 393.3 680.93 435.3 580.54C473.37 489.46 528.75 454.86 573.91 442.82C572.637 438.62 571.427 434.423 570.28 430.23Z" fill="url(#paint4_linear)"/>
|
||||
<path d="M86.09 231.67C29.49 293.67 -0.350001 380.86 0.679999 465.95C1.27 515.46 14.3 562.04 35.68 604.83C38.887 611.25 42.287 617.583 45.88 623.83C164.87 504.39 121.88 326.42 86.09 231.67Z" fill="url(#paint5_linear)"/>
|
||||
<path d="M299.44 442.82C260.94 341.82 302.06 253.95 338.77 201.82C314.561 189.357 293.024 172.28 275.37 151.55C246.768 151.001 218.283 155.354 191.15 164.42C147.87 178.75 111.31 203.92 82.06 235.96C117.06 328.63 159.12 502.72 42.76 619.54C64.86 658.14 93.76 693.3 126.24 724.3C139.051 736.513 152.625 747.899 166.88 758.39C240.92 695.33 356.29 591.92 299.44 442.82Z" fill="url(#paint6_linear)"/>
|
||||
<path d="M443 94.13C523.57 125.92 580 134.53 620.91 133.3L622.09 132.54C469.67 -127.46 270.37 77.24 270.37 77.24L270.66 77.75C313.65 70.13 376.13 67.76 443 94.13Z" fill="url(#paint7_linear)"/>
|
||||
<path d="M444 92.33C377.17 66 314.33 67 270.62 77.75C385.23 278.69 603.05 144.75 620.87 133.3C579.93 134.53 524.57 124.12 444 92.33Z" fill="url(#paint8_linear)"/>
|
||||
<defs>
|
||||
<linearGradient id="paint0_linear" x1="-88.5" y1="273.5" x2="843.5" y2="832" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#E54033"/>
|
||||
<stop offset="0.489583" stop-color="#FECA1A"/>
|
||||
<stop offset="1" stop-color="#AFD803"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint1_linear" x1="263632" y1="31154.5" x2="205286" y2="-28862.6" gradientUnits="userSpaceOnUse">
|
||||
<stop offset="0.15" stop-color="#6CBF00"/>
|
||||
<stop offset="1" stop-color="#AFD803"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint2_linear" x1="72.43" y1="766.73" x2="656.43" y2="624.73" gradientUnits="userSpaceOnUse">
|
||||
<stop offset="0.21" stop-color="#E54033"/>
|
||||
<stop offset="0.84" stop-color="#FECA1A"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint3_linear" x1="532.54" y1="727.34" x2="712.74" y2="728.69" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#FECA1A"/>
|
||||
<stop offset="0.4" stop-color="#FECA1A"/>
|
||||
<stop offset="1" stop-color="#AFD803"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint4_linear" x1="124.65" y1="770.37" x2="494.1" y2="270.2" gradientUnits="userSpaceOnUse">
|
||||
<stop offset="0.16" stop-color="#E54033"/>
|
||||
<stop offset="0.84" stop-color="#FECA1A"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint5_linear" x1="70.85" y1="273.39" x2="54.49" y2="596.45" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#FECA1A"/>
|
||||
<stop offset="0.76" stop-color="#E54033"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint6_linear" x1="251.58" y1="189.5" x2="152.91" y2="564.17" gradientUnits="userSpaceOnUse">
|
||||
<stop offset="0.16" stop-color="#FECA1A"/>
|
||||
<stop offset="1" stop-color="#E54033"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint7_linear" x1="289.8" y1="10.1199" x2="655.13" y2="144.78" gradientUnits="userSpaceOnUse">
|
||||
<stop offset="0.15" stop-color="#6CBF00"/>
|
||||
<stop offset="1" stop-color="#AFD803"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint8_linear" x1="263631" y1="31154.2" x2="205285" y2="-28862.9" gradientUnits="userSpaceOnUse">
|
||||
<stop offset="0.15" stop-color="#6CBF00"/>
|
||||
<stop offset="1" stop-color="#AFD803"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</svg>
|
After Width: | Height: | Size: 14 KiB |
|
@ -1,3 +1,5 @@
|
|||
|
||||
|
||||
{
|
||||
"about-to-withdraw": "You're about to withdraw",
|
||||
"above": "Above",
|
||||
|
@ -136,11 +138,17 @@
|
|||
"export-data-empty": "No data to export",
|
||||
"export-data-success": "CSV exported successfully",
|
||||
"export-pnl-csv": "Export PnL CSV",
|
||||
"export-trades-csv": "Export Trades CSV",
|
||||
"favorite": "Favorite",
|
||||
"favorites": "Favorites",
|
||||
"fee": "Fee",
|
||||
"fees": "Fees",
|
||||
"fee-discount": "Fee Discount",
|
||||
"filter": "Filter",
|
||||
"filters-selected": "{{selectedFilters}} selected",
|
||||
"filter-trade-history": "Filter Trade History",
|
||||
"first-deposit-desc": "There is a one-time cost of 0.035 SOL when you make your first deposit. This covers the rent on the Solana Blockchain for your account.",
|
||||
"from": "From",
|
||||
"funding": "Funding",
|
||||
"funding-chart-title": "Funding – Last 30 days (current bar is delayed)",
|
||||
"futures": "Futures",
|
||||
|
@ -156,6 +164,8 @@
|
|||
"hourly-borrow-interest": "Hourly Borrow Interest",
|
||||
"hourly-deposit-interest": "Hourly Deposit Interest",
|
||||
"hourly-funding": "Hourly Funding",
|
||||
"if-referred": "{{fee}} if referred or 10k MNGO",
|
||||
"if-referred-tooltip": "If you create your Mango Account from a referral link or have 10k MNGO in your Mango Account you get a 0.04% discount off futures fees.",
|
||||
"in-orders": "In Orders",
|
||||
"include-perp": "Include Perp",
|
||||
"include-spot": "Include Spot",
|
||||
|
@ -182,6 +192,7 @@
|
|||
"layout-tip-title": "Customize Layout",
|
||||
"learn": "Documentation",
|
||||
"learn-more": "Learn more",
|
||||
"lend": "Lend",
|
||||
"lets-go": "Let's Go",
|
||||
"leverage": "Leverage",
|
||||
"leverage-too-high": "Leverage too high. Reduce the amount to withdraw",
|
||||
|
@ -249,6 +260,7 @@
|
|||
"no-markets": "No markets found",
|
||||
"no-orders": "No open orders",
|
||||
"no-perp": "No perp positions",
|
||||
"no-trades-found": "No trades found...",
|
||||
"no-unsettled": "There are no unsettled funds",
|
||||
"no-wallet": "No wallet address",
|
||||
"node-url": "RPC Node URL",
|
||||
|
@ -263,6 +275,7 @@
|
|||
"orderbook": "Orderbook",
|
||||
"orderbook-animation": "Orderbook Animation",
|
||||
"orders": "Orders",
|
||||
"other": "Other",
|
||||
"performance": "Performance",
|
||||
"performance-insights": "Performance Insights",
|
||||
"period-progress": "Period Progress",
|
||||
|
@ -300,6 +313,7 @@
|
|||
"redeem-all": "Redeem All",
|
||||
"redeem-failure": "Error redeeming MNGO",
|
||||
"redeem-pnl": "Redeem",
|
||||
"redeem-positive": "Redeem positive",
|
||||
"redeem-success": "Successfully redeemed MNGO",
|
||||
"referrals": "Referrals",
|
||||
"refresh": "Refresh",
|
||||
|
@ -310,6 +324,7 @@
|
|||
"repay-partial": "Repay {{percentage}}% of borrow",
|
||||
"reposition": "Drag to reposition",
|
||||
"reset": "Reset",
|
||||
"reset-filters": "Reset Filters",
|
||||
"rolling-change": "24hr Change",
|
||||
"rpc-endpoint": "RPC Endpoint",
|
||||
"save": "Save",
|
||||
|
@ -356,6 +371,7 @@
|
|||
"themes-tip-title": "Color Themes",
|
||||
"time": "Time",
|
||||
"timeframe-desc": "Last {{timeframe}}",
|
||||
"to": "To",
|
||||
"token": "Token",
|
||||
"too-large": "Size Too Large",
|
||||
"tooltip-account-liquidated": "Account will be liquidated if Health Ratio reaches 0% and will continue until Init Health is above 0.",
|
||||
|
@ -406,6 +422,7 @@
|
|||
"unsettled-balance": "Redeemable Value",
|
||||
"unsettled-balances": "Unsettled Balances",
|
||||
"unsettled-positions": "Unsettled Positions",
|
||||
"update-filters": "Update Filters",
|
||||
"use-explorer-one": "Use the ",
|
||||
"use-explorer-three": "to verify any delayed transactions.",
|
||||
"use-explorer-two": "Explorer ",
|
||||
|
|
|
@ -6,5 +6,4 @@
|
|||
"public-key": "Delegate Public Key",
|
||||
"delegate-updated": "Delegate Updated",
|
||||
"set-error": "Could not set Delegate",
|
||||
"invalid-key": "Invalid public key"
|
||||
}
|
|
@ -1,3 +1,5 @@
|
|||
|
||||
|
||||
{
|
||||
"about-to-withdraw": "Estas a punto de retirar",
|
||||
"above": "Encima",
|
||||
|
@ -135,11 +137,17 @@
|
|||
"export-data-empty": "No hay datos para exportar",
|
||||
"export-data-success": "CSV exportado con éxito",
|
||||
"export-pnl-csv": "Export PnL CSV",
|
||||
"export-trades-csv": "Export Trades CSV",
|
||||
"favorite": "Favorito",
|
||||
"favorites": "Favoritos",
|
||||
"fee": "Tarifa",
|
||||
"fees": "Fees",
|
||||
"fee-discount": "Comisiones",
|
||||
"filter": "Filter",
|
||||
"filters-selected": "{{selectedFilters}} selected",
|
||||
"filter-trade-history": "Filter Trade History",
|
||||
"first-deposit-desc": "Necesita 0.035 SOL para crear una cuenta de mango.",
|
||||
"from": "From",
|
||||
"funding": "Fondos",
|
||||
"funding-chart-title": "Fondos (últimos 30 días)",
|
||||
"futures": "Futuros",
|
||||
|
@ -155,6 +163,8 @@
|
|||
"hourly-borrow-interest": "Interés por préstamo por hora",
|
||||
"hourly-deposit-interest": "Interés por depósito por hora",
|
||||
"hourly-funding": "Financiamiento por hora",
|
||||
"if-referred": "{{fee}} if referred or 10k MNGO",
|
||||
"if-referred-tooltip": "If you create your Mango Account from a referral link or have 10k MNGO in your Mango Account you get a 0.04% discount off futures fees.",
|
||||
"in-orders": "En órdenes",
|
||||
"includes-borrow": "Incluye el préstamo",
|
||||
"init-error": "No se pudo realizar la operación de depósito y cuenta de margen inicial",
|
||||
|
@ -179,6 +189,7 @@
|
|||
"layout-tip-title": "Personalizar diseño",
|
||||
"learn": "Aprender",
|
||||
"learn-more": "Aprender más",
|
||||
"lend": "Lend",
|
||||
"lets-go": "Vamos",
|
||||
"leverage": "Apalancamiento",
|
||||
"leverage-too-high": "Apalancamiento demasiado alto. Reducir la cantidad a retirar",
|
||||
|
@ -246,6 +257,7 @@
|
|||
"no-markets": "No se encontraron mercados",
|
||||
"no-orders": "No hay órdenes abiertas",
|
||||
"no-perp": "No hay puestos de delincuentes",
|
||||
"no-trades-found": "No trades found...",
|
||||
"no-unsettled": "No hay fondos pendientes",
|
||||
"no-wallet": "Sin dirección de billetera",
|
||||
"node-url": "URL del nodo RPC",
|
||||
|
@ -260,6 +272,7 @@
|
|||
"orderbook": "Libro de órdenes",
|
||||
"orderbook-animation": "Animación del libro de ordenes",
|
||||
"orders": "Órdenes",
|
||||
"other": "Other",
|
||||
"performance": "Performance",
|
||||
"performance-insights": "Performance Insights",
|
||||
"period-progress": "Period Progress",
|
||||
|
@ -307,6 +320,7 @@
|
|||
"repay-partial": "Pagar el {{porcentaje}}% del préstamo",
|
||||
"reposition": "Arrastra para reposicionar",
|
||||
"reset": "Reset",
|
||||
"reset-filters": "Reset Filters",
|
||||
"rolling-change": "24hr Change",
|
||||
"rpc-endpoint": "Punto final de RPC",
|
||||
"save": "Ahorrar",
|
||||
|
@ -353,6 +367,7 @@
|
|||
"themes-tip-title": "Temas de color",
|
||||
"time": "Tiempo",
|
||||
"timeframe-desc": "Last {{timeframe}}",
|
||||
"to": "To",
|
||||
"token": "Simbólico",
|
||||
"too-large": "Tamaño demasiado grande",
|
||||
"tooltip-account-liquidated": "La cuenta se liquidará si la relación de salud alcanza el 0% y continuará hasta que la salud inicial sea superior a 0.",
|
||||
|
@ -403,6 +418,7 @@
|
|||
"unsettled-balance": "Saldo pendiente",
|
||||
"unsettled-balances": "Saldos pendientes",
|
||||
"unsettled-positions": "Posiciones sin saldar",
|
||||
"update-filters": "Update Filters",
|
||||
"use-explorer-one": "Usar el ",
|
||||
"use-explorer-three": "para verificar cualquier transacción retrasada.",
|
||||
"use-explorer-two": "Explorer ",
|
||||
|
|
|
@ -5,6 +5,5 @@
|
|||
"info": "Grant control to another Solana account to use Mango on your behalf.",
|
||||
"public-key": "Delegate Public Key",
|
||||
"delegate-updated": "Delegate Updated",
|
||||
"set-error": "Could not set Delegate",
|
||||
"invalid-key": "Invalid public key"
|
||||
"set-error": "Could not set Delegate"
|
||||
}
|
|
@ -135,11 +135,17 @@
|
|||
"export-data-empty": "无资料可导出",
|
||||
"export-data-success": "CSV导出成功",
|
||||
"export-pnl-csv": "导出盈亏CSV",
|
||||
"export-trades-csv": "Export Trades CSV",
|
||||
"favorite": "喜爱",
|
||||
"favorites": "喜爱",
|
||||
"fee": "费率",
|
||||
"fees": "Fees",
|
||||
"fee-discount": "费率折扣",
|
||||
"filter": "Filter",
|
||||
"filters-selected": "{{selectedFilters}} selected",
|
||||
"filter-trade-history": "Filter Trade History",
|
||||
"first-deposit-desc": "创建Mango帐户最少需要0.035 SOL。",
|
||||
"from": "From",
|
||||
"funding": "资金费",
|
||||
"funding-chart-title": "资金费 – 前30天(图表有点延迟)",
|
||||
"futures": "永续合约",
|
||||
|
@ -155,6 +161,8 @@
|
|||
"hourly-borrow-interest": "1小时借贷利息",
|
||||
"hourly-deposit-interest": "1小时存款利息",
|
||||
"hourly-funding": "1小时资金费",
|
||||
"if-referred": "{{fee}} if referred or 10k MNGO",
|
||||
"if-referred-tooltip": "If you create your Mango Account from a referral link or have 10k MNGO in your Mango Account you get a 0.04% discount off futures fees.",
|
||||
"in-orders": "在掛单中",
|
||||
"includes-borrow": "包括存入",
|
||||
"init-error": "创建Mango帐户与存款出错了",
|
||||
|
@ -179,6 +187,7 @@
|
|||
"layout-tip-title": "个人化页面布局",
|
||||
"learn": "学习",
|
||||
"learn-more": "学习",
|
||||
"lend": "Lend",
|
||||
"lets-go": "前往",
|
||||
"leverage": "杠杆",
|
||||
"leverage-too-high": "杠杆太高。请减少取款数量",
|
||||
|
@ -246,6 +255,7 @@
|
|||
"no-markets": "无市场",
|
||||
"no-orders": "您没有订单",
|
||||
"no-perp": "您没有永续合约持仓",
|
||||
"no-trades-found": "No trades found...",
|
||||
"no-unsettled": "您没有未结清金额",
|
||||
"no-wallet": "没有钱包地址",
|
||||
"node-url": "RPC终点URL",
|
||||
|
@ -260,6 +270,7 @@
|
|||
"orderbook": "订单簿",
|
||||
"orderbook-animation": "订单动画",
|
||||
"orders": "订单",
|
||||
"other": "Other",
|
||||
"performance": "表现",
|
||||
"performance-insights": "表现分析",
|
||||
"period-progress": "期间进度",
|
||||
|
@ -307,6 +318,7 @@
|
|||
"repay-partial": "归还{{percentage}}%借贷",
|
||||
"reposition": "推动以重新定位",
|
||||
"reset": "重置",
|
||||
"reset-filters": "Reset Filters",
|
||||
"rolling-change": "24小时变动",
|
||||
"rpc-endpoint": "RPC终点",
|
||||
"save": "保存",
|
||||
|
@ -353,6 +365,7 @@
|
|||
"themes-tip-title": "颜色模式",
|
||||
"time": "时间",
|
||||
"timeframe-desc": "前{{timeframe}}",
|
||||
"to": "To",
|
||||
"token": "币种",
|
||||
"too-large": "数量太大",
|
||||
"tooltip-account-liquidated": "若帐户健康度降到0%您的帐户会被清算直到初始健康度达到0以上了。",
|
||||
|
@ -403,6 +416,7 @@
|
|||
"unsettled-balance": "未实现盈亏",
|
||||
"unsettled-balances": "未结清余额",
|
||||
"unsettled-positions": "未结清持仓",
|
||||
"update-filters": "Update Filters",
|
||||
"use-explorer-one": "使用",
|
||||
"use-explorer-three": "来验证延迟的交易",
|
||||
"use-explorer-two": "浏览器",
|
||||
|
|
|
@ -5,6 +5,5 @@
|
|||
"info": "将此帐户委托其他Solana帐户控制。",
|
||||
"public-key": "受托钱包地址",
|
||||
"delegate-updated": "已更换受托钱包",
|
||||
"set-error": "设置委托钱包出错",
|
||||
"invalid-key": "您输入的地址有问题"
|
||||
"set-error": "设置委托钱包出错"
|
||||
}
|
|
@ -135,11 +135,17 @@
|
|||
"export-data-empty": "無資料可導出",
|
||||
"export-data-success": "CSV導出成功",
|
||||
"export-pnl-csv": "導出盈虧CSV",
|
||||
"export-trades-csv": "Export Trades CSV",
|
||||
"favorite": "喜愛",
|
||||
"favorites": "喜愛",
|
||||
"fee": "費率",
|
||||
"fees": "Fees",
|
||||
"fee-discount": "費率折扣",
|
||||
"filter": "Filter",
|
||||
"filters-selected": "{{selectedFilters}} selected",
|
||||
"filter-trade-history": "Filter Trade History",
|
||||
"first-deposit-desc": "創建Mango帳戶最少需要0.035 SOL。",
|
||||
"from": "From",
|
||||
"funding": "資金費",
|
||||
"funding-chart-title": "資金費 – 前30天(圖表有點延遲)",
|
||||
"futures": "永續合約",
|
||||
|
@ -155,6 +161,8 @@
|
|||
"hourly-borrow-interest": "1小時借貸利息",
|
||||
"hourly-deposit-interest": "1小時存款利息",
|
||||
"hourly-funding": "1小時資金費",
|
||||
"if-referred": "{{fee}} if referred or 10k MNGO",
|
||||
"if-referred-tooltip": "If you create your Mango Account from a referral link or have 10k MNGO in your Mango Account you get a 0.04% discount off futures fees.",
|
||||
"in-orders": "在掛單中",
|
||||
"includes-borrow": "包括存入",
|
||||
"init-error": "創建Mango帳戶與存款出錯了",
|
||||
|
@ -179,6 +187,7 @@
|
|||
"layout-tip-title": "個人化頁面佈局",
|
||||
"learn": "學習",
|
||||
"learn-more": "學習",
|
||||
"lend": "Lend",
|
||||
"lets-go": "前往",
|
||||
"leverage": "槓桿",
|
||||
"leverage-too-high": "槓桿太高。請減少取款數量",
|
||||
|
@ -246,6 +255,7 @@
|
|||
"no-markets": "無市場",
|
||||
"no-orders": "您沒有訂單",
|
||||
"no-perp": "您沒有永續合約持倉",
|
||||
"no-trades-found": "No trades found...",
|
||||
"no-unsettled": "您沒有未結清金額",
|
||||
"no-wallet": "沒有錢包地址",
|
||||
"node-url": "RPC終點URL",
|
||||
|
@ -260,6 +270,7 @@
|
|||
"orderbook": "掛單簿",
|
||||
"orderbook-animation": "訂單動畫",
|
||||
"orders": "訂單",
|
||||
"other": "Other",
|
||||
"performance": "表現",
|
||||
"performance-insights": "表現分析",
|
||||
"period-progress": "期間進度",
|
||||
|
@ -307,6 +318,7 @@
|
|||
"repay-partial": "歸還{{percentage}}%借貸",
|
||||
"reposition": "推動以重新定位",
|
||||
"reset": "重置",
|
||||
"reset-filters": "Reset Filters",
|
||||
"rolling-change": "24小時變動",
|
||||
"rpc-endpoint": "RPC終點",
|
||||
"save": "保存",
|
||||
|
@ -353,6 +365,7 @@
|
|||
"themes-tip-title": "顏色模式",
|
||||
"time": "時間",
|
||||
"timeframe-desc": "前{{timeframe}}",
|
||||
"to": "To",
|
||||
"token": "幣種",
|
||||
"too-large": "數量太大",
|
||||
"tooltip-account-liquidated": "若帳戶健康度降到0%您的帳戶會被清算直到初始健康度達到0以上了。",
|
||||
|
@ -403,6 +416,7 @@
|
|||
"unsettled-balance": "未實現盈虧",
|
||||
"unsettled-balances": "未結清餘額",
|
||||
"unsettled-positions": "未結清持倉",
|
||||
"update-filters": "Update Filters",
|
||||
"use-explorer-one": "使用",
|
||||
"use-explorer-three": "來驗證延遲的交易",
|
||||
"use-explorer-two": "瀏覽器",
|
||||
|
|
|
@ -5,6 +5,5 @@
|
|||
"info": "將此帳戶委託其他Solana帳戶控制。",
|
||||
"public-key": "受託錢包地址",
|
||||
"delegate-updated": "已更換受託錢包",
|
||||
"set-error": "設置委託錢包出錯",
|
||||
"invalid-key": "您輸入的地址有問題"
|
||||
"set-error": "設置委託錢包出錯"
|
||||
}
|
|
@ -43,8 +43,3 @@ export const setStoreSelector = (state: MangoStore) => state.set
|
|||
export const accountInfosSelector = (state: MangoStore) => state.accountInfos
|
||||
|
||||
export const tradeHistorySelector = (state: MangoStore) => state.tradeHistory
|
||||
|
||||
export const walletSelector = (state: MangoStore) => state.wallet.current
|
||||
|
||||
export const walletConnectedSelector = (state: MangoStore) =>
|
||||
state.wallet.connected
|
||||
|
|
|
@ -26,7 +26,7 @@ import {
|
|||
BlockhashTimes,
|
||||
} from '@blockworks-foundation/mango-client'
|
||||
import { AccountInfo, Commitment, Connection, PublicKey } from '@solana/web3.js'
|
||||
import { EndpointInfo, WalletAdapter } from '../@types/types'
|
||||
import { EndpointInfo } from '../@types/types'
|
||||
import { isDefined, zipDict } from '../utils'
|
||||
import { Notification, notify } from '../utils/notifications'
|
||||
import { LAST_ACCOUNT_KEY } from '../components/AccountsModal'
|
||||
|
@ -39,6 +39,7 @@ import { MSRM_DECIMALS } from '@project-serum/serum/lib/token-instructions'
|
|||
import { getProfilePicture, ProfilePicture } from '@solflare-wallet/pfp'
|
||||
import { decodeBook } from '../hooks/useHydrateStore'
|
||||
import { IOrderLineAdapter } from '../public/charting_library/charting_library'
|
||||
import { Wallet } from '@solana/wallet-adapter-react'
|
||||
|
||||
export const ENDPOINTS: EndpointInfo[] = [
|
||||
{
|
||||
|
@ -191,9 +192,6 @@ export type MangoStore = {
|
|||
triggerCondition: 'above' | 'below'
|
||||
}
|
||||
wallet: {
|
||||
providerUrl: string
|
||||
connected: boolean
|
||||
current: WalletAdapter | undefined
|
||||
tokens: WalletToken[] | any[]
|
||||
pfp: ProfilePicture | undefined
|
||||
}
|
||||
|
@ -201,13 +199,14 @@ export type MangoStore = {
|
|||
uiLocked: boolean
|
||||
}
|
||||
tradeHistory: {
|
||||
initialLoad: boolean
|
||||
spot: any[]
|
||||
perp: any[]
|
||||
parsed: any[]
|
||||
}
|
||||
set: (x: (x: MangoStore) => void) => void
|
||||
actions: {
|
||||
fetchAllMangoAccounts: () => Promise<void>
|
||||
fetchAllMangoAccounts: (wallet: Wallet) => Promise<void>
|
||||
fetchMangoGroup: () => Promise<void>
|
||||
[key: string]: (args?) => void
|
||||
}
|
||||
|
@ -323,9 +322,6 @@ const useMangoStore = create<
|
|||
triggerCondition: 'above',
|
||||
},
|
||||
wallet: {
|
||||
providerUrl: '',
|
||||
connected: false,
|
||||
current: undefined,
|
||||
tokens: [],
|
||||
pfp: undefined,
|
||||
},
|
||||
|
@ -341,6 +337,7 @@ const useMangoStore = create<
|
|||
success: '',
|
||||
},
|
||||
tradeHistory: {
|
||||
initialLoad: false,
|
||||
spot: [],
|
||||
perp: [],
|
||||
parsed: [],
|
||||
|
@ -350,19 +347,18 @@ const useMangoStore = create<
|
|||
},
|
||||
set: (fn) => set(produce(fn)),
|
||||
actions: {
|
||||
async fetchWalletTokens() {
|
||||
async fetchWalletTokens(wallet: Wallet) {
|
||||
const groupConfig = get().selectedMangoGroup.config
|
||||
const wallet = get().wallet.current
|
||||
const connected = get().wallet.connected
|
||||
const connected = wallet?.adapter?.connected
|
||||
const connection = get().connection.current
|
||||
const cluster = get().connection.cluster
|
||||
const set = get().set
|
||||
|
||||
if (wallet?.publicKey && connected) {
|
||||
if (wallet?.adapter?.publicKey && connected) {
|
||||
const ownedTokenAccounts =
|
||||
await getTokenAccountsByOwnerWithWrappedSol(
|
||||
connection,
|
||||
wallet.publicKey
|
||||
wallet.adapter.publicKey
|
||||
)
|
||||
const tokens = []
|
||||
ownedTokenAccounts.forEach((account) => {
|
||||
|
@ -393,10 +389,9 @@ const useMangoStore = create<
|
|||
})
|
||||
}
|
||||
},
|
||||
async fetchProfilePicture() {
|
||||
async fetchProfilePicture(wallet: Wallet) {
|
||||
const set = get().set
|
||||
const wallet = get().wallet.current
|
||||
const walletPk = wallet?.publicKey
|
||||
const walletPk = wallet?.adapter?.publicKey
|
||||
const connection = get().connection.current
|
||||
|
||||
if (!walletPk) return
|
||||
|
@ -411,30 +406,29 @@ const useMangoStore = create<
|
|||
console.log('Could not get profile picture', e)
|
||||
}
|
||||
},
|
||||
async fetchAllMangoAccounts() {
|
||||
async fetchAllMangoAccounts(wallet) {
|
||||
const set = get().set
|
||||
const mangoGroup = get().selectedMangoGroup.current
|
||||
const mangoClient = get().connection.client
|
||||
const wallet = get().wallet.current
|
||||
const actions = get().actions
|
||||
|
||||
if (!wallet?.adapter?.publicKey || !mangoGroup) return
|
||||
|
||||
const delegateFilter = [
|
||||
{
|
||||
memcmp: {
|
||||
offset: MangoAccountLayout.offsetOf('delegate'),
|
||||
bytes: wallet?.publicKey.toBase58(),
|
||||
bytes: wallet.adapter.publicKey?.toBase58(),
|
||||
},
|
||||
},
|
||||
]
|
||||
const accountSorter = (a, b) =>
|
||||
a.publicKey.toBase58() > b.publicKey.toBase58() ? 1 : -1
|
||||
|
||||
if (!wallet?.publicKey || !mangoGroup) return
|
||||
|
||||
return Promise.all([
|
||||
mangoClient.getMangoAccountsForOwner(
|
||||
mangoGroup,
|
||||
wallet?.publicKey,
|
||||
wallet.adapter.publicKey,
|
||||
true
|
||||
),
|
||||
mangoClient.getAllMangoAccounts(mangoGroup, delegateFilter, false),
|
||||
|
@ -472,7 +466,7 @@ const useMangoStore = create<
|
|||
})
|
||||
.catch((err) => {
|
||||
if (mangoAccountRetryAttempt < 2) {
|
||||
actions.fetchAllMangoAccounts()
|
||||
actions.fetchAllMangoAccounts(wallet)
|
||||
mangoAccountRetryAttempt++
|
||||
} else {
|
||||
notify({
|
||||
|
@ -662,6 +656,9 @@ const useMangoStore = create<
|
|||
console.error('Error fetching trade history', e)
|
||||
})
|
||||
}
|
||||
set((state) => {
|
||||
state.tradeHistory.initialLoad = true
|
||||
})
|
||||
},
|
||||
async reloadMangoAccount() {
|
||||
const set = get().set
|
||||
|
|
|
@ -0,0 +1,45 @@
|
|||
/* Date Picker */
|
||||
|
||||
.react-datepicker {
|
||||
@apply rounded-md border-0 bg-th-bkg-3 font-body text-th-fgd-2 ring-1 ring-th-fgd-4;
|
||||
}
|
||||
|
||||
.react-datepicker-popper[data-placement^='bottom'] {
|
||||
@apply pt-1;
|
||||
}
|
||||
|
||||
.react-datepicker__header {
|
||||
@apply rounded-tl-md border-none bg-th-bkg-1 text-th-fgd-1;
|
||||
}
|
||||
|
||||
.react-datepicker__header:not(.react-datepicker__header--has-time-select) {
|
||||
@apply rounded-tr-md;
|
||||
}
|
||||
|
||||
.react-datepicker__current-month {
|
||||
@apply text-th-fgd-1;
|
||||
}
|
||||
|
||||
.react-datepicker__day-name {
|
||||
@apply text-th-fgd-2;
|
||||
}
|
||||
|
||||
.react-datepicker__triangle {
|
||||
@apply hidden;
|
||||
}
|
||||
|
||||
.react-datepicker__day {
|
||||
@apply text-th-fgd-2 hover:bg-th-bkg-4;
|
||||
}
|
||||
|
||||
.react-datepicker__day--selected {
|
||||
@apply bg-th-primary text-th-bkg-1 hover:bg-th-primary hover:text-th-bkg-1;
|
||||
}
|
||||
|
||||
.react-datepicker__day--keyboard-selected {
|
||||
@apply bg-transparent ring-2 ring-inset ring-th-fgd-4;
|
||||
}
|
||||
|
||||
.react-datepicker__day--today {
|
||||
@apply rounded-md ring-2 ring-inset ring-th-fgd-4;
|
||||
}
|
|
@ -13,7 +13,8 @@
|
|||
"resolveJsonModule": true,
|
||||
"jsx": "preserve",
|
||||
"isolatedModules": true,
|
||||
"incremental": true
|
||||
"incremental": true,
|
||||
"baseUrl": "."
|
||||
},
|
||||
"exclude": ["node_modules", ".next", "out", "public/datafeeds"],
|
||||
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", "**/*.js"]
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import { MangoAccount, TokenAccount } from '@blockworks-foundation/mango-client'
|
||||
import { Wallet } from '@solana/wallet-adapter-react'
|
||||
import { PublicKey } from '@solana/web3.js'
|
||||
import useMangoStore from '../stores/useMangoStore'
|
||||
|
||||
|
@ -7,24 +8,24 @@ export async function deposit({
|
|||
fromTokenAcc,
|
||||
mangoAccount,
|
||||
accountName,
|
||||
wallet,
|
||||
}: {
|
||||
amount: number
|
||||
fromTokenAcc: TokenAccount
|
||||
mangoAccount?: MangoAccount
|
||||
accountName?: string
|
||||
wallet: Wallet
|
||||
}) {
|
||||
const mangoGroup = useMangoStore.getState().selectedMangoGroup.current
|
||||
const wallet = useMangoStore.getState().wallet.current
|
||||
const tokenIndex = mangoGroup.getTokenIndex(fromTokenAcc.mint)
|
||||
const mangoClient = useMangoStore.getState().connection.client
|
||||
const referrer = useMangoStore.getState().referrerPk
|
||||
console.log('referrerPk', referrer)
|
||||
|
||||
if (mangoAccount) {
|
||||
return await mangoClient.deposit(
|
||||
mangoGroup,
|
||||
mangoAccount,
|
||||
wallet,
|
||||
wallet?.adapter,
|
||||
mangoGroup.tokens[tokenIndex].rootBank,
|
||||
mangoGroup.rootBankAccounts[tokenIndex].nodeBankAccounts[0].publicKey,
|
||||
mangoGroup.rootBankAccounts[tokenIndex].nodeBankAccounts[0].vault,
|
||||
|
@ -34,13 +35,13 @@ export async function deposit({
|
|||
} else {
|
||||
const existingAccounts = await mangoClient.getMangoAccountsForOwner(
|
||||
mangoGroup,
|
||||
wallet.publicKey,
|
||||
wallet?.adapter?.publicKey,
|
||||
false
|
||||
)
|
||||
console.log('in deposit and create, referrer is', referrer)
|
||||
return await mangoClient.createMangoAccountAndDeposit(
|
||||
mangoGroup,
|
||||
wallet,
|
||||
wallet?.adapter,
|
||||
mangoGroup.tokens[tokenIndex].rootBank,
|
||||
mangoGroup.rootBankAccounts[tokenIndex].nodeBankAccounts[0].publicKey,
|
||||
mangoGroup.rootBankAccounts[tokenIndex].nodeBankAccounts[0].vault,
|
||||
|
@ -57,21 +58,22 @@ export async function withdraw({
|
|||
amount,
|
||||
token,
|
||||
allowBorrow,
|
||||
wallet,
|
||||
}: {
|
||||
amount: number
|
||||
token: PublicKey
|
||||
allowBorrow: boolean
|
||||
wallet: Wallet
|
||||
}) {
|
||||
const mangoAccount = useMangoStore.getState().selectedMangoAccount.current
|
||||
const mangoGroup = useMangoStore.getState().selectedMangoGroup.current
|
||||
const wallet = useMangoStore.getState().wallet.current
|
||||
const tokenIndex = mangoGroup.getTokenIndex(token)
|
||||
const mangoClient = useMangoStore.getState().connection.client
|
||||
|
||||
return await mangoClient.withdraw(
|
||||
mangoGroup,
|
||||
mangoAccount,
|
||||
wallet,
|
||||
wallet?.adapter,
|
||||
mangoGroup.tokens[tokenIndex].rootBank,
|
||||
mangoGroup.rootBankAccounts[tokenIndex].nodeBankAccounts[0].publicKey,
|
||||
mangoGroup.rootBankAccounts[tokenIndex].nodeBankAccounts[0].vault,
|
||||
|
|
|
@ -1,116 +0,0 @@
|
|||
import EventEmitter from 'eventemitter3'
|
||||
import { PublicKey, Transaction } from '@solana/web3.js'
|
||||
import { notify } from '../../utils/notifications'
|
||||
import { WalletAdapter } from '../../@types/types'
|
||||
|
||||
interface BitpieWallet {
|
||||
getAccount(): Promise<string>
|
||||
signTransaction(transaction: Transaction): Promise<Transaction>
|
||||
signAllTransactions(transactions: Transaction[]): Promise<Transaction[]>
|
||||
}
|
||||
|
||||
interface BitpieWalletWindow extends Window {
|
||||
bitpie?: BitpieWallet
|
||||
}
|
||||
|
||||
declare const window: BitpieWalletWindow
|
||||
|
||||
export class BitpieWalletAdapter extends EventEmitter implements WalletAdapter {
|
||||
private _connecting: boolean
|
||||
private _wallet: BitpieWallet | null
|
||||
private _publicKey: PublicKey | null
|
||||
|
||||
constructor() {
|
||||
super()
|
||||
this._connecting = false
|
||||
this._wallet = null
|
||||
this._publicKey = null
|
||||
}
|
||||
|
||||
get publicKey(): PublicKey | null {
|
||||
return this._publicKey
|
||||
}
|
||||
|
||||
get ready(): boolean {
|
||||
return typeof window !== 'undefined' && !!window.bitpie
|
||||
}
|
||||
|
||||
get connecting(): boolean {
|
||||
return this._connecting
|
||||
}
|
||||
|
||||
get connected(): boolean {
|
||||
return !!this._wallet
|
||||
}
|
||||
|
||||
get autoApprove() {
|
||||
return true
|
||||
}
|
||||
|
||||
async connect(): Promise<void> {
|
||||
try {
|
||||
if (this.connected || this.connecting) return
|
||||
this._connecting = true
|
||||
|
||||
const wallet = typeof window !== 'undefined' && window.bitpie
|
||||
if (!wallet) return
|
||||
|
||||
let account: string
|
||||
try {
|
||||
account = await wallet.getAccount()
|
||||
} catch (error: any) {
|
||||
notify({
|
||||
title: 'Connection Error',
|
||||
type: 'error',
|
||||
description:
|
||||
'Please install Bitpie wallet and then reload this page.',
|
||||
})
|
||||
}
|
||||
|
||||
this._wallet = wallet
|
||||
this._publicKey = new PublicKey(account)
|
||||
|
||||
this.emit('connect')
|
||||
} catch (error: any) {
|
||||
this.emit('error', error)
|
||||
throw error
|
||||
} finally {
|
||||
this._connecting = false
|
||||
}
|
||||
}
|
||||
|
||||
async disconnect(): Promise<void> {
|
||||
if (this._wallet) {
|
||||
this._wallet = null
|
||||
this._publicKey = null
|
||||
}
|
||||
|
||||
this.emit('disconnect')
|
||||
}
|
||||
|
||||
async signTransaction(transaction: Transaction): Promise<Transaction> {
|
||||
try {
|
||||
const wallet = this._wallet
|
||||
if (!wallet) return
|
||||
|
||||
return (await wallet.signTransaction(transaction)) || transaction
|
||||
} catch (error: any) {
|
||||
this.emit('error', error)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
async signAllTransactions(
|
||||
transactions: Transaction[]
|
||||
): Promise<Transaction[]> {
|
||||
try {
|
||||
const wallet = this._wallet
|
||||
if (!wallet) return
|
||||
|
||||
return (await wallet.signAllTransactions(transactions)) || transactions
|
||||
} catch (error: any) {
|
||||
this.emit('error', error)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,101 +0,0 @@
|
|||
import EventEmitter from 'eventemitter3'
|
||||
import { PublicKey, Transaction } from '@solana/web3.js'
|
||||
import { notify } from '../../utils/notifications'
|
||||
import { DEFAULT_PUBLIC_KEY, WalletAdapter } from '../../@types/types'
|
||||
|
||||
type GlowEvent = 'disconnect' | 'connect'
|
||||
type GlowRequestMethod =
|
||||
| 'connect'
|
||||
| 'disconnect'
|
||||
| 'signTransaction'
|
||||
| 'signAllTransactions'
|
||||
|
||||
interface GlowProvider {
|
||||
publicKey?: PublicKey
|
||||
isConnected?: boolean
|
||||
autoApprove?: boolean
|
||||
signTransaction: (transaction: Transaction) => Promise<Transaction>
|
||||
signAllTransactions: (transactions: Transaction[]) => Promise<Transaction[]>
|
||||
connect: () => Promise<void>
|
||||
disconnect: () => Promise<void>
|
||||
on: (event: GlowEvent, handler: (args: any) => void) => void
|
||||
request: (method: GlowRequestMethod, params: any) => Promise<any>
|
||||
listeners: (event: GlowEvent) => (() => void)[]
|
||||
}
|
||||
|
||||
export class GlowWalletAdapter extends EventEmitter implements WalletAdapter {
|
||||
constructor() {
|
||||
super()
|
||||
this.connect = this.connect.bind(this)
|
||||
}
|
||||
|
||||
private get _provider(): GlowProvider | undefined {
|
||||
if ((window as any)?.glowSolana?.isGlow) {
|
||||
return (window as any).glowSolana
|
||||
}
|
||||
return undefined
|
||||
}
|
||||
|
||||
private _handleConnect = (...args) => {
|
||||
this.emit('connect', ...args)
|
||||
}
|
||||
|
||||
private _handleDisconnect = (...args) => {
|
||||
this.emit('disconnect', ...args)
|
||||
}
|
||||
|
||||
get connected() {
|
||||
return this._provider?.isConnected || false
|
||||
}
|
||||
|
||||
get autoApprove() {
|
||||
return this._provider?.autoApprove || false
|
||||
}
|
||||
|
||||
async signAllTransactions(
|
||||
transactions: Transaction[]
|
||||
): Promise<Transaction[]> {
|
||||
if (!this._provider) {
|
||||
return transactions
|
||||
}
|
||||
|
||||
return this._provider.signAllTransactions(transactions)
|
||||
}
|
||||
|
||||
get publicKey() {
|
||||
return this._provider?.publicKey || DEFAULT_PUBLIC_KEY
|
||||
}
|
||||
|
||||
async signTransaction(transaction: Transaction) {
|
||||
if (!this._provider) {
|
||||
return transaction
|
||||
}
|
||||
|
||||
return this._provider.signTransaction(transaction)
|
||||
}
|
||||
|
||||
connect() {
|
||||
if (!this._provider) {
|
||||
window.open('https://glow.app/', '_blank')
|
||||
notify({
|
||||
title: 'Connection Error',
|
||||
type: 'error',
|
||||
description: 'Please install Glow wallet and then reload this page.',
|
||||
})
|
||||
return
|
||||
}
|
||||
if (!this._provider.listeners('connect').length) {
|
||||
this._provider?.on('connect', this._handleConnect)
|
||||
}
|
||||
if (!this._provider.listeners('disconnect').length) {
|
||||
this._provider?.on('disconnect', this._handleDisconnect)
|
||||
}
|
||||
return this._provider?.connect()
|
||||
}
|
||||
|
||||
disconnect() {
|
||||
if (this._provider) {
|
||||
this._provider.disconnect()
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,94 +0,0 @@
|
|||
import EventEmitter from 'eventemitter3'
|
||||
import { PublicKey, Transaction } from '@solana/web3.js'
|
||||
import { DEFAULT_PUBLIC_KEY, WalletAdapter } from '../../@types/types'
|
||||
|
||||
type HuobiEvent = 'disconnect' | 'connect'
|
||||
type HuobiRequestMethod =
|
||||
| 'connect'
|
||||
| 'disconnect'
|
||||
| 'signTransaction'
|
||||
| 'signAllTransactions'
|
||||
|
||||
interface HuobiProvider {
|
||||
publicKey?: PublicKey
|
||||
isConnected?: boolean
|
||||
autoApprove?: boolean
|
||||
signTransaction: (transaction: Transaction) => Promise<Transaction>
|
||||
signAllTransactions: (transactions: Transaction[]) => Promise<Transaction[]>
|
||||
connect: () => Promise<void>
|
||||
disconnect: () => Promise<void>
|
||||
on: (event: HuobiEvent, handler: (args: any) => void) => void
|
||||
request: (method: HuobiRequestMethod, params: any) => Promise<any>
|
||||
listeners: (event: HuobiEvent) => (() => void)[]
|
||||
}
|
||||
|
||||
export class HuobiWalletAdapter extends EventEmitter implements WalletAdapter {
|
||||
constructor() {
|
||||
super()
|
||||
this.connect = this.connect.bind(this)
|
||||
}
|
||||
|
||||
private get _provider(): HuobiProvider | undefined {
|
||||
if ((window as any)?.huobiWallet?.isHuobiWallet) {
|
||||
return (window as any).huobiWallet
|
||||
}
|
||||
return undefined
|
||||
}
|
||||
|
||||
private _handleConnect = (...args) => {
|
||||
this.emit('connect', ...args)
|
||||
}
|
||||
|
||||
private _handleDisconnect = (...args) => {
|
||||
this.emit('disconnect', ...args)
|
||||
}
|
||||
|
||||
get connected() {
|
||||
return this._provider?.isConnected || false
|
||||
}
|
||||
|
||||
get autoApprove() {
|
||||
return this._provider?.autoApprove || false
|
||||
}
|
||||
|
||||
async signAllTransactions(
|
||||
transactions: Transaction[]
|
||||
): Promise<Transaction[]> {
|
||||
if (!this._provider) {
|
||||
return transactions
|
||||
}
|
||||
|
||||
return this._provider.signAllTransactions(transactions)
|
||||
}
|
||||
|
||||
get publicKey() {
|
||||
return this._provider?.publicKey || DEFAULT_PUBLIC_KEY
|
||||
}
|
||||
|
||||
async signTransaction(transaction: Transaction) {
|
||||
if (!this._provider) {
|
||||
return transaction
|
||||
}
|
||||
|
||||
return this._provider.signTransaction(transaction)
|
||||
}
|
||||
|
||||
connect() {
|
||||
if (!this._provider) {
|
||||
return
|
||||
}
|
||||
if (!this._provider.listeners('connect').length) {
|
||||
this._provider?.on('connect', this._handleConnect)
|
||||
}
|
||||
if (!this._provider.listeners('disconnect').length) {
|
||||
this._provider?.on('disconnect', this._handleDisconnect)
|
||||
}
|
||||
return this._provider?.connect()
|
||||
}
|
||||
|
||||
disconnect() {
|
||||
if (this._provider) {
|
||||
this._provider.disconnect()
|
||||
}
|
||||
}
|
||||
}
|
File diff suppressed because one or more lines are too long
|
@ -1,104 +0,0 @@
|
|||
import EventEmitter from 'eventemitter3'
|
||||
import { PublicKey, Transaction } from '@solana/web3.js'
|
||||
import { notify } from '../../utils/notifications'
|
||||
import { DEFAULT_PUBLIC_KEY, WalletAdapter } from '../../@types/types'
|
||||
|
||||
type PhantomEvent = 'disconnect' | 'connect'
|
||||
type PhantomRequestMethod =
|
||||
| 'connect'
|
||||
| 'disconnect'
|
||||
| 'signTransaction'
|
||||
| 'signAllTransactions'
|
||||
|
||||
interface PhantomProvider {
|
||||
publicKey?: PublicKey
|
||||
isConnected?: boolean
|
||||
autoApprove?: boolean
|
||||
signTransaction: (transaction: Transaction) => Promise<Transaction>
|
||||
signAllTransactions: (transactions: Transaction[]) => Promise<Transaction[]>
|
||||
connect: () => Promise<void>
|
||||
disconnect: () => Promise<void>
|
||||
on: (event: PhantomEvent, handler: (args: any) => void) => void
|
||||
request: (method: PhantomRequestMethod, params: any) => Promise<any>
|
||||
listeners: (event: PhantomEvent) => (() => void)[]
|
||||
}
|
||||
|
||||
export class PhantomWalletAdapter
|
||||
extends EventEmitter
|
||||
implements WalletAdapter
|
||||
{
|
||||
constructor() {
|
||||
super()
|
||||
this.connect = this.connect.bind(this)
|
||||
}
|
||||
|
||||
private get _provider(): PhantomProvider | undefined {
|
||||
if ((window as any)?.solana?.isPhantom) {
|
||||
return (window as any).solana
|
||||
}
|
||||
return undefined
|
||||
}
|
||||
|
||||
private _handleConnect = (...args) => {
|
||||
this.emit('connect', ...args)
|
||||
}
|
||||
|
||||
private _handleDisconnect = (...args) => {
|
||||
this.emit('disconnect', ...args)
|
||||
}
|
||||
|
||||
get connected() {
|
||||
return this._provider?.isConnected || false
|
||||
}
|
||||
|
||||
get autoApprove() {
|
||||
return this._provider?.autoApprove || false
|
||||
}
|
||||
|
||||
async signAllTransactions(
|
||||
transactions: Transaction[]
|
||||
): Promise<Transaction[]> {
|
||||
if (!this._provider) {
|
||||
return transactions
|
||||
}
|
||||
|
||||
return this._provider.signAllTransactions(transactions)
|
||||
}
|
||||
|
||||
get publicKey() {
|
||||
return this._provider?.publicKey || DEFAULT_PUBLIC_KEY
|
||||
}
|
||||
|
||||
async signTransaction(transaction: Transaction) {
|
||||
if (!this._provider) {
|
||||
return transaction
|
||||
}
|
||||
|
||||
return this._provider.signTransaction(transaction)
|
||||
}
|
||||
|
||||
connect() {
|
||||
if (!this._provider) {
|
||||
window.open('https://phantom.app/', '_blank')
|
||||
notify({
|
||||
title: 'Connection Error',
|
||||
type: 'error',
|
||||
description: 'Please install Phantom wallet and then reload this page.',
|
||||
})
|
||||
return
|
||||
}
|
||||
if (!this._provider.listeners('connect').length) {
|
||||
this._provider?.on('connect', this._handleConnect)
|
||||
}
|
||||
if (!this._provider.listeners('disconnect').length) {
|
||||
this._provider?.on('disconnect', this._handleDisconnect)
|
||||
}
|
||||
return this._provider?.connect()
|
||||
}
|
||||
|
||||
disconnect() {
|
||||
if (this._provider) {
|
||||
this._provider.disconnect()
|
||||
}
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue