Example 1 of 32
Crypto address lookup: labels and entity for an ETH wallet
Put a name to an Ethereum address: its label, the sources behind it and its ENS name, in three calls.
Give it an address and it answers three questions with three routes: what is it
(/labels/batch with a one-element list), who says so
(/labels/{address}/provenance), and does it have a name
(/wallets/{address}/ens).
export VIKE_API_KEY=...
python oc-whose-wallet.py # the default address is vitalik.eth's
python oc-whose-wallet.py 0x28C6c06298d514Db089934071355E5743bf21d60
python oc-whose-wallet.py --dry-run # prints the requests it would make, calls nothing"""Put a name to an Ethereum address: label, who said so, and its ENS name.
Run: VIKE_API_KEY=... python oc-whose-wallet.py [0xADDRESS] (--dry-run lists the requests and makes none)
"""
import os
import sys
from urllib.parse import urlencode
NAME = "oc-whose-wallet"
BASE = "https://data.vike.io"
DRY = "--dry-run" in sys.argv
ARGS = [a for a in sys.argv[1:] if not a.startswith("--")]
WALLET = ARGS[0] if ARGS else "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045"
def fail(why):
sys.exit(f"{NAME}: {why}")
def call(path, method="GET", **params):
url = BASE + path + ("?" + urlencode(params) if params else "")
if DRY:
print(method, url)
return None
key = os.environ.get("VIKE_API_KEY") or fail("set VIKE_API_KEY")
import requests
r = requests.request(method, url, headers={"X-API-Key": key}, timeout=60)
if r.status_code != 200:
fail(f"{method} {path} answered HTTP {r.status_code}: {r.text[:200]}")
return r.json()
def main():
# A POST with no body; one address is a one-element list. An address that is not valid comes back as [].
batch = call("/v1/onchain/labels/batch", method="POST", addresses=WALLET)
# Who said it: the consensus plus every source's own claim. A thin list can mean a source is rebuilding.
prov = call(f"/v1/onchain/labels/{WALLET}/provenance")
# Reverse ENS. source says which path answered: local (our store) or rpc (a fresh lookup).
ens = call(f"/v1/onchain/wallets/{WALLET}/ens")
if DRY:
return
if not batch:
fail(f"{WALLET} is not a valid address")
row = batch[0]
print(f"{WALLET}")
if row["found"]:
print(f" label {row['entity']} ({row['wallet_type']}), confidence {row['confidence']}")
print(f" 30 days ${row['volume_30d_usd']:,.0f} volume, {row['tx_30d']} txs, risk flag {row['risk_flag'] or 'none'}")
else:
print(" label none on record")
claims = "; ".join(s["label"] for s in prov.get("sources", []))
print(f" sources {prov.get('source_count', 0)}: {claims or 'none name it'}")
name = ens["ens"]
if name:
print(f" ens {name}")
elif ens["source"] == "rpc":
print(" ens none found, but an rpc miss may only mean the resolvers were unreachable: retry before concluding")
else:
print(" ens no name set")
if __name__ == "__main__":
try:
main()
except Exception as e: # a shape the example does not expect fails by name, not by traceback
fail(f"{type(e).__name__}: {e}")Read it this way
- A malformed address comes back as
[]./labels/batchanswers a batch whose every address is malformed with a200and an empty array, so the script checks the length as well as the status. found: falseis not a failure. The address has no label row; the script prints "none on record".risk_flagis the empty string when clean, so the script tests truthiness.- Sources speak for themselves. Each claim is printed in its source's own words, and they can disagree:
vitalik.ethdrew "Vitalik Buterin" and "sushiswap" on 2026-10-19. The provenance route also returns fewer rows rather than failing when a label source is mid-rebuild, so re-query a thin list before concluding.
An ENS miss over rpc is not proof of no name
source says which path answered. local with ens: null is a recorded fact: the address was resolved and has
no name. rpc with ens: null may only mean every resolver was unreachable, and it still comes back as a 200.
The script prints that case differently; retry before treating the address as unnamed.