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

View File

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

View File

@ -18,7 +18,7 @@ const Button: FunctionComponent<ButtonProps> = ({
onClick={onClick} onClick={onClick}
disabled={disabled} disabled={disabled}
className={`${className} px-6 py-2 border border-th-fgd-4 bg-th-bkg-2 rounded-md text-th-fgd-1 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`} disabled:text-th-fgd-4 disabled:cursor-not-allowed`}
{...props} {...props}
> >

View File

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

View File

@ -17,6 +17,7 @@ import BorrowModal from './BorrowModal'
import Button from './Button' import Button from './Button'
import Tooltip from './Tooltip' import Tooltip from './Tooltip'
import AccountsModal from './AccountsModal' import AccountsModal from './AccountsModal'
import { getMarginInfoString } from '../pages/account'
export default function MarginBalances() { export default function MarginBalances() {
const selectedMangoGroup = useMangoStore((s) => s.selectedMangoGroup.current) const selectedMangoGroup = useMangoStore((s) => s.selectedMangoGroup.current)
@ -56,7 +57,9 @@ export default function MarginBalances() {
<div className="flex justify-between pb-5"> <div className="flex justify-between pb-5">
<div className="w-8 h-8" /> <div className="w-8 h-8" />
<div className="flex flex-col items-center"> <div className="flex flex-col items-center">
<ElementTitle noMarignBottom>Margin Account</ElementTitle> <ElementTitle noMarignBottom>
{getMarginInfoString(selectedMarginAccount)}
</ElementTitle>
{selectedMarginAccount ? ( {selectedMarginAccount ? (
<Link href={'/account'}> <Link href={'/account'}>
<a className="pt-1 text-th-fgd-3 text-xs underline hover:no-underline"> <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 React, { FunctionComponent, useEffect, useMemo, useState } from 'react'
import { ExclamationCircleIcon } from '@heroicons/react/outline' import {
ExclamationCircleIcon,
InformationCircleIcon,
} from '@heroicons/react/outline'
import { import {
nativeToUi, nativeToUi,
sleep, sleep,
@ -20,6 +23,7 @@ import { PublicKey } from '@solana/web3.js'
import Loading from './Loading' import Loading from './Loading'
import Button from './Button' import Button from './Button'
import Slider from './Slider' import Slider from './Slider'
import Tooltip from './Tooltip'
import { notify } from '../utils/notifications' import { notify } from '../utils/notifications'
interface NewAccountProps { interface NewAccountProps {
@ -32,8 +36,11 @@ const NewAccount: FunctionComponent<NewAccountProps> = ({
const [inputAmount, setInputAmount] = useState(0) const [inputAmount, setInputAmount] = useState(0)
const [submitting, setSubmitting] = useState(false) const [submitting, setSubmitting] = useState(false)
const [invalidAmountMessage, setInvalidAmountMessage] = useState('') const [invalidAmountMessage, setInvalidAmountMessage] = useState('')
const [invalidNameMessage, setInvalidNameMessage] = useState('')
const [sliderPercentage, setSliderPercentage] = useState(0) const [sliderPercentage, setSliderPercentage] = useState(0)
const [maxButtonTransition, setMaxButtonTransition] = useState(false) const [maxButtonTransition, setMaxButtonTransition] = useState(false)
const [name, setName] = useState('')
const [showNewAccountName, setShowNewAccountName] = useState(true)
const { getTokenIndex, symbols } = useMarketList() const { getTokenIndex, symbols } = useMarketList()
const { connection, programId } = useConnection() const { connection, programId } = useConnection()
const mintDecimals = useMangoStore((s) => s.selectedMangoGroup.mintDecimals) const mintDecimals = useMangoStore((s) => s.selectedMangoGroup.mintDecimals)
@ -90,7 +97,8 @@ const NewAccount: FunctionComponent<NewAccountProps> = ({
wallet, wallet,
selectedAccount.account.mint, selectedAccount.account.mint,
selectedAccount.publicKey, selectedAccount.publicKey,
Number(inputAmount) Number(inputAmount),
name
) )
.then(async (_response: Array<any>) => { .then(async (_response: Array<any>) => {
await sleep(1000) await sleep(1000)
@ -142,6 +150,22 @@ const NewAccount: FunctionComponent<NewAccountProps> = ({
validateAmountInput(amount) 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 // turn off slider transition for dragging slider handle interaction
useEffect(() => { useEffect(() => {
if (maxButtonTransition) { if (maxButtonTransition) {
@ -151,67 +175,105 @@ const NewAccount: FunctionComponent<NewAccountProps> = ({
return ( return (
<> <>
<ElementTitle noMarignBottom>Create Margin Account</ElementTitle> <ElementTitle noMarignBottom>New Account</ElementTitle>
<div className="text-th-fgd-3 text-center pb-4 pt-2"> {showNewAccountName ? (
Make a deposit to initialize a new margin account <>
</div> <div className="flex items-center justify-center text-th-fgd-3 pb-4 pt-2">
<AccountSelect Create a public nickname for your account
symbols={symbols} <Tooltip content="Account names are stored on-chain">
accounts={depositAccounts} <InformationCircleIcon className="h-5 w-5 ml-2 text-th-primary" />
selectedAccount={selectedAccount} </Tooltip>
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
</div> </div>
</Button> <div className="pb-2 text-th-fgd-1">
</div> 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": { "dependencies": {
"@blockworks-foundation/mango-client": "2.1.2", "@blockworks-foundation/mango-client": "2.1.3",
"@emotion/react": "^11.1.5", "@emotion/react": "^11.1.5",
"@emotion/styled": "^11.1.5", "@emotion/styled": "^11.1.5",
"@headlessui/react": "^1.2.0", "@headlessui/react": "^1.2.0",

View File

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

View File

@ -29,6 +29,7 @@ import {
NUM_TOKENS, NUM_TOKENS,
} from '@blockworks-foundation/mango-client/lib/layout' } from '@blockworks-foundation/mango-client/lib/layout'
import { import {
makeAddMarginAccountInfoInstruction,
makeBorrowInstruction, makeBorrowInstruction,
makeSettleBorrowInstruction, makeSettleBorrowInstruction,
makeSettleFundsInstruction, makeSettleFundsInstruction,
@ -231,7 +232,8 @@ export async function initMarginAccountAndDeposit(
wallet: Wallet, wallet: Wallet,
token: PublicKey, token: PublicKey,
tokenAcc: PublicKey, tokenAcc: PublicKey,
quantity: number quantity: number,
accountName: string
): Promise<Array<any>> { ): Promise<Array<any>> {
const transaction = new Transaction() const transaction = new Transaction()
const signers = [] const signers = []
@ -283,9 +285,18 @@ export async function initMarginAccountAndDeposit(
programId, programId,
}) })
const setNameInstruction = makeAddMarginAccountInfoInstruction(
programId,
mangoGroup.publicKey,
accInstr.account.publicKey,
wallet.publicKey,
accountName
)
// Add all instructions to one atomic transaction // Add all instructions to one atomic transaction
transaction.add(accInstr.instruction) transaction.add(accInstr.instruction)
transaction.add(initMarginAccountInstruction) transaction.add(initMarginAccountInstruction)
transaction.add(setNameInstruction)
const tokenIndex = mangoGroup.getTokenIndex(token) const tokenIndex = mangoGroup.getTokenIndex(token)
const nativeQuantity = uiToNative( const nativeQuantity = uiToNative(
@ -1514,3 +1525,30 @@ export async function settleAllTrades(
'Settle All Trades' '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" resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39"
integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw== integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==
"@blockworks-foundation/mango-client@2.1.2": "@blockworks-foundation/mango-client@2.1.3":
version "2.1.2" version "2.1.3"
resolved "https://registry.yarnpkg.com/@blockworks-foundation/mango-client/-/mango-client-2.1.2.tgz#d1a0d4312c7ced1f98aec51aa59dbf3ed89cd3a6" resolved "https://registry.yarnpkg.com/@blockworks-foundation/mango-client/-/mango-client-2.1.3.tgz#6153c0ac53e2cd7f4c729fb3eea9ef5de1480ba2"
integrity sha512-o31ahedFM6ZcEn44cnn3img6Cl1fBDjBrI0clblJceB/nD2xRZeVCaQtSwcD5FeKczQAG/Mw13u5VBq/KvPX4w== integrity sha512-oD7wl7DUq2E4rSTMxZ/uiS6ZixF9CDh8Z4RSiStcmCe0Lc98gACtJAsloRLieyqTD6369r0ivlLsjk/FDy69SA==
dependencies: dependencies:
"@project-serum/common" "^0.0.1-beta.3" "@project-serum/common" "^0.0.1-beta.3"
"@project-serum/serum" "^0.13.20" "@project-serum/serum" "^0.13.20"