-
Notifications
You must be signed in to change notification settings - Fork 128
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
sim
committed
Jan 13, 2022
1 parent
621bd69
commit 030966d
Showing
32 changed files
with
1,032 additions
and
101 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,19 +1,26 @@ | ||
type Bip = 118 | 330 | ||
|
||
interface Wallet { | ||
name: string | ||
type Wallet = SingleWallet | MultisigWallet | LedgerWallet | ||
type LocalWallet = SingleWallet | MultisigWallet // wallet with name | ||
|
||
interface SingleWallet { | ||
address: string | ||
name: string | ||
} | ||
|
||
interface MultisigWallet extends SingleWallet { | ||
multisig: true | ||
} | ||
|
||
interface LedgerWallet { | ||
address: string | ||
ledger: true | ||
} | ||
|
||
interface StoredWallet extends Wallet { | ||
interface StoredWallet extends SingleWallet { | ||
encrypted: string | ||
} | ||
|
||
interface StoredWalletLegacy extends Wallet { | ||
interface StoredWalletLegacy extends SingleWallet { | ||
wallet: string | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
import GroupsIcon from "@mui/icons-material/Groups" | ||
|
||
const MultisigBadge = () => { | ||
return <GroupsIcon fontSize="small" /> | ||
} | ||
|
||
export default MultisigBadge |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1,2 @@ | ||
export { default as useAuth } from "./hooks/useAuth" | ||
export { default as isWallet } from "./scripts/is" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,152 @@ | ||
import { useState } from "react" | ||
import { useTranslation } from "react-i18next" | ||
import { useFieldArray, useForm } from "react-hook-form" | ||
import axios from "axios" | ||
import AddIcon from "@mui/icons-material/Add" | ||
import RemoveIcon from "@mui/icons-material/Remove" | ||
import { AccAddress, SimplePublicKey } from "@terra-money/terra.js" | ||
import { LegacyAminoMultisigPublicKey } from "@terra-money/terra.js" | ||
import { SAMPLE_ADDRESS } from "config/constants" | ||
import { getErrorMessage } from "utils/error" | ||
import { useLCDClient } from "data/queries/lcdClient" | ||
import { Grid } from "components/layout" | ||
import { Form, FormGroup, FormItem } from "components/form" | ||
import { FormError, FormWarning } from "components/form" | ||
import { Input, Submit, Paste } from "components/form" | ||
import validate from "../../scripts/validate" | ||
|
||
interface Values { | ||
addresses: { value: AccAddress }[] | ||
threshold: number | ||
} | ||
|
||
interface Props { | ||
onCreated: (publicKey: LegacyAminoMultisigPublicKey) => void | ||
} | ||
|
||
const CreateMultisigWalletForm = ({ onCreated }: Props) => { | ||
const { t } = useTranslation() | ||
const lcd = useLCDClient() | ||
|
||
/* form */ | ||
const defaultValues = { | ||
addresses: [{ value: "" }, { value: "" }, { value: "" }], | ||
threshold: 2, | ||
} | ||
|
||
const form = useForm<Values>({ mode: "onChange", defaultValues }) | ||
|
||
const { register, control, handleSubmit, formState } = form | ||
const { errors, isValid } = formState | ||
|
||
const fieldArray = useFieldArray({ control, name: "addresses" }) | ||
const { fields, append, remove } = fieldArray | ||
|
||
const [error, setError] = useState<Error>() | ||
|
||
const paste = async (lines: string[]) => { | ||
const values = lines.filter(AccAddress.validate).map((value) => ({ value })) | ||
if (values.length) fieldArray.replace(values) | ||
} | ||
|
||
/* query */ | ||
const getPublicKey = async (address: AccAddress) => { | ||
const accountInfo = await lcd.auth.accountInfo(address) | ||
const publicKey = accountInfo.getPublicKey() | ||
if (!publicKey) throw new Error(`Public key is null: ${address}`) | ||
return publicKey | ||
} | ||
|
||
const getPublicKeys = async (addresses: AccAddress[]) => { | ||
const results = await Promise.allSettled(addresses.map(getPublicKey)) | ||
|
||
return results.map((result) => { | ||
if (result.status === "rejected") { | ||
const message = axios.isAxiosError(result.reason) | ||
? getErrorMessage(result.reason) | ||
: result.reason | ||
|
||
throw new Error(message) | ||
} | ||
|
||
return result.value as SimplePublicKey | ||
}) | ||
} | ||
|
||
/* submit */ | ||
const [submitting, setSubmitting] = useState(false) | ||
|
||
const submit = async ({ addresses, threshold }: Values) => { | ||
setSubmitting(true) | ||
|
||
try { | ||
const values = addresses.map(({ value }) => value) | ||
const publicKeys = await getPublicKeys(values) | ||
const publicKey = new LegacyAminoMultisigPublicKey(threshold, publicKeys) | ||
onCreated(publicKey) | ||
} catch (error) { | ||
setError(error as Error) | ||
} | ||
|
||
setSubmitting(false) | ||
} | ||
|
||
/* render */ | ||
const length = fields.length | ||
return ( | ||
<Form onSubmit={handleSubmit(submit)}> | ||
<Grid gap={4}> | ||
<FormWarning> | ||
{t( | ||
"A new multisig wallet is created when the order of addresses or the threshold change" | ||
)} | ||
</FormWarning> | ||
<FormWarning>{t("Participants must have coins")}</FormWarning> | ||
</Grid> | ||
|
||
<FormItem label={t("Address")} extra={<Paste paste={paste} />}> | ||
{fields.map(({ id }, index) => ( | ||
<FormGroup | ||
button={ | ||
length - 1 === index | ||
? { | ||
onClick: () => append({ value: "" }), | ||
children: <AddIcon style={{ fontSize: 18 }} />, | ||
} | ||
: { | ||
onClick: () => remove(index), | ||
children: <RemoveIcon style={{ fontSize: 18 }} />, | ||
} | ||
} | ||
key={id} | ||
> | ||
<FormItem> | ||
<Input | ||
{...register(`addresses.${index}.value`, { | ||
validate: AccAddress.validate, | ||
})} | ||
placeholder={SAMPLE_ADDRESS} | ||
/> | ||
</FormItem> | ||
</FormGroup> | ||
))} | ||
</FormItem> | ||
|
||
<FormItem label={t("Threshold")} error={errors.threshold?.message}> | ||
<Input | ||
{...register("threshold", { | ||
valueAsNumber: true, | ||
validate: validate.index, | ||
})} | ||
placeholder={String(Math.ceil(length / 2))} | ||
/> | ||
</FormItem> | ||
|
||
{error && <FormError>{error.message}</FormError>} | ||
|
||
<Submit submitting={submitting} disabled={!isValid} /> | ||
</Form> | ||
) | ||
} | ||
|
||
export default CreateMultisigWalletForm |
Oops, something went wrong.