Verify Blob Availability Before Acting
When an agent stores data on Walrus, or receives a blob ID and plans to build on it, it needs to confirm the data is durably stored before depending on it. The authoritative signal is Walrus state recorded on Sui: a certified, unexpired, non-deletable Blob object, or a verified blob-status result backed by the onchain certification event. A write call returning or an aggregator read succeeding is not enough. This page describes the verify-before-act pattern and the checks to run.
What durable storage means on Walrus
In the standard store path, Walrus represents each stored blob as a Sui object of type Blob, paired with a Storage object that reserves space for a fixed period. A blob moves through two states: registered, where storage nodes expect its slivers, and certified, where enough slivers are stored to guarantee availability. A blob is safe to depend on only after it is certified, and only until its storage period ends.
The Blob and Storage objects expose fields you can read from Sui:
/// Reservation for storage for a given period, inclusive start, exclusive end.
public struct Storage has key, store {
id: UID,
start_epoch: u32,
end_epoch: u32,
storage_size: u64,
}
public struct Blob has key, store {
id: UID,
registered_epoch: u32,
blob_id: u256,
size: u64,
encoding_type: u8,
certified_epoch: option::Option<u32>, // set to the epoch first certified, if any
storage: Storage,
deletable: bool, // whether the owner can delete the blob before expiry
}
Why verify before acting
Several signals look like confirmation but do not prove durability:
- A write call returning, or an HTTP 200 from a publisher, reports that a request was accepted, not that the blob is certified and remains available.
- An aggregator read can return a stale cached 404 right after certification, or succeed only because another copy exists that you do not control.
- A blob expires at the end of its storage period, and a deletable blob can be removed by its owner before then.
Depending on a blob without reading the chain risks building on data that is not yet certified, has expired, or can disappear.
Do not treat an immediate aggregator 404 as proof the blob is missing. Right after certification, a cache in front of the aggregator might still serve a stale 404. Confirm against Sui state, or retry the read with backoff.
The check
Before you depend on a blob, confirm all three conditions. If you have the Blob object ID, read that object directly from Sui:
- Certified:
certified_epochis set, so the blob reached the certified state. - Within its storage period: the current epoch is less than
storage.end_epoch. Require enough epochs of margin for how long you need the data. - Not deletable, when you need persistence:
deletableisfalse, so the owner cannot remove the blob before it expires.
A certified, non-deletable Blob object looks like this when read as JSON:
{
"id": "0xe91eee8c5b6f35b9a250cfc29e30f0d9e5463a21fd8d1ddb0fc22d44db4eac50",
"registeredEpoch": 34,
"blobId": "M4hsZGQ1oCktdzegB6HnI6Mi28S2nqOPHxK-W7_4BUk",
"size": 17,
"encodingType": "RS2",
"certifiedEpoch": 34,
"storage": { "startEpoch": 34, "endEpoch": 35, "storageSize": 66034000 },
"deletable": false
}
Here certifiedEpoch is set, endEpoch is 35, and deletable is false, so the blob is safe to depend on while the current epoch is below 35.
Tooling
Check a blob by its ID with the CLI:
$ walrus blob-status --blob-id <BLOB_ID>
This reports whether the blob is stored and its availability period. When it reports a certified permanent blob, it also returns the related Sui event ID, a transaction ID and a sequence number. Read the current epoch with walrus info.
To check programmatically after writing a blob, use the Blob object ID from the write result and re-read that object from Sui before depending on it:
const res = await suiClient.getObject({
id: blobObjectId, // blobObject.id from the write result
options: { showContent: true },
});
if (res.data?.content?.dataType !== "moveObject") {
throw new Error("Blob object was not found on Sui");
}
const fields = res.data.content.fields as Record<string, any>;
const storageFields = fields.storage?.fields;
// The parsed Move Option<u32> is Some(epoch) when vec contains one value.
const certifiedEpoch = fields.certified_epoch?.fields?.vec?.[0] ?? null;
const certified = certifiedEpoch != null;
const endEpoch = Number(storageFields.end_epoch);
const deletable = fields.deletable === true;
const durable = certified && currentEpoch < endEpoch && !deletable;
if (!durable) {
throw new Error("Blob is not durably available yet; do not depend on it");
}
For trustless or offline checks, authenticate the BlobCertified event or the Blob object through the Sui client, which returns signed evidence usable as a portable proof of availability. A Sui smart contract can run the same certified, before-expiry, and not-deletable check when the Blob object is passed as an input. See the blob and storage_resource Move modules for the field accessors.
Verify integrity
Availability tells you a blob is still stored. Integrity tells you the bytes you read are the bytes
the writer committed. The link between the two is the blob_id field on the Blob object: a blob
ID is a commitment to the content, so a given blob ID always corresponds to the same bytes.
Verifying integrity means confirming the data you read matches the commitment recorded onchain. Two
layers give you that guarantee:
- Aggregator consistency checks. When you read through an aggregator, it checks that the slivers
it returns are consistent with the blob's encoding before serving the bytes. The default check is
sufficient for most cases. For a full check, add the query parameter
strict_consistency_check=true. Only addskip_consistency_check=truewhen the writer is known and trusted. See reading blobs over HTTP. - Binding to the onchain object. Read the bytes
by object ID, so the data you fetch is tied to
the specific
Blobobject whose certification and storage period you verified, not just to any certificate that happens to exist for the content.
For an offline or trustless check, the TypeScript SDK can re-encode content locally and derive its
blob ID, which you then compare against the blob_id on the Blob object. A match proves the bytes
are the committed content without trusting the aggregator. Note that the blob_id field read from
the Sui object is a u256 value, not the base64 blob ID used in aggregator URLs, so read by object
ID rather than splicing that field into a read URL.
Worked example: availability and integrity
The following function takes a Blob object ID, confirms the blob is durable, then reads the bytes
through an aggregator with the consistency check enabled. It returns the verified bytes, or throws
if any check fails. Set aggregator to an endpoint from the
Network Reference, and read the current epoch
with walrus info or from Walrus system state.
async function readVerifiedBlob(
suiClient: SuiClient,
aggregator: string,
blobObjectId: string,
currentEpoch: number,
): Promise<Uint8Array> {
// 1. Read the Blob object from Sui.
const res = await suiClient.getObject({
id: blobObjectId,
options: { showContent: true },
});
if (res.data?.content?.dataType !== "moveObject") {
throw new Error("Blob object was not found on Sui");
}
// 2. Check availability: certified, within its storage period, not deletable.
const fields = res.data.content.fields as Record<string, any>;
const certifiedEpoch = fields.certified_epoch?.fields?.vec?.[0] ?? null;
const endEpoch = Number(fields.storage?.fields?.end_epoch);
const deletable = fields.deletable === true;
const durable = certifiedEpoch != null && currentEpoch < endEpoch && !deletable;
if (!durable) {
throw new Error("Blob is not durably available; do not depend on it");
}
// 3. Read the bytes bound to this object, with the strict consistency check.
const url = `${aggregator}/v1/blobs/by-object-id/${blobObjectId}?strict_consistency_check=true`;
const read = await fetch(url);
if (!read.ok) {
// A CDN-fronted aggregator might serve a stale 404 right after certification.
throw new Error(`Read failed with status ${read.status}; retry with backoff`);
}
return new Uint8Array(await read.arrayBuffer());
}
This binds every step to onchain state: the object read proves certification and the storage window, and the aggregator read uses that same object and validates the encoding before returning the bytes.
Caveats
- Mainnet epochs last 2 weeks, so
end_epochgives a concrete time horizon for how long the data is guaranteed. - A blob ID is derived from content, so identical content always produces the same blob ID, and multiple
Blobobjects can certify it. Verifying by blob ID confirms that some non-expired certificate exists, not that your specific object is the one keeping it alive. If you need ownership or accounting guarantees, verify the specific object ID too. - Walrus blobs are public. Encrypt sensitive data, for example with Seal, before you store it.