Skip to content

[Feat] Add offline transaction methods#1

Open
rudolf-del wants to merge 5 commits intomasterfrom
feat-add-offline-transaction-methods
Open

[Feat] Add offline transaction methods#1
rudolf-del wants to merge 5 commits intomasterfrom
feat-add-offline-transaction-methods

Conversation

@rudolf-del
Copy link
Owner

@rudolf-del rudolf-del commented Jul 4, 2025

PR Type

Enhancement


Description

  • Add offline transaction building methods for TRX transfers

  • Add offline TRC-20 token transfer transaction building

  • Refactor timestamp and block reference utilities

  • Add Protocol Buffer definitions for transaction structures


Changes diagram

flowchart LR
  A["Client Code"] --> B["Transaction.build_offline()"]
  A --> C["Transaction.build_trc20_transfer_offline()"]
  B --> D["create_transaction_offline()"]
  C --> E["create_smart_contract_transaction_offline()"]
  D --> F["Protocol Buffer Transaction"]
  E --> F
  F --> G["JSON Transaction Dict"]
  G --> H["Unsigned Transaction"]
Loading

Changes walkthrough 📝

Relevant files
Tests
1 files
test_offline.py
Add comprehensive tests for offline transaction methods   
+93/-0   
Enhancement
4 files
async_tron.py
Refactor to use shared utilities and constants                     
+10/-13 
transaction.py
Core offline transaction building implementation                 
+263/-0 
tron.py
Add offline transaction class methods and refactor utilities
+161/-14
utils.py
Extract shared utility functions for timestamps and blocks
+37/-0   
Configuration changes
3 files
defaults.py
Add SIXTY_SECONDS constant for transaction expiration       
+5/-0     
.flake8
Update linting configuration with exclusions                         
+12/-0   
Makefile
Add Protocol Buffer generation target                                       
+8/-0     
Dependencies
4 files
tron_pb2.py
Generated Protocol Buffer Python bindings                               
+55/-0   
pyproject.toml
Add development dependencies for protobuf and testing       
+2/-0     
tron.proto
Protocol Buffer schema for TRON transactions                         
+153/-0 
tron_pb2.pyi
Type stubs for generated Protocol Buffer code                       
+375/-0 
Miscellaneous
1 files
types.py
Add TAddress type alias                                                                   
+1/-0     

