codepythonmit
ens-lookup
ens names to addresses and back, text records included. pure python, its own keccak-256, reverse lookups confirmed forward.
head a0af7072026-09-0824 files437 lines of python
record
- commits
- 10 on main, 2026-09-08 to 2026-09-08
- signed
- 10 good signature from 17BA…F3AB
- tests
- 22 passed 2 skipped
python 3.11.9 on my machine, 2026-09-23 - ci
- ci.yml lists python 3.10, 3.12, 3.13, 3.14.
- history
- every commit up to a0af707, each with its signed record. the history itself stays off this site, so from here you cannot tie a file to a commit.
- source
- ens-lookup.tar.gz, 13.8 KB · sha256
d2ec52fffa9e…: the files at a0af707, without README.md, which is shown below. - license
- mit (LICENSE)
readme
rendered from README.md at a0af707. i left out the badges, which load from github and shields.io, and one link, which shows as plain text. the install lines point at github, which answers 404 for this account at the moment.
ens-lookup
ens names to addresses and back, plus the text records (twitter, github, url, ...). pure python, nothing to install, a public rpc, no keys. keccak-256 is implemented in the package because the standard library only ships nist sha3, which pads differently.
i wrote this because i kept pasting names into a block explorer to check who an address was. now it is one command, and it works in a loop over a file.
$ enslookup vitalik.eth --all-text
vitalik.eth
address 0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045
resolver 0x231b0Ee14048e9dCcD1d247744d114a4EB5E8E63
avatar https://euc.li/vitalik.eth
description mi pinxe lo crino tcati
url https://vitalik.ca
com.twitter VitalikButerin
com.github vbuterin
$ enslookup 0xb8c2C29ee19D8307cb7255e1Cd9CbDE883A267d5
0xb8c2C29ee19D8307cb7255e1Cd9CbDE883A267d5
name nick.eth
resolver 0x4976fb03C32e5B8cfe2b6cCB31c09Ba78EBaBa41
url https://ens.domains/
com.twitter nicksdjohnson
com.github arachnid
install
pipx install git+https://github.com/alinaschanz/ens-lookup
or clone it and run python -m enslookup ... from the folder. python 3.10 or newer,
no dependencies.
use
enslookup vitalik.eth nick.eth # forward
enslookup 0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045 # reverse, checked against the forward record
enslookup ens.eth --text url,com.twitter,email # pick the text records
enslookup --batch names.txt --csv > out.csv # one query per line, - for stdin
enslookup --batch names.txt --json # json lines
enslookup vitalik.eth --rpc https://your.node # your own endpoint first
--all-text asks for avatar, description, url, email, com.twitter, com.github,
com.discord, org.telegram, location, keywords and notice. --no-text is faster.
an avatar record is also printed as an avatar url you can open: ipfs:// becomes a
gateway link and eip155:1/erc721:<contract>/<id> is followed through tokenURI to the
token's image (ensip-12).
batches run four lookups at a time (--workers).
exit code is 0 when at least one query resolved, 1 when none did, 2 when no rpc answered.
how it works
the long way round, on purpose, so the steps are visible:
namehash(name)per eip-137registry.resolver(node)on the ens registryresolver.addr(node)for the address,resolver.text(node, key)for the records (ensip-5 keys)- reverse:
namehash("<hex>.addr.reverse")->resolver.name(node), then step 1-3 on the result. if that does not land on the same address the name is printed as unverified, the way eip-181 asks for.
every call is a plain eth_call over json-rpc via urllib. the default endpoints are
publicnode, drpc, mevblocker, tenderly and blastapi, tried in that order; the first one
that answers is remembered.
what it does not do: offchain and wildcard names (ensip-10,
the *.cb.id and *.base.eth kind) show up as "nothing found", and normalization is
lowercase + nfc rather than full ensip-15, so emoji
names may hash differently than in a wallet.
tests
python -m pytest -q # offline: keccak vectors, namehash, checksums, a fake node
ENSLOOKUP_LIVE=1 python -m pytest -q # plus two lookups against mainnet
the sponge is checked against hashlib.sha3_256 for a few hundred input lengths (same
permutation, different padding byte), and the padding against the published keccak-256
vectors, so a wrong bit anywhere in keccak.py fails loudly.
see also
- onchain-notes: the daily card and gas scripts
- bigmoves: large transfers, which can use this package to name addresses
- gasweek, stablepeg
- the notes behind the numbers: alinaschanz.life, the short version on x
verify a release
from the next release on, every release carries the sdist and the wheel, a SHA256SUMS file, an
opentimestamps proof of that file, and a build provenance attestation made in github's own signing
flow. with the files downloaded into one folder:
sha256sum -c SHA256SUMS
gh attestation verify ./*.whl --owner alinaschanz
ots verify SHA256SUMS.ots
the attestation names the commit and the workflow run that produced the file; the timestamp proves the checksums existed before a certain bitcoin block; the commit itself is signed.
license
files
| path | lines | bytes |
|---|---|---|
.editorconfig# | 15 | 233 |
.github/dependabot.yml# | 8 | 237 |
.github/workflows/ci.yml# | 46 | 1,179 |
.github/workflows/pypi.yml# | 30 | 1,102 |
.github/workflows/release.yml# | 43 | 1,520 |
.github/workflows/scorecard.yml# | 33 | 889 |
.gitignore# | 7 | 66 |
.pre-commit-config.yaml# | 13 | 321 |
CHANGELOG.md# | 17 | 706 |
LICENSE# | 21 | 1,069 |
README.md# | shown above | |
enslookup/__init__.py# | 4 | 190 |
enslookup/__main__.py# | 5 | 52 |
enslookup/abi.py# | 53 | 1,588 |
enslookup/avatar.py# | 82 | 2,896 |
enslookup/cli.py# | 99 | 4,484 |
enslookup/ens.py# | 148 | 5,603 |
enslookup/keccak.py# | 84 | 2,901 |
enslookup/rpc.py# | 65 | 2,660 |
pyproject.toml# | 43 | 1,133 |
tests/test_avatar.py# | 56 | 2,566 |
tests/test_ens.py# | 153 | 5,732 |
tests/test_keccak.py# | 34 | 1,420 |
tests/test_live.py# | 21 | 590 |
history
the latest 10 commits. all 10, with the signature of each: the commit list.