Skip to content

Commit

Permalink
Merge pull request #162 from cosmos/chore/custom-request
Browse files Browse the repository at this point in the history
Remove axios and use custom fetch wrapper
  • Loading branch information
abefernan authored Jul 26, 2023
2 parents 554dbd7 + b710573 commit 43ebe91
Show file tree
Hide file tree
Showing 16 changed files with 1,137 additions and 8,659 deletions.
8 changes: 3 additions & 5 deletions components/forms/CreateTxForm/index.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { EncodeObject } from "@cosmjs/proto-signing";
import { Account, calculateFee } from "@cosmjs/stargate";
import { assert } from "@cosmjs/utils";
import axios from "axios";
import { NextRouter, withRouter } from "next/router";
import { useRef, useState } from "react";
import { useChains } from "../../../context/ChainsContext";
import { requestJson } from "../../../lib/request";
import { exportMsgToJson, gasOfTx } from "../../../lib/txMsgHelpers";
import { DbTransaction } from "../../../types";
import { MsgTypeUrl, MsgTypeUrls } from "../../../types/txMsg";
Expand Down Expand Up @@ -74,10 +74,8 @@ const CreateTxForm = ({ router, senderAddress, accountOnChain }: CreateTxFormPro
memo,
};

const {
data: { transactionID },
} = await axios.post("/api/transaction", {
dataJSON: JSON.stringify(tx),
const { transactionID } = await requestJson("/api/transaction", {
body: { dataJSON: JSON.stringify(tx) },
});

router.push(`/${chain.registryName}/${senderAddress}/transaction/${transactionID}`);
Expand Down
7 changes: 2 additions & 5 deletions components/forms/TransactionSigning.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@ import { LedgerSigner } from "@cosmjs/ledger-amino";
import { SigningStargateClient } from "@cosmjs/stargate";
import { assert } from "@cosmjs/utils";
import TransportWebUSB from "@ledgerhq/hw-transport-webusb";
import axios from "axios";
import { useCallback, useLayoutEffect, useState } from "react";
import { useChains } from "../../context/ChainsContext";
import { getConnectError } from "../../lib/errorHelpers";
import { requestJson } from "../../lib/request";
import { DbSignature, DbTransaction, WalletAccount } from "../../types";
import HashView from "../dataViews/HashView";
import Button from "../inputs/Button";
Expand Down Expand Up @@ -173,10 +173,7 @@ const TransactionSigning = (props: TransactionSigningProps) => {
signature: bases64EncodedSignature,
address: signerAddress,
};
const _res = await axios.post(
`/api/transaction/${props.transactionID}/signature`,
signature,
);
await requestJson(`/api/transaction/${props.transactionID}/signature`, { body: signature });
props.addSignature(signature);
setSigning("signed");
}
Expand Down
56 changes: 16 additions & 40 deletions context/ChainsContext/index.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { ReactNode, createContext, useContext, useEffect, useReducer } from "react";
import { isChainInfoFilled, setChain, setChains, setChainsError } from "./helpers";
import { getChain, getChainFromRegistry, getChainItemsFromRegistry } from "./service";
import { setChain, setChains, setChainsError } from "./helpers";
import { getChain, useChainFromRegistry, useChainsFromRegistry } from "./service";
import { setChainInStorage, setChainInUrl } from "./storage";
import { Action, ChainsContextType, State } from "./types";

Expand Down Expand Up @@ -35,47 +35,23 @@ export const ChainsProvider = ({ children }: ChainsProviderProps) => {
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");
}
}
})();
}, []);
const { chainItems, chainItemsError } = useChainsFromRegistry();
const { chainFromRegistry, chainFromRegistryError } = useChainFromRegistry(
state.chain,
chainItems,
);

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,
);
setChains(dispatch, chainItems);
setChainsError(dispatch, chainItemsError);
}, [chainItems, chainItemsError]);

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]);
useEffect(() => {
if (chainFromRegistry !== state.chain) {
setChain(dispatch, chainFromRegistry);
setChainsError(dispatch, chainFromRegistryError);
}
}, [chainFromRegistry, chainFromRegistryError, state.chain]);

