cosmos-multisig-ui/lib/chainRegistry.ts

198 lines
6.6 KiB
TypeScript

import { isChainInfoFilled } from "@/context/ChainsContext/helpers";
import { ChainInfo, ChainItems, ExplorerLinks } from "@/context/ChainsContext/types";
import { GithubChainRegistryItem, RegistryAsset, RegistryChain } from "@/types/chainRegistry";
import { preventUnhandledRejections } from "./promises";
import { requestGhJson } from "./request";
const chainRegistryRepo = "cosmos/chain-registry";
const repoBranch = "master";
const shaUrl = `https://api.github.com/repos/${chainRegistryRepo}/commits/${repoBranch}`;
const mainnetsUrl = `https://api.github.com/repos/${chainRegistryRepo}/contents`;
const testnetsUrl = `https://api.github.com/repos/${chainRegistryRepo}/contents/testnets`;
const registryCdnUrl = `https://cdn.jsdelivr.net/gh/${chainRegistryRepo}@${repoBranch}`;
const getShaFromRegistry = async () => {
const { sha }: { sha: string } = await requestGhJson(shaUrl);
return sha;
};
interface RegistryPromises {
readonly chainInfo: Promise<RegistryChain>;
readonly assetList: Promise<{ readonly assets: readonly RegistryAsset[] }>;
}
const getChainsFromRegistry = async () => {
const chains: ChainItems = { mainnets: new Map(), testnets: new Map(), localnets: new Map() };
const [mainnetGhItems, testnetGhItems]: [
readonly GithubChainRegistryItem[],
readonly GithubChainRegistryItem[],
] = await Promise.all([requestGhJson(mainnetsUrl), requestGhJson(testnetsUrl)]);
const mainnetPromisesMap = new Map<string, RegistryPromises>();
for (const { type, path } of mainnetGhItems) {
if (type !== "dir" || path.startsWith(".") || path.startsWith("_") || path === "testnets") {
continue;
}
mainnetPromisesMap.set(path, {
chainInfo: requestGhJson(`${registryCdnUrl}/${path}/chain.json`),
assetList: requestGhJson(`${registryCdnUrl}/${path}/assetlist.json`),
});
}
const mainnetPromisesArray = [
...Array.from(mainnetPromisesMap.values()).map(({ chainInfo }) => chainInfo),
...Array.from(mainnetPromisesMap.values()).map(({ assetList }) => assetList),
];
preventUnhandledRejections(...mainnetPromisesArray);
await Promise.allSettled(mainnetPromisesArray);
for (const { chainInfo, assetList } of mainnetPromisesMap.values()) {
try {
const registryChain = await chainInfo;
const { assets }: { assets: readonly RegistryAsset[] } = await assetList;
const chain = getChainInfoFromJsons(registryChain, assets);
if (isChainInfoFilled(chain)) {
chains.mainnets.set(chain.registryName, chain);
}
} catch {}
}
const testnetPromisesMap = new Map<string, RegistryPromises>();
for (const { type, path } of testnetGhItems) {
if (type !== "dir" || path.startsWith("testnets/.") || path.startsWith("testnets/_")) {
continue;
}
testnetPromisesMap.set(path, {
chainInfo: requestGhJson(`${registryCdnUrl}/${path}/chain.json`),
assetList: requestGhJson(`${registryCdnUrl}/${path}/assetlist.json`),
});
}
const testnetPromisesArray = [
...Array.from(testnetPromisesMap.values()).map(({ chainInfo }) => chainInfo),
...Array.from(testnetPromisesMap.values()).map(({ assetList }) => assetList),
];
preventUnhandledRejections(...testnetPromisesArray);
await Promise.allSettled(testnetPromisesArray);
for (const { chainInfo, assetList } of testnetPromisesMap.values()) {
try {
const registryChain = await chainInfo;
const { assets }: { assets: readonly RegistryAsset[] } = await assetList;
const chain = getChainInfoFromJsons(registryChain, assets);
if (isChainInfoFilled(chain)) {
chains.testnets.set(chain.registryName, chain);
}
} catch {}
}
return chains;
};
const getParsedCdnLogoUri = (registryUri: string | undefined) => {
if (!registryUri?.includes("github") || !registryUri.includes(chainRegistryRepo)) {
return registryUri;
}
const [, path] = registryUri.split(`${chainRegistryRepo}/${repoBranch}`);
return `${registryCdnUrl}${path}`;
};
const getLogoUri = (
{ logo_URIs: chainUris }: RegistryChain,
{ logo_URIs: firstAssetUris }: RegistryAsset,
) =>
getParsedCdnLogoUri(chainUris?.svg) ||
getParsedCdnLogoUri(chainUris?.png) ||
firstAssetUris?.svg ||
firstAssetUris?.png ||
"";
const getChainInfoFromJsons = (
registryChain: RegistryChain,
registryAssets: readonly RegistryAsset[],
): ChainInfo => {
const cdnRegistryAssets: readonly RegistryAsset[] = registryAssets.map(
({ logo_URIs, ...restProps }) => ({
logo_URIs: logo_URIs
? {
png: getParsedCdnLogoUri(logo_URIs.png) || "",
svg: getParsedCdnLogoUri(logo_URIs.svg) || "",
}
: undefined,
...restProps,
}),
);
const firstAsset = cdnRegistryAssets[0];
const logo = getLogoUri(registryChain, firstAsset);
const nodeAddresses = registryChain.apis?.rpc.map(({ address }) => address) ?? [];
let explorerLinks: ExplorerLinks = { tx: "", account: "" };
// Prefer same explorer for both tx and account links
for (const explorer of registryChain.explorers ?? []) {
if (explorer.tx_page && explorer.account_page) {
explorerLinks = { tx: explorer.tx_page, account: explorer.account_page };
break;
}
if (!explorerLinks.tx && explorer.tx_page) {
explorerLinks = { ...explorerLinks, tx: explorer.tx_page };
}
if (!explorerLinks.account && explorer.account_page) {
explorerLinks = { ...explorerLinks, 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;
const displayDenomExponent = displayUnit
? displayUnit.exponent
: firstAsset.denom_units[0].exponent;
const feeToken = registryChain.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: registryChain.chain_name,
logo,
addressPrefix: registryChain.bech32_prefix,
chainId: registryChain.chain_id,
chainDisplayName: registryChain.pretty_name,
nodeAddresses,
nodeAddress: "",
explorerLinks: explorerLinks,
denom: firstAssetDenom,
displayDenom,
displayDenomExponent,
gasPrice: formattedGasPrice,
assets: cdnRegistryAssets,
};
return chain;
};
export { getChainsFromRegistry, getShaFromRegistry };