Merge pull request #61 from cosmos/samepant/typescript
Typescript conversion and Nextjs version upgrade
This commit is contained in:
@@ -1,6 +1,8 @@
|
||||
import React from "react";
|
||||
|
||||
const DevHelper = (_props) => (
|
||||
interface Props {}
|
||||
|
||||
const DevHelper = (_props: Props) => (
|
||||
<div className="dev-helper">
|
||||
<h3>Dev Helper</h3>
|
||||
<h4>Pages</h4>
|
||||
@@ -4,18 +4,40 @@ import { StargateClient } from "@cosmjs/stargate";
|
||||
|
||||
import GearIcon from "../icons/Gear";
|
||||
import Button from "../inputs/Button";
|
||||
import Input from "../../components/inputs/Input";
|
||||
import Input from "../inputs/Input";
|
||||
import { useAppContext } from "../../context/AppContext";
|
||||
import Select from "../inputs/Select";
|
||||
import StackableContainer from "../layout/StackableContainer";
|
||||
|
||||
interface ChainOption {
|
||||
label: string;
|
||||
value: number;
|
||||
}
|
||||
|
||||
interface GithubChainRegistryItem {
|
||||
name: string;
|
||||
path: string;
|
||||
sha: string;
|
||||
size: number;
|
||||
url: string;
|
||||
html_url: string;
|
||||
git_url: string;
|
||||
download_url: string | null;
|
||||
type: string;
|
||||
_links: {
|
||||
self: string;
|
||||
git: string;
|
||||
html: string;
|
||||
};
|
||||
}
|
||||
|
||||
const ChainSelect = () => {
|
||||
const { state, dispatch } = useAppContext();
|
||||
|
||||
// UI State
|
||||
const [chainArray, setChainArray] = useState();
|
||||
const [chainOptions, setChainOptions] = useState();
|
||||
const [chainError, setChainError] = useState();
|
||||
const [chainArray, setChainArray] = useState([]);
|
||||
const [chainOptions, setChainOptions] = useState<ChainOption[]>([]);
|
||||
const [chainError, setChainError] = useState<string | null>(null);
|
||||
const [showSettings, setShowSettings] = useState(false);
|
||||
const [selectValue, setSelectValue] = useState({ label: "Loading...", value: -1 });
|
||||
|
||||
@@ -57,23 +79,23 @@ const ChainSelect = () => {
|
||||
// getting chain info from this repo: https://github.com/cosmos/chain-registry
|
||||
try {
|
||||
const res = await axios.get(url);
|
||||
const chains = res.data.filter((item) => {
|
||||
const chains = res.data.filter((item: GithubChainRegistryItem) => {
|
||||
return item.type == "dir" && !item.name.startsWith(".") && item.name != "testnets";
|
||||
});
|
||||
setChainArray(chains);
|
||||
const options = chains.map(({ name }, index) => {
|
||||
const options = chains.map(({ name }: GithubChainRegistryItem, index: number) => {
|
||||
return { label: name, value: index };
|
||||
});
|
||||
setChainOptions(options);
|
||||
setSelectValue(findExistingOption(options, state.chain.registryName));
|
||||
} catch (error) {
|
||||
setSelectValue(findExistingOption(options, state!.chain.registryName!));
|
||||
} catch (error: any) {
|
||||
console.log(error);
|
||||
setShowSettings(true);
|
||||
setChainError(error.message);
|
||||
}
|
||||
};
|
||||
|
||||
const findExistingOption = (options, registryName) => {
|
||||
const findExistingOption = (options: ChainOption[], registryName: string) => {
|
||||
const index = options.findIndex((option) => option.label === registryName);
|
||||
if (index >= 0) {
|
||||
return options[index];
|
||||
@@ -81,7 +103,7 @@ const ChainSelect = () => {
|
||||
return { label: "unkown chain", value: -1 };
|
||||
};
|
||||
|
||||
const getChainInfo = async (chainOption) => {
|
||||
const getChainInfo = async (chainOption: GithubChainRegistryItem) => {
|
||||
setChainError(null);
|
||||
try {
|
||||
const chainInfoUrl =
|
||||
@@ -102,7 +124,7 @@ const ChainSelect = () => {
|
||||
const chainDisplayName = chainData["pretty_name"];
|
||||
const registryName = chainOption.name;
|
||||
const explorerLink = getExplorerFromArray(chainData.explorers);
|
||||
let asset = "";
|
||||
let asset = null;
|
||||
let denom = "";
|
||||
let displayDenom = "";
|
||||
const displayDenomExponent = 6;
|
||||
@@ -143,21 +165,21 @@ const ChainSelect = () => {
|
||||
},
|
||||
});
|
||||
setShowSettings(false);
|
||||
} catch (error) {
|
||||
} catch (error: any) {
|
||||
console.log(error);
|
||||
setShowSettings(true);
|
||||
setChainError(error.message);
|
||||
}
|
||||
};
|
||||
|
||||
const getExplorerFromArray = (array) => {
|
||||
const getExplorerFromArray = (array: { kind: string; url: string; tx_page: string }[]) => {
|
||||
if (array && array.length > 0) {
|
||||
return array[0]["tx_page"];
|
||||
}
|
||||
return "";
|
||||
};
|
||||
|
||||
const getNodeFromArray = (nodeArray) => {
|
||||
const getNodeFromArray = (nodeArray: { address: string; provider: string }[]) => {
|
||||
// only return https connections
|
||||
const secureNodes = nodeArray.filter((node) => node.address.includes("https://"));
|
||||
if (secureNodes.length === 0) {
|
||||
@@ -166,7 +188,7 @@ const ChainSelect = () => {
|
||||
return secureNodes[0].address;
|
||||
};
|
||||
|
||||
const onChainSelect = (option) => {
|
||||
const onChainSelect = (option: ChainOption) => {
|
||||
const index = chainOptions.findIndex((opt) => opt.label === option.label);
|
||||
setSelectValue(chainOptions[index]);
|
||||
getChainInfo(chainArray[option.value]);
|
||||
@@ -176,7 +198,7 @@ const ChainSelect = () => {
|
||||
setChainError(null);
|
||||
try {
|
||||
// test client connection
|
||||
const client = await StargateClient.connect(tempNodeAddress);
|
||||
const client = await StargateClient.connect(tempNodeAddress!);
|
||||
await client.getHeight();
|
||||
|
||||
// change app state
|
||||
@@ -195,10 +217,10 @@ const ChainSelect = () => {
|
||||
explorerLink: tempExplorerLink,
|
||||
},
|
||||
});
|
||||
const selectedOption = findExistingOption(chainOptions, tempRegistryName);
|
||||
const selectedOption = findExistingOption(chainOptions, tempRegistryName!);
|
||||
setSelectValue(selectedOption);
|
||||
setShowSettings(false);
|
||||
} catch (error) {
|
||||
} catch (error: any) {
|
||||
console.log(error);
|
||||
setShowSettings(true);
|
||||
setChainError(error.message);
|
||||
@@ -237,14 +259,16 @@ const ChainSelect = () => {
|
||||
<Input
|
||||
width="48%"
|
||||
value={tempChainName}
|
||||
onChange={(e) => setChainName(e.target.value)}
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
|
||||
setChainName(e.target.value)
|
||||
}
|
||||
label="Chain Name"
|
||||
/>
|
||||
|
||||
<Input
|
||||
width="48%"
|
||||
value={tempChainId}
|
||||
onChange={(e) => setChainId(e.target.value)}
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => setChainId(e.target.value)}
|
||||
label="Chain ID"
|
||||
/>
|
||||
</div>
|
||||
@@ -252,13 +276,17 @@ const ChainSelect = () => {
|
||||
<Input
|
||||
width="48%"
|
||||
value={tempAddressPrefix}
|
||||
onChange={(e) => setAddressPrefix(e.target.value)}
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
|
||||
setAddressPrefix(e.target.value)
|
||||
}
|
||||
label="Bech32 Prefix (address prefix)"
|
||||
/>
|
||||
<Input
|
||||
width="48%"
|
||||
value={tempNodeAddress}
|
||||
onChange={(e) => setNodeAddress(e.target.value)}
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
|
||||
setNodeAddress(e.target.value)
|
||||
}
|
||||
label="RPC Node URL (must be https)"
|
||||
/>
|
||||
</div>
|
||||
@@ -266,13 +294,15 @@ const ChainSelect = () => {
|
||||
<Input
|
||||
width="48%"
|
||||
value={tempDisplayDenom}
|
||||
onChange={(e) => setDisplayDenom(e.target.value)}
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
|
||||
setDisplayDenom(e.target.value)
|
||||
}
|
||||
label="Display Denom"
|
||||
/>
|
||||
<Input
|
||||
width="48%"
|
||||
value={tempDenom}
|
||||
onChange={(e) => setDenom(e.target.value)}
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => setDenom(e.target.value)}
|
||||
label="Base Denom"
|
||||
/>
|
||||
</div>
|
||||
@@ -280,13 +310,15 @@ const ChainSelect = () => {
|
||||
<Input
|
||||
width="48%"
|
||||
value={tempDisplayDenomExponent}
|
||||
onChange={(e) => setDisplayDenomExponent(e.target.value)}
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
|
||||
setDisplayDenomExponent(parseInt(e.target.value))
|
||||
}
|
||||
label="Denom Exponent"
|
||||
/>
|
||||
<Input
|
||||
width="48%"
|
||||
value={tempGasPrice}
|
||||
onChange={(e) => setGasPrice(e.target.value)}
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => setGasPrice(e.target.value)}
|
||||
label="Gas Price"
|
||||
/>
|
||||
</div>
|
||||
@@ -294,7 +326,9 @@ const ChainSelect = () => {
|
||||
<Input
|
||||
width="48%"
|
||||
value={tempExplorerLink}
|
||||
onChange={(e) => setExplorerLink(e.target.value)}
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
|
||||
setExplorerLink(e.target.value)
|
||||
}
|
||||
label="Explorer Link (with '${txHash}' included)"
|
||||
/>
|
||||
</div>
|
||||
+7
-2
@@ -6,9 +6,14 @@ import Button from "../inputs/Button";
|
||||
import { explorerLinkTx } from "../../lib/displayHelpers";
|
||||
import { useAppContext } from "../../context/AppContext";
|
||||
|
||||
const CompletedTransaction = ({ transactionHash }) => {
|
||||
interface Props {
|
||||
transactionHash: string;
|
||||
}
|
||||
|
||||
const CompletedTransaction = ({ transactionHash }: Props) => {
|
||||
const { state } = useAppContext();
|
||||
const explorerLink = explorerLinkTx(state.chain.explorerLink, transactionHash);
|
||||
const baseURL = state.chain.explorerLink ? state.chain.explorerLink : "";
|
||||
const explorerLink = explorerLinkTx(baseURL, transactionHash);
|
||||
return (
|
||||
<StackableContainer lessPadding lessMargin>
|
||||
<StackableContainer lessPadding lessMargin lessRadius>
|
||||
@@ -1,7 +1,13 @@
|
||||
import React from "react";
|
||||
import copy from "copy-to-clipboard";
|
||||
|
||||
const CopyAndPaste = (props) => (
|
||||
interface Props {
|
||||
copyText: string;
|
||||
stroke?: string;
|
||||
strokeWidth?: number;
|
||||
}
|
||||
|
||||
const CopyAndPaste = (props: Props) => (
|
||||
<div className="icon" onClick={() => copy(props.copyText)}>
|
||||
<svg viewBox="0 0 100 100" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M13 32H41C53.1503 32 63 41.8497 63 54V94H13V32Z" />
|
||||
@@ -3,10 +3,15 @@ import React from "react";
|
||||
import { abbreviateLongString } from "../../lib/displayHelpers";
|
||||
import CopyAndPaste from "./CopyAndPaste";
|
||||
|
||||
const HashView = ({ hash, abbreviate }) => (
|
||||
interface Props {
|
||||
hash: string;
|
||||
abbreviate?: boolean;
|
||||
}
|
||||
|
||||
const HashView = (props: Props) => (
|
||||
<div className="hash-view">
|
||||
<div>{abbreviate ? abbreviateLongString(hash) : hash}</div>
|
||||
<CopyAndPaste copyText={hash} />
|
||||
<div>{props.abbreviate ? abbreviateLongString(props.hash) : props.hash}</div>
|
||||
<CopyAndPaste copyText={props.hash} />
|
||||
<style jsx>{`
|
||||
.hash-view {
|
||||
display: flex;
|
||||
+11
-2
@@ -1,16 +1,25 @@
|
||||
import React from "react";
|
||||
import { Coin } from "cosmjs-types/cosmos/base/v1beta1/coin";
|
||||
|
||||
import { useAppContext } from "../../context/AppContext";
|
||||
import { printableCoin } from "../../lib/displayHelpers";
|
||||
import StackableContainer from "../layout/StackableContainer";
|
||||
|
||||
const MultisigHoldings = (props) => {
|
||||
interface Props {
|
||||
holdings: Coin | null;
|
||||
}
|
||||
|
||||
const MultisigHoldings = (props: Props) => {
|
||||
const { state } = useAppContext();
|
||||
return (
|
||||
<StackableContainer lessPadding fullHeight>
|
||||
<h2>Holdings</h2>
|
||||
<StackableContainer lessPadding lessMargin>
|
||||
<span>{printableCoin(props.holdings, state.chain)}</span>
|
||||
{props.holdings ? (
|
||||
<span>{printableCoin(props.holdings, state.chain)}</span>
|
||||
) : (
|
||||
<span>None</span>
|
||||
)}
|
||||
</StackableContainer>
|
||||
<style jsx>{`
|
||||
span {
|
||||
@@ -3,7 +3,13 @@ import React from "react";
|
||||
import HashView from "./HashView";
|
||||
import StackableContainer from "../layout/StackableContainer";
|
||||
|
||||
const MultisigMembers = (props) => (
|
||||
interface Props {
|
||||
/** Addresses of the multisig members */
|
||||
members: string[];
|
||||
threshold: number;
|
||||
}
|
||||
|
||||
const MultisigMembers = (props: Props) => (
|
||||
<StackableContainer lessPadding>
|
||||
<div className="meta-data">
|
||||
<div>
|
||||
@@ -13,7 +19,7 @@ const MultisigMembers = (props) => (
|
||||
<div>
|
||||
<h2>Members</h2>
|
||||
<ul>
|
||||
{props.members.map((address) => (
|
||||
{props.members.map((address: string) => (
|
||||
<li key={address} className="info">
|
||||
<HashView hash={address} />
|
||||
</li>
|
||||
@@ -1,8 +1,14 @@
|
||||
import React from "react";
|
||||
|
||||
import { Account } from "@cosmjs/stargate";
|
||||
import { DbSignature } from "../../types";
|
||||
import StackableContainer from "../layout/StackableContainer";
|
||||
|
||||
const ThresholdInfo = ({ signatures, account }) => (
|
||||
interface Props {
|
||||
signatures: DbSignature[];
|
||||
account: Account;
|
||||
}
|
||||
|
||||
const ThresholdInfo = ({ signatures, account }: Props) => (
|
||||
<StackableContainer lessPadding lessMargin>
|
||||
<h2>Signatures</h2>
|
||||
<StackableContainer lessPadding lessMargin lessRadius>
|
||||
@@ -15,7 +21,7 @@ const ThresholdInfo = ({ signatures, account }) => (
|
||||
<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>
|
||||
@@ -1,11 +1,17 @@
|
||||
import React from "react";
|
||||
import { Coin } from "cosmjs-types/cosmos/base/v1beta1/coin";
|
||||
|
||||
import { DbTransaction } from "../../types";
|
||||
import { useAppContext } from "../../context/AppContext";
|
||||
import HashView from "./HashView";
|
||||
import StackableContainer from "../layout/StackableContainer";
|
||||
import { printableCoins } from "../../lib/displayHelpers";
|
||||
|
||||
const TransactionInfo = (props) => {
|
||||
interface Props {
|
||||
tx: DbTransaction;
|
||||
}
|
||||
|
||||
const TransactionInfo = (props: Props) => {
|
||||
const { state } = useAppContext();
|
||||
return (
|
||||
<StackableContainer lessPadding lessMargin>
|
||||
@@ -32,7 +38,7 @@ const TransactionInfo = (props) => {
|
||||
</li>
|
||||
<li>
|
||||
<label>Fee:</label>
|
||||
<div>{printableCoins(props.tx.fee.amount, state.chain)}</div>
|
||||
<div>{printableCoins(props.tx.fee.amount as Coin[], state.chain)}</div>
|
||||
</li>
|
||||
</>
|
||||
)}
|
||||
@@ -1,27 +0,0 @@
|
||||
import React from "react";
|
||||
import { withRouter } from "next/router";
|
||||
|
||||
import Button from "../../components/inputs/Button";
|
||||
import StackableContainer from "../layout/StackableContainer";
|
||||
|
||||
const TransactionForm = () => {
|
||||
return (
|
||||
<StackableContainer lessPadding>
|
||||
<h2>New transaction</h2>
|
||||
<p>Connect your wallet to create a new transaction.</p>
|
||||
<Button
|
||||
label="Connect Wallet"
|
||||
onClick={() => {
|
||||
this.props.onConnect(true);
|
||||
}}
|
||||
/>
|
||||
<style jsx>{`
|
||||
p {
|
||||
margin-top: 15px;
|
||||
}
|
||||
`}</style>
|
||||
</StackableContainer>
|
||||
);
|
||||
};
|
||||
|
||||
export default withRouter(TransactionForm);
|
||||
@@ -1,5 +1,5 @@
|
||||
import React, { useState } from "react";
|
||||
import { withRouter } from "next/router";
|
||||
import { withRouter, NextRouter } from "next/router";
|
||||
|
||||
import { useAppContext } from "../../context/AppContext";
|
||||
import Button from "../inputs/Button";
|
||||
@@ -7,7 +7,11 @@ import StackableContainer from "../layout/StackableContainer";
|
||||
import Input from "../inputs/Input";
|
||||
import { exampleAddress } from "../../lib/displayHelpers";
|
||||
|
||||
const FindMultisigForm = (props) => {
|
||||
interface Props {
|
||||
router: NextRouter;
|
||||
}
|
||||
|
||||
const FindMultisigForm = (props: Props) => {
|
||||
const { state } = useAppContext();
|
||||
const [address, setAddress] = useState("");
|
||||
const [_processing, setProcessing] = useState(false);
|
||||
@@ -28,11 +32,11 @@ const FindMultisigForm = (props) => {
|
||||
</StackableContainer>
|
||||
<StackableContainer lessPadding lessMargin>
|
||||
<Input
|
||||
onChange={(e) => setAddress(e.target.value)}
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => setAddress(e.target.value)}
|
||||
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,5 +1,5 @@
|
||||
import React, { useState } from "react";
|
||||
import { withRouter } from "next/router";
|
||||
import { withRouter, NextRouter } from "next/router";
|
||||
import { StargateClient } from "@cosmjs/stargate";
|
||||
|
||||
import { useAppContext } from "../../context/AppContext";
|
||||
@@ -14,23 +14,31 @@ const emptyPubKeyGroup = () => {
|
||||
return { address: "", compressedPubkey: "", keyError: "", isPubkey: false };
|
||||
};
|
||||
|
||||
const MultiSigForm = (props) => {
|
||||
interface Props {
|
||||
router: NextRouter;
|
||||
}
|
||||
|
||||
const MultiSigForm = (props: Props) => {
|
||||
const { state } = useAppContext();
|
||||
const [pubkeys, setPubkeys] = useState([emptyPubKeyGroup(), emptyPubKeyGroup()]);
|
||||
const [threshold, setThreshold] = useState(2);
|
||||
const [_processing, setProcessing] = useState(false);
|
||||
|
||||
const handleChangeThreshold = (e) => {
|
||||
let newThreshold = e.target.value;
|
||||
const handleChangeThreshold = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
let newThreshold = parseInt(e.target.value);
|
||||
if (newThreshold > pubkeys.length || newThreshold <= 0) {
|
||||
newThreshold = threshold;
|
||||
}
|
||||
setThreshold(newThreshold);
|
||||
};
|
||||
|
||||
const handleKeyGroupChange = (index, e) => {
|
||||
const handleKeyGroupChange = (index: number, e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const tempPubkeys = [...pubkeys];
|
||||
tempPubkeys[index][e.target.name] = e.target.value;
|
||||
if (e.target.name === "compressedPubkey") {
|
||||
tempPubkeys[index].compressedPubkey = e.target.value;
|
||||
} else if (e.target.name === "address") {
|
||||
tempPubkeys[index].address = e.target.value;
|
||||
}
|
||||
setPubkeys(tempPubkeys);
|
||||
};
|
||||
|
||||
@@ -39,7 +47,7 @@ const MultiSigForm = (props) => {
|
||||
setPubkeys(tempPubkeys.concat(emptyPubKeyGroup()));
|
||||
};
|
||||
|
||||
const handleRemove = (index) => {
|
||||
const handleRemove = (index: number) => {
|
||||
const tempPubkeys = [...pubkeys];
|
||||
const oldLength = tempPubkeys.length;
|
||||
tempPubkeys.splice(index, 1);
|
||||
@@ -48,8 +56,8 @@ const MultiSigForm = (props) => {
|
||||
setThreshold(newThreshold);
|
||||
};
|
||||
|
||||
const getPubkeyFromNode = async (address) => {
|
||||
const client = await StargateClient.connect(state.chain.nodeAddress);
|
||||
const getPubkeyFromNode = async (address: string) => {
|
||||
const client = await StargateClient.connect(state!.chain.nodeAddress!);
|
||||
const accountOnChain = await client.getAccount(address);
|
||||
console.log(accountOnChain);
|
||||
if (!accountOnChain || !accountOnChain.pubkey) {
|
||||
@@ -60,7 +68,7 @@ const MultiSigForm = (props) => {
|
||||
return accountOnChain.pubkey.value;
|
||||
};
|
||||
|
||||
const handleKeyBlur = async (index, e) => {
|
||||
const handleKeyBlur = async (index: number, e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
try {
|
||||
const tempPubkeys = [...pubkeys];
|
||||
let pubkey;
|
||||
@@ -82,7 +90,7 @@ const MultiSigForm = (props) => {
|
||||
tempPubkeys[index].compressedPubkey = pubkey;
|
||||
tempPubkeys[index].keyError = "";
|
||||
setPubkeys(tempPubkeys);
|
||||
} catch (error) {
|
||||
} catch (error: any) {
|
||||
console.log(error);
|
||||
const tempPubkeys = [...pubkeys];
|
||||
tempPubkeys[index].keyError = error.message;
|
||||
@@ -97,9 +105,9 @@ const MultiSigForm = (props) => {
|
||||
try {
|
||||
multisigAddress = await createMultisigFromCompressedSecp256k1Pubkeys(
|
||||
compressedPubkeys,
|
||||
parseInt(threshold, 10),
|
||||
state.chain.addressPrefix,
|
||||
state.chain.chainId,
|
||||
threshold,
|
||||
state!.chain.addressPrefix!,
|
||||
state!.chain.chainId!,
|
||||
);
|
||||
props.router.push(`/multi/${multisigAddress}`);
|
||||
} catch (error) {
|
||||
@@ -107,7 +115,7 @@ const MultiSigForm = (props) => {
|
||||
}
|
||||
};
|
||||
|
||||
const togglePubkey = (index) => {
|
||||
const togglePubkey = (index: number) => {
|
||||
const tempPubkeys = [...pubkeys];
|
||||
tempPubkeys[index].isPubkey = !tempPubkeys[index].isPubkey;
|
||||
setPubkeys(tempPubkeys);
|
||||
@@ -134,7 +142,7 @@ const MultiSigForm = (props) => {
|
||||
)}
|
||||
<div className="key-inputs">
|
||||
<Input
|
||||
onChange={(e) => {
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
handleKeyGroupChange(index, e);
|
||||
}}
|
||||
value={pubkeyGroup.isPubkey ? pubkeyGroup.compressedPubkey : pubkeyGroup.address}
|
||||
@@ -144,10 +152,10 @@ const MultiSigForm = (props) => {
|
||||
placeholder={`E.g. ${
|
||||
pubkeyGroup.isPubkey
|
||||
? examplePubkey(index)
|
||||
: exampleAddress(index, state.chain.addressPrefix)
|
||||
: exampleAddress(index, state!.chain.addressPrefix!)
|
||||
}`}
|
||||
error={pubkeyGroup.keyError}
|
||||
onBlur={(e) => {
|
||||
onBlur={(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
handleKeyBlur(index, e);
|
||||
}}
|
||||
/>
|
||||
@@ -1,29 +1,36 @@
|
||||
import axios from "axios";
|
||||
import { calculateFee } from "@cosmjs/stargate";
|
||||
import { calculateFee, Account } from "@cosmjs/stargate";
|
||||
import { Decimal } from "@cosmjs/math";
|
||||
import React, { useState } from "react";
|
||||
import { withRouter } from "next/router";
|
||||
import { withRouter, NextRouter } from "next/router";
|
||||
|
||||
import { useAppContext } from "../../context/AppContext";
|
||||
import Button from "../../components/inputs/Button";
|
||||
import Input from "../../components/inputs/Input";
|
||||
import Button from "../inputs/Button";
|
||||
import Input from "../inputs/Input";
|
||||
import StackableContainer from "../layout/StackableContainer";
|
||||
import { checkAddress, exampleAddress } from "../../lib/displayHelpers";
|
||||
|
||||
const TransactionForm = (props) => {
|
||||
interface Props {
|
||||
address: string | null;
|
||||
accountOnChain: Account | null;
|
||||
router: NextRouter;
|
||||
closeForm: () => void;
|
||||
}
|
||||
|
||||
const TransactionForm = (props: Props) => {
|
||||
const { state } = useAppContext();
|
||||
const [toAddress, setToAddress] = useState("");
|
||||
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, txAmount, txGas) => {
|
||||
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,
|
||||
@@ -31,7 +38,7 @@ const TransactionForm = (props) => {
|
||||
amount: [
|
||||
{
|
||||
amount: amountInAtomics,
|
||||
denom: state.chain.denom,
|
||||
denom: state!.chain.denom,
|
||||
},
|
||||
],
|
||||
};
|
||||
@@ -39,11 +46,11 @@ const TransactionForm = (props) => {
|
||||
typeUrl: "/cosmos.bank.v1beta1.MsgSend",
|
||||
value: msgSend,
|
||||
};
|
||||
const fee = calculateFee(Number(txGas), gasPrice);
|
||||
const fee = calculateFee(Number(txGas), gasPrice!);
|
||||
return {
|
||||
accountNumber: props.accountOnChain.accountNumber,
|
||||
sequence: props.accountOnChain.sequence,
|
||||
chainId: state.chain.chainId,
|
||||
accountNumber: props.accountOnChain!.accountNumber,
|
||||
sequence: props.accountOnChain!.sequence,
|
||||
chainId: state!.chain.chainId,
|
||||
msgs: [msg],
|
||||
fee: fee,
|
||||
memo: memo,
|
||||
@@ -51,9 +58,9 @@ const TransactionForm = (props) => {
|
||||
};
|
||||
|
||||
const handleCreate = async () => {
|
||||
const toAddressError = checkAddress(toAddress, state.chain.addressPrefix);
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -77,18 +84,18 @@ const TransactionForm = (props) => {
|
||||
label="To Address"
|
||||
name="toAddress"
|
||||
value={toAddress}
|
||||
onChange={(e) => setToAddress(e.target.value)}
|
||||
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}
|
||||
onChange={(e) => setAmount(e.target.value)}
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => setAmount(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<div className="form-item">
|
||||
@@ -97,7 +104,7 @@ const TransactionForm = (props) => {
|
||||
name="gas"
|
||||
type="number"
|
||||
value={gas}
|
||||
onChange={(e) => setGas(e.target.value)}
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => setGas(parseInt(e.target.value))}
|
||||
/>
|
||||
</div>
|
||||
<div className="form-item">
|
||||
@@ -109,7 +116,7 @@ const TransactionForm = (props) => {
|
||||
name="memo"
|
||||
type="text"
|
||||
value={memo}
|
||||
onChange={(e) => setMemo(e.target.value)}
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => setMemo(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<Button label="Create Transaction" onClick={handleCreate} />
|
||||
@@ -8,23 +8,32 @@ import { useAppContext } from "../../context/AppContext";
|
||||
import Button from "../inputs/Button";
|
||||
import HashView from "../dataViews/HashView";
|
||||
import StackableContainer from "../layout/StackableContainer";
|
||||
import { DbSignature, DbTransaction, WalletAccount } from "../../types";
|
||||
|
||||
import TransportWebUSB from "@ledgerhq/hw-transport-webusb";
|
||||
import { LedgerSigner } from "@cosmjs/ledger-amino";
|
||||
import { makeCosmoshubPath } from "@cosmjs/amino";
|
||||
|
||||
const TransactionSigning = (props) => {
|
||||
interface Props {
|
||||
signatures: DbSignature[];
|
||||
tx: DbTransaction;
|
||||
transactionID: string;
|
||||
addSignature: (signature: DbSignature) => void;
|
||||
}
|
||||
|
||||
const TransactionSigning = (props: Props) => {
|
||||
const { state } = useAppContext();
|
||||
const [walletAccount, setWalletAccount] = useState(null);
|
||||
const [sigError, setSigError] = useState(null);
|
||||
const [walletAccount, setWalletAccount] = useState<WalletAccount>();
|
||||
const [sigError, setSigError] = useState("");
|
||||
const [hasSigned, setHasSigned] = useState(false);
|
||||
const [walletType, setWalletType] = useState("");
|
||||
const [ledgerSigner, setLedgerSigner] = useState({});
|
||||
|
||||
const connectKeplr = async () => {
|
||||
try {
|
||||
await window.keplr.enable(state.chain.chainId);
|
||||
const tempWalletAccount = await window.keplr.getKey(state.chain.chainId);
|
||||
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,
|
||||
);
|
||||
@@ -43,11 +52,16 @@ const TransactionSigning = (props) => {
|
||||
// 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();
|
||||
const tempWalletAccount = accounts[0].address;
|
||||
console.log(accounts);
|
||||
const tempWalletAccount: WalletAccount = {
|
||||
bech32Address: accounts[0].address,
|
||||
pubkey: accounts[0].pubkey,
|
||||
algo: accounts[0].algo,
|
||||
};
|
||||
|
||||
const tempHasSigned = props.signatures.some(
|
||||
(sig) => sig.address === tempWalletAccount.bech32Address,
|
||||
@@ -60,19 +74,18 @@ const TransactionSigning = (props) => {
|
||||
|
||||
const signTransaction = async () => {
|
||||
const offlineSigner =
|
||||
walletType === "keplr" ? window.getOfflineSignerOnlyAmino(state.chain.chainId) : ledgerSigner;
|
||||
|
||||
const signerAddress =
|
||||
walletType === "keplr"
|
||||
? walletAccount.bech32Address
|
||||
: (await ledgerSigner.getAccounts())[0]?.address;
|
||||
? window.getOfflineSignerOnlyAmino(state!.chain.chainId)
|
||||
: ledgerSigner;
|
||||
|
||||
const signerAddress = walletAccount?.bech32Address;
|
||||
assert(signerAddress, "Missing signer address");
|
||||
const signingClient = await SigningStargateClient.offline(offlineSigner);
|
||||
|
||||
const signerData = {
|
||||
accountNumber: props.tx.accountNumber,
|
||||
sequence: props.tx.sequence,
|
||||
chainId: state.chain.chainId,
|
||||
chainId: state!.chain.chainId as string,
|
||||
};
|
||||
|
||||
const { bodyBytes, signatures } = await signingClient.sign(
|
||||
@@ -5,7 +5,13 @@ import { string } from "prop-types";
|
||||
const defaultDescription = "Create multisigs and send tokens on any cosmos based chain";
|
||||
const defaultOGURL = "";
|
||||
|
||||
const Head = (props) => (
|
||||
interface Props {
|
||||
title?: string;
|
||||
description?: string;
|
||||
url?: string;
|
||||
}
|
||||
|
||||
const Head = (props: Props) => (
|
||||
<NextHead>
|
||||
<meta charSet="UTF-8" />
|
||||
<title>{props.title || ""}</title>
|
||||
@@ -1,6 +1,9 @@
|
||||
import React from "react";
|
||||
|
||||
const Gear = (props) => (
|
||||
interface Props {
|
||||
color: string;
|
||||
}
|
||||
const Gear = (props: Props) => (
|
||||
<i>
|
||||
<svg viewBox="0 0 640 640" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
@@ -1,13 +1,16 @@
|
||||
import React from "react";
|
||||
|
||||
const Button = (props) => (
|
||||
interface Props {
|
||||
primary?: boolean;
|
||||
disabled?: boolean;
|
||||
href?: string;
|
||||
label: string;
|
||||
onClick?: () => any;
|
||||
}
|
||||
const Button = (props: Props) => (
|
||||
<>
|
||||
{props.href ? (
|
||||
<a
|
||||
className={props.primary ? "primary button" : "button"}
|
||||
href={props.href}
|
||||
disabled={props.disabled}
|
||||
>
|
||||
<a className={props.primary ? "primary button" : "button"} href={props.href}>
|
||||
{props.label}
|
||||
</a>
|
||||
) : (
|
||||
@@ -1,6 +1,19 @@
|
||||
import React from "react";
|
||||
|
||||
const Input = (props) => (
|
||||
interface Props {
|
||||
label?: string;
|
||||
type?: string;
|
||||
name?: string;
|
||||
onChange?: (e: React.ChangeEvent<HTMLInputElement>) => void;
|
||||
value: number | string | undefined;
|
||||
onBlur?: (e: React.ChangeEvent<HTMLInputElement>) => void;
|
||||
disabled?: boolean;
|
||||
error?: string;
|
||||
placeholder?: string;
|
||||
width?: string;
|
||||
}
|
||||
|
||||
const Input = (props: Props) => (
|
||||
<div className="text-input">
|
||||
<label>{props.label || ""}</label>
|
||||
<input
|
||||
@@ -1,9 +1,9 @@
|
||||
import React from "react";
|
||||
import Select from "react-select";
|
||||
import React, { CSSProperties } from "react";
|
||||
import Select, { ControlProps } from "react-select";
|
||||
|
||||
const StyledSelect = (props) => {
|
||||
const StyledSelect = (props: any) => {
|
||||
const customStyles = {
|
||||
control: (provided, state) => ({
|
||||
control: (provided: CSSProperties, state: ControlProps) => ({
|
||||
...provided,
|
||||
borderRadius: "10px",
|
||||
background: "none",
|
||||
@@ -16,7 +16,7 @@ const StyledSelect = (props) => {
|
||||
borderColor: "rgba(255, 255, 255, 1)",
|
||||
},
|
||||
}),
|
||||
option: (provided, state) => ({
|
||||
option: (provided: CSSProperties, state: any) => ({
|
||||
...provided,
|
||||
background: state.isSelected ? "rgba(255, 255, 255, 0.2)" : "none",
|
||||
color: "white",
|
||||
@@ -25,25 +25,25 @@ const StyledSelect = (props) => {
|
||||
background: "rgba(255, 255, 255, 0.2)",
|
||||
},
|
||||
}),
|
||||
menu: (provided) => ({
|
||||
menu: (provided: CSSProperties) => ({
|
||||
...provided,
|
||||
zIndex: 10,
|
||||
borderRadius: "10px",
|
||||
background: "#561253",
|
||||
}),
|
||||
singleValue: (provided) => ({
|
||||
singleValue: (provided: CSSProperties) => ({
|
||||
...provided,
|
||||
color: "white",
|
||||
}),
|
||||
input: (provided) => ({
|
||||
input: (provided: CSSProperties) => ({
|
||||
...provided,
|
||||
color: "white",
|
||||
}),
|
||||
placeholder: (provided) => ({
|
||||
placeholder: (provided: CSSProperties) => ({
|
||||
...provided,
|
||||
color: "rgba(255,255,255, 0.6)",
|
||||
}),
|
||||
dropdownIndicator: (provided) => ({
|
||||
dropdownIndicator: (provided: CSSProperties) => ({
|
||||
...provided,
|
||||
color: "rgba(255, 255, 255, 0.6)",
|
||||
"&:hover": {
|
||||
@@ -1,6 +1,12 @@
|
||||
import React from "react";
|
||||
|
||||
const ThresholdInput = (props) => (
|
||||
interface Props {
|
||||
onChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
|
||||
value: number;
|
||||
total: number;
|
||||
}
|
||||
|
||||
const ThresholdInput = (props: Props) => (
|
||||
<>
|
||||
<p>Signatures required to send a transaction</p>
|
||||
<div className="threshold-group">
|
||||
@@ -3,7 +3,13 @@ import React from "react";
|
||||
import Head from "../head";
|
||||
import StackableContainer from "./StackableContainer";
|
||||
|
||||
const Page = (props) => {
|
||||
interface Props {
|
||||
title?: string;
|
||||
rootMultisig?: string;
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
const Page = (props: Props) => {
|
||||
return (
|
||||
<div className="page">
|
||||
<Head title={props.title || "Cosmos Multisig Manager"} />
|
||||
@@ -1,6 +1,15 @@
|
||||
import React from "react";
|
||||
|
||||
const StackableContainer = (props) => (
|
||||
interface Props {
|
||||
base?: boolean;
|
||||
children: React.ReactNode;
|
||||
lessPadding?: boolean;
|
||||
lessMargin?: boolean;
|
||||
lessRadius?: boolean;
|
||||
fullHeight?: boolean;
|
||||
}
|
||||
|
||||
const StackableContainer = (props: Props) => (
|
||||
<div className={`container ${props.base ? "base" : ""}`}>
|
||||
{props.children}
|
||||
|
||||
@@ -1,63 +0,0 @@
|
||||
import React, { useEffect, createContext, useContext, useReducer } from "react";
|
||||
|
||||
import { AppReducer, initialState } from "./AppReducer";
|
||||
|
||||
const AppContext = createContext();
|
||||
|
||||
function getChainInfoFromUrl() {
|
||||
const url = location.search;
|
||||
const params = new URLSearchParams(url);
|
||||
const chainInfo = {
|
||||
nodeAddress: decodeURIComponent(params.get("nodeAddress")),
|
||||
denom: params.get("denom"),
|
||||
displayDenom: params.get("displayDenom"),
|
||||
displayDenomExponent: parseInt(params.get("displayDenomExponent"), 10),
|
||||
gasPrice: params.get("gasPrice"),
|
||||
chainId: params.get("chainId"),
|
||||
chainDisplayName: decodeURIComponent(params.get("chainDisplayName")),
|
||||
registryName: params.get("registryName"),
|
||||
addressPrefix: params.get("addressPrefix"),
|
||||
explorerLink: decodeURIComponent(params.get("explorerLink")),
|
||||
};
|
||||
|
||||
return chainInfo;
|
||||
}
|
||||
|
||||
function setChainInfoParams(chainInfo) {
|
||||
const params = new URLSearchParams();
|
||||
|
||||
Object.keys(chainInfo).forEach((key) => {
|
||||
params.set(key, encodeURIComponent(chainInfo[key]));
|
||||
});
|
||||
|
||||
window.history.replaceState({}, "", `${location.pathname}?${params}`);
|
||||
}
|
||||
|
||||
export function AppWrapper({ children }) {
|
||||
let existingState;
|
||||
if (typeof window !== "undefined") {
|
||||
existingState = JSON.parse(localStorage.getItem("state"));
|
||||
const urlChainInfo = getChainInfoFromUrl();
|
||||
|
||||
// query params should override saved state
|
||||
if (urlChainInfo.chainId) {
|
||||
console.log("setting state from url");
|
||||
existingState = { chain: urlChainInfo };
|
||||
}
|
||||
}
|
||||
const [state, dispatch] = useReducer(AppReducer, existingState ? existingState : initialState);
|
||||
|
||||
const contextValue = { state, dispatch };
|
||||
|
||||
useEffect(() => {
|
||||
if (state && state !== initialState) {
|
||||
localStorage.setItem("state", JSON.stringify(state));
|
||||
setChainInfoParams(state.chain);
|
||||
}
|
||||
}, [state]);
|
||||
|
||||
return <AppContext.Provider value={contextValue}>{children}</AppContext.Provider>;
|
||||
}
|
||||
export function useAppContext() {
|
||||
return useContext(AppContext);
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
import React, { useEffect, createContext, useContext, useReducer } from "react";
|
||||
|
||||
import { AppReducer, ChangeChainAction, initialState } from "./AppReducer";
|
||||
import { ChainInfo } from "../types";
|
||||
|
||||
export interface AppContextType {
|
||||
chain: ChainInfo;
|
||||
}
|
||||
|
||||
const AppContext = createContext<{
|
||||
state: AppContextType;
|
||||
dispatch: React.Dispatch<ChangeChainAction>;
|
||||
}>({ state: initialState, dispatch: () => {} });
|
||||
|
||||
function getChainInfoFromUrl(): ChainInfo {
|
||||
const url = location.search;
|
||||
const params = new URLSearchParams(url);
|
||||
const chainInfo: ChainInfo = {
|
||||
nodeAddress: decodeURIComponent(params.get("nodeAddress") || ""),
|
||||
denom: params.get("denom") || "",
|
||||
displayDenom: params.get("displayDenom") || "",
|
||||
displayDenomExponent: parseInt(params.get("displayDenomExponent") || "", 10),
|
||||
gasPrice: params.get("gasPrice") || "",
|
||||
chainId: params.get("chainId") || "",
|
||||
chainDisplayName: decodeURIComponent(params.get("chainDisplayName") || ""),
|
||||
registryName: params.get("registryName") || "",
|
||||
addressPrefix: params.get("addressPrefix") || "",
|
||||
explorerLink: decodeURIComponent(params.get("explorerLink") || ""),
|
||||
};
|
||||
|
||||
return chainInfo;
|
||||
}
|
||||
|
||||
function setChainInfoParams(chainInfo: ChainInfo) {
|
||||
const params = new URLSearchParams();
|
||||
|
||||
const keys = Object.keys(chainInfo) as Array<keyof ChainInfo>;
|
||||
|
||||
keys.forEach((value: keyof ChainInfo) => {
|
||||
params.set(value, encodeURIComponent(chainInfo[value] || ""));
|
||||
});
|
||||
|
||||
window.history.replaceState({}, "", `${location.pathname}?${params}`);
|
||||
}
|
||||
|
||||
export function AppWrapper({ children }: { children: React.ReactNode }) {
|
||||
let existingState;
|
||||
if (typeof window !== "undefined") {
|
||||
const storedState = localStorage.getItem("state");
|
||||
if (storedState) {
|
||||
existingState = JSON.parse(storedState);
|
||||
}
|
||||
|
||||
const urlChainInfo = getChainInfoFromUrl();
|
||||
|
||||
// query params should override saved state
|
||||
if (urlChainInfo.chainId) {
|
||||
console.log("setting state from url");
|
||||
existingState = { chain: urlChainInfo };
|
||||
}
|
||||
}
|
||||
const [state, dispatch] = useReducer(AppReducer, existingState ? existingState : initialState);
|
||||
|
||||
const contextValue = { state, dispatch };
|
||||
|
||||
useEffect(() => {
|
||||
if (state && state !== initialState) {
|
||||
localStorage.setItem("state", JSON.stringify(state));
|
||||
setChainInfoParams(state.chain);
|
||||
}
|
||||
}, [state]);
|
||||
|
||||
return <AppContext.Provider value={contextValue}>{children}</AppContext.Provider>;
|
||||
}
|
||||
export function useAppContext() {
|
||||
return useContext(AppContext);
|
||||
}
|
||||
@@ -1,9 +1,12 @@
|
||||
export const initialState = {
|
||||
import { ChainInfo } from "../types";
|
||||
import { AppContextType } from "./AppContext";
|
||||
|
||||
export const initialState: AppContextType = {
|
||||
chain: {
|
||||
nodeAddress: process.env.NEXT_PUBLIC_NODE_ADDRESS,
|
||||
denom: process.env.NEXT_PUBLIC_DENOM,
|
||||
displayDenom: process.env.NEXT_PUBLIC_DISPLAY_DENOM,
|
||||
displayDenomExponent: parseInt(process.env.NEXT_PUBLIC_DISPLAY_DENOM_EXPONENT, 10),
|
||||
displayDenomExponent: parseInt(process.env.NEXT_PUBLIC_DISPLAY_DENOM_EXPONENT || "", 10),
|
||||
gasPrice: process.env.NEXT_PUBLIC_GAS_PRICE,
|
||||
chainId: process.env.NEXT_PUBLIC_CHAIN_ID,
|
||||
chainDisplayName: process.env.NEXT_PUBLIC_CHAIN_DISPLAY_NAME,
|
||||
@@ -13,7 +16,12 @@ export const initialState = {
|
||||
},
|
||||
};
|
||||
|
||||
export const AppReducer = (state, action) => {
|
||||
export interface ChangeChainAction {
|
||||
type: "changeChain";
|
||||
value: ChainInfo;
|
||||
}
|
||||
|
||||
export const AppReducer = (state: AppContextType, action: ChangeChainAction) => {
|
||||
switch (action.type) {
|
||||
case "changeChain": {
|
||||
return {
|
||||
@@ -1,6 +1,8 @@
|
||||
import { Bech32, fromBase64, toBase64 } from "@cosmjs/encoding";
|
||||
import { sha512 } from "@cosmjs/crypto";
|
||||
import { Decimal } from "@cosmjs/math";
|
||||
import { Coin } from "@cosmjs/amino";
|
||||
import { ChainInfo } from "../types";
|
||||
|
||||
/**
|
||||
* Abbreviates long strings, typically used for
|
||||
@@ -9,7 +11,7 @@ import { Decimal } from "@cosmjs/math";
|
||||
* @param {string} longString The string to abbreviate.
|
||||
* @return {string} The abbreviated string.
|
||||
*/
|
||||
const abbreviateLongString = (longString) => {
|
||||
const abbreviateLongString = (longString: string) => {
|
||||
if (longString.length < 13) {
|
||||
// no need to abbreviate
|
||||
return longString;
|
||||
@@ -34,7 +36,7 @@ const thinSpace = "\u202F";
|
||||
* @param {object} chainInfo Provides information about a chain (e.g. node, prefix, denomination), object structure defined in '../context/AppReducer'.
|
||||
* @return {string} The abbreviated string.
|
||||
*/
|
||||
const printableCoin = (coin, chainInfo) => {
|
||||
const printableCoin = (coin: Coin, chainInfo: ChainInfo) => {
|
||||
// null, undefined and this sort of things
|
||||
if (!coin) return "–";
|
||||
|
||||
@@ -57,7 +59,7 @@ const printableCoin = (coin, chainInfo) => {
|
||||
return coin.amount + thinSpace + coin.denom;
|
||||
};
|
||||
|
||||
const printableCoins = (coins, chainInfo) => {
|
||||
const printableCoins = (coins: Coin[], chainInfo: ChainInfo) => {
|
||||
if (coins.length !== 1) {
|
||||
throw new Error("Implementation only supports exactly one coin entry.");
|
||||
}
|
||||
@@ -69,7 +71,7 @@ const printableCoins = (coins, chainInfo) => {
|
||||
*
|
||||
* `index` can be set to a small integer in order to get different addresses. Defaults to 0.
|
||||
*/
|
||||
const exampleAddress = (index, chainAddressPrefix) => {
|
||||
const exampleAddress = (index: number, chainAddressPrefix: string) => {
|
||||
const usedIndex = index || 0;
|
||||
let data = Bech32.decode("cosmos1vqpjljwsynsn58dugz0w8ut7kun7t8ls2qkmsq").data;
|
||||
for (let i = 0; i < usedIndex; ++i) {
|
||||
@@ -86,7 +88,7 @@ const exampleAddress = (index, chainAddressPrefix) => {
|
||||
* Note: the keys are not necessarily valid (as in points on the chain) and should only be used
|
||||
* as dummy data.
|
||||
*/
|
||||
const examplePubkey = (index) => {
|
||||
const examplePubkey = (index: number) => {
|
||||
const usedIndex = index || 0;
|
||||
let data = fromBase64("Akd/qKMWdZXyiMnSu6aFLpQEGDO0ijyal9mXUIcVaPNX");
|
||||
for (let i = 0; i < usedIndex; ++i) {
|
||||
@@ -101,14 +103,14 @@ const examplePubkey = (index) => {
|
||||
*
|
||||
* Returns null of there is no error.
|
||||
*/
|
||||
const checkAddress = (input, chainAddressPrefix) => {
|
||||
const checkAddress = (input: string, chainAddressPrefix: string) => {
|
||||
if (!input) return "Empty";
|
||||
|
||||
let data;
|
||||
let prefix;
|
||||
try {
|
||||
({ data, prefix } = Bech32.decode(input));
|
||||
} catch (error) {
|
||||
} catch (error: any) {
|
||||
return error.toString();
|
||||
}
|
||||
|
||||
@@ -127,7 +129,7 @@ const checkAddress = (input, chainAddressPrefix) => {
|
||||
* Returns a link to a transaction in an explorer if an explorer is configured
|
||||
* for transactions. Returns null otherwise.
|
||||
*/
|
||||
const explorerLinkTx = (link, hash) => {
|
||||
const explorerLinkTx = (link: string, hash: string) => {
|
||||
if (link && link.includes("${txHash}")) {
|
||||
return link.replace("${txHash}", hash);
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
import axios from "axios";
|
||||
import { DbAccount, DbSignature, DbTransaction } from "../types";
|
||||
|
||||
// Graphql base request for Faunadb
|
||||
const graphqlReq = axios.create({
|
||||
@@ -14,7 +15,7 @@ const graphqlReq = axios.create({
|
||||
* @param {object} multisig an object with address (string), pubkey JSON and chainId
|
||||
* @return Returns async function that makes a request to the faunadb graphql endpoint
|
||||
*/
|
||||
const createMultisig = async (multisig) => {
|
||||
const createMultisig = async (multisig: DbAccount) => {
|
||||
console.log(multisig);
|
||||
return graphqlReq({
|
||||
method: "POST",
|
||||
@@ -43,7 +44,7 @@ const createMultisig = async (multisig) => {
|
||||
* @param {string} chainId The chainId the multisig belongs to.
|
||||
* @return Returns async function that makes a request to the faunadb graphql endpoint
|
||||
*/
|
||||
const getMultisig = async (address, chainId) => {
|
||||
const getMultisig = async (address: string, chainId: string) => {
|
||||
return graphqlReq({
|
||||
method: "POST",
|
||||
data: {
|
||||
@@ -66,7 +67,7 @@ const getMultisig = async (address, chainId) => {
|
||||
* @param {object} transaction The base transaction
|
||||
* @return Returns async function that makes a request to the faunadb graphql endpoint
|
||||
*/
|
||||
const createTransaction = async (transaction) => {
|
||||
const createTransaction = async (transaction: DbTransaction) => {
|
||||
return graphqlReq({
|
||||
method: "POST",
|
||||
data: {
|
||||
@@ -87,7 +88,7 @@ const createTransaction = async (transaction) => {
|
||||
* @param {string} id Faunadb resource id
|
||||
* @return Returns async function that makes a request to the faunadb graphql endpoint
|
||||
*/
|
||||
const findTransactionByID = async (id) => {
|
||||
const findTransactionByID = async (id: string) => {
|
||||
return graphqlReq({
|
||||
method: "POST",
|
||||
data: {
|
||||
@@ -118,7 +119,7 @@ const findTransactionByID = async (id) => {
|
||||
* @param {string} txHash tx hash returned from broadcasting a tx
|
||||
* @return Returns async function that makes a request to the faunadb graphql endpoint
|
||||
*/
|
||||
const updateTxHash = async (id, txHash) => {
|
||||
const updateTxHash = async (id: string, txHash: string) => {
|
||||
return graphqlReq({
|
||||
method: "POST",
|
||||
data: {
|
||||
@@ -149,7 +150,7 @@ const updateTxHash = async (id, txHash) => {
|
||||
* @param {string} transactionId id of the transaction to relate the signature with
|
||||
* @return Returns async function that makes a request to the faunadb graphql endpoint
|
||||
*/
|
||||
const createSignature = async (signature, transactionId) => {
|
||||
const createSignature = async (signature: DbSignature, transactionId: string) => {
|
||||
return graphqlReq({
|
||||
method: "POST",
|
||||
data: {
|
||||
@@ -1,5 +1,7 @@
|
||||
import axios from "axios";
|
||||
import { createMultisigThresholdPubkey, pubkeyToAddress } from "@cosmjs/amino";
|
||||
import { Account } from "@cosmjs/stargate";
|
||||
import { StargateClient } from "@cosmjs/stargate";
|
||||
|
||||
/**
|
||||
* Turns array of compressed Secp256k1 pubkeys
|
||||
@@ -12,11 +14,11 @@ import { createMultisigThresholdPubkey, pubkeyToAddress } from "@cosmjs/amino";
|
||||
* @return {string} The multisig address.
|
||||
*/
|
||||
const createMultisigFromCompressedSecp256k1Pubkeys = async (
|
||||
compressedPubkeys,
|
||||
threshold,
|
||||
addressPrefix,
|
||||
chainId,
|
||||
) => {
|
||||
compressedPubkeys: string[],
|
||||
threshold: number,
|
||||
addressPrefix: string,
|
||||
chainId: string,
|
||||
): Promise<string> => {
|
||||
const pubkeys = compressedPubkeys.map((compressedPubkey) => {
|
||||
return {
|
||||
type: "tendermint/PubKeySecp256k1",
|
||||
@@ -46,12 +48,15 @@ const createMultisigFromCompressedSecp256k1Pubkeys = async (
|
||||
* @param client A connected stargate cosmoshub client
|
||||
* @return {object} The multisig account.
|
||||
*/
|
||||
const getMultisigAccount = async (address, client) => {
|
||||
const getMultisigAccount = async (
|
||||
address: string,
|
||||
client: StargateClient,
|
||||
): Promise<Account | 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 = await client.getAccount(address);
|
||||
let accountOnChain: Mutable<Account> | null = await client.getAccount(address);
|
||||
const chainId = await client.getChainId();
|
||||
|
||||
if (!accountOnChain || !accountOnChain.pubkey) {
|
||||
@@ -64,11 +69,15 @@ const getMultisigAccount = async (address, client) => {
|
||||
const pubkey = JSON.parse(res.data.pubkeyJSON);
|
||||
|
||||
if (!accountOnChain) {
|
||||
accountOnChain = {};
|
||||
accountOnChain = null;
|
||||
}
|
||||
accountOnChain.pubkey = pubkey;
|
||||
accountOnChain!.pubkey = pubkey;
|
||||
}
|
||||
return accountOnChain;
|
||||
};
|
||||
|
||||
type Mutable<Type> = {
|
||||
-readonly [Key in keyof Type]: Type[Key];
|
||||
};
|
||||
|
||||
export { createMultisigFromCompressedSecp256k1Pubkeys, getMultisigAccount };
|
||||
Vendored
+5
@@ -0,0 +1,5 @@
|
||||
/// <reference types="next" />
|
||||
/// <reference types="next/image-types/global" />
|
||||
|
||||
// NOTE: This file should not be edited
|
||||
// see https://nextjs.org/docs/basic-features/typescript for more information.
|
||||
Generated
+556
-3512
File diff suppressed because it is too large
Load Diff
+10
-5
@@ -19,19 +19,24 @@
|
||||
"@ledgerhq/hw-transport-webusb": "^6.24.1",
|
||||
"axios": "^0.21.1",
|
||||
"copy-to-clipboard": "^3.3.1",
|
||||
"cosmjs-types": "^0.4.1",
|
||||
"faunadb": "^4.1.1",
|
||||
"next": "^10.0.8",
|
||||
"react": "^16.13.1",
|
||||
"react-dom": "^16.13.1",
|
||||
"next": "^12.1.6",
|
||||
"react": "^17.0.2",
|
||||
"react-dom": "^17.0.2",
|
||||
"react-select": "^5.2.2",
|
||||
"uuid": "^8.3.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^17.0.35",
|
||||
"@types/react": "^18.0.9",
|
||||
"@types/react-dom": "^18.0.5",
|
||||
"@types/react-select": "^5.0.1",
|
||||
"cosmjs-types": "^0.4.1",
|
||||
"eslint": "^8.6.0",
|
||||
"eslint-config-prettier": "^8.3.0",
|
||||
"eslint-plugin-prettier": "^4.0.0",
|
||||
"eslint-plugin-react": "^7.28.0",
|
||||
"prettier": "^2.5.1"
|
||||
"prettier": "^2.5.1",
|
||||
"typescript": "^4.7.2"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
import React from "react";
|
||||
import { AppWrapper } from "../context/AppContext";
|
||||
import ChainSelect from "../components/chainSelect/ChainSelect";
|
||||
import type { AppProps } from "next/app";
|
||||
|
||||
function MultisigApp({ Component, pageProps }) {
|
||||
function MultisigApp({ Component, pageProps }: AppProps) {
|
||||
return (
|
||||
<AppWrapper>
|
||||
{process.env.NEXT_PUBLIC_MULTICHAIN.toLowerCase() === "true" && <ChainSelect />}
|
||||
{process.env.NEXT_PUBLIC_MULTICHAIN!.toLowerCase() === "true" && <ChainSelect />}
|
||||
<Component {...pageProps} />
|
||||
</AppWrapper>
|
||||
);
|
||||
+5
-3
@@ -1,10 +1,12 @@
|
||||
import type { NextApiRequest, NextApiResponse } from "next";
|
||||
import { getMultisig } from "../../../../../../lib/graphqlHelpers";
|
||||
|
||||
export default async function (req, res) {
|
||||
export default async function (req: NextApiRequest, res: NextApiResponse) {
|
||||
switch (req.method) {
|
||||
case "GET":
|
||||
try {
|
||||
const { multisigAddress, chainId } = req.query;
|
||||
const multisigAddress = req.query.multisigAddress.toString();
|
||||
const chainId = req.query.multisigAddress.toString();
|
||||
console.log("Function `getMultisig` invoked", multisigAddress, chainId);
|
||||
const getRes = await getMultisig(multisigAddress, chainId);
|
||||
if (!getRes.data.data.getMultisig) {
|
||||
@@ -14,7 +16,7 @@ export default async function (req, res) {
|
||||
console.log("success", getRes.data.data.getMultisig);
|
||||
res.status(200).send(getRes.data.data.getMultisig);
|
||||
return;
|
||||
} catch (err) {
|
||||
} catch (err: any) {
|
||||
console.log(err);
|
||||
res.status(400).send(err.message);
|
||||
return;
|
||||
+3
-2
@@ -1,6 +1,7 @@
|
||||
import type { NextApiRequest, NextApiResponse } from "next";
|
||||
import { createMultisig } from "../../../../../lib/graphqlHelpers";
|
||||
|
||||
export default async function (req, res) {
|
||||
export default async function (req: NextApiRequest, res: NextApiResponse) {
|
||||
switch (req.method) {
|
||||
case "POST":
|
||||
try {
|
||||
@@ -10,7 +11,7 @@ export default async function (req, res) {
|
||||
console.log("success", saveRes.data);
|
||||
res.status(200).send(saveRes.data.data.createMultisig);
|
||||
return;
|
||||
} catch (err) {
|
||||
} catch (err: any) {
|
||||
console.log(err);
|
||||
res.status(400).send(err.message);
|
||||
return;
|
||||
+4
-3
@@ -1,17 +1,18 @@
|
||||
import type { NextApiRequest, NextApiResponse } from "next";
|
||||
import { updateTxHash } from "../../../../lib/graphqlHelpers";
|
||||
|
||||
export default async function (req, res) {
|
||||
export default async function (req: NextApiRequest, res: NextApiResponse) {
|
||||
switch (req.method) {
|
||||
case "POST":
|
||||
try {
|
||||
const { transactionID } = req.query;
|
||||
const transactionID = req.query.transactionID.toString();
|
||||
const { txHash } = req.body;
|
||||
console.log("Function `updateTransaction` invoked", txHash);
|
||||
const saveRes = await updateTxHash(transactionID, txHash);
|
||||
console.log("success", saveRes.data);
|
||||
res.status(200).send(saveRes.data.data.updateTransaction);
|
||||
return;
|
||||
} catch (err) {
|
||||
} catch (err: any) {
|
||||
console.log(err);
|
||||
res.status(400).send(err.message);
|
||||
return;
|
||||
+4
-3
@@ -1,17 +1,18 @@
|
||||
import type { NextApiRequest, NextApiResponse } from "next";
|
||||
import { createSignature } from "../../../../lib/graphqlHelpers";
|
||||
|
||||
export default async function (req, res) {
|
||||
export default async function (req: NextApiRequest, res: NextApiResponse) {
|
||||
switch (req.method) {
|
||||
case "POST":
|
||||
try {
|
||||
const { transactionID } = req.query;
|
||||
const transactionID = req.query.transactionID.toString();
|
||||
const data = req.body;
|
||||
console.log("Function `createSignature` invoked", data);
|
||||
const saveRes = await createSignature(data, transactionID);
|
||||
console.log("success", saveRes.data);
|
||||
res.status(200).send(saveRes.data.data.createSignature);
|
||||
return;
|
||||
} catch (err) {
|
||||
} catch (err: any) {
|
||||
console.log(err);
|
||||
res.status(400).send(err.message);
|
||||
return;
|
||||
@@ -1,6 +1,7 @@
|
||||
import { createTransaction } from "../../../lib/graphqlHelpers";
|
||||
import type { NextApiRequest, NextApiResponse } from "next";
|
||||
|
||||
export default async function (req, res) {
|
||||
export default async function (req: NextApiRequest, res: NextApiResponse) {
|
||||
switch (req.method) {
|
||||
case "POST":
|
||||
try {
|
||||
@@ -10,7 +11,7 @@ export default async function (req, res) {
|
||||
console.log("success", saveRes.data);
|
||||
res.status(200).send({ transactionID: saveRes.data.data.createTransaction._id });
|
||||
return;
|
||||
} catch (err) {
|
||||
} catch (err: any) {
|
||||
console.log(err);
|
||||
res.status(400).send(err.message);
|
||||
return;
|
||||
@@ -1,6 +1,8 @@
|
||||
import React, { useState, useEffect } from "react";
|
||||
import { pubkeyToAddress } from "@cosmjs/amino";
|
||||
import { pubkeyToAddress, Pubkey } from "@cosmjs/amino";
|
||||
import { Account } from "@cosmjs/stargate";
|
||||
import { StargateClient } from "@cosmjs/stargate";
|
||||
import { Coin } from "cosmjs-types/cosmos/base/v1beta1/coin";
|
||||
import { useRouter } from "next/router";
|
||||
|
||||
import { useAppContext } from "../../../context/AppContext";
|
||||
@@ -13,39 +15,44 @@ import Page from "../../../components/layout/Page";
|
||||
import StackableContainer from "../../../components/layout/StackableContainer";
|
||||
import TransactionForm from "../../../components/forms/TransactionForm";
|
||||
|
||||
function participantPubkeysFromMultisig(multisigPubkey) {
|
||||
function participantPubkeysFromMultisig(multisigPubkey: Pubkey) {
|
||||
return multisigPubkey.value.pubkeys;
|
||||
}
|
||||
|
||||
function participantAddressesFromMultisig(multisigPubkey, addressPrefix) {
|
||||
return participantPubkeysFromMultisig(multisigPubkey).map((p) =>
|
||||
function participantAddressesFromMultisig(multisigPubkey: Pubkey, addressPrefix: string) {
|
||||
return participantPubkeysFromMultisig(multisigPubkey).map((p: Pubkey) =>
|
||||
pubkeyToAddress(p, addressPrefix),
|
||||
);
|
||||
}
|
||||
|
||||
const multipage = (_props) => {
|
||||
interface Props {}
|
||||
|
||||
const multipage = (_props: Props) => {
|
||||
const { state } = useAppContext();
|
||||
const [showTxForm, setShowTxForm] = useState(false);
|
||||
const [holdings, setHoldings] = useState("");
|
||||
const [accountOnChain, setAccountOnChain] = useState(null);
|
||||
const [holdings, setHoldings] = useState<Coin | null>(null);
|
||||
const [multisigAddress, setMultisigAddress] = useState("");
|
||||
const [accountOnChain, setAccountOnChain] = useState<Account | null>(null);
|
||||
const [accountError, setAccountError] = useState(null);
|
||||
const router = useRouter();
|
||||
|
||||
useEffect(() => {
|
||||
if (router.query.address) {
|
||||
fetchMultisig(router.query.address);
|
||||
const address = router.query.address?.toString();
|
||||
if (address) {
|
||||
setMultisigAddress(address);
|
||||
fetchMultisig(address);
|
||||
}
|
||||
}, [state, router.query.address]);
|
||||
|
||||
const fetchMultisig = async (address) => {
|
||||
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);
|
||||
const client = await StargateClient.connect(state!.chain.nodeAddress!);
|
||||
const tempHoldings = await client.getBalance(address, state!.chain.denom!);
|
||||
const tempAccountOnChain = await getMultisigAccount(address, client);
|
||||
setHoldings(tempHoldings);
|
||||
setAccountOnChain(tempAccountOnChain);
|
||||
} catch (error) {
|
||||
} catch (error: any) {
|
||||
setAccountError(error.message);
|
||||
console.log("Account error:", error);
|
||||
}
|
||||
@@ -57,14 +64,18 @@ const multipage = (_props) => {
|
||||
<StackableContainer>
|
||||
<label>Multisig Address</label>
|
||||
<h1>
|
||||
<HashView hash={router.query.address} />
|
||||
{router.query.address ? (
|
||||
<HashView hash={router.query.address?.toString()} />
|
||||
) : (
|
||||
"No Address"
|
||||
)}
|
||||
</h1>
|
||||
</StackableContainer>
|
||||
{accountOnChain?.pubkey && (
|
||||
<MultisigMembers
|
||||
members={participantAddressesFromMultisig(
|
||||
accountOnChain?.pubkey,
|
||||
state.chain.addressPrefix,
|
||||
state!.chain.addressPrefix!,
|
||||
)}
|
||||
threshold={accountOnChain?.pubkey.value.threshold}
|
||||
/>
|
||||
@@ -86,7 +97,7 @@ const multipage = (_props) => {
|
||||
)}
|
||||
{showTxForm ? (
|
||||
<TransactionForm
|
||||
address={router.query.address}
|
||||
address={multisigAddress}
|
||||
accountOnChain={accountOnChain}
|
||||
closeForm={() => {
|
||||
setShowTxForm(false);
|
||||
+42
-22
@@ -1,11 +1,15 @@
|
||||
import React from "react";
|
||||
import axios from "axios";
|
||||
import { StargateClient, makeMultisignedTx } from "@cosmjs/stargate";
|
||||
import React from "react";
|
||||
import { GetServerSideProps } from "next";
|
||||
import { StargateClient, makeMultisignedTx, Account } 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";
|
||||
import { useRouter } from "next/router";
|
||||
import { fromBase64 } from "@cosmjs/encoding";
|
||||
import { MultisigThresholdPubkey } from "@cosmjs/amino";
|
||||
|
||||
import { DbSignature, DbTransaction } from "../../../../types";
|
||||
import { useAppContext } from "../../../../context/AppContext";
|
||||
import Button from "../../../../components/inputs/Button";
|
||||
import { findTransactionByID } from "../../../../lib/graphqlHelpers";
|
||||
@@ -17,9 +21,18 @@ import TransactionInfo from "../../../../components/dataViews/TransactionInfo";
|
||||
import TransactionSigning from "../../../../components/forms/TransactionSigning";
|
||||
import CompletedTransaction from "../../../../components/dataViews/CompletedTransaction";
|
||||
|
||||
export async function getServerSideProps(context) {
|
||||
interface Props {
|
||||
props: {
|
||||
transactionJSON: string;
|
||||
transactionID: string;
|
||||
txHash: string;
|
||||
signatures: DbSignature[];
|
||||
};
|
||||
}
|
||||
|
||||
export const getServerSideProps: GetServerSideProps = async (context): Promise<Props> => {
|
||||
// get transaction info
|
||||
const transactionID = context.params.transactionID;
|
||||
const transactionID = context.params!.transactionID!.toString();
|
||||
let transactionJSON;
|
||||
let txHash;
|
||||
let signatures;
|
||||
@@ -30,7 +43,7 @@ export async function getServerSideProps(context) {
|
||||
txHash = getRes.data.data.findTransactionByID.txHash;
|
||||
transactionJSON = getRes.data.data.findTransactionByID.dataJSON;
|
||||
signatures = getRes.data.data.findTransactionByID.signatures.data || [];
|
||||
} catch (err) {
|
||||
} catch (err: any) {
|
||||
console.log(err);
|
||||
}
|
||||
return {
|
||||
@@ -41,7 +54,7 @@ export async function getServerSideProps(context) {
|
||||
signatures,
|
||||
},
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
const transactionPage = ({
|
||||
multisigAddress,
|
||||
@@ -49,36 +62,43 @@ const transactionPage = ({
|
||||
transactionID,
|
||||
signatures,
|
||||
txHash,
|
||||
}: {
|
||||
multisigAddress: string;
|
||||
transactionJSON: string;
|
||||
transactionID: string;
|
||||
signatures: DbSignature[];
|
||||
txHash: string;
|
||||
}) => {
|
||||
const { state } = useAppContext();
|
||||
const [currentSignatures, setCurrentSignatures] = useState(signatures);
|
||||
const [broadcastError, setBroadcastError] = useState("");
|
||||
const [isBroadcasting, setIsBroadcasting] = useState(false);
|
||||
const [transactionHash, setTransactionHash] = useState(txHash);
|
||||
const [_holdings, setHoldings] = useState("");
|
||||
const [accountOnChain, setAccountOnChain] = useState(null);
|
||||
const [_holdings, setHoldings] = useState<Coin | null>(null);
|
||||
const [accountOnChain, setAccountOnChain] = useState<Account | null>(null);
|
||||
const [accountError, setAccountError] = useState(null);
|
||||
const txInfo = (transactionJSON && JSON.parse(transactionJSON)) || null;
|
||||
const txInfo: DbTransaction = (transactionJSON && JSON.parse(transactionJSON)) || null;
|
||||
const router = useRouter();
|
||||
|
||||
const addSignature = (signature) => {
|
||||
setCurrentSignatures((prevState) => [...prevState, signature]);
|
||||
const addSignature = (signature: DbSignature) => {
|
||||
setCurrentSignatures((prevState: DbSignature[]) => [...prevState, signature]);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (router.query.address) {
|
||||
fetchMultisig(router.query.address);
|
||||
const address = router.query.address?.toString();
|
||||
if (address) {
|
||||
fetchMultisig(address);
|
||||
}
|
||||
}, [router.query.address]);
|
||||
}, [state, router.query.address]);
|
||||
|
||||
const fetchMultisig = async (address) => {
|
||||
const fetchMultisig = async (address: string) => {
|
||||
try {
|
||||
const client = await StargateClient.connect(state.chain.nodeAddress);
|
||||
const tempHoldings = await client.getBalance(address, state.chain.denom);
|
||||
const client = await StargateClient.connect(state!.chain.nodeAddress!);
|
||||
const tempHoldings = await client.getBalance(address, state!.chain.denom!);
|
||||
const tempAccountOnChain = await getMultisigAccount(address, client);
|
||||
setHoldings(tempHoldings);
|
||||
setAccountOnChain(tempAccountOnChain);
|
||||
} catch (error) {
|
||||
} catch (error: any) {
|
||||
setAccountError(error.message);
|
||||
console.log("Account error:", error);
|
||||
}
|
||||
@@ -91,13 +111,13 @@ const transactionPage = ({
|
||||
|
||||
const bodyBytes = fromBase64(currentSignatures[0].bodyBytes);
|
||||
const signedTx = makeMultisignedTx(
|
||||
accountOnChain.pubkey,
|
||||
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);
|
||||
const broadcaster = await StargateClient.connect(state!.chain.nodeAddress!);
|
||||
const result = await broadcaster.broadcastTx(
|
||||
Uint8Array.from(TxRaw.encode(signedTx).finish()),
|
||||
);
|
||||
@@ -106,7 +126,7 @@ const transactionPage = ({
|
||||
txHash: result.transactionHash,
|
||||
});
|
||||
setTransactionHash(result.transactionHash);
|
||||
} catch (e) {
|
||||
} catch (e: any) {
|
||||
setIsBroadcasting(false);
|
||||
setBroadcastError(e.message);
|
||||
}
|
||||
@@ -131,7 +151,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
|
||||
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "es2018",
|
||||
"lib": ["dom", "dom.iterable", "esnext"],
|
||||
"allowJs": true,
|
||||
"skipLibCheck": true,
|
||||
"strict": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"noEmit": true,
|
||||
"incremental": true,
|
||||
"esModuleInterop": true,
|
||||
"module": "esnext",
|
||||
"moduleResolution": "node",
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"jsx": "preserve"
|
||||
},
|
||||
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"],
|
||||
"exclude": ["node_modules"]
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
import { StdFee } from "@cosmjs/amino";
|
||||
import { EncodeObject } from "@cosmjs/proto-signing";
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
keplr: {
|
||||
enable: (chainId: string) => any;
|
||||
getKey: (chainId: string) => Promise<WalletAccount>;
|
||||
};
|
||||
getOfflineSignerOnlyAmino: any;
|
||||
}
|
||||
}
|
||||
|
||||
export interface DbSignature {
|
||||
bodyBytes: string;
|
||||
signature: string;
|
||||
address: string;
|
||||
}
|
||||
|
||||
export interface DbTransaction {
|
||||
accountNumber: number;
|
||||
sequence: number;
|
||||
chainId: string;
|
||||
msgs: EncodeObject[];
|
||||
fee: StdFee;
|
||||
memo: string;
|
||||
}
|
||||
|
||||
export interface DbAccount {
|
||||
address: string;
|
||||
pubkeyJSON: string;
|
||||
chainId: string;
|
||||
}
|
||||
|
||||
export interface WalletAccount {
|
||||
address?: Uint8Array;
|
||||
pubkey: Uint8Array;
|
||||
algo: string;
|
||||
bech32Address: string;
|
||||
isNanoLedger?: boolean;
|
||||
name?: string;
|
||||
}
|
||||
|
||||
export interface ChainInfo {
|
||||
nodeAddress?: string;
|
||||
denom?: string;
|
||||
displayDenom?: string;
|
||||
displayDenomExponent?: number;
|
||||
gasPrice?: string;
|
||||
chainId?: string;
|
||||
chainDisplayName?: string;
|
||||
registryName?: string;
|
||||
addressPrefix?: string;
|
||||
explorerLink?: string;
|
||||
}
|
||||
Reference in New Issue
Block a user