Node Service

Multi-chain JSON-RPC access through a unified gateway format.

Overview

ZAN Node Service provides JSON-RPC access to 26 blockchain ecosystems across 44 networks. The gateway exposes a unified request format — agents call any chain using the same POST /rpc/{ecosystem}/{network} structure, and the gateway handles protocol conversion internally (e.g., REST ↔ JSON-RPC for chains like Aptos and TON).

For complete method documentation, parameter formats, and chain-specific details, see the ZAN Node Service API Reference.

Quick start

import { createX402Client } from '@zan_team/x402';

const client = await createX402Client({
  gatewayUrl: 'https://x402.zan.top',
  privateKey: process.env.PRIVATE_KEY as `0x${string}`,
  autoPayment: true,
  preAuth: true,
});

// Simple call (no params)
const block = await client.call('eth', 'mainnet', 'eth_blockNumber');
console.log(block.result); // "0x1234567"

// Call with params
const blockData = await client.call('eth', 'mainnet', 'eth_getBlockByHash', [
  '0xb3b20624f8f0f86eb50dd04688409e5cea4bd02d700bf6e79e9384d47d6a5a35',
  false,
]);
console.log(blockData.result);

Unified gateway format

All chains use the same request pattern regardless of their native protocol:

client.call(ecosystem, network, method, params?)

The gateway internally converts between protocols as needed:

  • EVM chains → native JSON-RPC passthrough
  • Solana → Solana JSON-RPC passthrough
  • Aptos → REST API converted to JSON-RPC format
  • TON → REST API converted to JSON-RPC format

Agents don't need to know the underlying protocol — just use the unified format.

HTTP interface (reference)

POST /rpc/{ecosystem}/{network}
Authorization: Bearer <jwt>
Content-Type: application/json

{"jsonrpc": "2.0", "id": 1, "method": "eth_blockNumber", "params": []}

Response:

{"jsonrpc": "2.0", "id": 1, "result": "0x1234567"}

Billing

Credits are prepurchased via x402 payment and deducted per call based on method weight.

Method weights

Credit costs are sourced from the ns-common-commercial package, with per-chain pricing definitions. Below are representative costs for Ethereum methods:

CreditsMethods
0eth_chainId, eth_syncing, net_version, net_listening
10eth_blockNumber, eth_gasPrice, eth_maxPriorityFeePerGas, eth_feeHistory, eth_accounts
15eth_getBalance, eth_getTransactionByHash, eth_getTransactionReceipt, eth_getBlockTransactionCountByHash, eth_getBlockTransactionCountByNumber
20eth_getStorageAt, eth_getTransactionByBlockHashAndIndex
25eth_getBlockByHash, eth_getBlockByNumber, eth_getCode, eth_getTransactionCount, eth_getFilterChanges
40eth_call
75eth_getLogs, eth_getFilterLogs
80debug_traceTransaction, debug_traceCall, eth_blobBaseFee
100eth_estimateGas
200eth_sendRawTransaction
250eth_getBlockReceipts, debug_getRawReceipts
500debug_traceBlockByHash, debug_traceBlockByNumber, trace_block

Note: Different chains have their own method enums with chain-specific pricing. The default cost for methods not defined in the enum is 10 credits. For the full list of methods and costs per chain, see the ZAN Node Service API Reference.

Supported chains

Layer 1

EcosystemNetworks
ethmainnet, sepolia, hoodi
solanamainnet, devnet
bitcoinmainnet, testnet
tronmainnet, nile
tonmainnet
nearmainnet, testnet

Layer 2

EcosystemNetworks
arbitrumone, sepolia
optimismmainnet, sepolia
basemainnet, sepolia
zksyncmainnet
starknetmainnet
mantlemainnet
taikomainnet

Other EVM

EcosystemNetworks
bscmainnet, testnet
polygonmainnet, amoy
avalanchemainnet, fuji
fantommainnet
coremainnet
xlayermainnet
gravity_alphamainnet
hyperliquidmainnet, testnet
monadmainnet, testnet

Non-EVM

EcosystemNetworks
suimainnet, testnet
aptosmainnet, testnet

Emerging

EcosystemNetworks
pharosmainnet, atlantic
jovaymainnet, testnet

Blocked methods

The following methods are blocked for security reasons:

  • eth_accounts
  • eth_sign
  • personal_sign
  • eth_sendTransaction
  • eth_signTransaction
  • debug_traceTransaction
  • debug_traceBlockByNumber
  • debug_traceBlockByHash

Calling a blocked method returns MethodNotAllowedError (HTTP 403).

Error handling

ErrorHTTPScenarioSDK behavior (autoPayment: true)
InsufficientCreditsError402Credit balance too lowAuto-purchases credits, retries
MethodNotAllowedError403Method is blockedThrows (cannot retry)
ProviderNotFoundError404Invalid ecosystem/networkThrows
UpstreamError502/504Upstream node failureThrows (credits refunded)
NetworkErrorTimeout / connection errorThrows

Automatic refund

If the upstream node returns an error (5xx) after credits have been deducted, the gateway automatically refunds the credits. The UpstreamError includes a creditRefunded: true flag.

Examples

Multi-chain query

const chains = [
  { eco: 'eth', net: 'mainnet' },
  { eco: 'polygon', net: 'mainnet' },
  { eco: 'arbitrum', net: 'one' },
  { eco: 'base', net: 'mainnet' },
];

const blocks = await Promise.all(
  chains.map(({ eco, net }) => client.call(eco, net, 'eth_blockNumber'))
);

blocks.forEach((b, i) => {
  console.log(`${chains[i].eco}: ${parseInt(b.result, 16)}`);
});

Get token balance

// ERC-20 balanceOf call
const result = await client.call('eth', 'mainnet', 'eth_call', [{
  to: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48', // USDC
  data: '0x70a08231000000000000000000000000<address padded to 32 bytes>',
}, 'latest']);

console.log(`USDC balance (raw): ${result.result}`);

Solana account info

const info = await client.call('solana', 'mainnet', 'getAccountInfo', [
  'vines1vzrYbzLMRdu58ou5XTby4qAqVRLmqo36NKPTg',
  { encoding: 'jsonParsed' }
]);
console.log(info.result);

Error handling pattern

import { InsufficientCreditsError, UpstreamError, MethodNotAllowedError } from '@zan_team/x402';

try {
  const result = await client.call('eth', 'mainnet', 'eth_getLogs', [{
    fromBlock: '0x0',
    toBlock: 'latest',
    address: '0x...',
  }]);
} catch (err) {
  if (err instanceof UpstreamError) {
    console.log('Upstream failed, credits refunded — safe to retry');
  } else if (err instanceof MethodNotAllowedError) {
    console.log('This method is blocked');
  }
}

Discover available networks

const { networks } = await client.discovery.listNetworks();
networks.forEach(n => {
  console.log(`${n.ecosystem}/${n.network} (provider: ${n.providerId})`);
});

Next steps



Did this page help you?