diff --git a/components/ChainConnect/CustomChainForm.tsx b/components/ChainConnect/CustomChainForm.tsx index bb34867..7d5afde 100644 --- a/components/ChainConnect/CustomChainForm.tsx +++ b/components/ChainConnect/CustomChainForm.tsx @@ -37,7 +37,8 @@ export default function CustomChainForm() { bech32Prefix: z.string({ required_error: "Address prefix is required" }), gasPrice: z.string({ required_error: "Gas price is required" }), rpcNodes: z.string({ required_error: "Comma separated rpc nodes are required" }), - explorerLink: z.string({ required_error: "Explorer url is required" }), + explorerTxLink: z.string({ required_error: "Explorer tx url is required" }), + explorerAccountLink: z.string({ required_error: "Explorer account url is required" }), logo: z.string({ required_error: "Logo url is required" }), assets: z.string({ required_error: "Assets json is required" }), }) @@ -56,7 +57,8 @@ export default function CustomChainForm() { bech32Prefix: defaultChain.addressPrefix, gasPrice: defaultChain.gasPrice, rpcNodes: defaultChain.nodeAddresses.join(", "), - explorerLink: defaultChain.explorerLink, + explorerTxLink: defaultChain.explorerLink.tx, + explorerAccountLink: defaultChain.explorerLink.account, logo: defaultChain.logo, assets: JSON.stringify(defaultChain.assets), }, @@ -80,7 +82,10 @@ export default function CustomChainForm() { assets: JSON.parse(chainFromForm.assets) as RegistryAsset[], gasPrice: chainFromForm.gasPrice, addressPrefix: chainFromForm.bech32Prefix, - explorerLink: chainFromForm.explorerLink, + explorerLink: { + tx: chainFromForm.explorerTxLink, + account: chainFromForm.explorerAccountLink, + }, }, }); } @@ -190,10 +195,10 @@ export default function CustomChainForm() { )} /> ( - Explorer Link + Explorer Tx Link @@ -202,6 +207,19 @@ export default function CustomChainForm() { )} /> + ( + + Explorer Account Link + + + + with {"'${accountAddress}'"} included + + + )} + /> ): chain is ChainInfo => @@ -30,8 +30,7 @@ export const isChainInfoFilled = (chain: Partial): chain is ChainInfo chain.displayDenomExponent >= 0 && chain.assets?.length && chain.gasPrice && - chain.addressPrefix && - chain.explorerLink, + chain.addressPrefix, ); export const setChains = (dispatch: Dispatch, chains: ChainItems) => { diff --git a/context/ChainsContext/storage.tsx b/context/ChainsContext/storage.tsx index 1f8e96a..6aa3155 100644 --- a/context/ChainsContext/storage.tsx +++ b/context/ChainsContext/storage.tsx @@ -118,6 +118,7 @@ export const getChainFromUrl = (chainName: string) => { const nodeAddressesValue: readonly string[] = JSON.parse(nodeAddresses || "[]"); const assetsValue: readonly RegistryAsset[] = JSON.parse(assets || "[]"); + const explorerLinkValue: Partial = JSON.parse(explorerLink || "{}"); const urlChain: Partial = { registryName: chainName, @@ -132,7 +133,9 @@ export const getChainFromUrl = (chainName: string) => { ...(assetsValue.length && { assets: assetsValue }), ...(gasPrice && { gasPrice }), ...(addressPrefix && { addressPrefix }), - ...(explorerLink && { explorerLink }), + ...(explorerLink && { + explorerLink: { tx: explorerLinkValue.tx || "", account: explorerLinkValue.account || "" }, + }), }; return urlChain; @@ -158,6 +161,7 @@ export const getChainFromEnvfile = (chainName: string) => { const nodeAddressesValue: readonly string[] = JSON.parse(nodeAddresses || "[]"); const assetsValue: readonly RegistryAsset[] = JSON.parse(assets || "[]"); + const explorerLinkValue: Partial = JSON.parse(explorerLink || "{}"); const envfileChain: Partial = { registryName: chainName, @@ -172,7 +176,9 @@ export const getChainFromEnvfile = (chainName: string) => { ...(assetsValue.length && { assets: assetsValue }), ...(gasPrice && { gasPrice }), ...(addressPrefix && { addressPrefix }), - ...(explorerLink && { explorerLink }), + ...(explorerLinkValue && { + explorerLink: { tx: explorerLinkValue.tx || "", account: explorerLinkValue.account || "" }, + }), }; return envfileChain; diff --git a/context/ChainsContext/types.tsx b/context/ChainsContext/types.tsx index 543af8a..463bd59 100644 --- a/context/ChainsContext/types.tsx +++ b/context/ChainsContext/types.tsx @@ -33,9 +33,14 @@ export interface ChainInfo { readonly assets: readonly RegistryAsset[]; readonly gasPrice: string; readonly addressPrefix: string; - readonly explorerLink: string; + readonly explorerLink: ExplorerLink; } +export type ExplorerLink = { + readonly tx: string; + readonly account: string; +}; + export type NewConnection = | { readonly action: "edit"; diff --git a/lib/chainRegistry.ts b/lib/chainRegistry.ts index 2df591f..e3351db 100644 --- a/lib/chainRegistry.ts +++ b/lib/chainRegistry.ts @@ -1,5 +1,5 @@ import { isChainInfoFilled } from "@/context/ChainsContext/helpers"; -import { ChainInfo, ChainItems } from "@/context/ChainsContext/types"; +import { ChainInfo, ChainItems, ExplorerLink } from "@/context/ChainsContext/types"; import { GithubChainRegistryItem, RegistryAsset, RegistryChain } from "@/types/chainRegistry"; import { requestGhJson } from "./request"; @@ -123,7 +123,25 @@ const getChainInfoFromJsons = ( const firstAsset = cdnRegistryAssets[0]; const logo = getLogoUri(registryChain, firstAsset); const nodeAddresses = registryChain.apis?.rpc.map(({ address }) => address) ?? []; - const explorerLink = registryChain.explorers?.[0]?.tx_page ?? ""; + + let explorerLink: ExplorerLink = { tx: "", account: "" }; + + // Prefer same explorer for both tx and account links + for (const explorer of registryChain.explorers ?? []) { + if (explorer.tx_page && explorer.account_page) { + explorerLink = { tx: explorer.tx_page, account: explorer.account_page }; + break; + } + + if (!explorerLink.tx && explorer.tx_page) { + explorerLink = { ...explorerLink, tx: explorer.tx_page }; + } + + if (!explorerLink.account && explorer.account_page) { + explorerLink = { ...explorerLink, account: explorer.account_page }; + } + } + const firstAssetDenom = firstAsset.base; const displayUnit = firstAsset.denom_units.find((u) => u.denom == firstAsset.display); const displayDenom = displayUnit ? firstAsset.symbol : firstAsset.base; diff --git a/lib/displayHelpers.spec.ts b/lib/displayHelpers.spec.ts index 66ac39e..c93edc6 100644 --- a/lib/displayHelpers.spec.ts +++ b/lib/displayHelpers.spec.ts @@ -7,8 +7,13 @@ const testChainInfo: ChainInfo = { addressPrefix: "juno", chainId: "uni-6", chainDisplayName: "Juno Testnet", + logo: "https://raw.githubusercontent.com/cosmos/chain-registry/master/testnets/junotestnet/images/juno.svg", nodeAddress: "https://rpc.uni.junonetwork.io", - explorerLink: "https://testnet.ezstaking.tools/juno-testnet/txs/${txHash}", + nodeAddresses: ["https://rpc.uni.junonetwork.io"], + explorerLink: { + tx: "https://testnet.ezstaking.tools/juno-testnet/txs/${txHash}", + account: "https://testnet.app.ezstaking.io/juno-testnet/account/${accountAddress}", + }, denom: "ujunox", displayDenom: "JUNOX", displayDenomExponent: 6, diff --git a/lib/displayHelpers.ts b/lib/displayHelpers.ts index 095c274..d69b45c 100644 --- a/lib/displayHelpers.ts +++ b/lib/displayHelpers.ts @@ -162,8 +162,8 @@ const explorerLinkTx = (link: string, hash: string) => { * for accounts. Returns null otherwise. */ const explorerLinkAccount = (link: string, address: string) => { - if (link && link.includes("${address}")) { - return link.replace("${address}", address); + if (link && link.includes("${accountAddress}")) { + return link.replace("${accountAddress}", address); } return null; }; diff --git a/pages/[chainName]/[address]/index.tsx b/pages/[chainName]/[address]/index.tsx index 5c4df27..71c09bd 100644 --- a/pages/[chainName]/[address]/index.tsx +++ b/pages/[chainName]/[address]/index.tsx @@ -29,10 +29,7 @@ const Multipage = () => { const [accountError, setAccountError] = useState(null); const multisigAddress = router.query.address?.toString(); - const explorerHref = explorerLinkAccount( - process.env.NEXT_PUBLIC_EXPLORER_LINK_ACCOUNT || "", - multisigAddress || "", - ); + const explorerLink = explorerLinkAccount(chain.explorerLink.account, multisigAddress || ""); const fetchMultisig = useCallback( async (address: string) => { @@ -70,7 +67,7 @@ const Multipage = () => {

{multisigAddress ? : "No Address"}

- {explorerHref ? : null} + {explorerLink ? : null}
{pubkey ? (