DEV Community

OnFinality
OnFinality

Posted on Originally published at onfinality.io

Base NFT API: Query NFT Data Over Base RPC

If you searched for a Base NFT API, you probably need to read NFT data on Base: who owns a token, what a collection contains, or how a token moved between wallets. There is no single "NFT API" on Base. Instead you combine standard JSON-RPC calls to the Base network with optional indexer services that pre-aggregate ownership and metadata. This page shows what each layer does, how to connect, and how to keep NFT read traffic stable in production.

Which approach fits your NFT workload

Before writing code, decide whether you need raw on-chain reads or pre-indexed data. The right choice depends on how much aggregation you want to do yourself and how fresh the data must be.

Workload Best fit Why
Read one token's owner or balance Base RPC (eth_call) Direct contract read, always current at the queried block
List all tokens owned by a wallet Indexer API or your own indexer RPC alone cannot enumerate holdings without scanning logs
Fetch collection metadata and images Indexer API or tokenURI + off-chain fetch Metadata often lives on IPFS or HTTP, not fully on-chain
Track transfers in real time Base RPC (eth_getLogs or WebSocket) Logs give you the raw Transfer events as they happen
Backfill full transfer history Archive-capable RPC or indexer Historical log queries need archive state and careful paging
Verify a mint or sale on-chain Base RPC (eth_getTransactionReceipt) Receipts confirm what actually executed

A practical pattern is to use Base RPC for authoritative, point-in-time reads and an indexer for list-style queries. Many teams run both: RPC for correctness and indexer for speed on aggregated views.

Base chain settings at a glance

Use these values when adding Base to a wallet, backend config, or test harness. They match the network configuration OnFinality publishes for Base.

Setting Base mainnet Base Sepolia testnet
Chain ID 8453 84532
Native currency ETH (18 decimals) ETH (18 decimals)
Block explorer https://basescan.org https://sepolia.basescan.org
Public RPC (OnFinality) https://base.api.onfinality.io/public https://base-sepolia.api.onfinality.io/public
Transport HTTP HTTP

For a managed endpoint and network details, see the Base RPC network page and the Base Sepolia RPC network page.

Reading NFT data with JSON-RPC

Most ERC-721 and ERC-1155 reads go through eth_call with an encoded function selector. The three calls you will use most are ownerOf, balanceOf, and tokenURI for ERC-721, and balanceOf plus uri for ERC-1155.

Here is a minimal curl example that calls ownerOf(uint256) on an ERC-721 contract. The selector for ownerOf is 0x6352211e, followed by the token ID padded to 32 bytes.

curl -s https://base.api.onfinality.io/public \
  -H "Content-Type: application/json" \
  -d '{
    "jsonrpc": "2.0",
    "id": 1,
    "method": "eth_call",
    "params": [
      {
        "to": "0xYOUR_NFT_CONTRACT",
        "data": "0x6352211e0000000000000000000000000000000000000000000000000000000000000001"
      },
      "latest"
    ]
  }'
Enter fullscreen mode Exit fullscreen mode

The response is ABI-encoded. Decode it as an address. If the token does not exist, the call reverts and you get an error object instead of a value, so handle both cases.

In JavaScript, viem and ethers handle encoding for you. This example reads the owner and token URI of an ERC-721 token:

import { createPublicClient, http, parseAbi } from "viem";
import { base } from "viem/chains";

const client = createPublicClient({
  chain: base,
  transport: http("https://base.api.onfinality.io/public"),
});

const abi = parseAbi([
  "function ownerOf(uint256) view returns (address)",
  "function tokenURI(uint256) view returns (string)",
]);

const owner = await client.readContract({
  address: "0xYOUR_NFT_CONTRACT",
  abi,
  functionName: "ownerOf",
  args: [1n],
});

const uri = await client.readContract({
  address: "0xYOUR_NFT_CONTRACT",
  abi,
  functionName: "tokenURI",
  args: [1n],
});

console.log(owner, uri);
Enter fullscreen mode Exit fullscreen mode

tokenURI usually returns an ipfs:// or https:// link. Resolving it is an off-chain fetch, not an RPC call, so plan for caching and timeouts there.

Enumerating transfers and ownership with logs

To find transfers, query eth_getLogs filtered by the ERC-721 or ERC-1155 Transfer event topic and the contract address. This is how most indexers build ownership tables.

const logs = await client.getLogs({
  address: "0xYOUR_NFT_CONTRACT",
  fromBlock: 0n,
  toBlock: "latest",
});
Enter fullscreen mode Exit fullscreen mode

Two constraints matter. First, providers cap the block range per eth_getLogs request, so you must page through history in chunks. Second, deep historical queries need archive state; a non-archive node may reject or fail them. If you are backfilling a large collection, chunk by block range and persist your progress so a failed request does not force a full restart.

