Skip to content

feat: Add optional receipt waiting to Transaction.execute() - #1769

Merged
exploreriii merged 19 commits into
hiero-ledger:mainfrom
manishdait:feat/remove-get-receipt
Mar 3, 2026
Merged

feat: Add optional receipt waiting to Transaction.execute()#1769
exploreriii merged 19 commits into
hiero-ledger:mainfrom
manishdait:feat/remove-get-receipt

Conversation

@manishdait

Copy link
Copy Markdown
Contributor

Description:
This PR updates the Transaction.execute() method to include a wait_for_receipt parameter to remove get_receipt() from executing while being backward compatible.

  • When wait_for_receipt=True (default), the method returns the transaction receipt after execution.
  • When wait_for_receipt=False, the method returns the TransactionResponse immediately without waiting for receipt.

Related issue(s):

Fixes #398

Notes for reviewer:

Checklist

  • Documented (Code comments, README, etc.)
  • Tested (unit, integration, etc.)

@codecov

codecov Bot commented Feb 9, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

Impacted file tree graph

@@            Coverage Diff             @@
##             main    #1769      +/-   ##
==========================================
+ Coverage   93.35%   93.53%   +0.18%     
==========================================
  Files         141      141              
  Lines        9121     9143      +22     
==========================================
+ Hits         8515     8552      +37     
+ Misses        606      591      -15     
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@github-actions

Copy link
Copy Markdown

Hi, this is MergeConflictBot.
Your pull request cannot be merged because it contains merge conflicts.

Please resolve these conflicts locally and push the changes.

Quick Fix for CHANGELOG.md Conflicts

If your conflict is only in CHANGELOG.md, you can resolve it easily using the GitHub web editor:

  1. Click on the "Resolve conflicts" button in the PR
  2. Accept both changes (keep both changelog entries)
  3. Click "Mark as resolved"
  4. Commit the merge

For all other merge conflicts, please read:

Thank you for contributing!

@manishdait
manishdait force-pushed the feat/remove-get-receipt branch 4 times, most recently from a84e840 to 0a32369 Compare February 12, 2026 07:46
@manishdait
manishdait marked this pull request as ready for review February 12, 2026 08:04
@manishdait
manishdait requested review from a team as code owners February 12, 2026 08:04
@coderabbitai

coderabbitai Bot commented Feb 12, 2026

Copy link
Copy Markdown
Contributor

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

Adds an optional wait_for_receipt flag to Transaction.execute() (default True) to return either a TransactionReceipt or TransactionResponse; extends TransactionResponse with query-building and fetch methods for receipts/records; and propagates wait_for_receipt through chunked multi-part submissions, examples, and tests.

Changes

Cohort / File(s) Summary
Core transaction API
src/hiero_sdk_python/transaction/transaction.py, src/hiero_sdk_python/transaction/transaction_response.py
Added wait_for_receipt parameter to Transaction.execute() and updated return type to Union[TransactionReceipt, TransactionResponse]. Added TransactionResponse.get_receipt_query(), get_receipt(client, timeout), get_record_query(), and get_record(client, timeout); updated typings/imports and internal return logic.
Consensus / Topic submit
src/hiero_sdk_python/consensus/topic_message_submit_transaction.py
Added wait_for_receipt handling and new execute_all() returning per-chunk lists of TransactionReceipt or TransactionResponse; execute() delegates to execute_all() and returns the first element for compatibility.
File append (chunking)
src/hiero_sdk_python/file/file_append_transaction.py
Added execute_all() and wait_for_receipt to execute(); per-chunk build/sign/execute loop now returns a list of per-chunk receipts or responses; adjusted TYPE_CHECKING imports and return types.
Examples
examples/transaction/transaction_without_wait_for_receipt.py
New example demonstrating wait_for_receipt=False submission and subsequent use of TransactionResponse.get_receipt() and get_record().
Unit tests
tests/unit/transaction_test.py, tests/unit/transaction_response_test.py, tests/unit/file_append_transaction_test.py, tests/unit/topic_message_submit_transaction_test.py
Added/updated unit tests for execute behavior with and without wait_for_receipt, TransactionResponse query-building and retrieval flows, and chunked submit flows asserting per-chunk execution and correct return types.
Integration tests
tests/integration/transaction_e2e_test.py
New end-to-end tests covering default (wait) and non-wait flows, receipt/record retrieval via both direct and query-based paths, and multi-chunk topic submissions in both modes.
Changelog
CHANGELOG.md
Documented wait_for_receipt option and new TransactionResponse retrieval methods.

