Skip to content

Commit

Permalink
(feat): pr feedback pt 1
Browse files Browse the repository at this point in the history
  • Loading branch information
Jipperism committed Aug 7, 2024
1 parent 32f01d2 commit 23c6e03
Show file tree
Hide file tree
Showing 3 changed files with 130 additions and 80 deletions.
49 changes: 49 additions & 0 deletions src/abis/AggregatorV3Abi.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
export const AggregatorV3Abi = [
{
inputs: [],
name: "decimals",
outputs: [{ internalType: "uint8", name: "", type: "uint8" }],
stateMutability: "view",
type: "function",
},
{
inputs: [],
name: "description",
outputs: [{ internalType: "string", name: "", type: "string" }],
stateMutability: "view",
type: "function",
},
{
inputs: [{ internalType: "uint80", name: "_roundId", type: "uint80" }],
name: "getRoundData",
outputs: [
{ internalType: "uint80", name: "roundId", type: "uint80" },
{ internalType: "int256", name: "answer", type: "int256" },
{ internalType: "uint256", name: "startedAt", type: "uint256" },
{ internalType: "uint256", name: "updatedAt", type: "uint256" },
{ internalType: "uint80", name: "answeredInRound", type: "uint80" },
],
stateMutability: "view",
type: "function",
},
{
inputs: [],
name: "latestRoundData",
outputs: [
{ internalType: "uint80", name: "roundId", type: "uint80" },
{ internalType: "int256", name: "answer", type: "int256" },
{ internalType: "uint256", name: "startedAt", type: "uint256" },
{ internalType: "uint256", name: "updatedAt", type: "uint256" },
{ internalType: "uint80", name: "answeredInRound", type: "uint80" },
],
stateMutability: "view",
type: "function",
},
{
inputs: [],
name: "version",
outputs: [{ internalType: "uint256", name: "", type: "uint256" }],
stateMutability: "view",
type: "function",
},
] as const;
44 changes: 44 additions & 0 deletions src/utils/getRpcUrl.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,23 @@
import { alchemyApiKey, drpcApiPkey, infuraApiKey } from "./constants.js";
import { createPublicClient, fallback, http } from "viem";
import { base, baseSepolia, celo, optimism, sepolia } from "viem/chains";

export const selectedNetwork = (chainId: number) => {
switch (chainId) {
case 10:
return optimism;
case 8453:
return base;
case 42220:
return celo;
case 84532:
return baseSepolia;
case 11155111:
return sepolia;
default:
throw new Error(`Unsupported chain ID: ${chainId}`);
}
};

