Logo
New RPC users get 35% off their first monthView the offer
OnFinality Learn
Integration & Development11 min read

Web3 dapps: architecture, RPC integration, and a reproducible health check

A practical guide to how Web3 dapps connect smart contracts, frontends, wallets, and RPC infrastructure, with runnable diagnostics and a measurement procedure.

TL;DR

A Web3 dapp is a frontend plus one or more smart contracts, connected through wallets and an RPC layer that reads chain state and submits transactions. The contract is the backend logic, but the RPC endpoint is the runtime dependency that determines whether users can actually interact. This article explains the request path, shows runnable curl and JavaScript checks against a placeholder RPC_URL, and gives a measurement procedure you can run yourself rather than trusting published benchmarks. It also covers common failure modes, tradeoffs between public and dedicated RPC access, and next steps for hardening a dapp before launch.

What a Web3 dapp actually is

A Web3 dapp (decentralized application) is an application whose core business logic lives in smart contracts deployed to a blockchain, with a frontend that reads and writes that state through a wallet and an RPC endpoint. The contract is immutable once deployed unless it includes upgrade mechanisms, so the frontend and the RPC layer are where most operational change happens.

This article focuses on the integration and operations side: how a dapp request travels from a browser to a contract, how to verify each hop with runnable code, and how to measure the RPC behavior your users will experience. It assumes you have a deployed or test contract, a wallet, and an RPC_URL for the target network. For endpoint selection rather than methodology, see the RPC Assistant guidance on choosing an RPC provider for Web3 projects.

  • Smart contract: on-chain logic and state, the dapp's backend.
  • Frontend: UI that encodes calls and displays decoded results.
  • Wallet: signs transactions and manages keys; it does not execute contract logic.
  • RPC endpoint: the HTTP or WebSocket gateway that forwards reads and broadcasts signed transactions.

The request path from browser to contract

A read call such as balanceOf or a view function is an eth_call: the frontend ABI-encodes the function selector and arguments, the wallet or provider sends the call to the RPC endpoint, and the node executes it against the latest state without producing a transaction. A write call is different: the frontend builds a transaction, the wallet signs it, and the RPC endpoint broadcasts it to the mempool. The node then returns a transaction hash, and confirmation arrives later through receipts or logs.

This distinction matters for debugging. Read failures usually indicate endpoint availability, chain mismatch, or malformed calldata. Write failures usually indicate signing problems, nonce conflicts, insufficient gas, or a node that accepted the transaction but did not propagate it. Keeping the two paths separate in your logs shortens diagnosis considerably.

Prerequisites and assumptions

The examples below use a placeholder RPC_URL and PRIVATE_KEY. Replace them with values for your own environment; never commit real keys. You need Node.js 18 or later for the JavaScript examples, curl for the shell checks, and a contract address with a known read method. The examples use standard JSON-RPC methods (eth_chainId, eth_blockNumber, eth_getBalance, eth_call) that are widely implemented, but confirm method support for your specific network before relying on them in production.

If you are still selecting a network, the OnFinality networks directory lists supported chains and their endpoints. For production workloads, compare shared API access with dedicated node infrastructure before committing to an architecture.

Verifying the RPC hop with curl

Start by confirming that the endpoint answers, reports the expected chain, and is advancing. These three checks catch the most common misconfigurations: a wrong network, a stale or load-balanced node, and an endpoint that returns HTTP 200 with a JSON-RPC error body.

Run each request and inspect both the HTTP status and the JSON-RPC error field. A response with an error object is a failure even when the HTTP status is 200.

curl -s -X POST "$RPC_URL" \
  -H 'Content-Type: application/json' \
  -d '{"jsonrpc":"2.0","id":1,"method":"eth_chainId","params":[]}'

curl -s -X POST "$RPC_URL" \
  -H 'Content-Type: application/json' \
  -d '{"jsonrpc":"2.0","id":2,"method":"eth_blockNumber","params":[]}'

curl -s -X POST "$RPC_URL" \
  -H 'Content-Type: application/json' \
  -d '{"jsonrpc":"2.0","id":3,"method":"eth_getBalance","params":["0x0000000000000000000000000000000000000000","latest"]}'

Expected verification result

A healthy endpoint returns a JSON object with jsonrpc set to 2.0, the same id you sent, and a result field. eth_chainId should return the hex-encoded chain ID you expect; eth_blockNumber should return a hex block height that increases between calls; eth_getBalance should return a hex wei value. If any response contains an error object, record the code and message before changing anything else.

Repeat the block-number call after a short interval. If the height does not change, the endpoint may be pinned to a lagging backend or the network may be stalled. Distinguish the two by checking a second independent endpoint before concluding the network is at fault.

