Add account names (#44)

* handle account names on account page

* handle name in new account form

* handle marginbalances, responsive

* Add account name uses smart contract

* remove localStorage account name references

* adjust name validation message and label name as public

* new accounts are initialized with names

Co-authored-by: saml33 <slam.uke@gmail.com>
Co-authored-by: Tyler Shipe <tjshipe@gmail.com>
This commit is contained in:
dboures 2021-07-08 15:35:14 -04:00 committed by GitHub
parent 0ef18df358
commit 27c6c8b36d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 399 additions and 111 deletions

View File

@ -0,0 +1,118 @@
import { FunctionComponent, useState } from 'react'
import useMangoStore from '../stores/useMangoStore'
import {
ExclamationCircleIcon,
InformationCircleIcon,
} from '@heroicons/react/outline'
import Input from './Input'
import Button from './Button'
import Modal from './Modal'
import { ElementTitle } from './styles'
import Tooltip from './Tooltip'
import useConnection from '../hooks/useConnection'
import { PublicKey } from '@solana/web3.js'
import { addMarginAccountInfo } from '../utils/mango'
import { notify } from '../utils/notifications'
interface AccountNameModalProps {
accountName?: string
isOpen: boolean
onClose?: (x?) => void
}
const AccountNameModal: FunctionComponent<AccountNameModalProps> = ({
accountName,
isOpen,
onClose,
}) => {
const [name, setName] = useState(accountName || '')
const [invalidNameMessage, setInvalidNameMessage] = useState('')
const wallet = useMangoStore.getState().wallet.current
const selectedMangoGroup = useMangoStore((s) => s.selectedMangoGroup.current)
const selectedMarginAccount = useMangoStore(
(s) => s.selectedMarginAccount.current
)
const actions = useMangoStore((s) => s.actions)
const { connection, programId } = useConnection()
const submitName = async () => {
addMarginAccountInfo(
connection,
new PublicKey(programId),
selectedMangoGroup,
selectedMarginAccount,
wallet,
name
)
.then(() => {
actions.fetchMarginAccounts()
onClose()
})
.catch((err) => {
console.warn('Error setting account name:', err)
notify({
message: 'Could not set account name',
description: `${err}`,
txid: err.txid,
type: 'error',
})
})
}
const validateNameInput = () => {
if (name.length >= 33) {
setInvalidNameMessage('Account name must be 32 characters or less')
}
if (name.length === 0) {
setInvalidNameMessage('Enter an account name')
}
}
const onChangeNameInput = (name) => {
setName(name)
if (invalidNameMessage) {
setInvalidNameMessage('')
}
}
return (
<Modal onClose={onClose} isOpen={isOpen}>
<Modal.Header>
<div className="flex items-center">
<ElementTitle noMarignBottom>Name your Account</ElementTitle>
</div>
</Modal.Header>
<div className="flex items-center justify-center text-th-fgd-3 pb-4">
Edit the public nickname for your account
<Tooltip content="Account names are stored on-chain">
<InformationCircleIcon className="h-5 w-5 ml-2 text-th-primary" />
</Tooltip>
</div>
<div className="pb-2 text-th-fgd-1">Account Name</div>
<Input
type="text"
className={`border border-th-fgd-4 flex-grow`}
error={!!invalidNameMessage}
placeholder="e.g. Calypso"
value={name}
onBlur={validateNameInput}
onChange={(e) => onChangeNameInput(e.target.value)}
/>
{invalidNameMessage ? (
<div className="flex items-center pt-1.5 text-th-red">
<ExclamationCircleIcon className="h-4 w-4 mr-1.5" />
{invalidNameMessage}
</div>
) : null}
<Button
onClick={() => submitName()}
disabled={name.length >= 33}
className="mt-4 w-full"
>
Save Name
</Button>
</Modal>
)
}
export default AccountNameModal

View File

@ -1,11 +1,7 @@
import React, { FunctionComponent, useEffect, useState } from 'react'
import { RadioGroup } from '@headlessui/react'
import { CheckCircleIcon } from '@heroicons/react/solid'
import {
ChevronLeftIcon,
CurrencyDollarIcon,
PlusCircleIcon,
} from '@heroicons/react/outline'
import { CurrencyDollarIcon, PlusCircleIcon } from '@heroicons/react/outline'
import useMangoStore from '../stores/useMangoStore'
import { MarginAccount } from '@blockworks-foundation/mango-client'
import { abbreviateAddress } from '../utils'
@ -14,6 +10,7 @@ import Modal from './Modal'
import { ElementTitle } from './styles'
import Button, { LinkButton } from './Button'
import NewAccount from './NewAccount'
import { getMarginInfoString } from '../pages/account'
interface AccountsModalProps {
onClose: () => void
@ -128,7 +125,7 @@ const AccountsModal: FunctionComponent<AccountsModalProps> = ({
Select a Margin Account
</RadioGroup.Label>
<div className="space-y-2">
{marginAccounts.map((account) => (
{marginAccounts.map((account, i) => (
<RadioGroup.Option
key={account.publicKey.toString()}
value={account}
@ -150,11 +147,13 @@ const AccountsModal: FunctionComponent<AccountsModalProps> = ({
<CurrencyDollarIcon className="h-5 w-5 mr-2.5" />
<div>
<div className="pb-0.5">
{abbreviateAddress(account.publicKey)}
{marginAccounts[i] && marginAccounts[i].info
? getMarginInfoString(marginAccounts[i])
: abbreviateAddress(account.publicKey)}
</div>
{prices && selectedMangoGroup ? (
<div className="text-th-fgd-3 text-xs">
{getAccountInfo(account)}
{getAccountInfo(marginAccounts[i])}
</div>
) : null}
</div>
@ -178,11 +177,10 @@ const AccountsModal: FunctionComponent<AccountsModalProps> = ({
<>
<NewAccount onAccountCreation={handleNewAccountCreation} />
<LinkButton
className="flex items-center mt-4 text-th-fgd-3"
className="flex items-center justify-center mt-6 text-th-fgd-2 w-full"
onClick={() => setShowNewAccountForm(false)}
>
<ChevronLeftIcon className="h-5 w-5 mr-1" />
Back
Cancel
</LinkButton>
</>
)

View File

@ -76,7 +76,7 @@ const AlertsModal: FunctionComponent<AlertsModalProps> = ({
if (isCopied) {
const timer = setTimeout(() => {
setIsCopied(false)
}, 2000)
}, 1500)
return () => clearTimeout(timer)
}
}, [isCopied])

View File

@ -18,7 +18,7 @@ const Button: FunctionComponent<ButtonProps> = ({
onClick={onClick}
disabled={disabled}
className={`${className} px-6 py-2 border border-th-fgd-4 bg-th-bkg-2 rounded-md text-th-fgd-1
active:border-mango-yellow hover:bg-th-bkg-3 focus:outline-none disabled:bg-th-bkg-2
active:border-th-primary hover:bg-th-bkg-3 focus:outline-none disabled:bg-th-bkg-2
disabled:text-th-fgd-4 disabled:cursor-not-allowed`}
{...props}
>

View File

@ -186,7 +186,8 @@ const DepositModal: FunctionComponent<DepositModalProps> = ({
wallet,
selectedAccount.account.mint,
selectedAccount.publicKey,
Number(inputAmount)
Number(inputAmount),
'Account'
)
.then(async (_response: Array<any>) => {
await sleep(1000)

View File

@ -17,6 +17,7 @@ import BorrowModal from './BorrowModal'
import Button from './Button'
import Tooltip from './Tooltip'
import AccountsModal from './AccountsModal'
import { getMarginInfoString } from '../pages/account'
export default function MarginBalances() {
const selectedMangoGroup = useMangoStore((s) => s.selectedMangoGroup.current)
@ -56,7 +57,9 @@ export default function MarginBalances() {
<div className="flex justify-between pb-5">
<div className="w-8 h-8" />
<div className="flex flex-col items-center">
<ElementTitle noMarignBottom>Margin Account</ElementTitle>
<ElementTitle noMarignBottom>
{getMarginInfoString(selectedMarginAccount)}
</ElementTitle>
{selectedMarginAccount ? (
<Link href={'/account'}>
<a className="pt-1 text-th-fgd-3 text-xs underline hover:no-underline">

View File

@ -1,5 +1,8 @@
import React, { FunctionComponent, useEffect, useMemo, useState } from 'react'
import { ExclamationCircleIcon } from '@heroicons/react/outline'
import {
ExclamationCircleIcon,
InformationCircleIcon,
} from '@heroicons/react/outline'
import {
nativeToUi,
sleep,
@ -20,6 +23,7 @@ import { PublicKey } from '@solana/web3.js'
import Loading from './Loading'
import Button from './Button'
import Slider from './Slider'
import Tooltip from './Tooltip'
import { notify } from '../utils/notifications'
interface NewAccountProps {
@ -32,8 +36,11 @@ const NewAccount: FunctionComponent<NewAccountProps> = ({
const [inputAmount, setInputAmount] = useState(0)
const [submitting, setSubmitting] = useState(false)
const [invalidAmountMessage, setInvalidAmountMessage] = useState('')
const [invalidNameMessage, setInvalidNameMessage] = useState('')
const [sliderPercentage, setSliderPercentage] = useState(0)
const [maxButtonTransition, setMaxButtonTransition] = useState(false)
const [name, setName] = useState('')
const [showNewAccountName, setShowNewAccountName] = useState(true)
const { getTokenIndex, symbols } = useMarketList()
const { connection, programId } = useConnection()
const mintDecimals = useMangoStore((s) => s.selectedMangoGroup.mintDecimals)
@ -90,7 +97,8 @@ const NewAccount: FunctionComponent<NewAccountProps> = ({
wallet,
selectedAccount.account.mint,
selectedAccount.publicKey,
Number(inputAmount)
Number(inputAmount),
name
)
.then(async (_response: Array<any>) => {
await sleep(1000)
@ -142,6 +150,22 @@ const NewAccount: FunctionComponent<NewAccountProps> = ({
validateAmountInput(amount)
}
const validateNameInput = () => {
if (name.length >= 33) {
setInvalidNameMessage('Account name must be 32 characters or less')
}
if (name.length === 0) {
setInvalidNameMessage('Enter an account name')
}
}
const onChangeNameInput = (name) => {
setName(name)
if (invalidNameMessage) {
setInvalidNameMessage('')
}
}
// turn off slider transition for dragging slider handle interaction
useEffect(() => {
if (maxButtonTransition) {
@ -151,67 +175,105 @@ const NewAccount: FunctionComponent<NewAccountProps> = ({
return (
<>
<ElementTitle noMarignBottom>Create Margin Account</ElementTitle>
<div className="text-th-fgd-3 text-center pb-4 pt-2">
Make a deposit to initialize a new margin account
</div>
<AccountSelect
symbols={symbols}
accounts={depositAccounts}
selectedAccount={selectedAccount}
onSelectAccount={handleAccountSelect}
/>
<div className="flex justify-between pb-2 pt-4">
<div className={`text-th-fgd-1`}>Amount</div>
<div
className="text-th-fgd-1 underline cursor-pointer default-transition hover:text-th-primary hover:no-underline"
onClick={setMaxForSelectedAccount}
>
Max
</div>
</div>
<div className="flex">
<Input
type="number"
min="0"
className={`border border-th-fgd-4 flex-grow pr-11`}
placeholder="0.00"
error={!!invalidAmountMessage}
onBlur={(e) => validateAmountInput(e.target.value)}
value={inputAmount}
onChange={(e) => onChangeAmountInput(e.target.value)}
suffix={symbol}
/>
</div>
{invalidAmountMessage ? (
<div className="flex items-center pt-1.5 text-th-red">
<ExclamationCircleIcon className="h-4 w-4 mr-1.5" />
{invalidAmountMessage}
</div>
) : null}
<div className="pt-3 pb-4">
<Slider
value={sliderPercentage}
onChange={(v) => onChangeSlider(v)}
step={1}
maxButtonTransition={maxButtonTransition}
/>
</div>
<div className={`pt-8 flex justify-center`}>
<Button
disabled={
inputAmount <= 0 ||
inputAmount > getBalanceForAccount(selectedAccount)
}
onClick={handleNewAccountDeposit}
className="w-full"
>
<div className={`flex items-center justify-center`}>
{submitting && <Loading className="-ml-1 mr-3" />}
Create Account
<ElementTitle noMarignBottom>New Account</ElementTitle>
{showNewAccountName ? (
<>
<div className="flex items-center justify-center text-th-fgd-3 pb-4 pt-2">
Create a public nickname for your account
<Tooltip content="Account names are stored on-chain">
<InformationCircleIcon className="h-5 w-5 ml-2 text-th-primary" />
</Tooltip>
</div>
</Button>
</div>
<div className="pb-2 text-th-fgd-1">
Account Name <span className="text-th-fgd-3">(Optional)</span>
</div>
<Input
type="text"
className={`border border-th-fgd-4 flex-grow`}
error={!!invalidNameMessage}
placeholder="e.g. Calypso"
value={name}
onBlur={validateNameInput}
onChange={(e) => onChangeNameInput(e.target.value)}
/>
{invalidNameMessage ? (
<div className="flex items-center pt-1.5 text-th-red">
<ExclamationCircleIcon className="h-4 w-4 mr-1.5" />
{invalidNameMessage}
</div>
) : null}
<Button
onClick={() => setShowNewAccountName(false)}
disabled={name.length >= 33}
className="mt-4 w-full"
>
Next
</Button>
</>
) : (
<>
<div className="text-th-fgd-3 text-center pb-4 pt-2">
Make a deposit to initialize your new account
</div>
<AccountSelect
symbols={symbols}
accounts={depositAccounts}
selectedAccount={selectedAccount}
onSelectAccount={handleAccountSelect}
/>
<div className="flex justify-between pb-2 pt-4">
<div className={`text-th-fgd-1`}>Amount</div>
<div
className="text-th-fgd-1 underline cursor-pointer default-transition hover:text-th-primary hover:no-underline"
onClick={setMaxForSelectedAccount}
>
Max
</div>
</div>
<div className="flex">
<Input
type="number"
min="0"
className={`border border-th-fgd-4 flex-grow pr-11`}
placeholder="0.00"
error={!!invalidAmountMessage}
onBlur={(e) => validateAmountInput(e.target.value)}
value={inputAmount}
onChange={(e) => onChangeAmountInput(e.target.value)}
suffix={symbol}
/>
</div>
{invalidAmountMessage ? (
<div className="flex items-center pt-1.5 text-th-red">
<ExclamationCircleIcon className="h-4 w-4 mr-1.5" />
{invalidAmountMessage}
</div>
) : null}
<div className="pt-3 pb-4">
<Slider
value={sliderPercentage}
onChange={(v) => onChangeSlider(v)}
step={1}
maxButtonTransition={maxButtonTransition}
/>
</div>
<div className={`pt-8 flex justify-center`}>
<Button
disabled={
inputAmount <= 0 ||
inputAmount > getBalanceForAccount(selectedAccount)
}
onClick={handleNewAccountDeposit}
className="w-full"
>
<div className={`flex items-center justify-center`}>
{submitting && <Loading className="-ml-1 mr-3" />}
Create New Account
</div>
</Button>
</div>
</>
)}
</>
)
}

View File

@ -31,7 +31,7 @@
}
},
"dependencies": {
"@blockworks-foundation/mango-client": "2.1.2",
"@blockworks-foundation/mango-client": "2.1.3",
"@emotion/react": "^11.1.5",
"@emotion/styled": "^11.1.5",
"@headlessui/react": "^1.2.0",

View File

@ -1,11 +1,13 @@
import { useCallback, useState } from 'react'
import { useCallback, useEffect, useState } from 'react'
import {
CurrencyDollarIcon,
ExternalLinkIcon,
LinkIcon,
PencilIcon,
DuplicateIcon,
} from '@heroicons/react/outline'
import useMangoStore from '../stores/useMangoStore'
import { abbreviateAddress } from '../utils'
import { abbreviateAddress, copyToClipboard } from '../utils'
import useMarginInfo from '../hooks/useMarginInfo'
import PageBodyContainer from '../components/PageBodyContainer'
import TopBar from '../components/TopBar'
@ -15,6 +17,9 @@ import AccountOrders from '../components/account-page/AccountOrders'
import AccountHistory from '../components/account-page/AccountHistory'
import AccountsModal from '../components/AccountsModal'
import EmptyState from '../components/EmptyState'
import Button from '../components/Button'
import AccountNameModal from '../components/AccountNameModal'
import { MarginAccount } from '@blockworks-foundation/mango-client'
const TABS = [
'Assets',
@ -25,59 +30,115 @@ const TABS = [
'History',
]
export function getMarginInfoString(marginAccount: MarginAccount) {
return marginAccount?.info
? String.fromCharCode(...marginAccount?.info).replaceAll(
String.fromCharCode(0),
''
)
: 'Account'
}
export default function Account() {
const [activeTab, setActiveTab] = useState(TABS[0])
const [showAccountsModal, setShowAccountsModal] = useState(false)
const [showNameModal, setShowNameModal] = useState(false)
const [isCopied, setIsCopied] = useState(false)
const accountMarginInfo = useMarginInfo()
const connected = useMangoStore((s) => s.wallet.connected)
const selectedMarginAccount = useMangoStore(
(s) => s.selectedMarginAccount.current
)
const marginInfoString = getMarginInfoString(selectedMarginAccount)
const [accountName, setAccountName] = useState(marginInfoString)
const handleTabChange = (tabName) => {
setActiveTab(tabName)
}
const handleCloseAccounts = useCallback(() => {
const handleCloseAccountsModal = useCallback(() => {
setShowAccountsModal(false)
}, [])
const handleCloseNameModal = useCallback(() => {
setShowNameModal(false)
}, [])
useEffect(() => {
if (selectedMarginAccount) {
const marginInfoString = getMarginInfoString(selectedMarginAccount)
setAccountName(marginInfoString)
} else {
setAccountName('')
}
}, [selectedMarginAccount])
useEffect(() => {
if (isCopied) {
const timer = setTimeout(() => {
setIsCopied(false)
}, 1500)
return () => clearTimeout(timer)
}
}, [isCopied])
const handleCopyPublicKey = (code) => {
setIsCopied(true)
copyToClipboard(code)
}
return (
<div className={`bg-th-bkg-1 text-th-fgd-1 transition-all`}>
<TopBar />
<PageBodyContainer>
<div className="flex flex-col sm:flex-row items-center justify-between pt-8 pb-3 sm:pb-6 md:pt-10">
<h1 className={`text-th-fgd-1 text-2xl font-semibold`}>Account</h1>
<div className="flex flex-col md:flex-row md:items-end justify-between pt-8 pb-3 sm:pb-6 md:pt-10">
{selectedMarginAccount ? (
<div className="divide-x divide-th-fgd-4 flex justify-center w-full pt-4 sm:pt-0 sm:justify-end">
<div className="pr-4 text-xs text-th-fgd-1">
<div className="pb-0.5 text-2xs text-th-fgd-3">Owner</div>
<a
className="default-transition flex items-center text-th-fgd-2"
href={`https://explorer.solana.com/address/${selectedMarginAccount?.owner}`}
target="_blank"
rel="noopener noreferrer"
>
<span>{abbreviateAddress(selectedMarginAccount?.owner)}</span>
<ExternalLinkIcon className={`h-3 w-3 ml-1`} />
</a>
</div>
<div className="pl-4 text-xs text-th-fgd-1">
<div className="pb-0.5 text-2xs text-th-fgd-3">
Margin Account
<>
<div className="flex flex-col sm:flex-row sm:items-end pb-4 md:pb-0">
<h1 className={`font-semibold mr-3 text-th-fgd-1 text-2xl`}>
{accountName ? accountName : 'Account'}
</h1>
<div className="flex items-center pb-0.5 text-th-fgd-3 ">
{abbreviateAddress(selectedMarginAccount.publicKey)}
<DuplicateIcon
className="cursor-pointer default-transition h-4 w-4 ml-1.5 hover:text-th-fgd-1"
onClick={() =>
handleCopyPublicKey(selectedMarginAccount.publicKey)
}
/>
{isCopied ? (
<div className="ml-2 text-th-fgd-2 text-xs">Copied!</div>
) : null}
</div>
</div>
<div className="flex items-center">
<Button
className="text-xs flex flex-grow items-center justify-center mr-2 pt-0 pb-0 h-8 pl-3 pr-3"
onClick={() => setShowNameModal(true)}
>
<div className="flex items-center">
<PencilIcon className="h-4 w-4 mr-1.5" />
{accountName ? 'Edit Name' : 'Add Name'}
</div>
</Button>
<a
className="default-transition flex items-center text-th-fgd-2"
className="border border-th-fgd-4 bg-th-bkg-2 default-transition flex flex-grow font-bold h-8 items-center justify-center pl-3 pr-3 rounded-md text-th-fgd-1 text-xs hover:bg-th-bkg-3 hover:text-th-fgd-1 focus:outline-none"
href={`https://explorer.solana.com/address/${selectedMarginAccount?.publicKey}`}
target="_blank"
rel="noopener noreferrer"
>
<span>
{abbreviateAddress(selectedMarginAccount?.publicKey)}
</span>
<ExternalLinkIcon className={`h-3 w-3 ml-1`} />
<span>Explorer</span>
<ExternalLinkIcon className={`h-4 w-4 ml-1.5`} />
</a>
<Button
className="text-xs flex flex-grow items-center justify-center ml-2 pt-0 pb-0 h-8 pl-3 pr-3"
onClick={() => setShowAccountsModal(true)}
>
<div className="flex items-center">
<CurrencyDollarIcon className="h-4 w-4 mr-1.5" />
Accounts
</div>
</Button>
</div>
</div>
</>
) : null}
</div>
<div className="bg-th-bkg-2 overflow-none p-6 rounded-lg">
@ -141,10 +202,17 @@ export default function Account() {
</PageBodyContainer>
{showAccountsModal ? (
<AccountsModal
onClose={handleCloseAccounts}
onClose={handleCloseAccountsModal}
isOpen={showAccountsModal}
/>
) : null}
{showNameModal ? (
<AccountNameModal
accountName={accountName}
isOpen={showNameModal}
onClose={handleCloseNameModal}
/>
) : null}
</div>
)
}

View File

@ -29,6 +29,7 @@ import {
NUM_TOKENS,
} from '@blockworks-foundation/mango-client/lib/layout'
import {
makeAddMarginAccountInfoInstruction,
makeBorrowInstruction,
makeSettleBorrowInstruction,
makeSettleFundsInstruction,
@ -231,7 +232,8 @@ export async function initMarginAccountAndDeposit(
wallet: Wallet,
token: PublicKey,
tokenAcc: PublicKey,
quantity: number
quantity: number,
accountName: string
): Promise<Array<any>> {
const transaction = new Transaction()
const signers = []
@ -283,9 +285,18 @@ export async function initMarginAccountAndDeposit(
programId,
})
const setNameInstruction = makeAddMarginAccountInfoInstruction(
programId,
mangoGroup.publicKey,
accInstr.account.publicKey,
wallet.publicKey,
accountName
)
// Add all instructions to one atomic transaction
transaction.add(accInstr.instruction)
transaction.add(initMarginAccountInstruction)
transaction.add(setNameInstruction)
const tokenIndex = mangoGroup.getTokenIndex(token)
const nativeQuantity = uiToNative(
@ -1514,3 +1525,30 @@ export async function settleAllTrades(
'Settle All Trades'
)
}
export async function addMarginAccountInfo(
connection: Connection,
programId: PublicKey,
mangoGroup: MangoGroup,
marginAccount: MarginAccount,
wallet: Wallet,
info: string
) {
const transaction = new Transaction()
const instruction = makeAddMarginAccountInfoInstruction(
programId,
mangoGroup.publicKey,
marginAccount.publicKey,
wallet.publicKey,
info
)
transaction.add(instruction)
return await packageAndSend(
transaction,
connection,
wallet,
[],
'Add MarginAccount Info'
)
}

View File

@ -986,10 +986,10 @@
resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39"
integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==
"@blockworks-foundation/mango-client@2.1.2":
version "2.1.2"
resolved "https://registry.yarnpkg.com/@blockworks-foundation/mango-client/-/mango-client-2.1.2.tgz#d1a0d4312c7ced1f98aec51aa59dbf3ed89cd3a6"
integrity sha512-o31ahedFM6ZcEn44cnn3img6Cl1fBDjBrI0clblJceB/nD2xRZeVCaQtSwcD5FeKczQAG/Mw13u5VBq/KvPX4w==
"@blockworks-foundation/mango-client@2.1.3":
version "2.1.3"
resolved "https://registry.yarnpkg.com/@blockworks-foundation/mango-client/-/mango-client-2.1.3.tgz#6153c0ac53e2cd7f4c729fb3eea9ef5de1480ba2"
integrity sha512-oD7wl7DUq2E4rSTMxZ/uiS6ZixF9CDh8Z4RSiStcmCe0Lc98gACtJAsloRLieyqTD6369r0ivlLsjk/FDy69SA==
dependencies:
"@project-serum/common" "^0.0.1-beta.3"
"@project-serum/serum" "^0.13.20"