From c90ebf0a6ae2728c863362eb692fdf0213da4fca Mon Sep 17 00:00:00 2001 From: Fouad Date: Wed, 30 Oct 2024 17:15:38 +0000 Subject: [PATCH] Feature/wallet bonding fixes (#5064) * bonding and unbonding for nym nodes --- nym-wallet/Cargo.lock | 68 +++++++++---------- .../signatures/ed25519_signing_payload.rs | 4 +- .../Bonding/forms/nym-node/FormContext.tsx | 21 +++--- .../Bonding/forms/nym-node/NymNodeAmount.tsx | 36 +++++----- .../Bonding/forms/nym-node/NymNodeData.tsx | 29 ++++---- .../forms/nym-node/NymNodeSignature.tsx | 39 +++++++---- .../forms/nym-node/amountValidationSchema.ts | 4 +- .../Bonding/modals/BondNymNodeModal.tsx | 41 ++++++++--- .../components/Bonding/modals/UnbondModal.tsx | 5 +- nym-wallet/src/context/bonding.tsx | 40 ++++++++++- nym-wallet/src/context/mocks/bonding.tsx | 9 +++ nym-wallet/src/hooks/useGetNodeDetails.ts | 1 + nym-wallet/src/pages/bonding/Bonding.tsx | 19 +++++- .../settings-pages/NodeUnbondPage.tsx | 3 +- nym-wallet/src/types/global.ts | 2 +- 15 files changed, 203 insertions(+), 118 deletions(-) diff --git a/nym-wallet/Cargo.lock b/nym-wallet/Cargo.lock index bd31b34bc3..fe3e2b65d8 100644 --- a/nym-wallet/Cargo.lock +++ b/nym-wallet/Cargo.lock @@ -173,7 +173,7 @@ checksum = "721cae7de5c34fbb2acd27e21e6d2cf7b886dce0c27388d46c4e6c47ea4318dd" dependencies = [ "proc-macro2", "quote", - "syn 2.0.55", + "syn 2.0.85", ] [[package]] @@ -658,7 +658,7 @@ dependencies = [ "heck 0.4.1", "proc-macro2", "quote", - "syn 2.0.55", + "syn 2.0.85", ] [[package]] @@ -1022,7 +1022,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13b588ba4ac1a99f7f2964d24b3d896ddc6bf847ee3855dbd4366f058cfcd331" dependencies = [ "quote", - "syn 2.0.55", + "syn 2.0.85", ] [[package]] @@ -1083,7 +1083,7 @@ checksum = "83fdaf97f4804dcebfa5862639bc9ce4121e82140bec2a987ac5140294865b5b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.55", + "syn 2.0.85", ] [[package]] @@ -1751,7 +1751,7 @@ checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72" dependencies = [ "proc-macro2", "quote", - "syn 2.0.55", + "syn 2.0.85", ] [[package]] @@ -1931,7 +1931,7 @@ dependencies = [ "proc-macro-error2", "proc-macro2", "quote", - "syn 2.0.55", + "syn 2.0.85", ] [[package]] @@ -3667,7 +3667,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.55", + "syn 2.0.85", ] [[package]] @@ -3854,7 +3854,7 @@ dependencies = [ "pest_meta", "proc-macro2", "quote", - "syn 2.0.55", + "syn 2.0.85", ] [[package]] @@ -3983,7 +3983,7 @@ checksum = "4359fd9c9171ec6e8c62926d6faaf553a8dc3f64e1507e76da7911b4f6a04405" dependencies = [ "proc-macro2", "quote", - "syn 2.0.55", + "syn 2.0.85", ] [[package]] @@ -4140,7 +4140,7 @@ dependencies = [ "proc-macro-error-attr2", "proc-macro2", "quote", - "syn 2.0.55", + "syn 2.0.85", ] [[package]] @@ -4151,9 +4151,9 @@ checksum = "dc375e1527247fe1a97d8b7156678dfe7c1af2fc075c9a4db3690ecd2a148068" [[package]] name = "proc-macro2" -version = "1.0.79" +version = "1.0.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e835ff2298f5721608eb1a980ecaee1aef2c132bf95ecc026a11b7bf3c01c02e" +checksum = "f139b0662de085916d1fb67d2b4169d1addddda1919e696f3252b740b629986e" dependencies = [ "unicode-ident", ] @@ -4178,7 +4178,7 @@ dependencies = [ "itertools 0.11.0", "proc-macro2", "quote", - "syn 2.0.55", + "syn 2.0.85", ] [[package]] @@ -4704,7 +4704,7 @@ dependencies = [ "proc-macro2", "quote", "serde_derive_internals", - "syn 2.0.55", + "syn 2.0.85", ] [[package]] @@ -4798,9 +4798,9 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.210" +version = "1.0.214" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8e3592472072e6e22e0a54d5904d9febf8508f65fb8552499a1abc7d1078c3a" +checksum = "f55c3193aca71c12ad7890f1785d2b73e1b9f63a0bbc353c08ef26fe03fc56b5" dependencies = [ "serde_derive", ] @@ -4825,13 +4825,13 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.210" +version = "1.0.214" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "243902eda00fad750862fc144cea25caca5e20d615af0a81bee94ca738f1df1f" +checksum = "de523f781f095e28fa605cdce0f8307e451cc0fd14e2eb4cd2e98a355b147766" dependencies = [ "proc-macro2", "quote", - "syn 2.0.55", + "syn 2.0.85", ] [[package]] @@ -4842,14 +4842,14 @@ checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711" dependencies = [ "proc-macro2", "quote", - "syn 2.0.55", + "syn 2.0.85", ] [[package]] name = "serde_json" -version = "1.0.128" +version = "1.0.132" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ff5456707a1de34e7e37f2a6fd3d3f808c318259cbd01ab6377795054b483d8" +checksum = "d726bfaff4b320266d395898905d0eba0345aae23b54aee3a737e260fd46db03" dependencies = [ "itoa 1.0.9", "memchr", @@ -4865,7 +4865,7 @@ checksum = "8725e1dfadb3a50f7e5ce0b1a540466f6ed3fe7a0fca2ac2b8b831d31316bd00" dependencies = [ "proc-macro2", "quote", - "syn 2.0.55", + "syn 2.0.85", ] [[package]] @@ -5219,7 +5219,7 @@ dependencies = [ "proc-macro2", "quote", "rustversion", - "syn 2.0.55", + "syn 2.0.85", ] [[package]] @@ -5262,9 +5262,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.55" +version = "2.0.85" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "002a1b3dbf967edfafc32655d0f377ab0bb7b994aa1d32c8cc7e9b8bf3ebb8f0" +checksum = "5023162dfcd14ef8f32034d8bcd4cc5ddc61ef7a247c024a33e24e1f24d21b56" dependencies = [ "proc-macro2", "quote", @@ -5769,7 +5769,7 @@ checksum = "08904e7672f5eb876eaaf87e0ce17857500934f4981c4a0ab2b4aa98baac7fc3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.55", + "syn 2.0.85", ] [[package]] @@ -5856,7 +5856,7 @@ checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752" dependencies = [ "proc-macro2", "quote", - "syn 2.0.55", + "syn 2.0.85", ] [[package]] @@ -6021,7 +6021,7 @@ checksum = "5f4f31f56159e98206da9efd823404b79b6ef3143b4a7ab76e67b1751b25a4ab" dependencies = [ "proc-macro2", "quote", - "syn 2.0.55", + "syn 2.0.85", ] [[package]] @@ -6097,7 +6097,7 @@ checksum = "0ea0b99e8ec44abd6f94a18f28f7934437809dd062820797c52401298116f70e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.55", + "syn 2.0.85", "termcolor", ] @@ -6201,7 +6201,7 @@ dependencies = [ "proc-macro-error", "proc-macro2", "quote", - "syn 2.0.55", + "syn 2.0.85", ] [[package]] @@ -6316,7 +6316,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.55", + "syn 2.0.85", "wasm-bindgen-shared", ] @@ -6350,7 +6350,7 @@ checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.55", + "syn 2.0.85", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -6979,7 +6979,7 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", - "syn 2.0.55", + "syn 2.0.85", ] [[package]] diff --git a/nym-wallet/src-tauri/src/operations/signatures/ed25519_signing_payload.rs b/nym-wallet/src-tauri/src/operations/signatures/ed25519_signing_payload.rs index 53658067aa..aa59205169 100644 --- a/nym-wallet/src-tauri/src/operations/signatures/ed25519_signing_payload.rs +++ b/nym-wallet/src-tauri/src/operations/signatures/ed25519_signing_payload.rs @@ -116,12 +116,12 @@ pub async fn generate_gateway_bonding_msg_payload( #[tauri::command] pub async fn generate_nym_node_bonding_msg_payload( - nym_node: NymNode, + nymnode: NymNode, cost_params: NodeCostParams, pledge: DecCoin, state: tauri::State<'_, WalletState>, ) -> Result { - nym_node_bonding_msg_payload(nym_node, cost_params, pledge, state).await + nym_node_bonding_msg_payload(nymnode, cost_params, pledge, state).await } #[tauri::command] diff --git a/nym-wallet/src/components/Bonding/forms/nym-node/FormContext.tsx b/nym-wallet/src/components/Bonding/forms/nym-node/FormContext.tsx index 6ad0bcadbc..525545b91d 100644 --- a/nym-wallet/src/components/Bonding/forms/nym-node/FormContext.tsx +++ b/nym-wallet/src/components/Bonding/forms/nym-node/FormContext.tsx @@ -2,15 +2,15 @@ import React, { createContext, useContext, useMemo, useState } from 'react'; import { CurrencyDenom } from '@nymproject/types'; import { TBondNymNodeArgs, TBondMixNodeArgs } from 'src/types'; -const defaultNymNodeValues: TBondNymNodeArgs['nymNode'] = { +const defaultNymNodeValues: TBondNymNodeArgs['nymnode'] = { identity_key: 'H6rXWgsW89QsVyaNSS3qBe9zZFLhBS6Gn3YRkGFSoFW9', - custom_http_port: 1, + custom_http_port: null, host: '1.1.1.1', }; const defaultCostParams = (denom: CurrencyDenom): TBondNymNodeArgs['costParams'] => ({ interval_operating_cost: { amount: '40', denom }, - profit_margin_percent: '10', + profit_margin_percent: '40', }); const defaultAmount = (denom: CurrencyDenom): TBondMixNodeArgs['pledge'] => ({ @@ -21,14 +21,14 @@ const defaultAmount = (denom: CurrencyDenom): TBondMixNodeArgs['pledge'] => ({ interface FormContextType { step: 1 | 2 | 3 | 4; setStep: React.Dispatch>; - nymNodeData: TBondNymNodeArgs['nymNode']; - setNymNodeData: React.Dispatch>; + nymNodeData: TBondNymNodeArgs['nymnode']; + setNymNodeData: React.Dispatch>; costParams: TBondNymNodeArgs['costParams']; setCostParams: React.Dispatch>; amountData: TBondMixNodeArgs['pledge']; setAmountData: React.Dispatch>; - signature?: string; - setSignature: React.Dispatch>; + signature: string; + setSignature: React.Dispatch>; onError: (e: string) => void; } @@ -41,9 +41,8 @@ const FormContext = createContext({ setCostParams: () => {}, amountData: defaultAmount('nym'), setAmountData: () => {}, - signature: undefined, + signature: '', setSignature: () => {}, - onError: () => {}, }); @@ -52,10 +51,10 @@ const FormContextProvider = ({ children }: { children: React.ReactNode }) => { const denom = 'nym'; const [step, setStep] = useState<1 | 2 | 3 | 4>(1); - const [nymNodeData, setNymNodeData] = useState(defaultNymNodeValues); + const [nymNodeData, setNymNodeData] = useState(defaultNymNodeValues); const [costParams, setCostParams] = useState(defaultCostParams(denom)); const [amountData, setAmountData] = useState(defaultAmount(denom)); - const [signature, setSignature] = useState(); + const [signature, setSignature] = useState(''); const onError = (e: string) => { console.error(e); diff --git a/nym-wallet/src/components/Bonding/forms/nym-node/NymNodeAmount.tsx b/nym-wallet/src/components/Bonding/forms/nym-node/NymNodeAmount.tsx index 550dd639ad..e95d0ae934 100644 --- a/nym-wallet/src/components/Bonding/forms/nym-node/NymNodeAmount.tsx +++ b/nym-wallet/src/components/Bonding/forms/nym-node/NymNodeAmount.tsx @@ -1,22 +1,12 @@ import React from 'react'; import { Stack, TextField, Box, FormHelperText } from '@mui/material'; import { useForm } from 'react-hook-form'; -import { TBondNymNodeArgs } from 'src/types'; import { yupResolver } from '@hookform/resolvers/yup'; import { SimpleModal } from 'src/components/Modals/SimpleModal'; import { CurrencyFormField } from '@nymproject/react/currency/CurrencyFormField'; import { checkHasEnoughFunds } from 'src/utils'; import { nymNodeAmountSchema } from './amountValidationSchema'; - -const defaultNymNodeCostParamValues: TBondNymNodeArgs['costParams'] = { - profit_margin_percent: '10', - interval_operating_cost: { amount: '40', denom: 'nym' }, -}; - -const defaultNymNodePledgeValue: TBondNymNodeArgs['pledge'] = { - amount: '100', - denom: 'nym', -}; +import { useFormContext } from './FormContext'; type NymNodeDataProps = { onClose: () => void; @@ -26,6 +16,7 @@ type NymNodeDataProps = { }; const NymNodeAmount = ({ onClose, onBack, onNext, step }: NymNodeDataProps) => { + const { setAmountData, setCostParams, amountData, costParams } = useFormContext(); const { formState: { errors }, register, @@ -36,21 +27,26 @@ const NymNodeAmount = ({ onClose, onBack, onNext, step }: NymNodeDataProps) => { } = useForm({ mode: 'all', defaultValues: { - pledge: defaultNymNodePledgeValue, - ...defaultNymNodeCostParamValues, + pledge: amountData, + ...costParams, }, resolver: yupResolver(nymNodeAmountSchema()), }); - console.log(errors, 'errors'); - const handleRequestValidation = async () => { const values = getValues(); const hasSufficientTokens = await checkHasEnoughFunds(values.pledge.amount); if (hasSufficientTokens) { - handleSubmit(onNext)(); + handleSubmit((args) => { + setAmountData(args.pledge); + setCostParams({ + profit_margin_percent: args.profit_margin_percent, + interval_operating_cost: args.interval_operating_cost, + }); + onNext(); + })(); } else { setError('pledge.amount', { message: 'Not enough tokens' }); } @@ -77,8 +73,8 @@ const NymNodeAmount = ({ onClose, onBack, onNext, step }: NymNodeDataProps) => { setValue('pledge.amount', newValue.amount, { shouldValidate: true }); }} validationError={errors.pledge?.amount?.message} - denom={defaultNymNodePledgeValue.denom} - initialValue={defaultNymNodePledgeValue.amount} + denom={amountData.denom} + initialValue={amountData.amount} /> @@ -90,8 +86,8 @@ const NymNodeAmount = ({ onClose, onBack, onNext, step }: NymNodeDataProps) => { setValue('interval_operating_cost', newValue, { shouldValidate: true }); }} validationError={errors.interval_operating_cost?.amount?.message} - denom={defaultNymNodeCostParamValues.interval_operating_cost.denom} - initialValue={defaultNymNodeCostParamValues.interval_operating_cost.amount} + denom={costParams.interval_operating_cost.denom} + initialValue={costParams.interval_operating_cost.amount} /> Monthly operational costs of running your node. If your node is in the active set the amount will be paid diff --git a/nym-wallet/src/components/Bonding/forms/nym-node/NymNodeData.tsx b/nym-wallet/src/components/Bonding/forms/nym-node/NymNodeData.tsx index e32068a9c6..313dd61fa8 100644 --- a/nym-wallet/src/components/Bonding/forms/nym-node/NymNodeData.tsx +++ b/nym-wallet/src/components/Bonding/forms/nym-node/NymNodeData.tsx @@ -1,18 +1,12 @@ import React from 'react'; +import * as yup from 'yup'; import { Stack, TextField, FormControlLabel, Checkbox } from '@mui/material'; import { useForm } from 'react-hook-form'; import { IdentityKeyFormField } from '@nymproject/react/mixnodes/IdentityKeyFormField'; -import { TBondNymNodeArgs } from 'src/types'; import { yupResolver } from '@hookform/resolvers/yup'; -import * as yup from 'yup'; import { isValidHostname, validateRawPort } from 'src/utils'; import { SimpleModal } from 'src/components/Modals/SimpleModal'; - -const defaultNymNodeValues: TBondNymNodeArgs['nymNode'] = { - identity_key: 'H6rXWgsW89QsVyaNSS3qBe9zZFLhBS6Gn3YRkGFSoFW9', - custom_http_port: 1, - host: '1.1.1.1', -}; +import { useFormContext } from './FormContext'; const yupValidationSchema = yup.object().shape({ identity_key: yup.string().required('Identity key is required'), @@ -24,8 +18,13 @@ const yupValidationSchema = yup.object().shape({ custom_http_port: yup .number() - .required('A custom http port is required') - .test('valid-http', 'A valid http port is required', (value) => (value ? validateRawPort(value) : false)), + .nullable() + .test('valid-http', 'A valid http port is required', (value) => { + if (value === null) { + return true; + } + return value ? validateRawPort(value) : false; + }), }); type NymNodeDataProps = { @@ -36,6 +35,7 @@ type NymNodeDataProps = { }; const NymNodeData = ({ onClose, onNext, step }: NymNodeDataProps) => { + const { setNymNodeData, nymNodeData } = useFormContext(); const { formState: { errors }, register, @@ -43,14 +43,17 @@ const NymNodeData = ({ onClose, onNext, step }: NymNodeDataProps) => { handleSubmit, } = useForm({ mode: 'all', - defaultValues: defaultNymNodeValues, + defaultValues: nymNodeData, resolver: yupResolver(yupValidationSchema), }); const [showAdvancedOptions, setShowAdvancedOptions] = React.useState(false); const handleNext = async () => { - handleSubmit(onNext)(); + handleSubmit((args) => { + setNymNodeData(args); + onNext(); + })(); }; return ( @@ -69,7 +72,7 @@ const NymNodeData = ({ onClose, onNext, step }: NymNodeDataProps) => { required fullWidth label="Identity Key" - initialValue={defaultNymNodeValues.identity_key} + initialValue={nymNodeData.identity_key} errorText={errors.identity_key?.message?.toString()} onChanged={(value) => setValue('identity_key', value, { shouldValidate: true })} showTickOnValid={false} diff --git a/nym-wallet/src/components/Bonding/forms/nym-node/NymNodeSignature.tsx b/nym-wallet/src/components/Bonding/forms/nym-node/NymNodeSignature.tsx index e1e5814d55..c4c809ca82 100644 --- a/nym-wallet/src/components/Bonding/forms/nym-node/NymNodeSignature.tsx +++ b/nym-wallet/src/components/Bonding/forms/nym-node/NymNodeSignature.tsx @@ -1,4 +1,5 @@ import React, { useEffect, useState } from 'react'; +import * as yup from 'yup'; import { Stack, TextField, Typography } from '@mui/material'; import { useForm } from 'react-hook-form'; import { CopyToClipboard } from 'src/components/CopyToClipboard'; @@ -7,9 +8,11 @@ import { SimpleModal } from 'src/components/Modals/SimpleModal'; import { useBondingContext } from 'src/context'; import { TBondNymNodeArgs } from 'src/types'; import { Signature } from 'src/pages/bonding/types'; +import { yupResolver } from '@hookform/resolvers/yup'; +import { useFormContext } from './FormContext'; const NymNodeSignature = ({ - nymNode, + nymnode, pledge, costParams, step, @@ -17,27 +20,38 @@ const NymNodeSignature = ({ onClose, onBack, }: { - nymNode: TBondNymNodeArgs['nymNode']; + nymnode: TBondNymNodeArgs['nymnode']; pledge: TBondNymNodeArgs['pledge']; costParams: TBondNymNodeArgs['costParams']; step: number; - onNext: (data: Signature) => void; + onNext: () => void; onClose: () => void; onBack: () => void; }) => { const [message, setMessage] = useState(''); const [error, setError] = useState(); const { generateNymNodeMsgPayload } = useBondingContext(); + const { signature, setSignature } = useFormContext(); + + const yupValidationSchema = yup.object().shape({ + signature: yup.string().required('Signature is required'), + }); const { register, formState: { errors }, - } = useForm(); + handleSubmit, + } = useForm({ + defaultValues: { + signature, + }, + resolver: yupResolver(yupValidationSchema), + }); const generateMessage = async () => { try { const msg = await generateNymNodeMsgPayload({ - nymNode, + nymnode, pledge, costParams, }); @@ -53,10 +67,10 @@ const NymNodeSignature = ({ useEffect(() => { generateMessage(); - }, [nymNode, pledge, costParams]); + }, []); - const onSubmit = async (data: Signature) => { - onNext(data); + const handleNext = async () => { + handleSubmit(onNext)(); }; if (error) { @@ -66,11 +80,7 @@ const NymNodeSignature = ({ return ( - onSubmit({ - signature: 'signature', - }) - } + onOk={handleNext} onClose={onClose} header="Bond Nym Node" subHeader={`Step ${step}/3`} @@ -95,10 +105,13 @@ const NymNodeSignature = ({ setSignature(e.target.value)} id="outlined-multiline-static" name="signature" rows={3} placeholder="Paste Signature" + helperText={errors.signature?.message} + error={Boolean(errors.signature)} multiline fullWidth required diff --git a/nym-wallet/src/components/Bonding/forms/nym-node/amountValidationSchema.ts b/nym-wallet/src/components/Bonding/forms/nym-node/amountValidationSchema.ts index 1868a4f2e2..93ab8f1478 100644 --- a/nym-wallet/src/components/Bonding/forms/nym-node/amountValidationSchema.ts +++ b/nym-wallet/src/components/Bonding/forms/nym-node/amountValidationSchema.ts @@ -5,8 +5,8 @@ import { isLessThan, isGreaterThan, validateAmount } from 'src/utils'; const operatingCostAndPmValidation = (params?: TauriContractStateParams) => { const defaultParams = { profit_margin_percent: { - minimum: parseFloat(params?.profit_margin.minimum || '0%'), - maximum: parseFloat(params?.profit_margin.maximum || '100%'), + minimum: parseFloat(params?.profit_margin.minimum || '20%'), + maximum: parseFloat(params?.profit_margin.maximum || '50%'), }, interval_operating_cost: { diff --git a/nym-wallet/src/components/Bonding/modals/BondNymNodeModal.tsx b/nym-wallet/src/components/Bonding/modals/BondNymNodeModal.tsx index bfed31e765..f6288889ab 100644 --- a/nym-wallet/src/components/Bonding/modals/BondNymNodeModal.tsx +++ b/nym-wallet/src/components/Bonding/modals/BondNymNodeModal.tsx @@ -2,18 +2,24 @@ import React, { useContext, useEffect } from 'react'; import { ConfirmTx } from 'src/components/ConfirmTX'; import { ModalListItem } from 'src/components/Modals/ModalListItem'; import { useGetFee } from 'src/hooks/useGetFee'; -import { Signature } from 'src/pages/bonding/types'; import { BalanceWarning } from 'src/components/FeeWarning'; import { AppContext } from 'src/context'; +import { TBondNymNodeArgs } from 'src/types'; import FormContextProvider, { useFormContext } from '../forms/nym-node/FormContext'; import NymNodeData from '../forms/nym-node/NymNodeData'; import NymNodeAmount from '../forms/nym-node/NymNodeAmount'; import NymNodeSignature from '../forms/nym-node/NymNodeSignature'; -export const BondNymNodeModal = ({ onClose }: { onClose: () => void }) => { +export const BondNymNodeModal = ({ + onClose, + onBond, +}: { + onClose: () => void; + onBond: (data: TBondNymNodeArgs) => Promise; +}) => { const { fee, resetFeeState, feeError } = useGetFee(); const { userBalance } = useContext(AppContext); - const { setStep, step, onError, setSignature, amountData, costParams, nymNodeData } = useFormContext(); + const { setStep, step, onError, signature, amountData, costParams, nymNodeData } = useFormContext(); useEffect(() => { if (feeError) { @@ -21,12 +27,17 @@ export const BondNymNodeModal = ({ onClose }: { onClose: () => void }) => { } }, [feeError]); - const handleUpdateMixnodeData = async () => { + const handleUpdateNymnodeData = async () => { setStep(2); }; - const handleUpdateSignature = async (data: Signature) => { - setSignature(data.signature); + const handleBond = async () => { + onBond({ + nymnode: nymNodeData, + pledge: amountData, + costParams, + msgSignature: signature, + }); }; const handleConfirm = async () => {}; @@ -51,7 +62,7 @@ export const BondNymNodeModal = ({ onClose }: { onClose: () => void }) => { } if (step === 1) { - return ; + return ; } if (step === 2) { @@ -61,10 +72,10 @@ export const BondNymNodeModal = ({ onClose }: { onClose: () => void }) => { if (step === 3) { return ( setStep(2)} step={step} @@ -75,14 +86,22 @@ export const BondNymNodeModal = ({ onClose }: { onClose: () => void }) => { return null; }; -export const BondNymNodeModalWithState = ({ open, onClose }: { open: boolean; onClose: () => void }) => { +export const BondNymNode = ({ + open, + onClose, + onBond, +}: { + open: boolean; + onClose: () => void; + onBond: (data: TBondNymNodeArgs) => Promise; +}) => { if (!open) { return null; } return ( - + ); }; diff --git a/nym-wallet/src/components/Bonding/modals/UnbondModal.tsx b/nym-wallet/src/components/Bonding/modals/UnbondModal.tsx index 51e779a563..de2fbdd1ca 100644 --- a/nym-wallet/src/components/Bonding/modals/UnbondModal.tsx +++ b/nym-wallet/src/components/Bonding/modals/UnbondModal.tsx @@ -1,10 +1,9 @@ import * as React from 'react'; import { useEffect } from 'react'; import { Typography } from '@mui/material'; +import { TBondedNode } from 'src/context'; import { useGetFee } from 'src/hooks/useGetFee'; import { isGateway, isMixnode } from 'src/types'; -import { TBondedGateway } from 'src/requests/gatewayDetails'; -import { TBondedMixnode } from 'src/requests/mixnodeDetails'; import { ModalFee } from '../../Modals/ModalFee'; import { ModalListItem } from '../../Modals/ModalListItem'; import { SimpleModal } from '../../Modals/SimpleModal'; @@ -16,7 +15,7 @@ import { } from '../../../requests'; interface Props { - node: TBondedMixnode | TBondedGateway; + node: TBondedNode; onConfirm: () => Promise; onClose: () => void; onError: (e: string) => void; diff --git a/nym-wallet/src/context/bonding.tsx b/nym-wallet/src/context/bonding.tsx index 51671e02e6..bbc1047147 100644 --- a/nym-wallet/src/context/bonding.tsx +++ b/nym-wallet/src/context/bonding.tsx @@ -1,7 +1,7 @@ /* eslint-disable @typescript-eslint/naming-convention */ import React, { createContext, useContext, useEffect, useMemo, useState } from 'react'; import { FeeDetails, TransactionExecuteResult } from '@nymproject/types'; -import { isGateway, isMixnode, TUpdateBondArgs, isNymNode, TNymNodeSignatureArgs } from 'src/types'; +import { isGateway, isMixnode, TUpdateBondArgs, isNymNode, TNymNodeSignatureArgs, TBondNymNodeArgs } from 'src/types'; import { Console } from 'src/utils/console'; import useGetNodeDetails from 'src/hooks/useGetNodeDetails'; import { TBondedNymNode } from 'src/requests/nymNodeDetails'; @@ -20,6 +20,7 @@ import { migrateVestedMixnode as tauriMigrateVestedMixnode, migrateLegacyMixnode as migrateLegacyMixnodeReq, migrateLegacyGateway as migrateLegacyGatewayReq, + bondNymNode, } from '../requests'; export type TBondedNode = TBondedMixnode | TBondedGateway | TBondedNymNode; @@ -31,6 +32,7 @@ export type TBondingContext = { isVestingAccount: boolean; refresh: () => void; unbond: (fee?: FeeDetails) => Promise; + bond: (args: TBondNymNodeArgs) => Promise; updateBondAmount: (data: TUpdateBondArgs) => Promise; redeemRewards: (fee?: FeeDetails) => Promise; generateNymNodeMsgPayload: (data: TNymNodeSignatureArgs) => Promise; @@ -41,6 +43,9 @@ export type TBondingContext = { export const BondingContext = createContext({ isLoading: true, refresh: async () => undefined, + bond: async () => { + throw new Error('Not implemented'); + }, unbond: async () => { throw new Error('Not implemented'); }, @@ -70,7 +75,11 @@ export const BondingContextProvider: FCWithChildren = ({ children }): JSX.Elemen const { userBalance, clientDetails, network } = useContext(AppContext); - const { bondedNode, isLoading: isBondedNodeLoading } = useGetNodeDetails(clientDetails?.client_address, network); + const { + bondedNode, + isLoading: isBondedNodeLoading, + getNodeDetails, + } = useGetNodeDetails(clientDetails?.client_address, network); useEffect(() => { userBalance.fetchBalance(); @@ -91,6 +100,30 @@ export const BondingContextProvider: FCWithChildren = ({ children }): JSX.Elemen resetState(); }; + const bond = async (data: TBondNymNodeArgs) => { + let tx; + setIsLoading(true); + + try { + tx = await bondNymNode({ + ...data, + costParams: { + ...data.costParams, + profit_margin_percent: toPercentFloatString(data.costParams.profit_margin_percent), + }, + }); + if (clientDetails?.client_address) { + await getNodeDetails(clientDetails?.client_address); + } + } catch (e) { + Console.warn(e); + setError(`an error occurred: ${e as string}`); + } finally { + setIsLoading(false); + } + return tx; + }; + const unbond = async (fee?: FeeDetails) => { let tx; setIsLoading(true); @@ -143,7 +176,7 @@ export const BondingContextProvider: FCWithChildren = ({ children }): JSX.Elemen try { const message = await generateNymNodeMsgPayloadReq({ - nymNode: data.nymNode, + nymnode: data.nymnode, pledge: data.pledge, costParams: { ...data.costParams, @@ -187,6 +220,7 @@ export const BondingContextProvider: FCWithChildren = ({ children }): JSX.Elemen isLoading: isLoading || isBondedNodeLoading, error, bondedNode, + bond, unbond, refresh, redeemRewards, diff --git a/nym-wallet/src/context/mocks/bonding.tsx b/nym-wallet/src/context/mocks/bonding.tsx index 2ef46237f9..11abb64e6d 100644 --- a/nym-wallet/src/context/mocks/bonding.tsx +++ b/nym-wallet/src/context/mocks/bonding.tsx @@ -133,6 +133,14 @@ export const MockBondingContextProvider = ({ return TxResultMock; }; + const bond = async (): Promise => { + setIsLoading(true); + await mockSleep(SLEEP_MS); + setBondedData(bondedMixnodeMock); + setIsLoading(false); + return TxResultMock; + }; + const unbond = async (): Promise => { setIsLoading(true); await mockSleep(SLEEP_MS); @@ -189,6 +197,7 @@ export const MockBondingContextProvider = ({ error, bondMixnode, bondGateway, + bond, unbond, refresh, redeemRewards, diff --git a/nym-wallet/src/hooks/useGetNodeDetails.ts b/nym-wallet/src/hooks/useGetNodeDetails.ts index 9049b87ce5..1c9608a351 100644 --- a/nym-wallet/src/hooks/useGetNodeDetails.ts +++ b/nym-wallet/src/hooks/useGetNodeDetails.ts @@ -63,6 +63,7 @@ const useGetNodeDetails = (clientAddress?: string, network?: string) => { bondedNode, isLoading, isError, + getNodeDetails, }; }; diff --git a/nym-wallet/src/pages/bonding/Bonding.tsx b/nym-wallet/src/pages/bonding/Bonding.tsx index 843ada1f5a..b66ffbe58e 100644 --- a/nym-wallet/src/pages/bonding/Bonding.tsx +++ b/nym-wallet/src/pages/bonding/Bonding.tsx @@ -11,14 +11,14 @@ import { ConfirmationDetailProps, ConfirmationDetailsModal } from 'src/component import { ErrorModal } from 'src/components/Modals/ErrorModal'; import { LoadingModal } from 'src/components/Modals/LoadingModal'; import { AppContext, urls } from 'src/context/main'; -import { isGateway, isMixnode, isNymNode, TUpdateBondArgs } from 'src/types'; +import { isGateway, isMixnode, isNymNode, TBondNymNodeArgs, TUpdateBondArgs } from 'src/types'; import { BondedGateway } from 'src/components/Bonding/BondedGateway'; import { RedeemRewardsModal } from 'src/components/Bonding/modals/RedeemRewardsModal'; import { VestingWarningModal } from 'src/components/VestingWarningModal'; import MigrateLegacyNode from 'src/components/Bonding/modals/MigrateLegacyNode'; import { BondedNymNode } from 'src/components/Bonding/BondedNymNode'; import { UpdateBondAmountNymNode } from 'src/components/Bonding/modals/UpdateBondAmountNymNode'; -import { BondNymNodeModalWithState } from 'src/components/Bonding/modals/BondNymNodeModal'; +import { BondNymNode } from 'src/components/Bonding/modals/BondNymNodeModal'; import { BondingContextProvider, useBondingContext } from '../../context'; export const Bonding = () => { @@ -44,6 +44,7 @@ export const Bonding = () => { redeemRewards, updateBondAmount, refresh, + bond, migrateVestedMixnode, migrateLegacyNode, } = useBondingContext(); @@ -74,6 +75,18 @@ export const Bonding = () => { setShowMigrateLegacyNodeModal(shouldShowMigrateLegacyNodeModal()); }, [bondedNode]); + const handleBondNymNode = async (data: TBondNymNodeArgs) => { + setShowModal(undefined); + const tx = await bond(data); + if (tx) { + setConfirmationDetails({ + status: 'success', + title: 'Bonding successful', + txUrl: `${urls(network).blockExplorer}/transaction/${tx?.transaction_hash}`, + }); + } + }; + const handleMigrateVestedMixnode = async () => { setShowMigrationModal(false); const tx = await migrateVestedMixnode(); @@ -250,7 +263,7 @@ export const Bonding = () => { /> )} - + {showModal === 'update-bond-oversaturated' && uncappedSaturation && ( Promise; onError: (e: string) => void; } @@ -71,7 +70,7 @@ export const NodeUnbondPage = ({ bondedNode, onConfirm, onError }: Props) => { - {isConfirmed && !isNymNode(bondedNode) && ( + {isConfirmed && ( { diff --git a/nym-wallet/src/types/global.ts b/nym-wallet/src/types/global.ts index 23144fd871..ff58082ab4 100644 --- a/nym-wallet/src/types/global.ts +++ b/nym-wallet/src/types/global.ts @@ -36,7 +36,7 @@ export type TBondNymNodeArgs = TNymNodeSignatureArgs & { }; export type TNymNodeSignatureArgs = { - nymNode: NymNode; + nymnode: NymNode; costParams: NodeCostParams; pledge: DecCoin; };