diff --git a/examples/basic_user_details.py b/examples/basic_user_details.py new file mode 100644 index 00000000..f75eeb9a --- /dev/null +++ b/examples/basic_user_details.py @@ -0,0 +1,46 @@ +import json + +import example_utils + +from hyperliquid.utils import constants + + +def main(): + address, info, exchange = example_utils.setup(base_url=constants.MAINNET_API_URL, skip_ws=True) + + # Retrieve user transaction details + print(f"Fetching transaction details for address: {address}") + user_details = info.user_details(address) + + # Print the response type + print(f"\nResponse type: {user_details.get('type')}") + print(f"Total transactions: {len(user_details.get('txs', []))}") + + # Print details of the first few transactions + txs = user_details.get("txs", []) + if len(txs) > 0: + print("\nFirst 5 transactions:") + for i, tx in enumerate(txs[:5]): + print(f"\n--- Transaction {i + 1} ---") + print(f" Time: {tx.get('time')}") + print(f" User: {tx.get('user')}") + print(f" Block: {tx.get('block')}") + print(f" Hash: {tx.get('hash')}") + print(f" Error: {tx.get('error')}") + + # Print action details + action = tx.get("action") + if isinstance(action, dict): + print(f" Action Type: {action.get('type')}") + print(f" Action Details: {json.dumps(action, indent=4)}") + elif isinstance(action, list): + print(f" Action: {action}") + else: + print(f" Action: {action}") + else: + print("No transactions found for this user") + + +if __name__ == "__main__": + main() + diff --git a/hyperliquid/info.py b/hyperliquid/info.py index 5898386b..da233373 100644 --- a/hyperliquid/info.py +++ b/hyperliquid/info.py @@ -1,4 +1,5 @@ from hyperliquid.api import API +from hyperliquid.utils import constants from hyperliquid.utils.types import ( Any, Callable, @@ -754,6 +755,60 @@ def extra_agents(self, user: str) -> Any: """ return self.post("/info", {"type": "extraAgents", "user": user}) + def user_details(self, user: str) -> Any: + """Retrieve array of user transaction details. + + POST /explorer (via RPC endpoint) + + Args: + user (str): Onchain address in 42-character hexadecimal format; + e.g. 0x0000000000000000000000000000000000000000. + + Returns: + { + "type": "userDetails", + "txs": [ + { + "time": int, # Transaction creation timestamp + "user": str, # Creator's address + "action": dict, # Action performed in transaction (with "type" field) + "block": int, # Block number where transaction was included + "hash": str, # Transaction hash (66 character hex string) + "error": Optional[str] # Error message if transaction failed + }, + ... + ] + } + + Note: + The action field can contain various transaction types including: + - orders (type: "order") + - cancels (type: "cancel") + - leverage updates (type: "updateLeverage") + - vault transfers (type: "vaultTransfer") + - withdrawals (type: "withdraw2") + - deposits and other EVM transactions (type: "evmRawTx") + - and many other action types + """ + # The explorer endpoint requires the RPC URL, not the API URL + # Determine which RPC URL to use based on the current base_url + if "testnet" in self.base_url: + rpc_url = constants.TESTNET_RPC_URL + else: + rpc_url = constants.MAINNET_RPC_URL + + # Make request directly to RPC endpoint + import requests + + response = requests.post( + f"{rpc_url}/explorer", + json={"type": "userDetails", "user": user}, + headers={"Content-Type": "application/json"}, + timeout=self.timeout, + ) + self._handle_exception(response) + return response.json() + def _remap_coin_subscription(self, subscription: Subscription) -> None: if ( subscription["type"] == "l2Book" diff --git a/hyperliquid/utils/constants.py b/hyperliquid/utils/constants.py index 1b2bf974..36d6d9dd 100644 --- a/hyperliquid/utils/constants.py +++ b/hyperliquid/utils/constants.py @@ -1,3 +1,7 @@ MAINNET_API_URL = "https://api.hyperliquid.xyz" TESTNET_API_URL = "https://api.hyperliquid-testnet.xyz" LOCAL_API_URL = "http://localhost:3001" + +# RPC URLs for explorer endpoints +MAINNET_RPC_URL = "https://rpc.hyperliquid.xyz" +TESTNET_RPC_URL = "https://rpc.hyperliquid-testnet.xyz"