ID@agntn/explorersv0.6.1

Nineteen explorers. One shape.

Etherscan, Blockscout, Mempool, Solscan, Koios, dcrdata and the rest behind one TypeScript contract. Balances, transactions, unspent outputs, tokens, contracts, gas and blocks as exact strings, and a provider picked for you from the keys you have. Library, CLI, MCP, Pi and OMP all read through the same code.

Providers
19
12 keyless
Chains
30
12 EVM
No key
24 chains
read a balance
Install$ pnpm add @agntn/explorers

An address in, one Balance out

getBalance(address, chain) returns the same shape whether the answer came from Etherscan, Mempool or an Arweave gateway. ENS names resolve first, so vitalik.eth is as good as the hex. This panel walks through 3 addresses on three chains. Each one starts as a recorded sample and gets swapped for the worker's live answer the moment it lands.

  • balance is a string in the chain's smallest unit; balanceFormatted is the same number for humans
  • fetchedAt on every read, blockNumber and blockHash when the explorer says which block it meant, null when it doesn't
  • UTXO explorers add funded and spent totals and keep the mempool delta in unconfirmed; the rest leave them out rather than send zero
Read Open the explorer
CallgetBalance("vitalik.eth", "ethereum")recorded
Balance / ethereum

6.71215016 ETH

"6 712 150 161 831 460 931" smallest unit

Provider
Blockscout
Address
0xd8dA6BF2…A96045
In / out
not sent on Ethereum
Block
not named, null
Fetched
2026-09-05 17:24

History and detail, same Transaction

getTxHistory pages through an address, getTxDetail reads one hash, and both hand back the same Transaction. The explorer's own answer rides along in raw for the day you need a field nobody normalized. That day comes, usually on Bitcoin.

  • hash, from, to, value, fee, status and the token transfers inside, on every chain that has them
  • to is null without a recipient, createdContract names a deployment, and the one empty string is an Arweave data upload, never an address
  • OP_RETURN payloads on Bitcoin, Litecoin and Pepecoin come as hex, plus text when the bytes are printable
Read Transactions
ListgetTxHistory(address, "ethereum")limit 5 · recorded
  1. 0x18fbf47989…83cbab0 ETH0xFD8d…dC48 → 0xd8dA…6045 · block 25882055 · 2026-09-01 · 0x4f414d20
  2. 0xc26d92ef86…95a01b0 ETH0xFD8d…dC48 → 0xd8dA…6045 · block 25881912 · 2026-09-01 · 0x4f414d50
  3. 0x9e614e2ccc…41fab80.000066 ETH0x899A…3871 → 0xd8dA…6045 · block 25834334 · 2026-08-25
  4. 0x2a6b0fd28b…9437ba0.000409 ETH0xb8aE…6B89 → 0xd8dA…6045 · block 25830440 · 2026-08-25
  5. 0xff3d62e0f2…1abc1b0.000409 ETH0xb8aE…6B89 → 0xd8dA…6045 · block 25823663 · 2026-08-24

No recipient reads none, an Arweave upload data upload.

same Transaction shape from Blockscoutraw kept in .raw

Keys first, keyless next, Blockscout last

Set ETHERSCAN_API_KEY and Ethereum reads go through Etherscan. Unset it and they go through Blockscout. Nothing else in your code changes. The ranking never loads a provider module, so listing candidates is free and the one that answers is the only one that gets imported.

  • resolveProvider() ranks configured keys, then keyless providers, then anything else that serves the chain
  • withProvider() retries once on the next candidate after a rate or plan limit, no answer or a 5xx
  • An explicit --provider stays strict: a wrong chain is an UnsupportedChainError, not a silent switch
Read How a provider is picked
CallresolveProvider(undefined, "ethereum")balances · no keys set

Ranking

  1. 01Blockscout "blockscout"keyless
  2. 02Etherscan "etherscan"needs a key
  3. 03Blockchair "blockchair"optional key