Sequence Diagram(s)

sequenceDiagram
    actor User
    participant Client
    participant Transaction
    participant TransactionResponse
    participant Query
    participant Network

    rect rgba(100,150,200,0.5)
    Note over User,Network: Execute with wait_for_receipt=True (default)
    User->>Transaction: execute(client, wait_for_receipt=True)
    Transaction->>Network: submit transaction
    Network-->>Transaction: TransactionResponse
    Transaction->>TransactionResponse: get_receipt(client)
    TransactionResponse->>Query: build & execute receipt query
    Query->>Network: fetch receipt
    Network-->>Query: TransactionReceipt
    Query-->>TransactionResponse: TransactionReceipt
    TransactionResponse-->>Transaction: TransactionReceipt
    Transaction-->>User: TransactionReceipt
    end

    rect rgba(150,200,100,0.5)
    Note over User,Network: Execute with wait_for_receipt=False
    User->>Transaction: execute(client, wait_for_receipt=False)
    Transaction->>Network: submit transaction
    Network-->>Transaction: TransactionResponse
    Transaction-->>User: TransactionResponse (immediate)
    Note over User: Later: retrieve receipt on-demand
    User->>TransactionResponse: get_receipt(client)
    TransactionResponse->>Query: build & execute receipt query
    Query->>Network: fetch receipt
    Network-->>Query: TransactionReceipt
    Query-->>TransactionResponse: TransactionReceipt
    TransactionResponse-->>User: TransactionReceipt
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely summarizes the main change: adding an optional wait_for_receipt parameter to Transaction.execute().
Description check ✅ Passed The description accurately explains the purpose and behavior of the wait_for_receipt parameter, including default behavior and the two execution modes.
Linked Issues check ✅ Passed The PR fully addresses issue #398 by implementing an optional wait_for_receipt parameter (defaulting to True) that allows execute() to return immediately without waiting for network confirmation when set to False.
Out of Scope Changes check ✅ Passed All changes are directly related to implementing the wait_for_receipt parameter across Transaction, TopicMessageSubmitTransaction, FileAppendTransaction, related responses, and comprehensive test coverage.
Docstring Coverage ✅ Passed Docstring coverage is 98.25% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Tip

Try Coding Plans. Let us write the prompt for your AI agent so you can ship faster (with fewer bugs).
Share your feedback on Discord.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 8

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/hiero_sdk_python/transaction/transaction.py (1)

333-382: ⚠️ Potential issue | 🟡 Minor

Optional[bool] allows None, which silently behaves like False.

wait_for_receipt: Optional[bool] = True accepts None, and if None: is falsy — so execute(client, wait_for_receipt=None) would skip receipt retrieval without any warning, which may surprise callers. Use bool = True instead.

Proposed fix
     def execute(
         self, 
         client: "Client", 
         timeout: Optional[Union[int, float]] = None, 
-        wait_for_receipt: Optional[bool] = True
+        wait_for_receipt: bool = True
     ) -> Union["TransactionReceipt", "TransactionResponse"]:

Comment thread CHANGELOG.md Outdated
Comment thread examples/transaction/transaction_without_wait_for_receipt.py Outdated
Comment thread src/hiero_sdk_python/transaction/transaction_response.py
Comment thread tests/integration/transaction_e2e_test.py Outdated
Comment thread tests/unit/transaction_response_test.py
Comment thread tests/unit/transaction_test.py
Comment thread tests/unit/transaction_test.py Outdated
Comment thread tests/unit/transaction_test.py Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

Comment thread examples/transaction/transaction_without_wait_for_receipt.py
Comment thread tests/integration/transaction_e2e_test.py
Comment thread tests/integration/transaction_e2e_test.py
Comment thread tests/unit/transaction_response_test.py Outdated
Comment thread tests/unit/transaction_test.py
@exploreriii

Copy link
Copy Markdown
Contributor

Nice! looks pretty good, will look at this in more detail over next few days

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 13

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/hiero_sdk_python/consensus/topic_message_submit_transaction.py (1)

289-361: 🛠️ Refactor suggestion | 🟠 Major

Significant code duplication with FileAppendTransaction.execute / execute_all.

