Skip to content
This repository was archived by the owner on Jun 2, 2026. It is now read-only.
Open
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 1 addition & 3 deletions apps/api.nameai.io/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,8 +78,6 @@ Ensure that Docker and the AWS CLI are installed and configured on your machine.

You need to set up the following environment variables before running the script:

- `PROVIDER_URI_MAINNET`
- `PROVIDER_URI_SEPOLIA`
- `ALCHEMY_URI_MAINNET`
- `ALCHEMY_URI_SEPOLIA`
- `ENS_SUBGRAPH_URL_MAINNET`
Expand All @@ -93,7 +91,7 @@ If you are using GitHub Actions for deployment, you need to configure the follow

- `AWS_ROLE` - The IAM role to assume for AWS actions.
- `AWS_REGION` - The AWS region where your resources are located.
- `PROVIDER_URI_MAINNET`, `PROVIDER_URI_SEPOLIA`, `ALCHEMY_URI_MAINNET`, `ALCHEMY_URI_SEPOLIA`, `ENS_SUBGRAPH_URL_MAINNET`, `ENS_SUBGRAPH_URL_SEPOLIA` - The respective URIs for your application.
- `ALCHEMY_URI_MAINNET`, `ALCHEMY_URI_SEPOLIA`, `ENS_SUBGRAPH_URL_MAINNET`, `ENS_SUBGRAPH_URL_SEPOLIA` - The respective URIs for your application.
- `CERTIFICATE_NAME` - The name of the ACM certificate.
- `HOSTED_ZONE_NAME` - The name of your Route 53 hosted zone.
- `PROD_DOMAIN_NAME` - The production domain name.
Expand Down
3 changes: 3 additions & 0 deletions packages/nameguard-python/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,6 @@ ALCHEMY_URI_SEPOLIA=https://eth-sepolia.g.alchemy.com/v2/[YOUR_ALCHEMY_API_KEY]
# - https://thegraph.com/explorer/subgraphs/5XqPmWe6gjyrJtFn9cLy237i4cWw2j9HcUJEXsP5qGtH?view=Query&chain=arbitrum-one
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My assumption is that we should be removing all use of endpoints for The Graph or for general RPC provider URIs (such as ankr.com) from NameGuard.

All should switch to our existing ENSNode APIs.

I understand we need to keep the Alchemy hostnames because of fake-eth-name-check.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is still an open issue

ENS_SUBGRAPH_URL_MAINNET=https://api.thegraph.com/subgraphs/name/ensdomains/ens
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These need to be updated. There's no need to configure subgraph URLs anymore.

Subgraph API URLs should be a function of ENSNODE_URL_MAINNET or ENSNODE_URL_SEPOLIA with the correct path added to talk to the subgraph API endpoint.

ENS_SUBGRAPH_URL_SEPOLIA=https://api.studio.thegraph.com/query/49574/enssepolia/version/latest

# Use ENSNode API (default is true)
USE_ENSNODE_API=true
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking at the way the other environment variables here are structured, it looks like we should be defining two environment variables, each containing an ENSNode hostname. One for alpha (which is mainnet) and another for alpha-sepolia (which is sepolia).

6 changes: 3 additions & 3 deletions packages/nameguard-python/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,11 +75,11 @@ See the [NameGuard Python README](./apps/api.nameguard.io/README.md) for more de

### Environment Variables

NameGuard uses the specified Provider endpoints (e.g. Alchemy, Infura, your own Ethereum node, etc...) for `secure-primary-name/`. Provider endpoints have to be set by environment variables, e.g.:
NameGuard uses the ENSNode API for primary name lookups in `secure-primary-name`. The ENSNode API only returns normalized primary names, so unnormalized primary names are treated as having no primary name.

For `fake-eth-name-check`, NameGuard uses Alchemy endpoints. Alchemy endpoints have to be set by environment variables, e.g.:

