> ## Documentation Index
> Fetch the complete documentation index at: https://docs.datafdn.org/llms.txt
> Use this file to discover all available pages before exploring further.

# Setup CDR Client

> Learn how to install and configure the CDR SDK.

<Note>
  These docs track the Aeneid release of `@piplabs/cdr-sdk` (`v0.2.1`).
</Note>

### Prerequisites

* Node.js 18+ and npm 8+
* Node.js 22+ if you plan to use `HeliaProvider`
* A funded wallet on Aeneid testnet
* [viem](https://www.npmjs.com/package/viem) (v2.21+) for blockchain interactions

## Install

<CodeGroup>
  ```bash npm theme={null}
  npm install @piplabs/cdr-sdk viem
  ```

  ```bash pnpm theme={null}
  pnpm add @piplabs/cdr-sdk viem
  ```

  ```bash yarn theme={null}
  yarn add @piplabs/cdr-sdk viem
  ```
</CodeGroup>

<Note>`viem` (v2.21+) is a required peer dependency.</Note>

<Note>
  Storage providers are optional and pull their own peer dependencies. Install
  them only for the backend you use: `helia`, `multiformats`, and
  `@helia/unixfs` for `HeliaProvider`, `@storacha/client` for `StorachaProvider`,
  or `@filoz/synapse-sdk` for `SynapseProvider`.
</Note>

<Note>
  If you plan to mint DATA Foundation license tokens in an IP-gated flow, also install
  `@story-protocol/core-sdk`.
</Note>

## Initialize WASM

The CDR SDK uses a WebAssembly module for threshold cryptography. You must initialize it once before performing any encryption or decryption operations.

```typescript theme={null}
import { initWasm } from "@piplabs/cdr-sdk";

// Call once at application startup
await initWasm();
```

<Note>
  In a React application, initialize WASM in a provider component or top-level
  effect so it's ready before any CDR operations are attempted.
</Note>

## Browser and Bundler Guidance

* **Vite / webpack** - Import the SDK from normal ESM application code and call
  `initWasm()` before the first encryption or decryption. If your SSR build
  tries to evaluate the SDK server-side, move the import behind a client-only
  boundary.
* **Next.js / SSR** - Keep browser wallet flows in `"use client"` components.
  For route handlers or scripts that use CDR cryptography, run them in the Node
  runtime instead of Edge.
* **Edge runtime** - The current release is not documented for Edge runtimes.
  Prefer the browser or Node.js runtime on Aeneid.
* **TypeScript** - Use modern ESM resolution. `moduleResolution: "Bundler"` is
  a good default for browser apps; `moduleResolution: "NodeNext"` fits pure
  Node ESM projects.

```typescript theme={null}
// Next.js route handlers / server actions
export const runtime = "nodejs";
```

## DATA Foundation API REST Endpoint

Every `CDRClient` requires an `apiUrl`: the base URL of a DATA Foundation API REST
endpoint. The SDK reads all DKG state (active round, global public key,
threshold, participant count, registered validators, and validator
attestations) over this REST API. Contract state such as vaults and fees is
still read over the EVM `publicClient`.

| Network | DATA Foundation API REST URL | Notes                                       |
| ------- | ---------------------------- | ------------------------------------------- |
| Aeneid  | `http://172.192.41.96:1317`  | Plain HTTP. May change between deployments. |

<Note>
  For production deployments you can point `apiUrl` at your own Story node's
  REST gateway instead of the shared endpoint. Configure it through an
  environment variable so it is easy to swap.
</Note>

## Create the CDR Client

The `CDRClient` provides three sub-clients:

* **`observer`** - Read-only queries (fees, vault data, DKG state). Always available.
* **`uploader`** - Encryption and vault allocation. Requires a `walletClient`.
* **`consumer`** - Decryption and read requests. Requires a `walletClient`.

### In React (Wallet Connector)

In a React app, you typically get the wallet from a connector like Privy, RainbowKit, or wagmi. Create a read-only `CDRClient` up front, and build a write-capable client on demand from the wallet's provider.

```typescript hooks/use-cdr-client.ts theme={null}
import { useMemo } from "react";
import { createPublicClient, createWalletClient, custom, http } from "viem";
import { CDRClient } from "@piplabs/cdr-sdk";

// Example using Privy, adapt for your wallet connector
import { usePrivy, useWallets } from "@privy-io/react-auth";

export function useCDRClient() {
  const { authenticated } = usePrivy();
  const { wallets } = useWallets();
  const wallet = wallets[0];

  // Read-only client, always available
  const publicClient = useMemo(
    () =>
      createPublicClient({ transport: http(process.env.NEXT_PUBLIC_RPC_URL) }),
    [],
  );

  const apiUrl = process.env.NEXT_PUBLIC_DATAFDN_API_URL!;

  const client = useMemo(
    () => new CDRClient({ network: "testnet", publicClient, apiUrl }),
    [publicClient, apiUrl],
  );

  // Write client, created on demand from the wallet's provider
  const getWriteClient = async () => {
    if (!wallet) throw new Error("No wallet connected");
    const provider = await wallet.getEthereumProvider();
    const walletClient = createWalletClient({
      transport: custom(provider),
      account: wallet.address as `0x${string}`,
    });
    return new CDRClient({
      network: "testnet",
      publicClient,
      walletClient,
      apiUrl,
    });
  };

  return { client, publicClient, getWriteClient, address: wallet?.address };
}
```

Then in your components:

```typescript theme={null}
const { client, getWriteClient } = useCDRClient();

// Read-only operations work immediately
const vault = await client.observer.getVault(42);

// Write operations: get a write client first
const writeClient = await getWriteClient();
await writeClient.uploader.write({ uuid, accessAuxData: "0x", encryptedData });
```

### With Private Key (Backend / Scripts)

For server-side code, scripts, or CLI tools, you can use a private key directly:

```typescript theme={null}
import { createPublicClient, createWalletClient, http } from "viem";
import { privateKeyToAccount } from "viem/accounts";
import { CDRClient } from "@piplabs/cdr-sdk";

const account = privateKeyToAccount(`0x${process.env.WALLET_PRIVATE_KEY}`);

const publicClient = createPublicClient({
  transport: http(process.env.RPC_PROVIDER_URL),
});

const walletClient = createWalletClient({
  account,
  transport: http(process.env.RPC_PROVIDER_URL),
});

const client = new CDRClient({
  network: "testnet",
  publicClient,
  walletClient,
  apiUrl: process.env.DATAFDN_API_URL!,
});
```

### Read-Only (No Wallet)

If you only need to query vault data or DKG state, you can omit the `walletClient`:

```typescript theme={null}
const client = new CDRClient({
  network: "testnet",
  publicClient,
  apiUrl: process.env.DATAFDN_API_URL!,
});

// observer methods work without a wallet
const vault = await client.observer.getVault(123);
const allocateFee = await client.observer.getAllocateFee();
```

<Warning>
  Attempting to use `client.uploader` or `client.consumer` without a
  `walletClient` will throw a `WalletClientRequiredError`.
</Warning>

## Network Configuration

### Supported Network

| Network | `network` param | Default RPC URL             | DATA Foundation API REST URL | Description               |
| ------- | --------------- | --------------------------- | ---------------------------- | ------------------------- |
| Aeneid  | `"testnet"`     | `https://aeneid.datarpc.io` | `http://172.192.41.96:1317`  | Current supported release |

```typescript Testnet theme={null}
const publicClient = createPublicClient({
  transport: http("https://aeneid.datarpc.io"),
});
const client = new CDRClient({
  network: "testnet",
  publicClient,
  apiUrl: "http://172.192.41.96:1317",
});
```

### Custom RPC URL

You can point the SDK to any Aeneid-compatible RPC endpoint by changing the
`http()` transport URL. This is useful for third-party RPC providers with
higher rate limits. The `apiUrl` is configured independently; point it at the
shared DATA Foundation API endpoint or your own Story node's REST gateway.

```typescript theme={null}
const publicClient = createPublicClient({
  transport: http("https://your-aeneid-rpc.example.com"),
});
const walletClient = createWalletClient({
  account,
  transport: http("https://your-aeneid-rpc.example.com"),
});

// Use "testnet"
const client = new CDRClient({
  network: "testnet",
  publicClient,
  walletClient,
  apiUrl: "http://172.192.41.96:1317",
});
```

### Using Environment Variables

A common pattern is to configure the network via environment variables:

```typescript config.ts theme={null}
const RPC_URL = process.env.RPC_URL ?? "https://aeneid.datarpc.io";
const DATAFDN_API_URL = process.env.DATAFDN_API_URL ?? "http://172.192.41.96:1317";
const NETWORK = (process.env.NETWORK ?? "testnet") as "testnet";

const publicClient = createPublicClient({ transport: http(RPC_URL) });
const client = new CDRClient({
  network: NETWORK,
  publicClient,
  apiUrl: DATAFDN_API_URL,
});
```

```bash .env theme={null}
# Testnet (default)
RPC_URL=https://aeneid.datarpc.io
DATAFDN_API_URL=http://172.192.41.96:1317
NETWORK=testnet

# Alternate Aeneid RPC
RPC_URL=https://your-aeneid-rpc.example.com
DATAFDN_API_URL=http://your-data-node:1317
NETWORK=testnet
```

## Quick Start: End-to-End Secret Example

The script below creates an owner-only vault, writes a small secret, then reads
it back with the same wallet. It is fully runnable once `WALLET_PRIVATE_KEY` is
set.

```typescript quickstart-cdr.ts theme={null}
import { CDRClient, initWasm, uuidToLabel } from "@piplabs/cdr-sdk";
import {
  createPublicClient,
  createWalletClient,
  http,
  toHex,
} from "viem";
import { privateKeyToAccount } from "viem/accounts";

const RPC_URL = process.env.RPC_URL ?? "https://aeneid.datarpc.io";
const DATAFDN_API_URL = process.env.DATAFDN_API_URL ?? "http://172.192.41.96:1317";
const PRIVATE_KEY = process.env.WALLET_PRIVATE_KEY as `0x${string}` | undefined;

if (!PRIVATE_KEY) {
  throw new Error("Set WALLET_PRIVATE_KEY before running this script.");
}

const account = privateKeyToAccount(PRIVATE_KEY);
const publicClient = createPublicClient({ transport: http(RPC_URL) });
const walletClient = createWalletClient({
  account,
  transport: http(RPC_URL),
});

await initWasm();

const client = new CDRClient({
  network: "testnet",
  publicClient,
  walletClient,
  apiUrl: DATAFDN_API_URL,
});

// Use the wallet (EOA) address as both write and read condition. Only this
// EOA can encrypt to or decrypt from the vault.
const { uuid, txHash: allocateTx } = await client.uploader.allocate({
  updatable: false,
  writeConditionAddr: account.address,
  readConditionAddr: account.address,
  writeConditionData: "0x",
  readConditionData: "0x",
  skipConditionValidation: true,
});

const globalPubKey = await client.observer.getGlobalPubKey();
const ciphertext = await client.uploader.encryptDataKey({
  dataKey: new TextEncoder().encode("hello from CDR"),
  globalPubKey,
  label: uuidToLabel(uuid),
});

const { txHash: writeTx } = await client.uploader.write({
  uuid,
  accessAuxData: "0x",
  encryptedData: toHex(ciphertext.raw),
});

console.log("Vault UUID:", uuid);
console.log("Allocate tx:", allocateTx);
console.log("Write tx:", writeTx);

const { dataKey, txHash } = await client.consumer.accessCDR({
  uuid,
  accessAuxData: "0x",
  timeoutMs: 120_000,
});

console.log("Read tx:", txHash);
console.log("Recovered secret:", new TextDecoder().decode(dataKey));
```

<Note>
  This example sends three transactions total: `allocate()`, `write()`, and
  `read()`. For larger payloads, switch to `uploadFile()` / `downloadFile()`
  with deployed condition contracts (such as the DATA Foundation license-gated pattern
  in [IP Asset Vaults](/developers/cdr-sdk/ip-asset-vaults)).
</Note>

<Note>
  Any EOA address works as a write or read condition; only that EOA can
  perform the matching action. The high-level `uploadCDR()` / `uploadFile()`
  helpers validate that condition addresses point at deployed contracts, so
  EOA conditions go through the low-level `allocate()` call with
  `skipConditionValidation: true`.
</Note>

## Next Steps

* For network-side runtime behavior, see
  [Runtime Configuration](/developers/cdr-sdk/advanced-configuration).
* For the main integration flows, continue to
  [Encrypt & Decrypt](/developers/cdr-sdk/encrypt-and-decrypt).

## Error Handling

The SDK throws typed errors you can catch and handle:

| Error Class                     | Code                         | When                                                                       |
| ------------------------------- | ---------------------------- | -------------------------------------------------------------------------- |
| `CDRError`                      | varies                       | Base class for all SDK-specific errors                                     |
| `WalletClientRequiredError`     | `WALLET_CLIENT_REQUIRED`     | Accessing `uploader` or `consumer` without a `walletClient`                |
| `InvalidParamsError`            | `INVALID_PARAMS`             | Invalid parameter combinations, such as only passing one keypair parameter |
| `InvalidConditionContractError` | `INVALID_CONDITION_CONTRACT` | Condition address does not implement the required interface                |
| `LabelMismatchError`            | `LABEL_MISMATCH`             | Ciphertext label does not match the vault UUID                             |
| `ContentSizeExceededError`      | `CONTENT_SIZE_EXCEEDED`      | Encrypted data exceeds `maxEncryptedDataSize`                              |
| `EmptyVaultError`               | `EMPTY_VAULT`                | Reading a vault that has never been written to                             |
| `PartialCollectionTimeoutError` | `PARTIAL_COLLECTION_TIMEOUT` | `collectPartials` or `accessCDR` times out waiting for validator responses |
| `CidIntegrityError`             | `CID_INTEGRITY`              | Downloaded encrypted file does not match the vault CID                     |

<Note>
  On-chain transaction reverts (for example, a failed condition check) surface
  as the underlying `viem` contract errors, not a CDR-specific error class.
</Note>

All errors extend `CDRError`, which has a `code` property for programmatic handling:

```typescript theme={null}
import { CDRError, PartialCollectionTimeoutError } from "@piplabs/cdr-sdk";

try {
  const { dataKey } = await client.consumer.accessCDR({ ... });
} catch (err) {
  if (err instanceof PartialCollectionTimeoutError) {
    console.error("Not enough validators responded in time. Try increasing timeoutMs.");
  } else if (err instanceof CDRError) {
    console.error(`CDR error [${err.code}]: ${err.message}`);
  }
}
```
