BAMFS
Use BAMFS

Projects

An NFT that points at content. Its owner publishes versions and moves tags; nobody else can.

A project is an ABX NFT. ownerOf is the entire authorization model — publishing rights follow the token, with no migration step and no separate registry.

Identity

FieldMutablePurpose
(chainId, tokenId)noCanonical identity. The token id is the project id.
owneryesownerOf(tokenId). The only address that can publish.
bamfs.nameyesA display label. Not unique, not an identifier.
root kindlocked at first publishDirectory or single file.

The name is display metadata only, so "My Cool Art" is fine. The limits are 128 bytes and no control characters. Always store (chainId, tokenId) as the handle — never the name.

Creating one

Minting and publishing are separate acts, and upload does both:

bamfs upload ./build --name "My Cool Art" --tag v1.0.0

Keeping them apart is what lets you publish into a token you already hold, or bought from someone else:

bamfs upload ./build --project-id 17

With neither flag, the upload is still complete — the root is addressable at /g/cid/<root>/ whether or not a token ever points at it.

The mint is open and free with zero royalty. Nothing gatekeeps it.

Versions

Publishing writes the bamfs.cid param on the token, and BamfsProjectHook appends to an on-chain history as it does:

await client.publishVersion(tokenId, rootCid);

Reading that history back is a single eth_call against hook storage. There is no indexer, and nothing here depends on event logs — providers prune those, and an index that expires is not an index.

Version numbers start at 1 and never change meaning. The first publish also locks the root kind: publish a file root and every later version must be a file root, or the hook reverts with RootKindLocked.

Tags

Mutable named pointers from (tokenId, tag) → version, authorized against ownerOf:

bamfs tag set 17 stable 2
bamfs tag get 17 stable
bamfs tag list 17
bamfs tag rm 17 stable
  • Charset is ^[a-z0-9][a-z0-9._-]{0,63}$. Dots are allowed so semver shapes work. Uppercase is rejected, so case-folding cannot produce two tags that look identical but address differently.
  • latest is reserved and virtual. It always resolves to the head, has no storage, and writes to it revert — so every project has a resolvable latest and no off-chain resolver needs a special case.
  • A tag pins its version. Moving the head does not drag a tag forward. That is the point: a contract reading tag/stable/render.js is undisturbed by an unrelated publish.

Semver is a convention, not a protocol feature. The on-chain primitive is a flat tag → version map; the SDK ships isSemverTag and pickLatestSemverTag.

A tagged publish is two transactions — the CID lands on the collection and the tag on the hook, so no single-contract multicall spans them.

Transferring

One transaction, immediate and irreversible:

bamfs transfer 17 0xNewOwner

There is no window in which to reconsider. Transferring the token transfers the sole right to publish and to move tags. Both the CLI and the web UI confirm before sending.

The upside is that projects are sellable on any marketplace.

A preferred gateway

bamfs.gateway is an efficiency hint. The gateway tries that prefix first, then falls back to rebuilding from chain — so an unset, stale, or dead value costs latency and never correctness. Set it if you pin; ignore it otherwise.

Resolving

/g/p/<chainId>/<tokenId>/latest/<path>     the head
/g/p/<chainId>/<tokenId>/v/<n>/<path>      a fixed version
/g/p/<chainId>/<tokenId>/tag/<tag>/<path>  a tag
/g/cid/<rootCid>/<path>                    raw content, no project needed

A directory root with no sub-path serves index.html when present, and a listing otherwise — which is what makes a project URL a website.

Caching follows whether a URL can ever address different bytes: /cid/… and /v/N/… are immutable and cached for a year; latest and tags get short freshness with a long stale-while-revalidate. Responses carry X-BAMFS-Source: ipfs|chain.

On this page