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:
parent
0ef18df358
commit
27c6c8b36d
|
@ -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
|
|
@ -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>
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
|
|
|
@ -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])
|
||||||
|
|
|
@ -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}
|
||||||
>
|
>
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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">
|
||||||
|
|
|
@ -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>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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",
|
||||||
|
|
|
@ -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>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
|
@ -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"
|
||||||
|
|
Loading…
Reference in New Issue