```bash
export PROVIDER_URI_MAINNET=https://eth-mainnet.g.alchemy.com/v2/[YOUR_ALCHEMY_API_KEY]
export PROVIDER_URI_SEPOLIA=https://eth-sepolia.g.alchemy.com/v2/[YOUR_ALCHEMY_API_KEY]
export ALCHEMY_URI_MAINNET=https://eth-mainnet.g.alchemy.com/v2/[YOUR_ALCHEMY_API_KEY]
export ALCHEMY_URI_SEPOLIA=https://eth-sepolia.g.alchemy.com/v2/[YOUR_ALCHEMY_API_KEY]
export ENS_SUBGRAPH_URL_MAINNET="https://gateway-arbitrum.network.thegraph.com/api/[YOUR_SUBGRAPH_API_KEY]/subgraphs/id/5XqPmWe6gjyrJtFn9cLy237i4cWw2j9HcUJEXsP5qGtH"
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I understand these subgraph endpoints should be removed and replaced with ENSNode endpoints.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There shouldn't be any references to The Graph anymore.

Expand Down
4 changes: 2 additions & 2 deletions packages/nameguard-python/coverage_badge.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
6 changes: 3 additions & 3 deletions packages/nameguard-python/nameguard/models/result.py
Original file line number Diff line number Diff line change
Expand Up @@ -344,14 +344,14 @@ class SecurePrimaryNameStatus(str, Enum):
The status of a secure primary ENS name lookup performed by NameGuard.

* `normalized`: The ENS primary name was found and it is normalized.
* `no_primary_name`: The ENS primary name was not found.
* `unnormalized`: The ENS primary name was found, but it is not normalized.
* `no_primary_name`: The ENS primary name was not found, or the primary name is unnormalized.
The ENSNode API only returns normalized primary names, so unnormalized primary names
are treated as having no primary name.
* `uninspected`: The name was exceptionally long and was not inspected for performance reasons.
"""

NORMALIZED = 'normalized'
NO_PRIMARY_NAME = 'no_primary_name'
UNNORMALIZED = 'unnormalized'
UNINSPECTED = 'uninspected'


Expand Down
52 changes: 36 additions & 16 deletions packages/nameguard-python/nameguard/nameguard.py
Original file line number Diff line number Diff line change
@@ -1,17 +1,16 @@
import os
import re
from typing import Union
from typing import Union, Optional

from nameguard.models.checks import UNINSPECTED_CHECK_RESULT
from nameguard.models.result import UninspectedNameGuardReport
from nameguard.our_ens import OurENS
from ens_normalize import is_ens_normalized, ens_cure, DisallowedSequence, ens_process

import httpx
import requests
from label_inspector.inspector import Inspector
from label_inspector.config import initialize_inspector_config
from label_inspector.models import InspectorConfusableGraphemeResult, InspectorResult
from web3 import HTTPProvider
from web3.exceptions import ContractLogicError
from dotenv import load_dotenv

Expand Down Expand Up @@ -169,15 +168,6 @@ class NameGuard:
def __init__(self):
self._inspector = init_inspector()
load_dotenv()
# TODO use web sockets and async
self.ns = {}
for network_name, env_var in (
(NetworkName.MAINNET, 'PROVIDER_URI_MAINNET'),
(NetworkName.SEPOLIA, 'PROVIDER_URI_SEPOLIA'),
):
if os.environ.get(env_var) is None:
logger.warning(f'Environment variable {env_var} is not set')
self.ns[network_name] = OurENS(HTTPProvider(os.environ.get(env_var)))

# optimization
self.eth_label = self._inspector.analyse_label('eth', simple_confusables=True, omit_cure=True)
Expand Down Expand Up @@ -447,11 +437,42 @@ def _inspect_confusable(self, grapheme: InspectorConfusableGraphemeResult) -> Co
is_canonical=False,
)

async def get_primary_name(self, address: str, network_name: NetworkName) -> Optional[str]:
"""
Looks up the primary ENS name for an address using the ENSNode API.

The ENSNode API only returns normalized primary names. If the primary name
is unnormalized, this method returns None.

Args:
address: The Ethereum address to look up
network_name: The network to query (MAINNET or SEPOLIA)

Returns:
The normalized primary name, or None if no primary name exists or
the primary name is unnormalized.
"""
if network_name == NetworkName.MAINNET:
url = f'https://api.alpha.ensnode.io/api/resolve/primary-name/{address}/1'
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ethereum addresses are not being normalized to lowercase before being used in the ENSNode API URL, causing requests with checksummed/mixed-case addresses to fail.

View Details
📝 Patch Details
diff --git a/packages/nameguard-python/nameguard/nameguard.py b/packages/nameguard-python/nameguard/nameguard.py
index 1fff110..a6f64c5 100644
--- a/packages/nameguard-python/nameguard/nameguard.py
+++ b/packages/nameguard-python/nameguard/nameguard.py
@@ -452,6 +452,8 @@ class NameGuard:
             The normalized primary name, or None if no primary name exists or
             the primary name is unnormalized.
         """
+        # Normalize address to lowercase for ENSNode API compatibility
+        address = address.lower()
         if network_name == NetworkName.MAINNET:
             url = f'https://api.alpha.ensnode.io/api/resolve/primary-name/{address}/1'
         elif network_name == NetworkName.SEPOLIA:
diff --git a/packages/nameguard-python/tests/test_nameguard.py b/packages/nameguard-python/tests/test_nameguard.py
index fc3090f..5cb3481 100644
--- a/packages/nameguard-python/tests/test_nameguard.py
+++ b/packages/nameguard-python/tests/test_nameguard.py
@@ -694,10 +694,12 @@ async def test_secure_primary_name(nameguard: NameGuard):
 @pytest.mark.asyncio
 async def test_secure_primary_name_wrong_casing(nameguard: NameGuard):
     network = 'mainnet'
-    with pytest.raises(ProviderUnavailable):
-        await nameguard.secure_primary_name(
-            '0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96046', network, return_nameguard_report=True
-        )
+    # Test that mixed-case addresses are properly normalized and handled
+    r = await nameguard.secure_primary_name(
+        '0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96046', network, return_nameguard_report=True
+    )
+    # This address has no primary name
+    assert r.primary_name_status == 'no_primary_name'
 
 
 @pytest.mark.asyncio

Analysis

ENSNode API rejects mixed-case Ethereum addresses without normalization

What fails: The get_primary_name() method in nameguard/nameguard.py constructs ENSNode API URLs using address parameters without normalizing them to lowercase, causing requests with checksummed/mixed-case addresses to fail with HTTP 400 Bad Request errors.

How to reproduce:

# Call with mixed-case (checksummed) address
result = await nameguard.secure_primary_name(
    '0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96046',  # Mixed case
    'mainnet'
)
# Previously raised: ProviderUnavailable exception

Result: Before the fix, mixed-case addresses caused the ENSNode API to reject the request with HTTP 400, which was caught and converted to a ProviderUnavailable exception. This breaks legitimate use cases where Ethereum addresses are passed in checksummed format.

Expected: Mixed-case addresses should be automatically normalized to lowercase before being sent to the ENSNode API, allowing the API call to succeed.

Fix: Normalize the address parameter to lowercase in get_primary_name() method before embedding it in the API URL. The ENSNode API accepts lowercase addresses but rejects mixed-case ones. Updated test test_secure_primary_name_wrong_casing to verify that mixed-case addresses are now properly handled and return the expected primary name status.

Copy link
Copy Markdown
Contributor

@vercel vercel Bot Jan 21, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ethereum addresses are not being normalized to lowercase before being used in the ENSNode API URL, causing requests with checksummed/mixed-case addresses to fail with HTTP 400 Bad Request errors

Fix on Vercel

elif network_name == NetworkName.SEPOLIA:
url = f'http://api.alpha-sepolia.ensnode.io/api/resolve/primary-name/{address}/11155111'
else:
raise ValueError(f'Unsupported network: {network_name}')

async with httpx.AsyncClient() as client:
response = await client.get(url, params={'accelerate': 'true'})
if response.status_code == 404:
return None
response.raise_for_status()
return response.json().get('name')

async def secure_primary_name(
self, address: str, network_name: str, return_nameguard_report: bool = False
) -> SecurePrimaryNameResult:
try:
domain = self.ns[network_name].name(address)
domain = await self.get_primary_name(address, network_name)
except (httpx.RequestError, httpx.HTTPStatusError) as ex:
raise ProviderUnavailable(f'Communication error with ENSNode API occurred: {ex}')
except requests.exceptions.ConnectionError as ex:
raise ProviderUnavailable(f'Communication error with provider occurred: {ex}')
except ContractLogicError:
Expand All @@ -462,17 +483,16 @@ async def secure_primary_name(
primary_name = None
nameguard_report = None
if domain is None:
# ENSNode API returns None for addresses with no primary name or unnormalized primary names
status = SecurePrimaryNameStatus.NO_PRIMARY_NAME
impersonation_estimate = None
else:
# If ENSNode API returns a name, it's guaranteed to be normalized
nameguard_report = await self.inspect_name(network_name, domain)

if nameguard_report.highest_risk and nameguard_report.highest_risk.check.name == Check.UNINSPECTED.name:
status = SecurePrimaryNameStatus.UNINSPECTED
impersonation_estimate = None
elif nameguard_report.normalization == Normalization.UNNORMALIZED:
status = SecurePrimaryNameStatus.UNNORMALIZED
impersonation_estimate = None
else:
display_name = nameguard_report.beautiful_name
status = SecurePrimaryNameStatus.NORMALIZED
Expand Down
205 changes: 0 additions & 205 deletions packages/nameguard-python/nameguard/our_ens.py

This file was deleted.

5 changes: 2 additions & 3 deletions packages/nameguard-python/nameguard/web_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -494,9 +494,8 @@ async def secure_primary_name_get(
"""
## Performs a reverse lookup of an Ethereum `address` to a primary name.

Data sources for the primary name lookup include:
1. The Ethereum Provider configured in the NameGuard instance.
2. For ENS names using CCIP-Read: requests to externally defined gateway servers.
The primary name lookup uses the ENSNode API, which only returns normalized primary names.
If an address has an unnormalized primary name, it will be treated as having no primary name.

Returns `display_name` to be shown to users and estimates `impersonation_estimate`.

Expand Down
Loading
Loading