Field notes
Why I built a CLI alternative to Etherscan
by Arya Rahimi · · 8 min read
I had twelve Etherscan tabs open. Then I had zero. This is the short story of what was in those tabs, why none of the existing tools were the right shape for what I actually wanted to do, and what I built instead. The result is glnc: an open-source CLI that does the parts of Etherscan I keep needing, from the terminal, with no API key and no account.
The twelfth tab
It was a Tuesday. I had a hot wallet, two cold wallets, a multisig on Safe, and a treasury address I was watching for someone else. Each of them lived on at least two chains. To check what I wanted to check, I had opened Etherscan on Ethereum, then Polygonscan, then Arbiscan, then Basescan. For Bitcoin I had Mempool. For Solana I had Solscan. The twelfth tab was a sign-in modal asking me to create an Etherscan account so it could let me access the rate-limited API I needed for a single balance lookup I was going to do once and never again.
I closed all of it. The work I was doing was not exotic. I wanted one number per wallet per chain, in dollars, plus a running total. I wanted to know what one transaction did in plain English, including which tokens moved and where. I wanted current gas across every chain in a single glance, because the gas market on one chain is information about every other chain. None of that is unusual work for anyone who has spent more than a week onchain. All of it was friction.
The friction was the surprising part. Every step I was doing in a browser was a step that, in any other domain I work in, I would have already piped through jq a year ago. I run ripgrep on logs, not a log viewer. I run httpie on APIs, not a web UI. The browser was the only piece of my development surface that had not been pulled into a shell yet, and the only reason was that nobody had bothered to write the tool.
What already existed
Before writing anything I checked what was already in the wild. The honest answer is: a lot, but not the shape I wanted.
Foundry's cast is excellent at what it is. It is a low-level RPC scalpel: ABI encode and decode, sign messages, broadcast transactions, call contract methods, query storage slots. If you are writing contracts, cast belongs in your PATH, and nothing I have built changes that. It is also, by design, not a wallet inspector. To read a wallet's contents with cast you have to know which tokens to ask about, fetch decimals per token, format the numbers yourself, and price each asset on your own. That is fine for scripted work where you know what you are looking at. It is the wrong shape for "what is in this address."
Block explorers are the obvious other answer. Blockscout is the most credible open-source explorer, Otterscan is the self-hostable counterpart for L1 archive nodes, and Etherscan is the commercial one most of us reach for by default. All three are web applications, and all three are very good at being web applications. None of them are a terminal tool. The Etherscan API exists, requires an account, has a rate limit you will hit if you use it more than casually, and returns data shaped to populate a web page rather than to compose with a shell pipeline.
So the gap was specific. Nothing in the wild solved "give me the contents of this wallet, in dollars, across every chain, from my shell, without an account." That is the gap glnc fills. It is not a replacement for cast, it is not a replacement for Etherscan, and it does not pretend to be. It is the missing piece for the workflow where you would otherwise open twelve tabs.
Design goals
Three constraints, picked deliberately, written down before any code shipped.
No API key. Default to free public RPCs. If a user can pay for a faster lane (Reservoir for NFTs, an Etherscan key for history) they can supply one, but the tool must work without it. The first install should not block on a signup form. Onchain data is public and there is no good reason a quick balance check should require an account anywhere.
glnc balance vitalik.ethPipeable. Every command emits a stable, versioned JSON envelope on stdout when you pass --json. Watch mode emits NDJSON, one self-contained line per poll, so you can drop it into jq, xargs, cron, or a shell loop without inventing your own parser. Stdout is data-only when JSON is on; progress chatter goes to stderr.
glnc balance vitalik.eth --json | jq '.data.totalUsd'Multi-chain auto-detect. Pass any address and let the tool figure out the chain from the format. A bech32 string is Bitcoin. A base58 string of the right length is Solana. A .eth name is ENS, and the resolved 0x address is an EVM wallet that gets queried across every supported EVM chain in parallel. The --chain flag exists for the rare case you want to narrow the query, but you should never need it for the common case.
glnc balance bc1qxy2kgdygjrsqtzq2n0yrf2493p83kkfjhx0wlhWhat surprised me building it
ENS is just a contract. I had been treating ENS as a service in my head, the way you treat DNS as a service even though it is technically a protocol. It is not a service. ENS forward and reverse resolution are read calls against well-known mainnet contracts, and the only thing you need to do them is an Ethereum RPC. glnc's resolver is a small file that walks the standard registry and reverse-resolver contracts. There is no third-party API in that path, which means the most common name in the entire ecosystem resolves with zero external dependencies and zero rate-limit risk.
Decoding Uniswap's Universal Router is messier than expected. Most calldata decoding is a function-selector lookup followed by a clean ABI decode. The Universal Router is different: it packs an opaque bytes stream of commands, each with its own inputs, into a single execute call. To present a useful summary you have to walk that command stream, identify which inner operations matter, and merge the result with the token movements pulled out of the receipt logs. The first version of the decoder handled the single-command path. The version that ships handles nested cases: Governor proposing through a Timelock that schedules a MultiSend that wraps a Universal Router execute. Each layer is straightforward on its own. The interesting work is the recursion.
Public RPCs are great until they rate-limit. Free public endpoints handle the volume a CLI tool generates without trouble most of the time. The exception is Solana's public mainnet-beta, which throttles anonymous traffic aggressively enough that a single multi-wallet query can trip it. The fallback chain inside glnc tries solana-rpc.publicnode.com first, then api.mainnet-beta.solana.com, then solana-mainnet.public.blastapi.io. The order matters. The retry logic matters more. If you are writing your own multi-chain client, treat "the primary RPC is up" as an assumption you should test, not a default.
What it is and isn't
glnc is read-only. It does not sign transactions. It does not hold keys. It does not broadcast. There is no wallet integration, no private key file, no seed-phrase prompt anywhere in the source tree. If you want to send ETH from the command line, use cast. glnc reads what is already on chain.
glnc is not a replacement for cast. Cast is a development tool, and if you are writing contracts you need it. The two coexist on the same PATH and they do different jobs. There is a longer comparison if you want to read about exactly when to reach for each.
glnc is not a portfolio app. There is no dashboard, no notification service, no SaaS account, no team workspace. Output is text. The output is the product. If you want graphs and benchmarks, use a portfolio app; if you want a number you can pipe into something else, this is the tool.
What it is, then, is small. Three commands cover the cases I keep actually needing: balance for wallet contents, tx for "what did this transaction do," gas for current fee markets. Three more cover the adjacent jobs: history exports to CSV, alert watches for a condition and fires a webhook, interactive opens a REPL so you can poke around without retyping the binary name. Everything else is flags.
What's next
More chains. The current set covers most of what I use, but the long tail of L2s is real and the marginal cost of adding another EVM is low once the adapter pattern is in place. More schemas: versioned JSON contracts for the commands that do not yet have them, so scripted consumers can rely on the shape. Better decoders, because every protocol that ships a router ships a new decoding problem. Account abstraction is next on the list, both because zkSync paymasters already complicate the gas accounting and because EIP-7702 will eventually make the question "whose transaction is this" a useful thing to answer in plain English. PRs welcome.
If you have kept twelve Etherscan tabs open this week, install it and tell me what is broken.