Skip to content

Commit

Permalink
fix: optimal rpc for writes, no more infinite loading
Browse files Browse the repository at this point in the history
  • Loading branch information
Keyrxng committed Apr 10, 2024
1 parent 941d461 commit ca344eb
Show file tree
Hide file tree
Showing 8 changed files with 112 additions and 51 deletions.
2 changes: 1 addition & 1 deletion .knip.jsonc
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"$schema": "https://unpkg.com/knip@5/schema.json",
"entry": ["static/scripts/rewards/init.ts"],
"ignore": ["lib/**"]
"ignore": ["lib/**"],
}
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@
"@uniswap/permit2-sdk": "^1.2.0",
"axios": "^1.6.7",
"dotenv": "^16.4.4",
"ethers": "^5.7.2",
"ethers": "5.7.2",
"npm-run-all": "^4.1.5"
},
"devDependencies": {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,9 @@ 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 { toaster } from "../toaster";
import { connectWallet } from "../web3/connect-wallet";
import { checkRenderInvalidatePermitAdminControl, checkRenderMakeClaimControl } from "../web3/erc20-permit";
import { verifyCurrentNetwork } from "../web3/verify-current-network";
import { claimRewardsPagination } from "./claim-rewards-pagination";
import { renderTransaction } from "./render-transaction";
import { setClaimMessage } from "./set-claim-message";
Expand All @@ -31,34 +30,36 @@ export async function readClaimDataFromUrl(app: AppState) {

app.claims = decodeClaimData(base64encodedTxData).flat();
app.claimTxs = await getClaimedTxs(app);

try {
app.provider = await useFastestRpc(app);
} catch (e) {
toaster.create("error", `${e}`);
}
if (window.ethereum) {
try {
app.signer = await connectWallet();
} catch (error) {
/* empty */
}
window.ethereum.on("accountsChanged", () => {

try {
app.signer = await connectWallet(app);
} catch (error) {
/* empty */
}

try {
// this would throw on mobile browsers & non-web3 browsers
window?.ethereum.on("accountsChanged", () => {
checkRenderMakeClaimControl(app).catch(console.error);
checkRenderInvalidatePermitAdminControl(app).catch(console.error);
});
} else {
buttonController.hideAll();
toaster.create("info", "Please use a web3 enabled browser to collect this reward.");
} catch (err) {
/*
* handled feedback upstream already
* buttons are hidden and non-web3 infinite toast exists
*/
}

displayRewardDetails();
displayRewardPagination();

await renderTransaction();
if (app.networkId !== null) {
await verifyCurrentNetwork(app.networkId);
} else {
throw new Error("Network ID is null");
}
}

async function getClaimedTxs(app: AppState): Promise<Record<string, string>> {
Expand Down
10 changes: 6 additions & 4 deletions static/scripts/rewards/toaster.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,10 @@ export const viewClaimButton = document.getElementById("view-claim") as HTMLButt
const notifications = document.querySelector(".notifications") as HTMLUListElement;
export const buttonController = new ButtonController(controls);

function createToast(meaning: keyof typeof toaster.icons, text: string) {
function createToast(meaning: keyof typeof toaster.icons, text: string, timeout: number = 5000) {
if (meaning != "info") buttonController.hideLoader();
const toastDetails = {
timer: 5000,
timer: timeout,
} as {
timer: number;
timeoutId?: NodeJS.Timeout;
Expand All @@ -43,8 +43,10 @@ function createToast(meaning: keyof typeof toaster.icons, text: string) {

notifications.appendChild(toastContent); // Append the toast to the notification ul

// Setting a timeout to remove the toast after the specified duration
toastDetails.timeoutId = setTimeout(() => removeToast(toastContent, toastDetails.timeoutId), toastDetails.timer);
if (timeout !== Infinity) {
// Setting a timeout to remove the toast after the specified duration
toastDetails.timeoutId = setTimeout(() => removeToast(toastContent, toastDetails.timeoutId), toastDetails.timer);
}
}

function removeToast(toast: HTMLElement, timeoutId?: NodeJS.Timeout) {
Expand Down
68 changes: 56 additions & 12 deletions static/scripts/rewards/web3/connect-wallet.ts
Original file line number Diff line number Diff line change
@@ -1,33 +1,77 @@
import { JsonRpcSigner } from "@ethersproject/providers";
import { ethers } from "ethers";
import { buttonController, toaster } from "../toaster";
import { useFastestRpc } from "../rpc-optimization/get-optimal-provider";
import { AppState } from "../app-state";
import { verifyCurrentNetwork } from "./verify-current-network";

export async function connectWallet(): Promise<JsonRpcSigner | null> {
export async function connectWallet(app: AppState): Promise<JsonRpcSigner | null> {
try {
// take the wallet provider from the window
const wallet = new ethers.providers.Web3Provider(window.ethereum);

// the rewards are populated before the provider is so this is safe
const rewardNetworkId = app.reward.networkId;

// verify we are on the correct network immediately
await verifyCurrentNetwork(rewardNetworkId);

// get account access from the wallet provider i.e metamask
await wallet.send("eth_requestAccounts", []);

const signer = wallet.getSigner();
// get our optimal rpc provider
const rpc = await useFastestRpc(app);

// take the signer from the wallet
const walletSigner = wallet.getSigner();

// take the address of the signer
const walletAddress = await walletSigner.getAddress();

// use the rpc and connect the signer to an UncheckedRpcSigner instance
const rpcSigner = rpc.getUncheckedSigner(walletAddress) as JsonRpcSigner;

const address = await signer.getAddress();
// get the address of the signer
const newJsonSignerAddress = await rpcSigner.getAddress();

if (!address) {
// if we have window.eth then we should have an address
if (walletAddress == "" || newJsonSignerAddress == "" || !walletAddress || !newJsonSignerAddress) {
toaster.create("info", "Please connect your wallet to collect this reward.");
buttonController.hideAll();
console.error("Wallet not connected");
return null;
}

return signer;
if (walletAddress !== newJsonSignerAddress) {
// although this should never happen just use the wallet provider if it does
return walletSigner;
} else {
return rpcSigner;
}
} catch (error: unknown) {
if (error instanceof Error) {
console.error(error);
if (error?.message?.includes("missing provider")) {
toaster.create("info", "Please use a web3 enabled browser to collect this reward.");
} else {
toaster.create("info", "Please connect your wallet to collect this reward.");
connectErrorHandler(error);
}
return null;
}

function connectErrorHandler(error: unknown) {
if (error instanceof Error) {
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) {
// "Please use a mobile-friendly Web3 or desktop browser to collect this reward." push to desktop if possible?
toaster.create("warning", "Please use a mobile-friendly Web3 browser such as MetaMask to collect this reward", Infinity);
} else if (!window.ethereum) {
toaster.create("warning", "Please use a web3 enabled browser to collect this reward.", Infinity);
buttonController.hideAll();
}
} else {
toaster.create("error", error.message);
}
return null;
} else {
toaster.create("error", "An unknown error occurred.");
}
}
37 changes: 23 additions & 14 deletions static/scripts/rewards/web3/erc20-permit.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import { JsonRpcSigner, TransactionResponse } from "@ethersproject/providers";
import { BigNumber, BigNumberish, Contract, ethers } from "ethers";
import { TransactionResponse } from "@ethersproject/providers";
import { BigNumber, BigNumberish, Contract, Signer, ethers } from "ethers";
import { erc20Abi, permit2Abi } from "../abis";
import { AppState, app } from "../app-state";
import { permit2Address } from "../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";
import { connectWallet } from "./connect-wallet";

export async function fetchTreasury(
permit: Erc20Permit | Erc721Permit
Expand Down Expand Up @@ -60,6 +61,9 @@ async function checkPermitClaimability(app: AppState): Promise<boolean> {

async function transferFromPermit(permit2Contract: Contract, app: AppState) {
const reward = app.reward;
const signer = app.signer;
if (!signer) return null;

try {
const tx = await permit2Contract.permitTransferFrom(reward.permit, reward.transferDetails, reward.owner, reward.signature);
toaster.create("info", `Transaction sent`);
Expand Down Expand Up @@ -91,6 +95,7 @@ async function waitForTransaction(tx: TransactionResponse) {
buttonController.hideLoader();
buttonController.hideMakeClaim();
console.log(receipt.transactionHash);

return receipt;
} catch (error: unknown) {
if (error instanceof Error) {
Expand All @@ -103,21 +108,23 @@ async function waitForTransaction(tx: TransactionResponse) {

export function claimErc20PermitHandlerWrapper(app: AppState) {
return async function claimErc20PermitHandler() {
const signer = await connectWallet(app);
if (!signer) {
return;
}

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

const isPermitClaimable = await checkPermitClaimability(app);
if (!isPermitClaimable) return;

const permit2Contract = new ethers.Contract(permit2Address, permit2Abi, app.signer);
const permit2Contract = new ethers.Contract(permit2Address, permit2Abi, signer);
if (!permit2Contract) return;

const tx = await transferFromPermit(permit2Contract, app);
if (!tx) return;

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

const receipt = await waitForTransaction(tx);
if (!receipt) return;

Expand Down Expand Up @@ -168,9 +175,10 @@ async function checkPermitClaimable(app: AppState): Promise<boolean> {
return false;
}

let user: string;
let user: string | undefined;
try {
user = (await app.signer.getAddress()).toLowerCase();
const address = await app.signer?.getAddress();
user = address?.toLowerCase();
} catch (error: unknown) {
console.error("Error in signer.getAddress: ", error);
return false;
Expand All @@ -188,8 +196,8 @@ async function checkPermitClaimable(app: AppState): Promise<boolean> {

export async function checkRenderMakeClaimControl(app: AppState) {
try {
const address = await app.signer.getAddress();
const user = address.toLowerCase();
const address = await app.signer?.getAddress();
const user = address?.toLowerCase();

if (app.reward) {
const beneficiary = app.reward.transferDetails.to.toLowerCase();
Expand All @@ -207,8 +215,8 @@ export async function checkRenderMakeClaimControl(app: AppState) {

export async function checkRenderInvalidatePermitAdminControl(app: AppState) {
try {
const address = await app.signer.getAddress();
const user = address.toLowerCase();
const address = await app.signer?.getAddress();
const user = address?.toLowerCase();

if (app.reward) {
const owner = app.reward.owner.toLowerCase();
Expand Down Expand Up @@ -266,12 +274,13 @@ async function isNonceClaimed(app: AppState): Promise<boolean> {
return bit.and(flipped).eq(0);
}

async function invalidateNonce(signer: JsonRpcSigner, nonce: BigNumberish): Promise<void> {
async function invalidateNonce(signer: Signer | null, nonce: BigNumberish): Promise<void> {
if (!signer) return;
const permit2Contract = new ethers.Contract(permit2Address, permit2Abi, signer);
const { wordPos, bitPos } = nonceBitmap(nonce);
// mimics https://github.com/ubiquity/pay.ubq.fi/blob/c9e7ed90718fe977fd9f348db27adf31d91d07fb/scripts/solidity/test/Permit2.t.sol#L428
const bit = BigNumber.from(1).shl(bitPos);
const sourceBitmap = await permit2Contract.nonceBitmap(await signer.getAddress(), wordPos.toString());
const sourceBitmap = await permit2Contract.nonceBitmap(await signer?.getAddress(), wordPos.toString());
const mask = sourceBitmap.or(bit);
await permit2Contract.invalidateUnorderedNonces(wordPos, mask);
}
Expand Down
2 changes: 1 addition & 1 deletion static/scripts/rewards/web3/erc721-permit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { connectWallet } from "./connect-wallet";

export function claimErc721PermitHandler(reward: Erc721Permit) {
return async function claimHandler() {
const signer = await connectWallet();
const signer = await connectWallet(app);
if (!signer) {
return;
}
Expand Down
7 changes: 6 additions & 1 deletion yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -2700,7 +2700,7 @@ esutils@^2.0.2:
resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64"
integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==

ethers@^5.3.1, ethers@^5.7.2:
ethers@5.7.2, ethers@^5.3.1:
version "5.7.2"
resolved "https://registry.yarnpkg.com/ethers/-/ethers-5.7.2.tgz#3a7deeabbb8c030d4126b24f84e525466145872e"
integrity sha512-wswUsmWo1aOK8rR7DIKiWSw9DbLWe6x98Jrn8wcTflTVvaXhAMaB5zGAXy0GYQEQp9iO1iSHWVyARQm11zUtyg==
Expand Down Expand Up @@ -3974,6 +3974,11 @@ [email protected], minimist@^1.2.5, minimist@^1.2.6:
resolved "https://registry.yarnpkg.com/minipass/-/minipass-7.0.4.tgz#dbce03740f50a4786ba994c1fb908844d27b038c"
integrity sha512-jYofLM5Dam9279rdkWzqHozUo4ybjdZmCsDHePy5V/PbBcVMiSZR97gmAy45aqi8CK1lG2ECd356FU86avfwUQ==

mipd@^0.0.7:
version "0.0.7"
resolved "https://registry.yarnpkg.com/mipd/-/mipd-0.0.7.tgz#bb5559e21fa18dc3d9fe1c08902ef14b7ce32fd9"
integrity sha512-aAPZPNDQ3uMTdKbuO2YmAw2TxLHO0moa4YKAyETM/DTj5FloZo+a+8tU+iv4GmW+sOxKLSRwcSFuczk+Cpt6fg==

[email protected]:
version "2.1.2"
resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009"
Expand Down

0 comments on commit ca344eb

Please sign in to comment.