The executeexecute_all delegation pattern, including the chunk loop (clear frozen state, re-freeze, re-sign, call super().execute(...), collect responses), is nearly identical between TopicMessageSubmitTransaction and FileAppendTransaction. Consider extracting the shared multi-chunk execution logic into the base Transaction class or a mixin to avoid divergence.

Comment thread src/hiero_sdk_python/consensus/topic_message_submit_transaction.py
Comment thread src/hiero_sdk_python/consensus/topic_message_submit_transaction.py Outdated
Comment thread src/hiero_sdk_python/consensus/topic_message_submit_transaction.py
Comment thread src/hiero_sdk_python/file/file_append_transaction.py Outdated
Comment thread src/hiero_sdk_python/file/file_append_transaction.py Outdated
Comment thread tests/unit/file_append_transaction_test.py
Comment thread tests/unit/file_append_transaction_test.py
Comment thread tests/unit/topic_message_submit_transaction_test.py
Comment thread tests/unit/topic_message_submit_transaction_test.py
Comment thread tests/unit/topic_message_submit_transaction_test.py

@AntonioCeppellini AntonioCeppellini left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM! Nice work @manishdait :D

@github-actions

Copy link
Copy Markdown

Hi @manishdait,

This pull request has had no commit activity for 10 days. Are you still working on it?
To keep the PR active, you can:

  • Push a new commit.
  • Comment /working on the linked issue (not this PR).

If you're no longer working on this, please comment /unassign on the linked issue to release it for others. Otherwise, this PR may be closed due to inactivity.

Reach out on discord or join our office hours if you need assistance.

From the Python SDK Team

@Dosik13 Dosik13 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i left some typos to fix :D

Comment thread tests/integration/transaction_e2e_test.py Outdated
Comment thread src/hiero_sdk_python/transaction/transaction_response.py Outdated
Comment thread tests/integration/transaction_e2e_test.py
Comment thread tests/unit/transaction_response_test.py Outdated
Comment thread src/hiero_sdk_python/consensus/topic_message_submit_transaction.py
@manishdait
manishdait force-pushed the feat/remove-get-receipt branch from 34575e4 to f75e7ed Compare February 26, 2026 06:46

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/hiero_sdk_python/file/file_append_transaction.py (1)

379-399: ⚠️ Potential issue | 🟠 Major

Chunk transaction IDs are regenerated inside the chunk loop, causing ID drift across chunks.

execute_all() calls freeze_with(client) every iteration after clearing state. Since freeze_with() rebuilds _transaction_ids from the current transaction_id, later chunks no longer follow the originally generated sequence.

Proposed fix
-        # Generate transaction IDs for all chunks
-        self._transaction_ids = []
-        base_timestamp = self.transaction_id.valid_start
-
-        for i in range(self.get_required_chunks()):
-            if i == 0:
-                # First chunk uses the original transaction ID
-                chunk_transaction_id = self.transaction_id
-            else:
-                # Subsequent chunks get incremented timestamps
-                # Add i nanoseconds to space out chunks
-                chunk_valid_start = timestamp_pb2.Timestamp(
-                    seconds=base_timestamp.seconds,
-                    nanos=base_timestamp.nanos + i
-                )
-                chunk_transaction_id = TransactionId(
-                    account_id=self.transaction_id.account_id,
-                    valid_start=chunk_valid_start
-                )
-            self._transaction_ids.append(chunk_transaction_id)
+        if not self._transaction_ids:
+            self._transaction_ids = []
+            base_timestamp = self.transaction_id.valid_start
+            for i in range(self.get_required_chunks()):
+                if i == 0:
+                    chunk_transaction_id = self.transaction_id
+                else:
+                    chunk_valid_start = timestamp_pb2.Timestamp(
+                        seconds=base_timestamp.seconds,
+                        nanos=base_timestamp.nanos + i
+                    )
+                    chunk_transaction_id = TransactionId(
+                        account_id=self.transaction_id.account_id,
+                        valid_start=chunk_valid_start
+                    )
+                self._transaction_ids.append(chunk_transaction_id)

As per coding guidelines, “freeze_with() correctly generates sequential TransactionIds for each chunk.”

♻️ Duplicate comments (8)
examples/transaction/transaction_without_wait_for_receipt.py (1)

19-25: ⚠️ Potential issue | 🟡 Minor

