|
| 1 | +# Transaction Lifecycle in the Python SDK |
| 2 | + |
| 3 | +This guide explains the typical lifecycle of executing a transaction using the Hedera Python SDK. Transactions are requests to change the state of the Hedera network, such as creating accounts, transferring HBAR, or minting tokens. Understanding the lifecycle helps you avoid common pitfalls and ensures your transactions succeed. |
| 4 | + |
| 5 | +## Overview |
| 6 | + |
| 7 | +A typical transaction follows this flow: |
| 8 | + |
| 9 | +1. **Construct** the transaction |
| 10 | +2. **Freeze** the transaction |
| 11 | +3. **Sign** the transaction |
| 12 | +4. **Execute** the transaction |
| 13 | +5. **Check the receipt** |
| 14 | + |
| 15 | +The order matters because each step builds on the previous one. Skipping or reordering steps can cause errors. |
| 16 | + |
| 17 | +## 1. Construct the Transaction |
| 18 | + |
| 19 | +Start by creating a transaction object and populating it with the necessary data. You can use either Pythonic syntax (constructor arguments) or method chaining (fluent API). |
| 20 | + |
| 21 | +### Pythonic Syntax |
| 22 | +```python |
| 23 | +from hiero_sdk_python import TokenAssociateTransaction |
| 24 | + |
| 25 | +transaction = TokenAssociateTransaction( |
| 26 | + account_id=account_id, |
| 27 | + token_ids=[token_id] |
| 28 | +) |
| 29 | +``` |
| 30 | + |
| 31 | +### Method Chaining |
| 32 | +```python |
| 33 | +transaction = ( |
| 34 | + TokenAssociateTransaction() |
| 35 | + .set_account_id(account_id) |
| 36 | + .add_token_id(token_id) |
| 37 | +) |
| 38 | +``` |
| 39 | + |
| 40 | +This step collects all information for the transaction body. Fields can still be modified at this point. |
| 41 | + |
| 42 | +## 2. Freeze the Transaction |
| 43 | + |
| 44 | +Freezing finalizes the transaction payload and makes it immutable. It sets the transaction ID, node ID, and builds the protobuf body. |
| 45 | + |
| 46 | +```python |
| 47 | +transaction.freeze_with(client) |
| 48 | +``` |
| 49 | + |
| 50 | +- **Why freeze?** Hedera requires a consistent payload for signing and execution. Freezing prevents accidental changes. |
| 51 | +- **When to freeze?** Always before signing. Some transactions auto-freeze during execution, but manual freezing is recommended for clarity. |
| 52 | +- **What happens if you don't freeze?** Signing or executing may fail or behave unexpectedly. |
| 53 | + |
| 54 | +## 3. Sign the Transaction |
| 55 | + |
| 56 | +Hedera uses cryptographic signatures for authorization. Sign with the required keys (e.g., operator key, admin keys). |
| 57 | + |
| 58 | +```python |
| 59 | +transaction.sign(account_private_key) |
| 60 | +``` |
| 61 | + |
| 62 | +- **Who signs?** The operator (client account) often signs automatically, but additional keys may be needed (e.g., supply key for minting). |
| 63 | +- **Multiple signatures?** Call `.sign()` multiple times if required. |
| 64 | +- **Order?** Sign after freezing, as the payload must be finalized. |
| 65 | + |
| 66 | +## 4. Execute the Transaction |
| 67 | + |
| 68 | +Submit the transaction to the Hedera network. This returns a `TransactionReceipt` indicating the network has processed it. |
| 69 | + |
| 70 | +```python |
| 71 | +receipt = transaction.execute(client) |
| 72 | +``` |
| 73 | + |
| 74 | +- **Does this guarantee success?** No! It only confirms receipt. The network processes it asynchronously. |
| 75 | +- **What if you skip signing?** Execution will fail with an authorization error. |
| 76 | + |
| 77 | +## 5. Check the Receipt |
| 78 | + |
| 79 | +Fetch and verify the transaction receipt to confirm processing. |
| 80 | + |
| 81 | +```python |
| 82 | +# In this SDK `execute(client)` returns the receipt directly: |
| 83 | +receipt = transaction.execute(client) |
| 84 | + |
| 85 | +if receipt.status != ResponseCode.SUCCESS: |
| 86 | + print(f"Transaction failed: {ResponseCode(receipt.status).name}") |
| 87 | +else: |
| 88 | + print("Transaction successful!") |
| 89 | +``` |
| 90 | + |
| 91 | +- **Why check?** Always inspect `receipt.status` returned by `execute(client)`. The `execute` call confirms the network received your transaction; the receipt contains the final processing result. Your Python code will not automatically raise for Hedera-level failures — you must check the receipt to detect them. |
| 92 | + |
| 93 | +- **Common failure examples (from `src/hiero_sdk_python/response_code.py`):** |
| 94 | + - `INSUFFICIENT_ACCOUNT_BALANCE` — payer/account lacks sufficient funds |
| 95 | + - `INSUFFICIENT_TX_FEE` — submitted fee was too low to cover processing |
| 96 | + - `INVALID_SIGNATURE` — missing or incorrect cryptographic signature |
| 97 | + - `INVALID_PAYER_SIGNATURE` — payer's signature is invalid |
| 98 | + - `TRANSACTION_EXPIRED` — transaction timestamp/duration expired |
| 99 | + - `INVALID_TOKEN_ID` — supplied token ID does not exist |
| 100 | + - `TOKEN_NOT_ASSOCIATED_TO_ACCOUNT` — attempted token operation on an unassociated account |
| 101 | + - `TOKEN_ALREADY_ASSOCIATED_TO_ACCOUNT` — duplicate association attempt |
| 102 | + - `FAIL_BALANCE`, `FAIL_FEE` — generic failure categories |
| 103 | + |
| 104 | +- **Important:** These are examples only — many other ResponseCodes exist. Always consult `src/hiero_sdk_python/response_code.py` for the full list and handle codes relevant to your workflow. |
| 105 | + |
| 106 | +```python |
| 107 | +# Example: check and handle failure explicitly |
| 108 | +receipt = transaction.execute(client) |
| 109 | + |
| 110 | +if receipt.status != ResponseCode.SUCCESS: |
| 111 | + # react to failure: log, retry, or surface to caller |
| 112 | + print(f"Transaction processed with failure: {ResponseCode(receipt.status).name}") |
| 113 | + # e.g. inspect receipt fields for more info, then handle appropriately |
| 114 | +else: |
| 115 | + print("Transaction successful!") |
| 116 | +``` |
| 117 | + |
| 118 | +## Complete Example |
| 119 | + |
| 120 | +Here's a clean example associating a token with an account: |
| 121 | + |
| 122 | +```python |
| 123 | +import sys |
| 124 | +from hiero_sdk_python import TokenAssociateTransaction, ResponseCode |
| 125 | + |
| 126 | +def associate_token_with_account(client, account_id, account_private_key, token_id): |
| 127 | + """Associate a token with an account.""" |
| 128 | + |
| 129 | + receipt = ( |
| 130 | + TokenAssociateTransaction() |
| 131 | + .set_account_id(account_id) |
| 132 | + .add_token_id(token_id) |
| 133 | + .freeze_with(client) # Lock fields |
| 134 | + .sign(account_private_key) # Authorize |
| 135 | + .execute(client) # Submit to Hedera |
| 136 | + ) |
| 137 | + |
| 138 | + if receipt.status != ResponseCode.SUCCESS: |
| 139 | + print(f"Token association failed: {ResponseCode(receipt.status).name}") |
| 140 | + sys.exit(1) |
| 141 | + |
| 142 | + print("Token associated successfully!") |
| 143 | +``` |
| 144 | + |
| 145 | +For more examples, see `examples/tokens/token_grant_kyc.py` or `examples/tokens/token_associate.py`. |
| 146 | + |
| 147 | +## Correct vs. Incorrect Order |
| 148 | + |
| 149 | +### Correct |
| 150 | +```python |
| 151 | +transaction = TokenAssociateTransaction().set_account_id(account_id).freeze_with(client).sign(key).execute(client) |
| 152 | +``` |
| 153 | + |
| 154 | +### Incorrect (Signing before freezing) |
| 155 | +```python |
| 156 | +transaction = TokenAssociateTransaction().set_account_id(account_id).sign(key).freeze_with(client) # Error: Cannot sign unfrozen transaction |
| 157 | +``` |
| 158 | + |
| 159 | +### Incorrect (Modifying after freezing) |
| 160 | +```python |
| 161 | +transaction = TokenAssociateTransaction().set_account_id(account_id).freeze_with(client) |
| 162 | +transaction.set_account_id(new_id) # Error: Transaction is immutable |
| 163 | +``` |
| 164 | + |
| 165 | +## Flow Diagram |
| 166 | + |
| 167 | +``` |
| 168 | +[Construct] → [Freeze] → [Sign] → [Execute] → [Check Receipt] |
| 169 | + ↓ ↓ ↓ ↓ ↓ |
| 170 | + Build data Finalize Authorize Submit Verify status |
| 171 | +``` |
| 172 | + |
| 173 | +## Common Pitfalls |
| 174 | + |
| 175 | +- **Forgetting to freeze:** Leads to runtime errors during signing. |
| 176 | +- **Wrong signer:** Use the correct key (e.g., supply key for minting). |
| 177 | +- **Ignoring receipt:** Always check status; don't assume success. |
| 178 | +- **Auto-freeze/sign:** Works for simple transactions but can hide issues in complex ones. |
| 179 | +- **Order dependency:** Construct → Freeze → Sign → Execute → Receipt. |
| 180 | + |
| 181 | +For more details, refer to the SDK documentation or community calls on [Discord](https://github.com/hiero-ledger/hiero-sdk-python/blob/main/docs/discord.md). |
0 commit comments