Source constraint: All information in this skill is sourced from the Walrus Move example page, the Walrus Move example package, and the Walrus contracts. When extending this skill, only pull from these sources.
Every Walrus blob has a corresponding Blob object on Sui, defined in the walrus::blob module. It has the key and store abilities, so you can hold it, wrap it in your own objects, and transfer it from your own package. This enables patterns like NFTs backed by Walrus data, marketplaces that escrow blobs, and contracts that gate who can manage a blob.
All patterns in this skill are derived from:
- https://docs.wal.app/docs/examples/move
- https://github.com/MystenLabs/walrus/tree/main/docs/examples/move/walrus_dep
- https://github.com/MystenLabs/walrus/tree/main/contracts/walrus
If unsure about the Walrus Move API, check the source contracts before answering.
Related skills
| Topic | Skill | Load when |
|---|---|---|
| Move fundamentals | sui-move |
Writing Move code on Sui (abilities, TxContext, init) |
| Object model | object-model |
Ownership types, wrapping, dynamic fields |
| Project setup | sui-move-project |
Move.toml configuration, dependencies, [environments] |
| Blob lifecycle | walrus-blob-lifecycle |
Extending, deleting, sharing blobs from the CLI |
| Confidentiality | walrus-data-security |
Encrypting blob content before storage |
Skill Content
Key concepts
walrus::blob::Blob. The on-chain representation of a blob:public struct Blob has key, store. Because it hasstore, you can embed it in your own object types and transfer it. The object holds metadata only (blob ID, size, encoding, registration and certification epochs, the backingStorageresource, deletability). The data itself lives on Walrus storage nodes; Move cannot read blob content.- Wrapping pattern. A struct with
keyand its ownid: UIDthat holds ablob: Blobfield. Once wrapped, only the wrapping module's functions can reach the blob again, which gives your package control over where the blob can be transferred next and who can manage it. - Lifecycle through
walrus::system. Extending and deleting go through the sharedSystemobject.walrus::blobprovidesburnand the metadata functions. - Shared blobs.
walrus::shared_blobwraps aBlobin aSharedBlob, a shared object that works as a tip jar: anyone can add WAL and anyone can spend the stored funds to extend the blob.
Move.toml setup
Declare the Walrus package as a git dependency. Use the 2024 edition; with it the Sui framework and the Move standard library are resolved automatically, so do not add a Sui = { git = ... } dependency or an [addresses] section (see sui-move-project).
[package]
name = "my_walrus_app"
edition = "2024"
[dependencies]
Walrus = { git = "https://github.com/MystenLabs/walrus.git", rev = "main", subdir = "contracts/walrus" }
Which subdir to use:
| Target | subdir |
Notes |
|---|---|---|
| Development sources | contracts/walrus |
What main is developed against |
| Mainnet-deployed sources | mainnet-contracts/walrus |
The sources deployed on Sui Mainnet |
| Testnet-deployed sources | testnet-contracts/walrus |
The sources deployed on Sui Testnet; the latest version is at the bottom of its Move.lock |
The Network Reference lists the deployed package and object IDs. Pin rev to a commit for reproducible builds rather than a branch name.
The upstream example package (docs/examples/move/walrus_dep/Move.toml) still declares an explicit Sui git dependency, [addresses], and edition = "2024.beta"; that is the older form. The Walrus contracts themselves pin std and sui explicitly with implicit-dependencies = false only because their simulation tests cannot resolve implicit dependencies, which does not apply to your package.
Example: WrappedBlob
module my_app::wrapped_blob;
use walrus::blob::Blob;
public struct WrappedBlob has key {
id: UID,
blob: Blob,
}
public fun wrap(blob: Blob, ctx: &mut TxContext): WrappedBlob {
WrappedBlob { id: object::new(ctx), blob }
}
After wrapping, only this module's functions can reach the blob again. Add access control, metadata, or business logic around it.
Read blob properties
The walrus::blob module exposes accessors for every Blob field:
| Accessor | Returns | Description |
|---|---|---|
object_id |
ID |
The Sui object ID of the Blob object |
blob_id |
u256 |
The blob ID that identifies the data on Walrus |
size |
u64 |
The unencoded blob size in bytes |
encoding_type |
u8 |
The blob's erasure encoding |
registered_epoch |
u32 |
The Walrus epoch of registration |
certified_epoch |
&Option<u32> |
The epoch of first certification; none while uncertified |
storage |
&Storage |
The storage resource backing the blob |
end_epoch |
u32 |
The end epoch of the storage resource (exclusive) |
is_deletable |
bool |
Whether the owner can delete the blob before expiry |
encoded_size |
u64 |
The encoded size for a given number of shards |
To check whether a blob is still live, read certified_epoch (stays none until Walrus certifies the blob) and compare end_epoch against the current epoch from the shared system object with system.epoch().
Manage the blob lifecycle
The walrus::system module exposes lifecycle functions on the shared System object:
extend_blob(system, blob, extended_epochs, payment)extends storage byextended_epochsand draws the storage fee from aCoin<WAL>.extend_blob_with_resource(system, blob, extension)extends with aStorageresource you already own; it must match the blob's storage size and last longer.delete_blob(system, blob)consumes a deletable blob and returns itsStorageresource for reuse.
walrus::blob provides burn(blob), which destroys the Blob object without deleting the data from Walrus and without refunding storage, and metadata functions such as insert_or_update_metadata_pair, remove_metadata_pair, add_metadata, and take_metadata.
Share a blob
walrus::shared_blob wraps a Blob in a SharedBlob:
new(blob, ctx)shares the blob with zero funds.new_funded(blob, funds, ctx)shares it with an initialCoin<WAL>balance.fund(shared_blob, coin)adds WAL to the stored funds.extend(shared_blob, system, extended_epochs, ctx)extends the wrapped blob using the stored funds.
The CLI offers the same through walrus share --blob-obj-id <SUI_OBJ_ID> (see walrus-blob-lifecycle). You can also wrap a Blob in your own shared object with custom rules instead of using SharedBlob.
Storage pools from Move
Storage pools (a preview feature) are exposed as public functions on walrus::system: create_storage_pool, register_pooled_blob, certify_pooled_blob, delete_pooled_blob, extend_storage_pool, increase_storage_pool_capacity, and the Move-only create_storage_pool_with_storage, decrease_storage_pool_capacity_by_size, burn_expired_pooled_blob, and destroy. create_storage_pool returns the pool by value, so your transaction decides whether to keep it owned, wrap it, or share it. See storage pools for the cost model.
Build, test, and publish
sui move build
sui move test
sui client publish --skip-dependency-verification
The Walrus dependency is fetched from Git during the build.
Rules
Blobhaskeyandstore. Wrap it, store it in your objects, and transfer it from your package.- Use the 2024 edition and no explicit Sui dependency. The framework is resolved automatically; a
Sui = { git = ... }line and an[addresses]section are the pre-2024 form. - Pick the
subdirfor the network you deploy to.contracts/walrusfor development,mainnet-contracts/walrusortestnet-contracts/walrusfor the deployed sources. Pinrev. - Wrapped blobs need their own UID. The wrapper has
keyandid: UID; the blob keeps its own. - Lifecycle goes through
walrus::system. Extend and delete take the sharedSystemobject;burnis onwalrus::blob. - Blob content is off-chain. Move sees metadata only. Read content with the CLI, SDK, or HTTP API.
Debugging VMVerificationOrDeserializationError
This error on publish or call means the package was built against contracts that do not match what is deployed. Check in order:
- Wrong
subdirfor the network. Build againsttestnet-contracts/walrusfor Testnet andmainnet-contracts/walrus(orcontracts/walrusat a matching revision) for Mainnet. - Wrong
rev. The commit must match a revision compatible with the deployed package. - Stale
Move.lock. Delete it and rebuild:rm Move.lock && sui move build. EWrongVersion(abort code 1) fromsystem::inner_mut. Walrus uses versioned shared objects; a call through an old package ID aborts after an upgrade. The CLI and SDK refresh package IDs automatically; direct Move callers must use the current package ID from the Network Reference.
Common mistakes
- Saying
Bloblacksstore. It haskey, store; the docs' example embeds it in aWrappedBloband escrows it. - Adding
Sui = { git = ..., rev = "testnet-v1.35.0" }and[addresses]. Not needed withedition = "2024"; mismatched framework pins cause confusing dependency resolution errors. - Using
edition = "2024.beta". Use2024. - Trying to read blob content from Move. Only metadata is on-chain.
- Using
mainasrevin production. Pin a commit. - Calling Walrus system functions with a stale package ID after an upgrade. Query the current package ID; do not hardcode it.