The generated account key is discarded, making the created account hard to use afterward.

build_transaction() creates a PrivateKey but never returns or displays it. Users copying this example lose access to the new account credentials.

Proposed fix
 def build_transaction():
     """
     Build a new AccountCreateTransaction with a generated private key
     and a minimal initial balance.
     """
     key = PrivateKey.generate()
-    return AccountCreateTransaction().set_key_without_alias(key).set_initial_balance(1)
+    tx = AccountCreateTransaction().set_key_without_alias(key).set_initial_balance(1)
+    return tx, key
@@
-        tx = build_transaction()
+        tx, new_account_key = build_transaction()
+        print(f"Generated new account private key: {new_account_key.to_string()}")

As per coding guidelines, “examples work verbatim for users who copy-paste them.”

src/hiero_sdk_python/file/file_append_transaction.py (1)

345-347: ⚠️ Potential issue | 🟠 Major

execute() can return None for empty payloads, violating its declared return contract.

With set_contents(b""), get_required_chunks() becomes 0, execute_all() returns [], and Line 347 returns None. That breaks the annotated/public API contract (TransactionReceipt | TransactionResponse).

Proposed fix
         responses = self.execute_all(client, timeout, wait_for_receipt)
-        return responses[0] if responses else None
+        if not responses:
+            raise RuntimeError("No chunk responses were produced.")
+        return responses[0]
-        return math.ceil(len(self.contents) / self.chunk_size)
+        return max(1, math.ceil(len(self.contents) / self.chunk_size))

As per coding guidelines, “Public API contracts … are user-facing.”

src/hiero_sdk_python/consensus/topic_message_submit_transaction.py (1)

310-313: ⚠️ Potential issue | 🟡 Minor

Avoid the None fallback in execute() to keep the runtime contract strict.

Line 312 returns None on empty responses, but the method contract is TransactionReceipt | TransactionResponse. Prefer failing loudly instead of returning an undocumented type.

Proposed fix
         responses = self.execute_all(client, timeout, wait_for_receipt)
-        return responses[0] if responses else None
+        if not responses:
+            raise RuntimeError("No chunk responses were produced.")
+        return responses[0]
tests/unit/topic_message_submit_transaction_test.py (3)

301-304: 🧹 Nitpick | 🔵 Trivial

Remove try/except pytest.fail(...) wrappers around execute_all.

These wrappers reduce traceback quality and make failures harder to diagnose; let pytest surface the original exception directly.

Proposed simplification
-        try:
-            receipts = tx.execute_all(client)
-        except Exception as e:
-            pytest.fail(f"Should not raise exception, but raised: {e}")
+        receipts = tx.execute_all(client)
...
-        try:
-            receipts = tx.execute_all(client)
-        except Exception as e:
-            pytest.fail(f"Should not raise exception, but raised: {e}")
+        receipts = tx.execute_all(client)

As per coding guidelines, "Tests must provide useful error messages when they fail for future debugging."

Also applies to: 342-345


372-375: ⚠️ Potential issue | 🟡 Minor

Strengthen non-wait execute assertions for compatibility guarantees.

Line 374 currently checks only type; add negative-type and key public-attribute assertions to guard behavior drift.

Proposed assertion strengthening
         response = tx.execute(client, wait_for_receipt=False)

         assert isinstance(response, TransactionResponse)
+        assert not isinstance(response, TransactionReceipt), "Expected TransactionResponse, not TransactionReceipt"
+        assert response.transaction is tx, "Response should reference the submitted transaction"
+        assert response.validate_status is True, "Response should preserve validate_status default"

As per coding guidelines, "Assert return types where relevant" and "Assert public attributes exist."


431-433: ⚠️ Potential issue | 🟡 Minor

Assert expected list length before indexing responses.

Line 432 indexes responses[0] without asserting size, which produces less actionable failures.

Proposed fix
         responses = tx.execute_all(client, wait_for_receipt=False)

         assert isinstance(responses, list)
+        assert len(responses) == 1, "Single-chunk message should produce exactly one response"
         assert isinstance(responses[0], TransactionResponse)

As per coding guidelines, "Tests must provide useful error messages when they fail for future debugging."

tests/integration/transaction_e2e_test.py (2)

19-25: ⚠️ Potential issue | 🟠 Major

create_transaction() prevents account cleanup by discarding the private key.

