diff --git a/python/examples/06-send-tokens/main.py b/python/examples/06-send-tokens/main.py index 0ab63069..8988e515 100644 --- a/python/examples/06-send-tokens/main.py +++ b/python/examples/06-send-tokens/main.py @@ -26,7 +26,7 @@ class TransactionInfo(Model): @alice.on_interval(period=10.0) async def request_funds(ctx: Context): await ctx.send( - bob.identifier, + bob.address, PaymentRequest( wallet_address=str(ctx.wallet.address()), amount=AMOUNT, denom=DENOM ), @@ -56,7 +56,7 @@ async def send_payment(ctx: Context, sender: str, msg: PaymentRequest): ) # send the tx hash so alice can confirm - await ctx.send(alice.identifier, TransactionInfo(tx_hash=transaction.tx_hash)) + await ctx.send(alice.address, TransactionInfo(tx_hash=transaction.tx_hash)) bureau = Bureau() diff --git a/python/examples/07-msg-verification/main.py b/python/examples/07-msg-verification/main.py index d317a5b7..397eb0b0 100644 --- a/python/examples/07-msg-verification/main.py +++ b/python/examples/07-msg-verification/main.py @@ -24,7 +24,7 @@ async def send_message(ctx: Context): msg = "Hello there bob." digest = encode(msg) await ctx.send( - bob.identifier, + bob.address, Message(message=msg, digest=digest.hex(), signature=alice.sign_digest(digest)), ) @@ -53,7 +53,7 @@ async def bob_rx_message(ctx: Context, sender: str, msg: Message): # send the response await ctx.send( - alice.identifier, + alice.address, Message(message=msg, digest=digest.hex(), signature=bob.sign_digest(digest)), ) diff --git a/python/examples/09-booking-protocol-demo/user.py b/python/examples/09-booking-protocol-demo/user.py index fc09668a..192ec2f3 100644 --- a/python/examples/09-booking-protocol-demo/user.py +++ b/python/examples/09-booking-protocol-demo/user.py @@ -8,7 +8,9 @@ from uagents.setup import fund_agent_if_low -RESTAURANT_ADDRESS = "agent1qfpqn9jhvp9cg33f27q6jvmuv52dgyg9rfuu37rmxrletlqe7lewwjed5gy" +RESTAURANT_ADDRESS = ( + "test-agent://agent1qfpqn9jhvp9cg33f27q6jvmuv52dgyg9rfuu37rmxrletlqe7lewwjed5gy" +) user = Agent( name="user", diff --git a/python/examples/10-cleaning-demo/user.py b/python/examples/10-cleaning-demo/user.py index c0bc4cea..5c09768e 100644 --- a/python/examples/10-cleaning-demo/user.py +++ b/python/examples/10-cleaning-demo/user.py @@ -12,7 +12,9 @@ from uagents.setup import fund_agent_if_low -CLEANER_ADDRESS = "agent1qdfdx6952trs028fxyug7elgcktam9f896ays6u9art4uaf75hwy2j9m87w" +CLEANER_ADDRESS = ( + "test-agent://agent1qdfdx6952trs028fxyug7elgcktam9f896ays6u9art4uaf75hwy2j9m87w" +) user = Agent( name="user", diff --git a/python/src/uagents/config.py b/python/src/uagents/config.py index 6a3fde57..45192cd4 100644 --- a/python/src/uagents/config.py +++ b/python/src/uagents/config.py @@ -12,8 +12,8 @@ AGENT_PREFIX = "agent" LEDGER_PREFIX = "fetch" USER_PREFIX = "user" -TESTNET_PREFIX = "test-agent://" -MAINNET_PREFIX = "agent://" +TESTNET_PREFIX = "test-agent" +MAINNET_PREFIX = "agent" CONTRACT_ALMANAC = "fetch1tjagw8g8nn4cwuw00cf0m5tl4l6wfw9c0ue507fhx9e3yrsck8zs0l3q4w" TESTNET_CONTRACT_ALMANAC = ( diff --git a/python/src/uagents/context.py b/python/src/uagents/context.py index 5fa26546..73ee4230 100644 --- a/python/src/uagents/context.py +++ b/python/src/uagents/context.py @@ -34,7 +34,7 @@ from uagents.dispatch import JsonStr, dispatcher from uagents.envelope import Envelope from uagents.models import ErrorMessage, Model -from uagents.resolver import Resolver, extract_agent_address +from uagents.resolver import Resolver, split_destination from uagents.storage import KeyValueStore @@ -425,7 +425,7 @@ async def send_raw( ) # Destination without ledger prefix - destination_address = extract_agent_address(destination) + _, destination_address = split_destination(destination) if destination_address is not None: # Handle local dispatch of messages diff --git a/python/src/uagents/resolver.py b/python/src/uagents/resolver.py index 75ce79c9..e623c866 100644 --- a/python/src/uagents/resolver.py +++ b/python/src/uagents/resolver.py @@ -12,27 +12,55 @@ ) from uagents.network import get_almanac_contract, get_name_service_contract -# pylint: disable=arguments-differ +def is_valid_address(address: str) -> bool: + """ + Check if the given string is a valid address. + + Args: + address (str): The address to be checked. -def extract_agent_address(destination: str) -> str: + Returns: + bool: True if the address is valid; False otherwise. """ - Extract the agent address from the provided destination. + return len(address) == 65 and address.startswith(AGENT_PREFIX) + + +def is_valid_prefix(prefix: str) -> bool: + """ + Check if the given string is a valid prefix. Args: - destination (str): The destination address to check and extract. + prefix (str): The prefix to be checked. Returns: - str: The extracted agent address if valid, or None if not valid. + bool: True if the prefix is valid; False otherwise. """ + valid_prefixes = [TESTNET_PREFIX, MAINNET_PREFIX, ""] + return prefix in valid_prefixes - address = destination.split("://")[-1].split("/")[-1] - expected_length = 65 - if len(address) == expected_length and address.startswith(AGENT_PREFIX): - return address +def split_destination(destination: str) -> tuple: + """ + Split a destination string into prefix and remainder. - return None + If the destination contains "://", it splits the prefix and remainder based on that. + If not, it assumes an empty prefix and returns the entire destination as the remainder. + + Args: + destination (str): The destination string to be split. + + Returns: + tuple: A tuple containing the prefix and remainder as strings. + """ + + if "://" in destination: + prefix, remainder = destination.split("://", 1) + remainder = remainder.split("/", 1)[-1] + else: + return "", destination + + return prefix, remainder def query_record(agent_address: str, service: str, test: bool) -> dict: @@ -73,29 +101,6 @@ def get_agent_address(name: str, test: bool) -> str: return None -def is_agent_address(address) -> tuple: - """ - Check if the provided address is a valid agent address. - - Args: - address: The address to check. - - Returns: - bool: True if the address is a valid agent address, False otherwise. - """ - if not isinstance(address, str): - return False - - prefixes = [TESTNET_PREFIX, MAINNET_PREFIX, ""] - expected_length = 65 - - for prefix in prefixes: - if address.startswith(prefix) and len(address) == expected_length + len(prefix): - return (True, prefix) - - return (False, None) - - class Resolver(ABC): @abstractmethod # pylint: disable=unnecessary-pass @@ -136,14 +141,18 @@ async def resolve(self, destination: str) -> Tuple[Optional[str], List[str]]: Returns: Tuple[Optional[str], List[str]]: The address (if available) and resolved endpoints. """ - is_address, prefix = is_agent_address(destination) - if is_address: - return await self._almanc_resolver.resolve( - destination[len(prefix) :], not prefix == MAINNET_PREFIX + + prefix, remainder = split_destination(destination) + + if is_valid_prefix(prefix): + resolver = ( + self._almanc_resolver + if is_valid_address(remainder) + else self._name_service_resolver ) - return await self._name_service_resolver.resolve( - destination, not prefix == MAINNET_PREFIX - ) + return await resolver.resolve(destination) + + return None, [] class AlmanacResolver(Resolver): @@ -156,9 +165,7 @@ def __init__(self, max_endpoints: Optional[int] = None): """ self._max_endpoints = max_endpoints or DEFAULT_MAX_ENDPOINTS - async def resolve( - self, destination: str, test: bool - ) -> Tuple[Optional[str], List[str]]: + async def resolve(self, destination: str) -> Tuple[Optional[str], List[str]]: """ Resolve the destination using the Almanac contract. @@ -168,7 +175,9 @@ async def resolve( Returns: Tuple[str, List[str]]: The address and resolved endpoints. """ - result = query_record(destination, "service", test) + prefix, address = split_destination(destination) + is_testnet = prefix != MAINNET_PREFIX + result = query_record(address, "service", is_testnet) if result is not None: record = result.get("record") or {} endpoint_list = ( @@ -178,7 +187,7 @@ async def resolve( if len(endpoint_list) > 0: endpoints = [val.get("url") for val in endpoint_list] weights = [val.get("weight") for val in endpoint_list] - return destination, random.choices( + return address, random.choices( endpoints, weights=weights, k=min(self._max_endpoints, len(endpoints)), @@ -198,9 +207,7 @@ def __init__(self, max_endpoints: Optional[int] = None): self._max_endpoints = max_endpoints or DEFAULT_MAX_ENDPOINTS self._almanac_resolver = AlmanacResolver(max_endpoints=self._max_endpoints) - async def resolve( - self, destination: str, test: bool - ) -> Tuple[Optional[str], List[str]]: + async def resolve(self, destination: str) -> Tuple[Optional[str], List[str]]: """ Resolve the destination using the NameService contract. @@ -210,9 +217,11 @@ async def resolve( Returns: Tuple[Optional[str], List[str]]: The address (if available) and resolved endpoints. """ - address = get_agent_address(destination, test) + prefix, name = split_destination(destination) + is_testnet = prefix != MAINNET_PREFIX + address = get_agent_address(name, is_testnet) if address is not None: - return await self._almanac_resolver.resolve(address, test) + return await self._almanac_resolver.resolve(address) return None, [] diff --git a/python/tests/test_agent_address.py b/python/tests/test_agent_address.py index 505adc57..be469398 100644 --- a/python/tests/test_agent_address.py +++ b/python/tests/test_agent_address.py @@ -2,7 +2,7 @@ from uagents import Agent from uagents.crypto import Identity -from uagents.resolver import extract_agent_address +from uagents.resolver import split_destination, is_valid_address, is_valid_prefix class TestAgentAdress(unittest.TestCase): @@ -31,30 +31,29 @@ def test_agent_generate(self): def test_extract_valid_address(self): valid_addresses = [ "agent1qfl32tdwlyjatc7f9tjng6sm9y7yzapy6awx4h9rrwenputzmnv5g6skess", - "some-prefix://agent1qfl32tdwlyjatc7f9tjng6sm9y7yzapy6awx4h9rrwenputzmnv5g6skess", - "other-prefix://agent1qfl32tdwlyjatc7f9tjng6sm9y7yzapy6awx4h9rrwenputzmnv5g6skess", - "prefix://name/agent1qfl32tdwlyjatc7f9tjng6sm9y7yzapy6awx4h9rrwenputzmnv5g6skess", - "agent_name/agent1qfl32tdwlyjatc7f9tjng6sm9y7yzapy6awx4h9rrwenputzmnv5g6skess", + "test-agent://agent1qfl32tdwlyjatc7f9tjng6sm9y7yzapy6awx4h9rrwenputzmnv5g6skess", + "agent://agent1qfl32tdwlyjatc7f9tjng6sm9y7yzapy6awx4h9rrwenputzmnv5g6skess", + "test-agent://name/agent1qfl32tdwlyjatc7f9tjng6sm9y7yzapy6awx4h9rrwenputzmnv5g6skess", ] for val in valid_addresses: - self.assertEqual( - extract_agent_address(val) - == "agent1qfl32tdwlyjatc7f9tjng6sm9y7yzapy6awx4h9rrwenputzmnv5g6skess", - True, - ) + prefix, address = split_destination(val) + self.assertEqual(is_valid_address(address),True,) + self.assertEqual(is_valid_prefix(prefix),True,) def test_extract_invalid_address(self): invalid_addresses = [ - "other1qfl32tdwlyjatc7f9tjng6sm9y7yzapy6awx4h9rrwenputzmnv5g6skess", - "some-prefix:agent1qfl32tdwlyjatc7f9tjng6sm9y7yzapy6awx4h9rrwenputzmnv5g6skess", - "other-prefix://agent1qfl32tdwlyjatc7f9tjng6sm9y7yzapy6awx4h9rrwenputzmnv5g6skes", - "some-prefix://agent1qfl32tdwlyjatc7f9tjng6sm9y7yzapy6awx4h9rrwenputzmnv5g6skess/name", - "some-prefix::agent1qfl32tdwlyjatc7f9tjng6sm9y7yzapy6awx4h9rrwenputzmnv5g6skess", + "p://other1qfl32tdwlyjatc7f9tjng6sm9y7yzapy6awx4h9rrwenputzmnv5g6skess", + "prefix://myagent1qfl32tdwlyjatc7f9tjng6sm9y7yzapy6awx4h9rrwenputzmnv5g6skes", + "other-prefix://address1qfl32tdwlyjatc7f9tjng6sm9y7yzapy6awx4h9rrwenputzmnv5g6skes", + "some-prefix://name/alice1qfl32tdwlyjatc7f9tjng6sm9y7yzapy6awx4h9rrwenputzmnv5g6skess/name", + "some-prefix://bobqfl32tdwlyjatc7f9tjng6sm9y7yzapy6awx4h9rrwenputzmnv5g6skess", ] for val in invalid_addresses: - self.assertEqual(extract_agent_address(val) is None, True) + prefix, address = split_destination(val) + self.assertEqual(is_valid_address(address), False) + self.assertEqual(is_valid_prefix(prefix), False) if __name__ == "__main__":