Skip to content

Commit 310a545

Browse files
feat: Add support for include_children in TransactionGetReceiptQuery (#1114)
Signed-off-by: Antonio Ceppellini <[email protected]> Signed-off-by: AntonioCeppellini <[email protected]>
1 parent a13fc0b commit 310a545

File tree

7 files changed

+451
-46
lines changed

7 files changed

+451
-46
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ This changelog is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.
4646
- Enhanced `.github/ISSUE_TEMPLATE/01_good_first_issue.yml` with welcoming message and acceptance criteria sections to guide contributors in creating quality GFIs (#1052)
4747
- Add workflow to notify team about P0 issues `bot-p0-issues-notify-team.yml`
4848
- Added Issue Reminder (no-PR) bot, .github/scripts/issue_reminder_no_pr.sh and .github/workflows/bot-issue-reminder-no-pr.yml to automatically detect assigned issues with no linked pull requests for 7+ days and post a gentle ReminderBot comment.(#951)
49+
- Add support for include_children in TransactionGetReceiptQuery (#1100)(https://github.com/hiero-ledger/hiero-sdk-python/issues/1100)
4950
- Add new `.github/ISSUE_TEMPLATE/05_intermediate_issue.yml` file (1072)(https://github.com/hiero-ledger/hiero-sdk-python/issues/1072)
5051
- Add a workflow to notify the team when issues are labeled as “good first issues” or identified as candidates for that label: `bot-gfi-notify-team.yml`(#1115)
5152

examples/query/transaction_get_receipt_query.py

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -62,9 +62,26 @@ def create_account(client, operator_key):
6262
sys.exit(1)
6363

6464

65+
def _print_receipt_with_children(queried_receipt):
66+
"""Pretty-print receipt status and any child receipts."""
67+
print(f"✅ Queried transaction status: {ResponseCode(queried_receipt.status).name}")
68+
69+
children = queried_receipt.children
70+
print(f"Child receipts count: {len(children)}")
71+
72+
if not children:
73+
print("No child receipts returned (this can be normal depending on transaction type).")
74+
return
75+
76+
print("Child receipts:")
77+
for idx, child in enumerate(children, start=1):
78+
print(f" {idx}. status={ResponseCode(child.status).name}")
79+
80+
6581
def query_receipt():
6682
"""
67-
A full example that include account creation, Hbar transfer, and receipt querying
83+
A full example that include account creation, Hbar transfer, and receipt querying.
84+
Demonstrates include_child_receipts support (SDK API: set_include_children).
6885
"""
6986
# Config Client
7087
client, operator_id, operator_key = setup_client()
@@ -80,7 +97,7 @@ def query_receipt():
8097
.add_hbar_transfer(operator_id, -Hbar(amount).to_tinybars())
8198
.add_hbar_transfer(recipient_id, Hbar(amount).to_tinybars())
8299
.freeze_with(client)
83-
.sign(operator_key)
100+
.sign(operator_key)
84101
)
85102

86103
receipt = transaction.execute(client)
@@ -91,13 +108,15 @@ def query_receipt():
91108
)
92109

93110
# Query Transaction Receipt
94-
print("\nSTEP 3: Querying transaction receipt...")
95-
receipt_query = TransactionGetReceiptQuery().set_transaction_id(transaction_id)
111+
print("\nSTEP 3: Querying transaction receipt (include child receipts)...")
112+
receipt_query = TransactionGetReceiptQuery().set_transaction_id(transaction_id).set_include_children(True)
96113
queried_receipt = receipt_query.execute(client)
97114
print(
98115
f"✅ Success! Queried transaction status: {ResponseCode(queried_receipt.status).name}"
99116
)
100117

101118

119+
_print_receipt_with_children(queried_receipt)
120+
102121
if __name__ == "__main__":
103122
query_receipt()

src/hiero_sdk_python/query/transaction_get_receipt_query.py

Lines changed: 38 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,11 @@ class TransactionGetReceiptQuery(Query):
2424
2525
"""
2626

27-
def __init__(self, transaction_id: Optional[TransactionId] = None) -> None:
27+
def __init__(
28+
self,
29+
transaction_id: Optional[TransactionId] = None,
30+
include_children: bool = False,
31+
) -> None:
2832
"""
2933
Initializes a new instance of the TransactionGetReceiptQuery class.
3034
@@ -34,6 +38,7 @@ def __init__(self, transaction_id: Optional[TransactionId] = None) -> None:
3438
super().__init__()
3539
self.transaction_id: Optional[TransactionId] = transaction_id
3640
self._frozen: bool = False
41+
self.include_children = include_children
3742

3843
def _require_not_frozen(self) -> None:
3944
"""
@@ -62,6 +67,25 @@ def set_transaction_id(self, transaction_id: TransactionId) -> "TransactionGetRe
6267
self.transaction_id = transaction_id
6368
return self
6469

70+
def set_include_children(
71+
self, include_children: bool
72+
) -> "TransactionGetReceiptQuery":
73+
"""
74+
Sets include_children for which to retrieve the child transaction receipts.
75+
76+
Args:
77+
include_children: bool.
78+
79+
Returns:
80+
TransactionGetReceiptQuery: The current instance for method chaining.
81+
82+
Raises:
83+
ValueError: If the query is frozen and cannot be modified.
84+
"""
85+
self._require_not_frozen()
86+
self.include_children = include_children
87+
return self
88+
6589
def freeze(self) -> "TransactionGetReceiptQuery":
6690
"""
6791
Marks the query as frozen, preventing further modification.
@@ -100,6 +124,8 @@ def _make_request(self) -> query_pb2.Query:
100124
transaction_get_receipt.header.CopyFrom(query_header)
101125
transaction_get_receipt.transactionID.CopyFrom(self.transaction_id._to_proto())
102126

127+
transaction_get_receipt.include_child_receipts = self.include_children
128+
103129
query = query_pb2.Query()
104130
if not hasattr(query, "transactionGetReceipt"):
105131
raise AttributeError("Query object has no attribute 'transactionGetReceipt'")
@@ -219,8 +245,18 @@ def execute(self, client: Client) -> TransactionReceipt:
219245
"""
220246
self._before_execute(client)
221247
response = self._execute(client)
248+
parent = TransactionReceipt._from_proto(response.transactionGetReceipt.receipt, self.transaction_id)
249+
250+
if self.include_children:
251+
children = []
252+
253+
for child_proto in response.transactionGetReceipt.child_transaction_receipts:
254+
child_receipt = TransactionReceipt._from_proto(child_proto, self.transaction_id)
255+
children.append(child_receipt)
256+
257+
parent._set_children(children)
222258

223-
return TransactionReceipt._from_proto(response.transactionGetReceipt.receipt, self.transaction_id)
259+
return parent
224260

225261
def _get_query_response(
226262
self, response: response_pb2.Response

src/hiero_sdk_python/transaction/transaction_receipt.py

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,6 @@
1616
from hiero_sdk_python.contract.contract_id import ContractId
1717
from hiero_sdk_python.schedule.schedule_id import ScheduleId
1818
from hiero_sdk_python.tokens.token_id import TokenId
19-
from hiero_sdk_python.transaction.transaction_id import TransactionId
20-
2119
from hiero_sdk_python.transaction.transaction_id import TransactionId
2220
from hiero_sdk_python.hapi.services import transaction_receipt_pb2, response_code_pb2
2321
from hiero_sdk_python.account.account_id import AccountId
@@ -39,7 +37,8 @@ class TransactionReceipt:
3937
def __init__(
4038
self,
4139
receipt_proto: transaction_receipt_pb2.TransactionReceipt,
42-
transaction_id: Optional[TransactionId] = None
40+
transaction_id: Optional[TransactionId] = None,
41+
children: Optional[list["TransactionReceipt"]] = None,
4342
) -> None:
4443
"""
4544
Initializes the TransactionReceipt with the provided protobuf receipt.
@@ -51,6 +50,7 @@ def __init__(
5150
self._transaction_id: Optional[TransactionId] = transaction_id
5251
self.status: Optional[response_code_pb2.ResponseCodeEnum] = receipt_proto.status
5352
self._receipt_proto: transaction_receipt_pb2.TransactionReceipt = receipt_proto
53+
self._children: list["TransactionReceipt"] = children or []
5454

5555
@property
5656
def token_id(self) -> Optional[TokenId]:
@@ -183,7 +183,7 @@ def node_id(self):
183183
int: The node ID if present; otherwise, 0.
184184
"""
185185
return self._receipt_proto.node_id
186-
186+
187187
@property
188188
def topic_sequence_number(self) -> int:
189189
"""
@@ -207,6 +207,25 @@ def topic_running_hash(self) -> Optional[bytes]:
207207

208208
return None
209209

210+
@property
211+
def children(self) -> list["TransactionReceipt"]:
212+
"""
213+
Returns the child transaction receipts associated with this receipt.
214+
215+
Returns:
216+
list[TransactionReceipt]: Child receipts (empty if not requested or none exist).
217+
"""
218+
return self._children
219+
220+
def _set_children(self, children: list["TransactionReceipt"]) -> None:
221+
"""
222+
Internal setter for child receipts (used by receipt queries).
223+
224+
Args:
225+
children (list[TransactionReceipt]): Child receipts.
226+
"""
227+
self._children = children
228+
210229
def _to_proto(self):
211230
"""
212231
Returns the underlying protobuf transaction receipt.
Lines changed: 184 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,184 @@
1+
"""
2+
E2E integration tests for TransactionGetReceiptQuery include_child_receipts support.
3+
4+
These tests validate the full SDK flow against a real Hedera network:
5+
- submit a real transaction
6+
- query its receipt
7+
- verify children behavior with and without include_children flag
8+
9+
NOTE:
10+
The contract used in these tests (StatefulContract) does NOT deterministically
11+
produce child receipts, so we only assert API correctness and stability,
12+
not children count > 0.
13+
"""
14+
15+
import pytest
16+
17+
from hiero_sdk_python.hbar import Hbar
18+
from hiero_sdk_python.query.transaction_get_receipt_query import TransactionGetReceiptQuery
19+
from hiero_sdk_python.response_code import ResponseCode
20+
from hiero_sdk_python.transaction.transfer_transaction import TransferTransaction
21+
22+
from tests.integration.utils import env
23+
24+
25+
def _extract_tx_id(tx, receipt):
26+
"""
27+
Best-effort extraction of TransactionId for E2E tests.
28+
"""
29+
tx_id = getattr(receipt, "transaction_id", None)
30+
if tx_id is not None:
31+
return tx_id
32+
33+
tx_id = getattr(tx, "transaction_id", None)
34+
if tx_id is not None:
35+
return tx_id
36+
37+
tx_id = getattr(tx, "_transaction_id", None)
38+
if tx_id is not None:
39+
return tx_id
40+
41+
tx_ids = getattr(tx, "_transaction_ids", None)
42+
if tx_ids:
43+
return tx_ids[0]
44+
45+
raise AssertionError(
46+
"Unable to extract TransactionId from transaction or receipt."
47+
)
48+
49+
50+
def _submit_simple_transfer(env):
51+
"""
52+
Submit a simple transfer transaction and return its TransactionId.
53+
"""
54+
receiver = env.create_account(initial_hbar=0.0)
55+
56+
tx = (
57+
TransferTransaction()
58+
.add_hbar_transfer(env.operator_id, Hbar(-0.01).to_tinybars())
59+
.add_hbar_transfer(receiver.id, Hbar(0.01).to_tinybars())
60+
)
61+
62+
receipt = tx.execute(env.client)
63+
assert receipt.status == ResponseCode.SUCCESS
64+
65+
return _extract_tx_id(tx, receipt)
66+
67+
68+
@pytest.mark.integration
69+
def test_get_receipt_query_children_empty_when_not_requested_e2e(env):
70+
"""
71+
E2E:
72+
When include_children is NOT requested, receipt.children must be empty.
73+
"""
74+
tx_id = _submit_simple_transfer(env)
75+
76+
receipt = (
77+
TransactionGetReceiptQuery()
78+
.set_transaction_id(tx_id)
79+
.execute(env.client)
80+
)
81+
82+
assert receipt.status == ResponseCode.SUCCESS
83+
assert receipt.children == []
84+
85+
86+
@pytest.mark.integration
87+
def test_get_receipt_query_children_list_when_requested_e2e(env):
88+
"""
89+
E2E:
90+
When include_children IS requested, receipt.children must exist and be a list.
91+
The list may be empty depending on transaction type.
92+
"""
93+
tx_id = _submit_simple_transfer(env)
94+
95+
receipt = (
96+
TransactionGetReceiptQuery()
97+
.set_transaction_id(tx_id)
98+
.set_include_children(True)
99+
.execute(env.client)
100+
)
101+
102+
assert receipt.status == ResponseCode.SUCCESS
103+
assert isinstance(receipt.children, list)
104+
105+
106+
@pytest.mark.integration
107+
def test_get_receipt_query_children_with_contract_execute_e2e(env):
108+
"""
109+
E2E:
110+
Execute a real contract transaction and query its receipt with include_children enabled.
111+
112+
We assert:
113+
- no crash
114+
- receipt.children exists and is a list
115+
"""
116+
from examples.contract.contracts.contract_utils import (
117+
CONTRACT_DEPLOY_GAS,
118+
STATEFUL_CONTRACT_BYTECODE,
119+
)
120+
from hiero_sdk_python.file.file_create_transaction import FileCreateTransaction
121+
from hiero_sdk_python.contract.contract_create_transaction import ContractCreateTransaction
122+
from hiero_sdk_python.contract.contract_execute_transaction import ContractExecuteTransaction
123+
from hiero_sdk_python.contract.contract_function_parameters import ContractFunctionParameters
124+
125+
# Upload contract bytecode
126+
file_receipt = (
127+
FileCreateTransaction()
128+
.set_keys(env.operator_key.public_key())
129+
.set_contents(STATEFUL_CONTRACT_BYTECODE)
130+
.set_file_memo("transaction receipt children test")
131+
.execute(env.client)
132+
)
133+
assert file_receipt.status == ResponseCode.SUCCESS
134+
file_id = file_receipt.file_id
135+
assert file_id is not None
136+
137+
# Deploy contract
138+
constructor_params = ContractFunctionParameters().add_bytes32(
139+
b"Initial message from constructor"
140+
)
141+
contract_receipt = (
142+
ContractCreateTransaction()
143+
.set_admin_key(env.operator_key.public_key())
144+
.set_gas(CONTRACT_DEPLOY_GAS)
145+
.set_constructor_parameters(constructor_params)
146+
.set_bytecode_file_id(file_id)
147+
.execute(env.client)
148+
)
149+
assert contract_receipt.status == ResponseCode.SUCCESS
150+
contract_id = contract_receipt.contract_id
151+
assert contract_id is not None
152+
153+
# Execute contract function
154+
execute_tx = (
155+
ContractExecuteTransaction()
156+
.set_contract_id(contract_id)
157+
.set_gas(1_000_000)
158+
.set_function(
159+
"setMessage",
160+
ContractFunctionParameters().add_bytes32(b"Updated message".ljust(32, b"\x00")),
161+
)
162+
)
163+
execute_receipt = execute_tx.execute(env.client)
164+
assert execute_receipt.status == ResponseCode.SUCCESS
165+
166+
try:
167+
tx_id = _extract_tx_id(execute_tx, execute_receipt)
168+
except AssertionError as e:
169+
pytest.skip(str(e))
170+
171+
queried = (
172+
TransactionGetReceiptQuery()
173+
.set_transaction_id(tx_id)
174+
.set_include_children(True)
175+
.execute(env.client)
176+
)
177+
178+
assert queried.status == ResponseCode.SUCCESS
179+
180+
if len(queried.children) > 0:
181+
for child in queried.children:
182+
assert child.status is not None
183+
184+
assert isinstance(queried.children, list)

0 commit comments

Comments
 (0)