-
Notifications
You must be signed in to change notification settings - Fork 2
feat(nameguard): Add ENSNode API for primary name resolution #713
base: main
Are you sure you want to change the base?
Changes from 4 commits
5550563
127a5f7
5f18320
9795c6d
a502021
49d2610
24275b5
ab08179
9e93704
9413536
675d901
94304ea
d6b2448
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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 | ||
| ENS_SUBGRAPH_URL_MAINNET=https://api.thegraph.com/subgraphs/name/ensdomains/ens | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 |
||
| ENS_SUBGRAPH_URL_SEPOLIA=https://api.studio.thegraph.com/query/49574/enssepolia/version/latest | ||
|
|
||
| # Use ENSNode API (default is true) | ||
| USE_ENSNODE_API=true | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 |
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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" | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There shouldn't be any references to The Graph anymore. |
||
|
|
||
| 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 | ||
|
|
||
|
|
@@ -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) | ||
|
|
@@ -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' | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 Detailsdiff --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
AnalysisENSNode API rejects mixed-case Ethereum addresses without normalizationWhat fails: The How to reproduce: # Call with mixed-case (checksummed) address
result = await nameguard.secure_primary_name(
'0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96046', # Mixed case
'mainnet'
)
# Previously raised: ProviderUnavailable exceptionResult: Before the fix, mixed-case addresses caused the ENSNode API to reject the request with HTTP 400, which was caught and converted to a 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
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
| 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: | ||
|
|
@@ -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 | ||
|
|
||
This file was deleted.
There was a problem hiding this comment.
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.There was a problem hiding this comment.
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