diff --git a/packages/erc7984example/app/_components/ERC7984Demo.tsx b/packages/erc7984example/app/_components/ERC7984Demo.tsx index 0658b97..c0eecbd 100644 --- a/packages/erc7984example/app/_components/ERC7984Demo.tsx +++ b/packages/erc7984example/app/_components/ERC7984Demo.tsx @@ -1,9 +1,10 @@ "use client"; -import { useMemo, useState, useEffect } from "react"; +import { useEffect, useMemo, useState } from "react"; +import { FHEBenchmark } from "./FHEBenchmark"; +import { ethers } from "ethers"; import { useFhevm } from "fhevm-sdk"; import { useAccount } from "wagmi"; -import { ethers } from "ethers"; import { RainbowKitCustomConnectButton } from "~~/components/helper/RainbowKitCustomConnectButton"; import { useERC7984Wagmi } from "~~/hooks/erc7984/useERC7984Wagmi"; import { useDeployedContractInfo } from "~~/hooks/helper"; @@ -101,11 +102,7 @@ export const ERC7984Demo = () => { setClaimStatus("checking"); try { - const contract = new ethers.Contract( - airdropContract.address, - airdropContract.abi, - erc7984.ethersSigner - ); + const contract = new ethers.Contract(airdropContract.address, airdropContract.abi, erc7984.ethersSigner); const claimed = await contract.hasClaimed(address, erc7984.contractAddress); setAlreadyClaimed(claimed); @@ -128,11 +125,7 @@ export const ERC7984Demo = () => { setClaimStatus("claiming"); try { - const contract = new ethers.Contract( - airdropContract.address, - airdropContract.abi, - erc7984.ethersSigner - ); + const contract = new ethers.Contract(airdropContract.address, airdropContract.abi, erc7984.ethersSigner); notification.info("Claiming tokens..."); @@ -198,19 +191,13 @@ export const ERC7984Demo = () => { "disabled:opacity-40 disabled:pointer-events-none disabled:cursor-not-allowed"; // Primary (accent) button - const primaryButtonClass = - buttonClass + - " text-[#2D2D2D] cursor-pointer"; + const primaryButtonClass = buttonClass + " text-[#2D2D2D] cursor-pointer"; // Secondary button - const secondaryButtonClass = - buttonClass + - " !bg-[#2D2D2D] text-[#F4F4F4] hover:!bg-[#A38025] cursor-pointer"; + const secondaryButtonClass = buttonClass + " !bg-[#2D2D2D] text-[#F4F4F4] hover:!bg-[#A38025] cursor-pointer"; // Success/confirmed state - const successButtonClass = - buttonClass + - " !bg-[#A38025] text-[#F4F4F4] hover:!bg-[#2D2D2D]"; + const successButtonClass = buttonClass + " !bg-[#A38025] text-[#F4F4F4] hover:!bg-[#2D2D2D]"; const titleClass = "font-semibold text-[#2D2D2D] text-2xl mb-4 pb-3 border-b border-[#2D2D2D]"; const sectionClass = "glass-card-strong p-8 mb-6 text-[#2D2D2D] relative z-10"; @@ -220,12 +207,12 @@ export const ERC7984Demo = () => {
- - ⚠️ - + ⚠️

Wallet not connected

-

Connect your wallet to use the ERC7984 confidential token demo.

+

+ Connect your wallet to use the ERC7984 confidential token demo. +

@@ -239,7 +226,9 @@ export const ERC7984Demo = () => { {/* Header */}

ERC7984 Confidential Token Demo

-

Interact with the Fully Homomorphic Encryption confidential token contract

+

+ Interact with the Fully Homomorphic Encryption confidential token contract +

{/* Balance Handle Display */} @@ -272,17 +261,15 @@ export const ERC7984Demo = () => { onClick={handleClaim} disabled={!address || claimStatus === "claiming" || claimStatus === "checking" || alreadyClaimed} > - {!address ? ( - "Connect Wallet" - ) : claimStatus === "checking" ? ( - "⏳ Checking..." - ) : claimStatus === "claiming" ? ( - "⏳ Claiming Tokens..." - ) : alreadyClaimed ? ( - "✅ Already Claimed" - ) : ( - "💧 Get Free Tokens" - )} + {!address + ? "Connect Wallet" + : claimStatus === "checking" + ? "⏳ Checking..." + : claimStatus === "claiming" + ? "⏳ Claiming Tokens..." + : alreadyClaimed + ? "✅ Already Claimed" + : "💧 Get Free Tokens"}
@@ -382,6 +369,9 @@ export const ERC7984Demo = () => { + + {/* FHE Performance Benchmark */} + ); }; @@ -433,9 +423,7 @@ function printPropertyTruncated(name: string, value: unknown) { // Truncate long strings const shouldTruncate = displayValue.length > 12; - const truncatedValue = shouldTruncate - ? `${displayValue.slice(0, 6)}...${displayValue.slice(-4)}` - : displayValue; + const truncatedValue = shouldTruncate ? `${displayValue.slice(0, 6)}...${displayValue.slice(-4)}` : displayValue; return (
@@ -461,9 +449,7 @@ function printBooleanProperty(name: string, value: boolean) { {name} {value ? "✓ true" : "✗ false"} @@ -471,4 +457,3 @@ function printBooleanProperty(name: string, value: boolean) {
); } - diff --git a/packages/erc7984example/app/_components/FHEBenchmark.tsx b/packages/erc7984example/app/_components/FHEBenchmark.tsx new file mode 100644 index 0000000..4756d86 --- /dev/null +++ b/packages/erc7984example/app/_components/FHEBenchmark.tsx @@ -0,0 +1,225 @@ +"use client"; + +import { useState } from "react"; +import { FhevmInstance } from "fhevm-sdk"; +import { useAccount } from "wagmi"; +import { useDeployedContractInfo } from "~~/hooks/helper"; +import { useWagmiEthers } from "~~/hooks/wagmi/useWagmiEthers"; +import type { AllowedChainIds } from "~~/utils/helper/networks"; + +type BenchmarkResult = { + operation: string; + duration: number; + timestamp: string; +}; + +type FHEBenchmarkProps = { + instance: FhevmInstance | undefined; + fhevmStatus: string; +}; + +export const FHEBenchmark = ({ instance, fhevmStatus }: FHEBenchmarkProps) => { + const { chain } = useAccount(); + const chainId = chain?.id; + + const initialMockChains = { 31337: "http://localhost:8545" }; + + const [results, setResults] = useState([]); + const [isRunning, setIsRunning] = useState(false); + const [threadInfo, setThreadInfo] = useState("Click 'Check Threading' to analyze"); + const [statusMessage, setStatusMessage] = useState(""); + + const delay = (ms: number) => new Promise(resolve => setTimeout(resolve, ms)); + + // Get ethers signer and contract info + const { ethersSigner } = useWagmiEthers(initialMockChains); + const allowedChainId = typeof chainId === "number" ? (chainId as AllowedChainIds) : undefined; + const { data: erc7984 } = useDeployedContractInfo({ contractName: "ERC7984Example", chainId: allowedChainId }); + + const addResult = (operation: string, duration: number) => { + setResults(prev => [ + ...prev, + { + operation, + duration, + timestamp: new Date().toISOString(), + }, + ]); + }; + + const checkThreading = () => { + try { + const info: string[] = []; + + // Check for crossOriginIsolated (required for SharedArrayBuffer/multi-threading) + info.push(`crossOriginIsolated: ${window.crossOriginIsolated}`); + + // Check for SharedArrayBuffer support + info.push(`SharedArrayBuffer: ${typeof SharedArrayBuffer !== "undefined"}`); + + // Check navigator.hardwareConcurrency + info.push(`CPU cores: ${navigator.hardwareConcurrency || "unknown"}`); + + // Check if relayerSDK is loaded + const w = window as any; + info.push(`relayerSDK loaded: ${!!w.relayerSDK}`); + + setThreadInfo(info.join(" | ")); + } catch (e) { + setThreadInfo(`Error: ${e}`); + } + }; + + const waitWithCountdown = async (seconds: number, message: string) => { + for (let i = seconds; i > 0; i--) { + setStatusMessage(`${message} (${i}s remaining)`); + await delay(1000); + } + setStatusMessage(""); + }; + + const runEncryptionBenchmark = async () => { + if (!instance || !ethersSigner || !erc7984?.address) { + alert("Instance, signer, or contract not ready"); + return; + } + + setIsRunning(true); + setResults([]); // Clear previous results + const userAddress = await ethersSigner.getAddress(); + const totalRuns = 3; // Reduced to 3 runs to minimize rate limit risk + + try { + // Warm-up run + setStatusMessage("Running warm-up encryption..."); + console.log("[Benchmark] Warm-up encryption..."); + const warmupStart = performance.now(); + const warmupInput = instance.createEncryptedInput(erc7984.address, userAddress); + (warmupInput as any).add64(BigInt(100)); + await (warmupInput as any).encrypt(); + const warmupEnd = performance.now(); + addResult("Warm-up (euint64)", warmupEnd - warmupStart); + + // Wait before next request + await waitWithCountdown(10, "Rate limit cooldown"); + + // Run benchmark encryptions with delays + for (let i = 0; i < totalRuns; i++) { + setStatusMessage(`Running encryption #${i + 1}...`); + console.log(`[Benchmark] Encryption #${i + 1}...`); + const startN = performance.now(); + const inputN = instance.createEncryptedInput(erc7984.address, userAddress); + (inputN as any).add64(BigInt(i * 1000 + 12345)); + await (inputN as any).encrypt(); + const endN = performance.now(); + addResult(`Encrypt euint64 #${i + 1}`, endN - startN); + + // Wait between requests (except after the last one) + if (i < totalRuns - 1) { + await waitWithCountdown(10, "Rate limit cooldown"); + } + } + + setStatusMessage("Benchmark complete!"); + console.log("[Benchmark] Encryption complete!"); + await delay(2000); + setStatusMessage(""); + } catch (e) { + console.error("[Benchmark] Encryption error:", e); + addResult("Encryption ERROR", -1); + setStatusMessage("Error occurred during benchmark"); + } finally { + setIsRunning(false); + } + }; + + const clearResults = () => { + setResults([]); + }; + + const isReady = instance && ethersSigner && erc7984?.address; + const validResults = results.filter(r => r.duration > 0 && !r.operation.includes("Warm-up")); + const avgDuration = + validResults.length > 0 ? validResults.reduce((a, b) => a + b.duration, 0) / validResults.length : 0; + + return ( +
+

FHE Performance Benchmark

+ + {/* Threading Info */} +
+
Threading Status:
+
{threadInfo}
+ +
+ + {/* Status */} +
+
+ FHEVM Status: {fhevmStatus} +
+
+ Ready: {isReady ? "Yes" : "No"} + {!isReady && " - Connect wallet and wait for FHEVM instance"} +
+
+ + {/* Rate Limit Warning */} +
+ Rate Limit Warning: The FHE encryption service has strict rate limits. + More than 5 requests in 10 seconds will result in a 1-hour ban. This benchmark includes 10-second delays between + operations to stay safe. +
+ + {/* Actions */} +
+ + +
+ + {/* Status Message */} + {statusMessage && ( +
{statusMessage}
+ )} + + {/* Results Table */} + {results.length > 0 && ( +
+ + + + + + + + + + {results.map((r, i) => ( + + + + + + ))} + +
OperationDuration (ms)Time
{r.operation} + {r.duration < 0 ? "ERROR" : r.duration.toFixed(2)} + {r.timestamp.split("T")[1].split(".")[0]}
+
+ )} + + {/* Summary */} + {validResults.length > 0 && ( +
+ Average (excluding warm-up): {avgDuration.toFixed(2)} ms across {validResults.length} runs +
+ )} +
+ ); +}; diff --git a/packages/erc7984example/app/layout.tsx b/packages/erc7984example/app/layout.tsx index 810a9f4..3191aa5 100644 --- a/packages/erc7984example/app/layout.tsx +++ b/packages/erc7984example/app/layout.tsx @@ -1,5 +1,5 @@ -import "@rainbow-me/rainbowkit/styles.css"; import Script from "next/script"; +import "@rainbow-me/rainbowkit/styles.css"; import { DappWrapperWithProviders } from "~~/components/DappWrapperWithProviders"; import { ThemeProvider } from "~~/components/ThemeProvider"; import "~~/styles/globals.css"; @@ -14,16 +14,10 @@ const DappWrapper = ({ children }: { children: React.ReactNode }) => { return ( - + -