Self-hosting
A live project runs two reference services: a resolver and an effects runner. You run both on infrastructure you own — or point at a remote provider that runs them for you; the interface is the same either way. The resolver holds no signing key.
The resolver
The resolver re-indexes the chain's event logs into a local projection and serves the token API. It is read-only. Its endpoints include:
GET /t/{chainId}/{address}/{tokenId}: token metadata — ERC-721tokenURIon a 1/1, Series, or code project; ERC-1155urion an edition, same route — and.../imagefor the image.GET /c/{chainId}/{address}: ERC-7572 collection metadata.GET /api/project/{address}: the full reconstructed project state, includingabx_provenance.GET /d/{chainId}/{address}: a per-contract dashboard.GET /health.GET /.well-known/abx-service: the public service descriptor.POST /v1/projectsand the rest of the bearer-authed control plane: register, list, deregister, reindex, status. Enabled by settingABX_RESOLVER_ADMIN_TOKENon the node; the token authorizes index control only, never signing.
Don't hand-build these URLs. A contract commits its own metadata base on-chain
(tokenURIBase / contractURIBase), so the chain — not this list — is the authoritative answer to
where a given project's metadata lives. abx tokenuri <address> and abx contracturi <address> read
it from the contract and follow it. That matters because the routes look similar enough to guess
wrong: dropping the {tokenId} off /t/… does not give you collection metadata (that's /c/…).
When a read does miss, the response says which of three things went wrong, in a machine code — see
the error taxonomy. A 400
invalid_request means the path shape was wrong and names the right template; 404 unknown_route
means no such route here; 404 not_registered means the path was fine but this node doesn't index
that contract. Only the last one is about the project, and none of them mean the service is down.
Run it locally with abx serve. This also runs a chain watcher that polls for new logs, about every
12 seconds, and re-indexes each registered project. Deploy it to a host with
abx deploy-resolver --provider <fly | render | vps>, which writes a self-contained deployment
artifact for that target. The production image installs @artblocks/abx-cli from npm and runs without
a browser.
Is it caught up?
abx status answers that. Bare, it lists every project this node tracks with its indexing state; with
an address, it shows one project in detail — the scan floor, blocks indexed against chain head, and the
cause if something went wrong:
abx status # this node: chain, storage, and every project's state
abx status <address> # one project: lifecycle, floor, blocks indexed vs head
abx status <address> --remote <name> --watch # ask a hosted node or a provider, and follow alongThe five states are queued → backfilling → live, plus stale (was live, now lagging — still
serving) and failed (carries a cause; retried with backoff). They are the same five words a
managed provider reports, so the question doesn't
change shape when the answer comes from someone else's infrastructure.
Two behaviors worth knowing about your own node: a registration is durable before catch-up starts, so
a flaky RPC makes for a slower backfill rather than a lost project; and a backfill interrupted by a
restart is re-queued and picked up by the watcher rather than sitting there registered-but-empty.
The chain watcher also marks projects stale when it falls far behind head, which is how a failing RPC
becomes visible instead of silently serving old state.
Two optional knobs: ABX_REGISTER_DEADLINE_MS (default 8000) is how long a registration may hold the
HTTP request open before it answers "accepted, still catching up" and finishes in the background — the
registration is durable either way, so this only decides who waits. ABX_STALE_LAG_BLOCKS (default
5000) is how far behind head the watcher may fall before it reports stale.
abx status is about serving: who has the data and how fresh it is. For what the chain itself says
about a contract — owner, supply, royalty, locks — use abx state <address>.
Because a resolver re-indexes from the chain, moving to a new host is a configuration change. See migrating.
The effects runner
A code project's still image is rendered off-chain, so the resolver image stays browser-free and a separate runner does the rendering. The runner uses Playwright and Chromium. It watches the spine, renders new mints and parameter changes, and publishes each render to the resolver.
- Run it locally with
abx effects. Add--onceto sweep every project once and exit. - Host it with
abx deploy-effects --resolver-url <https://your-resolver>. - Render one project's missing stills with
abx render <address>.
Set ABX_EFFECTS_URL on the resolver so its chain watcher notifies the runner as soon as something
changes. A periodic sweep is the fallback.
Storage
The resolver and runner read and serve the source bytes from a storage backend. For a hosted pair, use a shared, addressable backend such as IPFS, Arweave, or S3-compatible cloud, not local disk. See Storage.
Migrating
abx migrate <address> --from <source-resolver-url> --to <dest-resolver-url> moves a project's
off-chain state to a new resolver. It reconstructs the on-chain truth, copies the source resolver's
off-chain values (description, external URL, off-chain traits, image locators), re-pins images to a
durable backend after verifying them against the on-chain hash, and runs a parity check between source
and destination. It does not cut over: you re-point DNS, or the on-chain base URI with set-token-uri
and set-contract-uri, when you are ready. The two resolvers never talk to each other.