Skip to content

Commit

Permalink
Merge branch 'improve-page-load' into development
Browse files Browse the repository at this point in the history
  • Loading branch information
0x4007 committed Feb 19, 2024
2 parents f088377 + 6854e2f commit 5932b60
Show file tree
Hide file tree
Showing 9 changed files with 104 additions and 58 deletions.
3 changes: 2 additions & 1 deletion .cspell.json
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
{
"$schema": "https://raw.githubusercontent.com/streetsidesoftware/cspell/main/cspell.schema.json",
"version": "0.2",
"ignorePaths": ["**/*.json", "**/*.css", "node_modules", "**/*.log"],
"ignorePaths": ["**/*.json", "**/*.css", "node_modules", "**/*.log", "/lib"],
"useGitignore": true,
"language": "en",
"words": [
"solmate",
"binkey",
"binsec",
"cirip",
Expand Down
2 changes: 2 additions & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
node_modules
/lib
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ http://localhost:8080?claim=eyJwZXJtaXQiOnsicGVybWl0dGVkIjp7InRva2VuIjoiMHgxMWZF

This section describes how to invalidate the following [permit](https://github.com/ubiquity/ubiquity-dollar/issues/643#issuecomment-1607152588) (i.e. invalidate a permit2 nonce)

1. Setup `.env` file with the required env varibales: `NONCE` (nonce number), `NONCE_SIGNER_ADDRESS` (i.e. the bot's wallet) and `RPC_PROVIDER_URL`. For this [permit URL](https://github.com/ubiquity/ubiquity-dollar/issues/643#issuecomment-1607152588) the `.env` file will look like this:
1. Setup `.env` file with the required env variables: `NONCE` (nonce number), `NONCE_SIGNER_ADDRESS` (i.e. the bot's wallet) and `RPC_PROVIDER_URL`. For this [permit URL](https://github.com/ubiquity/ubiquity-dollar/issues/643#issuecomment-1607152588) the `.env` file will look like this:

```
NONCE="9867970486646789738815952475601005014850694197864057371518032581271992954680"
Expand Down
13 changes: 8 additions & 5 deletions static/scripts/rewards/helpers.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import axios from "axios";
import { Contract, ethers } from "ethers";
import { erc20Abi } from "./abis";
import { JsonRpcProvider } from "@ethersproject/providers";
import { networkRpcs } from "./constants";

type DataType = {
Expand Down Expand Up @@ -34,13 +35,11 @@ const RPC_HEADER = {
"Content-Type": "application/json",
};

export async function getErc20Contract(contractAddress: string, networkId: number): Promise<Contract> {
const providerUrl = await getOptimalRPC(networkId);
const provider = new ethers.providers.JsonRpcProvider(providerUrl);
export async function getErc20Contract(contractAddress: string, provider: JsonRpcProvider): Promise<Contract> {
return new ethers.Contract(contractAddress, erc20Abi, provider);
}

export async function getOptimalRPC(networkId: number): Promise<string> {
export async function getOptimalProvider(networkId: number) {
const promises = networkRpcs[networkId].map(async (baseURL: string) => {
try {
const startTime = performance.now();
Expand All @@ -66,5 +65,9 @@ export async function getOptimalRPC(networkId: number): Promise<string> {
});

const { baseURL: optimalRPC } = await Promise.any(promises);
return optimalRPC;
return new ethers.providers.JsonRpcProvider(optimalRPC, {
name: optimalRPC,
chainId: networkId,
ensAddress: "",
});
}
6 changes: 3 additions & 3 deletions static/scripts/rewards/render-transaction/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { networkExplorers } from "../constants";
import { getOptimalRPC } from "../helpers";
import { getOptimalProvider } from "../helpers";
import { ClaimTx } from "./tx-type";

class AppState {
Expand All @@ -16,9 +16,9 @@ class AppState {

async currentNetworkRpc(): Promise<string> {
if (!this.currentTx) {
return getOptimalRPC(1);
return (await getOptimalProvider(1)).connection.url;
}
return getOptimalRPC(this.currentTx.networkId);
return (await getOptimalProvider(this.currentTx.networkId)).connection.url;
}

get currentExplorerUrl(): string {
Expand Down
37 changes: 28 additions & 9 deletions static/scripts/rewards/render-transaction/render-token-symbol.ts
Original file line number Diff line number Diff line change
@@ -1,26 +1,45 @@
import { BigNumberish, utils } from "ethers";
import { BigNumberish, Contract, utils } from "ethers";
import { getErc20Contract } from "../helpers";
import { MaxUint256 } from "@uniswap/permit2-sdk";
import { JsonRpcProvider } from "@ethersproject/providers";

export const tokens = [
{
name: "WXDAI",
address: "0xe91d153e0b41518a2ce8dd3d7944fa863463a97d",
},
{
name: "DAI",
address: "0x6b175474e89094c44da98b954eedeac495271d0f",
},
];

export async function renderTokenSymbol({
table,
requestedAmountElement,
tokenAddress,
ownerAddress,
networkId,
amount,
explorerUrl,
provider,
}: {
table: Element;
requestedAmountElement: Element;
tokenAddress: string;
ownerAddress: string;
networkId: number;
amount: BigNumberish;
explorerUrl: string;
provider: JsonRpcProvider;
}): Promise<void> {
const contract = await getErc20Contract(tokenAddress, networkId);
const symbol = await contract.symbol();
const decimals = await contract.decimals();
let symbol = tokenAddress === tokens[0].address ? tokens[0].name : tokenAddress === tokens[1].address ? tokens[1].name : false;
let decimals = tokenAddress === tokens[0].address ? 18 : tokenAddress === tokens[1].address ? 18 : MaxUint256;

if (!symbol || decimals === MaxUint256) {
const contract: Contract = await getErc20Contract(tokenAddress, provider);
symbol = await contract.symbol();
decimals = await contract.decimals();
}

table.setAttribute(`data-contract-loaded`, "true");
requestedAmountElement.innerHTML = `<a target="_blank" rel="noopener noreferrer" href="${explorerUrl}/token/${tokenAddress}?a=${ownerAddress}">${utils.formatUnits(
amount,
Expand All @@ -32,16 +51,16 @@ export async function renderNftSymbol({
table,
requestedAmountElement,
tokenAddress,
networkId,
explorerUrl,
provider,
}: {
table: Element;
requestedAmountElement: Element;
tokenAddress: string;
networkId: number;
explorerUrl: string;
provider: JsonRpcProvider;
}): Promise<void> {
const contract = await getErc20Contract(tokenAddress, networkId);
const contract = await getErc20Contract(tokenAddress, provider);
const symbol = await contract.symbol();
table.setAttribute(`data-contract-loaded`, "true");
requestedAmountElement.innerHTML = `<a target="_blank" rel="noopener noreferrer" href="${explorerUrl}/token/${tokenAddress}">1 ${symbol}</a>`;
Expand Down
25 changes: 16 additions & 9 deletions static/scripts/rewards/render-transaction/render-transaction.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { JsonRpcProvider } from "@ethersproject/providers";
import { Type } from "@sinclair/typebox";
import { Value } from "@sinclair/typebox/value";
import { networkExplorers } from "../constants";
import { getOptimalProvider } from "../helpers";
import { claimButton, hideClaimButton, resetClaimButton } from "../toaster";
import { claimErc20PermitHandler, fetchTreasury, generateInvalidatePermitAdminControl } from "../web3/erc20-permit";
import { claimErc721PermitHandler } from "../web3/erc721-permit";
Expand All @@ -13,6 +15,8 @@ import { setClaimMessage } from "./set-claim-message";
import { claimTxT } from "./tx-type";
import { removeAllEventListeners } from "./utils";

let optimalRPC: JsonRpcProvider;

export async function init() {
const table = document.getElementsByTagName(`table`)[0];

Expand All @@ -29,6 +33,9 @@ export async function init() {
try {
const claimTxs = Value.Decode(Type.Array(claimTxT), JSON.parse(atob(base64encodedTxData)));
app.claimTxs = claimTxs;
optimalRPC = await getOptimalProvider(app.currentTx?.networkId ?? app.claimTxs[0].networkId);

handleNetwork(app.currentTx?.networkId ?? app.claimTxs[0].networkId).catch(console.error);
} catch (error) {
console.error(error);
setClaimMessage({ type: "Error", message: `Invalid claim data passed in URL` });
Expand Down Expand Up @@ -60,7 +67,7 @@ export async function init() {
app.nextTx();
rewardsCount.innerHTML = `${app.currentIndex + 1}/${app.claimTxs.length} reward`;
table.setAttribute(`data-claim`, "none");
renderTransaction().catch(console.error);
renderTransaction(optimalRPC, true).catch(console.error);
});
}

Expand All @@ -71,15 +78,15 @@ export async function init() {
app.previousTx();
rewardsCount.innerHTML = `${app.currentIndex + 1}/${app.claimTxs.length} reward`;
table.setAttribute(`data-claim`, "none");
renderTransaction().catch(console.error);
renderTransaction(optimalRPC, true).catch(console.error);
});
}

setPagination(nextTxButton, prevTxButton);
}
}

renderTransaction().catch(console.error);
renderTransaction(optimalRPC, true).catch(console.error);
}

function setPagination(nextTxButton: Element | null, prevTxButton: Element | null) {
Expand All @@ -94,7 +101,7 @@ function setPagination(nextTxButton: Element | null, prevTxButton: Element | nul
}

type Success = boolean;
export async function renderTransaction(nextTx?: boolean): Promise<Success> {
export async function renderTransaction(provider: JsonRpcProvider, nextTx?: boolean): Promise<Success> {
const table = document.getElementsByTagName(`table`)[0];
resetClaimButton();

Expand All @@ -119,7 +126,7 @@ export async function renderTransaction(nextTx?: boolean): Promise<Success> {
handleNetwork(app.currentTx.networkId).catch(console.error);

if (app.currentTx.type === "erc20-permit") {
const treasury = await fetchTreasury(app.currentTx);
const treasury = await fetchTreasury(app.currentTx, provider);

// insert tx data into table
const requestedAmountElement = insertErc20PermitTableData(app.currentTx, table, treasury);
Expand All @@ -128,35 +135,35 @@ export async function renderTransaction(nextTx?: boolean): Promise<Success> {
renderTokenSymbol({
tokenAddress: app.currentTx.permit.permitted.token,
ownerAddress: app.currentTx.owner,
networkId: app.currentTx.networkId,
amount: app.currentTx.transferDetails.requestedAmount,
explorerUrl: networkExplorers[app.currentTx.networkId],
table,
requestedAmountElement,
provider,
}).catch(console.error);

const toElement = document.getElementById(`rewardRecipient`) as Element;
renderEnsName({ element: toElement, address: app.currentTx.transferDetails.to }).catch(console.error);

generateInvalidatePermitAdminControl(app.currentTx).catch(console.error);

claimButton.element.addEventListener("click", claimErc20PermitHandler(app.currentTx));
claimButton.element.addEventListener("click", claimErc20PermitHandler(app.currentTx, optimalRPC));
} else if (app.currentTx.type === "erc721-permit") {
const requestedAmountElement = insertErc721PermitTableData(app.currentTx, table);
table.setAttribute(`data-claim`, "ok");

renderNftSymbol({
tokenAddress: app.currentTx.nftAddress,
networkId: app.currentTx.networkId,
explorerUrl: networkExplorers[app.currentTx.networkId],
table,
requestedAmountElement,
provider,
}).catch(console.error);

const toElement = document.getElementById(`rewardRecipient`) as Element;
renderEnsName({ element: toElement, address: app.currentTx.request.beneficiary }).catch(console.error);

claimButton.element.addEventListener("click", claimErc721PermitHandler(app.currentTx));
claimButton.element.addEventListener("click", claimErc721PermitHandler(app.currentTx, provider));
}

return true;
Expand Down
61 changes: 39 additions & 22 deletions static/scripts/rewards/web3/erc20-permit.ts
Original file line number Diff line number Diff line change
@@ -1,38 +1,55 @@
import { BigNumber, BigNumberish, ethers } from "ethers";
import { erc20Abi, permit2Abi } from "../abis";
import { permit2Abi } from "../abis";
import { permit2Address } from "../constants";
import { getOptimalRPC } from "../helpers";
import invalidateButton from "../invalidate-component";
import { renderTransaction } from "../render-transaction/render-transaction";
import { getErc20Contract, getOptimalProvider } from "../helpers";
import { Erc20Permit } from "../render-transaction/tx-type";
import { claimButton, errorToast, loadingClaimButton, resetClaimButton, toaster } from "../toaster";
import { toaster, resetClaimButton, errorToast, loadingClaimButton, claimButton } from "../toaster";
import { renderTransaction } from "../render-transaction/render-transaction";
import { connectWallet } from "./wallet";
import invalidateButton from "../invalidate-component";
import { JsonRpcProvider } from "@ethersproject/providers";
import { tokens } from "../render-transaction/render-token-symbol";

export async function fetchTreasury(permit: Erc20Permit): Promise<{ balance: BigNumber; allowance: BigNumber; decimals: number; symbol: string }> {
export async function fetchTreasury(
permit: Erc20Permit,
provider: JsonRpcProvider
): Promise<{ balance: BigNumber; allowance: BigNumber; decimals: number; symbol: string }> {
try {
const providerUrl = await getOptimalRPC(permit.networkId);
const provider = new ethers.providers.JsonRpcProvider(providerUrl);
const tokenAddress = permit.permit.permitted.token;
const tokenContract = new ethers.Contract(tokenAddress, erc20Abi, provider);
const balance = await tokenContract.balanceOf(permit.owner);
const allowance = await tokenContract.allowance(permit.owner, permit2Address);
const decimals = await tokenContract.decimals();
const symbol = await tokenContract.symbol();
return { balance, allowance, decimals, symbol };
const tokenAddress = permit.permit.permitted.token.toLowerCase();
const tokenContract = await getErc20Contract(tokenAddress, provider);

if (tokenAddress === tokens[0].address || tokenAddress === tokens[1].address) {
const decimals = tokenAddress === tokens[0].address ? 18 : tokenAddress === tokens[1].address ? 18 : -1;
const symbol = tokenAddress === tokens[0].address ? tokens[0].name : tokenAddress === tokens[1].address ? tokens[1].name : "";

const [balance, allowance] = await Promise.all([tokenContract.balanceOf(permit.owner), tokenContract.allowance(permit.owner, permit2Address)]);

return { balance, allowance, decimals, symbol };
} else {
console.log(`Hardcode this token in render-token-symbol.ts and save two calls: ${tokenAddress}`);
const [balance, allowance, decimals, symbol] = await Promise.all([
tokenContract.balanceOf(permit.owner),
tokenContract.allowance(permit.owner, permit2Address),
tokenContract.decimals(),
tokenContract.symbol(),
]);

return { balance, allowance, decimals, symbol };
}
} catch (error: unknown) {
return { balance: BigNumber.from(-1), allowance: BigNumber.from(-1), decimals: -1, symbol: "" };
}
}

export function claimErc20PermitHandler(permit: Erc20Permit) {
export function claimErc20PermitHandler(permit: Erc20Permit, provider: JsonRpcProvider) {
return async function handler() {
try {
const signer = await connectWallet();
if (!signer) {
return;
}

if (!(await checkPermitClaimable(permit, signer))) {
if (!(await checkPermitClaimable(permit, signer, provider))) {
return;
}

Expand All @@ -45,7 +62,7 @@ export function claimErc20PermitHandler(permit: Erc20Permit) {
console.log(receipt.transactionHash); // @TODO: post to database

claimButton.element.removeEventListener("click", handler);
renderTransaction(true).catch(console.error);
renderTransaction(provider).catch(console.error);
} catch (error: unknown) {
if (error instanceof Error) {
console.log(error);
Expand All @@ -56,7 +73,7 @@ export function claimErc20PermitHandler(permit: Erc20Permit) {
};
}

export async function checkPermitClaimable(permit: Erc20Permit, signer: ethers.providers.JsonRpcSigner | null) {
export async function checkPermitClaimable(permit: Erc20Permit, signer: ethers.providers.JsonRpcSigner | null, provider: JsonRpcProvider) {
const isClaimed = await isNonceClaimed(permit);
if (isClaimed) {
toaster.create("error", `Your reward for this task has already been claimed or invalidated.`);
Expand All @@ -68,7 +85,7 @@ export async function checkPermitClaimable(permit: Erc20Permit, signer: ethers.p
return false;
}

const { balance, allowance } = await fetchTreasury(permit);
const { balance, allowance } = await fetchTreasury(permit, provider);
const permitted = BigNumber.from(permit.permit.permitted.amount);
const isSolvent = balance.gte(permitted);
const isAllowed = allowance.gte(permitted);
Expand Down Expand Up @@ -134,8 +151,8 @@ export async function generateInvalidatePermitAdminControl(permit: Erc20Permit)

//mimics https://github.com/Uniswap/permit2/blob/a7cd186948b44f9096a35035226d7d70b9e24eaf/src/SignatureTransfer.sol#L150
export async function isNonceClaimed(permit: Erc20Permit): Promise<boolean> {
const providerUrl = await getOptimalRPC(permit.networkId);
const provider = new ethers.providers.JsonRpcProvider(providerUrl);
const provider = await getOptimalProvider(permit.networkId);

const permit2Contract = new ethers.Contract(permit2Address, permit2Abi, provider);

const { wordPos, bitPos } = nonceBitmap(BigNumber.from(permit.permit.nonce));
Expand Down
Loading

0 comments on commit 5932b60

Please sign in to comment.