Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature: Show transaction hashes for claimed permits #173

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions build/esbuild-build.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ export const esBuildContext: esbuild.BuildOptions = {
".svg": "dataurl",
},
outdir: "static/out",
define: createEnvDefines(["SUPABASE_URL", "SUPABASE_ANON_KEY"], { allNetworkUrls }),
define: createEnvDefines(["SUPABASE_URL", "SUPABASE_ANON_KEY"], { extraRpcs: allNetworkUrls }),
};

esbuild
Expand Down Expand Up @@ -62,6 +62,5 @@ function createEnvDefines(envVarNames: string[], extras: Record<string, unknown>
defines[key] = JSON.stringify(extras[key]);
}
}
defines["extraRpcs"] = JSON.stringify(extraRpcs);
return defines;
}
19 changes: 18 additions & 1 deletion static/scripts/rewards/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import axios from "axios";
import { Contract, ethers } from "ethers";
import { erc20Abi } from "./abis";
import { JsonRpcProvider } from "@ethersproject/providers";
import { networkRpcs } from "./constants";
import { networkRpcs, networkExplorers } from "./constants";

type DataType = {
jsonrpc: string;
Expand Down Expand Up @@ -71,3 +71,20 @@ export async function getOptimalProvider(networkId: number) {
ensAddress: "",
});
}

export function getExplorerLinkForTx(networkId: number, hash: string): string {
if (!hash) return "#";
return `${networkExplorers[networkId]}/tx/${hash}`;
}

export function shortenTxHash(hash: string | undefined, length = 10): string {
if (!hash) return "";

const prefixLength = Math.floor(length / 2);
const suffixLength = length - prefixLength;

const prefix = hash.slice(0, prefixLength);
const suffix = hash.slice(-suffixLength);

return prefix + "..." + suffix;
}
52 changes: 50 additions & 2 deletions static/scripts/rewards/web3/erc20-permit.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,20 @@
import { BigNumber, BigNumberish, ethers } from "ethers";
import { permit2Abi } from "../abis";
import { permit2Address } from "../constants";
import { getErc20Contract, getOptimalProvider } from "../helpers";
import { getErc20Contract, getOptimalProvider, getExplorerLinkForTx, shortenTxHash } from "../helpers";
import { Erc20Permit } from "../render-transaction/tx-type";
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";
import { createClient } from "@supabase/supabase-js";

declare const SUPABASE_URL: string;
declare const SUPABASE_ANON_KEY: string;

const supabase = createClient(SUPABASE_URL, SUPABASE_ANON_KEY);

export async function fetchTreasury(
permit: Erc20Permit,
Expand Down Expand Up @@ -59,7 +65,8 @@ export function claimErc20PermitHandler(permit: Erc20Permit, provider: JsonRpcPr
toaster.create("info", `Transaction sent`);
const receipt = await tx.wait();
toaster.create("success", `Claim Complete.`);
console.log(receipt.transactionHash); // @TODO: post to database

await updatePermitTxHash(permit, receipt.transactionHash);

claimButton.element.removeEventListener("click", handler);
renderTransaction(provider).catch(console.error);
Expand All @@ -74,6 +81,13 @@ export function claimErc20PermitHandler(permit: Erc20Permit, provider: JsonRpcPr
}

export async function checkPermitClaimable(permit: Erc20Permit, signer: ethers.providers.JsonRpcSigner | null, provider: JsonRpcProvider) {
const permitHash = await doesPermitHaveTxHash(permit);
if (permitHash !== null) {
const explorerLink = `<a href="${getExplorerLinkForTx(permit.networkId, permitHash)}" target=_blank >${shortenTxHash(permitHash)}</a>`;
Copy link
Member

@0x4007 0x4007 Feb 29, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh I understand, its only if it exists in the database. Perhaps we can do an end-to-end test somehow? How did you test locally? We can just do the same.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1- generate claim through script
2- write it to db manually (as null transaction column)
3- and test the rest :)

I assume that (I have to somehow) permits are written to db correctly.

Copy link
Member

@0x4007 0x4007 Feb 29, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suppose for another task we should replace the claim button with the transaction hash if it has already been claimed or validated.

We can substitute the claim button with one that has this icon, and upon mouse over it can say "View Claim"

<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24"><path d="M212.309-140.001q-30.308 0-51.308-21t-21-51.308v-535.382q0-30.308 21-51.308t51.308-21h252.305V-760H212.309q-4.616 0-8.463 3.846-3.846 3.847-3.846 8.463v535.382q0 4.616 3.846 8.463 3.847 3.846 8.463 3.846h535.382q4.616 0 8.463-3.846 3.846-3.847 3.846-8.463v-252.305h59.999v252.305q0 30.308-21 51.308t-51.308 21H212.309Zm176.46-206.615-42.153-42.153L717.847-760H560v-59.999h259.999V-560H760v-157.847L388.769-346.616Z"/></svg>

toaster.create("error", `This reward has already been claimed. Transaction hash: ${explorerLink}`);
return false;
}

const isClaimed = await isNonceClaimed(permit);
if (isClaimed) {
toaster.create("error", `Your reward for this task has already been claimed or invalidated.`);
Expand Down Expand Up @@ -182,3 +196,37 @@ export function nonceBitmap(nonce: BigNumberish): { wordPos: BigNumber; bitPos:
const bitPos = BigNumber.from(nonce).and(255).toNumber();
return { wordPos, bitPos };
}

export async function doesPermitHaveTxHash(permit: Erc20Permit): Promise<string | null> {
const { data, error } = await supabase
.from("permits")
.select("transaction")
// using only nonce in the condition as it's defined unique on db
.eq("nonce", permit.permit.nonce.toString());

if (data?.length == 1 && data[0].transaction !== null) {
return data[0].transaction;
}

if (error !== null) {
console.error(error);
throw error;
}

return null;
}

export async function updatePermitTxHash(permit: Erc20Permit, hash: string): Promise<void> {
const { error } = await supabase
.from("permits")
.update({ transaction: hash })
rndquu marked this conversation as resolved.
Show resolved Hide resolved
// using only nonce in the condition as it's defined unique on db
.eq("nonce", permit.permit.nonce.toString());

if (error !== null) {
console.error(error);
throw error;
} else {
return;
}
}