use helper function + code tidy
This commit is contained in:
+145
-115
@@ -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<NodeOwnership> {
|
||||
const ownsMixnodePromise = client.ownsMixNode();
|
||||
const ownsGatewayPromise = client.ownsGateway();
|
||||
export async function checkNodesOwnership(
|
||||
client: ValidatorClient
|
||||
): Promise<NodeOwnership> {
|
||||
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,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<string>()
|
||||
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<HTMLInputElement>) => {
|
||||
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: (
|
||||
<InputAdornment position='end'>{DENOM}</InputAdornment>
|
||||
),
|
||||
}}
|
||||
onChange={(e: ChangeEvent<HTMLInputElement>) => {
|
||||
if (
|
||||
typeof parseInt(e.target.value) === 'number' &&
|
||||
parseInt(accountBalance.amount) - parseInt(e.target.value) < 1
|
||||
) {
|
||||
setAllocationWarning(true)
|
||||
} else {
|
||||
setAllocationWarning(false)
|
||||
}
|
||||
}}
|
||||
onChange={handleAmountChange}
|
||||
/>
|
||||
</Grid>
|
||||
{allocationWarning && (
|
||||
<Grid item>
|
||||
<Alert severity='info'>
|
||||
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.
|
||||
<Alert severity={!isValidAmount ? 'error' : 'info'}>
|
||||
{allocationWarning}
|
||||
</Alert>
|
||||
</Grid>
|
||||
)}
|
||||
@@ -471,6 +484,7 @@ export default function BondNodeForm(props: TBondNodeFormProps) {
|
||||
color='primary'
|
||||
type='submit'
|
||||
className={classes.button}
|
||||
disabled={!isValidAmount}
|
||||
>
|
||||
Bond
|
||||
</Button>
|
||||
|
||||
@@ -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 (
|
||||
<>
|
||||
<NodeTypeChooser nodeType={nodeType} setNodeType={setNodeType} />
|
||||
@@ -152,7 +151,7 @@ const BondNode = () => {
|
||||
// We started bonding
|
||||
return (
|
||||
<Confirmation
|
||||
isLoading={bondingFinished}
|
||||
isLoading={isLoading}
|
||||
error={bondingError}
|
||||
progressMessage={`${nodeType} bonding is in progress...`}
|
||||
successMessage={`${nodeType} bonding was successful!`}
|
||||
|
||||
@@ -7,10 +7,12 @@ import { DENOM } from '../../pages/_app'
|
||||
import { theme } from '../../lib/theme'
|
||||
import {
|
||||
basicRawCoinValueValidation,
|
||||
checkAllocationSize,
|
||||
makeBasicStyle,
|
||||
validateIdentityKey,
|
||||
} from '../../common/helpers'
|
||||
import { useGetBalance } from '../../hooks/useGetBalance'
|
||||
import { printableBalanceToNative } from '@nymproject/nym-validator-client/dist/currency'
|
||||
|
||||
type DelegateFormProps = {
|
||||
onSubmit: (event: any) => 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<string>()
|
||||
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) {
|
||||
</Grid>
|
||||
{allocationWarning && (
|
||||
<Grid item>
|
||||
<Alert severity='info'>
|
||||
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.
|
||||
<Alert severity={!validAmount ? 'error' : 'info'}>
|
||||
{allocationWarning}
|
||||
</Alert>
|
||||
</Grid>
|
||||
)}
|
||||
@@ -137,6 +139,7 @@ export default function DelegateForm(props: DelegateFormProps) {
|
||||
color='primary'
|
||||
type='submit'
|
||||
className={classes.button}
|
||||
disabled={!validAmount}
|
||||
>
|
||||
Delegate stake
|
||||
</Button>
|
||||
|
||||
@@ -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 (
|
||||
<>
|
||||
<NodeTypeChooser nodeType={nodeType} setNodeType={setNodeType} />
|
||||
@@ -92,7 +91,7 @@ const DelegateToNode = () => {
|
||||
// We started delegation
|
||||
return (
|
||||
<Confirmation
|
||||
finished={delegationFinished}
|
||||
isLoading={isLoading}
|
||||
error={delegationError}
|
||||
progressMessage={`Delegating stake on ${nodeType} ${nodeIdentity} is in progress...`}
|
||||
successMessage={`Successfully delegated ${stakeValue} stake on ${nodeType} ${nodeIdentity}`}
|
||||
|
||||
@@ -1,114 +1,128 @@
|
||||
import React, { useContext, useEffect } from "react";
|
||||
import { Button, 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 NodeTypeChooser from "../NodeTypeChooser";
|
||||
import { printableCoin } from "@nymproject/nym-validator-client";
|
||||
import { theme } from "../../lib/theme";
|
||||
import { makeBasicStyle } from "../../common/helpers";
|
||||
import NodeIdentityForm from "../NodeIdentityForm";
|
||||
|
||||
import React, { useContext, useEffect } from 'react'
|
||||
import { printableCoin } from '@nymproject/nym-validator-client'
|
||||
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 NodeTypeChooser from '../NodeTypeChooser'
|
||||
import { theme } from '../../lib/theme'
|
||||
import { makeBasicStyle } from '../../common/helpers'
|
||||
import NodeIdentityForm from '../NodeIdentityForm'
|
||||
|
||||
const DelegationCheck = () => {
|
||||
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 <NoClientError />
|
||||
}
|
||||
|
||||
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 (<NoClientError/>)
|
||||
}
|
||||
|
||||
// we haven't clicked delegate button yet
|
||||
if (!checkStarted) {
|
||||
return (
|
||||
<React.Fragment>
|
||||
<NodeTypeChooser nodeType={nodeType} setNodeType={setNodeType}/>
|
||||
<NodeIdentityForm onSubmit={checkDelegation} buttonText="Check stake value"/>
|
||||
</React.Fragment>
|
||||
)
|
||||
}
|
||||
|
||||
// We started the check
|
||||
const stakeMessage = `Current stake on ${nodeType} ${nodeIdentity} is ${stakeValue}`
|
||||
return (
|
||||
<Confirmation
|
||||
finished={checkFinished}
|
||||
error={checkError}
|
||||
progressMessage={`${nodeType} (${nodeIdentity}) stake check is in progress...`}
|
||||
successMessage={stakeMessage}
|
||||
failureMessage={`Failed to check stake value on ${nodeType} ${nodeIdentity}!`}
|
||||
/>
|
||||
)
|
||||
// we haven't clicked delegate button yet
|
||||
if (!isLoading) {
|
||||
return (
|
||||
<>
|
||||
<NodeTypeChooser nodeType={nodeType} setNodeType={setNodeType} />
|
||||
<NodeIdentityForm
|
||||
onSubmit={checkDelegation}
|
||||
buttonText='Check stake value'
|
||||
/>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
// We started the check
|
||||
const stakeMessage = `Current stake on ${nodeType} ${nodeIdentity} is ${stakeValue}`
|
||||
return (
|
||||
<React.Fragment>
|
||||
<main className={classes.layout}>
|
||||
<Paper className={classes.paper}>
|
||||
<Typography component="h1" variant="h4" align="center" className={classes.wrapper}>
|
||||
Check your stake on a {nodeType}
|
||||
</Typography>
|
||||
{getDelegationCheckContent()}
|
||||
</Paper>
|
||||
</main>
|
||||
</React.Fragment>
|
||||
);
|
||||
<Confirmation
|
||||
isLoading={isLoading}
|
||||
error={checkError}
|
||||
progressMessage={`${nodeType} (${nodeIdentity}) stake check is in progress...`}
|
||||
successMessage={stakeMessage}
|
||||
failureMessage={`Failed to check stake value on ${nodeType} ${nodeIdentity}!`}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<main className={classes.layout}>
|
||||
<Paper className={classes.paper}>
|
||||
<Typography
|
||||
component='h1'
|
||||
variant='h4'
|
||||
align='center'
|
||||
className={classes.wrapper}
|
||||
>
|
||||
Check your stake on a {nodeType}
|
||||
</Typography>
|
||||
{getDelegationCheckContent()}
|
||||
</Paper>
|
||||
</main>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
export default DelegationCheck;
|
||||
export default DelegationCheck
|
||||
|
||||
@@ -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 (<NoClientError />)
|
||||
}
|
||||
|
||||
// we haven't checked whether we actually own a node to unbond
|
||||
if (!checkedOwnership) {
|
||||
return (<LinearProgress />)
|
||||
}
|
||||
|
||||
// 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 (
|
||||
<Grid container spacing={3}>
|
||||
<Grid item xs={12}>
|
||||
<Typography gutterBottom>
|
||||
You seem to have both a mixnode and a gateway bonded - how the hell did you manage to do that?
|
||||
</Typography>
|
||||
</Grid>
|
||||
</Grid>
|
||||
)
|
||||
}
|
||||
|
||||
// we don't own anything
|
||||
if (!ownsMixnode && !ownsGateway) {
|
||||
return (
|
||||
<Grid container spacing={3}>
|
||||
<Grid item xs={12}>
|
||||
<Typography gutterBottom>
|
||||
You do not currently have a mixnode or a gateway bonded.
|
||||
</Typography>
|
||||
</Grid>
|
||||
</Grid>
|
||||
)
|
||||
}
|
||||
|
||||
// we haven't clicked unbond button yet
|
||||
if (!unbondingStarted) {
|
||||
return (
|
||||
<UnbondNotice onClick={unbondNode}/>
|
||||
)
|
||||
}
|
||||
|
||||
// We started unbonding
|
||||
return (
|
||||
<Confirmation
|
||||
finished={unbondingFinished}
|
||||
error={unbondingError}
|
||||
progressMessage={`${nodeType} unbonding is in progress...`}
|
||||
successMessage={`${nodeType} unbonding was successful!`}
|
||||
failureMessage={`Failed to unbond the ${nodeType}!`}
|
||||
/>
|
||||
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 <NoClientError />
|
||||
}
|
||||
|
||||
let headerText = "Node"
|
||||
if (ownsGateway || ownsGateway) {
|
||||
headerText = nodeType
|
||||
// we haven't checked whether we actually own a node to unbond
|
||||
if (!checkedOwnership) {
|
||||
return <LinearProgress />
|
||||
}
|
||||
|
||||
// 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 (
|
||||
<Grid container spacing={3}>
|
||||
<Grid item xs={12}>
|
||||
<Typography gutterBottom>
|
||||
You seem to have both a mixnode and a gateway bonded - how the
|
||||
hell did you manage to do that?
|
||||
</Typography>
|
||||
</Grid>
|
||||
</Grid>
|
||||
)
|
||||
}
|
||||
|
||||
// we don't own anything
|
||||
if (!ownsMixnode && !ownsGateway) {
|
||||
return (
|
||||
<Grid container spacing={3}>
|
||||
<Grid item xs={12}>
|
||||
<Typography gutterBottom>
|
||||
You do not currently have a mixnode or a gateway bonded.
|
||||
</Typography>
|
||||
</Grid>
|
||||
</Grid>
|
||||
)
|
||||
}
|
||||
|
||||
// we haven't clicked unbond button yet
|
||||
if (!isLoading) {
|
||||
return <UnbondNotice onClick={unbondNode} />
|
||||
}
|
||||
|
||||
// We started unbonding
|
||||
return (
|
||||
<React.Fragment>
|
||||
<main className={classes.layout}>
|
||||
<Paper className={classes.paper}>
|
||||
<ExecFeeNotice name={"unbonding"}/>
|
||||
<Typography component="h1" variant="h4" align="center" className={classes.wrapper}>
|
||||
Unbond a {headerText}
|
||||
</Typography>
|
||||
{getUnbondContent()}
|
||||
</Paper>
|
||||
</main>
|
||||
</React.Fragment>
|
||||
);
|
||||
<Confirmation
|
||||
isLoading={isLoading}
|
||||
error={unbondingError}
|
||||
progressMessage={`${nodeType} unbonding is in progress...`}
|
||||
successMessage={`${nodeType} unbonding was successful!`}
|
||||
failureMessage={`Failed to unbond the ${nodeType}!`}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
let headerText = 'Node'
|
||||
if (ownsGateway || ownsGateway) {
|
||||
headerText = nodeType
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<main className={classes.layout}>
|
||||
<Paper className={classes.paper}>
|
||||
<ExecFeeNotice name={'unbonding'} />
|
||||
<Typography
|
||||
component='h1'
|
||||
variant='h4'
|
||||
align='center'
|
||||
className={classes.wrapper}
|
||||
>
|
||||
Unbond a {headerText}
|
||||
</Typography>
|
||||
{getUnbondContent()}
|
||||
</Paper>
|
||||
</main>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default UnbondNode
|
||||
export default UnbondNode
|
||||
|
||||
@@ -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 <NoClientError />
|
||||
}
|
||||
|
||||
|
||||
const getUndelegationContent = () => {
|
||||
// we're not signed in
|
||||
if (client === null) {
|
||||
return (<NoClientError />)
|
||||
}
|
||||
|
||||
// we haven't clicked undelegate button yet
|
||||
if (!undelegationStarted) {
|
||||
return (
|
||||
<React.Fragment >
|
||||
<NodeTypeChooser nodeType={nodeType} setNodeType={setNodeType} />
|
||||
<NodeIdentityForm onSubmit={undelegateFromNode} buttonText={"Remove delegation"}/>
|
||||
</React.Fragment>
|
||||
)
|
||||
}
|
||||
|
||||
// We started delegation
|
||||
return (
|
||||
<Confirmation
|
||||
finished={undelegationFinished}
|
||||
error={undelegationError}
|
||||
progressMessage={`${nodeType} undelegation is in progress...`}
|
||||
successMessage={`${nodeType} undelegation was successful!`}
|
||||
failureMessage={`Failed to undelegate from a ${nodeType}!`}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
// we haven't clicked undelegate button yet
|
||||
if (!undelegationStarted) {
|
||||
return (
|
||||
<React.Fragment>
|
||||
<main className={classes.layout}>
|
||||
<Paper className={classes.paper}>
|
||||
<ExecFeeNotice name={"undelegating stake"}/>
|
||||
<Typography component="h1" variant="h4" align="center" className={classes.wrapper}>
|
||||
Undelegate stake from {nodeType}
|
||||
</Typography>
|
||||
{getUndelegationContent()}
|
||||
</Paper>
|
||||
</main>
|
||||
<NodeTypeChooser nodeType={nodeType} setNodeType={setNodeType} />
|
||||
<NodeIdentityForm
|
||||
onSubmit={undelegateFromNode}
|
||||
buttonText={'Remove delegation'}
|
||||
/>
|
||||
</React.Fragment>
|
||||
);
|
||||
)
|
||||
}
|
||||
|
||||
// We started delegation
|
||||
return (
|
||||
<Confirmation
|
||||
isLoading={isLoading}
|
||||
error={undelegationError}
|
||||
progressMessage={`${nodeType} undelegation is in progress...`}
|
||||
successMessage={`${nodeType} undelegation was successful!`}
|
||||
failureMessage={`Failed to undelegate from a ${nodeType}!`}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<main className={classes.layout}>
|
||||
<Paper className={classes.paper}>
|
||||
<ExecFeeNotice name={'undelegating stake'} />
|
||||
<Typography
|
||||
component='h1'
|
||||
variant='h4'
|
||||
align='center'
|
||||
className={classes.wrapper}
|
||||
>
|
||||
Undelegate stake from {nodeType}
|
||||
</Typography>
|
||||
{getUndelegationContent()}
|
||||
</Paper>
|
||||
</main>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
export default UndelegateFromNode;
|
||||
export default UndelegateFromNode
|
||||
|
||||
@@ -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<Coin>();
|
||||
|
||||
console.log(basicRawCoinValueValidation(accountBalance?.amount));
|
||||
const { client } = useContext(ValidatorClientContext)
|
||||
const [isLoading, setIsLoading] = useState(false)
|
||||
const [balanceCheckError, setBalanceCheckError] = useState(null)
|
||||
const [accountBalance, setAccountBalance] = useState<Coin>()
|
||||
|
||||
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,
|
||||
};
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
+246
-242
@@ -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 <SendNymForm address={client.address || ""} setFormStatus={setFormStatus} />;
|
||||
case 1:
|
||||
return <Review {...transaction}/>;
|
||||
case 2:
|
||||
const successMessage = `Funds transfer was complete! - sent ${printableCoin(transaction.coin)} to ${transaction.recipient}`
|
||||
return <Confirmation
|
||||
finished={sendingFinished}
|
||||
progressMessage="Funds transfer is in progress..."
|
||||
successMessage={successMessage}
|
||||
failureMessage="Failed to complete the transfer"
|
||||
error={sendingError}
|
||||
/>
|
||||
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 (
|
||||
<React.Fragment>
|
||||
<Stepper activeStep={activeStep} className={classes.stepper}>
|
||||
{steps.map((label) => (
|
||||
<Step key={label}>
|
||||
<StepLabel>{label}</StepLabel>
|
||||
</Step>
|
||||
))}
|
||||
</Stepper>
|
||||
<React.Fragment>
|
||||
{activeStep === steps.length ? (
|
||||
<React.Fragment>
|
||||
<Typography variant="h5" gutterBottom>
|
||||
Payment complete.
|
||||
</Typography>
|
||||
<Typography variant="subtitle1">
|
||||
You (<b>{transaction.sender}</b>)
|
||||
</Typography>
|
||||
<Typography variant="subtitle1">
|
||||
have sent <b>{printableCoin(transaction.coin)}</b>
|
||||
</Typography>
|
||||
<Typography variant="subtitle1">
|
||||
to <b>{transaction.recipient}</b>.
|
||||
</Typography>
|
||||
</React.Fragment>
|
||||
) : (
|
||||
<React.Fragment>
|
||||
<form onSubmit={handleNext}>
|
||||
{getStepContent(activeStep)}
|
||||
<div className={classes.buttons}>
|
||||
{activeStep !== 0 && (
|
||||
<Button onClick={handleBack} className={classes.button}>
|
||||
Back
|
||||
</Button>
|
||||
)}
|
||||
<Button
|
||||
variant="contained"
|
||||
color="primary"
|
||||
type="submit"
|
||||
disabled={checkButtonDisabled()}
|
||||
className={classes.button}
|
||||
>
|
||||
{activeStep === 1 ? 'Send' : (activeStep === steps.length - 1 ? 'Send again' : 'Next')}
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
</React.Fragment>
|
||||
)}
|
||||
</React.Fragment>
|
||||
</React.Fragment>
|
||||
<SendNymForm
|
||||
address={client.address || ''}
|
||||
setFormStatus={setFormStatus}
|
||||
/>
|
||||
)
|
||||
case 1:
|
||||
return <Review {...transaction} />
|
||||
case 2:
|
||||
const successMessage = `Funds transfer was complete! - sent ${printableCoin(
|
||||
transaction.coin
|
||||
)} to ${transaction.recipient}`
|
||||
return (
|
||||
<Confirmation
|
||||
isLoading={isLoading}
|
||||
progressMessage='Funds transfer is in progress...'
|
||||
successMessage={successMessage}
|
||||
failureMessage='Failed to complete the transfer'
|
||||
error={sendingError}
|
||||
/>
|
||||
)
|
||||
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 (
|
||||
<React.Fragment>
|
||||
<MainNav />
|
||||
<main className={classes.layout}>
|
||||
<Paper className={classes.paper}>
|
||||
<Typography component="h1" variant="h4" align="center">
|
||||
Send Nym
|
||||
</Typography>
|
||||
return false
|
||||
}
|
||||
|
||||
{client === null ? (
|
||||
<NoClientError />
|
||||
) : (
|
||||
getStepperContent()
|
||||
)}
|
||||
</Paper>
|
||||
</main>
|
||||
</React.Fragment >
|
||||
);
|
||||
const getStepperContent = () => {
|
||||
return (
|
||||
<>
|
||||
<Stepper activeStep={activeStep} className={classes.stepper}>
|
||||
{steps.map((label) => (
|
||||
<Step key={label}>
|
||||
<StepLabel>{label}</StepLabel>
|
||||
</Step>
|
||||
))}
|
||||
</Stepper>
|
||||
<>
|
||||
{activeStep === steps.length ? (
|
||||
<>
|
||||
<Typography variant='h5' gutterBottom>
|
||||
Payment complete.
|
||||
</Typography>
|
||||
<Typography variant='subtitle1'>
|
||||
You (<b>{transaction.sender}</b>)
|
||||
</Typography>
|
||||
<Typography variant='subtitle1'>
|
||||
have sent <b>{printableCoin(transaction.coin)}</b>
|
||||
</Typography>
|
||||
<Typography variant='subtitle1'>
|
||||
to <b>{transaction.recipient}</b>.
|
||||
</Typography>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<form onSubmit={handleNext}>
|
||||
{getStepContent(activeStep)}
|
||||
<div className={classes.buttons}>
|
||||
{activeStep !== 0 && (
|
||||
<Button onClick={handleBack} className={classes.button}>
|
||||
Back
|
||||
</Button>
|
||||
)}
|
||||
<Button
|
||||
variant='contained'
|
||||
color='primary'
|
||||
type='submit'
|
||||
disabled={checkButtonDisabled()}
|
||||
className={classes.button}
|
||||
>
|
||||
{activeStep === 1
|
||||
? 'Send'
|
||||
: activeStep === steps.length - 1
|
||||
? 'Send again'
|
||||
: 'Next'}
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<MainNav />
|
||||
<main className={classes.layout}>
|
||||
<Paper className={classes.paper}>
|
||||
<Typography component='h1' variant='h4' align='center'>
|
||||
Send Nym
|
||||
</Typography>
|
||||
|
||||
{client === null ? <NoClientError /> : getStepperContent()}
|
||||
</Paper>
|
||||
</main>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user