Skip to content

Commit

Permalink
fix: update root on local fork automatically + fix chain bug (#18)
Browse files Browse the repository at this point in the history
  • Loading branch information
yum0e authored Jul 26, 2023
1 parent 027d357 commit b01f4be
Show file tree
Hide file tree
Showing 12 changed files with 242 additions and 78 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
/.next
/front/.next/
/out/
/front/script/register-root/latest-root.txt

# production
/build
Expand Down
21 changes: 20 additions & 1 deletion front/next.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,27 @@ const nextConfig = {
config.experiments = {
...config.experiments,
};
config.ignoreWarnings = [
{ module: /src\/utils\/useContract.tsx/ },
{ module: /src\/utils\/index.ts/ },
{ module: /src\/app\/page.tsx/ },

{ module: /node_modules\/node-fetch\/lib\/index\.js/ },
{ module: /node_modules\/cross-fetch\/polyfill\.js/ },
{ module: /node_modules\/@walletconnect\/keyvaluestorage\/dist\/cjs\/node-js\/db.js/ },
{ module: /node_modules\/@walletconnect\/keyvaluestorage\/dist\/cjs\/node-js\/index.js/ },
{ module: /node_modules\/@walletconnect\/keyvaluestorage\/dist\/cjs\/index.js/ },
{ module: /node_modules\/@walletconnect\/core\/dist\/index.cjs.js/ },
{ module: /node_modules\/@walletconnect\/sign-client\/dist\/index.cjs.js/ },
{ module: /node_modules\/@walletconnect\/universal-provider\/dist\/index.es.js/ },
{ module: /node_modules\/@walletconnect\/ethereum-provider\/dist\/index.es.js/ },
{ module: /node_modules\/@wagmi\/connectors\/dist\/walletConnect.js/ },
{ module: /node_modules\/@wagmi\/core\/dist\/connectors\/walletConnect.js/ },
{ module: /node_modules\/wagmi\/dist\/connectors\/walletConnect.js/ },
{ module: /node_modules\/@rainbow-me\/rainbowkit\/dist\/index.js/ },
];
return config;
},
};

