Binary Storage
Oak consensus stores structured content and binary references. The binary bytes live in one of two places: storage the author runs (author-hosted) or the validator's configured BlobStore (validator-hosted). Either way, the bytes are not replicated as Oak segment content. A CID proves content identity, not continued availability, and CID encodings are variable length.
The truth-provenance-edge separation also means binary integrity stays verifiable end-to-end without relying on platform trust: any consumer can hash the bytes they fetched and compare against the CID stored in Oak. Follow the upload and retrieval flow below to publish one binary and verify its CID-backed integrity.
Two Hosting Modes
message of a normal write. The validator stores the CID as data and never fetches the bytes.binaryData plus mimeType). The validator stores the bytes before queueing and records jcr:data, jcr:blobId, and ipfsCid on the content node.Validator-hosted upload is on by default (-Doak.proposal.validator.binary.upload.enabled). In sepolia mode, its settlement must set CAPABILITY_VALIDATOR_HOSTED_BINARY on the payment contract's settleWrite call, or the validator rejects the write. Mock mode, the default, skips that check. See Smart Contract.
The validator also serves a lazy-upload handshake: POST /v1/binary/declare-intent returns an intentToken, GET /v1/binary/check-intent/{token} reports its status, and POST /v1/binary/complete-upload records a CID. On current trunk the session never moves past PENDING on its own, and the recorded CID is not written to the content node, so this flow is not yet usable end to end.
The ipfsCid write parameter is limited to registered enterprise clients and to CIDs the validator already knows.
The Truth → Provenance → Edge Flow
Oak Chain is the source of truth for CIDs, while the bytes flow from storage to edge CDNs.
How It Works
- Oak Chain stores the CID (content-addressed hash). This is the truth.
- Author or validator storage hosts the binary (IPFS, Azure Blob, or a pinning service). This is provenance.
- Edge CDN caches the binary globally (Cloudflare R2, Fastly). This is delivery.
- User can verify binary integrity by hashing and comparing to the CID
Why Split It This Way
Architecture
graph LR
subgraph Oak["Oak Segment Store"]
META[Metadata Node
+ CID reference]
end
subgraph IPFS["IPFS Network"]
BIN[Binary Content]
end
META -->|CID| BIN
style META fill:#627EEA,color:#fff
style BIN fill:#4ade80,color:#000
Why IPFS?
Author-Hosted Flow
1. Author Uploads to IPFS
# Upload binary to IPFS
ipfs add my-image.jpg
# Returns: QmXyz...abc (CID)
2. Author Writes CID to Oak
curl -X POST http://localhost:8090/v1/propose-write \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "proposalId=0x1111111111111111111111111111111111111111111111111111111111111111" \
-d "walletAddress=0x742d35cc..." \
-d "organization=PixelPirates" \
--data-urlencode "message={\"jcr:primaryType\":\"dam:Asset\",\"jcr:content\":{\"renditions\":{\"original\":{\"ipfs:cid\":\"QmXyz...abc\",\"jcr:mimeType\":\"image/jpeg\",\"size\":1048576}}}}" \
-d "contentType=asset" \
-d "ethereumTxHash=<tx hash; in mock mode, reuse the proposalId>" \
-d "signature=<personal_sign of the exact message>"
3. Readers Fetch from IPFS
# Get the node from Oak (clusterId comes from /v1/explorer/content/nav)
curl "http://localhost:8090/v1/explorer/content/clusters/{clusterId}/node?path=/oak-chain/.../content/asset-..."
# The response lists the node's properties; the CID is inside the stored message
{
"properties": [
{ "name": "message", "type": "STRING", "value": "{\"jcr:primaryType\":\"dam:Asset\", ... \"ipfs:cid\":\"QmXyz...abc\" ...}" },
...
]
}
# Fetch binary from IPFS gateway
curl https://ipfs.io/ipfs/QmXyz...abc > image.jpg
Validator-Hosted Flow
curl -X POST http://localhost:8090/v1/propose-write \
-F "proposalId=0x1111111111111111111111111111111111111111111111111111111111111111" \
-F "walletAddress=0x742d35cc..." \
-F "organization=PixelPirates" \
-F "message={\"title\":\"Hero image\"}" \
-F "contentType=asset" \
-F "ethereumTxHash=<tx hash; in mock mode, reuse the proposalId>" \
-F "signature=<personal_sign of the exact message>" \
-F "file=@my-image.jpg;type=image/jpeg"
The committed node carries the CID in its ipfsCid property and a gateway URL in ipfsGateway.
Direct Binary Access
For performance, use direct IPFS access:
async function getAsset(clusterId, path) {
// 1. Get the node from Oak
const res = await fetch(
`http://validator:8090/v1/explorer/content/clusters/${clusterId}/node?path=${encodeURIComponent(path)}`
);
const { properties } = await res.json();
// 2. Get binary from IPFS (direct, no validator). Validator-hosted
// writes store the CID in ipfsCid; author-hosted writes keep it in message.
const cid = properties.find((p) => p.name === 'ipfsCid')?.value;
const binary = await fetch(`https://ipfs.io/ipfs/${cid}`);
return binary.blob();
}
Storage Model
Consensus Stores Binary References
Validator Storage:
├── Oak Segments (structured content + binary references)
└── BlobStore (binary blobs, only for validator-hosted uploads)
Authors Own Binaries (Author-Hosted Mode)
Authors are responsible for:
- Uploading to IPFS
- Pinning (ensuring availability)
- Paying for IPFS storage (Filecoin, Pinata, etc.)
This separates:
- Consensus (validators) - structured content
- Storage (IPFS) - binary blobs
Pinning Services
Example: Image Upload Flow
// Upload the file to your own IPFS node (Kubo RPC API) or pinning service first.
async function addToIpfs(file) {
const form = new FormData();
form.append('file', file);
const res = await fetch('http://127.0.0.1:5001/api/v0/add', { method: 'POST', body: form });
const { Hash } = await res.json();
return Hash; // the CID
}
async function uploadImage(file, wallet, org, proposalId, paymentTxHash) {
// 1. Upload to IPFS
const cid = await addToIpfs(file);
// 2. Sign the exact message you will submit
const message = JSON.stringify({
'jcr:primaryType': 'dam:Asset',
'ipfs:cid': cid,
'jcr:mimeType': file.type,
'size': file.size,
'uploadedAt': new Date().toISOString()
});
const signature = await window.ethereum.request({
method: 'personal_sign',
params: [message, wallet],
});
// 3. Write metadata to Oak
const response = await fetch('http://validator:8090/v1/propose-write', {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: new URLSearchParams({
proposalId,
walletAddress: wallet,
organization: org,
message,
contentType: 'asset',
ethereumTxHash: paymentTxHash, // From settleWrite
signature,
})
});
// 202 Accepted: the body includes storagePath and operationId
const result = await response.json();
return { cid, oakPath: result.storagePath };
}
For author-owned IPFS flows, the important fields are still proposalId, ethereumTxHash, signature, and the CID-backed metadata. The ethereumTxHash comes from the settleWrite call on ValidatorPaymentV5; capability flags are part of that on-chain call, not the validator request.
Garbage Collection
When content is deleted from Oak:
- CID reference removed from Oak
- Binary remains in IPFS (content-addressed)
- Author can unpin if no longer needed
- IPFS GC eventually removes unpinned content
Persistence Surface
The Oak/JCR property surface is the constant. Structured content, metadata, and relationships live in the segment store, and Oak Chain hardens that write path with signatures and consensus.
Binary persistence is deployment-specific. The runtime currently contains IPFS, Azure, and AWS BlobStore paths. A CID remains stable for a given byte sequence, but availability requires a reachable pin or storage provider and cache duration remains a delivery policy.
Next Steps
- Content Paths - Path structure
- Content Consumption - Reading content and real-time events