Hosting NFT metadata on BTFS

Upload your NFT image and metadata JSON to BTFS — TRON's decentralized file network — and use the resulting URIs as your tokenURI.

📘

Prerequisites

BitTorrent File System (BTFS) is a decentralized file-sharing protocol built on the TRON network and the BitTorrent ecosystem. NFT metadata — JSON files describing each token's name, image, and attributes — is stored off-chain and referenced by URI from the contract's tokenURI function. BTFS provides one option for hosting that metadata in a way that does not depend on a single web host staying online.

This guide walks you through uploading both an image and a metadata JSON to BTFS, getting the URIs you can plug into your TRC-721 contract.

Where to host metadata: BTFS vs alternatives

OptionCostPersistenceDecentralization
BTFSCurrent client and storage-service pricingDepends on the storage contract, renewal, and replica availabilityDecentralized over BTFS network
IPFS + pinning service (e.g., Pinata)Subscription or pay-per-pinAs long as pinnedDecentralized over IPFS network
ArweaveOne-time payment for permanent storagePermanentDecentralized
Self-hosted CDNHosting feeAs long as you maintain the hostCentralized

BTFS is a natural choice within the TRON ecosystem.

Step 1 — Install and initialize BTFS

Install from the official go-btfs repository, then download the latest stable build for your operating system and architecture from Releases and verify the SHA-256 digest shown on the release page.

When btfs init initializes the local node, it generates a node key from which both TRON-format and BTTC-format addresses can be derived. After initialization, apply the storage-renter profile to enable purchasing storage from the BTFS network:

btfs config profile apply storage-client

After the node starts, run btfs id to inspect its addresses. The gas and storage-payment flow in this guide uses the BTTC-format BttcAddress (0x...); do not confuse it with the TRON Base58-format address (T...).

BTFS initialization output showing the node address

Step 2 — Start the node and prepare storage funding

Run btfs daemon. On first startup, the node displays its newly generated BTTC address. If it reports an insufficient gas balance, leave the daemon running, verify the address and required balance printed by your local node, and send enough BTT over the BTTC network from a wallet you control to bring that address up to the amount required by this client run. BTT pays BTTC gas; it does not pay storage fees. After detecting the balance, the node continues deploying the vault. When deployment finishes, run btfs id and confirm that BttcAddress matches the address you funded and that VaultAddress is also present.

An uploader is a BTFS storage renter and pays storage providers in WBTT. Obtain WBTT through the installed BTFS Dashboard's current Swap flow or a BTTC wallet you control, then transfer it to this node's BttcAddress. Use the local node to deposit that WBTT into its own vault:

# Show the vault's WBTT balance; output uses 10^18 base units
btfs vault balance

# Example: move 1 WBTT (10^18 base units) from this node's BttcAddress to its VaultAddress
WBTT_AMOUNT_WEI=1000000000000000000
btfs vault deposit "$WBTT_AMOUNT_WEI"

After the deposit transaction confirms, run btfs vault balance again and confirm that the balance increased. Then upload a small file first to validate the complete flow. Dashboard layouts can change between releases, so check the installed version's release notes, btfs vault --help, and on-screen prompts before moving funds.

⚠️

The legacy btfs wallet deposit flow does not apply to current BTFS releases. Never send funds to an address copied from a screenshot, sample output, or third-party guide. Use only the addresses generated by your local node and verified with btfs id.

Step 3 — Upload the image

Step 3.1 — Add the image to your local node

Prepare an image file (this guide uses coral.jpeg) and add it to the local BTFS node:

btfs add coral.jpeg
BTFS add output showing the resulting hash

The output includes a hash like QmUK9nwtLEiHBJ48HAZHNmSQ53U6ADbRhATxs2tomadwKw — copy this hash for the next step.

Step 3.2 — Upload to the BTFS network

