Fetch a block
Fetching a block is the read where the Api and Proxy providers diverge the most, so it is worth doing deliberately.
ApiNetworkProvideraddresses a block by hash:getBlock(blockHash)andgetLatestBlock().ProxyNetworkProvideraddresses a block by shard + nonce:getBlock({ shard, blockNonce })andgetLatestBlock(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
{
"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"
}
}
{
"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 — 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 — 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
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.
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.
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
- Fetch network config and status
is where the
blockNoncethis recipe queries comes from. - Configure a network provider covers Api vs Proxy, the distinction this recipe leans on hardest.
- Custom API/Proxy request reaches block sub-resources the typed methods do not model yet.