module.exports = nextConfig;
module.exports = nextConfig;
7 changes: 5 additions & 2 deletions front/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,12 @@
"version": "0.1.0",
"private": true,
"scripts": {
"dev": "NODE_OPTIONS='--max-http-header-size=24576' next dev -p 3000 & nodemon --watch ../src/Airdrop.sol --exec 'npm run deploy-airdrop && sleep 3 && ./script/log.sh'",
"dev": "NODE_OPTIONS='--max-http-header-size=24576' next dev -p 3000 & nodemon --watch ../src/Airdrop.sol --exec 'npm run deploy-airdrop && sleep 3 && ./script/log.sh' & nodemon --watch ./script/register-root/latest-root.txt --exec 'yarn register-root' & nodemon --exec 'yarn fetch-hub'",
"build": "next build",
"start": "NODE_OPTIONS='--max-http-header-size=24576' next start",
"lint": "next lint",
"fetch-hub": "npx ts-node script/register-root/fetch-hub.ts",
"register-root": "npx ts-node script/register-root/register-root.ts",
"deploy-airdrop": "forge script DeployAirdrop --rpc-url http://localhost:8545 -vv --mnemonics 'test test test test test test test test test test test junk' --sender '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266' --broadcast && ./script/generate-abi-from-front.sh"
},
"browser": {
Expand All @@ -16,10 +18,11 @@
},
"dependencies": {
"@rainbow-me/rainbowkit": "^1.0.2",
"@sismo-core/sismo-connect-react": "0.0.18",
"@sismo-core/sismo-connect-react": "0.0.20",
"@types/node": "20.2.3",
"@types/react": "18.2.7",
"@types/react-dom": "18.2.4",
"axios": "^1.4.0",
"eslint": "8.41.0",
"eslint-config-next": "13.4.3",
"next": "13.4.3",
Expand Down
40 changes: 40 additions & 0 deletions front/script/register-root/fetch-hub.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
const axios = require("axios");
const fs = require("fs");
const path = require("path");

const latestRootFilePath = path.join(__dirname, "latest-root.txt");

const getLatestRoot = (): string => {
try {
return fs.readFileSync(latestRootFilePath, "utf-8") as string;
} catch (e: any) {
return "";
}
};

const fetchHub = async () => {
while (true) {
try {
const res = (await axios(
" https://hub.sismo.io/available-data/gnosis/hydra-s2?latest=true&isOnChain=true"
)) as { data: { items: [{ identifier: string }] } };
const root = res.data?.items?.[0].identifier;

const latestRootSaved = getLatestRoot();

if (latestRootSaved !== root) {
fs.writeFileSync(latestRootFilePath, root, { flag: "w" });
console.log(`New latest root ${root} fetched.`);
}
} catch (e) {
console.error(
"Error while fetching the latest root for Sismo Connect, you seem to have lost your internet connection."
);
}
await new Promise((resolve) => setTimeout(resolve, 10000));
}
};

fetchHub();

module.exports = { latestRootFilePath, path, getLatestRoot };
22 changes: 22 additions & 0 deletions front/script/register-root/register-root.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
#!/bin/bash

lastRoot=$1

# remove 0x prefix and leading zeros on a hex string
remove_zeros() {
awk '{sub(/^0x*/, "");}1' | awk '{sub(/^0*/, "");}1'
}

# get the available roots registry contract address
# by calling the get function of the sismoConnectAddressesProvider contract
availableRootsRegistryContractAddress=0x$(echo $(cast call 0x3Cd5334eB64ebBd4003b72022CC25465f1BFcEe6 "get(string)" "sismoConnectAvailableRootsRegistry") | remove_zeros)

# get the owner of the roots registry contract
# first remove the 0x prefix, then remove the leading zeros with awk
rootsRegistryContractOwner=0x$(echo $(cast call $availableRootsRegistryContractAddress "owner()") | remove_zeros)

# impersonate the owner of the roots registry contract
cast rpc anvil_impersonateAccount $rootsRegistryContractOwner

# register the root
cast send $availableRootsRegistryContractAddress 'registerRoot(uint256)' $lastRoot --from $rootsRegistryContractOwner --unlocked
25 changes: 25 additions & 0 deletions front/script/register-root/register-root.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
const { readFileSync } = require("fs");
const { spawnSync } = require("child_process");
const fetchHubExports = require("./fetch-hub");

const registerRoot = (root: string) => {
const registerRootScriptPath = fetchHubExports.path.join(__dirname, "register-root.sh");
const child = spawnSync(`${registerRootScriptPath} ${root}`, {
shell: true,
});

if (child.status !== 0) {
console.error(child.stderr.toString());
throw new Error("Error while registering root on the local fork");
}

console.group(`Root ${root} successfully registered on the local fork`);
};

const main = async () => {
let root = fetchHubExports.getLatestRoot();
console.log(`Registering root ${root} on the local fork...`);
registerRoot(root === "" ? "0x" : root);
};

main();
19 changes: 12 additions & 7 deletions front/src/app/components/Header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,25 +16,30 @@ const Header: React.FC = () => {
the frontend <br />
3. The frontend forwards the response to ERC20 smart contract via claimWithSismo function{" "}
<br />
4. The smart contract verifies the proofs contained in the response and stores
verified claims and auths <br />
4. The smart contract verifies the proofs contained in the response and stores verified
claims and auths <br />
5. The frontend reads the verified claims and auths from the contract and displays them
</p>
<div>
<p>
<b className="code-snippet">front/src/app/page.tsx</b>: Frontend - Integrate Sismo Connect Button and make Sismo Connect request
<b className="code-snippet">front/src/app/page.tsx</b>: Frontend - Integrate Sismo Connect
Button and make Sismo Connect request
</p>
<p>
<b className="code-snippet">front/src/app/sismo-connect-config.ts</b>: Sismo Connect configuration and requests
<b className="code-snippet">front/src/app/sismo-connect-config.ts</b>: Sismo Connect
configuration and requests
</p>
<p>
<b className="code-snippet">src/Airdrop.sol</b>: Contract - verifies Sismo Connect response and stores verified claims, auths and signed message
<b className="code-snippet">src/Airdrop.sol</b>: Contract - verifies Sismo Connect
response and stores verified claims, auths and signed message
</p>
<p className="callout">
{" "}
Notes: <br />
1. You should exactly the same Configuration (AppId and impersonation), AuthRequests and ClaimsRequests in the frontend and in your contract <br />
2. If you are using metamask and transactions hang. Go to settings > advanced > clear activity and nonce data <br />
1. You should exactly have the same Configuration (AppId and impersonation), AuthRequests
and ClaimsRequests in the frontend and in your contract <br />
2. If you are using metamask and transactions hang. Go to settings &gt; advanced &gt;
clear activity and nonce data <br />
3. First ZK Proof generation takes longer time, especially with bad internet as there is a
zkey file to download once in the data vault connection <br />
4. The more proofs you request, the longer it takes to generate them (about 2 secs per
Expand Down
34 changes: 18 additions & 16 deletions front/src/app/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import { useConnectModal } from "@rainbow-me/rainbowkit";
import { formatEther } from "viem";
import {
formatError,
getAuthRequestsAndClaimRequestsFromSismoConnectRequest,
getProofDataForAuth,
getProofDataForClaim,
getUserIdFromHex,
Expand Down Expand Up @@ -65,9 +64,6 @@ export default function Home() {
chain: CHAIN,
});

// Get the SismoConnectConfig and Sismo Connect Request from the contract
// Set react state accordingly to display the Sismo Connect Button

useEffect(() => {
setClaimError(error);
if (!responseBytes) return;
Expand All @@ -76,19 +72,12 @@ export default function Home() {

/* ************************* Reset state **************************** */
function resetApp() {
setPageState("init");
setSismoConnectVerifiedResult(null);
setClaimError(null);
const url = new URL(window.location.href);
url.searchParams.delete("sismoConnectResponseCompressed");
window.history.replaceState({}, "", url.toString());
setResponseBytes("");
window.location.href = "/";
}

/* ************ Handle the airdrop claim button click ******************* */
async function claimAirdrop() {
if (!address) return;
setClaimError("");
try {
if (chain?.id !== CHAIN.id) await switchNetworkAsync?.(CHAIN.id);
setPageState("confirmingTransaction");
Expand All @@ -114,7 +103,12 @@ export default function Home() {
} catch (e: any) {
setClaimError(formatError(e));
} finally {
setPageState("responseReceived");
setPageState((prev) => {
if (prev === "verified") {
return "verified";
}
return "responseReceived";
});
}
}

Expand Down Expand Up @@ -159,7 +153,7 @@ export default function Home() {
/>
</>
)}
{claimError !== null && (
{!claimError && (
<div className="status-wrapper">
{pageState == "responseReceived" && (
<button onClick={() => claimAirdrop()}>{"Claim"}</button>
Expand All @@ -179,8 +173,16 @@ export default function Home() {
{claimError.slice(0, 50) ===
'The contract function "balanceOf" returned no data' && (
<p style={{ color: "#0BDA51" }}>
Please restart your frontend with "yarn dev" command and try again, it will
automatically deploy a new contract for you!
If you are developing on a local fork, please restart your frontend with "yarn
dev" command and try again, it will automatically deploy a new contract for you!
</p>
)}
{claimError.includes("RegistryRootNotAvailable") && (
<p style={{ color: "#0BDA51" }}>
If you are developing on a local fork, you should restart your local chain and
your frontend. Your fork needs to be updated with the latest registry root sent
on chain. In metamask, go to "settings" &gt; "advanced" &gt; "clear activity and
nonce data" to not encounter nonce errors after restart.
</p>
)}
{claimError.slice(0, 16) === "Please switch to" && (
Expand Down
6 changes: 0 additions & 6 deletions front/src/utils/misc.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
import { decodeAbiParameters, encodeAbiParameters } from "viem";
import { abi as AirdropABI } from "../../../abi/Airdrop.json";
import { errorsABI } from "./errorsABI";
import {
AuthRequest,
AuthType,
Expand All @@ -19,10 +17,6 @@ export function removeDashAndCapitalizeFirstLetter(str: string) {
return str.replace(/-/g, " ").replace(/\b\w/g, (l) => l.toUpperCase());
}

export const baseContractInputs = {
abi: [...AirdropABI, ...errorsABI],
};

export const signMessage = (address: `0x${string}`) => {
if (!address) return "";

Expand Down
51 changes: 33 additions & 18 deletions front/src/utils/useContract.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,16 @@ import {
} from "viem";
import { useAccount, useNetwork, useSwitchNetwork, useWalletClient } from "wagmi";
import { waitForTransaction, getPublicClient } from "@wagmi/core";
import { abi as AirdropABI } from "../../../abi/Airdrop.json";
import Airdrop from "../../../abi/Airdrop.json";
import { errorsABI } from "./errorsABI";
import { formatError } from "./misc";
import { fundMyAccountOnLocalFork } from "./fundMyAccountOnLocalFork";
import { transactions } from "../../../broadcast/Airdrop.s.sol/5151111/run-latest.json";

export const airdropABI = [...Airdrop.abi, ...errorsABI] as const;

export type ContractClaim = {
airdropContract: GetContractReturnType<typeof AirdropABI, PublicClient, WalletClient>;
airdropContract: GetContractReturnType<typeof airdropABI, PublicClient, WalletClient>;
switchNetworkAsync: ((chainId?: number | undefined) => Promise<Chain>) | undefined;
waitingForTransaction: (hash: `0x${string}`) => Promise<TransactionReceipt | undefined>;
error: string;
Expand All @@ -40,20 +42,25 @@ export default function useContract({

const airdropContract = getContract({
address: transactions[0].contractAddress as `0x${string}`,
abi: [...AirdropABI, ...errorsABI],
abi: airdropABI,
publicClient,
walletClient: walletClient as WalletClient,
});

/* ************* Handle simulateContract call & chain errors ************ */
useEffect(() => {
if (currentChain?.id !== chain.id) return setError(`Please switch to ${chain.name} network`);
if (currentChain?.id !== chain.id) {
return setError(`Please switch to ${chain.name} network`);
}
setError("");
}, [currentChain]);

useEffect(() => {
if (!isConnected) return;
if (!responseBytes) return;
if (currentChain?.id !== chain.id) {
return setError(`Please switch to ${chain.name} network`);
}
async function simulate() {
try {
await airdropContract.simulate.claimWithSismo([responseBytes, address]);
Expand All @@ -64,25 +71,33 @@ export default function useContract({
}

simulate();
}, [address, isConnected, responseBytes]);
}, [address, isConnected, responseBytes, currentChain]);

async function waitingForTransaction(
hash: `0x${string}`
): Promise<TransactionReceipt | undefined> {
let txReceipt: TransactionReceipt | undefined;
if (chain.id === 5151111) {
const timeout = new Promise((_, reject) =>
setTimeout(() => {
setError(
"Transaction timed-out: If you are running a local fork on Anvil please make sure to reset your wallet nonce. In metamask: Go to settings > advanced > clear activity and nonce data"
);
}, 10000)
);
const txReceiptPromise = hash && waitForTransaction({ hash: hash });
const race = await Promise.race([txReceiptPromise, timeout]);
txReceipt = race as TransactionReceipt;
} else {
txReceipt = hash && (await waitForTransaction({ hash: hash }));
try {
if (chain.id === 5151111) {
const timeout = new Promise((_, reject) =>
setTimeout(
() =>
reject(
new Error(
"Transaction timed-out: If you are running a local fork on Anvil please make sure to reset your wallet nonce. In metamask: Go to settings > advanced > clear activity and nonce data"
)
),
10000
)
);
const txReceiptPromise = hash && waitForTransaction({ hash: hash });
const race = await Promise.race([txReceiptPromise, timeout]);
txReceipt = race as TransactionReceipt;
} else {
txReceipt = hash && (await waitForTransaction({ hash: hash }));
}
} catch (e: any) {
setError(formatError(e));
}
return txReceipt;
}
Expand Down
Loading

0 comments on commit b01f4be

Please sign in to comment.