Skip to main content
This guide was written by the Chainstack team.
A token page needs two different kinds of information: market context and contract state. A chart can tell you where a token trades, but it does not tell you how many units a particular wallet holds at a specific block. This guide builds a small Base token dashboard with Serialized Data for USD prices, hourly candles, and market activity, and Chainstack RPC for a direct ERC-20 balance read. You can enter a token contract, optionally add a wallet address, and inspect both without connecting a wallet or signing a transaction. The result is a useful starting point for a wallet asset page or a support tool investigating a reported balance. If your application only needs prices and charts, Serialized alone may cover that requirement. The RPC layer becomes useful when you also need to query contract state at a stated block or add contract-specific behavior.

What the dashboard does

  • Shows the selected token’s name, symbol, USD price, 24-hour volume, price change, and liquidity from Serialized.
  • Plots the latest 24 available hourly USD close prices, with the underlying times and swap counts available as text.
  • Reads an optional wallet’s token balance and native ETH balance through Chainstack, using one selected Base block height.
  • Displays market timestamps, candle pool identifiers, and the RPC block number and hash separately.
  • Keeps working when one provider or panel fails, instead of presenting an error as a zero balance or price.
This is a local, manually refreshed, read-only example. It does not calculate PnL, quote swaps, execute trades, or claim that a token is safe. It supports standard ERC-20 contracts that implement decimals(); it is not a portfolio-discovery service.

Why use these two APIs together?

Serialized Data supplies indexed market information. The same market API calls work on Solana and 18 EVM chains by changing the chain parameter and supplying the appropriate token address. Reconstructing a chart directly from RPC would require you to identify the relevant pools, decode swaps, handle token orientation and decimals, and maintain a price history. The market API supplies that prepared dataset. Chainstack’s Base RPC infrastructure gives the application a separate path to contract state. In this example, the balance panel calls balanceOf(wallet) on the token contract, rather than inferring holdings from trade history. That is useful when transfers, airdrops, or other activity make a trading position different from a current contract balance. The request path is simple: browser → local Node.js server → Serialized and Chainstack. Only the server holds credentials. The wallet address goes to the configured RPC endpoint; this example does not send it to Serialized because its market requests only need the token address.

1. Prepare your credentials and runtime

Use Node.js 22 or later. The example uses built-in fetch, the HTTP server, and the --env-file flag, so there are no npm dependencies to install. Create a Serialized API key using the authentication instructions. Your first 100,000 credits are free when you add a card. Send the key directly in the Authorization header. Do not add a Bearer prefix. Serialized’s public demo can be used from the documentation playground, but programmatic demo calls are restricted; the application below uses the production API and your own key. Create a Chainstack account and obtain a Base mainnet HTTPS JSON-RPC endpoint. Follow the node management instructions and Base tooling guide to locate the endpoint. Use the key-protected HTTPS URL for this example. A node password or a blockchain wallet private key is not required by this code. Check that the endpoint is for Base mainnet. Its chain ID is 8453, represented as 0x2105 by eth_chainId. Base Sepolia is a different network. The server checks the returned chain ID before it reads a wallet. Create a directory containing server.mjs and index.html from the complete source below. Create a .env file next to them:
.env
Keep .env out of version control. The RPC URL itself can contain a credential. Neither credential belongs in browser JavaScript, screenshots, or a public repository.

2. Understand the two market requests

The server sends these requests to https://api.serialized.xyz, with the raw API key in the Authorization header:
The token details endpoint returns a { data, meta } envelope. The dashboard uses data.priceUsd, data.stats24h.volumeUsd, data.stats24h.priceChangePct, data.liquidityUsd, and data.pool.address. It validates the returned chain and token address before displaying the snapshot. A ticker is a display label, not a unique token identifier. The token candles endpoint returns data as an array. Its default quote is native, so quote=usd is essential for a USD chart. The server also rejects a candle response whose meta.quote is not usd. The chart uses each candle’s close, rather than pretending to be a full candlestick chart. Candle time is Unix seconds. meta.asOf and the token’s lastTradeAt are milliseconds. Convert candle time with new Date(candle.time * 1000); pass meta.asOf directly to new Date(). Mixing these units produces incorrect dates without necessarily crashing the application. The default candle series follows Serialized’s reference market, rather than every pool combined. The chart displays meta.pools and the snapshot displays its main market. A reference market can change between the two requests, so compare those identifiers before treating the panels as one market view. Historical series may also include a token’s earlier launch-curve market. fill=true can insert flat, zero-volume buckets when no trade occurred. The newest bucket can still be forming, and a young token may have fewer than 24 buckets. The heading deliberately says hourly close prices, not a guaranteed complete 24-hour trading history. Do not add these candle volumes and label the sum as token-wide 24-hour volume; that figure comes from stats24h.volumeUsd.

3. Read balances at an explicit block height

