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

# Uploader

> Methods for encrypting data and writing it to CDR vaults.

## Uploader

The `Uploader` sub-client handles vault allocation, TDH2 encryption, and writing encrypted data on-chain. Requires a `walletClient`.

```typescript theme={null}
const uploader = client.uploader;
```

<Note>
  Use `uploadCDR()` for small secrets stored directly in the vault and
  `uploadFile()` when the encrypted bytes should live in an external storage
  backend.
</Note>

<Note>
  `v0.1.1` also exposes `createVault` as an alias for `uploadCDR`, and
  `createFileVault` as an alias for `uploadFile`.
</Note>

### Methods

* uploadCDR
* uploadFile
* allocate
* write
* encryptDataKey

***

### uploadCDR

High-level method that allocates a vault, encrypts your data, and writes the ciphertext in a single call.

| Method      | Type                                                      |
| ----------- | --------------------------------------------------------- |
| `uploadCDR` | `(params: UploadCDRParams) => Promise<UploadCDRResponse>` |

Parameters:

* `params.dataKey`: `Uint8Array` - The secret payload bytes to encrypt. Despite the name, this can be arbitrary data, not only a cryptographic key.
* `params.globalPubKey`: `Uint8Array` - The DKG global public key (from `observer.getGlobalPubKey()`)
* `params.updatable`: `boolean` - Whether the vault can be rewritten after initial write
* `params.writeConditionAddr`: `` `0x${string}` `` - Address of the write condition contract
* `params.readConditionAddr`: `` `0x${string}` `` - Address of the read condition contract
* `params.writeConditionData`: `` `0x${string}` `` - ABI-encoded data passed to the write condition
* `params.readConditionData`: `` `0x${string}` `` - ABI-encoded data passed to the read condition
* `params.accessAuxData`: `` `0x${string}` `` - Auxiliary data passed to conditions during write
* `params.allocateFeeOverride` *(optional)*: `bigint` - Skip fee query and use this value
* `params.writeFeeOverride` *(optional)*: `bigint` - Skip fee query and use this value

```typescript Example theme={null}
import { encodeAbiParameters } from "viem";

// Story license-gated pattern: only the uploader can write, and only callers
// holding a license token for `ipId` can read. See
// /developers/cdr-sdk/ip-asset-vaults for the full setup.
const OWNER_WRITE_CONDITION = "0x4C9bFC96d7092b590D497A191826C3dA2277c34B";
const LICENSE_READ_CONDITION = "0xC0640AD4CF2CaA9914C8e5C44234359a9102f7a3";
const LICENSE_TOKEN = "0xFe3838BFb30B34170F00030B52eA4893d8aAC6bC";

const writeConditionData = encodeAbiParameters(
  [{ type: "address" }],
  [walletClient.account!.address],
);

const readConditionData = encodeAbiParameters(
  [{ type: "address" }, { type: "address" }],
  [LICENSE_TOKEN, ipId],
);

const globalPubKey = await client.observer.getGlobalPubKey();
const dataKey = new TextEncoder().encode("my secret");

const { uuid, ciphertext, txHashes } = await client.uploader.uploadCDR({
  dataKey,
  globalPubKey,
  updatable: false,
  writeConditionAddr: OWNER_WRITE_CONDITION,
  readConditionAddr: LICENSE_READ_CONDITION,
  writeConditionData,
  readConditionData,
  accessAuxData: "0x",
});

console.log(`Vault UUID: ${uuid}`);
console.log(`Allocate tx: ${txHashes.allocate}`);
console.log(`Write tx: ${txHashes.write}`);
```

<Note>
  `OwnerWriteCondition` only implements `checkWriteCondition`, so it cannot
  be used as `readConditionAddr`. For an owner-only flow where the same
  wallet encrypts and decrypts, use the low-level `allocate()` example below
  with your wallet (EOA) address as both conditions and
  `skipConditionValidation: true`.
</Note>

<Note>
  Keep `uploadCDR()` payloads small enough that the resulting TDH2 ciphertext
  fits the vault limit (`observer.getMaxEncryptedDataSize()`, which is 1024
  bytes on Aeneid).
</Note>

```typescript UploadCDRResponse theme={null}
interface UploadCDRResponse {
  uuid: number;
  ciphertext: TDH2Ciphertext;
  txHashes: {
    allocate: `0x${string}`;
    write: `0x${string}`;
  };
}
```

***

### uploadFile

High-level method that encrypts file bytes locally, uploads the encrypted blob
through a `StorageProvider`, and writes the encrypted file key plus content
pointer to CDR in one call.

Parameters:

* `params.content`: `Uint8Array` - File bytes to encrypt and upload
* `params.storageProvider`: `StorageProvider` - Backend used for upload and download
* `params.globalPubKey`: `Uint8Array` - DKG global public key
* `params.updatable`: `boolean` - Whether the vault can be rewritten
* `params.writeConditionAddr`: `` `0x${string}` `` - Address of the write condition contract
* `params.readConditionAddr`: `` `0x${string}` `` - Address of the read condition contract
* `params.writeConditionData`: `` `0x${string}` `` - ABI-encoded write condition data
* `params.readConditionData`: `` `0x${string}` `` - ABI-encoded read condition data
* `params.accessAuxData`: `` `0x${string}` `` - Auxiliary data passed to conditions during write
* `params.allocateFeeOverride` *(optional)*: `bigint` - Skip the allocate fee query
* `params.writeFeeOverride` *(optional)*: `bigint` - Skip the write fee query

