alina schanz

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

contents
  1. record
  2. readme
  3. files
  4. history

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:

  1. namehash(name) per eip-137
  2. registry.resolver(node) on the ens registry
  3. resolver.addr(node) for the address, resolver.text(node, key) for the records (ensip-5 keys)
  4. 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

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

mit

files

history

  1. a0af707megood signature from 17BA…F3AB

  2. ebf54f3megood signature from 17BA…F3AB

  3. 81f9c92megood signature from 17BA…F3AB

  4. 179943dmegood signature from 17BA…F3AB

  5. 83b8d28megood signature from 17BA…F3AB

  6. a2510a6megood signature from 17BA…F3AB

  7. 0ca84f8megood signature from 17BA…F3AB

  8. 453d87emegood signature from 17BA…F3AB

  9. 1913b1cmegood signature from 17BA…F3AB

  10. a5d4c0fmegood signature from 17BA…F3AB

the latest 10 commits. all 10, with the signature of each: the commit list.