BAMFS
Protocol

Architecture

Two layers: permissionless content, and an NFT that points at it.

BAMFS is a content-addressed, immutable filesystem that lives entirely in contract storage. Everything else follows from one split.

Two layers

The content layer is permissionless and immutable. Anyone can write bytes. Nothing is owned, nothing can be changed, and identical content deduplicates on write. A root CID resolves whether or not anybody has claimed it.

The project layer is an NFT. A project is an ABX token, and ownerOf is the entire authorization model. Its owner publishes root CIDs and moves tags.

Content does not need a project to exist or resolve. A project is one representation of content, and anyone can build a different one over the same bytes.

The content layer

Four contracts, each doing one thing:

ContractRole
IStorageBackend → SSTORE2BackendByte-blob storage. A write deploys a contract whose bytecode is the blob.
ContentStoreDeduplication. store(bytes) → keccak256(bytes); storing a known chunk writes nothing.
FileStoreAn ordered list of chunk hashes, plus a MIME type and a compression id.
DirectoryStoreA lex-sorted list of (name, childCid, isDirectory) triples.

The CIDs are plain keccak hashes behind a one-byte domain tag:

fileCid = keccak256(
    0x01 || keccak256(chunkHash_0 || … || chunkHash_n)
         || keccak256(bytes(mimeType))
         || uint8(compression)
         || bytes32(outputHash)
)

entryHash_i  = keccak256(keccak256(bytes(name_i)) || cid_i || bool(isDirectory_i))
directoryCid = keccak256(0x02 || keccak256(entryHash_0 || … || entryHash_n))

outputHash commits to the decompressed bytes and is bytes32(0) when the file is uncompressed — the field is always present so the preimage is uniform. It is why two files with identical stored bytes but different codecs get different CIDs.

Two consequences worth internalizing:

  • The backend address is part of every CID. Chunk ids are keccak256(abi.encodePacked(backend, data)), so a different backend address is a different content-address space — not a configuration preference. Use the canonical addresses.
  • Chunk size is a backend property, not a protocol one. The EVM backend caps at 24,575 bytes (EIP-170 minus a STOP prefix). File CIDs are computed over chunk contents, so a backend with a different cap yields the same CID for the same input bytes.

There is no protocol-level cap on directory entries — only the block gas budget at createDirectory time.

The project layer

Project state rides on ABX PostParams:

ParamHolds
bamfs.cidThe current root CID.
bamfs.nameA display name. Metadata only.
bamfs.gatewayAn optional preferred IPFS gateway.

BamfsProjectHook is a configure hook — a veto that runs before any param value persists. It is what makes the layer coherent:

  • Roots are kind-locked. The first publish records whether the root is a directory or a file, by probing the stores. Every later publish re-probes and reverts with RootKindLocked on a mismatch. CIDs carry no kind bit, so persisting it once lets consumers branch on a single field.
  • Versions are append-only. Each publish appends to a uint32-numbered history held in hook storage, alongside a collection-wide publish ledger.
  • Tags are mutable named pointers. latest is reserved and always resolves to the current root.

A second hook, BamfsProjectEnumeration, runs on transfer and maintains the owner → tokens index. ABX collections are not ERC721Enumerable, so without it "which projects does this address own" has no on-chain answer.

Ownership transfer is a plain ERC-721 transfer: one transaction, immediate, irreversible. It hands over the sole right to publish and to move tags.

No indexer, by construction

Every query is an eth_call against contract storage. Version history, tags, the recent-publish feed, and ownership all live in hook storage that the hooks maintain as writes happen.

An earlier design read these from event logs, which worked until providers pruned them — they keep a rolling window, so an index built on events expires while one built on storage does not. That correction is why the hooks carry state at all.

Companion contracts

Optional, and none of them are on the path to reading content:

  • IpfsCidResolver derives a canonical IPFS CIDv1 on-chain. See IPFS.
  • IpfsCidVerifier records ZK-proven CID bindings. See ZK proofs.
  • CrossChainRegistry records bridge-authenticated existence claims. See Cross-chain.

Determinism

  1. Same bytes → same file CID, across clients and languages.
  2. Same tree → same directory CID, given lex-sorted entries.
  3. Same tree → same IPFS CID, for codecs the chain can inflate.

Papers

The IPFS-derivation techniques are published as defensive publications:

On this page