Upload the file by hash. For NFT metadata that requires long-term hosting, enable auto-renewal with BTFS 4.1 or later. For a fixed term instead, replace --autorenew with an explicit duration such as --storage-length=30. Auto-renewal still requires sufficient funds in the vault; monitor its balance and renewal status rather than treating a content-addressed URI itself as a guarantee of permanent storage.

The complete example below uses jq to extract the session ID from this upload's response, query its status, and confirm auto-renewal. If jq is unavailable, the script prints a prerequisite message and does not start the upload:

if command -v jq >/dev/null 2>&1; then
  FILE_CID=QmUK9nwtLEiHBJ48HAZHNmSQ53U6ADbRhATxs2tomadwKw
  UPLOAD_RESULT=$(btfs storage upload "$FILE_CID" --autorenew) &&
    printf '%s\n' "$UPLOAD_RESULT" &&
    SESSION_ID=$(printf '%s\n' "$UPLOAD_RESULT" | jq -er '.ID') &&
    btfs storage upload "$SESSION_ID" status &&
    btfs storage upload renew info "$FILE_CID"
else
  printf '%s\n' "Install jq, then run this block again" >&2
fi

The status command checks only the current state once. Re-run it with the same SESSION_ID until every shard is complete. You can also run btfs storage upload renew list to see every file with auto-renewal enabled on this node.

BTFS daemon log showing File storage successful

Step 3.3 — Verify the file is retrievable

On a second BTFS node that has never imported this CID, retrieve the file to confirm that the node can discover and fetch it over the network:

btfs cat /btfs/QmUK9nwtLEiHBJ48HAZHNmSQ53U6ADbRhATxs2tomadwKw > downloaded-coral.jpeg

Calculate SHA-256 digests for the original and downloaded files and confirm that they match:

Linux:

sha256sum coral.jpeg downloaded-coral.jpeg

macOS:

shasum -a 256 coral.jpeg downloaded-coral.jpeg

If a second node is unavailable, retrieve https://<GATEWAY_HOST>/btfs/<CID> through an independent BTFS gateway and compare the content; do not perform this check through the uploader node's own local gateway. A public gateway may be rate-limited, migrated, or retired, so combine this check with the completed upload-session status rather than treating gateway access alone as proof of long-term availability.

Step 4 — Construct the metadata JSON

Create a JSON file (this guide uses coral.json) following the TRC-721 metadata schema. Use the content-addressed btfs://<CID> form for image so the metadata is not tied to one HTTP gateway:

{
  "name": "Coral #1",
  "description": "A unique TRC-721 collectible.",
  "image": "btfs://QmUK9nwtLEiHBJ48HAZHNmSQ53U6ADbRhATxs2tomadwKw",
  "attributes": [
    { "trait_type": "Color", "value": "Pink" },
    { "trait_type": "Rarity", "value": "Rare" }
  ]
}

Step 5 — Upload the metadata JSON

Upload the JSON to BTFS the same way as the image — btfs add followed by btfs storage upload with the same storage term or --autorenew option — to get a hash for the metadata file. The image and metadata are separate CIDs; when using auto-renewal, run renew info for each one and confirm that both are enabled:

BTFS upload of the metadata JSON file

After the upload completes, retrieve the metadata through the independent node or gateway used in Step 3.3 and validate the returned JSON:

btfs cat /btfs/QmWq4cp588QD8tzrSxvPs2bGikDdKyA35BT3iysBcP1jFD

Step 6 — Use the URI as tokenURI when minting

Use the metadata URI from Step 5, such as btfs://QmWq4cp588QD8tzrSxvPs2bGikDdKyA35BT3iysBcP1jFD, as the tokenURI argument when calling mintWithTokenURI. Before minting, confirm that the target wallet, marketplace, or indexer resolves btfs://. If it accepts only HTTPS, use a gateway conversion layer that you can maintain or replace instead of binding the token to an example gateway domain. See Issuing a TRC-721 token for the full minting flow.


Related resources