Transactions
Two reads, one shape
const history = await provider.getTxHistory(address, "ethereum", { limit: 10 });
const one = await provider.getTxDetail?.(hash, "ethereum");
getTxHistory is required on every provider and pages through an address. getTxDetail is optional and reads one hash; TONAPI, Blockberry and Aptos don't have it. Both return Transaction:
interface Transaction {
hash: string;
blockNumber: number;
timestamp?: string; // ISO
from: string; // "" when the explorer names no sender
to: string | null; // null when the explorer names no recipient
createdContract?: string; // the deployed contract, from Etherscan and Blockscout
value: string; // smallest native unit
valueFormatted: string;
gasUsed?: string;
gasPrice?: string;
fee?: string; // smallest native unit
status: "success" | "failed" | "pending";
methodId?: string; // first four bytes of the input
functionName?: string; // when the explorer decoded it
isContractInteraction: boolean;
tokenTransfers: TokenTransfer[]; // the fungible transfers inside this transaction
opReturn?: OpReturnPayload[]; // Bitcoin family
inscriptions?: Inscription[]; // detail from mempool and blockstream
raw?: Record<string, unknown>; // the explorer's own answer
}
to is null whenever the explorer names no recipient, and that is not only a contract creation. Solscan, Helius and Blockberry never name one, because a Solana or Sui transaction has no single receiver; TONAPI names one for a TON or Jetton transfer, Horizon for an operation that moved an asset, Blockchair on Ethereum only, since its Bitcoin, Bitcoin Cash, Litecoin, Dogecoin, eCash and Zcash dashboards carry inputs and outputs instead. eCash Explorer never names the other side at all: a send has a null recipient, a receive an empty sender. A deployment is the transaction with createdContract: Etherscan and Blockscout report the address the transaction deployed, from contractAddress and created_contract, and no other provider does. A missing recipient on Arweave is an empty string, because there it means a data upload, not a deployment. from has no null: when there's no sender to name, it's an empty string. Blockchair's UTXO dashboards never give one, and Blockberry leaves it empty for activity with no account on the other side. A pending transaction has blockNumber 0, never a guess at the next height.
Options
interface TxHistoryOptions {
startBlock?: number; // inclusive
endBlock?: number; // inclusive
sort?: "asc" | "desc";
limit?: number; // provider caps apply, 100 on most
page?: number; // the first page is 1
tags?: readonly TagFilter[]; // Arweave only, see below
}
clampMaxResults(limit, max) is what every provider runs the limit through: a missing or zero limit means the provider's maximum, anything else gets rounded and kept between 1 and that maximum. Every provider honours page, so build your pager on any of them. Most explorers only hand out a cursor, though. Mempool, Blockstream, Blockscout, Solscan, Helius, TONAPI and Blockberry walk it past the earlier rows themselves, one request per explorer page. That's why they stop at page * limit <= 1000 and throw past it. Better than page one again, right? TRONSCAN and Blockchair skip rows on their side. TRONSCAN goes up to 10,000 rows, 50 a page, and Blockchair up to a million. The Arweave gateway has the same 1000-row window and wants block bounds for older history, dcrdata takes up to 250 a page and no block bounds at all, Horizon up to 200 and pages up to 10, one request per page walked, and no ledger bounds either.
tags is the odd one out. Only Arweave reads it, because there an upload carries whatever labels its author gave it. Each entry is { name, values }. A tag matches any of its values, and every tag has to match:
await arweave.getTxHistory(address, "arweave", {
tags: [{ name: "Content-Type", values: ["text/plain"] }],
});
That's the author's text posts and nothing else. Why not filter after the read? The window is 1000 rows, and the posts you want can sit behind all of them. A provider without capabilities.txHistoryTags ignores the field when you call it directly, so check the flag first. explorers_tx_history does that for you and refuses, instead of handing back the whole unfiltered history.
OP_RETURN
In the Bitcoin family, every provider that hands out the output script decodes it into opReturn. That's Mempool, Blockstream, Blockchair, WhatsOnChain, Blockbook and Haskoin. Bitcoin SV's OP_FALSE OP_RETURN counts too. Each payload has the pushed bytes as lowercase hex. text shows up only when those bytes are printable UTF-8. Runes, Omni and other binary carriers keep the hex and skip the text. Nobody wants mojibake handed to them as a message. The CLI prints the text when there's one and the hex otherwise.
Inscriptions
Got a puzzle that hides its clue in an Ordinals inscription? getTxDetail from Mempool or Blockstream reads it straight out of the reveal's witness. No indexer involved. Each entry in inscriptions names the input that carried it, its contentType and its size in bytes. A compressed body adds contentEncoding, usually br.
The body comes as hex, plus text when it's printable. That holds up to 4096 bytes. Past that you get the size and nothing else. Why? A 300 kB image in hex would eat an agent's context for breakfast. The bytes are still in raw, in that input's witness. History rows skip inscriptions, so ask for the one transaction.
Token transfers inside
tokenTransfers lists the fungible transfers the explorer attributed to this transaction, each with contract, symbol, decimals, value, valueFormatted, from, to and the txHash. Etherscan and Blockscout fill it on EVM chains, TONAPI with Jetton transfers minus the failed ones, Koios with Cardano native assets. An address that only ever received tokens from other people has an empty native history and a full transfer history, which is why that read exists on its own.
raw
The explorer's own answer, untouched, on the transaction that produced it. Not part of the contract and different on every provider, but there for the day you need a field nobody normalized, like every input and output of a Bitcoin transaction. A script gets it in full. The MCP tools drop it unless the call says raw: true, and the docs worker drops it before an answer leaves, so the Explorer never shows it. It's the biggest thing in the object by far, and most callers don't want it.
Ambiguity
64 hex characters is a hash on Ethereum, on the Bitcoin family, TRON, Cardano, Decred and Stellar. A base58 string of 64 to 88 characters is a hash on Solana, 43 or 44 on Sui. Arweave addresses and transaction ids share a shape, so getTxDetail there needs you to know what you hold; the CLI's -m detail is that statement.