Skip to content

Commit

Permalink
chore: suggest replacement rpc
Browse files Browse the repository at this point in the history
  • Loading branch information
Keyrxng committed Jun 8, 2024
1 parent 0848083 commit d752f72
Show file tree
Hide file tree
Showing 7 changed files with 5,956 additions and 8,387 deletions.
4 changes: 2 additions & 2 deletions static/scripts/rewards/cirip/query-reverse-ens.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { AppState } from "../app-state";
import { app } from "../app-state";
import { useRpcHandler } from "../web3/use-rpc-handler";
import { reverseEnsInterface } from "./ens-lookup";

export async function queryReverseEns(address: string, networkID: number) {
// Try to get the ENS name from localStorage
const cachedEnsName = localStorage.getItem(address);
const endpoint = (await useRpcHandler({ networkId: networkID } as AppState)).connection.url;
const endpoint = app.provider?.connection.url || (await useRpcHandler(app)).connection.url;

if (!endpoint) {
console.error("ENS lookup failed: No endpoint found for network ID", networkID);
Expand Down
1 change: 0 additions & 1 deletion static/scripts/rewards/init.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import { grid } from "./the-grid";

displayCommitHash(); // @DEV: display commit hash in footer
grid(document.getElementById("grid") as HTMLElement, gridLoadedCallback); // @DEV: display grid background

readClaimDataFromUrl(app).catch(console.error); // @DEV: read claim data from URL

declare const commitHash: string; // @DEV: passed in at build time check build/esbuild-build.ts
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,11 @@ export async function readClaimDataFromUrl(app: AppState) {
try {
app.provider = await useRpcHandler(app);
} catch (e) {
toaster.create("error", `e`);
if (e instanceof Error) {
toaster.create("error", e.message);
} else {
toaster.create("error", JSON.stringify(e));
}
}

try {
Expand Down
131 changes: 101 additions & 30 deletions static/scripts/rewards/web3/connect-wallet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@ import { JsonRpcSigner } from "@ethersproject/providers";
import { ethers } from "ethers";
import { buttonController, toaster } from "../toaster";
import { app } from "../app-state";
import { useHandler } from "../web3/use-rpc-handler";

const mediaQuery = window.matchMedia("(max-width: 768px)");

export async function connectWallet(): Promise<JsonRpcSigner | null> {
try {
Expand All @@ -13,20 +16,17 @@ export async function connectWallet(): Promise<JsonRpcSigner | null> {

const address = await signer.getAddress();

const provider = new ethers.providers.JsonRpcProvider(wallet.provider.url);

if (!address) {
buttonController.hideAll();
console.error("Wallet not connected");
return null;
}

const isOkay = await stressTestWalletRpc(provider);
const isOkay = await stressTestWalletRpc(wallet);

if (!isOkay) {
toaster.create("error", "We have detected potential issues with your in-wallet RPC. Accept the request to replace it with a more reliable one.");
await addFastestHandlerNetwork();
return null;
await addFastestHandlerNetwork(wallet);
}

return signer;
Expand All @@ -35,49 +35,121 @@ export async function connectWallet(): Promise<JsonRpcSigner | null> {
}
}

async function addFastestHandlerNetwork() {
const provider = await useRpcHandler(app.networkId ?? provider.network.chainId);
const url = provider.connection.url;
async function addFastestHandlerNetwork(wallet: ethers.providers.Web3Provider) {
const networkId = app.networkId ?? (await wallet.getNetwork()).chainId;
const handler = useHandler(networkId);
let provider = await handler.getFastestRpcProvider();
const appUrl = app.provider?.connection?.url;

const latencies = handler.getLatencies();
const latenciesArray = Object.entries(latencies).map(([url, latency]) => ({ url, latency }) as { url: string; latency: number });
const sorted = latenciesArray.sort((a, b) => a.latency - b.latency);

let toSuggest = sorted[0];

for await (const { url } of sorted) {
const _url = url.split("__")[1];
toaster.create("info", `Testing ${_url}...`);
if (_url !== appUrl) {
provider = new ethers.providers.JsonRpcProvider(_url);

const isOkay = await stressTestWalletRpc(provider);

if (isOkay) {
toSuggest = { url: _url, latency: latencies[url] };
break;
}
}
}

try {
await provider.request({
method: "wallet_addEthereumChain",
params: [
{
chainId: app.networkId,
chainName: url,
rpcUrls: [url],
},
],
});
await addHandlerSuggested(wallet, toSuggest.url);
if (!mediaQuery.matches) {
toaster.create("success", `Optimal RPC network added: ${toSuggest.url}`);
}
} catch (error) {
console.error(error);
toaster.create("error"`Failed to add optimal RPC network. Please add it manually. Network ID: ${app.networkId}, URL: ${url}`);
toaster.create("info", `Please replace your in-wallet RPC manually and then refresh the page.`, 15000);
toaster.create("info", `Replacement RPC: ${toSuggest.url}`, Infinity);
}
}

async function addHandlerSuggested(provider: ethers.providers.Web3Provider, url: string) {
const symbol = app.networkId === 1 ? "ETH" : "XDAI";
const altSymbol = app.networkId === 1 ? "eth" : "xdai";
const altSymbol2 = app.networkId === 1 ? "Eth" : "xDai";

if (mediaQuery.matches) {
/**
* https://github.com/MetaMask/metamask-mobile/issues/9519
*
* Until this is resolved it is not possible for us to add a network on mobile
* so we'll show a toast suggesting they do it manually
*/

toaster.create("info", `Please replace your in-wallet RPC manually and then refresh the page.`, 15000);
toaster.create("info", `Replacement RPC: ${url}`, Infinity);
return;
}

// It will not work unless the symbols match, so we try them all
for (const _symbol of [symbol, altSymbol, altSymbol2]) {
// this does not work on mobile yet
await addProvider(provider, url, _symbol, app.networkId);
}
}

async function addProvider(provider: ethers.providers.Web3Provider, url: string, symbol: string, chainId: number | null) {
const _chainId = chainId || (await provider.getNetwork()).chainId;
try {
await provider.send("wallet_addEthereumChain", [
{
chainId: `0x${_chainId.toString(16)}`,
chainName: _chainId === 1 ? "Ethereum" : "Gnosis",
nativeCurrency: {
name: _chainId === 1 ? "ETH" : "XDAI",
symbol,
decimals: 18,
},
rpcUrls: [url],
blockExplorerUrls: [`https://${_chainId === 1 ? "etherscan" : "gnosisscan"}.io`],
},
]);
} catch {
console.error("Failed to add network");
}
}

async function stressTestWalletRpc(provider: ethers.providers.Web3Provider) {
const success = [];
const success: Promise<string | boolean>[] = [];

for (let i = 0; i < 10; i++) {
success.push(await testEthCall(provider));
success.push(testNonceBitmapEthCall(provider));
}

return success.filter((s) => s === "0x" + "00".repeat(32)).length > 9;
// if the test takes too long, we'll just assume it's not working
const timeoutPromise = new Promise<[false]>((resolve) => {
setTimeout(() => {
resolve([false]);
}, 7000);
});

const results = await Promise.race([Promise.all(success), timeoutPromise]);

return results.filter((s) => s === "0x" + "00".repeat(32)).length > 9 && results.filter((s) => s === false).length < 1;
}

async function testEthCall(provider: ethers.providers.Web3Provider) {
async function testNonceBitmapEthCall(provider: ethers.providers.Web3Provider) {
try {
return await provider.send("eth_call", [
{
to: "0x000000000022D473030F116dDEE9F6B43aC78BA3",
input: "0x4fe02b44000000000000000000000000d9530f3fbbea11bed01dc09e79318f2f20223716001fd097bcb5a1759ce02c0a671386a0bbbfa8216559e5855698a9d4de4cddea",
// input works for desktop, needs to be data for mobile
data: "0x4fe02b44000000000000000000000000d9530f3fbbea11bed01dc09e79318f2f20223716001fd097bcb5a1759ce02c0a671386a0bbbfa8216559e5855698a9d4de4cddea",
},
"latest",
]);
} catch (er) {
console.log(er);
return false;
} catch {
// if the call fails, we'll assume the RPC is not working
}
}

Expand All @@ -86,7 +158,6 @@ function connectErrorHandler(error: unknown) {
console.error(error);
if (error?.message?.includes("missing provider")) {
// mobile browsers don't really support window.ethereum
const mediaQuery = window.matchMedia("(max-width: 768px)");

if (mediaQuery.matches) {
toaster.create("warning", "Please use a mobile-friendly Web3 browser such as MetaMask to collect this reward", Infinity);
Expand All @@ -98,7 +169,7 @@ function connectErrorHandler(error: unknown) {
toaster.create("error", error.message);
}
} else {
toaster.create("error", "An unknown error occurred.");
toaster.create("error", "An unknown error occurred" + JSON.stringify(error));
}

if (window.location.href.includes("localhost")) {
Expand Down
4 changes: 3 additions & 1 deletion static/scripts/rewards/web3/erc20-permit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -118,13 +118,15 @@ async function waitForTransaction(tx: TransactionResponse) {

export function claimErc20PermitHandlerWrapper(app: AppState) {
return async function claimErc20PermitHandler() {
const signer = await connectWallet();
const signer = await connectWallet(); // we are re-testing the in-wallet rpc at this point
if (!signer) {
buttonController.hideAll();
toaster.create("error", `Please connect your wallet to claim this reward.`);
return;
}

app.signer = signer; // update this here to be sure it's set if it wasn't before

buttonController.hideMakeClaim();
buttonController.showLoader();

Expand Down
2 changes: 1 addition & 1 deletion static/scripts/rewards/web3/use-rpc-handler.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { RPCHandler } from "@ubiquity-dao/rpc-handler";
import { AppState } from "../app-state";

export async function useHandler(networkId: number) {
export function useHandler(networkId: number) {
const config = {
networkId: networkId,
autoStorage: true,
Expand Down
Loading

0 comments on commit d752f72

Please sign in to comment.