> For the complete documentation index, see [llms.txt](https://docs.zama.org/protocol/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.zama.org/protocol/sdk/guides/configuration.md).

# Configuration

The SDK uses `createConfig` to wire together chains, relayers, a provider, an optional signer, and storage into a single configuration object. This guide walks through each piece.

## Steps

### 1. Pick your chains

Import pre-configured chain objects from `@zama-fhe/sdk/chains`. Each chain includes contract addresses, relayer URLs, and chain IDs.

```ts
import { sepolia, mainnet, hoodi } from "@zama-fhe/sdk/chains";
```

| Chain          | Chain ID   | Description             |
| -------------- | ---------- | ----------------------- |
| `mainnet`      | `1`        | Ethereum Mainnet        |
| `sepolia`      | `11155111` | Sepolia Testnet         |
| `hoodi`        | `560048`   | Hoodi Testnet           |
| `ingenTestnet` | `364301`   | InGen Testnet           |
| `bscTestnet`   | `97`       | BNB Smart Chain Testnet |
| `hardhat`      | `31337`    | Local Hardhat node      |

`anvil` is also exported as an alias for `hardhat` (both target chain ID `31337`), for Foundry users.

{% hint style="info" %}
The Sepolia testnet relayer needs **no API key** — presets like `sepolia` work as-is, so leave `auth` unset. Only the Zama-hosted **mainnet** relayer requires a key; see [Authentication](/protocol/sdk/guides/authentication.md).
{% endhint %}

### 2. Pick a relayer

Relayers tell the SDK how to run FHE operations on each chain.

| Relayer       | Environment | Description                                  |
| ------------- | ----------- | -------------------------------------------- |
| `web()`       | Browser     | Runs WASM in a Web Worker via CDN            |
| `node()`      | Node.js     | Uses native worker threads                   |
| `cleartext()` | Local dev   | No FHE infrastructure — cleartext operations |

```ts
import { cleartext } from "@zama-fhe/sdk";
import { web } from "@zama-fhe/sdk/web";
import { node } from "@zama-fhe/sdk/node";
```

Chain-specific data (`relayerUrl`, `network`, `executorAddress`, etc.) comes from the chain preset. The relayer factory only accepts pool/worker options.

```ts
// Browser — uses relayerUrl from the chain preset
web();

// Node.js — pool options only; chain data comes from the preset
node({ poolSize: 4 });

// Local dev — no KMS, no gateway; executorAddress comes from the chain preset
cleartext();
```

If you need to override a chain field (e.g. proxy relayer requests through your backend), spread the preset in the `chains` array:

```ts
import { sepolia, type FheChain } from "@zama-fhe/sdk/chains";

const mySepolia = {
  ...sepolia,
  relayerUrl: "https://your-app.com/api/relayer/11155111",
} as const satisfies FheChain;
```

### 3. Set up chain access

The SDK separates read access (provider) from wallet authority (signer). The provider handles contract reads and receipt polling. The signer handles signing and write transactions. Both are created automatically by `createConfig` — you pass your Web3 library's native objects.

{% tabs %}
{% tab title="wagmi (React)" %}

```tsx
// createConfig from @zama-fhe/react-sdk/wagmi accepts your wagmiConfig directly — see step 4 below.
```

{% endtab %}

{% tab title="viem" %}

```ts
import { createPublicClient, createWalletClient, custom, http } from "viem";
import { sepolia } from "viem/chains";

const publicClient = createPublicClient({
  chain: sepolia,
  transport: http("https://sepolia.infura.io/v3/YOUR_KEY"),
});
const walletClient = createWalletClient({ chain: sepolia, transport: custom(window.ethereum!) });
```

{% endtab %}

{% tab title="ethers" %}

```ts
// Browser — pass the raw EIP-1193 provider
// createConfig({ ..., ethereum: window.ethereum! })

// Node.js — pass an ethers Signer (provider is extracted automatically)
// const provider = new ethers.JsonRpcProvider(rpcUrl);
// createConfig({ ..., signer: new ethers.Wallet(privateKey, provider) })
```

{% endtab %}
{% endtabs %}

For full type information, see the [ViemProvider](/protocol/sdk/api-references/sdk/viemprovider.md) / [ViemSigner](/protocol/sdk/api-references/sdk/viemsigner.md) and [EthersProvider](/protocol/sdk/api-references/sdk/ethersprovider.md) / [EthersSigner](/protocol/sdk/api-references/sdk/etherssigner.md) reference pages. You can also implement [GenericProvider](/protocol/sdk/api-references/sdk/genericprovider.md) and [GenericSigner](/protocol/sdk/api-references/sdk/genericsigner.md) for a custom integration.

### 4. Create the config

`createConfig` takes your chains, relayers, and signer adapter and returns a config object.

{% tabs %}
{% tab title="React + wagmi" %}

```tsx
import { web } from "@zama-fhe/sdk/web";
import { createConfig as createZamaConfig } from "@zama-fhe/react-sdk/wagmi";
import { sepolia, mainnet, type FheChain } from "@zama-fhe/sdk/chains";

// Override relayerUrl to proxy through your backend
const mySepolia = {
  ...sepolia,
  relayerUrl: "https://your-app.com/api/relayer/11155111",
} as const satisfies FheChain;
const myMainnet = {
  ...mainnet,
  relayerUrl: "https://your-app.com/api/relayer/1",
} as const satisfies FheChain;

const zamaConfig = createZamaConfig({
  chains: [mySepolia, myMainnet],
  wagmiConfig,
  relayers: { [mySepolia.id]: web(), [myMainnet.id]: web() },
});
```

{% endtab %}

{% tab title="Browser (viem)" %}

```ts
import { createConfig } from "@zama-fhe/sdk/viem";
import { ZamaSDK } from "@zama-fhe/sdk";
import { web } from "@zama-fhe/sdk/web";
import { sepolia, mainnet, type FheChain } from "@zama-fhe/sdk/chains";

const mySepolia = {
  ...sepolia,
  relayerUrl: "https://your-app.com/api/relayer/11155111",
} as const satisfies FheChain;
const myMainnet = {
  ...mainnet,
  relayerUrl: "https://your-app.com/api/relayer/1",
} as const satisfies FheChain;

const config = createConfig({
  chains: [mySepolia, myMainnet],
  publicClient,
  walletClient,
  relayers: { [mySepolia.id]: web(), [myMainnet.id]: web() },
});

const sdk = new ZamaSDK(config);
```

{% endtab %}

{% tab title="Browser (ethers)" %}

```ts
import { createConfig } from "@zama-fhe/sdk/ethers";
import { ZamaSDK } from "@zama-fhe/sdk";
import { web } from "@zama-fhe/sdk/web";
import { sepolia, type FheChain } from "@zama-fhe/sdk/chains";

const mySepolia = {
  ...sepolia,
  relayerUrl: "https://your-app.com/api/relayer/11155111",
} as const satisfies FheChain;

const config = createConfig({
  chains: [mySepolia],
  ethereum: window.ethereum!,
  relayers: { [mySepolia.id]: web() },
});

const sdk = new ZamaSDK(config);
```

{% endtab %}

{% tab title="Node.js" %}

```ts
import { createConfig } from "@zama-fhe/sdk/viem";
import { ZamaSDK, memoryStorage } from "@zama-fhe/sdk";
import { node } from "@zama-fhe/sdk/node";
import { sepolia, type FheChain } from "@zama-fhe/sdk/chains";

const mySepolia = {
  ...sepolia,
  network: "https://sepolia.infura.io/v3/YOUR_KEY",
} as const satisfies FheChain;

const config = createConfig({
  chains: [mySepolia],
  publicClient,
  walletClient,
  storage: memoryStorage,
  relayers: { [mySepolia.id]: node({ poolSize: 4 }) },
});

const sdk = new ZamaSDK(config);
```

{% endtab %}

{% tab title="Custom signer/provider" %}
When the built-in adapters don't fit your setup — for example, a server-side relayer that implements `GenericSigner` directly — use the generic `createConfig` from `@zama-fhe/sdk`:

```ts
import { createConfig, ZamaSDK, memoryStorage } from "@zama-fhe/sdk";
import { node } from "@zama-fhe/sdk/node";
import { sepolia, type FheChain } from "@zama-fhe/sdk/chains";

const mySepolia = {
  ...sepolia,
  network: "https://sepolia.infura.io/v3/YOUR_KEY",
} as const satisfies FheChain;

const config = createConfig({
  chains: [mySepolia],
  signer: myCustomSigner, // implements GenericSigner
  provider: myCustomProvider, // implements GenericProvider
  storage: memoryStorage,
  relayers: { [mySepolia.id]: node({ poolSize: 4 }) },
});

const sdk = new ZamaSDK(config);
```

See [GenericSigner](/protocol/sdk/api-references/sdk/genericsigner.md) and [GenericProvider](/protocol/sdk/api-references/sdk/genericprovider.md) for the interfaces your adapter must implement.
{% endtab %}

{% tab title="Web Extensions" %}
MV3 Chrome extensions can use `chromeSessionStorage` as `permitStorage` so permits survive service worker restarts:

```ts
import { createConfig } from "@zama-fhe/sdk/viem";
import { ZamaSDK, indexedDBStorage, chromeSessionStorage } from "@zama-fhe/sdk";
import { web } from "@zama-fhe/sdk/web";
import { sepolia, type FheChain } from "@zama-fhe/sdk/chains";

const mySepolia = {
  ...sepolia,
  relayerUrl: "https://your-app.com/api/relayer/11155111",
} as const satisfies FheChain;

const config = createConfig({
  chains: [mySepolia],
  publicClient,
  walletClient,
  storage: indexedDBStorage,
  permitStorage: chromeSessionStorage,
  relayers: { [mySepolia.id]: web() },
});

const sdk = new ZamaSDK(config);
```

Your `manifest.json` must include the `"storage"` permission. See the [Web Extensions guide](/protocol/sdk/guides/web-extensions.md) for manifest configuration, multi-context sharing, and browser close behavior.
{% endtab %}
{% endtabs %}

Browser apps should proxy relayer requests through a backend to keep the API key secret. See the [Authentication guide](/protocol/sdk/guides/authentication.md) for the full setup.

### 5. (Optional) Configure TTLs and event listener

You can tune how long the transport key pair and permits remain valid, and subscribe to lifecycle events for debugging:

```ts
const config = createConfig({
  chains: [sepolia],
  wagmiConfig,
  relayers: { [sepolia.id]: web() },
  transportKeyPairTTL: 604800, // 7 days in seconds (default: 2592000 = 30 days)
  permitTTL: 7, // 7 days (default: 30 days)
  onEvent: ({ type, tokenAddress, ...rest }) => {
    console.debug(`[zama] ${type}`, rest);
  },
});
```

When done with the SDK, call `sdk.terminate()` to clean up the Web Worker or thread pool.

### 6. (Optional) Choose a storage backend

The transport key pair is cached so users don't get a wallet popup on every decrypt. By default, `createConfig` picks the right storage for your environment. Override with the `storage` field if needed:

| Storage             | When to use                                               |
| ------------------- | --------------------------------------------------------- |
| `indexedDBStorage`  | Browser apps — persists across reloads and sessions       |
| `memoryStorage`     | Tests, scripts, throwaway sessions                        |
| `asyncLocalStorage` | Node.js servers — isolates transport key pair per request |

```ts
import { indexedDBStorage, memoryStorage } from "@zama-fhe/sdk";
// Node.js per-request isolation:
// import { asyncLocalStorage } from "@zama-fhe/sdk/node";
```

For full storage options see the [GenericStorage](/protocol/sdk/api-references/sdk/genericstorage.md) reference.

### 7. (Optional) Supply a logger

The SDK is **silent by default** — it emits no console output of its own. Operation failures always surface through the rejected promise or typed error, never as a stray `console.error`. To observe internal diagnostics, pass a `logger` to `createConfig`:

```ts
const config = createConfig({
  chains: [sepolia],
  wagmiConfig,
  relayers: { [sepolia.id]: web() },
  logger: console, // or a pino / winston / OpenTelemetry DiagLogger instance
});
```

The `logger` is a minimal four-level interface — `error`, `warn`, `info`, `debug` — that `console` and common logging libraries satisfy directly, so no adapter is needed. The SDK never bundles a logging library or imposes a format; level filtering is left to your logger. Levels follow these conventions:

| Level   | What the SDK emits                                                                           |
| ------- | -------------------------------------------------------------------------------------------- |
| `error` | Unexpected internal failures only — never failures already surfaced via a rejection          |
| `warn`  | Recoverable or degraded conditions (a fallback path, a retry, a swallowed best-effort write) |
| `info`  | Reserved for coarse lifecycle milestones; not currently emitted                              |
| `debug` | Verbose diagnostics — worker lifecycle, request timing, orchestration progress               |

The logger is configured once here and flows SDK-wide — including into worker request tracing, the credential store, and the artifact cache. There is deliberately no per-relayer logger option; `createConfig({ logger })` is the single source of truth.

## Shared relayer options

When multiple chains use the same relayer, create it once and reference that single instance from each chain:

```ts
import { sepolia, mainnet, type FheChain } from "@zama-fhe/sdk/chains";

const sharedWeb = web({ threads: 8 });

const mySepolia = { ...sepolia, relayerUrl: "/api/relayer/11155111" } as const satisfies FheChain;
const myMainnet = { ...mainnet, relayerUrl: "/api/relayer/1" } as const satisfies FheChain;

const config = createConfig({
  chains: [mySepolia, myMainnet],
  publicClient,
  walletClient,
  relayers: { [mySepolia.id]: sharedWeb, [myMainnet.id]: sharedWeb },
});
```

Chains that reference the *same* relayer object — the result of a single `web()` call — share one worker, reducing memory usage.

## Next steps

* [Authentication](/protocol/sdk/guides/authentication.md) — set up a backend proxy or use a direct API key
* [Shield Tokens](/protocol/sdk/guides/shield-tokens.md) — convert public ERC-20 tokens into confidential form
* [Chain Objects](/protocol/sdk/api-references/sdk/network-presets.md) — pre-configured chain definitions for Sepolia, Mainnet, and more
* [GenericStorage reference](/protocol/sdk/api-references/sdk/genericstorage.md) — custom storage implementations


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://docs.zama.org/protocol/sdk/guides/configuration.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
