diff --git a/.cspell.json b/.cspell.json index d54182ac..2cc81b35 100644 --- a/.cspell.json +++ b/.cspell.json @@ -36,7 +36,8 @@ "URLSAFE", "WXDAI", "XDAI", - "xmark" + "xmark", + "keyrxng" ], "dictionaries": ["typescript", "node", "software-terms", "html"], "import": ["@cspell/dict-typescript/cspell-ext.json", "@cspell/dict-node/cspell-ext.json", "@cspell/dict-software-terms"], diff --git a/.gitmodules b/.gitmodules index 858d45f6..3f36c69e 100644 --- a/.gitmodules +++ b/.gitmodules @@ -4,7 +4,4 @@ branch = v1.4.0 [submodule "lib/permit2"] path = lib/permit2 - url = https://github.com/Uniswap/permit2 -[submodule "lib/chainlist"] - path = lib/chainlist - url = https://github.com/DefiLlama/chainlist.git + url = https://github.com/Uniswap/permit2 \ No newline at end of file diff --git a/build/esbuild-build.ts b/build/esbuild-build.ts index 2d0162ab..2935e194 100644 --- a/build/esbuild-build.ts +++ b/build/esbuild-build.ts @@ -1,19 +1,9 @@ import { execSync } from "child_process"; import { config } from "dotenv"; import esbuild from "esbuild"; -import extraRpcs from "../lib/chainlist/constants/extraRpcs"; const typescriptEntries = ["static/scripts/rewards/init.ts"]; export const entries = [...typescriptEntries]; -const allNetworkUrls: Record = {}; -// this flattens all the rpcs into a single object, with key names that match the networkIds. The arrays are just of URLs per network ID. - -Object.keys(extraRpcs).forEach((networkId) => { - const officialUrls = extraRpcs[networkId].rpcs.filter((rpc) => typeof rpc === "string"); - const extraUrls: string[] = extraRpcs[networkId].rpcs.filter((rpc) => rpc.url !== undefined).map((rpc) => rpc.url); - allNetworkUrls[networkId] = [...officialUrls, ...extraUrls]; -}); - export const esBuildContext: esbuild.BuildOptions = { sourcemap: true, entryPoints: entries, @@ -29,7 +19,6 @@ export const esBuildContext: esbuild.BuildOptions = { }, outdir: "static/out", define: createEnvDefines(["SUPABASE_URL", "SUPABASE_ANON_KEY"], { - extraRpcs: allNetworkUrls, commitHash: execSync(`git rev-parse --short HEAD`).toString().trim(), }), }; diff --git a/lib/chainlist b/lib/chainlist deleted file mode 160000 index 80592837..00000000 --- a/lib/chainlist +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 805928372c7a97eec513e8c74c344dd759f07e1c diff --git a/package.json b/package.json index 865c69d9..5394e9b2 100644 --- a/package.json +++ b/package.json @@ -33,6 +33,7 @@ ], "dependencies": { "@ethersproject/providers": "^5.7.2", + "@keyrxng/rpc-handler": "^1.0.1", "@octokit/core": "^5.1.0", "@octokit/plugin-create-or-update-text-file": "^4.0.1", "@octokit/plugin-throttling": "^8.1.3", diff --git a/static/scripts/rewards/app-state.ts b/static/scripts/rewards/app-state.ts index 00d5fdbe..5cbbf7d4 100644 --- a/static/scripts/rewards/app-state.ts +++ b/static/scripts/rewards/app-state.ts @@ -1,5 +1,5 @@ import { JsonRpcProvider } from "@ethersproject/providers"; -import { networkExplorers } from "./constants"; +import { networkExplorers } from "@keyrxng/rpc-handler/dist/esm/src/constants"; import { RewardPermit } from "./render-transaction/tx-type"; export class AppState { diff --git a/static/scripts/rewards/cirip/ens-lookup.ts b/static/scripts/rewards/cirip/ens-lookup.ts index 67a772fd..fe01c3a0 100644 --- a/static/scripts/rewards/cirip/ens-lookup.ts +++ b/static/scripts/rewards/cirip/ens-lookup.ts @@ -2,15 +2,16 @@ import { ethers } from "ethers"; import abi from "../abis/cirip.json"; import { fetchEns } from "./fetch-ens"; import { queryReverseEns } from "./query-reverse-ens"; +import { RPCHandler } from "@keyrxng/rpc-handler/dist/esm/src"; -export const UBIQUITY_RPC_ENDPOINT = "https://rpc-pay.ubq.fi/v1/mainnet"; +// export const UBIQUITY_RPC_ENDPOINT = "https://rpc-pay.ubq.fi/v1/mainnet"; export const reverseEnsInterface = new ethers.utils.Interface(abi); // addEventListener("fetch", event => { // event.respondWith(handleRequest(event.request).catch(err => new Response(err.stack, { status: 500 }))); // }); -export async function ensLookup(addr: string) { +export async function ensLookup(addr: string, handler: RPCHandler) { const _address = "/".concat(addr); // quick adapter // try { @@ -22,12 +23,13 @@ export async function ensLookup(addr: string) { const address = _address.substring(start + 1, start + 43).toLowerCase(); let reverseRecord = null as null | string; - // let response = ""; try { - reverseRecord = await queryReverseEns(address); - const responseParsed = JSON.parse(reverseRecord).result; - const _reverseRecord = ethers.utils.defaultAbiCoder.decode([ethers.utils.ParamType.from("string[]")], responseParsed); - reverseRecord = _reverseRecord[0][0]; + reverseRecord = await queryReverseEns(address, handler); + if (reverseRecord !== undefined) { + const responseParsed = JSON.parse(reverseRecord).result; + const _reverseRecord = ethers.utils.defaultAbiCoder.decode([ethers.utils.ParamType.from("string[]")], responseParsed); + reverseRecord = _reverseRecord[0][0]; + } } catch (e) { console.error(e); // throw "Error contacting ethereum node. \nCause: '" + e + "'. \nResponse: " + response; diff --git a/static/scripts/rewards/cirip/query-reverse-ens.ts b/static/scripts/rewards/cirip/query-reverse-ens.ts index 76f5f106..5e541e77 100644 --- a/static/scripts/rewards/cirip/query-reverse-ens.ts +++ b/static/scripts/rewards/cirip/query-reverse-ens.ts @@ -1,6 +1,7 @@ -import { reverseEnsInterface, UBIQUITY_RPC_ENDPOINT } from "./ens-lookup"; +import { RPCHandler } from "@keyrxng/rpc-handler/dist/esm/src"; +import { reverseEnsInterface } from "./ens-lookup"; -export async function queryReverseEns(address: string) { +export async function queryReverseEns(address: string, handler: RPCHandler) { // Try to get the ENS name from localStorage const cachedEnsName = localStorage.getItem(address); @@ -11,22 +12,15 @@ export async function queryReverseEns(address: string) { // If the ENS name is not in localStorage, fetch it from the API const data = reverseEnsInterface.encodeFunctionData("getNames", [[address.substring(2)]]); - const response = await fetch(UBIQUITY_RPC_ENDPOINT, { - method: "POST", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify({ - jsonrpc: "2.0", - id: "1", - method: "eth_call", - params: [{ to: "0x3671aE578E63FdF66ad4F3E12CC0c0d71Ac7510C", data: data }, "latest"], - }), - }); + const provider = handler.getProvider(); - const ensName = await response.text(); + const ensName = await provider.send("eth_call", [{ to: "0x3671aE578E63FdF66ad4F3E12CC0c0d71Ac7510C", data: data }, "latest"]); - // Store the ENS name in localStorage for future use + if (ensName === "0x") { + return; + } + + // Save the ENS name to localStorage localStorage.setItem(address, ensName); return ensName; diff --git a/static/scripts/rewards/constants.ts b/static/scripts/rewards/constants.ts deleted file mode 100644 index 7dd21ff9..00000000 --- a/static/scripts/rewards/constants.ts +++ /dev/null @@ -1,56 +0,0 @@ -// type RPC = { url: string; tracking?: string; trackingDetails?: string }; -// type Network = { name?: string; rpcs: RPC[]; websiteDead?: boolean; rpcWorking?: boolean }; -// type Networks = { [key: string]: Network }; - -declare const extraRpcs: Record; // @DEV: passed in at build time check build/esbuild-build.ts - -export enum NetworkIds { - Mainnet = 1, - Goerli = 5, - Gnosis = 100, - Anvil = 31337, -} - -export enum Tokens { - DAI = "0x6b175474e89094c44da98b954eedeac495271d0f", - WXDAI = "0xe91d153e0b41518a2ce8dd3d7944fa863463a97d", -} - -export const networkNames = { - [NetworkIds.Mainnet]: "Ethereum Mainnet", - [NetworkIds.Goerli]: "Goerli Testnet", - [NetworkIds.Gnosis]: "Gnosis Chain", - [NetworkIds.Anvil]: "http://127.0.0.1:8545", -}; - -export const networkCurrencies: Record = { - [NetworkIds.Mainnet]: { symbol: "ETH", decimals: 18 }, - [NetworkIds.Goerli]: { symbol: "GoerliETH", decimals: 18 }, - [NetworkIds.Gnosis]: { symbol: "XDAI", decimals: 18 }, - [NetworkIds.Anvil]: { symbol: "XDAI", decimals: 18 }, -}; - -export function getNetworkName(networkId?: number) { - const networkName = networkNames[networkId as keyof typeof networkNames]; - if (!networkName) { - console.error(`Unknown network ID: ${networkId}`); - } - return networkName ?? "Unknown Network"; -} - -export const networkExplorers: Record = { - [NetworkIds.Mainnet]: "https://etherscan.io", - [NetworkIds.Goerli]: "https://goerli.etherscan.io", - [NetworkIds.Gnosis]: "https://gnosisscan.io", - [NetworkIds.Anvil]: "https://gnosisscan.io", -}; - -export const networkRpcs: Record = { - [NetworkIds.Mainnet]: extraRpcs[NetworkIds.Mainnet], - [NetworkIds.Goerli]: extraRpcs[NetworkIds.Goerli], - [NetworkIds.Gnosis]: extraRpcs[NetworkIds.Gnosis], - [NetworkIds.Anvil]: ["http://127.0.0.1:8545"], -}; - -export const permit2Address = "0x000000000022D473030F116dDEE9F6B43aC78BA3"; -export const nftAddress = "0xAa1bfC0e51969415d64d6dE74f27CDa0587e645b"; diff --git a/static/scripts/rewards/render-transaction/read-claim-data-from-url.ts b/static/scripts/rewards/render-transaction/read-claim-data-from-url.ts index bd00b673..cf5978d6 100644 --- a/static/scripts/rewards/render-transaction/read-claim-data-from-url.ts +++ b/static/scripts/rewards/render-transaction/read-claim-data-from-url.ts @@ -2,7 +2,6 @@ import { Type } from "@sinclair/typebox"; import { Value } from "@sinclair/typebox/value"; import { createClient } from "@supabase/supabase-js"; import { AppState, app } from "../app-state"; -import { useFastestRpc } from "../rpc-optimization/get-optimal-provider"; import { buttonController, toaster } from "../toaster"; import { connectWallet } from "../web3/connect-wallet"; import { checkRenderInvalidatePermitAdminControl, checkRenderMakeClaimControl } from "../web3/erc20-permit"; @@ -11,7 +10,7 @@ import { claimRewardsPagination } from "./claim-rewards-pagination"; import { renderTransaction } from "./render-transaction"; import { setClaimMessage } from "./set-claim-message"; import { RewardPermit, claimTxT } from "./tx-type"; - +import { useRpcHandler } from "../web3/use-rpc-handler"; declare const SUPABASE_URL: string; declare const SUPABASE_ANON_KEY: string; @@ -31,7 +30,11 @@ export async function readClaimDataFromUrl(app: AppState) { app.claims = decodeClaimData(base64encodedTxData).flat(); app.claimTxs = await getClaimedTxs(app); - app.provider = await useFastestRpc(app); + + const handler = await useRpcHandler(app); + + app.provider = handler.getProvider(); + console.log("app.provider", app.provider); if (window.ethereum) { app.signer = await connectWallet().catch(console.error); window.ethereum.on("accountsChanged", () => { diff --git a/static/scripts/rewards/render-transaction/render-ens-name.ts b/static/scripts/rewards/render-transaction/render-ens-name.ts index df4043ed..ccf60121 100644 --- a/static/scripts/rewards/render-transaction/render-ens-name.ts +++ b/static/scripts/rewards/render-transaction/render-ens-name.ts @@ -1,5 +1,6 @@ -import { app } from "../app-state"; +import { AppState, app } from "../app-state"; import { ensLookup } from "../cirip/ens-lookup"; +import { useRpcHandler } from "../web3/use-rpc-handler"; type EnsParams = | { @@ -7,18 +8,23 @@ type EnsParams = address: string; tokenAddress: string; tokenView: true; + networkId: number; } | { element: Element; address: string; + networkId: number; tokenAddress?: undefined; tokenView?: false; }; -export async function renderEnsName({ element, address, tokenAddress, tokenView }: EnsParams): Promise { +export async function renderEnsName({ element, address, tokenAddress, tokenView, networkId }: EnsParams): Promise { let href: string = ""; + + const handler = await useRpcHandler({ networkId } as AppState); + try { - const resolved = await ensLookup(address); + const resolved = await ensLookup(address, handler); let ensName: undefined | string; if (resolved.reverseRecord) { ensName = resolved.reverseRecord; diff --git a/static/scripts/rewards/render-transaction/render-transaction.ts b/static/scripts/rewards/render-transaction/render-transaction.ts index b3a69a94..fd0e3725 100644 --- a/static/scripts/rewards/render-transaction/render-transaction.ts +++ b/static/scripts/rewards/render-transaction/render-transaction.ts @@ -1,5 +1,5 @@ import { app } from "../app-state"; -import { networkExplorers } from "../constants"; +import { networkExplorers } from "@keyrxng/rpc-handler/dist/esm/src/constants"; import { buttonController, getMakeClaimButton, viewClaimButton } from "../toaster"; import { checkRenderInvalidatePermitAdminControl, claimErc20PermitHandlerWrapper, fetchTreasury } from "../web3/erc20-permit"; import { claimErc721PermitHandler } from "../web3/erc721-permit"; @@ -44,7 +44,7 @@ export async function renderTransaction(): Promise { }).catch(console.error); const toElement = document.getElementById(`rewardRecipient`) as Element; - renderEnsName({ element: toElement, address: app.reward.transferDetails.to }).catch(console.error); + renderEnsName({ element: toElement, address: app.reward.transferDetails.to, networkId: app.networkId ?? app.reward.networkId }).catch(console.error); if (app.provider) { checkRenderInvalidatePermitAdminControl(app).catch(console.error); @@ -71,7 +71,7 @@ export async function renderTransaction(): Promise { }).catch(console.error); const toElement = document.getElementById(`rewardRecipient`) as Element; - renderEnsName({ element: toElement, address: app.reward.transferDetails.to }).catch(console.error); + renderEnsName({ element: toElement, address: app.reward.transferDetails.to, networkId: app.networkId ?? app.reward.networkId }).catch(console.error); getMakeClaimButton().addEventListener("click", claimErc721PermitHandler(app.reward)); } diff --git a/static/scripts/rewards/rpc-optimization/get-fastest-rpc-provider.ts b/static/scripts/rewards/rpc-optimization/get-fastest-rpc-provider.ts deleted file mode 100644 index e0cf1e7e..00000000 --- a/static/scripts/rewards/rpc-optimization/get-fastest-rpc-provider.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { ethers } from "ethers"; - -export function getFastestRpcProvider(networkId: number) { - const latencies: Record = JSON.parse(localStorage.getItem("rpcLatencies") || "{}"); - - // Filter out latencies with a value of less than 0 because -1 means it failed - // Also filter out latencies that do not belong to the desired network - const validLatencies = Object.entries(latencies).filter(([key, latency]) => latency >= 0 && key.endsWith(`_${networkId}`)); - - // Get all valid latencies from localStorage and find the fastest RPC - const sortedLatencies = validLatencies.sort((a, b) => a[1] - b[1]); - const optimalRPC = sortedLatencies[0][0].split("_").slice(0, -1).join("_"); // Remove the network ID from the key - - return new ethers.providers.JsonRpcProvider(optimalRPC, { - name: optimalRPC, - chainId: networkId, - }); -} diff --git a/static/scripts/rewards/rpc-optimization/get-optimal-provider.ts b/static/scripts/rewards/rpc-optimization/get-optimal-provider.ts deleted file mode 100644 index b47a3ede..00000000 --- a/static/scripts/rewards/rpc-optimization/get-optimal-provider.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { JsonRpcProvider } from "@ethersproject/providers"; -import { AppState } from "../app-state"; -import { getFastestRpcProvider } from "./get-fastest-rpc-provider"; -import { testRpcPerformance } from "./test-rpc-performance"; - -let isTestStarted = false; -let isTestCompleted = false; - -export async function useFastestRpc(app: AppState): Promise { - const networkId = app.reward.networkId || app.networkId || app.claims[0].networkId; - if (!networkId) throw new Error("Network ID not found"); - - if (networkId === 31337) - return new JsonRpcProvider("http://127.0.0.1:8545", { - name: "http://127.0.0.1:8545", - chainId: 31337, - }); - - if (!isTestCompleted && !isTestStarted) { - isTestStarted = true; - await testRpcPerformance(networkId).catch(console.error); - isTestCompleted = true; - } - - return getFastestRpcProvider(networkId); -} diff --git a/static/scripts/rewards/rpc-optimization/test-rpc-performance.ts b/static/scripts/rewards/rpc-optimization/test-rpc-performance.ts deleted file mode 100644 index 11d42b80..00000000 --- a/static/scripts/rewards/rpc-optimization/test-rpc-performance.ts +++ /dev/null @@ -1,67 +0,0 @@ -import axios from "axios"; -import { networkRpcs } from "../constants"; - -type DataType = { - jsonrpc: string; - id: number; - result: { - number: string; - timestamp: string; - hash: string; - }; -}; - -function verifyBlock(data: DataType) { - try { - const { jsonrpc, id, result } = data; - const { number, timestamp, hash } = result; - return jsonrpc === "2.0" && id === 1 && parseInt(number, 16) > 0 && parseInt(timestamp, 16) > 0 && hash.match(/[0-9|a-f|A-F|x]/gm)?.join("").length === 66; - } catch (error) { - return false; - } -} - -const RPC_BODY = JSON.stringify({ - jsonrpc: "2.0", - method: "eth_getBlockByNumber", - params: ["latest", false], - id: 1, -}); - -const RPC_HEADER = { - "Content-Type": "application/json", -}; - -function raceUntilSuccess(promises: Promise[]) { - return new Promise((resolve) => { - promises.forEach((promise: Promise) => { - promise.then(resolve).catch(() => {}); - }); - }); -} - -export async function testRpcPerformance(networkId: number) { - const latencies: Record = JSON.parse(localStorage.getItem("rpcLatencies") || "{}"); - - const promises = networkRpcs[networkId].map(async (baseURL: string) => { - const startTime = performance.now(); - const API = axios.create({ - baseURL, - headers: RPC_HEADER, - }); - - const { data } = await API.post("", RPC_BODY); - const endTime = performance.now(); - const latency = endTime - startTime; - if (verifyBlock(data)) { - // Save the latency in localStorage - latencies[`${baseURL}_${networkId}`] = latency; - localStorage.setItem("rpcLatencies", JSON.stringify(latencies)); - } else { - // Throw an error to indicate an invalid block data - throw new Error(`Invalid block data from ${baseURL}`); - } - }); - - await raceUntilSuccess(promises); -} diff --git a/static/scripts/rewards/web3/add-network.ts b/static/scripts/rewards/web3/add-network.ts index 1c9dd6f2..770eaa23 100644 --- a/static/scripts/rewards/web3/add-network.ts +++ b/static/scripts/rewards/web3/add-network.ts @@ -1,5 +1,5 @@ import { ethers } from "ethers"; -import { getNetworkName, networkCurrencies, networkExplorers, networkRpcs } from "../constants"; +import { getNetworkName, networkCurrencies, networkExplorers, networkRpcs } from "@keyrxng/rpc-handler/dist/esm/src/constants"; export async function addNetwork(provider: ethers.providers.Web3Provider, networkId: number): Promise { try { diff --git a/static/scripts/rewards/web3/erc20-permit.ts b/static/scripts/rewards/web3/erc20-permit.ts index dbcb11f7..98966d17 100644 --- a/static/scripts/rewards/web3/erc20-permit.ts +++ b/static/scripts/rewards/web3/erc20-permit.ts @@ -2,7 +2,7 @@ import { JsonRpcSigner, TransactionResponse } from "@ethersproject/providers"; import { BigNumber, BigNumberish, Contract, ethers } from "ethers"; import { erc20Abi, permit2Abi } from "../abis"; import { AppState, app } from "../app-state"; -import { permit2Address } from "../constants"; +import { permit2Address } from "@keyrxng/rpc-handler/dist/esm/src/constants"; import { supabase } from "../render-transaction/read-claim-data-from-url"; import { Erc20Permit, Erc721Permit } from "../render-transaction/tx-type"; import { MetaMaskError, buttonController, errorToast, getMakeClaimButton, toaster } from "../toaster"; diff --git a/static/scripts/rewards/web3/not-on-correct-network.ts b/static/scripts/rewards/web3/not-on-correct-network.ts index 595525c9..a723b438 100644 --- a/static/scripts/rewards/web3/not-on-correct-network.ts +++ b/static/scripts/rewards/web3/not-on-correct-network.ts @@ -1,5 +1,5 @@ import { ethers } from "ethers"; -import { getNetworkName } from "../constants"; +import { getNetworkName } from "@keyrxng/rpc-handler/dist/esm/src/constants"; import { buttonController, toaster } from "../toaster"; import { switchNetwork } from "./switch-network"; diff --git a/static/scripts/rewards/web3/use-rpc-handler.ts b/static/scripts/rewards/web3/use-rpc-handler.ts new file mode 100644 index 00000000..71ce31f6 --- /dev/null +++ b/static/scripts/rewards/web3/use-rpc-handler.ts @@ -0,0 +1,17 @@ +import { RPCHandler } from "@keyrxng/rpc-handler/dist/esm/src/rpc-handler"; +import { HandlerConstructorConfig } from "@keyrxng/rpc-handler/dist/esm/src"; +import { AppState } from "../app-state"; + +export async function useRpcHandler(app: AppState) { + const config: HandlerConstructorConfig = { + networkId: app.networkId ?? app.reward.networkId, + autoStorage: true, + cacheRefreshCycles: 10, + }; + + const handler = new RPCHandler(config); + + await handler.getFastestRpcProvider(); + + return handler; +} diff --git a/tsconfig.json b/tsconfig.json index 31f00cd4..31241232 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,5 +1,5 @@ { - "include": ["src", "lib/chainlist/constants/extraRpcs.js", "static", "build", "scripts/typescript", "globals.d.ts"], + "include": ["src", "static", "build", "scripts/typescript", "globals.d.ts"], "compilerOptions": { /* Visit https://aka.ms/tsconfig to read more about this file */ /* Projects */ diff --git a/yarn.lock b/yarn.lock index cf356f4f..78d18204 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1194,6 +1194,13 @@ wrap-ansi "^8.1.0" wrap-ansi-cjs "npm:wrap-ansi@^7.0.0" +"@keyrxng/rpc-handler@^1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@keyrxng/rpc-handler/-/rpc-handler-1.0.1.tgz#b9cf9166fbf0f983131ac48f1afbbe9cf5413062" + integrity sha512-F08mCZfee/zou0oKQsU7Y4Gm2gsHg86fHBQUbWv23lf6p9Ew0ZWTojZAANQXQrOSi5zFeexGE4gxOkeried2dg== + dependencies: + "@ethersproject/providers" "5.7.2" + "@nodelib/fs.scandir@2.1.5": version "2.1.5" resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5"