The wallet panel first calls eth_chainId and eth_getBlockByNumber("latest", false). It then uses that returned hexadecimal block number for eth_getCode, both eth_call requests, and eth_getBalance. Reusing the number avoids silently reading each field from a different advancing latest block. The contract calls use the ERC-20 ABI selectors below:
The server decodes each 32-byte return value as BigInt. It formats the token amount using the contract’s decimals, and formats the native ETH balance with 18 decimals. It never converts the raw uint256 balance to a JavaScript Number. For example, 1000000000000000001 base units at 18 decimals must display as 1.000000000000000001, not 1. ERC-20 defines balanceOf and marks decimals as optional. If decimals is absent, reverts, or returns an invalid word, the balance panel reports an error. It does not assume 18 or substitute Serialized’s metadata. An ordinary zero balance still displays as 0. After the reads, the server fetches the selected block again and checks its hash. This detects a block replacement visible during that interval. It is a lightweight consistency check, not an atomic snapshot or a finality guarantee. The panel explicitly labels the selected latest block as not finalized. A stricter accounting workflow should use an appropriate safe or finalized block policy and account for reorgs. See Base’s eth_call reference and eth_getBalance reference for the request and return formats. These are read-only RPC requests; they do not broadcast transactions or charge gas to the wallet.

4. Start the dashboard

After copying the complete source below and adding .env, run this command from the same directory:
Open http://127.0.0.1:3000. The default token is DEGEN on Base, using the contract address from Serialized’s own documentation. It is a sample identifier, not a token recommendation. Click Load dashboard to fetch the market panels. To test the RPC panel, add a public wallet address you want to inspect and load again. No wallet connection or wallet ownership is needed for this read. A wallet that holds none of the selected token should return 0 if the call succeeds. Expect a market snapshot, a USD close-price chart, and, when a wallet is supplied, token units, ETH, token decimals, block number, block hash, and the read time. Missing market fields display Unavailable. A provider error remains attached to its panel. The page clears the previous result before each load so that an old balance is not left beside a new token.

5. Interpret the result correctly

A market price and a wallet balance answer different questions. Serialized’s meta.asOf describes its market response; the RPC panel identifies the block height used for the contract reads. Neither implies that both providers sampled the same instant. The example does not multiply the current balance by the market price. That calculation would be an indicative mark, not a sell quote, cost basis, or PnL, and it would combine observations from different times. A real liquidation estimate needs executable quotes and position-size-aware liquidity assumptions. Token liquidity is market context, not an amount every wallet can withdraw at the displayed price. Likewise, a recent response timestamp does not prove that a thinly traded token has traded recently. The dashboard displays lastTradeAt separately when available. The balance is what this contract reports for this address and block. It does not prove that the token is transferable, redeemable, or safe. Token names and symbols are untrusted text. The UI uses textContent and creates chart elements directly, avoiding HTML interpolation from API metadata.

Troubleshoot the common failures

  • Serialized HTTP 401 or 403: check that the key is valid and sent as the raw Authorization value. If you tried the demo host from code, switch to the production host with a key.
  • Serialized HTTP 402: inspect the account’s billing or quota status in the portal. Do not retry an unresolved billing state in a tight loop.
  • HTTP 429: wait before retrying. For a deployed version, respect Retry-After and implement bounded backoff. This local example performs no automatic retries.
  • Market 404 or missing chart: verify the token contract and Base network, and test the same token in the relevant documentation playground. A deployed contract is not a guarantee of indexed markets or candle history.
  • Wrong RPC network: replace the endpoint with a Base mainnet endpoint. The server deliberately refuses another chain rather than displaying unrelated balances.
  • No token contract or an eth_call failure: check that the token input is a contract address, not a wallet or pool address, and that it implements the expected ERC-20 methods.
  • Block changed: refresh the snapshot. For workflows that need stronger settlement assurances, change the block-selection policy rather than hiding this error.
  • One panel fails while others load: inspect that panel’s error. The server uses Promise.allSettled so a market outage does not become a fabricated RPC balance, or vice versa.

Before turning the example into a public application

The server binds to 127.0.0.1 and exposes only the fixed dashboard workflow. It does not serve .env or act as a general RPC proxy. For an Internet-facing deployment, add application authentication, rate limits, concurrency limits, usage budgets, and appropriate HTTPS hosting before accepting arbitrary requests. The current implementation sends two Serialized requests per load, plus seven RPC calls when a wallet is supplied. Serialized currently documents 2 credits for token details and 5 for candles, or 7 credits per successful two-panel market load. That is 7,000 credits for 1,000 loads before any caching. Verify the current credit schedule and Chainstack request-unit rules before estimating spend; a Serialized credit is not a Chainstack request unit. Keep manual refresh while prototyping. Before adding polling, cache shared market data by chain, token, interval, and quote, collapse simultaneous identical requests, and cache wallet snapshots separately with their block references. Market data can be shared across users; wallet-specific results need their own keys and privacy policy. Stop hidden-tab refreshes and avoid polling historical candles at the same cadence as a live price. For streaming, follow Serialized’s documented subscription and reconnection model rather than appending an unverified WebSocket snippet. Serialized Audit can be a later panel with its own credentials, billing, and risk interpretation. Neither feature is required to make this first guide useful.

Complete source: server.mjs

Copy the following file in full. It contains the fixed API calls, input checks, balance decoding, per-panel error handling, and localhost server.
server.mjs

Complete source: index.html

Copy this file next to server.mjs. The chart is an SVG close-price line with labeled USD range and UTC endpoints. Expand the chart values to inspect the exact source timestamps and displayed close prices.
index.html

Further reading