update state management and validation

This commit is contained in:
fmtabbara
2021-08-03 18:08:54 +01:00
parent 26fb13fe2d
commit f96d768341
4 changed files with 424 additions and 349 deletions
+90 -339
View File
@@ -1,336 +1,51 @@
import React, { useEffect, useState, ChangeEvent } from 'react'
import { printableBalanceToNative } from '@nymproject/nym-validator-client/dist/currency'
import { Coin, nativeToPrintable } from '@nymproject/nym-validator-client'
import { Alert } from '@material-ui/lab'
import React from 'react'
import {
Button,
Checkbox,
FormControlLabel,
InputAdornment,
Grid,
InputAdornment,
TextField,
useMediaQuery,
} from '@material-ui/core'
import bs58 from 'bs58'
import semver from 'semver'
import { Alert } from '@material-ui/lab'
import { Coin, nativeToPrintable } from '@nymproject/nym-validator-client'
import { NodeType } from '../../common/node'
import { theme } from '../../lib/theme'
import {
basicRawCoinValueValidation,
checkAllocationSize,
makeBasicStyle,
validateRawPort,
} from '../../common/helpers'
import { DENOM } from '../../pages/_app'
import { theme } from '../../lib/theme'
import { BondingInformation } from './NodeBond'
import { useGetBalance } from '../../hooks/useGetBalance'
const DEFAULT_MIX_PORT = 1789
const DEFAULT_VERLOC_PORT = 1790
const DEFAULT_HTTP_API_PORT = 8000
const DEFAULT_CLIENTS_PORT = 9000
import { useBondForm } from './useBondForm'
type TBondNodeFormProps = {
type: NodeType
minimumMixnodeBond: Coin
minimumGatewayBond: Coin
onSubmit: (event: any) => void
minimumBond: Coin
onSubmit: (values: BondingInformation) => void
}
type TFormStringValue = { value: string }
type TFormInput = {
amount: TFormStringValue
identityKey: TFormStringValue
sphinxKey: TFormStringValue
host: TFormStringValue
version: TFormStringValue
mixPort?: TFormStringValue
verlocPort?: TFormStringValue
location?: TFormStringValue
clientsPort?: TFormStringValue
httpApiPort?: TFormStringValue
}
type TFormData = {
amount: string
identityKey: string
sphinxKey: string
host: string
version: string
mixPort?: string
verlocPort?: string
location?: string
clientsPort?: string
httpApiPort?: string
}
export default function BondNodeForm(props: TBondNodeFormProps) {
const classes = makeBasicStyle(theme)
const [validity, setValidity] = React.useState({
validAmount: true,
validSphinxKey: true,
validIdentityKey: true,
validHost: true,
validVersion: true,
validLocation: true,
validMixPort: true,
// this should have probably be somehow split to be subclasses of the validity matrix
// the above is more true now as more fields are added. This looks kinda disgusting...
// mixnode-specific:
validVerlocPort: true,
validHttpApiPort: true,
// gateway-specific:
validClientsPort: true,
})
export const BondNodeForm = ({
type,
minimumBond,
onSubmit,
}: TBondNodeFormProps) => {
const [advancedShown, setAdvancedShown] = React.useState(false)
const [allocationWarning, setAllocationWarning] = useState<string>()
const [isValidAmount, setIsValidAmount] = useState(true)
const { getBalance, accountBalance } = useGetBalance()
useEffect(() => {
getBalance()
}, [getBalance])
const manageForm = useBondForm({ type, minimumBond })
const matches = useMediaQuery('(min-width:768px)')
const handleCheckboxToggle = () => {
setAdvancedShown((prevSet) => !prevSet)
}
const handleAmountChange = (e: ChangeEvent<HTMLInputElement>) => {
const parsed = +e.target.value
const balance = +accountBalance.amount
if (isNaN(parsed)) {
setIsValidAmount(false)
} else {
try {
const allocationCheck = checkAllocationSize(
+printableBalanceToNative(e.target.value),
balance,
'bond'
)
if (allocationCheck.error) {
setAllocationWarning(allocationCheck.message)
setIsValidAmount(false)
} else {
setAllocationWarning(allocationCheck.message)
setIsValidAmount(true)
}
} catch {
setIsValidAmount(false)
}
}
}
// if arguments are undefined we provided a default value for
// verlocPort, mixPort, httpApiPort and clientsPort
const validateForm = ({
amount,
sphinxKey,
identityKey,
host,
version,
location,
verlocPort = DEFAULT_VERLOC_PORT.toString(),
mixPort = DEFAULT_MIX_PORT.toString(),
httpApiPort = DEFAULT_HTTP_API_PORT.toString(),
clientsPort = DEFAULT_CLIENTS_PORT.toString(),
}: TFormData): boolean => {
let newValidity = {
validAmount: validateAmount(amount),
validSphinxKey: validateKey(sphinxKey),
validIdentityKey: validateKey(identityKey),
validHost: validateHost(host),
validVersion: validateVersion(version),
validMixPort: validateRawPort(mixPort),
validLocation:
props.type == NodeType.Gateway ? validateLocation(location) : true,
validVerlocPort:
props.type === NodeType.Mixnode ? validateRawPort(verlocPort) : true,
validHttpApiPort:
props.type === NodeType.Mixnode ? validateRawPort(httpApiPort) : true,
validClientsPort:
props.type === NodeType.Gateway ? validateRawPort(clientsPort) : true,
}
setValidity((previousState) => {
return { ...previousState, ...newValidity }
})
// check if all values are true and return result
return Object.values({ ...validity, ...newValidity }).every(
(isValid) => isValid === true
)
}
const validateAmount = (rawValue: string): boolean => {
// tests basic coin value requirements, like no more than 6 decimal places, value lower than total supply, etc
if (!basicRawCoinValueValidation(rawValue)) {
return false
}
// this conversion seems really iffy but I'm not sure how to better approach it
let nativeValueString = printableBalanceToNative(rawValue)
let nativeValue = parseInt(nativeValueString)
if (props.type == NodeType.Mixnode) {
return nativeValue >= parseInt(props.minimumMixnodeBond.amount)
} else {
return nativeValue >= parseInt(props.minimumGatewayBond.amount)
}
}
const validateKey = (key: string): boolean => {
// it must be a valid base58 key
try {
const bytes = bs58.decode(key)
// of length 32
return bytes.length === 32
} catch {
return false
}
}
const validateHost = (host: string): boolean => {
// I don't think that proper checks are in scope of the change here
// what would need to be checked is whether one of the following is true:
// - host is an ipv4 address
// - host is an ipv6 address
// - host is a valid hostname
// so at least perform the dumbest possible checks
// ipv4 needs 4 dot-separated octets
// ipv6 can have multiple possible representations, but it needs to contain at least two colons
// a hostname (in this case) needs to have a top level domain present
const dot_occurrences = host.trim().split('.').length - 1
const colon_occurrences = host.trim().split(':').length - 1
if (dot_occurrences === 3) {
// possible ipv4
// make sure it has no ports attached!
return colon_occurrences == 0
} else if (colon_occurrences >= 2) {
// possible ipv6
return true
} else if (dot_occurrences >= 1) {
// possible hostname
// make sure it has no ports attached!
return colon_occurrences == 0
}
return false
}
const validateVersion = (version: string): boolean => {
// check if its a valid semver
return semver.valid(version) && semver.minor(version) >= 11
}
const validateLocation = (location: string): boolean => {
// right now only perform the stupid check of whether the user copy-pasted the tooltip... (with or without brackets)
return !location.trim().includes('physical location of your node')
}
const constructMixnodeBondingInfo = ({
amount,
host,
httpApiPort,
mixPort,
verlocPort,
sphinxKey,
identityKey,
version,
}: TFormData): BondingInformation => {
return {
amount,
nodeDetails: {
host,
http_api_port: httpApiPort
? parseInt(httpApiPort)
: DEFAULT_HTTP_API_PORT,
mix_port: mixPort ? parseInt(mixPort) : DEFAULT_MIX_PORT,
verloc_port: verlocPort ? parseInt(verlocPort) : DEFAULT_VERLOC_PORT,
sphinx_key: sphinxKey,
identity_key: identityKey,
version,
},
}
}
const constructGatewayBondingInfo = ({
amount,
host,
mixPort,
clientsPort,
sphinxKey,
identityKey,
version,
location,
}: TFormData): BondingInformation => {
return {
amount,
nodeDetails: {
host,
mix_port: mixPort ? parseInt(mixPort) : DEFAULT_MIX_PORT,
clients_port: clientsPort
? parseInt(clientsPort)
: DEFAULT_CLIENTS_PORT,
sphinx_key: sphinxKey,
identity_key: identityKey,
version,
location,
},
}
}
const submitForm = (event: React.FormEvent<HTMLFormElement>) => {
event.preventDefault()
const target = event.target as typeof event.target & TFormInput
const data: TFormData = {
amount: target.amount.value,
identityKey: target.identityKey.value,
sphinxKey: target.sphinxKey.value,
host: target.host.value,
version: target.version.value,
mixPort: target.mixPort?.value,
verlocPort: target.verlocPort?.value,
location: target.location?.value,
clientsPort: target.clientsPort?.value,
httpApiPort: target.httpApiPort?.value,
}
if (validateForm(data)) {
let dataToSubmit
if (props.type == NodeType.Mixnode) {
dataToSubmit = constructMixnodeBondingInfo(data)
return props.onSubmit(dataToSubmit)
} else {
dataToSubmit = constructGatewayBondingInfo(data)
return props.onSubmit(dataToSubmit)
}
}
}
let minimumBond = props.minimumMixnodeBond
if (props.type == NodeType.Gateway) {
minimumBond = props.minimumGatewayBond
}
// if this whole interface wasn't to be completely redone in a month time, I would have definitely redone the form
// but I guess it's fine for time being
return (
<form onSubmit={submitForm}>
<form
onSubmit={(e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault()
manageForm.handleSubmit(onSubmit)
}}
>
<Grid container spacing={3}>
<Grid item xs={12} sm={8}>
<TextField
required
value={manageForm.formData.amount.value}
onChange={manageForm.handleAmountChange}
id="amount"
name="amount"
label={`Amount to bond ${
@@ -338,9 +53,9 @@ export default function BondNodeForm(props: TBondNodeFormProps) {
? '(minimum ' + nativeToPrintable(minimumBond.amount) + ')'
: ''
}`}
error={!validateAmount}
error={manageForm.formData.amount.isValid === false}
helperText={
!validateAmount
manageForm.formData.amount.isValid === false
? `Enter a valid bond amount (minimum ${nativeToPrintable(
minimumBond.amount
)})`
@@ -352,48 +67,58 @@ export default function BondNodeForm(props: TBondNodeFormProps) {
<InputAdornment position="end">{DENOM}</InputAdornment>
),
}}
onChange={handleAmountChange}
/>
</Grid>
{allocationWarning && (
{manageForm.allocationWarning.message && (
<Grid item>
<Alert severity={!isValidAmount ? 'error' : 'info'}>
{allocationWarning}
<Alert
severity={manageForm.allocationWarning.error ? 'error' : 'info'}
>
{manageForm.allocationWarning.message}
</Alert>
</Grid>
)}
<Grid item xs={12}>
<TextField
error={!validity.validIdentityKey}
value={manageForm.formData.identityKey.value}
onChange={manageForm.handleIdentityKeyChange}
error={manageForm.formData.identityKey.isValid === false}
required
id="identityKey"
name="identityKey"
label="Identity key"
fullWidth
{...(manageForm.formData.identityKey.isValid === false
? { helperText: 'Enter a valid identity key' }
: {})}
/>
</Grid>
<Grid item xs={12}>
<TextField
error={!validity.validSphinxKey}
value={manageForm.formData.sphinxKey.value}
onChange={manageForm.handleShinxKeyChange}
error={manageForm.formData.sphinxKey.isValid === false}
required
id="sphinxKey"
name="sphinxKey"
label="Sphinx key"
fullWidth
{...(!validity.validSphinxKey
{...(manageForm.formData.sphinxKey.isValid === false
? { helperText: 'Enter a valid sphinx key' }
: {})}
/>
</Grid>
<Grid item xs={12} sm={6}>
<TextField
error={!validity.validHost}
value={manageForm.formData.host.value}
onChange={manageForm.handleHostChange}
error={manageForm.formData.host.isValid === false}
required
id="host"
name="host"
label="Host"
fullWidth
{...(!validity.validHost
{...(manageForm.formData.host.isValid === false
? { helperText: 'Enter a valid IP or a hostname (without port)' }
: {})}
/>
@@ -401,15 +126,17 @@ export default function BondNodeForm(props: TBondNodeFormProps) {
{/* if it's a gateway - get location */}
<Grid item xs={12} sm={6}>
{props.type === NodeType.Gateway && (
{type === NodeType.Gateway && (
<TextField
error={!validity.validLocation}
value={manageForm.formData.location.value}
onChange={manageForm.handleLocationChange}
error={manageForm.formData.location.isValid === false}
required
id="location"
name="location"
label="Location"
fullWidth
{...(!validity.validLocation
{...(manageForm.formData.location.isValid === false
? { helperText: 'Enter a valid location of your node' }
: {})}
/>
@@ -418,13 +145,15 @@ export default function BondNodeForm(props: TBondNodeFormProps) {
<Grid item xs={12} sm={6}>
<TextField
error={!validity.validVersion}
value={manageForm.formData.version.value}
onChange={manageForm.handleVersionChange}
error={manageForm.formData.version.isValid === false}
required
id="version"
name="version"
label="Version"
fullWidth
{...(!validity.validVersion
{...(manageForm.formData.version.isValid === false
? {
helperText:
'Enter a valid version (min. 0.11.0), like 0.11.0',
@@ -438,10 +167,15 @@ export default function BondNodeForm(props: TBondNodeFormProps) {
control={
<Checkbox
checked={advancedShown}
onChange={handleCheckboxToggle}
onChange={() => {
setAdvancedShown((shown) => {
if (shown) manageForm.initialisePorts()
return !shown
})
}}
/>
}
label="Show advanced options"
label="Use advanced options"
/>
</Grid>
@@ -449,53 +183,63 @@ export default function BondNodeForm(props: TBondNodeFormProps) {
<>
<Grid item xs={12} sm={4}>
<TextField
error={!validity.validMixPort}
value={manageForm.formData.mixPort.value}
onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
manageForm.handlePortChange('mixPort', e.target.value)
}
error={manageForm.formData.mixPort.isValid === false}
variant="outlined"
id="mixPort"
name="mixPort"
label="Mix Port"
fullWidth
defaultValue={DEFAULT_MIX_PORT}
/>
</Grid>
{/*yes, I also hate so many layers of indentation here*/}
{props.type === NodeType.Mixnode ? (
{type === NodeType.Mixnode ? (
<>
<Grid item xs={12} sm={4}>
<TextField
error={!validity.validVerlocPort}
value={manageForm.formData.verlocPort.value}
onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
manageForm.handlePortChange('verlocPort', e.target.value)
}
error={manageForm.formData.verlocPort.isValid === false}
variant="outlined"
id="verlocPort"
name="verlocPort"
label="Verloc Port"
fullWidth
defaultValue={DEFAULT_VERLOC_PORT}
/>
</Grid>
<Grid item xs={12} sm={4}>
<TextField
error={!validity.validHttpApiPort}
value={manageForm.formData.httpApiPort.value}
onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
manageForm.handlePortChange('httpApiPort', e.target.value)
}
error={manageForm.formData.httpApiPort.isValid === false}
variant="outlined"
id="httpApiPort"
name="httpApiPort"
label="HTTP API Port"
fullWidth
defaultValue={DEFAULT_HTTP_API_PORT}
/>
</Grid>
</>
) : (
<Grid item xs={12} sm={4}>
<TextField
error={!validity.validClientsPort}
value={manageForm.formData.clientsPort.value}
onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
manageForm.handlePortChange('clientsPort', e.target.value)
}
error={manageForm.formData.clientsPort.isValid === false}
variant="outlined"
id="clientsPort"
name="clientsPort"
label="client WS API Port"
fullWidth
defaultValue={DEFAULT_CLIENTS_PORT}
/>
</Grid>
)}
@@ -503,13 +247,20 @@ export default function BondNodeForm(props: TBondNodeFormProps) {
)}
</Grid>
<div className={classes.buttons}>
<div
style={{
display: 'flex',
alignItems: 'center',
justifyContent: 'flex-end',
padding: theme.spacing(2),
}}
>
<Button
variant="contained"
color="primary"
type="submit"
className={classes.button}
disabled={!isValidAmount}
size="large"
disabled={!manageForm.isValidForm}
>
Bond
</Button>
+8 -10
View File
@@ -1,4 +1,5 @@
import React, { useContext, useEffect } from 'react'
import { useRouter } from 'next/router'
import Typography from '@material-ui/core/Typography'
import { Grid, LinearProgress, Paper } from '@material-ui/core'
import { Gateway, MixNode } from '@nymproject/nym-validator-client/dist/types'
@@ -7,12 +8,11 @@ import { printableBalanceToNative } from '@nymproject/nym-validator-client/dist/
import Confirmation from '../Confirmation'
import { ValidatorClientContext } from '../../contexts/ValidatorClient'
import NoClientError from '../NoClientError'
import { useRouter } from 'next/router'
import BondNodeForm from './BondNodeForm'
import { BondNodeForm } from './BondNodeForm'
import { NodeType } from '../../common/node'
import Link from '../Link'
import { theme } from '../../lib/theme'
import { checkNodesOwnership, makeBasicStyle } from '../../common/helpers'
import { checkNodesOwnership } from '../../common/helpers'
import NodeTypeChooser from '../NodeTypeChooser'
import ExecFeeNotice from '../ExecFeeNotice'
import { UDENOM } from '../../pages/_app'
@@ -23,7 +23,6 @@ export type BondingInformation = {
}
const BondNode = () => {
const classes = makeBasicStyle(theme)
const router = useRouter()
const { client } = useContext(ValidatorClientContext)
@@ -62,15 +61,11 @@ const BondNode = () => {
const bondNode = async (bondingInformation: BondingInformation) => {
setIsLoading(true)
console.log(`BOND button pressed`)
console.log(bondingInformation)
let amountValue = parseInt(
printableBalanceToNative(bondingInformation.amount)
)
let amount = coin(amountValue, UDENOM)
console.log(bondingInformation.nodeDetails)
if (nodeType == NodeType.Mixnode) {
let mixnode = bondingInformation.nodeDetails as MixNode
client
@@ -141,8 +136,11 @@ const BondNode = () => {
<BondNodeForm
onSubmit={bondNode}
type={nodeType}
minimumGatewayBond={minimumGatewayBond}
minimumMixnodeBond={minimumMixnodeBond}
minimumBond={
nodeType === NodeType.Mixnode
? minimumMixnodeBond
: minimumGatewayBond
}
/>
</>
)
+205
View File
@@ -0,0 +1,205 @@
import { useEffect, useState } from 'react'
import { Coin } from '@cosmjs/amino'
import { printableBalanceToNative } from '@nymproject/nym-validator-client/dist/currency'
import { BondingInformation } from './NodeBond'
import {
formatDataForSubmission,
isValidHostname,
validateAmount,
validateKey,
validateLocation,
validateVersion,
} from './utils'
import { checkAllocationSize, validateRawPort } from '../../common/helpers'
import { NodeType } from '../../common/node'
import { useGetBalance } from '../../hooks/useGetBalance'
const DEFAULT_PORTS = {
MIX_PORT: 1789,
VERLOC_PORT: 1790,
HTTP_API_PORT: 8000,
CLIENTS_PORT: 9000,
}
const initialPorts = {
mixPort: { value: DEFAULT_PORTS.MIX_PORT.toString(), isValid: true },
verlocPort: {
value: DEFAULT_PORTS.VERLOC_PORT.toString(),
isValid: true,
},
clientsPort: {
value: DEFAULT_PORTS.CLIENTS_PORT.toString(),
isValid: true,
},
httpApiPort: {
value: DEFAULT_PORTS.HTTP_API_PORT.toString(),
isValid: true,
},
}
const initialData = {
amount: { value: '', isValid: undefined },
identityKey: { value: '', isValid: undefined },
sphinxKey: { value: '', isValid: undefined },
host: { value: '', isValid: undefined },
version: { value: '', isValid: undefined },
location: { value: '', isValid: true },
...initialPorts,
}
type TDataField = { value: string; isValid?: boolean }
type TData = {
amount: TDataField
identityKey: TDataField
sphinxKey: TDataField
host: TDataField
version: TDataField
location: TDataField
mixPort: TDataField
verlocPort: TDataField
clientsPort: TDataField
httpApiPort: TDataField
}
export const useBondForm = ({
type,
minimumBond,
}: {
type: NodeType
minimumBond: Coin
}) => {
const [formData, setFormData] = useState(initialData)
const [isValidForm, setIsValidForm] = useState(false)
const [allocationWarning, setAllocationWarning] = useState({
error: false,
message: undefined,
})
const { getBalance, accountBalance } = useGetBalance()
useEffect(() => {
getBalance()
}, [getBalance])
useEffect(() => {
if (type === NodeType.Mixnode)
setFormData((data) => ({ ...data, location: initialData.location }))
}, [type])
useEffect(() => {
const keys = Object.keys(formData)
const isValid = keys
.map((key) => formData[key].isValid)
.every((value) => value === true)
setIsValidForm(isValid)
}, [formData])
const handleSubmit = (cb: (data: BondingInformation) => void) => {
const keys = Object.keys(formData)
const values = keys.reduce((a, c: keyof TData) => {
return {
...a,
[c]: formData[c].value,
}
}, {})
const formatted = formatDataForSubmission(values, type)
cb(formatted)
}
const handleAmountChange = (e: React.ChangeEvent<HTMLInputElement>) => {
let isValid = false
try {
if (
!isNaN(+e.target.value) &&
validateAmount(e.target.value, minimumBond.amount)
)
isValid = true
const allocationCheck = checkAllocationSize(
+printableBalanceToNative(e.target.value),
+accountBalance.amount,
'bond'
)
setAllocationWarning(allocationCheck)
} catch (e) {
console.log(e)
}
setFormData((data) => ({
...data,
amount: { value: e.target.value, isValid },
}))
}
const handleIdentityKeyChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const isValid = validateKey(e.target.value)
setFormData((data) => ({
...data,
identityKey: { value: e.target.value, isValid },
}))
}
const handleShinxKeyChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const isValid = validateKey(e.target.value)
setFormData((data) => ({
...data,
sphinxKey: { value: e.target.value, isValid },
}))
}
const handleHostChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const isValid = isValidHostname(e.target.value)
setFormData((data) => ({
...data,
host: { value: e.target.value, isValid },
}))
}
const handleVersionChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const isValid = validateVersion(e.target.value)
setFormData((data) => ({
...data,
version: { value: e.target.value, isValid },
}))
}
const handleLocationChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const isValid = validateLocation(e.target.value)
setFormData((data) => ({
...data,
location: { value: e.target.value, isValid },
}))
}
const handlePortChange = (
port: 'mixPort' | 'verlocPort' | 'clientsPort' | 'httpApiPort',
value: string
) => {
const isValid = validateRawPort(value)
setFormData((data) => ({
...data,
[port]: { value, isValid },
}))
}
const initialisePorts = () =>
setFormData((data) => ({ ...data, ...initialPorts }))
return {
formData,
isValidForm,
allocationWarning,
handleAmountChange,
handleIdentityKeyChange,
handleShinxKeyChange,
handleHostChange,
handleVersionChange,
handleLocationChange,
handlePortChange,
handleSubmit,
initialisePorts,
}
}
+121
View File
@@ -0,0 +1,121 @@
import { printableBalanceToNative } from '@nymproject/nym-validator-client/dist/currency'
import { Gateway, MixNode } from '@nymproject/nym-validator-client/dist/types'
import bs58 from 'bs58'
import semver from 'semver'
import { basicRawCoinValueValidation } from '../../common/helpers'
import { NodeType } from '../../common/node'
import { BondingInformation } from './NodeBond'
export const validateAmount = (rawValue: string, minimum: string): boolean => {
// tests basic coin value requirements, like no more than 6 decimal places, value lower than total supply, etc
if (!basicRawCoinValueValidation(rawValue)) {
return false
}
// this conversion seems really iffy but I'm not sure how to better approach it
let nativeValueString = printableBalanceToNative(rawValue)
let nativeValue = parseInt(nativeValueString)
console.log(nativeValue, minimum)
return nativeValue >= parseInt(minimum)
}
export const validateKey = (key: string): boolean => {
// it must be a valid base58 key
try {
const bytes = bs58.decode(key)
// of length 32
return bytes.length === 32
} catch {
return false
}
}
export const isValidHostname = (value) => {
if (typeof value !== 'string') return false
const validHostnameChars = /^[a-zA-Z0-9-.]{1,253}\.?$/g
if (!validHostnameChars.test(value)) {
return false
}
if (value.endsWith('.')) {
value = value.slice(0, value.length - 1)
}
if (value.length > 253) {
return false
}
const labels = value.split('.')
const isValid = labels.every(function (label) {
const validLabelChars = /^([a-zA-Z0-9-]+)$/g
const validLabel =
validLabelChars.test(label) &&
label.length < 64 &&
!label.startsWith('-') &&
!label.endsWith('-')
return validLabel
})
return isValid
}
// check if its a valid semver
export const validateVersion = (version: string): boolean =>
semver.valid(version) && semver.minor(version) >= 11
export const validateLocation = (location: string): boolean => {
// right now only perform the stupid check of whether the user copy-pasted the tooltip... (with or without brackets)
return !location.trim().includes('physical location of your node')
}
export const formatDataForSubmission = <T>(
{
amount,
host,
identityKey,
sphinxKey,
clientsPort,
httpApiPort,
mixPort,
verlocPort,
location,
version,
}: { [key: string]: string | undefined },
nodeType: NodeType
) => {
let data = {
amount,
nodeDetails: {
identity_key: identityKey,
sphinx_key: sphinxKey,
host,
version,
mix_port: parseInt(mixPort),
},
}
if (nodeType === NodeType.Mixnode) {
data = {
...data,
nodeDetails: {
...data.nodeDetails,
verloc_port: parseInt(verlocPort),
http_api_port: parseInt(httpApiPort),
} as MixNode,
}
} else {
data = {
...data,
nodeDetails: {
...data.nodeDetails,
location,
clients_port: parseInt(clientsPort),
} as Gateway,
}
}
return data as BondingInformation
}