Skip to main content
IntermediateEst6 minsdk-core15.4.1Project build checked

Fetch a block

Fetching a block is the read where the Api and Proxy providers diverge the most, so it is worth doing deliberately.

  • ApiNetworkProvider addresses a block by hash: getBlock(blockHash) and getLatestBlock().
  • ProxyNetworkProvider addresses a block by shard + nonce: getBlock({ shard, blockNonce }) and getLatestBlock(shard).

The API is a cross-shard index, so a hash is a global key. The proxy talks to observers of one shard at a time, so it needs the shard plus a coordinate within it. The block methods are not on the shared INetworkProvider interface, so the functions in this recipe are typed against the concrete provider classes.

Prerequisites

  • Node.js >= 20.19.0.
  • Network access. No wallet, no PEM, no EGLD.

Install

mkdir fetch-a-block
cd fetch-a-block
# Create the project files shown on this page.
npm install
npm run build
npm start
Complete project files omitted from the main walkthrough
package.json
{
"name": "cookbook-recipe-fetch-a-block",
"version": "1.0.0",
"private": true,
"description": "Cookbook recipe — fetch a block: the latest block and a block by hash via ApiNetworkProvider, and a block by shard + nonce via ProxyNetworkProvider.",
"scripts": {
"build": "tsc",
"start": "node dist/index.js",
"typecheck": "tsc --noEmit --strict"
},
"dependencies": {
"@multiversx/sdk-core": "15.4.1",
"axios": "1.18.1",
"bignumber.js": "9.3.1",
"protobufjs": "7.6.5"
},
"devDependencies": {
"@types/node": "20.19.43",
"typescript": "5.9.3"
},
"engines": {
"node": ">=20.19.0"
}
}
tsconfig.json
{
"compilerOptions": {
"target": "ES2022",
"lib": ["ES2022"],
"allowJs": false,
"skipLibCheck": true,
"strict": true,
"noImplicitAny": true,
"strictNullChecks": true,
"noUncheckedIndexedAccess": true,
"exactOptionalPropertyTypes": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"forceConsistentCasingInFileNames": true,
"esModuleInterop": true,
"module": "commonjs",
"moduleResolution": "node",
"resolveJsonModule": true,
"declaration": false,
"sourceMap": false,
"outDir": "dist",
"types": ["node"]
},
"include": ["src"],
"exclude": ["node_modules", "dist"]
}

Fetching blocks

src/blocks.ts
// src/blocks.ts — the subject of this recipe: fetching a block. This is where
// the Api and Proxy providers diverge the most, so the functions below are
// typed against the concrete provider classes, not INetworkProvider (the block
// methods are NOT on the shared interface).
//
// - ApiNetworkProvider identifies a block by HASH:
// getBlock(blockHash) / getLatestBlock()
// - ProxyNetworkProvider identifies a block by SHARD + NONCE:
// getBlock({ shard, blockNonce }) / getLatestBlock(shard)
//
// Why the difference: the API is a cross-shard index, so a hash is a global
// key. The proxy talks to observers of one shard at a time, so it needs the
// shard plus a coordinate (hash or nonce) within it.

import type { ApiNetworkProvider, ProxyNetworkProvider, BlockOnNetwork } from '@multiversx/sdk-core';

/** API: the most recent block the index has seen. */
export async function fetchLatestBlockApi(api: ApiNetworkProvider): Promise<BlockOnNetwork> {
return api.getLatestBlock();
}

/** API: any block, addressed by its hash. */
export async function fetchBlockByHashApi(
api: ApiNetworkProvider,
blockHash: string,
): Promise<BlockOnNetwork> {
return api.getBlock(blockHash);
}

/**
* Proxy: a block addressed by shard + nonce. Get the nonce from
* `getNetworkStatus(shard)` (use `highestFinalNonce` so the block is final).
*/
export async function fetchBlockByNonceProxy(
proxy: ProxyNetworkProvider,
shard: number,
blockNonce: bigint,
): Promise<BlockOnNetwork> {
return proxy.getBlock({ shard, blockNonce });
}

Wiring it up

