Add functionality to create a validator #28
@ -1,5 +1,5 @@
|
|||||||
import React, { useEffect } from "react";
|
import React, { useEffect } from "react";
|
||||||
import {useLocation, useNavigate, useParams, useSearchParams } from "react-router-dom";
|
import {useLocation, useNavigate, useSearchParams } from "react-router-dom";
|
||||||
|
|
||||||
import { Button, Box, Container, Typography, colors } from "@mui/material";
|
import { Button, Box, Container, Typography, colors } from "@mui/material";
|
||||||
|
|
||||||
|
@ -1,12 +1,14 @@
|
|||||||
import React, { useEffect, useMemo, useState } from 'react';
|
import React, { useEffect, useMemo, useState } from 'react';
|
||||||
import { Box, MenuItem, Select, TextField, Typography } from '@mui/material';
|
|
||||||
import { enqueueSnackbar } from 'notistack';
|
import { enqueueSnackbar } from 'notistack';
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
|
|
||||||
|
import { Box, MenuItem, Select, TextField, Typography } from '@mui/material';
|
||||||
import { MsgCreateValidator } from 'cosmjs-types/cosmos/staking/v1beta1/tx';
|
import { MsgCreateValidator } from 'cosmjs-types/cosmos/staking/v1beta1/tx';
|
||||||
import { fromBech32, toBech32 } from '@cosmjs/encoding';
|
import { fromBech32, toBech32 } from '@cosmjs/encoding';
|
||||||
import { LoadingButton } from '@mui/lab';
|
import { LoadingButton } from '@mui/lab';
|
||||||
import { EncodeObject, encodePubkey } from '@cosmjs/proto-signing';
|
import { EncodeObject, encodePubkey } from '@cosmjs/proto-signing';
|
||||||
import { Registry } from '@cerc-io/registry-sdk';
|
import { Registry } from '@cerc-io/registry-sdk';
|
||||||
|
|
||||||
import { useWalletConnectContext } from '../context/WalletConnectContext';
|
import { useWalletConnectContext } from '../context/WalletConnectContext';
|
||||||
import { Participant } from '../types';
|
import { Participant } from '../types';
|
||||||
|
|
||||||
@ -21,27 +23,45 @@ const Validator = () => {
|
|||||||
const [participant, setParticipant] = useState<Participant | null>(null);
|
const [participant, setParticipant] = useState<Participant | null>(null);
|
||||||
const [isError, setIsError] = useState(false);
|
const [isError, setIsError] = useState(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
if (!session) {
|
if (!session) {
|
||||||
navigate("/connect-wallet?redirectTo=create-validator");
|
navigate("/connect-wallet?redirectTo=validator");
|
||||||
|
}
|
||||||
|
}, [session, navigate]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!cosmosAddress) {
|
||||||
|
setParticipant(null);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const isMonikerValid = useMemo(()=>{
|
const fetchParticipant = async () => {
|
||||||
return moniker.trim().length > 0;
|
const registry = new Registry(process.env.REACT_APP_REGISTRY_GQL_ENDPOINT!);
|
||||||
}, [moniker]);
|
|
||||||
|
|
||||||
const isPubKeyValid = useMemo(()=>{
|
try {
|
||||||
return pubKey.length === 44
|
const fetchedParticipant = await registry.getParticipantByAddress(cosmosAddress);
|
||||||
}, [pubKey]);
|
if (fetchedParticipant) {
|
||||||
|
setParticipant(fetchedParticipant);
|
||||||
const msgCreateValidator: MsgCreateValidator = useMemo(() => {
|
} else {
|
||||||
let value = '';
|
enqueueSnackbar("Participant not found", { variant: "error" });
|
||||||
if (pubKey.length === 44) {
|
setParticipant(null);
|
||||||
value = pubKey;
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error fetching participant", error);
|
||||||
|
setParticipant(null);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
fetchParticipant();
|
||||||
|
}, [cosmosAddress]);
|
||||||
|
|
||||||
|
const isMonikerValid = useMemo(() => moniker.trim().length > 0, [moniker]);
|
||||||
|
const isPubKeyValid = useMemo(() => pubKey.length === 44, [pubKey]);
|
||||||
|
|
||||||
|
const msgCreateValidator: MsgCreateValidator = useMemo(() => {
|
||||||
const encodedPubKey = encodePubkey({
|
const encodedPubKey = encodePubkey({
|
||||||
type: "tendermint/PubKeyEd25519",
|
type: "tendermint/PubKeyEd25519",
|
||||||
value,
|
value: pubKey.length === 44 ? pubKey : '',
|
||||||
});
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@ -70,15 +90,25 @@ const Validator = () => {
|
|||||||
|
|
||||||
const msgCreateValidatorEncodeObject: EncodeObject = {
|
const msgCreateValidatorEncodeObject: EncodeObject = {
|
||||||
typeUrl: '/cosmos.staking.v1beta1.MsgCreateValidator',
|
typeUrl: '/cosmos.staking.v1beta1.MsgCreateValidator',
|
||||||
value: MsgCreateValidator.toJSON(msgCreateValidator)
|
value: MsgCreateValidator.toJSON(msgCreateValidator),
|
||||||
};
|
};
|
||||||
|
|
||||||
const sendTransaction = async (transactionMessage: EncodeObject) => {
|
const sendTransaction = async () => {
|
||||||
try {
|
if (
|
||||||
setIsLoading(true);
|
!isMonikerValid ||
|
||||||
|
!isPubKeyValid ||
|
||||||
|
!msgCreateValidator.validatorAddress
|
||||||
|
) {
|
||||||
|
setIsError(true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const params = { transactionMessage, signer: cosmosAddress };
|
setIsLoading(true);
|
||||||
const responseFromWallet = await signClient!.request<{ code: number }>({
|
enqueueSnackbar("View and sign the message from your Laconic Wallet", { variant: "info" });
|
||||||
|
|
||||||
|
try {
|
||||||
|
const params = { transactionMessage: msgCreateValidatorEncodeObject, signer: cosmosAddress };
|
||||||
|
const response = await signClient!.request<{ code: number }>({
|
||||||
topic: session!.topic,
|
topic: session!.topic,
|
||||||
chainId: `cosmos:${process.env.REACT_APP_LACONICD_CHAIN_ID}`,
|
chainId: `cosmos:${process.env.REACT_APP_LACONICD_CHAIN_ID}`,
|
||||||
request: {
|
request: {
|
||||||
@ -86,61 +116,34 @@ const Validator = () => {
|
|||||||
params,
|
params,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
if (responseFromWallet.code !== 0) {
|
|
||||||
|
if (response.code !== 0) {
|
||||||
enqueueSnackbar("Transaction not sent", { variant: "error" });
|
enqueueSnackbar("Transaction not sent", { variant: "error" });
|
||||||
} else {
|
} else {
|
||||||
navigate("/onboarding-success", {
|
navigate("/onboarding-success", { state: { cosmosAddress } });
|
||||||
state: { cosmosAddress },
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error);
|
console.error("Error sending transaction", error);
|
||||||
enqueueSnackbar("Error in sending transaction", { variant: "error" });
|
enqueueSnackbar("Error in sending transaction", { variant: "error" });
|
||||||
} finally {
|
} finally {
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
const replacer = (key: string, value: any): any => {
|
||||||
const registry = new Registry(process.env.REACT_APP_REGISTRY_GQL_ENDPOINT!);
|
if (value instanceof Uint8Array) {
|
||||||
|
return Buffer.from(value).toString('hex');
|
||||||
const fetchParticipant = async () => {
|
|
||||||
try {
|
|
||||||
if (!cosmosAddress) {
|
|
||||||
setParticipant(null);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const fetchedParticipant: Participant = await registry.getParticipantByAddress(cosmosAddress);
|
|
||||||
if (!fetchedParticipant) {
|
|
||||||
enqueueSnackbar("Participant not found", { variant: "error" });
|
|
||||||
setParticipant(null);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
setParticipant(fetchedParticipant);
|
|
||||||
} catch (error) {
|
|
||||||
console.error("Error fetching participant", error);
|
|
||||||
setParticipant(null);
|
|
||||||
}
|
}
|
||||||
|
return value;
|
||||||
};
|
};
|
||||||
|
|
||||||
fetchParticipant();
|
|
||||||
}, [cosmosAddress]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box
|
<Box sx={{ display: "flex", flexDirection: "column", marginTop: 6, gap: 1 }}>
|
||||||
sx={{
|
|
||||||
display: "flex",
|
|
||||||
flexDirection: "column",
|
|
||||||
marginTop: 6,
|
|
||||||
gap: 1,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Typography variant="h5">Create a validator</Typography>
|
<Typography variant="h5">Create a validator</Typography>
|
||||||
<Typography variant="body1">Select Laconic account:</Typography>
|
<Typography variant="body1">Select Laconic account:</Typography>
|
||||||
<Select
|
<Select
|
||||||
sx={{ marginBottom: 2 }}
|
sx={{ marginBottom: 2 }}
|
||||||
labelId="demo-simple-select-label"
|
id="cosmos-address-select"
|
||||||
id="demo-simple-select"
|
|
||||||
value={cosmosAddress}
|
value={cosmosAddress}
|
||||||
onChange={(e) => setCosmosAddress(e.target.value)}
|
onChange={(e) => setCosmosAddress(e.target.value)}
|
||||||
style={{ maxWidth: "600px", display: "block" }}
|
style={{ maxWidth: "600px", display: "block" }}
|
||||||
@ -153,16 +156,16 @@ const Validator = () => {
|
|||||||
</Select>
|
</Select>
|
||||||
|
|
||||||
{Boolean(cosmosAddress) && (
|
{Boolean(cosmosAddress) && (
|
||||||
<div>
|
<>
|
||||||
{participant === null ? (
|
{participant ? (
|
||||||
<Typography>No participant found</Typography>
|
|
||||||
) : (
|
|
||||||
<Typography>Onboarded participant</Typography>
|
<Typography>Onboarded participant</Typography>
|
||||||
|
) : (
|
||||||
|
<Typography>No participant found</Typography>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
backgroundColor: participant === null ? "white" : "lightgray",
|
backgroundColor: participant ? "lightgray" : "white",
|
||||||
padding: 3,
|
padding: 3,
|
||||||
wordWrap: "break-word",
|
wordWrap: "break-word",
|
||||||
marginBottom: 3,
|
marginBottom: 3,
|
||||||
@ -170,16 +173,16 @@ const Validator = () => {
|
|||||||
>
|
>
|
||||||
{participant && (
|
{participant && (
|
||||||
<pre style={{ whiteSpace: "pre-wrap", margin: 0 }}>
|
<pre style={{ whiteSpace: "pre-wrap", margin: 0 }}>
|
||||||
<div>
|
|
||||||
Cosmos Address: {participant.cosmosAddress} <br />
|
Cosmos Address: {participant.cosmosAddress} <br />
|
||||||
Nitro Address: {participant.nitroAddress} <br />
|
Nitro Address: {participant.nitroAddress} <br />
|
||||||
Role: {participant.role} <br />
|
Role: {participant.role} <br />
|
||||||
KYC ID: {participant.kycId} <br />
|
KYC ID: {participant.kycId} <br />
|
||||||
</div>
|
|
||||||
</pre>
|
</pre>
|
||||||
)}
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
|
{participant?.role === "validator" && (
|
||||||
|
<>
|
||||||
<Box style={{ maxWidth: "600px" }}>
|
<Box style={{ maxWidth: "600px" }}>
|
||||||
<TextField
|
<TextField
|
||||||
id="moniker"
|
id="moniker"
|
||||||
@ -190,9 +193,9 @@ const Validator = () => {
|
|||||||
value={moniker}
|
value={moniker}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
setIsError(false);
|
setIsError(false);
|
||||||
setMoniker(e.target.value)
|
setMoniker(e.target.value);
|
||||||
}}
|
}}
|
||||||
error={!isMonikerValid && isError }
|
error={!isMonikerValid && isError}
|
||||||
helperText={!isMonikerValid && isError ? "Moniker is required" : ""}
|
helperText={!isMonikerValid && isError ? "Moniker is required" : ""}
|
||||||
/>
|
/>
|
||||||
<TextField
|
<TextField
|
||||||
@ -204,41 +207,22 @@ const Validator = () => {
|
|||||||
value={pubKey}
|
value={pubKey}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
setIsError(false);
|
setIsError(false);
|
||||||
setPubKey(e.target.value)
|
setPubKey(e.target.value);
|
||||||
}}
|
}}
|
||||||
error={!isPubKeyValid && isError}
|
error={!isPubKeyValid && isError}
|
||||||
helperText={!isPubKeyValid ? "Public key must be 44 characters" : ""}
|
helperText={!isPubKeyValid && isError ? "Public key must be 44 characters" : ""}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
<>
|
|
||||||
<Typography>Send transaction to chain</Typography>
|
<Typography>Send transaction to chain</Typography>
|
||||||
<Box
|
<Box sx={{ backgroundColor: "lightgray", padding: 3, wordWrap: "break-word" }}>
|
||||||
sx={{
|
|
||||||
backgroundColor: "lightgray",
|
|
||||||
padding: 3,
|
|
||||||
wordWrap: "break-word",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<pre style={{ whiteSpace: "pre-wrap", margin: 0 }}>
|
<pre style={{ whiteSpace: "pre-wrap", margin: 0 }}>
|
||||||
{JSON.stringify(msgCreateValidator, null, 2)}
|
{JSON.stringify(msgCreateValidator, replacer, 2)}
|
||||||
</pre>
|
</pre>
|
||||||
</Box>
|
</Box>
|
||||||
<Box marginTop={1} marginBottom={1}>
|
<Box marginTop={1} marginBottom={1}>
|
||||||
<LoadingButton
|
<LoadingButton
|
||||||
variant="contained"
|
variant="contained"
|
||||||
onClick={async () => {
|
onClick={sendTransaction}
|
||||||
console.log(msgCreateValidatorEncodeObject);
|
|
||||||
if (
|
|
||||||
msgCreateValidator.pubkey?.value?.length !== 34 ||
|
|
||||||
msgCreateValidator.description.moniker.length === 0 ||
|
|
||||||
msgCreateValidator.validatorAddress.length === 0
|
|
||||||
) {
|
|
||||||
setIsError(true);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
await sendTransaction(msgCreateValidatorEncodeObject);
|
|
||||||
}}
|
|
||||||
loading={isLoading}
|
loading={isLoading}
|
||||||
disabled={isError}
|
disabled={isError}
|
||||||
>
|
>
|
||||||
@ -246,7 +230,8 @@ const Validator = () => {
|
|||||||
</LoadingButton>
|
</LoadingButton>
|
||||||
</Box>
|
</Box>
|
||||||
</>
|
</>
|
||||||
</div>
|
)}
|
||||||
|
</>
|
||||||
)}
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
|
Loading…
Reference in New Issue
Block a user