diff --git a/wallet-web/components/ExecFeeNotice.tsx b/wallet-web/components/ExecFeeNotice.tsx
index 5807ba0a46..39cb196e9a 100644
--- a/wallet-web/components/ExecFeeNotice.tsx
+++ b/wallet-web/components/ExecFeeNotice.tsx
@@ -1,15 +1,12 @@
import {Alert} from '@material-ui/lab';
import { getDisplayExecGasFee } from "../common/helpers";
-type ExecFeeNoticeProps = {
- name: string
-}
-const ExecFeeNotice = (props: ExecFeeNoticeProps) => {
+const ExecFeeNotice = ({name}: {name: string}) => {
return (
The gas fee for
- {props.name}
+ {name}
{`is ${getDisplayExecGasFee()}`}
)
diff --git a/wallet-web/components/bond/BondNodeForm.tsx b/wallet-web/components/bond/BondNodeForm.tsx
index d5e0265ce6..fb150ff8e4 100644
--- a/wallet-web/components/bond/BondNodeForm.tsx
+++ b/wallet-web/components/bond/BondNodeForm.tsx
@@ -1,380 +1,455 @@
-import React from 'react';
-import Grid from '@material-ui/core/Grid';
-import TextField from '@material-ui/core/TextField';
-import { Button, Checkbox, FormControlLabel, InputAdornment } from "@material-ui/core";
+import React from "react";
+import Grid from "@material-ui/core/Grid";
+import TextField from "@material-ui/core/TextField";
+import {
+ Button,
+ Checkbox,
+ FormControlLabel,
+ InputAdornment,
+} from "@material-ui/core";
import bs58 from "bs58";
-import semver from "semver"
+import semver from "semver";
import { NodeType } from "../../common/node";
import { theme } from "../../lib/theme";
-import { basicRawCoinValueValidation, makeBasicStyle, validateRawPort } from "../../common/helpers";
+import {
+ basicRawCoinValueValidation,
+ makeBasicStyle,
+ validateRawPort,
+} from "../../common/helpers";
import { Coin, nativeToPrintable } from "@nymproject/nym-validator-client";
import { DENOM } from "../../pages/_app";
import { printableBalanceToNative } from "@nymproject/nym-validator-client/dist/currency";
import { BondingInformation } from "./NodeBond";
-const DEFAULT_MIX_PORT = 1789
-const DEFAULT_VERLOC_PORT = 1790
-const DEFAULT_HTTP_API_PORT = 8000
-const DEFAULT_CLIENTS_PORT = 9000
+const DEFAULT_MIX_PORT = 1789;
+const DEFAULT_VERLOC_PORT = 1790;
+const DEFAULT_HTTP_API_PORT = 8000;
+const DEFAULT_CLIENTS_PORT = 9000;
-type BondNodeFormProps = {
- type: NodeType
- minimumMixnodeBond: Coin,
- minimumGatewayBond: Coin,
- onSubmit: (event: any) => void
-}
+type TBondNodeFormProps = {
+ type: NodeType;
+ minimumMixnodeBond: Coin;
+ minimumGatewayBond: Coin;
+ onSubmit: (event: any) => void;
+};
-export default function BondNodeForm(props: BondNodeFormProps) {
- const classes = makeBasicStyle(theme);
+type TFormInput = {
+ amount: string;
+ host: string;
+ http_api_port: string;
+ mixPort: string;
+ verlocPort: string;
+ sphinxKey: string;
+ identityKey: string;
+ version: string;
+ location?: string;
+ clientsPort?: string;
+ httpApiPort?: string;
+};
- const [validity, setValidity] = React.useState({
- validAmount: true,
- validSphinxKey: true,
- validIdentityKey: true,
- validHost: true,
- validVersion: true,
- validLocation: true,
- validMixPort: true,
+export default function BondNodeForm(props: TBondNodeFormProps) {
+ const classes = makeBasicStyle(theme);
- // 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,
+ const [validity, setValidity] = React.useState({
+ validAmount: true,
+ validSphinxKey: true,
+ validIdentityKey: true,
+ validHost: true,
+ validVersion: true,
+ validLocation: true,
+ validMixPort: true,
- // gateway-specific:
- validClientsPort: 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,
- const [advancedShown, setAdvancedShown] = React.useState(false)
+ // gateway-specific:
+ validClientsPort: true,
+ });
- const handleCheckboxToggle = () => {
- setAdvancedShown((prevSet) => !prevSet);
+ const [advancedShown, setAdvancedShown] = React.useState(false);
+
+ const handleCheckboxToggle = () => {
+ setAdvancedShown((prevSet) => !prevSet);
+ };
+
+ const validateForm = ({
+ amount,
+ sphinxKey,
+ identityKey,
+ host,
+ version,
+ verlocPort,
+ location,
+ mixPort,
+ httpApiPort,
+ clientsPort,
+ }: TFormInput): boolean => {
+ let validAmount = validateAmount(amount);
+ let validSphinxKey = validateKey(sphinxKey);
+ let validIdentityKey = validateKey(identityKey);
+ let validHost = validateHost(host);
+ let validVersion = validateVersion(version);
+
+ let validLocation =
+ props.type == NodeType.Gateway ? validateLocation(location) : true;
+
+ let newValidity = {
+ validAmount: validAmount,
+ validSphinxKey: validSphinxKey,
+ validIdentityKey: validIdentityKey,
+ validHost: validHost,
+ validVersion: validVersion,
+ validLocation: validLocation,
+ };
+
+ if (advancedShown) {
+ let validMixPort = validateRawPort(mixPort);
+ let validVerlocPort =
+ props.type == NodeType.Mixnode ? validateRawPort(verlocPort) : true;
+ let validHttpApiPort =
+ props.type == NodeType.Mixnode ? validateRawPort(httpApiPort) : true;
+ let validClientsPort =
+ props.type == NodeType.Gateway ? validateRawPort(clientsPort) : true;
+
+ newValidity = {
+ ...newValidity,
+ ...{
+ validMixPort: validMixPort,
+ validVerlocPort: validVerlocPort,
+ validHttpApiPort: validHttpApiPort,
+ validClientsPort: validClientsPort,
+ },
+ };
}
+ setValidity((previousState) => {
+ return { ...previousState, ...newValidity };
+ });
- const validateForm = (event: any): boolean => {
- let validAmount = validateAmount(event.target.amount.value);
- let validSphinxKey = validateKey(event.target.sphinxkey.value);
- let validIdentityKey = validateKey(event.target.identity.value);
- let validHost = validateHost(event.target.host.value);
- let validVersion = validateVersion(event.target.version.value);
+ // just AND everything together
+ const reducer = (acc, current) => acc && current;
+ return Object.entries(newValidity)
+ .map((entry) => entry[1])
+ .reduce(reducer, true);
+ };
- let validLocation = (props.type == NodeType.Gateway) ? validateLocation(event.target.location.value) : 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;
+ }
- let newValidity = {
- validAmount: validAmount,
- validSphinxKey: validSphinxKey,
- validIdentityKey: validIdentityKey,
- validHost: validHost,
- validVersion: validVersion,
+ // 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);
+ }
+ };
- validLocation: validLocation,
- }
+ 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;
+ }
+ };
- if (advancedShown) {
- let validMixPort = validateRawPort(event.target.mixPort.value)
- let validVerlocPort = (props.type == NodeType.Mixnode) ? validateRawPort(event.target.verlocPort.value) : true;
- let validHttpApiPort = (props.type == NodeType.Mixnode) ? validateRawPort(event.target.httpApiPort.value) : true;
- let validClientsPort = (props.type == NodeType.Gateway) ? validateRawPort(event.target.clientsPort.value) : true;
+ 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
- newValidity = {
- ...newValidity, ...{
- validMixPort: validMixPort,
- validVerlocPort: validVerlocPort,
- validHttpApiPort: validHttpApiPort,
- validClientsPort: validClientsPort,
+ // 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.split(".").length - 1;
+ const colon_occurrences = host.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,
+ }: TFormInput): BondingInformation => {
+ return {
+ amount,
+ nodeDetails: {
+ host,
+ http_api_port: advancedShown
+ ? parseInt(httpApiPort)
+ : DEFAULT_HTTP_API_PORT,
+ mix_port: advancedShown ? parseInt(mixPort) : DEFAULT_MIX_PORT,
+ verloc_port: advancedShown ? parseInt(verlocPort) : DEFAULT_VERLOC_PORT,
+ sphinx_key: sphinxKey,
+ identity_key: identityKey,
+ version,
+ },
+ };
+ };
+
+ const constructGatewayBondingInfo = (
+ data: TFormInput
+ ): BondingInformation => {
+ return {
+ amount: data.amount,
+ nodeDetails: {
+ host: data.host,
+ mix_port: advancedShown ? parseInt(data.mixPort) : DEFAULT_MIX_PORT,
+ clients_port: advancedShown
+ ? parseInt(data.clientsPort)
+ : DEFAULT_CLIENTS_PORT,
+ sphinx_key: data.sphinxKey,
+ identity_key: data.identityKey,
+ version: data.version,
+ location: data.location,
+ },
+ };
+ };
+
+ const submitForm = (event: React.FormEvent) => {
+ event.preventDefault();
+ const target = event.target as unknown as TFormInput;
+
+ if (validateForm(target)) {
+ if (props.type == NodeType.Mixnode) {
+ return props.onSubmit(constructMixnodeBondingInfo(target));
+ } else {
+ return props.onSubmit(constructGatewayBondingInfo(target));
+ }
+ }
+ };
+
+ 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 (
+
+ );
+}
diff --git a/wallet-web/components/bond/NodeBond.tsx b/wallet-web/components/bond/NodeBond.tsx
index f7f1b18cf6..1eb7a706f6 100644
--- a/wallet-web/components/bond/NodeBond.tsx
+++ b/wallet-web/components/bond/NodeBond.tsx
@@ -150,7 +150,7 @@ const BondNode = () => {
-
+
Bond a {nodeType}