Configure eslint for typescript and adapt code
This commit is contained in:
+10
-3
@@ -7,6 +7,7 @@ module.exports = {
|
||||
globals: {
|
||||
process: "readonly",
|
||||
},
|
||||
parser: "@typescript-eslint/parser",
|
||||
parserOptions: {
|
||||
sourceType: "module",
|
||||
ecmaVersion: 2020,
|
||||
@@ -14,9 +15,10 @@ module.exports = {
|
||||
jsx: true,
|
||||
},
|
||||
},
|
||||
plugins: ["prettier"],
|
||||
plugins: ["prettier", "@typescript-eslint"],
|
||||
extends: [
|
||||
"eslint:recommended",
|
||||
"plugin:@typescript-eslint/recommended",
|
||||
"plugin:react/recommended",
|
||||
"prettier",
|
||||
"plugin:prettier/recommended",
|
||||
@@ -27,12 +29,17 @@ module.exports = {
|
||||
"no-console": "off",
|
||||
"no-param-reassign": "warn",
|
||||
"no-shadow": "warn",
|
||||
"no-unused-vars": ["warn", { argsIgnorePattern: "^_", varsIgnorePattern: "^_" }],
|
||||
"no-unused-vars": "off", // Use @typescript-eslint/no-unused-vars instead
|
||||
"prefer-const": "warn",
|
||||
radix: ["warn", "always"],
|
||||
"spaced-comment": ["warn", "always"],
|
||||
"spaced-comment": ["warn", "always", { line: { markers: ["/ <reference"] } }],
|
||||
"react/no-unescaped-entities": ["warn", { forbid: [">", "}"] }], // by default we can't use ' which is annoying
|
||||
"react/prop-types": "off", // we take care of this with TypeScript
|
||||
"@typescript-eslint/no-empty-function": "off",
|
||||
"@typescript-eslint/no-unused-vars": [
|
||||
"warn",
|
||||
{ argsIgnorePattern: "^_", varsIgnorePattern: "^_" },
|
||||
],
|
||||
},
|
||||
overrides: [],
|
||||
};
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
import React from "react";
|
||||
|
||||
interface Props {}
|
||||
|
||||
const DevHelper = (_props: Props) => (
|
||||
const DevHelper = () => (
|
||||
<div className="dev-helper">
|
||||
<h3>Dev Helper</h3>
|
||||
<h4>Pages</h4>
|
||||
|
||||
@@ -8,6 +8,7 @@ import Input from "../inputs/Input";
|
||||
import { useAppContext } from "../../context/AppContext";
|
||||
import Select from "../inputs/Select";
|
||||
import StackableContainer from "../layout/StackableContainer";
|
||||
import { assert } from "@cosmjs/utils";
|
||||
|
||||
interface ChainOption {
|
||||
label: string;
|
||||
@@ -87,7 +88,9 @@ const ChainSelect = () => {
|
||||
return { label: name, value: index };
|
||||
});
|
||||
setChainOptions(options);
|
||||
setSelectValue(findExistingOption(options, state!.chain.registryName!));
|
||||
assert(state.chain.registryName, "registryName missing");
|
||||
setSelectValue(findExistingOption(options, state.chain.registryName));
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
} catch (error: any) {
|
||||
console.log(error);
|
||||
setShowSettings(true);
|
||||
@@ -165,10 +168,11 @@ const ChainSelect = () => {
|
||||
},
|
||||
});
|
||||
setShowSettings(false);
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
} catch (error: any) {
|
||||
console.log(error);
|
||||
setShowSettings(true);
|
||||
setChainError(error.message);
|
||||
setChainError(error.toString());
|
||||
}
|
||||
};
|
||||
|
||||
@@ -198,7 +202,8 @@ const ChainSelect = () => {
|
||||
setChainError(null);
|
||||
try {
|
||||
// test client connection
|
||||
const client = await StargateClient.connect(tempNodeAddress!);
|
||||
assert(tempNodeAddress, "tempNodeAddress missing");
|
||||
const client = await StargateClient.connect(tempNodeAddress);
|
||||
await client.getHeight();
|
||||
|
||||
// change app state
|
||||
@@ -217,9 +222,11 @@ const ChainSelect = () => {
|
||||
explorerLink: tempExplorerLink,
|
||||
},
|
||||
});
|
||||
const selectedOption = findExistingOption(chainOptions, tempRegistryName!);
|
||||
assert(tempRegistryName, "tempRegistryName missing");
|
||||
const selectedOption = findExistingOption(chainOptions, tempRegistryName);
|
||||
setSelectValue(selectedOption);
|
||||
setShowSettings(false);
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
} catch (error: any) {
|
||||
console.log(error);
|
||||
setShowSettings(true);
|
||||
@@ -311,7 +318,7 @@ const ChainSelect = () => {
|
||||
width="48%"
|
||||
value={tempDisplayDenomExponent}
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
|
||||
setDisplayDenomExponent(parseInt(e.target.value))
|
||||
setDisplayDenomExponent(parseInt(e.target.value, 10))
|
||||
}
|
||||
label="Denom Exponent"
|
||||
/>
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import React from "react";
|
||||
import { Account } from "@cosmjs/stargate";
|
||||
import { DbSignature } from "../../types";
|
||||
import StackableContainer from "../layout/StackableContainer";
|
||||
import { AccountWithPubkey } from "../../lib/multisigHelpers";
|
||||
|
||||
interface Props {
|
||||
signatures: DbSignature[];
|
||||
account: Account;
|
||||
account: AccountWithPubkey;
|
||||
}
|
||||
|
||||
const ThresholdInfo = ({ signatures, account }: Props) => (
|
||||
@@ -21,7 +21,7 @@ const ThresholdInfo = ({ signatures, account }: Props) => (
|
||||
<div className="threshold">
|
||||
<div className="current">{signatures.length}</div>
|
||||
<div className="label divider">of</div>
|
||||
<div className="required">{account.pubkey!.value.threshold}</div>
|
||||
<div className="required">{account.pubkey.value.threshold}</div>
|
||||
<div className="label">signatures complete</div>
|
||||
</div>
|
||||
</StackableContainer>
|
||||
|
||||
@@ -6,6 +6,7 @@ import Button from "../inputs/Button";
|
||||
import StackableContainer from "../layout/StackableContainer";
|
||||
import Input from "../inputs/Input";
|
||||
import { exampleAddress } from "../../lib/displayHelpers";
|
||||
import { assert } from "@cosmjs/utils";
|
||||
|
||||
interface Props {
|
||||
router: NextRouter;
|
||||
@@ -22,6 +23,8 @@ const FindMultisigForm = (props: Props) => {
|
||||
props.router.push(`/multi/${address}`);
|
||||
};
|
||||
|
||||
assert(state.chain.addressPrefix, "addressPrefix missing");
|
||||
|
||||
return (
|
||||
<StackableContainer>
|
||||
<StackableContainer lessPadding>
|
||||
@@ -36,7 +39,7 @@ const FindMultisigForm = (props: Props) => {
|
||||
value={address}
|
||||
label="Multisig Address"
|
||||
name="address"
|
||||
placeholder={`E.g. ${exampleAddress(0, state!.chain.addressPrefix!)}`}
|
||||
placeholder={`E.g. ${exampleAddress(0, state.chain.addressPrefix)}`}
|
||||
/>
|
||||
<Button label="Use this Multisig" onClick={handleSearch} primary />
|
||||
</StackableContainer>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import React, { useState } from "react";
|
||||
import { withRouter, NextRouter } from "next/router";
|
||||
import { StargateClient } from "@cosmjs/stargate";
|
||||
import { assert } from "@cosmjs/utils";
|
||||
|
||||
import { useAppContext } from "../../context/AppContext";
|
||||
import Button from "../inputs/Button";
|
||||
@@ -25,7 +26,7 @@ const MultiSigForm = (props: Props) => {
|
||||
const [_processing, setProcessing] = useState(false);
|
||||
|
||||
const handleChangeThreshold = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
let newThreshold = parseInt(e.target.value);
|
||||
let newThreshold = parseInt(e.target.value, 10);
|
||||
if (newThreshold > pubkeys.length || newThreshold <= 0) {
|
||||
newThreshold = threshold;
|
||||
}
|
||||
@@ -57,7 +58,8 @@ const MultiSigForm = (props: Props) => {
|
||||
};
|
||||
|
||||
const getPubkeyFromNode = async (address: string) => {
|
||||
const client = await StargateClient.connect(state!.chain.nodeAddress!);
|
||||
assert(state.chain.nodeAddress, "nodeAddress missing");
|
||||
const client = await StargateClient.connect(state.chain.nodeAddress);
|
||||
const accountOnChain = await client.getAccount(address);
|
||||
console.log(accountOnChain);
|
||||
if (!accountOnChain || !accountOnChain.pubkey) {
|
||||
@@ -90,6 +92,7 @@ const MultiSigForm = (props: Props) => {
|
||||
tempPubkeys[index].compressedPubkey = pubkey;
|
||||
tempPubkeys[index].keyError = "";
|
||||
setPubkeys(tempPubkeys);
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
} catch (error: any) {
|
||||
console.log(error);
|
||||
const tempPubkeys = [...pubkeys];
|
||||
@@ -103,11 +106,13 @@ const MultiSigForm = (props: Props) => {
|
||||
const compressedPubkeys = pubkeys.map((item) => item.compressedPubkey);
|
||||
let multisigAddress;
|
||||
try {
|
||||
assert(state.chain.addressPrefix, "addressPrefix missing");
|
||||
assert(state.chain.chainId, "chainId missing");
|
||||
multisigAddress = await createMultisigFromCompressedSecp256k1Pubkeys(
|
||||
compressedPubkeys,
|
||||
threshold,
|
||||
state!.chain.addressPrefix!,
|
||||
state!.chain.chainId!,
|
||||
state.chain.addressPrefix,
|
||||
state.chain.chainId,
|
||||
);
|
||||
props.router.push(`/multi/${multisigAddress}`);
|
||||
} catch (error) {
|
||||
@@ -127,45 +132,50 @@ const MultiSigForm = (props: Props) => {
|
||||
<StackableContainer lessPadding>
|
||||
<p>Add the addresses that will make up this multisig.</p>
|
||||
</StackableContainer>
|
||||
{pubkeys.map((pubkeyGroup, index) => (
|
||||
<StackableContainer lessPadding lessMargin key={index}>
|
||||
<div className="key-row">
|
||||
{pubkeys.length > 2 && (
|
||||
<button
|
||||
className="remove"
|
||||
onClick={() => {
|
||||
handleRemove(index);
|
||||
}}
|
||||
>
|
||||
✕
|
||||
</button>
|
||||
)}
|
||||
<div className="key-inputs">
|
||||
<Input
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
handleKeyGroupChange(index, e);
|
||||
}}
|
||||
value={pubkeyGroup.isPubkey ? pubkeyGroup.compressedPubkey : pubkeyGroup.address}
|
||||
label={pubkeyGroup.isPubkey ? "Public Key (Secp256k1)" : "Address"}
|
||||
name={pubkeyGroup.isPubkey ? "compressedPubkey" : "address"}
|
||||
width="100%"
|
||||
placeholder={`E.g. ${
|
||||
pubkeyGroup.isPubkey
|
||||
? examplePubkey(index)
|
||||
: exampleAddress(index, state!.chain.addressPrefix!)
|
||||
}`}
|
||||
error={pubkeyGroup.keyError}
|
||||
onBlur={(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
handleKeyBlur(index, e);
|
||||
}}
|
||||
/>
|
||||
<button className="toggle-type" onClick={() => togglePubkey(index)}>
|
||||
Use {pubkeyGroup.isPubkey ? "Address" : "Public Key"}
|
||||
</button>
|
||||
{pubkeys.map((pubkeyGroup, index) => {
|
||||
assert(state.chain.addressPrefix, "addressPrefix missing");
|
||||
return (
|
||||
<StackableContainer lessPadding lessMargin key={index}>
|
||||
<div className="key-row">
|
||||
{pubkeys.length > 2 && (
|
||||
<button
|
||||
className="remove"
|
||||
onClick={() => {
|
||||
handleRemove(index);
|
||||
}}
|
||||
>
|
||||
✕
|
||||
</button>
|
||||
)}
|
||||
<div className="key-inputs">
|
||||
<Input
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
handleKeyGroupChange(index, e);
|
||||
}}
|
||||
value={
|
||||
pubkeyGroup.isPubkey ? pubkeyGroup.compressedPubkey : pubkeyGroup.address
|
||||
}
|
||||
label={pubkeyGroup.isPubkey ? "Public Key (Secp256k1)" : "Address"}
|
||||
name={pubkeyGroup.isPubkey ? "compressedPubkey" : "address"}
|
||||
width="100%"
|
||||
placeholder={`E.g. ${
|
||||
pubkeyGroup.isPubkey
|
||||
? examplePubkey(index)
|
||||
: exampleAddress(index, state.chain.addressPrefix)
|
||||
}`}
|
||||
error={pubkeyGroup.keyError}
|
||||
onBlur={(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
handleKeyBlur(index, e);
|
||||
}}
|
||||
/>
|
||||
<button className="toggle-type" onClick={() => togglePubkey(index)}>
|
||||
Use {pubkeyGroup.isPubkey ? "Address" : "Public Key"}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</StackableContainer>
|
||||
))}
|
||||
</StackableContainer>
|
||||
);
|
||||
})}
|
||||
|
||||
<Button label="Add another address" onClick={() => handleAddKey()} />
|
||||
</StackableContainer>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import axios from "axios";
|
||||
import { calculateFee, Account } from "@cosmjs/stargate";
|
||||
import { calculateFee } from "@cosmjs/stargate";
|
||||
import { Decimal } from "@cosmjs/math";
|
||||
import { assert } from "@cosmjs/utils";
|
||||
import React, { useState } from "react";
|
||||
import { withRouter, NextRouter } from "next/router";
|
||||
|
||||
@@ -9,10 +10,11 @@ import Button from "../inputs/Button";
|
||||
import Input from "../inputs/Input";
|
||||
import StackableContainer from "../layout/StackableContainer";
|
||||
import { checkAddress, exampleAddress } from "../../lib/displayHelpers";
|
||||
import { AccountWithPubkey } from "../../lib/multisigHelpers";
|
||||
|
||||
interface Props {
|
||||
address: string | null;
|
||||
accountOnChain: Account | null;
|
||||
accountOnChain: AccountWithPubkey | null;
|
||||
router: NextRouter;
|
||||
closeForm: () => void;
|
||||
}
|
||||
@@ -23,14 +25,14 @@ const TransactionForm = (props: Props) => {
|
||||
const [amount, setAmount] = useState("0");
|
||||
const [memo, setMemo] = useState("");
|
||||
const [gas, setGas] = useState(200000);
|
||||
const [gasPrice, _setGasPrice] = useState(state!.chain.gasPrice);
|
||||
const [gasPrice, _setGasPrice] = useState(state.chain.gasPrice);
|
||||
const [_processing, setProcessing] = useState(false);
|
||||
const [addressError, setAddressError] = useState("");
|
||||
|
||||
const createTransaction = (txToAddress: string, txAmount: string, txGas: number) => {
|
||||
const amountInAtomics = Decimal.fromUserInput(
|
||||
txAmount,
|
||||
Number(state!.chain.displayDenomExponent),
|
||||
Number(state.chain.displayDenomExponent),
|
||||
).atomics;
|
||||
const msgSend = {
|
||||
fromAddress: props.address,
|
||||
@@ -38,7 +40,7 @@ const TransactionForm = (props: Props) => {
|
||||
amount: [
|
||||
{
|
||||
amount: amountInAtomics,
|
||||
denom: state!.chain.denom,
|
||||
denom: state.chain.denom,
|
||||
},
|
||||
],
|
||||
};
|
||||
@@ -46,11 +48,14 @@ const TransactionForm = (props: Props) => {
|
||||
typeUrl: "/cosmos.bank.v1beta1.MsgSend",
|
||||
value: msgSend,
|
||||
};
|
||||
const fee = calculateFee(Number(txGas), gasPrice!);
|
||||
assert(gasPrice, "gasPrice missing");
|
||||
const fee = calculateFee(Number(txGas), gasPrice);
|
||||
const { accountOnChain } = props;
|
||||
assert(accountOnChain, "accountOnChain missing");
|
||||
return {
|
||||
accountNumber: props.accountOnChain!.accountNumber,
|
||||
sequence: props.accountOnChain!.sequence,
|
||||
chainId: state!.chain.chainId,
|
||||
accountNumber: accountOnChain.accountNumber,
|
||||
sequence: accountOnChain.sequence,
|
||||
chainId: state.chain.chainId,
|
||||
msgs: [msg],
|
||||
fee: fee,
|
||||
memo: memo,
|
||||
@@ -58,9 +63,10 @@ const TransactionForm = (props: Props) => {
|
||||
};
|
||||
|
||||
const handleCreate = async () => {
|
||||
const toAddressError = checkAddress(toAddress, state!.chain.addressPrefix!);
|
||||
assert(state.chain.addressPrefix, "addressPrefix missing");
|
||||
const toAddressError = checkAddress(toAddress, state.chain.addressPrefix);
|
||||
if (toAddressError) {
|
||||
setAddressError(`Invalid address for network ${state!.chain.chainId}: ${toAddressError}`);
|
||||
setAddressError(`Invalid address for network ${state.chain.chainId}: ${toAddressError}`);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -73,6 +79,8 @@ const TransactionForm = (props: Props) => {
|
||||
props.router.push(`${props.address}/transaction/${transactionID}`);
|
||||
};
|
||||
|
||||
assert(state.chain.addressPrefix, "addressPrefix missing");
|
||||
|
||||
return (
|
||||
<StackableContainer lessPadding>
|
||||
<button className="remove" onClick={() => props.closeForm()}>
|
||||
@@ -86,12 +94,12 @@ const TransactionForm = (props: Props) => {
|
||||
value={toAddress}
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => setToAddress(e.target.value)}
|
||||
error={addressError}
|
||||
placeholder={`E.g. ${exampleAddress(0, state!.chain.addressPrefix!)}`}
|
||||
placeholder={`E.g. ${exampleAddress(0, state.chain.addressPrefix)}`}
|
||||
/>
|
||||
</div>
|
||||
<div className="form-item">
|
||||
<Input
|
||||
label={`Amount (${state!.chain.displayDenom})`}
|
||||
label={`Amount (${state.chain.displayDenom})`}
|
||||
name="amount"
|
||||
type="number"
|
||||
value={amount}
|
||||
@@ -104,7 +112,9 @@ const TransactionForm = (props: Props) => {
|
||||
name="gas"
|
||||
type="number"
|
||||
value={gas}
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => setGas(parseInt(e.target.value))}
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
|
||||
setGas(parseInt(e.target.value, 10))
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<div className="form-item">
|
||||
|
||||
@@ -31,8 +31,9 @@ const TransactionSigning = (props: Props) => {
|
||||
|
||||
const connectKeplr = async () => {
|
||||
try {
|
||||
await window.keplr.enable(state!.chain.chainId!);
|
||||
const tempWalletAccount = await window.keplr.getKey(state!.chain.chainId!);
|
||||
assert(state.chain.chainId, "chainId missing");
|
||||
await window.keplr.enable(state.chain.chainId);
|
||||
const tempWalletAccount = await window.keplr.getKey(state.chain.chainId);
|
||||
console.log(tempWalletAccount);
|
||||
const tempHasSigned = props.signatures.some(
|
||||
(sig) => sig.address === tempWalletAccount.bech32Address,
|
||||
@@ -46,13 +47,15 @@ const TransactionSigning = (props: Props) => {
|
||||
};
|
||||
|
||||
const connectLedger = async () => {
|
||||
assert(state.chain.addressPrefix, "addressPrefix missing");
|
||||
|
||||
// Prepare ledger
|
||||
const ledgerTransport = await TransportWebUSB.create(120000, 120000);
|
||||
|
||||
// Setup signer
|
||||
const offlineSigner = new LedgerSigner(ledgerTransport, {
|
||||
hdPaths: [makeCosmoshubPath(0)],
|
||||
prefix: state!.chain.addressPrefix,
|
||||
prefix: state.chain.addressPrefix,
|
||||
});
|
||||
console.log(offlineSigner);
|
||||
const accounts = await offlineSigner.getAccounts();
|
||||
@@ -73,10 +76,10 @@ const TransactionSigning = (props: Props) => {
|
||||
};
|
||||
|
||||
const signTransaction = async () => {
|
||||
assert(state.chain.chainId, "chainId missing");
|
||||
|
||||
const offlineSigner =
|
||||
walletType === "keplr"
|
||||
? window.getOfflineSignerOnlyAmino(state!.chain.chainId)
|
||||
: ledgerSigner;
|
||||
walletType === "keplr" ? window.getOfflineSignerOnlyAmino(state.chain.chainId) : ledgerSigner;
|
||||
|
||||
const signerAddress = walletAccount?.bech32Address;
|
||||
assert(signerAddress, "Missing signer address");
|
||||
@@ -85,7 +88,7 @@ const TransactionSigning = (props: Props) => {
|
||||
const signerData = {
|
||||
accountNumber: props.tx.accountNumber,
|
||||
sequence: props.tx.sequence,
|
||||
chainId: state!.chain.chainId as string,
|
||||
chainId: state.chain.chainId,
|
||||
};
|
||||
|
||||
const { bodyBytes, signatures } = await signingClient.sign(
|
||||
|
||||
@@ -5,6 +5,7 @@ interface Props {
|
||||
disabled?: boolean;
|
||||
href?: string;
|
||||
label: string;
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
onClick?: () => any;
|
||||
}
|
||||
const Button = (props: Props) => (
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import React, { CSSProperties } from "react";
|
||||
import Select, { ControlProps } from "react-select";
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const StyledSelect = (props: any) => {
|
||||
const customStyles = {
|
||||
control: (provided: CSSProperties, state: ControlProps) => ({
|
||||
@@ -16,6 +17,7 @@ const StyledSelect = (props: any) => {
|
||||
borderColor: "rgba(255, 255, 255, 1)",
|
||||
},
|
||||
}),
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
option: (provided: CSSProperties, state: any) => ({
|
||||
...provided,
|
||||
background: state.isSelected ? "rgba(255, 255, 255, 0.2)" : "none",
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Bech32, fromBase64, toBase64 } from "@cosmjs/encoding";
|
||||
import { fromBase64, fromBech32, toBase64, toBech32 } from "@cosmjs/encoding";
|
||||
import { sha512 } from "@cosmjs/crypto";
|
||||
import { Decimal } from "@cosmjs/math";
|
||||
import { Coin } from "@cosmjs/amino";
|
||||
@@ -73,11 +73,11 @@ const printableCoins = (coins: Coin[], chainInfo: ChainInfo) => {
|
||||
*/
|
||||
const exampleAddress = (index: number, chainAddressPrefix: string) => {
|
||||
const usedIndex = index || 0;
|
||||
let data = Bech32.decode("cosmos1vqpjljwsynsn58dugz0w8ut7kun7t8ls2qkmsq").data;
|
||||
let data = fromBech32("cosmos1vqpjljwsynsn58dugz0w8ut7kun7t8ls2qkmsq").data;
|
||||
for (let i = 0; i < usedIndex; ++i) {
|
||||
data = sha512(data).slice(0, data.length); // hash one time and trim to original length
|
||||
}
|
||||
return Bech32.encode(chainAddressPrefix, data);
|
||||
return toBech32(chainAddressPrefix, data);
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -109,7 +109,8 @@ const checkAddress = (input: string, chainAddressPrefix: string) => {
|
||||
let data;
|
||||
let prefix;
|
||||
try {
|
||||
({ data, prefix } = Bech32.decode(input));
|
||||
({ data, prefix } = fromBech32(input));
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
} catch (error: any) {
|
||||
return error.toString();
|
||||
}
|
||||
|
||||
+16
-14
@@ -1,5 +1,5 @@
|
||||
import axios from "axios";
|
||||
import { createMultisigThresholdPubkey, pubkeyToAddress } from "@cosmjs/amino";
|
||||
import { createMultisigThresholdPubkey, Pubkey, pubkeyToAddress } from "@cosmjs/amino";
|
||||
import { Account } from "@cosmjs/stargate";
|
||||
import { StargateClient } from "@cosmjs/stargate";
|
||||
|
||||
@@ -40,6 +40,9 @@ const createMultisigFromCompressedSecp256k1Pubkeys = async (
|
||||
return res.data.address;
|
||||
};
|
||||
|
||||
/** Like Account but with non-optional pubkey */
|
||||
export type AccountWithPubkey = Account & { readonly pubkey: Pubkey };
|
||||
|
||||
/**
|
||||
* This gets a multisigs account (pubkey, sequence, account number, etc) from
|
||||
* a node and/or the api if the multisig was made on this app
|
||||
@@ -51,33 +54,32 @@ const createMultisigFromCompressedSecp256k1Pubkeys = async (
|
||||
const getMultisigAccount = async (
|
||||
address: string,
|
||||
client: StargateClient,
|
||||
): Promise<Account | null> => {
|
||||
): Promise<AccountWithPubkey | null> => {
|
||||
// we need the multisig pubkeys to create transactions, if the multisig
|
||||
// is new, and has never submitted a transaction its pubkeys will not be
|
||||
// available from a node. If the multisig was created with this instance
|
||||
// of this tool its pubkey will be available in the fauna datastore
|
||||
let accountOnChain: Mutable<Account> | null = await client.getAccount(address);
|
||||
const accountOnChain = await client.getAccount(address);
|
||||
const chainId = await client.getChainId();
|
||||
if (!accountOnChain) return null;
|
||||
|
||||
if (!accountOnChain || !accountOnChain.pubkey) {
|
||||
let pubkey: Pubkey;
|
||||
if (accountOnChain.pubkey) {
|
||||
pubkey = accountOnChain.pubkey;
|
||||
} else {
|
||||
console.log("No pubkey on chain for: ", address);
|
||||
const res = await axios.get(`/api/chain/${chainId}/multisig/${address}`);
|
||||
|
||||
if (res.status !== 200) {
|
||||
throw new Error("Multisig has no pubkey on node, and was not created using this tool.");
|
||||
}
|
||||
const pubkey = JSON.parse(res.data.pubkeyJSON);
|
||||
|
||||
if (!accountOnChain) {
|
||||
accountOnChain = null;
|
||||
}
|
||||
accountOnChain!.pubkey = pubkey;
|
||||
pubkey = JSON.parse(res.data.pubkeyJSON);
|
||||
}
|
||||
return accountOnChain;
|
||||
};
|
||||
|
||||
type Mutable<Type> = {
|
||||
-readonly [Key in keyof Type]: Type[Key];
|
||||
return {
|
||||
...accountOnChain,
|
||||
pubkey: pubkey,
|
||||
};
|
||||
};
|
||||
|
||||
export { createMultisigFromCompressedSecp256k1Pubkeys, getMultisigAccount };
|
||||
|
||||
Generated
+923
-124
File diff suppressed because it is too large
Load Diff
+5
-2
@@ -4,8 +4,8 @@
|
||||
"dev": "next dev",
|
||||
"build": "next build",
|
||||
"start": "next start",
|
||||
"lint": "eslint --max-warnings 0 \"./**/*.js\"",
|
||||
"lint:fix": "eslint --max-warnings 0 \"./**/*.js\" --fix"
|
||||
"lint": "eslint --max-warnings 0 \"./**/*.{js,jsx,ts,tsx}\"",
|
||||
"lint:fix": "eslint --max-warnings 0 \"./**/*.{js,jsx,ts,tsx}\" --fix"
|
||||
},
|
||||
"dependencies": {
|
||||
"@cosmjs/amino": "^0.28.4",
|
||||
@@ -15,6 +15,7 @@
|
||||
"@cosmjs/math": "^0.28.4",
|
||||
"@cosmjs/proto-signing": "^0.28.4",
|
||||
"@cosmjs/stargate": "^0.28.4",
|
||||
"@cosmjs/utils": "^0.28.4",
|
||||
"@keplr-wallet/types": "^0.9.0-alpha.4",
|
||||
"@ledgerhq/hw-transport-webusb": "^6.24.1",
|
||||
"axios": "^0.21.1",
|
||||
@@ -31,6 +32,8 @@
|
||||
"@types/react": "^18.0.9",
|
||||
"@types/react-dom": "^18.0.5",
|
||||
"@types/react-select": "^5.0.1",
|
||||
"@typescript-eslint/eslint-plugin": "^5.27.1",
|
||||
"@typescript-eslint/parser": "^5.27.1",
|
||||
"cosmjs-types": "^0.4.1",
|
||||
"eslint": "^8.6.0",
|
||||
"eslint-config-prettier": "^8.3.0",
|
||||
|
||||
+2
-1
@@ -4,9 +4,10 @@ import ChainSelect from "../components/chainSelect/ChainSelect";
|
||||
import type { AppProps } from "next/app";
|
||||
|
||||
function MultisigApp({ Component, pageProps }: AppProps) {
|
||||
const showChainSelect = process.env.NEXT_PUBLIC_MULTICHAIN?.toLowerCase() === "true";
|
||||
return (
|
||||
<AppWrapper>
|
||||
{process.env.NEXT_PUBLIC_MULTICHAIN!.toLowerCase() === "true" && <ChainSelect />}
|
||||
{showChainSelect && <ChainSelect />}
|
||||
<Component {...pageProps} />
|
||||
</AppWrapper>
|
||||
);
|
||||
|
||||
@@ -16,6 +16,7 @@ export default async function (req: NextApiRequest, res: NextApiResponse) {
|
||||
console.log("success", getRes.data.data.getMultisig);
|
||||
res.status(200).send(getRes.data.data.getMultisig);
|
||||
return;
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
} catch (err: any) {
|
||||
console.log(err);
|
||||
res.status(400).send(err.message);
|
||||
|
||||
@@ -11,6 +11,7 @@ export default async function (req: NextApiRequest, res: NextApiResponse) {
|
||||
console.log("success", saveRes.data);
|
||||
res.status(200).send(saveRes.data.data.createMultisig);
|
||||
return;
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
} catch (err: any) {
|
||||
console.log(err);
|
||||
res.status(400).send(err.message);
|
||||
|
||||
@@ -12,6 +12,7 @@ export default async function (req: NextApiRequest, res: NextApiResponse) {
|
||||
console.log("success", saveRes.data);
|
||||
res.status(200).send(saveRes.data.data.updateTransaction);
|
||||
return;
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
} catch (err: any) {
|
||||
console.log(err);
|
||||
res.status(400).send(err.message);
|
||||
|
||||
@@ -12,6 +12,7 @@ export default async function (req: NextApiRequest, res: NextApiResponse) {
|
||||
console.log("success", saveRes.data);
|
||||
res.status(200).send(saveRes.data.data.createSignature);
|
||||
return;
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
} catch (err: any) {
|
||||
console.log(err);
|
||||
res.status(400).send(err.message);
|
||||
|
||||
@@ -11,6 +11,7 @@ export default async function (req: NextApiRequest, res: NextApiResponse) {
|
||||
console.log("success", saveRes.data);
|
||||
res.status(200).send({ transactionID: saveRes.data.data.createTransaction._id });
|
||||
return;
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
} catch (err: any) {
|
||||
console.log(err);
|
||||
res.status(400).send(err.message);
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import React, { useState, useEffect } from "react";
|
||||
import { pubkeyToAddress, Pubkey } from "@cosmjs/amino";
|
||||
import { Account } from "@cosmjs/stargate";
|
||||
import { StargateClient } from "@cosmjs/stargate";
|
||||
import { assert } from "@cosmjs/utils";
|
||||
import { Coin } from "cosmjs-types/cosmos/base/v1beta1/coin";
|
||||
import { useRouter } from "next/router";
|
||||
|
||||
import { useAppContext } from "../../../context/AppContext";
|
||||
import Button from "../../../components/inputs/Button";
|
||||
import { getMultisigAccount } from "../../../lib/multisigHelpers";
|
||||
import { AccountWithPubkey, getMultisigAccount } from "../../../lib/multisigHelpers";
|
||||
import HashView from "../../../components/dataViews/HashView";
|
||||
import MultisigHoldings from "../../../components/dataViews/MultisigHoldings";
|
||||
import MultisigMembers from "../../../components/dataViews/MultisigMembers";
|
||||
@@ -25,14 +25,12 @@ function participantAddressesFromMultisig(multisigPubkey: Pubkey, addressPrefix:
|
||||
);
|
||||
}
|
||||
|
||||
interface Props {}
|
||||
|
||||
const multipage = (_props: Props) => {
|
||||
const multipage = () => {
|
||||
const { state } = useAppContext();
|
||||
const [showTxForm, setShowTxForm] = useState(false);
|
||||
const [holdings, setHoldings] = useState<Coin | null>(null);
|
||||
const [multisigAddress, setMultisigAddress] = useState("");
|
||||
const [accountOnChain, setAccountOnChain] = useState<Account | null>(null);
|
||||
const [accountOnChain, setAccountOnChain] = useState<AccountWithPubkey | null>(null);
|
||||
const [accountError, setAccountError] = useState(null);
|
||||
const router = useRouter();
|
||||
|
||||
@@ -47,17 +45,22 @@ const multipage = (_props: Props) => {
|
||||
const fetchMultisig = async (address: string) => {
|
||||
setAccountError(null);
|
||||
try {
|
||||
const client = await StargateClient.connect(state!.chain.nodeAddress!);
|
||||
const tempHoldings = await client.getBalance(address, state!.chain.denom!);
|
||||
assert(state.chain.nodeAddress, "Node address missing");
|
||||
const client = await StargateClient.connect(state.chain.nodeAddress);
|
||||
assert(state.chain.denom, "denom missing");
|
||||
const tempHoldings = await client.getBalance(address, state.chain.denom);
|
||||
const tempAccountOnChain = await getMultisigAccount(address, client);
|
||||
setHoldings(tempHoldings);
|
||||
setAccountOnChain(tempAccountOnChain);
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
} catch (error: any) {
|
||||
setAccountError(error.message);
|
||||
console.log("Account error:", error);
|
||||
}
|
||||
};
|
||||
|
||||
assert(state.chain.addressPrefix, "address prefix missing");
|
||||
|
||||
return (
|
||||
<Page>
|
||||
<StackableContainer base>
|
||||
@@ -71,13 +74,13 @@ const multipage = (_props: Props) => {
|
||||
)}
|
||||
</h1>
|
||||
</StackableContainer>
|
||||
{accountOnChain?.pubkey && (
|
||||
{accountOnChain && (
|
||||
<MultisigMembers
|
||||
members={participantAddressesFromMultisig(
|
||||
accountOnChain?.pubkey,
|
||||
state!.chain.addressPrefix!,
|
||||
accountOnChain.pubkey,
|
||||
state.chain.addressPrefix,
|
||||
)}
|
||||
threshold={accountOnChain?.pubkey.value.threshold}
|
||||
threshold={accountOnChain.pubkey.value.threshold}
|
||||
/>
|
||||
)}
|
||||
{accountError && (
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import axios from "axios";
|
||||
import React from "react";
|
||||
import { GetServerSideProps } from "next";
|
||||
import { StargateClient, makeMultisignedTx, Account } from "@cosmjs/stargate";
|
||||
import { StargateClient, makeMultisignedTx } from "@cosmjs/stargate";
|
||||
import { Coin } from "cosmjs-types/cosmos/base/v1beta1/coin";
|
||||
import { TxRaw } from "cosmjs-types/cosmos/tx/v1beta1/tx";
|
||||
import { useState, useEffect } from "react";
|
||||
@@ -13,13 +13,14 @@ import { DbSignature, DbTransaction } from "../../../../types";
|
||||
import { useAppContext } from "../../../../context/AppContext";
|
||||
import Button from "../../../../components/inputs/Button";
|
||||
import { findTransactionByID } from "../../../../lib/graphqlHelpers";
|
||||
import { getMultisigAccount } from "../../../../lib/multisigHelpers";
|
||||
import { AccountWithPubkey, getMultisigAccount } from "../../../../lib/multisigHelpers";
|
||||
import Page from "../../../../components/layout/Page";
|
||||
import StackableContainer from "../../../../components/layout/StackableContainer";
|
||||
import ThresholdInfo from "../../../../components/dataViews/ThresholdInfo";
|
||||
import TransactionInfo from "../../../../components/dataViews/TransactionInfo";
|
||||
import TransactionSigning from "../../../../components/forms/TransactionSigning";
|
||||
import CompletedTransaction from "../../../../components/dataViews/CompletedTransaction";
|
||||
import { assert } from "@cosmjs/utils";
|
||||
|
||||
interface Props {
|
||||
props: {
|
||||
@@ -32,7 +33,8 @@ interface Props {
|
||||
|
||||
export const getServerSideProps: GetServerSideProps = async (context): Promise<Props> => {
|
||||
// get transaction info
|
||||
const transactionID = context.params!.transactionID!.toString();
|
||||
const transactionID = context.params?.transactionID?.toString();
|
||||
assert(transactionID, "Transaction ID missing");
|
||||
let transactionJSON;
|
||||
let txHash;
|
||||
let signatures;
|
||||
@@ -43,7 +45,7 @@ export const getServerSideProps: GetServerSideProps = async (context): Promise<P
|
||||
txHash = getRes.data.data.findTransactionByID.txHash;
|
||||
transactionJSON = getRes.data.data.findTransactionByID.dataJSON;
|
||||
signatures = getRes.data.data.findTransactionByID.signatures.data || [];
|
||||
} catch (err: any) {
|
||||
} catch (err: unknown) {
|
||||
console.log(err);
|
||||
}
|
||||
return {
|
||||
@@ -75,7 +77,7 @@ const transactionPage = ({
|
||||
const [isBroadcasting, setIsBroadcasting] = useState(false);
|
||||
const [transactionHash, setTransactionHash] = useState(txHash);
|
||||
const [_holdings, setHoldings] = useState<Coin | null>(null);
|
||||
const [accountOnChain, setAccountOnChain] = useState<Account | null>(null);
|
||||
const [accountOnChain, setAccountOnChain] = useState<AccountWithPubkey | null>(null);
|
||||
const [accountError, setAccountError] = useState(null);
|
||||
const txInfo: DbTransaction = (transactionJSON && JSON.parse(transactionJSON)) || null;
|
||||
const router = useRouter();
|
||||
@@ -93,13 +95,16 @@ const transactionPage = ({
|
||||
|
||||
const fetchMultisig = async (address: string) => {
|
||||
try {
|
||||
const client = await StargateClient.connect(state!.chain.nodeAddress!);
|
||||
const tempHoldings = await client.getBalance(address, state!.chain.denom!);
|
||||
assert(state.chain.nodeAddress, "Node address missing");
|
||||
const client = await StargateClient.connect(state.chain.nodeAddress);
|
||||
assert(state.chain.denom, "denom missing");
|
||||
const tempHoldings = await client.getBalance(address, state.chain.denom);
|
||||
const tempAccountOnChain = await getMultisigAccount(address, client);
|
||||
setHoldings(tempHoldings);
|
||||
setAccountOnChain(tempAccountOnChain);
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
} catch (error: any) {
|
||||
setAccountError(error.message);
|
||||
setAccountError(error.toString());
|
||||
console.log("Account error:", error);
|
||||
}
|
||||
};
|
||||
@@ -109,15 +114,18 @@ const transactionPage = ({
|
||||
setIsBroadcasting(true);
|
||||
setBroadcastError("");
|
||||
|
||||
if (!accountOnChain) throw new Error("Account on chain value missing.");
|
||||
|
||||
const bodyBytes = fromBase64(currentSignatures[0].bodyBytes);
|
||||
const signedTx = makeMultisignedTx(
|
||||
accountOnChain!.pubkey as MultisigThresholdPubkey,
|
||||
accountOnChain.pubkey as MultisigThresholdPubkey,
|
||||
txInfo.sequence,
|
||||
txInfo.fee,
|
||||
bodyBytes,
|
||||
new Map(currentSignatures.map((s) => [s.address, fromBase64(s.signature)])),
|
||||
);
|
||||
const broadcaster = await StargateClient.connect(state!.chain.nodeAddress!);
|
||||
assert(state.chain.nodeAddress, "Node address missing");
|
||||
const broadcaster = await StargateClient.connect(state.chain.nodeAddress);
|
||||
const result = await broadcaster.broadcastTx(
|
||||
Uint8Array.from(TxRaw.encode(signedTx).finish()),
|
||||
);
|
||||
@@ -126,9 +134,10 @@ const transactionPage = ({
|
||||
txHash: result.transactionHash,
|
||||
});
|
||||
setTransactionHash(result.transactionHash);
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
} catch (e: any) {
|
||||
setIsBroadcasting(false);
|
||||
setBroadcastError(e.message);
|
||||
setBroadcastError(e.toString());
|
||||
}
|
||||
};
|
||||
|
||||
@@ -151,7 +160,7 @@ const transactionPage = ({
|
||||
<ThresholdInfo signatures={currentSignatures} account={accountOnChain} />
|
||||
)}
|
||||
{accountOnChain &&
|
||||
currentSignatures.length >= parseInt(accountOnChain!.pubkey!.value.threshold, 10) &&
|
||||
currentSignatures.length >= parseInt(accountOnChain.pubkey.value.threshold, 10) &&
|
||||
!transactionHash && (
|
||||
<>
|
||||
<Button
|
||||
|
||||
@@ -4,9 +4,11 @@ import { EncodeObject } from "@cosmjs/proto-signing";
|
||||
declare global {
|
||||
interface Window {
|
||||
keplr: {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
enable: (chainId: string) => any;
|
||||
getKey: (chainId: string) => Promise<WalletAccount>;
|
||||
};
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
getOfflineSignerOnlyAmino: any;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user