cosmos-multisig-ui/components/forms/CreateMultisigForm/formSchema.ts
2024-06-10 12:32:00 +02:00

131 lines
4.1 KiB
TypeScript

import { ChainInfo } from "@/context/ChainsContext/types";
import { pubkeyToAddress } from "@cosmjs/amino";
import { StargateClient } from "@cosmjs/stargate";
import { z } from "zod";
import { checkAddressOrPubkey } from "../../../lib/displayHelpers";
export const getCreateMultisigSchema = (chain: ChainInfo) =>
z
.object({
members: z.array(
z.object({
member: z
.string()
.trim()
.superRefine(async (member, ctx) => {
if (!member) {
return z.NEVER;
}
const addressOrPubkeyError = checkAddressOrPubkey(member, chain.addressPrefix);
if (addressOrPubkeyError) {
ctx.addIssue({ code: z.ZodIssueCode.custom, message: addressOrPubkeyError });
} else {
try {
const address = member.startsWith(chain.addressPrefix)
? member
: pubkeyToAddress(
{ type: "tendermint/PubKeySecp256k1", value: member },
chain.addressPrefix,
);
const client = await StargateClient.connect(chain.nodeAddress);
const accountOnChain = await client.getAccount(address);
if (!accountOnChain || !accountOnChain.pubkey) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: "This account needs to send a transaction to appear on chain",
});
}
} catch {
return z.NEVER;
}
}
}),
}),
),
threshold: z.coerce
.number({ invalid_type_error: "Threshold must be a number" })
.int("Threshold can't have decimals")
.min(1, "Threshold must be at least 1"),
})
.superRefine(({ members }, ctx) => {
if (members.length !== 2) {
return;
}
const firstEmptyMemberIndex = members.findIndex(({ member }) => member.trim() === "");
if (firstEmptyMemberIndex !== -1) {
const issue = {
code: z.ZodIssueCode.custom,
message: "At least 2 members needed",
path: [`members.${firstEmptyMemberIndex}.member`],
};
ctx.addIssue(issue);
}
})
.superRefine(({ members }, ctx) => {
const addresses = members.map(({ member }) => {
if (!member.startsWith(chain.addressPrefix)) {
try {
const address = pubkeyToAddress(
{ type: "tendermint/PubKeySecp256k1", value: member },
chain.addressPrefix,
);
return address;
} catch {}
}
return member;
});
const dupedAddresses = addresses.filter((member, i) => addresses.indexOf(member) !== i);
const dupedAddressesIndexes: number[][] = [];
for (const dupedAddress of dupedAddresses) {
const dupedIndexes = [];
for (let i = 0; i < addresses.length; ++i) {
const index = addresses.indexOf(dupedAddress, i);
if (index !== -1) {
dupedIndexes.push(index);
}
}
dupedAddressesIndexes.push(dupedIndexes.sort());
}
if (dupedAddressesIndexes.length) {
for (const dupedIndexes of dupedAddressesIndexes) {
for (const duplicateIndex of dupedIndexes) {
const issue = {
code: z.ZodIssueCode.custom,
message: `Members cannot be duplicate (${dupedIndexes
.map((index) => `#${index + 1}`)
.join(", ")})`,
path: [`members.${duplicateIndex}.member`],
};
ctx.addIssue(issue);
}
}
} else {
return z.NEVER;
}
})
.refine(
({ members, threshold }) => threshold <= members.filter(({ member }) => member !== "").length,
({ members }) => ({
message: `Threshold can't be higher than the number of members (${
members.filter(({ member }) => member !== "").length
})`,
path: ["threshold"],
}),
);