-
Notifications
You must be signed in to change notification settings - Fork 47
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* feat: nfd domains lookups * chore: pr comments * docs: regen docs * chore: mypy tweaks
- Loading branch information
1 parent
54b8a5e
commit 69d1f0b
Showing
9 changed files
with
322 additions
and
14 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
# AlgoKit Task NFD Lookup | ||
|
||
The AlgoKit NFD Lookup feature allows you to perform a lookup via NFD domain or address, returning the associated address or domain respectively using the AlgoKit CLI. The feature is powered by [NFDomains MainNet API](https://api-docs.nf.domains/). | ||
|
||
## Usage | ||
|
||
Available commands and possible usage as follows: | ||
|
||
```bash | ||
$ ~ algokit task nfd-lookup | ||
Usage: algokit task nfd-lookup [OPTIONS] VALUE | ||
|
||
Perform a lookup via NFD domain or address, returning the associated address or domain respectively. | ||
|
||
Options: | ||
-o, --output [full|tiny|address] Output format for NFD API response. Defaults to address|domain resolved. | ||
-h, --help Show this message and exit. | ||
``` | ||
|
||
## Options | ||
|
||
- `VALUE`: Specifies the NFD domain or Algorand address to lookup. This argument is required. | ||
- `--output, -o [full|tiny|address]`: Specifies the output format for NFD API response. Defaults to address|domain resolved. | ||
|
||
> When using the `full` and `tiny` output formats, please be aware that these match the [views in get requests of the NFD API](https://api-docs.nf.domains/quick-start#views-in-get-requests). The `address` output format, which is used by default, refers to the respective domain name or address resolved and outputs it as a string (if found). | ||
## Example | ||
|
||
To perform a lookup, you can use the nfd-lookup command as follows: | ||
|
||
```bash | ||
$ algokit task nfd-lookup {NFD_DOMAIN_OR_ALGORAND_ADDRESS} | ||
``` | ||
|
||
This will perform a lookup and return the associated address or domain. If you want to specify the output format, you can use the --output flag: | ||
|
||
```bash | ||
$ algokit task nfd-lookup {NFD_DOMAIN_OR_ALGORAND_ADDRESS} --output full | ||
``` | ||
|
||
If the lookup is successful, the result will be output to the console in a JSON format. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
import logging | ||
|
||
import click | ||
|
||
from algokit.cli.tasks.utils import validate_address | ||
from algokit.core.tasks.nfd import NFDMatchType, nfd_lookup_by_address, nfd_lookup_by_domain | ||
|
||
logger = logging.getLogger(__name__) | ||
|
||
|
||
def is_nfd(value: str) -> bool: | ||
return value.endswith(".algo") | ||
|
||
|
||
def is_algorand_address(value: str) -> bool: | ||
try: | ||
validate_address(value) | ||
return True | ||
except Exception: | ||
return False | ||
|
||
|
||
@click.command( | ||
name="nfd-lookup", | ||
help="Perform a lookup via NFD domain or address, returning the associated address or domain respectively.", | ||
) | ||
@click.argument( | ||
"value", | ||
type=click.STRING, | ||
) | ||
@click.option( | ||
"--output", | ||
"-o", | ||
required=False, | ||
default=NFDMatchType.ADDRESS.value, | ||
type=click.Choice([e.value for e in NFDMatchType]), | ||
help="Output format for NFD API response. Defaults to address|domain resolved.", | ||
) | ||
def nfd_lookup( | ||
value: str, | ||
output: str, | ||
) -> None: | ||
if not is_nfd(value) and not is_algorand_address(value): | ||
raise click.ClickException("Invalid input. Must be either a valid NFD domain or an Algorand address.") | ||
|
||
try: | ||
if is_nfd(value): | ||
click.echo(nfd_lookup_by_domain(value, NFDMatchType(output))) | ||
elif is_algorand_address(value): | ||
click.echo(nfd_lookup_by_address(value, NFDMatchType(output))) | ||
except Exception as err: | ||
raise click.ClickException(str(err)) from err |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,101 @@ | ||
import json | ||
import logging | ||
from enum import Enum | ||
|
||
import httpx | ||
|
||
logger = logging.getLogger(__name__) | ||
|
||
NF_DOMAINS_API_URL = "https://api.nf.domains" | ||
|
||
|
||
class NFDMatchType(Enum): | ||
FULL = "full" | ||
TINY = "tiny" | ||
ADDRESS = "address" | ||
|
||
|
||
def _process_get_request(url: str) -> dict: | ||
response = httpx.get(url) | ||
|
||
try: | ||
response.raise_for_status() | ||
data = response.json() | ||
if not isinstance(data, dict): | ||
raise ValueError("Response JSON is not a dictionary") | ||
return data | ||
except httpx.HTTPStatusError as err: | ||
logger.debug(f"Error response: {err.response}") | ||
|
||
if err.response.status_code == httpx.codes.NOT_FOUND: | ||
raise Exception("Not found!") from err | ||
if err.response.status_code == httpx.codes.BAD_REQUEST: | ||
raise Exception(f"Invalid request: {err.response.text}") from err | ||
if err.response.status_code == httpx.codes.UNAUTHORIZED: | ||
raise Exception(f"Unauthorized to access NFD API: {err.response.text}") from err | ||
if err.response.status_code == httpx.codes.FORBIDDEN: | ||
raise Exception(f"Forbidden to access NFD API: {err.response.text}") from err | ||
if err.response.status_code == httpx.codes.TOO_MANY_REQUESTS: | ||
raise Exception(f"Too many requests to NFD API: {err.response.text}") from err | ||
|
||
raise Exception( | ||
f'NFD lookup failed with status code {err.response.status_code} and message "{err.response.text}"' | ||
) from err | ||
|
||
|
||
def nfd_lookup_by_address(address: str, view: NFDMatchType) -> str: | ||
""" | ||
Perform a lookup on an API to retrieve information about a given address. | ||
Args: | ||
address (str): The address to perform the lookup on. | ||
view (NFDMatchType): The type of view to retrieve from the API. | ||
It can be one of the following: "full", "tiny", or "address". | ||
Returns: | ||
str: If the view is "address", returns the name associated with the address as a string. | ||
If the view is not "address", returns the JSON response from the API as a string with an indentation of 2. | ||
Raises: | ||
Exception: If the content from the API is not a dictionary, raises an exception with the unexpected response. | ||
""" | ||
|
||
view_type = "thumbnail" if view.value == NFDMatchType.ADDRESS.value else view.value | ||
url = f"{NF_DOMAINS_API_URL}/nfd/lookup?address={address}&view={view_type}&allowUnverified=false" | ||
content = _process_get_request(url) | ||
if isinstance(content, dict): | ||
if view.value == NFDMatchType.ADDRESS.value: | ||
return str(content[address]["name"]) | ||
else: | ||
return json.dumps(content, indent=2) | ||
|
||
raise Exception(f"Unexpected response from NFD API: {content}") | ||
|
||
|
||
def nfd_lookup_by_domain(domain: str, view: NFDMatchType) -> str: | ||
""" | ||
Performs a lookup on a given domain using the NF Domains API. | ||
Args: | ||
domain (str): The domain to be looked up. | ||
view (NFDMatchType): The type of information to retrieve. | ||
It can be one of the following: NFDMatchType.FULL, NFDMatchType.TINY, or NFDMatchType.ADDRESS. | ||
Returns: | ||
str: If the view is NFDMatchType.ADDRESS, returns the owner of the domain as a string. | ||
If the view is not NFDMatchType.ADDRESS, returns the response JSON stringified with indentation. | ||
Raises: | ||
Exception: If the response from the NF Domains API is not a dictionary. | ||
""" | ||
|
||
view_type = "brief" if view.value == NFDMatchType.ADDRESS.value else view.value | ||
url = f"{NF_DOMAINS_API_URL}/nfd/{domain}?view={view_type}&poll=false" | ||
content = _process_get_request(url) | ||
if isinstance(content, dict): | ||
if view == NFDMatchType.ADDRESS: | ||
return str(content["owner"]) | ||
else: | ||
return json.dumps(content, indent=2) | ||
|
||
raise Exception(f"Unexpected response from NFD API: {content}") |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,77 @@ | ||
import algosdk | ||
from pytest_httpx import HTTPXMock | ||
|
||
from tests.utils.approvals import verify | ||
from tests.utils.click_invoker import invoke | ||
|
||
|
||
def test_nfd_lookup_by_domain_success(httpx_mock: HTTPXMock) -> None: | ||
# Arrange | ||
httpx_mock.add_response( | ||
url="https://api.nf.domains/nfd/dummy.algo?view=brief&poll=false", | ||
json={ | ||
"name": "dummy.algo", | ||
"owner": "A" * 58, | ||
"depositAccount": "A" * 58, | ||
"properties": {}, | ||
}, | ||
) | ||
|
||
# Act | ||
result = invoke("task nfd-lookup dummy.algo") | ||
|
||
# Assert | ||
assert result.exit_code == 0 | ||
verify(result.output) | ||
|
||
|
||
def test_nfd_lookup_by_address_success(httpx_mock: HTTPXMock) -> None: | ||
# Arrange | ||
_, dummy_wallet = algosdk.account.generate_account() # type: ignore[no-untyped-call] | ||
httpx_mock.add_response( | ||
url=f"https://api.nf.domains/nfd/lookup?address={dummy_wallet}&view=thumbnail&allowUnverified=false", | ||
json={ | ||
dummy_wallet: { | ||
"appID": 222222222, | ||
"state": "owned", | ||
"timeChanged": "2022-02-02", | ||
"depositAccount": "A" * 58, | ||
"name": "dummy.algo", | ||
"owner": "A" * 58, | ||
"properties": {}, | ||
"caAlgo": ["A" * 58], | ||
} | ||
}, | ||
) | ||
|
||
# Act | ||
result = invoke(f"task nfd-lookup {dummy_wallet}") | ||
|
||
# Assert | ||
assert result.exit_code == 0 | ||
verify(result.output.replace(dummy_wallet, "A" * 58)) | ||
|
||
|
||
def test_nfd_lookup_error(httpx_mock: HTTPXMock) -> None: | ||
# Arrange | ||
httpx_mock.add_response( | ||
url="https://api.nf.domains/nfd/dummy.algo?view=brief&poll=false", | ||
status_code=400, | ||
json={"message": "Invalid request"}, | ||
) | ||
|
||
# Act | ||
result = invoke("task nfd-lookup dummy.algo") | ||
|
||
# Assert | ||
assert result.exit_code == 1 | ||
assert "Invalid request" in result.output | ||
|
||
|
||
def test_nfd_lookup_invalid_input() -> None: | ||
# Act | ||
result = invoke("task nfd-lookup dummy") | ||
|
||
# Assert | ||
assert result.exit_code == 1 | ||
assert "Invalid input. Must be either a valid NFD domain or an Algorand address." in result.output |
2 changes: 2 additions & 0 deletions
2
tests/tasks/test_nfd_lookup.test_nfd_lookup_by_address_success.approved.txt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
DEBUG: HTTP Request: GET https://api.nf.domains/nfd/lookup?address=AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA&view=thumbnail&allowUnverified=false "HTTP/1.1 200 OK" | ||
dummy.algo |
Oops, something went wrong.