For live transfer feeds, a WebSocket subscription to logs avoids polling. Not every endpoint exposes WebSocket transport, so confirm transport support before designing around it. If you need persistent subscriptions, dedicated node infrastructure is often a better fit than a shared public endpoint.

When a raw RPC endpoint is not enough

RPC gives you correctness but not convenience. It cannot answer "list every NFT this wallet owns" without you scanning logs and reconstructing state. That is the job of an indexer or an NFT data API.

Common reasons teams add an indexer layer on top of Base RPC:

  • Wallet portfolio views that must return quickly for many collections.
  • Marketplace or gallery pages that need collection-level metadata and counts.
  • Analytics that aggregate transfers across thousands of contracts.
  • Metadata caching so you are not resolving tokenURI on every page load.

You can run your own indexer against a Base RPC endpoint, or use a hosted NFT data service. Either way, the RPC endpoint underneath still needs to handle your indexing load, which is usually far heavier than a single user request.

Production readiness checklist for NFT read traffic

NFT workloads are read-heavy and bursty. A drop or a new collection can multiply request volume in minutes. Before you ship, check these items.

Check What to verify
Endpoint capacity Your provider can absorb burst reads from wallets, galleries, and indexers
Archive access Historical log and state queries are supported if you backfill
Transport HTTP for reads, WebSocket if you subscribe to live logs
Failover A second endpoint or provider is configured for automatic retry
Caching Metadata and tokenURI responses are cached with sensible TTLs
Batching Multiple reads are batched with JSON-RPC batch requests where possible
Monitoring You track error rates, latency, and rate-limit responses

If you expect steady or high NFT read volume, compare a shared public endpoint against a managed plan and dedicated nodes. OnFinality offers Base RPC API access and dedicated nodes; see RPC pricing and the full list of supported RPC networks to plan capacity.

Common failure modes and how to debug them

Most Base NFT API problems fall into a few repeatable categories.

Symptom Likely cause Fix
eth_call reverts Token does not exist or wrong contract Confirm token ID and contract address
Empty log results Wrong topic or block range Check the Transfer topic hash and widen the range
Request rejected on old blocks Non-archive node Use an archive-capable endpoint
Rate-limit errors under load Shared endpoint limits Batch requests, cache, or move to a managed plan
Metadata fails to load IPFS gateway or HTTP timeout Add a fallback gateway and cache results
Inconsistent ownership Reading at different blocks Pin a block number for consistent snapshots

A quick diagnostic is to replay the failing call against a known-good block and compare. If it succeeds there, the issue is usually state depth or range, not your ABI encoding.

Testing on Base Sepolia

Build and test against Base Sepolia before mainnet. The chain ID is 84532, and the OnFinality public endpoint is https://base-sepolia.api.onfinality.io/public. Deploy a test ERC-721, mint a few tokens, and run your ownerOf, balanceOf, and eth_getLogs paths against it. Test your paging logic here, where mistakes are cheap, rather than on mainnet where a bad backfill can be expensive.

Key Takeaways

  • A Base NFT API is a combination of Base JSON-RPC calls and, optionally, an indexer for aggregated data.
  • Use eth_call for ownerOf, balanceOf, and tokenURI; use eth_getLogs for transfers.
  • RPC cannot enumerate a wallet's holdings on its own; that needs an indexer or your own indexing job.
  • Historical log queries need archive state and block-range paging.
  • Confirm transport support before relying on WebSocket subscriptions.
  • Plan for bursty read traffic with caching, batching, and failover.
  • Test on Base Sepolia (chain ID 84532) before mainnet.

Frequently Asked Questions

Is there a single Base NFT API?

No. Base exposes standard Ethereum JSON-RPC, so NFT reads use the same methods as any EVM chain. Aggregated NFT data comes from indexers or hosted data services layered on top.

Can I list all NFTs owned by a wallet using only RPC?

Not directly. You would have to scan Transfer logs and reconstruct ownership yourself. An indexer is the practical choice for portfolio-style queries.

Do I need an archive node for NFT history?

If you query old blocks or backfill transfer history, yes. Archive-capable endpoints serve historical state that a pruned node cannot.

Does Base RPC support WebSocket subscriptions?

Transport support varies by endpoint. Check your provider's documentation before designing around live log subscriptions.

How do I avoid rate limits during a backfill?

Batch requests, page through logs in chunks, cache metadata, and consider a managed or dedicated endpoint for sustained indexing load.

Where can I find Base endpoint and chain details?

See the Base RPC network page and Base Sepolia RPC network page for chain settings and endpoints.

Related resources

Originally published at OnFinality.

Top comments (0)