export const alchemyUrl = (chainId: number) => {
switch (chainId) {
Expand Down Expand Up @@ -51,9 +70,34 @@ const drpcUrl = (chainId: number) => {
}
};

const rpc_timeout = 20_000;

export const getRpcUrl = (chainId: number) => {
const alchemy = alchemyUrl(chainId);
const infura = infuraUrl(chainId);
const drpc = drpcUrl(chainId);
return [alchemy, infura, drpc].filter((url) => url)[0];
};

const fallBackProvider = (chainId: number) => {
const alchemy = alchemyUrl(chainId)
? [http(alchemyUrl(chainId), { timeout: rpc_timeout })]
: [];
const infura = infuraUrl(chainId)
? [http(infuraUrl(chainId), { timeout: rpc_timeout })]
: [];
const drpc = drpcUrl(chainId)
? [http(drpcUrl(chainId), { timeout: rpc_timeout })]
: [];
return fallback([...alchemy, ...drpc, ...infura], {
retryCount: 5,
});
};

/* Returns a PublicClient instance for the configured network. */
// @ts-expect-error viem typings
export const getEvmClient = (chainId: number) =>
createPublicClient({
chain: selectedNetwork(chainId),
transport: fallBackProvider(chainId),
});
117 changes: 37 additions & 80 deletions src/utils/getTokenPriceInUSD.ts
Original file line number Diff line number Diff line change
@@ -1,97 +1,60 @@
import { ethers } from "ethers";
import { getRpcUrl } from "./getRpcUrl.js";
import {
ChainId,
currenciesByNetwork,
Currency,
} from "@hypercerts-org/marketplace-sdk";
import { getAddress } from "viem";
import { getEvmClient } from "./getRpcUrl.js";
import { AggregatorV3Abi } from "../abis/AggregatorV3Abi.js";

export const getTokenPriceInUSD = async (
chainId: ChainId,
tokenAddress: string,
) => {
const provider = new ethers.JsonRpcProvider(getRpcUrl(chainId));
// This constant describes the ABI interface of the contract, which will provide the price of ETH
// It looks like a lot, and it is, but this information is generated when we compile the contract
// We need to let ethers know how to interact with this contract.
const aggregatorV3InterfaceABI = [
{
inputs: [],
name: "decimals",
outputs: [{ internalType: "uint8", name: "", type: "uint8" }],
stateMutability: "view",
type: "function",
},
{
inputs: [],
name: "description",
outputs: [{ internalType: "string", name: "", type: "string" }],
stateMutability: "view",
type: "function",
},
{
inputs: [{ internalType: "uint80", name: "_roundId", type: "uint80" }],
name: "getRoundData",
outputs: [
{ internalType: "uint80", name: "roundId", type: "uint80" },
{ internalType: "int256", name: "answer", type: "int256" },
{ internalType: "uint256", name: "startedAt", type: "uint256" },
{ internalType: "uint256", name: "updatedAt", type: "uint256" },
{ internalType: "uint80", name: "answeredInRound", type: "uint80" },
],
stateMutability: "view",
type: "function",
},
{
inputs: [],
name: "latestRoundData",
outputs: [
{ internalType: "uint80", name: "roundId", type: "uint80" },
{ internalType: "int256", name: "answer", type: "int256" },
{ internalType: "uint256", name: "startedAt", type: "uint256" },
{ internalType: "uint256", name: "updatedAt", type: "uint256" },
{ internalType: "uint80", name: "answeredInRound", type: "uint80" },
],
stateMutability: "view",
type: "function",
},
{
inputs: [],
name: "version",
outputs: [{ internalType: "uint256", name: "", type: "uint256" }],
stateMutability: "view",
type: "function",
},
];
const client = getEvmClient(chainId);

// The address of the contract which will provide the price of ETH
const feedAddress = tokenAddressToFeedAddress(chainId, tokenAddress);

if (!feedAddress) {
throw new Error("Token not supported");
throw new Error(`Feed address not found for ${tokenAddress}`);
}

// We create an instance of the contract which we can interact with
const priceFeed = new ethers.Contract(
feedAddress,
aggregatorV3InterfaceABI,
provider,
);
// We get the data from the last round of the contract
// Determine how many decimals the price feed has (10**decimals)
const [roundData, decimals] = await Promise.all([
priceFeed.latestRoundData(),
priceFeed.decimals(),
]);
const priceFeed = {
abi: AggregatorV3Abi,
address: feedAddress,
};

const [roundDataResult, decimalsResult] = await client.multicall({
contracts: [
{ ...priceFeed, functionName: "latestRoundData" },
{ ...priceFeed, functionName: "decimals" },
],
});

if (roundDataResult.status === "failure") {
throw new Error(
`Failed to fetch round data result: ${roundDataResult.error}`,
);
}

if (decimalsResult.status === "failure") {
throw new Error(`Failed to fetch decimals result: ${decimalsResult.error}`);
}

const roundData = roundDataResult.result[1];
const decimals = decimalsResult.result;

// We convert the price to a number and return it
return Number(
(roundData.answer.toString() / Math.pow(10, Number(decimals))).toFixed(2),
return (
Number((roundData * BigInt(100)) / BigInt(10) ** BigInt(decimals)) / 100
);
};

const tokenAddressToFeedAddress = (chainId: ChainId, tokenAddress: string) => {
const currencies = currenciesByNetwork[chainId];
const currency = Object.values(currencies).find(
(currency) => currency.address === tokenAddress,
(currency) => getAddress(currency.address) === getAddress(tokenAddress),
);

if (!currency) {
Expand All @@ -105,20 +68,15 @@ const tokenAddressToFeedAddress = (chainId: ChainId, tokenAddress: string) => {
throw new Error("Chain not supported");
}

return feedsForChain?.[symbol];
return feedsForChain?.[symbol] as `0x${string}` | undefined;
};

export const getTokenPricesForChain = async (chainId: ChainId) => {
const currencies = currenciesByNetwork[chainId];
const prices = await Promise.all(
Object.values(currencies).map(async (currency: Currency) => {
try {
const price = await getTokenPriceInUSD(chainId, currency.address);
return { ...currency, price };
} catch (error) {
console.error(error);
return { ...currency, price: 0 };
}
const price = await getTokenPriceInUSD(chainId, currency.address);
return { ...currency, price };
}),
);

Expand Down Expand Up @@ -161,7 +119,6 @@ const feedsPerChain: Record<ChainId, Partial<CurrencyFeeds>> = {
USDC: "0x16a9FA2FDa030272Ce99B29CF780dFA30361E0f3",
},
[ChainId.CELO]: {
// TODO: DAI on CELO not supported
ETH: "0x1FcD30A73D67639c1cD89ff5746E7585731c083B",
WETH: "0x1FcD30A73D67639c1cD89ff5746E7585731c083B",
USDC: "0xc7A353BaE210aed958a1A2928b654938EC59DaB2",
Expand Down

0 comments on commit 23c6e03

Please sign in to comment.