Skip to content

Commit ae4d328

Browse files
docs: add receipt_status_error.md and example script (fixes #878) (#883)
Signed-off-by: jaideepkathiresan <jvk7557@gmail.com>
1 parent 78657fb commit ae4d328

File tree

3 files changed

+147
-0
lines changed

3 files changed

+147
-0
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,10 @@ This changelog is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.
88

99
### Added
1010

11+
- Add comprehensive documentation for `ReceiptStatusError` in `docs/sdk_developers/training/receipt_status_error.md`
12+
- Add practical example `examples/errors/receipt_status_error.py` demonstrating transaction error handling
13+
- Document error handling patterns and best practices for transaction receipts
14+
1115
- Add detail to `token_airdrop.py` and `token_airdrop_cancel.py`
1216
- Add workflow: github bot to respond to unverified PR commits (#750)
1317
- Add workflow: bot workflow which notifies developers of workflow failures in their pull requests.
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
# ReceiptStatusError
2+
3+
This guide explains `ReceiptStatusError` in the Hiero SDK, how it differs from other errors, and how to handle it effectively.
4+
5+
## Error Handling Overview
6+
7+
Many developers assume that if `execute()` doesn't throw an exception, the transaction or query succeeded. However, failures can occur at different stages:
8+
9+
1. **Precheck (Before Submission):**
10+
* Occurs if the transaction is malformed or fails initial validation by the node.
11+
* The SDK raises a `PrecheckError` (or similar) immediately.
12+
* The transaction never reaches consensus.
13+
14+
2. **Network/Node Retry Failures:**
15+
* Occurs if the SDK cannot reach a node or receives transient errors (e.g., `BUSY`).
16+
* The SDK automatically retries up to a limit.
17+
* If retries are exhausted, a `MaxAttemptsError` or `TimeoutError` is raised.
18+
19+
3. **Receipt Status Errors (Post-Consensus):**
20+
* Occurs **after** the network reaches consensus on the transaction.
21+
* The transaction was successfully submitted and processed, but the logic failed (e.g., insufficient funds, token supply full, invalid signature for the specific operation).
22+
* This is where `ReceiptStatusError` comes in.
23+
24+
## What is ReceiptStatusError?
25+
26+
`ReceiptStatusError` is an exception that represents a failure during the **consensus/execution** phase.
27+
28+
* **Timing:** It happens after the transaction is submitted and processed by the network.
29+
* **Content:** It contains the `transaction_receipt`, which holds the status code (e.g., `INSUFFICIENT_ACCOUNT_BALANCE`) and other metadata.
30+
* **Usage:** You must explicitly check the receipt status or configure your client/transaction to throw this error.
31+
32+
## Handling ReceiptStatusError
33+
34+
When you execute a transaction, you typically get a receipt. You should check the status of this receipt.
35+
36+
```python
37+
from hiero_sdk_python.response_code import ResponseCode
38+
from hiero_sdk_python.exceptions import ReceiptStatusError
39+
40+
# ... client and transaction setup ...
41+
42+
try:
43+
# Execute the transaction
44+
receipt = tx.execute(client)
45+
46+
# Check if the status is SUCCESS
47+
if receipt.status != ResponseCode.SUCCESS:
48+
# Raise the specific error with details
49+
raise ReceiptStatusError(receipt.status, tx.transaction_id, receipt)
50+
51+
print("Transaction succeeded!")
52+
53+
except ReceiptStatusError as e:
54+
print(f"Transaction failed post-consensus: {e}")
55+
print(f"Status Code: {e.status}")
56+
print(f"Transaction ID: {e.transaction_id}")
57+
# You can access the full receipt for debugging
58+
# print(e.transaction_receipt)
59+
60+
except Exception as e:
61+
print(f"An unexpected error occurred: {e}")
62+
```
63+
64+
## Summary
65+
66+
* **Precheck errors** happen *before* the network processes the transaction.
67+
* **ReceiptStatusErrors** happen *after* the network processes the transaction but finds it invalid according to ledger rules.
68+
* Always check `receipt.status` to ensure your transaction actually did what you intended.
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
#!/usr/bin/env python3
2+
"""
3+
Example demonstrating how to handle ReceiptStatusError in the Hiero SDK.
4+
5+
run:
6+
uv run examples/errors/receipt_status_error.py
7+
python examples/errors/receipt_status_error.py
8+
"""
9+
import os
10+
import dotenv
11+
12+
from hiero_sdk_python.client.client import Client
13+
from hiero_sdk_python.account.account_id import AccountId
14+
from hiero_sdk_python.crypto.private_key import PrivateKey
15+
from hiero_sdk_python.response_code import ResponseCode
16+
from hiero_sdk_python.tokens.token_associate_transaction import TokenAssociateTransaction
17+
from hiero_sdk_python.tokens.token_id import TokenId
18+
from hiero_sdk_python.exceptions import ReceiptStatusError
19+
20+
dotenv.load_dotenv()
21+
22+
def main():
23+
# Initialize the client
24+
# For this example, we assume we are running against a local node or testnet
25+
# You would typically load these from environment variables
26+
operator_id_str = os.environ.get("OPERATOR_ID", "")
27+
operator_key_str = os.environ.get("OPERATOR_KEY", "")
28+
29+
try:
30+
operator_id = AccountId.from_string(operator_id_str)
31+
operator_key = PrivateKey.from_string(operator_key_str)
32+
except Exception as e:
33+
print(f"Error parsing operator credentials: {e}")
34+
return
35+
36+
client = Client()
37+
client.set_operator(operator_id, operator_key)
38+
39+
# Create a transaction that is likely to fail post-consensus
40+
# Here we try to associate a non-existent token
41+
42+
print("Creating transaction...")
43+
transaction = TokenAssociateTransaction() \
44+
.set_account_id(operator_id) \
45+
.add_token_id(TokenId(0,0,3)) \
46+
.freeze_with(client) \
47+
.sign(operator_key)
48+
49+
try:
50+
print("Executing transaction...")
51+
# execute() submits the transaction to the network and returns a receipt
52+
# Note: this does NOT automatically raise an exception if the transaction fails post-consensus.
53+
receipt = transaction.execute(client)
54+
print(f"Transaction submitted. ID: {receipt.transaction_id}")
55+
56+
# Check if the execution raised something other than SUCCESS
57+
# If not, we raise our custom ReceiptStatusError for handling.
58+
if receipt.status != ResponseCode.SUCCESS:
59+
raise ReceiptStatusError(receipt.status, receipt.transaction_id, receipt)
60+
# If we reach here, the transaction succeeded
61+
print("Transaction successful!")
62+
63+
# This exception is raised when the transaction raised something other than SUCCESS
64+
except ReceiptStatusError as e:
65+
print("\nCaught ReceiptStatusError!")
66+
print(f"Status: {e.status} ({ResponseCode(e.status).name})")
67+
print(f"Transaction ID: {e.transaction_id}")
68+
print("This error means the transaction reached consensus but failed logic execution.")
69+
70+
# Catch all for unexpected errors
71+
except Exception as e:
72+
print(f"\nAn unexpected error occurred: {e}")
73+
74+
if __name__ == "__main__":
75+
main()

0 commit comments

Comments
 (0)