diff --git a/package.json b/package.json index db52cb926..b0e26f319 100644 --- a/package.json +++ b/package.json @@ -18,6 +18,7 @@ "deploy:sepolia": "pnpm hardhat:deploy:sepolia && pnpm generate", "fork": "pnpm hardhat:fork", "format": "pnpm next:format && pnpm hardhat:format", + "sdk": "pnpm --filter fhevm-sdk", "sdk:build": "pnpm --filter ./packages/fhevm-sdk build", "sdk:watch": "pnpm --filter ./packages/fhevm-sdk watch", "sdk:test": "pnpm --filter ./packages/fhevm-sdk test", diff --git a/packages/fhevm-sdk2/package.json b/packages/fhevm-sdk2/package.json new file mode 100644 index 000000000..92e614513 --- /dev/null +++ b/packages/fhevm-sdk2/package.json @@ -0,0 +1,48 @@ +{ + "name": "fhevm-sdk", + "version": "0.1.0", + "private": true, + "type": "module", + "main": "dist/index.js", + "module": "dist/index.js", + "types": "src/index.ts", + "exports": { + ".": { + "types": "./src/index.ts", + "default": "./dist/index.js" + }, + "./react": { + "types": "./src/react/index.ts", + "default": "./dist/react/index.js" + } + }, + "scripts": { + "build": "tsc -p tsconfig.json", + "watch": "tsc -p tsconfig.json --watch", + "clean": "rm -rf dist", + "test": "vitest run --coverage", + "test:watch": "vitest" + }, + "dependencies": { + "idb": "^8.0.3", + "loadjs": "^4.3.0" + }, + "peerDependencies": { + "@zama-fhe/relayer-sdk": "^0.2.0", + "react": "^18.0.0 || ^19.0.0", + "viem": "2.34.0" + }, + "devDependencies": { + "@types/loadjs": "^4.0.4", + "@types/node": "~18.19.50", + "@types/react": "~19.0.7", + "@vitest/coverage-v8": "2.1.9", + "@zama-fhe/relayer-sdk": "0.2.0", + "fake-indexeddb": "~6.0.0", + "jsdom": "^27.0.0", + "react": "~19.0.0", + "typescript": "~5.8.2", + "viem": "2.34.0", + "vitest": "~2.1.8" + } +} diff --git a/packages/fhevm-sdk2/src/core/FhevmClient.ts b/packages/fhevm-sdk2/src/core/FhevmClient.ts new file mode 100644 index 000000000..be843703a --- /dev/null +++ b/packages/fhevm-sdk2/src/core/FhevmClient.ts @@ -0,0 +1,126 @@ +import { type EIP712, type FhevmInstance, type RelayerEncryptedInput } from '@zama-fhe/relayer-sdk/bundle'; +import { Address, TypedDataDomain, WalletClient } from 'viem'; +import { FhevmEnvironment } from './FhevmEnvironment'; + +export type FhevmClientInitOptions = { + contractAddress: Address; + durationDays: number; + walletClient: WalletClient; + sdkUrl?: string; +}; + +export type SignatureCacheItem = { + kp: { publicKey: string; privateKey: string }; + contractAddresses: string[]; + userAddress: string; + startTimestamp: number; + durationDays: number; + signature: string; +}; + +export class FhevmClient { + private ins!: FhevmInstance; + private durationDays!: number; + private initOptions!: FhevmClientInitOptions; + + async init(options: FhevmClientInitOptions) { + await FhevmEnvironment.init({ sdkUrl: options.sdkUrl }); + + this.initOptions = options; + this.ins = await (window as any).relayerSDK.createInstance({ + ...(window as any).relayerSDK.SepoliaConfig, + network: options.walletClient, + }); + + this.durationDays = options.durationDays; + } + + async userDecrypt(handle: string) { + this._checkInitStatus(); + + const { walletClient, contractAddress } = this.initOptions; + const sigItem = await this._loadOrRequestEIP712Signature(contractAddress, walletClient, this.durationDays); + + const r = await this.ins!.userDecrypt( + [{ handle, contractAddress }], + sigItem.kp.privateKey, + sigItem.kp.publicKey, + sigItem.signature, + sigItem.contractAddresses, + sigItem.userAddress, + sigItem.startTimestamp, + sigItem.durationDays + ); + return r[handle] as T; + } + + async userEncrypt(inputFn: (input: RelayerEncryptedInput) => void) { + this._checkInitStatus(); + + const { walletClient, contractAddress } = this.initOptions; + const encryptedInput = this.ins!.createEncryptedInput(contractAddress, walletClient.account!.address); + // Call the input function to add data to the encrypted input + inputFn(encryptedInput); + return encryptedInput.encrypt(); + } + + async publicDecrypt(handles: string[]) { + this._checkInitStatus(); + return await this.ins!.publicDecrypt(handles); + } + + private _checkInitStatus() { + if (!this.ins) { + throw new Error('FhevmClient is not initialized. Call init() first.'); + } + } + + private async _loadOrRequestEIP712Signature(contractAddress: Address, wc: WalletClient, durationDays: number) { + const userAddress = wc.account!.address; + const cacheKey = `${contractAddress}_${userAddress}`; + let cache = await this._getSignatureCacheItem(cacheKey); + if (!cache) { + const kp = this.ins.generateKeypair(); + const ts = Math.floor(Date.now() / 1000); + const eip712 = this.ins.createEIP712(kp.publicKey, [contractAddress], ts, durationDays); + const sig = await this._requestEIP712Signature(wc, eip712); + cache = { + kp, + contractAddresses: [contractAddress], + userAddress: userAddress, + startTimestamp: ts, + durationDays, + signature: sig, + }; + await this._saveToCache(cacheKey, cache); + } + return cache; + } + + private async _getSignatureCacheItem(cacheKey: string): Promise { + try { + const value = JSON.parse(localStorage.getItem(cacheKey) as string); + const expiredAt = value.startTimestamp + value.durationDays * 3600 * 24; + if (expiredAt < Math.floor(Date.now() / 1000)) { + return null; + } + return value; + } catch {} + return null; + } + + private async _saveToCache(cacheKey: string, item: SignatureCacheItem) { + localStorage.setItem(cacheKey, JSON.stringify(item)); + } + + private async _requestEIP712Signature(wc: WalletClient, eip712: EIP712) { + const signature = await wc.signTypedData({ + account: wc.account!.address, + domain: eip712.domain as TypedDataDomain, + types: eip712.types, + primaryType: eip712.primaryType, + message: eip712.message, + }); + return signature; + } +} diff --git a/packages/fhevm-sdk2/src/core/FhevmEnvironment.ts b/packages/fhevm-sdk2/src/core/FhevmEnvironment.ts new file mode 100644 index 000000000..f6ed0756a --- /dev/null +++ b/packages/fhevm-sdk2/src/core/FhevmEnvironment.ts @@ -0,0 +1,69 @@ +import loadjs from 'loadjs'; +import { SDK_CDN_URL } from './constants'; +import { type FhevmInstance } from '@zama-fhe/relayer-sdk/web'; +export type FhevmEnvironmentInitOptions = { + /** sdk url, default: https://cdn.zama.ai/relayer-sdk-js/0.2.0/relayer-sdk-js.umd.cjs */ + sdkUrl?: string; +}; + +export type FhevmEnvironmentStatus = 'sdk-loading' | 'sdk-loaded' | 'sdk-initializing' | 'sdk-initialized'; + +export class FhevmEnvironment { + private static _isFhevmInitialized = false; + private static _fhevmInstance: FhevmInstance; + private static _onStatusChange: (status: FhevmEnvironmentStatus) => void; + static async init(options?: FhevmEnvironmentInitOptions) { + if (this._isFhevmInitialized) { + return true; + } + const sdkUrl = options?.sdkUrl || SDK_CDN_URL; + try { + if (!loadjs.isDefined('relayer-sdk-js')) { + this._notify('sdk-loading'); + await loadjs(sdkUrl, 'relayer-sdk-js', { async: true, returnPromise: true }); + this._notify('sdk-loaded'); + this._notify('sdk-initializing'); + await (window as any).relayerSDK.initSDK(); + this._notify('sdk-initialized'); + } + this._isFhevmInitialized = true; + return true; + } catch (e) { + throw new Error(`Failed to load FHEVM SDK from ${sdkUrl}. Please check the URL or your network connection.`, { + cause: e, + }); + } + } + + static isFhevmInitialized(): boolean { + return this._isFhevmInitialized; + } + + static onStatusChange(handler: (e: FhevmEnvironmentStatus) => void) { + this._onStatusChange = handler; + } + + private static _notify(status: FhevmEnvironmentStatus) { + this._onStatusChange?.(status); + } + + static async getFhevmInstance(options: { walletClient?: any }) { + if (this._fhevmInstance) { + return this._fhevmInstance; + } + if (!this.isFhevmInitialized()) { + throw new Error('FHEVM SDK is not initialized. Please call FhevmEnvironment.init() first.'); + } + + if (!options.walletClient) { + throw new Error('Wallet client is required to create an FHEVM instance.'); + } + + this._fhevmInstance = await (window as any).relayerSDK.createInstance({ + ...(window as any).relayerSDK.SepoliaConfig, + network: options.walletClient, + }); + console.log('FHEVM instance created:', this._fhevmInstance); + return this._fhevmInstance; + } +} diff --git a/packages/fhevm-sdk2/src/core/constants.ts b/packages/fhevm-sdk2/src/core/constants.ts new file mode 100644 index 000000000..cd1442c0d --- /dev/null +++ b/packages/fhevm-sdk2/src/core/constants.ts @@ -0,0 +1,2 @@ +export const SDK_CDN_URL = + "https://cdn.zama.ai/relayer-sdk-js/0.2.0/relayer-sdk-js.umd.cjs"; diff --git a/packages/fhevm-sdk2/src/index.ts b/packages/fhevm-sdk2/src/index.ts new file mode 100644 index 000000000..a03b61ce7 --- /dev/null +++ b/packages/fhevm-sdk2/src/index.ts @@ -0,0 +1,5 @@ +export { FhevmClient, type FhevmClientInitOptions } from './core/FhevmClient'; +export { FhevmEnvironment, type FhevmEnvironmentInitOptions } from './core/FhevmEnvironment'; + +export { useFHEDecryption } from './react/useFHEDecryption'; +export { useFHEEncryption } from './react/useFHEEncryption'; diff --git a/packages/fhevm-sdk2/src/react/useFHEDecryption.ts b/packages/fhevm-sdk2/src/react/useFHEDecryption.ts new file mode 100644 index 000000000..11e2a0f52 --- /dev/null +++ b/packages/fhevm-sdk2/src/react/useFHEDecryption.ts @@ -0,0 +1,26 @@ +import { useCallback, useMemo } from 'react'; +import { FhevmClient } from '../core/FhevmClient'; + +export type FHEDecryptRequest = { handle: string }; + +export const useFHEDecryption = (params: { + client: FhevmClient | undefined; +}) => { + const { client } = params; + + const canDecrypt = useMemo(() => Boolean(client), [client]); + + const decrypt = useCallback( + async (handle: string): Promise => { + if (!client) return undefined; + + return await client.userDecrypt(handle); + }, + [client], + ); + + return { + canDecrypt, + decrypt, + } as const; +}; diff --git a/packages/fhevm-sdk2/src/react/useFHEEncryption.ts b/packages/fhevm-sdk2/src/react/useFHEEncryption.ts new file mode 100644 index 000000000..6924396b3 --- /dev/null +++ b/packages/fhevm-sdk2/src/react/useFHEEncryption.ts @@ -0,0 +1,90 @@ +import { useCallback, useMemo } from 'react'; +import { FhevmClient } from '../core/FhevmClient'; +import { RelayerEncryptedInput } from '@zama-fhe/relayer-sdk/bundle'; + +export type EncryptResult = { + handles: Uint8Array[]; + inputProof: Uint8Array; +}; + +// Map external encrypted integer type to RelayerEncryptedInput builder method +export const getEncryptionMethod = (internalType: string) => { + switch (internalType) { + case 'externalEbool': + return 'addBool' as const; + case 'externalEuint8': + return 'add8' as const; + case 'externalEuint16': + return 'add16' as const; + case 'externalEuint32': + return 'add32' as const; + case 'externalEuint64': + return 'add64' as const; + case 'externalEuint128': + return 'add128' as const; + case 'externalEuint256': + return 'add256' as const; + case 'externalEaddress': + return 'addAddress' as const; + default: + console.warn(`Unknown internalType: ${internalType}, defaulting to add64`); + return 'add64' as const; + } +}; + +// Convert Uint8Array or hex-like string to 0x-prefixed hex string +export const toHex = (value: Uint8Array | string): `0x${string}` => { + if (typeof value === 'string') { + return (value.startsWith('0x') ? value : `0x${value}`) as `0x${string}`; + } + // value is Uint8Array + return ('0x' + Buffer.from(value).toString('hex')) as `0x${string}`; +}; + +// Build contract params from EncryptResult and ABI for a given function +export const buildParamsFromAbi = (enc: EncryptResult, abi: any[], functionName: string): any[] => { + const fn = abi.find((item: any) => item.type === 'function' && item.name === functionName); + if (!fn) throw new Error(`Function ABI not found for ${functionName}`); + + return fn.inputs.map((input: any, index: number) => { + const raw = index === 0 ? enc.handles[0] : enc.inputProof; + switch (input.type) { + case 'bytes32': + case 'bytes': + return toHex(raw); + case 'uint256': + return BigInt(raw as unknown as string); + case 'address': + case 'string': + return raw as unknown as string; + case 'bool': + return Boolean(raw); + default: + console.warn(`Unknown ABI param type ${input.type}; passing as hex`); + return toHex(raw); + } + }); +}; + +export const useFHEEncryption = (params: { + client: FhevmClient | undefined; +}) => { + const { client } = params; + + const canEncrypt = useMemo(() => Boolean(client), [client]); + + const encryptWith = useCallback( + async (buildFn: (builder: RelayerEncryptedInput) => void): Promise => { + if (!client) return undefined; + + const enc = await client.userEncrypt(buildFn); + return enc; + }, + [client], + ); + + return { + canEncrypt, + encryptWith, + } as const; +}; diff --git a/packages/fhevm-sdk2/test/exports.test.ts b/packages/fhevm-sdk2/test/exports.test.ts new file mode 100644 index 000000000..829f7f28d --- /dev/null +++ b/packages/fhevm-sdk2/test/exports.test.ts @@ -0,0 +1,13 @@ +import { describe, it, expect } from 'vitest'; +import * as main from '../src'; +import * as react from '../src/react'; + +describe('exports', () => { + it('main exports are present', () => { + expect(main).toBeTruthy(); + }); + + it('react exports are present', () => { + expect(react).toBeTruthy(); + }); +}); diff --git a/packages/fhevm-sdk2/tsconfig.json b/packages/fhevm-sdk2/tsconfig.json new file mode 100644 index 000000000..a5cf95a6a --- /dev/null +++ b/packages/fhevm-sdk2/tsconfig.json @@ -0,0 +1,19 @@ +{ + "compilerOptions": { + "target": "ES2022", + "lib": ["ES2022", "DOM"], + "module": "ES2022", + "moduleResolution": "Bundler", + "esModuleInterop": true, + "skipLibCheck": true, + "strict": true, + "declaration": true, + "outDir": "dist", + "rootDir": "src", + "resolveJsonModule": true, + "jsx": "react-jsx", + "types": ["node"] + }, + "include": ["src/**/*"] +} + diff --git a/packages/fhevm-sdk2/vitest.config.ts b/packages/fhevm-sdk2/vitest.config.ts new file mode 100644 index 000000000..9d47e1e33 --- /dev/null +++ b/packages/fhevm-sdk2/vitest.config.ts @@ -0,0 +1,11 @@ +import { defineConfig } from "vitest/config"; + +export default defineConfig({ + test: { + environment: "jsdom", + globals: true, + setupFiles: ["./vitest.setup.ts"], + include: ["test/**/*.test.ts"], + }, +}); + diff --git a/packages/fhevm-sdk2/vitest.setup.ts b/packages/fhevm-sdk2/vitest.setup.ts new file mode 100644 index 000000000..eec59e5e6 --- /dev/null +++ b/packages/fhevm-sdk2/vitest.setup.ts @@ -0,0 +1,2 @@ +import "fake-indexeddb/auto"; + diff --git a/packages/nextjs/app/_components/FHECounterDemo.tsx b/packages/nextjs/app/_components/FHECounterDemo.tsx index 5a8acce16..2053ffbb7 100644 --- a/packages/nextjs/app/_components/FHECounterDemo.tsx +++ b/packages/nextjs/app/_components/FHECounterDemo.tsx @@ -1,10 +1,10 @@ "use client"; -import { useMemo } from "react"; -import { useFhevm } from "@fhevm-sdk"; +import { FhevmEnvironment, useFHEDecryption, useFHEEncryption } from "fhevm-sdk"; +import { Hex, bytesToHex } from "viem"; import { useAccount } from "wagmi"; import { RainbowKitCustomConnectButton } from "~~/components/helper/RainbowKitCustomConnectButton"; -import { useFHECounterWagmi } from "~~/hooks/fhecounter-example/useFHECounterWagmi"; +import { useFHECounterClient } from "~~/hooks/fhecounter-example/useFHECounterClient"; /* * Main FHECounter React component with 3 buttons @@ -13,56 +13,11 @@ import { useFHECounterWagmi } from "~~/hooks/fhecounter-example/useFHECounterWag * - "Decrement" button: allows you to decrement the FHECounter count handle using FHE operations. */ export const FHECounterDemo = () => { - const { isConnected, chain } = useAccount(); + const { isConnected } = useAccount(); - const chainId = chain?.id; - - ////////////////////////////////////////////////////////////////////////////// - // FHEVM instance - ////////////////////////////////////////////////////////////////////////////// - - // Create EIP-1193 provider from wagmi for FHEVM - const provider = useMemo(() => { - if (typeof window === "undefined") return undefined; - - // Get the wallet provider from window.ethereum - return (window as any).ethereum; - }, []); - - const initialMockChains = { 31337: "http://localhost:8545" }; - - const { - instance: fhevmInstance, - status: fhevmStatus, - error: fhevmError, - } = useFhevm({ - provider, - chainId, - initialMockChains, - enabled: true, // use enabled to dynamically create the instance on-demand - }); - - ////////////////////////////////////////////////////////////////////////////// - // useFHECounter is a custom hook containing all the FHECounter logic, including - // - calling the FHECounter contract - // - encrypting FHE inputs - // - decrypting FHE handles - ////////////////////////////////////////////////////////////////////////////// - - const fheCounter = useFHECounterWagmi({ - instance: fhevmInstance, - initialMockChains, - }); - - ////////////////////////////////////////////////////////////////////////////// - // UI Stuff: - // -------- - // A basic page containing - // - A bunch of debug values allowing you to better visualize the React state - // - 1x "Decrypt" button (to decrypt the latest FHECounter count handle) - // - 1x "Increment" button (to increment the FHECounter) - // - 1x "Decrement" button (to decrement the FHECounter) - ////////////////////////////////////////////////////////////////////////////// + const { fhevmClient, contractClient } = useFHECounterClient(); + const { canEncrypt, encryptWith } = useFHEEncryption({ client: fhevmClient }); + const { canDecrypt, decrypt } = useFHEDecryption({ client: fhevmClient }); const buttonClass = "inline-flex items-center justify-center px-6 py-3 font-semibold shadow-lg " + @@ -70,23 +25,11 @@ export const FHECounterDemo = () => { "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:ring-offset-gray-900 " + "disabled:opacity-50 disabled:pointer-events-none disabled:cursor-not-allowed"; - // Primary (accent) button β€” #FFD208 with dark text and warm hover #A38025 - const primaryButtonClass = - buttonClass + - " bg-[#FFD208] text-[#2D2D2D] hover:bg-[#A38025] focus-visible:ring-[#2D2D2D] cursor-pointer"; - // Secondary (neutral dark) button β€” #2D2D2D with light text and accent focus const secondaryButtonClass = - buttonClass + - " bg-black text-[#F4F4F4] hover:bg-[#1F1F1F] focus-visible:ring-[#FFD208] cursor-pointer"; - - // Success/confirmed state β€” deeper gold #A38025 with dark text - const successButtonClass = - buttonClass + - " bg-[#A38025] text-[#2D2D2D] hover:bg-[#8F6E1E] focus-visible:ring-[#2D2D2D]"; + buttonClass + " bg-black text-[#F4F4F4] hover:bg-[#1F1F1F] focus-visible:ring-[#FFD208] cursor-pointer"; const titleClass = "font-bold text-gray-900 text-xl mb-4 border-b-1 border-gray-700 pb-2"; - const sectionClass = "bg-[#f4f4f4] shadow-lg p-6 mb-6 text-gray-900"; if (!isConnected) { return ( @@ -108,6 +51,9 @@ export const FHECounterDemo = () => { ); } + if (!fhevmClient || !contractClient) { + return
Loading...
; + } return (
@@ -115,136 +61,56 @@ export const FHECounterDemo = () => {

FHE Counter Demo

Interact with the Fully Homomorphic Encryption Counter contract

-
- - {/* Count Handle Display */} -
-

πŸ”’ Count Handle

-
- {printProperty("Encrypted Handle", fheCounter.handle || "No handle available")} - {printProperty("Decrypted Value", fheCounter.isDecrypted ? fheCounter.clear : "Not decrypted yet")} -
+

+ πŸ”§ FHEVM Instance Status: {FhevmEnvironment.isFhevmInitialized() ? "βœ… Connected" : "❌ Disconnected"} +

{/* Action Buttons */}
-
- - {/* Messages */} - {fheCounter.message && ( -
-

πŸ’¬ Messages

-
-

{fheCounter.message}

-
-
- )} - - {/* Status Cards */} -
-
-

πŸ”§ FHEVM Instance

-
- {printProperty("Instance Status", fhevmInstance ? "βœ… Connected" : "❌ Disconnected")} - {printProperty("Status", fhevmStatus)} - {printProperty("Error", fhevmError ?? "No errors")} -
-
+
); }; - -function printProperty(name: string, value: unknown) { - let displayValue: string; - - if (typeof value === "boolean") { - return printBooleanProperty(name, value); - } else if (typeof value === "string" || typeof value === "number") { - displayValue = String(value); - } else if (typeof value === "bigint") { - displayValue = String(value); - } else if (value === null) { - displayValue = "null"; - } else if (value === undefined) { - displayValue = "undefined"; - } else if (value instanceof Error) { - displayValue = value.message; - } else { - displayValue = JSON.stringify(value); - } - return ( -
- {name} - - {displayValue} - -
- ); -} - -function printBooleanProperty(name: string, value: boolean) { - return ( -
- {name} - - {value ? "βœ“ true" : "βœ— false"} - -
- ); -} diff --git a/packages/nextjs/components/DappWrapperWithProviders.tsx b/packages/nextjs/components/DappWrapperWithProviders.tsx index c11a07688..b874dacc2 100644 --- a/packages/nextjs/components/DappWrapperWithProviders.tsx +++ b/packages/nextjs/components/DappWrapperWithProviders.tsx @@ -1,7 +1,6 @@ "use client"; import { useEffect, useState } from "react"; -import { InMemoryStorageProvider } from "@fhevm-sdk"; import { RainbowKitProvider, darkTheme, lightTheme } from "@rainbow-me/rainbowkit"; import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; import { AppProgressBar as ProgressBar } from "next-nprogress-bar"; @@ -39,9 +38,7 @@ export const DappWrapperWithProviders = ({ children }: { children: React.ReactNo
-
- {children} -
+
{children}
diff --git a/packages/nextjs/contracts/FHECounterContractClient.ts b/packages/nextjs/contracts/FHECounterContractClient.ts new file mode 100644 index 000000000..6706328ee --- /dev/null +++ b/packages/nextjs/contracts/FHECounterContractClient.ts @@ -0,0 +1,66 @@ +import deployedContracts from "./deployedContracts"; +import { ContractClientBase } from "@bizjs/chainkit-evm"; +import { type Address, type Chain, type Hash, type Hex, type WalletClient } from "viem"; + +const abi = deployedContracts[31337].FHECounter.abi; + +export type FHECounterContractClientOptions = { + contractAddress: string; + chain: Chain; + endppoint?: string; + walletClient?: WalletClient | undefined; +}; + +export class FHECounterContractClient extends ContractClientBase { + constructor(options: FHECounterContractClientOptions) { + super({ + abi, + chain: options.chain, + contractAddress: options.contractAddress as Address, + endpoint: options.endppoint, + walletClient: options.walletClient, + }); + } + + setWalletClient(walletClient: unknown) { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + (this as any).walletClient = walletClient; + } + + async decrement(handle: Hex, inputProof: Hex) { + const hash = await this.simulateAndWriteContract({ + functionName: "decrement", + args: [handle, inputProof], + }); + return hash; + } + + async increment(handle: Hex, inputProof: Hex) { + const hash = await this.simulateAndWriteContract({ + functionName: "increment", + args: [handle, inputProof], + }); + return hash; + } + + async getCount() { + const value = await this.readContract({ + functionName: "getCount", + }); + return value as Hex; + } + + async protocolId() { + const value = await this.readContract({ + functionName: "protocolId", + }); + return Number(value); + } + + async waitForTransaction(hash: Hash) { + return await this.waitForTransactionReceipt({ + hash, + confirmations: 1, + }); + } +} diff --git a/packages/nextjs/hooks/fhecounter-example/useFHECounterClient.tsx b/packages/nextjs/hooks/fhecounter-example/useFHECounterClient.tsx new file mode 100644 index 000000000..adc92c681 --- /dev/null +++ b/packages/nextjs/hooks/fhecounter-example/useFHECounterClient.tsx @@ -0,0 +1,59 @@ +"use client"; + +import { useEffect, useMemo, useState } from "react"; +import { FhevmClient } from "fhevm-sdk"; +import { useAccount, useWalletClient } from "wagmi"; +import { FHECounterContractClient } from "~~/contracts/FHECounterContractClient"; + +/** + * useFHECounterWagmi - Minimal FHE Counter hook for Wagmi devs + * + * What it does: + * - Reads the current encrypted counter + * - Decrypts the handle on-demand with useFHEDecrypt + * - Encrypts inputs and writes increment/decrement + * + * Pass your FHEVM instance and a simple key-value storage for the decryption signature. + * That's it. Everything else is handled for you. + */ +export const useFHECounterClient = () => { + const account = useAccount(); + const { data: walletClient } = useWalletClient(); + const [fhevmClient, setFhevmClient] = useState(undefined); + + useEffect(() => { + // ζ²‘ζœ‰εΏ…θ¦ζ‘δ»Άζ—ΆοΌŒη›΄ζŽ₯ι€€ε‡Ί + if (!account?.chainId || !account?.address || !walletClient) { + setFhevmClient(undefined); + return; + } + + async function initClient() { + const client = new FhevmClient(); + await client.init({ + contractAddress: "0xead137D42d2E6A6a30166EaEf97deBA1C3D1954e", + durationDays: 7, + walletClient: walletClient as any, + }); + + setFhevmClient(client); + } + initClient(); + }, [account?.chainId, account?.address, walletClient]); + + const contractClient = useMemo(() => { + if (!account.chain) return undefined; + return new FHECounterContractClient({ + contractAddress: "0xead137D42d2E6A6a30166EaEf97deBA1C3D1954e", + chain: account.chain!, + }); + }, [account.chain]); + + useEffect(() => { + if (contractClient && walletClient) { + contractClient.setWalletClient(walletClient); + } + }, [walletClient, contractClient]); + + return { fhevmClient, contractClient }; +}; diff --git a/packages/nextjs/hooks/fhecounter-example/useFHECounterWagmi.tsx b/packages/nextjs/hooks/fhecounter-example/useFHECounterWagmi.tsx deleted file mode 100644 index 2ea555f20..000000000 --- a/packages/nextjs/hooks/fhecounter-example/useFHECounterWagmi.tsx +++ /dev/null @@ -1,202 +0,0 @@ -"use client"; - -import { useCallback, useEffect, useMemo, useRef, useState } from "react"; -import { useDeployedContractInfo } from "../helper"; -import { useWagmiEthers } from "../wagmi/useWagmiEthers"; -import { FhevmInstance } from "@fhevm-sdk"; -import { - buildParamsFromAbi, - getEncryptionMethod, - useFHEDecrypt, - useFHEEncryption, - useInMemoryStorage, -} from "@fhevm-sdk"; -import { ethers } from "ethers"; -import type { Contract } from "~~/utils/helper/contract"; -import type { AllowedChainIds } from "~~/utils/helper/networks"; -import { useReadContract } from "wagmi"; - -/** - * useFHECounterWagmi - Minimal FHE Counter hook for Wagmi devs - * - * What it does: - * - Reads the current encrypted counter - * - Decrypts the handle on-demand with useFHEDecrypt - * - Encrypts inputs and writes increment/decrement - * - * Pass your FHEVM instance and a simple key-value storage for the decryption signature. - * That's it. Everything else is handled for you. - */ -export const useFHECounterWagmi = (parameters: { - instance: FhevmInstance | undefined; - initialMockChains?: Readonly>; -}) => { - const { instance, initialMockChains } = parameters; - const { storage: fhevmDecryptionSignatureStorage } = useInMemoryStorage(); - - // Wagmi + ethers interop - const { chainId, accounts, isConnected, ethersReadonlyProvider, ethersSigner } = useWagmiEthers(initialMockChains); - - // Resolve deployed contract info once we know the chain - const allowedChainId = typeof chainId === "number" ? (chainId as AllowedChainIds) : undefined; - const { data: fheCounter } = useDeployedContractInfo({ contractName: "FHECounter", chainId: allowedChainId }); - - // Simple status string for UX messages - const [message, setMessage] = useState(""); - - type FHECounterInfo = Contract<"FHECounter"> & { chainId?: number }; - - const isRefreshing = false as unknown as boolean; // derived from wagmi below - const [isProcessing, setIsProcessing] = useState(false); - - // ------------- - // Helpers - // ------------- - const hasContract = Boolean(fheCounter?.address && fheCounter?.abi); - const hasProvider = Boolean(ethersReadonlyProvider); - const hasSigner = Boolean(ethersSigner); - - const getContract = (mode: "read" | "write") => { - if (!hasContract) return undefined; - const providerOrSigner = mode === "read" ? ethersReadonlyProvider : ethersSigner; - if (!providerOrSigner) return undefined; - return new ethers.Contract( - fheCounter!.address, - (fheCounter as FHECounterInfo).abi, - providerOrSigner, - ); - }; - - // Read count handle via wagmi - const readResult = useReadContract({ - address: (hasContract ? (fheCounter!.address as unknown as `0x${string}`) : undefined) as - | `0x${string}` - | undefined, - abi: (hasContract ? ((fheCounter as FHECounterInfo).abi as any) : undefined) as any, - functionName: "getCount" as const, - query: { - enabled: Boolean(hasContract && hasProvider), - refetchOnWindowFocus: false, - }, - }); - - const countHandle = useMemo(() => (readResult.data as string | undefined) ?? undefined, [readResult.data]); - const canGetCount = Boolean(hasContract && hasProvider && !readResult.isFetching); - const refreshCountHandle = useCallback(async () => { - const res = await readResult.refetch(); - if (res.error) setMessage("FHECounter.getCount() failed: " + (res.error as Error).message); - }, [readResult]); - // derive isRefreshing from wagmi - // eslint-disable-next-line @typescript-eslint/no-unused-vars - const _derivedIsRefreshing = readResult.isFetching; - - // Wagmi handles initial fetch via `enabled` - - // Decrypt (reuse existing decrypt hook for simplicity) - const requests = useMemo(() => { - if (!hasContract || !countHandle || countHandle === ethers.ZeroHash) return undefined; - return [{ handle: countHandle, contractAddress: fheCounter!.address } as const]; - }, [hasContract, fheCounter?.address, countHandle]); - - const { - canDecrypt, - decrypt, - isDecrypting, - message: decMsg, - results, - } = useFHEDecrypt({ - instance, - ethersSigner: ethersSigner as any, - fhevmDecryptionSignatureStorage, - chainId, - requests, - }); - - useEffect(() => { - if (decMsg) setMessage(decMsg); - }, [decMsg]); - - const clearCount = useMemo(() => { - if (!countHandle) return undefined; - if (countHandle === ethers.ZeroHash) return { handle: countHandle, clear: BigInt(0) } as const; - const clear = results[countHandle]; - if (typeof clear === "undefined") return undefined; - return { handle: countHandle, clear } as const; - }, [countHandle, results]); - - const isDecrypted = Boolean(countHandle && clearCount?.handle === countHandle); - const decryptCountHandle = decrypt; - - // Mutations (increment/decrement) - const { encryptWith } = useFHEEncryption({ instance, ethersSigner: ethersSigner as any, contractAddress: fheCounter?.address }); - const canUpdateCounter = useMemo( - () => Boolean(hasContract && instance && hasSigner && !isProcessing), - [hasContract, instance, hasSigner, isProcessing], - ); - - const getEncryptionMethodFor = (functionName: "increment" | "decrement") => { - const functionAbi = fheCounter?.abi.find(item => item.type === "function" && item.name === functionName); - if (!functionAbi) return { method: undefined as string | undefined, error: `Function ABI not found for ${functionName}` } as const; - if (!functionAbi.inputs || functionAbi.inputs.length === 0) - return { method: undefined as string | undefined, error: `No inputs found for ${functionName}` } as const; - const firstInput = functionAbi.inputs[0]!; - return { method: getEncryptionMethod(firstInput.internalType), error: undefined } as const; - }; - - const updateCounter = useCallback( - async (value: number) => { - if (isProcessing || !canUpdateCounter || value === 0) return; - const op = value > 0 ? "increment" : "decrement"; - const valueAbs = Math.abs(value); - setIsProcessing(true); - setMessage(`Starting ${op}(${valueAbs})...`); - try { - const { method, error } = getEncryptionMethodFor(op); - if (!method) return setMessage(error ?? "Encryption method not found"); - - setMessage(`Encrypting with ${method}...`); - const enc = await encryptWith(builder => { - (builder as any)[method](valueAbs); - }); - if (!enc) return setMessage("Encryption failed"); - - const writeContract = getContract("write"); - if (!writeContract) return setMessage("Contract info or signer not available"); - - const params = buildParamsFromAbi(enc, [...fheCounter!.abi] as any[], op); - const tx = await (op === "increment" ? writeContract.increment(...params) : writeContract.decrement(...params)); - setMessage("Waiting for transaction..."); - await tx.wait(); - setMessage(`${op}(${valueAbs}) completed!`); - refreshCountHandle(); - } catch (e) { - setMessage(`${op} failed: ${e instanceof Error ? e.message : String(e)}`); - } finally { - setIsProcessing(false); - } - }, - [isProcessing, canUpdateCounter, encryptWith, getContract, refreshCountHandle, fheCounter?.abi], - ); - - return { - contractAddress: fheCounter?.address, - canDecrypt, - canGetCount, - canUpdateCounter, - updateCounter, - decryptCountHandle, - refreshCountHandle, - isDecrypted, - message, - clear: clearCount?.clear, - handle: countHandle, - isDecrypting, - isRefreshing, - isProcessing, - // Wagmi-specific values - chainId, - accounts, - isConnected, - ethersSigner, - }; -}; diff --git a/packages/nextjs/hooks/wagmi/useWagmiEthers.ts b/packages/nextjs/hooks/wagmi/useWagmiEthers.ts deleted file mode 100644 index 788b63947..000000000 --- a/packages/nextjs/hooks/wagmi/useWagmiEthers.ts +++ /dev/null @@ -1,70 +0,0 @@ -"use client"; - -import { useEffect, useMemo, useRef } from "react"; -import { ethers } from "ethers"; -import { useAccount, useWalletClient } from "wagmi"; - -export const useWagmiEthers = (initialMockChains?: Readonly>) => { - const { address, isConnected, chain } = useAccount(); - const { data: walletClient } = useWalletClient(); - - const chainId = chain?.id ?? walletClient?.chain?.id; - const accounts = address ? [address] : undefined; - - const ethersProvider = useMemo(() => { - if (!walletClient) return undefined; - - const eip1193Provider = { - request: async (args: any) => { - return await walletClient.request(args); - }, - on: () => { - console.log("Provider events not fully implemented for wagmi"); - }, - removeListener: () => { - console.log("Provider removeListener not fully implemented for wagmi"); - }, - } as ethers.Eip1193Provider; - - return new ethers.BrowserProvider(eip1193Provider); - }, [walletClient]); - - const ethersReadonlyProvider = useMemo(() => { - if (!ethersProvider) return undefined; - - const rpcUrl = initialMockChains?.[chainId || 0]; - if (rpcUrl) { - return new ethers.JsonRpcProvider(rpcUrl); - } - - return ethersProvider; - }, [ethersProvider, initialMockChains, chainId]); - - const ethersSigner = useMemo(() => { - if (!ethersProvider || !address) return undefined; - return new ethers.JsonRpcSigner(ethersProvider, address); - }, [ethersProvider, address]); - - // Stable refs consumers can reuse - const ropRef = useRef(ethersReadonlyProvider); - const chainIdRef = useRef(chainId); - - useEffect(() => { - ropRef.current = ethersReadonlyProvider; - }, [ethersReadonlyProvider]); - - useEffect(() => { - chainIdRef.current = chainId; - }, [chainId]); - - return { - chainId, - accounts, - isConnected, - ethersProvider, - ethersReadonlyProvider, - ethersSigner, - ropRef, - chainIdRef, - } as const; -}; diff --git a/packages/nextjs/package.json b/packages/nextjs/package.json index ba0d3d9e0..a15ffae99 100644 --- a/packages/nextjs/package.json +++ b/packages/nextjs/package.json @@ -15,9 +15,10 @@ "vercel:yolo": "vercel --build-env YARN_ENABLE_IMMUTABLE_INSTALLS=false --build-env ENABLE_EXPERIMENTAL_COREPACK=1 --build-env NEXT_PUBLIC_IGNORE_BUILD_ERROR=true --build-env VERCEL_TELEMETRY_DISABLED=1" }, "dependencies": { + "@bizjs/chainkit-evm": "^0.2.0", + "@fhevm-sdk": "workspace:*", "@heroicons/react": "~2.1.5", "@rainbow-me/rainbowkit": "2.2.8", - "@fhevm-sdk": "workspace:*", "@tanstack/react-query": "~5.59.15", "@uniswap/sdk-core": "~5.8.2", "@uniswap/v2-sdk": "~4.6.1", @@ -25,6 +26,7 @@ "blo": "~1.2.0", "burner-connector": "0.0.18", "daisyui": "5.0.9", + "fhevm-sdk": "workspace:*", "idb": "^8.0.3", "kubo-rpc-client": "~5.0.2", "next": "~15.2.3", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 45434acc8..3accc54df 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -65,6 +65,49 @@ importers: specifier: ~2.1.8 version: 2.1.9(@edge-runtime/vm@3.2.0)(@types/node@18.19.127)(jsdom@27.0.0(bufferutil@4.0.9)(postcss@8.4.49)(utf-8-validate@5.0.10))(lightningcss@1.29.2) + packages/fhevm-sdk2: + dependencies: + idb: + specifier: ^8.0.3 + version: 8.0.3 + loadjs: + specifier: ^4.3.0 + version: 4.3.0 + devDependencies: + '@types/loadjs': + specifier: ^4.0.4 + version: 4.0.4 + '@types/node': + specifier: ~18.19.50 + version: 18.19.127 + '@types/react': + specifier: ~19.0.7 + version: 19.0.14 + '@vitest/coverage-v8': + specifier: 2.1.9 + version: 2.1.9(vitest@2.1.9(@edge-runtime/vm@3.2.0)(@types/node@18.19.127)(jsdom@27.0.0(bufferutil@4.0.9)(postcss@8.4.49)(utf-8-validate@5.0.10))(lightningcss@1.29.2)) + '@zama-fhe/relayer-sdk': + specifier: 0.2.0 + version: 0.2.0(bufferutil@4.0.9)(utf-8-validate@5.0.10) + fake-indexeddb: + specifier: ~6.0.0 + version: 6.0.1 + jsdom: + specifier: ^27.0.0 + version: 27.0.0(bufferutil@4.0.9)(postcss@8.4.49)(utf-8-validate@5.0.10) + react: + specifier: ~19.0.0 + version: 19.0.0 + typescript: + specifier: ~5.8.2 + version: 5.8.3 + viem: + specifier: 2.34.0 + version: 2.34.0(bufferutil@4.0.9)(typescript@5.8.3)(utf-8-validate@5.0.10)(zod@3.25.76) + vitest: + specifier: ~2.1.8 + version: 2.1.9(@edge-runtime/vm@3.2.0)(@types/node@18.19.127)(jsdom@27.0.0(bufferutil@4.0.9)(postcss@8.4.49)(utf-8-validate@5.0.10))(lightningcss@1.29.2) + packages/hardhat: dependencies: '@fhevm/solidity': @@ -145,7 +188,7 @@ importers: version: 2.3.0(bufferutil@4.0.9)(hardhat@2.26.3(bufferutil@4.0.9)(ts-node@10.9.2(@types/node@20.19.17)(typescript@5.9.2))(typescript@5.9.2)(utf-8-validate@5.0.10))(typescript@5.9.2)(utf-8-validate@5.0.10) mocha: specifier: ^11.7.1 - version: 11.7.2 + version: 11.7.4 prettier: specifier: ^3.6.2 version: 3.6.2 @@ -176,6 +219,9 @@ importers: packages/nextjs: dependencies: + '@bizjs/chainkit-evm': + specifier: ^0.2.0 + version: 0.2.0(viem@2.34.0(bufferutil@4.0.9)(typescript@5.8.3)(utf-8-validate@5.0.10)(zod@3.25.76)) '@fhevm-sdk': specifier: workspace:* version: link:../fhevm-sdk @@ -206,6 +252,9 @@ importers: daisyui: specifier: 5.0.9 version: 5.0.9 + fhevm-sdk: + specifier: workspace:* + version: link:../fhevm-sdk2 idb: specifier: ^8.0.3 version: 8.0.3 @@ -393,6 +442,11 @@ packages: '@bcoe/v8-coverage@0.2.3': resolution: {integrity: sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==} + '@bizjs/chainkit-evm@0.2.0': + resolution: {integrity: sha512-hC9PJR3VzggBNN93JeUUtyMbQIDWbzvm0H+dOe1T8sFx/Hzxx24HsldyZv/2dby7uJ1Lyr5OSBatQl0XlKDmDg==} + peerDependencies: + viem: ^2.29.0 + '@bytecodealliance/preview2-shim@0.17.2': resolution: {integrity: sha512-mNm/lblgES8UkVle8rGImXOz4TtL3eU3inHay/7TVchkKrb/lgcVvTK0+VAw8p5zQ0rgQsXm1j5dOlAAd+MeoA==} @@ -1913,6 +1967,9 @@ packages: '@types/json5@0.0.29': resolution: {integrity: sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==} + '@types/loadjs@4.0.4': + resolution: {integrity: sha512-IrO82vQKb37H9R+CJYoKTJBlMcIcSCA64ZPIBrLScH2vHMgXLnXtLc76PyKKzdbkj/lRLIhoH+fFAZ0UvIP5Dw==} + '@types/lodash@4.17.20': resolution: {integrity: sha512-H3MHACvFUEiujabxhaI/ImO6gUrd8oOurg7LQtS7mbwIXA/cUqWrvBsaeJ23aZEPk1TAYkurjfMbSELfoCXlGA==} @@ -1959,8 +2016,8 @@ packages: '@types/resolve@0.0.8': resolution: {integrity: sha512-auApPaJf3NPfe18hSoJkp8EbZzer2ISk7o8mCC3M9he/a04+gbMF97NkpD2S8riMGvm4BMRI59/SZQSaLTKpsQ==} - '@types/secp256k1@4.0.6': - resolution: {integrity: sha512-hHxJU6PAEUn0TP4S/ZOzuTUvJWuZ6eIKeNKb5RBpODvSl6hp1Wrw4s7ATY50rklRCScUDpHzVA/DQdSjJ3UoYQ==} + '@types/secp256k1@4.0.7': + resolution: {integrity: sha512-Rcvjl6vARGAKRO6jHeKMatGrvOMGrR/AR11N1x2LqintPCyDZ7NBhrh238Z2VZc7aM7KIwnFpFQ7fnfK4H/9Qw==} '@types/trusted-types@2.0.7': resolution: {integrity: sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==} @@ -2653,8 +2710,8 @@ packages: axios@0.21.4: resolution: {integrity: sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg==} - axios@1.12.2: - resolution: {integrity: sha512-vMJzPewAlRyOgxV2dU0Cuz2O8zzzx9VYtbJOaBgXFeLc4IV/Eg50n4LowmehOOR61S8ZMpc2K5Sa7g6A4jfkUw==} + axios@1.13.0: + resolution: {integrity: sha512-zt40Pz4zcRXra9CVV31KeyofwiNvAbJ5B6YPz9pMJ+yOSLikvPT4Yi5LjfgjRa9CawVYBaD1JQzIVcIvBejKeA==} axobject-query@4.1.0: resolution: {integrity: sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==} @@ -2914,8 +2971,8 @@ packages: ci-info@2.0.0: resolution: {integrity: sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==} - cipher-base@1.0.6: - resolution: {integrity: sha512-3Ek9H3X6pj5TgenXYtNWdaBon1tgYCaebd+XPg0keyjEbEfkD4KkmAxkQ/i1vYvxdcT5nscLBfq9VJRmCBcFSw==} + cipher-base@1.0.7: + resolution: {integrity: sha512-Mz9QMT5fJe7bKI7MH31UilT5cEK5EHHRCccw/YRFsRY47AuNgaV6HY3rscp0/I4Q+tTW/5zoqpSeRRI54TkDWA==} engines: {node: '>= 0.10'} cjs-module-lexer@1.2.3: @@ -4913,6 +4970,9 @@ packages: lit@3.3.0: resolution: {integrity: sha512-DGVsqsOIHBww2DqnuZzW7QsuCdahp50ojuDaBPC7jUDRpYoH0z7kHBBYZewRzer75FwtrkmkKk7iOAwSaWdBmw==} + loadjs@4.3.0: + resolution: {integrity: sha512-vNX4ZZLJBeDEOBvdr2v/F+0aN5oMuPu7JTqrMwp+DtgK+AryOlpy6Xtm2/HpNr+azEa828oQjOtWsB6iDtSfSQ==} + locate-path@5.0.0: resolution: {integrity: sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==} engines: {node: '>=8'} @@ -5145,8 +5205,8 @@ packages: engines: {node: '>= 14.0.0'} hasBin: true - mocha@11.7.2: - resolution: {integrity: sha512-lkqVJPmqqG/w5jmmFtiRvtA2jkDyNVUcefFJKb2uyX4dekk8Okgqop3cgbFiaIvj8uCRJVTP5x9dfxGyXm2jvQ==} + mocha@11.7.4: + resolution: {integrity: sha512-1jYAaY8x0kAZ0XszLWu14pzsf4KV740Gld4HXkhNTXwcHx4AUEDkPzgEHg9CM5dVcW+zv036tjpsEbLraPJj4w==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} hasBin: true @@ -6548,6 +6608,10 @@ packages: resolution: {integrity: sha512-tB82LpAIWjhLYbqjx3X4zEeHN6M8CiuOEy2JY8SEQVdYRe3CCHOFaqrBW1doLDrfpWhplcW7BL+bO3/6S3pcDQ==} engines: {node: '>= 0.4'} + to-buffer@1.2.2: + resolution: {integrity: sha512-db0E3UJjcFhpDhAF4tLo03oli3pwl3dbnzXOUIlRKrp+ldk/VUxzpWYZENsw2SZiuBjHAk7DfB0VU7NKdpb6sw==} + engines: {node: '>= 0.4'} + to-fast-properties@2.0.0: resolution: {integrity: sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==} engines: {node: '>=4'} @@ -7492,6 +7556,10 @@ snapshots: '@bcoe/v8-coverage@0.2.3': {} + '@bizjs/chainkit-evm@0.2.0(viem@2.34.0(bufferutil@4.0.9)(typescript@5.8.3)(utf-8-validate@5.0.10)(zod@3.25.76))': + dependencies: + viem: 2.34.0(bufferutil@4.0.9)(typescript@5.8.3)(utf-8-validate@5.0.10)(zod@3.25.76) + '@bytecodealliance/preview2-shim@0.17.2': {} '@chainsafe/is-ip@2.1.0': {} @@ -9570,11 +9638,13 @@ snapshots: '@types/json5@0.0.29': {} + '@types/loadjs@4.0.4': {} + '@types/lodash@4.17.20': {} '@types/minimatch@6.0.0': dependencies: - minimatch: 10.0.3 + minimatch: 9.0.5 '@types/mkdirp@0.5.2': dependencies: @@ -9618,7 +9688,7 @@ snapshots: dependencies: '@types/node': 22.7.5 - '@types/secp256k1@4.0.6': + '@types/secp256k1@4.0.7': dependencies: '@types/node': 22.7.5 @@ -11033,7 +11103,7 @@ snapshots: transitivePeerDependencies: - debug - axios@1.12.2: + axios@1.13.0: dependencies: follow-redirects: 1.15.11(debug@4.4.3) form-data: 4.0.4 @@ -11148,7 +11218,7 @@ snapshots: browserify-aes@1.2.0: dependencies: buffer-xor: 1.0.3 - cipher-base: 1.0.6 + cipher-base: 1.0.7 create-hash: 1.2.0 evp_bytestokey: 1.0.3 inherits: 2.0.4 @@ -11368,10 +11438,11 @@ snapshots: ci-info@2.0.0: {} - cipher-base@1.0.6: + cipher-base@1.0.7: dependencies: inherits: 2.0.4 safe-buffer: 5.2.1 + to-buffer: 1.2.2 cjs-module-lexer@1.2.3: {} @@ -11503,7 +11574,7 @@ snapshots: create-hash@1.2.0: dependencies: - cipher-base: 1.0.6 + cipher-base: 1.0.7 inherits: 2.0.4 md5.js: 1.3.5 ripemd160: 2.0.3 @@ -11511,7 +11582,7 @@ snapshots: create-hmac@1.1.7: dependencies: - cipher-base: 1.0.6 + cipher-base: 1.0.7 create-hash: 1.2.0 inherits: 2.0.4 ripemd160: 2.0.3 @@ -12382,7 +12453,7 @@ snapshots: ethereum-cryptography@0.1.3: dependencies: '@types/pbkdf2': 3.1.2 - '@types/secp256k1': 4.0.6 + '@types/secp256k1': 4.0.7 blakejs: 1.2.1 browserify-aes: 1.2.0 bs58check: 2.1.2 @@ -12984,7 +13055,7 @@ snapshots: '@ethersproject/bytes': 5.8.0 '@ethersproject/units': 5.8.0 '@solidity-parser/parser': 0.20.2 - axios: 1.12.2 + axios: 1.13.0 brotli-wasm: 2.0.1 chalk: 4.1.2 cli-table3: 0.6.5 @@ -13793,6 +13864,8 @@ snapshots: lit-element: 4.2.1 lit-html: 3.3.1 + loadjs@4.3.0: {} + locate-path@5.0.0: dependencies: p-locate: 4.1.0 @@ -14014,7 +14087,7 @@ snapshots: yargs-parser: 20.2.9 yargs-unparser: 2.0.0 - mocha@11.7.2: + mocha@11.7.4: dependencies: browser-stdout: 1.3.1 chokidar: 4.0.3 @@ -14024,6 +14097,7 @@ snapshots: find-up: 5.0.0 glob: 10.4.5 he: 1.2.0 + is-path-inside: 3.0.3 js-yaml: 4.1.0 log-symbols: 4.1.0 minimatch: 9.0.5 @@ -15480,6 +15554,12 @@ snapshots: safe-buffer: 5.2.1 typed-array-buffer: 1.0.3 + to-buffer@1.2.2: + dependencies: + isarray: 2.0.5 + safe-buffer: 5.2.1 + typed-array-buffer: 1.0.3 + to-fast-properties@2.0.0: {} to-regex-range@5.0.1: