Skip to main content

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

The Uploader sub-client handles vault allocation, TDH2 encryption, and writing encrypted data on-chain. Requires a walletClient.
const uploader = client.uploader;
Use uploadCDR() for small secrets stored directly in the vault and uploadFile() when the encrypted bytes should live in an external storage backend.
The SDK also exposes createVault as an alias for uploadCDR, and createFileVault as an alias for uploadFile.

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.
MethodType
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 (optional): Uint8Array - The DKG global public key (from observer.getGlobalPubKey()). Auto-queried via the Observer if omitted.
  • 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
Example
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}`);
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.
Keep uploadCDR() payloads small enough that the resulting TDH2 ciphertext fits the vault limit (observer.getMaxEncryptedDataSize(), which is 1024 bytes on Aeneid).
UploadCDRResponse
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 (optional): Uint8Array - DKG global public key. Auto-queried via the Observer if omitted.
  • 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.pin (optional): boolean - Whether the storage provider should pin the uploaded blob
  • params.allocateFeeOverride (optional): bigint - Skip the allocate fee query
  • params.writeFeeOverride (optional): bigint - Skip the write fee query
Example
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}`);
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.
uploadFile() keeps the file bytes off-chain. The vault stores a TDH2 ciphertext of a small JSON payload containing { cid, key }.
In browser code, pass file bytes from new Uint8Array(await file.arrayBuffer()) instead of readFile(...).

allocate

Creates a new CDR vault on-chain with the specified access control conditions.
MethodType
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
Example
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}`);
uploadCDR() and uploadFile() do not expose skipConditionValidation, so use a real condition contract with those high-level helpers.
AllocateResponse
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.
MethodType
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
Example
import { toHex } from "viem";

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

encryptDataKey

Locally encrypts data using TDH2 threshold encryption. No blockchain interaction.
MethodType
encryptDataKey(params: EncryptParams) => Promise<TDH2Ciphertext>
Parameters:
  • params.dataKey: Uint8Array - The plaintext data to encrypt
  • params.globalPubKey (optional): Uint8Array - DKG global public key (34 bytes). Auto-queried via the Observer if omitted.
  • params.label: Uint8Array - 32-byte label binding ciphertext to a vault (use uuidToLabel(uuid))
Example
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
TDH2Ciphertext
interface TDH2Ciphertext {
  raw: Uint8Array;   // serialized ciphertext (cb-mpc format)
  label: Uint8Array; // 32-byte context binding
}