From b0b84511aa55bce9f2f16421e8d2900b65068e78 Mon Sep 17 00:00:00 2001
From: ctrlc03 <93448202+ctrlc03@users.noreply.github.com>
Date: Thu, 5 Mar 2026 15:25:43 +0000
Subject: [PATCH 1/3] refactor: enclave-sdk modularity
---
docs/pages/sdk.mdx | 95 +++-
packages/enclave-react/src/useEnclaveSDK.ts | 13 +-
packages/enclave-sdk/README.md | 231 +++++++---
packages/enclave-sdk/package.json | 15 +
.../src/{ => contracts}/contract-client.ts | 242 +++++-----
packages/enclave-sdk/src/contracts/index.ts | 9 +
packages/enclave-sdk/src/contracts/types.ts | 25 ++
packages/enclave-sdk/src/enclave-sdk.ts | 415 ++++--------------
.../enclave-sdk/src/encryption/encrypt.ts | 105 +++++
packages/enclave-sdk/src/encryption/index.ts | 21 +
packages/enclave-sdk/src/encryption/types.ts | 32 ++
.../src/{ => events}/event-listener.ts | 106 +++--
packages/enclave-sdk/src/events/index.ts | 30 ++
packages/enclave-sdk/src/events/types.ts | 156 +++++++
packages/enclave-sdk/src/index.ts | 32 +-
packages/enclave-sdk/src/types.ts | 325 ++------------
packages/enclave-sdk/tests/sdk.test.ts | 20 +-
packages/enclave-sdk/tsup.config.js | 4 +
templates/default/server/index.ts | 14 +-
templates/default/tests/integration.spec.ts | 2 +-
20 files changed, 979 insertions(+), 913 deletions(-)
rename packages/enclave-sdk/src/{ => contracts}/contract-client.ts (54%)
create mode 100644 packages/enclave-sdk/src/contracts/index.ts
create mode 100644 packages/enclave-sdk/src/contracts/types.ts
create mode 100644 packages/enclave-sdk/src/encryption/encrypt.ts
create mode 100644 packages/enclave-sdk/src/encryption/index.ts
create mode 100644 packages/enclave-sdk/src/encryption/types.ts
rename packages/enclave-sdk/src/{ => events}/event-listener.ts (75%)
create mode 100644 packages/enclave-sdk/src/events/index.ts
create mode 100644 packages/enclave-sdk/src/events/types.ts
diff --git a/docs/pages/sdk.mdx b/docs/pages/sdk.mdx
index 92522ff639..7cebdb95e9 100644
--- a/docs/pages/sdk.mdx
+++ b/docs/pages/sdk.mdx
@@ -37,9 +37,16 @@ export default defineConfig({
```ts
import { EnclaveSDK, EnclaveEventType, RegistryEventType } from '@enclave-e3/sdk'
import { createPublicClient, createWalletClient, http, custom } from 'viem'
+import { sepolia } from 'viem/chains'
-const publicClient = createPublicClient({ transport: http(import.meta.env.VITE_RPC_URL) })
-const walletClient = createWalletClient({ transport: custom(window.ethereum) })
+const publicClient = createPublicClient({
+ chain: sepolia,
+ transport: http(import.meta.env.VITE_RPC_URL),
+})
+const walletClient = createWalletClient({
+ chain: sepolia,
+ transport: custom(window.ethereum),
+})
const sdk = new EnclaveSDK({
publicClient,
@@ -47,11 +54,30 @@ const sdk = new EnclaveSDK({
contracts: {
enclave: import.meta.env.VITE_ENCLAVE_ADDRESS,
ciphernodeRegistry: import.meta.env.VITE_REGISTRY_ADDRESS,
+ feeToken: import.meta.env.VITE_FEE_TOKEN_ADDRESS,
},
- chainId: 11155111,
+ chain: sepolia,
+ thresholdBfvParamsPresetName: 'INSECURE_THRESHOLD_512',
})
+```
+
+For server-side or simpler setups, use the factory method:
-await sdk.initialize()
+```ts
+import { EnclaveSDK } from '@enclave-e3/sdk'
+import { sepolia } from 'viem/chains'
+
+const sdk = EnclaveSDK.create({
+ rpcUrl: import.meta.env.VITE_RPC_URL,
+ contracts: {
+ enclave: import.meta.env.VITE_ENCLAVE_ADDRESS,
+ ciphernodeRegistry: import.meta.env.VITE_REGISTRY_ADDRESS,
+ feeToken: import.meta.env.VITE_FEE_TOKEN_ADDRESS,
+ },
+ chain: sepolia,
+ privateKey: '0x...', // optional — omit for read-only
+ thresholdBfvParamsPresetName: 'INSECURE_THRESHOLD_512',
+})
```
### Requesting computations
@@ -59,8 +85,7 @@ await sdk.initialize()
```ts
const hash = await sdk.requestE3({
threshold: [2, 3],
- startWindow: [BigInt(Date.now()), BigInt(Date.now() + 5 * 60 * 1000)],
- duration: BigInt(1800),
+ inputWindow: [BigInt(Date.now()), BigInt(Date.now() + 5 * 60 * 1000)],
e3Program: '0x...',
e3ProgramParams: '0x',
computeProviderParams: '0x',
@@ -91,31 +116,65 @@ sdk.off(EnclaveEventType.E3_REQUESTED, e3Handler)
import { useEnclaveSDK } from '@enclave-e3/react'
export function Dashboard() {
- const { isInitialized, requestE3, onEnclaveEvent, EnclaveEventType } = useEnclaveSDK({
+ const { isInitialized, requestE3, onEnclaveEvent, off, EnclaveEventType } = useEnclaveSDK({
autoConnect: true,
contracts: {
enclave: import.meta.env.VITE_ENCLAVE_ADDRESS,
ciphernodeRegistry: import.meta.env.VITE_REGISTRY_ADDRESS,
+ feeToken: import.meta.env.VITE_FEE_TOKEN_ADDRESS,
},
- chainId: 31337,
+ thresholdBfvParamsPresetName: 'INSECURE_THRESHOLD_512',
})
useEffect(() => {
if (!isInitialized) return
- const handle = (event) => console.log('Activated', event.data)
- onEnclaveEvent(EnclaveEventType.E3_ACTIVATED, handle)
- return () => off(EnclaveEventType.E3_ACTIVATED, handle)
+ const handle = (event) => console.log('E3 Requested', event.data)
+ onEnclaveEvent(EnclaveEventType.E3_REQUESTED, handle)
+ return () => off(EnclaveEventType.E3_REQUESTED, handle)
}, [isInitialized])
return
}
```
+The hook uses wagmi's `usePublicClient` and `useWalletClient` internally, so your app must be inside
+a wagmi provider.
+
+## Encryption functions
+
+The SDK includes standalone FHE encryption functions that can be called via the SDK instance or
+imported directly:
+
+```ts
+// Via SDK instance (uses the configured preset name automatically)
+const publicKey = await sdk.generatePublicKey()
+const encrypted = await sdk.encryptNumber(42n, publicKey)
+const { encryptedData, proof } = await sdk.encryptNumberAndGenProof(42n, publicKey)
+
+// Standalone import (pass preset name explicitly)
+import { generatePublicKey, encryptNumber } from '@enclave-e3/sdk'
+
+const pk = await generatePublicKey('INSECURE_THRESHOLD_512')
+const ct = await encryptNumber(42n, pk, 'INSECURE_THRESHOLD_512')
+```
+
+## Modular sub-module imports
+
+The SDK is organized into three sub-modules (`contracts`, `events`, `encryption`) that can be
+imported independently for tree-shaking:
+
+```ts
+import { generatePublicKey, encryptNumber } from '@enclave-e3/sdk/encryption'
+import { ContractClient } from '@enclave-e3/sdk/contracts'
+import { EventListener, EnclaveEventType } from '@enclave-e3/sdk/events'
+```
+
+All exports are also available from the main `@enclave-e3/sdk` entry point.
+
## Configuration contract map
-Provide both the Enclave and CiphernodeRegistry addresses. The template exposes these via `.env` /
-`enclave.config.yaml`. For multi-chain apps, instantiate multiple SDKs or call
-`sdk.updateConfig({ contracts: { ... }, chainId })` whenever wallets switch networks.
+Provide the Enclave, CiphernodeRegistry, and FeeToken addresses. The template exposes these via
+`.env` / `enclave.config.yaml`. For multi-chain apps, instantiate separate SDK instances per chain.
## Working with the template
@@ -151,12 +210,14 @@ that wraps or complements the core Enclave SDK.
- **Historical events**: `sdk.getHistoricalEvents(type, fromBlock, toBlock)` fetches logs without a
WebSocket provider; useful for CRON jobs or health checks.
-- **Gas controls**: pass `gasLimit` to `requestE3`, `activateE3`, or `publishInput` if you want
- fixed limits when interacting with Enclave on L2s.
+- **Gas controls**: pass `gasLimit` to `requestE3`, `publishCiphertextOutput`, or other write methods
+ if you want fixed limits when interacting with Enclave on L2s.
- **Event polling**: `sdk.startEventPolling()` is handy in serverless environments where websockets
are unavailable.
- **Custom transports**: on the server, call `createWalletClient({ account, transport: http(rpc) })`
and load the private key from Vaults/KMS.
+- **Fee token approval**: call `sdk.approveFeeToken(amount)` before `requestE3` to approve the fee
+ token spend.
## Troubleshooting
@@ -164,7 +225,7 @@ that wraps or complements the core Enclave SDK.
| ------------------------------ | ------------------------------------------------------------------------------------------------------------------------ |
| `MISSING_PUBLIC_CLIENT` errors | Ensure `createPublicClient` uses an HTTP or WebSocket RPC reachable from the app. |
| `INVALID_ADDRESS` | Double-check contract addresses passed into the SDK match the current chain. |
-| Hooks never initialize | Confirm `autoConnect` is true, wallet is connected, and React component is inside a provider that renders on the client. |
+| Hooks never initialize | Confirm `autoConnect` is true, wallet is connected, and React component is inside a wagmi provider that renders on the client. |
| No events arrive | Switch to a WebSocket RPC or call `sdk.startEventPolling()` so logs are polled over HTTP. |
For additional API surface details see `packages/enclave-sdk/README.md` inside this repo.
diff --git a/packages/enclave-react/src/useEnclaveSDK.ts b/packages/enclave-react/src/useEnclaveSDK.ts
index 9f47a26544..e05e7c36d8 100644
--- a/packages/enclave-react/src/useEnclaveSDK.ts
+++ b/packages/enclave-react/src/useEnclaveSDK.ts
@@ -23,7 +23,6 @@ export interface UseEnclaveSDKConfig {
ciphernodeRegistry: `0x${string}`
feeToken: `0x${string}`
}
- chainId?: number
autoConnect?: boolean
thresholdBfvParamsPresetName: ThresholdBfvParamsPresetName
}
@@ -32,13 +31,10 @@ export interface UseEnclaveSDKReturn {
sdk: EnclaveSDK | null
isInitialized: boolean
error: string | null
- // Contract interaction methods (only the ones commonly used)
requestE3: typeof EnclaveSDK.prototype.requestE3
getThresholdBfvParamsSet: typeof EnclaveSDK.prototype.getThresholdBfvParamsSet
- // Event handling
onEnclaveEvent: (eventType: T, callback: EventCallback) => void
off: (eventType: T, callback: EventCallback) => void
- // Event types for convenience
EnclaveEventType: typeof EnclaveEventType
RegistryEventType: typeof RegistryEventType
}
@@ -64,7 +60,8 @@ export interface UseEnclaveSDKReturn {
* autoConnect: true,
* contracts: {
* enclave: '0x...',
- * ciphernodeRegistry: '0x...'
+ * ciphernodeRegistry: '0x...',
+ * feeToken: '0x...',
* },
* thresholdBfvParamsPresetName: 'INSECURE_THRESHOLD_512',
* });
@@ -102,12 +99,10 @@ export const useEnclaveSDK = (config: UseEnclaveSDKConfig): UseEnclaveSDKReturn
ciphernodeRegistry: '0x0000000000000000000000000000000000000000',
feeToken: '0x0000000000000000000000000000000000000000',
},
- chainId: config.chainId,
thresholdBfvParamsPresetName: config.thresholdBfvParamsPresetName,
}
const newSdk = new EnclaveSDK(sdkConfig)
- await newSdk.initialize()
setSdk(newSdk)
sdkRef.current = newSdk
setIsInitialized(true)
@@ -116,7 +111,7 @@ export const useEnclaveSDK = (config: UseEnclaveSDKConfig): UseEnclaveSDKReturn
setError(errorMessage)
console.error('SDK initialization failed:', err)
}
- }, [publicClient, walletClient, config.contracts, config.chainId, config.thresholdBfvParamsPresetName])
+ }, [publicClient, walletClient, config.contracts, config.thresholdBfvParamsPresetName])
// Initialize SDK when wagmi clients are available
useEffect(() => {
@@ -146,7 +141,6 @@ export const useEnclaveSDK = (config: UseEnclaveSDKConfig): UseEnclaveSDKReturn
return sdk.getThresholdBfvParamsSet()
}, [sdk])
- // Contract interaction methods
const requestE3 = useCallback(
(...args: Parameters) => {
if (!sdk) throw new Error('SDK not initialized')
@@ -155,7 +149,6 @@ export const useEnclaveSDK = (config: UseEnclaveSDKConfig): UseEnclaveSDKReturn
[sdk],
)
- // Event handling methods
const onEnclaveEvent = useCallback(
(eventType: T, callback: EventCallback) => {
if (!sdk) throw new Error('SDK not initialized')
diff --git a/packages/enclave-sdk/README.md b/packages/enclave-sdk/README.md
index 140fdc149b..be4f668cca 100644
--- a/packages/enclave-sdk/README.md
+++ b/packages/enclave-sdk/README.md
@@ -8,7 +8,9 @@ real-time event listening, contract interaction methods, and comprehensive error
- **Event-driven architecture**: Listen to smart contract events in real-time
- **Type-safe**: Built with TypeScript and uses generated types from contracts
- **Easy contract interactions**: Simple methods for reading from and writing to contracts
-- **React integration**: Includes React hooks for easy frontend integration
+- **React integration**: Includes React hooks for easy frontend integration (via `@enclave-e3/react`)
+- **Modular architecture**: Tree-shakeable sub-modules for contracts, events, and encryption
+- **Encryption helpers**: Standalone FHE encryption functions with optional ZK proof generation
- **Error handling**: Comprehensive error handling with custom error types
- **Gas estimation**: Built-in gas estimation for transactions
- **Event polling**: Support for both WebSocket and polling-based event listening
@@ -24,13 +26,16 @@ pnpm add @enclave-e3/sdk
```typescript
import { EnclaveSDK, EnclaveEventType, RegistryEventType } from '@enclave-e3/sdk'
import { createPublicClient, createWalletClient, http, custom } from 'viem'
+import { sepolia } from 'viem/chains'
// Initialize clients
const publicClient = createPublicClient({
+ chain: sepolia,
transport: http('YOUR_RPC_URL'),
})
const walletClient = createWalletClient({
+ chain: sepolia,
transport: custom(window.ethereum),
})
@@ -41,27 +46,25 @@ const sdk = new EnclaveSDK({
contracts: {
enclave: '0x...', // Your Enclave contract address
ciphernodeRegistry: '0x...', // Your CiphernodeRegistry contract address
+ feeToken: '0x...', // Your ERC-20 fee token address
},
- chainId: 1, // Optional
+ chain: sepolia,
+ thresholdBfvParamsPresetName: 'INSECURE_THRESHOLD_512',
})
-// Initialize the SDK
-await sdk.initialize()
-
// Listen to events with the unified event system
sdk.onEnclaveEvent(EnclaveEventType.E3_REQUESTED, (event) => {
console.log('E3 Requested:', event.data)
})
-sdk.onEnclaveEvent(RegistryEventType.CIPHERNODE_ADDED, (event) => {
- console.log('Ciphernode Added:', event.data)
+sdk.onEnclaveEvent(RegistryEventType.COMMITTEE_REQUESTED, (event) => {
+ console.log('Committee Requested:', event.data)
})
// Interact with contracts
const hash = await sdk.requestE3({
threshold: [1, 3],
- startWindow: [BigInt(0), BigInt(100)],
- duration: BigInt(3600),
+ inputWindow: [BigInt(0), BigInt(100)],
e3Program: '0x...',
e3ProgramParams: '0x...',
computeProviderParams: '0x...',
@@ -69,6 +72,29 @@ const hash = await sdk.requestE3({
})
```
+### Factory Method
+
+For a simpler setup (especially on the server), use the static `EnclaveSDK.create()` factory:
+
+```typescript
+import { EnclaveSDK } from '@enclave-e3/sdk'
+import { sepolia } from 'viem/chains'
+
+const sdk = EnclaveSDK.create({
+ rpcUrl: 'wss://sepolia.example.com',
+ contracts: {
+ enclave: '0x...',
+ ciphernodeRegistry: '0x...',
+ feeToken: '0x...',
+ },
+ chain: sepolia,
+ privateKey: '0x...', // optional — omit for read-only
+ thresholdBfvParamsPresetName: 'INSECURE_THRESHOLD_512',
+})
+```
+
+The factory auto-detects HTTP vs WebSocket transports and creates the appropriate viem clients.
+
## Usage within a browser
Usage within a typescript project should work out of the box, however in order to use wasm related
@@ -105,8 +131,6 @@ The SDK uses a unified event system with TypeScript enums for type safety:
enum EnclaveEventType {
// E3 Lifecycle
E3_REQUESTED = 'E3Requested',
- E3_ACTIVATED = 'E3Activated',
- INPUT_PUBLISHED = 'InputPublished',
CIPHERTEXT_OUTPUT_PUBLISHED = 'CiphertextOutputPublished',
PLAINTEXT_OUTPUT_PUBLISHED = 'PlaintextOutputPublished',
@@ -114,10 +138,16 @@ enum EnclaveEventType {
E3_PROGRAM_ENABLED = 'E3ProgramEnabled',
E3_PROGRAM_DISABLED = 'E3ProgramDisabled',
+ // Encryption Scheme Management
+ ENCRYPTION_SCHEME_ENABLED = 'EncryptionSchemeEnabled',
+ ENCRYPTION_SCHEME_DISABLED = 'EncryptionSchemeDisabled',
+
// Configuration
CIPHERNODE_REGISTRY_SET = 'CiphernodeRegistrySet',
MAX_DURATION_SET = 'MaxDurationSet',
- // ... more events
+ ALLOWED_E3_PROGRAMS_PARAMS_SET = 'AllowedE3ProgramsParamsSet',
+ OWNERSHIP_TRANSFERRED = 'OwnershipTransferred',
+ INITIALIZED = 'Initialized',
}
```
@@ -125,13 +155,12 @@ enum EnclaveEventType {
```typescript
enum RegistryEventType {
- CIPHERNODE_ADDED = 'CiphernodeAdded',
- CIPHERNODE_REMOVED = 'CiphernodeRemoved',
COMMITTEE_REQUESTED = 'CommitteeRequested',
COMMITTEE_PUBLISHED = 'CommitteePublished',
COMMITTEE_FINALIZED = 'CommitteeFinalized',
ENCLAVE_SET = 'EnclaveSet',
- // ... more events
+ OWNERSHIP_TRANSFERRED = 'OwnershipTransferred',
+ INITIALIZED = 'Initialized',
}
```
@@ -152,51 +181,119 @@ interface EnclaveEvent {
## React Integration
-The SDK includes a React hook for easy integration:
+The SDK includes a React hook via the `@enclave-e3/react` package:
+
+```bash
+pnpm add @enclave-e3/react
+```
```typescript
-import { useEnclaveSDK } from "@enclave-e3/contracts/sdk";
+import { useEnclaveSDK } from '@enclave-e3/react'
function MyComponent() {
const {
sdk,
isInitialized,
- isConnecting,
error,
- connectWallet,
requestE3,
onEnclaveEvent,
+ off,
EnclaveEventType,
+ RegistryEventType,
} = useEnclaveSDK({
contracts: {
- enclave: "0x...",
- ciphernodeRegistry: "0x...",
+ enclave: '0x...',
+ ciphernodeRegistry: '0x...',
+ feeToken: '0x...',
},
- rpcUrl: "YOUR_RPC_URL",
autoConnect: true,
- });
+ thresholdBfvParamsPresetName: 'INSECURE_THRESHOLD_512',
+ })
useEffect(() => {
if (isInitialized) {
- onEnclaveEvent(EnclaveEventType.E3_REQUESTED, (event) => {
- console.log("New E3 request:", event);
- });
+ const handler = (event) => {
+ console.log('New E3 request:', event)
+ }
+ onEnclaveEvent(EnclaveEventType.E3_REQUESTED, handler)
+ return () => off(EnclaveEventType.E3_REQUESTED, handler)
}
- }, [isInitialized]);
+ }, [isInitialized])
return (
- {!isInitialized && (
-
- )}
+ {error &&
Error: {error}
}
+ {!isInitialized &&
Initializing...
}
{/* Your UI */}
- );
+ )
}
```
+The hook uses wagmi's `usePublicClient` and `useWalletClient` under the hood, so your app must be
+wrapped in a wagmi provider.
+
+## Encryption Functions
+
+The SDK provides standalone encryption functions for FHE (Fully Homomorphic Encryption) operations.
+These can be used via the SDK instance or imported directly for tree-shaking:
+
+### Via the SDK instance
+
+```typescript
+// Generate a public key
+const publicKey = await sdk.generatePublicKey()
+
+// Encrypt a single number
+const encrypted = await sdk.encryptNumber(42n, publicKey)
+
+// Encrypt a vector
+const encryptedVec = await sdk.encryptVector(BigUint64Array.from([1n, 2n, 3n]), publicKey)
+
+// Encrypt with ZK proof generation
+const { encryptedData, proof } = await sdk.encryptNumberAndGenProof(42n, publicKey)
+```
+
+### Standalone imports
+
+```typescript
+import {
+ generatePublicKey,
+ encryptNumber,
+ encryptVector,
+ encryptNumberAndGenProof,
+ encryptVectorAndGenProof,
+ encryptNumberAndGenInputs,
+ encryptVectorAndGenInputs,
+ computePublicKeyCommitment,
+ getThresholdBfvParamsSet,
+} from '@enclave-e3/sdk'
+
+const presetName = 'INSECURE_THRESHOLD_512'
+
+const publicKey = await generatePublicKey(presetName)
+const encrypted = await encryptNumber(42n, publicKey, presetName)
+const { encryptedData, proof } = await encryptNumberAndGenProof(42n, publicKey, presetName)
+```
+
+## Modular Imports
+
+The SDK is organized into three sub-modules that can be imported independently for tree-shaking:
+
+```typescript
+// Encryption functions and types
+import { generatePublicKey, encryptNumber } from '@enclave-e3/sdk/encryption'
+
+// Contract client and types
+import { ContractClient } from '@enclave-e3/sdk/contracts'
+import type { ContractAddresses, E3 } from '@enclave-e3/sdk/contracts'
+
+// Event listener and types
+import { EventListener, EnclaveEventType, RegistryEventType } from '@enclave-e3/sdk/events'
+```
+
+All sub-module exports are also re-exported from the main `@enclave-e3/sdk` entry point for convenience.
+
## API Reference
### Core Methods
@@ -204,11 +301,13 @@ function MyComponent() {
#### Contract Interactions
```typescript
+// Approve fee token spending
+await sdk.approveFeeToken(amount: bigint);
+
// Request a new E3 computation
await sdk.requestE3({
threshold: [number, number],
- startWindow: [bigint, bigint],
- duration: bigint,
+ inputWindow: [bigint, bigint],
e3Program: `0x${string}`,
e3ProgramParams: `0x${string}`,
computeProviderParams: `0x${string}`,
@@ -216,14 +315,12 @@ await sdk.requestE3({
gasLimit?: bigint
});
-// Activate an E3 computation
-await sdk.activateE3(e3Id: bigint, gasLimit?: bigint);
-
-// Publish input data
-await sdk.publishInput(e3Id: bigint, data: `0x${string}`, gasLimit?: bigint);
+// Publish ciphertext output
+await sdk.publishCiphertextOutput(e3Id: bigint, ciphertextOutput: `0x${string}`, proof: `0x${string}`, gasLimit?: bigint);
// Read operations
const e3Data = await sdk.getE3(e3Id: bigint);
+const publicKey = await sdk.getE3PublicKey(e3Id: bigint);
```
#### Event Handling
@@ -233,6 +330,8 @@ sdk.onEnclaveEvent(eventType: AllEventTypes, callback: EventCallback);
sdk.off(eventType: AllEventTypes, callback: EventCallback);
+sdk.once(eventType: AllEventTypes, callback: EventCallback);
+
const logs = await sdk.getHistoricalEvents(
eventType: AllEventTypes,
fromBlock?: bigint,
@@ -244,6 +343,29 @@ await sdk.startEventPolling();
sdk.stopEventPolling();
```
+#### Encryption
+
+```typescript
+// Get BFV parameter set
+const params = await sdk.getThresholdBfvParamsSet();
+
+// Key generation
+const publicKey = await sdk.generatePublicKey();
+const commitment = await sdk.computePublicKeyCommitment(publicKey);
+
+// Encrypt data
+const encrypted = await sdk.encryptNumber(data: bigint, publicKey: Uint8Array);
+const encryptedVec = await sdk.encryptVector(data: BigUint64Array, publicKey: Uint8Array);
+
+// Encrypt with proof inputs (for ZK verification)
+const { encryptedData, circuitInputs } = await sdk.encryptNumberAndGenInputs(data, publicKey);
+const { encryptedData, circuitInputs } = await sdk.encryptVectorAndGenInputs(data, publicKey);
+
+// Encrypt with full ZK proof generation
+const { encryptedData, proof } = await sdk.encryptNumberAndGenProof(data, publicKey);
+const { encryptedData, proof } = await sdk.encryptVectorAndGenProof(data, publicKey);
+```
+
#### Utilities
```typescript
@@ -253,9 +375,6 @@ const gas = await sdk.estimateGas(functionName, args, contractAddress, abi, valu
// Transaction waiting
const receipt = await sdk.waitForTransaction(hash);
-// Configuration updates
-sdk.updateConfig(newConfig: Partial);
-
// Cleanup
sdk.cleanup();
```
@@ -269,11 +388,15 @@ interface SDKConfig {
contracts: {
enclave: `0x${string}`
ciphernodeRegistry: `0x${string}`
+ feeToken: `0x${string}`
}
- chainId?: number
+ chain?: Chain
+ thresholdBfvParamsPresetName: ThresholdBfvParamsPresetName
}
```
+`thresholdBfvParamsPresetName` must be one of: `'INSECURE_THRESHOLD_512'` or `'SECURE_THRESHOLD_8192'`.
+
## Error Handling
The SDK includes comprehensive error handling:
@@ -297,8 +420,8 @@ try {
### Building the SDK
```bash
-cd packages/enclave-contracts
-pnpm compile
+cd packages/enclave-sdk
+pnpm build
```
### Running the Demo
@@ -314,19 +437,21 @@ The demo showcases all SDK features including real-time event listening and cont
### Testing
```bash
-cd packages/enclave-contracts
+cd packages/enclave-sdk
pnpm test
```
## Architecture
-The SDK consists of several key components:
+The SDK is organized into a modular architecture with three domain-specific sub-modules:
+
+- **EnclaveSDK** (`enclave-sdk.ts`): Main orchestrator class that delegates to sub-modules
+- **Contracts** (`contracts/`): `ContractClient` for contract read/write operations, type definitions for contract addresses and E3 data
+- **Events** (`events/`): `EventListener` for real-time and historical event subscriptions, typed event enums and data interfaces
+- **Encryption** (`encryption/`): Standalone FHE encryption functions, BFV parameter management, ZK proof generation
+- **Utils** (`utils.ts`): Helper functions, error classes, encoding utilities
-- **EnclaveSDK**: Main orchestrator class
-- **ContractClient**: Handles contract read/write operations
-- **EventListener**: Manages real-time event listening
-- **Types**: TypeScript definitions with full type safety
-- **Utils**: Helper functions and error classes
+Each sub-module has its own `index.ts` entry point and can be imported independently.
## License
diff --git a/packages/enclave-sdk/package.json b/packages/enclave-sdk/package.json
index e47fbc43fa..89a80fa876 100644
--- a/packages/enclave-sdk/package.json
+++ b/packages/enclave-sdk/package.json
@@ -8,6 +8,21 @@
"import": "./dist/index.js",
"require": "./dist/index.cjs",
"default": "./dist/index.js"
+ },
+ "./encryption": {
+ "types": "./dist/encryption/index.d.ts",
+ "import": "./dist/encryption/index.js",
+ "require": "./dist/encryption/index.cjs"
+ },
+ "./contracts": {
+ "types": "./dist/contracts/index.d.ts",
+ "import": "./dist/contracts/index.js",
+ "require": "./dist/contracts/index.cjs"
+ },
+ "./events": {
+ "types": "./dist/events/index.d.ts",
+ "import": "./dist/events/index.js",
+ "require": "./dist/events/index.cjs"
}
},
"main": "./dist/index.cjs",
diff --git a/packages/enclave-sdk/src/contract-client.ts b/packages/enclave-sdk/src/contracts/contract-client.ts
similarity index 54%
rename from packages/enclave-sdk/src/contract-client.ts
rename to packages/enclave-sdk/src/contracts/contract-client.ts
index 50ff5c16a1..f7e84aa09c 100644
--- a/packages/enclave-sdk/src/contract-client.ts
+++ b/packages/enclave-sdk/src/contracts/contract-client.ts
@@ -4,80 +4,114 @@
// without even the implied warranty of MERCHANTABILITY
// or FITNESS FOR A PARTICULAR PURPOSE.
-import { Abi, Hash, PublicClient, TransactionReceipt, WalletClient } from 'viem'
+import {
+ type Abi,
+ type Chain,
+ type Hash,
+ type PublicClient,
+ type TransactionReceipt,
+ type WalletClient,
+ createPublicClient,
+ createWalletClient,
+ http,
+ webSocket,
+} from 'viem'
+import { privateKeyToAccount } from 'viem/accounts'
import { CiphernodeRegistryOwnable__factory, Enclave__factory, EnclaveToken__factory } from '@enclave-e3/contracts/types'
-import { type E3 } from './types'
-import { SDKError, isValidAddress } from './utils'
+import type { ContractAddresses, E3 } from './types'
+import { SDKError, isValidAddress } from '../utils'
+
+export interface ContractClientConfig {
+ publicClient: PublicClient
+ walletClient?: WalletClient
+ contracts: ContractAddresses
+}
export class ContractClient {
+ private publicClient: PublicClient
+ private walletClient?: WalletClient
+ private contracts: ContractAddresses
private contractInfo: {
enclave: { address: `0x${string}`; abi: Abi }
ciphernodeRegistry: { address: `0x${string}`; abi: Abi }
feeToken: { address: `0x${string}`; abi: Abi }
- } | null = null
-
- constructor(
- private publicClient: PublicClient,
- private walletClient?: WalletClient,
- private addresses: {
- enclave: `0x${string}`
- ciphernodeRegistry: `0x${string}`
- feeToken: `0x${string}`
- } = {
- enclave: '0x0000000000000000000000000000000000000000',
- ciphernodeRegistry: '0x0000000000000000000000000000000000000000',
- feeToken: '0x0000000000000000000000000000000000000000',
- },
- ) {
- if (!isValidAddress(addresses.enclave)) {
+ }
+
+ constructor(config: ContractClientConfig) {
+ const { publicClient, walletClient, contracts } = config
+
+ if (!isValidAddress(contracts.enclave)) {
throw new SDKError('Invalid Enclave contract address', 'INVALID_ADDRESS')
}
- if (!isValidAddress(addresses.ciphernodeRegistry)) {
+ if (!isValidAddress(contracts.ciphernodeRegistry)) {
throw new SDKError('Invalid CiphernodeRegistry contract address', 'INVALID_ADDRESS')
}
- if (!isValidAddress(addresses.feeToken)) {
+ if (!isValidAddress(contracts.feeToken)) {
throw new SDKError('Invalid FeeToken contract address', 'INVALID_ADDRESS')
}
+
+ this.publicClient = publicClient
+ this.walletClient = walletClient
+ this.contracts = contracts
+
+ this.contractInfo = {
+ enclave: {
+ address: contracts.enclave,
+ abi: Enclave__factory.abi,
+ },
+ ciphernodeRegistry: {
+ address: contracts.ciphernodeRegistry,
+ abi: CiphernodeRegistryOwnable__factory.abi,
+ },
+ feeToken: {
+ address: contracts.feeToken,
+ abi: EnclaveToken__factory.abi,
+ },
+ }
}
- /**
- * Initialize contract instances
- */
- public async initialize(): Promise {
- try {
- this.contractInfo = {
- enclave: {
- address: this.addresses.enclave,
- abi: Enclave__factory.abi,
- },
- ciphernodeRegistry: {
- address: this.addresses.ciphernodeRegistry,
- abi: CiphernodeRegistryOwnable__factory.abi,
- },
- feeToken: {
- address: this.addresses.feeToken,
- abi: EnclaveToken__factory.abi,
- },
- }
- } catch (error) {
- throw new SDKError(`Failed to initialize contracts: ${error}`, 'INITIALIZATION_FAILED')
+ public static create(options: {
+ rpcUrl: string
+ contracts: ContractAddresses
+ privateKey?: `0x${string}`
+ chain: Chain
+ }): ContractClient {
+ const isWebSocket = options.rpcUrl.startsWith('ws://') || options.rpcUrl.startsWith('wss://')
+ const transport = isWebSocket
+ ? webSocket(options.rpcUrl, {
+ keepAlive: { interval: 30_000 },
+ reconnect: { attempts: 5, delay: 2_000 },
+ })
+ : http(options.rpcUrl)
+
+ const publicClient = createPublicClient({
+ chain: options.chain,
+ transport,
+ }) as PublicClient
+
+ let walletClient: WalletClient | undefined
+ if (options.privateKey) {
+ const account = privateKeyToAccount(options.privateKey)
+ walletClient = createWalletClient({
+ account,
+ chain: options.chain,
+ transport,
+ })
}
+
+ return new ContractClient({ publicClient, walletClient, contracts: options.contracts })
+ }
+
+ public getPublicClient(): PublicClient {
+ return this.publicClient
}
- /**
- * Approve the fee token for the Enclave
- * approve(address spender, uint256 amount)
- */
public async approveFeeToken(amount: bigint): Promise {
if (!this.walletClient) {
throw new SDKError('Wallet client required for write operations', 'NO_WALLET')
}
- if (!this.contractInfo) {
- await this.initialize()
- }
-
try {
const account = this.walletClient.account
if (!account) {
@@ -85,80 +119,62 @@ export class ContractClient {
}
const { request } = await this.publicClient.simulateContract({
- address: this.addresses.feeToken,
+ address: this.contracts.feeToken,
abi: EnclaveToken__factory.abi,
functionName: 'approve',
- args: [this.addresses.enclave, amount],
+ args: [this.contracts.enclave, amount],
account,
})
- const hash = await this.walletClient.writeContract(request)
-
- return hash
+ return await this.walletClient.writeContract(request)
} catch (error) {
throw new SDKError(`Failed to approve fee token: ${error}`, 'APPROVE_FEE_TOKEN_FAILED')
}
}
- /**
- * Request a new E3 computation
- * request(uint32[2] threshold, uint256[2] inputWindow, IE3Program e3Program, bytes e3ProgramParams, bytes computeProviderParams, bytes customParams)
- */
- public async requestE3(
- threshold: [number, number],
- inputWindow: [bigint, bigint],
- e3Program: `0x${string}`,
- e3ProgramParams: `0x${string}`,
- computeProviderParams: `0x${string}`,
- customParams?: `0x${string}`,
- gasLimit?: bigint,
- ): Promise {
+ public async requestE3(params: {
+ threshold: [number, number]
+ inputWindow: [bigint, bigint]
+ e3Program: `0x${string}`
+ e3ProgramParams: `0x${string}`
+ computeProviderParams: `0x${string}`
+ customParams?: `0x${string}`
+ gasLimit?: bigint
+ }): Promise {
if (!this.walletClient) {
throw new SDKError('Wallet client required for write operations', 'NO_WALLET')
}
- if (!this.contractInfo) {
- await this.initialize()
- }
-
try {
const account = this.walletClient.account
if (!account) {
throw new SDKError('No account connected', 'NO_ACCOUNT')
}
- // Simulate transaction
const { request } = await this.publicClient.simulateContract({
- address: this.addresses.enclave,
+ address: this.contracts.enclave,
abi: Enclave__factory.abi,
functionName: 'request',
args: [
{
- threshold,
- inputWindow,
- e3Program,
- e3ProgramParams,
- computeProviderParams,
- customParams: customParams || '0x',
+ threshold: params.threshold,
+ inputWindow: params.inputWindow,
+ e3Program: params.e3Program,
+ e3ProgramParams: params.e3ProgramParams,
+ computeProviderParams: params.computeProviderParams,
+ customParams: params.customParams || '0x',
},
],
account,
- gas: gasLimit,
+ gas: params.gasLimit,
})
- // Execute transaction
- const hash = await this.walletClient.writeContract(request)
-
- return hash
+ return await this.walletClient.writeContract(request)
} catch (error) {
throw new SDKError(`Failed to request E3: ${error}`, 'REQUEST_E3_FAILED')
}
}
- /**
- * Publish ciphertext output for an E3 computation
- * publishCiphertextOutput(uint256 e3Id, bytes memory ciphertextOutput, bytes memory proof)
- */
public async publishCiphertextOutput(
e3Id: bigint,
ciphertextOutput: `0x${string}`,
@@ -169,19 +185,14 @@ export class ContractClient {
throw new SDKError('Wallet client required for write operations', 'NO_WALLET')
}
- if (!this.contractInfo) {
- await this.initialize()
- }
-
try {
const account = this.walletClient.account
if (!account) {
throw new SDKError('No account connected', 'NO_ACCOUNT')
}
- // Simulate transaction
const { request } = await this.publicClient.simulateContract({
- address: this.addresses.enclave,
+ address: this.contracts.enclave,
abi: Enclave__factory.abi,
functionName: 'publishCiphertextOutput',
args: [e3Id, ciphertextOutput, proof],
@@ -189,27 +200,16 @@ export class ContractClient {
gas: gasLimit,
})
- // Execute transaction
- const hash = await this.walletClient.writeContract(request)
-
- return hash
+ return await this.walletClient.writeContract(request)
} catch (error) {
throw new SDKError(`Failed to publish ciphertext output: ${error}`, 'PUBLISH_CIPHERTEXT_OUTPUT_FAILED')
}
}
- /**
- * Get E3 information
- * Based on the contract: getE3(uint256 e3Id) returns (E3 memory e3)
- */
public async getE3(e3Id: bigint): Promise {
- if (!this.contractInfo) {
- await this.initialize()
- }
-
try {
const result: E3 = await this.publicClient.readContract({
- address: this.addresses.enclave,
+ address: this.contracts.enclave,
abi: Enclave__factory.abi,
functionName: 'getE3',
args: [e3Id],
@@ -221,20 +221,10 @@ export class ContractClient {
}
}
- /**
- * Get the public key for an E3 computation
- * Based on the contract: committeePublicKey(uint256 e3Id) returns (bytes32 publicKeyHash)
- * @param e3Id
- * @returns The public key
- */
public async getE3PublicKey(e3Id: bigint): Promise<`0x${string}`> {
- if (!this.contractInfo) {
- await this.initialize()
- }
-
try {
const result: `0x${string}` = await this.publicClient.readContract({
- address: this.addresses.ciphernodeRegistry,
+ address: this.contracts.ciphernodeRegistry,
abi: CiphernodeRegistryOwnable__factory.abi,
functionName: 'committeePublicKey',
args: [e3Id],
@@ -246,9 +236,6 @@ export class ContractClient {
}
}
- /**
- * Estimate gas for a transaction
- */
public async estimateGas(
functionName: string,
args: readonly unknown[],
@@ -275,25 +262,18 @@ export class ContractClient {
...(value !== undefined && { value }),
}
- const gas = await this.publicClient.estimateContractGas(estimateParams)
-
- return gas
+ return await this.publicClient.estimateContractGas(estimateParams)
} catch (error) {
throw new SDKError(`Failed to estimate gas: ${error}`, 'GAS_ESTIMATION_FAILED')
}
}
- /**
- * Wait for transaction confirmation
- */
public async waitForTransaction(hash: Hash): Promise {
try {
- const receipt = await this.publicClient.waitForTransactionReceipt({
+ return await this.publicClient.waitForTransactionReceipt({
hash,
confirmations: 1,
})
-
- return receipt
} catch (error) {
throw new SDKError(`Failed to wait for transaction: ${error}`, 'TRANSACTION_WAIT_FAILED')
}
diff --git a/packages/enclave-sdk/src/contracts/index.ts b/packages/enclave-sdk/src/contracts/index.ts
new file mode 100644
index 0000000000..baa6bd4728
--- /dev/null
+++ b/packages/enclave-sdk/src/contracts/index.ts
@@ -0,0 +1,9 @@
+// SPDX-License-Identifier: LGPL-3.0-only
+//
+// This file is provided WITHOUT ANY WARRANTY;
+// without even the implied warranty of MERCHANTABILITY
+// or FITNESS FOR A PARTICULAR PURPOSE.
+
+export { ContractClient } from './contract-client'
+export type { ContractClientConfig } from './contract-client'
+export type { ContractAddresses, E3 } from './types'
diff --git a/packages/enclave-sdk/src/contracts/types.ts b/packages/enclave-sdk/src/contracts/types.ts
new file mode 100644
index 0000000000..804ee38b50
--- /dev/null
+++ b/packages/enclave-sdk/src/contracts/types.ts
@@ -0,0 +1,25 @@
+// SPDX-License-Identifier: LGPL-3.0-only
+//
+// This file is provided WITHOUT ANY WARRANTY;
+// without even the implied warranty of MERCHANTABILITY
+// or FITNESS FOR A PARTICULAR PURPOSE.
+
+export interface ContractAddresses {
+ enclave: `0x${string}`
+ ciphernodeRegistry: `0x${string}`
+ feeToken: `0x${string}`
+}
+
+export interface E3 {
+ seed: bigint
+ threshold: readonly [number, number]
+ requestBlock: bigint
+ inputWindow: readonly [bigint, bigint]
+ encryptionSchemeId: string
+ e3Program: string
+ e3ProgramParams: string
+ decryptionVerifier: string
+ committeePublicKey: string
+ ciphertextOutput: string
+ plaintextOutput: string
+}
diff --git a/packages/enclave-sdk/src/enclave-sdk.ts b/packages/enclave-sdk/src/enclave-sdk.ts
index 4424a01560..792c6595b0 100644
--- a/packages/enclave-sdk/src/enclave-sdk.ts
+++ b/packages/enclave-sdk/src/enclave-sdk.ts
@@ -4,53 +4,47 @@
// without even the implied warranty of MERCHANTABILITY
// or FITNESS FOR A PARTICULAR PURPOSE.
-import { type Abi, type Hash, type Log, PublicClient, WalletClient, createPublicClient, createWalletClient, http, webSocket } from 'viem'
+import {
+ type Abi,
+ type Chain,
+ type Hash,
+ type Log,
+ type PublicClient,
+ type WalletClient,
+ createPublicClient,
+ createWalletClient,
+ http,
+ webSocket,
+} from 'viem'
import { privateKeyToAccount } from 'viem/accounts'
-import { hardhat, mainnet, monadTestnet, sepolia } from 'viem/chains'
-import initializeWasm from '@enclave-e3/wasm/init'
-import { CiphernodeRegistryOwnable__factory, Enclave__factory } from '@enclave-e3/contracts/types'
-import { ContractClient } from './contract-client'
-import { EventListener } from './event-listener'
-import { EnclaveEventType, ThresholdBfvParamsPresetNames } from './types'
+import { ContractClient } from './contracts/contract-client'
+import { EventListener } from './events/event-listener'
+import {
+ getThresholdBfvParamsSet,
+ generatePublicKey,
+ computePublicKeyCommitment,
+ encryptNumber,
+ encryptVector,
+ encryptNumberAndGenInputs,
+ encryptNumberAndGenProof,
+ encryptVectorAndGenInputs,
+ encryptVectorAndGenProof,
+} from './encryption/encrypt'
+import { ThresholdBfvParamsPresetNames } from './encryption/types'
import { SDKError, isValidAddress } from './utils'
-import type {
- AllEventTypes,
- E3,
- EventCallback,
- SDKConfig,
- BfvParams,
- VerifiableEncryptionResult,
- EncryptedValueAndPublicInputs,
- ThresholdBfvParamsPresetName,
-} from './types'
-import {
- bfv_encrypt_number,
- bfv_encrypt_vector,
- generate_public_key,
- bfv_verifiable_encrypt_number,
- bfv_verifiable_encrypt_vector,
- compute_pk_commitment,
- get_bfv_params,
-} from '@enclave-e3/wasm'
-import { generateProof } from './greco'
+import type { SDKConfig } from './types'
+import type { AllEventTypes, EventCallback } from './events/types'
+import type { E3 } from './contracts/types'
+import type { BfvParams, EncryptedValueAndPublicInputs, ThresholdBfvParamsPresetName, VerifiableEncryptionResult } from './encryption/types'
export class EnclaveSDK {
- public static readonly chains = {
- 1: mainnet,
- 11155111: sepolia,
- 41454: monadTestnet,
- 31337: hardhat,
- } as const
-
private eventListener: EventListener
private contractClient: ContractClient
- private initialized = false
- private thresholdBfvParamsPresetName?: ThresholdBfvParamsPresetName
+ private thresholdBfvParamsPresetName: ThresholdBfvParamsPresetName
private publicClient: PublicClient
- // TODO: use zod for config validation
constructor(private config: SDKConfig) {
if (!config.publicClient) {
throw new SDKError('Public client is required', 'MISSING_PUBLIC_CLIENT')
@@ -80,201 +74,68 @@ export class EnclaveSDK {
}
this.thresholdBfvParamsPresetName = config.thresholdBfvParamsPresetName
- this.eventListener = new EventListener(config.publicClient)
- this.contractClient = new ContractClient(config.publicClient, config.walletClient, config.contracts)
this.publicClient = config.publicClient
- }
- /**
- * Initialize the SDK
- */
- // TODO: Delete this it is redundant
- public async initialize(): Promise {
- if (this.initialized) return
-
- try {
- await this.contractClient.initialize()
- this.initialized = true
- } catch (error) {
- throw new SDKError(`Failed to initialize SDK: ${error}`, 'SDK_INITIALIZATION_FAILED')
- }
+ this.contractClient = new ContractClient({
+ publicClient: config.publicClient,
+ walletClient: config.walletClient,
+ contracts: config.contracts,
+ })
+
+ this.eventListener = new EventListener({
+ publicClient: config.publicClient,
+ contracts: config.contracts,
+ })
}
- /**
- * Get the public client used by the SDK
- * @returns The public client
- */
- public getPublicClient = (): PublicClient => {
+ // --- Encryption (delegates to standalone functions) ---
+
+ public getPublicClient(): PublicClient {
return this.publicClient
}
public async getThresholdBfvParamsSet(): Promise {
- await initializeWasm()
- let params = get_bfv_params(this.thresholdBfvParamsPresetName as ThresholdBfvParamsPresetName)
- return {
- degree: Number(params.degree), // degree is returned as a bigint from wasm
- plaintextModulus: params.plaintext_modulus as bigint,
- moduli: params.moduli as bigint[],
- error1Variance: params.error1_variance,
- }
+ return getThresholdBfvParamsSet(this.thresholdBfvParamsPresetName)
}
public async generatePublicKey(): Promise {
- await initializeWasm()
- const protocolParams = await this.getThresholdBfvParamsSet()
- return generate_public_key(protocolParams.degree, protocolParams.plaintextModulus, BigUint64Array.from(protocolParams.moduli))
+ return generatePublicKey(this.thresholdBfvParamsPresetName)
}
public async computePublicKeyCommitment(publicKey: Uint8Array): Promise {
- await initializeWasm()
- const protocolParams = await this.getThresholdBfvParamsSet()
-
- return compute_pk_commitment(
- publicKey,
- protocolParams.degree,
- protocolParams.plaintextModulus,
- BigUint64Array.from(protocolParams.moduli),
- )
+ return computePublicKeyCommitment(publicKey, this.thresholdBfvParamsPresetName)
}
- /**
- * Encrypt a number using the configured protocol
- * @param data - The number to encrypt
- * @param publicKey - The public key to use for encryption
- * @returns The encrypted number
- */
public async encryptNumber(data: bigint, publicKey: Uint8Array): Promise {
- await initializeWasm()
- const protocolParams = await this.getThresholdBfvParamsSet()
-
- return bfv_encrypt_number(
- data,
- publicKey,
- protocolParams.degree,
- protocolParams.plaintextModulus,
- BigUint64Array.from(protocolParams.moduli),
- )
+ return encryptNumber(data, publicKey, this.thresholdBfvParamsPresetName)
}
- /**
- * Encrypt a vector using the configured protocol
- * @param data - The vector to encrypt
- * @param publicKey - The public key to use for encryption
- * @returns The ciphertext
- */
public async encryptVector(data: BigUint64Array, publicKey: Uint8Array): Promise {
- await initializeWasm()
- const protocolParams = await this.getThresholdBfvParamsSet()
-
- return bfv_encrypt_vector(
- data,
- publicKey,
- protocolParams.degree,
- protocolParams.plaintextModulus,
- BigUint64Array.from(protocolParams.moduli),
- )
+ return encryptVector(data, publicKey, this.thresholdBfvParamsPresetName)
}
- /**
- * This function encrypts a number using the configured FHE protocol
- * and generates the necessary public inputs for a zk-SNARK proof.
- * @param data The number to encrypt
- * @param publicKey The public key to use for encryption
- * @returns The encrypted number and the inputs for the zk-SNARK proof
- */
public async encryptNumberAndGenInputs(data: bigint, publicKey: Uint8Array): Promise {
- await initializeWasm()
- const protocolParams = await this.getThresholdBfvParamsSet()
-
- const [encryptedData, circuitInputs] = bfv_verifiable_encrypt_number(
- data,
- publicKey,
- protocolParams.degree,
- protocolParams.plaintextModulus,
- BigUint64Array.from(protocolParams.moduli),
- )
-
- return {
- encryptedData,
- circuitInputs: JSON.parse(circuitInputs),
- }
+ return encryptNumberAndGenInputs(data, publicKey, this.thresholdBfvParamsPresetName)
}
- /**
- * Encrypt a number using the configured protocol and generate a zk-SNARK proof using Greco
- * @param data - The number to encrypt
- * @param publicKey - The public key to use for encryption
- * @returns The encrypted number and the proof
- */
public async encryptNumberAndGenProof(data: bigint, publicKey: Uint8Array): Promise {
- const { circuitInputs: publicInputs, encryptedData } = await this.encryptNumberAndGenInputs(data, publicKey)
- const proof = await generateProof(publicInputs)
-
- return {
- encryptedData,
- proof,
- }
+ return encryptNumberAndGenProof(data, publicKey, this.thresholdBfvParamsPresetName)
}
- /**
- * Encrypt a vector and generate inputs for an E3 computation
- * @param data - The vector to encrypt
- * @param publicKey - The public key to use for encryption
- * @returns The encrypted vector and the inputs for the E3 computation
- */
public async encryptVectorAndGenInputs(data: BigUint64Array, publicKey: Uint8Array): Promise {
- await initializeWasm()
- const protocolParams = await this.getThresholdBfvParamsSet()
-
- const [encryptedData, circuitInputs] = bfv_verifiable_encrypt_vector(
- data,
- publicKey,
- protocolParams.degree,
- protocolParams.plaintextModulus,
- BigUint64Array.from(protocolParams.moduli),
- )
-
- return {
- encryptedData,
- circuitInputs: JSON.parse(circuitInputs),
- }
+ return encryptVectorAndGenInputs(data, publicKey, this.thresholdBfvParamsPresetName)
}
- /**
- * Encrypt a vector using the configured protocol and generate a zk-SNARK proof using Greco
- * @param data - The vector to encrypt
- * @param publicKey - The public key to use for encryption
- * @returns The encrypted vector and the proof
- */
public async encryptVectorAndGenProof(data: BigUint64Array, publicKey: Uint8Array): Promise {
- const { circuitInputs: publicInputs, encryptedData } = await this.encryptVectorAndGenInputs(data, publicKey)
-
- const proof = await generateProof(publicInputs)
-
- return {
- encryptedData,
- proof,
- }
+ return encryptVectorAndGenProof(data, publicKey, this.thresholdBfvParamsPresetName)
}
- /**
- * Approve the fee token for the Enclave
- * @param amount - The amount to approve
- * @returns The approval transaction hash
- */
- public async approveFeeToken(amount: bigint): Promise {
- console.log('>>> APPROVE FEE TOKEN')
-
- if (!this.initialized) {
- await this.initialize()
- }
+ // --- Contracts (delegates to ContractClient) ---
+ public async approveFeeToken(amount: bigint): Promise {
return this.contractClient.approveFeeToken(amount)
}
- /**
- * Request a new E3 computation
- */
public async requestE3(params: {
threshold: [number, number]
inputWindow: [bigint, bigint]
@@ -284,181 +145,71 @@ export class EnclaveSDK {
customParams?: `0x${string}`
gasLimit?: bigint
}): Promise {
- console.log('>>> REQUEST')
-
- if (!this.initialized) {
- await this.initialize()
- }
-
- return this.contractClient.requestE3(
- params.threshold,
- params.inputWindow,
- params.e3Program,
- params.e3ProgramParams,
- params.computeProviderParams,
- params.customParams,
- params.gasLimit,
- )
+ return this.contractClient.requestE3(params)
}
- /**
- * Get the public key for an E3 computation
- * @param e3Id - The ID of the E3 computation
- * @returns The public key
- */
public async getE3PublicKey(e3Id: bigint): Promise<`0x${string}`> {
- if (!this.initialized) {
- await this.initialize()
- }
-
return this.contractClient.getE3PublicKey(e3Id)
}
- /**
- * Publish ciphertext output for an E3 computation
- */
public async publishCiphertextOutput(
e3Id: bigint,
ciphertextOutput: `0x${string}`,
proof: `0x${string}`,
gasLimit?: bigint,
): Promise {
- if (!this.initialized) {
- await this.initialize()
- }
-
return this.contractClient.publishCiphertextOutput(e3Id, ciphertextOutput, proof, gasLimit)
}
- /**
- * Get E3 information
- */
public async getE3(e3Id: bigint): Promise {
- if (!this.initialized) {
- await this.initialize()
- }
-
return this.contractClient.getE3(e3Id)
}
- /**
- * Unified Event Listening - Listen to any Enclave or Registry event
- */
- public onEnclaveEvent(eventType: T, callback: EventCallback): void {
- // Determine which contract to listen to based on event type
- const isEnclaveEvent = Object.values(EnclaveEventType).includes(eventType as EnclaveEventType)
- const contractAddress = isEnclaveEvent ? this.config.contracts.enclave : this.config.contracts.ciphernodeRegistry
- const abi = isEnclaveEvent ? Enclave__factory.abi : CiphernodeRegistryOwnable__factory.abi
+ public async estimateGas(
+ functionName: string,
+ args: readonly unknown[],
+ contractAddress: `0x${string}`,
+ abi: Abi,
+ value?: bigint,
+ ): Promise {
+ return this.contractClient.estimateGas(functionName, args, contractAddress, abi, value)
+ }
+
+ public async waitForTransaction(hash: Hash): Promise {
+ return this.contractClient.waitForTransaction(hash)
+ }
+
+ // --- Events (delegates to EventListener) ---
- void this.eventListener.watchContractEvent(contractAddress, eventType, abi, callback)
+ public onEnclaveEvent(eventType: T, callback: EventCallback): void {
+ this.eventListener.onEnclaveEvent(eventType, callback)
}
- /**
- * Remove event listener
- */
public off(eventType: T, callback: EventCallback): void {
this.eventListener.off(eventType, callback)
}
- /**
- * Handle an event only once
- */
public once(type: T, callback: EventCallback): void {
- const handler: EventCallback = (event) => {
- this.off(type, handler)
- const prom = callback(event)
- if (prom) {
- prom.catch((e) => console.log(e))
- }
- }
- this.onEnclaveEvent(type, handler)
+ this.eventListener.once(type, callback)
}
- /**
- * Get historical events
- */
public async getHistoricalEvents(eventType: AllEventTypes, fromBlock?: bigint, toBlock?: bigint): Promise {
- const isEnclaveEvent = Object.values(EnclaveEventType).includes(eventType as EnclaveEventType)
- const contractAddress = isEnclaveEvent ? this.config.contracts.enclave : this.config.contracts.ciphernodeRegistry
- const abi = isEnclaveEvent ? Enclave__factory.abi : CiphernodeRegistryOwnable__factory.abi
-
- return this.eventListener.getHistoricalEvents(contractAddress, eventType, abi, fromBlock, toBlock)
+ return this.eventListener.getHistoricalEvents(eventType, fromBlock, toBlock)
}
- /**
- * Start polling for events
- */
public async startEventPolling(): Promise {
void this.eventListener.startPolling()
}
- /**
- * Stop polling for events
- */
public stopEventPolling(): void {
this.eventListener.stopPolling()
}
- /**
- * Utility methods
- */
-
- /**
- * Estimate gas for a transaction
- */
- public async estimateGas(
- functionName: string,
- args: readonly unknown[],
- contractAddress: `0x${string}`,
- abi: Abi,
- value?: bigint,
- ): Promise {
- return this.contractClient.estimateGas(functionName, args, contractAddress, abi, value)
- }
-
- /**
- * Wait for transaction confirmation
- */
- public async waitForTransaction(hash: Hash): Promise {
- return this.contractClient.waitForTransaction(hash)
- }
-
- /**
- * Clean up resources
- */
public cleanup(): void {
this.eventListener.cleanup()
}
- /**
- * Update SDK configuration
- */
- // TODO: We should delete this as we don't want a stateful client.
- public updateConfig(newConfig: Partial): void {
- if (newConfig.publicClient) {
- this.config.publicClient = newConfig.publicClient
- this.eventListener = new EventListener(newConfig.publicClient)
- }
-
- if (newConfig.walletClient) {
- this.config.walletClient = newConfig.walletClient
- }
-
- if (newConfig.contracts) {
- this.config.contracts = {
- ...this.config.contracts,
- ...newConfig.contracts,
- }
- }
-
- if (newConfig.chainId) {
- this.config.chainId = newConfig.chainId
- }
-
- this.contractClient = new ContractClient(this.config.publicClient, this.config.walletClient, this.config.contracts)
-
- this.initialized = false
- }
+ // --- Factory ---
public static create(options: {
rpcUrl: string
@@ -468,11 +219,9 @@ export class EnclaveSDK {
feeToken: `0x${string}`
}
privateKey?: `0x${string}`
- chainId: keyof typeof EnclaveSDK.chains
+ chain: Chain
thresholdBfvParamsPresetName: ThresholdBfvParamsPresetName
}): EnclaveSDK {
- const chain = EnclaveSDK.chains[options.chainId]
-
const isWebSocket = options.rpcUrl.startsWith('ws://') || options.rpcUrl.startsWith('wss://')
const transport = isWebSocket
? webSocket(options.rpcUrl, {
@@ -480,16 +229,18 @@ export class EnclaveSDK {
reconnect: { attempts: 5, delay: 2_000 },
})
: http(options.rpcUrl)
+
const publicClient = createPublicClient({
- chain,
+ chain: options.chain,
transport,
}) as SDKConfig['publicClient']
- let walletClient: WalletClient | undefined = undefined
+
+ let walletClient: WalletClient | undefined
if (options.privateKey) {
const account = privateKeyToAccount(options.privateKey)
walletClient = createWalletClient({
account,
- chain,
+ chain: options.chain,
transport,
})
}
@@ -498,7 +249,7 @@ export class EnclaveSDK {
publicClient,
walletClient,
contracts: options.contracts,
- chainId: options.chainId,
+ chain: options.chain,
thresholdBfvParamsPresetName: options.thresholdBfvParamsPresetName,
})
}
diff --git a/packages/enclave-sdk/src/encryption/encrypt.ts b/packages/enclave-sdk/src/encryption/encrypt.ts
new file mode 100644
index 0000000000..cba48c5ddf
--- /dev/null
+++ b/packages/enclave-sdk/src/encryption/encrypt.ts
@@ -0,0 +1,105 @@
+// SPDX-License-Identifier: LGPL-3.0-only
+//
+// This file is provided WITHOUT ANY WARRANTY;
+// without even the implied warranty of MERCHANTABILITY
+// or FITNESS FOR A PARTICULAR PURPOSE.
+
+import initializeWasm from '@enclave-e3/wasm/init'
+import {
+ bfv_encrypt_number,
+ bfv_encrypt_vector,
+ generate_public_key,
+ bfv_verifiable_encrypt_number,
+ bfv_verifiable_encrypt_vector,
+ compute_pk_commitment,
+ get_bfv_params,
+} from '@enclave-e3/wasm'
+import { generateProof } from '../greco'
+import type { BfvParams, EncryptedValueAndPublicInputs, ThresholdBfvParamsPresetName, VerifiableEncryptionResult } from './types'
+
+async function resolveParams(presetName: ThresholdBfvParamsPresetName): Promise {
+ await initializeWasm()
+ const params = get_bfv_params(presetName)
+ return {
+ degree: Number(params.degree),
+ plaintextModulus: params.plaintext_modulus as bigint,
+ moduli: params.moduli as bigint[],
+ error1Variance: params.error1_variance,
+ }
+}
+
+export async function getThresholdBfvParamsSet(presetName: ThresholdBfvParamsPresetName): Promise {
+ return resolveParams(presetName)
+}
+
+export async function generatePublicKey(presetName: ThresholdBfvParamsPresetName): Promise {
+ const params = await resolveParams(presetName)
+ return generate_public_key(params.degree, params.plaintextModulus, BigUint64Array.from(params.moduli))
+}
+
+export async function computePublicKeyCommitment(pk: Uint8Array, presetName: ThresholdBfvParamsPresetName): Promise {
+ const params = await resolveParams(presetName)
+ return compute_pk_commitment(pk, params.degree, params.plaintextModulus, BigUint64Array.from(params.moduli))
+}
+
+export async function encryptNumber(data: bigint, pk: Uint8Array, presetName: ThresholdBfvParamsPresetName): Promise {
+ const params = await resolveParams(presetName)
+ return bfv_encrypt_number(data, pk, params.degree, params.plaintextModulus, BigUint64Array.from(params.moduli))
+}
+
+export async function encryptVector(data: BigUint64Array, pk: Uint8Array, presetName: ThresholdBfvParamsPresetName): Promise {
+ const params = await resolveParams(presetName)
+ return bfv_encrypt_vector(data, pk, params.degree, params.plaintextModulus, BigUint64Array.from(params.moduli))
+}
+
+export async function encryptNumberAndGenInputs(
+ data: bigint,
+ pk: Uint8Array,
+ presetName: ThresholdBfvParamsPresetName,
+): Promise {
+ const params = await resolveParams(presetName)
+ const [encryptedData, circuitInputs] = bfv_verifiable_encrypt_number(
+ data,
+ pk,
+ params.degree,
+ params.plaintextModulus,
+ BigUint64Array.from(params.moduli),
+ )
+ return { encryptedData, circuitInputs: JSON.parse(circuitInputs) }
+}
+
+export async function encryptNumberAndGenProof(
+ data: bigint,
+ pk: Uint8Array,
+ presetName: ThresholdBfvParamsPresetName,
+): Promise {
+ const { circuitInputs, encryptedData } = await encryptNumberAndGenInputs(data, pk, presetName)
+ const proof = await generateProof(circuitInputs)
+ return { encryptedData, proof }
+}
+
+export async function encryptVectorAndGenInputs(
+ data: BigUint64Array,
+ pk: Uint8Array,
+ presetName: ThresholdBfvParamsPresetName,
+): Promise {
+ const params = await resolveParams(presetName)
+ const [encryptedData, circuitInputs] = bfv_verifiable_encrypt_vector(
+ data,
+ pk,
+ params.degree,
+ params.plaintextModulus,
+ BigUint64Array.from(params.moduli),
+ )
+ return { encryptedData, circuitInputs: JSON.parse(circuitInputs) }
+}
+
+export async function encryptVectorAndGenProof(
+ data: BigUint64Array,
+ pk: Uint8Array,
+ presetName: ThresholdBfvParamsPresetName,
+): Promise {
+ const { circuitInputs, encryptedData } = await encryptVectorAndGenInputs(data, pk, presetName)
+ const proof = await generateProof(circuitInputs)
+ return { encryptedData, proof }
+}
diff --git a/packages/enclave-sdk/src/encryption/index.ts b/packages/enclave-sdk/src/encryption/index.ts
new file mode 100644
index 0000000000..c34a561efb
--- /dev/null
+++ b/packages/enclave-sdk/src/encryption/index.ts
@@ -0,0 +1,21 @@
+// SPDX-License-Identifier: LGPL-3.0-only
+//
+// This file is provided WITHOUT ANY WARRANTY;
+// without even the implied warranty of MERCHANTABILITY
+// or FITNESS FOR A PARTICULAR PURPOSE.
+
+export {
+ getThresholdBfvParamsSet,
+ generatePublicKey,
+ computePublicKeyCommitment,
+ encryptNumber,
+ encryptVector,
+ encryptNumberAndGenInputs,
+ encryptNumberAndGenProof,
+ encryptVectorAndGenInputs,
+ encryptVectorAndGenProof,
+} from './encrypt'
+
+export type { BfvParams, ThresholdBfvParamsPresetName, VerifiableEncryptionResult, EncryptedValueAndPublicInputs } from './types'
+
+export { ThresholdBfvParamsPresetNames } from './types'
diff --git a/packages/enclave-sdk/src/encryption/types.ts b/packages/enclave-sdk/src/encryption/types.ts
new file mode 100644
index 0000000000..e1af22d3c3
--- /dev/null
+++ b/packages/enclave-sdk/src/encryption/types.ts
@@ -0,0 +1,32 @@
+// SPDX-License-Identifier: LGPL-3.0-only
+//
+// This file is provided WITHOUT ANY WARRANTY;
+// without even the implied warranty of MERCHANTABILITY
+// or FITNESS FOR A PARTICULAR PURPOSE.
+
+import type { ProofData } from '@aztec/bb.js'
+import type { CircuitInputs } from '../greco'
+
+export interface BfvParams {
+ degree: number
+ plaintextModulus: bigint
+ moduli: bigint[]
+ error1Variance: string | undefined
+}
+
+export type ThresholdBfvParamsPresetName = 'INSECURE_THRESHOLD_512' | 'SECURE_THRESHOLD_8192'
+
+export const ThresholdBfvParamsPresetNames = [
+ 'INSECURE_THRESHOLD_512',
+ 'SECURE_THRESHOLD_8192',
+] as const satisfies ReadonlyArray
+
+export interface VerifiableEncryptionResult {
+ encryptedData: Uint8Array
+ proof: ProofData
+}
+
+export interface EncryptedValueAndPublicInputs {
+ encryptedData: Uint8Array
+ circuitInputs: CircuitInputs
+}
diff --git a/packages/enclave-sdk/src/event-listener.ts b/packages/enclave-sdk/src/events/event-listener.ts
similarity index 75%
rename from packages/enclave-sdk/src/event-listener.ts
rename to packages/enclave-sdk/src/events/event-listener.ts
index 949c86bf4e..8d338590af 100644
--- a/packages/enclave-sdk/src/event-listener.ts
+++ b/packages/enclave-sdk/src/events/event-listener.ts
@@ -5,34 +5,68 @@
// or FITNESS FOR A PARTICULAR PURPOSE.
import { type Abi, type Log, type PublicClient } from 'viem'
+import { CiphernodeRegistryOwnable__factory, Enclave__factory } from '@enclave-e3/contracts/types'
import {
+ EnclaveEventType,
type AllEventTypes,
type EnclaveEvent,
type EnclaveEventData,
- type EnclaveEventType,
+ type EnclaveEventType as EnclaveEventTypeT,
type EventCallback,
type EventListenerConfig,
type RegistryEventData,
type RegistryEventType,
type SDKEventEmitter,
} from './types'
-import { SDKError, sleep } from './utils'
+import type { ContractAddresses } from '../contracts/types'
+import { SDKError, sleep } from '../utils'
+
+export interface EventListenerOptions {
+ publicClient: PublicClient
+ contracts: ContractAddresses
+ config?: EventListenerConfig
+}
export class EventListener implements SDKEventEmitter {
private listeners: Map> = new Map()
private activeWatchers: Map void> = new Map()
private isPolling = false
private lastBlockNumber: bigint = BigInt(0)
+ private publicClient: PublicClient
+ private contracts: ContractAddresses
+ private config: EventListenerConfig
+
+ constructor(options: EventListenerOptions) {
+ this.publicClient = options.publicClient
+ this.contracts = options.contracts
+ this.config = options.config || {}
+ }
+
+ private resolveContract(eventType: AllEventTypes): { address: `0x${string}`; abi: Abi } {
+ const isEnclaveEvent = Object.values(EnclaveEventType).includes(eventType as EnclaveEventType)
+ return {
+ address: isEnclaveEvent ? this.contracts.enclave : this.contracts.ciphernodeRegistry,
+ abi: isEnclaveEvent ? Enclave__factory.abi : CiphernodeRegistryOwnable__factory.abi,
+ }
+ }
- constructor(
- private publicClient: PublicClient,
- private config: EventListenerConfig = {},
- ) {}
+ public onEnclaveEvent(eventType: T, callback: EventCallback): void {
+ const { address, abi } = this.resolveContract(eventType)
+ void this.watchContractEvent(address, eventType, abi, callback)
+ }
+
+ public once(type: T, callback: EventCallback): void {
+ const handler: EventCallback = (event) => {
+ this.off(type, handler)
+ const prom = callback(event)
+ if (prom) {
+ prom.catch((e) => console.error(e))
+ }
+ }
+ this.onEnclaveEvent(type, handler)
+ }
- /**
- * Listen to specific contract events
- */
public async watchContractEvent(
address: `0x${string}`,
eventType: T,
@@ -40,21 +74,16 @@ export class EventListener implements SDKEventEmitter {
callback: EventCallback,
): Promise {
const watcherKey = `${address}:${eventType}`
- console.log(`watchContractEvent: ${watcherKey}`)
if (!this.listeners.has(eventType)) {
this.listeners.set(eventType, new Set())
}
- console.log('Added callback')
this.listeners.get(eventType)!.add(callback as EventCallback)
// eslint-disable-next-line @typescript-eslint/no-this-alias
const emitter = this
- // If we don't have an active watcher for this event, create one
if (!this.activeWatchers.has(watcherKey)) {
- console.log('Adding active watcher for ' + watcherKey)
-
try {
const unwatch = this.publicClient.watchContractEvent({
address,
@@ -64,13 +93,10 @@ export class EventListener implements SDKEventEmitter {
onLogs(logs: Log[]) {
for (let i = 0; i < logs.length; i++) {
const log = logs[i]
- if (!log) {
- console.log('warning: Log was falsy when a log was expected!')
- break
- }
+ if (!log) break
const event: EnclaveEvent = {
type: eventType,
- data: (log as unknown as { args: unknown }).args as T extends EnclaveEventType
+ data: (log as unknown as { args: unknown }).args as T extends EnclaveEventTypeT
? EnclaveEventData[T]
: T extends RegistryEventType
? RegistryEventData[T]
@@ -80,9 +106,7 @@ export class EventListener implements SDKEventEmitter {
blockNumber: log.blockNumber ?? BigInt(0),
transactionHash: log.transactionHash ?? '0x',
}
- console.log('Created event, now emitting event...')
emitter.emit(event)
- console.log('Event emitted')
}
},
})
@@ -94,9 +118,6 @@ export class EventListener implements SDKEventEmitter {
}
}
- /**
- * Listen to all logs from a specific address
- */
public async watchLogs(address: `0x${string}`, callback: (log: Log) => void): Promise {
const watcherKey = `logs:${address}`
@@ -118,9 +139,6 @@ export class EventListener implements SDKEventEmitter {
}
}
- /**
- * Start polling for historical events
- */
public async startPolling(): Promise {
if (this.isPolling) return
@@ -128,7 +146,6 @@ export class EventListener implements SDKEventEmitter {
try {
this.lastBlockNumber = await this.publicClient.getBlockNumber()
-
void this.pollForEvents()
} catch (error) {
this.isPolling = false
@@ -136,41 +153,26 @@ export class EventListener implements SDKEventEmitter {
}
}
- /**
- * Stop polling for events
- */
public stopPolling(): void {
this.isPolling = false
}
- /**
- * Get historical events
- */
- public async getHistoricalEvents(
- address: `0x${string}`,
- eventType: AllEventTypes,
- abi: Abi,
- fromBlock?: bigint,
- toBlock?: bigint,
- ): Promise {
+ public async getHistoricalEvents(eventType: AllEventTypes, fromBlock?: bigint, toBlock?: bigint): Promise {
+ const { address, abi } = this.resolveContract(eventType)
+
try {
- const logs = await this.publicClient.getContractEvents({
+ return await this.publicClient.getContractEvents({
address,
abi,
eventName: eventType as string,
fromBlock: fromBlock || this.config.fromBlock,
toBlock: toBlock || this.config.toBlock,
})
-
- return logs
} catch (error) {
throw new SDKError(`Failed to get historical events: ${error}`, 'HISTORICAL_EVENTS_FAILED')
}
}
- /**
- * SDKEventEmitter implementation
- */
public on(eventType: T, callback: EventCallback): void {
if (!this.listeners.has(eventType)) {
this.listeners.set(eventType, new Set())
@@ -184,7 +186,6 @@ export class EventListener implements SDKEventEmitter {
callbacks.delete(callback as EventCallback)
if (callbacks.size === 0) {
this.listeners.delete(eventType)
- // Find and stop corresponding watchers
const watchersToRemove: string[] = []
this.activeWatchers.forEach((unwatch, key) => {
if (key.endsWith(`:${eventType}`)) {
@@ -202,12 +203,9 @@ export class EventListener implements SDKEventEmitter {
}
public emit(event: EnclaveEvent): void {
- console.log('emit() called for ' + event.type)
const callbacks = this.listeners.get(event.type)
if (callbacks) {
- console.log('Have ' + callbacks.size + ' callbacks')
callbacks.forEach((callback) => {
- console.log('Running callback...')
try {
void (callback as EventCallback)(event)
} catch (error) {
@@ -217,13 +215,9 @@ export class EventListener implements SDKEventEmitter {
}
}
- /**
- * Clean up all listeners and watchers
- */
public cleanup(): void {
this.stopPolling()
- // Stop all active watchers
this.activeWatchers.forEach((unwatch) => {
try {
unwatch()
@@ -232,8 +226,6 @@ export class EventListener implements SDKEventEmitter {
}
})
this.activeWatchers.clear()
-
- // Clear all listeners
this.listeners.clear()
}
diff --git a/packages/enclave-sdk/src/events/index.ts b/packages/enclave-sdk/src/events/index.ts
new file mode 100644
index 0000000000..5b2d35a021
--- /dev/null
+++ b/packages/enclave-sdk/src/events/index.ts
@@ -0,0 +1,30 @@
+// SPDX-License-Identifier: LGPL-3.0-only
+//
+// This file is provided WITHOUT ANY WARRANTY;
+// without even the implied warranty of MERCHANTABILITY
+// or FITNESS FOR A PARTICULAR PURPOSE.
+
+export { EventListener } from './event-listener'
+export type { EventListenerOptions } from './event-listener'
+
+export { EnclaveEventType, RegistryEventType } from './types'
+
+export type {
+ AllEventTypes,
+ EnclaveEvent,
+ EventCallback,
+ EventFilter,
+ SDKEventEmitter,
+ EventListenerConfig,
+ E3RequestedData,
+ E3ActivatedData,
+ CiphertextOutputPublishedData,
+ PlaintextOutputPublishedData,
+ CiphernodeAddedData,
+ CiphernodeRemovedData,
+ CommitteeRequestedData,
+ CommitteePublishedData,
+ CommitteeFinalizedData,
+ EnclaveEventData,
+ RegistryEventData,
+} from './types'
diff --git a/packages/enclave-sdk/src/events/types.ts b/packages/enclave-sdk/src/events/types.ts
new file mode 100644
index 0000000000..bd82143c5c
--- /dev/null
+++ b/packages/enclave-sdk/src/events/types.ts
@@ -0,0 +1,156 @@
+// SPDX-License-Identifier: LGPL-3.0-only
+//
+// This file is provided WITHOUT ANY WARRANTY;
+// without even the implied warranty of MERCHANTABILITY
+// or FITNESS FOR A PARTICULAR PURPOSE.
+
+import type { Log } from 'viem'
+
+export enum EnclaveEventType {
+ E3_REQUESTED = 'E3Requested',
+ CIPHERTEXT_OUTPUT_PUBLISHED = 'CiphertextOutputPublished',
+ PLAINTEXT_OUTPUT_PUBLISHED = 'PlaintextOutputPublished',
+ E3_PROGRAM_ENABLED = 'E3ProgramEnabled',
+ E3_PROGRAM_DISABLED = 'E3ProgramDisabled',
+ ENCRYPTION_SCHEME_ENABLED = 'EncryptionSchemeEnabled',
+ ENCRYPTION_SCHEME_DISABLED = 'EncryptionSchemeDisabled',
+ CIPHERNODE_REGISTRY_SET = 'CiphernodeRegistrySet',
+ MAX_DURATION_SET = 'MaxDurationSet',
+ ALLOWED_E3_PROGRAMS_PARAMS_SET = 'AllowedE3ProgramsParamsSet',
+ OWNERSHIP_TRANSFERRED = 'OwnershipTransferred',
+ INITIALIZED = 'Initialized',
+}
+
+export enum RegistryEventType {
+ COMMITTEE_REQUESTED = 'CommitteeRequested',
+ COMMITTEE_PUBLISHED = 'CommitteePublished',
+ COMMITTEE_FINALIZED = 'CommitteeFinalized',
+ ENCLAVE_SET = 'EnclaveSet',
+ OWNERSHIP_TRANSFERRED = 'OwnershipTransferred',
+ INITIALIZED = 'Initialized',
+}
+
+export type AllEventTypes = EnclaveEventType | RegistryEventType
+
+export interface E3RequestedData {
+ e3Id: bigint
+ e3: {
+ seed: bigint
+ threshold: readonly [number, number]
+ requestBlock: bigint
+ inputWindow: readonly [bigint, bigint]
+ encryptionSchemeId: string
+ e3Program: string
+ e3ProgramParams: string
+ decryptionVerifier: string
+ committeePublicKey: string
+ ciphertextOutput: string
+ plaintextOutput: string
+ }
+ filter: string
+ e3Program: string
+}
+
+export interface E3ActivatedData {
+ e3Id: bigint
+ expiration: bigint
+ committeePublicKey: string
+}
+
+export interface CiphertextOutputPublishedData {
+ e3Id: bigint
+ ciphertextOutput: string
+}
+
+export interface PlaintextOutputPublishedData {
+ e3Id: bigint
+ plaintextOutput: string
+}
+
+export interface CiphernodeAddedData {
+ node: string
+ index: bigint
+ numNodes: bigint
+ size: bigint
+}
+
+export interface CiphernodeRemovedData {
+ node: string
+ index: bigint
+ numNodes: bigint
+ size: bigint
+}
+
+export interface CommitteeRequestedData {
+ e3Id: bigint
+ seed: bigint
+ threshold: [bigint, bigint]
+ requestBlock: bigint
+ committeeDeadline: bigint
+}
+
+export interface CommitteePublishedData {
+ e3Id: bigint
+ nodes: string[]
+ publicKey: string
+}
+
+export interface CommitteeFinalizedData {
+ e3Id: bigint
+ nodes: string[]
+}
+
+export interface EnclaveEventData {
+ [EnclaveEventType.E3_REQUESTED]: E3RequestedData
+ [EnclaveEventType.CIPHERTEXT_OUTPUT_PUBLISHED]: CiphertextOutputPublishedData
+ [EnclaveEventType.PLAINTEXT_OUTPUT_PUBLISHED]: PlaintextOutputPublishedData
+ [EnclaveEventType.E3_PROGRAM_ENABLED]: { e3Program: string }
+ [EnclaveEventType.E3_PROGRAM_DISABLED]: { e3Program: string }
+ [EnclaveEventType.ENCRYPTION_SCHEME_ENABLED]: { encryptionSchemeId: string }
+ [EnclaveEventType.ENCRYPTION_SCHEME_DISABLED]: { encryptionSchemeId: string }
+ [EnclaveEventType.CIPHERNODE_REGISTRY_SET]: { ciphernodeRegistry: string }
+ [EnclaveEventType.MAX_DURATION_SET]: { maxDuration: bigint }
+ [EnclaveEventType.ALLOWED_E3_PROGRAMS_PARAMS_SET]: { e3ProgramParams: string[] }
+ [EnclaveEventType.OWNERSHIP_TRANSFERRED]: { previousOwner: string; newOwner: string }
+ [EnclaveEventType.INITIALIZED]: { version: bigint }
+}
+
+export interface RegistryEventData {
+ [RegistryEventType.COMMITTEE_REQUESTED]: CommitteeRequestedData
+ [RegistryEventType.COMMITTEE_PUBLISHED]: CommitteePublishedData
+ [RegistryEventType.COMMITTEE_FINALIZED]: CommitteeFinalizedData
+ [RegistryEventType.ENCLAVE_SET]: { enclave: string }
+ [RegistryEventType.OWNERSHIP_TRANSFERRED]: { previousOwner: string; newOwner: string }
+ [RegistryEventType.INITIALIZED]: { version: bigint }
+}
+
+export interface EnclaveEvent {
+ type: T
+ data: T extends EnclaveEventType ? EnclaveEventData[T] : T extends RegistryEventType ? RegistryEventData[T] : unknown
+ log: Log
+ timestamp: Date
+ blockNumber: bigint
+ transactionHash: string
+}
+
+export type EventCallback = (event: EnclaveEvent) => void | Promise
+
+export interface EventFilter {
+ address?: `0x${string}`
+ fromBlock?: bigint
+ toBlock?: bigint
+ args?: Partial
+}
+
+export interface SDKEventEmitter {
+ on(eventType: T, callback: EventCallback): void
+ off(eventType: T, callback: EventCallback): void
+ emit(event: EnclaveEvent): void
+}
+
+export interface EventListenerConfig {
+ fromBlock?: bigint
+ toBlock?: bigint
+ polling?: boolean
+ pollingInterval?: number
+}
diff --git a/packages/enclave-sdk/src/index.ts b/packages/enclave-sdk/src/index.ts
index 671847ebb3..34ca248550 100644
--- a/packages/enclave-sdk/src/index.ts
+++ b/packages/enclave-sdk/src/index.ts
@@ -8,21 +8,35 @@
export { EnclaveSDK } from './enclave-sdk'
// Core classes
-export { EventListener } from './event-listener'
-export { ContractClient } from './contract-client'
+export { EventListener } from './events/event-listener'
+export { ContractClient } from './contracts/contract-client'
+export type { ContractClientConfig } from './contracts/contract-client'
+export type { EventListenerOptions } from './events/event-listener'
-// Types and interfaces
+// Standalone encryption functions
+export {
+ getThresholdBfvParamsSet,
+ generatePublicKey,
+ computePublicKeyCommitment,
+ encryptNumber,
+ encryptVector,
+ encryptNumberAndGenInputs,
+ encryptNumberAndGenProof,
+ encryptVectorAndGenInputs,
+ encryptVectorAndGenProof,
+} from './encryption/encrypt'
+
+// Types and interfaces (re-exported from sub-modules via types.ts)
export type {
- E3,
SDKConfig,
+ ContractAddresses,
+ E3,
EventListenerConfig,
- ContractInstances,
EventFilter,
EventCallback,
SDKEventEmitter,
AllEventTypes,
EnclaveEvent,
- // Event data types
E3RequestedData,
E3ActivatedData,
CiphertextOutputPublishedData,
@@ -37,10 +51,11 @@ export type {
BfvParams,
VerifiableEncryptionResult,
EncryptedValueAndPublicInputs,
+ ThresholdBfvParamsPresetName,
} from './types'
-// enums and constants
-export { EnclaveEventType, RegistryEventType, ThresholdBfvParamsPresetName, ThresholdBfvParamsPresetNames } from './types'
+// Enums and constants
+export { EnclaveEventType, RegistryEventType, ThresholdBfvParamsPresetNames } from './types'
// Export utilities
export {
@@ -54,7 +69,6 @@ export {
generateEventId,
sleep,
getCurrentTimestamp,
- // BFV and E3 utilities
DEFAULT_COMPUTE_PROVIDER_PARAMS,
DEFAULT_E3_CONFIG,
encodeBfvParams,
diff --git a/packages/enclave-sdk/src/types.ts b/packages/enclave-sdk/src/types.ts
index 5ab38010e2..413e35ed80 100644
--- a/packages/enclave-sdk/src/types.ts
+++ b/packages/enclave-sdk/src/types.ts
@@ -4,300 +4,43 @@
// without even the implied warranty of MERCHANTABILITY
// or FITNESS FOR A PARTICULAR PURPOSE.
-import type { Log, PublicClient, WalletClient } from 'viem'
-import type { ProofData } from '@aztec/bb.js'
-import type { CiphernodeRegistryOwnable, Enclave, MockCiphernodeRegistry, MockUSDC, EnclaveToken } from '@enclave-e3/contracts/types'
+import type { Chain, PublicClient, WalletClient } from 'viem'
+import type { ContractAddresses } from './contracts/types'
+import type { ThresholdBfvParamsPresetName } from './encryption/types'
+
+// Re-export all sub-module types for backward compatibility
+export type { BfvParams, ThresholdBfvParamsPresetName, VerifiableEncryptionResult, EncryptedValueAndPublicInputs } from './encryption/types'
+
+export { ThresholdBfvParamsPresetNames } from './encryption/types'
+
+export type { ContractAddresses, E3 } from './contracts/types'
+
+export { EnclaveEventType, RegistryEventType } from './events/types'
+
+export type {
+ AllEventTypes,
+ EnclaveEvent,
+ EventCallback,
+ EventFilter,
+ SDKEventEmitter,
+ EventListenerConfig,
+ E3RequestedData,
+ E3ActivatedData,
+ CiphertextOutputPublishedData,
+ PlaintextOutputPublishedData,
+ CiphernodeAddedData,
+ CiphernodeRemovedData,
+ CommitteeRequestedData,
+ CommitteePublishedData,
+ CommitteeFinalizedData,
+ EnclaveEventData,
+ RegistryEventData,
+} from './events/types'
-import type { CircuitInputs } from './greco'
-
-/**
- * SDK configuration
- */
export interface SDKConfig {
- /**
- * The public client to use to interact with the blockchain
- */
publicClient: PublicClient
-
- /**
- * The wallet client to use to send/sign transactions
- */
walletClient?: WalletClient
-
- /**
- * The Enclave contracts
- */
- contracts: {
- /**
- * The Enclave contract address
- */
- enclave: `0x${string}`
-
- /**
- * The CiphernodeRegistry contract address
- */
- ciphernodeRegistry: `0x${string}`
-
- /**
- * The FeeToken contract address
- */
- feeToken: `0x${string}`
- }
-
- /**
- * The chain ID to which the contracts are deployed
- */
- chainId?: number
-
- /**
- * The threshold BFV parameters preset name to use for the Enclave requests
- */
+ contracts: ContractAddresses
+ chain?: Chain
thresholdBfvParamsPresetName: ThresholdBfvParamsPresetName
}
-
-export interface EventListenerConfig {
- fromBlock?: bigint
- toBlock?: bigint
- polling?: boolean
- pollingInterval?: number
-}
-
-export interface ContractInstances {
- enclave: Enclave
- ciphernodeRegistry: CiphernodeRegistryOwnable | MockCiphernodeRegistry
- feeToken: EnclaveToken | MockUSDC
-}
-
-// Unified Event System
-export enum EnclaveEventType {
- // E3 Lifecycle Events
- E3_REQUESTED = 'E3Requested',
- CIPHERTEXT_OUTPUT_PUBLISHED = 'CiphertextOutputPublished',
- PLAINTEXT_OUTPUT_PUBLISHED = 'PlaintextOutputPublished',
-
- // E3 Program Management
- E3_PROGRAM_ENABLED = 'E3ProgramEnabled',
- E3_PROGRAM_DISABLED = 'E3ProgramDisabled',
-
- // Encryption Scheme Management
- ENCRYPTION_SCHEME_ENABLED = 'EncryptionSchemeEnabled',
- ENCRYPTION_SCHEME_DISABLED = 'EncryptionSchemeDisabled',
-
- // Configuration
- CIPHERNODE_REGISTRY_SET = 'CiphernodeRegistrySet',
- MAX_DURATION_SET = 'MaxDurationSet',
- ALLOWED_E3_PROGRAMS_PARAMS_SET = 'AllowedE3ProgramsParamsSet',
-
- // Ownership
- OWNERSHIP_TRANSFERRED = 'OwnershipTransferred',
- INITIALIZED = 'Initialized',
-}
-
-export enum RegistryEventType {
- // Committee Management
- COMMITTEE_REQUESTED = 'CommitteeRequested',
- COMMITTEE_PUBLISHED = 'CommitteePublished',
- COMMITTEE_FINALIZED = 'CommitteeFinalized',
-
- // Configuration
- ENCLAVE_SET = 'EnclaveSet',
-
- // Ownership
- OWNERSHIP_TRANSFERRED = 'OwnershipTransferred',
- INITIALIZED = 'Initialized',
-}
-
-// Union type for all events
-export type AllEventTypes = EnclaveEventType | RegistryEventType
-
-// Event data interfaces based on TypeChain types
-export interface E3 {
- seed: bigint
- threshold: readonly [number, number]
- requestBlock: bigint
- inputWindow: readonly [bigint, bigint]
- encryptionSchemeId: string
- e3Program: string
- e3ProgramParams: string
- decryptionVerifier: string
- committeePublicKey: string
- ciphertextOutput: string
- plaintextOutput: string
-}
-
-export interface E3RequestedData {
- e3Id: bigint
- e3: E3
- filter: string
- e3Program: string
-}
-
-export interface E3ActivatedData {
- e3Id: bigint
- expiration: bigint
- committeePublicKey: string
-}
-
-export interface CiphertextOutputPublishedData {
- e3Id: bigint
- ciphertextOutput: string
-}
-
-export interface PlaintextOutputPublishedData {
- e3Id: bigint
- plaintextOutput: string
-}
-
-export interface CiphernodeAddedData {
- node: string
- index: bigint
- numNodes: bigint
- size: bigint
-}
-
-export interface CiphernodeRemovedData {
- node: string
- index: bigint
- numNodes: bigint
- size: bigint
-}
-
-export interface CommitteeRequestedData {
- e3Id: bigint
- seed: bigint
- threshold: [bigint, bigint]
- requestBlock: bigint
- committeeDeadline: bigint
-}
-
-export interface CommitteePublishedData {
- e3Id: bigint
- nodes: string[]
- publicKey: string
-}
-
-export interface CommitteeFinalizedData {
- e3Id: bigint
- nodes: string[]
-}
-
-// Event data mapping
-export interface EnclaveEventData {
- [EnclaveEventType.E3_REQUESTED]: E3RequestedData
- [EnclaveEventType.CIPHERTEXT_OUTPUT_PUBLISHED]: CiphertextOutputPublishedData
- [EnclaveEventType.PLAINTEXT_OUTPUT_PUBLISHED]: PlaintextOutputPublishedData
- [EnclaveEventType.E3_PROGRAM_ENABLED]: { e3Program: string }
- [EnclaveEventType.E3_PROGRAM_DISABLED]: { e3Program: string }
- [EnclaveEventType.ENCRYPTION_SCHEME_ENABLED]: { encryptionSchemeId: string }
- [EnclaveEventType.ENCRYPTION_SCHEME_DISABLED]: { encryptionSchemeId: string }
- [EnclaveEventType.CIPHERNODE_REGISTRY_SET]: { ciphernodeRegistry: string }
- [EnclaveEventType.MAX_DURATION_SET]: { maxDuration: bigint }
- [EnclaveEventType.ALLOWED_E3_PROGRAMS_PARAMS_SET]: {
- e3ProgramParams: string[]
- }
- [EnclaveEventType.OWNERSHIP_TRANSFERRED]: {
- previousOwner: string
- newOwner: string
- }
- [EnclaveEventType.INITIALIZED]: { version: bigint }
-}
-
-export interface RegistryEventData {
- [RegistryEventType.COMMITTEE_REQUESTED]: CommitteeRequestedData
- [RegistryEventType.COMMITTEE_PUBLISHED]: CommitteePublishedData
- [RegistryEventType.COMMITTEE_FINALIZED]: CommitteeFinalizedData
- [RegistryEventType.ENCLAVE_SET]: { enclave: string }
- [RegistryEventType.OWNERSHIP_TRANSFERRED]: {
- previousOwner: string
- newOwner: string
- }
- [RegistryEventType.INITIALIZED]: { version: bigint }
-}
-
-// Generic event structure
-export interface EnclaveEvent {
- type: T
- data: T extends EnclaveEventType ? EnclaveEventData[T] : T extends RegistryEventType ? RegistryEventData[T] : unknown
- log: Log
- timestamp: Date
- blockNumber: bigint
- transactionHash: string
-}
-
-export type EventCallback = (event: EnclaveEvent) => void | Promise
-
-export interface EventFilter {
- address?: `0x${string}`
- fromBlock?: bigint
- toBlock?: bigint
- args?: Partial
-}
-
-export interface SDKEventEmitter {
- on(eventType: T, callback: EventCallback): void
- off(eventType: T, callback: EventCallback): void
- emit(event: EnclaveEvent): void
-}
-
-/**
- * Result of verifiable encryption using BFV
- */
-export interface VerifiableEncryptionResult {
- /**
- * The encrypted data
- */
- encryptedData: Uint8Array
- /**
- * The proof generated by Greco
- */
- proof: ProofData
-}
-
-/**
- * BFV parameters for an Enclave program request
- * Example for BFV
- * 512, // degree
- * 10, // plaintext_modulus
- * 0xffffee001, // moduli
- * 0xffffc4001, // moduli
- */
-export interface BfvParams {
- /**
- * The degree of the polynomial
- */
- degree: number
- /**
- * The plaintext modulus
- */
- plaintextModulus: bigint
- /**
- * The moduli
- */
- moduli: bigint[]
- /**
- * error1
- */
- error1Variance: string | undefined
-}
-
-export type ThresholdBfvParamsPresetName = 'INSECURE_THRESHOLD_512' | 'SECURE_THRESHOLD_8192'
-
-export const ThresholdBfvParamsPresetNames = [
- 'INSECURE_THRESHOLD_512',
- 'SECURE_THRESHOLD_8192',
-] as const satisfies ReadonlyArray
-
-/**
- * The result of encrypting a value and generating a proof
- */
-export interface EncryptedValueAndPublicInputs {
- /**
- * The encrypted data
- */
- encryptedData: Uint8Array
-
- /**
- * The public inputs for the proof
- */
- circuitInputs: CircuitInputs
-}
diff --git a/packages/enclave-sdk/tests/sdk.test.ts b/packages/enclave-sdk/tests/sdk.test.ts
index c3b64ada36..0133a12167 100644
--- a/packages/enclave-sdk/tests/sdk.test.ts
+++ b/packages/enclave-sdk/tests/sdk.test.ts
@@ -8,12 +8,14 @@ import { describe, expect, it } from 'vitest'
import { EnclaveSDK } from '../src/enclave-sdk'
import { zeroAddress } from 'viem'
+import { hardhat } from 'viem/chains'
+import { generatePublicKey, encryptNumber as standaloneEncryptNumber, encryptVector as standaloneEncryptVector } from '../src/encryption'
describe('encryptNumber', () => {
describe('trbfv', () => {
// create SDK with default config
const sdk = EnclaveSDK.create({
- chainId: 31337,
+ chain: hardhat,
contracts: {
enclave: zeroAddress,
ciphernodeRegistry: zeroAddress,
@@ -58,4 +60,20 @@ describe('encryptNumber', () => {
expect(value.proof).to.be.an.instanceOf(Object)
}, 9999999)
})
+
+ describe('standalone encryption (no blockchain setup)', () => {
+ it('should encrypt a number using standalone functions', async () => {
+ const pk = await generatePublicKey('INSECURE_THRESHOLD_512')
+ const ct = await standaloneEncryptNumber(10n, pk, 'INSECURE_THRESHOLD_512')
+ expect(ct).to.be.an.instanceof(Uint8Array)
+ expect(ct.length).to.equal(9_242)
+ })
+
+ it('should encrypt a vector using standalone functions', async () => {
+ const pk = await generatePublicKey('INSECURE_THRESHOLD_512')
+ const ct = await standaloneEncryptVector(new BigUint64Array([1n, 2n]), pk, 'INSECURE_THRESHOLD_512')
+ expect(ct).to.be.an.instanceof(Uint8Array)
+ expect(ct.length).to.equal(9_242)
+ })
+ })
})
diff --git a/packages/enclave-sdk/tsup.config.js b/packages/enclave-sdk/tsup.config.js
index 0849fad7aa..1ea0f71191 100644
--- a/packages/enclave-sdk/tsup.config.js
+++ b/packages/enclave-sdk/tsup.config.js
@@ -7,9 +7,12 @@
import { defineConfig } from 'tsup'
import { baseConfig } from '@enclave-e3/config/tsup'
+const entry = ['src/index.ts', 'src/encryption/index.ts', 'src/contracts/index.ts', 'src/events/index.ts']
+
export default defineConfig([
{
...baseConfig,
+ entry,
include: ['./src/**/*.ts'],
format: ['esm'],
outExtension: () => ({
@@ -23,6 +26,7 @@ export default defineConfig([
},
{
...baseConfig,
+ entry,
include: ['./src/**/*.ts'],
format: ['cjs'],
outExtension: () => ({
diff --git a/templates/default/server/index.ts b/templates/default/server/index.ts
index c477cc70e8..97ced12f93 100644
--- a/templates/default/server/index.ts
+++ b/templates/default/server/index.ts
@@ -7,6 +7,7 @@
import express, { Request, Response } from 'express'
import { EnclaveSDK, RegistryEventType, CommitteePublishedData } from '@enclave-e3/sdk'
import { Log, PublicClient } from 'viem'
+import { hardhat } from 'viem/chains'
import { handleTestInteraction } from './testHandler'
import { getCheckedEnvVars } from './utils'
import { callFheRunner } from './runner'
@@ -25,11 +26,7 @@ interface E3Session {
const e3Sessions = new Map()
async function createPrivateSDK() {
- const { CHAIN_ID, PRIVATE_KEY, CIPHERNODE_REGISTRY_CONTRACT, ENCLAVE_CONTRACT, FEE_TOKEN_CONTRACT, RPC_URL } = getCheckedEnvVars()
-
- if (!isSupportedChain(CHAIN_ID)) {
- throw new Error(`Unsupported CHAIN_ID: ${CHAIN_ID}`)
- }
+ const { PRIVATE_KEY, CIPHERNODE_REGISTRY_CONTRACT, ENCLAVE_CONTRACT, FEE_TOKEN_CONTRACT, RPC_URL } = getCheckedEnvVars()
const sdk = EnclaveSDK.create({
rpcUrl: RPC_URL,
@@ -39,11 +36,10 @@ async function createPrivateSDK() {
ciphernodeRegistry: CIPHERNODE_REGISTRY_CONTRACT as `0x${string}`,
feeToken: FEE_TOKEN_CONTRACT as `0x${string}`,
},
- chainId: CHAIN_ID,
+ chain: hardhat,
thresholdBfvParamsPresetName: 'INSECURE_THRESHOLD_512',
})
- await sdk.initialize()
return sdk
}
@@ -225,10 +221,6 @@ function isValidHexString(value: string): value is `0x${string}` {
return value.startsWith('0x') && /^0x[a-fA-F0-9]*$/.test(value)
}
-function isSupportedChain(value: any): value is keyof typeof EnclaveSDK.chains {
- return value in EnclaveSDK.chains
-}
-
async function handleWebhookRequest(req: Request, res: Response) {
try {
console.log('📨 Webhook received:')
diff --git a/templates/default/tests/integration.spec.ts b/templates/default/tests/integration.spec.ts
index d80e68a3a7..05cca55a46 100644
--- a/templates/default/tests/integration.spec.ts
+++ b/templates/default/tests/integration.spec.ts
@@ -137,7 +137,7 @@ describe('Integration', () => {
const store = new Map()
const sdk = EnclaveSDK.create({
- chainId: 31337,
+ chain: hardhat,
contracts: {
enclave: contracts.enclave,
ciphernodeRegistry: contracts.ciphernodeRegistry,
From fd41569eda873931935a1e6765b975f19b148cd5 Mon Sep 17 00:00:00 2001
From: ctrlc03 <93448202+ctrlc03@users.noreply.github.com>
Date: Thu, 5 Mar 2026 17:15:39 +0000
Subject: [PATCH 2/3] chore: add getE3Quote and status functions
---
docs/pages/sdk.mdx | 14 ++---
.../packages/crisp-sdk/tests/vote.test.ts | 3 +-
packages/enclave-sdk/README.md | 18 ++++--
.../src/contracts/contract-client.ts | 60 +++++++++++++++----
packages/enclave-sdk/src/contracts/index.ts | 3 +-
packages/enclave-sdk/src/contracts/types.ts | 39 ++++++++++++
packages/enclave-sdk/src/enclave-sdk.ts | 15 ++++-
packages/enclave-sdk/src/index.ts | 3 +-
packages/enclave-sdk/src/types.ts | 3 +-
templates/default/server/index.ts | 15 +++--
templates/default/server/types.ts | 2 +-
templates/default/tests/integration.spec.ts | 59 ++++++++++--------
12 files changed, 173 insertions(+), 61 deletions(-)
diff --git a/docs/pages/sdk.mdx b/docs/pages/sdk.mdx
index 7cebdb95e9..d2dc71a0c2 100644
--- a/docs/pages/sdk.mdx
+++ b/docs/pages/sdk.mdx
@@ -210,8 +210,8 @@ that wraps or complements the core Enclave SDK.
- **Historical events**: `sdk.getHistoricalEvents(type, fromBlock, toBlock)` fetches logs without a
WebSocket provider; useful for CRON jobs or health checks.
-- **Gas controls**: pass `gasLimit` to `requestE3`, `publishCiphertextOutput`, or other write methods
- if you want fixed limits when interacting with Enclave on L2s.
+- **Gas controls**: pass `gasLimit` to `requestE3`, `publishCiphertextOutput`, or other write
+ methods if you want fixed limits when interacting with Enclave on L2s.
- **Event polling**: `sdk.startEventPolling()` is handy in serverless environments where websockets
are unavailable.
- **Custom transports**: on the server, call `createWalletClient({ account, transport: http(rpc) })`
@@ -221,11 +221,11 @@ that wraps or complements the core Enclave SDK.
## Troubleshooting
-| Symptom | Triage |
-| ------------------------------ | ------------------------------------------------------------------------------------------------------------------------ |
-| `MISSING_PUBLIC_CLIENT` errors | Ensure `createPublicClient` uses an HTTP or WebSocket RPC reachable from the app. |
-| `INVALID_ADDRESS` | Double-check contract addresses passed into the SDK match the current chain. |
+| Symptom | Triage |
+| ------------------------------ | ------------------------------------------------------------------------------------------------------------------------------ |
+| `MISSING_PUBLIC_CLIENT` errors | Ensure `createPublicClient` uses an HTTP or WebSocket RPC reachable from the app. |
+| `INVALID_ADDRESS` | Double-check contract addresses passed into the SDK match the current chain. |
| Hooks never initialize | Confirm `autoConnect` is true, wallet is connected, and React component is inside a wagmi provider that renders on the client. |
-| No events arrive | Switch to a WebSocket RPC or call `sdk.startEventPolling()` so logs are polled over HTTP. |
+| No events arrive | Switch to a WebSocket RPC or call `sdk.startEventPolling()` so logs are polled over HTTP. |
For additional API surface details see `packages/enclave-sdk/README.md` inside this repo.
diff --git a/examples/CRISP/packages/crisp-sdk/tests/vote.test.ts b/examples/CRISP/packages/crisp-sdk/tests/vote.test.ts
index 9b7c2fdb1c..52544a8bd9 100644
--- a/examples/CRISP/packages/crisp-sdk/tests/vote.test.ts
+++ b/examples/CRISP/packages/crisp-sdk/tests/vote.test.ts
@@ -35,8 +35,7 @@ describe('Vote', () => {
json: async () => ({ ciphertext: previousCiphertext }),
}) as Response
- const mockPreviousCiphertextNotFoundResponse = () =>
- ({ ok: false, status: 404 }) as Response
+ const mockPreviousCiphertextNotFoundResponse = () => ({ ok: false, status: 404 }) as Response
beforeEach(() => {
vi.clearAllMocks()
diff --git a/packages/enclave-sdk/README.md b/packages/enclave-sdk/README.md
index be4f668cca..96c19315bf 100644
--- a/packages/enclave-sdk/README.md
+++ b/packages/enclave-sdk/README.md
@@ -8,7 +8,8 @@ real-time event listening, contract interaction methods, and comprehensive error
- **Event-driven architecture**: Listen to smart contract events in real-time
- **Type-safe**: Built with TypeScript and uses generated types from contracts
- **Easy contract interactions**: Simple methods for reading from and writing to contracts
-- **React integration**: Includes React hooks for easy frontend integration (via `@enclave-e3/react`)
+- **React integration**: Includes React hooks for easy frontend integration (via
+ `@enclave-e3/react`)
- **Modular architecture**: Tree-shakeable sub-modules for contracts, events, and encryption
- **Encryption helpers**: Standalone FHE encryption functions with optional ZK proof generation
- **Error handling**: Comprehensive error handling with custom error types
@@ -292,7 +293,8 @@ import type { ContractAddresses, E3 } from '@enclave-e3/sdk/contracts'
import { EventListener, EnclaveEventType, RegistryEventType } from '@enclave-e3/sdk/events'
```
-All sub-module exports are also re-exported from the main `@enclave-e3/sdk` entry point for convenience.
+All sub-module exports are also re-exported from the main `@enclave-e3/sdk` entry point for
+convenience.
## API Reference
@@ -395,7 +397,8 @@ interface SDKConfig {
}
```
-`thresholdBfvParamsPresetName` must be one of: `'INSECURE_THRESHOLD_512'` or `'SECURE_THRESHOLD_8192'`.
+`thresholdBfvParamsPresetName` must be one of: `'INSECURE_THRESHOLD_512'` or
+`'SECURE_THRESHOLD_8192'`.
## Error Handling
@@ -446,9 +449,12 @@ pnpm test
The SDK is organized into a modular architecture with three domain-specific sub-modules:
- **EnclaveSDK** (`enclave-sdk.ts`): Main orchestrator class that delegates to sub-modules
-- **Contracts** (`contracts/`): `ContractClient` for contract read/write operations, type definitions for contract addresses and E3 data
-- **Events** (`events/`): `EventListener` for real-time and historical event subscriptions, typed event enums and data interfaces
-- **Encryption** (`encryption/`): Standalone FHE encryption functions, BFV parameter management, ZK proof generation
+- **Contracts** (`contracts/`): `ContractClient` for contract read/write operations, type
+ definitions for contract addresses and E3 data
+- **Events** (`events/`): `EventListener` for real-time and historical event subscriptions, typed
+ event enums and data interfaces
+- **Encryption** (`encryption/`): Standalone FHE encryption functions, BFV parameter management, ZK
+ proof generation
- **Utils** (`utils.ts`): Helper functions, error classes, encoding utilities
Each sub-module has its own `index.ts` entry point and can be imported independently.
diff --git a/packages/enclave-sdk/src/contracts/contract-client.ts b/packages/enclave-sdk/src/contracts/contract-client.ts
index f7e84aa09c..b22f988790 100644
--- a/packages/enclave-sdk/src/contracts/contract-client.ts
+++ b/packages/enclave-sdk/src/contracts/contract-client.ts
@@ -19,7 +19,7 @@ import {
import { privateKeyToAccount } from 'viem/accounts'
import { CiphernodeRegistryOwnable__factory, Enclave__factory, EnclaveToken__factory } from '@enclave-e3/contracts/types'
-import type { ContractAddresses, E3 } from './types'
+import type { ContractAddresses, E3, E3RequestParams, E3Stage, FailureReason } from './types'
import { SDKError, isValidAddress } from '../utils'
export interface ContractClientConfig {
@@ -132,15 +132,7 @@ export class ContractClient {
}
}
- public async requestE3(params: {
- threshold: [number, number]
- inputWindow: [bigint, bigint]
- e3Program: `0x${string}`
- e3ProgramParams: `0x${string}`
- computeProviderParams: `0x${string}`
- customParams?: `0x${string}`
- gasLimit?: bigint
- }): Promise {
+ public async requestE3(params: E3RequestParams): Promise {
if (!this.walletClient) {
throw new SDKError('Wallet client required for write operations', 'NO_WALLET')
}
@@ -221,6 +213,41 @@ export class ContractClient {
}
}
+ public async getE3Quote(requestParams: E3RequestParams): Promise {
+ try {
+ return this.publicClient.readContract({
+ address: this.contracts.enclave,
+ abi: Enclave__factory.abi,
+ functionName: 'getE3Quote',
+ args: [
+ {
+ threshold: requestParams.threshold,
+ inputWindow: requestParams.inputWindow,
+ e3Program: requestParams.e3Program,
+ e3ProgramParams: requestParams.e3ProgramParams,
+ computeProviderParams: requestParams.computeProviderParams,
+ customParams: requestParams.customParams || '0x',
+ },
+ ],
+ })
+ } catch (error) {
+ throw new SDKError(`Failed to get E3 quote: ${error}`, 'GET_E3_QUOTE_FAILED')
+ }
+ }
+
+ public async getFailureReason(e3Id: bigint): Promise {
+ try {
+ return this.publicClient.readContract({
+ address: this.contracts.enclave,
+ abi: Enclave__factory.abi,
+ functionName: 'getFailureReason',
+ args: [e3Id],
+ })
+ } catch (error) {
+ throw new SDKError(`Failed to get failure reason: ${error}`, 'GET_FAILURE_REASON_FAILED')
+ }
+ }
+
public async getE3PublicKey(e3Id: bigint): Promise<`0x${string}`> {
try {
const result: `0x${string}` = await this.publicClient.readContract({
@@ -236,6 +263,19 @@ export class ContractClient {
}
}
+ public async getE3Stage(e3Id: bigint): Promise {
+ try {
+ return this.publicClient.readContract({
+ address: this.contracts.enclave,
+ abi: Enclave__factory.abi,
+ functionName: 'getE3Stage',
+ args: [e3Id],
+ })
+ } catch (error) {
+ throw new SDKError(`Failed to get E3 stage: ${error}`, 'GET_E3_STAGE_FAILED')
+ }
+ }
+
public async estimateGas(
functionName: string,
args: readonly unknown[],
diff --git a/packages/enclave-sdk/src/contracts/index.ts b/packages/enclave-sdk/src/contracts/index.ts
index baa6bd4728..aef581427c 100644
--- a/packages/enclave-sdk/src/contracts/index.ts
+++ b/packages/enclave-sdk/src/contracts/index.ts
@@ -6,4 +6,5 @@
export { ContractClient } from './contract-client'
export type { ContractClientConfig } from './contract-client'
-export type { ContractAddresses, E3 } from './types'
+export type { ContractAddresses, E3, E3RequestParams } from './types'
+export { E3Stage, FailureReason } from './types'
diff --git a/packages/enclave-sdk/src/contracts/types.ts b/packages/enclave-sdk/src/contracts/types.ts
index 804ee38b50..6bdf06fc53 100644
--- a/packages/enclave-sdk/src/contracts/types.ts
+++ b/packages/enclave-sdk/src/contracts/types.ts
@@ -23,3 +23,42 @@ export interface E3 {
ciphertextOutput: string
plaintextOutput: string
}
+
+export interface RequestParams {
+ gasLimit?: bigint
+}
+
+export interface E3RequestParams extends RequestParams {
+ threshold: readonly [number, number]
+ inputWindow: readonly [bigint, bigint]
+ e3Program: `0x${string}`
+ e3ProgramParams: `0x${string}`
+ computeProviderParams: `0x${string}`
+ customParams?: `0x${string}`
+}
+
+export enum E3Stage {
+ None,
+ Requested,
+ CommitteeFinalized,
+ KeyPublished,
+ CiphertextReady,
+ Complete,
+ Failed,
+}
+
+export enum FailureReason {
+ None,
+ CommitteeFormationTimeout,
+ InsufficientCommitteeMembers,
+ DKGTimeout,
+ DKGInvalidShares,
+ NoInputsReceived,
+ ComputeTimeout,
+ ComputeProviderExpired,
+ ComputeProviderFailed,
+ RequesterCancelled,
+ DecryptionTimeout,
+ DecryptionInvalidShares,
+ VerificationFailed,
+}
diff --git a/packages/enclave-sdk/src/enclave-sdk.ts b/packages/enclave-sdk/src/enclave-sdk.ts
index 792c6595b0..9b1bcc141c 100644
--- a/packages/enclave-sdk/src/enclave-sdk.ts
+++ b/packages/enclave-sdk/src/enclave-sdk.ts
@@ -36,7 +36,8 @@ import { SDKError, isValidAddress } from './utils'
import type { SDKConfig } from './types'
import type { AllEventTypes, EventCallback } from './events/types'
-import type { E3 } from './contracts/types'
+import type { E3, E3RequestParams } from './contracts/types'
+import { E3Stage, FailureReason } from './contracts/types'
import type { BfvParams, EncryptedValueAndPublicInputs, ThresholdBfvParamsPresetName, VerifiableEncryptionResult } from './encryption/types'
export class EnclaveSDK {
@@ -165,6 +166,18 @@ export class EnclaveSDK {
return this.contractClient.getE3(e3Id)
}
+ public async getE3Quote(params: E3RequestParams): Promise {
+ return this.contractClient.getE3Quote(params)
+ }
+
+ public async getFailureReason(e3Id: bigint): Promise {
+ return this.contractClient.getFailureReason(e3Id)
+ }
+
+ public async getE3Stage(e3Id: bigint): Promise {
+ return this.contractClient.getE3Stage(e3Id)
+ }
+
public async estimateGas(
functionName: string,
args: readonly unknown[],
diff --git a/packages/enclave-sdk/src/index.ts b/packages/enclave-sdk/src/index.ts
index 34ca248550..d7bbb6fe6c 100644
--- a/packages/enclave-sdk/src/index.ts
+++ b/packages/enclave-sdk/src/index.ts
@@ -31,6 +31,7 @@ export type {
SDKConfig,
ContractAddresses,
E3,
+ E3RequestParams,
EventListenerConfig,
EventFilter,
EventCallback,
@@ -55,7 +56,7 @@ export type {
} from './types'
// Enums and constants
-export { EnclaveEventType, RegistryEventType, ThresholdBfvParamsPresetNames } from './types'
+export { EnclaveEventType, RegistryEventType, ThresholdBfvParamsPresetNames, E3Stage, FailureReason } from './types'
// Export utilities
export {
diff --git a/packages/enclave-sdk/src/types.ts b/packages/enclave-sdk/src/types.ts
index 413e35ed80..2eb98c115a 100644
--- a/packages/enclave-sdk/src/types.ts
+++ b/packages/enclave-sdk/src/types.ts
@@ -13,7 +13,8 @@ export type { BfvParams, ThresholdBfvParamsPresetName, VerifiableEncryptionResul
export { ThresholdBfvParamsPresetNames } from './encryption/types'
-export type { ContractAddresses, E3 } from './contracts/types'
+export type { ContractAddresses, E3, E3RequestParams } from './contracts/types'
+export { E3Stage, FailureReason } from './contracts/types'
export { EnclaveEventType, RegistryEventType } from './events/types'
diff --git a/templates/default/server/index.ts b/templates/default/server/index.ts
index 97ced12f93..7b2152143b 100644
--- a/templates/default/server/index.ts
+++ b/templates/default/server/index.ts
@@ -5,7 +5,8 @@
// or FITNESS FOR A PARTICULAR PURPOSE.
import express, { Request, Response } from 'express'
-import { EnclaveSDK, RegistryEventType, CommitteePublishedData } from '@enclave-e3/sdk'
+import { EnclaveSDK } from '@enclave-e3/sdk'
+import { RegistryEventType, type CommitteePublishedData } from '@enclave-e3/sdk/events'
import { Log, PublicClient } from 'viem'
import { hardhat } from 'viem/chains'
import { handleTestInteraction } from './testHandler'
@@ -25,10 +26,14 @@ interface E3Session {
const e3Sessions = new Map()
+let sdkInstance: EnclaveSDK | null = null
+
async function createPrivateSDK() {
+ if (sdkInstance) return sdkInstance
+
const { PRIVATE_KEY, CIPHERNODE_REGISTRY_CONTRACT, ENCLAVE_CONTRACT, FEE_TOKEN_CONTRACT, RPC_URL } = getCheckedEnvVars()
- const sdk = EnclaveSDK.create({
+ sdkInstance = EnclaveSDK.create({
rpcUrl: RPC_URL,
privateKey: PRIVATE_KEY as `0x${string}`,
contracts: {
@@ -40,7 +45,7 @@ async function createPrivateSDK() {
thresholdBfvParamsPresetName: 'INSECURE_THRESHOLD_512',
})
- return sdk
+ return sdkInstance
}
async function runProgram(e3Id: bigint): Promise {
@@ -65,7 +70,7 @@ async function runProgram(e3Id: bigint): Promise {
let e3ProgramParams = session.e3ProgramParams
if (!e3ProgramParams) {
const sdk = await createPrivateSDK()
- const e3Details = (await sdk.getE3(e3Id)) as any
+ const e3Details = await sdk.getE3(e3Id)
e3ProgramParams = e3Details.e3ProgramParams
session.e3ProgramParams = e3ProgramParams
}
@@ -283,8 +288,6 @@ if (process.env.TEST_MODE) {
app.get('/test', handleTestInteraction)
}
-app.get('/sessions', handleGetSessions)
-
async function startServer() {
try {
await setupEventListeners()
diff --git a/templates/default/server/types.ts b/templates/default/server/types.ts
index b40c156349..306a712401 100644
--- a/templates/default/server/types.ts
+++ b/templates/default/server/types.ts
@@ -4,7 +4,7 @@
// without even the implied warranty of MERCHANTABILITY
// or FITNESS FOR A PARTICULAR PURPOSE.
-import { AllEventTypes } from '@enclave-e3/sdk'
+import type { AllEventTypes } from '@enclave-e3/sdk/events'
export enum ProgramEventType {
INPUT_PUBLISHED = 'InputPublished',
diff --git a/templates/default/tests/integration.spec.ts b/templates/default/tests/integration.spec.ts
index 05cca55a46..477184b56a 100644
--- a/templates/default/tests/integration.spec.ts
+++ b/templates/default/tests/integration.spec.ts
@@ -5,18 +5,18 @@
// or FITNESS FOR A PARTICULAR PURPOSE.
import {
- AllEventTypes,
+ EnclaveSDK,
calculateInputWindow,
DEFAULT_COMPUTE_PROVIDER_PARAMS,
DEFAULT_E3_CONFIG,
- E3,
- EnclaveEvent,
- EnclaveEventType,
- EnclaveSDK,
encodeBfvParams,
encodeComputeProviderParams,
- RegistryEventType,
+ decodePlaintextOutput,
} from '@enclave-e3/sdk'
+import { EnclaveEventType, RegistryEventType } from '@enclave-e3/sdk/events'
+import type { AllEventTypes, EnclaveEvent } from '@enclave-e3/sdk/events'
+import { E3Stage } from '@enclave-e3/sdk/contracts'
+import type { E3 } from '@enclave-e3/sdk/contracts'
import { createWalletClient, hexToBytes, http } from 'viem'
import assert from 'assert'
@@ -35,12 +35,6 @@ export function getContractAddresses() {
}
}
-function hexToUint8Array(hexString: string) {
- const hex = hexString.startsWith('0x') ? hexString.slice(2) : hexString
- const m = hex.match(/.{2}/g)?.map((byte) => parseInt(byte, 16)) ?? []
- return new Uint8Array(m)
-}
-
type E3Shared = {
e3Id: bigint
e3Program: string
@@ -137,13 +131,13 @@ describe('Integration', () => {
const store = new Map()
const sdk = EnclaveSDK.create({
- chain: hardhat,
contracts: {
enclave: contracts.enclave,
ciphernodeRegistry: contracts.ciphernodeRegistry,
feeToken: contracts.feeToken,
},
rpcUrl: 'ws://localhost:8545',
+ chain: anvil,
thresholdBfvParamsPresetName: 'INSECURE_THRESHOLD_512',
privateKey: testPrivateKey,
})
@@ -162,7 +156,7 @@ describe('Integration', () => {
const { waitForEvent } = await setupEventListeners(sdk, store)
const threshold: [number, number] = [DEFAULT_E3_CONFIG.threshold_min, DEFAULT_E3_CONFIG.threshold_max]
- const duration = 225
+ const duration = 250
const inputWindow = await calculateInputWindow(publicClient, duration)
const thresholdBfvParams = await sdk.getThresholdBfvParamsSet()
const e3ProgramParams = encodeBfvParams(thresholdBfvParams)
@@ -182,16 +176,22 @@ describe('Integration', () => {
await new Promise((resolve) => setTimeout(resolve, 1000))
+ // Verify fee quoting works
+ const requestParams = {
+ threshold,
+ inputWindow,
+ e3Program: contracts.e3Program,
+ e3ProgramParams,
+ computeProviderParams,
+ }
+ const quote = await sdk.getE3Quote(requestParams)
+ console.log('E3 quote:', quote)
+ assert(quote >= 0n, 'E3 quote should be a non-negative bigint')
+
// REQUEST phase
await waitForEvent(EnclaveEventType.E3_REQUESTED, async () => {
console.log('Requested E3...')
- await sdk.requestE3({
- threshold,
- inputWindow,
- e3Program: contracts.e3Program,
- e3ProgramParams,
- computeProviderParams,
- })
+ await sdk.requestE3(requestParams)
})
state = store.get(0n)
@@ -200,6 +200,10 @@ describe('Integration', () => {
assert.strictEqual(state.type, 'requested')
console.log('E3 Sucessfully Requested!')
+ // Verify E3 stage after request
+ const stageAfterRequest = await sdk.getE3Stage(0n)
+ assert.strictEqual(stageAfterRequest, E3Stage.Requested, 'E3 stage should be Requested after requestE3')
+
// Ciphernodes will publish a public key within the COMMITTEE_PUBLISHED event
event = await waitForEvent(RegistryEventType.COMMITTEE_PUBLISHED)
@@ -210,6 +214,10 @@ describe('Integration', () => {
assert.strictEqual(state.type, 'committee_published')
assert.strictEqual(state.publicKey, event.data.publicKey)
+ // Verify E3 stage after committee published
+ const stageAfterCommittee = await sdk.getE3Stage(0n)
+ assert.strictEqual(stageAfterCommittee, E3Stage.KeyPublished, 'E3 stage should be KeyPublished after committee published')
+
let { e3Id } = state
// INPUT PUBLISHING phase
@@ -228,7 +236,7 @@ describe('Integration', () => {
account.address,
contracts.e3Program,
)
- await publicClient.waitForTransactionReceipt({ hash: txHash })
+ await sdk.waitForTransaction(txHash)
txHash = await publishInput(
walletClient,
e3Id,
@@ -236,13 +244,14 @@ describe('Integration', () => {
account.address,
contracts.e3Program,
)
- await publicClient.waitForTransactionReceipt({ hash: txHash })
+ await sdk.waitForTransaction(txHash)
const plaintextEvent = await waitForEvent(EnclaveEventType.PLAINTEXT_OUTPUT_PUBLISHED)
- const parsed = hexToUint8Array(plaintextEvent.data.plaintextOutput)
+ const result = decodePlaintextOutput(plaintextEvent.data.plaintextOutput)
+ assert(result !== null, 'Failed to decode plaintext output')
- expect(BigInt(parsed[0])).toBe(num1 + num2)
+ expect(BigInt(result)).toBe(num1 + num2)
console.log('Answer was correct')
}, 9999999)
})
From 4c4e0f2b4ad4d6ba558c55636dbd968226508ba8 Mon Sep 17 00:00:00 2001
From: ctrlc03 <93448202+ctrlc03@users.noreply.github.com>
Date: Thu, 5 Mar 2026 17:57:38 +0000
Subject: [PATCH 3/3] chore: pr comments
---
docs/pages/sdk.mdx | 9 +++--
packages/enclave-react/src/useEnclaveSDK.ts | 2 +-
packages/enclave-sdk/README.md | 2 +-
packages/enclave-sdk/package.json | 8 ++---
packages/enclave-sdk/src/constants.ts | 9 +++++
.../src/{encryption => crypto}/encrypt.ts | 2 +-
.../src/{encryption => crypto}/index.ts | 2 ++
.../src/{encryption => crypto}/types.ts | 2 +-
.../user-data-encryption.ts} | 8 ++---
packages/enclave-sdk/src/enclave-sdk.ts | 34 ++++++++----------
.../enclave-sdk/src/events/event-listener.ts | 35 ++++++++++++-------
packages/enclave-sdk/src/index.ts | 6 ++--
packages/enclave-sdk/src/types.ts | 8 ++---
packages/enclave-sdk/tests/sdk.test.ts | 2 +-
packages/enclave-sdk/tsup.config.js | 2 +-
scripts/build-circuits.ts | 2 +-
templates/default/server/index.ts | 2 +-
templates/default/tests/integration.spec.ts | 34 +++++++++---------
18 files changed, 95 insertions(+), 74 deletions(-)
create mode 100644 packages/enclave-sdk/src/constants.ts
rename packages/enclave-sdk/src/{encryption => crypto}/encrypt.ts (98%)
rename packages/enclave-sdk/src/{encryption => crypto}/index.ts (91%)
rename packages/enclave-sdk/src/{encryption => crypto}/types.ts (93%)
rename packages/enclave-sdk/src/{greco.ts => crypto/user-data-encryption.ts} (91%)
diff --git a/docs/pages/sdk.mdx b/docs/pages/sdk.mdx
index d2dc71a0c2..b4076dc31d 100644
--- a/docs/pages/sdk.mdx
+++ b/docs/pages/sdk.mdx
@@ -85,7 +85,10 @@ const sdk = EnclaveSDK.create({
```ts
const hash = await sdk.requestE3({
threshold: [2, 3],
- inputWindow: [BigInt(Date.now()), BigInt(Date.now() + 5 * 60 * 1000)],
+ inputWindow: [
+ BigInt(Math.floor(Date.now() / 1000)),
+ BigInt(Math.floor(Date.now() / 1000) + 5 * 60),
+ ],
e3Program: '0x...',
e3ProgramParams: '0x',
computeProviderParams: '0x',
@@ -164,7 +167,7 @@ The SDK is organized into three sub-modules (`contracts`, `events`, `encryption`
imported independently for tree-shaking:
```ts
-import { generatePublicKey, encryptNumber } from '@enclave-e3/sdk/encryption'
+import { generatePublicKey, encryptNumber } from '@enclave-e3/sdk/crypto'
import { ContractClient } from '@enclave-e3/sdk/contracts'
import { EventListener, EnclaveEventType } from '@enclave-e3/sdk/events'
```
@@ -216,6 +219,8 @@ that wraps or complements the core Enclave SDK.
are unavailable.
- **Custom transports**: on the server, call `createWalletClient({ account, transport: http(rpc) })`
and load the private key from Vaults/KMS.
+- **Get E3 quote**: call `sdk.getE3Quote(requestParams)` to get the fee amount required for an E3
+ request.
- **Fee token approval**: call `sdk.approveFeeToken(amount)` before `requestE3` to approve the fee
token spend.
diff --git a/packages/enclave-react/src/useEnclaveSDK.ts b/packages/enclave-react/src/useEnclaveSDK.ts
index e05e7c36d8..28be4dde3e 100644
--- a/packages/enclave-react/src/useEnclaveSDK.ts
+++ b/packages/enclave-react/src/useEnclaveSDK.ts
@@ -24,7 +24,7 @@ export interface UseEnclaveSDKConfig {
feeToken: `0x${string}`
}
autoConnect?: boolean
- thresholdBfvParamsPresetName: ThresholdBfvParamsPresetName
+ thresholdBfvParamsPresetName?: ThresholdBfvParamsPresetName
}
export interface UseEnclaveSDKReturn {
diff --git a/packages/enclave-sdk/README.md b/packages/enclave-sdk/README.md
index 96c19315bf..d069b55c0f 100644
--- a/packages/enclave-sdk/README.md
+++ b/packages/enclave-sdk/README.md
@@ -283,7 +283,7 @@ The SDK is organized into three sub-modules that can be imported independently f
```typescript
// Encryption functions and types
-import { generatePublicKey, encryptNumber } from '@enclave-e3/sdk/encryption'
+import { generatePublicKey, encryptNumber } from '@enclave-e3/sdk/crypto'
// Contract client and types
import { ContractClient } from '@enclave-e3/sdk/contracts'
diff --git a/packages/enclave-sdk/package.json b/packages/enclave-sdk/package.json
index 89a80fa876..2e9775383f 100644
--- a/packages/enclave-sdk/package.json
+++ b/packages/enclave-sdk/package.json
@@ -9,10 +9,10 @@
"require": "./dist/index.cjs",
"default": "./dist/index.js"
},
- "./encryption": {
- "types": "./dist/encryption/index.d.ts",
- "import": "./dist/encryption/index.js",
- "require": "./dist/encryption/index.cjs"
+ "./crypto": {
+ "types": "./dist/crypto/index.d.ts",
+ "import": "./dist/crypto/index.js",
+ "require": "./dist/crypto/index.cjs"
},
"./contracts": {
"types": "./dist/contracts/index.d.ts",
diff --git a/packages/enclave-sdk/src/constants.ts b/packages/enclave-sdk/src/constants.ts
new file mode 100644
index 0000000000..8be49ca04b
--- /dev/null
+++ b/packages/enclave-sdk/src/constants.ts
@@ -0,0 +1,9 @@
+// SPDX-License-Identifier: LGPL-3.0-only
+//
+// This file is provided WITHOUT ANY WARRANTY;
+// without even the implied warranty of MERCHANTABILITY
+// or FITNESS FOR A PARTICULAR PURPOSE.
+
+import type { ThresholdBfvParamsPresetName } from './crypto/types'
+
+export const DEFAULT_THRESHOLD_BFV_PARAMS_PRESET_NAME: ThresholdBfvParamsPresetName = 'INSECURE_THRESHOLD_512'
diff --git a/packages/enclave-sdk/src/encryption/encrypt.ts b/packages/enclave-sdk/src/crypto/encrypt.ts
similarity index 98%
rename from packages/enclave-sdk/src/encryption/encrypt.ts
rename to packages/enclave-sdk/src/crypto/encrypt.ts
index cba48c5ddf..8f92dc2c9c 100644
--- a/packages/enclave-sdk/src/encryption/encrypt.ts
+++ b/packages/enclave-sdk/src/crypto/encrypt.ts
@@ -14,7 +14,7 @@ import {
compute_pk_commitment,
get_bfv_params,
} from '@enclave-e3/wasm'
-import { generateProof } from '../greco'
+import { generateProof } from './user-data-encryption'
import type { BfvParams, EncryptedValueAndPublicInputs, ThresholdBfvParamsPresetName, VerifiableEncryptionResult } from './types'
async function resolveParams(presetName: ThresholdBfvParamsPresetName): Promise {
diff --git a/packages/enclave-sdk/src/encryption/index.ts b/packages/enclave-sdk/src/crypto/index.ts
similarity index 91%
rename from packages/enclave-sdk/src/encryption/index.ts
rename to packages/enclave-sdk/src/crypto/index.ts
index c34a561efb..b2a1261b1c 100644
--- a/packages/enclave-sdk/src/encryption/index.ts
+++ b/packages/enclave-sdk/src/crypto/index.ts
@@ -16,6 +16,8 @@ export {
encryptVectorAndGenProof,
} from './encrypt'
+export { generateProof } from './user-data-encryption'
+
export type { BfvParams, ThresholdBfvParamsPresetName, VerifiableEncryptionResult, EncryptedValueAndPublicInputs } from './types'
export { ThresholdBfvParamsPresetNames } from './types'
diff --git a/packages/enclave-sdk/src/encryption/types.ts b/packages/enclave-sdk/src/crypto/types.ts
similarity index 93%
rename from packages/enclave-sdk/src/encryption/types.ts
rename to packages/enclave-sdk/src/crypto/types.ts
index e1af22d3c3..0a0ff40853 100644
--- a/packages/enclave-sdk/src/encryption/types.ts
+++ b/packages/enclave-sdk/src/crypto/types.ts
@@ -5,7 +5,7 @@
// or FITNESS FOR A PARTICULAR PURPOSE.
import type { ProofData } from '@aztec/bb.js'
-import type { CircuitInputs } from '../greco'
+import type { CircuitInputs } from './user-data-encryption'
export interface BfvParams {
degree: number
diff --git a/packages/enclave-sdk/src/greco.ts b/packages/enclave-sdk/src/crypto/user-data-encryption.ts
similarity index 91%
rename from packages/enclave-sdk/src/greco.ts
rename to packages/enclave-sdk/src/crypto/user-data-encryption.ts
index 14b261ca17..ecbf389aeb 100644
--- a/packages/enclave-sdk/src/greco.ts
+++ b/packages/enclave-sdk/src/crypto/user-data-encryption.ts
@@ -5,11 +5,11 @@
// or FITNESS FOR A PARTICULAR PURPOSE.
import { Barretenberg, UltraHonkBackend, type ProofData } from '@aztec/bb.js'
-import userDataEncryptionCt0Circuit from '../../../circuits/bin/threshold/target/user_data_encryption_ct0.json'
-import userDataEncryptionCt1Circuit from '../../../circuits/bin/threshold/target/user_data_encryption_ct1.json'
-import userDataEncryptionCircuit from '../../../circuits/bin/recursive_aggregation/wrapper/threshold/target/user_data_encryption.json'
+import userDataEncryptionCt0Circuit from '../../../../circuits/bin/threshold/target/user_data_encryption_ct0.json'
+import userDataEncryptionCt1Circuit from '../../../../circuits/bin/threshold/target/user_data_encryption_ct1.json'
+import userDataEncryptionCircuit from '../../../../circuits/bin/recursive_aggregation/wrapper/threshold/target/user_data_encryption.json'
import { CompiledCircuit, Noir } from '@noir-lang/noir_js'
-import { proofToFields } from './utils'
+import { proofToFields } from '../utils'
// Conversion to Noir types
export type Field = string
diff --git a/packages/enclave-sdk/src/enclave-sdk.ts b/packages/enclave-sdk/src/enclave-sdk.ts
index 9b1bcc141c..e17843c164 100644
--- a/packages/enclave-sdk/src/enclave-sdk.ts
+++ b/packages/enclave-sdk/src/enclave-sdk.ts
@@ -30,15 +30,16 @@ import {
encryptNumberAndGenProof,
encryptVectorAndGenInputs,
encryptVectorAndGenProof,
-} from './encryption/encrypt'
-import { ThresholdBfvParamsPresetNames } from './encryption/types'
+} from './crypto/encrypt'
+import { ThresholdBfvParamsPresetNames } from './crypto/types'
import { SDKError, isValidAddress } from './utils'
+import { DEFAULT_THRESHOLD_BFV_PARAMS_PRESET_NAME } from './constants'
import type { SDKConfig } from './types'
import type { AllEventTypes, EventCallback } from './events/types'
import type { E3, E3RequestParams } from './contracts/types'
import { E3Stage, FailureReason } from './contracts/types'
-import type { BfvParams, EncryptedValueAndPublicInputs, ThresholdBfvParamsPresetName, VerifiableEncryptionResult } from './encryption/types'
+import type { BfvParams, EncryptedValueAndPublicInputs, ThresholdBfvParamsPresetName, VerifiableEncryptionResult } from './crypto/types'
export class EnclaveSDK {
private eventListener: EventListener
@@ -63,18 +64,13 @@ export class EnclaveSDK {
throw new SDKError('Invalid FeeToken contract address', 'INVALID_ADDRESS')
}
- if (!config.thresholdBfvParamsPresetName) {
- throw new SDKError('Threshold BFV parameters preset name is required', 'MISSING_THRESHOLD_BFV_PARAMS_PRESET_NAME')
- }
+ const presetName = config.thresholdBfvParamsPresetName ?? DEFAULT_THRESHOLD_BFV_PARAMS_PRESET_NAME
- if (!Object.values(ThresholdBfvParamsPresetNames).includes(config.thresholdBfvParamsPresetName)) {
- throw new SDKError(
- `Invalid threshold BFV parameters preset name: ${config.thresholdBfvParamsPresetName}`,
- 'INVALID_THRESHOLD_BFV_PARAMS_PRESET_NAME',
- )
+ if (!Object.values(ThresholdBfvParamsPresetNames).includes(presetName)) {
+ throw new SDKError(`Invalid threshold BFV parameters preset name: ${presetName}`, 'INVALID_THRESHOLD_BFV_PARAMS_PRESET_NAME')
}
- this.thresholdBfvParamsPresetName = config.thresholdBfvParamsPresetName
+ this.thresholdBfvParamsPresetName = presetName
this.publicClient = config.publicClient
this.contractClient = new ContractClient({
@@ -194,24 +190,24 @@ export class EnclaveSDK {
// --- Events (delegates to EventListener) ---
- public onEnclaveEvent(eventType: T, callback: EventCallback): void {
- this.eventListener.onEnclaveEvent(eventType, callback)
+ public async onEnclaveEvent(eventType: T, callback: EventCallback): Promise {
+ return this.eventListener.onEnclaveEvent(eventType, callback)
}
public off(eventType: T, callback: EventCallback): void {
this.eventListener.off(eventType, callback)
}
- public once(type: T, callback: EventCallback): void {
- this.eventListener.once(type, callback)
+ public async once(type: T, callback: EventCallback): Promise {
+ return this.eventListener.once(type, callback)
}
public async getHistoricalEvents(eventType: AllEventTypes, fromBlock?: bigint, toBlock?: bigint): Promise {
return this.eventListener.getHistoricalEvents(eventType, fromBlock, toBlock)
}
- public async startEventPolling(): Promise {
- void this.eventListener.startPolling()
+ public startEventPolling(): Promise {
+ return this.eventListener.startPolling()
}
public stopEventPolling(): void {
@@ -233,7 +229,7 @@ export class EnclaveSDK {
}
privateKey?: `0x${string}`
chain: Chain
- thresholdBfvParamsPresetName: ThresholdBfvParamsPresetName
+ thresholdBfvParamsPresetName?: ThresholdBfvParamsPresetName
}): EnclaveSDK {
const isWebSocket = options.rpcUrl.startsWith('ws://') || options.rpcUrl.startsWith('wss://')
const transport = isWebSocket
diff --git a/packages/enclave-sdk/src/events/event-listener.ts b/packages/enclave-sdk/src/events/event-listener.ts
index 8d338590af..c736a37c71 100644
--- a/packages/enclave-sdk/src/events/event-listener.ts
+++ b/packages/enclave-sdk/src/events/event-listener.ts
@@ -8,7 +8,7 @@ import { type Abi, type Log, type PublicClient } from 'viem'
import { CiphernodeRegistryOwnable__factory, Enclave__factory } from '@enclave-e3/contracts/types'
import {
- EnclaveEventType,
+ RegistryEventType,
type AllEventTypes,
type EnclaveEvent,
type EnclaveEventData,
@@ -16,7 +16,7 @@ import {
type EventCallback,
type EventListenerConfig,
type RegistryEventData,
- type RegistryEventType,
+ type RegistryEventType as RegistryEventTypeT,
type SDKEventEmitter,
} from './types'
import type { ContractAddresses } from '../contracts/types'
@@ -43,20 +43,31 @@ export class EventListener implements SDKEventEmitter {
this.config = options.config || {}
}
+ // Registry-exclusive event names that don't collide with EnclaveEventType.
+ // Shared names like 'OwnershipTransferred' and 'Initialized' exist in both
+ // enums with the same string value, so they cannot be disambiguated at
+ // runtime; those default to the Enclave contract.
+ private static readonly REGISTRY_ONLY_EVENTS: ReadonlySet = new Set([
+ RegistryEventType.COMMITTEE_REQUESTED,
+ RegistryEventType.COMMITTEE_PUBLISHED,
+ RegistryEventType.COMMITTEE_FINALIZED,
+ RegistryEventType.ENCLAVE_SET,
+ ])
+
private resolveContract(eventType: AllEventTypes): { address: `0x${string}`; abi: Abi } {
- const isEnclaveEvent = Object.values(EnclaveEventType).includes(eventType as EnclaveEventType)
+ const isRegistryEvent = EventListener.REGISTRY_ONLY_EVENTS.has(eventType as string)
return {
- address: isEnclaveEvent ? this.contracts.enclave : this.contracts.ciphernodeRegistry,
- abi: isEnclaveEvent ? Enclave__factory.abi : CiphernodeRegistryOwnable__factory.abi,
+ address: isRegistryEvent ? this.contracts.ciphernodeRegistry : this.contracts.enclave,
+ abi: isRegistryEvent ? CiphernodeRegistryOwnable__factory.abi : Enclave__factory.abi,
}
}
- public onEnclaveEvent(eventType: T, callback: EventCallback): void {
+ public async onEnclaveEvent(eventType: T, callback: EventCallback): Promise {
const { address, abi } = this.resolveContract(eventType)
- void this.watchContractEvent(address, eventType, abi, callback)
+ return this.watchContractEvent(address, eventType, abi, callback)
}
- public once(type: T, callback: EventCallback): void {
+ public async once(type: T, callback: EventCallback): Promise {
const handler: EventCallback = (event) => {
this.off(type, handler)
const prom = callback(event)
@@ -64,7 +75,7 @@ export class EventListener implements SDKEventEmitter {
prom.catch((e) => console.error(e))
}
}
- this.onEnclaveEvent(type, handler)
+ return this.onEnclaveEvent(type, handler)
}
public async watchContractEvent(
@@ -98,7 +109,7 @@ export class EventListener implements SDKEventEmitter {
type: eventType,
data: (log as unknown as { args: unknown }).args as T extends EnclaveEventTypeT
? EnclaveEventData[T]
- : T extends RegistryEventType
+ : T extends RegistryEventTypeT
? RegistryEventData[T]
: unknown,
log,
@@ -165,8 +176,8 @@ export class EventListener implements SDKEventEmitter {
address,
abi,
eventName: eventType as string,
- fromBlock: fromBlock || this.config.fromBlock,
- toBlock: toBlock || this.config.toBlock,
+ fromBlock: fromBlock ?? this.config.fromBlock,
+ toBlock: toBlock ?? this.config.toBlock,
})
} catch (error) {
throw new SDKError(`Failed to get historical events: ${error}`, 'HISTORICAL_EVENTS_FAILED')
diff --git a/packages/enclave-sdk/src/index.ts b/packages/enclave-sdk/src/index.ts
index d7bbb6fe6c..a1b14d5cbe 100644
--- a/packages/enclave-sdk/src/index.ts
+++ b/packages/enclave-sdk/src/index.ts
@@ -24,7 +24,8 @@ export {
encryptNumberAndGenProof,
encryptVectorAndGenInputs,
encryptVectorAndGenProof,
-} from './encryption/encrypt'
+ generateProof,
+} from './crypto'
// Types and interfaces (re-exported from sub-modules via types.ts)
export type {
@@ -57,6 +58,7 @@ export type {
// Enums and constants
export { EnclaveEventType, RegistryEventType, ThresholdBfvParamsPresetNames, E3Stage, FailureReason } from './types'
+export { DEFAULT_THRESHOLD_BFV_PARAMS_PRESET_NAME } from './constants'
// Export utilities
export {
@@ -79,5 +81,3 @@ export {
decodePlaintextOutput,
type ComputeProviderParams,
} from './utils'
-
-export { generateProof } from './greco'
diff --git a/packages/enclave-sdk/src/types.ts b/packages/enclave-sdk/src/types.ts
index 2eb98c115a..a116fdd139 100644
--- a/packages/enclave-sdk/src/types.ts
+++ b/packages/enclave-sdk/src/types.ts
@@ -6,12 +6,12 @@
import type { Chain, PublicClient, WalletClient } from 'viem'
import type { ContractAddresses } from './contracts/types'
-import type { ThresholdBfvParamsPresetName } from './encryption/types'
+import type { ThresholdBfvParamsPresetName } from './crypto/types'
// Re-export all sub-module types for backward compatibility
-export type { BfvParams, ThresholdBfvParamsPresetName, VerifiableEncryptionResult, EncryptedValueAndPublicInputs } from './encryption/types'
+export type { BfvParams, ThresholdBfvParamsPresetName, VerifiableEncryptionResult, EncryptedValueAndPublicInputs } from './crypto/types'
-export { ThresholdBfvParamsPresetNames } from './encryption/types'
+export { ThresholdBfvParamsPresetNames } from './crypto/types'
export type { ContractAddresses, E3, E3RequestParams } from './contracts/types'
export { E3Stage, FailureReason } from './contracts/types'
@@ -43,5 +43,5 @@ export interface SDKConfig {
walletClient?: WalletClient
contracts: ContractAddresses
chain?: Chain
- thresholdBfvParamsPresetName: ThresholdBfvParamsPresetName
+ thresholdBfvParamsPresetName?: ThresholdBfvParamsPresetName
}
diff --git a/packages/enclave-sdk/tests/sdk.test.ts b/packages/enclave-sdk/tests/sdk.test.ts
index 0133a12167..ce5339789e 100644
--- a/packages/enclave-sdk/tests/sdk.test.ts
+++ b/packages/enclave-sdk/tests/sdk.test.ts
@@ -9,7 +9,7 @@ import { describe, expect, it } from 'vitest'
import { EnclaveSDK } from '../src/enclave-sdk'
import { zeroAddress } from 'viem'
import { hardhat } from 'viem/chains'
-import { generatePublicKey, encryptNumber as standaloneEncryptNumber, encryptVector as standaloneEncryptVector } from '../src/encryption'
+import { generatePublicKey, encryptNumber as standaloneEncryptNumber, encryptVector as standaloneEncryptVector } from '../src/crypto'
describe('encryptNumber', () => {
describe('trbfv', () => {
diff --git a/packages/enclave-sdk/tsup.config.js b/packages/enclave-sdk/tsup.config.js
index 1ea0f71191..b607d9cf52 100644
--- a/packages/enclave-sdk/tsup.config.js
+++ b/packages/enclave-sdk/tsup.config.js
@@ -7,7 +7,7 @@
import { defineConfig } from 'tsup'
import { baseConfig } from '@enclave-e3/config/tsup'
-const entry = ['src/index.ts', 'src/encryption/index.ts', 'src/contracts/index.ts', 'src/events/index.ts']
+const entry = ['src/index.ts', 'src/crypto/index.ts', 'src/contracts/index.ts', 'src/events/index.ts']
export default defineConfig([
{
diff --git a/scripts/build-circuits.ts b/scripts/build-circuits.ts
index 09fa4b2f78..39c3017485 100644
--- a/scripts/build-circuits.ts
+++ b/scripts/build-circuits.ts
@@ -9,7 +9,7 @@ import { execSync } from 'child_process'
import { createHash } from 'crypto'
import { appendFileSync, copyFileSync, existsSync, mkdirSync, readdirSync, readFileSync, rmSync, statSync, writeFileSync } from 'fs'
import { basename, join, resolve } from 'path'
-import { ALL_GROUPS, CIRCUIT_GROUPS, CIRCUIT_VARIANTS, type CircuitGroup, type CircuitVariant } from './circuit-constants'
+import { ALL_GROUPS, CIRCUIT_GROUPS, CIRCUIT_VARIANTS, type CircuitGroup } from './circuit-constants'
interface CircuitInfo {
name: string
diff --git a/templates/default/server/index.ts b/templates/default/server/index.ts
index 7b2152143b..a27acb2cc9 100644
--- a/templates/default/server/index.ts
+++ b/templates/default/server/index.ts
@@ -215,7 +215,7 @@ async function setupEventListeners() {
console.log('📡 Setting up event listeners...')
// we need to listen to CommitteePublished to know when an E3 is ready
- sdk.onEnclaveEvent(RegistryEventType.COMMITTEE_PUBLISHED, handleCommitteePublishedEvent)
+ await sdk.onEnclaveEvent(RegistryEventType.COMMITTEE_PUBLISHED, handleCommitteePublishedEvent)
await listenToInputPublishedEvents(sdk.getPublicClient(), PROGRAM_ADDRESS as `0x${string}`, 0n)
diff --git a/templates/default/tests/integration.spec.ts b/templates/default/tests/integration.spec.ts
index 477184b56a..b44f08aded 100644
--- a/templates/default/tests/integration.spec.ts
+++ b/templates/default/tests/integration.spec.ts
@@ -60,14 +60,14 @@ type E3State = E3StateRequested | E3StatePublished | E3StateOutputPublished
async function setupEventListeners(sdk: EnclaveSDK, store: Map) {
async function waitForEvent(type: T, trigger?: () => Promise): Promise> {
return new Promise((resolve, reject) => {
- sdk.once(type, resolve)
+ sdk.once(type, resolve).catch(reject)
if (trigger) {
trigger().catch(reject)
}
})
}
- sdk.onEnclaveEvent(EnclaveEventType.E3_REQUESTED, (event) => {
+ await sdk.onEnclaveEvent(EnclaveEventType.E3_REQUESTED, (event) => {
const id = event.data.e3Id
if (store.has(id)) {
@@ -80,7 +80,7 @@ async function setupEventListeners(sdk: EnclaveSDK, store: Map)
})
})
- sdk.onEnclaveEvent(RegistryEventType.COMMITTEE_PUBLISHED, (event) => {
+ await sdk.onEnclaveEvent(RegistryEventType.COMMITTEE_PUBLISHED, (event) => {
const id = event.data.e3Id
const state = store.get(id)
@@ -100,7 +100,7 @@ async function setupEventListeners(sdk: EnclaveSDK, store: Map)
})
})
- sdk.onEnclaveEvent(EnclaveEventType.PLAINTEXT_OUTPUT_PUBLISHED, (event) => {
+ await sdk.onEnclaveEvent(EnclaveEventType.PLAINTEXT_OUTPUT_PUBLISHED, (event) => {
const id = event.data.e3Id
const state = store.get(id)
@@ -169,13 +169,6 @@ describe('Integration', () => {
let state
let event
- // Approve fee token
- console.log('Approving fee token...')
- const hash = await sdk.approveFeeToken(100000000000n)
- console.log('Fee token approved:', hash)
-
- await new Promise((resolve) => setTimeout(resolve, 1000))
-
// Verify fee quoting works
const requestParams = {
threshold,
@@ -188,6 +181,13 @@ describe('Integration', () => {
console.log('E3 quote:', quote)
assert(quote >= 0n, 'E3 quote should be a non-negative bigint')
+ // Approve fee token
+ console.log('Approving fee token...')
+ const hash = await sdk.approveFeeToken(quote)
+ console.log('Fee token approved:', hash)
+
+ await new Promise((resolve) => setTimeout(resolve, 1000))
+
// REQUEST phase
await waitForEvent(EnclaveEventType.E3_REQUESTED, async () => {
console.log('Requested E3...')
@@ -201,7 +201,7 @@ describe('Integration', () => {
console.log('E3 Sucessfully Requested!')
// Verify E3 stage after request
- const stageAfterRequest = await sdk.getE3Stage(0n)
+ const stageAfterRequest = await sdk.getE3Stage(state.e3Id)
assert.strictEqual(stageAfterRequest, E3Stage.Requested, 'E3 stage should be Requested after requestE3')
// Ciphernodes will publish a public key within the COMMITTEE_PUBLISHED event
@@ -209,17 +209,15 @@ describe('Integration', () => {
const publicKeyBytes = hexToBytes(event.data.publicKey as `0x${string}`)
- state = store.get(0n)
+ state = store.get(state.e3Id)
assert(state, 'store should have E3State but it was falsey')
assert.strictEqual(state.type, 'committee_published')
assert.strictEqual(state.publicKey, event.data.publicKey)
// Verify E3 stage after committee published
- const stageAfterCommittee = await sdk.getE3Stage(0n)
+ const stageAfterCommittee = await sdk.getE3Stage(state.e3Id)
assert.strictEqual(stageAfterCommittee, E3Stage.KeyPublished, 'E3 stage should be KeyPublished after committee published')
- let { e3Id } = state
-
// INPUT PUBLISHING phase
console.log('PUBLISHING PRIVATE INPUT')
const num1 = 1n
@@ -231,7 +229,7 @@ describe('Integration', () => {
let txHash = await publishInput(
walletClient,
- e3Id,
+ state.e3Id,
`0x${Array.from(enc1, (b) => b.toString(16).padStart(2, '0')).join('')}` as `0x${string}`,
account.address,
contracts.e3Program,
@@ -239,7 +237,7 @@ describe('Integration', () => {
await sdk.waitForTransaction(txHash)
txHash = await publishInput(
walletClient,
- e3Id,
+ state.e3Id,
`0x${Array.from(enc2, (b) => b.toString(16).padStart(2, '0')).join('')}` as `0x${string}`,
account.address,
contracts.e3Program,