From 12d07fd87075178fecdb2970420f66bd9f175ea2 Mon Sep 17 00:00:00 2001 From: fmtabbara Date: Thu, 22 Jul 2021 22:11:59 +0100 Subject: [PATCH] use helper function + code tidy --- wallet-web/common/helpers.ts | 260 +++++----- wallet-web/components/bond/BondNodeForm.tsx | 56 +- wallet-web/components/bond/NodeBond.tsx | 13 +- .../components/delegate/DelegateForm.tsx | 33 +- .../components/delegate/NodeDelegation.tsx | 13 +- .../delegation-check/DelegationCheck.tsx | 212 ++++---- wallet-web/components/unbond/UnbondNode.tsx | 261 +++++----- .../undelegate/NodeUndelegation.tsx | 181 ++++--- wallet-web/hooks/useGetBalance.ts | 36 +- wallet-web/pages/send.tsx | 488 +++++++++--------- 10 files changed, 816 insertions(+), 737 deletions(-) diff --git a/wallet-web/common/helpers.ts b/wallet-web/common/helpers.ts index a432413b05..56f821fd1e 100644 --- a/wallet-web/common/helpers.ts +++ b/wallet-web/common/helpers.ts @@ -1,147 +1,177 @@ -import { createStyles, makeStyles, Theme } from "@material-ui/core/styles"; +import { createStyles, makeStyles, Theme } from '@material-ui/core/styles' import ValidatorClient, { - nymGasLimits, - nymGasPrice, - printableCoin -} from "@nymproject/nym-validator-client"; -import { ADDRESS_LENGTH, DENOM, KEY_LENGTH } from "../pages/_app"; -import { buildFeeTable } from "@cosmjs/launchpad"; -import bs58 from "bs58"; + nymGasLimits, + nymGasPrice, + printableCoin, +} from '@nymproject/nym-validator-client' +import { ADDRESS_LENGTH, DENOM, KEY_LENGTH } from '../pages/_app' +import { buildFeeTable } from '@cosmjs/launchpad' +import bs58 from 'bs58' export const makeBasicStyle = makeStyles((theme: Theme) => - createStyles({ - appBar: { - position: 'relative', - }, - root: { - textAlign: 'center', - paddingTop: theme.spacing(4), - }, - layout: { - width: 'auto', - marginLeft: theme.spacing(2), - marginRight: theme.spacing(2), - [theme.breakpoints.up(600 + theme.spacing(2) * 2)]: { - width: 650, - marginLeft: 'auto', - marginRight: 'auto', - }, - }, - paper: { - marginTop: theme.spacing(3), - marginBottom: theme.spacing(3), - padding: theme.spacing(2), - [theme.breakpoints.up(600 + theme.spacing(3) * 2)]: { - marginTop: theme.spacing(6), - marginBottom: theme.spacing(6), - padding: theme.spacing(3), - }, - }, - stepper: { - padding: theme.spacing(3, 0, 5), - }, - buttons: { - display: 'flex', - justifyContent: 'flex-end', - }, - button: { - marginTop: theme.spacing(3), - marginLeft: theme.spacing(1), - }, - menuButton: { - marginRight: theme.spacing(2), - }, - list: { - width: 250, - }, - wrapper: { - marginTop: theme.spacing(1), - marginBottom: theme.spacing(3), - } - }) -); + createStyles({ + appBar: { + position: 'relative', + }, + root: { + textAlign: 'center', + paddingTop: theme.spacing(4), + }, + layout: { + width: 'auto', + marginLeft: theme.spacing(2), + marginRight: theme.spacing(2), + [theme.breakpoints.up(600 + theme.spacing(2) * 2)]: { + width: 650, + marginLeft: 'auto', + marginRight: 'auto', + }, + }, + paper: { + marginTop: theme.spacing(3), + marginBottom: theme.spacing(3), + padding: theme.spacing(2), + [theme.breakpoints.up(600 + theme.spacing(3) * 2)]: { + marginTop: theme.spacing(6), + marginBottom: theme.spacing(6), + padding: theme.spacing(3), + }, + }, + stepper: { + padding: theme.spacing(3, 0, 5), + }, + buttons: { + display: 'flex', + justifyContent: 'flex-end', + }, + button: { + marginTop: theme.spacing(3), + marginLeft: theme.spacing(1), + }, + menuButton: { + marginRight: theme.spacing(2), + }, + list: { + width: 250, + }, + wrapper: { + marginTop: theme.spacing(1), + marginBottom: theme.spacing(3), + }, + }) +) type NodeOwnership = { - ownsMixnode: boolean, - ownsGateway: boolean + ownsMixnode: boolean + ownsGateway: boolean } -export async function checkNodesOwnership(client: ValidatorClient): Promise { - const ownsMixnodePromise = client.ownsMixNode(); - const ownsGatewayPromise = client.ownsGateway(); +export async function checkNodesOwnership( + client: ValidatorClient +): Promise { + const ownsMixnodePromise = client.ownsMixNode() + const ownsGatewayPromise = client.ownsGateway() - let ownsMixnode = false; - let ownsGateway = false; + let ownsMixnode = false + let ownsGateway = false - await Promise.allSettled([ownsMixnodePromise, ownsGatewayPromise]).then((results) => { - if (results[0].status === "fulfilled") { - ownsMixnode = results[0].value - } else { - console.error("failed to check for mixnode ownership") - } - if (results[1].status === "fulfilled") { - ownsGateway = results[1].value - } else { - console.error("failed to check for gateway ownership") - } - }) - - return { - ownsMixnode, - ownsGateway + await Promise.allSettled([ownsMixnodePromise, ownsGatewayPromise]).then( + (results) => { + if (results[0].status === 'fulfilled') { + ownsMixnode = results[0].value + } else { + console.error('failed to check for mixnode ownership') + } + if (results[1].status === 'fulfilled') { + ownsGateway = results[1].value + } else { + console.error('failed to check for gateway ownership') + } } + ) + + return { + ownsMixnode, + ownsGateway, + } } export const validateClientAddress = (address: string): boolean => { - return address.length === ADDRESS_LENGTH && address.startsWith(DENOM) + return address.length === ADDRESS_LENGTH && address.startsWith(DENOM) } export const validateIdentityKey = (key: string): boolean => { - try { - const bytes = bs58.decode(key); - // of length 32 - return bytes.length === KEY_LENGTH; - } catch { - return false - } + try { + const bytes = bs58.decode(key) + // of length 32 + return bytes.length === KEY_LENGTH + } catch { + return false + } } export const validateRawPort = (rawPort: string): boolean => { - // first of all it must be an integer - const port = parseInt(rawPort) - if (port == null) { - return false - } - // and it must be a non-zero 16 bit unsigned integer - return port >= 1 && port <= 65535 + // first of all it must be an integer + const port = parseInt(rawPort) + if (port == null) { + return false + } + // and it must be a non-zero 16 bit unsigned integer + return port >= 1 && port <= 65535 } export const basicRawCoinValueValidation = (rawAmount: string): boolean => { - let amountFloat = parseFloat(rawAmount) - if (isNaN(amountFloat)) { - return false - } + let amountFloat = parseFloat(rawAmount) + if (isNaN(amountFloat)) { + return false + } - // it cannot have more than 6 decimal places - if ((amountFloat) != parseFloat(amountFloat.toFixed(6))) { - return false - } + // it cannot have more than 6 decimal places + if (amountFloat != parseFloat(amountFloat.toFixed(6))) { + return false + } - // it cannot be larger than the total supply - if (amountFloat > 1_000_000_000_000_000) { - return false - } + // it cannot be larger than the total supply + if (amountFloat > 1_000_000_000_000_000) { + return false + } - // it can't be lower than one micro coin - return amountFloat >= 0.000001; + // it can't be lower than one micro coin + return amountFloat >= 0.000001 } export const getDisplayExecGasFee = (): string => { - const table = buildFeeTable(nymGasPrice(DENOM), nymGasLimits, nymGasLimits) - return printableCoin(table.exec.amount[0]) + const table = buildFeeTable(nymGasPrice(DENOM), nymGasLimits, nymGasLimits) + return printableCoin(table.exec.amount[0]) } export const getDisplaySendGasFee = (): string => { - const table = buildFeeTable(nymGasPrice(DENOM), nymGasLimits, nymGasLimits) - return printableCoin(table.send.amount[0]) + const table = buildFeeTable(nymGasPrice(DENOM), nymGasLimits, nymGasLimits) + return printableCoin(table.send.amount[0]) +} + +// Check amount to bond or delegate is valid +export const checkAllocationSize = ( + allocationValue: number, + walletValue: number +) => { + if (allocationValue > walletValue) { + return { + error: true, + message: 'The allocation size is greater than the value of your wallet', + } + } + + if (walletValue > 1 && walletValue - allocationValue < 1) { + return { + error: false, + message: + "You're about to allocate all of your tokens. You may want to keep some in order to unbond this mixnode at a later time.", + } + } + + return { + error: false, + message: undefined, + } } diff --git a/wallet-web/components/bond/BondNodeForm.tsx b/wallet-web/components/bond/BondNodeForm.tsx index 7d381dfcd8..36d0eeebaf 100644 --- a/wallet-web/components/bond/BondNodeForm.tsx +++ b/wallet-web/components/bond/BondNodeForm.tsx @@ -16,6 +16,7 @@ import { NodeType } from '../../common/node' import { theme } from '../../lib/theme' import { basicRawCoinValueValidation, + checkAllocationSize, makeBasicStyle, validateRawPort, } from '../../common/helpers' @@ -72,7 +73,8 @@ export default function BondNodeForm(props: TBondNodeFormProps) { }) const [advancedShown, setAdvancedShown] = React.useState(false) - const [allocationWarning, setAllocationWarning] = useState(false) + const [allocationWarning, setAllocationWarning] = useState() + const [isValidAmount, setIsValidAmount] = useState(true) const { getBalance, accountBalance } = useGetBalance() useEffect(() => { @@ -83,6 +85,27 @@ export default function BondNodeForm(props: TBondNodeFormProps) { setAdvancedShown((prevSet) => !prevSet) } + const handleAmountChange = (e: ChangeEvent) => { + const parsed = +e.target.value + const balance = +accountBalance.amount + + if (isNaN(parsed)) { + setIsValidAmount(false) + } else { + const allocationCheck = checkAllocationSize( + +printableBalanceToNative(e.target.value), + balance + ) + if (allocationCheck.error) { + setAllocationWarning(allocationCheck.message) + setIsValidAmount(false) + } else { + setAllocationWarning(allocationCheck.message) + setIsValidAmount(true) + } + } + } + const validateForm = ({ amount, sphinxKey, @@ -287,37 +310,27 @@ export default function BondNodeForm(props: TBondNodeFormProps) { label={`Amount to bond (minimum ${nativeToPrintable( minimumBond.amount )} ${minimumBond.denom})`} - error={!validity.validAmount} - {...(!validity.validAmount - ? { - helperText: `Enter a valid bond amount (minimum ${nativeToPrintable( + error={!validateAmount} + helperText={ + !validateAmount + ? `Enter a valid bond amount (minimum ${nativeToPrintable( minimumBond.amount - )})`, - } - : {})} + )})` + : '' + } fullWidth InputProps={{ endAdornment: ( {DENOM} ), }} - onChange={(e: ChangeEvent) => { - if ( - typeof parseInt(e.target.value) === 'number' && - parseInt(accountBalance.amount) - parseInt(e.target.value) < 1 - ) { - setAllocationWarning(true) - } else { - setAllocationWarning(false) - } - }} + onChange={handleAmountChange} /> {allocationWarning && ( - - You're about to allocate all of your tokens. You may want to keep - some in order to unbond this mixnode at a later time. + + {allocationWarning} )} @@ -471,6 +484,7 @@ export default function BondNodeForm(props: TBondNodeFormProps) { color='primary' type='submit' className={classes.button} + disabled={!isValidAmount} > Bond diff --git a/wallet-web/components/bond/NodeBond.tsx b/wallet-web/components/bond/NodeBond.tsx index 69513ad3e6..3e14c4eaaa 100644 --- a/wallet-web/components/bond/NodeBond.tsx +++ b/wallet-web/components/bond/NodeBond.tsx @@ -27,8 +27,7 @@ const BondNode = () => { const router = useRouter() const { client } = useContext(ValidatorClientContext) - const [bondingStarted, setBondingStarted] = React.useState(false) - const [bondingFinished, setBondingFinished] = React.useState(false) + const [isLoading, setIsLoading] = React.useState(false) const [bondingError, setBondingError] = React.useState(null) const [checkedOwnership, setCheckedOwnership] = React.useState(false) @@ -61,7 +60,7 @@ const BondNode = () => { }, [client]) const bondNode = async (bondingInformation: BondingInformation) => { - setBondingStarted(true) + setIsLoading(true) console.log(`BOND button pressed`) console.log(bondingInformation) @@ -80,7 +79,7 @@ const BondNode = () => { console.log('bonded mixnode!', value) }) .catch(setBondingError) - .finally(() => setBondingFinished(true)) + .finally(() => setIsLoading(false)) } else { let gateway = bondingInformation.nodeDetails as Gateway client @@ -89,7 +88,7 @@ const BondNode = () => { console.log('bonded gateway!', value) }) .catch(setBondingError) - .finally(() => setBondingFinished(true)) + .finally(() => setIsLoading(false)) } } @@ -135,7 +134,7 @@ const BondNode = () => { } // we haven't clicked bond button yet - if (!bondingStarted) { + if (!isLoading) { return ( <> @@ -152,7 +151,7 @@ const BondNode = () => { // We started bonding return ( void @@ -21,33 +23,34 @@ export default function DelegateForm(props: DelegateFormProps) { const [validAmount, setValidAmount] = useState(true) const [validIdentity, setValidIdentity] = useState(true) - const [allocationWarning, setAllocationWarning] = useState(false) + const [allocationWarning, setAllocationWarning] = useState() const { getBalance, accountBalance } = useGetBalance() useEffect(() => { getBalance() }, [getBalance]) - // const [checkboxSet, setCheckboxSet] = React.useState(false) - - // const handleCheckboxToggle = () => { - // setCheckboxSet((prevSet) => !prevSet); - // } - const handleAmountChange = (event: any) => { // don't ask me about that. javascript works in mysterious ways // and this is apparently a good way of checking if string // is purely made of numeric characters - let parsed = +event.target.value + const parsed = +event.target.value + const balance = +accountBalance.amount + if (isNaN(parsed)) { setValidAmount(false) } else { - if (parsed > 0 && parseInt(accountBalance.amount) - parsed < 1) { - setAllocationWarning(true) + const allocationCheck = checkAllocationSize( + +printableBalanceToNative(event.target.value), + balance + ) + if (allocationCheck.error) { + setAllocationWarning(allocationCheck.message) + setValidAmount(false) } else { - setAllocationWarning(false) + setAllocationWarning(allocationCheck.message) + setValidAmount(true) } - setValidAmount(true) } } @@ -111,9 +114,8 @@ export default function DelegateForm(props: DelegateFormProps) { {allocationWarning && ( - - You're about to allocate all of your tokens. You may want to keep - some in order to unbond this mixnode at a later time. + + {allocationWarning} )} @@ -137,6 +139,7 @@ export default function DelegateForm(props: DelegateFormProps) { color='primary' type='submit' className={classes.button} + disabled={!validAmount} > Delegate stake diff --git a/wallet-web/components/delegate/NodeDelegation.tsx b/wallet-web/components/delegate/NodeDelegation.tsx index a865ba8606..92f7426939 100644 --- a/wallet-web/components/delegate/NodeDelegation.tsx +++ b/wallet-web/components/delegate/NodeDelegation.tsx @@ -21,8 +21,7 @@ const DelegateToNode = () => { const router = useRouter() const { client } = useContext(ValidatorClientContext) - const [delegationStarted, setDelegationStarted] = React.useState(false) - const [delegationFinished, setDelegationFinished] = React.useState(false) + const [isLoading, setIsLoading] = React.useState(false) const [delegationError, setDelegationError] = React.useState(null) const [nodeType, setNodeType] = React.useState(NodeType.Mixnode) @@ -52,7 +51,7 @@ const DelegateToNode = () => { setNodeIdentity(nodeIdentity) setStakeValue(printableCoin(delegationValue)) - setDelegationStarted(true) + setIsLoading(true) if (nodeType == NodeType.Mixnode) { client @@ -61,7 +60,7 @@ const DelegateToNode = () => { console.log('delegated to mixnode!', value) }) .catch(setDelegationError) - .finally(() => setDelegationFinished(true)) + .finally(() => setIsLoading(false)) } else { client .delegateToGateway(nodeIdentity, delegationValue) @@ -69,7 +68,7 @@ const DelegateToNode = () => { console.log('delegated to gateway!', value) }) .catch(setDelegationError) - .finally(() => setDelegationFinished(true)) + .finally(() => setIsLoading(false)) } } @@ -80,7 +79,7 @@ const DelegateToNode = () => { } // we haven't clicked delegate button yet - if (!delegationStarted) { + if (!isLoading) { return ( <> @@ -92,7 +91,7 @@ const DelegateToNode = () => { // We started delegation return ( { - const classes = makeBasicStyle(theme); - const router = useRouter() - const {client} = useContext(ValidatorClientContext) + const classes = makeBasicStyle(theme) + const router = useRouter() + const { client } = useContext(ValidatorClientContext) - const [checkStarted, setCheckStarted] = React.useState(false) - const [checkFinished, setCheckFinished] = React.useState(false) - const [checkError, setCheckError] = React.useState(null) + const [isLoading, setIsLoading] = React.useState(false) + const [checkError, setCheckError] = React.useState(null) - const [nodeType, setNodeType] = React.useState(NodeType.Mixnode) - const [stakeValue, setStakeValue] = React.useState("0") - const [nodeIdentity, setNodeIdentity] = React.useState("") + const [nodeType, setNodeType] = React.useState(NodeType.Mixnode) + const [stakeValue, setStakeValue] = React.useState('0') + const [nodeIdentity, setNodeIdentity] = React.useState('') + useEffect(() => { + const checkClient = async () => { + if (client === null) { + await router.push('/') + } + } + checkClient() + }, [client]) - useEffect(() => { - const checkClient = async () => { - if (client === null) { - await router.push("/") - } - } - checkClient() - }, [client]) + // eh, crude, but I guess does the trick + const handleDelegationCheckError = (err: Error) => { + if ( + err.message.includes( + 'Could not find any delegation information associated with' + ) + ) { + setStakeValue('0 HAL') + } else { + setCheckError(err) + } + } + const checkDelegation = async (event) => { + event.preventDefault() - // eh, crude, but I guess does the trick - const handleDelegationCheckError = (err: Error) => { - if (err.message.includes("Could not find any delegation information associated with")) { - setStakeValue("0 HAL") - } else { - setCheckError(err) - } + console.log(`CHECK DELEGATION button pressed`) + + let identity = event.target.identity.value + setNodeIdentity(identity) + setIsLoading(true) + + if (nodeType == NodeType.Mixnode) { + client + .getMixDelegation(identity, client.address) + .then((value) => { + setStakeValue(printableCoin(value.amount)) + }) + .catch(handleDelegationCheckError) + .finally(() => setIsLoading(false)) + } else { + client + .getGatewayDelegation(identity, client.address) + .then((value) => { + setStakeValue(printableCoin(value.amount)) + }) + .catch(handleDelegationCheckError) + .finally(() => setIsLoading(false)) + } + } + + const getDelegationCheckContent = () => { + // we're not signed in + if (client === null) { + return } - const checkDelegation = async (event) => { - event.preventDefault(); - - console.log(`CHECK DELEGATION button pressed`); - - let identity = event.target.identity.value - setNodeIdentity(identity) - setCheckStarted(true) - - if (nodeType == NodeType.Mixnode) { - client.getMixDelegation(identity, client.address).then((value => { - setStakeValue(printableCoin(value.amount)) - })).catch(handleDelegationCheckError).finally(() => setCheckFinished(true)) - } else { - client.getGatewayDelegation(identity, client.address).then((value => { - setStakeValue(printableCoin(value.amount)) - })).catch(handleDelegationCheckError).finally(() => setCheckFinished(true)) - } - - } - - const getDelegationCheckContent = () => { - // we're not signed in - if (client === null) { - return () - } - - // we haven't clicked delegate button yet - if (!checkStarted) { - return ( - - - - - ) - } - - // We started the check - const stakeMessage = `Current stake on ${nodeType} ${nodeIdentity} is ${stakeValue}` - return ( - - ) + // we haven't clicked delegate button yet + if (!isLoading) { + return ( + <> + + + + ) } + // We started the check + const stakeMessage = `Current stake on ${nodeType} ${nodeIdentity} is ${stakeValue}` return ( - -
- - - Check your stake on a {nodeType} - - {getDelegationCheckContent()} - -
-
- ); + + ) + } + + return ( + <> +
+ + + Check your stake on a {nodeType} + + {getDelegationCheckContent()} + +
+ + ) } - -export default DelegationCheck; +export default DelegationCheck diff --git a/wallet-web/components/unbond/UnbondNode.tsx b/wallet-web/components/unbond/UnbondNode.tsx index d60e9ee7bf..81d8cd7fe7 100644 --- a/wallet-web/components/unbond/UnbondNode.tsx +++ b/wallet-web/components/unbond/UnbondNode.tsx @@ -1,141 +1,148 @@ -import { NodeType } from "../../common/node"; -import { useRouter } from "next/router"; -import React, { useContext, useEffect } from "react"; -import { ValidatorClientContext } from "../../contexts/ValidatorClient"; -import NoClientError from "../NoClientError"; -import { Grid, LinearProgress, Paper } from "@material-ui/core"; -import Typography from "@material-ui/core/Typography"; -import UnbondNotice from "./UnbondNotice"; -import Confirmation from "../Confirmation"; -import { theme } from "../../lib/theme"; -import { checkNodesOwnership, makeBasicStyle } from "../../common/helpers"; -import ExecFeeNotice from "../ExecFeeNotice"; +import React, { useContext, useEffect } from 'react' +import Typography from '@material-ui/core/Typography' +import { Grid, LinearProgress, Paper } from '@material-ui/core' +import { useRouter } from 'next/router' +import { NodeType } from '../../common/node' +import { ValidatorClientContext } from '../../contexts/ValidatorClient' +import NoClientError from '../NoClientError' +import UnbondNotice from './UnbondNotice' +import Confirmation from '../Confirmation' +import { theme } from '../../lib/theme' +import { checkNodesOwnership, makeBasicStyle } from '../../common/helpers' +import ExecFeeNotice from '../ExecFeeNotice' const UnbondNode = () => { - const classes = makeBasicStyle(theme); - const router = useRouter() - const {client} = useContext(ValidatorClientContext) + const classes = makeBasicStyle(theme) + const router = useRouter() + const { client } = useContext(ValidatorClientContext) - const [unbondingStarted, setUnbondingStarted] = React.useState(false) - const [unbondingFinished, setUnbondingFinished] = React.useState(false) - const [unbondingError, setUnbondingError] = React.useState(null) + const [isLoading, setIsLoading] = React.useState(false) + const [unbondingError, setUnbondingError] = React.useState(null) - const [checkedOwnership, setCheckedOwnership] = React.useState(false) - const [ownsMixnode, setOwnsMixnode] = React.useState(false) - const [ownsGateway, setOwnsGateway] = React.useState(false) + const [checkedOwnership, setCheckedOwnership] = React.useState(false) + const [ownsMixnode, setOwnsMixnode] = React.useState(false) + const [ownsGateway, setOwnsGateway] = React.useState(false) - const [nodeType, setNodeType] = React.useState(NodeType.Mixnode) + const [nodeType, setNodeType] = React.useState(NodeType.Mixnode) - useEffect(() => { - const checkOwnership = async () => { - if (client === null) { - await router.push("/") - } else { - const nodeOwnership = await checkNodesOwnership(client).finally(() => setCheckedOwnership(true)); - setOwnsMixnode(nodeOwnership.ownsMixnode) - setOwnsGateway(nodeOwnership.ownsGateway) - if (nodeOwnership.ownsGateway) { - setNodeType(NodeType.Gateway) - } - } - } - checkOwnership() - }, [client]) - - const unbondNode = async (event) => { - setUnbondingStarted(true) - event.preventDefault(); - console.log(`UNBOND button pressed`); - - if (nodeType == NodeType.Mixnode) { - client.unbondMixnode() - .then(value => console.log("unbonded mixnode!", value)) - .catch(err => setUnbondingError(err)) - .finally(() => setUnbondingFinished(true)) - } else { - client.unbondGateway() - .then(value => console.log("unbonded gateway!", value)) - .catch(err => setUnbondingError(err)) - .finally(() => setUnbondingFinished(true)) - } - } - - const getUnbondContent = () => { - // we're not signed in - if (client === null) { - return () - } - - // we haven't checked whether we actually own a node to unbond - if (!checkedOwnership) { - return () - } - - // somehow this address has both a mixnode and a gateway bonded - this is super undesirable - // if that happens it means the user must have sent transactions outside the wallet before the contract update - // so they can send transactions outside the wallet to fix themselves up - if (ownsMixnode && ownsGateway) { - return ( - - - - You seem to have both a mixnode and a gateway bonded - how the hell did you manage to do that? - - - - ) - } - - // we don't own anything - if (!ownsMixnode && !ownsGateway) { - return ( - - - - You do not currently have a mixnode or a gateway bonded. - - - - ) - } - - // we haven't clicked unbond button yet - if (!unbondingStarted) { - return ( - - ) - } - - // We started unbonding - return ( - + useEffect(() => { + const checkOwnership = async () => { + if (client === null) { + await router.push('/') + } else { + const nodeOwnership = await checkNodesOwnership(client).finally(() => + setCheckedOwnership(true) ) + setOwnsMixnode(nodeOwnership.ownsMixnode) + setOwnsGateway(nodeOwnership.ownsGateway) + if (nodeOwnership.ownsGateway) { + setNodeType(NodeType.Gateway) + } + } + } + checkOwnership() + }, [client]) + + const unbondNode = async (event) => { + setIsLoading(true) + event.preventDefault() + console.log(`UNBOND button pressed`) + + if (nodeType == NodeType.Mixnode) { + client + .unbondMixnode() + .then((value) => console.log('unbonded mixnode!', value)) + .catch((err) => setUnbondingError(err)) + .finally(() => setIsLoading(false)) + } else { + client + .unbondGateway() + .then((value) => console.log('unbonded gateway!', value)) + .catch((err) => setUnbondingError(err)) + .finally(() => setIsLoading(false)) + } + } + + const getUnbondContent = () => { + // we're not signed in + if (client === null) { + return } - let headerText = "Node" - if (ownsGateway || ownsGateway) { - headerText = nodeType + // we haven't checked whether we actually own a node to unbond + if (!checkedOwnership) { + return } + // somehow this address has both a mixnode and a gateway bonded - this is super undesirable + // if that happens it means the user must have sent transactions outside the wallet before the contract update + // so they can send transactions outside the wallet to fix themselves up + if (ownsMixnode && ownsGateway) { + return ( + + + + You seem to have both a mixnode and a gateway bonded - how the + hell did you manage to do that? + + + + ) + } + + // we don't own anything + if (!ownsMixnode && !ownsGateway) { + return ( + + + + You do not currently have a mixnode or a gateway bonded. + + + + ) + } + + // we haven't clicked unbond button yet + if (!isLoading) { + return + } + + // We started unbonding return ( - -
- - - - Unbond a {headerText} - - {getUnbondContent()} - -
-
- ); + + ) + } + + let headerText = 'Node' + if (ownsGateway || ownsGateway) { + headerText = nodeType + } + + return ( + <> +
+ + + + Unbond a {headerText} + + {getUnbondContent()} + +
+ + ) } -export default UnbondNode \ No newline at end of file +export default UnbondNode diff --git a/wallet-web/components/undelegate/NodeUndelegation.tsx b/wallet-web/components/undelegate/NodeUndelegation.tsx index 3fe1f64493..c04aa1464d 100644 --- a/wallet-web/components/undelegate/NodeUndelegation.tsx +++ b/wallet-web/components/undelegate/NodeUndelegation.tsx @@ -1,101 +1,112 @@ -import React, { useContext, useEffect } from "react"; -import { Paper } from "@material-ui/core"; -import Typography from "@material-ui/core/Typography"; -import { useRouter } from "next/router"; -import { ValidatorClientContext } from "../../contexts/ValidatorClient"; -import { NodeType } from "../../common/node"; -import NoClientError from "../NoClientError"; -import Confirmation from "../Confirmation"; -import { theme } from "../../lib/theme"; -import { makeBasicStyle } from "../../common/helpers"; -import NodeTypeChooser from "../NodeTypeChooser"; -import NodeIdentityForm from "../NodeIdentityForm"; -import ExecFeeNotice from "../ExecFeeNotice"; - +import React, { useContext, useEffect } from 'react' +import { Paper } from '@material-ui/core' +import Typography from '@material-ui/core/Typography' +import { useRouter } from 'next/router' +import { ValidatorClientContext } from '../../contexts/ValidatorClient' +import { NodeType } from '../../common/node' +import NoClientError from '../NoClientError' +import Confirmation from '../Confirmation' +import { theme } from '../../lib/theme' +import { makeBasicStyle } from '../../common/helpers' +import NodeTypeChooser from '../NodeTypeChooser' +import NodeIdentityForm from '../NodeIdentityForm' +import ExecFeeNotice from '../ExecFeeNotice' const UndelegateFromNode = () => { - const classes = makeBasicStyle(theme); - const router = useRouter() - const { client } = useContext(ValidatorClientContext) + const classes = makeBasicStyle(theme) + const router = useRouter() + const { client } = useContext(ValidatorClientContext) - const [undelegationStarted, setUndelegationStarted] = React.useState(false) - const [undelegationFinished, setUndelegationFinished] = React.useState(false) - const [undelegationError, setUndelegationError] = React.useState(null) + const [isLoading, setIsLoading] = React.useState(false) + const [undelegationError, setUndelegationError] = React.useState(null) - const [nodeType, setNodeType] = React.useState(NodeType.Mixnode) + const [nodeType, setNodeType] = React.useState(NodeType.Mixnode) - useEffect(() => { - const checkClient = async () => { - if (client === null) { - await router.push("/") - } - } - checkClient() - }, [client]) + useEffect(() => { + const checkClient = async () => { + if (client === null) { + await router.push('/') + } + } + checkClient() + }, [client]) + const undelegateFromNode = async (event) => { + event.preventDefault() - const undelegateFromNode = async (event) => { - event.preventDefault(); + console.log(`UNDELEGATE button pressed`) - console.log(`UNDELEGATE button pressed`); + let address = event.target.identity.value + setIsLoading(true) - let address = event.target.identity.value - setUndelegationStarted(true) + if (nodeType == NodeType.Mixnode) { + client + .removeMixnodeDelegation(address) + .then((value) => { + console.log('undelegated from mixnode!', value) + }) + .catch(setUndelegationError) + .finally(() => setIsLoading(false)) + } else { + client + .removeGatewayDelegation(address) + .then((value) => { + console.log('undelegated from gateway!', value) + }) + .catch(setUndelegationError) + .finally(() => setIsLoading(false)) + } + } - if (nodeType == NodeType.Mixnode) { - client.removeMixnodeDelegation(address).then((value => { - console.log("undelegated from mixnode!", value) - })).catch(setUndelegationError).finally(() => setUndelegationFinished(true)) - } else { - client.removeGatewayDelegation(address).then((value => { - console.log("undelegated from gateway!", value) - })).catch(setUndelegationError).finally(() => setUndelegationFinished(true)) - } + const getUndelegationContent = () => { + // we're not signed in + if (client === null) { + return } - - const getUndelegationContent = () => { - // we're not signed in - if (client === null) { - return () - } - - // we haven't clicked undelegate button yet - if (!undelegationStarted) { - return ( - - - - - ) - } - - // We started delegation - return ( - - ) - } - - return ( + // we haven't clicked undelegate button yet + if (!undelegationStarted) { + return ( -
- - - - Undelegate stake from {nodeType} - - {getUndelegationContent()} - -
+ +
- ); + ) + } + + // We started delegation + return ( + + ) + } + + return ( + <> +
+ + + + Undelegate stake from {nodeType} + + {getUndelegationContent()} + +
+ + ) } - -export default UndelegateFromNode; +export default UndelegateFromNode diff --git a/wallet-web/hooks/useGetBalance.ts b/wallet-web/hooks/useGetBalance.ts index 1a933c67e1..f562d950aa 100644 --- a/wallet-web/hooks/useGetBalance.ts +++ b/wallet-web/hooks/useGetBalance.ts @@ -1,30 +1,28 @@ -import { useCallback, useContext, useState } from "react"; -import { Coin, printableCoin } from "@nymproject/nym-validator-client"; -import { ValidatorClientContext } from "../contexts/ValidatorClient"; -import { basicRawCoinValueValidation } from "../common/helpers"; +import { useCallback, useContext, useState } from 'react' +import { Coin, printableCoin } from '@nymproject/nym-validator-client' +import { ValidatorClientContext } from '../contexts/ValidatorClient' +import { basicRawCoinValueValidation } from '../common/helpers' export const useGetBalance = () => { - const { client } = useContext(ValidatorClientContext); - const [isLoading, setIsLoading] = useState(false); - const [balanceCheckError, setBalanceCheckError] = useState(null); - const [accountBalance, setAccountBalance] = useState(); - - console.log(basicRawCoinValueValidation(accountBalance?.amount)); + const { client } = useContext(ValidatorClientContext) + const [isLoading, setIsLoading] = useState(false) + const [balanceCheckError, setBalanceCheckError] = useState(null) + const [accountBalance, setAccountBalance] = useState() const getBalance = useCallback(async () => { if (client) { - setIsLoading(true); - console.log(`using the context client, our address is ${client.address}`); + setIsLoading(true) + console.log(`using the context client, our address is ${client.address}`) try { - const value = await client.getBalance(client.address); - setAccountBalance(value); - setIsLoading(false); + const value = await client.getBalance(client.address) + setAccountBalance(value) + setIsLoading(false) } catch (e) { - setBalanceCheckError(e); + setBalanceCheckError(e) } } - }, []); + }, []) return { balanceCheckError, @@ -32,5 +30,5 @@ export const useGetBalance = () => { accountBalance, printedBalance: printableCoin(accountBalance), getBalance, - }; -}; + } +} diff --git a/wallet-web/pages/send.tsx b/wallet-web/pages/send.tsx index 64cdb233d5..5d9dc46df4 100644 --- a/wallet-web/pages/send.tsx +++ b/wallet-web/pages/send.tsx @@ -1,258 +1,262 @@ -import React, { useContext } from 'react'; -import { makeStyles } from '@material-ui/core/styles'; -import Paper from '@material-ui/core/Paper'; -import Stepper from '@material-ui/core/Stepper'; -import Step from '@material-ui/core/Step'; -import StepLabel from '@material-ui/core/StepLabel'; -import Button from '@material-ui/core/Button'; -import Typography from '@material-ui/core/Typography'; -import { Review } from '../components/send-funds/Review'; -import SendNymForm from '../components/send-funds/SendNymForm'; -import { coin, Coin, printableCoin } from '@nymproject/nym-validator-client'; -import Confirmation from '../components/Confirmation'; -import MainNav from '../components/MainNav'; -import { ValidatorClientContext } from "../contexts/ValidatorClient"; -import NoClientError from "../components/NoClientError"; -import { UDENOM } from './_app'; -import { printableBalanceToNative } from "@nymproject/nym-validator-client/dist/currency"; +import React, { useContext } from 'react' +import { makeStyles } from '@material-ui/core/styles' +import Paper from '@material-ui/core/Paper' +import Stepper from '@material-ui/core/Stepper' +import Step from '@material-ui/core/Step' +import StepLabel from '@material-ui/core/StepLabel' +import Button from '@material-ui/core/Button' +import Typography from '@material-ui/core/Typography' +import { Review } from '../components/send-funds/Review' +import SendNymForm from '../components/send-funds/SendNymForm' +import { coin, Coin, printableCoin } from '@nymproject/nym-validator-client' +import Confirmation from '../components/Confirmation' +import MainNav from '../components/MainNav' +import { ValidatorClientContext } from '../contexts/ValidatorClient' +import NoClientError from '../components/NoClientError' +import { UDENOM } from './_app' +import { printableBalanceToNative } from '@nymproject/nym-validator-client/dist/currency' const useStyles = makeStyles((theme) => ({ - appBar: { - position: 'relative', + appBar: { + position: 'relative', + }, + layout: { + width: 'auto', + marginLeft: theme.spacing(2), + marginRight: theme.spacing(2), + [theme.breakpoints.up(600 + theme.spacing(2) * 2)]: { + width: 600, + marginLeft: 'auto', + marginRight: 'auto', }, - layout: { - width: 'auto', - marginLeft: theme.spacing(2), - marginRight: theme.spacing(2), - [theme.breakpoints.up(600 + theme.spacing(2) * 2)]: { - width: 600, - marginLeft: 'auto', - marginRight: 'auto', - }, + }, + paper: { + marginTop: theme.spacing(3), + marginBottom: theme.spacing(3), + padding: theme.spacing(2), + [theme.breakpoints.up(600 + theme.spacing(3) * 2)]: { + marginTop: theme.spacing(6), + marginBottom: theme.spacing(6), + padding: theme.spacing(3), }, - paper: { - marginTop: theme.spacing(3), - marginBottom: theme.spacing(3), - padding: theme.spacing(2), - [theme.breakpoints.up(600 + theme.spacing(3) * 2)]: { - marginTop: theme.spacing(6), - marginBottom: theme.spacing(6), - padding: theme.spacing(3), - }, - }, - stepper: { - padding: theme.spacing(3, 0, 5), - }, - buttons: { - display: 'flex', - justifyContent: 'flex-end', - }, - button: { - marginTop: theme.spacing(3), - marginLeft: theme.spacing(1), - }, -})); + }, + stepper: { + padding: theme.spacing(3, 0, 5), + }, + buttons: { + display: 'flex', + justifyContent: 'flex-end', + }, + button: { + marginTop: theme.spacing(3), + marginLeft: theme.spacing(1), + }, +})) -const steps = ['Enter addresses', 'Review & send', 'Await confirmation']; +const steps = ['Enter addresses', 'Review & send', 'Await confirmation'] export interface SendFundsMsg { - sender: string, - recipient: string, - coin?: Coin, + sender: string + recipient: string + coin?: Coin } export default function SendFunds() { - const getStepContent = (step) => { - switch (step) { - case 0: - return ; - case 1: - return ; - case 2: - const successMessage = `Funds transfer was complete! - sent ${printableCoin(transaction.coin)} to ${transaction.recipient}` - return - default: - throw new Error('Unknown step'); - } - } - - - const classes = useStyles(); - const { client } = useContext(ValidatorClientContext) - - console.log("client in send", client) - - - // Here's the React state - const [activeStep, setActiveStep] = React.useState(0); - const send: SendFundsMsg = { sender: "", recipient: "", coin: null }; - const [transaction, setTransaction] = React.useState(send); - const [formFilled, setFormFilled] = React.useState(false) - - const [sendingStarted, setSendingStarted] = React.useState(false) - const [sendingFinished, setSendingFinished] = React.useState(false) - const [sendingError, setSendingError] = React.useState(null) - - - const setFormStatus = (nonEmpty: boolean) => { - setFormFilled(nonEmpty) - } - - const handleNext = (event) => { - event.preventDefault(); - if (activeStep == 0) { - console.log("activeStep is 0, handling form") - try { - handleForm(event); - setActiveStep(activeStep + 1); - } catch (e) { - // right now just don't do anything. - // this error can be thrown when a value with more than 6 fractionalDigits is entered - // ideally it should show an error to the user, but it'd involve some additional - // work to correctly wire it to the form and I'm not sure it's worth it at this current - // time - } - } else if (activeStep == 1) { - console.log("activeStep is 1, sending funds") - setActiveStep(activeStep + 1); - setSendingStarted(true) - console.log("starting funds transfer") - sendFunds(transaction).then(() => { - console.log("funds transfer is finished!") - setSendingStarted(false) - setSendingFinished(true) - }).catch(err => { - setSendingError(err) - setSendingStarted(false) - setSendingFinished(true) - }); - } else { - console.log("resetting the progress") - setSendingStarted(false) - setSendingFinished(false) - setSendingError(null) - setActiveStep(0) - } - }; - - const handleBack = () => { - setActiveStep(activeStep - 1); - }; - - const getCoinValue = (raw: string): number => { - let native = printableBalanceToNative(raw) - return parseInt(native) - } - - const handleForm = (event) => { - event.preventDefault(); - let coinValue = getCoinValue(event.target.amount.value) - - const send: SendFundsMsg = { - sender: client.address, - recipient: event.target.recipient.value, - coin: coin(coinValue, UDENOM) - }; - console.log("Setting transaction", send); - setTransaction(send); - } - - const sendFunds = async (transaction: SendFundsMsg) => { - console.log(`using the context client, our address is ${client.address}`); - await client.send(client.address, transaction.recipient, [transaction.coin]); - } - - const checkButtonDisabled = (): boolean => { - if (activeStep === 0) { - return !formFilled - // the form must be filled - } else if (activeStep === 1) { - // it should always be enabled - return false - } else if (activeStep === 2) { - // transfer must be completed - return sendingStarted - } - - return false - } - - const getStepperContent = () => { + const getStepContent = (step) => { + switch (step) { + case 0: return ( - - - {steps.map((label) => ( - - {label} - - ))} - - - {activeStep === steps.length ? ( - - - Payment complete. - - - You ({transaction.sender}) - - - have sent {printableCoin(transaction.coin)} - - - to {transaction.recipient}. - - - ) : ( - -
- {getStepContent(activeStep)} -
- {activeStep !== 0 && ( - - )} - -
-
-
- )} -
-
+ ) + case 1: + return + case 2: + const successMessage = `Funds transfer was complete! - sent ${printableCoin( + transaction.coin + )} to ${transaction.recipient}` + return ( + + ) + default: + throw new Error('Unknown step') + } + } + + const classes = useStyles() + const { client } = useContext(ValidatorClientContext) + + console.log('client in send', client) + + // Here's the React state + const send: SendFundsMsg = { sender: '', recipient: '', coin: null } + const [activeStep, setActiveStep] = React.useState(0) + const [transaction, setTransaction] = React.useState(send) + const [formFilled, setFormFilled] = React.useState(false) + + const [isLoading, setIsLoading] = React.useState(false) + const [sendingError, setSendingError] = React.useState(null) + + const setFormStatus = (nonEmpty: boolean) => { + setFormFilled(nonEmpty) + } + + const handleNext = (event) => { + event.preventDefault() + if (activeStep == 0) { + console.log('activeStep is 0, handling form') + try { + handleForm(event) + setActiveStep(activeStep + 1) + } catch (e) { + // right now just don't do anything. + // this error can be thrown when a value with more than 6 fractionalDigits is entered + // ideally it should show an error to the user, but it'd involve some additional + // work to correctly wire it to the form and I'm not sure it's worth it at this current + // time + } + } else if (activeStep == 1) { + console.log('activeStep is 1, sending funds') + setActiveStep(activeStep + 1) + setIsLoading(true) + console.log('starting funds transfer') + sendFunds(transaction) + .then(() => { + console.log('funds transfer is finished!') + setIsLoading(false) + }) + .catch((err) => { + setSendingError(err) + setIsLoading(false) + }) + } else { + console.log('resetting the progress') + setIsLoading(false) + setSendingError(null) + setActiveStep(0) + } + } + + const handleBack = () => { + setActiveStep(activeStep - 1) + } + + const getCoinValue = (raw: string): number => { + let native = printableBalanceToNative(raw) + return parseInt(native) + } + + const handleForm = (event) => { + event.preventDefault() + let coinValue = getCoinValue(event.target.amount.value) + + const send: SendFundsMsg = { + sender: client.address, + recipient: event.target.recipient.value, + coin: coin(coinValue, UDENOM), + } + console.log('Setting transaction', send) + setTransaction(send) + } + + const sendFunds = async (transaction: SendFundsMsg) => { + console.log(`using the context client, our address is ${client.address}`) + await client.send(client.address, transaction.recipient, [transaction.coin]) + } + + const checkButtonDisabled = (): boolean => { + if (activeStep === 0) { + return !formFilled + // the form must be filled + } else if (activeStep === 1) { + // it should always be enabled + return false + } else if (activeStep === 2) { + // transfer must be completed + return isLoading } - return ( - - -
- - - Send Nym - + return false + } - {client === null ? ( - - ) : ( - getStepperContent() - )} - -
-
- ); + const getStepperContent = () => { + return ( + <> + + {steps.map((label) => ( + + {label} + + ))} + + <> + {activeStep === steps.length ? ( + <> + + Payment complete. + + + You ({transaction.sender}) + + + have sent {printableCoin(transaction.coin)} + + + to {transaction.recipient}. + + + ) : ( + <> +
+ {getStepContent(activeStep)} +
+ {activeStep !== 0 && ( + + )} + +
+
+ + )} + + + ) + } + + return ( + <> + +
+ + + Send Nym + + + {client === null ? : getStepperContent()} + +
+ + ) }