Without returning/storing the generated key, teardown cannot reliably delete created accounts, causing long-term testnet state leakage.

Suggested refactor direction
 def create_transaction():
     """Create a minimal valid AccountCreateTransaction for integration tests."""
-    return (
-        AccountCreateTransaction()
-        .set_key_without_alias(PrivateKey.generate())
-        .set_initial_balance(1)
-    )
+    key = PrivateKey.generate()
+    tx = (
+        AccountCreateTransaction()
+        .set_key_without_alias(key)
+        .set_initial_balance(1)
+    )
+    return tx, key

As per coding guidelines, "Are accounts, tokens, and allowances properly cleaned up to avoid state leakage?"


48-49: ⚠️ Potential issue | 🟡 Minor

Assert transaction_id is populated before equality comparison.

Line 48 can pass even if both IDs are unset/default; assert non-null (and ideally non-default fields) first.

Proposed assertion hardening
     assert response.transaction is tx
+    assert response.transaction_id is not None, "Expected a populated transaction_id from execute()"
     assert response.transaction_id == tx.transaction_id
     assert response.validate_status is True

As per coding guidelines, "Tests should assert observable network behavior, not just SUCCESS."


ℹ️ Review info

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 34575e4 and f75e7ed.

📒 Files selected for processing (11)
  • CHANGELOG.md
  • examples/transaction/transaction_without_wait_for_receipt.py
  • src/hiero_sdk_python/consensus/topic_message_submit_transaction.py
  • src/hiero_sdk_python/file/file_append_transaction.py
  • src/hiero_sdk_python/transaction/transaction.py
  • src/hiero_sdk_python/transaction/transaction_response.py
  • tests/integration/transaction_e2e_test.py
  • tests/unit/file_append_transaction_test.py
  • tests/unit/topic_message_submit_transaction_test.py
  • tests/unit/transaction_response_test.py
  • tests/unit/transaction_test.py

Comment thread CHANGELOG.md
Comment thread tests/integration/transaction_e2e_test.py
Comment thread tests/unit/file_append_transaction_test.py
Comment thread tests/unit/topic_message_submit_transaction_test.py

@exploreriii exploreriii left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @manishdait please rebase and LGTM

Comment thread src/hiero_sdk_python/consensus/topic_message_submit_transaction.py Outdated
Signed-off-by: Manish Dait <daitmanish88@gmail.com>
Signed-off-by: Manish Dait <daitmanish88@gmail.com>
Signed-off-by: Manish Dait <daitmanish88@gmail.com>
Signed-off-by: Manish Dait <daitmanish88@gmail.com>
Signed-off-by: Manish Dait <daitmanish88@gmail.com>
Signed-off-by: Manish Dait <daitmanish88@gmail.com>
Signed-off-by: Manish Dait <daitmanish88@gmail.com>
Signed-off-by: Manish Dait <daitmanish88@gmail.com>
Signed-off-by: Manish Dait <daitmanish88@gmail.com>
Signed-off-by: Manish Dait <daitmanish88@gmail.com>
Signed-off-by: Manish Dait <daitmanish88@gmail.com>
chore: move changelog entry
Signed-off-by: Manish Dait <daitmanish88@gmail.com>
chore: misssing return types
Signed-off-by: Manish Dait <daitmanish88@gmail.com>
Signed-off-by: Manish Dait <daitmanish88@gmail.com>
Signed-off-by: Manish Dait <daitmanish88@gmail.com>
Signed-off-by: Manish Dait <daitmanish88@gmail.com>
Signed-off-by: Manish Dait <daitmanish88@gmail.com>
Signed-off-by: Manish Dait <daitmanish88@gmail.com>
Signed-off-by: Manish Dait <daitmanish88@gmail.com>
@manishdait
manishdait force-pushed the feat/remove-get-receipt branch from 1ad23e6 to 0c6c063 Compare March 3, 2026 13:52
@exploreriii
exploreriii merged commit 6ac3e4e into hiero-ledger:main Mar 3, 2026
20 checks passed
@exploreriii

Copy link
Copy Markdown
Contributor

Thank you, @manishdait !

@manishdait
manishdait deleted the feat/remove-get-receipt branch March 5, 2026 07:11
mizoz pushed a commit to mizoz/hiero-sdk-python that referenced this pull request Mar 10, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Remove getReceipt from execution

5 participants