Merge branch 'master' into feat/add-cw-msgs
This commit is contained in:
commit
cc1fbc225d
@ -4,6 +4,7 @@ NEXT_PUBLIC_NODE_ADDRESS=https://cosmoshub.validator.network:443
|
||||
NEXT_PUBLIC_DENOM=uatom
|
||||
NEXT_PUBLIC_DISPLAY_DENOM=ATOM
|
||||
NEXT_PUBLIC_DISPLAY_DENOM_EXPONENT=6
|
||||
NEXT_PUBLIC_ASSETS=[{"description":"The native staking and governance token of the Cosmos Hub.","denom_units":[{"denom":"uatom","exponent":0},{"denom":"atom","exponent":6}],"base":"uatom","name":"Cosmos Hub Atom","display":"atom","symbol":"ATOM","logo_URIs":{"png":"https://raw.githubusercontent.com/cosmos/chain-registry/master/cosmoshub/images/atom.png","svg":"https://raw.githubusercontent.com/cosmos/chain-registry/master/cosmoshub/images/atom.svg"},"coingecko_id":"cosmos"}]
|
||||
NEXT_PUBLIC_GAS_PRICE=0.03uatom
|
||||
NEXT_PUBLIC_CHAIN_ID=cosmoshub-4
|
||||
NEXT_PUBLIC_ADDRESS_PREFIX=cosmos
|
||||
|
||||
@ -4,6 +4,7 @@ NEXT_PUBLIC_NODE_ADDRESS=https://rpc.uni.junonetwork.io:443
|
||||
NEXT_PUBLIC_DENOM=ujunox
|
||||
NEXT_PUBLIC_DISPLAY_DENOM=JUNOX
|
||||
NEXT_PUBLIC_DISPLAY_DENOM_EXPONENT=6
|
||||
NEXT_PUBLIC_ASSETS=[{"description":"The native token of JUNO Chain","denom_units":[{"denom":"ujunox","exponent":0},{"denom":"junox","exponent":6}],"base":"ujunox","name":"Juno Testnet","display":"junox","symbol":"JUNOX","logo_URIs":{"png":"https://raw.githubusercontent.com/cosmos/chain-registry/master/testnets/junotestnet/images/juno.png","svg":"https://raw.githubusercontent.com/cosmos/chain-registry/master/testnets/junotestnet/images/juno.svg"},"coingecko_id":"juno-network"}]
|
||||
NEXT_PUBLIC_GAS_PRICE=0.04ujunox
|
||||
NEXT_PUBLIC_CHAIN_ID=uni-6
|
||||
NEXT_PUBLIC_ADDRESS_PREFIX=juno
|
||||
|
||||
@ -1,281 +1,97 @@
|
||||
import { StargateClient } from "@cosmjs/stargate";
|
||||
import { assert } from "@cosmjs/utils";
|
||||
import axios from "axios";
|
||||
import { useRouter } from "next/router";
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
import { useAppContext } from "../../context/AppContext";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useChains } from "../../context/ChainsContext";
|
||||
import {
|
||||
setChain,
|
||||
setChainFromRegistry,
|
||||
setChainsError,
|
||||
} from "../../context/ChainsContext/helpers";
|
||||
import { RegistryAsset } from "../../types/chainRegistry";
|
||||
import GearIcon from "../icons/Gear";
|
||||
import Button from "../inputs/Button";
|
||||
import Input from "../inputs/Input";
|
||||
import Select from "../inputs/Select";
|
||||
import StackableContainer from "../layout/StackableContainer";
|
||||
import {
|
||||
RegistryChainApisRpc,
|
||||
RegistryChainExplorer,
|
||||
getAssetsFromRegistry,
|
||||
getChainFromRegistry,
|
||||
} from "./chainregistry";
|
||||
|
||||
interface ChainOption {
|
||||
label: string;
|
||||
value: number;
|
||||
readonly label: string;
|
||||
readonly value: string;
|
||||
}
|
||||
|
||||
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 chainsUrl = "https://api.github.com/repos/cosmos/chain-registry/contents";
|
||||
const testnetsUrl = "https://api.github.com/repos/cosmos/chain-registry/contents/testnets";
|
||||
|
||||
const ChainSelect = () => {
|
||||
const router = useRouter();
|
||||
const { state, dispatch } = useAppContext();
|
||||
const { chain, chains, chainsError, chainsDispatch } = useChains();
|
||||
|
||||
// UI State
|
||||
const [chainArray, setChainArray] = useState<GithubChainRegistryItem[]>([]);
|
||||
const [chainOptions, setChainOptions] = useState<ChainOption[]>([]);
|
||||
const [chainError, setChainError] = useState<string | null>(null);
|
||||
const [optionToConfirm, setOptionToConfirm] = useState<ChainOption | null>(null);
|
||||
const [showAuxView, setShowAuxView] = useState<null | "settings" | "confirmRedirect">(null);
|
||||
const [storedOption, setStoredOption] = useState<ChainOption | null>(null);
|
||||
const [selectValue, setSelectValue] = useState({ label: "Loading...", value: -1 });
|
||||
const [chainInForm, setChainInForm] = useState(chain);
|
||||
const [stringAssets, setStringAssets] = useState(JSON.stringify(chain.assets));
|
||||
|
||||
// Chain State
|
||||
const [tempChainId, setChainId] = useState(state.chain.chainId);
|
||||
const [tempNodeAddress, setNodeAddress] = useState(state.chain.nodeAddress);
|
||||
const [tempAddressPrefix, setAddressPrefix] = useState(state.chain.addressPrefix);
|
||||
const [tempDenom, setDenom] = useState(state.chain.denom);
|
||||
const [tempDisplayDenom, setDisplayDenom] = useState(state.chain.displayDenom);
|
||||
const [tempDisplayDenomExponent, setDisplayDenomExponent] = useState(
|
||||
state.chain.displayDenomExponent,
|
||||
);
|
||||
const [tempAssets, setAssets] = useState(state.chain.assets);
|
||||
const [tempGasPrice, setGasPrice] = useState(state.chain.gasPrice);
|
||||
const [tempChainName, setChainName] = useState(state.chain.chainDisplayName);
|
||||
const [tempRegistryName, setRegistryName] = useState(state.chain.registryName);
|
||||
const [tempExplorerLink, setExplorerLink] = useState(state.chain.explorerLink);
|
||||
|
||||
const getGhJson = useCallback(async () => {
|
||||
// getting chain info from this repo: https://github.com/cosmos/chain-registry
|
||||
try {
|
||||
const [{ data: chains }, { data: testnets }] = await Promise.all([
|
||||
axios.get(chainsUrl),
|
||||
axios.get(testnetsUrl),
|
||||
]);
|
||||
|
||||
const allChains: GithubChainRegistryItem[] = [...chains, ...testnets].filter(
|
||||
(item: GithubChainRegistryItem) => {
|
||||
return item.type == "dir" && !item.name.startsWith(".") && !item.name.startsWith("_");
|
||||
},
|
||||
);
|
||||
setChainArray(allChains);
|
||||
|
||||
const options = allChains.map(({ name }: GithubChainRegistryItem, index: number) => ({
|
||||
label: name,
|
||||
value: index,
|
||||
}));
|
||||
setChainOptions(options);
|
||||
|
||||
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);
|
||||
setShowAuxView("settings");
|
||||
setChainError(error.message);
|
||||
}
|
||||
}, [state.chain.registryName]);
|
||||
const chainArray = [...chains.mainnets, ...chains.testnets];
|
||||
const chainOptions: readonly ChainOption[] = chainArray.map(({ name }) => ({
|
||||
label: name,
|
||||
value: name,
|
||||
}));
|
||||
const selectValue = chainOptions.find((option) => option.value === chain.registryName) ?? {
|
||||
label: "unknown chain",
|
||||
value: chain.registryName,
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
getGhJson();
|
||||
}, [getGhJson]);
|
||||
setChainInForm(chain);
|
||||
setStringAssets(JSON.stringify(chain.assets));
|
||||
}, [chain]);
|
||||
|
||||
useEffect(() => {
|
||||
// set settings form fields to new values
|
||||
setChainId(state.chain.chainId);
|
||||
setNodeAddress(state.chain.nodeAddress);
|
||||
setAddressPrefix(state.chain.addressPrefix);
|
||||
setDenom(state.chain.denom);
|
||||
setDisplayDenom(state.chain.displayDenom);
|
||||
setDisplayDenomExponent(state.chain.displayDenomExponent);
|
||||
setAssets(state.chain.assets);
|
||||
setGasPrice(state.chain.gasPrice);
|
||||
setChainName(state.chain.chainDisplayName);
|
||||
setExplorerLink(state.chain.explorerLink);
|
||||
setRegistryName(state.chain.registryName);
|
||||
}, [state]);
|
||||
|
||||
const findExistingOption = (options: ChainOption[], registryName: string) => {
|
||||
const index = options.findIndex((option) => option.label === registryName);
|
||||
if (index >= 0) {
|
||||
return options[index];
|
||||
}
|
||||
return {
|
||||
label:
|
||||
registryName === process.env.NEXT_PUBLIC_REGISTRY_NAME ? registryName : "unknown chain",
|
||||
value: -1,
|
||||
};
|
||||
};
|
||||
|
||||
const getChainInfo = async (chainOption: GithubChainRegistryItem) => {
|
||||
setChainError(null);
|
||||
|
||||
try {
|
||||
const chainData = await getChainFromRegistry(chainOption.path);
|
||||
const registryAssets = await getAssetsFromRegistry(chainOption.path);
|
||||
assert(registryAssets.length >= 1, "No assets found in registry");
|
||||
const firstAsset = registryAssets[0];
|
||||
|
||||
const nodeAddress = await getNodeFromArray(chainData.apis.rpc);
|
||||
const explorerLink = getExplorerFromArray(chainData.explorers);
|
||||
const firstAssetDenom = firstAsset.base;
|
||||
const displayDenom = firstAsset.symbol;
|
||||
const displayUnit = firstAsset.denom_units.find((u) => u.denom == firstAsset.display);
|
||||
const displayDenomExponent = displayUnit?.exponent ?? 6;
|
||||
|
||||
const feeToken = chainData.fees.fee_tokens.find(
|
||||
(token) => token.denom == firstAssetDenom,
|
||||
) ?? { denom: firstAssetDenom };
|
||||
const gasPrice =
|
||||
feeToken.average_gas_price ??
|
||||
feeToken.low_gas_price ??
|
||||
feeToken.high_gas_price ??
|
||||
feeToken.fixed_min_gas_price ??
|
||||
0.03;
|
||||
const formattedGasPrice = firstAsset ? `${gasPrice}${firstAssetDenom}` : "";
|
||||
|
||||
// change app state
|
||||
dispatch({
|
||||
type: "changeChain",
|
||||
value: {
|
||||
registryName: chainOption.name,
|
||||
addressPrefix: chainData.bech32_prefix,
|
||||
chainId: chainData.chain_id,
|
||||
chainDisplayName: chainData.pretty_name,
|
||||
nodeAddress,
|
||||
explorerLink,
|
||||
denom: firstAssetDenom,
|
||||
displayDenom,
|
||||
displayDenomExponent,
|
||||
gasPrice: formattedGasPrice,
|
||||
assets: registryAssets,
|
||||
},
|
||||
});
|
||||
|
||||
setShowAuxView(null);
|
||||
} catch (error) {
|
||||
if (error instanceof Error) {
|
||||
setChainError(error.message);
|
||||
} else {
|
||||
setChainError("Error getting chain info");
|
||||
}
|
||||
|
||||
console.error("Error getting chain info", error);
|
||||
setShowAuxView("settings");
|
||||
const assets: readonly RegistryAsset[] = JSON.parse(stringAssets);
|
||||
setChainInForm((oldChain) => ({ ...oldChain, assets }));
|
||||
} catch {
|
||||
setChainsError(chainsDispatch, "Assets needs to be valid JSON");
|
||||
}
|
||||
};
|
||||
}, [chainsDispatch, stringAssets]);
|
||||
|
||||
const getExplorerFromArray = (explorers: readonly RegistryChainExplorer[]) => {
|
||||
return explorers[0]?.tx_page ?? "";
|
||||
};
|
||||
|
||||
const getNodeFromArray = async (nodeArray: readonly RegistryChainApisRpc[]) => {
|
||||
// only return https connections
|
||||
const secureNodes = nodeArray
|
||||
.filter((node) => node.address.startsWith("https://"))
|
||||
.map(({ address }) => address);
|
||||
|
||||
if (secureNodes.length === 0) {
|
||||
throw new Error("No SSL enabled RPC nodes available for this chain");
|
||||
}
|
||||
|
||||
for (const node of secureNodes) {
|
||||
try {
|
||||
// test client connection
|
||||
const client = await StargateClient.connect(node);
|
||||
await client.getHeight();
|
||||
return node;
|
||||
} catch {}
|
||||
}
|
||||
|
||||
throw new Error("No RPC nodes available for this chain");
|
||||
};
|
||||
|
||||
const changeChain = (option: ChainOption) => {
|
||||
const index = chainOptions.findIndex((opt) => opt.label === option.label);
|
||||
setSelectValue(chainOptions[index]);
|
||||
getChainInfo(chainArray[option.value]);
|
||||
};
|
||||
|
||||
const onChainSelect = (option: ChainOption) => {
|
||||
if (router.pathname !== "/" && option.label !== selectValue.label) {
|
||||
setStoredOption(option);
|
||||
const selectChainOption = (chainOption: ChainOption) => {
|
||||
if (router.pathname !== "/" && chainOption.value !== selectValue.value) {
|
||||
setOptionToConfirm(chainOption);
|
||||
setShowAuxView("confirmRedirect");
|
||||
return;
|
||||
}
|
||||
|
||||
changeChain(option);
|
||||
setStoredOption(null);
|
||||
setChainsError(chainsDispatch, null);
|
||||
setChainFromRegistry(chainsDispatch, chainOption.value);
|
||||
setOptionToConfirm(null);
|
||||
};
|
||||
|
||||
const redirectAndChangeChain = () => {
|
||||
setShowAuxView(null);
|
||||
|
||||
if (storedOption) {
|
||||
changeChain(storedOption);
|
||||
setStoredOption(null);
|
||||
if (optionToConfirm) {
|
||||
setChainFromRegistry(chainsDispatch, optionToConfirm.value);
|
||||
setOptionToConfirm(null);
|
||||
}
|
||||
|
||||
router.push("/");
|
||||
};
|
||||
|
||||
const setChainFromForm = async () => {
|
||||
setChainError(null);
|
||||
setChainsError(chainsDispatch, null);
|
||||
|
||||
try {
|
||||
// test client connection
|
||||
assert(tempNodeAddress, "tempNodeAddress missing");
|
||||
const client = await StargateClient.connect(tempNodeAddress);
|
||||
const client = await StargateClient.connect(chainInForm.nodeAddress);
|
||||
await client.getHeight();
|
||||
|
||||
// change app state
|
||||
dispatch({
|
||||
type: "changeChain",
|
||||
value: {
|
||||
nodeAddress: tempNodeAddress,
|
||||
denom: tempDenom,
|
||||
displayDenom: tempDisplayDenom,
|
||||
displayDenomExponent: tempDisplayDenomExponent,
|
||||
assets: tempAssets,
|
||||
gasPrice: tempGasPrice,
|
||||
chainId: tempChainId,
|
||||
chainDisplayName: tempChainName,
|
||||
registryName: tempRegistryName,
|
||||
addressPrefix: tempAddressPrefix,
|
||||
explorerLink: tempExplorerLink,
|
||||
},
|
||||
});
|
||||
assert(tempRegistryName, "tempRegistryName missing");
|
||||
const selectedOption = findExistingOption(chainOptions, tempRegistryName);
|
||||
setSelectValue(selectedOption);
|
||||
setShowAuxView(null);
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
} catch (error: any) {
|
||||
console.log(error);
|
||||
setChain(chainsDispatch, chainInForm);
|
||||
} catch (error) {
|
||||
if (error instanceof Error) {
|
||||
setChainsError(chainsDispatch, error.message);
|
||||
} else {
|
||||
setChainsError(chainsDispatch, "Error when setting new chain");
|
||||
}
|
||||
setShowAuxView("settings");
|
||||
setChainError(error.message);
|
||||
}
|
||||
};
|
||||
|
||||
@ -286,10 +102,10 @@ const ChainSelect = () => {
|
||||
<div className="flex">
|
||||
<div className="select-parent">
|
||||
<Select
|
||||
options={chainOptions}
|
||||
onChange={onChainSelect}
|
||||
value={selectValue}
|
||||
name="chain-select"
|
||||
options={chainOptions}
|
||||
value={selectValue}
|
||||
onChange={selectChainOption}
|
||||
/>
|
||||
</div>
|
||||
{showAuxView ? (
|
||||
@ -297,7 +113,7 @@ const ChainSelect = () => {
|
||||
className="remove"
|
||||
onClick={() => {
|
||||
setShowAuxView(null);
|
||||
setStoredOption(null);
|
||||
setOptionToConfirm(null);
|
||||
}}
|
||||
>
|
||||
✕
|
||||
@ -310,40 +126,42 @@ const ChainSelect = () => {
|
||||
</div>
|
||||
{showAuxView === "settings" ? (
|
||||
<>
|
||||
{chainError && <p className="error">{chainError}</p>}
|
||||
{chainsError ? <p className="error">{chainsError}</p> : null}
|
||||
<StackableContainer lessPadding lessMargin lessRadius>
|
||||
<p>Settings</p>
|
||||
<div className="settings-group">
|
||||
<Input
|
||||
width="48%"
|
||||
value={tempChainName}
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
|
||||
setChainName(e.target.value)
|
||||
value={chainInForm.chainDisplayName}
|
||||
onChange={({ target }) =>
|
||||
setChainInForm((oldChain) => ({ ...oldChain, chainDisplayName: target.value }))
|
||||
}
|
||||
label="Chain Name"
|
||||
/>
|
||||
|
||||
<Input
|
||||
width="48%"
|
||||
value={tempChainId}
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => setChainId(e.target.value)}
|
||||
value={chainInForm.chainId}
|
||||
onChange={({ target }) =>
|
||||
setChainInForm((oldChain) => ({ ...oldChain, chainId: target.value }))
|
||||
}
|
||||
label="Chain ID"
|
||||
/>
|
||||
</div>
|
||||
<div className="settings-group">
|
||||
<Input
|
||||
width="48%"
|
||||
value={tempAddressPrefix}
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
|
||||
setAddressPrefix(e.target.value)
|
||||
value={chainInForm.addressPrefix}
|
||||
onChange={({ target }) =>
|
||||
setChainInForm((oldChain) => ({ ...oldChain, addressPrefix: target.value }))
|
||||
}
|
||||
label="Bech32 Prefix (address prefix)"
|
||||
/>
|
||||
<Input
|
||||
width="48%"
|
||||
value={tempNodeAddress}
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
|
||||
setNodeAddress(e.target.value)
|
||||
value={chainInForm.nodeAddress}
|
||||
onChange={({ target }) =>
|
||||
setChainInForm((oldChain) => ({ ...oldChain, nodeAddress: target.value }))
|
||||
}
|
||||
label="RPC Node URL (must be https)"
|
||||
/>
|
||||
@ -351,49 +169,54 @@ const ChainSelect = () => {
|
||||
<div className="settings-group">
|
||||
<Input
|
||||
width="48%"
|
||||
value={tempDisplayDenom}
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
|
||||
setDisplayDenom(e.target.value)
|
||||
value={chainInForm.displayDenom}
|
||||
onChange={({ target }) =>
|
||||
setChainInForm((oldChain) => ({ ...oldChain, displayDenom: target.value }))
|
||||
}
|
||||
label="Display Denom"
|
||||
/>
|
||||
<Input
|
||||
width="48%"
|
||||
value={tempDenom}
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => setDenom(e.target.value)}
|
||||
value={chainInForm.denom}
|
||||
onChange={({ target }) =>
|
||||
setChainInForm((oldChain) => ({ ...oldChain, denom: target.value }))
|
||||
}
|
||||
label="Base Denom"
|
||||
/>
|
||||
</div>
|
||||
<div className="settings-group">
|
||||
<Input
|
||||
width="48%"
|
||||
value={tempDisplayDenomExponent}
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
|
||||
setDisplayDenomExponent(parseInt(e.target.value, 10))
|
||||
value={chainInForm.displayDenomExponent}
|
||||
onChange={({ target }) =>
|
||||
setChainInForm((oldChain) => ({
|
||||
...oldChain,
|
||||
displayDenomExponent: Number(target.value),
|
||||
}))
|
||||
}
|
||||
label="Denom Exponent"
|
||||
/>
|
||||
<Input
|
||||
width="48%"
|
||||
value={JSON.stringify(tempAssets)}
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
|
||||
setAssets(JSON.parse(e.target.value))
|
||||
}
|
||||
value={stringAssets}
|
||||
onChange={({ target }) => setStringAssets(target.value)}
|
||||
label="Assets"
|
||||
/>
|
||||
</div>
|
||||
<div className="settings-group">
|
||||
<Input
|
||||
width="48%"
|
||||
value={tempGasPrice}
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => setGasPrice(e.target.value)}
|
||||
value={chainInForm.gasPrice}
|
||||
onChange={({ target }) =>
|
||||
setChainInForm((oldChain) => ({ ...oldChain, gasPrice: target.value }))
|
||||
}
|
||||
label="Gas Price"
|
||||
/>
|
||||
<Input
|
||||
width="48%"
|
||||
value={tempExplorerLink}
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
|
||||
setExplorerLink(e.target.value)
|
||||
value={chainInForm.explorerLink}
|
||||
onChange={({ target }) =>
|
||||
setChainInForm((oldChain) => ({ ...oldChain, explorerLink: target.value }))
|
||||
}
|
||||
label="Explorer Link (with '${txHash}' included)"
|
||||
/>
|
||||
@ -402,13 +225,13 @@ const ChainSelect = () => {
|
||||
</StackableContainer>
|
||||
</>
|
||||
) : null}
|
||||
{showAuxView === "confirmRedirect" && storedOption ? (
|
||||
{showAuxView === "confirmRedirect" && optionToConfirm ? (
|
||||
<StackableContainer lessPadding lessMargin lessRadius>
|
||||
<p>
|
||||
If you change to {storedOption.label} your unsaved changes will be lost and you will
|
||||
be redirected to the main screen
|
||||
If you change to {optionToConfirm.label} your unsaved changes will be lost and you
|
||||
will be redirected to the main screen
|
||||
</p>
|
||||
<Button label={`Change to ${storedOption.label}`} onClick={redirectAndChangeChain} />
|
||||
<Button label={`Change to ${optionToConfirm.label}`} onClick={redirectAndChangeChain} />
|
||||
</StackableContainer>
|
||||
) : null}
|
||||
</StackableContainer>
|
||||
|
||||
@ -1,16 +1,16 @@
|
||||
import { useAppContext } from "../../context/AppContext";
|
||||
import { useChains } from "../../context/ChainsContext";
|
||||
import { explorerLinkTx } from "../../lib/displayHelpers";
|
||||
import Button from "../inputs/Button";
|
||||
import StackableContainer from "../layout/StackableContainer";
|
||||
import HashView from "./HashView";
|
||||
|
||||
interface Props {
|
||||
transactionHash: string;
|
||||
interface CompletedTransactionProps {
|
||||
readonly transactionHash: string;
|
||||
}
|
||||
|
||||
const CompletedTransaction = ({ transactionHash }: Props) => {
|
||||
const { state } = useAppContext();
|
||||
const baseURL = state.chain.explorerLink ? state.chain.explorerLink : "";
|
||||
const CompletedTransaction = ({ transactionHash }: CompletedTransactionProps) => {
|
||||
const { chain } = useChains();
|
||||
const baseURL = chain.explorerLink ? chain.explorerLink : "";
|
||||
const explorerLink = explorerLinkTx(baseURL, transactionHash);
|
||||
return (
|
||||
<StackableContainer lessPadding lessMargin>
|
||||
|
||||
@ -1,14 +1,14 @@
|
||||
import { Coin } from "cosmjs-types/cosmos/base/v1beta1/coin";
|
||||
import { useAppContext } from "../../context/AppContext";
|
||||
import { useChains } from "../../context/ChainsContext";
|
||||
import { printableCoin } from "../../lib/displayHelpers";
|
||||
import StackableContainer from "../layout/StackableContainer";
|
||||
|
||||
interface Props {
|
||||
holdings: readonly Coin[];
|
||||
interface MultisigHoldingsProps {
|
||||
readonly holdings: readonly Coin[];
|
||||
}
|
||||
|
||||
const MultisigHoldings = (props: Props) => {
|
||||
const { state } = useAppContext();
|
||||
const MultisigHoldings = (props: MultisigHoldingsProps) => {
|
||||
const { chain } = useChains();
|
||||
return (
|
||||
<StackableContainer lessPadding fullHeight>
|
||||
<h2>Holdings</h2>
|
||||
@ -16,7 +16,7 @@ const MultisigHoldings = (props: Props) => {
|
||||
{props.holdings.length ? (
|
||||
props.holdings.map((holding) => (
|
||||
<StackableContainer key={holding.denom} lessPadding lessMargin>
|
||||
<span>{printableCoin(holding, state.chain)}</span>
|
||||
<span>{printableCoin(holding, chain)}</span>
|
||||
</StackableContainer>
|
||||
))
|
||||
) : (
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { MsgCreateVestingAccount } from "cosmjs-types/cosmos/vesting/v1beta1/tx";
|
||||
import { useAppContext } from "../../../context/AppContext";
|
||||
import { useChains } from "../../../context/ChainsContext";
|
||||
import { printableCoins } from "../../../lib/displayHelpers";
|
||||
import HashView from "../HashView";
|
||||
|
||||
@ -8,7 +8,7 @@ interface TxMsgCreateVestingAccountDetailsProps {
|
||||
}
|
||||
|
||||
const TxMsgCreateVestingAccountDetails = ({ msgValue }: TxMsgCreateVestingAccountDetailsProps) => {
|
||||
const { state } = useAppContext();
|
||||
const { chain } = useChains();
|
||||
const endTimeDateObj = new Date(msgValue.endTime.multiply(1000).toNumber());
|
||||
const endTimeDate = endTimeDateObj.toLocaleDateString();
|
||||
const endTimeHours = endTimeDateObj.toLocaleTimeString().slice(0, -3);
|
||||
@ -20,7 +20,7 @@ const TxMsgCreateVestingAccountDetails = ({ msgValue }: TxMsgCreateVestingAccoun
|
||||
</li>
|
||||
<li>
|
||||
<label>Amount:</label>
|
||||
<div>{printableCoins(msgValue.amount, state.chain)}</div>
|
||||
<div>{printableCoins(msgValue.amount, chain)}</div>
|
||||
</li>
|
||||
<li>
|
||||
<label>From:</label>
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { assert } from "@cosmjs/utils";
|
||||
import { MsgDelegate } from "cosmjs-types/cosmos/staking/v1beta1/tx";
|
||||
import { useAppContext } from "../../../context/AppContext";
|
||||
import { useChains } from "../../../context/ChainsContext";
|
||||
import { printableCoin } from "../../../lib/displayHelpers";
|
||||
import HashView from "../HashView";
|
||||
|
||||
@ -9,7 +9,7 @@ interface TxMsgDelegateDetailsProps {
|
||||
}
|
||||
|
||||
const TxMsgDelegateDetails = ({ msgValue }: TxMsgDelegateDetailsProps) => {
|
||||
const { state } = useAppContext();
|
||||
const { chain } = useChains();
|
||||
assert(
|
||||
msgValue.amount,
|
||||
"Amount must be set, see https://github.com/osmosis-labs/telescope/issues/386",
|
||||
@ -22,7 +22,7 @@ const TxMsgDelegateDetails = ({ msgValue }: TxMsgDelegateDetailsProps) => {
|
||||
</li>
|
||||
<li>
|
||||
<label>Amount:</label>
|
||||
<div>{printableCoin(msgValue.amount, state.chain)}</div>
|
||||
<div>{printableCoin(msgValue.amount, chain)}</div>
|
||||
</li>
|
||||
<li>
|
||||
<label>Validator Address:</label>
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { assert } from "@cosmjs/utils";
|
||||
import { MsgBeginRedelegate } from "cosmjs-types/cosmos/staking/v1beta1/tx";
|
||||
import { useAppContext } from "../../../context/AppContext";
|
||||
import { useChains } from "../../../context/ChainsContext";
|
||||
import { printableCoin } from "../../../lib/displayHelpers";
|
||||
import HashView from "../HashView";
|
||||
|
||||
@ -9,7 +9,7 @@ interface TxMsgRedelegateDetailsProps {
|
||||
}
|
||||
|
||||
const TxMsgRedelegateDetails = ({ msgValue }: TxMsgRedelegateDetailsProps) => {
|
||||
const { state } = useAppContext();
|
||||
const { chain } = useChains();
|
||||
assert(
|
||||
msgValue.amount,
|
||||
"Amount must be set, same as https://github.com/osmosis-labs/telescope/issues/386",
|
||||
@ -22,7 +22,7 @@ const TxMsgRedelegateDetails = ({ msgValue }: TxMsgRedelegateDetailsProps) => {
|
||||
</li>
|
||||
<li>
|
||||
<label>Amount:</label>
|
||||
<div>{printableCoin(msgValue.amount, state.chain)}</div>
|
||||
<div>{printableCoin(msgValue.amount, chain)}</div>
|
||||
</li>
|
||||
<li>
|
||||
<label>Src Validator Address:</label>
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { MsgSend } from "cosmjs-types/cosmos/bank/v1beta1/tx";
|
||||
import { useAppContext } from "../../../context/AppContext";
|
||||
import { useChains } from "../../../context/ChainsContext";
|
||||
import { printableCoins } from "../../../lib/displayHelpers";
|
||||
import HashView from "../HashView";
|
||||
|
||||
@ -8,7 +8,7 @@ interface TxMsgSendDetailsProps {
|
||||
}
|
||||
|
||||
const TxMsgSendDetails = ({ msgValue }: TxMsgSendDetailsProps) => {
|
||||
const { state } = useAppContext();
|
||||
const { chain } = useChains();
|
||||
|
||||
return (
|
||||
<>
|
||||
@ -17,7 +17,7 @@ const TxMsgSendDetails = ({ msgValue }: TxMsgSendDetailsProps) => {
|
||||
</li>
|
||||
<li>
|
||||
<label>Amount:</label>
|
||||
<div>{printableCoins(msgValue.amount, state.chain)}</div>
|
||||
<div>{printableCoins(msgValue.amount, chain)}</div>
|
||||
</li>
|
||||
<li>
|
||||
<label>To:</label>
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { assert } from "@cosmjs/utils";
|
||||
import { MsgUndelegate } from "cosmjs-types/cosmos/staking/v1beta1/tx";
|
||||
import { useAppContext } from "../../../context/AppContext";
|
||||
import { useChains } from "../../../context/ChainsContext";
|
||||
import { printableCoin } from "../../../lib/displayHelpers";
|
||||
import HashView from "../HashView";
|
||||
|
||||
@ -9,7 +9,7 @@ interface TxMsgUndelegateDetailsProps {
|
||||
}
|
||||
|
||||
const TxMsgUndelegateDetails = ({ msgValue: msg }: TxMsgUndelegateDetailsProps) => {
|
||||
const { state } = useAppContext();
|
||||
const { chain } = useChains();
|
||||
assert(
|
||||
msg.amount,
|
||||
"Amount must be set, same as https://github.com/osmosis-labs/telescope/issues/386",
|
||||
@ -22,7 +22,7 @@ const TxMsgUndelegateDetails = ({ msgValue: msg }: TxMsgUndelegateDetailsProps)
|
||||
</li>
|
||||
<li>
|
||||
<label>Amount:</label>
|
||||
<div>{printableCoin(msg.amount, state.chain)}</div>
|
||||
<div>{printableCoin(msg.amount, chain)}</div>
|
||||
</li>
|
||||
<li>
|
||||
<label>Validator Address:</label>
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { EncodeObject } from "@cosmjs/proto-signing";
|
||||
import { useAppContext } from "../../../context/AppContext";
|
||||
import { useChains } from "../../../context/ChainsContext";
|
||||
import { printableCoins } from "../../../lib/displayHelpers";
|
||||
import { DbTransaction } from "../../../types";
|
||||
import { MsgTypeUrls } from "../../../types/txMsg";
|
||||
@ -53,7 +53,7 @@ interface TransactionInfoProps {
|
||||
}
|
||||
|
||||
const TransactionInfo = ({ tx }: TransactionInfoProps) => {
|
||||
const { state } = useAppContext();
|
||||
const { chain } = useChains();
|
||||
|
||||
return (
|
||||
<>
|
||||
@ -68,7 +68,7 @@ const TransactionInfo = ({ tx }: TransactionInfoProps) => {
|
||||
</li>
|
||||
<li>
|
||||
<label>Fee:</label>
|
||||
<div>{printableCoins(tx.fee.amount, state.chain)}</div>
|
||||
<div>{printableCoins(tx.fee.amount, chain)}</div>
|
||||
</li>
|
||||
</>
|
||||
) : null}
|
||||
|
||||
@ -1,8 +1,7 @@
|
||||
import { MsgWithdrawDelegatorRewardEncodeObject } from "@cosmjs/stargate";
|
||||
import { assert } from "@cosmjs/utils";
|
||||
import { useEffect, useState } from "react";
|
||||
import { MsgGetter } from "..";
|
||||
import { useAppContext } from "../../../../context/AppContext";
|
||||
import { useChains } from "../../../../context/ChainsContext";
|
||||
import { checkAddress, exampleAddress } from "../../../../lib/displayHelpers";
|
||||
import { MsgCodecs, MsgTypeUrls } from "../../../../types/txMsg";
|
||||
import Input from "../../../inputs/Input";
|
||||
@ -19,8 +18,7 @@ const MsgClaimRewardsForm = ({
|
||||
setMsgGetter,
|
||||
deleteMsg,
|
||||
}: MsgClaimRewardsFormProps) => {
|
||||
const { state } = useAppContext();
|
||||
assert(state.chain.addressPrefix, "addressPrefix missing");
|
||||
const { chain } = useChains();
|
||||
|
||||
const [validatorAddress, setValidatorAddress] = useState("");
|
||||
const [validatorAddressError, setValidatorAddressError] = useState("");
|
||||
@ -30,12 +28,10 @@ const MsgClaimRewardsForm = ({
|
||||
setValidatorAddressError("");
|
||||
|
||||
const isMsgValid = (): boolean => {
|
||||
assert(state.chain.addressPrefix, "addressPrefix missing");
|
||||
|
||||
const addressErrorMsg = checkAddress(validatorAddress, state.chain.addressPrefix);
|
||||
const addressErrorMsg = checkAddress(validatorAddress, chain.addressPrefix);
|
||||
if (addressErrorMsg) {
|
||||
setValidatorAddressError(
|
||||
`Invalid address for network ${state.chain.chainId}: ${addressErrorMsg}`,
|
||||
`Invalid address for network ${chain.chainId}: ${addressErrorMsg}`,
|
||||
);
|
||||
return false;
|
||||
}
|
||||
@ -55,13 +51,7 @@ const MsgClaimRewardsForm = ({
|
||||
|
||||
setMsgGetter({ isMsgValid, msg });
|
||||
} catch {}
|
||||
}, [
|
||||
delegatorAddress,
|
||||
setMsgGetter,
|
||||
state.chain.addressPrefix,
|
||||
state.chain.chainId,
|
||||
validatorAddress,
|
||||
]);
|
||||
}, [chain.addressPrefix, chain.chainId, delegatorAddress, setMsgGetter, validatorAddress]);
|
||||
|
||||
return (
|
||||
<StackableContainer lessPadding lessMargin>
|
||||
@ -76,7 +66,7 @@ const MsgClaimRewardsForm = ({
|
||||
value={validatorAddress}
|
||||
onChange={({ target }) => setValidatorAddress(target.value)}
|
||||
error={validatorAddressError}
|
||||
placeholder={`E.g. ${exampleAddress(0, state.chain.addressPrefix)}`}
|
||||
placeholder={`E.g. ${exampleAddress(0, chain.addressPrefix)}`}
|
||||
/>
|
||||
</div>
|
||||
<style jsx>{`
|
||||
|
||||
@ -1,9 +1,8 @@
|
||||
import { Decimal } from "@cosmjs/math";
|
||||
import { EncodeObject } from "@cosmjs/proto-signing";
|
||||
import { assert } from "@cosmjs/utils";
|
||||
import { useEffect, useState } from "react";
|
||||
import { MsgGetter } from "..";
|
||||
import { useAppContext } from "../../../../context/AppContext";
|
||||
import { useChains } from "../../../../context/ChainsContext";
|
||||
import {
|
||||
datetimeLocalFromTimestamp,
|
||||
timestampFromDatetimeLocal,
|
||||
@ -24,8 +23,7 @@ const MsgCreateVestingAccountForm = ({
|
||||
setMsgGetter,
|
||||
deleteMsg,
|
||||
}: MsgCreateVestingAccountFormProps) => {
|
||||
const { state } = useAppContext();
|
||||
assert(state.chain.addressPrefix, "addressPrefix missing");
|
||||
const { chain } = useChains();
|
||||
|
||||
const [toAddress, setToAddress] = useState("");
|
||||
const [amount, setAmount] = useState("0");
|
||||
@ -40,20 +38,14 @@ const MsgCreateVestingAccountForm = ({
|
||||
|
||||
useEffect(() => {
|
||||
try {
|
||||
assert(state.chain.denom, "denom missing");
|
||||
|
||||
setToAddressError("");
|
||||
setAmountError("");
|
||||
setEndTimeError("");
|
||||
|
||||
const isMsgValid = (): boolean => {
|
||||
assert(state.chain.addressPrefix, "addressPrefix missing");
|
||||
|
||||
const addressErrorMsg = checkAddress(toAddress, state.chain.addressPrefix);
|
||||
const addressErrorMsg = checkAddress(toAddress, chain.addressPrefix);
|
||||
if (addressErrorMsg) {
|
||||
setToAddressError(
|
||||
`Invalid address for network ${state.chain.chainId}: ${addressErrorMsg}`,
|
||||
);
|
||||
setToAddressError(`Invalid address for network ${chain.chainId}: ${addressErrorMsg}`);
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -72,13 +64,13 @@ const MsgCreateVestingAccountForm = ({
|
||||
};
|
||||
|
||||
const amountInAtomics = amount
|
||||
? Decimal.fromUserInput(amount, Number(state.chain.displayDenomExponent)).atomics
|
||||
? Decimal.fromUserInput(amount, Number(chain.displayDenomExponent)).atomics
|
||||
: "0";
|
||||
|
||||
const msgValue = MsgCodecs[MsgTypeUrls.CreateVestingAccount].fromPartial({
|
||||
fromAddress,
|
||||
toAddress,
|
||||
amount: [{ amount: amountInAtomics, denom: state.chain.denom }],
|
||||
amount: [{ amount: amountInAtomics, denom: chain.denom }],
|
||||
endTime: timestampFromDatetimeLocal(endTime, "s"),
|
||||
delayed,
|
||||
});
|
||||
@ -89,14 +81,14 @@ const MsgCreateVestingAccountForm = ({
|
||||
} catch {}
|
||||
}, [
|
||||
amount,
|
||||
chain.addressPrefix,
|
||||
chain.chainId,
|
||||
chain.denom,
|
||||
chain.displayDenomExponent,
|
||||
delayed,
|
||||
endTime,
|
||||
fromAddress,
|
||||
setMsgGetter,
|
||||
state.chain.addressPrefix,
|
||||
state.chain.chainId,
|
||||
state.chain.denom,
|
||||
state.chain.displayDenomExponent,
|
||||
toAddress,
|
||||
]);
|
||||
|
||||
@ -113,13 +105,13 @@ const MsgCreateVestingAccountForm = ({
|
||||
value={toAddress}
|
||||
onChange={({ target }) => setToAddress(target.value)}
|
||||
error={toAddressError}
|
||||
placeholder={`E.g. ${exampleAddress(0, state.chain.addressPrefix)}`}
|
||||
placeholder={`E.g. ${exampleAddress(0, chain.addressPrefix)}`}
|
||||
/>
|
||||
</div>
|
||||
<div className="form-item">
|
||||
<Input
|
||||
type="number"
|
||||
label={`Amount (${state.chain.displayDenom})`}
|
||||
label={`Amount (${chain.displayDenom})`}
|
||||
name="amount"
|
||||
value={amount}
|
||||
onChange={({ target }) => setAmount(target.value)}
|
||||
|
||||
@ -1,9 +1,8 @@
|
||||
import { Decimal } from "@cosmjs/math";
|
||||
import { MsgDelegateEncodeObject } from "@cosmjs/stargate";
|
||||
import { assert } from "@cosmjs/utils";
|
||||
import { useEffect, useState } from "react";
|
||||
import { MsgGetter } from "..";
|
||||
import { useAppContext } from "../../../../context/AppContext";
|
||||
import { useChains } from "../../../../context/ChainsContext";
|
||||
import { checkAddress, exampleAddress } from "../../../../lib/displayHelpers";
|
||||
import { MsgCodecs, MsgTypeUrls } from "../../../../types/txMsg";
|
||||
import Input from "../../../inputs/Input";
|
||||
@ -16,8 +15,7 @@ interface MsgDelegateFormProps {
|
||||
}
|
||||
|
||||
const MsgDelegateForm = ({ delegatorAddress, setMsgGetter, deleteMsg }: MsgDelegateFormProps) => {
|
||||
const { state } = useAppContext();
|
||||
assert(state.chain.addressPrefix, "addressPrefix missing");
|
||||
const { chain } = useChains();
|
||||
|
||||
const [validatorAddress, setValidatorAddress] = useState("");
|
||||
const [amount, setAmount] = useState("0");
|
||||
@ -27,18 +25,14 @@ const MsgDelegateForm = ({ delegatorAddress, setMsgGetter, deleteMsg }: MsgDeleg
|
||||
|
||||
useEffect(() => {
|
||||
try {
|
||||
assert(state.chain.denom, "denom missing");
|
||||
|
||||
setValidatorAddressError("");
|
||||
setAmountError("");
|
||||
|
||||
const isMsgValid = (): boolean => {
|
||||
assert(state.chain.addressPrefix, "addressPrefix missing");
|
||||
|
||||
const addressErrorMsg = checkAddress(validatorAddress, state.chain.addressPrefix);
|
||||
const addressErrorMsg = checkAddress(validatorAddress, chain.addressPrefix);
|
||||
if (addressErrorMsg) {
|
||||
setValidatorAddressError(
|
||||
`Invalid address for network ${state.chain.chainId}: ${addressErrorMsg}`,
|
||||
`Invalid address for network ${chain.chainId}: ${addressErrorMsg}`,
|
||||
);
|
||||
return false;
|
||||
}
|
||||
@ -53,13 +47,13 @@ const MsgDelegateForm = ({ delegatorAddress, setMsgGetter, deleteMsg }: MsgDeleg
|
||||
|
||||
const amountInAtomics = Decimal.fromUserInput(
|
||||
amount || "0",
|
||||
Number(state.chain.displayDenomExponent),
|
||||
Number(chain.displayDenomExponent),
|
||||
).atomics;
|
||||
|
||||
const msgValue = MsgCodecs[MsgTypeUrls.Delegate].fromPartial({
|
||||
delegatorAddress,
|
||||
validatorAddress,
|
||||
amount: { amount: amountInAtomics, denom: state.chain.denom },
|
||||
amount: { amount: amountInAtomics, denom: chain.denom },
|
||||
});
|
||||
|
||||
const msg: MsgDelegateEncodeObject = { typeUrl: MsgTypeUrls.Delegate, value: msgValue };
|
||||
@ -68,12 +62,12 @@ const MsgDelegateForm = ({ delegatorAddress, setMsgGetter, deleteMsg }: MsgDeleg
|
||||
} catch {}
|
||||
}, [
|
||||
amount,
|
||||
chain.addressPrefix,
|
||||
chain.chainId,
|
||||
chain.denom,
|
||||
chain.displayDenomExponent,
|
||||
delegatorAddress,
|
||||
setMsgGetter,
|
||||
state.chain.addressPrefix,
|
||||
state.chain.chainId,
|
||||
state.chain.denom,
|
||||
state.chain.displayDenomExponent,
|
||||
validatorAddress,
|
||||
]);
|
||||
|
||||
@ -90,13 +84,13 @@ const MsgDelegateForm = ({ delegatorAddress, setMsgGetter, deleteMsg }: MsgDeleg
|
||||
value={validatorAddress}
|
||||
onChange={({ target }) => setValidatorAddress(target.value)}
|
||||
error={validatorAddressError}
|
||||
placeholder={`E.g. ${exampleAddress(0, state.chain.addressPrefix)}`}
|
||||
placeholder={`E.g. ${exampleAddress(0, chain.addressPrefix)}`}
|
||||
/>
|
||||
</div>
|
||||
<div className="form-item">
|
||||
<Input
|
||||
type="number"
|
||||
label={`Amount (${state.chain.displayDenom})`}
|
||||
label={`Amount (${chain.displayDenom})`}
|
||||
name="amount"
|
||||
value={amount}
|
||||
onChange={({ target }) => setAmount(target.value)}
|
||||
|
||||
@ -1,9 +1,8 @@
|
||||
import { Decimal } from "@cosmjs/math";
|
||||
import { EncodeObject } from "@cosmjs/proto-signing";
|
||||
import { assert } from "@cosmjs/utils";
|
||||
import { useEffect, useState } from "react";
|
||||
import { MsgGetter } from "..";
|
||||
import { useAppContext } from "../../../../context/AppContext";
|
||||
import { useChains } from "../../../../context/ChainsContext";
|
||||
import { checkAddress, exampleAddress } from "../../../../lib/displayHelpers";
|
||||
import { MsgCodecs, MsgTypeUrls } from "../../../../types/txMsg";
|
||||
import Input from "../../../inputs/Input";
|
||||
@ -20,8 +19,7 @@ const MsgRedelegateForm = ({
|
||||
setMsgGetter,
|
||||
deleteMsg,
|
||||
}: MsgRedelegateFormProps) => {
|
||||
const { state } = useAppContext();
|
||||
assert(state.chain.addressPrefix, "addressPrefix missing");
|
||||
const { chain } = useChains();
|
||||
|
||||
const [validatorSrcAddress, setValidatorSrcAddress] = useState("");
|
||||
const [validatorDstAddress, setValidatorDstAddress] = useState("");
|
||||
@ -33,27 +31,23 @@ const MsgRedelegateForm = ({
|
||||
|
||||
useEffect(() => {
|
||||
try {
|
||||
assert(state.chain.denom, "denom missing");
|
||||
|
||||
setValidatorSrcAddressError("");
|
||||
setValidatorDstAddressError("");
|
||||
setAmountError("");
|
||||
|
||||
const isMsgValid = (): boolean => {
|
||||
assert(state.chain.addressPrefix, "addressPrefix missing");
|
||||
|
||||
const srcAddressErrorMsg = checkAddress(validatorSrcAddress, state.chain.addressPrefix);
|
||||
const srcAddressErrorMsg = checkAddress(validatorSrcAddress, chain.addressPrefix);
|
||||
if (srcAddressErrorMsg) {
|
||||
setValidatorSrcAddressError(
|
||||
`Invalid address for network ${state.chain.chainId}: ${srcAddressErrorMsg}`,
|
||||
`Invalid address for network ${chain.chainId}: ${srcAddressErrorMsg}`,
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
const dstAddressErrorMsg = checkAddress(validatorDstAddress, state.chain.addressPrefix);
|
||||
const dstAddressErrorMsg = checkAddress(validatorDstAddress, chain.addressPrefix);
|
||||
if (dstAddressErrorMsg) {
|
||||
setValidatorDstAddressError(
|
||||
`Invalid address for network ${state.chain.chainId}: ${dstAddressErrorMsg}`,
|
||||
`Invalid address for network ${chain.chainId}: ${dstAddressErrorMsg}`,
|
||||
);
|
||||
return false;
|
||||
}
|
||||
@ -68,14 +62,14 @@ const MsgRedelegateForm = ({
|
||||
|
||||
const amountInAtomics = Decimal.fromUserInput(
|
||||
amount || "0",
|
||||
Number(state.chain.displayDenomExponent),
|
||||
Number(chain.displayDenomExponent),
|
||||
).atomics;
|
||||
|
||||
const msgValue = MsgCodecs[MsgTypeUrls.BeginRedelegate].fromPartial({
|
||||
delegatorAddress,
|
||||
validatorSrcAddress,
|
||||
validatorDstAddress,
|
||||
amount: { amount: amountInAtomics, denom: state.chain.denom },
|
||||
amount: { amount: amountInAtomics, denom: chain.denom },
|
||||
});
|
||||
|
||||
const msg: EncodeObject = { typeUrl: MsgTypeUrls.BeginRedelegate, value: msgValue };
|
||||
@ -84,12 +78,12 @@ const MsgRedelegateForm = ({
|
||||
} catch {}
|
||||
}, [
|
||||
amount,
|
||||
chain.addressPrefix,
|
||||
chain.chainId,
|
||||
chain.denom,
|
||||
chain.displayDenomExponent,
|
||||
delegatorAddress,
|
||||
setMsgGetter,
|
||||
state.chain.addressPrefix,
|
||||
state.chain.chainId,
|
||||
state.chain.denom,
|
||||
state.chain.displayDenomExponent,
|
||||
validatorDstAddress,
|
||||
validatorSrcAddress,
|
||||
]);
|
||||
@ -107,7 +101,7 @@ const MsgRedelegateForm = ({
|
||||
value={validatorSrcAddress}
|
||||
onChange={({ target }) => setValidatorSrcAddress(target.value)}
|
||||
error={validatorSrcAddressError}
|
||||
placeholder={`E.g. ${exampleAddress(0, state.chain.addressPrefix)}`}
|
||||
placeholder={`E.g. ${exampleAddress(0, chain.addressPrefix)}`}
|
||||
/>
|
||||
</div>
|
||||
<div className="form-item">
|
||||
@ -117,13 +111,13 @@ const MsgRedelegateForm = ({
|
||||
value={validatorDstAddress}
|
||||
onChange={({ target }) => setValidatorDstAddress(target.value)}
|
||||
error={validatorDstAddressError}
|
||||
placeholder={`E.g. ${exampleAddress(0, state.chain.addressPrefix)}`}
|
||||
placeholder={`E.g. ${exampleAddress(0, chain.addressPrefix)}`}
|
||||
/>
|
||||
</div>
|
||||
<div className="form-item">
|
||||
<Input
|
||||
type="number"
|
||||
label={`Amount (${state.chain.displayDenom})`}
|
||||
label={`Amount (${chain.displayDenom})`}
|
||||
name="amount"
|
||||
value={amount}
|
||||
onChange={({ target }) => setAmount(target.value)}
|
||||
|
||||
@ -1,11 +1,10 @@
|
||||
import { Decimal } from "@cosmjs/math";
|
||||
import { MsgSendEncodeObject } from "@cosmjs/stargate";
|
||||
import { assert } from "@cosmjs/utils";
|
||||
import { useEffect, useState } from "react";
|
||||
import { MsgGetter } from "..";
|
||||
import { useAppContext } from "../../../../context/AppContext";
|
||||
import { useChains } from "../../../../context/ChainsContext";
|
||||
import { checkAddress, exampleAddress } from "../../../../lib/displayHelpers";
|
||||
import { ChainInfo } from "../../../../types";
|
||||
import { RegistryAsset } from "../../../../types/chainRegistry";
|
||||
import { MsgCodecs, MsgTypeUrls } from "../../../../types/txMsg";
|
||||
import Input from "../../../inputs/Input";
|
||||
import Select from "../../../inputs/Select";
|
||||
@ -13,7 +12,7 @@ import StackableContainer from "../../../layout/StackableContainer";
|
||||
|
||||
const customDenomOption = { label: "Custom (enter denom below)", value: "custom" } as const;
|
||||
|
||||
const getDenomOptions = (assets: ChainInfo["assets"]) => {
|
||||
const getDenomOptions = (assets: readonly RegistryAsset[]) => {
|
||||
if (!assets?.length) {
|
||||
return [customDenomOption];
|
||||
}
|
||||
@ -28,10 +27,9 @@ interface MsgSendFormProps {
|
||||
}
|
||||
|
||||
const MsgSendForm = ({ fromAddress, setMsgGetter, deleteMsg }: MsgSendFormProps) => {
|
||||
const { state } = useAppContext();
|
||||
assert(state.chain.addressPrefix, "addressPrefix missing");
|
||||
const { chain } = useChains();
|
||||
|
||||
const denomOptions = getDenomOptions(state.chain.assets);
|
||||
const denomOptions = getDenomOptions(chain.assets);
|
||||
|
||||
const [toAddress, setToAddress] = useState("");
|
||||
const [selectedDenom, setSelectedDenom] = useState(denomOptions[0]);
|
||||
@ -43,18 +41,14 @@ const MsgSendForm = ({ fromAddress, setMsgGetter, deleteMsg }: MsgSendFormProps)
|
||||
const [amountError, setAmountError] = useState("");
|
||||
|
||||
useEffect(() => {
|
||||
assert(state.chain.denom, "denom missing");
|
||||
|
||||
setToAddressError("");
|
||||
setCustomDenomError("");
|
||||
setAmountError("");
|
||||
|
||||
const isMsgValid = (): boolean => {
|
||||
assert(state.chain.addressPrefix, "addressPrefix missing");
|
||||
|
||||
const addressErrorMsg = checkAddress(toAddress, state.chain.addressPrefix);
|
||||
const addressErrorMsg = checkAddress(toAddress, chain.addressPrefix);
|
||||
if (addressErrorMsg) {
|
||||
setToAddressError(`Invalid address for network ${state.chain.chainId}: ${addressErrorMsg}`);
|
||||
setToAddressError(`Invalid address for network ${chain.chainId}: ${addressErrorMsg}`);
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -85,7 +79,7 @@ const MsgSendForm = ({ fromAddress, setMsgGetter, deleteMsg }: MsgSendFormProps)
|
||||
return Decimal.fromUserInput(amount, 0).atomics;
|
||||
}
|
||||
|
||||
const foundAsset = state.chain.assets?.find((asset) => asset.symbol === denom);
|
||||
const foundAsset = chain.assets.find((asset) => asset.symbol === denom);
|
||||
const exponent =
|
||||
foundAsset?.denom_units.find((unit) => unit.denom === foundAsset.symbol.toLowerCase())
|
||||
?.exponent ?? 0;
|
||||
@ -107,14 +101,13 @@ const MsgSendForm = ({ fromAddress, setMsgGetter, deleteMsg }: MsgSendFormProps)
|
||||
setMsgGetter({ isMsgValid, msg });
|
||||
}, [
|
||||
amount,
|
||||
chain.addressPrefix,
|
||||
chain.assets,
|
||||
chain.chainId,
|
||||
customDenom,
|
||||
fromAddress,
|
||||
selectedDenom.value,
|
||||
setMsgGetter,
|
||||
state.chain.addressPrefix,
|
||||
state.chain.assets,
|
||||
state.chain.chainId,
|
||||
state.chain.denom,
|
||||
toAddress,
|
||||
]);
|
||||
|
||||
@ -131,7 +124,7 @@ const MsgSendForm = ({ fromAddress, setMsgGetter, deleteMsg }: MsgSendFormProps)
|
||||
value={toAddress}
|
||||
onChange={({ target }) => setToAddress(target.value)}
|
||||
error={toAddressError}
|
||||
placeholder={`E.g. ${exampleAddress(0, state.chain.addressPrefix)}`}
|
||||
placeholder={`E.g. ${exampleAddress(0, chain.addressPrefix)}`}
|
||||
/>
|
||||
</div>
|
||||
<div className="form-item form-select">
|
||||
|
||||
@ -1,8 +1,7 @@
|
||||
import { EncodeObject } from "@cosmjs/proto-signing";
|
||||
import { assert } from "@cosmjs/utils";
|
||||
import { useEffect, useState } from "react";
|
||||
import { MsgGetter } from "..";
|
||||
import { useAppContext } from "../../../../context/AppContext";
|
||||
import { useChains } from "../../../../context/ChainsContext";
|
||||
import { checkAddress, exampleAddress } from "../../../../lib/displayHelpers";
|
||||
import { MsgCodecs, MsgTypeUrls } from "../../../../types/txMsg";
|
||||
import Input from "../../../inputs/Input";
|
||||
@ -19,8 +18,7 @@ const MsgSetWithdrawAddressForm = ({
|
||||
setMsgGetter,
|
||||
deleteMsg,
|
||||
}: MsgSetWithdrawAddressFormProps) => {
|
||||
const { state } = useAppContext();
|
||||
assert(state.chain.addressPrefix, "addressPrefix missing");
|
||||
const { chain } = useChains();
|
||||
|
||||
const [withdrawAddress, setWithdrawAddress] = useState("");
|
||||
const [withdrawAddressError, setWithdrawAddressError] = useState("");
|
||||
@ -30,12 +28,10 @@ const MsgSetWithdrawAddressForm = ({
|
||||
setWithdrawAddressError("");
|
||||
|
||||
const isMsgValid = (): boolean => {
|
||||
assert(state.chain.addressPrefix, "addressPrefix missing");
|
||||
|
||||
const addressErrorMsg = checkAddress(withdrawAddress, state.chain.addressPrefix);
|
||||
const addressErrorMsg = checkAddress(withdrawAddress, chain.addressPrefix);
|
||||
if (addressErrorMsg) {
|
||||
setWithdrawAddressError(
|
||||
`Invalid address for network ${state.chain.chainId}: ${addressErrorMsg}`,
|
||||
`Invalid address for network ${chain.chainId}: ${addressErrorMsg}`,
|
||||
);
|
||||
return false;
|
||||
}
|
||||
@ -51,13 +47,7 @@ const MsgSetWithdrawAddressForm = ({
|
||||
|
||||
setMsgGetter({ isMsgValid, msg });
|
||||
} catch {}
|
||||
}, [
|
||||
delegatorAddress,
|
||||
setMsgGetter,
|
||||
state.chain.addressPrefix,
|
||||
state.chain.chainId,
|
||||
withdrawAddress,
|
||||
]);
|
||||
}, [chain.addressPrefix, chain.chainId, delegatorAddress, setMsgGetter, withdrawAddress]);
|
||||
|
||||
return (
|
||||
<StackableContainer lessPadding lessMargin>
|
||||
@ -72,7 +62,7 @@ const MsgSetWithdrawAddressForm = ({
|
||||
value={withdrawAddress}
|
||||
onChange={({ target }) => setWithdrawAddress(target.value)}
|
||||
error={withdrawAddressError}
|
||||
placeholder={`E.g. ${exampleAddress(0, state.chain.addressPrefix)}`}
|
||||
placeholder={`E.g. ${exampleAddress(0, chain.addressPrefix)}`}
|
||||
/>
|
||||
</div>
|
||||
<style jsx>{`
|
||||
|
||||
@ -1,8 +1,7 @@
|
||||
import { MsgTransferEncodeObject } from "@cosmjs/stargate";
|
||||
import { assert } from "@cosmjs/utils";
|
||||
import { useEffect, useState } from "react";
|
||||
import { MsgGetter } from "..";
|
||||
import { useAppContext } from "../../../../context/AppContext";
|
||||
import { useChains } from "../../../../context/ChainsContext";
|
||||
import {
|
||||
datetimeLocalFromTimestamp,
|
||||
timestampFromDatetimeLocal,
|
||||
@ -31,8 +30,7 @@ interface MsgTransferFormProps {
|
||||
}
|
||||
|
||||
const MsgTransferForm = ({ fromAddress, setMsgGetter, deleteMsg }: MsgTransferFormProps) => {
|
||||
const { state } = useAppContext();
|
||||
assert(state.chain.addressPrefix, "addressPrefix missing");
|
||||
const { chain } = useChains();
|
||||
|
||||
const [sourcePort, setSourcePort] = useState("transfer");
|
||||
const [sourceChannel, setSourceChannel] = useState("");
|
||||
@ -82,7 +80,7 @@ const MsgTransferForm = ({ fromAddress, setMsgGetter, deleteMsg }: MsgTransferFo
|
||||
|
||||
const addressErrorMsg = checkAddress(toAddress, null); // Allow address from any chain
|
||||
if (addressErrorMsg) {
|
||||
setToAddressError(`Invalid address for network ${state.chain.chainId}: ${addressErrorMsg}`);
|
||||
setToAddressError(`Invalid address for network ${chain.chainId}: ${addressErrorMsg}`);
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -110,13 +108,13 @@ const MsgTransferForm = ({ fromAddress, setMsgGetter, deleteMsg }: MsgTransferFo
|
||||
setMsgGetter({ isMsgValid, msg });
|
||||
}, [
|
||||
amount,
|
||||
chain.chainId,
|
||||
denom,
|
||||
fromAddress,
|
||||
memo,
|
||||
setMsgGetter,
|
||||
sourceChannel,
|
||||
sourcePort,
|
||||
state.chain.chainId,
|
||||
timeout,
|
||||
toAddress,
|
||||
]);
|
||||
@ -134,7 +132,7 @@ const MsgTransferForm = ({ fromAddress, setMsgGetter, deleteMsg }: MsgTransferFo
|
||||
value={toAddress}
|
||||
onChange={({ target }) => setToAddress(target.value)}
|
||||
error={toAddressError}
|
||||
placeholder={`E.g. ${exampleAddress(0, state.chain.addressPrefix)}`}
|
||||
placeholder={`E.g. ${exampleAddress(0, chain.addressPrefix)}`}
|
||||
/>
|
||||
</div>
|
||||
<div className="form-item">
|
||||
|
||||
@ -1,9 +1,8 @@
|
||||
import { Decimal } from "@cosmjs/math";
|
||||
import { MsgUndelegateEncodeObject } from "@cosmjs/stargate";
|
||||
import { assert } from "@cosmjs/utils";
|
||||
import { useEffect, useState } from "react";
|
||||
import { MsgGetter } from "..";
|
||||
import { useAppContext } from "../../../../context/AppContext";
|
||||
import { useChains } from "../../../../context/ChainsContext";
|
||||
import { checkAddress, exampleAddress } from "../../../../lib/displayHelpers";
|
||||
import { MsgCodecs, MsgTypeUrls } from "../../../../types/txMsg";
|
||||
import Input from "../../../inputs/Input";
|
||||
@ -20,8 +19,7 @@ const MsgUndelegateForm = ({
|
||||
setMsgGetter,
|
||||
deleteMsg,
|
||||
}: MsgUndelegateFormProps) => {
|
||||
const { state } = useAppContext();
|
||||
assert(state.chain.addressPrefix, "addressPrefix missing");
|
||||
const { chain } = useChains();
|
||||
|
||||
const [validatorAddress, setValidatorAddress] = useState("");
|
||||
const [amount, setAmount] = useState("0");
|
||||
@ -31,18 +29,14 @@ const MsgUndelegateForm = ({
|
||||
|
||||
useEffect(() => {
|
||||
try {
|
||||
assert(state.chain.denom, "denom missing");
|
||||
|
||||
setValidatorAddressError("");
|
||||
setAmountError("");
|
||||
|
||||
const isMsgValid = (): boolean => {
|
||||
assert(state.chain.addressPrefix, "addressPrefix missing");
|
||||
|
||||
const addressErrorMsg = checkAddress(validatorAddress, state.chain.addressPrefix);
|
||||
const addressErrorMsg = checkAddress(validatorAddress, chain.addressPrefix);
|
||||
if (addressErrorMsg) {
|
||||
setValidatorAddressError(
|
||||
`Invalid address for network ${state.chain.chainId}: ${addressErrorMsg}`,
|
||||
`Invalid address for network ${chain.chainId}: ${addressErrorMsg}`,
|
||||
);
|
||||
return false;
|
||||
}
|
||||
@ -57,13 +51,13 @@ const MsgUndelegateForm = ({
|
||||
|
||||
const amountInAtomics = Decimal.fromUserInput(
|
||||
amount || "0",
|
||||
Number(state.chain.displayDenomExponent),
|
||||
Number(chain.displayDenomExponent),
|
||||
).atomics;
|
||||
|
||||
const msgValue = MsgCodecs[MsgTypeUrls.Undelegate].fromPartial({
|
||||
delegatorAddress,
|
||||
validatorAddress,
|
||||
amount: { amount: amountInAtomics, denom: state.chain.denom },
|
||||
amount: { amount: amountInAtomics, denom: chain.denom },
|
||||
});
|
||||
|
||||
const msg: MsgUndelegateEncodeObject = { typeUrl: MsgTypeUrls.Undelegate, value: msgValue };
|
||||
@ -72,12 +66,12 @@ const MsgUndelegateForm = ({
|
||||
} catch {}
|
||||
}, [
|
||||
amount,
|
||||
chain.addressPrefix,
|
||||
chain.chainId,
|
||||
chain.denom,
|
||||
chain.displayDenomExponent,
|
||||
delegatorAddress,
|
||||
setMsgGetter,
|
||||
state.chain.addressPrefix,
|
||||
state.chain.chainId,
|
||||
state.chain.denom,
|
||||
state.chain.displayDenomExponent,
|
||||
validatorAddress,
|
||||
]);
|
||||
|
||||
@ -94,13 +88,13 @@ const MsgUndelegateForm = ({
|
||||
value={validatorAddress}
|
||||
onChange={({ target }) => setValidatorAddress(target.value)}
|
||||
error={validatorAddressError}
|
||||
placeholder={`E.g. ${exampleAddress(0, state.chain.addressPrefix)}`}
|
||||
placeholder={`E.g. ${exampleAddress(0, chain.addressPrefix)}`}
|
||||
/>
|
||||
</div>
|
||||
<div className="form-item">
|
||||
<Input
|
||||
type="number"
|
||||
label={`Amount (${state.chain.displayDenom})`}
|
||||
label={`Amount (${chain.displayDenom})`}
|
||||
name="amount"
|
||||
value={amount}
|
||||
onChange={({ target }) => setAmount(target.value)}
|
||||
|
||||
@ -4,7 +4,7 @@ import { assert } from "@cosmjs/utils";
|
||||
import axios from "axios";
|
||||
import { NextRouter, withRouter } from "next/router";
|
||||
import { useRef, useState } from "react";
|
||||
import { useAppContext } from "../../../context/AppContext";
|
||||
import { useChains } from "../../../context/ChainsContext";
|
||||
import { exportMsgToJson, gasOfTx } from "../../../lib/txMsgHelpers";
|
||||
import { DbTransaction } from "../../../types";
|
||||
import { MsgTypeUrl, MsgTypeUrls } from "../../../types/txMsg";
|
||||
@ -25,7 +25,7 @@ interface CreateTxFormProps {
|
||||
}
|
||||
|
||||
const CreateTxForm = ({ router, senderAddress, accountOnChain }: CreateTxFormProps) => {
|
||||
const { state } = useAppContext();
|
||||
const { chain } = useChains();
|
||||
|
||||
const [processing, setProcessing] = useState(false);
|
||||
const [msgTypes, setMsgTypes] = useState<readonly MsgTypeUrl[]>([]);
|
||||
@ -36,9 +36,6 @@ const CreateTxForm = ({ router, senderAddress, accountOnChain }: CreateTxFormPro
|
||||
const [gasLimitError, setGasLimitError] = useState("");
|
||||
const [showCreateTxError, setShowTxError] = useState(false);
|
||||
|
||||
const gasPrice = state.chain.gasPrice;
|
||||
assert(gasPrice, "gasPrice missing");
|
||||
|
||||
const addMsgType = (newMsgType: MsgTypeUrl) => {
|
||||
setMsgKeys((oldMsgKeys) => [...oldMsgKeys, crypto.randomUUID()]);
|
||||
setMsgTypes((oldMsgTypes) => {
|
||||
@ -54,7 +51,6 @@ const CreateTxForm = ({ router, senderAddress, accountOnChain }: CreateTxFormPro
|
||||
|
||||
assert(typeof accountOnChain.accountNumber === "number", "accountNumber missing");
|
||||
assert(msgGetters.current.length, "form filled incorrectly");
|
||||
assert(state.chain.chainId, "chainId missing");
|
||||
|
||||
const msgs = msgGetters.current
|
||||
.filter(({ isMsgValid }) => isMsgValid())
|
||||
@ -72,9 +68,9 @@ const CreateTxForm = ({ router, senderAddress, accountOnChain }: CreateTxFormPro
|
||||
const tx: DbTransaction = {
|
||||
accountNumber: accountOnChain.accountNumber,
|
||||
sequence: accountOnChain.sequence,
|
||||
chainId: state.chain.chainId,
|
||||
chainId: chain.chainId,
|
||||
msgs,
|
||||
fee: calculateFee(gasLimit, gasPrice),
|
||||
fee: calculateFee(gasLimit, chain.gasPrice),
|
||||
memo,
|
||||
};
|
||||
|
||||
@ -84,7 +80,7 @@ const CreateTxForm = ({ router, senderAddress, accountOnChain }: CreateTxFormPro
|
||||
dataJSON: JSON.stringify(tx),
|
||||
});
|
||||
|
||||
router.push(`/multi/${senderAddress}/transaction/${transactionID}`);
|
||||
router.push(`/${chain.registryName}/${senderAddress}/transaction/${transactionID}`);
|
||||
} catch (error) {
|
||||
console.error("Creat transaction error:", error);
|
||||
setShowTxError(true);
|
||||
@ -145,7 +141,7 @@ const CreateTxForm = ({ router, senderAddress, accountOnChain }: CreateTxFormPro
|
||||
<Input
|
||||
label="Gas Price"
|
||||
name="gas-price"
|
||||
value={gasPrice}
|
||||
value={chain.gasPrice}
|
||||
disabled={true}
|
||||
error={gasLimitError}
|
||||
/>
|
||||
|
||||
@ -1,8 +1,7 @@
|
||||
import { StargateClient } from "@cosmjs/stargate";
|
||||
import { assert } from "@cosmjs/utils";
|
||||
import { NextRouter, withRouter } from "next/router";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useAppContext } from "../../context/AppContext";
|
||||
import { useChains } from "../../context/ChainsContext";
|
||||
import { exampleAddress } from "../../lib/displayHelpers";
|
||||
import { getMultisigAccount } from "../../lib/multisigHelpers";
|
||||
import Button from "../inputs/Button";
|
||||
@ -14,12 +13,12 @@ interface Props {
|
||||
}
|
||||
|
||||
const FindMultisigForm = (props: Props) => {
|
||||
const { state } = useAppContext();
|
||||
const { chain } = useChains();
|
||||
const [address, setAddress] = useState("");
|
||||
const [multisigError, setMultisigError] = useState("");
|
||||
|
||||
const handleSearch = () => {
|
||||
props.router.push(`/multi/${address}`);
|
||||
props.router.push(`/${chain.registryName}/${address}`);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
@ -30,10 +29,8 @@ const FindMultisigForm = (props: Props) => {
|
||||
}
|
||||
|
||||
try {
|
||||
assert(state.chain.nodeAddress, "Node address missing");
|
||||
const client = await StargateClient.connect(state.chain.nodeAddress);
|
||||
assert(state.chain.addressPrefix, "addressPrefix missing");
|
||||
await getMultisigAccount(address, state.chain.addressPrefix, client);
|
||||
const client = await StargateClient.connect(chain.nodeAddress);
|
||||
await getMultisigAccount(address, chain.addressPrefix, client);
|
||||
setMultisigError("");
|
||||
} catch (error) {
|
||||
if (error instanceof Error) {
|
||||
@ -44,9 +41,7 @@ const FindMultisigForm = (props: Props) => {
|
||||
console.error("Multisig error:", error);
|
||||
}
|
||||
})();
|
||||
}, [address, state.chain.addressPrefix, state.chain.nodeAddress]);
|
||||
|
||||
assert(state.chain.addressPrefix, "addressPrefix missing");
|
||||
}, [address, chain.addressPrefix, chain.nodeAddress]);
|
||||
|
||||
return (
|
||||
<StackableContainer>
|
||||
@ -62,7 +57,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, chain.addressPrefix)}`}
|
||||
error={multisigError}
|
||||
/>
|
||||
<Button
|
||||
|
||||
@ -1,8 +1,7 @@
|
||||
import { StargateClient } from "@cosmjs/stargate";
|
||||
import { assert } from "@cosmjs/utils";
|
||||
import { NextRouter, withRouter } from "next/router";
|
||||
import { useState } from "react";
|
||||
import { useAppContext } from "../../context/AppContext";
|
||||
import { useChains } from "../../context/ChainsContext";
|
||||
import { exampleAddress, examplePubkey } from "../../lib/displayHelpers";
|
||||
import { createMultisigFromCompressedSecp256k1Pubkeys } from "../../lib/multisigHelpers";
|
||||
import Button from "../inputs/Button";
|
||||
@ -19,7 +18,7 @@ interface Props {
|
||||
}
|
||||
|
||||
const MultiSigForm = (props: Props) => {
|
||||
const { state } = useAppContext();
|
||||
const { chain } = useChains();
|
||||
const [pubkeys, setPubkeys] = useState([emptyPubKeyGroup(), emptyPubKeyGroup()]);
|
||||
const [threshold, setThreshold] = useState(2);
|
||||
const [processing, setProcessing] = useState(false);
|
||||
@ -57,8 +56,7 @@ const MultiSigForm = (props: Props) => {
|
||||
};
|
||||
|
||||
const getPubkeyFromNode = async (address: string) => {
|
||||
assert(state.chain.nodeAddress, "nodeAddress missing");
|
||||
const client = await StargateClient.connect(state.chain.nodeAddress);
|
||||
const client = await StargateClient.connect(chain.nodeAddress);
|
||||
const accountOnChain = await client.getAccount(address);
|
||||
console.log(accountOnChain);
|
||||
if (!accountOnChain || !accountOnChain.pubkey) {
|
||||
@ -105,15 +103,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,
|
||||
chain.addressPrefix,
|
||||
chain.chainId,
|
||||
);
|
||||
props.router.push(`/multi/${multisigAddress}`);
|
||||
props.router.push(`/${chain.registryName}/${multisigAddress}`);
|
||||
} catch (error) {
|
||||
console.log("Failed to creat multisig: ", error);
|
||||
}
|
||||
@ -132,7 +128,6 @@ const MultiSigForm = (props: Props) => {
|
||||
<p>Add the addresses that will make up this multisig.</p>
|
||||
</StackableContainer>
|
||||
{pubkeys.map((pubkeyGroup, index) => {
|
||||
assert(state.chain.addressPrefix, "addressPrefix missing");
|
||||
return (
|
||||
<StackableContainer lessPadding lessMargin key={index}>
|
||||
<div className="key-row">
|
||||
@ -160,7 +155,7 @@ const MultiSigForm = (props: Props) => {
|
||||
placeholder={`E.g. ${
|
||||
pubkeyGroup.isPubkey
|
||||
? examplePubkey(index)
|
||||
: exampleAddress(index, state.chain.addressPrefix)
|
||||
: exampleAddress(index, chain.addressPrefix)
|
||||
}`}
|
||||
error={pubkeyGroup.keyError}
|
||||
onBlur={(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
|
||||
@ -6,7 +6,7 @@ import { assert } from "@cosmjs/utils";
|
||||
import TransportWebUSB from "@ledgerhq/hw-transport-webusb";
|
||||
import axios from "axios";
|
||||
import { useCallback, useLayoutEffect, useState } from "react";
|
||||
import { useAppContext } from "../../context/AppContext";
|
||||
import { useChains } from "../../context/ChainsContext";
|
||||
import { getConnectError } from "../../lib/errorHelpers";
|
||||
import { DbSignature, DbTransaction, WalletAccount } from "../../types";
|
||||
import HashView from "../dataViews/HashView";
|
||||
@ -21,18 +21,18 @@ interface LoadingStates {
|
||||
readonly ledger?: boolean;
|
||||
}
|
||||
|
||||
interface Props {
|
||||
signatures: DbSignature[];
|
||||
tx: DbTransaction;
|
||||
pubkey: MultisigThresholdPubkey;
|
||||
transactionID: string;
|
||||
addSignature: (signature: DbSignature) => void;
|
||||
interface TransactionSigningProps {
|
||||
readonly signatures: DbSignature[];
|
||||
readonly tx: DbTransaction;
|
||||
readonly pubkey: MultisigThresholdPubkey;
|
||||
readonly transactionID: string;
|
||||
readonly addSignature: (signature: DbSignature) => void;
|
||||
}
|
||||
|
||||
const TransactionSigning = (props: Props) => {
|
||||
const TransactionSigning = (props: TransactionSigningProps) => {
|
||||
const memberPubkeys = props.pubkey.value.pubkeys.map(({ value }) => value);
|
||||
|
||||
const { state } = useAppContext();
|
||||
const { chain } = useChains();
|
||||
const [walletAccount, setWalletAccount] = useState<WalletAccount>();
|
||||
const [sigError, setSigError] = useState("");
|
||||
const [connectError, setConnectError] = useState("");
|
||||
@ -44,13 +44,12 @@ const TransactionSigning = (props: Props) => {
|
||||
const connectKeplr = useCallback(async () => {
|
||||
try {
|
||||
setLoading((oldLoading) => ({ ...oldLoading, keplr: true }));
|
||||
assert(state.chain.chainId, "chainId missing");
|
||||
|
||||
await window.keplr.enable(state.chain.chainId);
|
||||
await window.keplr.enable(chain.chainId);
|
||||
window.keplr.defaultOptions = {
|
||||
sign: { preferNoSetFee: true, preferNoSetMemo: true, disableBalanceCheck: true },
|
||||
};
|
||||
const tempWalletAccount = await window.keplr.getKey(state.chain.chainId);
|
||||
const tempWalletAccount = await window.keplr.getKey(chain.chainId);
|
||||
setWalletAccount(tempWalletAccount);
|
||||
|
||||
const pubkey = toBase64(tempWalletAccount.pubKey);
|
||||
@ -76,7 +75,7 @@ const TransactionSigning = (props: Props) => {
|
||||
} finally {
|
||||
setLoading((newLoading) => ({ ...newLoading, keplr: false }));
|
||||
}
|
||||
}, [memberPubkeys, props.signatures, state.chain.chainId]);
|
||||
}, [chain.chainId, memberPubkeys, props.signatures]);
|
||||
|
||||
useLayoutEffect(() => {
|
||||
const accountChangeKey = "keplr_keystorechange";
|
||||
@ -91,7 +90,6 @@ const TransactionSigning = (props: Props) => {
|
||||
const connectLedger = async () => {
|
||||
try {
|
||||
setLoading((newLoading) => ({ ...newLoading, ledger: true }));
|
||||
assert(state.chain.addressPrefix, "addressPrefix missing");
|
||||
|
||||
// Prepare ledger
|
||||
const ledgerTransport = await TransportWebUSB.create(120000, 120000);
|
||||
@ -99,7 +97,7 @@ const TransactionSigning = (props: Props) => {
|
||||
// Setup signer
|
||||
const offlineSigner = new LedgerSigner(ledgerTransport, {
|
||||
hdPaths: [makeCosmoshubPath(0)],
|
||||
prefix: state.chain.addressPrefix,
|
||||
prefix: chain.addressPrefix,
|
||||
});
|
||||
const accounts = await offlineSigner.getAccounts();
|
||||
const tempWalletAccount: WalletAccount = {
|
||||
@ -138,12 +136,9 @@ const TransactionSigning = (props: Props) => {
|
||||
const signTransaction = async () => {
|
||||
try {
|
||||
setLoading((newLoading) => ({ ...newLoading, signing: true }));
|
||||
assert(state.chain.chainId, "chainId missing");
|
||||
|
||||
const offlineSigner =
|
||||
walletType === "Keplr"
|
||||
? window.getOfflineSignerOnlyAmino(state.chain.chainId)
|
||||
: ledgerSigner;
|
||||
walletType === "Keplr" ? window.getOfflineSignerOnlyAmino(chain.chainId) : ledgerSigner;
|
||||
|
||||
const signerAddress = walletAccount?.bech32Address;
|
||||
assert(signerAddress, "Missing signer address");
|
||||
@ -152,7 +147,7 @@ const TransactionSigning = (props: Props) => {
|
||||
const signerData = {
|
||||
accountNumber: props.tx.accountNumber,
|
||||
sequence: props.tx.sequence,
|
||||
chainId: state.chain.chainId,
|
||||
chainId: chain.chainId,
|
||||
};
|
||||
|
||||
const { bodyBytes, signatures } = await signingClient.sign(
|
||||
|
||||
@ -1,82 +0,0 @@
|
||||
import { createContext, useContext, useEffect, useReducer } from "react";
|
||||
import { ChainInfo } from "../types";
|
||||
import { AppReducer, ChangeChainAction, initialState } from "./AppReducer";
|
||||
|
||||
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: decodeURIComponent(params.get("denom") || ""),
|
||||
displayDenom: decodeURIComponent(params.get("displayDenom") || ""),
|
||||
displayDenomExponent: parseInt(
|
||||
decodeURIComponent(params.get("displayDenomExponent") || ""),
|
||||
10,
|
||||
),
|
||||
assets: JSON.parse(decodeURIComponent(params.get("assets") || "[]")),
|
||||
gasPrice: decodeURIComponent(params.get("gasPrice") || ""),
|
||||
chainId: decodeURIComponent(params.get("chainId") || ""),
|
||||
chainDisplayName: decodeURIComponent(params.get("chainDisplayName") || ""),
|
||||
registryName: decodeURIComponent(params.get("registryName") || ""),
|
||||
addressPrefix: decodeURIComponent(params.get("addressPrefix") || ""),
|
||||
explorerLink: decodeURIComponent(params.get("explorerLink") || ""),
|
||||
};
|
||||
|
||||
return chainInfo;
|
||||
}
|
||||
|
||||
function setChainInfoParams(chainInfo: ChainInfo) {
|
||||
const params = new URLSearchParams();
|
||||
|
||||
for (const [key, value] of Object.entries(chainInfo)) {
|
||||
if (Array.isArray(value)) {
|
||||
params.set(key, encodeURIComponent(JSON.stringify(value)));
|
||||
} else {
|
||||
params.set(key, encodeURIComponent(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,33 +0,0 @@
|
||||
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),
|
||||
gasPrice: process.env.NEXT_PUBLIC_GAS_PRICE,
|
||||
chainId: process.env.NEXT_PUBLIC_CHAIN_ID,
|
||||
chainDisplayName: process.env.NEXT_PUBLIC_CHAIN_DISPLAY_NAME,
|
||||
registryName: process.env.NEXT_PUBLIC_REGISTRY_NAME,
|
||||
addressPrefix: process.env.NEXT_PUBLIC_ADDRESS_PREFIX,
|
||||
explorerLink: process.env.NEXT_PUBLIC_EXPLORER_LINK_TX,
|
||||
},
|
||||
};
|
||||
|
||||
export interface ChangeChainAction {
|
||||
type: "changeChain";
|
||||
value: ChainInfo;
|
||||
}
|
||||
|
||||
export const AppReducer = (state: AppContextType, action: ChangeChainAction) => {
|
||||
switch (action.type) {
|
||||
case "changeChain": {
|
||||
return {
|
||||
...state,
|
||||
chain: action.value,
|
||||
};
|
||||
}
|
||||
}
|
||||
};
|
||||
36
context/ChainsContext/helpers.tsx
Normal file
36
context/ChainsContext/helpers.tsx
Normal file
@ -0,0 +1,36 @@
|
||||
import { ChainInfo, ChainItems, Dispatch } from "./types";
|
||||
|
||||
export const emptyChain: ChainInfo = {
|
||||
nodeAddress: "",
|
||||
denom: "",
|
||||
displayDenom: "",
|
||||
displayDenomExponent: 0,
|
||||
assets: [],
|
||||
gasPrice: "",
|
||||
chainId: "",
|
||||
chainDisplayName: "",
|
||||
registryName: "",
|
||||
addressPrefix: "",
|
||||
explorerLink: "",
|
||||
};
|
||||
|
||||
export const isChainInfoFilled = ({ displayDenomExponent, assets, ...restFields }: ChainInfo) =>
|
||||
displayDenomExponent >= 0 &&
|
||||
assets.length > 0 &&
|
||||
Object.values(restFields).every((value) => value !== "");
|
||||
|
||||
export const setChains = (dispatch: Dispatch, chains: ChainItems) => {
|
||||
dispatch({ type: "setChains", payload: chains });
|
||||
};
|
||||
|
||||
export const setChain = (dispatch: Dispatch, chain: ChainInfo) => {
|
||||
dispatch({ type: "setChain", payload: chain });
|
||||
};
|
||||
|
||||
export const setChainFromRegistry = (dispatch: Dispatch, chainName: string) => {
|
||||
dispatch({ type: "setChain", payload: { ...emptyChain, registryName: chainName } });
|
||||
};
|
||||
|
||||
export const setChainsError = (dispatch: Dispatch, chainsError: string | null) => {
|
||||
dispatch({ type: "setChainsError", payload: chainsError });
|
||||
};
|
||||
89
context/ChainsContext/index.tsx
Normal file
89
context/ChainsContext/index.tsx
Normal file
@ -0,0 +1,89 @@
|
||||
import { ReactNode, createContext, useContext, useEffect, useReducer } from "react";
|
||||
import { isChainInfoFilled, setChain, setChains, setChainsError } from "./helpers";
|
||||
import { getChain, getChainFromRegistry, getChainItemsFromRegistry } from "./service";
|
||||
import { setChainInStorage, setChainInUrl } from "./storage";
|
||||
import { Action, ChainsContextType, State } from "./types";
|
||||
|
||||
const ChainsContext = createContext<ChainsContextType | undefined>(undefined);
|
||||
|
||||
const chainsReducer = (state: State, action: Action) => {
|
||||
switch (action.type) {
|
||||
case "setChains": {
|
||||
return { ...state, chains: action.payload };
|
||||
}
|
||||
case "setChain": {
|
||||
setChainInStorage(action.payload);
|
||||
setChainInUrl(action.payload);
|
||||
return { ...state, chain: action.payload };
|
||||
}
|
||||
case "setChainsError": {
|
||||
return { ...state, chainsError: action.payload };
|
||||
}
|
||||
default: {
|
||||
throw new Error("Unhandled action type");
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
interface ChainsProviderProps {
|
||||
readonly children: ReactNode;
|
||||
}
|
||||
|
||||
export const ChainsProvider = ({ children }: ChainsProviderProps) => {
|
||||
const [state, dispatch] = useReducer(chainsReducer, {
|
||||
chain: getChain(),
|
||||
chains: { mainnets: [], testnets: [] },
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
(async function getChainsFromGithubRegistry() {
|
||||
try {
|
||||
const newChainItems = await getChainItemsFromRegistry();
|
||||
setChains(dispatch, newChainItems);
|
||||
} catch (error) {
|
||||
if (error instanceof Error) {
|
||||
setChainsError(dispatch, error.message);
|
||||
} else {
|
||||
setChainsError(dispatch, "Failed to get chains from registry");
|
||||
}
|
||||
}
|
||||
})();
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
(async function getChainFromRegistryIfEmpty() {
|
||||
if (
|
||||
isChainInfoFilled(state.chain) ||
|
||||
!state.chains.mainnets.length ||
|
||||
!state.chains.testnets.length
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
const isTestnet = !!state.chains.testnets.find(
|
||||
({ name }) => name === state.chain.registryName,
|
||||
);
|
||||
|
||||
try {
|
||||
const chainFromRegistry = await getChainFromRegistry(state.chain.registryName, isTestnet);
|
||||
setChain(dispatch, chainFromRegistry);
|
||||
} catch (error) {
|
||||
if (error instanceof Error) {
|
||||
setChainsError(dispatch, error.message);
|
||||
} else {
|
||||
setChainsError(dispatch, `Failed to get chain ${state.chain.registryName} from registry`);
|
||||
}
|
||||
}
|
||||
})();
|
||||
}, [state.chain, state.chains.mainnets.length, state.chains.testnets]);
|
||||
|
||||
return <ChainsContext.Provider value={{ state, dispatch }}>{children}</ChainsContext.Provider>;
|
||||
};
|
||||
|
||||
export const useChains = () => {
|
||||
const context = useContext(ChainsContext);
|
||||
if (context === undefined) {
|
||||
throw new Error("useChains must be used within a ChainsProvider");
|
||||
}
|
||||
return { ...context.state, chainsDispatch: context.dispatch };
|
||||
};
|
||||
167
context/ChainsContext/service.tsx
Normal file
167
context/ChainsContext/service.tsx
Normal file
@ -0,0 +1,167 @@
|
||||
import { StargateClient } from "@cosmjs/stargate";
|
||||
import { assert } from "@cosmjs/utils";
|
||||
import {
|
||||
GithubChainRegistryItem,
|
||||
RegistryAsset,
|
||||
RegistryChain,
|
||||
RegistryChainApisRpc,
|
||||
RegistryChainExplorer,
|
||||
} from "../../types/chainRegistry";
|
||||
import { emptyChain, isChainInfoFilled } from "./helpers";
|
||||
import {
|
||||
getChainFromEnvfile,
|
||||
getChainFromStorage,
|
||||
getChainFromUrl,
|
||||
setChainInStorage,
|
||||
setChainInUrl,
|
||||
} from "./storage";
|
||||
import { ChainInfo, ChainItems } from "./types";
|
||||
|
||||
const chainsUrl = "https://api.github.com/repos/cosmos/chain-registry/contents";
|
||||
const testnetsUrl = "https://api.github.com/repos/cosmos/chain-registry/contents/testnets";
|
||||
const registryGhUrl = "https://cdn.jsdelivr.net/gh/cosmos/chain-registry@master/";
|
||||
|
||||
const getChains = async (chainUrl: string) => {
|
||||
const response = await fetch(chainUrl);
|
||||
if (!response.ok) {
|
||||
throw new Error("Failed to get chains from registry");
|
||||
}
|
||||
|
||||
const chainItems: readonly GithubChainRegistryItem[] = await response.json();
|
||||
return chainItems;
|
||||
};
|
||||
|
||||
export const getChainItemsFromRegistry: () => Promise<ChainItems> = async () => {
|
||||
const [mainnets, testnets] = await Promise.all([getChains(chainsUrl), getChains(testnetsUrl)]);
|
||||
|
||||
const nonChainsFilter = (item: GithubChainRegistryItem) =>
|
||||
item.type === "dir" && !item.name.startsWith(".") && !item.name.startsWith("_");
|
||||
|
||||
return {
|
||||
mainnets: mainnets.filter(nonChainsFilter),
|
||||
testnets: testnets.filter(nonChainsFilter),
|
||||
};
|
||||
};
|
||||
|
||||
export const getChainItemFromRegistry = async (chainName: string, isTestnet?: boolean) => {
|
||||
const chainGhPath = isTestnet ? "testnets/" + chainName : chainName;
|
||||
const chainGhUrl = registryGhUrl + chainGhPath + "/chain.json";
|
||||
|
||||
const response = await fetch(chainGhUrl);
|
||||
if (!response.ok) {
|
||||
throw new Error(`Failed to get ${chainName} chain from registry`);
|
||||
}
|
||||
|
||||
const chain: RegistryChain = await response.json();
|
||||
return chain;
|
||||
};
|
||||
|
||||
export const getAssetItemsFromRegistry = async (chainName: string, isTestnet?: boolean) => {
|
||||
const assetsGhPath = isTestnet ? "testnets/" + chainName : chainName;
|
||||
const assetsGhUrl = registryGhUrl + assetsGhPath + "/assetlist.json";
|
||||
|
||||
const response = await fetch(assetsGhUrl);
|
||||
if (!response.ok) {
|
||||
throw new Error(`Failed to get assets for ${chainName} chain from registry`);
|
||||
}
|
||||
|
||||
const assets: readonly RegistryAsset[] = (await response.json()).assets;
|
||||
return assets;
|
||||
};
|
||||
|
||||
const getNodeFromArray = async (nodeArray: readonly RegistryChainApisRpc[]) => {
|
||||
// only return https connections
|
||||
const secureNodes = nodeArray
|
||||
.filter(({ address }) => address.startsWith("https://"))
|
||||
.map(({ address }) => address);
|
||||
|
||||
if (!secureNodes.length) {
|
||||
throw new Error("No SSL enabled RPC nodes available for this chain");
|
||||
}
|
||||
|
||||
for (const node of secureNodes) {
|
||||
try {
|
||||
// test client connection
|
||||
const client = await StargateClient.connect(node);
|
||||
await client.getHeight();
|
||||
return node;
|
||||
} catch {}
|
||||
}
|
||||
|
||||
throw new Error("No RPC nodes available for this chain");
|
||||
};
|
||||
|
||||
const getExplorerFromArray = (explorers: readonly RegistryChainExplorer[]) => {
|
||||
return explorers[0]?.tx_page ?? "";
|
||||
};
|
||||
|
||||
export const getChainFromRegistry = async (chainName: string, isTestnet?: boolean) => {
|
||||
const chainItem = await getChainItemFromRegistry(chainName, isTestnet);
|
||||
const registryAssets = await getAssetItemsFromRegistry(chainName, isTestnet);
|
||||
const firstAsset = registryAssets[0];
|
||||
|
||||
const nodeAddress = await getNodeFromArray(chainItem.apis.rpc);
|
||||
const explorerLink = getExplorerFromArray(chainItem.explorers);
|
||||
const firstAssetDenom = firstAsset.base;
|
||||
const displayDenom = firstAsset.symbol;
|
||||
const displayUnit = firstAsset.denom_units.find((u) => u.denom == firstAsset.display);
|
||||
assert(displayUnit, `Unit not found for ${firstAsset.display}`);
|
||||
|
||||
const feeToken = chainItem.fees.fee_tokens.find((token) => token.denom == firstAssetDenom) ?? {
|
||||
denom: firstAssetDenom,
|
||||
};
|
||||
const gasPrice =
|
||||
feeToken.average_gas_price ??
|
||||
feeToken.low_gas_price ??
|
||||
feeToken.high_gas_price ??
|
||||
feeToken.fixed_min_gas_price ??
|
||||
0.03;
|
||||
const formattedGasPrice = firstAsset ? `${gasPrice}${firstAssetDenom}` : "";
|
||||
|
||||
const chain: ChainInfo = {
|
||||
registryName: chainName,
|
||||
addressPrefix: chainItem.bech32_prefix,
|
||||
chainId: chainItem.chain_id,
|
||||
chainDisplayName: chainItem.pretty_name,
|
||||
nodeAddress,
|
||||
explorerLink,
|
||||
denom: firstAssetDenom,
|
||||
displayDenom,
|
||||
displayDenomExponent: displayUnit.exponent,
|
||||
gasPrice: formattedGasPrice,
|
||||
assets: registryAssets,
|
||||
};
|
||||
|
||||
assert(isChainInfoFilled(chain), `Chain ${chainName} loaded from the registry with missing data`);
|
||||
|
||||
return chain;
|
||||
};
|
||||
|
||||
export const getChain = () => {
|
||||
if (typeof window === "undefined") return emptyChain;
|
||||
|
||||
const rootRoute = location.pathname.split("/")[1];
|
||||
// Avoid app from thinking the /create and /api routes are registryNames
|
||||
const chainNameFromUrl = ["create", "api"].includes(rootRoute) ? null : rootRoute;
|
||||
|
||||
const chainFromUrl = getChainFromUrl(chainNameFromUrl);
|
||||
if (chainFromUrl) {
|
||||
setChainInStorage(chainFromUrl);
|
||||
return chainFromUrl;
|
||||
}
|
||||
|
||||
const chainFromStorage = getChainFromStorage(chainNameFromUrl);
|
||||
if (chainFromStorage) {
|
||||
setChainInUrl(chainFromStorage);
|
||||
return chainFromStorage;
|
||||
}
|
||||
|
||||
const chainFromEnvfile = getChainFromEnvfile(chainNameFromUrl);
|
||||
if (chainFromEnvfile) {
|
||||
setChainInStorage(chainFromEnvfile);
|
||||
setChainInUrl(chainFromEnvfile);
|
||||
return chainFromEnvfile;
|
||||
}
|
||||
|
||||
return { ...emptyChain, registryName: chainNameFromUrl || "cosmoshub" };
|
||||
};
|
||||
72
context/ChainsContext/storage.tsx
Normal file
72
context/ChainsContext/storage.tsx
Normal file
@ -0,0 +1,72 @@
|
||||
import { isChainInfoFilled } from "./helpers";
|
||||
import { ChainInfo } from "./types";
|
||||
|
||||
const localStorageKey = "context-chain-info";
|
||||
|
||||
export const getChainFromUrl = (chainName: string | null) => {
|
||||
const params = new URLSearchParams(location.search);
|
||||
|
||||
const chain: ChainInfo = {
|
||||
registryName: decodeURIComponent(params.get("registryName") || ""),
|
||||
chainId: decodeURIComponent(params.get("chainId") || ""),
|
||||
nodeAddress: decodeURIComponent(params.get("nodeAddress") || ""),
|
||||
denom: decodeURIComponent(params.get("denom") || ""),
|
||||
displayDenom: decodeURIComponent(params.get("displayDenom") || ""),
|
||||
displayDenomExponent: Number(decodeURIComponent(params.get("displayDenomExponent") || "")),
|
||||
assets: JSON.parse(decodeURIComponent(params.get("assets") || "[]")),
|
||||
gasPrice: decodeURIComponent(params.get("gasPrice") || ""),
|
||||
chainDisplayName: decodeURIComponent(params.get("chainDisplayName") || ""),
|
||||
addressPrefix: decodeURIComponent(params.get("addressPrefix") || ""),
|
||||
explorerLink: decodeURIComponent(params.get("explorerLink") || ""),
|
||||
};
|
||||
|
||||
const isChainNameValid = chain.registryName === chainName || !chainName;
|
||||
return isChainNameValid && isChainInfoFilled(chain) ? chain : null;
|
||||
};
|
||||
|
||||
export const setChainInUrl = (chain: ChainInfo) => {
|
||||
const params = new URLSearchParams();
|
||||
|
||||
for (const [key, value] of Object.entries(chain)) {
|
||||
if (typeof value === "object") {
|
||||
params.set(key, encodeURIComponent(JSON.stringify(value)));
|
||||
} else {
|
||||
params.set(key, encodeURIComponent(value ?? ""));
|
||||
}
|
||||
}
|
||||
|
||||
window.history.replaceState({}, "", `${location.pathname}?${params}`);
|
||||
};
|
||||
|
||||
export const getChainFromStorage = (chainName: string | null) => {
|
||||
const storedChain = localStorage.getItem(localStorageKey);
|
||||
if (!storedChain) return null;
|
||||
|
||||
const chain: ChainInfo = JSON.parse(storedChain);
|
||||
const isChainNameValid = chain.registryName === chainName || !chainName;
|
||||
return isChainNameValid && isChainInfoFilled(chain) ? chain : null;
|
||||
};
|
||||
|
||||
export const setChainInStorage = (chain: ChainInfo) => {
|
||||
const stringChain = JSON.stringify(chain);
|
||||
localStorage.setItem(localStorageKey, stringChain);
|
||||
};
|
||||
|
||||
export const getChainFromEnvfile = (chainName: string | null) => {
|
||||
const chain: ChainInfo = {
|
||||
nodeAddress: process.env.NEXT_PUBLIC_NODE_ADDRESS || "",
|
||||
denom: process.env.NEXT_PUBLIC_DENOM || "",
|
||||
displayDenom: process.env.NEXT_PUBLIC_DISPLAY_DENOM || "",
|
||||
displayDenomExponent: Number(process.env.NEXT_PUBLIC_DISPLAY_DENOM_EXPONENT || 0),
|
||||
assets: JSON.parse(process.env.NEXT_PUBLIC_ASSETS || "[]"),
|
||||
gasPrice: process.env.NEXT_PUBLIC_GAS_PRICE || "",
|
||||
chainId: process.env.NEXT_PUBLIC_CHAIN_ID || "",
|
||||
chainDisplayName: process.env.NEXT_PUBLIC_CHAIN_DISPLAY_NAME || "",
|
||||
registryName: process.env.NEXT_PUBLIC_REGISTRY_NAME || "",
|
||||
addressPrefix: process.env.NEXT_PUBLIC_ADDRESS_PREFIX || "",
|
||||
explorerLink: process.env.NEXT_PUBLIC_EXPLORER_LINK_TX || "",
|
||||
};
|
||||
|
||||
const isChainNameValid = chain.registryName === chainName || !chainName;
|
||||
return isChainNameValid && isChainInfoFilled(chain) ? chain : null;
|
||||
};
|
||||
47
context/ChainsContext/types.tsx
Normal file
47
context/ChainsContext/types.tsx
Normal file
@ -0,0 +1,47 @@
|
||||
import { GithubChainRegistryItem, RegistryAsset } from "../../types/chainRegistry";
|
||||
|
||||
export interface ChainsContextType {
|
||||
readonly state: State;
|
||||
readonly dispatch: Dispatch;
|
||||
}
|
||||
|
||||
export interface State {
|
||||
readonly chains: ChainItems;
|
||||
readonly chain: ChainInfo;
|
||||
readonly chainsError?: string | null;
|
||||
}
|
||||
|
||||
export type Dispatch = (action: Action) => void;
|
||||
|
||||
export interface ChainItems {
|
||||
readonly mainnets: readonly GithubChainRegistryItem[];
|
||||
readonly testnets: readonly GithubChainRegistryItem[];
|
||||
}
|
||||
|
||||
export interface ChainInfo {
|
||||
readonly registryName: string;
|
||||
readonly chainId: string;
|
||||
readonly chainDisplayName: string;
|
||||
readonly nodeAddress: string;
|
||||
readonly denom: string;
|
||||
readonly displayDenom: string;
|
||||
readonly displayDenomExponent: number;
|
||||
readonly assets: readonly RegistryAsset[];
|
||||
readonly gasPrice: string;
|
||||
readonly addressPrefix: string;
|
||||
readonly explorerLink: string;
|
||||
}
|
||||
|
||||
export type Action =
|
||||
| {
|
||||
readonly type: "setChains";
|
||||
readonly payload: ChainItems;
|
||||
}
|
||||
| {
|
||||
readonly type: "setChain";
|
||||
readonly payload: ChainInfo;
|
||||
}
|
||||
| {
|
||||
readonly type: "setChainsError";
|
||||
readonly payload: string | null;
|
||||
};
|
||||
@ -2,7 +2,7 @@ import { Coin } from "@cosmjs/amino";
|
||||
import { sha512 } from "@cosmjs/crypto";
|
||||
import { fromBase64, fromBech32, toBase64, toBech32 } from "@cosmjs/encoding";
|
||||
import { Decimal } from "@cosmjs/math";
|
||||
import { ChainInfo } from "../types";
|
||||
import { ChainInfo } from "../context/ChainsContext/types";
|
||||
|
||||
function capitalizeFirstLetter(str: string): string {
|
||||
return str.charAt(0).toUpperCase() + str.slice(1);
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
import { MultisigThresholdPubkey, SinglePubkey } from "@cosmjs/amino";
|
||||
import { Account, StargateClient } from "@cosmjs/stargate";
|
||||
import { assert } from "@cosmjs/utils";
|
||||
import { Coin } from "cosmjs-types/cosmos/base/v1beta1/coin";
|
||||
import { useRouter } from "next/router";
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
@ -10,7 +9,7 @@ import MultisigMembers from "../../../components/dataViews/MultisigMembers";
|
||||
import Button from "../../../components/inputs/Button";
|
||||
import Page from "../../../components/layout/Page";
|
||||
import StackableContainer from "../../../components/layout/StackableContainer";
|
||||
import { useAppContext } from "../../../context/AppContext";
|
||||
import { useChains } from "../../../context/ChainsContext";
|
||||
import { explorerLinkAccount } from "../../../lib/displayHelpers";
|
||||
import { getMultisigAccount } from "../../../lib/multisigHelpers";
|
||||
|
||||
@ -22,8 +21,7 @@ function participantPubkeysFromMultisig(
|
||||
|
||||
const Multipage = () => {
|
||||
const router = useRouter();
|
||||
const { state } = useAppContext();
|
||||
assert(state.chain.addressPrefix, "address prefix missing");
|
||||
const { chain } = useChains();
|
||||
|
||||
const [holdings, setHoldings] = useState<readonly Coin[]>([]);
|
||||
const [accountOnChain, setAccountOnChain] = useState<Account | null>(null);
|
||||
@ -40,15 +38,13 @@ const Multipage = () => {
|
||||
async (address: string) => {
|
||||
setAccountError(null);
|
||||
try {
|
||||
assert(state.chain.nodeAddress, "Node address missing");
|
||||
const client = await StargateClient.connect(state.chain.nodeAddress);
|
||||
assert(state.chain.denom, "denom missing");
|
||||
const client = await StargateClient.connect(chain.nodeAddress);
|
||||
const tempHoldings = await client.getAllBalances(address);
|
||||
setHoldings(tempHoldings);
|
||||
assert(state.chain.addressPrefix, "addressPrefix missing");
|
||||
|
||||
const [newPubkey, newAccountOnChain] = await getMultisigAccount(
|
||||
address,
|
||||
state.chain.addressPrefix,
|
||||
chain.addressPrefix,
|
||||
client,
|
||||
);
|
||||
setPubkey(newPubkey);
|
||||
@ -59,7 +55,7 @@ const Multipage = () => {
|
||||
console.log("Account error:", error);
|
||||
}
|
||||
},
|
||||
[state.chain.addressPrefix, state.chain.denom, state.chain.nodeAddress],
|
||||
[chain.addressPrefix, chain.nodeAddress],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
@ -79,7 +75,7 @@ const Multipage = () => {
|
||||
{pubkey ? (
|
||||
<MultisigMembers
|
||||
members={participantPubkeysFromMultisig(pubkey)}
|
||||
addressPrefix={state.chain.addressPrefix}
|
||||
addressPrefix={chain.addressPrefix}
|
||||
threshold={pubkey.value.threshold}
|
||||
/>
|
||||
) : null}
|
||||
@ -111,7 +107,7 @@ const Multipage = () => {
|
||||
) : null}
|
||||
<Button
|
||||
label="Create New Transaction"
|
||||
onClick={() => router.push(`/multi/${multisigAddress}/transaction/new`)}
|
||||
onClick={() => router.push(`/${chain.registryName}/${multisigAddress}/transaction/new`)}
|
||||
disabled={!accountOnChain || !multisigAddress}
|
||||
/>
|
||||
</StackableContainer>
|
||||
@ -13,7 +13,7 @@ import TransactionSigning from "../../../../components/forms/TransactionSigning"
|
||||
import Button from "../../../../components/inputs/Button";
|
||||
import Page from "../../../../components/layout/Page";
|
||||
import StackableContainer from "../../../../components/layout/StackableContainer";
|
||||
import { useAppContext } from "../../../../context/AppContext";
|
||||
import { useChains } from "../../../../context/ChainsContext";
|
||||
import { findTransactionByID } from "../../../../lib/graphqlHelpers";
|
||||
import { getMultisigAccount } from "../../../../lib/multisigHelpers";
|
||||
import { dbTxFromJson } from "../../../../lib/txMsgHelpers";
|
||||
@ -66,7 +66,7 @@ const TransactionPage = ({
|
||||
signatures: DbSignature[];
|
||||
txHash: string;
|
||||
}) => {
|
||||
const { state } = useAppContext();
|
||||
const { chain } = useChains();
|
||||
const [currentSignatures, setCurrentSignatures] = useState(signatures);
|
||||
const [broadcastError, setBroadcastError] = useState("");
|
||||
const [isBroadcasting, setIsBroadcasting] = useState(false);
|
||||
@ -85,10 +85,9 @@ const TransactionPage = ({
|
||||
const fetchMultisig = useCallback(
|
||||
async (address: string) => {
|
||||
try {
|
||||
assert(state.chain.nodeAddress, "Node address missing");
|
||||
const client = await StargateClient.connect(state.chain.nodeAddress);
|
||||
assert(state.chain.addressPrefix, "addressPrefix missing");
|
||||
const result = await getMultisigAccount(address, state.chain.addressPrefix, client);
|
||||
const client = await StargateClient.connect(chain.nodeAddress);
|
||||
|
||||
const result = await getMultisigAccount(address, chain.addressPrefix, client);
|
||||
setPubkey(result[0]);
|
||||
setAccountOnChain(result[1]);
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
@ -97,7 +96,7 @@ const TransactionPage = ({
|
||||
console.log("Account error:", error);
|
||||
}
|
||||
},
|
||||
[state.chain.addressPrefix, state.chain.nodeAddress],
|
||||
[chain.addressPrefix, chain.nodeAddress],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
@ -126,8 +125,8 @@ const TransactionPage = ({
|
||||
bodyBytes,
|
||||
new Map(currentSignatures.map((s) => [s.address, fromBase64(s.signature)])),
|
||||
);
|
||||
assert(state.chain.nodeAddress, "Node address missing");
|
||||
const broadcaster = await StargateClient.connect(state.chain.nodeAddress);
|
||||
|
||||
const broadcaster = await StargateClient.connect(chain.nodeAddress);
|
||||
const result = await broadcaster.broadcastTx(signedTxBytes);
|
||||
console.log(result);
|
||||
const _res = await axios.post(`/api/transaction/${transactionID}`, {
|
||||
@ -146,7 +145,12 @@ const TransactionPage = ({
|
||||
: false;
|
||||
|
||||
return (
|
||||
<Page goBack={{ pathname: `/multi/${multisigAddress}`, title: "multisig" }}>
|
||||
<Page
|
||||
goBack={{
|
||||
pathname: `/${chain.registryName}/${multisigAddress}`,
|
||||
title: "multisig",
|
||||
}}
|
||||
>
|
||||
<StackableContainer base>
|
||||
<StackableContainer>
|
||||
<h1>{transactionHash ? "Completed Transaction" : "In Progress Transaction"}</h1>
|
||||
@ -1,15 +1,14 @@
|
||||
import { Account, StargateClient } from "@cosmjs/stargate";
|
||||
import { assert } from "@cosmjs/utils";
|
||||
import { useRouter } from "next/router";
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
import CreateTxForm from "../../../../components/forms/CreateTxForm";
|
||||
import Page from "../../../../components/layout/Page";
|
||||
import StackableContainer from "../../../../components/layout/StackableContainer";
|
||||
import { useAppContext } from "../../../../context/AppContext";
|
||||
import { useChains } from "../../../../context/ChainsContext";
|
||||
import { getMultisigAccount } from "../../../../lib/multisigHelpers";
|
||||
|
||||
const NewTransactionPage = () => {
|
||||
const { state } = useAppContext();
|
||||
const { chain } = useChains();
|
||||
const [accountOnChain, setAccountOnChain] = useState<Account | null>(null);
|
||||
const [accountError, setAccountError] = useState(null);
|
||||
const router = useRouter();
|
||||
@ -19,10 +18,9 @@ const NewTransactionPage = () => {
|
||||
async (address: string) => {
|
||||
setAccountError(null);
|
||||
try {
|
||||
assert(state.chain.nodeAddress, "Node address missing");
|
||||
const client = await StargateClient.connect(state.chain.nodeAddress);
|
||||
assert(state.chain.addressPrefix, "addressPrefix missing");
|
||||
const result = await getMultisigAccount(address, state.chain.addressPrefix, client);
|
||||
const client = await StargateClient.connect(chain.nodeAddress);
|
||||
|
||||
const result = await getMultisigAccount(address, chain.addressPrefix, client);
|
||||
setAccountOnChain(result[1]);
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
} catch (error: any) {
|
||||
@ -30,7 +28,7 @@ const NewTransactionPage = () => {
|
||||
console.log("Account error:", error);
|
||||
}
|
||||
},
|
||||
[state.chain.addressPrefix, state.chain.nodeAddress],
|
||||
[chain.addressPrefix, chain.nodeAddress],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
@ -40,7 +38,13 @@ const NewTransactionPage = () => {
|
||||
}, [fetchMultisig, multisigAddress]);
|
||||
|
||||
return (
|
||||
<Page goBack={{ pathname: `/multi/${multisigAddress}`, title: "multisig", needsConfirm: true }}>
|
||||
<Page
|
||||
goBack={{
|
||||
pathname: `/${chain.registryName}/${multisigAddress}`,
|
||||
title: "multisig",
|
||||
needsConfirm: true,
|
||||
}}
|
||||
>
|
||||
<StackableContainer base>
|
||||
{accountError || !accountOnChain ? (
|
||||
<StackableContainer>
|
||||
@ -1,15 +1,14 @@
|
||||
import React from "react";
|
||||
import { AppWrapper } from "../context/AppContext";
|
||||
import ChainSelect from "../components/chainSelect/ChainSelect";
|
||||
import type { AppProps } from "next/app";
|
||||
import ChainSelect from "../components/chainSelect/ChainSelect";
|
||||
import { ChainsProvider } from "../context/ChainsContext";
|
||||
|
||||
function MultisigApp({ Component, pageProps }: AppProps) {
|
||||
const showChainSelect = process.env.NEXT_PUBLIC_MULTICHAIN?.toLowerCase() === "true";
|
||||
return (
|
||||
<AppWrapper>
|
||||
<ChainsProvider>
|
||||
{showChainSelect && <ChainSelect />}
|
||||
<Component {...pageProps} />
|
||||
</AppWrapper>
|
||||
</ChainsProvider>
|
||||
);
|
||||
}
|
||||
export default MultisigApp;
|
||||
|
||||
@ -1,18 +1,17 @@
|
||||
import React from "react";
|
||||
|
||||
import FindMultisigForm from "../components/forms/FindMultisigForm";
|
||||
import Page from "../components/layout/Page";
|
||||
import StackableContainer from "../components/layout/StackableContainer";
|
||||
import { useAppContext } from "../context/AppContext";
|
||||
import { useChains } from "../context/ChainsContext";
|
||||
|
||||
const MultiPage = () => {
|
||||
const { state } = useAppContext();
|
||||
const { chain } = useChains();
|
||||
|
||||
return (
|
||||
<Page>
|
||||
<StackableContainer base>
|
||||
<StackableContainer lessPadding>
|
||||
<h1 className="title">
|
||||
<span>{state.chain.chainDisplayName}</span> Multisig Manager
|
||||
<span>{chain.chainDisplayName}</span> Multisig Manager
|
||||
</h1>
|
||||
</StackableContainer>
|
||||
<FindMultisigForm />
|
||||
|
||||
@ -1,4 +1,19 @@
|
||||
import axios from "axios";
|
||||
export 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;
|
||||
};
|
||||
}
|
||||
|
||||
export interface RegistryChainApisRpc {
|
||||
readonly address: string;
|
||||
@ -36,57 +51,28 @@ export interface RegistryChain {
|
||||
readonly pretty_name: string;
|
||||
}
|
||||
|
||||
export interface RegistryChainResponse {
|
||||
readonly data: RegistryChain;
|
||||
}
|
||||
|
||||
/**
|
||||
* See https://github.com/cosmos/chain-registry/blob/1e9ecde770951cab90f0853a624411d79af90b83/provenance/assetlist.json#L8-L12
|
||||
*/
|
||||
export interface RegistryAssetDenomUnit {
|
||||
denom: string;
|
||||
exponent: number;
|
||||
aliases?: string[];
|
||||
readonly denom: string;
|
||||
readonly exponent: number;
|
||||
readonly aliases?: readonly string[];
|
||||
}
|
||||
|
||||
/**
|
||||
* See https://github.com/cosmos/chain-registry/blob/1e9ecde770951cab90f0853a624411d79af90b83/provenance/assetlist.json#L5-L28
|
||||
*/
|
||||
export interface RegistryAsset {
|
||||
description: string;
|
||||
denom_units: RegistryAssetDenomUnit[];
|
||||
base: string;
|
||||
name: string;
|
||||
display: string;
|
||||
symbol: string;
|
||||
logo_URIs: {
|
||||
png: string;
|
||||
svg: string;
|
||||
readonly denom_units: readonly RegistryAssetDenomUnit[];
|
||||
readonly base: string;
|
||||
readonly display: string;
|
||||
readonly name: string;
|
||||
readonly symbol: string;
|
||||
readonly description?: string;
|
||||
readonly logo_URIs?: {
|
||||
readonly png: string;
|
||||
readonly svg: string;
|
||||
};
|
||||
coingecko_id: string;
|
||||
readonly coingecko_id?: string;
|
||||
}
|
||||
|
||||
export interface RegistryAssetsResponse {
|
||||
readonly data: { readonly assets: readonly RegistryAsset[] };
|
||||
}
|
||||
|
||||
const registryGhUrl = "https://cdn.jsdelivr.net/gh/cosmos/chain-registry@master/";
|
||||
|
||||
export const getChainFromRegistry = async (chainGhName: string): Promise<RegistryChain> => {
|
||||
const chainGhUrl = registryGhUrl + chainGhName + "/chain.json";
|
||||
|
||||
const { data: chain }: RegistryChainResponse = await axios.get(chainGhUrl);
|
||||
return chain;
|
||||
};
|
||||
|
||||
export const getAssetsFromRegistry = async (
|
||||
chainGhName: string,
|
||||
): Promise<readonly RegistryAsset[]> => {
|
||||
const assetsGhUrl = registryGhUrl + chainGhName + "/assetlist.json";
|
||||
|
||||
const {
|
||||
data: { assets },
|
||||
}: RegistryAssetsResponse = await axios.get(assetsGhUrl);
|
||||
|
||||
return assets;
|
||||
};
|
||||
@ -1,6 +1,5 @@
|
||||
import { StdFee } from "@cosmjs/amino";
|
||||
import { EncodeObject } from "@cosmjs/proto-signing";
|
||||
import { RegistryAsset } from "../components/chainSelect/chainregistry";
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
@ -46,17 +45,3 @@ export interface WalletAccount {
|
||||
isNanoLedger?: boolean;
|
||||
name?: string;
|
||||
}
|
||||
|
||||
export interface ChainInfo {
|
||||
nodeAddress?: string;
|
||||
denom?: string;
|
||||
displayDenom?: string;
|
||||
displayDenomExponent?: number;
|
||||
assets?: readonly RegistryAsset[];
|
||||
gasPrice?: string;
|
||||
chainId?: string;
|
||||
chainDisplayName?: string;
|
||||
registryName?: string;
|
||||
addressPrefix?: string;
|
||||
explorerLink?: string;
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user