src/index.ts
// src/index.ts — fetch the latest block by hash (API), re-fetch that exact
// block by its hash to prove round-trip, then fetch a final block by shard +
// nonce (Proxy). No wallet, no gas.
//
// Usage:
// npm run build && npm start

import { ApiNetworkProvider, ProxyNetworkProvider } from '@multiversx/sdk-core';
import { fetchLatestBlockApi, fetchBlockByHashApi, fetchBlockByNonceProxy } from './blocks';

const MAINNET_API = 'https://api.multiversx.com';
const MAINNET_GATEWAY = 'https://gateway.multiversx.com';

async function main(): Promise<void> {
const api = new ApiNetworkProvider(MAINNET_API, { clientName: 'mvx-cookbook' });
const proxy = new ProxyNetworkProvider(MAINNET_GATEWAY, { clientName: 'mvx-cookbook' });

// API — latest block, then the same block re-fetched by its hash.
const latest = await fetchLatestBlockApi(api);
console.log('API getLatestBlock():');
console.log(` shard ${latest.shard}, nonce ${latest.nonce}, epoch ${latest.epoch}`);
console.log(` hash ${latest.hash}`);

const byHash = await fetchBlockByHashApi(api, latest.hash);
console.log('API getBlock(hash) — round-trip:');
console.log(` shard ${byHash.shard}, nonce ${byHash.nonce} (same block: ${byHash.hash === latest.hash})`);

// Proxy — a final block on shard 1, addressed by nonce.
const shard = 1;
const status = await proxy.getNetworkStatus(shard);
const finalNonce = status.highestFinalNonce;
const byNonce = await fetchBlockByNonceProxy(proxy, shard, finalNonce);
console.log(`\nProxy getBlock({ shard: ${shard}, blockNonce: ${finalNonce} }):`);
console.log(` shard ${byNonce.shard}, nonce ${byNonce.nonce}, epoch ${byNonce.epoch}`);
console.log(` hash ${byNonce.hash}`);
}

main().catch((err: unknown) => {
console.error(err);
process.exitCode = 1;
});

Run it

npm start

Expected output (live values, so nonces and hashes will differ when you run it):

API getLatestBlock():
shard 0, nonce 31313292, epoch 2175
hash 5a3d6c018a5aa443ea558c0cd9c4e23738d1184bbbda3fbe072fda931c50458e
API getBlock(hash) — round-trip:
shard 0, nonce 31313292 (same block: true)

Proxy getBlock({ shard: 1, blockNonce: 31302554 }):
shard 1, nonce 31302554, epoch 2175
hash daf493760526846b1d0d571ce801573ddfe1383866b9338ee56afe611fbb1462

How it works

Latest, then round-trip by hash. The recipe calls getLatestBlock() on the Api provider, then feeds that block's hash straight back into getBlock(hash) and asserts the two are the same block, a live proof that both API paths agree.

Proxy needs a nonce, so read the status first. For the proxy's by-nonce lookup, the recipe reads getNetworkStatus(shard).highestFinalNonce and requests that block, so it always asks for a final (irreversible) block rather than a tip that might still reorg.

BlockOnNetwork fields. Both paths return the same shape: shard, nonce (bigint), hash, previousHash, timestamp, round, epoch.

Pitfalls

Pitfall 1: ProxyNetworkProvider.getLatestBlock() is broken (sdk-core v15.4.1)

It returns an empty block (shard: NaN, nonce: 0n, hash: '') because it does not unwrap the gateway's response.block envelope, unlike getBlock() which does. Workaround: use getBlock({ shard, blockNonce }) with a nonce from getNetworkStatus(shard), which is exactly what this recipe does. The Api provider's getLatestBlock() works correctly.

Pitfall 2: ProxyNetworkProvider.getBlock rejects the genesis nonce

The internal guard is else if (args.blockNonce), and 0n is falsy, so requesting block nonce 0 throws Block hash or block nonce not provided. If you genuinely need the genesis block from the proxy, query it by hash instead of nonce.

Pitfall 3: block methods are not on INetworkProvider

getBlock / getLatestBlock exist only on the concrete ApiNetworkProvider and ProxyNetworkProvider classes, and with different signatures. A variable typed as INetworkProvider cannot call them at all. Type against the concrete provider, as this recipe does.

See also