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

# IP Asset Vaults

> Learn how to create CDR vaults backed by IP Assets that require license tokens to decrypt.

CDR vaults can be gated behind Story Protocol license tokens so that only
license holders can decrypt the vault contents. In the current Aeneid release,
this is a manual integration: you encode the CDR condition data yourself and
mint Story license tokens separately.

### Prerequisites

* [CDR SDK setup](/developers/cdr-sdk/setup) complete
* `@story-protocol/core-sdk` installed if you plan to mint license tokens in code
* Familiarity with [IP Assets](/concepts/ip-asset) and [License Tokens](/concepts/licensing-module/license-token)

## Aeneid Contracts

| Contract             | Address                                      |
| -------------------- | -------------------------------------------- |
| OwnerWriteCondition  | `0x4C9bFC96d7092b590D497A191826C3dA2277c34B` |
| LicenseReadCondition | `0xC0640AD4CF2CaA9914C8e5C44234359a9102f7a3` |
| LicenseToken         | `0xFe3838BFb30B34170F00030B52eA4893d8aAC6bC` |

## How the Story License Read Pattern Works

Every CDR vault has a `writeConditionAddr` and `readConditionAddr`. For a
Story license-gated vault on Aeneid:

* `writeConditionAddr` usually points at `OwnerWriteCondition`, with
  `writeConditionData = abi.encode(ownerAddress)`
* `readConditionAddr` points at `LicenseReadCondition`, with
  `readConditionData = abi.encode(licenseTokenAddress, ipId)`
* `accessAuxData` at read time is
  `abi.encode(uint256[] licenseTokenIds)`

The CDR SDK does not register the IP Asset or mint the license token for you.
Create the IP and obtain its `ipId` with Story tooling first, then configure
the vault.

If you have not registered the asset yet, start with
[Register IP Asset](/developers/typescript-sdk/register-ip-asset). If you need
to attach or inspect license terms before minting, see
[Attach Terms](/developers/typescript-sdk/attach-terms).

## Upload a License-Gated Vault

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

const globalPubKey = await client.observer.getGlobalPubKey();
const dataKey = new TextEncoder().encode("confidential IP content");

const writeCondData = encodeAbiParameters(
  [{ type: "address" }],
  [uploaderAddress],
);

const readCondData = encodeAbiParameters(
  [{ type: "address" }, { type: "address" }],
  [
    "0xFe3838BFb30B34170F00030B52eA4893d8aAC6bC",
    ipId,
  ],
);

await client.uploader.uploadCDR({
  dataKey,
  globalPubKey,
  updatable: false,
  writeConditionAddr: "0x4C9bFC96d7092b590D497A191826C3dA2277c34B",
  writeConditionData: writeCondData,
  readConditionAddr: "0xC0640AD4CF2CaA9914C8e5C44234359a9102f7a3",
  readConditionData: readCondData,
  accessAuxData: "0x",
});
```

<Note>
  The same condition setup works with `uploadFile()` if the encrypted content
  lives off-chain.
</Note>

## Mint a License Token Before Reading

Before a user can read a Story license-gated vault, they still need to mint a
license token. The Story core SDK's `wipClient` handles the WIP wrap and
approval steps so the reader can wrap IP, approve the RoyaltyModule, and mint
in three short calls.

**WIP** is **Wrapped IP**, the ERC-20 wrapped form of the native `IP` token.
Story's royalty / license flows use WIP, so the reader first wraps IP, then
approves the RoyaltyModule to spend it.

```typescript theme={null}
import { parseEther, http } from "viem";
import { StoryClient } from "@story-protocol/core-sdk";

const ROYALTY_MODULE = "0xD2f60c40fEbccf6311f8B47c4f2Ec6b040400086";

const storyClient = StoryClient.newClient({
  transport: http("https://aeneid.storyrpc.io"),
  account: readerAccount,
  chainId: "aeneid",
});

// 1. Wrap 1 IP → 1 WIP so the mint fee can be paid in WIP.
await storyClient.wipClient.deposit({
  amount: parseEther("1"),
});

// 2. Approve the RoyaltyModule to spend WIP for the mint.
await storyClient.wipClient.approve({
  spender: ROYALTY_MODULE,
  amount: parseEther("1"),
});

// 3. Mint the Story license token.
const mintResult = await storyClient.license.mintLicenseTokens({
  licensorIpId: ipId,
  licenseTermsId: BigInt(2054),
  amount: 1,
});

const licenseTokenId = mintResult.licenseTokenIds![0];
```

<Note>
  `licenseTermsId: 2054` is only an example. Replace it with the license terms
  ID actually attached to your IP Asset. You receive that ID when you register
  the asset or attach terms.
</Note>

<Note>
  The wrap, approve, and mint calls above are all on-chain transactions. The
  later `accessCDR()` call adds one more on-chain read request.
</Note>

## Read with a License Token

At read time, pass the caller's license token ID through `accessAuxData`.

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

const accessAuxData = encodeAbiParameters(
  [{ type: "uint256[]" }],
  [[BigInt(licenseTokenId)]],
);

// Sends 1 read transaction, then collects partials and combines locally
const { dataKey } = await client.consumer.accessCDR({
  uuid,
  accessAuxData,
  timeoutMs: 120_000,
});

const content = new TextDecoder().decode(dataKey);
console.log(`Decrypted IP content: ${content}`);
```

<Warning>
  If the caller does not hold a valid license token for the vault's IP Asset,
  the read request reverts on-chain and validators will not produce partial
  decryptions.
</Warning>

## Custom Condition Contracts

License gating is just one pattern. You can deploy your own condition contract
for any access control logic by implementing one or both of these interfaces:

```solidity theme={null}
interface ICDRWriteCondition {
    function checkWriteCondition(
        uint32 uuid,
        bytes calldata accessAuxData,
        bytes calldata conditionData,
        address caller
    ) external view returns (bool);
}

interface ICDRReadCondition {
    function checkReadCondition(
        uint32 uuid,
        bytes calldata accessAuxData,
        bytes calldata conditionData,
        address caller
    ) external view returns (bool);
}
```

The CDR contract calls these functions before allowing a `write()` or `read()` operation. Return `true` to allow, `false` to deny. Then pass your contract's address as `readConditionAddr` or `writeConditionAddr` when allocating a vault.
