Skip to content

Commit 12e2c53

Browse files
feat: add AccountRecordsQuery (#407)
Signed-off-by: nadineloe <[email protected]> Co-authored-by: nadineloe <[email protected]>
1 parent d8f478e commit 12e2c53

File tree

7 files changed

+627
-2
lines changed

7 files changed

+627
-2
lines changed

CHANGELOG.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,17 +10,19 @@ This changelog is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.
1010
### Added
1111
- Add Google-style docstrings to `AccountInfo` class and its methods in `account_info.py`.
1212

13+
- add AccountRecordsQuery class
14+
1315
### Changed
1416

1517
### Fixed
18+
1619
- Added explicit read permissions to examples.yml (#623)
1720

1821
### Breaking Changes
1922

2023

2124
## [0.1.7] - 2025-10-28
2225

23-
2426
### Added
2527
- Expanded `README.md` with a new "Follow Us" section detailing how to watch, star, and fork the repository (#472).
2628
- Refactored `examples/topic_create.py` into modular functions for better readability and reuse.
@@ -129,7 +131,6 @@ This changelog is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.
129131
## [0.1.5] - 2025-09-25
130132

131133
### Added
132-
133134
- ScheduleSignTransaction class
134135
- NodeUpdateTransaction class
135136
- NodeDeleteTransaction class

docs/sdk_users/running_examples.md

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ You can choose either syntax or even mix both styles in your projects.
1818
- [Updating an Account](#updating-an-account)
1919
- [Allowance Approve Transaction](#allowance-approve-transaction)
2020
- [Allowance Delete Transaction](#allowance-delete-transaction)
21+
- [Querying Account Records](#querying-account-records)
2122
- [Token Transactions](#token-transactions)
2223
- [Creating a Token](#creating-a-token)
2324
- [Minting a Fungible Token](#minting-a-fungible-token)
@@ -345,6 +346,28 @@ transaction.sign(owner_private_key)
345346
transaction.execute(client)
346347
```
347348

349+
### Querying Account Records
350+
351+
#### Pythonic Syntax:
352+
```python
353+
records = AccountRecordsQuery(account_id=account_id).execute(client)
354+
355+
for record in records:
356+
print(record)
357+
```
358+
359+
#### Method Chaining:
360+
```python
361+
records = (
362+
AccountRecordsQuery()
363+
.set_account_id(account_id)
364+
.execute(client)
365+
)
366+
367+
for record in records:
368+
print(record)
369+
```
370+
348371
## Token Transactions
349372

350373
### Creating a Token

examples/query_account_records.py

Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
1+
"""
2+
Example demonstrating account records query on the network.
3+
"""
4+
5+
import os
6+
import sys
7+
8+
from dotenv import load_dotenv
9+
10+
from hiero_sdk_python import (
11+
AccountCreateTransaction,
12+
AccountId,
13+
Client,
14+
Hbar,
15+
Network,
16+
PrivateKey,
17+
ResponseCode,
18+
)
19+
from hiero_sdk_python.account.account_records_query import AccountRecordsQuery
20+
from hiero_sdk_python.transaction.transfer_transaction import TransferTransaction
21+
22+
load_dotenv()
23+
24+
25+
def setup_client():
26+
"""Initialize and set up the client with operator account"""
27+
network = Network(os.getenv('NETWORK'))
28+
client = Client(network)
29+
30+
operator_id = AccountId.from_string(os.getenv("OPERATOR_ID"))
31+
operator_key = PrivateKey.from_string(os.getenv("OPERATOR_KEY"))
32+
client.set_operator(operator_id, operator_key)
33+
34+
return client
35+
36+
37+
def create_account(client):
38+
"""Create a test account"""
39+
account_private_key = PrivateKey.generate_ed25519()
40+
account_public_key = account_private_key.public_key()
41+
42+
receipt = (
43+
AccountCreateTransaction()
44+
.set_key(account_public_key)
45+
.set_initial_balance(Hbar(2))
46+
.set_account_memo("Test account for records query")
47+
.freeze_with(client)
48+
.execute(client)
49+
)
50+
51+
if receipt.status != ResponseCode.SUCCESS:
52+
print(f"Account creation failed with status: {ResponseCode(receipt.status).name}")
53+
sys.exit(1)
54+
55+
account_id = receipt.account_id
56+
print(f"\nAccount created with ID: {account_id}")
57+
58+
return account_id, account_private_key
59+
60+
61+
def format_single_record(record, idx):
62+
"""Format a single account record"""
63+
lines = []
64+
lines.append(f"\nRecord #{idx}")
65+
lines.append("-" * 80)
66+
67+
transaction_id = getattr(record, 'transaction_id', None)
68+
if transaction_id:
69+
lines.append(f" Transaction ID: {transaction_id}")
70+
71+
consensus_timestamp = getattr(record, 'consensus_timestamp', None)
72+
if consensus_timestamp:
73+
lines.append(f" Consensus Timestamp: {consensus_timestamp}")
74+
75+
transaction_fee = getattr(record, 'transaction_fee', None)
76+
if transaction_fee:
77+
lines.append(f" Transaction Fee: {transaction_fee} tinybar")
78+
79+
transaction_hash = getattr(record, 'transaction_hash', None)
80+
if transaction_hash:
81+
lines.append(f" Transaction Hash: {transaction_hash.hex()}")
82+
83+
receipt = getattr(record, 'receipt', None)
84+
if receipt:
85+
lines.append(f" Receipt Status: {ResponseCode(receipt.status).name}")
86+
87+
transfers = getattr(record, 'transfers', None)
88+
if transfers:
89+
lines.append(f" Transfers:")
90+
for account_id, amount_in_tinybar in transfers.items():
91+
amount_hbar = amount_in_tinybar / 100_000_000 # Convert tinybar to hbar
92+
lines.append(f" {account_id}: {amount_hbar:+.8f} ℏ")
93+
94+
lines.append("-" * 80)
95+
return "\n".join(lines)
96+
97+
98+
def format_account_records(records):
99+
"""Format account records for readable display"""
100+
if not records:
101+
return "No records found"
102+
103+
output = ["=" * 80]
104+
105+
for idx, record in enumerate(records, 1):
106+
output.append(format_single_record(record, idx))
107+
108+
output.append("=" * 80)
109+
return "\n".join(output)
110+
111+
112+
def query_account_records():
113+
"""
114+
Demonstrates the account record query functionality by:
115+
1. Setting up client with operator account
116+
2. Creating a new account and setting it as the operator
117+
3. Querying account records and displaying basic information
118+
4. Performing a transfer transaction
119+
5. Querying account records again to see updated transaction history
120+
"""
121+
client = setup_client()
122+
123+
# Store the original operator account ID before creating new account
124+
original_operator_id = client.operator_account_id
125+
126+
# Create a new account
127+
account_id, account_private_key = create_account(client)
128+
129+
records_before = AccountRecordsQuery().set_account_id(account_id).execute(client)
130+
131+
print(f"\nAccount {account_id} has {len(records_before)} transaction records")
132+
print("\nTransaction records (before transfer):")
133+
print(format_account_records(records_before))
134+
135+
# Set the newly created account as the operator for executing the transfer
136+
client.set_operator(account_id, account_private_key)
137+
138+
# Perform a transfer transaction from the newly created account to the original operator
139+
print(f"\nTransferring 1 ℏ from {account_id} to {original_operator_id}...")
140+
receipt = (
141+
TransferTransaction()
142+
.add_hbar_transfer(account_id, -Hbar(1).to_tinybars())
143+
.add_hbar_transfer(original_operator_id, Hbar(1).to_tinybars())
144+
.execute(client)
145+
)
146+
147+
if receipt.status != ResponseCode.SUCCESS:
148+
print(f"Transfer failed with status: {ResponseCode(receipt.status).name}")
149+
sys.exit(1)
150+
151+
records_after = AccountRecordsQuery().set_account_id(account_id).execute(client)
152+
153+
print(f"\nAccount {account_id} has {len(records_after)} transaction record(s)")
154+
print("\nTransaction records (after transfer):")
155+
print(format_account_records(records_after))
156+
157+
158+
if __name__ == "__main__":
159+
query_account_records()

src/hiero_sdk_python/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
from .account.account_delete_transaction import AccountDeleteTransaction
1111
from .account.account_allowance_approve_transaction import AccountAllowanceApproveTransaction
1212
from .account.account_allowance_delete_transaction import AccountAllowanceDeleteTransaction
13+
from .account.account_records_query import AccountRecordsQuery
1314

1415
# Crypto
1516
from .crypto.private_key import PrivateKey
@@ -149,6 +150,7 @@
149150
"AccountDeleteTransaction",
150151
"AccountAllowanceApproveTransaction",
151152
"AccountAllowanceDeleteTransaction",
153+
"AccountRecordsQuery",
152154

153155
# Crypto
154156
"PrivateKey",
Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
"""
2+
Query to get records about a specific account on the network.
3+
"""
4+
5+
import traceback
6+
from typing import List, Optional
7+
8+
from hiero_sdk_python.account.account_id import AccountId
9+
from hiero_sdk_python.channels import _Channel
10+
from hiero_sdk_python.client.client import Client
11+
from hiero_sdk_python.executable import _Method
12+
from hiero_sdk_python.hapi.services import crypto_get_account_records_pb2, query_pb2, response_pb2
13+
from hiero_sdk_python.hapi.services.crypto_get_account_records_pb2 import (
14+
CryptoGetAccountRecordsResponse,
15+
)
16+
from hiero_sdk_python.query.query import Query
17+
from hiero_sdk_python.transaction.transaction_record import TransactionRecord
18+
19+
20+
class AccountRecordsQuery(Query):
21+
"""
22+
A query to retrieve records about a specific Account.
23+
24+
This class constructs and executes a query to retrieve records of all transactions
25+
against the specified account within the last 25 hours.
26+
"""
27+
28+
def __init__(self, account_id: Optional[AccountId] = None):
29+
"""
30+
Initializes a new AccountRecordsQuery instance with an optional account_id.
31+
32+
Args:
33+
account_id (Optional[AccountId]): The ID of the account to query.
34+
"""
35+
super().__init__()
36+
self.account_id: Optional[AccountId] = account_id
37+
38+
def set_account_id(self, account_id: Optional[AccountId]) -> "AccountRecordsQuery":
39+
"""
40+
Sets the ID of the account to query.
41+
42+
Args:
43+
account_id (Optional[AccountId]): The ID of the account.
44+
45+
Returns:
46+
AccountRecordsQuery: Returns self for method chaining.
47+
"""
48+
self.account_id = account_id
49+
return self
50+
51+
def _make_request(self) -> query_pb2.Query:
52+
"""
53+
Constructs the protobuf request for the query.
54+
55+
Builds a CryptoGetAccountRecordsQuery protobuf message with the
56+
appropriate header and account ID.
57+
58+
Returns:
59+
Query: The protobuf query message.
60+
61+
Raises:
62+
ValueError: If the account ID is not set.
63+
Exception: If any other error occurs during request construction.
64+
"""
65+
try:
66+
if not self.account_id:
67+
raise ValueError("Account ID must be set before making the request.")
68+
69+
query_header = self._make_request_header()
70+
71+
crypto_records_query = crypto_get_account_records_pb2.CryptoGetAccountRecordsQuery()
72+
crypto_records_query.header.CopyFrom(query_header)
73+
crypto_records_query.accountID.CopyFrom(self.account_id._to_proto())
74+
75+
query = query_pb2.Query()
76+
query.cryptoGetAccountRecords.CopyFrom(crypto_records_query)
77+
78+
return query
79+
except Exception as e:
80+
print(f"Exception in _make_request: {e}")
81+
traceback.print_exc()
82+
raise
83+
84+
def _get_method(self, channel: _Channel) -> _Method:
85+
"""
86+
Returns the appropriate gRPC method for the account records query.
87+
88+
Implements the abstract method from Query to provide the specific
89+
gRPC method for getting account records.
90+
91+
Args:
92+
channel (_Channel): The channel containing service stubs
93+
94+
Returns:
95+
_Method: The method wrapper containing the query function
96+
"""
97+
return _Method(transaction_func=None, query_func=channel.crypto.getAccountRecords)
98+
99+
def execute(self, client: Client) -> List[TransactionRecord]:
100+
"""
101+
Executes the account records query.
102+
103+
Sends the query to the Hedera network and processes the response
104+
to return a list of TransactionRecord objects.
105+
106+
This function delegates the core logic to `_execute()`, and may propagate
107+
exceptions raised by it.
108+
109+
Args:
110+
client (Client): The client instance to use for execution
111+
112+
Returns:
113+
List[TransactionRecord]: The account records from the network
114+
115+
Raises:
116+
PrecheckError: If the query fails with a non-retryable error
117+
MaxAttemptsError: If the query fails after the maximum number of attempts
118+
ReceiptStatusError: If the query fails with a receipt status error
119+
"""
120+
self._before_execute(client)
121+
response = self._execute(client)
122+
123+
records = []
124+
for record in response.cryptoGetAccountRecords.records:
125+
records.append(TransactionRecord._from_proto(record))
126+
127+
return records
128+
129+
def _get_query_response(
130+
self, response: response_pb2.Response
131+
) -> CryptoGetAccountRecordsResponse:
132+
"""
133+
Extracts the account records response from the full response.
134+
135+
Implements the abstract method from Query to extract the
136+
specific account records response object.
137+
138+
Args:
139+
response: The full response from the network
140+
141+
Returns:
142+
The crypto get account records response object
143+
"""
144+
return response.cryptoGetAccountRecords

0 commit comments

Comments
 (0)