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
| Option | Cost | Persistence | Decentralization |
|---|---|---|---|
| BTFS | Current client and storage-service pricing | Depends on the storage contract, renewal, and replica availability | Decentralized over BTFS network |
| IPFS + pinning service (e.g., Pinata) | Subscription or pay-per-pin | As long as pinned | Decentralized over IPFS network |
| Arweave | One-time payment for permanent storage | Permanent | Decentralized |
| Self-hosted CDN | Hosting fee | As long as you maintain the host | Centralized |
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-clientAfter 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...).
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 legacybtfs wallet depositflow 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 withbtfs 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
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
fiThe 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.
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.jpegCalculate SHA-256 digests for the original and downloaded files and confirm that they match:
Linux:
sha256sum coral.jpeg downloaded-coral.jpegmacOS:
shasum -a 256 coral.jpeg downloaded-coral.jpegIf 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:
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/QmWq4cp588QD8tzrSxvPs2bGikDdKyA35BT3iysBcP1jFDStep 6 — Use the URI as tokenURI when minting
tokenURI when mintingUse 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
- TRC-721 — standard overview
- TRC-721 protocol interfaces — function and event reference for TRC-721
- Issuing a TRC-721 token — deployment and minting walkthrough
Updated 7 days ago