```typescript Example theme={null}
import { HeliaProvider } from "@piplabs/cdr-sdk";
import { readFile } from "node:fs/promises";
import { createHelia } from "helia";
import { unixfs } from "@helia/unixfs";
import { CID } from "multiformats/cid";
import { encodeAbiParameters } from "viem";

const helia = await createHelia();
const storage = new HeliaProvider({
  helia,
  unixfs: unixfs(helia),
  CID: (s) => CID.parse(s),
});

// Story license-gated pattern: see /developers/cdr-sdk/ip-asset-vaults for
// the full license setup. For an owner-only file flow, use the low-level
// `allocate()` path with your wallet (EOA) address as both conditions.
const OWNER_WRITE_CONDITION = "0x4C9bFC96d7092b590D497A191826C3dA2277c34B";
const LICENSE_READ_CONDITION = "0xC0640AD4CF2CaA9914C8e5C44234359a9102f7a3";
const LICENSE_TOKEN = "0xFe3838BFb30B34170F00030B52eA4893d8aAC6bC";

const writeConditionData = encodeAbiParameters(
  [{ type: "address" }],
  [walletClient.account!.address],
);

const readConditionData = encodeAbiParameters(
  [{ type: "address" }, { type: "address" }],
  [LICENSE_TOKEN, ipId],
);

const fileBytes = await readFile("./example.pdf");
const globalPubKey = await client.observer.getGlobalPubKey();
const { uuid, cid } = await client.uploader.uploadFile({
  content: new Uint8Array(fileBytes),
  storageProvider: storage,
  globalPubKey,
  updatable: false,
  writeConditionAddr: OWNER_WRITE_CONDITION,
  readConditionAddr: LICENSE_READ_CONDITION,
  writeConditionData,
  readConditionData,
  accessAuxData: "0x",
});

console.log(`Vault UUID: ${uuid}`);
console.log(`Stored CID: ${cid}`);
```

<Note>
  `HeliaProvider` is the only storage backend fully tested on Aeneid in the
  current release. `GatewayProvider`, `StorachaProvider`, and
  `SynapseProvider` are implemented but were not yet end-to-end validated in
  the release run.
</Note>

<Note>
  `uploadFile()` keeps the file bytes off-chain. The vault stores a TDH2
  ciphertext of a small JSON payload containing `{ cid, key }`.
</Note>

<Note>
  In browser code, pass file bytes from
  `new Uint8Array(await file.arrayBuffer())` instead of `readFile(...)`.
</Note>

***

### allocate

Creates a new CDR vault on-chain with the specified access control conditions.

| Method     | Type                                                    |
| ---------- | ------------------------------------------------------- |
| `allocate` | `(params: AllocateParams) => Promise<AllocateResponse>` |

Parameters:

* `params.updatable`: `boolean` - Whether the vault can be rewritten
* `params.writeConditionAddr`: `` `0x${string}` `` - Write condition contract address
* `params.readConditionAddr`: `` `0x${string}` `` - Read condition contract address
* `params.writeConditionData`: `` `0x${string}` `` - ABI-encoded write condition data
* `params.readConditionData`: `` `0x${string}` `` - ABI-encoded read condition data
* `params.feeOverride` *(optional)*: `bigint` - Skip fee query
* `params.skipConditionValidation` *(optional)*: `boolean` - Skip interface validation when intentionally using an EOA condition address

```typescript Example theme={null}
const userAddress = walletClient.account!.address;

const { txHash, uuid } = await client.uploader.allocate({
  updatable: false,
  writeConditionAddr: userAddress,
  readConditionAddr: userAddress,
  writeConditionData: "0x",
  readConditionData: "0x",
  skipConditionValidation: true,
});

console.log(`Vault ${uuid} allocated at tx: ${txHash}`);
```

<Note>
  `uploadCDR()` and `uploadFile()` do not expose `skipConditionValidation`, so
  use a real condition contract with those high-level helpers.
</Note>

```typescript AllocateResponse theme={null}
interface AllocateResponse {
  txHash: `0x${string}`;
  uuid: number; // parsed from VaultAllocated event
}
```

***

### write

Writes encrypted data to an existing vault. The caller must satisfy the vault's write condition.

| Method  | Type                                              |
| ------- | ------------------------------------------------- |
| `write` | `(params: WriteParams) => Promise<WriteResponse>` |

Parameters:

* `params.uuid`: `number` - The vault UUID
* `params.accessAuxData`: `` `0x${string}` `` - Auxiliary data passed to the write condition
* `params.encryptedData`: `` `0x${string}` `` - Hex-encoded TDH2 ciphertext
* `params.feeOverride` *(optional)*: `bigint` - Skip fee query

```typescript Example theme={null}
import { toHex } from "viem";

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

```typescript WriteResponse theme={null}
interface WriteResponse {
  txHash: `0x${string}`;
}
```

***

### encryptDataKey

Locally encrypts data using TDH2 threshold encryption. No blockchain interaction.

| Method           | Type                                                 |
| ---------------- | ---------------------------------------------------- |
| `encryptDataKey` | `(params: EncryptParams) => Promise<TDH2Ciphertext>` |

Parameters:

* `params.dataKey`: `Uint8Array` - The plaintext data to encrypt
* `params.globalPubKey`: `Uint8Array` - DKG global public key (34 bytes)
* `params.label`: `Uint8Array` - 32-byte label binding ciphertext to a vault (use `uuidToLabel(uuid)`)

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

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

console.log(ciphertext.raw); // Uint8Array - serialized TDH2 ciphertext
console.log(ciphertext.label); // Uint8Array - the label used
```

```typescript TDH2Ciphertext theme={null}
interface TDH2Ciphertext {
  raw: Uint8Array;   // serialized ciphertext (cb-mpc format)
  label: Uint8Array; // 32-byte context binding
}
```