With keys set

Picks
Etherscan
Env
ETHERSCAN_API_KEY

16 other providers don't serve ethereum and are never asked. A rate or plan limit on the first moves the read to the next one, once.

ranked without importing a providercreate() loads the winner

19 backends, honest about what they serve

Etherscan wants an API key and a chain id, Koios wants the address in a POST body, the Arweave gateway answers half its questions over GraphQL. Each provider keeps that to itself and maps its answers onto the shared types. An operation a backend can't serve is missing, not stubbed with convincing nonsense, and Aptos stays registered with none.

Import@agntn/explorers/providers/<key>

listProviders()19 providers · registry order
Operations
Etherscankey10 chains
  • bal: served
  • his: served
  • tx: served
  • utx: absent
  • key: absent
  • con: served
  • tok: served
  • trf: served
  • gas: served
  • blk: served
8 of 10
Blockscoutnone10 chains
  • bal: served
  • his: served
  • tx: served
  • utx: absent
  • key: absent
  • con: served
  • tok: served
  • trf: served
  • gas: served
  • blk: served
8 of 10
Blockchairoptional7 chains
  • bal: served
  • his: served
  • tx: served
  • utx: absent
  • key: absent
  • con: absent
  • tok: absent
  • trf: absent
  • gas: absent
  • blk: served
4 of 10
Mempoolnone3 chains
  • bal: served
  • his: served
  • tx: served
  • utx: served
  • key: served
  • con: absent
  • tok: absent
  • trf: absent
  • gas: served
  • blk: served
7 of 10
Blockstreamnone1 chain
  • bal: served
  • his: served
  • tx: served
  • utx: served
  • key: served
  • con: absent
  • tok: absent
  • trf: absent
  • gas: absent
  • blk: served
6 of 10
Solscankey1 chain
  • bal: served
  • his: served
  • tx: served
  • utx: absent
  • key: absent
  • con: absent
  • tok: absent
  • trf: absent
  • gas: absent
  • blk: served
4 of 10
Heliuskey1 chain
  • bal: absent
  • his: served
  • tx: served
  • utx: absent
  • key: absent
  • con: absent
  • tok: served
  • trf: absent
  • gas: absent
  • blk: absent
3 of 10
TONAPInone1 chain
  • bal: served
  • his: served
  • tx: absent
  • utx: absent
  • key: absent
  • con: absent
  • tok: absent
  • trf: absent
  • gas: absent
  • blk: absent
2 of 10
TRONSCANkey1 chain
  • bal: served
  • his: served
  • tx: served
  • utx: absent
  • key: absent
  • con: absent
  • tok: absent
  • trf: absent
  • gas: absent
  • blk: served
4 of 10
Aptos Explorernone1 chain
  • bal: absent
  • his: absent
  • tx: absent
  • utx: absent
  • key: absent
  • con: absent
  • tok: absent
  • trf: absent
  • gas: absent
  • blk: absent
0 of 10
Blockberrykey1 chain
  • bal: served
  • his: served
  • tx: absent
  • utx: absent
  • key: absent
  • con: absent
  • tok: absent
  • trf: absent
  • gas: absent
  • blk: absent
2 of 10
Koiosnone1 chain
  • bal: served
  • his: served
  • tx: served
  • utx: absent
  • key: absent
  • con: absent
  • tok: served
  • trf: absent
  • gas: absent
  • blk: absent
4 of 10
Arweave gatewaynone1 chain
  • bal: served
  • his: served
  • tx: served
  • utx: absent
  • key: absent
  • con: absent
  • tok: absent
  • trf: absent
  • gas: absent
  • blk: served
4 of 10
dcrdatanone1 chain
  • bal: served
  • his: served
  • tx: served
  • utx: absent
  • key: absent
  • con: absent
  • tok: absent
  • trf: absent
  • gas: absent
  • blk: served
