vike
GuidesOn-chain

Whose wallet is this

Put a name to an Ethereum address with its label, the sources behind that label 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).

Run
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
oc-whose-wallet.py
"""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 recipe 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/batch answers a batch whose every address is malformed with a 200 and an empty array, so the script checks the length as well as the status.
  • found: false is not a failure. The address has no label row; the script prints "none on record".
  • risk_flag is 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.eth drew "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.

On this page