diff --git a/components/RPCList/index.js b/components/RPCList/index.js index 20f9455b61..4c8fc98ea5 100644 --- a/components/RPCList/index.js +++ b/components/RPCList/index.js @@ -6,10 +6,81 @@ import useAddToNetwork from "../../hooks/useAddToNetwork"; import { useLlamaNodesRpcData } from "../../hooks/useLlamaNodesRpcData"; import { FATHOM_DROPDOWN_EVENTS_ID } from "../../hooks/useAnalytics"; import { useRpcStore } from "../../stores"; -import { renderProviderText } from "../../utils"; +import { renderProviderText, containsAny } from "../../utils"; import { Tooltip } from "../../components/Tooltip"; import useAccount from "../../hooks/useAccount"; import { Popover, PopoverDisclosure, usePopoverStore } from "@ariakit/react/popover"; +import { useQuery } from "@tanstack/react-query"; + +// Functions to test trace and archive support + +const SUPPORT_STATUS = { + supported: "supported", + not_supported: "not-supported", + error: "error", + testing: "testing", + unknown: "unknown", +}; + +const testTraceSupport = async (rpcUrl) => { + // Test trace support (with fake tx hash) + + const fakeTxHash = "0xdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef"; + const tracePayload = { + jsonrpc: "2.0", + method: "debug_traceTransaction", + params: [fakeTxHash, {}], + id: 1, + }; + + try { + const traceRes = await fetch(rpcUrl, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify(tracePayload), + signal: AbortSignal.timeout(7000), + }).then((r) => r.json()); + + const errorMessage = traceRes?.error?.message || traceRes?.message; + + // some RPCs might allow the trace with restrictions, better marks those as 'failed to test' instead of 'not supported' + if (containsAny(errorMessage, ["auth", "rate", "limit", "api", "allowed", "whitelist", "origin"])) { + throw new Error(errorMessage); + } + + const traceSupported = + containsAny(errorMessage, ["transaction not found", `transaction ${fakeTxHash} not found`]) || + traceRes?.result === null; + + return traceSupported ? SUPPORT_STATUS.supported : SUPPORT_STATUS.not_supported; + } catch (error) { + return SUPPORT_STATUS.error; + } +}; + +const testArchiveSupport = async (rpcUrl) => { + // Test archive support + const archivePayload = { + jsonrpc: "2.0", + method: "eth_getBalance", + params: ["0x0000000000000000000000000000000000000000", "0x1"], + id: 1, + }; + + try { + const archiveRes = await fetch(rpcUrl, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify(archivePayload), + signal: AbortSignal.timeout(7000), + }).then((r) => r.json()); + + const archiveSupported = Boolean(archiveRes.result); + return archiveSupported ? SUPPORT_STATUS.supported : SUPPORT_STATUS.not_supported; + } catch (error) { + return SUPPORT_STATUS.error; + } +}; export default function RPCList({ chain, lang }) { const [sortChains, setSorting] = useState(true); @@ -72,7 +143,13 @@ export default function RPCList({ chain, lang }) { return { ...rest, - data: { ...data, height, latency: lat, trust, disableConnect }, + data: { + ...data, + height, + latency: lat, + trust, + disableConnect, + }, }; }); }, [chains]); @@ -113,6 +190,8 @@ export default function RPCList({ chain, lang }) {