4 of 10
Horizonnone1 chain
  • bal: served
  • his: served
  • tx: served
  • utx: absent
  • key: absent
  • con: absent
  • tok: served
  • trf: served
  • gas: served
  • blk: served
7 of 10
WhatsOnChainoptional1 chain
  • bal: served
  • his: served
  • tx: served
  • utx: served
  • key: absent
  • con: absent
  • tok: absent
  • trf: absent
  • gas: absent
  • blk: served
5 of 10
Blockbooknone1 chain
  • bal: served
  • his: served
  • tx: served
  • utx: served
  • key: absent
  • con: absent
  • tok: absent
  • trf: absent
  • gas: absent
  • blk: served
5 of 10
Haskoinnone1 chain
  • bal: served
  • his: served
  • tx: served
  • utx: served
  • key: absent
  • con: absent
  • tok: absent
  • trf: absent
  • gas: absent
  • blk: served
5 of 10
eCash Explorernone1 chain
  • bal: absent
  • his: served
  • tx: absent
  • utx: absent
  • key: absent
  • con: absent
  • tok: absent
  • trf: absent
  • gas: absent
  • blk: served
2 of 10
read from builtins / no networkcreate("<key>") loads one

Eleven tools, three hosts

explorers mcp serves the tools over stdio, the Pi and OMP extensions render them in the terminal. All three go through the same withProvider() as the CLI, so selection, keys and the one retry behave the same wherever the call starts. Your context window gets the normalized object and nothing else.

  • explorers_balance takes one address or up to twenty, and resolves ENS names before it reads
  • Every tool is read-only and says so in its annotations; nothing here signs or sends
  • Over MCP, raw records, ABIs and source stay out of the answer until a call asks for them
  • A provider that can't serve an operation answers with UnsupportedOperationError, not an empty list
Read MCP, Pi and OMP
Callexplorers_balance("vitalik.eth")
Tool / read-only

Ethereum

The agent gets the normalized Balance and nothing else, the raw answer stays on the server until a call asks.

address
"vitalik.eth"
chain
"ethereum"
provider
"blockscout"
balanceFormatted
"6.71215016" ETH
blockNumber
null
MCP · Pi · OMPexplorers mcp · stdio · via Blockscout

Same calls, every provider

Provider is the abstract base with two required reads and eight optional ones. Concrete classes implement the mappers and the explorer calls, nothing else leaks upward. A sub path import like @agntn/explorers/providers/mempool gives you one backend without the other eighteen in your bundle.

  • getBalance and getTxHistory on every provider; the other eight only where they are real
  • ExplorerError, HTTPError, AuthError, RateLimitError, PlanRestrictedError, NotFoundError and three more
  • API keys are stripped from every URL before an error message exists
Read Getting started
Fileethereum.tssame calls · 19 providers

Read

import { create, resolveAddresses, resolveProvider } from "@agntn/explorers";// blockscout: keyless, nothing to configureconst provider = await create(resolveProvider(undefined, "ethereum"));const [address] = await resolveAddresses("vitalik.eth", "ethereum");const balance = await provider.getBalance(address, "ethereum");const history = await provider.getTxHistory(address, "ethereum", { limit: 5 });// "6.71215016" ETH, then 0x18fbf479…83cbab first
Startpnpm add @agntn/explorersNode.js 26 or newer

Start with one command

One install gives you the library, the explorers CLI and the MCP server. Ask for a provider, or let the keys you have pick one, and read.

  • PinPre-1.0, so pin exact versions.
  • KeysExplorer keys live in the environment, never in your code.
  • ReadEverything here reads. Nothing signs, sends or holds a wallet.

First balance

$ pnpm add @agntn/explorersimport { create, resolveProvider } from "@agntn/explorers";const provider = await create(resolveProvider(undefined, "ethereum"));const balance = await provider.getBalance("0xd8dA…6045", "ethereum");balance.balanceFormatted;  // a string, exact