This additional metadata adds latency and cost to calculate. It should only be calculated and returned if explicitly requested through an optional param.
Background info: Contracts were proposed to be added to the Contracts data files in ENSAwards that weren't actually contracts. An investigation revealed they were actually EOAs. ENSAwards already has a suite of unit tests that aim to protect against bad data. If the primary name lookup APIs in ENSApi made it easy to query this additional metadata, it could support improved data quality in ENSAwards. More broadly, I can see a number of other special use cases where returning this metadata could be helpful.
Note: The following spec was quickly generated using ChatGPT.
Spec: Add viem-based account classification (EOA-like vs Contract)
Goal
Implement a small utility to classify an on-chain account as EOA-like (no deployed runtime bytecode) or Contract (has runtime bytecode), using viem PublicClient.getBytecode() on the chain specified by chainId.
Requirements
1) Define AccountClassification enum
- Create an enum named
AccountClassification
- It must have exactly two values:
Example shape (TypeScript):
AccountClassification.EOALike
AccountClassification.Contract
2) Define classifyAccount function
Create an async function:
Name: classifyAccount
Inputs:
account: object containing:
chainId: number
address: Address (from viem)
clientsByChainId: Map<number, PublicClient> (from viem)
Output:
Promise<AccountClassification>
3) Error if chainId missing
- If
account.chainId is not present as a key in clientsByChainId, the function must throw an Error.
- Error message should be explicit and include the missing chainId (e.g.,
"No PublicClient configured for chainId=XXXX").
4) Error if classification fails
- If anything fails during classification (e.g., RPC error, invalid params, viem throws, unexpected response), the function must throw an Error.
- The thrown error should:
- Preserve the original error as the
cause when possible (Node/TS supports new Error(msg, { cause })).
- Include enough context to debug (at minimum:
chainId and address).
No “UNKNOWN” / fallback return values—fail hard.
5) Use getBytecode to classify
- Use the correct
PublicClient from clientsByChainId for the given chainId.
- Call
publicClient.getBytecode({ address }).
- Classification rules:
- If bytecode is exactly
'0x' (or undefined if viem returns that for empty code), return AccountClassification.EOALike.
- Otherwise, return
AccountClassification.Contract.
Acceptance Criteria
- ✅ Enum exists with only
EOALike and Contract.
- ✅
classifyAccount(account, clientsByChainId) returns the expected enum value for:
- An EOA address on that chain (returns
EOALike)
- A deployed contract address on that chain (returns
Contract)
- ✅ Missing chainId in the map throws an error.
- ✅ Any RPC/viem failure during
getBytecode throws an error with chain/address context.
- ✅ Uses viem
getBytecode via the chain-specific PublicClient.
Notes / Non-goals
- This utility does not attempt to distinguish “true EOA” vs “self-destructed contract” / “counterfactual address”; it only classifies based on current deployed bytecode.
- No caching required in this initial implementation.
This additional metadata adds latency and cost to calculate. It should only be calculated and returned if explicitly requested through an optional param.
Background info: Contracts were proposed to be added to the Contracts data files in ENSAwards that weren't actually contracts. An investigation revealed they were actually EOAs. ENSAwards already has a suite of unit tests that aim to protect against bad data. If the primary name lookup APIs in ENSApi made it easy to query this additional metadata, it could support improved data quality in ENSAwards. More broadly, I can see a number of other special use cases where returning this metadata could be helpful.
Note: The following spec was quickly generated using ChatGPT.
Spec: Add viem-based account classification (EOA-like vs Contract)
Goal
Implement a small utility to classify an on-chain account as EOA-like (no deployed runtime bytecode) or Contract (has runtime bytecode), using viem
PublicClient.getBytecode()on the chain specified bychainId.Requirements
1) Define
AccountClassificationenumAccountClassificationEOALikeContractExample shape (TypeScript):
AccountClassification.EOALikeAccountClassification.Contract2) Define
classifyAccountfunctionCreate an async function:
Name:
classifyAccountInputs:
account: object containing:chainId: numberaddress: Address(from viem)clientsByChainId:Map<number, PublicClient>(from viem)Output:
Promise<AccountClassification>3) Error if chainId missing
account.chainIdis not present as a key inclientsByChainId, the function must throw an Error."No PublicClient configured for chainId=XXXX").4) Error if classification fails
causewhen possible (Node/TS supportsnew Error(msg, { cause })).chainIdandaddress).No “UNKNOWN” / fallback return values—fail hard.
5) Use
getBytecodeto classifyPublicClientfromclientsByChainIdfor the givenchainId.publicClient.getBytecode({ address }).'0x'(orundefinedif viem returns that for empty code), returnAccountClassification.EOALike.AccountClassification.Contract.Acceptance Criteria
EOALikeandContract.classifyAccount(account, clientsByChainId)returns the expected enum value for:EOALike)Contract)getBytecodethrows an error with chain/address context.getBytecodevia the chain-specificPublicClient.Notes / Non-goals