e5f41731ae
* bootstrap next app + add overview page * fix AssetList type * fix up nav stuff * Refactor Nav component and add network components pages * Refactor WorldMap component and update TelegramIcon, GitHubIcon, NymVpnIcon, DiscordIcon, and TwitterIcon components * add service providers page * mixnodes page * delegations page + use material react table for all tables * nodes map page * Refactor StyledLink component and remove unnecessary console.log statements * Refactor ESLint configuration, remove unused dependencies, and update component imports * update deps * Refactor imports and update dependencies * fix dark mode * build single mixnode page * build single gateway page * Refactor handleOnDelegate function to use useCallback in mixnodes page.tsx * Add defaults for constants --------- Co-authored-by: Mark Sinclair <mmsinclair@users.noreply.github.com>
192 lines
4.9 KiB
TypeScript
192 lines
4.9 KiB
TypeScript
'use client'
|
|
|
|
import React, { useState } from 'react'
|
|
import { Box, SxProps } from '@mui/material'
|
|
import { IdentityKeyFormField } from '@nymproject/react/mixnodes/IdentityKeyFormField'
|
|
import { CurrencyFormField } from '@nymproject/react/currency/CurrencyFormField'
|
|
import { CurrencyDenom, DecCoin } from '@nymproject/types'
|
|
import { useWalletContext } from '@/app/context/wallet'
|
|
import { urls } from '@/app/utils'
|
|
import { useDelegationsContext } from '@/app/context/delegations'
|
|
import { validateAmount } from '@/app/utils/currency'
|
|
import { SimpleModal } from './SimpleModal'
|
|
import { ModalListItem } from './ModalListItem'
|
|
import { DelegationModalProps } from './DelegationModal'
|
|
|
|
const MIN_AMOUNT_TO_DELEGATE = 10
|
|
|
|
type Props = {
|
|
mixId: number
|
|
identityKey: string
|
|
header?: string
|
|
buttonText?: string
|
|
rewardInterval?: string
|
|
estimatedReward?: number
|
|
profitMarginPercentage?: string | null
|
|
nodeUptimePercentage?: number | null
|
|
denom: CurrencyDenom
|
|
sx?: SxProps
|
|
backdropProps?: object
|
|
onClose: () => void
|
|
onOk?: (delegationModalProps: DelegationModalProps) => void
|
|
}
|
|
|
|
export const DelegateModal = ({
|
|
mixId,
|
|
identityKey,
|
|
onClose,
|
|
onOk,
|
|
denom,
|
|
sx,
|
|
}: Props) => {
|
|
const [amount, setAmount] = useState<DecCoin | undefined>({
|
|
amount: '10',
|
|
denom: 'nym',
|
|
})
|
|
const [isValidated, setValidated] = useState<boolean>(false)
|
|
const [errorAmount, setErrorAmount] = useState<string | undefined>()
|
|
|
|
const { address, balance } = useWalletContext()
|
|
const { handleDelegate } = useDelegationsContext()
|
|
|
|
const validate = async () => {
|
|
let newValidatedValue = true
|
|
let errorAmountMessage
|
|
|
|
if (amount && !(await validateAmount(amount.amount, '0'))) {
|
|
newValidatedValue = false
|
|
errorAmountMessage = 'Please enter a valid amount'
|
|
}
|
|
|
|
if (amount && +amount.amount < MIN_AMOUNT_TO_DELEGATE) {
|
|
errorAmountMessage = `Min. delegation amount: ${MIN_AMOUNT_TO_DELEGATE} ${denom.toUpperCase()}`
|
|
newValidatedValue = false
|
|
}
|
|
|
|
if (!amount?.amount.length) {
|
|
newValidatedValue = false
|
|
}
|
|
|
|
if (amount && balance.data && +balance.data - +amount.amount <= 0) {
|
|
errorAmountMessage = 'Not enough funds'
|
|
newValidatedValue = false
|
|
}
|
|
|
|
setErrorAmount(errorAmountMessage)
|
|
setValidated(newValidatedValue)
|
|
}
|
|
|
|
const delegateToMixnode = async ({
|
|
delegationMixId,
|
|
delegationAmount,
|
|
}: {
|
|
delegationMixId: number
|
|
delegationAmount: string
|
|
}) => {
|
|
try {
|
|
const tx = await handleDelegate(delegationMixId, delegationAmount)
|
|
return tx
|
|
} catch (e) {
|
|
console.error('Failed to delegate to mixnode', e)
|
|
throw e
|
|
}
|
|
}
|
|
|
|
const handleConfirm = async () => {
|
|
if (mixId && amount && onOk) {
|
|
onOk({
|
|
status: 'loading',
|
|
})
|
|
try {
|
|
if (!address) {
|
|
throw new Error('Please connect your wallet')
|
|
}
|
|
|
|
const tx = await delegateToMixnode({
|
|
delegationMixId: mixId,
|
|
delegationAmount: amount.amount,
|
|
})
|
|
|
|
if (!tx) {
|
|
throw new Error('Failed to delegate')
|
|
}
|
|
|
|
onOk({
|
|
status: 'success',
|
|
message: 'Delegation can take up to one hour to process',
|
|
transactions: [
|
|
{
|
|
url: `${urls('MAINNET').blockExplorer}/transaction/${
|
|
tx.transactionHash
|
|
}`,
|
|
hash: tx.transactionHash,
|
|
},
|
|
],
|
|
})
|
|
} catch (e) {
|
|
console.error('Failed to delegate', e)
|
|
onOk({
|
|
status: 'error',
|
|
message: (e as Error).message,
|
|
})
|
|
}
|
|
}
|
|
}
|
|
|
|
const handleAmountChanged = (newAmount: DecCoin) => {
|
|
setAmount(newAmount)
|
|
}
|
|
|
|
React.useEffect(() => {
|
|
validate()
|
|
}, [amount, identityKey, mixId])
|
|
|
|
return (
|
|
<SimpleModal
|
|
open
|
|
onClose={onClose}
|
|
onOk={handleConfirm}
|
|
header="Delegate"
|
|
okLabel="Delegate"
|
|
okDisabled={!isValidated}
|
|
sx={sx}
|
|
>
|
|
<Box sx={{ mt: 3 }} gap={2}>
|
|
<IdentityKeyFormField
|
|
required
|
|
fullWidth
|
|
label="Node identity key"
|
|
onChanged={() => undefined}
|
|
initialValue={identityKey}
|
|
readOnly
|
|
showTickOnValid={false}
|
|
/>
|
|
</Box>
|
|
|
|
<Box display="flex" gap={2} alignItems="center" sx={{ mt: 3 }}>
|
|
<CurrencyFormField
|
|
showCoinMark={false}
|
|
required
|
|
fullWidth
|
|
autoFocus
|
|
label="Amount"
|
|
initialValue={amount?.amount || '10'}
|
|
onChanged={handleAmountChanged}
|
|
denom={denom}
|
|
validationError={errorAmount}
|
|
/>
|
|
</Box>
|
|
<Box sx={{ mt: 3 }}>
|
|
<ModalListItem
|
|
label="Account balance"
|
|
value={`${balance.data} NYM`}
|
|
divider
|
|
fontWeight={600}
|
|
/>
|
|
</Box>
|
|
|
|
<ModalListItem label="Est. fee for this transaction will be calculated in your connected wallet" />
|
|
</SimpleModal>
|
|
)
|
|
}
|