Concepts
Four ideas you need before things make sense — what Walrus stores, what Waldrop tracks on Sui, what SEAL adds, and how epochs differ between the two chains.
Walrus blobs vs Waldrop BlobRefs
Walrus is a content-addressed storage network. You upload bytes, you get a
blob_id — a hash. Anyone with the id can download the bytes. The id is
all the network knows about you: there's no user, no account, no folder.
That's a problem for apps. "Show me my files" doesn't work when the storage layer doesn't know what "my" means.
Waldrop closes the gap with a Sui Move package. Every time you upload, the
SDK signs one extra transaction (register_blob) that creates a BlobRef
on Sui:
Now you can ask Sui "give me all BlobRefs owned by address X" and get a
typed list back. That's client.blob.list({ owner }).
BlobStore — the per-user index
A user's BlobRefs live inside a single Sui object called a BlobStore:
One per user (shared object on Sui). Holds a Table of BlobRefs
keyed by blob_id, total bytes, total count, viewer allowlist,
per-blob share table.
One per uploaded blob. Lives inside the BlobStore's Table — not a
standalone object. Drops via Move's drop ability when released.
The first time you call client.blob.upload(...) without a blobStoreId,
the SDK creates a BlobStore + registers your first BlobRef in one PTB. The
result returns blobStoreId: 0x… — save it, you'll need it for
encrypted uploads.
Subscription — gates on plan tier
register_blob requires a Subscription object id. The contract reads the
subscription's planTier to enforce:
- SEAL access — Free tier can't upload encrypted blobs.
- File-count cap — Free tier limited to N blobs per BlobStore.
- Lifetime cap — Plans cap max
expiryEpochbased on tier.
Subscribe via the Waldrop dapp once — the SDK reads the resulting object
via client.subscription.get({ owner }).
SEAL — threshold encryption
When you set encrypted: true on upload, the SDK runs the plaintext through
SEAL before the publisher PUT. SEAL is identity-based threshold encryption:
The identity is a 53-byte string the SDK constructs:
The 16-byte marker is what the BlobRef records on-chain. Per-blob shares
key off this marker — granting access to one blob doesn't grant access to
the whole store.
What SEAL protects · what it doesn't
| Protected | Not protected |
|---|---|
| The blob contents | Filename + MIME type (plaintext on-chain) |
Who can decrypt (gated by seal_approve) | Upload metadata (size, hash, sender address) |
| Network observers (publisher knows the source IP) |
To hide filenames too, you'd need encrypted metadata — out of scope for this SDK version.
Walrus epochs vs Sui epochs
Two separate epoch clocks, both used by Waldrop:
~24 hours. Recorded on every BlobRef as storedEpoch /
expiryEpoch — used for "is this blob still active"
checks.
1 day on testnet, 14 days on mainnet. The unit you pay storage in —
client.blob.upload({ epochs }) is Walrus epochs, not Sui epochs.
The contract just stores expiryEpoch as a number — it's intentionally
ambiguous about which clock. The dapp uses Sui epoch since that's what's
trivially readable on-chain.
The complete upload pipeline
Putting it all together:
Each step is independent — if step 4 succeeds but step 6 fails, you have a checkpoint. See the Resume guide.