merge main

This commit is contained in:
saml33 2023-09-08 09:53:01 +10:00
commit d0845efbae
26 changed files with 292 additions and 102 deletions

View File

@ -1,57 +1,78 @@
This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app). This repo contains the Next.js app for the Mango v4 user interface.
## Dependency Management ## ⚡️ Quickstart
When updating dependencies, there are various files that must be kept up-to-date. Newly added, or updated dependencies can introduce unwanted/malicious scripts that can introduce risks for users and/or developers. The `lavamoat allow-scripts` feature allows us to deny by default, but adds some additional steps to the usual workflow. To get started, follow these steps:
`yarn.lock`: 1. **Clone the repo:** Begin by cloning the repository using the command:
- Instead of running `yarn` or `yarn install`, run `yarn setup` to ensure the `yarn.lock` file is in sync and that dependency scripts are run according to the `allowScripts` policy (set in `packages.json`) ```bash
- If `lavamoat` detects new scripts that are not explicitely allowed/denied, it'll throw and error with details (see below) git clone git@github.com:blockworks-foundation/mango-v4-ui.git
- Running `yarn setup` will also dedupe the `yarn.lock` file to reduce the dependency tree. Note CI will fail if there are dupes in `yarn.lock`! ```
The `allowScripts` configuration in `package.json`: 2. **Install Dependencies:** Move into the directory and install the dependencies:
- There are two ways to configure script policies: ```bash
1. Update the allow-scripts section manually by adding the missing package in the `allowScripts` section in `package.json` cd mango-v4-ui
2. Run `yarn allow-scripts auto` to update the `allowScripts` configuration automatically yarn setup
- Review each new package to determine whether the install script needs to run or not, testing if necessary. ```
- Use `npx can-i-ignore-scripts` to help assessing whether scripts are needed
3. **Run the app:**
## Getting Started
First, run the development server:
```bash ```bash
npm run dev
# or
yarn dev yarn dev
``` ```
Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. 4. Browse to http://localhost:3000
You can start editing the page by modifying `pages/index.tsx`. The page auto-updates as you edit the file. ## ⌨️ Contributor's Guide
[API routes](https://nextjs.org/docs/api-routes/introduction) can be accessed on [http://localhost:3000/api/hello](http://localhost:3000/api/hello). This endpoint can be edited in `pages/api/hello.ts`. ### Code quality
The `pages/api` directory is mapped to `/api/*`. Files in this directory are treated as [API routes](https://nextjs.org/docs/api-routes/introduction) instead of React pages. - Avoid duplication
- Consider performance (use useMemo and useCallback where appropriate)
- Create logical components and give them descriptive names
- Destructure objects and arrays
- Define constants for event functions unless they are very simple e.g. a single state update
- Create hooks for shared logic
- Add translation keys in alphabetical order
## Learn More ### Branching
To learn more about Next.js, take a look at the following resources: Prefix your branches with your Git username and give them concise and descriptive names
e.g. username/branch-name
- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API. ### Commits
- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome! Add commits for each self-contained change and give your commits clear messages that describe the change. Smaller commits that encompass a specific change are preferred over large commits with many changes
## Deploy on Vercel ### PRs
The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js. All PRs should have a meaningful name and include a description of what the changes are.
Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details. If there are visual changes, include screenshots in the description.
## Creating Color Themes If the PR is unfinished include a "TODO" section with work not yet completed. If there are known issues/bugs include a section outlining what they are.
#### Drafts
Opening draft PRs is a good way for other contributors to know a feature is being worked on. This is most useful for larger/complex features and is not a requirement. When your feature is at a point where you'd like to gather feedback or it's close to completion open a draft PR and share the preview link in the relevant Discord channel
Prefix "WIP:" to your draft PR name
### Reviews
When your changes are finished, who you request review from depends on the type of changes.
For complex changes e.g. new transactions, large features, lots of client or backend interactions you should at a minimum include @tlrsssss in your review
For changes that affect visual elements of the app (including text changes), request a review from @saml33 at a minimum
If you're unsure, request a review from @tlrssss and @saml33
If your work involves other parts of the stack (backend, client, etc.) request a review from the relevant person in that area
## 🎨 Creating Color Themes
1. Copy one of the other color themes in [tailwind.config.js](https://github.com/blockworks-foundation/mango-v4-ui/blob/main/tailwind.config.js) (starting line 25) 1. Copy one of the other color themes in [tailwind.config.js](https://github.com/blockworks-foundation/mango-v4-ui/blob/main/tailwind.config.js) (starting line 25)
2. Modify the colors. For the variables bkg-\* and fgd-\* pick a base color for bkg-1 and fgd-1 then adjust the lightness for 2-4. Use this same process to create dark/hover variations for the colors that have these properties. The base color can be anything that works for your theme. 2. Modify the colors. For the variables bkg-\* and fgd-\* pick a base color for bkg-1 and fgd-1 then adjust the lightness for 2-4. Use this same process to create dark/hover variations for the colors that have these properties. The base color can be anything that works for your theme.

View File

@ -37,6 +37,8 @@ import { NFT } from 'types'
import { useViewport } from 'hooks/useViewport' import { useViewport } from 'hooks/useViewport'
import useLocalStorageState from 'hooks/useLocalStorageState' import useLocalStorageState from 'hooks/useLocalStorageState'
import { SIDEBAR_COLLAPSE_KEY } from 'utils/constants' import { SIDEBAR_COLLAPSE_KEY } from 'utils/constants'
import { createTransferInstruction } from '@solana/spl-token'
import { PublicKey, TransactionInstruction } from '@solana/web3.js'
const SideNav = ({ collapsed }: { collapsed: boolean }) => { const SideNav = ({ collapsed }: { collapsed: boolean }) => {
const { t } = useTranslation(['common', 'search']) const { t } = useTranslation(['common', 'search'])
@ -46,6 +48,10 @@ const SideNav = ({ collapsed }: { collapsed: boolean }) => {
const themeData = mangoStore((s) => s.themeData) const themeData = mangoStore((s) => s.themeData)
const nfts = mangoStore((s) => s.wallet.nfts.data) const nfts = mangoStore((s) => s.wallet.nfts.data)
const { mangoAccount } = useMangoAccount() const { mangoAccount } = useMangoAccount()
const setPrependedGlobalAdditionalInstructions = mangoStore(
(s) => s.actions.setPrependedGlobalAdditionalInstructions,
)
const router = useRouter() const router = useRouter()
const { pathname } = router const { pathname } = router
@ -88,14 +94,39 @@ const SideNav = ({ collapsed }: { collapsed: boolean }) => {
return mangoNfts return mangoNfts
}, [nfts]) }, [nfts])
//mark transactions with used nfts
useEffect(() => {
let newInstruction: TransactionInstruction[] = []
if (mangoNfts.length && theme) {
const collectionAddress = CUSTOM_SKINS[theme.toLowerCase()]
const usedNft = mangoNfts.find(
(nft) => nft.collectionAddress === collectionAddress,
)
if (usedNft && publicKey && collectionAddress) {
newInstruction = [
createTransferInstruction(
new PublicKey(usedNft.tokenAccount),
new PublicKey(usedNft.tokenAccount),
publicKey,
1,
),
]
}
}
setPrependedGlobalAdditionalInstructions(newInstruction)
}, [mangoNfts, theme, themeData])
// find sidebar image url from skin nft for theme // find sidebar image url from skin nft for theme
const sidebarImageUrl = useMemo(() => { const sidebarImageUrl = useMemo(() => {
if (!theme) return themeData.sideImagePath if (!theme) return themeData.sideImagePath
const collectionAddress = CUSTOM_SKINS[theme.toLowerCase()] const collectionAddress = CUSTOM_SKINS[theme.toLowerCase()]
if (collectionAddress && mangoNfts.length) { if (collectionAddress && mangoNfts.length) {
const sidebarImageUrl = const attributes = mangoNfts.find(
mangoNfts.find((nft) => nft.collectionAddress === collectionAddress) (nft) => nft.collectionAddress === collectionAddress,
?.image || themeData.sideImagePath )?.json?.attributes
const sidebarImageUrl = attributes
? attributes[0].value || themeData.sideImagePath
: ''
return sidebarImageUrl return sidebarImageUrl
} }
return themeData.sideImagePath return themeData.sideImagePath

View File

@ -468,9 +468,18 @@ const ListToken = ({ goBack }: { goBack: () => void }) => {
Number(tierPreset.maintLiabWeight), Number(tierPreset.maintLiabWeight),
Number(tierPreset.initLiabWeight), Number(tierPreset.initLiabWeight),
Number(tierPreset.liquidationFee), Number(tierPreset.liquidationFee),
Number(tierPreset.delayIntervalSeconds),
Number(tierPreset.delayGrowthLimit),
Number(tierPreset.stableGrowthLimit),
Number(tierPreset.minVaultToDepositsRatio), Number(tierPreset.minVaultToDepositsRatio),
new BN(tierPreset.netBorrowLimitWindowSizeTs), new BN(tierPreset.netBorrowLimitWindowSizeTs),
new BN(tierPreset.netBorrowLimitPerWindowQuote), new BN(tierPreset.netBorrowLimitPerWindowQuote),
Number(tierPreset.borrowWeightScale),
Number(tierPreset.depositWeightScale),
Number(tierPreset.reduceOnly),
Number(tierPreset.tokenConditionalSwapTakerFeeRate),
Number(tierPreset.tokenConditionalSwapMakerFeeRate),
Number(tierPreset.flashLoanDepositFeeRate),
) )
.accounts({ .accounts({
admin: MANGO_DAO_WALLET, admin: MANGO_DAO_WALLET,
@ -502,13 +511,16 @@ const ListToken = ({ goBack }: { goBack: () => void }) => {
null, null,
null, null,
null, null,
tierPreset.borrowWeightScale, null,
tierPreset.depositWeightScale, null,
false, false,
false, false,
null, null,
null, null,
null, null,
null,
null,
null,
) )
.accounts({ .accounts({
oracle: new PublicKey(advForm.oraclePk), oracle: new PublicKey(advForm.oraclePk),
@ -524,7 +536,9 @@ const ListToken = ({ goBack }: { goBack: () => void }) => {
} as AccountMeta, } as AccountMeta,
]) ])
.instruction() .instruction()
if (!tierPreset.insuranceFound) {
proposalTx.push(editIx) proposalTx.push(editIx)
}
} else { } else {
const trustlessIx = await client!.program.methods const trustlessIx = await client!.program.methods
.tokenRegisterTrustless(Number(advForm.tokenIndex), advForm.name) .tokenRegisterTrustless(Number(advForm.tokenIndex), advForm.name)

View File

@ -15,7 +15,7 @@ const CreateAccountModal = ({ isOpen, onClose }: ModalProps) => {
return ( return (
<Modal isOpen={isOpen} onClose={onClose}> <Modal isOpen={isOpen} onClose={onClose}>
<div className="flex min-h-[264px] flex-col items-center justify-center"> <div className="flex min-h-[400px] flex-col items-center justify-center">
<CreateAccountForm customClose={handleClose} /> <CreateAccountForm customClose={handleClose} />
</div> </div>
</Modal> </Modal>

View File

@ -122,7 +122,7 @@ const CreateSwitchboardOracleModal = ({
minRequiredOracleResults: 3, minRequiredOracleResults: 3,
minRequiredJobResults: 2, minRequiredJobResults: 2,
minUpdateDelaySeconds: 6, minUpdateDelaySeconds: 6,
forceReportPeriod: 24 * 60 * 60, forceReportPeriod: 60 * 60,
withdrawAuthority: MANGO_DAO_WALLET, withdrawAuthority: MANGO_DAO_WALLET,
authority: payer, authority: payer,
crankDataBuffer: crankAccount.dataBuffer?.publicKey, crankDataBuffer: crankAccount.dataBuffer?.publicKey,

View File

@ -196,6 +196,9 @@ const DashboardSuggestedValues = ({
bank.reduceOnly ? 0 : null, bank.reduceOnly ? 0 : null,
null, null,
null, null,
getNullOrVal(fieldsToChange.tokenConditionalSwapTakerFeeRate),
getNullOrVal(fieldsToChange.tokenConditionalSwapMakerFeeRate),
getNullOrVal(fieldsToChange.loanFeeRate),
) )
.accounts({ .accounts({
group: group!.publicKey, group: group!.publicKey,

View File

@ -102,7 +102,7 @@ const MangoAccountsListModal = ({
return ( return (
<Modal isOpen={isOpen} onClose={onClose}> <Modal isOpen={isOpen} onClose={onClose}>
<div className="inline-block w-full transform overflow-x-hidden"> <div className="inline-block w-full transform overflow-x-hidden">
<div className="flex min-h-[364px] flex-col justify-between"> <div className="flex min-h-[400px] flex-col justify-between">
<div> <div>
<h2 className="text-center">{t('accounts')}</h2> <h2 className="text-center">{t('accounts')}</h2>
{loading ? ( {loading ? (

View File

@ -14,7 +14,11 @@ import {
TrHead, TrHead,
} from '@components/shared/TableElements' } from '@components/shared/TableElements'
import Tooltip from '@components/shared/Tooltip' import Tooltip from '@components/shared/Tooltip'
import { NoSymbolIcon, UsersIcon } from '@heroicons/react/20/solid' import {
EyeSlashIcon,
NoSymbolIcon,
UsersIcon,
} from '@heroicons/react/20/solid'
import { useWallet } from '@solana/wallet-adapter-react' import { useWallet } from '@solana/wallet-adapter-react'
import { PublicKey } from '@solana/web3.js' import { PublicKey } from '@solana/web3.js'
import mangoStore from '@store/mangoStore' import mangoStore from '@store/mangoStore'
@ -32,6 +36,7 @@ import PerpSideBadge from './PerpSideBadge'
import TableMarketName from './TableMarketName' import TableMarketName from './TableMarketName'
import { useSortableData } from 'hooks/useSortableData' import { useSortableData } from 'hooks/useSortableData'
import { useCallback } from 'react' import { useCallback } from 'react'
import { useHiddenMangoAccounts } from 'hooks/useHiddenMangoAccounts'
const TradeHistory = () => { const TradeHistory = () => {
const { t } = useTranslation(['common', 'trade']) const { t } = useTranslation(['common', 'trade'])
@ -45,6 +50,7 @@ const TradeHistory = () => {
} = useTradeHistory() } = useTradeHistory()
const { width } = useViewport() const { width } = useViewport()
const { connected } = useWallet() const { connected } = useWallet()
const { hiddenAccounts } = useHiddenMangoAccounts()
const showTableView = width ? width > breakpoints.md : false const showTableView = width ? width > breakpoints.md : false
const formattedTableData = useCallback(() => { const formattedTableData = useCallback(() => {
@ -142,6 +148,14 @@ const TradeHistory = () => {
{tableData.map((trade, index: number) => { {tableData.map((trade, index: number) => {
const { side, price, market, size, feeCost, liquidity, value } = const { side, price, market, size, feeCost, liquidity, value } =
trade trade
let counterpartyAddress = ''
if ('taker' in trade) {
counterpartyAddress =
trade.liquidity === 'Taker'
? trade.maker.toString()
: trade.taker.toString()
}
return ( return (
<TrBody <TrBody
key={`${side}${size}${price}${index}`} key={`${side}${size}${price}${index}`}
@ -184,29 +198,41 @@ const TradeHistory = () => {
<Td className="xl:!pl-0"> <Td className="xl:!pl-0">
{'taker' in trade ? ( {'taker' in trade ? (
<div className="flex justify-end"> <div className="flex justify-end">
{!hiddenAccounts?.includes(counterpartyAddress) ? (
<Tooltip <Tooltip
content={`View Counterparty ${abbreviateAddress( content={t('trade:tooltip-view-counterparty', {
pk: abbreviateAddress(
trade.liquidity === 'Taker' trade.liquidity === 'Taker'
? new PublicKey(trade.maker) ? new PublicKey(trade.maker)
: new PublicKey(trade.taker), : new PublicKey(trade.taker),
)}`} ),
})}
delay={0} delay={0}
> >
<a <a
className="" className=""
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
href={`/?address=${ href={`/?address=${counterpartyAddress}`}
trade.liquidity === 'Taker'
? trade.maker
: trade.taker
}`}
> >
<IconButton size="small"> <IconButton size="small">
<UsersIcon className="h-4 w-4" /> <UsersIcon className="h-4 w-4" />
</IconButton> </IconButton>
</a> </a>
</Tooltip> </Tooltip>
) : (
<Tooltip
content={t('trade:tooltip-private-counterparty')}
>
<IconButton
className="bg-th-bkg-1"
disabled
size="small"
>
<EyeSlashIcon className="h-4 w-4" />
</IconButton>
</Tooltip>
)}
</div> </div>
) : null} ) : null}
</Td> </Td>
@ -220,6 +246,13 @@ const TradeHistory = () => {
<div> <div>
{combinedTradeHistory.map((trade, index: number) => { {combinedTradeHistory.map((trade, index: number) => {
const { side, price, market, size, liquidity } = trade const { side, price, market, size, liquidity } = trade
let counterpartyAddress = ''
if ('taker' in trade) {
counterpartyAddress =
trade.liquidity === 'Taker'
? trade.maker.toString()
: trade.taker.toString()
}
return ( return (
<div <div
className="flex items-center justify-between border-b border-th-bkg-3 p-4" className="flex items-center justify-between border-b border-th-bkg-3 p-4"
@ -259,18 +292,41 @@ const TradeHistory = () => {
</div> </div>
</div> </div>
{'taker' in trade ? ( {'taker' in trade ? (
!hiddenAccounts?.includes(counterpartyAddress) ? (
<Tooltip
content={t('trade:tooltip-view-counterparty', {
pk: abbreviateAddress(
liquidity === 'Taker'
? new PublicKey(trade.maker)
: new PublicKey(trade.taker),
),
})}
delay={0}
>
<a <a
className="" className=""
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
href={`/?address=${ href={`/?address=${counterpartyAddress}`}
liquidity === 'Taker' ? trade.maker : trade.taker
}`}
> >
<IconButton size="small"> <IconButton size="small">
<UsersIcon className="h-4 w-4" /> <UsersIcon className="h-4 w-4" />
</IconButton> </IconButton>
</a> </a>
</Tooltip>
) : (
<Tooltip
content={t('trade:tooltip-private-counterparty')}
>
<IconButton
className="bg-th-bkg-1"
disabled
size="small"
>
<EyeSlashIcon className="h-4 w-4" />
</IconButton>
</Tooltip>
)
) : null} ) : null}
</div> </div>
</div> </div>

View File

@ -39,7 +39,7 @@ export const getOracleProvider = (
case OracleProvider.Switchboard: case OracleProvider.Switchboard:
return [ return [
'Switchboard', 'Switchboard',
`https://switchboard.xyz/explorer/3/${marketOrBase.oracle.toString()}`, `https://app.switchboard.xyz/solana/mainnet-beta/feed/${marketOrBase.oracle.toString()}`,
] ]
case OracleProvider.Stub: case OracleProvider.Stub:
return ['Stub', ''] return ['Stub', '']

View File

@ -22,8 +22,8 @@
}, },
"dependencies": { "dependencies": {
"@blockworks-foundation/mango-feeds": "0.1.7", "@blockworks-foundation/mango-feeds": "0.1.7",
"@blockworks-foundation/mango-v4": "^0.19.15", "@blockworks-foundation/mango-v4": "^0.19.20",
"@blockworks-foundation/mango-v4-settings": "0.2.6", "@blockworks-foundation/mango-v4-settings": "0.2.8",
"@headlessui/react": "1.6.6", "@headlessui/react": "1.6.6",
"@heroicons/react": "2.0.10", "@heroicons/react": "2.0.10",
"@metaplex-foundation/js": "0.19.4", "@metaplex-foundation/js": "0.19.4",

View File

@ -197,6 +197,7 @@
"vote": "Vote", "vote": "Vote",
"yes": "Yes", "yes": "Yes",
"you": "You", "you": "You",
"using-ledger": "Using Ledger" "using-ledger": "Using Ledger",
"sign-to-in-app-notifications": "Sign to in app notifications"
} }

View File

@ -106,9 +106,11 @@
"tooltip-ioc": "Immediate-Or-Cancel (IOC) orders are guaranteed to be the taker and must be executed immediately. Any portion of the order that can't be filled immediately will be cancelled", "tooltip-ioc": "Immediate-Or-Cancel (IOC) orders are guaranteed to be the taker and must be executed immediately. Any portion of the order that can't be filled immediately will be cancelled",
"tooltip-insured": "Whether or not {{tokenOrMarket}} losses can be recovered from the insurance fund in the event of bankruptcies.", "tooltip-insured": "Whether or not {{tokenOrMarket}} losses can be recovered from the insurance fund in the event of bankruptcies.",
"tooltip-post": "Post orders are guaranteed to be the maker or they will be canceled", "tooltip-post": "Post orders are guaranteed to be the maker or they will be canceled",
"tooltip-private-counterparty": "Counterparty has Private Account enabled",
"tooltip-slippage": "An estimate of the difference between the current price and the price your trade will be executed at", "tooltip-slippage": "An estimate of the difference between the current price and the price your trade will be executed at",
"tooltip-volume-alert": "Volume Alert Settings",
"tooltip-stable-price": "Stable price is used in a safety mechanism that limits a user's ability to enter risky positions when the oracle price is changing rapidly", "tooltip-stable-price": "Stable price is used in a safety mechanism that limits a user's ability to enter risky positions when the oracle price is changing rapidly",
"tooltip-view-counterparty": "View counterparty {{pk}}",
"tooltip-volume-alert": "Volume Alert Settings",
"total-pnl": "Total PnL", "total-pnl": "Total PnL",
"trade-sounds-tooltip": "Play a sound alert for every new trade", "trade-sounds-tooltip": "Play a sound alert for every new trade",
"trades": "Trades", "trades": "Trades",

View File

@ -197,6 +197,7 @@
"vote": "Vote", "vote": "Vote",
"yes": "Yes", "yes": "Yes",
"you": "You", "you": "You",
"using-ledger": "Using Ledger" "using-ledger": "Using Ledger",
"sign-to-in-app-notifications": "Sign to in app notifications"
} }

View File

@ -106,9 +106,11 @@
"tooltip-ioc": "Immediate-Or-Cancel (IOC) orders are guaranteed to be the taker and must be executed immediately. Any portion of the order that can't be filled immediately will be cancelled", "tooltip-ioc": "Immediate-Or-Cancel (IOC) orders are guaranteed to be the taker and must be executed immediately. Any portion of the order that can't be filled immediately will be cancelled",
"tooltip-insured": "Whether or not {{tokenOrMarket}} losses can be recovered from the insurance fund in the event of bankruptcies.", "tooltip-insured": "Whether or not {{tokenOrMarket}} losses can be recovered from the insurance fund in the event of bankruptcies.",
"tooltip-post": "Post orders are guaranteed to be the maker or they will be canceled", "tooltip-post": "Post orders are guaranteed to be the maker or they will be canceled",
"tooltip-private-counterparty": "Counterparty has Private Account enabled",
"tooltip-slippage": "An estimate of the difference between the current price and the price your trade will be executed at", "tooltip-slippage": "An estimate of the difference between the current price and the price your trade will be executed at",
"tooltip-volume-alert": "Volume Alert Settings",
"tooltip-stable-price": "Stable price is used in a safety mechanism that limits a user's ability to enter risky positions when the oracle price is changing rapidly", "tooltip-stable-price": "Stable price is used in a safety mechanism that limits a user's ability to enter risky positions when the oracle price is changing rapidly",
"tooltip-view-counterparty": "View counterparty {{pk}}",
"tooltip-volume-alert": "Volume Alert Settings",
"total-pnl": "Total PnL", "total-pnl": "Total PnL",
"trade-sounds-tooltip": "Play a sound alert for every new trade", "trade-sounds-tooltip": "Play a sound alert for every new trade",
"trades": "Trades", "trades": "Trades",
@ -119,6 +121,7 @@
"tweet-position": "Tweet", "tweet-position": "Tweet",
"unrealized-pnl": "Unrealized PnL", "unrealized-pnl": "Unrealized PnL",
"unsettled": "Unsettled", "unsettled": "Unsettled",
"view-counterparty": "View counterparty {{pk}}",
"volume-alert": "Volume Alert", "volume-alert": "Volume Alert",
"volume-alert-desc": "Play a sound whenever volume exceeds your alert threshold" "volume-alert-desc": "Play a sound whenever volume exceeds your alert threshold"
} }

View File

@ -197,6 +197,7 @@
"vote": "Vote", "vote": "Vote",
"yes": "Yes", "yes": "Yes",
"you": "You", "you": "You",
"using-ledger": "Using Ledger" "using-ledger": "Using Ledger",
"sign-to-in-app-notifications": "Sign to in app notifications"
} }

View File

@ -106,9 +106,11 @@
"tooltip-ioc": "Immediate-Or-Cancel (IOC) orders are guaranteed to be the taker and must be executed immediately. Any portion of the order that can't be filled immediately will be cancelled", "tooltip-ioc": "Immediate-Or-Cancel (IOC) orders are guaranteed to be the taker and must be executed immediately. Any portion of the order that can't be filled immediately will be cancelled",
"tooltip-insured": "Whether or not {{tokenOrMarket}} losses can be recovered from the insurance fund in the event of bankruptcies.", "tooltip-insured": "Whether or not {{tokenOrMarket}} losses can be recovered from the insurance fund in the event of bankruptcies.",
"tooltip-post": "Post orders are guaranteed to be the maker or they will be canceled", "tooltip-post": "Post orders are guaranteed to be the maker or they will be canceled",
"tooltip-private-counterparty": "Counterparty has Private Account enabled",
"tooltip-slippage": "An estimate of the difference between the current price and the price your trade will be executed at", "tooltip-slippage": "An estimate of the difference between the current price and the price your trade will be executed at",
"tooltip-volume-alert": "Volume Alert Settings",
"tooltip-stable-price": "Stable price is used in a safety mechanism that limits a user's ability to enter risky positions when the oracle price is changing rapidly", "tooltip-stable-price": "Stable price is used in a safety mechanism that limits a user's ability to enter risky positions when the oracle price is changing rapidly",
"tooltip-view-counterparty": "View counterparty {{pk}}",
"tooltip-volume-alert": "Volume Alert Settings",
"total-pnl": "Total PnL", "total-pnl": "Total PnL",
"trade-sounds-tooltip": "Play a sound alert for every new trade", "trade-sounds-tooltip": "Play a sound alert for every new trade",
"trades": "Trades", "trades": "Trades",
@ -119,6 +121,7 @@
"tweet-position": "Tweet", "tweet-position": "Tweet",
"unrealized-pnl": "Unrealized PnL", "unrealized-pnl": "Unrealized PnL",
"unsettled": "Unsettled", "unsettled": "Unsettled",
"view-counterparty": "View counterparty {{pk}}",
"volume-alert": "Volume Alert", "volume-alert": "Volume Alert",
"volume-alert-desc": "Play a sound whenever volume exceeds your alert threshold" "volume-alert-desc": "Play a sound whenever volume exceeds your alert threshold"
} }

View File

@ -196,6 +196,7 @@
"vote": "投票", "vote": "投票",
"yes": "是", "yes": "是",
"you": "你", "you": "你",
"using-ledger": "Using Ledger" "using-ledger": "Using Ledger",
"sign-to-in-app-notifications": "Sign to in app notifications"
} }

View File

@ -110,8 +110,10 @@
"tooltip-insured": "如果发生破产,{{tokenOrMarket}}损失是否可以从保险基金中归还", "tooltip-insured": "如果发生破产,{{tokenOrMarket}}损失是否可以从保险基金中归还",
"tooltip-ioc": "IOC交易若不吃单就会被取消。任何无法立刻成交的部分将被取消", "tooltip-ioc": "IOC交易若不吃单就会被取消。任何无法立刻成交的部分将被取消",
"tooltip-post": "Post交易若不挂单就会被取消。", "tooltip-post": "Post交易若不挂单就会被取消。",
"tooltip-private-counterparty": "Counterparty has Private Account enabled",
"tooltip-slippage": "当前价格与您的交易将执行的价格之间的差值的估计", "tooltip-slippage": "当前价格与您的交易将执行的价格之间的差值的估计",
"tooltip-stable-price": "稳定价格用于一个安全机制。此机制可以限制用户在预言机价格快速波动时下风险高的订单", "tooltip-stable-price": "稳定价格用于一个安全机制。此机制可以限制用户在预言机价格快速波动时下风险高的订单",
"tooltip-view-counterparty": "View counterparty {{pk}}",
"tooltip-volume-alert": "交易量警报设定", "tooltip-volume-alert": "交易量警报设定",
"total-pnl": "总盈亏", "total-pnl": "总盈亏",
"trade-sounds-tooltip": "为每笔新交易播放警报声音", "trade-sounds-tooltip": "为每笔新交易播放警报声音",
@ -119,6 +121,7 @@
"tweet-position": "分享至Twitter", "tweet-position": "分享至Twitter",
"unrealized-pnl": "未实现盈亏", "unrealized-pnl": "未实现盈亏",
"unsettled": "未结清", "unsettled": "未结清",
"view-counterparty": "View counterparty {{pk}}",
"volume-alert": "交易量警报", "volume-alert": "交易量警报",
"volume-alert-desc": "交易量超过警报设定时播放声音" "volume-alert-desc": "交易量超过警报设定时播放声音"
} }

View File

@ -196,5 +196,7 @@
"vote": "投票", "vote": "投票",
"yes": "是", "yes": "是",
"you": "你", "you": "你",
"using-ledger": "Using Ledger",
"sign-to-in-app-notifications": "Sign to in app notifications"
"using-ledger": "Using Ledger" "using-ledger": "Using Ledger"
} }

View File

@ -106,8 +106,10 @@
"tooltip-insured": "如果發生破產,{{tokenOrMarket}}損失是否可以從保險基金中歸還", "tooltip-insured": "如果發生破產,{{tokenOrMarket}}損失是否可以從保險基金中歸還",
"tooltip-ioc": "IOC交易若不吃單就會被取消。任何無法立刻成交的部分將被取消", "tooltip-ioc": "IOC交易若不吃單就會被取消。任何無法立刻成交的部分將被取消",
"tooltip-post": "Post交易若不掛單就會被取消。", "tooltip-post": "Post交易若不掛單就會被取消。",
"tooltip-private-counterparty": "Counterparty has Private Account enabled",
"tooltip-slippage": "當前價格與您的交易將執行的價格之間的差值的估計", "tooltip-slippage": "當前價格與您的交易將執行的價格之間的差值的估計",
"tooltip-stable-price": "穩定價格用於一個安全機制。此機制可以限制用戶在預言機價格快速波動時下風險高的訂單", "tooltip-stable-price": "穩定價格用於一個安全機制。此機制可以限制用戶在預言機價格快速波動時下風險高的訂單",
"tooltip-view-counterparty": "View counterparty {{pk}}",
"tooltip-volume-alert": "交易量警報設定", "tooltip-volume-alert": "交易量警報設定",
"total-pnl": "總盈虧", "total-pnl": "總盈虧",
"trade-sounds-tooltip": "為每筆新交易播放警報聲音", "trade-sounds-tooltip": "為每筆新交易播放警報聲音",
@ -119,6 +121,7 @@
"tweet-position": "分享至Twitter", "tweet-position": "分享至Twitter",
"unrealized-pnl": "未實現盈虧", "unrealized-pnl": "未實現盈虧",
"unsettled": "未結清", "unsettled": "未結清",
"view-counterparty": "View counterparty {{pk}}",
"volume-alert": "交易量警報", "volume-alert": "交易量警報",
"volume-alert-desc": "交易量超過警報設定時播放聲音" "volume-alert-desc": "交易量超過警報設定時播放聲音"
} }

View File

@ -9,6 +9,7 @@ import {
Keypair, Keypair,
PublicKey, PublicKey,
RecentPrioritizationFees, RecentPrioritizationFees,
TransactionInstruction,
} from '@solana/web3.js' } from '@solana/web3.js'
import { OpenOrders, Order } from '@project-serum/serum/lib/market' import { OpenOrders, Order } from '@project-serum/serum/lib/market'
import { Orderbook } from '@project-serum/serum' import { Orderbook } from '@project-serum/serum'
@ -114,10 +115,18 @@ export const emptyWallet = new EmptyWallet(Keypair.generate())
const initMangoClient = ( const initMangoClient = (
provider: AnchorProvider, provider: AnchorProvider,
opts = { prioritizationFee: DEFAULT_PRIORITY_FEE }, opts: {
prioritizationFee: number
prependedGlobalAdditionalInstructions: TransactionInstruction[]
} = {
prioritizationFee: DEFAULT_PRIORITY_FEE,
prependedGlobalAdditionalInstructions: [],
},
): MangoClient => { ): MangoClient => {
return MangoClient.connect(provider, CLUSTER, MANGO_V4_ID[CLUSTER], { return MangoClient.connect(provider, CLUSTER, MANGO_V4_ID[CLUSTER], {
prioritizationFee: opts.prioritizationFee, prioritizationFee: opts.prioritizationFee,
prependedGlobalAdditionalInstructions:
opts.prependedGlobalAdditionalInstructions,
idsSource: 'api', idsSource: 'api',
postSendTxCallback: ({ txid }: { txid: string }) => { postSendTxCallback: ({ txid }: { txid: string }) => {
notify({ notify({
@ -193,6 +202,7 @@ export type MangoStore = {
details: ProfileDetails | null details: ProfileDetails | null
loadDetails: boolean loadDetails: boolean
} }
prependedGlobalAdditionalInstructions: TransactionInstruction[]
priorityFee: number priorityFee: number
selectedMarket: { selectedMarket: {
name: string | undefined name: string | undefined
@ -283,6 +293,9 @@ export type MangoStore = {
connectMangoClientWithWallet: (wallet: WalletAdapter) => Promise<void> connectMangoClientWithWallet: (wallet: WalletAdapter) => Promise<void>
loadMarketFills: () => Promise<void> loadMarketFills: () => Promise<void>
updateConnection: (url: string) => void updateConnection: (url: string) => void
setPrependedGlobalAdditionalInstructions: (
instructions: TransactionInstruction[],
) => void
estimatePriorityFee: (feeMultiplier: number) => Promise<void> estimatePriorityFee: (feeMultiplier: number) => Promise<void>
} }
} }
@ -358,6 +371,7 @@ const mangoStore = create<MangoStore>()(
details: { profile_name: '', trader_category: '', wallet_pk: '' }, details: { profile_name: '', trader_category: '', wallet_pk: '' },
}, },
priorityFee: DEFAULT_PRIORITY_FEE, priorityFee: DEFAULT_PRIORITY_FEE,
prependedGlobalAdditionalInstructions: [],
selectedMarket: { selectedMarket: {
name: 'SOL/USDC', name: 'SOL/USDC',
current: undefined, current: undefined,
@ -1016,8 +1030,11 @@ const mangoStore = create<MangoStore>()(
) )
provider.opts.skipPreflight = true provider.opts.skipPreflight = true
const priorityFee = get().priorityFee ?? DEFAULT_PRIORITY_FEE const priorityFee = get().priorityFee ?? DEFAULT_PRIORITY_FEE
const client = initMangoClient(provider, { const client = initMangoClient(provider, {
prioritizationFee: priorityFee, prioritizationFee: priorityFee,
prependedGlobalAdditionalInstructions:
get().prependedGlobalAdditionalInstructions,
}) })
set((s) => { set((s) => {
@ -1033,6 +1050,25 @@ const mangoStore = create<MangoStore>()(
} }
} }
}, },
async setPrependedGlobalAdditionalInstructions(
instructions: TransactionInstruction[],
) {
const set = get().set
const client = mangoStore.getState().client
const provider = client.program.provider as AnchorProvider
provider.opts.skipPreflight = true
const newClient = initMangoClient(provider, {
prioritizationFee: get().priorityFee,
prependedGlobalAdditionalInstructions: instructions,
})
set((s) => {
s.client = newClient
s.prependedGlobalAdditionalInstructions = instructions
})
},
async fetchProfileDetails(walletPk: string) { async fetchProfileDetails(walletPk: string) {
const set = get().set const set = get().set
set((state) => { set((state) => {
@ -1128,7 +1164,11 @@ const mangoStore = create<MangoStore>()(
options, options,
) )
newProvider.opts.skipPreflight = true newProvider.opts.skipPreflight = true
const newClient = initMangoClient(newProvider) const newClient = initMangoClient(newProvider, {
prependedGlobalAdditionalInstructions:
get().prependedGlobalAdditionalInstructions,
prioritizationFee: DEFAULT_PRIORITY_FEE,
})
set((state) => { set((state) => {
state.connection = newConnection state.connection = newConnection
state.client = newClient state.client = newClient
@ -1180,6 +1220,8 @@ const mangoStore = create<MangoStore>()(
provider.opts.skipPreflight = true provider.opts.skipPreflight = true
const newClient = initMangoClient(provider, { const newClient = initMangoClient(provider, {
prioritizationFee: feeEstimate, prioritizationFee: feeEstimate,
prependedGlobalAdditionalInstructions:
get().prependedGlobalAdditionalInstructions,
}) })
set((state) => { set((state) => {
state.priorityFee = feeEstimate state.priorityFee = feeEstimate

View File

@ -7,6 +7,7 @@ import {
Serum3Market, Serum3Market,
} from '@blockworks-foundation/mango-v4' } from '@blockworks-foundation/mango-v4'
import { Modify } from '@blockworks-foundation/mango-v4' import { Modify } from '@blockworks-foundation/mango-v4'
import { JsonMetadata } from '@metaplex-foundation/js'
import { Event } from '@project-serum/serum/lib/queue' import { Event } from '@project-serum/serum/lib/queue'
import { PublicKey } from '@solana/web3.js' import { PublicKey } from '@solana/web3.js'
import { formatTradeHistory } from 'hooks/useTradeHistory' import { formatTradeHistory } from 'hooks/useTradeHistory'
@ -305,6 +306,7 @@ export interface NFT {
name: string name: string
mint: string mint: string
tokenAccount: string tokenAccount: string
json: JsonMetadata | null
} }
export interface PerpStatsItem { export interface PerpStatsItem {

View File

@ -49,7 +49,7 @@ export const FAVORITE_SWAPS_KEY = 'favoriteSwaps-0.1'
export const THEME_KEY = 'theme-0.1' export const THEME_KEY = 'theme-0.1'
export const RPC_PROVIDER_KEY = 'rpcProviderKey-0.8' export const RPC_PROVIDER_KEY = 'rpcProviderKey-0.9'
export const PRIORITY_FEE_KEY = 'priorityFeeKey-0.2' export const PRIORITY_FEE_KEY = 'priorityFeeKey-0.2'

View File

@ -84,5 +84,5 @@ export const nftThemeMeta: NftThemeMeta = {
export const CUSTOM_SKINS: { [key: string]: string } = { export const CUSTOM_SKINS: { [key: string]: string } = {
bonk: '6FUYsgvSPiLsMpKZqLWswkw7j4juudZyVopU6RYKLkQ3', bonk: '6FUYsgvSPiLsMpKZqLWswkw7j4juudZyVopU6RYKLkQ3',
pepe: '6FUYsgvSPiLsMpKZqLWswkw7j4juudZyVopU6RYKLkQ3', pepe: 'B4QhXJaSnUBT8aWiPz5GmB9sjXdQDtHXv8x1WsRFHNJx',
} }

View File

@ -93,6 +93,7 @@ const enhanceNFT = (nft: NftWithATA) => {
collectionAddress: nft.collection?.address.toBase58(), collectionAddress: nft.collection?.address.toBase58(),
mint: nft.mint.address.toBase58(), mint: nft.mint.address.toBase58(),
tokenAccount: nft.tokenAccountAddress?.toBase58() || '', tokenAccount: nft.tokenAccountAddress?.toBase58() || '',
json: nft.json,
} }
} }

View File

@ -26,24 +26,24 @@
dependencies: dependencies:
ws "^8.13.0" ws "^8.13.0"
"@blockworks-foundation/mango-v4-settings@0.2.6": "@blockworks-foundation/mango-v4-settings@0.2.8":
version "0.2.6" version "0.2.8"
resolved "https://registry.yarnpkg.com/@blockworks-foundation/mango-v4-settings/-/mango-v4-settings-0.2.6.tgz#725a8cf669e164cd7694d97989472f7852afad68" resolved "https://registry.yarnpkg.com/@blockworks-foundation/mango-v4-settings/-/mango-v4-settings-0.2.8.tgz#fa88d6ddb6dcc1dd1cd8f75ab96bc0d41629799d"
integrity sha512-RK8O8lbflIN9IgNE1uUkjrtlv/7f0BjIqTwcuLNFos6/e/Q2/AnlXRlD5Y9WnO6xS7mXNsw9kr05xCxeYZzM1Q== integrity sha512-20h5+Vky3YZfd3ekcnMgUxp1XaddFgtSNE6SPCy1oOAOjzvDHgZ3AVJd95WTTjTONGTZqqmTbP2ReWIu4ZfP6w==
dependencies: dependencies:
bn.js "^5.2.1" bn.js "^5.2.1"
eslint-config-prettier "^9.0.0" eslint-config-prettier "^9.0.0"
"@blockworks-foundation/mango-v4@^0.19.15": "@blockworks-foundation/mango-v4@^0.19.20":
version "0.19.15" version "0.19.20"
resolved "https://registry.yarnpkg.com/@blockworks-foundation/mango-v4/-/mango-v4-0.19.15.tgz#0e62d9c2f06c103b39abb648a529702048fd2706" resolved "https://registry.yarnpkg.com/@blockworks-foundation/mango-v4/-/mango-v4-0.19.20.tgz#5932b98bfc94eed1f7cdffdef535afe19c1cce03"
integrity sha512-aWtnBgbPJ2F1RZ4dCpu2ans5KQmt7d4en55Dp/n17hWjRUGA4tTPwthE8uoeRqc6ih19ldgDj2oMQAl6XzWQzg== integrity sha512-UGPmJ/NPDdIHM7BcYSOOZHDYDse6mefTGI2IK9MRxaqAVpqfFj3Kjk8mM12O+CW1cySaMl3NUAyR/Ul0jGY+jg==
dependencies: dependencies:
"@coral-xyz/anchor" "^0.27.0" "@coral-xyz/anchor" "^0.27.0"
"@project-serum/serum" "0.13.65" "@project-serum/serum" "0.13.65"
"@pythnetwork/client" "~2.14.0" "@pythnetwork/client" "~2.14.0"
"@solana/spl-token" "0.3.7" "@solana/spl-token" "0.3.7"
"@solana/web3.js" "^1.73.2" "@solana/web3.js" "^1.78.2"
"@switchboard-xyz/sbv2-lite" "^0.1.6" "@switchboard-xyz/sbv2-lite" "^0.1.6"
big.js "^6.1.1" big.js "^6.1.1"
binance-api-node "^0.12.0" binance-api-node "^0.12.0"
@ -1999,7 +1999,7 @@
"@wallet-standard/app" "^1.0.1" "@wallet-standard/app" "^1.0.1"
"@wallet-standard/base" "^1.0.1" "@wallet-standard/base" "^1.0.1"
"@solana/web3.js@^1.17.0", "@solana/web3.js@^1.21.0", "@solana/web3.js@^1.22.0", "@solana/web3.js@^1.32.0", "@solana/web3.js@^1.36.0", "@solana/web3.js@^1.44.3", "@solana/web3.js@^1.50.1", "@solana/web3.js@^1.56.2", "@solana/web3.js@^1.63.1", "@solana/web3.js@^1.66.2", "@solana/web3.js@^1.68.0", "@solana/web3.js@^1.73.2", "@solana/web3.js@^1.78.3": "@solana/web3.js@^1.17.0", "@solana/web3.js@^1.21.0", "@solana/web3.js@^1.22.0", "@solana/web3.js@^1.32.0", "@solana/web3.js@^1.36.0", "@solana/web3.js@^1.44.3", "@solana/web3.js@^1.50.1", "@solana/web3.js@^1.56.2", "@solana/web3.js@^1.63.1", "@solana/web3.js@^1.66.2", "@solana/web3.js@^1.68.0", "@solana/web3.js@^1.78.2", "@solana/web3.js@^1.78.3":
version "1.78.4" version "1.78.4"
resolved "https://registry.yarnpkg.com/@solana/web3.js/-/web3.js-1.78.4.tgz#e8ca9abe4ec2af5fc540c1d272efee24aaffedb3" resolved "https://registry.yarnpkg.com/@solana/web3.js/-/web3.js-1.78.4.tgz#e8ca9abe4ec2af5fc540c1d272efee24aaffedb3"
integrity sha512-up5VG1dK+GPhykmuMIozJZBbVqpm77vbOG6/r5dS7NBGZonwHfTLdBbsYc3rjmaQ4DpCXUa3tUc4RZHRORvZrw== integrity sha512-up5VG1dK+GPhykmuMIozJZBbVqpm77vbOG6/r5dS7NBGZonwHfTLdBbsYc3rjmaQ4DpCXUa3tUc4RZHRORvZrw==