Storage
Storage is where a project's source bytes live. The chain commits to those bytes by hash, and a backend holds them. Storage is stateless in the CLI: there is no config file, so you select a backend per command with a flag, or set it in the environment.
Backends
| Backend | What it is | Notes |
|---|---|---|
fs | local disk (the default) | no public URL; for local development |
cloud | S3, R2, or a CDN | needs a public read base (--public-base or ABX_S3_PUBLIC_BASE) |
ipfs | IPFS | Pinata, or a local Kubo node |
arweave | Arweave | permanent; uses Turbo by default |
Select a backend with --backend, or set ABX_STORAGE_BACKEND. The order is the flag, then the
environment, then the fs default. Secrets are read from the environment only, for example
ABX_S3_ACCESS_KEY_ID and ABX_S3_SECRET_ACCESS_KEY for cloud, or PINATA_JWT for IPFS.
Commands
abx storage show: the resolved backend, and where each setting came from.abx storage upload <path>: upload one file and print its locator, which is the URI thatabx attachtakes.abx storage balance: Arweave upload credits and the funded address.abx storage topup --usd <n>: buy Arweave upload credits.abx storage backup-key --out <path>: copy the managed storage key to a path you choose.
Arweave and Turbo
Arweave uploads go through Turbo by default. Uploads under 100 KB are free and need no setup. Larger
uploads use prepaid credits. On the first upload, a managed Arweave key is created at
.abx-self-host/arweave-key.json; it signs uploads and holds the credits, and it is reused across
projects. Read paths such as serve and verify never create a key.
Credits attach to an identity. --storage-signer selects it: arweave, the default, uses the managed
key; eth uses your EVM signing key as the Turbo identity, so credits sit on that address. With eth
and --sign, a browser wallet signs each upload with no gas.
Check credits with abx storage balance before concluding that an upload needs funding, since both the
managed key and the ETH wallet can hold credits. Back up the managed key with abx storage backup-key
before topping up. A gateway or provider is swappable, because the on-chain hash is the anchor.
A fresh Arweave URL can 404 for a few minutes
arweave.net — the default gateway, and the one baked into the locator — indexes new uploads on a delay,
so a just-published render can 404 there for several minutes after Turbo has already confirmed it. The
bytes are fine; only that gateway is behind, and other ar.io gateways typically serve them sooner. This
is propagation, not a failed render: do not re-run abx render --force, which re-uploads everything
and fixes nothing. abx render prints this reminder after an Arweave publish.
The gateway is fixed at publish time, because the locator is what gets registered with the resolver —
a redirect can't repair it afterwards. To bake a different one, set ABX_ARWEAVE_GATEWAY before you
render (abx storage show prints the gateway in effect).
Is it retrievable yet?
"Accepted" and "retrievable" are different states, and an upload result only tells you the first. abx storage status <locator> tells you the second:
abx storage status ar://<txid>
abx storage status ipfs://<cid>/0.png --json # exits non-zero until readyIt probes the gateway your project actually uses plus two others, because propagation is per-gateway — so it can distinguish three states rather than two:
| Verdict | Means | What to do |
|---|---|---|
ready | Your gateway serves the bytes. | Safe to reference in a token. |
propagating | Another gateway serves them, so the data provably exists on the network; yours is behind. | Wait. Do not re-upload — you would pay twice for bytes that are already stored. |
unreachable | No probed gateway serves them. | Either still settling or the locator is wrong. The check says so rather than guessing, because from outside those look identical. |
The exit code is the verdict, so waiting is a one-liner rather than a retry ladder you write yourself:
until abx storage status ar://<txid> --json > /dev/null; do sleep 10; doneAnything programmatic should call locatorStatus() from @artblocks/abx-storage directly instead of
spawning the CLI per check.
Never upload on the display path during a mint
The failure this exists to prevent: upload during a mint, write the locator into the token, and the token
renders broken for the first minutes of its life. Publish first, confirm ready, then reference. Two
separate integrations hit this before the check existed, and both independently built their own version
of it.