Reading contract state from a dapp frontend

The following Node.js example uses ethers to read a contract through RPC_URL. It performs an eth_call, decodes the result, and prints it. This is the same path a browser frontend takes, minus the wallet, which makes it a useful first integration test.

Keep the ABI minimal: include only the functions you call. A smaller ABI reduces the chance of decoding errors and makes the test easier to audit.

import { JsonRpcProvider, Contract } from 'ethers';

const provider = new JsonRpcProvider(process.env.RPC_URL);
const abi = ['function totalSupply() view returns (uint256)'];
const contract = new Contract(process.env.CONTRACT_ADDRESS, abi, provider);

const [network, blockNumber, supply] = await Promise.all([
  provider.getNetwork(),
  provider.getBlockNumber(),
  contract.totalSupply()
]);

console.log({ chainId: network.chainId.toString(), blockNumber, supply: supply.toString() });

Submitting a signed transaction safely

Write paths require a signer. The example below sends a transaction and waits for one confirmation. It uses a placeholder PRIVATE_KEY and should only run against a test network with a funded test key. In production, signing belongs in the wallet or a managed signer, never in frontend code.

Before broadcasting, fetch the nonce and current fee data from the same endpoint you will use for the send. Mixing endpoints between nonce lookup and broadcast is a common source of replacement-transaction errors.

import { JsonRpcProvider, Wallet } from 'ethers';

const provider = new JsonRpcProvider(process.env.RPC_URL);
const wallet = new Wallet(process.env.PRIVATE_KEY, provider);

const tx = await wallet.sendTransaction({
  to: process.env.RECIPIENT_ADDRESS,
  value: 0n
});
console.log('broadcast', tx.hash);

const receipt = await tx.wait(1);
console.log('confirmed', receipt.status, receipt.blockNumber);

A reproducible RPC measurement procedure

Published latency numbers are rarely comparable because they depend on client location, payload, concurrency, and time of day. Run your own measurement instead. Use a fixed payload (eth_blockNumber), a fixed number of samples (for example 200 sequential requests), and record p50, p95, and error rate. Repeat at three times of day and from the region where your users are concentrated.

Record results in a table like the one below. Do not fill in numbers you did not measure; leave cells blank until you have data. Compare endpoints under identical conditions, and treat error rate as a first-class metric alongside latency.

  • Metric columns: endpoint label, region, sample count, p50 ms, p95 ms, error rate, notes.
  • Run the same script against each candidate endpoint and the same network.
  • Keep the raw samples so you can recompute percentiles later.
  • Re-run after any provider or network change; do not reuse stale tables.

Troubleshooting common dapp failures

Most dapp incidents fall into a small set of categories. Use the request path from earlier to localize the failure before changing code.

If reads fail but the endpoint answers eth_chainId, check the contract address and chain ID in your configuration. If writes fail after signing, compare the nonce returned by eth_getTransactionCount with the nonce in the signed transaction. If users report intermittent failures, measure error rate per endpoint rather than relying on a single reproduction.

  • Chain mismatch: wallet network differs from the contract's deployment network.
  • Stale state: endpoint serves a lagging block; compare block heights across endpoints.
  • Nonce conflict: pending transaction from the same key blocks the new one.
  • Rate limiting: HTTP 429 or JSON-RPC error codes under bursty frontend traffic.
  • Reverted call: eth_call returns an error with revert data; decode it before retrying.

Limitations and tradeoffs

Public RPC endpoints are convenient for prototypes but typically impose rate limits and offer no availability commitment. Dedicated nodes improve isolation and predictability but add operational cost and require monitoring. A common pattern is to use a managed API for reads and a dedicated or private endpoint for writes and latency-sensitive paths.

No single measurement proves an endpoint is reliable. Latency measured from one region does not predict another, and a low error rate over one hour does not guarantee behavior during congestion. Treat any RPC choice as a hypothesis to be re-tested, and keep a fallback endpoint configured. For pricing and capacity planning, review the RPC pricing page and the API service overview.

Next steps for production readiness

Once the read and write paths work, add monitoring around the metrics you measured: block height lag, p95 latency, and error rate per endpoint. Configure at least one fallback endpoint and test failover deliberately. Document the chain ID, contract addresses, and expected block time for each deployment so on-call responders can verify configuration quickly.

For teams standardizing on a provider, the RPC Assistant comparison of multi-chain RPC endpoints covers the operational questions to ask before committing. Pair that with the blockchain node hosting guide if you plan to run your own infrastructure alongside managed access.

Never Worry about Infrastructure Again

OnFinality takes away the heavy lifting of DevOps so you can build smarter and faster.

Get Started