return <ChainsContext.Provider value={{ state, dispatch }}>{children}</ChainsContext.Provider>;
};
Expand Down
182 changes: 107 additions & 75 deletions context/ChainsContext/service.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { StargateClient } from "@cosmjs/stargate";
import { assert } from "@cosmjs/utils";
import { useEffect, useState } from "react";
import { requestJson } from "../../lib/request";
import {
GithubChainRegistryItem,
RegistryAsset,
Expand All @@ -19,53 +20,52 @@ 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),
};
const registryGhUrl = "https://cdn.jsdelivr.net/gh/cosmos/chain-registry/";

const nonChainsFilter = (item: GithubChainRegistryItem) =>
item.type === "dir" && !item.name.startsWith(".") && !item.name.startsWith("_");

export const useChainsFromRegistry = () => {
const [chainItems, setChainItems] = useState<ChainItems>({ mainnets: [], testnets: [] });
const [chainItemsError, setChainItemsError] = useState<string | null>(null);

useEffect(() => {
(async function () {
try {
const [mainnets, testnets] = await Promise.all([
requestJson(chainsUrl),
requestJson(testnetsUrl),
]);
setChainItems({
mainnets: mainnets.filter(nonChainsFilter),
testnets: testnets.filter(nonChainsFilter),
});
} catch (e) {
if (e instanceof Error) {
setChainItemsError(e.message);
} else {
setChainItemsError("Failed to get chains from registry");
}
}
})();
}, []);

return { chainItems, chainItemsError };
};

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();
const chain: RegistryChain = await requestJson(chainGhUrl);
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;
const assets: readonly RegistryAsset[] = (await requestJson(assetsGhUrl)).assets;
return assets;
};

Expand Down Expand Up @@ -95,46 +95,78 @@ 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 useChainFromRegistry = (chain: ChainInfo, chains: ChainItems) => {
const [chainFromRegistry, setChainFromRegistry] = useState<ChainInfo>(chain);
const [chainFromRegistryError, setChainFromRegistryError] = useState<string | null>(null);

useEffect(() => {
(async function () {
try {
if (isChainInfoFilled(chain) || !chains.mainnets.length || !chains.testnets.length) {
return;
}

const isTestnet = !!chains.testnets.find(({ name }) => name === chain.registryName);
const chainItem = await getChainItemFromRegistry(chain.registryName, isTestnet);
const registryAssets = await getAssetItemsFromRegistry(chain.registryName, 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);

if (!displayUnit) {
return setChainFromRegistryError(`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 newChain: ChainInfo = {
registryName: chain.registryName,
addressPrefix: chainItem.bech32_prefix,
chainId: chainItem.chain_id,
chainDisplayName: chainItem.pretty_name,
nodeAddress,
explorerLink,
denom: firstAssetDenom,
displayDenom,
displayDenomExponent: displayUnit.exponent,
gasPrice: formattedGasPrice,
assets: registryAssets,
};

if (!isChainInfoFilled(newChain)) {
setChainFromRegistryError(
`Chain ${newChain.registryName} loaded from the registry with missing data`,
);
return;
}

setChainFromRegistry(newChain);
} catch (e) {
if (e instanceof Error) {
setChainFromRegistryError(e.message);
} else {
setChainFromRegistryError(`Failed to get chain ${chain.registryName} from registry`);
}
}
})();
}, [chain, chains.mainnets.length, chains.testnets]);

return { chainFromRegistry, chainFromRegistryError };
};

export const getChain = () => {
Expand Down
31 changes: 31 additions & 0 deletions hooks/useRequestJson.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { useEffect, useState } from "react";
import { RequestConfig, requestJson } from "../lib/request";

type Status =
| { readonly stage: "idle" | "loading"; readonly data?: never; readonly error?: never }
| { readonly stage: "resolved"; readonly data: unknown; readonly error?: never }
| { readonly stage: "rejected"; readonly data?: never; readonly error: string };

export default function useRequestJson(endpoint: string, config: RequestConfig = {}) {
const [status, setStatus] = useState<Status>({ stage: "idle" });
const { stage, data, error } = status;

useEffect(() => {
(async function () {
if (stage === "idle") {
setStatus({ stage: "loading" });
}

if (stage === "loading") {
try {
const newData = await requestJson(endpoint, config);
setStatus({ stage: "resolved", data: newData });
} catch (e) {
setStatus({ stage: "rejected", error: e instanceof Error ? e.message : String(e) });
}
}
})();
}, [config, endpoint, stage]);

return { loading: stage === "idle" || stage === "loading", data, error };
}
Loading

1 comment on commit 43ebe91

@vercel
Copy link

@vercel vercel bot commented on 43ebe91 Jul 26, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.