Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,11 @@ jobs:
run: npm install -g pnpm

# Install dependencies
# --ignore-scripts: skip dependency build/postinstall scripts. pnpm 10+
# otherwise fails with ERR_PNPM_IGNORED_BUILDS on packages with build
# scripts (keccak, nx, etc.) unless they are explicitly approved.
- name: Install dependencies
run: pnpm install --frozen-lockfile
run: pnpm install --frozen-lockfile --ignore-scripts

# Run linting
- name: Run lint
Expand Down Expand Up @@ -86,8 +89,9 @@ jobs:
run: npm install -g pnpm

# Install dependencies (this will be fast if cache is hit)
# --ignore-scripts: see note in the lint-format job above.
- name: Install dependencies
run: pnpm install --frozen-lockfile
run: pnpm install --frozen-lockfile --ignore-scripts

# Build the project
- name: Build
Expand Down
218 changes: 20 additions & 198 deletions examples/relayers/zama/README.md
Original file line number Diff line number Diff line change
@@ -1,215 +1,37 @@
# Zama Relayer Example
# Zama FHE Examples

This directory contains a first end-to-end example of using the OpenZeppelin Relayer SDK with a Zama FHE-enabled EVM contract.
This directory contains two end-to-end examples of using the OpenZeppelin Relayer SDK with a Zama FHE-enabled EVM contract. Both examples interact with the same counter contract deployed from Zama's [`fhevm-hardhat-template`](https://github.com/zama-ai/fhevm-hardhat-template), and both share a single `.env` (placed at this directory level) so you only configure your relayer once.

## Objective
| Subdirectory | Zama package | Style |
| ------------------------------- | ------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| [`relayer-sdk/`](./relayer-sdk) | `@zama-fhe/relayer-sdk@0.4.x` | Legacy / minimal. All glue lives in user code: hand-roll EIP-712 hashing, call `relayersApi.signTypedData` / `sendTransaction`, poll for status. |
| [`zama-sdk/`](./zama-sdk) | `@zama-fhe/sdk@3.x` + `GenericSigner` | Recommended for new integrations. The relayer is plugged in as a `GenericSigner`; reads use the built-in `ViemProvider`; application code uses the high-level `ZamaSDK` API. |

This example shows the minimum viable integration path:
## Which one should I use?

1. Configure a relayer and a deployed FHE contract.
2. Read encrypted state from the contract.
3. Decrypt the state, first via public decryption when possible.
4. Fall back to user decryption authorized with an EIP-712 signature produced by the relayer.
5. Send an encrypted transaction through the relayer.
6. Poll the relayer until the transaction is mined or confirmed.
- **Starting a new integration?** Use [`zama-sdk/`](./zama-sdk). It uses Zama's officially released top-level SDK. The adapter pattern is reusable across the entire Zama SDK — once the relayer is wrapped as a `GenericSigner`, you get `ZamaSDK`, the ERC-7984 `Token` wrapper, the wrappers registry, decrypt session caching, and event hooks for free.
- **Already on `@zama-fhe/relayer-sdk@0.4.x`?** [`relayer-sdk/`](./relayer-sdk) is the closest reference and stays around for parity. It is also useful if you want to see exactly which raw relayer API calls a Zama FHE flow needs — there is no abstraction in the way.

## Quickstart
The two examples can run side-by-side from the same `.env` and produce equivalent results on the counter contract.

1. Install dependencies from the repository root:
## Shared configuration

Copy [`.env.example`](./.env.example) to `.env` in this directory:

```bash
pnpm install
cp examples/relayers/zama/.env.example examples/relayers/zama/.env
```

2. Copy [`.env.example`](./.env.example) to `.env` in this directory.

3. Fill in:
Required values (used by both examples):

- `RELAYER_API_KEY`
- `RELAYER_ID`
- `ZAMA_CONTRACT_ADDRESS`
- `RPC_URL`
- `RELAYER_BASE_PATH` if you are not using the default

4. (Optional) Generate a reusable decryption keypair:

```bash
npx ts-node examples/relayers/zama/generate-keypair.ts
```

Copy the output `ZAMA_PUBLIC_KEY` and `ZAMA_PRIVATE_KEY` values into your `.env` file. If you skip this step, the counter script will generate a fresh keypair on each run.

5. Run the example from the repository root:

```bash
npx ts-node examples/relayers/zama/counter.ts
```

6. Check that the script:

- reads the encrypted counter value
- decrypts it
- sends an encrypted increment through the relayer
- waits for confirmation
- reads and decrypts the updated value

## What The Example Does

The main script is [counter.ts](./counter.ts). It interacts with a counter contract deployed from Zama's `fhevm-hardhat-template`.

The flow is:

1. Load configuration from `.env`.
2. Create a Zama FHE instance for Sepolia.
3. Fetch the relayer address from the OpenZeppelin Relayer API.
4. Read the encrypted counter value with `getCount()`.
5. Try to decrypt it.
6. Encrypt an increment input and submit `increment()` through the relayer.
7. Wait for confirmation.
8. Read and decrypt the updated counter again.

This makes the example useful as both:

- A functional demo.
- A reference for the relayer-specific parts of an FHE flow.

## Architecture

There are four moving parts in this example:

- Your script: orchestrates the flow and prints progress.
- The OpenZeppelin relayer: signs typed data and submits transactions.
- The Zama FHE SDK instance: encrypts inputs and manages decryption flows.
- The deployed counter contract: stores the encrypted state on-chain.

The important boundary is that the relayer is not doing the encryption itself. The Zama SDK handles encryption and decryption primitives, while the relayer provides transaction execution and signature authorization.

## Files