Need help?
  • Type /help how to ... in the comments thread for any questions about Qodo Merge usage.
  • Check out the documentation for more information.
  • Summary by CodeRabbit

    • New Features

      • Added offline transaction creation for TRX and TRC-20 transfers, allowing unsigned transactions to be built without network access.
      • Introduced Protocol Buffers schema and generated Python classes for Tron blockchain transactions and contracts.
      • Added utility functions for timestamp and block reference extraction.
      • Added a Makefile target to generate Python gRPC files from proto definitions.
      • Added a new optional "offline" extra dependency including protobuf.
      • Added specific exception for missing protobuf module to improve error handling.
      • Introduced type alias for address strings to improve code clarity.
    • Bug Fixes

      • Replaced hardcoded expiration values with a centralized constant for consistency.
    • Chores

      • Updated development dependencies and linting exclusions.
      • Added new tests verifying offline transaction creation.
    • Documentation

      • Provided detailed type stubs for Protocol Buffers-generated classes.

    @rudolf-del
    Copy link
    Owner Author

    /describe

    @qodo-code-review
    Copy link

    PR Description updated to latest commit (5933846)

    @rudolf-del
    Copy link
    Owner Author

    @coderabbitai resume

    @coderabbitai
    Copy link

    coderabbitai bot commented Jul 4, 2025

    Walkthrough

    This update introduces offline transaction creation capabilities for the Tron blockchain by adding protobuf definitions, generated Python files, and utility modules. It refactors existing code to use new constants, utility functions, and type aliases, and adds comprehensive tests for offline transaction logic. Linting and build configurations are also updated.

    Changes

    File(s) Change Summary
    .flake8 Added exclude directive to ignore specific directories and files during linting.
    Makefile Added generate-proto target for generating Python gRPC files from tron.proto.
    pyproject.toml Added extra offline with protobuf; added dev dependencies grpcio-tools and freezegun.
    test-requirements.txt Added grpcio-tools and freezegun to test dependencies.
    tests/test_offline.py Added unit tests for offline transaction creation using frozen timestamps and expected JSON outputs.
    tests/utils.py Modified assertion on permission field structure in transaction checks; reformatted key set assertion.
    tronpy/async_tron.py
    tronpy/tron.py
    Refactored to use new constants and utility functions for timestamps and block references; removed magic numbers; added offline transaction methods.
    tronpy/defaults.py Added SIXTY_SECONDS constant for 60,000 milliseconds and protobuf missing error message constant.
    tronpy/types.py Introduced TAddress type alias for addresses as strings.
    tronpy/utils.py Added utility functions for current timestamp and block ID parsing.
    tronpy/proto/src/tron.proto Added Protocol Buffers schema for Tron transactions and contracts.
    tronpy/proto/src/tron_pb2.py
    tronpy/proto/src/tron_pb2.pyi
    Added generated Python protobuf classes and type stubs for Tron protocol messages.
    tronpy/proto/transaction.py Added functions for offline transaction creation and transaction ID calculation using protobuf.
    tronpy/proto/init.py Added module exporting offline transaction creation functions.
    tronpy/exceptions.py Added ProtobufImportError exception class to signal missing protobuf module.
    setup.py Added optional extra offline with protobuf dependency to setup configuration.

    Sequence Diagram(s)

    sequenceDiagram
        participant User
        participant Transaction
        participant ProtobufUtils
        participant tron_pb2
    
        User->>Transaction: build_offline(owner, to, amount, ref_block_id)
        Transaction->>ProtobufUtils: create_transaction_offline(...)
        ProtobufUtils->>tron_pb2: Build protobuf TransferContract
        ProtobufUtils->>ProtobufUtils: calculate_txid()
        ProtobufUtils-->>Transaction: Return transaction dict
        Transaction-->>User: Return Transaction instance
    
    Loading
    sequenceDiagram
        participant User
        participant Transaction
        participant ProtobufUtils
        participant tron_pb2
    
        User->>Transaction: build_trc20_transfer_offline(from, to, amount, contract, ref_block_id, fee_limit)
        Transaction->>ProtobufUtils: create_smart_contract_transaction_offline(...)
        ProtobufUtils->>tron_pb2: Build protobuf TriggerSmartContract
        ProtobufUtils->>ProtobufUtils: calculate_txid()
        ProtobufUtils-->>Transaction: Return transaction dict
        Transaction-->>User: Return Transaction instance
    
    Loading

    Poem

    In burrows deep, with code so neat,
    The rabbit crafts a Tron-sized feat.
    Protobufs bloom, transactions fly,
    Offline, unsigned, beneath the sky.
    With constants clear and types anew,
    The blockchain hops with something new!
    🐇✨

    ✨ Finishing Touches
    • 📝 Generate Docstrings

    🪧 Tips

    Chat

    There are 3 ways to chat with CodeRabbit:

    • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
      • I pushed a fix in commit <commit_id>, please review it.
      • Explain this complex logic.
      • Open a follow-up GitHub issue for this discussion.
    • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
      • @coderabbitai explain this code block.
      • @coderabbitai modularize this function.
    • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
      • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
      • @coderabbitai read src/utils.ts and explain its main purpose.
      • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
      • @coderabbitai help me debug CodeRabbit configuration file.

    Support

    Need help? Create a ticket on our support page for assistance with any issues or questions.

    Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

    CodeRabbit Commands (Invoked using PR comments)

    • @coderabbitai pause to pause the reviews on a PR.
    • @coderabbitai resume to resume the paused reviews.
    • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
    • @coderabbitai full review to do a full review from scratch and review all the files again.
    • @coderabbitai summary to regenerate the summary of the PR.
    • @coderabbitai generate docstrings to generate docstrings for this PR.
    • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
    • @coderabbitai resolve resolve all the CodeRabbit review comments.
    • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
    • @coderabbitai help to get help.

    Other keywords and placeholders

    • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
    • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
    • Add @coderabbitai anywhere in the PR title to generate the title automatically.

    CodeRabbit Configuration File (.coderabbit.yaml)

    • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
    • Please see the configuration documentation for more information.
    • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

    Documentation and Community

    • Visit our Documentation for detailed information on how to use CodeRabbit.
    • Join our Discord Community to get help, request features, and share feedback.
    • Follow us on X/Twitter for updates and announcements.

    @coderabbitai
    Copy link

    coderabbitai bot commented Jul 4, 2025

    ✅ Actions performed

    Reviews resumed.

    @rudolf-del
    Copy link
    Owner Author

    @coderabbitai generate docstrings

    @coderabbitai
    Copy link

    coderabbitai bot commented Jul 4, 2025

    ✅ Actions performed

    Docstrings generation started.

    coderabbitai bot added a commit that referenced this pull request Jul 4, 2025
    Docstrings generation was requested by @rudolf-del.
    
    * #1 (comment)
    
    The following files were modified:
    
    * `tests/test_offline.py`
    * `tronpy/async_tron.py`
    * `tronpy/defaults.py`
    * `tronpy/proto/src/tron_pb2.pyi`
    * `tronpy/proto/transaction.py`
    * `tronpy/tron.py`
    * `tronpy/utils.py`
    @coderabbitai
    Copy link

    coderabbitai bot commented Jul 4, 2025

    Note

    Generated docstrings for this pull request at #2

    Copy link

    @coderabbitai coderabbitai bot left a comment

    Choose a reason for hiding this comment

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

    Actionable comments posted: 4

    🔭 Outside diff range comments (2)
    tronpy/utils.py (1)

    12-14: Likewise, emit raw hash bytes & add basic validation

    -def get_ref_block_hash(ref_block_id: str) -> str:
    -    return ref_block_id[16:32]
    +def get_ref_block_hash(ref_block_id: str) -> bytes:
    +    """
    +    Extract first 8 bytes of block hash from block id.
    +    """
    +    if len(ref_block_id) != 64:
    +        raise ValueError("ref_block_id must be 32-byte hex string")
    +    return bytes.fromhex(ref_block_id[16:32])
    tronpy/tron.py (1)

    145-145: Fix variable name in isinstance check

    The code is checking json (which refers to the imported module) instead of the data parameter.

    -        if isinstance(json, str):
    +        if isinstance(data, str):
    🧹 Nitpick comments (7)
    tronpy/types.py (1)

    1-1: Consider using typing.NewType for stronger type-safety

    TAddress = str is convenient but provides no protection against accidentally passing arbitrary strings. Defining it as a NewType keeps runtime cost at zero while allowing static checkers to flag misuse:

    -from typing import Final
    -TAddress = str
    +from typing import NewType, Final
    +
    +# Tron address as Base58/hex string.
    +TAddress: Final = NewType("TAddress", str)

    This change is optional but will catch many category-errors for free.

    tronpy/utils.py (1)

    4-6: Add docstring & prefer time.time_ns() to eliminate rounding loss

    int(time.time() * 1000) truncates micro-seconds. Using time.time_ns() // 1_000_000 is equally portable and precise:

    -def current_timestamp() -> int:
    -    return int(time.time() * 1000)
    +def current_timestamp() -> int:
    +    """Return current epoch time in milliseconds."""
    +    return time.time_ns() // 1_000_000
    tronpy/defaults.py (1)

    36-37: Name constant to reflect units

    SIXTY_SECONDS = 60_000 is milliseconds but the name implies seconds. Recommend SIXTY_SECONDS_MS or ONE_MINUTE_MS to avoid mis-use:

    -SIXTY_SECONDS: typing.Final[int] = 60_000
    -"""Milliseconds in 60 seconds"""
    +ONE_MINUTE_MS: typing.Final[int] = 60_000  # milliseconds
    .flake8 (1)

    5-15: Duplicate .venv entry and trailing whitespace

    Cleaning this block removes redundancy and keeps config tidy:

     exclude=
         .circleci,
    -    .venv,
    +    .venv,
         .eggs,
         .git,
         .tox,
    -    .venv,
         build,
         dist,
         docs,
         .env
    -    
    +
    tests/test_offline.py (1)

    75-75: Consider extracting the encoded data for better readability.

    The long hex-encoded data string makes the test harder to read and understand.

    Consider extracting the encoded data to a constant with a descriptive comment:

    +# TRC-20 transfer(address,uint256) function call data for 1,000,000 tokens
    +TRC20_TRANSFER_DATA = "a9059cbb0000000000000000000000009ce29546a328107df7b685ed3183fa8ae70a46a700000000000000000000000000000000000000000000000000000000000f4240"
    
    -                            "data": "a9059cbb0000000000000000000000009ce29546a328107df7b685ed3183fa8ae70a46a700000000000000000000000000000000000000000000000000000000000f4240",  # noqa: E501
    +                            "data": TRC20_TRANSFER_DATA,
    tronpy/tron.py (1)

    220-367: Well-implemented offline transaction builders with excellent documentation

    The new methods provide comprehensive offline transaction creation capabilities with:

    • Detailed docstrings explaining all parameters and usage
    • Clear examples and references to external documentation
    • Proper delegation to protobuf-based implementation

    While static analysis flags the high argument count, this is reasonable for offline transaction construction where all details must be provided upfront.

    For future enhancements, consider implementing a builder pattern to manage the multiple parameters more elegantly, especially if more transaction types are added.

    tronpy/proto/src/tron_pb2.pyi (1)

    216-223: SUCESS typo mirrors upstream but hurts code-completion

    The enum value is spelled SUCESS (missing “C”). This typo exists in the official Tron proto, so the stub is technically correct, but it still leaks a misspelled symbol into user code.

    If you can’t fix the upstream proto, consider adding an alias inside the generated Python module so consumers can use the correctly-spelled SUCCESS while retaining wire compatibility:

    # in tron_pb2.py, after enum definition
    Transaction.Result.code.SUCCESS = Transaction.Result.code.SUCESS  # type: ignore[attr-defined]

    No change needed in the stub, just flagging the ergonomic issue.

    📜 Review details

    Configuration used: CodeRabbit UI
    Review profile: CHILL
    Plan: Pro

    📥 Commits

    Reviewing files that changed from the base of the PR and between 18318b2 and 5933846.

    📒 Files selected for processing (13)
    • .flake8 (1 hunks)
    • Makefile (1 hunks)
    • pyproject.toml (1 hunks)
    • tests/test_offline.py (1 hunks)
    • tronpy/async_tron.py (6 hunks)
    • tronpy/defaults.py (2 hunks)
    • tronpy/proto/src/tron.proto (1 hunks)
    • tronpy/proto/src/tron_pb2.py (1 hunks)
    • tronpy/proto/src/tron_pb2.pyi (1 hunks)
    • tronpy/proto/transaction.py (1 hunks)
    • tronpy/tron.py (8 hunks)
    • tronpy/types.py (1 hunks)
    • tronpy/utils.py (1 hunks)
    🧰 Additional context used
    🧬 Code Graph Analysis (1)
    tronpy/async_tron.py (3)
    tronpy/defaults.py (1)
    • conf_for_name (40-41)
    tronpy/utils.py (3)
    • current_timestamp (4-5)
    • get_ref_block_bytes (8-9)
    • get_ref_block_hash (12-13)
    tronpy/tron.py (1)
    • get_latest_solid_block_id (988-992)
    🪛 Buf (1.54.0)
    tronpy/proto/src/tron.proto

    1-1: Files must have a package defined.

    (PACKAGE_DEFINED)

    🪛 Pylint (3.3.7)
    tronpy/proto/transaction.py

    [error] 67-67: Module 'google.protobuf.any_pb2' has no 'Any' member

    (E1101)


    [refactor] 108-108: Too many arguments (6/5)

    (R0913)


    [refactor] 108-108: Too many positional arguments (6/5)

    (R0917)


    [refactor] 108-108: Too many local variables (21/15)

    (R0914)


    [error] 179-179: Module 'google.protobuf.any_pb2' has no 'Any' member

    (E1101)

    tronpy/tron.py

    [refactor] 303-303: Too many arguments (7/5)

    (R0913)


    [refactor] 303-303: Too many positional arguments (7/5)

    (R0917)

    🔇 Additional comments (12)
    pyproject.toml (1)

    44-45: Re-evaluate pinning very old freezegun version

    freezegun==0.3.4 dates back to 2018; current releases are 1.x with important bug-fixes (e.g. py3.11 compat, DST issues). Unless you have a hard constraint, bumping prevents avoidable test flakiness:

    -freezegun = "0.3.4"
    +freezegun = "^1.4"   # latest at time of writing

    Same for grpcio-tools; consider an upper cap rather than a patch-only pin to avoid silent ABI breaks.

    tronpy/async_tron.py (3)

    11-11: LGTM! Good refactoring with centralized utilities.

    The imports properly bring in the new constants and utility functions that eliminate code duplication.

    Also applies to: 31-31


    206-206: LGTM! Elimination of magic numbers.

    Replacing hardcoded 60_000 milliseconds with the named constant SIXTY_SECONDS improves readability and maintainability.

    Also applies to: 240-240, 358-358, 362-362


    209-211: LGTM! Centralized block reference utilities.

    Using get_ref_block_bytes and get_ref_block_hash utility functions eliminates code duplication and improves consistency across the codebase.

    Also applies to: 282-284

    tronpy/proto/src/tron.proto (1)

    38-153: LGTM! Comprehensive transaction message structure.

    The Transaction message definition properly captures all the necessary components for Tron blockchain transactions, including:

    • Extensive contract type enumeration
    • Proper nesting of contract, result, and raw data structures
    • Support for multiple signatures and results

    The structure aligns well with Tron's transaction format.

    tronpy/proto/src/tron_pb2.py (1)

    1-55: LGTM! Standard protobuf-generated code.

    This is properly generated Python protobuf code. The file header correctly indicates it's autogenerated and should not be manually edited. The structure follows standard protobuf Python bindings conventions.

    Ensure this file is regenerated using the Makefile target whenever the .proto file changes.

    tests/test_offline.py (2)

    8-45: LGTM! Well-structured deterministic test.

    The test properly uses freezegun to ensure deterministic timestamps and transaction IDs. The comprehensive assertion validates the complete offline transaction structure for TRX transfers.


    48-93: LGTM! Comprehensive smart contract transaction test.

    The test validates TRC-20 token transfer transaction creation with proper assertion of the encoded data payload and fee limit. The frozen time approach ensures reproducible results.

    tronpy/tron.py (1)

    196-201: Good refactoring to use centralized constants and utility functions

    The changes improve code maintainability by:

    • Using the SIXTY_SECONDS constant instead of hardcoded values
    • Replacing manual block ID slicing with dedicated utility functions
    tronpy/proto/transaction.py (1)

    136-160: Excellent implementation of ERC-20 ABI encoding

    The encoding logic correctly:

    • Removes Tron's address prefix to get the EVM-compatible address
    • Properly pads both address and amount to 32 bytes
    • Uses the correct function selector for transfer(address,uint256)

    The detailed comments with references to external documentation are particularly valuable.

    tronpy/proto/src/tron_pb2.pyi (2)

    7-12: Depending on google.protobuf.internal is brittle

    The stub imports _containers and _enum_type_wrapper from google.protobuf.internal.*.
    Those APIs are private and may change without notice between protobuf releases, breaking type-checking.

    If possible, prefer relying on google.protobuf.message.Message public helpers or ship a minimal local wrapper that exposes only the names you need, so upgrades do not silently break the stubs.


    59-68: Verify the lowercase authority message name

    Every other message type follows the conventional PascalCase (AccountId, TransferContract, …), but this one is declared as lowercase authority.
    Double-check the source .proto: if the original message is actually Authority, the stub will not match the generated implementation, causing import errors at runtime and mypy false positives.

    -class authority(_message.Message):
    +class Authority(_message.Message):

    Confirm the proto definition before renaming.

    Comment on lines 8 to 10
    def get_ref_block_bytes(ref_block_id: str) -> str:
    return ref_block_id[12:16]

    Copy link

    Choose a reason for hiding this comment

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

    ⚠️ Potential issue

    Return bytes rather than hex substring to match protobuf expectations

    ref_block_bytes in Tron transactions is raw bytes; returning a hex slice as str shifts the conversion burden downstream and risks double-encoding. Suggest:

    -def get_ref_block_bytes(ref_block_id: str) -> str:
    -    return ref_block_id[12:16]
    +def get_ref_block_bytes(ref_block_id: str) -> bytes:
    +    """
    +    Extract last 2 bytes of block height (Big-Endian) from 32-byte block id.
    +    """
    +    return bytes.fromhex(ref_block_id[12:16])
    📝 Committable suggestion

    ‼️ IMPORTANT
    Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

    Suggested change
    def get_ref_block_bytes(ref_block_id: str) -> str:
    return ref_block_id[12:16]
    def get_ref_block_bytes(ref_block_id: str) -> bytes:
    """
    Extract last 2 bytes of block height (Big-Endian) from 32-byte block id.
    """
    return bytes.fromhex(ref_block_id[12:16])
    🤖 Prompt for AI Agents
    In tronpy/utils.py around lines 8 to 10, the function get_ref_block_bytes
    currently returns a hex substring as a string, but it should return raw bytes to
    match protobuf expectations. Modify the function to extract the relevant slice
    from the input string, decode it from hex to bytes, and return the bytes object
    instead of a string.
    

    Comment on lines +1 to +4
    syntax = "proto3";

    import "google/protobuf/any.proto";

    Copy link

    Choose a reason for hiding this comment

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

    🛠️ Refactor suggestion

    Add package definition to prevent namespace conflicts.

    The protobuf file is missing a package definition, which can lead to naming conflicts and makes the generated code harder to organize.

    Add a package definition after the syntax declaration:

     syntax = "proto3";
    +
    +package tronpy;
     
     import "google/protobuf/any.proto";

    This will generate the Python code with proper module organization and prevent potential conflicts with other protobuf definitions.

    📝 Committable suggestion

    ‼️ IMPORTANT
    Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

    Suggested change
    syntax = "proto3";
    import "google/protobuf/any.proto";
    syntax = "proto3";
    package tronpy;
    import "google/protobuf/any.proto";
    🧰 Tools
    🪛 Buf (1.54.0)

    1-1: Files must have a package defined.

    (PACKAGE_DEFINED)

    🤖 Prompt for AI Agents
    In tronpy/proto/src/tron.proto at lines 1 to 4, the protobuf file lacks a
    package definition, which can cause namespace conflicts and disorganized
    generated code. Add a package declaration immediately after the syntax line to
    define a unique package name, ensuring proper module organization and avoiding
    naming collisions in the generated Python code.
    

    Copy link

    @coderabbitai coderabbitai bot left a comment

    Choose a reason for hiding this comment

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

    Actionable comments posted: 1

    🧹 Nitpick comments (1)
    tronpy/proto/transaction.py (1)

    136-264: Consider refactoring to reduce complexity

    The function correctly implements TRC-20 token transfer transaction creation with proper ABI encoding. However, it has high complexity with 6 parameters and many local variables.

    The ABI encoding logic for transfer(address,uint256) is correctly implemented:

    • Function selector 0xa9059cbb is correct
    • Address encoding properly removes Tron's 0x41 prefix and pads to 32 bytes
    • Amount encoding correctly converts to big-endian hex and pads

    Consider extracting the ABI encoding logic into a separate helper function to reduce complexity:

    +def _encode_transfer_call_data(to_address: str, amount: int) -> bytes:
    +    """Encode transfer(address,uint256) call data according to Ethereum ABI."""
    +    to_hex = to_hex_address(to_address)
    +    # Remove the 0x41 prefix (1-byte Tron address prefix) → 20-byte EVM
    +    to_evm = to_hex[2:]
    +    to_padded = to_evm.rjust(64, "0")
    +    amount_hex = hex(amount)[2:].rjust(64, "0")
    +    data_hex = "a9059cbb" + to_padded + amount_hex  # 4-byte selector + args
    +    return bytes.fromhex(data_hex)
    +
     def create_smart_contract_transaction_offline(
         from_address: str,
         to_address: str,
         amount: int,
         ref_block_id: str,
         fee_limit: int,
         contract_address: str,
     ) -> Transaction:
         # ... existing code ...
    -    # Encode `transfer(address,uint256)` call data section
    -    to_hex = to_hex_address(to_address)
    -    # Remove the 0x41 prefix (1-byte Tron address prefix) → 20-byte EVM
    -    to_evm = to_hex[2:]
    -    to_padded = to_evm.rjust(64, "0")
    -    amount_hex = hex(amount)[2:].rjust(64, "0")
    -    data_hex = "a9059cbb" + to_padded + amount_hex  # 4-byte selector + args
    -    data_bytes = bytes.fromhex(data_hex)
    +    data_bytes = _encode_transfer_call_data(to_address, amount)
    +    data_hex = data_bytes.hex()
    📜 Review details

    Configuration used: CodeRabbit UI
    Review profile: CHILL
    Plan: Pro

    📥 Commits

    Reviewing files that changed from the base of the PR and between 5933846 and 33eb470.

    📒 Files selected for processing (2)
    • tronpy/proto/transaction.py (1 hunks)
    • tronpy/utils.py (1 hunks)
    🚧 Files skipped from review as they are similar to previous changes (1)
    • tronpy/utils.py
    🧰 Additional context used
    🪛 Pylint (3.3.7)
    tronpy/proto/transaction.py

    [error] 95-95: Module 'google.protobuf.any_pb2' has no 'Any' member

    (E1101)


    [refactor] 136-136: Too many arguments (6/5)

    (R0913)


    [refactor] 136-136: Too many positional arguments (6/5)

    (R0917)


    [refactor] 136-136: Too many local variables (21/15)

    (R0914)


    [error] 212-212: Module 'google.protobuf.any_pb2' has no 'Any' member

    (E1101)

    🔇 Additional comments (4)
    tronpy/proto/transaction.py (4)

    1-17: LGTM: Clean imports and well-defined TypedDict

    The imports are appropriate for the module's functionality, and the Transaction TypedDict provides clear type annotations for the transaction structure.


    19-31: LGTM: Correct transaction ID calculation

    The calculate_txid function correctly implements the transaction ID calculation by hashing the serialized raw transaction data using SHA-256, which aligns with Tron's transaction ID specification.


    38-57: LGTM: Well-implemented TAPoS metadata extraction

    The _get_tapos_meta helper function correctly extracts TAPoS metadata using the appropriate utility functions and constants. The 60-second expiration time is reasonable for transaction validity.


    59-134: LGTM: Correct offline TRX transfer transaction creation

    The create_transaction_offline function correctly implements offline TRX transfer transaction creation. The protobuf usage and JSON conversion logic are properly implemented.

    contract=[
    tron_pb2.Transaction.Contract(
    type=tron_pb2.Transaction.Contract.ContractType.TransferContract,
    parameter=google.protobuf.any_pb2.Any(
    Copy link

    Choose a reason for hiding this comment

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

    💡 Verification agent

    🧩 Analysis chain

    Verify protobuf imports are properly configured

    The static analysis tool flags google.protobuf.any_pb2.Any as potentially missing, but this is likely a false positive since protobuf modules are dynamically generated.

    Please verify that the protobuf imports work correctly by running the following script:

    Also applies to: 212-212


    🏁 Script executed:

    #!/bin/bash
    # Description: Verify protobuf imports and dependencies are properly configured
    
    # Check if protobuf is installed and importable
    python -c "
    import sys
    try:
        import google.protobuf.any_pb2
        print('✓ google.protobuf.any_pb2 is importable')
        
        # Check if Any class exists
        any_instance = google.protobuf.any_pb2.Any()
        print('✓ Any class is accessible and can be instantiated')
        
        # Check if tron_pb2 is importable
        from tronpy.proto.src import tron_pb2
        print('✓ tron_pb2 is importable')
        
        # Check if required protobuf classes exist
        transaction = tron_pb2.Transaction()
        print('✓ tron_pb2.Transaction is accessible')
        
        print('All protobuf imports are working correctly')
    except ImportError as e:
        print(f'❌ Import error: {e}')
        sys.exit(1)
    except Exception as e:
        print(f'❌ Error: {e}')
        sys.exit(1)
    "

    Length of output: 909


    Ensure ‘base58’ Is Declared as a Dependency

    The protobuf imports themselves are valid, but from tronpy.proto.src import tron_pb2 currently fails with

    ImportError: No module named 'base58'  
    

    because the generated tron_pb2 code relies on the base58 package.

    Please add base58 to your project’s dependencies so that all protobuf modules import correctly.

    • Add base58 to requirements.txt or setup.py (or pyproject.toml)
    • After updating dependencies, verify with:

    pip install base58
    python - <<EOF
    from tronpy.proto.src import tron_pb2
    _ = tron_pb2.Transaction()
    print("✓ tron_pb2.Transaction is accessible")
    EOF

    Affected locations:

    • tronpy/proto/transaction.py:95
    • tronpy/proto/transaction.py:212
    🧰 Tools
    🪛 Pylint (3.3.7)

    [error] 95-95: Module 'google.protobuf.any_pb2' has no 'Any' member

    (E1101)

    🤖 Prompt for AI Agents
    In tronpy/proto/transaction.py around lines 95 and 212, the generated tron_pb2
    module requires the base58 package, which is not currently declared as a
    dependency. To fix this, add base58 to your project's dependency list in
    requirements.txt, setup.py, or pyproject.toml. Then, install the package and
    verify the import of tron_pb2.Transaction works without ImportError by running
    the provided test script.
    

    @qodo-code-review
    Copy link

    PR Reviewer Guide 🔍

    Here are some key observations to aid the review process:

    ⏱️ Estimated effort to review: 4 🔵🔵🔵🔵⚪
    🧪 PR contains tests
    🔒 No security concerns identified
    ⚡ Recommended focus areas for review

    Hardcoded Selector

    The function selector for transfer method is hardcoded as "a9059cbb". This should be computed dynamically using keccak256 hash to ensure correctness and maintainability.

    data_hex = "a9059cbb" + to_padded + amount_hex  # 4-byte selector + args
    data_bytes = bytes.fromhex(data_hex)
    Address Validation

    The address conversion logic assumes addresses are valid and properly formatted. Missing validation could lead to runtime errors with malformed addresses.

    to_hex = to_hex_address(to_address)
    # Remove the 0x41 prefix (1-byte Tron address prefix) → 20-byte EVM
    to_evm = to_hex[2:]
    to_padded = to_evm.rjust(64, "0")
    Generated Code

    This is auto-generated protobuf code that should not be manually edited. Verify the generation process and ensure the proto definitions are correct.

    # -*- coding: utf-8 -*-
    # Generated by the protocol buffer compiler.  DO NOT EDIT!
    # source: tron.proto
    # Protobuf Python Version: 4.25.1
    """Generated protocol buffer code."""
    from google.protobuf import descriptor as _descriptor
    from google.protobuf import descriptor_pool as _descriptor_pool
    from google.protobuf import symbol_database as _symbol_database
    from google.protobuf.internal import builder as _builder
    
    # @@protoc_insertion_point(imports)
    
    _sym_db = _symbol_database.Default()
    
    
    from google.protobuf import any_pb2 as google_dot_protobuf_dot_any__pb2  # noqa: F401, E402
    
    DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(
        b'\n\ntron.proto\x1a\x19google/protobuf/any.proto"M\n\x10TransferContract\x12\x15\n\rowner_address\x18\x01 \x01(\x0c\x12\x12\n\nto_address\x18\x02 \x01(\x0c\x12\x0e\n\x06\x61mount\x18\x03 \x01(\x03"\x95\x01\n\x14TriggerSmartContract\x12\x15\n\rowner_address\x18\x01 \x01(\x0c\x12\x18\n\x10\x63ontract_address\x18\x02 \x01(\x0c\x12\x12\n\ncall_value\x18\x03 \x01(\x03\x12\x0c\n\x04\x64\x61ta\x18\x04 \x01(\x0c\x12\x18\n\x10\x63\x61ll_token_value\x18\x05 \x01(\x03\x12\x10\n\x08token_id\x18\x06 \x01(\x03"*\n\tAccountId\x12\x0c\n\x04name\x18\x01 \x01(\x0c\x12\x0f\n\x07\x61\x64\x64ress\x18\x02 \x01(\x0c"A\n\tauthority\x12\x1b\n\x07\x61\x63\x63ount\x18\x01 \x01(\x0b\x32\n.AccountId\x12\x17\n\x0fpermission_name\x18\x02 \x01(\x0c"r\n\x11MarketOrderDetail\x12\x14\n\x0cmakerOrderId\x18\x01 \x01(\x0c\x12\x14\n\x0ctakerOrderId\x18\x02 \x01(\x0c\x12\x18\n\x10\x66illSellQuantity\x18\x03 \x01(\x03\x12\x17\n\x0f\x66illBuyQuantity\x18\x04 \x01(\x03"\xc3\x14\n\x0bTransaction\x12"\n\x08raw_data\x18\x01 \x01(\x0b\x32\x10.Transaction.raw\x12\x11\n\tsignature\x18\x02 \x03(\x0c\x12 \n\x03ret\x18\x05 \x03(\x0b\x32\x13.Transaction.Result\x1a\xaf\n\n\x08\x43ontract\x12\x30\n\x04type\x18\x01 \x01(\x0e\x32".Transaction.Contract.ContractType\x12\'\n\tparameter\x18\x02 \x01(\x0b\x32\x14.google.protobuf.Any\x12\x10\n\x08provider\x18\x03 \x01(\x0c\x12\x14\n\x0c\x43ontractName\x18\x04 \x01(\x0c\x12\x15\n\rPermission_id\x18\x05 \x01(\x05"\x88\t\n\x0c\x43ontractType\x12\x19\n\x15\x41\x63\x63ountCreateContract\x10\x00\x12\x14\n\x10TransferContract\x10\x01\x12\x19\n\x15TransferAssetContract\x10\x02\x12\x15\n\x11VoteAssetContract\x10\x03\x12\x17\n\x13VoteWitnessContract\x10\x04\x12\x19\n\x15WitnessCreateContract\x10\x05\x12\x16\n\x12\x41ssetIssueContract\x10\x06\x12\x19\n\x15WitnessUpdateContract\x10\x08\x12!\n\x1dParticipateAssetIssueContract\x10\t\x12\x19\n\x15\x41\x63\x63ountUpdateContract\x10\n\x12\x19\n\x15\x46reezeBalanceContract\x10\x0b\x12\x1b\n\x17UnfreezeBalanceContract\x10\x0c\x12\x1b\n\x17WithdrawBalanceContract\x10\r\x12\x19\n\x15UnfreezeAssetContract\x10\x0e\x12\x17\n\x13UpdateAssetContract\x10\x0f\x12\x1a\n\x16ProposalCreateContract\x10\x10\x12\x1b\n\x17ProposalApproveContract\x10\x11\x12\x1a\n\x16ProposalDeleteContract\x10\x12\x12\x18\n\x14SetAccountIdContract\x10\x13\x12\x12\n\x0e\x43ustomContract\x10\x14\x12\x17\n\x13\x43reateSmartContract\x10\x1e\x12\x18\n\x14TriggerSmartContract\x10\x1f\x12\x0f\n\x0bGetContract\x10 \x12\x19\n\x15UpdateSettingContract\x10!\x12\x1a\n\x16\x45xchangeCreateContract\x10)\x12\x1a\n\x16\x45xchangeInjectContract\x10*\x12\x1c\n\x18\x45xchangeWithdrawContract\x10+\x12\x1f\n\x1b\x45xchangeTransactionContract\x10,\x12\x1d\n\x19UpdateEnergyLimitContract\x10-\x12#\n\x1f\x41\x63\x63ountPermissionUpdateContract\x10.\x12\x14\n\x10\x43learABIContract\x10\x30\x12\x1b\n\x17UpdateBrokerageContract\x10\x31\x12\x1c\n\x18ShieldedTransferContract\x10\x33\x12\x1b\n\x17MarketSellAssetContract\x10\x34\x12\x1d\n\x19MarketCancelOrderContract\x10\x35\x12\x1b\n\x17\x46reezeBalanceV2Contract\x10\x36\x12\x1d\n\x19UnfreezeBalanceV2Contract\x10\x37\x12"\n\x1eWithdrawExpireUnfreezeContract\x10\x38\x12\x1c\n\x18\x44\x65legateResourceContract\x10\x39\x12\x1e\n\x1aUnDelegateResourceContract\x10:\x12\x1f\n\x1b\x43\x61ncelAllUnfreezeV2Contract\x10;\x1a\xbb\x07\n\x06Result\x12\x0b\n\x03\x66\x65\x65\x18\x01 \x01(\x03\x12%\n\x03ret\x18\x02 \x01(\x0e\x32\x18.Transaction.Result.code\x12\x37\n\x0b\x63ontractRet\x18\x03 \x01(\x0e\x32".Transaction.Result.contractResult\x12\x14\n\x0c\x61ssetIssueID\x18\x0e \x01(\t\x12\x17\n\x0fwithdraw_amount\x18\x0f \x01(\x03\x12\x17\n\x0funfreeze_amount\x18\x10 \x01(\x03\x12 \n\x18\x65xchange_received_amount\x18\x12 \x01(\x03\x12&\n\x1e\x65xchange_inject_another_amount\x18\x13 \x01(\x03\x12(\n exchange_withdraw_another_amount\x18\x14 \x01(\x03\x12\x13\n\x0b\x65xchange_id\x18\x15 \x01(\x03\x12 \n\x18shielded_transaction_fee\x18\x16 \x01(\x03\x12\x0f\n\x07orderId\x18\x19 \x01(\x0c\x12(\n\x0corderDetails\x18\x1a \x03(\x0b\x32\x12.MarketOrderDetail\x12\x1e\n\x16withdraw_expire_amount\x18\x1b \x01(\x03\x12Q\n\x18\x63\x61ncel_unfreezeV2_amount\x18\x1c \x03(\x0b\x32/.Transaction.Result.CancelUnfreezeV2AmountEntry\x1a=\n\x1b\x43\x61ncelUnfreezeV2AmountEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\x03:\x02\x38\x01"\x1e\n\x04\x63ode\x12\n\n\x06SUCESS\x10\x00\x12\n\n\x06\x46\x41ILED\x10\x01"\xc3\x02\n\x0e\x63ontractResult\x12\x0b\n\x07\x44\x45\x46\x41ULT\x10\x00\x12\x0b\n\x07SUCCESS\x10\x01\x12\n\n\x06REVERT\x10\x02\x12\x18\n\x14\x42\x41\x44_JUMP_DESTINATION\x10\x03\x12\x11\n\rOUT_OF_MEMORY\x10\x04\x12\x18\n\x14PRECOMPILED_CONTRACT\x10\x05\x12\x13\n\x0fSTACK_TOO_SMALL\x10\x06\x12\x13\n\x0fSTACK_TOO_LARGE\x10\x07\x12\x15\n\x11ILLEGAL_OPERATION\x10\x08\x12\x12\n\x0eSTACK_OVERFLOW\x10\t\x12\x11\n\rOUT_OF_ENERGY\x10\n\x12\x0f\n\x0bOUT_OF_TIME\x10\x0b\x12\x17\n\x13JVM_STACK_OVER_FLOW\x10\x0c\x12\x0b\n\x07UNKNOWN\x10\r\x12\x13\n\x0fTRANSFER_FAILED\x10\x0e\x12\x10\n\x0cINVALID_CODE\x10\x0f\x1a\xea\x01\n\x03raw\x12\x17\n\x0fref_block_bytes\x18\x01 \x01(\x0c\x12\x15\n\rref_block_num\x18\x03 \x01(\x03\x12\x16\n\x0eref_block_hash\x18\x04 \x01(\x0c\x12\x12\n\nexpiration\x18\x08 \x01(\x03\x12\x19\n\x05\x61uths\x18\t \x03(\x0b\x32\n.authority\x12\x0c\n\x04\x64\x61ta\x18\n \x01(\x0c\x12\'\n\x08\x63ontract\x18\x0b \x03(\x0b\x32\x15.Transaction.Contract\x12\x0f\n\x07scripts\x18\x0c \x01(\x0c\x12\x11\n\ttimestamp\x18\x0e \x01(\x03\x12\x11\n\tfee_limit\x18\x12 \x01(\x03\x62\x06proto3'  # noqa: E501
    )
    
    _globals = globals()
    _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
    _builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, "tron_pb2", _globals)
    if _descriptor._USE_C_DESCRIPTORS == False:  # noqa: E712
        DESCRIPTOR._options = None
        _globals["_TRANSACTION_RESULT_CANCELUNFREEZEV2AMOUNTENTRY"]._options = None
        _globals["_TRANSACTION_RESULT_CANCELUNFREEZEV2AMOUNTENTRY"]._serialized_options = b"8\001"
        _globals["_TRANSFERCONTRACT"]._serialized_start = 41
        _globals["_TRANSFERCONTRACT"]._serialized_end = 118
        _globals["_TRIGGERSMARTCONTRACT"]._serialized_start = 121
        _globals["_TRIGGERSMARTCONTRACT"]._serialized_end = 270
        _globals["_ACCOUNTID"]._serialized_start = 272
        _globals["_ACCOUNTID"]._serialized_end = 314
        _globals["_AUTHORITY"]._serialized_start = 316
        _globals["_AUTHORITY"]._serialized_end = 381
        _globals["_MARKETORDERDETAIL"]._serialized_start = 383
        _globals["_MARKETORDERDETAIL"]._serialized_end = 497
        _globals["_TRANSACTION"]._serialized_start = 500
        _globals["_TRANSACTION"]._serialized_end = 3127
        _globals["_TRANSACTION_CONTRACT"]._serialized_start = 605
        _globals["_TRANSACTION_CONTRACT"]._serialized_end = 1932
        _globals["_TRANSACTION_CONTRACT_CONTRACTTYPE"]._serialized_start = 772
        _globals["_TRANSACTION_CONTRACT_CONTRACTTYPE"]._serialized_end = 1932
        _globals["_TRANSACTION_RESULT"]._serialized_start = 1935
        _globals["_TRANSACTION_RESULT"]._serialized_end = 2890
        _globals["_TRANSACTION_RESULT_CANCELUNFREEZEV2AMOUNTENTRY"]._serialized_start = 2471
        _globals["_TRANSACTION_RESULT_CANCELUNFREEZEV2AMOUNTENTRY"]._serialized_end = 2532
        _globals["_TRANSACTION_RESULT_CODE"]._serialized_start = 2534
        _globals["_TRANSACTION_RESULT_CODE"]._serialized_end = 2564
        _globals["_TRANSACTION_RESULT_CONTRACTRESULT"]._serialized_start = 2567
        _globals["_TRANSACTION_RESULT_CONTRACTRESULT"]._serialized_end = 2890
        _globals["_TRANSACTION_RAW"]._serialized_start = 2893
        _globals["_TRANSACTION_RAW"]._serialized_end = 3127
    # @@protoc_insertion_point(module_scope)

    @qodo-code-review
    Copy link

    qodo-code-review bot commented Jul 4, 2025

    PR Code Suggestions ✨

    Explore these optional code suggestions:

    CategorySuggestion                                                                                                                                    Impact
    General
    Validate block ID length before processing

    The function should validate that ref_block_id has the expected length (64
    characters for a valid block ID) before extracting substrings. Invalid block IDs
    could cause incorrect TAPoS metadata or silent failures.

    tronpy/proto/transaction.py [38-56]

     def _get_tapos_meta(ref_block_id: str) -> typing.Tuple[str, str, int, int]:
         """
         Extracts TAPoS (Transaction as Proof of Stake) metadata from a given block ID.
     
         Parameters:
             ref_block_id (str): The block ID to extract TAPoS metadata from.
     
         Returns:
             tuple: A tuple containing:
                 - Reference block bytes as a hex string
                 - Reference block hash as a hex string
                 - Current timestamp in milliseconds
                 - Expiration timestamp in milliseconds (current time plus 60 seconds)
         """
    +    if len(ref_block_id) != 64:
    +        raise ValueError(f"Invalid block ID length: expected 64 characters, got {len(ref_block_id)}")
         ref_block_bytes_hex = get_ref_block_bytes(ref_block_id)
         ref_block_hash_hex = get_ref_block_hash(ref_block_id)
         timestamp = current_timestamp()
         expiration = timestamp + SIXTY_SECONDS
         return ref_block_bytes_hex, ref_block_hash_hex, timestamp, expiration
    • Apply / Chat
    Suggestion importance[1-10]: 7

    __

    Why: The suggestion correctly identifies a missing validation for ref_block_id length, which improves robustness by preventing potential IndexError exceptions.

    Medium
    • Update

    @rudolf-del rudolf-del force-pushed the feat-add-offline-transaction-methods branch from 33eb470 to a868703 Compare July 4, 2025 12:32
    Copy link

    @coderabbitai coderabbitai bot left a comment

    Choose a reason for hiding this comment

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

    Actionable comments posted: 0

    ♻️ Duplicate comments (1)
    tronpy/proto/transaction.py (1)

    95-95: Protobuf dependency issue already addressed

    Based on past review comments, the base58 dependency issue has been identified and should be resolved by ensuring base58 is properly declared in the project dependencies.

    🧹 Nitpick comments (1)
    tronpy/proto/transaction.py (1)

    136-263: Consider refactoring to reduce complexity

    The function correctly implements TRC-20 token transfer transaction creation with proper ABI encoding and comprehensive documentation. However, the high complexity could be improved.

    Issues identified:

    • 6 parameters (exceeds typical 5-parameter limit)
    • 21 local variables (exceeds typical 15-variable limit)
    • Function length of ~127 lines

    Suggested improvements:

    • Extract ABI encoding logic into a separate function
    • Consider using a dataclass or configuration object for parameters
    • Split the function into smaller, focused methods

    Proposed refactor for ABI encoding:

    +def _encode_transfer_abi(to_address: str, amount: int) -> bytes:
    +    """Encode transfer(address,uint256) call data per Ethereum ABI spec."""
    +    to_hex = to_hex_address(to_address)
    +    to_evm = to_hex[2:]  # Remove 0x41 prefix
    +    to_padded = to_evm.rjust(64, "0")
    +    amount_hex = hex(amount)[2:].rjust(64, "0")
    +    data_hex = "a9059cbb" + to_padded + amount_hex
    +    return bytes.fromhex(data_hex)
    +
     def create_smart_contract_transaction_offline(
         from_address: str,
         to_address: str,
         amount: int,
         ref_block_id: str,
         fee_limit: int,
         contract_address: str,
     ) -> Transaction:
    -    # ... existing ABI encoding logic ...
    +    data_bytes = _encode_transfer_abi(to_address, amount)

    This would reduce the function's complexity while maintaining the excellent documentation and functionality.

    📜 Review details

    Configuration used: CodeRabbit UI
    Review profile: CHILL
    Plan: Pro

    📥 Commits

    Reviewing files that changed from the base of the PR and between 33eb470 and a868703.

    📒 Files selected for processing (3)
    • Makefile (1 hunks)
    • tronpy/proto/transaction.py (1 hunks)
    • tronpy/utils.py (1 hunks)
    🚧 Files skipped from review as they are similar to previous changes (2)
    • Makefile
    • tronpy/utils.py
    🧰 Additional context used
    🪛 Pylint (3.3.7)
    tronpy/proto/transaction.py

    [error] 95-95: Module 'google.protobuf.any_pb2' has no 'Any' member

    (E1101)


    [refactor] 136-136: Too many arguments (6/5)

    (R0913)


    [refactor] 136-136: Too many positional arguments (6/5)

    (R0917)


    [refactor] 136-136: Too many local variables (21/15)

    (R0914)


    [error] 212-212: Module 'google.protobuf.any_pb2' has no 'Any' member

    (E1101)

    🔇 Additional comments (5)
    tronpy/proto/transaction.py (5)

    1-9: LGTM: Import structure is well-organized

    The imports are logically grouped and include all necessary dependencies for offline transaction creation. The static analysis warning about google.protobuf.any_pb2.Any appears to be a false positive based on the past review verification.


    12-16: LGTM: TypedDict provides clear transaction structure

    The Transaction TypedDict effectively defines the expected structure for offline transactions with proper type hints. The optional permission field correctly reflects the Tron transaction format.


    19-30: LGTM: Transaction ID calculation is correct

    The calculate_txid function correctly implements Tron's transaction ID calculation by hashing the serialized raw data with SHA-256. The implementation is clean and well-documented.


    38-56: LGTM: TAPoS metadata extraction is efficient

    The _get_tapos_meta helper function correctly extracts Transaction as Proof of Stake metadata and properly calculates the expiration timestamp. Good separation of concerns.


    59-133: LGTM: TRX transfer transaction creation is comprehensive

    The create_transaction_offline function correctly implements offline TRX transfer transaction creation. The protobuf construction is accurate, address handling is proper, and the return structure matches the expected transaction format.

    Copy link

    @coderabbitai coderabbitai bot left a comment

    Choose a reason for hiding this comment

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

    Actionable comments posted: 0

    🧹 Nitpick comments (1)
    tronpy/async_tron.py (1)

    317-386: Consider parameter consolidation for better maintainability.

    The build_trc20_transfer_offline method is well-implemented with excellent documentation, but the high parameter count flagged by static analysis could impact maintainability.

    Consider consolidating related parameters into a configuration object:

    @classmethod
    def build_trc20_transfer_offline(
        cls,
    -    from_address: TAddress,
    -    to_address: TAddress,
    -    amount: int,
    -    contract_address: TAddress,
    -    ref_block_id: str,
    -    fee_limit: int = DEFAULT_CONF["fee_limit"],
    +    transfer_params: dict,
    ) -> "AsyncTransaction":
        """Build an **unsigned** ``TriggerSmartContract`` transaction that calls the
        TRC-20 standard ``transfer`` function completely offline.
    
        Parameters
        ----------
    +    transfer_params : dict
    +        Dictionary containing: from_address, to_address, amount, 
    +        contract_address, ref_block_id, and optionally fee_limit

    However, keeping the current signature may be preferred for API consistency with the synchronous version.

    📜 Review details

    Configuration used: CodeRabbit UI
    Review profile: CHILL
    Plan: Pro

    📥 Commits

    Reviewing files that changed from the base of the PR and between a868703 and d4e784e.

    📒 Files selected for processing (2)
    • tests/test_offline.py (1 hunks)
    • tronpy/async_tron.py (7 hunks)
    🚧 Files skipped from review as they are similar to previous changes (1)
    • tests/test_offline.py
    🧰 Additional context used
    🧬 Code Graph Analysis (1)
    tronpy/async_tron.py (4)
    tronpy/defaults.py (1)
    • conf_for_name (40-41)
    tronpy/proto/transaction.py (2)
    • create_smart_contract_transaction_offline (136-263)
    • create_transaction_offline (59-133)
    tronpy/utils.py (3)
    • current_timestamp (4-11)
    • get_ref_block_bytes (14-24)
    • get_ref_block_hash (27-37)
    tronpy/tron.py (4)
    • build_offline (221-300)
    • txid (50-52)
    • build_trc20_transfer_offline (303-367)
    • fee_limit (411-414)
    🪛 Pylint (3.3.7)
    tronpy/async_tron.py

    [refactor] 318-318: Too many arguments (7/5)

    (R0913)


    [refactor] 318-318: Too many positional arguments (7/5)

    (R0917)

    🔇 Additional comments (4)
    tronpy/async_tron.py (4)

    11-11: LGTM: Clean import organization.

    The import additions properly bring in the centralized constants, utility functions, and protobuf transaction creation capabilities needed for the new offline functionality.

    Also applies to: 30-30, 32-32


    207-207: Excellent refactoring to use centralized constant.

    Replacing hardcoded 60_000 millisecond values with the SIXTY_SECONDS constant improves maintainability and ensures consistency across the codebase.

    Also applies to: 398-398, 516-516, 520-520


    210-210: Good refactoring to eliminate code duplication.

    Replacing manual string slicing with the utility functions get_ref_block_bytes and get_ref_block_hash centralizes the TAPoS metadata extraction logic and reduces the chance of errors.

    Also applies to: 212-212, 440-440, 442-442


    231-315: Well-implemented offline TRX transfer method.

    The build_offline method provides a clean interface for creating unsigned TRX transfer transactions without network calls. The comprehensive docstring clearly explains the purpose, parameters, return value, and usage examples.

    Copy link

    @coderabbitai coderabbitai bot left a comment

    Choose a reason for hiding this comment

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

    Actionable comments posted: 1

    📜 Review details

    Configuration used: CodeRabbit UI
    Review profile: CHILL
    Plan: Pro

    📥 Commits

    Reviewing files that changed from the base of the PR and between d4e784e and a6f3d0b.

    📒 Files selected for processing (3)
    • pyproject.toml (2 hunks)
    • setup.py (1 hunks)
    • test-requirements.txt (1 hunks)
    ✅ Files skipped from review due to trivial changes (1)
    • test-requirements.txt
    🚧 Files skipped from review as they are similar to previous changes (1)
    • pyproject.toml

    ]

    extras_hdwallet = {"mnemonic": ["mnemonic==0.20"]}
    extras_hdwallet = {"mnemonic": ["mnemonic==0.20"], "offline": ["protobuf"]}
    Copy link

    Choose a reason for hiding this comment

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

    🛠️ Refactor suggestion

    Add version constraint for protobuf dependency.

    The protobuf dependency lacks version constraints, which creates inconsistency with the existing mnemonic dependency pattern and could lead to compatibility issues, security vulnerabilities, and reproducibility problems.

    Apply this diff to add appropriate version constraints:

    -extras_hdwallet = {"mnemonic": ["mnemonic==0.20"], "offline": ["protobuf"]}
    +extras_hdwallet = {"mnemonic": ["mnemonic==0.20"], "offline": ["protobuf>=3.20.0,<5.0.0"]}

    Consider using a version range that's compatible with the protobuf files generated in this PR to ensure stability and predictable behavior.

    📝 Committable suggestion

    ‼️ IMPORTANT
    Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

    Suggested change
    extras_hdwallet = {"mnemonic": ["mnemonic==0.20"], "offline": ["protobuf"]}
    extras_hdwallet = {"mnemonic": ["mnemonic==0.20"], "offline": ["protobuf>=3.20.0,<5.0.0"]}
    🤖 Prompt for AI Agents
    In setup.py at line 22, the protobuf dependency in extras_hdwallet lacks a
    version constraint, unlike the mnemonic dependency which specifies
    "mnemonic==0.20". To fix this, add an appropriate version range constraint to
    the protobuf dependency that matches the protobuf files generated in this PR,
    ensuring compatibility and stability. Update the extras_hdwallet dictionary to
    include this version constraint for protobuf.
    

    Copy link

    @coderabbitai coderabbitai bot left a comment

    Choose a reason for hiding this comment

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

    Actionable comments posted: 0

    🧹 Nitpick comments (1)
    tronpy/defaults.py (1)

    41-41: Remove redundant None parameter from dict.get()

    The None parameter is redundant since dict.get() returns None by default when the key is not found.

    -    return ALL.get(name, None)
    +    return ALL.get(name)
    📜 Review details

    Configuration used: CodeRabbit UI
    Review profile: CHILL
    Plan: Pro

    📥 Commits

    Reviewing files that changed from the base of the PR and between a6f3d0b and 188606c.

    📒 Files selected for processing (8)
    • tests/test_offline.py (1 hunks)
    • tests/utils.py (2 hunks)
    • tronpy/async_tron.py (8 hunks)
    • tronpy/defaults.py (2 hunks)
    • tronpy/exceptions.py (1 hunks)
    • tronpy/proto/__init__.py (1 hunks)
    • tronpy/proto/transaction.py (1 hunks)
    • tronpy/tron.py (9 hunks)
    ✅ Files skipped from review due to trivial changes (2)
    • tronpy/exceptions.py
    • tronpy/proto/init.py
    🚧 Files skipped from review as they are similar to previous changes (3)
    • tests/test_offline.py
    • tronpy/tron.py
    • tronpy/async_tron.py
    🧰 Additional context used
    🧬 Code Graph Analysis (1)
    tronpy/proto/transaction.py (4)
    tronpy/exceptions.py (1)
    • ProtobufImportError (69-74)
    tronpy/keys/__init__.py (2)
    • to_raw_address (109-111)
    • to_address (229-230)
    tronpy/utils.py (3)
    • current_timestamp (4-11)
    • get_ref_block_bytes (14-24)
    • get_ref_block_hash (27-37)
    tronpy/tron.py (3)
    • Transaction (107-378)
    • expiration (418-420)
    • fee_limit (422-425)
    🪛 Ruff (0.11.9)
    tronpy/defaults.py

    41-41: Use ALL.get(name) instead of ALL.get(name, None)

    Replace ALL.get(name, None) with ALL.get(name)

    (SIM910)

    🔇 Additional comments (10)
    tronpy/defaults.py (2)

    36-37: Well-defined constant with proper typing

    The SIXTY_SECONDS constant is appropriately typed as typing.Final[int] and clearly documented. This improves code maintainability by replacing magic numbers with a named constant.


    44-46: Consistent error message handling

    The PROTOBUF_NOT_INSTALLED_ERROR_MESSAGE constant ensures consistent error messaging across the codebase when protobuf dependencies are missing.

    tronpy/proto/transaction.py (6)

    6-11: Proper protobuf import error handling

    The try-except block correctly handles protobuf import failures using the custom ProtobufImportError exception, which provides better error categorization than generic ImportError.


    18-22: Well-structured TypedDict definition

    The Transaction TypedDict provides clear type hints for the transaction structure, improving code maintainability and IDE support.


    25-36: Correct transaction ID calculation

    The calculate_txid function properly computes the transaction ID using SHA-256 hash of the serialized raw transaction data, which aligns with TRON protocol specifications.


    175-199: Comprehensive ABI encoding implementation

    The smart contract call data encoding follows the Ethereum ABI specification correctly:

    • Uses proper function selector (0xa9059cbb for transfer(address,uint256))
    • Handles address conversion from TRON format to EVM format
    • Properly pads arguments to 32-byte words
    • Includes helpful comments explaining the encoding process

    65-139: Robust offline transaction creation

    The create_transaction_offline function correctly:

    • Converts addresses to raw format for protobuf
    • Extracts TAPoS metadata using utility functions
    • Builds the protobuf transaction structure
    • Returns a properly formatted transaction dictionary

    142-269: Comprehensive TRC-20 transaction builder

    The create_smart_contract_transaction_offline function provides a complete implementation for offline TRC-20 token transfers with proper:

    • Address conversions
    • ABI encoding for the transfer function
    • TAPoS metadata extraction
    • Protobuf transaction construction
    • Fee limit handling
    tests/utils.py (2)

    14-18: Updated permission structure for offline transactions

    The permission structure assertion has been updated to expect a detailed dictionary instead of None, which aligns with the new offline transaction functionality. The structure includes proper keys (keys, threshold, permission_name) that match the protobuf-based transaction format.


    30-35: Improved code formatting

    The multi-line set literal formatting improves readability while maintaining the same functionality.

    Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

    Projects

    None yet

    Development

    Successfully merging this pull request may close these issues.

    2 participants