This guide was written by the Chainstack team.
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.
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 thechain 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-infetch, 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
.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 tohttps://api.serialized.xyz, with the raw API key in the Authorization header:
{ 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 callseth_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:
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:
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’smeta.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
Authorizationvalue. 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-Afterand 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_callfailure: 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.allSettledso a market outage does not become a fabricated RPC balance, or vice versa.
Before turning the example into a public application
The server binds to127.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 toserver.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