agent0-sdk 1.2.0__py3-none-any.whl → 1.4.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
agent0_sdk/__init__.py CHANGED
@@ -24,16 +24,21 @@ from .core.models import (
24
24
  try:
25
25
  from .core.sdk import SDK
26
26
  from .core.agent import Agent
27
+ from .core.transaction_handle import TransactionHandle, TransactionMined
27
28
  _sdk_available = True
28
29
  except ImportError:
29
30
  SDK = None
30
31
  Agent = None
32
+ TransactionHandle = None
33
+ TransactionMined = None
31
34
  _sdk_available = False
32
35
 
33
- __version__ = "1.2.0"
36
+ __version__ = "1.4.1"
34
37
  __all__ = [
35
38
  "SDK",
36
39
  "Agent",
40
+ "TransactionHandle",
41
+ "TransactionMined",
37
42
  "AgentId",
38
43
  "ChainId",
39
44
  "Address",
agent0_sdk/core/agent.py CHANGED
@@ -23,6 +23,8 @@ if TYPE_CHECKING:
23
23
 
24
24
  logger = logging.getLogger(__name__)
25
25
 
26
+ from .transaction_handle import TransactionHandle
27
+
26
28
 
27
29
  class Agent:
28
30
  """Represents an individual agent with its registration data."""
@@ -79,6 +81,27 @@ class Agent:
79
81
  """Get agent wallet address (read-only)."""
80
82
  return self.registration_file.walletAddress
81
83
 
84
+ def getWallet(self) -> Optional[Address]:
85
+ """Read the verified agent wallet from the Identity Registry (on-chain).
86
+
87
+ This calls the contract function `getAgentWallet(agentId)` and returns:
88
+ - the wallet address if set and non-zero
89
+ - None if unset/cleared (zero address)
90
+ """
91
+ if not self.agentId:
92
+ raise ValueError("Agent must be registered before reading wallet from chain.")
93
+
94
+ agent_id_int = int(self.agentId.split(":")[-1]) if ":" in self.agentId else int(self.agentId)
95
+ wallet = self.sdk.web3_client.call_contract(self.sdk.identity_registry, "getAgentWallet", agent_id_int)
96
+
97
+ if not wallet or not isinstance(wallet, str):
98
+ return None
99
+
100
+ if wallet.lower() == "0x0000000000000000000000000000000000000000":
101
+ return None
102
+
103
+ return wallet
104
+
82
105
  @property
83
106
  def walletChainId(self) -> Optional[int]:
84
107
  """Get agent wallet chain ID (read-only)."""
@@ -179,11 +202,11 @@ class Agent:
179
202
  """Collect all metadata entries for registration.
180
203
 
181
204
  Note: agentWallet is now a reserved metadata key and cannot be set via setMetadata().
182
- It must be set separately using setAgentWallet() with EIP-712 signature verification.
205
+ It must be set separately using setWallet() with signature verification.
183
206
  """
184
207
  metadata_entries = []
185
208
 
186
- # Note: agentWallet is no longer set via metadata - it's now reserved and managed via setAgentWallet()
209
+ # Note: agentWallet is no longer set via metadata - it's now reserved and managed via setWallet()
187
210
 
188
211
  # Add ENS name metadata
189
212
  if self.ensEndpoint:
@@ -491,7 +514,7 @@ class Agent:
491
514
  self.registration_file.updatedAt = int(time.time())
492
515
  return self
493
516
 
494
- def setAgentWallet(
517
+ def setWallet(
495
518
  self,
496
519
  new_wallet: Address,
497
520
  chainId: Optional[int] = None,
@@ -499,11 +522,10 @@ class Agent:
499
522
  new_wallet_signer: Optional[Union[str, Any]] = None,
500
523
  deadline: Optional[int] = None,
501
524
  signature: Optional[bytes] = None,
502
- ) -> 'Agent':
503
- """Set agent wallet address on-chain (ERC-8004 agentWallet).
525
+ ) -> Optional[TransactionHandle["Agent"]]:
526
+ """Set agent wallet address on-chain (verified agentWallet).
504
527
 
505
- This method is **on-chain only**. The `agentWallet` is a verified attribute and must be set via
506
- the IdentityRegistry `setAgentWallet` function.
528
+ This method is **on-chain only**. The `agentWallet` is a verified attribute.
507
529
 
508
530
  EOAs: provide `new_wallet_signer` (private key string or eth-account account) OR ensure the SDK
509
531
  signer address matches `new_wallet` so the SDK can auto-sign.\n
@@ -517,7 +539,7 @@ class Agent:
517
539
  deadline: Signature deadline timestamp. Defaults to now+60s (must be <= now+5min per contract).
518
540
  signature: Raw signature bytes (intended for ERC-1271 / external signing only)
519
541
  """
520
- # Breaking/clean: this API is only meaningful for already-registered agents.
542
+ # This API is only meaningful for already-registered agents.
521
543
  if not self.agentId:
522
544
  raise ValueError(
523
545
  "Cannot set agent wallet before the agent is registered on-chain. "
@@ -555,18 +577,14 @@ class Agent:
555
577
 
556
578
  # Check if wallet is already set to this address (skip if same)
557
579
  try:
558
- current_wallet = self.sdk.web3_client.call_contract(
559
- self.sdk.identity_registry,
560
- "getAgentWallet",
561
- agent_id_int
562
- )
580
+ current_wallet = self.getWallet()
563
581
  if current_wallet and current_wallet.lower() == addr.lower():
564
582
  logger.debug(f"Agent wallet is already set to {addr}, skipping on-chain update")
565
583
  # Still update local registration file
566
584
  self.registration_file.walletAddress = addr
567
585
  self.registration_file.walletChainId = chainId
568
586
  self.registration_file.updatedAt = int(time.time())
569
- return self
587
+ return None
570
588
  except Exception as e:
571
589
  logger.debug(f"Could not check current agent wallet: {e}, proceeding with update")
572
590
 
@@ -625,7 +643,7 @@ class Agent:
625
643
  if recovered.lower() != addr.lower():
626
644
  raise ValueError(f"Signature verification failed: recovered {recovered} but expected {addr}")
627
645
 
628
- # Call setAgentWallet on the contract
646
+ # Submit on-chain tx (tx sender is SDK signer: owner/operator)
629
647
  try:
630
648
  txHash = self.sdk.web3_client.transact_contract(
631
649
  self.sdk.identity_registry,
@@ -635,21 +653,61 @@ class Agent:
635
653
  deadline,
636
654
  signature
637
655
  )
638
-
639
- # Wait for transaction
640
- receipt = self.sdk.web3_client.wait_for_transaction(txHash)
641
- logger.debug(f"Agent wallet set on-chain: {txHash}")
642
-
643
656
  except Exception as e:
644
657
  raise ValueError(f"Failed to set agent wallet on-chain: {e}")
645
-
646
- # Update local registration file
647
- self.registration_file.walletAddress = addr
648
- self.registration_file.walletChainId = chainId
649
- self.registration_file.updatedAt = int(time.time())
650
- self._last_registered_wallet = addr
651
-
652
- return self
658
+
659
+ def _apply(_receipt: Dict[str, Any]) -> "Agent":
660
+ self.registration_file.walletAddress = addr
661
+ self.registration_file.walletChainId = chainId
662
+ self.registration_file.updatedAt = int(time.time())
663
+ self._last_registered_wallet = addr
664
+ return self
665
+
666
+ return TransactionHandle(web3_client=self.sdk.web3_client, tx_hash=txHash, compute_result=_apply)
667
+
668
+ def unsetWallet(self) -> Optional[TransactionHandle["Agent"]]:
669
+ """Unset agent wallet address on-chain (verified agentWallet).
670
+
671
+ This method is **on-chain only** and requires the agent to be registered.
672
+ It unsets the on-chain value and clears the local
673
+ `walletAddress` / `walletChainId` fields.
674
+ """
675
+ if not self.agentId:
676
+ raise ValueError(
677
+ "Cannot unset agent wallet before the agent is registered on-chain. "
678
+ "Call agent.register(...) / agent.registerIPFS() first to obtain agentId."
679
+ )
680
+
681
+ # Parse agent ID (tokenId is always the last segment)
682
+ agent_id_int = int(self.agentId.split(":")[-1]) if ":" in self.agentId else int(self.agentId)
683
+
684
+ # Optional short-circuit if already unset (best-effort).
685
+ try:
686
+ current_wallet = self.getWallet()
687
+ if current_wallet is None:
688
+ self.registration_file.walletAddress = None
689
+ self.registration_file.walletChainId = None
690
+ self.registration_file.updatedAt = int(time.time())
691
+ return None
692
+ except Exception:
693
+ pass
694
+
695
+ try:
696
+ txHash = self.sdk.web3_client.transact_contract(
697
+ self.sdk.identity_registry,
698
+ "unsetAgentWallet",
699
+ agent_id_int
700
+ )
701
+ except Exception as e:
702
+ raise ValueError(f"Failed to unset agent wallet on-chain: {e}")
703
+
704
+ def _apply(_receipt: Dict[str, Any]) -> "Agent":
705
+ self.registration_file.walletAddress = None
706
+ self.registration_file.walletChainId = None
707
+ self.registration_file.updatedAt = int(time.time())
708
+ return self
709
+
710
+ return TransactionHandle(web3_client=self.sdk.web3_client, tx_hash=txHash, compute_result=_apply)
653
711
 
654
712
  def setENS(self, name: str, version: str = "1.0") -> 'Agent':
655
713
  """Set ENS name both on-chain and in registration file."""
@@ -716,121 +774,112 @@ class Agent:
716
774
  return self.registration_file
717
775
 
718
776
  # Registration (on-chain)
719
- def registerIPFS(self) -> RegistrationFile:
720
- """Register agent on-chain with IPFS flow (mint -> pin -> set URI) or update existing registration."""
777
+ def registerIPFS(self) -> TransactionHandle[RegistrationFile]:
778
+ """Register agent on-chain with IPFS flow (mint -> pin -> set URI) or update existing registration.
779
+
780
+ Submitted-by-default: returns a TransactionHandle immediately after the first tx is submitted.
781
+ """
721
782
  # Validate basic info
722
783
  if not self.registration_file.name or not self.registration_file.description:
723
784
  raise ValueError("Agent must have name and description before registration")
724
785
 
725
786
  if self.registration_file.agentId:
726
- # Agent already registered - update registration file and redeploy
727
- logger.debug("Agent already registered, updating registration file")
728
-
729
- # Upload updated registration file to IPFS
787
+ # Agent already registered: upload -> submit setAgentURI; do metadata best-effort after confirmation.
730
788
  ipfsCid = self.sdk.ipfs_client.addRegistrationFile(
731
789
  self.registration_file,
732
790
  chainId=self.sdk.chain_id(),
733
- identityRegistryAddress=self.sdk.identity_registry.address
791
+ identityRegistryAddress=self.sdk.identity_registry.address,
734
792
  )
735
-
736
- # Update metadata on-chain if agent is already registered
737
- # Only send transactions for dirty (changed) metadata to save gas
738
- if self._dirty_metadata:
739
- metadata_entries = self._collectMetadataForRegistration()
740
- agentId = int(self.agentId.split(":")[-1])
741
- for entry in metadata_entries:
742
- # Only send transaction if this metadata key is dirty
743
- if entry["key"] in self._dirty_metadata:
744
- txHash = self.sdk.web3_client.transact_contract(
745
- self.sdk.identity_registry,
746
- "setMetadata",
747
- agentId,
748
- entry["key"],
749
- entry["value"]
750
- )
751
- try:
752
- self.sdk.web3_client.wait_for_transaction(txHash, timeout=30)
753
- except Exception as e:
754
- logger.warning(f"Transaction timeout for {entry['key']}: {e}")
755
- logger.debug(f"Updated metadata on-chain: {entry['key']}")
756
- else:
757
- logger.debug("No metadata changes detected, skipping metadata updates")
758
-
759
- # Update agent URI on-chain
760
- agentId = int(self.agentId.split(":")[-1])
793
+
794
+ agentId_int = int(self.agentId.split(":")[-1])
761
795
  txHash = self.sdk.web3_client.transact_contract(
762
796
  self.sdk.identity_registry,
763
797
  "setAgentURI",
764
- agentId,
765
- f"ipfs://{ipfsCid}"
798
+ agentId_int,
799
+ f"ipfs://{ipfsCid}",
766
800
  )
767
- try:
768
- self.sdk.web3_client.wait_for_transaction(txHash, timeout=30)
769
- logger.debug(f"Updated agent URI on-chain: {txHash}")
770
- except Exception as e:
771
- logger.warning(f"URI update timeout (transaction sent: {txHash}): {e}")
772
-
773
- # Clear dirty flags after successful registration
774
- self._last_registered_wallet = self.walletAddress
775
- self._last_registered_ens = self.ensEndpoint
776
- self._dirty_metadata.clear()
777
-
778
- return self.registration_file
779
- else:
780
- # First time registration
781
- logger.debug("Registering agent for the first time")
782
-
783
- # Step 1: Register on-chain without URI
784
- self._registerWithoutUri()
785
-
786
- # Step 2: Prepare registration file with agent ID (already set by _registerWithoutUri)
787
- # No need to modify agentId as it's already set correctly
788
-
789
- # Step 3: Upload to IPFS
801
+
802
+ def _apply(_receipt: Dict[str, Any]) -> RegistrationFile:
803
+ # Best-effort metadata updates (may involve additional txs)
804
+ if self._dirty_metadata:
805
+ metadata_entries = self._collectMetadataForRegistration()
806
+ for entry in metadata_entries:
807
+ if entry["key"] in self._dirty_metadata:
808
+ try:
809
+ h = self.sdk.web3_client.transact_contract(
810
+ self.sdk.identity_registry,
811
+ "setMetadata",
812
+ agentId_int,
813
+ entry["key"],
814
+ entry["value"],
815
+ )
816
+ self.sdk.web3_client.wait_for_transaction(h, timeout=30)
817
+ except Exception as e:
818
+ logger.warning(f"Metadata update failed or timed out for {entry['key']} (tx sent): {e}")
819
+
820
+ self.registration_file.agentURI = f"ipfs://{ipfsCid}"
821
+ self.registration_file.updatedAt = int(time.time())
822
+ self._last_registered_wallet = self.walletAddress
823
+ self._last_registered_ens = self.ensEndpoint
824
+ self._dirty_metadata.clear()
825
+ return self.registration_file
826
+
827
+ return TransactionHandle(web3_client=self.sdk.web3_client, tx_hash=txHash, compute_result=_apply)
828
+
829
+ # First time registration: tx1=register(no URI) -> wait -> upload -> tx2=setAgentURI -> wait
830
+ metadata_entries = self._collectMetadataForRegistration()
831
+ txHash = self.sdk.web3_client.transact_contract(
832
+ self.sdk.identity_registry,
833
+ "register",
834
+ "",
835
+ metadata_entries,
836
+ )
837
+
838
+ def _apply_first(receipt: Dict[str, Any]) -> RegistrationFile:
839
+ agentId_minted = self._extractAgentIdFromReceipt(receipt)
840
+ self.registration_file.agentId = f"{self.sdk.chain_id()}:{agentId_minted}"
841
+ self.registration_file.updatedAt = int(time.time())
842
+
790
843
  ipfsCid = self.sdk.ipfs_client.addRegistrationFile(
791
844
  self.registration_file,
792
845
  chainId=self.sdk.chain_id(),
793
- identityRegistryAddress=self.sdk.identity_registry.address
846
+ identityRegistryAddress=self.sdk.identity_registry.address,
794
847
  )
795
-
796
- # Step 4: Set agent URI on-chain
797
- agentId = int(self.agentId.split(":")[-1])
798
- txHash = self.sdk.web3_client.transact_contract(
848
+
849
+ txHash2 = self.sdk.web3_client.transact_contract(
799
850
  self.sdk.identity_registry,
800
851
  "setAgentURI",
801
- agentId,
802
- f"ipfs://{ipfsCid}"
852
+ agentId_minted,
853
+ f"ipfs://{ipfsCid}",
803
854
  )
804
- try:
805
- self.sdk.web3_client.wait_for_transaction(txHash, timeout=30)
806
- logger.debug(f"Set agent URI on-chain: {txHash}")
807
- except Exception as e:
808
- logger.warning(f"URI set timeout (transaction sent: {txHash}): {e}")
809
-
810
- # Clear dirty flags after successful registration
855
+ self.sdk.web3_client.wait_for_transaction(txHash2, timeout=30)
856
+
857
+ self.registration_file.agentURI = f"ipfs://{ipfsCid}"
858
+ self.registration_file.updatedAt = int(time.time())
811
859
  self._last_registered_wallet = self.walletAddress
812
860
  self._last_registered_ens = self.ensEndpoint
813
861
  self._dirty_metadata.clear()
814
-
815
862
  return self.registration_file
816
863
 
817
- def register(self, agentUri: str) -> RegistrationFile:
818
- """Register agent on-chain with direct URI or update existing registration."""
864
+ return TransactionHandle(web3_client=self.sdk.web3_client, tx_hash=txHash, compute_result=_apply_first)
865
+
866
+ def register(self, agentUri: str) -> TransactionHandle[RegistrationFile]:
867
+ """Register agent on-chain with direct URI (submitted-by-default)."""
819
868
  # Validate basic info
820
869
  if not self.registration_file.name or not self.registration_file.description:
821
870
  raise ValueError("Agent must have name and description before registration")
822
871
 
823
872
  if self.registration_file.agentId:
824
- # Agent already registered - update agent URI
825
- logger.debug("Agent already registered, updating agent URI")
826
- self.setAgentUri(agentUri)
827
- return self.registration_file
828
- else:
829
- # First time registration
830
- logger.debug("Registering agent for the first time")
831
- return self._registerWithUri(agentUri)
873
+ # Update URI on-chain for existing agent
874
+ updated = self.updateRegistration(agentURI=agentUri)
875
+ if isinstance(updated, TransactionHandle):
876
+ return updated
877
+ # Should not happen (agentURI was provided), but keep a safe fallback.
878
+ raise RuntimeError("Expected updateRegistration to return a TransactionHandle when agentURI is provided")
879
+
880
+ return self._registerWithUri(agentUri)
832
881
 
833
- def _registerWithoutUri(self, idem: Optional[IdemKey] = None) -> RegistrationFile:
882
+ def _registerWithoutUri(self, idem: Optional[IdemKey] = None) -> TransactionHandle[RegistrationFile]:
834
883
  """Register without URI (IPFS flow step 1) with metadata."""
835
884
  # Collect metadata for registration
836
885
  metadata_entries = self._collectMetadataForRegistration()
@@ -843,19 +892,15 @@ class Agent:
843
892
  metadata_entries
844
893
  )
845
894
 
846
- # Wait for transaction
847
- receipt = self.sdk.web3_client.wait_for_transaction(txHash)
848
-
849
- # Get agent ID from events
850
- agentId = self._extractAgentIdFromReceipt(receipt)
851
-
852
- # Update registration file
853
- self.registration_file.agentId = f"{self.sdk.chain_id()}:{agentId}"
854
- self.registration_file.updatedAt = int(time.time())
855
-
856
- return self.registration_file
895
+ def _apply(receipt: Dict[str, Any]) -> RegistrationFile:
896
+ agentId = self._extractAgentIdFromReceipt(receipt)
897
+ self.registration_file.agentId = f"{self.sdk.chain_id()}:{agentId}"
898
+ self.registration_file.updatedAt = int(time.time())
899
+ return self.registration_file
857
900
 
858
- def _registerWithUri(self, agentURI: URI, idem: Optional[IdemKey] = None) -> RegistrationFile:
901
+ return TransactionHandle(web3_client=self.sdk.web3_client, tx_hash=txHash, compute_result=_apply)
902
+
903
+ def _registerWithUri(self, agentURI: URI, idem: Optional[IdemKey] = None) -> TransactionHandle[RegistrationFile]:
859
904
  """Register with direct URI and metadata."""
860
905
  # Update registration file
861
906
  self.registration_file.agentURI = agentURI
@@ -872,17 +917,13 @@ class Agent:
872
917
  metadata_entries
873
918
  )
874
919
 
875
- # Wait for transaction
876
- receipt = self.sdk.web3_client.wait_for_transaction(txHash)
877
-
878
- # Get agent ID from events
879
- agentId = self._extractAgentIdFromReceipt(receipt)
880
-
881
- # Update registration file
882
- self.registration_file.agentId = f"{self.sdk.chain_id()}:{agentId}"
883
- self.registration_file.updatedAt = int(time.time())
884
-
885
- return self.registration_file
920
+ def _apply(receipt: Dict[str, Any]) -> RegistrationFile:
921
+ agentId = self._extractAgentIdFromReceipt(receipt)
922
+ self.registration_file.agentId = f"{self.sdk.chain_id()}:{agentId}"
923
+ self.registration_file.updatedAt = int(time.time())
924
+ return self.registration_file
925
+
926
+ return TransactionHandle(web3_client=self.sdk.web3_client, tx_hash=txHash, compute_result=_apply)
886
927
 
887
928
  def _extractAgentIdFromReceipt(self, receipt: Dict[str, Any]) -> int:
888
929
  """Extract agent ID from transaction receipt."""
@@ -927,7 +968,7 @@ class Agent:
927
968
  self,
928
969
  agentURI: Optional[URI] = None,
929
970
  idem: Optional[IdemKey] = None,
930
- ) -> RegistrationFile:
971
+ ) -> Union[RegistrationFile, TransactionHandle[RegistrationFile]]:
931
972
  """Update registration after edits."""
932
973
  if not self.registration_file.agentId:
933
974
  raise ValueError("Agent must be registered before updating")
@@ -941,15 +982,19 @@ class Agent:
941
982
 
942
983
  # Update on-chain URI if needed
943
984
  if agentURI is not None:
944
- agentId = int(self.registration_file.agentId.split(":")[-1])
985
+ agentId_int = int(self.registration_file.agentId.split(":")[-1])
945
986
  txHash = self.sdk.web3_client.transact_contract(
946
987
  self.sdk.identity_registry,
947
988
  "setAgentURI",
948
- agentId,
949
- agentURI
989
+ agentId_int,
990
+ agentURI,
950
991
  )
951
- self.sdk.web3_client.wait_for_transaction(txHash)
952
-
992
+
993
+ def _apply(_receipt: Dict[str, Any]) -> RegistrationFile:
994
+ return self.registration_file
995
+
996
+ return TransactionHandle(web3_client=self.sdk.web3_client, tx_hash=txHash, compute_result=_apply)
997
+
953
998
  return self.registration_file
954
999
 
955
1000
  def setAgentUri(self, uri: str) -> 'Agent':
@@ -969,11 +1014,11 @@ class Agent:
969
1014
  to: Address,
970
1015
  approve_operator: bool = False,
971
1016
  idem: Optional[IdemKey] = None,
972
- ) -> Dict[str, Any]:
1017
+ ) -> TransactionHandle[Dict[str, Any]]:
973
1018
  """Transfer agent ownership.
974
1019
 
975
1020
  Note: When an agent is transferred, the agentWallet is automatically reset
976
- to the zero address on-chain. The new owner must call setAgentWallet() to
1021
+ to the zero address on-chain. The new owner must call setWallet() to
977
1022
  set a new wallet address with EIP-712 signature verification.
978
1023
  """
979
1024
  if not self.registration_file.agentId:
@@ -989,22 +1034,22 @@ class Agent:
989
1034
  to,
990
1035
  agentId
991
1036
  )
992
-
993
- receipt = self.sdk.web3_client.wait_for_transaction(txHash)
994
-
995
- # Note: agentWallet will be reset to zero address by the contract
996
- # Update local state to reflect this
997
- self.registration_file.walletAddress = None
998
- self._last_registered_wallet = None
999
-
1000
- return {
1001
- "txHash": txHash,
1002
- "agentId": self.registration_file.agentId,
1003
- "from": self.sdk.web3_client.account.address,
1004
- "to": to
1005
- }
1006
1037
 
1007
- def addOperator(self, operator: Address, idem: Optional[IdemKey] = None) -> Dict[str, Any]:
1038
+ def _apply(_receipt: Dict[str, Any]) -> Dict[str, Any]:
1039
+ # Note: agentWallet will be reset to zero address by the contract
1040
+ self.registration_file.walletAddress = None
1041
+ self._last_registered_wallet = None
1042
+ self.registration_file.updatedAt = int(time.time())
1043
+ return {
1044
+ "txHash": txHash,
1045
+ "agentId": self.registration_file.agentId,
1046
+ "from": self.sdk.web3_client.account.address,
1047
+ "to": to,
1048
+ }
1049
+
1050
+ return TransactionHandle(web3_client=self.sdk.web3_client, tx_hash=txHash, compute_result=_apply)
1051
+
1052
+ def addOperator(self, operator: Address, idem: Optional[IdemKey] = None) -> TransactionHandle[Dict[str, Any]]:
1008
1053
  """Add operator (setApprovalForAll)."""
1009
1054
  if not self.registration_file.agentId:
1010
1055
  raise ValueError("Agent must be registered before adding operators")
@@ -1015,12 +1060,14 @@ class Agent:
1015
1060
  operator,
1016
1061
  True
1017
1062
  )
1018
-
1019
- receipt = self.sdk.web3_client.wait_for_transaction(txHash)
1020
-
1021
- return {"txHash": txHash, "operator": operator}
1022
1063
 
1023
- def removeOperator(self, operator: Address, idem: Optional[IdemKey] = None) -> Dict[str, Any]:
1064
+ return TransactionHandle(
1065
+ web3_client=self.sdk.web3_client,
1066
+ tx_hash=txHash,
1067
+ compute_result=lambda _receipt: {"txHash": txHash, "operator": operator},
1068
+ )
1069
+
1070
+ def removeOperator(self, operator: Address, idem: Optional[IdemKey] = None) -> TransactionHandle[Dict[str, Any]]:
1024
1071
  """Remove operator."""
1025
1072
  if not self.registration_file.agentId:
1026
1073
  raise ValueError("Agent must be registered before removing operators")
@@ -1031,18 +1078,20 @@ class Agent:
1031
1078
  operator,
1032
1079
  False
1033
1080
  )
1034
-
1035
- receipt = self.sdk.web3_client.wait_for_transaction(txHash)
1036
-
1037
- return {"txHash": txHash, "operator": operator}
1038
1081
 
1039
- def transfer(self, newOwnerAddress: str) -> Dict[str, Any]:
1082
+ return TransactionHandle(
1083
+ web3_client=self.sdk.web3_client,
1084
+ tx_hash=txHash,
1085
+ compute_result=lambda _receipt: {"txHash": txHash, "operator": operator},
1086
+ )
1087
+
1088
+ def transfer(self, newOwnerAddress: str) -> TransactionHandle[Dict[str, Any]]:
1040
1089
  """Transfer agent ownership to a new address.
1041
1090
 
1042
1091
  Only the current owner can transfer the agent.
1043
1092
 
1044
1093
  Note: When an agent is transferred, the agentWallet is automatically reset
1045
- to the zero address on-chain. The new owner must call setAgentWallet() to
1094
+ to the zero address on-chain. The new owner must call setWallet() to
1046
1095
  set a new wallet address with EIP-712 signature verification.
1047
1096
 
1048
1097
  Args:
@@ -1097,17 +1146,20 @@ class Agent:
1097
1146
  checksum_address,
1098
1147
  token_id
1099
1148
  )
1100
-
1101
- receipt = self.sdk.web3_client.wait_for_transaction(txHash)
1102
-
1103
- logger.debug(f"Agent {self.registration_file.agentId} successfully transferred to {checksum_address}")
1104
-
1105
- # Note: agentWallet will be reset to zero address by the contract
1106
- # Update local state to reflect this
1107
- self.registration_file.walletAddress = None
1108
- self._last_registered_wallet = None
1109
-
1110
- return {"txHash": txHash, "from": currentOwner, "to": checksum_address, "agentId": self.registration_file.agentId}
1149
+
1150
+ def _apply(_receipt: Dict[str, Any]) -> Dict[str, Any]:
1151
+ logger.debug(f"Agent {self.registration_file.agentId} successfully transferred to {checksum_address}")
1152
+ self.registration_file.walletAddress = None
1153
+ self._last_registered_wallet = None
1154
+ self.registration_file.updatedAt = int(time.time())
1155
+ return {
1156
+ "txHash": txHash,
1157
+ "from": currentOwner,
1158
+ "to": checksum_address,
1159
+ "agentId": self.registration_file.agentId,
1160
+ }
1161
+
1162
+ return TransactionHandle(web3_client=self.sdk.web3_client, tx_hash=txHash, compute_result=_apply)
1111
1163
 
1112
1164
  def activate(self, idem: Optional[IdemKey] = None) -> RegistrationFile:
1113
1165
  """Activate agent (soft "undelete")."""