From 649127319d008be8dc4504f90b83a564ba1b8010 Mon Sep 17 00:00:00 2001 From: Ali Mahdiyar Date: Sun, 21 Apr 2024 17:42:25 +0330 Subject: [PATCH] (feat) show computed result --- packages/nextjs/app/page.tsx | 534 +++++++++++++++++++++++--- packages/nextjs/components/Header.tsx | 19 +- 2 files changed, 473 insertions(+), 80 deletions(-) diff --git a/packages/nextjs/app/page.tsx b/packages/nextjs/app/page.tsx index c6a25f3..04686f1 100644 --- a/packages/nextjs/app/page.tsx +++ b/packages/nextjs/app/page.tsx @@ -1,80 +1,490 @@ "use client"; -import Link from "next/link"; +import React, { useCallback, useEffect, useMemo, useState } from "react"; +import { usePathname, useRouter, useSearchParams } from "next/navigation"; +import { prepareWriteContract, readContract, waitForTransaction, writeContract } from "@wagmi/core"; import type { NextPage } from "next"; -import { useAccount } from "wagmi"; -import { BugAntIcon, MagnifyingGlassIcon } from "@heroicons/react/24/outline"; +import { useAccount, useContractRead } from "wagmi"; +import { CopyString } from "~~/components/nillion/CopyString"; +import { NillionOnboarding } from "~~/components/nillion/NillionOnboarding"; import { Address } from "~~/components/scaffold-eth"; +import { useDeployedContractInfo } from "~~/hooks/scaffold-eth"; +import { getUserKeyFromSnap } from "~~/utils/nillion/getUserKeyFromSnap"; +import { nillionConfig } from "~~/utils/nillion/nillionConfig"; +import { retrieveSecretInteger } from "~~/utils/nillion/retrieveSecretInteger"; +import { storeProgram } from "~~/utils/nillion/storeProgram"; +import { storeSecretsInteger } from "~~/utils/nillion/storeSecretsInteger"; + +interface StringObject { + [key: string]: string | null; +} const Home: NextPage = () => { + const { data: deployedContractData } = useDeployedContractInfo("YourContract"); + const [programId, setProgramId] = useState(null); + const { data: currentRespondersCount } = useContractRead({ + address: deployedContractData?.address, + functionName: "getPartiesAndSecretsCount", + abi: deployedContractData?.abi, + args: programId ? [programId] : undefined, + watch: true, + }); + const secretName = useMemo(() => `r${String(currentRespondersCount)}_response`, [currentRespondersCount]); + const [partyIdToSecretIds, setPartyIdToSecretIds] = useState(""); + useEffect(() => { + async function loadParties() { + if (deployedContractData && currentRespondersCount && programId) { + // const res = (await multicall({ + // allowFailure: false, + // contracts: Array.from(Array(Number(currentRespondersCount)).keys()).map(i => ({ + // address: deployedContractData.address, + // abi: deployedContractData.abi, + // functionName: "partiesAndSecrets", + // args: [programId, BigInt(i)], + // })), + // })) as unknown as string[]; + const res = (await Promise.all( + Array.from(Array(Number(currentRespondersCount)).keys()).map(i => + readContract({ + address: deployedContractData.address, + abi: deployedContractData.abi, + functionName: "partiesAndSecrets", + args: [programId, BigInt(i)], + }), + ), + )) as unknown as string[]; + console.log({ res }); + setPartyIdToSecretIds(res.join(" ")); + } + } + + loadParties(); + }, [currentRespondersCount, deployedContractData, programId]); + const { address: connectedAddress } = useAccount(); + const [connectedToSnap, setConnectedToSnap] = useState(false); + const [userKey, setUserKey] = useState(null); + const [userId, setUserId] = useState(null); + const [nillion, setNillion] = useState(null); + const [nillionClient, setNillionClient] = useState(null); + + const [programName] = useState("identicall"); + const [computeResult, setComputeResult] = useState<{ [key: string]: bigint } | null>(null); + const [identifier, setIdentifier] = useState(""); + const [brightId, setBrightId] = useState(""); + const [secretValue, setSecretValue] = useState(""); + useEffect(() => { + const encoder = new TextEncoder(); + const data = encoder.encode(brightId); + crypto.subtle.digest("SHA-256", data).then(hashBuffer => { + const hashArray = Array.from(new Uint8Array(hashBuffer)); // Convert buffer to byte array + const hashHex = hashArray.map(byte => byte.toString(16).padStart(2, "0")).join(""); // Convert bytes to hex string + setSecretValue(BigInt("0x" + hashHex).toString()); + }); + }, [brightId]); + + const [storedSecretsNameToStoreId, setStoredSecretsNameToStoreId] = useState({ + my_int1: null, + my_int2: null, + }); + + // connect to snap + async function handleConnectToSnap() { + const snapResponse = await getUserKeyFromSnap(); + setUserKey(snapResponse?.user_key || null); + setConnectedToSnap(snapResponse?.connectedToSnap || false); + } + + const router = useRouter(); + const pathname = usePathname(); + const searchParams = useSearchParams(); + + const [myProgramIds, setMyProgramIds] = useState([]); + useEffect(() => { + try { + setMyProgramIds(JSON.parse(localStorage.getItem("myProgramIds") || "[]")); + } catch (e) {} + }, []); + // store program in the Nillion network and set the resulting program id + const handleStoreProgram = useCallback(async () => { + setStateText("Storing program..."); + const newProgramId = await storeProgram(nillionClient, programName); + const queryString = new URLSearchParams({ + ...Object.fromEntries(searchParams.entries()), + p: encodeURIComponent(newProgramId), + i: encodeURIComponent(identifier), + }); + const newMyProjectIds = [...myProgramIds, newProgramId]; + localStorage.setItem("myProgramIds", JSON.stringify(newMyProjectIds)); + setMyProgramIds(newMyProjectIds); + setStateText(""); + router.replace(`${pathname}?${queryString}`); + }, [nillionClient, programName, searchParams, identifier, myProgramIds, router, pathname]); + + useEffect(() => { + const pArg = searchParams.get("p"); + if (pArg) { + setProgramId(decodeURIComponent(pArg)); + } + const iArg = searchParams.get("i"); + if (iArg) { + setIdentifier(decodeURIComponent(iArg)); + } + }, [searchParams]); + + const isMyProgram = useMemo(() => Boolean(programId && myProgramIds.includes(programId)), [myProgramIds, programId]); + + async function handleRetrieveInt(secret_name: string, store_id: string | null) { + if (store_id) { + const value = await retrieveSecretInteger(nillionClient, store_id, secret_name); + alert(`${secret_name} is ${value}`); + } + } + + // reset nillion values + const resetNillion = () => { + setConnectedToSnap(false); + setUserKey(null); + setUserId(null); + setNillion(null); + setNillionClient(null); + }; + + useEffect(() => { + // when wallet is disconnected, reset nillion + if (!connectedAddress) { + resetNillion(); + } + }, [connectedAddress]); + + // Initialize nillionClient for use on page + useEffect(() => { + console.log({ userKey }); + if (userKey) { + const getNillionClientLibrary = async () => { + const nillionClientUtil = await import("~~/utils/nillion/nillionClient"); + const libraries = await nillionClientUtil.getNillionClient(userKey); + setNillion(libraries.nillion); + setNillionClient(libraries.nillionClient); + return libraries.nillionClient; + }; + getNillionClientLibrary().then(nillionClient => { + const user_id = nillionClient.user_id; + setUserId(user_id); + }); + } + }, [userKey]); + + const [stateText, setStateText] = useState(""); + + // handle form submit to store secrets with bindings + async function handleSecretFormSubmit() { + try { + if (programId && deployedContractData && currentRespondersCount !== undefined) { + setStateText("Storing secret..."); + const partyName = "Responder" + String(currentRespondersCount); + + const store_id = await storeSecretsInteger( + nillion, + nillionClient, + [{ name: secretName, value: secretValue }], + programId, + partyName, + [], + [], + [], + [programId.split("/")[0]], + ); + const partyIdToSecretId = `${nillionClient.party_id}:${store_id}`; + const { request } = await prepareWriteContract({ + address: deployedContractData.address, + abi: deployedContractData.abi, + functionName: "addPartyAndSecret", + args: [programId, partyIdToSecretId], + }); + setStateText("Awaiting user confirmation..."); + const { hash } = await writeContract(request); + setStateText("Sending transaction..."); + await waitForTransaction({ + hash, + }); + console.log("Secret stored at store_id:", store_id); + setStoredSecretsNameToStoreId(prevSecrets => ({ + ...prevSecrets, + [secretName]: store_id, + })); + } + } catch (e) { + setStateText(""); + } + } + + // compute on secrets + async function handleCompute() { + if (programId) { + // await compute( + // nillion, + // nillionClient, + // Object.values(storedSecretsNameToStoreId), + // programId, + // "same_response_count_for_r4", + // ).then(result => setComputeResult(result)); + try { + // create program bindings with the program id + const program_bindings = new nillion.ProgramBindings(programId); + + // add input and output party details (name and party id) to program bindings + Array.from(Array(4).keys()).forEach(i => { + const partyName = "Responder" + i; + const party_id = nillionClient.party_id; + program_bindings.add_input_party(partyName, party_id); + }); + program_bindings.add_input_party("Responder4", nillionClient.party_id); + program_bindings.add_output_party("Responder4", nillionClient.party_id); + + // create a compute time secrets object + const compute_time_secrets = new nillion.Secrets(); + const newComputeTimeSecret = nillion.Secret.new_unsigned_integer(secretValue); + compute_time_secrets.insert("r4_response", newComputeTimeSecret); + + // create a public variables object + const public_variables = new nillion.PublicVariables(); + + // compute + const compute_result_uuid = await nillionClient.compute( + nillionConfig.cluster_id, + program_bindings, + partyIdToSecretIds.split(" ").map(s => s.split(":")[1]), + compute_time_secrets, + public_variables, + ); + setComputeResult(await nillionClient.compute_result(compute_result_uuid)); + } catch (error: any) { + console.log("error", error); + return "error"; + } + } + } + + const [verified, setVerified] = useState(false); return ( <> -
-
-

- Welcome to - Scaffold-Nillion - Scaffold-ETH 2 + Nillion +
+
+

+ Identicall + Fight sybil attacks on social graphs + {!connectedAddress &&

Connect your MetaMask Flask wallet

} + {connectedAddress && connectedToSnap && !userKey && ( + + + + )}

-
-

Connected Address:

-
-
-

- Get started by editing{" "} - - packages/nextjs/app/page.tsx - -

-

- Edit your smart contract{" "} - - YourContract.sol - {" "} - in{" "} - - packages/hardhat/contracts - -

- -
- Nillion demo apps - - Nillion Password Manager - {" "} - - Nillion Hello World - {" "} - - Nillion Blind Compute - {" "} -
+ + {connectedAddress && ( +
+

Connected Wallet Address:

+
+
+ )} + + {connectedAddress && !connectedToSnap && ( + + )} + + {connectedToSnap && ( +
+ {userKey && ( +
+
+

+ 🤫 Nillion User Key from{" "} + + MetaMask Flask + + : +

+ + +
+ + {userId && ( +
+

Connected as Nillion User ID:

+ +
+ )} +
+ )} +
+ )}
-
- -

- Tinker with your smart contract using the{" "} - - Debug Contract - {" "} - tab. -

-
-
- -

- Explore your local transactions with the{" "} - - Block Explorer - {" "} - tab. -

-
+ {!connectedToSnap ? ( + + ) : !verified ? ( +
+

Verify your Nillion user with BrightID

+ +
+ ) : !programId ? ( +
+

Launch an investigation!

+

+ Press the button below to start an investigation and invite the others to see if you all know the same + BrightID of the person. Optionally, you can type an identifier of the person (e.g. Discord Id, phone + number, etc) to share +

+
+ + setIdentifier(e.target.value)} + required + className={`mt-1 block w-full px-3 py-2 bg-white border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm text-black`} + /> +
+ {stateText ? ( + stateText + ) : ( + + )} +
+ ) : ( +
+
+

Investigating person with identifier {identifier}

✅ {programName} program + stored
+ + +
+
+

+ What is the BrightID that you know for the person with identifier {identifier}? +

+

+ Current responses count:{" "} + {currentRespondersCount !== undefined ? String(currentRespondersCount) : "..."} +

+
The BrightID you enter is hashed before being sent to the server
+ +
+
+ {!!storedSecretsNameToStoreId[secretName] && userKey ? ( +
Successfully submitted!
+ ) : ( +
+
+ setBrightId(e.target.value)} + required + disabled={!programId} + className={`mt-1 block w-full px-3 py-2 bg-white border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm ${ + !programId ? "cursor-not-allowed bg-gray-100" : "bg-white" + }`} + /> +
+
+ {stateText ? ( + stateText + ) : isMyProgram ? ( +
+
[DEV Mode] party and secret ids:
+