- [counter.ts](./counter.ts): main end-to-end example.
- [generate-keypair.ts](./generate-keypair.ts): generates a Zama decryption keypair for reuse across runs.
- [types.ts](./types.ts): local typings for the decryption keypair and EIP-712 payload.
- [abi.json](./abi.json): ABI for the example counter contract.
- [.env.example](./.env.example): required environment variables.

## Prerequisites

- Node.js `>= 22.14.0`
- Project dependencies installed with `pnpm install`
- Access to an OpenZeppelin relayer with:
- a valid `RELAYER_API_KEY`
- a `RELAYER_ID`
- an EVM address returned by the relayer API
- A Zama FHE counter contract deployed on Sepolia

## Configuration

Copy [`.env.example`](./.env.example) to `.env` in this directory and fill in the values:

```bash
RELAYER_API_KEY=
RELAYER_ID=
ZAMA_CONTRACT_ADDRESS=
RPC_URL=https://ethereum-sepolia-rpc.publicnode.com
RELAYER_BASE_PATH=http://localhost:8080

# Optional: reuse an existing keypair for user decryption
# ZAMA_PUBLIC_KEY=
# ZAMA_PRIVATE_KEY=
```

Notes:

- `RELAYER_BASE_PATH` defaults to `http://localhost:8080` in the script.
- `RPC_URL` defaults to `https://ethereum-sepolia-rpc.publicnode.com` in the script. Point it to the RPC for the network you are targeting.
- If `ZAMA_PUBLIC_KEY` and `ZAMA_PRIVATE_KEY` are not set, the script generates a fresh decryption keypair.
- Reusing the same decryption keypair is useful if you want consistent user decryption behavior across runs.

## Expected Output

At a high level, the script should:

- Print the relayer id, contract address, generated or loaded public key, and relayer address.
- Read the encrypted counter handle.
- Attempt decryption and print the clear value if successful.
- Submit an increment transaction through the relayer.
- Poll until the transaction reaches `mined` or `confirmed`.
- Read and decrypt the new counter value.

## Decryption Model

The script uses two decryption paths:

1. Public decryption.
If the encrypted handle can be decrypted publicly, the script uses that result directly.

2. User decryption.
If public decryption is not available, the script creates an EIP-712 payload through the Zama SDK and asks the relayer to sign it with `signTypedData`. That signature is then used to authorize `userDecrypt`.

This fallback structure is useful because it shows both the simple path and the relayer-authorized path in one example.

## Why The Relayer Matters Here

In this example, the relayer is responsible for two separate concerns:

- Transaction submission: it sends the encrypted `increment()` call on-chain.
- Authorization: it signs the EIP-712 payload used in the user decryption flow.

That is the main integration point this example should teach.

## References

- [Zama Relayer SDK Guides](https://docs.zama.org/protocol/relayer-sdk-guides) — official documentation for the Zama relayer SDK, including setup, encryption, and decryption flows.

## Using on Mainnet

The example defaults to Sepolia. To run against Ethereum mainnet:

1. In `counter.ts`, replace `SepoliaConfig` with `MainnetConfig` in the Zama instance configuration:

```ts
import {
MainnetConfig,
createInstance,
type FhevmInstance,
type FhevmInstanceConfig,
} from '@zama-fhe/relayer-sdk/node';
```

```ts
const zamaConfig: FhevmInstanceConfig = {
...MainnetConfig,
network: configValues.rpcUrl,
auth: { __type: 'ApiKeyHeader', value: process.env.ZAMA_FHEVM_API_KEY! },
};
```

2. Add the following to your `.env`:

```bash
# Zama FHE API key for mainnet (required for mainnet access)
ZAMA_FHEVM_API_KEY=
# Mainnet RPC URL
RPC_URL=https://ethereum-rpc.publicnode.com
```

3. Update `ZAMA_CONTRACT_ADDRESS` to point to your mainnet contract.

4. Make sure your relayer is configured for Ethereum mainnet.

> **Note:** Mainnet requires API key authentication with Zama's gateway. See the [mainnet API key guide](https://github.com/zama-ai/relayer-sdk/blob/main/docs/mainnet-api-key.md) for instructions on obtaining an API key.

## Current Limitations
- `RPC_URL` (defaults to a public Sepolia RPC)
- `RELAYER_BASE_PATH` (defaults to `http://localhost:8080`)

- The example is hardcoded for Sepolia via `SepoliaConfig`, plus an explicit Sepolia RPC URL.
- It assumes a counter contract shape compatible with the included ABI.
- It is a demo script, so logging and error handling are intentionally simple.
- It does not cover relayer creation or contract deployment.
Optional, only used by the legacy `relayer-sdk/` example's user-decryption flow:

## Troubleshooting
- `ZAMA_PUBLIC_KEY`, `ZAMA_PRIVATE_KEY` — reuse a previously generated decryption keypair across runs. The legacy example ships a [`generate-keypair.ts`](./relayer-sdk/generate-keypair.ts) helper that prints values you can paste back into `.env`. The `zama-sdk/` example does not need these — `@zama-fhe/sdk` manages decrypt keypairs and sessions internally.

- `Missing required environment variable`: one of the required values in `.env` is unset.
- `did not return an address`: the configured relayer id is valid for the API, but the response did not include an EVM address.
- `Public decryption failed`: this can be expected depending on the contract and permissions. The script should then try user decryption.
- `User decryption failed`: check that the relayer can sign typed data correctly and that the contract address and decryption keypair are the ones you expect.
- Transaction polling timeout: the transaction may still be pending, the relayer may be unhealthy, or the target chain may be slow.
Each subdirectory's `README.md` has the full quickstart, architecture notes, and troubleshooting for that path.
Loading
Loading