@cdr-kit/core/Storage adapters

Storage adapters

CDR vaults route payloads by size. Pick a CdrStorageProvider — 8 ship in @cdr-kit/core (5 core + 3 ecosystem). All speak the same upload/download 2-method interface.
import { createPinataStorage, createSupabaseStorage, /* ... */ } from "@cdr-kit/core"

Pick the right adapter

  • Buyer-only / dashboardscreateReadOnlyGatewayStorage (no pin creds needed, throws on upload).
  • Hosted IPFS pinningcreatePinataStorage (JWT, default Pinata gateway).
  • Already-have-Supabase shopscreateSupabaseStorage (bucket + service-role key, bare REST — no @supabase/supabase-js dep).
  • Self-hosted Kubo / custom pinnercreateIpfsStorage (multipart POST + JSON CID response).
  • AWS S3 / R2 / MinIOcreateS3Storage (signed-URL downloads, lazy-loads @aws-sdk/client-s3).
  • UCAN-backed pinningcreateStorachaStorage (peer-dep @web3-storage/w3up-client).
  • Browser-embedded IPFScreateHeliaStorage (peer-dep helia, user is the node).
  • Tests / CIcreateMemoryStorage (content-addressed, real CIDv1).

createPinataStorage

ts
import { createPinataStorage, uploadFile } from "@cdr-kit/core";

const storage = createPinataStorage({ jwt: process.env.PINATA_JWT! });
const { uuid, cid, txHashes } = await uploadFile(client, {
content: fileBytes,
storage,
readConditionAddr,
readConditionData,
});

createSupabaseStorage

ts
import { createSupabaseStorage } from "@cdr-kit/core";

const storage = createSupabaseStorage({
supabaseUrl: process.env.SUPABASE_URL!,
key: process.env.SUPABASE_SERVICE_ROLE_KEY!,
bucket: "cdr-secrets",
pathPrefix: "vaults/",       // optional, default "cdr/"
bucketIsPublic: false,        // download uses /authenticated/ when private
});

The "CID" is the bucket-relative path the object was uploaded to. Supabase doesn't speak IPFS, but CdrStorageProvider only requires a string round-trip handle.

On the read side, pass skipCidVerification: true to downloadFile whenever the storage handle is not an IPFS CIDv1 (Supabase paths, S3 keys, anything custom). Without it, the SDK's default multibase decode will throw because the handle isn't a valid CID — verified live on Aeneid in 0.7.1.

ts
const { content } = await downloadFile(client, {
uuid,
storage,
skipCidVerification: true,   // ← required for Supabase / S3 / any non-IPFS storage
});

createReadOnlyGatewayStorage

ts
import { createReadOnlyGatewayStorage, downloadFile } from "@cdr-kit/core";

// Buyer-side: read from any public IPFS gateway. Throws on upload() — by design.
const storage = createReadOnlyGatewayStorage({ gatewayUrl: "https://gateway.pinata.cloud" });
const { content } = await downloadFile(client, { uuid, storage });

For dashboards that consume but never produce. Pair with a seller using createPinataStorage (or any IPFS pinner) on the write side.

createIpfsStorage

ts
import { createIpfsStorage } from "@cdr-kit/core";

// Self-hosted Kubo, or any pinning service with a multipart POST + JSON CID response.
const storage = createIpfsStorage({
addUrl: "http://kubo:5001/api/v0/add",
gatewayUrl: "http://kubo:8080",
headers: { Authorization: "Bearer ..." },
});

createMemoryStorage

ts
import { createMemoryStorage } from "@cdr-kit/core";

// Unit tests, CI, mocks. Content-addressed, returns a real CIDv1.
const storage = createMemoryStorage();

Inline cap + routing

CDR sizes payloads at write time. The hard cap comes from the on-chain CDR.maxEncryptedDataSize() (currently ≈ 1 KB after TDH2 overhead). getInlineLimit(client) reads it once + caches; shouldUseFile(content, limit) picks the routing.

ts
import { shouldUseFile, getInlineLimit } from "@cdr-kit/core";

const limit = await getInlineLimit(client);   // reads CDR.maxEncryptedDataSize() once + caches
if (shouldUseFile(content, limit)) {
await uploadFile(client, { content, storage, readConditionAddr, readConditionData });
} else {
await writeVaultData(client, { uuid, dataKey: content });
}

createS3Storage

Works against AWS S3, Cloudflare R2, or any S3-compatible store. The SDK is loaded lazily via dynamic import so non-S3 deployments don't pay the bundle cost. Install @aws-sdk/client-s3 yourself in your project.

ts
import { createS3Storage } from "@cdr-kit/core";

// Works against AWS S3, Cloudflare R2, MinIO, or any S3-compatible store.
// Lazy-loads @aws-sdk/client-s3 via dynamic import — install it yourself.
const storage = createS3Storage({
bucket: "cdr-secrets",
region: "auto",                                     // R2: "auto"; AWS: "us-east-1" etc.
endpoint: "https://<acct>.r2.cloudflarestorage.com", // omit for AWS
accessKeyId: process.env.S3_ACCESS_KEY_ID!,
secretAccessKey: process.env.S3_SECRET_ACCESS_KEY!,
pathPrefix: "cdr/",                                  // optional, default "cdr/"
forcePathStyle: true,                                // R2/MinIO: true; AWS: omit
});

createStorachaStorage

UCAN-backed pinning service. Lazy-loads @storacha/client; install it yourself. The agent address must be authorized for the Space via a base64 UCAN proof.

ts
import { createStorachaStorage } from "@cdr-kit/core";

// Storacha — UCAN-backed pinning over @storacha/client.
// Install: pnpm add @storacha/client
const storage = createStorachaStorage({
key: process.env.STORACHA_KEY!,         // `w3 key create`
spaceDid: process.env.STORACHA_SPACE_DID!,
proof: process.env.STORACHA_PROOF!,     // `w3 delegation create` (base64 UCAN)
gatewayUrl: "https://w3s.link",          // optional, default w3s.link
});

createHeliaStorage

Browser-side embedded IPFS. The user's tab IS the IPFS node — no remote pinner needed. Lazy-loads helia + @helia/unixfs. Pin @peculiar/webcrypto: 1.7.0 in pnpm.overrides (Helia's WASM init is sensitive to webcrypto version drift).

ts
import { createHeliaStorage } from "@cdr-kit/core";

// Browser-side embedded IPFS via Helia — no remote pinner, the user is the node.
// Install: pnpm add helia @helia/unixfs
// Pin @peculiar/webcrypto: 1.7.0 in pnpm.overrides (Helia's wasm setup is fragile).
// Pass an existing Helia instance via { helia } to reuse, or leave empty to spawn one.
const storage = createHeliaStorage();