From 8258480bc10fdd310b3965e38563880bfc866cba Mon Sep 17 00:00:00 2001 From: Alejandro-Morales Date: Tue, 23 Jan 2024 06:43:52 -0600 Subject: [PATCH 01/15] feat: updates to match aname contract-V2 --- python/src/uagents/config.py | 2 +- python/src/uagents/network.py | 38 ++++++++++++++++++++++++----------- 2 files changed, 27 insertions(+), 13 deletions(-) diff --git a/python/src/uagents/config.py b/python/src/uagents/config.py index 983d2780..d07bdf34 100644 --- a/python/src/uagents/config.py +++ b/python/src/uagents/config.py @@ -25,7 +25,7 @@ "fetch1479lwv5vy8skute5cycuz727e55spkhxut0valrcm38x9caa2x8q99ef0q" ) TESTNET_CONTRACT_NAME_SERVICE = ( - "fetch1mxz8kn3l5ksaftx8a9pj9a6prpzk2uhxnqdkwuqvuh37tw80xu6qges77l" + "fetch1kewgfwxwtuxcnppr547wj6sd0e5fkckyp48dazsh89hll59epgpspmh0tn" ) REGISTRATION_FEE = 500000000000000000 REGISTRATION_DENOM = "atestfet" diff --git a/python/src/uagents/network.py b/python/src/uagents/network.py index 17fb28ed..68232341 100644 --- a/python/src/uagents/network.py +++ b/python/src/uagents/network.py @@ -345,7 +345,7 @@ def is_domain_public(self, domain: str): Returns: bool: True if the domain is public, False otherwise. """ - res = self.query({"domain_record": {"domain": f".{domain}"}}) + res = self.query({"domain_record": {"domain": f"VAL.{domain}"}}) return res["is_public"] def get_registration_tx( @@ -370,24 +370,38 @@ def get_registration_tx( Optional[Transaction]: The registration transaction, or None if the name is not available or not owned by the wallet address. """ - if not self.is_name_available(name, domain) and not self.is_owner( - name, domain, wallet_address - ): + transaction = Transaction() + + contract = ( + TESTNET_CONTRACT_NAME_SERVICE if test else MAINNET_CONTRACT_NAME_SERVICE + ) + + if self.is_name_available(name, domain): + price_per_second = self.query({"contract_state": {}})["price_per_second"] + amount = int(price_per_second["amount"]) * 86400 + denom = price_per_second["denom"] + + registration_msg = {"register": {"domain": f"{name}.{domain}"}} + + transaction.add_message( + create_cosmwasm_execute_msg( + wallet_address, contract, registration_msg, funds=f"{amount}{denom}" + ) + ) + elif not self.is_owner(name, domain, wallet_address): return None - registration_msg = { - "register": { + agent_record = {"address": agent_address, "weight": 1} + + record_msg = { + "update_record": { "domain": f"{name}.{domain}", - "agent_address": agent_address, + "agent_records": [agent_record], } } - contract = ( - TESTNET_CONTRACT_NAME_SERVICE if test else MAINNET_CONTRACT_NAME_SERVICE - ) - transaction = Transaction() transaction.add_message( - create_cosmwasm_execute_msg(wallet_address, contract, registration_msg) + create_cosmwasm_execute_msg(wallet_address, contract, record_msg) ) return transaction From 614c7cb9eac0994eab8749a5576a171e3aa1cb44 Mon Sep 17 00:00:00 2001 From: Alejandro-Morales Date: Wed, 24 Jan 2024 08:07:28 -0600 Subject: [PATCH 02/15] feat: added public domain query --- python/src/uagents/network.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/python/src/uagents/network.py b/python/src/uagents/network.py index 68232341..edce7390 100644 --- a/python/src/uagents/network.py +++ b/python/src/uagents/network.py @@ -345,8 +345,10 @@ def is_domain_public(self, domain: str): Returns: bool: True if the domain is public, False otherwise. """ - res = self.query({"domain_record": {"domain": f"VAL.{domain}"}}) - return res["is_public"] + res = self.query({"query_domain_flags": {"domain": domain}}).get("domain_flags") + if res: + return res["web3_flags"]["is_public"] + return False def get_registration_tx( self, From 62e9656d9dee4650bbd6084ea405d1deff5fa8d9 Mon Sep 17 00:00:00 2001 From: Alejandro-Morales Date: Wed, 24 Jan 2024 11:10:16 -0600 Subject: [PATCH 03/15] feat: public domain query and use of example.agent --- python/examples/13-agent-name-service/agent1.py | 2 +- python/examples/13-agent-name-service/agent2.py | 3 ++- python/src/uagents/network.py | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/python/examples/13-agent-name-service/agent1.py b/python/examples/13-agent-name-service/agent1.py index 126249ad..484263b3 100644 --- a/python/examples/13-agent-name-service/agent1.py +++ b/python/examples/13-agent-name-service/agent1.py @@ -22,7 +22,7 @@ class Message(Model): my_wallet = LocalWallet.from_unsafe_seed("registration test wallet") name_service_contract = get_name_service_contract(test=True) faucet = get_faucet() -DOMAIN = "agent" +DOMAIN = "example.agent" faucet.get_wealth(my_wallet.address()) diff --git a/python/examples/13-agent-name-service/agent2.py b/python/examples/13-agent-name-service/agent2.py index 27bb10ca..c3fb77b7 100644 --- a/python/examples/13-agent-name-service/agent2.py +++ b/python/examples/13-agent-name-service/agent2.py @@ -12,10 +12,11 @@ class Message(Model): endpoint=["http://localhost:8000/submit"], ) +DOMAIN = "example.agent" @alice.on_interval(period=5) async def alice_interval_handler(ctx: Context): - bob_name = "bob-0.agent" + bob_name = "bob-0" + "." + DOMAIN ctx.logger.info(f"Sending message to {bob_name}...") await ctx.send(bob_name, Message(message="Hello there bob.")) diff --git a/python/src/uagents/network.py b/python/src/uagents/network.py index edce7390..de18e6bf 100644 --- a/python/src/uagents/network.py +++ b/python/src/uagents/network.py @@ -345,7 +345,7 @@ def is_domain_public(self, domain: str): Returns: bool: True if the domain is public, False otherwise. """ - res = self.query({"query_domain_flags": {"domain": domain}}).get("domain_flags") + res = self.query({"query_domain_flags": {"domain": domain.split(".")[-1]}}).get("domain_flags") if res: return res["web3_flags"]["is_public"] return False From 4eb1821d885879c27cb516c72828834be14e6ab2 Mon Sep 17 00:00:00 2001 From: Alejandro-Morales Date: Wed, 24 Jan 2024 11:21:58 -0600 Subject: [PATCH 04/15] fix: run black --- python/examples/13-agent-name-service/agent2.py | 1 + python/src/uagents/network.py | 4 +++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/python/examples/13-agent-name-service/agent2.py b/python/examples/13-agent-name-service/agent2.py index c3fb77b7..8adc6507 100644 --- a/python/examples/13-agent-name-service/agent2.py +++ b/python/examples/13-agent-name-service/agent2.py @@ -14,6 +14,7 @@ class Message(Model): DOMAIN = "example.agent" + @alice.on_interval(period=5) async def alice_interval_handler(ctx: Context): bob_name = "bob-0" + "." + DOMAIN diff --git a/python/src/uagents/network.py b/python/src/uagents/network.py index de18e6bf..060e0dcb 100644 --- a/python/src/uagents/network.py +++ b/python/src/uagents/network.py @@ -345,7 +345,9 @@ def is_domain_public(self, domain: str): Returns: bool: True if the domain is public, False otherwise. """ - res = self.query({"query_domain_flags": {"domain": domain.split(".")[-1]}}).get("domain_flags") + res = self.query({"query_domain_flags": {"domain": domain.split(".")[-1]}}).get( + "domain_flags" + ) if res: return res["web3_flags"]["is_public"] return False From dbf8d56e3def4779801098f028bc98f5ede51c3d Mon Sep 17 00:00:00 2001 From: Alejandro-Morales Date: Mon, 5 Feb 2024 10:08:26 -0600 Subject: [PATCH 05/15] feat: resolve agent address using weighted_random_sample --- python/src/uagents/resolver.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/python/src/uagents/resolver.py b/python/src/uagents/resolver.py index f51dc2af..0ca10e87 100644 --- a/python/src/uagents/resolver.py +++ b/python/src/uagents/resolver.py @@ -129,9 +129,16 @@ def get_agent_address(name: str, test: bool) -> str: query_msg = {"domain_record": {"domain": f"{name}"}} result = get_name_service_contract(test).query(query_msg) if result["record"] is not None: - registered_address = result["record"]["records"][0]["agent_address"]["records"] - if len(registered_address) > 0: - return registered_address[0]["address"] + registered_records = result["record"]["records"][0]["agent_address"]["records"] + print(registered_records) + if len(registered_records) > 0: + addresses = [val.get("address") for val in registered_records] + weights = [val.get("weight") for val in registered_records] + selected_address_list = weighted_random_sample(addresses, weights=weights) + selected_address = selected_address_list[0] if selected_address_list else None + print("SELECTED:") + print(selected_address) + return selected_address return None From 5961b6bd0dc3e491ed16aebc5f0c2fb0dfa8fa92 Mon Sep 17 00:00:00 2001 From: Alejandro-Morales Date: Mon, 5 Feb 2024 10:14:53 -0600 Subject: [PATCH 06/15] fix: delete prints --- python/src/uagents/resolver.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/python/src/uagents/resolver.py b/python/src/uagents/resolver.py index 0ca10e87..96fc2048 100644 --- a/python/src/uagents/resolver.py +++ b/python/src/uagents/resolver.py @@ -130,13 +130,11 @@ def get_agent_address(name: str, test: bool) -> str: result = get_name_service_contract(test).query(query_msg) if result["record"] is not None: registered_records = result["record"]["records"][0]["agent_address"]["records"] - print(registered_records) if len(registered_records) > 0: addresses = [val.get("address") for val in registered_records] weights = [val.get("weight") for val in registered_records] selected_address_list = weighted_random_sample(addresses, weights=weights) selected_address = selected_address_list[0] if selected_address_list else None - print("SELECTED:") print(selected_address) return selected_address return None From bdb26ed97fe3a73b377a9a0af47a7712f5dc804b Mon Sep 17 00:00:00 2001 From: Alejandro-Morales Date: Mon, 5 Feb 2024 10:16:40 -0600 Subject: [PATCH 07/15] fix: run black --- python/src/uagents/resolver.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/python/src/uagents/resolver.py b/python/src/uagents/resolver.py index 96fc2048..d9a2e90b 100644 --- a/python/src/uagents/resolver.py +++ b/python/src/uagents/resolver.py @@ -134,7 +134,9 @@ def get_agent_address(name: str, test: bool) -> str: addresses = [val.get("address") for val in registered_records] weights = [val.get("weight") for val in registered_records] selected_address_list = weighted_random_sample(addresses, weights=weights) - selected_address = selected_address_list[0] if selected_address_list else None + selected_address = ( + selected_address_list[0] if selected_address_list else None + ) print(selected_address) return selected_address return None From a32626515f9263ee0c10b38f99ec0c5ebe7bc19c Mon Sep 17 00:00:00 2001 From: Alejandro-Morales Date: Mon, 5 Feb 2024 10:17:50 -0600 Subject: [PATCH 08/15] fix: delete print --- python/src/uagents/resolver.py | 1 - 1 file changed, 1 deletion(-) diff --git a/python/src/uagents/resolver.py b/python/src/uagents/resolver.py index d9a2e90b..e721a4a0 100644 --- a/python/src/uagents/resolver.py +++ b/python/src/uagents/resolver.py @@ -137,7 +137,6 @@ def get_agent_address(name: str, test: bool) -> str: selected_address = ( selected_address_list[0] if selected_address_list else None ) - print(selected_address) return selected_address return None From e1fe34d8fd33cde45b798c74ac62ef606e54eec9 Mon Sep 17 00:00:00 2001 From: Alejandro-Morales Date: Mon, 5 Feb 2024 11:55:46 -0600 Subject: [PATCH 09/15] feat: allow multiple registration addresses --- python/src/uagents/network.py | 53 ++++++++++++++++++++++++++--------- 1 file changed, 39 insertions(+), 14 deletions(-) diff --git a/python/src/uagents/network.py b/python/src/uagents/network.py index 060e0dcb..05c0f1f2 100644 --- a/python/src/uagents/network.py +++ b/python/src/uagents/network.py @@ -2,7 +2,7 @@ import asyncio from datetime import datetime, timedelta -from typing import Any, Optional, Dict, List +from typing import Any, Optional, Dict, List, Union from cosmpy.aerial.contract import LedgerContract from cosmpy.aerial.client import ( @@ -80,6 +80,29 @@ def add_testnet_funds(wallet_address: str): ) +def parse_record_config( + record: Optional[Union[str, List[str], Dict[str, dict]]] +) -> List[Dict[str, Any]]: + """ + Parse the user-provided record configuration. + + Returns: + List[Dict[str, Any]]: The parsed record configuration in correct format. + """ + if isinstance(record, dict): + records = [ + {"address": val[0], "weight": val[1].get("weight") or 1} + for val in record.items() + ] + elif isinstance(record, list): + records = [{"address": val, "weight": 1} for val in record] + elif isinstance(record, str): + records = [{"address": record, "weight": 1}] + else: + records = None + return records + + async def wait_for_tx_to_complete( tx_hash: str, ledger: LedgerClient, @@ -356,7 +379,7 @@ def get_registration_tx( self, name: str, wallet_address: str, - agent_address: str, + agent_records: List[Dict[str, Any]], domain: str, test: bool, ): @@ -395,12 +418,10 @@ def get_registration_tx( elif not self.is_owner(name, domain, wallet_address): return None - agent_record = {"address": agent_address, "weight": 1} - record_msg = { "update_record": { "domain": f"{name}.{domain}", - "agent_records": [agent_record], + "agent_records": agent_records, } } @@ -414,7 +435,7 @@ async def register( self, ledger: LedgerClient, wallet: LocalWallet, - agent_address: str, + agent_records: Optional[Union[str, List[str], Dict[str, dict]]], name: str, domain: str, ): @@ -431,13 +452,17 @@ async def register( logger.info("Registering name...") chain_id = ledger.query_chain_id() - if not get_almanac_contract(chain_id == "dorado-1").is_registered( - agent_address - ): - logger.warning( - f"Agent {name} needs to be registered in almanac contract to register its name" - ) - return + records = parse_record_config(agent_records) + agent_addresses = [val.get("address") for val in records] + + for agent_address in agent_addresses: + if not get_almanac_contract(chain_id == "dorado-1").is_registered( + agent_address + ): + logger.warning( + f"Address {agent_address} needs to be registered in almanac contract to be registered in a domain" + ) + return if not self.is_domain_public(domain): logger.warning( @@ -448,7 +473,7 @@ async def register( transaction = self.get_registration_tx( name, str(wallet.address()), - agent_address, + records, domain, chain_id == "dorado-1", ) From c2d716e008fadf00fa4fb792a8792213cf91d318 Mon Sep 17 00:00:00 2001 From: Alejandro-Morales Date: Tue, 6 Feb 2024 05:46:08 -0600 Subject: [PATCH 10/15] fix: line too long --- python/src/uagents/network.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/python/src/uagents/network.py b/python/src/uagents/network.py index 05c0f1f2..02494661 100644 --- a/python/src/uagents/network.py +++ b/python/src/uagents/network.py @@ -460,7 +460,8 @@ async def register( agent_address ): logger.warning( - f"Address {agent_address} needs to be registered in almanac contract to be registered in a domain" + f"Address {agent_address} needs to be registered in almanac contract " + f"to be registered in a domain" ) return From f8316aea158bc17f4c19501d918091ea185c8f53 Mon Sep 17 00:00:00 2001 From: Alejandro-Morales Date: Tue, 6 Feb 2024 06:50:12 -0600 Subject: [PATCH 11/15] feat: added overwrite option --- python/src/uagents/network.py | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/python/src/uagents/network.py b/python/src/uagents/network.py index 02494661..62392393 100644 --- a/python/src/uagents/network.py +++ b/python/src/uagents/network.py @@ -375,6 +375,24 @@ def is_domain_public(self, domain: str): return res["web3_flags"]["is_public"] return False + def get_previous_records(self, name: str, domain: str): + """ + Retrieve the previous records for a given name within a specified domain. + + Args: + name (str): The name whose records are to be retrieved. + domain (str): The domain within which the name is registered. + + Returns: + A list of dictionaries, where each dictionary contains + details of a record associated with the given name. + """ + query_msg = {"domain_record": {"domain": f"{name}.{domain}"}} + result = self.query(query_msg) + if result["record"] is not None: + return result["record"]["records"][0]["agent_address"]["records"] + return [] + def get_registration_tx( self, name: str, @@ -438,6 +456,7 @@ async def register( agent_records: Optional[Union[str, List[str], Dict[str, dict]]], name: str, domain: str, + overwrite: bool = True, ): """ Register a name within a domain using the NameService contract. @@ -448,6 +467,8 @@ async def register( agent_address (str): The address of the agent. name (str): The name to be registered. domain (str): The domain in which the name is registered. + overwrite (bool, optional): Whether to overwrite the existing name. + Defaults to True. """ logger.info("Registering name...") chain_id = ledger.query_chain_id() @@ -471,6 +492,15 @@ async def register( ) return + if not overwrite: + previous_records = self.get_previous_records(name, domain) + records = list( + { + f"{rec['address']}_{rec['weight']}": rec + for rec in previous_records + records + }.values() + ) + transaction = self.get_registration_tx( name, str(wallet.address()), From e7c359e58582ea784cfc0100553447b96a333c27 Mon Sep 17 00:00:00 2001 From: Alejandro-Morales Date: Tue, 6 Feb 2024 07:18:34 -0600 Subject: [PATCH 12/15] small fix on docstring --- python/src/uagents/network.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/python/src/uagents/network.py b/python/src/uagents/network.py index 62392393..20d7ed2d 100644 --- a/python/src/uagents/network.py +++ b/python/src/uagents/network.py @@ -467,8 +467,9 @@ async def register( agent_address (str): The address of the agent. name (str): The name to be registered. domain (str): The domain in which the name is registered. - overwrite (bool, optional): Whether to overwrite the existing name. - Defaults to True. + overwrite (bool, optional): Specifies whether to overwrite any existing + addresses registered to the domain. If False, the address will be + appended to the previous records. Defaults to True. """ logger.info("Registering name...") chain_id = ledger.query_chain_id() From d4f4b242af27b480fb0578a79c099f42cb0d7d41 Mon Sep 17 00:00:00 2001 From: Alejandro-Morales Date: Thu, 8 Feb 2024 05:46:59 -0600 Subject: [PATCH 13/15] small updates on james comments --- python/src/uagents/config.py | 2 +- python/src/uagents/network.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/python/src/uagents/config.py b/python/src/uagents/config.py index d07bdf34..983d2780 100644 --- a/python/src/uagents/config.py +++ b/python/src/uagents/config.py @@ -25,7 +25,7 @@ "fetch1479lwv5vy8skute5cycuz727e55spkhxut0valrcm38x9caa2x8q99ef0q" ) TESTNET_CONTRACT_NAME_SERVICE = ( - "fetch1kewgfwxwtuxcnppr547wj6sd0e5fkckyp48dazsh89hll59epgpspmh0tn" + "fetch1mxz8kn3l5ksaftx8a9pj9a6prpzk2uhxnqdkwuqvuh37tw80xu6qges77l" ) REGISTRATION_FEE = 500000000000000000 REGISTRATION_DENOM = "atestfet" diff --git a/python/src/uagents/network.py b/python/src/uagents/network.py index 20d7ed2d..8509db5c 100644 --- a/python/src/uagents/network.py +++ b/python/src/uagents/network.py @@ -483,7 +483,7 @@ async def register( ): logger.warning( f"Address {agent_address} needs to be registered in almanac contract " - f"to be registered in a domain" + + "to be registered in a domain" ) return From bd2e904a18d59c335ececefe0dfe3c0ecee75df6 Mon Sep 17 00:00:00 2001 From: Alejandro-Morales Date: Thu, 8 Feb 2024 05:57:33 -0600 Subject: [PATCH 14/15] small fix --- python/src/uagents/network.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/python/src/uagents/network.py b/python/src/uagents/network.py index 8509db5c..bfb8f82d 100644 --- a/python/src/uagents/network.py +++ b/python/src/uagents/network.py @@ -482,8 +482,8 @@ async def register( agent_address ): logger.warning( - f"Address {agent_address} needs to be registered in almanac contract " - + "to be registered in a domain" + "Address %s needs to be registered in almanac contract " + "to be registered in a domain", agent_address ) return From ff7c5db39c0ed1a86f01f001c86340dd45e48f2a Mon Sep 17 00:00:00 2001 From: Alejandro-Morales Date: Thu, 8 Feb 2024 05:59:42 -0600 Subject: [PATCH 15/15] small fix --- python/src/uagents/network.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/python/src/uagents/network.py b/python/src/uagents/network.py index bfb8f82d..e0cbbdd6 100644 --- a/python/src/uagents/network.py +++ b/python/src/uagents/network.py @@ -483,7 +483,8 @@ async def register( ): logger.warning( "Address %s needs to be registered in almanac contract " - "to be registered in a domain", agent_address + "to be registered in a domain", + agent_address, ) return