> ## Documentation Index
> Fetch the complete documentation index at: https://docs.story.foundation/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.1.1`). The
  SDK is not yet published to npm, so install it from source for now.
</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 from Source

<CodeGroup>
  ```bash Build SDK theme={null}
  git clone https://github.com/piplabs/cdr-sdk.git --branch 0.1.1 --depth 1
  cd cdr-sdk && pnpm install && pnpm build
  cd ../your-project
  npm install ../cdr-sdk/packages/sdk viem
  ```
</CodeGroup>

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

<Note>
  If you plan to mint Story 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";
```

## 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 client = useMemo(
    () => new CDRClient({ network: "testnet", publicClient }),
    [publicClient],
  );

  // 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,
    });
  };

  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,
});
```

### 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,
});

// 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              | Description               |
| ------- | --------------- | ---------------------------- | ------------------------- |
| Aeneid  | `"testnet"`     | `https://aeneid.storyrpc.io` | Current supported release |

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

### 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.

```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,
});
```

### 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.storyrpc.io";
const NETWORK = (process.env.NETWORK ?? "testnet") as "testnet";

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

```bash .env theme={null}
# Testnet (default)
RPC_URL=https://aeneid.storyrpc.io
NETWORK=testnet

# Alternate Aeneid RPC
RPC_URL=https://your-aeneid-rpc.example.com
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.storyrpc.io";
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,
});

// 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 Story 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`                |
| `ObserverRequiredError`         | `OBSERVER_REQUIRED`          | Auto-querying `globalPubKey` / `threshold` without an `observer`           |
| `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`                              |
| `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                     |
| `RpcConsensusError`             | `RPC_CONSENSUS`              | Validation RPCs disagree about a DKG value                                 |
| `ContractRevertError`           | `CONTRACT_REVERT`            | On-chain transaction reverted                                              |

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}`);
  }
}
```
