guide7 min read

BNB Smart Chain RPC Checklist Before Production Traffic

BNB Smart Chain RPC checklist before production: confirm chain ID 56, full vs archive state, WebSocket uptime, and what safe and finalized return.

By RPC Directory•
#BNB Smart Chain#BSC#RPC#Chain ID 56#Archive#WebSocket#Finality
$bnb-smart-chain-mainnet-rpc.md

A BNB Smart Chain URL that returns eth_blockNumber is not a production endpoint. It is a node that answered one method. Before you point an indexer, a keeper, or a user-facing app at it, check four things on that exact URL: chain ID 56, whether the node is full or archive, whether the WebSocket actually stays up, and what safe and finalized return. Those checks are cheap. A wrong chain ID or a pruned node is not.

This is not a ranking of providers. Use the BNB Smart Chain mainnet hub to compare public endpoints and paid rows, then probe the one you intend to ship.

Confirm chain ID 56 before the first transaction

Mainnet is chain ID 56 (0x38). Chapel testnet is 97. A URL labeled "BSC" can still be the wrong network, a fork, or a stale proxy.

Call eth_chainId on the endpoint you will configure, not on a different host from the same vendor. Expect 0x38. If you also call net_version, it should agree. Do this in the same process that will sign transactions, after any fallback or load balancer, so you are not checking a different backend than production.

A wallet or SDK that already has a chain object is not a substitute. The SDK can be on 56 while the HTTP transport still points at another URL. Read the chain ID from the transport.

Full node vs archive: decide from the calls, not the label

A full BNB Smart Chain node serves recent state. That is enough for eth_call against the latest block, eth_sendRawTransaction, recent receipts, and logs over a short range. It is not enough if you need state at an old block.

You need archive access when the workload does any of these:

  • eth_call or eth_getBalance at a historical block, not just latest
  • Reconstructing a position, allowance, or vault from an old block hash
  • debug_ or trace_ against older transactions
  • An indexer that backfills from genesis or from a block outside the full node's retained window

"Archive" on a pricing page is a claim. Confirm it with a call you actually make. Pick an old block (a known height from months ago, not latest minus 20), run eth_getBalance or eth_call at that height, and keep the error. A pruned node usually returns a missing-state or header error. A timeout is not the same as "not archive." Retry once, then treat a consistent historical failure as pruned.

Do not assume the public endpoints on the hub are archive. Most shared public nodes are not. If the product only needs the tip of the chain, say so and stay on a full node. Archive is extra disk and extra cost. Pay for it when the queries require it.

Log filters are a separate limit. eth_getLogs on BNB Smart Chain is often capped by block range, topic count, or timeout, and some public endpoints reject it outright. Probe the method on the URL you will use, with a range your indexer actually requests. The eth_getLogs caps guide covers the failure modes. A node that is "archive" can still refuse a wide filter.

WebSockets: subscribe only after the socket survives a restart

HTTPS is the default for reads and for eth_sendRawTransaction. Use a WebSocket when you need eth_subscribe (new heads, logs, pending transactions) and you have measured the connection.

Before you flip the transport:

  • Open the wss:// URL, not a guessed port on the HTTP host.
  • Subscribe to newHeads and confirm events arrive.
  • Kill the connection and confirm your client reconnects without duplicating side effects.
  • Check whether the provider counts WebSocket traffic against the same quota as HTTP. Some do. Some drop idle sockets.

A public WebSocket is fine for a local script. It is a poor primary feed for a keeper that must see every head. If the socket drops, you miss blocks until reconnect. For anything that settles money, keep an HTTP poll of eth_blockNumber or eth_getBlockByNumber with finalized as a backstop, even if the socket is the fast path.

Confirmation tags are not one shared meaning

BNB Smart Chain documents the same tag names Ethereum uses, and then defines them for BSC. On the BNB Chain JSON-RPC list, eth_getBlockByNumber accepts:

  • latest: the latest mined block. It can still be reorged.
  • safe: the latest justified head.
  • finalized: the latest finalized block.

Those are not informal synonyms for "wait a few blocks." Call each tag on your endpoint and log the block number. If safe errors, or if safe and latest are always identical, the node or the proxy is not implementing the tag you think it is. Do not paper over that by waiting an arbitrary number of blocks in application code and still labeling the result safe.

BSC also has its own finality methods, from BEP-126 Fast Finality. Official docs describe economic finality at block n+2, with a stated transaction finality time of about 3.75 seconds at a 1.5 second block time. If Fast Finality is unavailable, the chain falls back to probabilistic finality (on the order of 88 blocks for a majority of validators, about 132 seconds, and 120 blocks for a supermajority, about 180 seconds). The methods that expose this are eth_getFinalizedHeader, eth_getFinalizedBlock, and eth_newFinalizedHeaderFilter. They take a verifiedValidatorNum. The docs also allow -1 (at least half of validators), -2 (at least two thirds), and -3 (all current validators). Not every provider implements these. If eth_getFinalizedBlock returns method-not-found, you do not have BSC economic finality on that URL. Use finalized on eth_getBlockByNumber, or stay on latest and accept reorg risk. Do not silently treat the error as finalized.

Ethereum is changing what safe means on Ethereum. The planned Fast Confirmation behavior is a client-side flag on the order of about 13 seconds. It is not economic finality, and it is not the BSC justified head. If the same worker process polls safe on Ethereum and on chain ID 56, split the policy. Importing an Ethereum wait, or treating a future Ethereum safe as "confirmed enough" on BSC, will either stall BSC traffic or accept a weaker guarantee than you think. Read the tag off the node you are calling. When Ethereum's client default changes, re-check Ethereum. Do not assume BSC moved with it.

Practical split:

  • Tip-of-chain UX, quotes, and mempool-style reads: latest, and retry on reorg.
  • "Do not reverse this under honest majority" on BSC: safe only after you have seen it advance, and you know this node returns the justified head.
  • Irreversible for the app: finalized, or eth_getFinalizedBlock if the endpoint implements it and you have chosen a validator threshold.
  • Cross-chain workers: one confirmation policy per chain ID, stored next to the RPC URL.

Probe the endpoint you will ship, then look at paid rows

Run this set once against the candidate URL and save the responses. You want a record, not a vibe.

  1. eth_chainId returns 0x38.
  2. eth_blockNumber moves when you poll twice a few seconds apart.
  3. eth_getBlockByNumber with latest, safe, and finalized returns three heights you can explain. safe and finalized must not error if your app depends on them.
  4. eth_getFinalizedBlock with a documented threshold, if you plan to use Fast Finality. Method-not-found means the endpoint does not offer it.
  5. One historical eth_call or eth_getBalance at an old block, if you need archive.
  6. One eth_getLogs filter in the range your indexer uses.
  7. If you need push: a wss newHeads subscription that survives a disconnect.

Public endpoints on the hub are shared and rate-limited. Use them for the probe and for development. For production, compare the paid rows on the same hub for archive, WebSocket, and method coverage. Dwellir is one of those rows: a flat response credit rather than a compute-unit table, which is easier to budget when the mix of eth_call, logs, and traces is not stable. It is not the only option, and a cheaper URL that fails the probe above is not a savings. The public vs paid guide covers when a shared endpoint stops being enough. The pricing calculator is there for the rest.

If the probe fails, do not ship the URL because the marketing name said mainnet. Change endpoint, or change the call, until chain ID, node mode, socket, and confirmation tags match the job.