Guide

Getting Started

Install the package, read one balance, and get the same shape back from Etherscan, Blockscout, Mempool, Koios or an Arweave gateway.
Pre-1.0. The API and the tool list can still move. Pin exact versions if you build on it now.

Why this exists

Every block explorer answers the same questions and none of them agree on how. Etherscan wants a chain id and a key in the query, Blockscout wants neither, Koios wants the address in a POST body, TONAPI answers in nanotons and Arweave in winstons, and Solscan and Helius disagree about which Solana questions exist at all. Keep one client per explorer in a script or an agent and you have thirteen ways to misread a number.

So @agntn/explorers puts one abstract Provider in front of all of it. Two required reads, six optional ones, one Balance, one Transaction, one TokenBalance, one GasData, one BlockInfo. Amounts are strings in the chain's smallest unit, so nothing is rounded before you decide it should be. And when you do not say which provider, it picks one from the keys you have.

Install

pnpm add @agntn/explorers

Node.js 24 or newer.

First call

Blockscout needs no key, so this works with no config at all:

balance.ts
import { create, resolveEns, resolveProvider } from "@agntn/explorers";

const provider = await create(resolveProvider(undefined, "ethereum"));
const address = await resolveEns("vitalik.eth");
if (!address) throw new Error("ENS name did not resolve");

const balance = await provider.getBalance(address, "ethereum");
const transactions = await provider.getTxHistory(address, "ethereum", { limit: 10 });

console.log(`${balance.balanceFormatted} ${balance.symbol}`);
console.log(transactions.map((transaction) => transaction.hash));

Set ETHERSCAN_API_KEY and the same two lines go through Etherscan instead. Nothing else changes. The whole ranking is in Provider selection.

Same call, any chain

const mempool = await create("mempool");
await mempool.getBalance("1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa", "bitcoin");

const arweave = await create("arweave");
await arweave.getBalance("FPjbN_btYKzcf8QASjs30v5C0FPv7XpwKXENBW8dqVw", "arweave");

The Explorer runs these exact calls against the docs worker, so you see what comes back before writing a line.

Optional operations

Required operations live on Provider. Optional ones stay absent when a backend cannot serve them, so check both the flag and the method:

if (provider.capabilities.contractInfo && provider.getContractInfo) {
  const contract = await provider.getContractInfo(
    "0x1f9840a85d5aF5bf1D1762F925BDADdC4201F984",
    "ethereum",
  );
  console.log(contract.isVerified, contract.name);
}

There is no stub that returns convincing nonsense. A provider without contract metadata has no getContractInfo, full stop.

What ships

OperationMethodWhere
native balancegetBalanceevery provider except Helius and Aptos
transaction historygetTxHistoryevery provider except Aptos
one transactiongetTxDetailall but TONAPI, Blockberry and Aptos
contract metadatagetContractInfoEtherscan, Blockscout
token holdingsgetTokenBalancesEtherscan, Blockscout, Helius, Koios
token transfersgetTokenTransfersEtherscan, Blockscout
gas suggestionsgetGasDataEtherscan, Blockscout, Mempool on Bitcoin and Litecoin
one blockgetBlockInfoall but TONAPI, Helius, Blockberry, Koios and Aptos

The per provider detail, with the traps, is on the provider pages. The per chain view is on Chains.

One provider, no registry

import { Mempool } from "@agntn/explorers/providers/mempool";

const mempool = new Mempool({ timeout: 30_000 });

A sub path import gives you the class and leaves the other twelve out of your bundle. create() is asynchronous for the same reason: it imports the one provider it was asked for and nothing else. Everything the registry answers without an instance, providers(), has(), supportsChain(), supportsCapability(), getDefaultURL() and resolveProvider(), stays synchronous and reads only metadata.

Next

@agntn/explorers·MIT license· Read-only. Addresses you type go to a public explorer API, never to a wallet.