From 3645b4a80e181aa502246a8138bb083f4d75d43e Mon Sep 17 00:00:00 2001 From: abefernan <44572727+abefernan@users.noreply.github.com> Date: Mon, 10 Jun 2024 12:32:40 +0200 Subject: [PATCH] Add new create multisig form --- components/forms/CreateMultisigForm/index.tsx | 172 ++++++++++++++++++ 1 file changed, 172 insertions(+) create mode 100644 components/forms/CreateMultisigForm/index.tsx diff --git a/components/forms/CreateMultisigForm/index.tsx b/components/forms/CreateMultisigForm/index.tsx new file mode 100644 index 0000000..0be29c2 --- /dev/null +++ b/components/forms/CreateMultisigForm/index.tsx @@ -0,0 +1,172 @@ +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; +import { + Form, + FormControl, + FormDescription, + FormField, + FormItem, + FormLabel, + FormMessage, +} from "@/components/ui/form"; +import { Input } from "@/components/ui/input"; +import { toastError } from "@/lib/utils"; +import { StargateClient } from "@cosmjs/stargate"; +import { zodResolver } from "@hookform/resolvers/zod"; +import { useRouter } from "next/router"; +import { useEffect } from "react"; +import { useFieldArray, useForm, useWatch } from "react-hook-form"; +import { z } from "zod"; +import { useChains } from "../../../context/ChainsContext"; +import { createMultisigFromCompressedSecp256k1Pubkeys } from "../../../lib/multisigHelpers"; +import ConfirmCreateMultisig from "./ConfirmCreateMultisig"; +import MemberFormField from "./MemberFormField"; +import { getCreateMultisigSchema } from "./formSchema"; + +export default function CreateMultisigForm() { + const router = useRouter(); + const { chain } = useChains(); + + const createMultisigSchema = getCreateMultisigSchema(chain); + + const createMultisigForm = useForm>({ + resolver: zodResolver(createMultisigSchema), + defaultValues: { members: [{ member: "" }, { member: "" }], threshold: 2 }, + }); + + const { + fields: membersFields, + append: membersAppend, + remove: membersRemove, + } = useFieldArray({ name: "members", control: createMultisigForm.control }); + + const watchedMembers = useWatch({ control: createMultisigForm.control, name: "members" }); + + useEffect(() => { + if (watchedMembers.every(({ member }) => member !== "")) { + membersAppend({ member: "" }, { shouldFocus: false }); + createMultisigForm.trigger(); + } + + if ( + watchedMembers.length > 2 && + watchedMembers.filter(({ member }) => member === "").length > 1 + ) { + const memberToRemove = watchedMembers.findIndex(({ member }) => member === ""); + membersRemove(memberToRemove); + createMultisigForm.trigger(); + } + }, [createMultisigForm, membersAppend, membersRemove, watchedMembers]); + + useEffect(() => { + const numMembers = watchedMembers.filter(({ member }) => member !== "").length; + createMultisigForm.setValue("threshold", Math.max(2, numMembers)); + }, [createMultisigForm, watchedMembers]); + + const submitCreateMultisig = async () => { + // Caution: threshold is string instead of number + const { members, threshold } = createMultisigForm.getValues(); + + const pubkeys = await Promise.all( + members + .filter(({ member }) => member !== "") + .map(async ({ member }) => { + try { + if (!member.startsWith(chain.addressPrefix)) { + return member; + } + + const client = await StargateClient.connect(chain.nodeAddress); + const accountOnChain = await client.getAccount(member); + + if (!accountOnChain || !accountOnChain.pubkey) { + return member; + } + + return String(accountOnChain.pubkey.value); + } catch { + return member; + } + }), + ); + + try { + const multisigAddress = await createMultisigFromCompressedSecp256k1Pubkeys( + pubkeys, + Number(threshold), + chain.addressPrefix, + chain.chainId, + ); + + router.push(`/${chain.registryName}/${multisigAddress}`); + } catch (e) { + console.error("Failed to create multisig:", e); + toastError({ + description: "Failed to create multisig", + fullError: e instanceof Error ? e : undefined, + }); + } + }; + + return ( + <> + + + Create multisig + +

+ Fill the form to create a new multisig account on{" "} + {chain.chainDisplayName || "Cosmos Hub"}. +

+

+ You can paste several addresses on the first input if they are separated by whitespace + or commas. +

+
+
+ +
+ + {membersFields.map((arrayField, index) => ( + + ))} + ( + + Threshold + + Number of signatures needed to broadcast a transaction + + +
+ + + out of{" "} + + {watchedMembers.filter(({ member }) => member !== "").length} + {" "} + members + +
+
+ +
+ )} + /> + + + +
+
+ + ); +}