EVM × Sui Walrus GitHub
Six examples · all implemented

Put a Walrus blob behind an EVM contract.

Six small dApps showing the integration shape: an EVM contract that stores a Walrus URL or blob id, and the browser / CLI code that puts the bytes there. Each example is a tiny Solidity contract plus a few hundred lines of TypeScript. Click any title for a step-by-step walkthrough you can run locally.

Repo: MystenLabs/evm-sui Stack: OZ ERC-721 · Foundry · Next.js · viem · wagmi · @mysten/walrus Networks: Anvil (local) · Walrus testnet
the six

Six examples, six contract shapes.

Click any title for the per-example walkthrough.

01

EvmWal NFT — image + metadata

implemented · 01-evmwal-nft/

What it shows

A vanilla OpenZeppelin ERC-721 + ERC721URIStorage whose tokenURI points at a Walrus aggregator URL. Two write paths drop the same kind of string into the same contract: an operator backend route signing with @mysten/walrus, or the public Walrus testnet publisher PUT.

ready ~200 LOC 1 URL prefix from an IPFS NFT
Shape
// the entire EVM-side contract surface
function mint(string tokenURI_) external returns (uint256);
function mintTo(address to, string tokenURI_) external returns (uint256);
event Minted(uint256 indexed tokenId, address indexed minter, string tokenURI_);
picker
walrus PUT
mint(uri)
02

Walrus Sites for dApp frontend hosting

implemented · 02-walrus-sites/

What it shows

Publish a Next.js or SPA build with site-builder publish; a Sui object holds the site, a SuiNS name points at it, and the public wal.app portal resolves it for any visitor. Updates land via one Sui transaction. Optional ENS bridge via DNSLink or a CNAME to the portal host.

trivial ~10 LOC config SuiNS link + optional CNAME
Shape · CLI + SuiNS link
# build once, publish to Walrus Sites
pnpm build
site-builder publish ./out \
  --epochs 200

# → site_object_id: 0xa1b2…
# → resolves at <base36-of-id>.wal.app
#   (or yourdapp.wal.app once linked to a SuiNS name)
build/
site-builder
SuiNS · wal.app
03

WalrusResolver — mutable pointer

implemented · WalrusResolver.sol + keeper

What it shows

A compact Solidity registry mapping ENS name → current blobId + Sui object id, gated on ENS ownership. One eth_call resolves the pointer; one aggregator GET returns the bytes. A small TS keeper extends the underlying Sui Blob's epoch retention on a cron — and anyone can run a keeper for any name they're willing to pay WAL for.

trivial ~50 LOC total 1 contract deploy + keeper cron
Shape · Solidity registry
// WalrusResolver.sol — ENS-gated pointer registry
contract WalrusResolver {
  function setWalrusBlob(
    bytes32 node, bytes32 blobId, bytes32 suiObjectId, bytes8 ct
  ) external;

  function walrusBlob(bytes32 node) external view returns (
    bytes32 blobId, bytes32 suiObjectId, bytes8 contentType
  );

  event WalrusBlobChanged(
    bytes32 indexed node, bytes32 blobId, bytes32 suiObjectId, bytes8 contentType, uint64 at
  );
}
EVM read
aggregator GET
verified bytes
04

DAO governance proposals

implemented · 04-dao-proposals/ + Governance.sol

What it shows

One contract storing proposalId → (proposer, blobId, deadline, yes, no). Proposal body uploaded to Walrus via the public publisher (so the proposer pays, not the DAO operator); contract stores the 32-byte blobId. Two CLIs (propose + tally) drive the end-to-end flow.

straightforward ~100 LOC showcase-only voting surface store blobId, not CID
Shape · proposer-pays write + DAO-readable bytes
// Governance.sol — body lives on Walrus, weight on-chain
struct Proposal {
  address proposer;
  bytes32 blobId;       // Walrus blob holding the proposal markdown
  uint64  deadline;
  uint128 yes;
  uint128 no;
}
function propose(bytes32 blobId, uint64 deadline) external returns (uint256 id);
function vote(uint256 id, bool support) external;
proposer
walrus PUT
propose(blobId)
05

Verifiable token list / dApp manifest

implemented · 05-verifiable-manifest/

What it shows

An 80-line TS client that combines showcase 03's mutable pointer with a strict JSON schema. Resolves an ENS name in one eth_call, fetches the manifest by content-addressed blob id, and returns a typed object. Optional Reed-Solomon hash verification via @mysten/walrus for trustless retrieval.

straightforward ~80 LOC swap CID lookup for blobId lookup
Shape · one fetch + one verification
// resolve "tokens.uniswap.eth" → current blobId → manifest JSON
const { manifest, blobId, contentType } = await resolveTokenList("tokens.uniswap.eth", {
  rpcUrl: EVM_RPC_URL,
  resolverAddress: RESOLVER_ADDRESS,
  aggregator: "https://aggregator.walrus-testnet.walrus.space",
});

// manifest is a typed Uniswap TokenList; blobId is the 32-byte source of truth.
pointer.call
aggregator GET
JSON.parse
06

Quilted ERC-721 collection drop

implemented · 06-quilted-collection/ + QuiltedCollection.sol

What it shows

A 10 000-token ERC-721 backed by two Walrus Quilts (images first, then metadata, so the metadata can reference the images' quiltId without a circular dep). The contract stores one quiltId; tokenURI(id) deterministically returns the aggregator URL for that token's metadata. One Walrus upload per layer, 10 000 retrievable tokens.

solid ~120 LOC requires walrus CLI two-quilt packer + 1-line tokenURI
Shape · quilt + deterministic tokenURI
# pack images first, then metadata referencing them
pnpm pack:quilt ./images    # → QID_IMAGES
# generate meta/N.json with image=<agg>/by-quilt-id/<QID_IMAGES>/N.png
pnpm pack:quilt ./meta      # → QID_METADATA

// QuiltedCollection.tokenURI
function tokenURI(uint256 id) public view returns (string memory) {
    return string.concat(aggregator, "/v1/blobs/by-quilt-id/", quiltId, "/", id.toString(), ".json");
}
drop/
store-quilt ×2
contract(QID_META)