~/krishna_dhakal
#Blockchain#React#JavaScript#Web3

Rebuilding a Blockchain Explorer with React and a JavaScript SDK

> March 10, 2022

When I joined 0chain as a Software Engineer, the blockchain explorer had accumulated significant technical debt. The JavaScript SDK had inconsistent APIs, the React frontend was tightly coupled to implementation details, and adding new features required changes in too many places. This post covers how we rebuilt it.


> The Starting Point


The original explorer was a single-page React app that called the blockchain node APIs directly from components. There was no abstraction layer — each component built its own fetch calls, handled its own errors, and formatted its own data. The result was a tangled mess of duplicated logic.


> Redesigning the SDK Layer


The first step was designing a clean JavaScript SDK that provided a stable interface between the UI and the blockchain:


// Before — scattered direct calls in components
const fetchBlock = async (hash) => {
  const res = await fetch(`${NODE_URL}/v1/block/get?hash=${hash}`);
  if (!res.ok) throw new Error("fetch failed");
  const data = await res.json();
  return data;
};

// After — SDK with consistent interface
class ZChainSDK {
  constructor(nodeUrl) {
    this.nodeUrl = nodeUrl;
  }

  async getBlock(hash) {
    return this._request(`/v1/block/get`, { hash });
  }

  async getTransaction(txnHash) {
    return this._request(`/v1/transaction/get/confirmation`, { hash: txnHash });
  }

  async _request(endpoint, params) {
    const url = new URL(this.nodeUrl + endpoint);
    Object.entries(params).forEach(([k, v]) => url.searchParams.set(k, v));
    const res = await fetch(url.toString());
    if (!res.ok) throw new ZChainError(res.status, await res.text());
    return res.json();
  }
}

> React Architecture


With the SDK in place, components became simple consumers of clean data:


import { useZChain } from '../hooks/useZChain';

function BlockDetail({ hash }) {
  const { data: block, error, loading } = useZChain('getBlock', hash);

  if (loading) return <Terminal text="fetching block..." />;
  if (error) return <ErrorPanel message={error.message} />;

  return (
    <BlockCard
      hash={block.hash}
      round={block.round}
      transactions={block.transactions}
      miner={block.miner_id}
    />
  );
}

The `useZChain` hook handles caching, deduplication, and error normalisation — a thin wrapper around React Query.


> Handling Real-Time Updates


Blockchain explorers need live data. We used WebSocket subscriptions for new blocks:


class ZChainSDK {
  subscribeToBlocks(onBlock) {
    const ws = new WebSocket(`${this.wsUrl}/v1/blocks/stream`);
    ws.onmessage = (event) => {
      const block = JSON.parse(event.data);
      onBlock(block);
    };
    return () => ws.close();
  }
}

useEffect(() => {
  const unsubscribe = sdk.subscribeToBlocks((block) => {
    setLatestBlocks((prev) => [block, ...prev].slice(0, 20));
  });
  return unsubscribe;
}, []);

> Results


  • **Developer onboarding time** dropped from days to hours — new engineers had a single SDK entry point to learn.
  • **Bug surface** reduced by ~40% — duplicated fetch logic was one of the main sources of inconsistencies.
  • **Feature velocity** improved — adding a new explorer page no longer required touching networking code.

> Key Lessons


  1. **Separate your data layer early** — mixing fetch logic into components is a debt that compounds fast.
  2. **Design for the consumer** — the SDK API should match how the UI thinks about data, not how the blockchain exposes it.
  3. **Typed interfaces pay dividends** — even in JavaScript, JSDoc types (or migrating to TypeScript) prevent entire classes of bugs.

Rebuilding with a clean SDK layer was one of the highest-leverage decisions of that project.