On this page

Read-only queries work without a signer or a PAX balance. Choose an EVM JSON-RPC endpoint, confirm its network, and record the block number when several reads must describe the same state.

Verify the endpoint

Set PAXEER_RPC_URL to your EVM endpoint. The result of this request must be 0x7d, which is decimal 125.

: "${PAXEER_RPC_URL:?Set your Paxeer EVM RPC URL}"
curl --fail-with-body --silent --show-error "$PAXEER_RPC_URL"   -H 'Content-Type: application/json'   --data '{"jsonrpc":"2.0","id":1,"method":"eth_chainId","params":[]}'

HTTP 200 can still contain a JSON-RPC error. Check both the HTTP result and the response body. A Cosmos RPC server exposes routes such as /status; it is not interchangeable with the EVM service.

Inspect accounts and contracts with Cast

Set ACCOUNT_ADDRESS and CONTRACT_ADDRESS to addresses from your application or deployment records. Use an explicit function signature to encode and decode a call.

cast block-number --rpc-url "$PAXEER_RPC_URL"
cast balance "$ACCOUNT_ADDRESS" --rpc-url "$PAXEER_RPC_URL"
cast nonce "$ACCOUNT_ADDRESS" --rpc-url "$PAXEER_RPC_URL"
cast code "$CONTRACT_ADDRESS" --rpc-url "$PAXEER_RPC_URL"
cast call "$CONTRACT_ADDRESS" 'number()(uint256)'   --rpc-url "$PAXEER_RPC_URL"
cast calldata 'setNumber(uint256)' 42

The final command only encodes calldata. It does not submit a transaction. cast balance returns native base units by default. A missing contract, wrong ABI, or unavailable historical state can make a read fail even when the RPC endpoint is healthy.

Read token data at one block

Install viem in a Node project and save this as read-token.mjs. Set TOKEN_ADDRESS and ACCOUNT_ADDRESS to the EVM addresses you want to query, then run node read-token.mjs.

import { createPublicClient, formatUnits, getAddress, http, parseAbi } from "viem";

const rpcUrl = process.env.PAXEER_RPC_URL;
if (!rpcUrl) throw new Error("Set PAXEER_RPC_URL");
const token = getAddress(process.env.TOKEN_ADDRESS ?? "");
const account = getAddress(process.env.ACCOUNT_ADDRESS ?? "");
const client = createPublicClient({ transport: http(rpcUrl) });
if (await client.getChainId() !== 125) throw new Error("Unexpected chain ID");

const blockNumber = await client.getBlockNumber();
const abi = parseAbi([
  "function decimals() view returns (uint8)",
  "function symbol() view returns (string)",
  "function balanceOf(address owner) view returns (uint256)",
]);
const [decimals, symbol, balance] = await Promise.all([
  client.readContract({ address: token, abi, functionName: "decimals", blockNumber }),
  client.readContract({ address: token, abi, functionName: "symbol", blockNumber }),
  client.readContract({ address: token, abi, functionName: "balanceOf", args: [account], blockNumber }),
]);
console.log({ block: blockNumber.toString(), symbol, balance: formatUnits(balance, decimals) });

The requests run concurrently at the same block using ordinary contract calls. For tokens with a custom metadata interface, replace the example ABI with the contract’s published ABI.

Fetch events in bounded ranges

Save the following as read-transfers.mjs. Provide the token address, RPC URL, and an inclusive FROM_BLOCK/TO_BLOCK range. Start at the contract’s deployment block when building a full history.

import { createPublicClient, getAddress, http, parseAbiItem } from "viem";

const rpcUrl = process.env.PAXEER_RPC_URL;
if (!rpcUrl || !process.env.FROM_BLOCK || !process.env.TO_BLOCK) {
  throw new Error("Set PAXEER_RPC_URL, FROM_BLOCK and TO_BLOCK");
}
const address = getAddress(process.env.TOKEN_ADDRESS ?? "");
const client = createPublicClient({ transport: http(rpcUrl) });
if (await client.getChainId() !== 125) throw new Error("Unexpected chain ID");
const first = BigInt(process.env.FROM_BLOCK);
const last = BigInt(process.env.TO_BLOCK);
if (first < 0n || last < first) throw new Error("Invalid block range");
const event = parseAbiItem("event Transfer(address indexed from, address indexed to, uint256 value)");
const chunkSize = 500n;

for (let fromBlock = first; fromBlock <= last; fromBlock += chunkSize) {
  const toBlock = fromBlock + chunkSize - 1n < last ? fromBlock + chunkSize - 1n : last;
  const logs = await client.getLogs({ address, event, fromBlock, toBlock, strict: true });
  for (const log of logs) {
    console.log(JSON.stringify(log, (_, value) => typeof value === "bigint" ? value.toString() : value));
  }
}

Start with 500-block chunks and reduce the size if your provider returns a range or result-limit error. A production indexer should commit processed logs and its cursor together, resume only after successful chunks, and deduplicate by chain ID, block hash, transaction hash, and log index.

Keep transaction views consistent

eth_ methods provide the EVM view. Legacy pax_ and pax2_ methods can include additional Cosmos activity and use different transaction indices; their availability is configured by the node. Keep one namespace throughout an indexer and use hashes for lookup. See JSON-RPC namespaces.

For historical reads, the provider must retain state at that height. A recent balance read can work while an older eth_call fails. For a submitted transaction, inspect the receipt’s status, not just whether a hash exists.

References: viem contract reads, event logs, and Ethereum JSON-RPC.

Paxeer source: paxeer-network/ — rpc/state.go (StateAPI), rpc/filter.go (FilterAPI), and rpc/tx.go (TransactionAPI).
Paxeer Network · Developer documentationBack to top ↑

Ask Paxeer Docs

Answers from the documentation.

What would you like to know?

Ask a question, find a guide, or get help with your next step.

Enter to send · Shift+Enter for a new line