diff --git a/.coderabbit.yaml b/.coderabbit.yaml index 9d540fe11..41740b075 100644 --- a/.coderabbit.yaml +++ b/.coderabbit.yaml @@ -220,7 +220,7 @@ reviews: - Call `_make_request_header()` exactly once - Populate protobuf fields via `_to_proto()` helpers - Avoid manual `QueryHeader` mutation - + Subclasses MUST NOT: - Set `responseType` directly - Inject payment logic @@ -281,7 +281,7 @@ reviews: - Payment-safe - Execution-consistent - Strictly aligned with Hedera query semantics - + # TRANSACTION REVIEW INSTRUCTIONS - CORE FOUNDATION (MOST CRITICAL MODULE) transaction_review_instructions: &transaction_review_instructions | You are acting as a senior maintainer and security architect reviewing the **core transaction module** in the hiero-sdk-python project. @@ -603,7 +603,7 @@ reviews: - Changes encoding/decoding results for same inputs - Modifies ContractId EVM alias ↔ numeric mapping - Alters payable amount (amount vs initial_balance) semantics - + If breaking: require deprecation warnings (e.g. warnings.warn(..., DeprecationWarning)), migration docs, dual-behavior tests. ---------------------------------------------------------- REVIEW FOCUS 2 — ABI ENCODING/DECODING SAFETY (CRITICAL) @@ -674,7 +674,7 @@ reviews: - Protobuf-aligned with Hedera/Hiero - Gas/value safe - Deterministic and user-protective - + # NODES REVIEW INSTRUCTIONS — PRIVILEGED NODE LIFECYCLE OPERATIONS node_review_instructions: &node_review_instructions | You are acting as a senior maintainer and security architect reviewing the nodes module @@ -799,7 +799,7 @@ reviews: - Certificate and endpoint handling edge cases are tested Missing coverage should be flagged clearly. - + ---------------------------------------------------------- EXPLICIT NON-GOALS ---------------------------------------------------------- @@ -823,7 +823,7 @@ reviews: file_review_instructions: &file_review_instructions | You are acting as a senior maintainer reviewing file service-related code in the hiero-sdk-python project. - + This includes: - File transactions (FileCreateTransaction, FileUpdateTransaction, FileAppendTransaction, FileDeleteTransaction) - File query (FileContentsQuery, FileInfoQuery) @@ -843,19 +843,19 @@ reviews: (HIGH SENSITIVITY) ---------------------------------------------------------- Public API contracts for FileId, FileInfo, and file transactions are user-facing. - + Verify that: - Setter method signatures (e.g., set_contents, set_file_memo) stay backward compatible. - Method chaining (returning self) is preserved. - FileId constructors and string representations don't break existing use cases. - + If breaking changes are necessary, they must be explicit and deprecation warnings should be added. ---------------------------------------------------------- REVIEW FOCUS 2 — CHUNKING SEMANTICS (HIGH VERIFICATION) ---------------------------------------------------------- Specific to FileAppendTransaction, which natively handles chunking. - + Verify that: - freeze_with() correctly generates sequential TransactionIds for each chunk. - valid_start timestamps for each chunk are spaced out correctly (e.g., incremented by at least 1 nanosecond). @@ -868,7 +868,7 @@ reviews: REVIEW FOCUS 3 — MEMO HANDLING ---------------------------------------------------------- Nuanced memo distinction across the file module: - + Verify that: - The distinction between file_memo (the metadata attribute of the file) and transaction_memo (the note on the transaction itself) is respected. - FileCreateTransaction handles file_memo as a native string. @@ -880,7 +880,7 @@ reviews: REVIEW FOCUS 4 — TRANSACTION BASE CLASS CONTRACT ---------------------------------------------------------- All file transaction classes MUST inherit from Transaction. - + Required implementations: - _build_proto_body() - build_transaction_body() @@ -895,7 +895,7 @@ reviews: REVIEW FOCUS 5 — PROTOBUF ALIGNMENT ---------------------------------------------------------- Serialization and deserialization MUST map directly to Hedera protobufs. - + Verify that: - fileCreate, fileUpdate, fileAppend, and fileDelete fields map exactly to their respective protobuf bodies. - Null-safe conversions are handling optional properties safely. @@ -944,7 +944,7 @@ reviews: - PrivateKey (Ed25519 and ECDSA(secp256k1)) - PublicKey (Ed25519 and ECDSA(secp256k1)) - EvmAddress - + NOTE: - Review focus levels indicate areas requiring careful verification. - They do NOT imply severity or urgency. @@ -1168,7 +1168,7 @@ reviews: Flag if rule ordering breaks CODEOWNERS precedence (last match wins): - A broad rule (e.g. *) appears after more specific rules - A broader path (e.g. /.github/**) appears after a more specific subpath rule - (e.g. /.github/workflows/**) + (e.g. /.github/workflows/**) - A sensitive path (e.g. /.github/workflows/**) is followed by a broader rule that would override it ---------------------------------------------------------- @@ -1232,17 +1232,17 @@ reviews: - path: "src/hiero_sdk_python/**/*.py" instructions: | ## Protobuf Schema Alignment Review - + ### Step 1 — Detect protobuf imports - + Scan the file for all imports matching this pattern: `from hiero_sdk_python.hapi. import _pb2` - + For each import found, skip `_grpc` stubs (those end in `_pb2_grpc`) — only process `_pb2` message modules. - + ### Step 2 — Construct the canonical schema URL - + For each `_pb2` import: 1. Extract the path segment after `hapi` (e.g. `services` from `hiero_sdk_python.hapi.services`) @@ -1250,34 +1250,34 @@ reviews: (e.g. `transaction_record` from `transaction_record_pb2`) 3. Build the canonical URL: `https://github.com/hashgraph/hedera-protobufs/blob/v0.72.0-rc.2//.proto` - + **Example:** ``` Import: from hiero_sdk_python.hapi.services import transaction_record_pb2 Schema: https://github.com/hashgraph/hedera-protobufs/blob/v0.72.0-rc.2/services/transaction_record.proto ``` - + ### Step 3 — Compare the SDK class against the proto schema - + For each SDK class that calls `_from_proto` or `_to_proto` using messages from the detected proto module, verify the following: - + #### 3a. Field coverage - Every non-deprecated proto field SHOULD have a corresponding SDK attribute. - Flag any proto field that is present in the schema but missing from the SDK class (either as a dataclass field or handled in `_from_proto`). - Flag any SDK attribute that references a proto field name that does not exist in the schema. - + #### 3b. Field name mapping - Proto field names use `snake_case` (e.g. `ethereum_hash`) or `camelCase` (e.g. `transactionHash`, `consensusTimestamp`). Verify that `_from_proto` accesses the correct proto field name, not a renamed/misspelled version. - Verify `_to_proto` sets the correct proto field name. - + #### 3c. Field type alignment Check that each SDK field type correctly represents the proto type: - + | Proto type | Expected SDK type | |------------------------|----------------------------------------------| | `string` | `str \| None` or `str` | @@ -1288,7 +1288,7 @@ reviews: | `MessageType` | corresponding SDK wrapper class or `None` | | `repeated MessageType` | `list[SdkType]` | | `repeated scalar` | `list[scalar]` | - + #### 3d. `oneof` field handling - Identify all `oneof` blocks in the proto schema for the message. - Verify that `_to_proto` does NOT set more than one field from the same @@ -1299,32 +1299,32 @@ reviews: - Common `oneof` groups to watch for in `TransactionRecord`: - `oneof body { contractCallResult, contractCreateResult }` (fields 7, 8) - `oneof entropy { prng_bytes, prng_number }` (fields 19, 20) - + #### 3e. Bytes field normalization - Proto `bytes` fields default to `b""` (empty bytes) when unset, not `None`. - If the SDK models the field as `bytes | None`, verify that `_from_proto` uses `proto. or None` to normalize empty bytes to `None`. - Conversely, if `_to_proto` sets a bytes field, verify it guards against setting `None` (which would cause a protobuf type error). - + #### 3f. `_from_proto` / `_to_proto` symmetry - Every field parsed in `_from_proto` SHOULD also be serialized in `_to_proto`, and vice versa, unless the field is intentionally read-only (e.g. server-set fields that clients never send). - Flag any asymmetry that is not accompanied by a code comment explaining why. - + #### 3g. Repeated field default values - SDK attributes representing proto `repeated` fields MUST default to an empty list (`field(default_factory=list)`), never `None`. - Flag any `repeated` field that defaults to `None`. - + #### 3h. Type annotation consistency - The codebase uses `X | None` union syntax (Python 3.10+). Flag any use of `Optional[X]` from `typing` in newly added or modified code, as it is inconsistent with the established style. - + ### Step 4 — Report format - + For each issue found, report: - **File and line**: where the issue occurs - **Proto field**: the canonical proto field name and number from the schema @@ -1333,7 +1333,7 @@ reviews: Wrong default | Style inconsistency - **Description**: concise explanation of the discrepancy - **Suggested fix**: the corrected code snippet - + If no issues are found for a file, state: All mapped proto fields align with the schema at ``. - path: "src/hiero_sdk_python/tokens/**/*.py" @@ -1348,39 +1348,39 @@ reviews: instructions: | You are acting as a senior maintainer reviewing SDK examples. Your goal is to ensure examples work verbatim for users who copy-paste them. - **Priority 1 - Correctness**: + **Priority 1 - Correctness**: - Verify transaction lifecycle chain (construction -> freeze_with -> sign -> execute). - Ensure `freeze_with(client)` is called BEFORE signing. - Validate that methods referenced actually exist in the `hiero_sdk_python` codebase. - Ensure response validation checks `receipt.status` against `ResponseCode` enums (e.g., `ResponseCode.SUCCESS`). - **Priority 2 - Transaction Lifecycle**: + **Priority 2 - Transaction Lifecycle**: - Check method chaining logic. - Verify correct signing order (especially for multi-sig). - Ensure explicit `.execute(client)` calls. - Verify response property extraction (e.g., using `.token_id`, `.account_id`, `.serial_numbers`). - Ensure error handling uses `ResponseCode(receipt.status).name` for clarity. - **Priority 3 - Naming & Clarity**: + **Priority 3 - Naming & Clarity**: - Enforce role-based naming: `operator_id`/`_key`, `treasury_account_id`/`_key`, `receiver_id`/`_key`. - Use `_id` suffix for AccountId and `_key` suffix for PrivateKey variables. - Validate negative examples explicitly check for failure codes (e.g., `TOKEN_HAS_NO_PAUSE_KEY`). - Ensure logical top-to-bottom flow without ambiguity. - **Priority 4 - Consistency**: + **Priority 4 - Consistency**: - Verify standard patterns: `def main()`, `if __name__ == "__main__":`, `load_dotenv()`. - - **IMPORT RULES**: + - **IMPORT RULES**: 1. Accept both top-level imports (e.g., `from hiero_sdk_python import PrivateKey`) and fully qualified imports (e.g., `from hiero_sdk_python.crypto.private_key import PrivateKey`). 2. STRICTLY validate that the import path actually exists in the project structure. Compare against other files in `/examples` or your knowledge of the SDK file tree. 3. Flag hallucinations immediately (e.g., `hiero_sdk_python.keys` does not exist). - Check for `try-except` blocks with `sys.exit(1)` for critical failures. - **Priority 5 - User Experience**: + **Priority 5 - User Experience**: - Ensure comments explain SDK usage patterns (for users, not contributors). - Avoid nitpicking functional code. - Suggest type hints or docstrings only if they significantly improve clarity. - **Philosophy**: + **Philosophy**: - Examples are copied by users - prioritize explicitness over brevity. - Avoid suggestions that `ruff` or linters would catch. - Be concise, technical, and opinionated. @@ -1402,7 +1402,7 @@ reviews: - Assert public attributes exist (e.g., `assert hasattr(obj, 'account_id')`). - Assert return types where relevant (e.g., `assert isinstance(result, AccountId)`). - Assert fluent setters return `self` (e.g., `assert tx.set_memo("test") is tx`). - - Assert backward-compatible defaults are maintained. + - Assert backward-compatible defaults are maintained. - If a breaking change is introduced, tests must assert deprecation behavior and test old behavior until removal. **PRIORITY 2 - Constructor & Setter Behavior**: @@ -1411,11 +1411,11 @@ reviews: - Verify that setters validate input and raise appropriate exceptions. - Test that getters return expected values after construction/setting. - **PRIORITY 3 - Comprehensive Coverage**: + **PRIORITY 3 - Comprehensive Coverage**: - Unit tests should be extensive - test even if we don't expect users to use it currently. - Cover happy paths AND unhappy paths/edge cases. - Test boundary conditions, null/None values, empty collections, etc. - - Avoid brittle ordering assertions unless order is part of the contract. + - Avoid brittle ordering assertions unless order is part of the contract. **PRIORITY 4 - No Mocks for Non-Existent Modules**: - All imports must reference actual SDK modules - no hallucinated paths. @@ -1425,15 +1425,15 @@ reviews: **PRIORITY 5 - Test Framework Philosophy**: - Prefer repetitive but clear tests over abstracted helper functions. - Some core functionality may warrant helper files (considered an exception). - - Discourage custom helper functions; prefer pytest fixtures when shared setup is needed. + - Discourage custom helper functions; prefer pytest fixtures when shared setup is needed. - Prefer testing real functionality over mocked behavior. **AVOID**: - Linter or formatting feedback (leave that to ruff/pre-commit). - Nitpicking minor stylistic issues unless they impact maintainability. - - Overly abstracted test helpers that obscure what's being tested. + - Overly abstracted test helpers that obscure what's being tested. - **PHILOSOPHY**: + **PHILOSOPHY**: - Unit tests protect our future selves - be defensive and forward-looking. - Tests should be readable by SDK developers: clear names, brief docstrings, key inline comments. - When tests fail, we should immediately know what broke and why. @@ -1460,17 +1460,17 @@ reviews: - One major behavior per test (clear focus). - Tests should be readable: clear names, brief docstrings, key inline comments. - Minimal abstraction layers - prefer clarity over DRY. - - Is the file too monolithic? Flag if tests should be split into smaller modules. + - Is the file too monolithic? Flag if tests should be split into smaller modules. - Are helper functions good candidates for pytest fixtures or shared utilities? **PRIORITY 3 - Isolation & Cleanup**: - Local account creation over operator reuse (avoid state pollution). - Are accounts, tokens, and allowances properly cleaned up to avoid state leakage? - - Recommend teardown strategies or fixture scoping improvements. + - Recommend teardown strategies or fixture scoping improvements. - Tests should not depend on execution order (avoid brittle assumptions). **PRIORITY 4 - Assertions & Coverage**: - - Do tests validate only success/failure, or also assert resulting state? + - Do tests validate only success/failure, or also assert resulting state? - Suggest additional assertions that would strengthen correctness (balances, allowances, ownership). - Cover happy paths AND unhappy paths (e.g., invalid spender, revoked allowance, insufficient balance). - Avoid timing-based or flaky assumptions. @@ -1478,17 +1478,17 @@ reviews: **PRIORITY 5 - Observability & Debugging**: - Could structured logging or transaction metadata improve debugging? - Suggest capturing allowance values, transaction IDs, and balances in logs. - - Ensure error messages provide context for failure diagnosis. + - Ensure error messages provide context for failure diagnosis. **AVOID**: - Linter or formatting feedback. - Overly abstracted helpers that obscure what's being tested. - Timing-dependent assertions (use explicit waits or retries if needed). - **PHILOSOPHY**: + **PHILOSOPHY**: - Integration tests validate real network behavior - they must be reliable and safe. - Tests should protect against regressions while being maintainable. - - When tests fail in CI, developers should immediately understand what broke. + - When tests fail in CI, developers should immediately understand what broke. - Redundant setup code should be refactored, but clarity trumps abstraction. # --- DOCUMENTATION REVIEW INSTRUCTIONS --- @@ -1936,7 +1936,7 @@ reviews: - `__hash__` behavior for alias-only IDs with `num=0` - Invalid/None arguments raising errors, not silently defaulting - `_from_proto(_to_proto(x))` field round-trip equality - + --- ## Severity labels diff --git a/.env.example b/.env.example index 074bc0130..266bbde03 100644 --- a/.env.example +++ b/.env.example @@ -8,4 +8,4 @@ NETWORK= #eg testnet/local/previewnet/mainnet, default to testnet or lo # TOKEN_ID= #optional # TOKEN_NAME= #optional # TOKEN_SYMBOL= #optional -# TOPIC_ID= #optional \ No newline at end of file +# TOPIC_ID= #optional diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 01a10d7bd..3908799f5 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -48,7 +48,7 @@ # Documentation & legal /README.md @hiero-ledger/hiero-sdk-python-maintainers -/LICENSE @hiero-ledger/hiero-sdk-python-maintainers @hiero-ledger/github-maintainers @hiero-ledger/tsc +/LICENSE @hiero-ledger/hiero-sdk-python-maintainers @hiero-ledger/github-maintainers @hiero-ledger/tsc # Git ignore definitions **/.gitignore @hiero-ledger/hiero-sdk-python-maintainers @hiero-ledger/github-maintainers diff --git a/.github/ISSUE_TEMPLATE/01_good_first_issue_candidate.yml b/.github/ISSUE_TEMPLATE/01_good_first_issue_candidate.yml index fad68075c..5a1508e99 100644 --- a/.github/ISSUE_TEMPLATE/01_good_first_issue_candidate.yml +++ b/.github/ISSUE_TEMPLATE/01_good_first_issue_candidate.yml @@ -22,9 +22,9 @@ body: > This issue is not yet a confirmed Good First Issue. > It is being evaluated for suitability and may require > clarification or refinement before it is ready to be picked up. - > + > > **Please wait for maintainer confirmation before asking to be assigned.** - > + > > Maintainers and reviewers can read more about [Good First Issues](https://github.com/hiero-ledger/hiero-sdk-python/blob/main/docs/maintainers/good_first_issues_guidelines.md) and [Good First Issue Candidates](https://github.com/hiero-ledger/hiero-sdk-python/blob/main/docs/maintainers/good_first_issue_candidate_guidelines.md) validations: @@ -49,13 +49,13 @@ body: > [!IMPORTANT] > **This issue does not require prior domain knowledge.** > - > - No Hiero or Hedera experience needed - > - No distributed ledger background required + > - No Hiero or Hedera experience needed + > - No distributed ledger background required > - **Basic Python and Git are sufficient** > [!NOTE] - > ⏱️ **Typical time to complete:** 30–90 minutes (once setup is done) - > 🧩 **Difficulty:** Small, well-contained change + > ⏱️ **Typical time to complete:** 30–90 minutes (once setup is done) + > 🧩 **Difficulty:** Small, well-contained change > 🎓 **Best for:** New contributors **🏁 Completion** @@ -114,7 +114,7 @@ body: def run_demo(): """Monolithic token association demo.""" - print(f"🚀 Connecting to Hedera {network_name} network!") + print(f"🚀 Connecting to Hedera {network_name} network!") client = Client(Network(network_name)) operator_id = AccountId.from_string(os.getenv("OPERATOR_ID", "")) operator_key = PrivateKey.from_string(os.getenv("OPERATOR_KEY", "")) diff --git a/.github/ISSUE_TEMPLATE/02_good_first_issue.yml b/.github/ISSUE_TEMPLATE/02_good_first_issue.yml index 85aab60d9..1f8742650 100644 --- a/.github/ISSUE_TEMPLATE/02_good_first_issue.yml +++ b/.github/ISSUE_TEMPLATE/02_good_first_issue.yml @@ -33,13 +33,13 @@ body: > [!IMPORTANT] > **This issue does not require prior domain knowledge.** > - > - No Hiero or Hedera experience needed - > - No distributed ledger background required + > - No Hiero or Hedera experience needed + > - No distributed ledger background required > - **Basic Python and Git are sufficient** > [!NOTE] - > ⏱️ **Typical time to complete:** 30–90 minutes (once setup is done) - > 🧩 **Difficulty:** Small, well-contained change + > ⏱️ **Typical time to complete:** 30–90 minutes (once setup is done) + > 🧩 **Difficulty:** Small, well-contained change > 🎓 **Best for:** New contributors **🏁 Completion** @@ -98,7 +98,7 @@ body: def run_demo(): """Monolithic token association demo.""" - print(f"🚀 Connecting to Hedera {network_name} network!") + print(f"🚀 Connecting to Hedera {network_name} network!") client = Client(Network(network_name)) operator_id = AccountId.from_string(os.getenv("OPERATOR_ID", "")) operator_key = PrivateKey.from_string(os.getenv("OPERATOR_KEY", "")) diff --git a/.github/ISSUE_TEMPLATE/05_advanced_issue.yml b/.github/ISSUE_TEMPLATE/05_advanced_issue.yml index b91b3805b..a55915024 100644 --- a/.github/ISSUE_TEMPLATE/05_advanced_issue.yml +++ b/.github/ISSUE_TEMPLATE/05_advanced_issue.yml @@ -101,4 +101,4 @@ body: - [Office Hours](https://zoom-lfx.platform.linuxfoundation.org/meeting/99912667426?password=5b584a0e-1ed7-49d3-b2fc-dc5ddc888338) (Wednesdays, 2pm UTC) - [Discord #hiero-python-sdk](https://github.com/hiero-ledger/hiero-sdk-python/blob/main/docs/discord.md) validations: - required: false \ No newline at end of file + required: false diff --git a/.github/ISSUE_TEMPLATE/bug.yml b/.github/ISSUE_TEMPLATE/bug.yml index 87d1218c8..f45de92ad 100644 --- a/.github/ISSUE_TEMPLATE/bug.yml +++ b/.github/ISSUE_TEMPLATE/bug.yml @@ -69,4 +69,4 @@ body: - Linux - macOS - Windows - - Other \ No newline at end of file + - Other diff --git a/.github/coderabbit/release-pr-prompt.md b/.github/coderabbit/release-pr-prompt.md index 65b478e91..15c5b41d0 100644 --- a/.github/coderabbit/release-pr-prompt.md +++ b/.github/coderabbit/release-pr-prompt.md @@ -31,4 +31,4 @@ Identify any modifications to the public-facing interface. --- ### 🏁 Summary Verdict -Provide a 1-sentence summary of the "Safety" of this release candidate. \ No newline at end of file +Provide a 1-sentence summary of the "Safety" of this release candidate. diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 8a4ae6615..9cdb6aeda 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -14,4 +14,4 @@ updates: directory: "/" schedule: interval: "daily" - open-pull-requests-limit: 10 \ No newline at end of file + open-pull-requests-limit: 10 diff --git a/.github/scripts/archive/gfi_notify_team.js b/.github/scripts/archive/gfi_notify_team.js index 5711893ef..c6dc2a435 100644 --- a/.github/scripts/archive/gfi_notify_team.js +++ b/.github/scripts/archive/gfi_notify_team.js @@ -10,10 +10,10 @@ ${TEAM_ALIAS} ${message} -Repository: ${owner}/${repo} +Repository: ${owner}/${repo} Issue: #${issue.number} - ${issue.title || '(no title)'} -Best Regards, +Best Regards, Python SDK team`; try { @@ -103,4 +103,4 @@ module.exports = async ({ github, context }) => { } catch (err) { console.log('❌ Error:', err.message); } -}; \ No newline at end of file +}; diff --git a/.github/scripts/bot-assignment-check.sh b/.github/scripts/bot-assignment-check.sh index 889abf40c..fe9c40d52 100644 --- a/.github/scripts/bot-assignment-check.sh +++ b/.github/scripts/bot-assignment-check.sh @@ -17,7 +17,7 @@ get_permission() { is_spam_user() { local spam_file=".github/spam-list.txt" - + # Check static spam list if [[ -f "$spam_file" ]]; then if grep -vE '^\s*#|^\s*$' "$spam_file" | grep -qxF "$ASSIGNEE"; then @@ -40,14 +40,14 @@ issue_has_gfi() { # This allows mentors to be assigned to mentorship issues without consuming their assignment limit assignments_count() { local permission="${1:-none}" - + if [[ "$permission" == "triage" ]]; then echo "Triage user detected — excluding mentor-duty issues from count." >&2 # For triage users, exclude issues with 'mentor-duty' label gh api "repos/${REPO}/issues?per_page=100&page=1" \ -f assignee="${ASSIGNEE}" \ -f state=open \ - --jq '.[] + --jq '.[] | select(.pull_request == null) | select(any(.labels[]; .name == "mentor-duty") | not) | .number' | grep -c . || echo 0 @@ -67,11 +67,11 @@ post_comment() { msg_spam_non_gfi() { cat < 1 )); then echo "Spam user limit exceeded (Max 1 allowed). Revoking assignment." remove_assignee post_comment "$(msg_spam_limit_exceeded "$COUNT")" exit 1 fi - + echo "Spam-listed user assignment valid. User has $COUNT assignment(s)." else # Normal users have a limit of 2 open assignments echo "Current open assignments count: $COUNT" - + if (( COUNT > 2 )); then echo "Limit exceeded (Max 2 allowed). Revoking assignment." remove_assignee post_comment "$(msg_normal_limit_exceeded)" exit 1 fi - + echo "Assignment valid. User has $COUNT assignments." fi diff --git a/.github/scripts/bot-beginner-assign-on-comment.js b/.github/scripts/bot-beginner-assign-on-comment.js index 77fae5508..22a3f657e 100644 --- a/.github/scripts/bot-beginner-assign-on-comment.js +++ b/.github/scripts/bot-beginner-assign-on-comment.js @@ -252,7 +252,7 @@ Please try a GFI first, then come back — we’ll be happy to assign this! 😊 } return; } - + // --- ASSIGNMENT LOGIC --- if (issue.assignees && issue.assignees.length > 0) { try{ diff --git a/.github/scripts/bot-community-calls.sh b/.github/scripts/bot-community-calls.sh index 061d16ba6..311bd4a67 100755 --- a/.github/scripts/bot-community-calls.sh +++ b/.github/scripts/bot-community-calls.sh @@ -90,7 +90,7 @@ echo "$ISSUE_DATA" | echo "Skipping issue #$ISSUE_NUM created by bot account @$AUTHOR" continue fi - + for EXCLUDED in "${EXCLUDED_AUTHORS[@]}"; do if [ "$AUTHOR" = "$EXCLUDED" ]; then echo "Skipping issue #$ISSUE_NUM by excluded author @$AUTHOR" diff --git a/.github/scripts/bot-conventional-pr-title.js b/.github/scripts/bot-conventional-pr-title.js index 4df04fcf0..dc483cc0e 100644 --- a/.github/scripts/bot-conventional-pr-title.js +++ b/.github/scripts/bot-conventional-pr-title.js @@ -317,4 +317,4 @@ module.exports = { run, suggestConventionalType, formatMessage -}; \ No newline at end of file +}; diff --git a/.github/scripts/bot-gfi-assign-on-comment.js b/.github/scripts/bot-gfi-assign-on-comment.js index 58c2862a6..730a810aa 100644 --- a/.github/scripts/bot-gfi-assign-on-comment.js +++ b/.github/scripts/bot-gfi-assign-on-comment.js @@ -94,7 +94,7 @@ function commentAlreadyAssigned(requesterUsername, issue) { return ( `Hi @${requesterUsername} — this issue is already assigned to ${getCurrentAssigneeMention(issue)}, so I can’t assign it again. -👉 **Choose a different Good First Issue to work on next:** +👉 **Choose a different Good First Issue to work on next:** [Browse unassigned Good First Issues](${UNASSIGNED_GFI_SEARCH_URL}) Once you find one you like, comment \`/assign\` to get started.` @@ -248,7 +248,7 @@ module.exports = async ({ github, context }) => { username, }); - if (isTeamMember) { + if (isTeamMember) { console.log('[gfi-assign] Skip reminder: commenter is collaborator'); return; } @@ -403,7 +403,7 @@ module.exports = async ({ github, context }) => { if (mentorAssignmentSucceeded) { try { const { triggerCodeRabbitPlan, hasExistingCodeRabbitPlan } = require('./coderabbit_plan_trigger.js'); - + // Check if CodeRabbit plan already exists to avoid duplicate comments const planExists = await hasExistingCodeRabbitPlan(github, owner, repo, issueNumber); if (planExists) { diff --git a/.github/scripts/bot-inactivity-unassign.sh b/.github/scripts/bot-inactivity-unassign.sh index 2726c8a25..f75144a06 100755 --- a/.github/scripts/bot-inactivity-unassign.sh +++ b/.github/scripts/bot-inactivity-unassign.sh @@ -91,7 +91,7 @@ fi # Get list of open issues with assignees (pagination via gh) ISSUES=$( - gh api "repos/$REPO/issues" --paginate --jq '.[] + gh api "repos/$REPO/issues" --paginate --jq '.[] | select(.state=="open" and (.assignees|length>0) and (.pull_request|not)) | .number' 2>/dev/null || true ) @@ -108,7 +108,7 @@ for ISSUE in $ISSUES; do echo " [INFO] Issue created at: ${ISSUE_CREATED_AT:-(unknown)}" echo - # Fetch full timeline with pagination and flatten array + # Fetch full timeline with pagination and flatten array TIMELINE=$(gh api --paginate -H "Accept: application/vnd.github.mockingbird-preview+json" "repos/$REPO/issues/$ISSUE/timeline" 2>/dev/null | jq -s 'add' || echo "[]") TIMELINE=${TIMELINE:-'[]'} diff --git a/.github/scripts/bot-issue-reminder-no-pr.sh b/.github/scripts/bot-issue-reminder-no-pr.sh index 58dfe5b22..b52a38966 100644 --- a/.github/scripts/bot-issue-reminder-no-pr.sh +++ b/.github/scripts/bot-issue-reminder-no-pr.sh @@ -66,7 +66,7 @@ has_recent_working_command() { return 1 # False fi - # The 'since' parameter is an optimization, but the API may still return comments + # The 'since' parameter is an optimization, but the API may still return comments # updated since the cutoff, not just created. We still need to check the create time. for created_at in $working_comments; do local comment_ts diff --git a/.github/scripts/bot-mentor-assignment.js b/.github/scripts/bot-mentor-assignment.js index 2e9fd0d94..57b5b737c 100644 --- a/.github/scripts/bot-mentor-assignment.js +++ b/.github/scripts/bot-mentor-assignment.js @@ -77,7 +77,7 @@ async function isNewContributor(github, owner, repo, login) { try { console.log(`Checking for merged PRs by ${login} in ${targetOwner}/${targetRepo}`); - + const iterator = github.paginate.iterator(github.rest.pulls.list, { owner: targetOwner, repo: targetRepo, @@ -94,7 +94,7 @@ async function isNewContributor(github, owner, repo, login) { return false; } } - + console.log(`No merged PRs found for ${login}. Considered a new starter.`); return true; } catch (error) { diff --git a/.github/scripts/bot-p0-issues-notify-team.js b/.github/scripts/bot-p0-issues-notify-team.js index 8958994c2..1793a716e 100644 --- a/.github/scripts/bot-p0-issues-notify-team.js +++ b/.github/scripts/bot-p0-issues-notify-team.js @@ -1,9 +1,9 @@ -// Script to notify the team when a P0 issue is created. +// Script to notify the team when a P0 issue is created. const marker = ''; async function notifyTeam(github, owner, repo, issue, marker) { - const comment = `${marker} :rotating_light: Attention Team :rotating_light: + const comment = `${marker} :rotating_light: Attention Team :rotating_light: @hiero-ledger/hiero-sdk-python-maintainers @hiero-ledger/hiero-sdk-python-committers @hiero-ledger/hiero-sdk-python-triage A new P0 issue has been created: #${issue.number} - ${issue.title || '(no title)'} @@ -54,4 +54,4 @@ module.exports = async ({ github, context }) => { } catch (err) { console.log('❌ Error:', err.message); } -}; \ No newline at end of file +}; diff --git a/.github/scripts/bot-pr-draft-explainer.js b/.github/scripts/bot-pr-draft-explainer.js index 6515ecd3b..c95191097 100644 --- a/.github/scripts/bot-pr-draft-explainer.js +++ b/.github/scripts/bot-pr-draft-explainer.js @@ -118,12 +118,12 @@ module.exports = async ({ github, context }) => { const authorLogin = pr.user?.login; const greetingTarget = authorLogin ? `@${authorLogin}` : "there"; - + if (!pr.draft) { console.log(`PR #${prNumber} is not draft. Skipping.`); return; - } - + } + console.log(`PR #${prNumber} was converted to draft. Checking if explanation is needed.`); // Prevent duplicate comments @@ -141,7 +141,7 @@ module.exports = async ({ github, context }) => { console.log("Skipping explanation to avoid potential duplicate."); return; } - + if (alreadyCommented) { console.log("Explanation already exists — skipping."); return; diff --git a/.github/scripts/bot-pr-draft-ready-reminder.js b/.github/scripts/bot-pr-draft-ready-reminder.js index 0490d71d7..be8baf98d 100644 --- a/.github/scripts/bot-pr-draft-ready-reminder.js +++ b/.github/scripts/bot-pr-draft-ready-reminder.js @@ -76,7 +76,7 @@ If these updates address the feedback, you can: - click **“Ready for review”** (recommended), or - use the \`/review\` command. -Thanks for keeping things moving! 🙌 +Thanks for keeping things moving! 🙌 — Hiero SDK Automation Team `.trim(); } @@ -193,7 +193,7 @@ module.exports = async function ({ github, context }) { console.log("Skipping reminder to avoid potential duplicate."); return; } - + if (alreadyCommented) { console.log("Reminder already exists — skipping."); return; diff --git a/.github/scripts/bot-verified-commits.js b/.github/scripts/bot-verified-commits.js index eccc8ce9e..533d22950 100644 --- a/.github/scripts/bot-verified-commits.js +++ b/.github/scripts/bot-verified-commits.js @@ -58,11 +58,11 @@ function validatePRNumber(prNumber) { // Fetches commits with bounded pagination and counts unverified ones async function getCommitVerificationStatus(github, owner, repo, prNumber) { console.log(`[${CONFIG.BOT_NAME}] Fetching commits for PR #${prNumber}...`); - + const commits = []; let page = 0; let truncated = false; - + try { for await (const response of github.paginate.iterator( github.rest.pulls.listCommits, @@ -85,18 +85,18 @@ async function getCommitVerificationStatus(github, owner, repo, prNumber) { }); throw error; } - + const unverifiedCommits = commits.filter( commit => commit.commit?.verification?.verified !== true ); - + console.log(`[${CONFIG.BOT_NAME}] Found ${commits.length} total, ${unverifiedCommits.length} unverified`); - + // Fail-closed: if truncated and no unverified found, treat as potentially unverified const unverifiedCount = truncated && unverifiedCommits.length === 0 ? 1 : unverifiedCommits.length; - + return { total: commits.length, unverified: unverifiedCount, @@ -109,14 +109,14 @@ async function getCommitVerificationStatus(github, owner, repo, prNumber) { // Uses bounded pagination and early return for efficiency async function hasExistingBotComment(github, owner, repo, prNumber) { console.log(`[${CONFIG.BOT_NAME}] Checking for existing bot comments...`); - + // Support both with and without [bot] suffix for GitHub Actions bot account const botLogins = new Set([ CONFIG.BOT_LOGIN, `${CONFIG.BOT_LOGIN}[bot]`, 'github-actions[bot]', ]); - + let page = 0; try { for await (const response of github.paginate.iterator( @@ -150,7 +150,7 @@ async function hasExistingBotComment(github, owner, repo, prNumber) { }); throw error; } - + console.log(`[${CONFIG.BOT_NAME}] Existing bot comment: false`); return false; } @@ -171,9 +171,9 @@ function buildVerificationComment( return `- \`${sha}\` ${msg}`; }).join('\n') : (truncated ? '- Unable to enumerate commits due to pagination limit.' : ''); - - const moreCommits = unverifiedCommits.length > maxDisplay - ? `\n- ...and ${unverifiedCommits.length - maxDisplay} more` + + const moreCommits = unverifiedCommits.length > maxDisplay + ? `\n- ...and ${unverifiedCommits.length - maxDisplay} more` : ''; const countText = truncated ? `at least ${unverifiedCount}` : `${unverifiedCount}`; @@ -182,7 +182,7 @@ function buildVerificationComment( : ''; return `${CONFIG.COMMENT_MARKER} -Hi, this is ${CONFIG.BOT_NAME}. +Hi, this is ${CONFIG.BOT_NAME}. Your pull request cannot be merged as it has **${countText} unverified commit(s)**: ${commitList}${moreCommits}${truncationNote} @@ -220,7 +220,7 @@ async function postVerificationComment( } console.log(`[${CONFIG.BOT_NAME}] Posting verification failure comment...`); - + try { await github.rest.issues.createComment({ @@ -252,37 +252,37 @@ async function main({ github, context }) { process.env.PR_NUMBER || context.payload?.pull_request?.number ); const repoPattern = /^[A-Za-z0-9_.-]+$/; - + // Validate repo context if (!repoPattern.test(owner) || !repoPattern.test(repo)) { console.error(`[${CONFIG.BOT_NAME}] Invalid repo context`, { owner, repo }); return { success: false, unverifiedCount: 0 }; } - + console.log(`[${CONFIG.BOT_NAME}] Starting verification for ${owner}/${repo} PR #${prNumber}`); - + if (!prNumber) { console.log(`[${CONFIG.BOT_NAME}] Invalid PR number`); return { success: false, unverifiedCount: 0 }; } - + try { // Get commit verification status - const { total, unverified, unverifiedCommits, truncated } = + const { total, unverified, unverifiedCommits, truncated } = await getCommitVerificationStatus(github, owner, repo, prNumber); - + // All commits verified - success if (unverified === 0) { console.log(`[${CONFIG.BOT_NAME}] ✅ All ${total} commits are verified`); return { success: true, unverifiedCount: 0 }; } - + // Some commits unverified console.log(`[${CONFIG.BOT_NAME}] ❌ Found ${unverified} unverified commits`); - + // Check for existing comment to avoid duplicates const existingComment = await hasExistingBotComment(github, owner, repo, prNumber); - + if (existingComment) { console.log(`[${CONFIG.BOT_NAME}] Bot already commented. Skipping duplicate.`); } else { @@ -298,7 +298,7 @@ async function main({ github, context }) { truncated ); } - + return { success: false, unverifiedCount: unverified }; } catch (error) { console.error(`[${CONFIG.BOT_NAME}] Verification failed`, { diff --git a/.github/scripts/linked_issue_enforce.js b/.github/scripts/linked_issue_enforce.js index 78ccd05b2..fbd925dae 100644 --- a/.github/scripts/linked_issue_enforce.js +++ b/.github/scripts/linked_issue_enforce.js @@ -58,7 +58,7 @@ async function getLinkedIssues(github, pr, owner, repo) { } } -// Validation +// Validation async function validatePR(github, pr, owner, repo) { const issues = await getLinkedIssues(github, pr, owner, repo); diff --git a/.github/scripts/pr-check-changelog.sh b/.github/scripts/pr-check-changelog.sh index 44f703cc7..9ca555198 100755 --- a/.github/scripts/pr-check-changelog.sh +++ b/.github/scripts/pr-check-changelog.sh @@ -36,7 +36,7 @@ set -euo pipefail # - Immediate Fail Check: If 'added_bullets' is empty, sets failed=1 and exits. # (You cannot merge code without a changelog entry). # -# 3️⃣ Context Tracking +# 3️⃣ Context Tracking # As the script reads the file line-by-line, it tracks: # - current_release: Main version header (e.g., [Unreleased] or [1.0.0]). # - current_subtitle: Sub-category (e.g., ### Added, ### Fixed). @@ -277,7 +277,7 @@ if [[ -n "$wrong_release_entries" ]]; then post_pr_comment "$WRONG_SECTION_MARKER ⚠️ **Changelog placement issue** -Thanks for adding a changelog entry! 🙌 +Thanks for adding a changelog entry! 🙌 However, one or more entries in this PR were added under a **released version**. 📌 New changelog entries should always go under **[Unreleased]**, grouped beneath an appropriate category (e.g. *Added*, *Fixed*). diff --git a/.github/scripts/pr-check-test-files.sh b/.github/scripts/pr-check-test-files.sh index 424617b2d..e4e3a00db 100755 --- a/.github/scripts/pr-check-test-files.sh +++ b/.github/scripts/pr-check-test-files.sh @@ -4,24 +4,24 @@ set -euo pipefail # ====================================================================================================================================================== # @file: pr-check-test-files.sh # -# @Description A CI check written in bash that enforces the '_test.py' suffix to ensure Pytest can automatically discover -# new or renamed test files in a Pull Request. -# -# @logic: -# 1. Identifies files as (A) Added, (R) Renamed, or (C) Copied using 'git diff' to check -# relevant filename ($file1 for added files, $file2 for renamed/copied files). +# @Description A CI check written in bash that enforces the '_test.py' suffix to ensure Pytest can automatically discover +# new or renamed test files in a Pull Request. +# +# @logic: +# 1. Identifies files as (A) Added, (R) Renamed, or (C) Copied using 'git diff' to check +# relevant filename ($file1 for added files, $file2 for renamed/copied files). # 2. Validates paths against allowed test directories (unit/integration). # 3. Excludes specific utility (ex., conftest.py, utils.py) and non-Python files. # 4. Parses tab-separated Git output via IFS=$'\t' to ensure robust filename handling. # 5. Routes files using case statement based on git status # # @types: -# - String: Used for file paths and git status codes. +# - String: Used for file paths and git status codes. # - Array: Used for 'TEST_DIRS', 'EXCEPTION_NAMES', and accumulating errors. # # @Parameters: # - None: This script does not accept CLI arguments. It derives the input from the current Git state compared against origin/main. -# +# # @Dependencies: # - Git: (for diff) # - Bash: (runs the script) diff --git a/.github/scripts/pr_inactivity_reminder.js b/.github/scripts/pr_inactivity_reminder.js index a98a34464..72de0036e 100644 --- a/.github/scripts/pr_inactivity_reminder.js +++ b/.github/scripts/pr_inactivity_reminder.js @@ -82,7 +82,7 @@ If you're no longer working on this, please comment \`/unassign\` on the linked Reach out on discord or join our office hours if you need assistance. - ${discordLink} -- ${office_hours_calendar} +- ${office_hours_calendar} From the Python SDK Team`; diff --git a/.github/scripts/release-pr-coderabbit-gate.js b/.github/scripts/release-pr-coderabbit-gate.js index 72fe00ca6..c4e0fca31 100644 --- a/.github/scripts/release-pr-coderabbit-gate.js +++ b/.github/scripts/release-pr-coderabbit-gate.js @@ -16,7 +16,7 @@ const MARKER = ""; function loadPrompt() { const promptPath = path.join( - process.env.GITHUB_WORKSPACE || ".", + process.env.GITHUB_WORKSPACE || ".", ".github/coderabbit/release-pr-prompt.md" ); try { @@ -140,4 +140,4 @@ module.exports = async ({ github, context }) => { console.error(`Error in release PR coderabbit gate: ${error.message}`); console.log(`PR #${issue_number || 'unknown'} (${headRef || '?'} → ${baseRef || '?'})`); } -}; \ No newline at end of file +}; diff --git a/.github/scripts/update-spam-list.js b/.github/scripts/update-spam-list.js index 33c99b32a..aa1183f88 100644 --- a/.github/scripts/update-spam-list.js +++ b/.github/scripts/update-spam-list.js @@ -1,6 +1,6 @@ /** * GitHub Actions script to automatically update the spam list by querying PRs - * + * * This script: * - Identifies spam users from closed unmerged PRs with 'spam' label * - Identifies rehabilitated users from merged PRs with 'Good First Issue' label @@ -16,7 +16,7 @@ const dryRun = (process.env.DRY_RUN || 'false').toString().toLowerCase() === 'tr async function computeSpamListUpdates(spamUsers, rehabilitatedUsers) { let currentSpamList = []; - + try { const content = await fs.readFile(SPAM_LIST_PATH, 'utf8'); currentSpamList = content @@ -29,15 +29,15 @@ async function computeSpamListUpdates(spamUsers, rehabilitatedUsers) { } // File doesn't exist yet, start with empty list } - + const additions = []; const removals = []; const finalSpamList = new Set(currentSpamList); - + // Process spam users for (const [username, spamDate] of spamUsers.entries()) { const rehabDate = rehabilitatedUsers.get(username); - + if (!rehabDate || spamDate > rehabDate) { // User is spam (either never rehabilitated or spammed after rehabilitation) if (!finalSpamList.has(username)) { @@ -46,11 +46,11 @@ async function computeSpamListUpdates(spamUsers, rehabilitatedUsers) { } } } - + // Process rehabilitated users for (const [username, rehabDate] of rehabilitatedUsers.entries()) { const spamDate = spamUsers.get(username); - + if (!spamDate || rehabDate > spamDate) { // User is rehabilitated (merged PR more recent than spam) if (finalSpamList.has(username)) { @@ -59,11 +59,11 @@ async function computeSpamListUpdates(spamUsers, rehabilitatedUsers) { } } } - + // Sort additions and removals alphabetically additions.sort((a, b) => a.localeCompare(b)); removals.sort((a, b) => a.localeCompare(b)); - + return { additions, removals, @@ -74,10 +74,10 @@ async function computeSpamListUpdates(spamUsers, rehabilitatedUsers) { function generateSummary(additions, removals) { const title = `Update spam list (${additions.length} additions, ${removals.length} removals)`; - + let body = '## Automated Spam List Update\n\n'; body += 'This issue details the updates to the spam list based on recent PR activity.\n\n'; - + if (additions.length > 0) { body += `### ➕ Additions (${additions.length})\n\n`; body += 'The following users were added to the spam list:\n\n'; @@ -86,7 +86,7 @@ function generateSummary(additions, removals) { } body += '\n'; } - + if (removals.length > 0) { body += `### ➖ Removals (${removals.length})\n\n`; body += 'The following users were removed from the spam list (rehabilitated):\n\n'; @@ -95,22 +95,22 @@ function generateSummary(additions, removals) { } body += '\n'; } - + if (additions.length === 0 && removals.length === 0) { body += '### ℹ️ No Changes\n\n'; body += 'No updates were needed for the spam list.\n'; } - + return { title, body }; } // Main function to orchestrate the spam list update - + module.exports = async ({github, context, core}) => { const { owner, repo } = context.repo; try { console.log('Starting spam list update...'); - + if (dryRun) { console.log('⚠️ Running in DRY RUN mode - no files will be modified'); } @@ -166,7 +166,7 @@ module.exports = async ({github, context, core}) => { // Use pagination iterator with your existing pattern for (const { name, query, process } of searches) { console.log(`Fetching ${name}...`); - + const iterator = github.paginate.iterator( github.rest.search.issuesAndPullRequests, { @@ -191,13 +191,13 @@ module.exports = async ({github, context, core}) => { spamUsers, rehabilitatedUsers ); - + console.log(`Additions: ${additions.length}`); console.log(`Removals: ${removals.length}`); - + const { title, body } = generateSummary(additions, removals); const hasChanges = additions.length > 0 || removals.length > 0; - + if (hasChanges) { if (dryRun) { console.log('[DRY RUN] Would create issue with:'); @@ -228,9 +228,9 @@ module.exports = async ({github, context, core}) => { } else { console.log('No changes needed, skipping issue creation'); } - + } catch (error) { core.setFailed(`Failed to update spam list: ${error.message}`); throw error; } -}; \ No newline at end of file +}; diff --git a/.github/spam-list.txt b/.github/spam-list.txt index ae5f18a3b..75e6d23bd 100644 --- a/.github/spam-list.txt +++ b/.github/spam-list.txt @@ -1,12 +1,12 @@ -KubanjaElijahEldred -by22Jy -CODEAbhinav-art -zhanglinqian -Halbot100 -roberthallers -SergioChan -ndpvt-web -OnlyTerp -shixian-HFUT -Yuki9814 -ashrafzunaira18 \ No newline at end of file +KubanjaElijahEldred +by22Jy +CODEAbhinav-art +zhanglinqian +Halbot100 +roberthallers +SergioChan +ndpvt-web +OnlyTerp +shixian-HFUT +Yuki9814 +ashrafzunaira18 diff --git a/.github/workflows/bot-advanced-check.yml b/.github/workflows/bot-advanced-check.yml index cd41a943b..bc2419763 100644 --- a/.github/workflows/bot-advanced-check.yml +++ b/.github/workflows/bot-advanced-check.yml @@ -68,4 +68,4 @@ jobs: DRY_RUN_USER: ${{ github.event_name == 'workflow_dispatch' && inputs.username || '' }} run: | chmod +x .github/scripts/bot-advanced-check.sh - .github/scripts/bot-advanced-check.sh \ No newline at end of file + .github/scripts/bot-advanced-check.sh diff --git a/.github/workflows/bot-assignment-check.yml b/.github/workflows/bot-assignment-check.yml index 13db23e17..88ebde0cf 100644 --- a/.github/workflows/bot-assignment-check.yml +++ b/.github/workflows/bot-assignment-check.yml @@ -30,4 +30,4 @@ jobs: chmod +x .github/scripts/bot-assignment-check.sh # Run the script - ./.github/scripts/bot-assignment-check.sh \ No newline at end of file + ./.github/scripts/bot-assignment-check.sh diff --git a/.github/workflows/bot-coderabbit-plan-trigger.yml b/.github/workflows/bot-coderabbit-plan-trigger.yml index bf88d0b39..8a1d3d358 100644 --- a/.github/workflows/bot-coderabbit-plan-trigger.yml +++ b/.github/workflows/bot-coderabbit-plan-trigger.yml @@ -29,10 +29,10 @@ jobs: # Only run when the newly added label is beginner, intermediate, or advanced (case-insensitive) if: > github.event_name == 'workflow_dispatch' || - (github.event.action == 'labeled' && + (github.event.action == 'labeled' && github.event.issue.state == 'open' && contains(fromJson('["beginner","intermediate","advanced","Beginner","Intermediate","Advanced"]'), github.event.label.name)) - + steps: - name: Harden the runner uses: step-security/harden-runner@fe104658747b27e96e4f7e80cd0a94068e53901d # v2.16.1 @@ -49,5 +49,5 @@ jobs: uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd #v8.0.0 with: script: | - const script = require('./.github/scripts/coderabbit_plan_trigger.js'); - await script({ github, context}); \ No newline at end of file + const script = require('./.github/scripts/coderabbit_plan_trigger.js'); + await script({ github, context}); diff --git a/.github/workflows/bot-linked-issue-enforcer.yml b/.github/workflows/bot-linked-issue-enforcer.yml index fbac3a03e..b4552302c 100644 --- a/.github/workflows/bot-linked-issue-enforcer.yml +++ b/.github/workflows/bot-linked-issue-enforcer.yml @@ -38,5 +38,5 @@ jobs: uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd #v8.0.0 with: script: | - const script = require('./.github/scripts/linked_issue_enforce.js'); + const script = require('./.github/scripts/linked_issue_enforce.js'); await script({ github, context}); diff --git a/.github/workflows/bot-p0-issues-notify-team.yml b/.github/workflows/bot-p0-issues-notify-team.yml index 4a753cc20..3730978f1 100644 --- a/.github/workflows/bot-p0-issues-notify-team.yml +++ b/.github/workflows/bot-p0-issues-notify-team.yml @@ -33,5 +33,5 @@ jobs: uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd #v8.0.0 with: script: | - const script = require('./.github/scripts/bot-p0-issues-notify-team.js'); + const script = require('./.github/scripts/bot-p0-issues-notify-team.js'); await script({ github, context}); diff --git a/.github/workflows/bot-workflows.yml b/.github/workflows/bot-workflows.yml index 2de71473a..517d8729a 100644 --- a/.github/workflows/bot-workflows.yml +++ b/.github/workflows/bot-workflows.yml @@ -46,8 +46,8 @@ jobs: run: | REPO="${{ github.repository }}" COMMENT=$(cat <> "$GITHUB_OUTPUT" - echo "labels_count=0" >> "$GITHUB_OUTPUT" - echo "labels_multiline=" >> "$GITHUB_OUTPUT" - echo "pr_number=$INPUT_PR_NUMBER" >> "$GITHUB_OUTPUT" - echo "is_fork_pr=$INPUT_IS_FORK_PR" >> "$GITHUB_OUTPUT" - echo "dry_run=$INPUT_DRY_RUN" >> "$GITHUB_OUTPUT" - echo "source_event=workflow_dispatch" >> "$GITHUB_OUTPUT" - exit 1 - fi - labels=$(jq -c '.labels // []' "$labels_file") - pr_number=$(jq -r '.pr_number // 0' "$labels_file") - is_fork_pr=$(jq -r '.is_fork_pr // false' "$labels_file") - dry_run=$(jq -r '.dry_run // "true"' "$labels_file") - source_event=$(jq -r '.source_event // ""' "$labels_file") - labels_multiline=$(jq -r '.labels // [] | .[]' "$labels_file") - labels_count=$(echo "$labels" | jq 'length') - echo "labels=$labels" >> "$GITHUB_OUTPUT" - echo "labels_count=$labels_count" >> "$GITHUB_OUTPUT" - { - echo "labels_multiline<> "$GITHUB_OUTPUT" - echo "pr_number=$pr_number" >> "$GITHUB_OUTPUT" - echo "is_fork_pr=$is_fork_pr" >> "$GITHUB_OUTPUT" - echo "dry_run=$dry_run" >> "$GITHUB_OUTPUT" - echo "source_event=$source_event" >> "$GITHUB_OUTPUT" - - - name: Validate labels payload - id: validate - run: | - if [ "$PR_NUMBER" = "0" ] || [ "$(echo "$LABELS" | jq -r '. | length')" = "0" ]; then - echo "Invalid payload: pr_number=$PR_NUMBER or labels empty. Skipping label addition." - echo "valid_payload=false" >> "$GITHUB_OUTPUT" - else - echo "valid_payload=true" >> "$GITHUB_OUTPUT" - fi - env: - PR_NUMBER: ${{ steps.read.outputs.pr_number }} - LABELS: ${{ steps.read.outputs.labels }} - - - name: Determine if labels should be applied - id: should_apply - run: | - if [ "${{ steps.read.outputs.is_fork_pr }}" = "true" ]; then - echo "apply=false" >> "$GITHUB_OUTPUT" - echo "reason=fork PR" >> "$GITHUB_OUTPUT" - elif [ "${{ steps.validate.outputs.valid_payload }}" != "true" ]; then - echo "apply=false" >> "$GITHUB_OUTPUT" - echo "reason=invalid payload" >> "$GITHUB_OUTPUT" - elif [ "${{ steps.read.outputs.source_event }}" = "workflow_dispatch" ] && [ "${{ steps.read.outputs.dry_run }}" = "true" ]; then - echo "apply=false" >> "$GITHUB_OUTPUT" - echo "reason=dry run" >> "$GITHUB_OUTPUT" - else - echo "apply=true" >> "$GITHUB_OUTPUT" - echo "reason=" >> "$GITHUB_OUTPUT" - fi - - - name: Add labels to PR - if: ${{ steps.should_apply.outputs.apply == 'true' }} - uses: actions-ecosystem/action-add-labels@1a9c3715c0037e96b97bb38cb4c4b56a1f1d4871 # main + - name: Apply Labels + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 with: - github_token: ${{ secrets.GITHUB_TOKEN }} - labels: ${{ steps.read.outputs.labels_multiline }} - number: ${{ steps.read.outputs.pr_number }} - + script: | + const fs = require('fs'); + if (!fs.existsSync('labels.json')) { + console.log("No payload file found. Nothing to apply."); + return; + } + + const data = JSON.parse(fs.readFileSync('labels.json', 'utf8')); + console.log(`Applying labels to PR #${data.pr_number}: ${data.labels.join(', ')}`); + + await github.rest.issues.addLabels({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: data.pr_number, + labels: data.labels + }); + console.log("Successfully applied labels!"); diff --git a/.github/workflows/sync-issue-labels-compute.yml b/.github/workflows/sync-issue-labels-compute.yml index e3db19955..0648b7616 100644 --- a/.github/workflows/sync-issue-labels-compute.yml +++ b/.github/workflows/sync-issue-labels-compute.yml @@ -1,207 +1,105 @@ -name: Compute Linked Issue Labels +name: Sync Linked Issue Labels - Compute on: pull_request: - types: [opened, edited, reopened, synchronize, ready_for_review] - workflow_dispatch: - inputs: - pr_number: - description: "PR number to sync labels for" - required: true - type: number - dry-run-enabled: - description: "Dry run (log only, do not apply labels)" - required: false - type: boolean - default: true + types: [opened, edited, reopened, synchronize] permissions: - actions: write - pull-requests: read issues: read contents: read +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number }} + cancel-in-progress: true + jobs: - compute-labels: - concurrency: - group: sync-issue-labels-compute-pr-${{ github.event.pull_request.number || github.event.inputs.pr_number }} - cancel-in-progress: true + sync: runs-on: ubuntu-latest - outputs: - pr_number: ${{ steps.compute.outputs.pr_number }} - dry_run: ${{ steps.compute.outputs.dry_run }} - is_fork_pr: ${{ steps.compute.outputs.is_fork_pr }} steps: - - name: Harden the runner - uses: step-security/harden-runner@a90bcbc6539c36a85cdfeb73f7e2f433735f215b # v2.15.0 + - name: Harden Runner + uses: step-security/harden-runner@f808768d1510423e83855289c910610ca9b43176 # v2.17.0 with: egress-policy: audit - - name: Compute linked issue labels + - name: Compute Labels id: compute - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - PR_NUMBER: ${{ github.event.pull_request.number || github.event.inputs.pr_number }} - DRY_RUN: 'true' - REQUESTED_DRY_RUN: ${{ github.event_name == 'workflow_dispatch' && format('{0}', github.event.inputs['dry-run-enabled']) || 'true' }} - IS_FORK_PR: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.repo.fork || 'false' }} - MAX_LINKED_ISSUES: '20' uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 with: - result-encoding: json script: | - const MAX_LINKED_ISSUES = Number(process.env.MAX_LINKED_ISSUES || "20"); - - function extractLabels(labelData) { - const result = []; - for (const item of labelData) { - const name = typeof item === "string" ? item : item && item.name; - if (name && name.trim()) result.push(name.trim()); - } - return result; - } - - function extractLinkedIssueNumbers(prBody, owner, repo) { - const numbers = new Set(); - const closingRefRegex = /(?:fix|fixes|fixed|close|closes|closed|resolve|resolves|resolved)\s+(?:([A-Za-z0-9_.-]+)\/([A-Za-z0-9_.-]+))?#(\d+)\b/gi; - const lines = String(prBody || "").split(/\r?\n/); - for (const line of lines) { - let m; - while ((m = closingRefRegex.exec(line)) !== null) { - const refOwner = (m[1] || "").toLowerCase(); - const refRepo = (m[2] || "").toLowerCase(); - if (refOwner && refRepo && (refOwner !== owner.toLowerCase() || refRepo !== repo.toLowerCase())) continue; - numbers.add(Number(m[3])); - } - } - const all = Array.from(numbers); - if (all.length > MAX_LINKED_ISSUES) { - console.log(`[sync] Limiting linked issue refs from ${all.length} to ${MAX_LINKED_ISSUES}.`); - } - return all.slice(0, MAX_LINKED_ISSUES); - } - - const prNumber = Number(process.env.PR_NUMBER); - if (!prNumber) { - core.setOutput('has_labels', 'false'); - core.setOutput('labels', '[]'); - core.setOutput('pr_number', ''); - core.setOutput('dry_run', 'true'); - core.setOutput('is_fork_pr', String(process.env.IS_FORK_PR || 'false')); - core.setOutput('source_event', context.eventName); - return; - } + const prNumber = context.payload.pull_request.number; + console.log(`--- Processing PR #${prNumber} ---`); const { data: prData } = await github.rest.pulls.get({ owner: context.repo.owner, repo: context.repo.repo, pull_number: prNumber }); - const prAuthor = (prData.user && prData.user.login) || ""; + const prAuthor = prData.user.login; if (/\[bot\]$/i.test(prAuthor) || /dependabot/i.test(prAuthor)) { - console.log(`[sync] Skipping bot-authored PR from ${prAuthor}.`); - core.setOutput('has_labels', 'false'); - core.setOutput('labels', '[]'); - core.setOutput('pr_number', String(prNumber)); - core.setOutput('dry_run', 'true'); - core.setOutput('is_fork_pr', String(process.env.IS_FORK_PR || 'false')); - core.setOutput('source_event', context.eventName); + console.log(`Skipping bot-authored PR (Author: ${prAuthor})`); return; } - const linkedIssues = extractLinkedIssueNumbers(prData.body || "", context.repo.owner, context.repo.repo); - if (!linkedIssues.length) { - console.log("[sync] No linked issue references found in PR body."); - core.setOutput('has_labels', 'false'); - core.setOutput('labels', '[]'); - core.setOutput('pr_number', String(prNumber)); - core.setOutput('dry_run', 'true'); - core.setOutput('is_fork_pr', String(process.env.IS_FORK_PR || 'false')); - core.setOutput('source_event', context.eventName); + const regex = /(?:fix|fixes|fixed|close|closes|closed|resolve|resolves|resolved)[:\s]+\s*#(\d+)\b/gi; + const issueNumbers = new Set(); + let match; + while ((match = regex.exec(prData.body || "")) !== null) { + issueNumbers.add(Number(match[1])); + } + + if (issueNumbers.size === 0) { + console.log("No linked issues found in the PR description."); return; } - console.log(`[sync] Linked issues: ${linkedIssues.map(n => '#' + n).join(', ')}`); + console.log(`Detected linked issues: #${Array.from(issueNumbers).join(', #')}`); - const allLabels = []; - for (const num of linkedIssues) { + const discoveredLabels = new Set(); + for (const num of issueNumbers) { try { - const { data } = await github.rest.issues.get({ + const { data: issue } = await github.rest.issues.get({ owner: context.repo.owner, repo: context.repo.repo, issue_number: num }); - if (data.pull_request) { console.log(`[sync] Skipping #${num}: is a PR reference.`); continue; } - const labels = extractLabels(data.labels || []); - console.log(`[sync] Issue #${num} labels: ${labels.length ? labels.join(', ') : '(none)'}`); - allLabels.push(...labels); - } catch (err) { - if (err && err.status === 404) { console.log(`[sync] Issue #${num} not found. Skipping.`); continue; } - throw err; + if (!issue.pull_request) { + const names = (issue.labels || []).map(l => typeof l === 'string' ? l : l.name); + console.log(`Found labels on issue #${num}: [${names.join(', ')}]`); + names.forEach(l => discoveredLabels.add(l)); + } else { + console.log(`Skipping #${num} because it is a Pull Request, not an Issue.`); + } + } catch (e) { + console.log(`Error fetching labels for issue #${num}: ${e.message}`); } } - const existing = extractLabels(prData.labels || []); - const existingSet = new Set(existing); - const deduped = Array.from(new Set(allLabels)); - const toAdd = deduped.filter(l => !existingSet.has(l)); - - console.log(`[sync] Existing: ${existing.length ? existing.join(', ') : '(none)'}`); - console.log(`[sync] To add: ${toAdd.length ? toAdd.join(', ') : '(none)'}`); + const currentLabels = (prData.labels || []).map(l => typeof l === 'string' ? l : l.name); + console.log(`Current PR labels: [${currentLabels.join(', ')}]`); - const labels = toAdd; - const hasLabels = labels.length > 0; - core.setOutput('has_labels', String(hasLabels)); - core.setOutput('labels', JSON.stringify(labels)); - core.setOutput('pr_number', String(prNumber)); - core.setOutput('dry_run', String(process.env.REQUESTED_DRY_RUN || 'true')); - core.setOutput('is_fork_pr', String(process.env.IS_FORK_PR || 'false')); - core.setOutput('source_event', context.eventName); - return { has_labels: hasLabels, labels, pr_number: String(prNumber), dry_run: process.env.REQUESTED_DRY_RUN, is_fork_pr: process.env.IS_FORK_PR, source_event: context.eventName }; + const newLabels = Array.from(discoveredLabels).filter(label => !currentLabels.includes(label)); + + if (newLabels.length > 0) { + console.log(`New labels to be added: [${newLabels.join(', ')}]`); + } else { + console.log("No new labels discovered (all found labels are already present)."); + } - - name: Write labels artifact payload - env: - LABELS_JSON: ${{ steps.compute.outputs.labels }} - PR_NUMBER: ${{ steps.compute.outputs.pr_number }} - IS_FORK_PR: ${{ steps.compute.outputs.is_fork_pr }} - DRY_RUN: ${{ steps.compute.outputs.dry_run }} - SOURCE_EVENT: ${{ steps.compute.outputs.source_event }} - uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 - with: - script: | const fs = require('fs'); - const parsed = JSON.parse(process.env.LABELS_JSON || '[]'); - const payload = { - pr_number: Number(process.env.PR_NUMBER || 0), - labels: Array.isArray(parsed) ? parsed : [], - is_fork_pr: /^true$/i.test(process.env.IS_FORK_PR || ''), - dry_run: /^true$/i.test(process.env.DRY_RUN || ''), - source_event: process.env.SOURCE_EVENT || '', - }; - fs.writeFileSync('labels.json', JSON.stringify(payload)); - console.log(`Wrote labels artifact payload for PR #${payload.pr_number}: ${payload.labels.length} labels`); - - - name: Upload labels artifact + const result = { pr_number: prNumber, labels: newLabels }; + fs.writeFileSync('labels.json', JSON.stringify(result)); + console.log(`Calculated labels: ${newLabels.join(', ')}`); + + - name: Check for Labels File + id: check_file + run: | + if [ -f labels.json ]; then + echo "exists=true" >> $GITHUB_OUTPUT + else + echo "exists=false" >> $GITHUB_OUTPUT + fi + + - name: Upload Artifact + if: steps.check_file.outputs.exists == 'true' uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 with: - name: pr-labels-${{ steps.compute.outputs.pr_number }} + name: pr-labels-data path: labels.json retention-days: 1 - - dispatch-add: - needs: compute-labels - if: ${{ needs.compute-labels.outputs.is_fork_pr != 'true' }} - runs-on: ubuntu-latest - steps: - - name: Trigger add workflow - uses: step-security/workflow-dispatch@acca1a315af3bf7f33dd116d3cb405cb83f5cbdc # v1.2.8 - with: - workflow: .github/workflows/sync-issue-labels-add.yml - repo: ${{ github.repository }} - ref: main - token: ${{ secrets.GH_ACCESS_TOKEN }} - inputs: >- - { - "upstream_run_id":"${{ github.run_id }}", - "pr_number":"${{ needs.compute-labels.outputs.pr_number }}", - "dry_run":"${{ needs.compute-labels.outputs.dry_run }}", - "is_fork_pr":"${{ needs.compute-labels.outputs.is_fork_pr }}" - } - diff --git a/.github/workflows/tck-test.yml b/.github/workflows/tck-test.yml index d5717b155..9b37a817b 100644 --- a/.github/workflows/tck-test.yml +++ b/.github/workflows/tck-test.yml @@ -27,7 +27,7 @@ jobs: uses: step-security/harden-runner@fe104658747b27e96e4f7e80cd0a94068e53901d # v2.16.1 with: egress-policy: audit - + - name: Checkout repository uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 @@ -46,7 +46,7 @@ jobs: - name: Generate Proto Files run: uv run generate_proto.py - + - name: Run unit tests run: | uv run pytest tests/tck -v diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index b09832e48..ac06cb43c 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,14 +1,15 @@ repos: - repo: https://github.com/gitleaks/gitleaks - rev: v8.16.3 + rev: v8.30.0 hooks: - id: gitleaks - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.4.0 + rev: v6.0.0 hooks: - id: end-of-file-fixer - id: trailing-whitespace -- repo: https://github.com/pylint-dev/pylint - rev: v2.17.2 +- repo: https://github.com/astral-sh/ruff-pre-commit + rev: v0.15.9 hooks: - - id: pylint + - id: ruff-check + - id: ruff-format diff --git a/CHANGELOG.md b/CHANGELOG.md index 5de531849..bacd7fb9c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -436,7 +436,7 @@ This changelog is based on [Keep a Changelog](https://keepachangelog.com/en/1.1. - chore: clarify wording in the bot-assignment-check.sh (#1748) - Refactored SDK dependencies to use version ranges, moved build-only deps out of runtime, removed unused core deps and added optional extras. - + ### Fixed - Added a fork guard condition to prevent Codecov upload failures on fork PRs due to missing token. (`#1485`) - Corrected broken documentation links in SDK developer training files.(#1707) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index dd201a87d..5106bbdec 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -169,4 +169,3 @@ Thank you for contributing to the Hiero Python SDK! 🎉 - **Quick Links:** - Join the main [Linux Foundation Decentralized Trust (LFDT) Discord Server](https://discord.gg/hyperledger). - Go directly to the [#hiero-python-sdk channel](https://discord.com/channels/905194001349627914/1336494517544681563) - diff --git a/README.md b/README.md index 6a89fc4eb..5df8b359c 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ A Python SDK for interacting with the Hedera Hashgraph platform. ->**Python compatibility:** +>**Python compatibility:** > The SDK supports Python ≥ 3.10 and is tested on Python 3.10–3.14. Newer Python versions may work but have not yet been validated. ## Quick Start diff --git a/RELEASE.md b/RELEASE.md index a31463374..c23d59895 100644 --- a/RELEASE.md +++ b/RELEASE.md @@ -16,24 +16,24 @@ MAJOR.MINOR.PATCH ## Release Steps -1. **Update the Version** +1. **Update the Version** Decide whether the changes are major, minor, or patch increments. -2. **Update the Changelog** +2. **Update the Changelog** Move your entries from the **[Unreleased]** section in `CHANGELOG.md` to a new version heading with today’s date (e.g., `## [0.2.0] - 2025-02-20`). -3. **Create a Release Branch** +3. **Create a Release Branch** - `release-v0.2.0` or similar (e.g., `release-v0.2.0-beta.1` for beta versions). -4. **Run Tests** +4. **Run Tests** - Ensure all tests pass locally (run `pytest`). - Confirm CI passes (integration tests, etc.). -5. **Merge into `main`** +5. **Merge into `main`** - Create a Pull Request from `release-vX.X.X` into `main`. - Wait for code review, ensure everything is green. -6. **Tag the Release** +6. **Tag the Release** Once merged, create a git tag with the new version: ```bash git tag -a v0.2.0 -m "Release 0.2.0" diff --git a/codecov.yml b/codecov.yml index 34b3fa451..99a96aff5 100644 --- a/codecov.yml +++ b/codecov.yml @@ -10,7 +10,7 @@ coverage: target: 92% threshold: 2% -ignore: +ignore: - "tck/**" comment: diff --git a/docs/blogs.md b/docs/blogs.md index 10c555ef3..95b5f1758 100644 --- a/docs/blogs.md +++ b/docs/blogs.md @@ -1,4 +1,4 @@ -# Writing Blog Posts +# Writing Blog Posts We welcome blog posts to the **Hiero Website Repository** about the Python SDK @@ -18,4 +18,4 @@ Additional helpful resources: The Hiero website community will review your new blog post and publish it once approved. -Thank you! \ No newline at end of file +Thank you! diff --git a/docs/community-calls/18-02-25.md b/docs/community-calls/18-02-25.md index 911b68dc9..b2952eede 100644 --- a/docs/community-calls/18-02-25.md +++ b/docs/community-calls/18-02-25.md @@ -42,11 +42,11 @@ - `main` is always stable/production-ready. - **Committing**: Use conventional commit prefixes such as `feat`, `fix`, `docs`, `chore`, `test`, `refactor`, `style`. - Meaningful commits are more important than perfect prefixes. - - **Outcome**: - - PR titles should ideally include `feat:` or `fix:`. + - **Outcome**: + - PR titles should ideally include `feat:` or `fix:`. - Individual commits can be more descriptive if that’s more natural, but clarity is key. -- **Merging**: - - **Squash & Merge** is preferred. +- **Merging**: + - **Squash & Merge** is preferred. - Use **GPG key signing** (e.g., `git commit -s -S -m "added docstrings"`). - **Outcome**: General consensus on using squash, ensuring commits are signed, and writing good commit messages. @@ -57,14 +57,14 @@ - Suggestion that future examples could become a general doc referencing multiple SDKs, with a link to Hiero specifics. - For now, we’ll add a specialized `README` in `/examples` to document syntax and transaction types. -**Outcome**: +**Outcome**: - Decision to move the current syntax/transaction-type content from `README.md` into a new `README` under `examples/`. --- ## Questions / Additional Notes -- Concerns were raised about GPG key setup on macOS requiring extra configuration not documented. +- Concerns were raised about GPG key setup on macOS requiring extra configuration not documented. - **Action**: update `README.md` with a GPG setup walkthrough. - Issues raised installing `uv` and running tests. All resolved, will be documented with steps. - Differences across various SDKs were noted. Plans to start an **SDK meeting in two weeks** to explore more seamless cross-SDK development. @@ -75,15 +75,14 @@ ## Summary of Outcomes -1. **Versioning**: Adopt `0.1.0` for next release; continue using semantic versioning. -2. **Release Cadence**: Aim for every 2 weeks, or extend to 4 weeks if needed. -3. **Branching & Committing**: Trunk-based with feature/fix branches, conventional commit messages (with some flexibility). -4. **Merging**: Prefer squash and GPG signing. -5. **README**: Move large sections (e.g., syntax/transaction details) to a separate doc in `/examples/`. +1. **Versioning**: Adopt `0.1.0` for next release; continue using semantic versioning. +2. **Release Cadence**: Aim for every 2 weeks, or extend to 4 weeks if needed. +3. **Branching & Committing**: Trunk-based with feature/fix branches, conventional commit messages (with some flexibility). +4. **Merging**: Prefer squash and GPG signing. +5. **README**: Move large sections (e.g., syntax/transaction details) to a separate doc in `/examples/`. 6. **Action Items**: - Finalize the `0.1.0` release. - Document GPG setup steps for macOS users. - Provide notes on `uv` installation/test fix. - Schedule a cross-SDK alignment meeting in two weeks. - Remove dummy versions. - diff --git a/docs/discord.md b/docs/discord.md index 5c8006b50..1e2d80573 100644 --- a/docs/discord.md +++ b/docs/discord.md @@ -38,4 +38,4 @@ Follow these essential steps: --- -Congratulations! You've joined the Hiero Python developer community chat. \ No newline at end of file +Congratulations! You've joined the Hiero Python developer community chat. diff --git a/docs/github/04_workflow_documentation.md b/docs/github/04_workflow_documentation.md index 239137d99..726715cf1 100644 --- a/docs/github/04_workflow_documentation.md +++ b/docs/github/04_workflow_documentation.md @@ -64,15 +64,15 @@ jobs: gfi-assign: if: github.event.issue.pull_request == null runs-on: hl-sdk-py-lin-md - + concurrency: group: gfi-assign-${{ github.event.issue.number }} cancel-in-progress: false - + steps: - name: Checkout repository uses: actions/checkout@v6.0.1 - + - name: Run GFI /assign handler uses: actions/github-script@v8.0.0 with: @@ -101,13 +101,13 @@ permissions: jobs: changelog-check: runs-on: hl-sdk-py-lin-md - + steps: - name: Checkout repository uses: actions/checkout@v6.0.1 with: fetch-depth: 0 - + - name: Run changelog validation env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} @@ -227,31 +227,31 @@ function getSkillLevel(issueLabels) { ```javascript /** * Determines the contributor difficulty tier from issue labels. - * + * * Assumes exactly one skill label should be present on the issue. * If multiple skill labels exist, the first match in priority order is returned. * Returns null if no skill label is found, which triggers maintainer escalation * notifications. - * + * * @param {string[]} issueLabels - List of GitHub label strings on the issue - * @returns {string|null} Skill level identifier (Good First Issue, Beginner, + * @returns {string|null} Skill level identifier (Good First Issue, Beginner, * Intermediate, Advanced) or null if no skill label found - * + * * @sideEffects None. This is a pure function that only reads labels. - * + * * @edgeCases * - Multiple skill labels: Priority order is Good First Issue > Beginner > Intermediate > Advanced * - No labels: Returns null (expected to trigger escalation) */ function determineContributorSkillLevelFromLabels(issueLabels) { const skillLabels = ['Good First Issue', 'Beginner', 'Intermediate', 'Advanced']; - + for (const skillLabel of skillLabels) { if (issueLabels.includes(skillLabel)) { return skillLabel; } } - + return null; } ``` @@ -261,21 +261,21 @@ function determineContributorSkillLevelFromLabels(issueLabels) { ```javascript /** * Extracts listed skill prerequisites from the issue body. - * + * * Looks for a "## Prerequisites" section and parses the checklist items. - * + * * @assumptions * - Prerequisites section format is consistent with issue templates * - Each prerequisite is a checklist item (- [ ] or - [x]) * - Unknown prerequisites are skipped with a warning log - * + * * @param {string} issueBody - The markdown text of the GitHub issue * @returns {string[]} List of prerequisite identifiers found in the issue - * + * * @sideEffects * - Logs warnings for unrecognized prerequisites via console.warn * - Does NOT validate if prerequisites are met - * + * * @edgeCases * - Missing "## Prerequisites" section returns empty array * - Malformed lines in prerequisites are skipped silently @@ -331,21 +331,21 @@ async function validateAssignmentPrerequisites(contributorId, issue, github) { console.warn(`Contributor ${contributorId} not found or inactive`); return false; } - + /* Exit condition 2: Verify issue has required skill labels */ const skillLabels = extractSkillLabels(issue); if (skillLabels.length === 0) { console.error(`Issue ${issue.number} missing skill level label`); return false; } - + /* Exit condition 3: Check contributor skill matches issue requirement */ const requiredSkill = skillLabels[0]; if (!contributorHasSkill(contributorId, requiredSkill)) { console.info(`Contributor ${contributorId} lacks skill: ${requiredSkill}`); return false; } - + return true; } ``` @@ -425,7 +425,7 @@ async function transitionIssueLabelsOnAssignment(issue, oldSkillLevel, newSkillL --- -Great workflow documentation follows a simple principle: separate orchestration from logic. +Great workflow documentation follows a simple principle: separate orchestration from logic. - **Workflow YAML files** document triggers and script references - **Script files** document major rules and implementation details diff --git a/docs/maintainers/advanced_issue_guidelines.md b/docs/maintainers/advanced_issue_guidelines.md index cb18a042b..8f806a8cd 100644 --- a/docs/maintainers/advanced_issue_guidelines.md +++ b/docs/maintainers/advanced_issue_guidelines.md @@ -8,15 +8,15 @@ in the Hiero Python SDK repository. It offers shared language and examples to help: **Issue creators:** -- Describe larger, more complex tasks clearly -- Set expectations around scope, impact, and collaboration -- Provide helpful context for experienced contributors +- Describe larger, more complex tasks clearly +- Set expectations around scope, impact, and collaboration +- Provide helpful context for experienced contributors **Maintainers:** -- Apply the Advanced label consistently -- Keep issue difficulty labels clear and useful +- Apply the Advanced label consistently +- Keep issue difficulty labels clear and useful -This isn’t a rulebook, and it’s not meant to limit what kinds of contributions are welcome. +This isn’t a rulebook, and it’s not meant to limit what kinds of contributions are welcome. All contributions — from small fixes to major improvements — are valuable to the Hiero project. The **Advanced** label simply highlights work that involves deeper design, broader impact, @@ -30,9 +30,9 @@ Advanced Issues represent **high-impact, high-responsibility work**. They’re a great fit for contributors who: -- Have deep familiarity with the Python SDK -- Enjoy designing solutions and evaluating trade-offs -- Are comfortable thinking about long-term impact +- Have deep familiarity with the Python SDK +- Enjoy designing solutions and evaluating trade-offs +- Are comfortable thinking about long-term impact These issues often involve shaping how the SDK evolves over time. @@ -42,10 +42,10 @@ These issues often involve shaping how the SDK evolves over time. Advanced Issues are designed for contributors who: -- Have strong Python SDK and domain knowledge -- Understand performance, concurrency, and API stability considerations -- Feel comfortable proposing and discussing designs -- Are open to conversations about breaking changes and long-term direction +- Have strong Python SDK and domain knowledge +- Understand performance, concurrency, and API stability considerations +- Feel comfortable proposing and discussing designs +- Are open to conversations about breaking changes and long-term direction These issues usually involve more discussion, iteration, and collaboration than earlier issue levels. @@ -55,10 +55,10 @@ These issues usually involve more discussion, iteration, and collaboration than Advanced Issues often: -- Are design-heavy -- Affect multiple parts of the SDK -- Have long-term maintenance impact -- Involve discussion, iteration, and review +- Are design-heavy +- Affect multiple parts of the SDK +- Have long-term maintenance impact +- Involve discussion, iteration, and review They’re a great fit for contributors who enjoy tackling complex problems and shaping the future of the project. @@ -69,39 +69,39 @@ They’re a great fit for contributors who enjoy tackling complex problems and s Here are some examples of tasks that often fit well at this level: ### Core SDK Changes -- Significant behavior changes with clear motivation -- Refactors spanning multiple related subsystems -- Improvements to core execution paths or abstractions -- Bug fixes that require investigation across multiple layers +- Significant behavior changes with clear motivation +- Refactors spanning multiple related subsystems +- Improvements to core execution paths or abstractions +- Bug fixes that require investigation across multiple layers ### Architecture & Design -- Introducing new abstractions or subsystems -- Improving extensibility or testability through redesign -- Decoupling tightly coupled components -- Addressing systemic architectural issues +- Introducing new abstractions or subsystems +- Improving extensibility or testability through redesign +- Decoupling tightly coupled components +- Addressing systemic architectural issues ### Interfaces & Contracts -- Evolving public or internal APIs with clear rationale -- Formalizing or refining existing contracts -- Improving type consistency across large areas of the codebase -- Introducing shared types or protocols +- Evolving public or internal APIs with clear rationale +- Formalizing or refining existing contracts +- Improving type consistency across large areas of the codebase +- Introducing shared types or protocols ### Documentation & Guidance -- Writing or updating architectural documentation -- Explaining non-obvious design decisions -- Adding migration notes or deprecation guidance -- Aligning docs with new behavior or APIs +- Writing or updating architectural documentation +- Explaining non-obvious design decisions +- Adding migration notes or deprecation guidance +- Aligning docs with new behavior or APIs ### Examples & Developer Experience -- Designing new examples for advanced features -- Updating examples to reflect new APIs or workflows -- Improving clarity around advanced usage patterns +- Designing new examples for advanced features +- Updating examples to reflect new APIs or workflows +- Improving clarity around advanced usage patterns ### Testing & Validation -- Designing new test strategies -- Adding comprehensive coverage for new abstractions -- Refactoring test architecture to support new designs -- Introducing regression tests for complex scenarios +- Designing new test strategies +- Adding comprehensive coverage for new abstractions +- Refactoring test architecture to support new designs +- Introducing regression tests for complex scenarios --- @@ -111,18 +111,18 @@ Advanced Issues are not just “bigger versions” of other issue types. If a task: -- Can be completed by following existing patterns -- Is mostly mechanical or scripted -- Has very limited impact or risk +- Can be completed by following existing patterns +- Is mostly mechanical or scripted +- Has very limited impact or risk …it may be a better fit for **Beginner** or **Intermediate** labels. Advanced Issues usually involve: -- Design choices -- Trade-offs -- Broader context -- Long-term considerations +- Design choices +- Trade-offs +- Broader context +- Long-term considerations --- @@ -130,9 +130,9 @@ Advanced Issues usually involve: Advanced Issues are usually: -- ⏱ **Estimated time:** 3+ days -- 📄 **Scope:** Multiple modules or repository-wide -- 🧠 **Challenge level:** Design, iteration, and long-term ownership +- ⏱ **Estimated time:** 3+ days +- 📄 **Scope:** Multiple modules or repository-wide +- 🧠 **Challenge level:** Design, iteration, and long-term ownership They often evolve through discussion and may require multiple review cycles. @@ -142,32 +142,32 @@ They often evolve through discussion and may require multiple review cycles. ### Implement HIP-1261 fee estimate query support in the Python SDK -The Hiero Python SDK doesn’t currently support fee estimate queries as defined in +The Hiero Python SDK doesn’t currently support fee estimate queries as defined in HIP-1261. This makes it harder for developers to programmatically estimate transaction fees before execution. This issue focuses on **designing and implementing full SDK support** for HIP-1261, including: -- Public APIs -- Internal request/response handling -- Tests and examples +- Public APIs +- Internal request/response handling +- Tests and examples The implementation should align with the HIP specification and stay consistent with patterns used across other SDKs. -**Reference design document:** +**Reference design document:** https://github.com/hiero-ledger/sdk-collaboration-hub/blob/main/proposals/hips/hip-1261.md ### Suggested Steps -1. Review HIP-1261 to understand the intended behavior and constraints -2. Design the Python SDK API surface for fee estimate queries +1. Review HIP-1261 to understand the intended behavior and constraints +2. Design the Python SDK API surface for fee estimate queries 3. Implement the feature across the SDK, including: - - Public-facing query or transaction classes - - Internal request/response handling - - Validation and error handling -4. Add unit and integration tests -5. Provide at least one usage example + - Public-facing query or transaction classes + - Internal request/response handling + - Validation and error handling +4. Add unit and integration tests +5. Provide at least one usage example --- @@ -175,15 +175,15 @@ https://github.com/hiero-ledger/sdk-collaboration-hub/blob/main/proposals/hips/h Advanced Issues are supported through: -- Design discussions in issues and PRs -- Maintainer and community feedback -- Iterative review cycles +- Design discussions in issues and PRs +- Maintainer and community feedback +- Iterative review cycles Support focuses on: -- Exploring design options -- Evaluating trade-offs -- Ensuring long-term maintainability +- Exploring design options +- Evaluating trade-offs +- Ensuring long-term maintainability The goal is to build strong, well-considered solutions together. @@ -193,11 +193,11 @@ The goal is to build strong, well-considered solutions together. An issue is often a good fit for the **Advanced** label when it: -- Involves system-level thinking -- Has long-term impact on the SDK -- Benefits from experienced review and iteration +- Involves system-level thinking +- Has long-term impact on the SDK +- Benefits from experienced review and iteration --- -Advanced Issues are about shaping the future of the project — +Advanced Issues are about shaping the future of the project — through thoughtful design, collaboration, and long-term vision. diff --git a/docs/maintainers/beginner_issues_guidelines.md b/docs/maintainers/beginner_issues_guidelines.md index 50203abb5..e53b5ff42 100644 --- a/docs/maintainers/beginner_issues_guidelines.md +++ b/docs/maintainers/beginner_issues_guidelines.md @@ -8,15 +8,15 @@ in the Hiero Python SDK repository. It helps: **Issue creators:** -- Describe approachable, confidence-building tasks -- Set expectations without over-prescribing solutions -- Provide helpful context for newer contributors +- Describe approachable, confidence-building tasks +- Set expectations without over-prescribing solutions +- Provide helpful context for newer contributors **Maintainers:** -- Apply the Beginner label consistently -- Keep issue difficulty levels clear and welcoming +- Apply the Beginner label consistently +- Keep issue difficulty levels clear and welcoming -This isn’t a rulebook, and it’s not meant to limit what kinds of contributions are welcome. +This isn’t a rulebook, and it’s not meant to limit what kinds of contributions are welcome. All contributions — from small fixes to larger improvements — are valuable to the Hiero project. The **Beginner** label highlights work that supports learning, exploration, and growing confidence. @@ -29,15 +29,15 @@ Beginner Issues represent the **next step after Good First Issues**. They’re a great fit for contributors who: -- Are comfortable with the basic contribution workflow -- Want to explore the codebase a little more -- Are ready to take on slightly more independent work +- Are comfortable with the basic contribution workflow +- Want to explore the codebase a little more +- Are ready to take on slightly more independent work These issues help contributors grow their confidence in: -- Reading and understanding existing code -- Making small, thoughtful changes -- Asking good questions and learning by doing +- Reading and understanding existing code +- Making small, thoughtful changes +- Asking good questions and learning by doing --- @@ -45,10 +45,10 @@ These issues help contributors grow their confidence in: Beginner Issues are designed for contributors who: -- Have basic Python experience -- Are familiar with Git and the SDK workflow -- Can navigate a few files in the repository -- Are open to learning how things work under the hood +- Have basic Python experience +- Are familiar with Git and the SDK workflow +- Can navigate a few files in the repository +- Are open to learning how things work under the hood These issues usually involve more exploration than Good First Issues, but still stay focused, approachable, and well-scoped. @@ -59,10 +59,10 @@ but still stay focused, approachable, and well-scoped. Beginner Issues often: -- Involve a small number of files -- Require reading and understanding existing behavior -- Leave room for small decisions and learning -- Stay focused on a clear, specific goal +- Involve a small number of files +- Require reading and understanding existing behavior +- Leave room for small decisions and learning +- Stay focused on a clear, specific goal They’re a great fit for contributors who want to grow their skills without feeling overwhelmed. @@ -73,41 +73,41 @@ They’re a great fit for contributors who want to grow their skills without fee Here are examples of tasks that often fit well at this level: ### Core SDK Changes -- Improving `__str__` or `__repr__` methods -- Small, localized improvements to utility functions -- Minor behavior tweaks with clear intent -- Changes that require understanding how existing code behaves +- Improving `__str__` or `__repr__` methods +- Small, localized improvements to utility functions +- Minor behavior tweaks with clear intent +- Changes that require understanding how existing code behaves ### Typing & Code Quality -- Adding missing type hints in simple functions -- Fixing incorrect type annotations -- Improving consistency within a file +- Adding missing type hints in simple functions +- Fixing incorrect type annotations +- Improving consistency within a file ### Documentation -- Writing or expanding docs for narrow, well-defined areas -- Improving clarity in existing documentation -- Adding helpful explanations to examples +- Writing or expanding docs for narrow, well-defined areas +- Improving clarity in existing documentation +- Adding helpful explanations to examples ### Examples -- Creating simple examples based on existing ones -- Adding missing steps to demonstrate functionality -- Improving readability or output clarity -- Making examples more instructional +- Creating simple examples based on existing ones +- Adding missing steps to demonstrate functionality +- Improving readability or output clarity +- Making examples more instructional ### Tests -- Extending existing tests with additional assertions -- Covering specific edge cases -- Improving test naming and clarity +- Extending existing tests with additional assertions +- Covering specific edge cases +- Improving test naming and clarity --- ## Usually Not Good Fits -- Purely mechanical tasks -- Large architectural refactors -- Cross-cutting changes across many subsystems -- Work requiring deep protocol or DLT expertise -- Designing new testing frameworks +- Purely mechanical tasks +- Large architectural refactors +- Cross-cutting changes across many subsystems +- Work requiring deep protocol or DLT expertise +- Designing new testing frameworks These tasks may be a better fit for **Good First Issue**, **Intermediate**, or **Advanced** labels. @@ -117,9 +117,9 @@ These tasks may be a better fit for **Good First Issue**, **Intermediate**, or * Beginner Issues are usually: -- ⏱ **Estimated time:** A few hours to a day -- 📄 **Scope:** One or a few related files -- 🧠 **Challenge level:** Exploration, learning, and light decision-making +- ⏱ **Estimated time:** A few hours to a day +- 📄 **Scope:** One or a few related files +- 🧠 **Challenge level:** Exploration, learning, and light decision-making They’re designed to be achievable in a single pull request. @@ -129,15 +129,15 @@ They’re designed to be achievable in a single pull request. Beginner Issues are supported through: -- Issue and PR discussions -- Maintainer and community feedback -- Friendly guidance and clarification +- Issue and PR discussions +- Maintainer and community feedback +- Friendly guidance and clarification Support focuses on: -- Helping contributors understand the code -- Clarifying expectations -- Making the learning process enjoyable +- Helping contributors understand the code +- Clarifying expectations +- Making the learning process enjoyable The goal is to build confidence while keeping the experience positive and encouraging. @@ -147,19 +147,19 @@ The goal is to build confidence while keeping the experience positive and encour An issue is often a good fit for the **Beginner** label when it: -- Builds naturally on Good First Issues -- Requires light investigation or interpretation -- Has a clear goal without being fully scripted -- Encourages learning and questions +- Builds naturally on Good First Issues +- Requires light investigation or interpretation +- Has a clear goal without being fully scripted +- Encourages learning and questions An issue may be better suited for another label if it: -- Is purely mechanical -- Requires deep domain expertise -- Spans many unrelated parts of the codebase -- Involves architectural or design-level decisions +- Is purely mechanical +- Requires deep domain expertise +- Spans many unrelated parts of the codebase +- Involves architectural or design-level decisions --- -Beginner Issues are about growing skills — +Beginner Issues are about growing skills — through curiosity, exploration, and thoughtful contributions. diff --git a/docs/maintainers/difficulty_overview_guidelines.md b/docs/maintainers/difficulty_overview_guidelines.md index e72e6d7d0..08f848efd 100644 --- a/docs/maintainers/difficulty_overview_guidelines.md +++ b/docs/maintainers/difficulty_overview_guidelines.md @@ -2,22 +2,22 @@ ## How to Use This Document -This page gives a simple overview of how issue labels are used in the +This page gives a simple overview of how issue labels are used in the Hiero **Python SDK** repository. It’s here to help: **Contributors:** -- Find issues that match their experience level -- Understand what to expect from different types of tasks -- Grow their skills over time +- Find issues that match their experience level +- Understand what to expect from different types of tasks +- Grow their skills over time **Maintainers:** -- Apply labels consistently -- Set clear expectations for issue scope and readiness -- Make the contribution experience smoother for everyone +- Apply labels consistently +- Set clear expectations for issue scope and readiness +- Make the contribution experience smoother for everyone -This isn’t a rulebook, and it’s not about limiting who can contribute. +This isn’t a rulebook, and it’s not about limiting who can contribute. It’s about helping people find the right starting point and build confidence along the way. --- @@ -26,10 +26,10 @@ It’s about helping people find the right starting point and build confidence a The labeling system is designed to: -- Help contributors choose tasks that feel comfortable and achievable -- Make expectations around scope and independence clearer -- Support a natural progression from first PR to deeper ownership -- Create a positive, trust-building contribution experience +- Help contributors choose tasks that feel comfortable and achievable +- Make expectations around scope and independence clearer +- Support a natural progression from first PR to deeper ownership +- Create a positive, trust-building contribution experience Everyone is welcome to contribute — labels simply help guide the journey. @@ -39,15 +39,15 @@ Everyone is welcome to contribute — labels simply help guide the journey. Each issue typically has: -- **One difficulty label** - (Good First Issue, Beginner, Intermediate, or Advanced) -- An optional **readiness label** - (Good First Issue: Candidate) +- **One difficulty label** + (Good First Issue, Beginner, Intermediate, or Advanced) +- An optional **readiness label** + (Good First Issue: Candidate) Together, these give contributors a sense of both: -- How complex the task is -- How ready it is for first-time contributors +- How complex the task is +- How ready it is for first-time contributors --- @@ -57,7 +57,7 @@ Together, these give contributors a sense of both: |------|----------------| | **Good First Issue: Candidate** | This issue might become a Good First Issue, but could use a little more clarity or polish first | -The candidate label is a friendly “almost ready” marker. +The candidate label is a friendly “almost ready” marker. It gives maintainers time to refine the issue before promoting it. --- @@ -73,22 +73,22 @@ It gives maintainers time to refine the issue before promoting it. Each level reflects a gradual increase in: -- Independence -- Context -- Technical ownership +- Independence +- Context +- Technical ownership --- ## How the Labels Work Together -- **Good First Issue: Candidate** - → Signals potential, not final readiness +- **Good First Issue: Candidate** + → Signals potential, not final readiness -- **Good First Issue** - → Fully guided, step-by-step, and friendly for first-time contributors +- **Good First Issue** + → Fully guided, step-by-step, and friendly for first-time contributors -- **Beginner / Intermediate / Advanced** - → Indicate increasing levels of exploration, judgment, and ownership +- **Beginner / Intermediate / Advanced** + → Indicate increasing levels of exploration, judgment, and ownership The goal is to make it easy for contributors to find issues that match their comfort level and grow from there. @@ -98,12 +98,12 @@ The goal is to make it easy for contributors to find issues that match their com In general: -- Each issue has **one main difficulty label** -- The candidate label is optional and temporary -- If a task needs more clarity, it can stay in the candidate stage -- If an issue feels more complex than expected, it’s okay to choose a higher difficulty level +- Each issue has **one main difficulty label** +- The candidate label is optional and temporary +- If a task needs more clarity, it can stay in the candidate stage +- If an issue feels more complex than expected, it’s okay to choose a higher difficulty level -Exploratory or open-ended issues usually work best with +Exploratory or open-ended issues usually work best with **Intermediate** or **Advanced** labels, where discussion and design are part of the process. --- @@ -114,10 +114,10 @@ The candidate label exists to help maintainers prepare issues for new contributo Once an issue feels: -- Clear -- Well-scoped -- Easy to follow -- And friendly for first-time contributors +- Clear +- Well-scoped +- Easy to follow +- And friendly for first-time contributors It can be promoted to a **Good First Issue**. @@ -129,10 +129,10 @@ Taking a little extra time here helps create a smoother, more welcoming experien Issue labels exist to: -- Support contributors -- Set clear expectations -- Encourage steady skill growth -- Build confidence over time +- Support contributors +- Set clear expectations +- Encourage steady skill growth +- Build confidence over time **Accuracy matters more than volume**, because a great first experience is worth more than a long list of confusing issues. diff --git a/docs/maintainers/good_first_issue_candidate_guidelines.md b/docs/maintainers/good_first_issue_candidate_guidelines.md index ebc4e9bb8..daf2eaf65 100644 --- a/docs/maintainers/good_first_issue_candidate_guidelines.md +++ b/docs/maintainers/good_first_issue_candidate_guidelines.md @@ -1,23 +1,23 @@ -# Good First Issue — Candidate Guidelines +# Good First Issue — Candidate Guidelines **Hiero Python SDK** ## How to Use This Document -This guide is intended to support **maintainers and issue creators** who use the +This guide is intended to support **maintainers and issue creators** who use the **`good first issue: candidate`** label. It provides shared language and guidance to help: **Issue creators:** -- Flag issues that *might* be a good starting point for new contributors -- Identify where additional clarity or detail is needed -- Prepare issues for promotion to a full Good First Issue +- Flag issues that *might* be a good starting point for new contributors +- Identify where additional clarity or detail is needed +- Prepare issues for promotion to a full Good First Issue **Maintainers:** -- Protect the quality and trustworthiness of Good First Issues -- Give issues time to be refined before promotion +- Protect the quality and trustworthiness of Good First Issues +- Give issues time to be refined before promotion -This document is not a strict rulebook, and it is not meant to limit what kinds of contributions are welcome. +This document is not a strict rulebook, and it is not meant to limit what kinds of contributions are welcome. All contributions are valuable to the Hiero project. The **candidate** label simply helps ensure that Good First Issues offer a smooth and confidence-building first experience. @@ -38,10 +38,10 @@ The candidate label is **not** a softer or lower-quality GFI — it is a **prepa Some issues look small and approachable, but may still be missing: -- Clear step-by-step instructions -- Explicit file paths or locations -- Well-defined acceptance criteria -- Confidence about difficulty or scope +- Clear step-by-step instructions +- Explicit file paths or locations +- Well-defined acceptance criteria +- Confidence about difficulty or scope The candidate label helps signal: @@ -59,9 +59,9 @@ Apply the **candidate** label when an issue appears suitable for a Good First Is The issue likely qualifies because: -- The change appears small, localized, and low risk -- The task fits within allowed GFI categories (documentation, examples, typing, or small mechanical edits) -- The issue does not require exploration, investigation, or design decisions +- The change appears small, localized, and low risk +- The task fits within allowed GFI categories (documentation, examples, typing, or small mechanical edits) +- The issue does not require exploration, investigation, or design decisions Suitability can be evaluated using the @@ -73,8 +73,8 @@ Suitability can be evaluated using the One or more of the following is still missing: -- Explicit step-by-step implementation instructions -- Clear and objective acceptance criteria +- Explicit step-by-step implementation instructions +- Clear and objective acceptance criteria At this stage, the issue may include partial notes, rough ideas, or reminders for maintainers to clarify later — and that’s okay. @@ -84,8 +84,8 @@ At this stage, the issue may include partial notes, rough ideas, or reminders fo Use the candidate label when: -- The issue *seems* easy, but you are not fully confident -- The scope or effort needs maintainer confirmation +- The issue *seems* easy, but you are not fully confident +- The scope or effort needs maintainer confirmation The candidate stage allows time to confirm that the issue is truly appropriate for first-time contributors. @@ -101,9 +101,9 @@ The candidate label is intended for issues that are potential Good First Issues If a contributor must decide: -- *What* should change -- *How* something should behave -- *What* is correct or expected +- *What* should change +- *How* something should behave +- *What* is correct or expected Then the issue is **not** a candidate. @@ -113,10 +113,10 @@ Then the issue is **not** a candidate. Do not use the candidate label for issues involving: -- SDK or protocol behavior -- Public APIs or contracts -- `to_proto` / `from_proto` logic -- Serialization, deserialization, or networking +- SDK or protocol behavior +- Public APIs or contracts +- `to_proto` / `from_proto` logic +- Serialization, deserialization, or networking --- @@ -124,12 +124,12 @@ Do not use the candidate label for issues involving: Avoid using the candidate label for: -- Investigations or debugging tasks -- Issues blocked on other PRs or decisions -- Work requiring domain or protocol knowledge +- Investigations or debugging tasks +- Issues blocked on other PRs or decisions +- Work requiring domain or protocol knowledge -> ⚠️ If an issue clearly does *not* meet Good First Issue criteria, -> do **not** label it as a candidate. +> ⚠️ If an issue clearly does *not* meet Good First Issue criteria, +> do **not** label it as a candidate. > The candidate label is for issues that *might* qualify after refinement — not for issues that never will. --- @@ -143,20 +143,20 @@ An issue should only be promoted to a full **Good First Issue** once it is clear Before promoting an issue to Good First Issue, consider checking whether: - [ ] The problem is clearly described and easy to understand for someone new to the project. -- [ ] The solution is **explicitly specified** -- [ ] The change is small, localized, and low risk -- [ ] The issue touches a single file or clearly defined location -- [ ] Acceptance criteria are objective and complete -- [ ] No interpretation, investigation, or initiative is required +- [ ] The solution is **explicitly specified** +- [ ] The change is small, localized, and low risk +- [ ] The issue touches a single file or clearly defined location +- [ ] Acceptance criteria are objective and complete +- [ ] No interpretation, investigation, or initiative is required --- ### 🔁 Promotion Process 1. Review the issue against the [**Good First Issue Guidelines**](./good_first_issues_guidelines.md) -2. Add missing details, steps, and acceptance criteria -3. Remove the `good first issue: candidate` label -4. Apply the **Good First Issue** label +2. Add missing details, steps, and acceptance criteria +3. Remove the `good first issue: candidate` label +4. Apply the **Good First Issue** label There is no rush — taking the time to refine an issue is part of creating a welcoming and supportive community. @@ -166,6 +166,5 @@ There is no rush — taking the time to refine an issue is part of creating a we The **Good First Issue — Candidate** label helps make the Hiero Python SDK accessible and friendly for everyone — especially new contributors. -All contributions are welcome. +All contributions are welcome. This label simply helps highlight the best place to begin. - diff --git a/docs/maintainers/good_first_issues_guidelines.md b/docs/maintainers/good_first_issues_guidelines.md index 8ea15af7c..413f9de11 100644 --- a/docs/maintainers/good_first_issues_guidelines.md +++ b/docs/maintainers/good_first_issues_guidelines.md @@ -7,15 +7,15 @@ This guide is intended to help **maintainers and issue creators** use the **Good It provides shared language, examples, and guidance to help: **Issue creators:** -- Feel confident proposing a Good First Issue -- Understand what makes an issue approachable for new contributors -- Decide when a task might be better suited for another issue label +- Feel confident proposing a Good First Issue +- Understand what makes an issue approachable for new contributors +- Decide when a task might be better suited for another issue label **Maintainers:** -- Apply the Good First Issue label consistently -- Keep issue difficulty labels clear, predictable, and helpful +- Apply the Good First Issue label consistently +- Keep issue difficulty labels clear, predictable, and helpful -This document is not meant to limit contributions or discourage initiative. +This document is not meant to limit contributions or discourage initiative. All contributions — large or small — are valuable to the Hiero project. The Good First Issue label simply highlights tasks that are especially friendly for **first-time contributors**. @@ -28,16 +28,16 @@ Good First Issues (GFIs) are designed to provide a **welcoming, confidence-build They help contributors: -- Get set up successfully -- Navigate the repository -- Open their first pull request +- Get set up successfully +- Navigate the repository +- Open their first pull request -For many contributors, this is their **first interaction with open source** or with the Hiero SDK codebase. +For many contributors, this is their **first interaction with open source** or with the Hiero SDK codebase. Good First Issues are designed to make that experience smoother by offering: -- Clear, well-scoped tasks -- **Explicit or step-by-step implementation instructions** -- Predictable and easy-to-verify outcomes +- Clear, well-scoped tasks +- **Explicit or step-by-step implementation instructions** +- Predictable and easy-to-verify outcomes The emphasis is on learning the workflow — **not** on researching behavior, making design decisions, or filling in missing context. @@ -47,10 +47,10 @@ The emphasis is on learning the workflow — **not** on researching behavior, ma Good First Issues are intended for contributors who: -- Have basic Python knowledge -- Are familiar with Git and GitHub -- Are new to the Hiero SDK -- Do not yet understand the SDK’s architecture or internal design +- Have basic Python knowledge +- Are familiar with Git and GitHub +- Are new to the Hiero SDK +- Do not yet understand the SDK’s architecture or internal design Ideally, everything needed to complete the task is included directly in the issue description. @@ -62,16 +62,16 @@ If a task requires investigation, interpretation, or design judgment, it is like A Good First Issue works best when it is: -- Clearly defined -- **Fully specified or scripted with explicit instructions** -- Small in scope (often a single file or location) -- Easy to review and verify +- Clearly defined +- **Fully specified or scripted with explicit instructions** +- Small in scope (often a single file or location) +- Easy to review and verify The solution path should feel clear and direct, allowing contributors to focus on learning the contribution process rather than figuring out what needs to be done. ### Helpful Rule of Thumb -> If a contributor must **decide what to do or how something should work**, +> If a contributor must **decide what to do or how something should work**, > it is **not** a Good First Issue. --- @@ -80,9 +80,9 @@ The solution path should feel clear and direct, allowing contributors to focus o Good First Issues are intentionally small and focused: -- ⏱ **Estimated time:** ~1–4 hours (including setup) -- 📄 **Scope:** One file or a clearly defined section -- 🧠 **Type:** Straightforward, mechanical, low-risk changes +- ⏱ **Estimated time:** ~1–4 hours (including setup) +- 📄 **Scope:** One file or a clearly defined section +- 🧠 **Type:** Straightforward, mechanical, low-risk changes Most of the effort should go into getting comfortable with the workflow — not solving complex technical problems. @@ -94,16 +94,16 @@ The following are good fits for Good First Issues **when the solution is explici ### Small, Focused Source Changes -> ⚠️ In most cases, changes to `src` functionality are **not** Good First Issues. +> ⚠️ In most cases, changes to `src` functionality are **not** Good First Issues. > This category applies only when the change is **purely mechanical and fully specified**. ### Suitable for Good First Issues (rare, explicit cases only): -- Very small, explicitly described edits to existing code -- Changes that do not require understanding how the code is used elsewhere +- Very small, explicitly described edits to existing code +- Changes that do not require understanding how the code is used elsewhere ### Not Suitable for Good First Issues -- Any change that requires deciding *how* something should behave -- Any change that affects public behavior or SDK contracts +- Any change that requires deciding *how* something should behave +- Any change that affects public behavior or SDK contracts --- @@ -112,13 +112,13 @@ The following are good fits for Good First Issues **when the solution is explici Typing-related Good First Issues must be **fully specified and mechanical**. ### Suitable for Good First Issues -- Adding missing return type hints when the expected type is explicitly stated -- Fixing incorrect or overly broad annotations when the correct type is provided +- Adding missing return type hints when the expected type is explicitly stated +- Fixing incorrect or overly broad annotations when the correct type is provided ### Not Suitable for Good First Issues -- Inferring types by interpreting code behavior -- Cross-file or large-scale typing refactors -- Resolving complex type-system issues +- Inferring types by interpreting code behavior +- Cross-file or large-scale typing refactors +- Resolving complex type-system issues --- @@ -127,17 +127,17 @@ Typing-related Good First Issues must be **fully specified and mechanical**. Documentation tasks must be **explicitly scoped and instruction-driven**. ### Suitable for Good First Issues -- Fixing identified typos or grammar issues -- Replacing text with a provided version -- Making explicitly described changes to docstrings, comments, or print statements -- Renaming variables or examples when new names are provided -- Splitting or combining examples when explicitly instructed +- Fixing identified typos or grammar issues +- Replacing text with a provided version +- Making explicitly described changes to docstrings, comments, or print statements +- Renaming variables or examples when new names are provided +- Splitting or combining examples when explicitly instructed ### Not Suitable for Good First Issues -- Writing new documentation -- Adding explanations that require interpreting code behavior -- Deciding what should be documented -- Choosing which steps or details should exist +- Writing new documentation +- Adding explanations that require interpreting code behavior +- Deciding what should be documented +- Choosing which steps or details should exist --- @@ -146,23 +146,23 @@ Documentation tasks must be **explicitly scoped and instruction-driven**. > ⚠️ Most test-related work is better suited for **Beginner or Intermediate Issues**. ### Suitable for Good First Issues (rare, explicit cases only): -- Adding a clearly specified assertion to an existing test -- Small mechanical edits with no test-design decisions +- Adding a clearly specified assertion to an existing test +- Small mechanical edits with no test-design decisions ### Not Suitable for Good First Issues -- Creating new test files -- Designing new test cases -- Extending coverage based on interpretation +- Creating new test files +- Designing new test cases +- Extending coverage based on interpretation --- ## Summary: What Is NOT a Good First Issue -- ❌ Issues without a clearly defined or provided solution -- ❌ Tasks requiring interpretation, investigation, or initiative -- ❌ Changes to `src` functionality that affect behavior -- ❌ Creating new documentation, examples, or tests -- ❌ Work spanning multiple files or subsystems +- ❌ Issues without a clearly defined or provided solution +- ❌ Tasks requiring interpretation, investigation, or initiative +- ❌ Changes to `src` functionality that affect behavior +- ❌ Creating new documentation, examples, or tests +- ❌ Work spanning multiple files or subsystems --- @@ -170,21 +170,21 @@ Documentation tasks must be **explicitly scoped and instruction-driven**. ### Label an issue as GFI if it: -- ✅ Touches a single file or clearly defined location -- ✅ Has a clear, well-defined scope -- ✅ Requires no domain or protocol knowledge -- ✅ Can be reviewed quickly -- ✅ Has low risk of unintended side effects -- ✅ Includes explicit or step-by-step instructions +- ✅ Touches a single file or clearly defined location +- ✅ Has a clear, well-defined scope +- ✅ Requires no domain or protocol knowledge +- ✅ Can be reviewed quickly +- ✅ Has low risk of unintended side effects +- ✅ Includes explicit or step-by-step instructions ### Do NOT label an issue as GFI if it: -- ❌ Touches multiple subsystems -- ❌ Changes SDK behavior or contracts -- ❌ Requires domain or protocol knowledge -- ❌ Could introduce subtle side effects -- ❌ Requires extensive review or testing -- ❌ Requires interpretation or design decisions +- ❌ Touches multiple subsystems +- ❌ Changes SDK behavior or contracts +- ❌ Requires domain or protocol knowledge +- ❌ Could introduce subtle side effects +- ❌ Requires extensive review or testing +- ❌ Requires interpretation or design decisions Such issues are better labeled as **Beginner Issues**. @@ -192,18 +192,18 @@ Such issues are better labeled as **Beginner Issues**. ## Important Reminders -1. Good First Issues are often promoted automatically, making them highly visible -2. Good First Issues are typically self-assigned, so they must be achievable by anyone -3. Quality matters more than quantity — fewer, safer GFIs are better -4. Every GFI should clearly define what “done” means -5. Link to relevant documentation whenever possible to help contributors succeed +1. Good First Issues are often promoted automatically, making them highly visible +2. Good First Issues are typically self-assigned, so they must be achievable by anyone +3. Quality matters more than quantity — fewer, safer GFIs are better +4. Every GFI should clearly define what “done” means +5. Link to relevant documentation whenever possible to help contributors succeed --- ## Additional Resources -- [Contributing Guide](../../CONTRIBUTING.md) -- [DCO Signing Guide](../sdk_developers/signing.md) -- [Changelog Entry Guide](../sdk_developers/changelog_entry.md) -- [Discord Community](../discord.md) +- [Contributing Guide](../../CONTRIBUTING.md) +- [DCO Signing Guide](../sdk_developers/signing.md) +- [Changelog Entry Guide](../sdk_developers/changelog_entry.md) +- [Discord Community](../discord.md) - [Community Calls](https://zoom-lfx.platform.linuxfoundation.org/meeting/92041330205?password=2f345bee-0c14-4dd5-9883-06fbc9c60581) diff --git a/docs/maintainers/intermediate_issue_guidelines.md b/docs/maintainers/intermediate_issue_guidelines.md index 5a8c5e9e2..e4a73db8c 100644 --- a/docs/maintainers/intermediate_issue_guidelines.md +++ b/docs/maintainers/intermediate_issue_guidelines.md @@ -8,15 +8,15 @@ in the Hiero Python SDK repository. It helps: **Issue creators:** -- Describe moderately complex tasks clearly -- Set expectations around scope and independence -- Provide enough context without over-prescribing solutions +- Describe moderately complex tasks clearly +- Set expectations around scope and independence +- Provide enough context without over-prescribing solutions **Maintainers:** -- Apply the Intermediate label consistently -- Keep issue difficulty levels clear and helpful +- Apply the Intermediate label consistently +- Keep issue difficulty levels clear and helpful -This isn’t a rulebook, and it’s not meant to limit what kinds of contributions are welcome. +This isn’t a rulebook, and it’s not meant to limit what kinds of contributions are welcome. All contributions — from small fixes to major improvements — are valuable to the Hiero project. The **Intermediate** label highlights work that involves investigation, reasoning, @@ -30,15 +30,15 @@ Intermediate Issues represent the **next step after Beginner Issues**. They’re a great fit for contributors who: -- Are comfortable navigating the codebase -- Enjoy investigating how things work -- Are ready to take more ownership of their changes +- Are comfortable navigating the codebase +- Enjoy investigating how things work +- Are ready to take more ownership of their changes These issues help contributors grow their confidence in: -- Understanding existing behavior -- Making thoughtful, localized changes -- Working more independently +- Understanding existing behavior +- Making thoughtful, localized changes +- Working more independently --- @@ -46,10 +46,10 @@ These issues help contributors grow their confidence in: Intermediate Issues are designed for contributors who: -- Are familiar with the Python SDK structure -- Can read and reason about existing implementations -- Are comfortable working across multiple files -- Can ask focused questions when needed +- Are familiar with the Python SDK structure +- Can read and reason about existing implementations +- Are comfortable working across multiple files +- Can ask focused questions when needed These issues usually involve more exploration than Beginner Issues, but still have clear goals and boundaries. @@ -60,10 +60,10 @@ but still have clear goals and boundaries. Intermediate Issues often: -- Involve multiple related files -- Require understanding existing behavior -- Leave room for thoughtful implementation choices -- Stay focused on a specific, well-defined goal +- Involve multiple related files +- Require understanding existing behavior +- Leave room for thoughtful implementation choices +- Stay focused on a specific, well-defined goal They’re a great fit for contributors who enjoy learning by digging into the code. @@ -74,48 +74,48 @@ They’re a great fit for contributors who enjoy learning by digging into the co Here are examples of tasks that often fit well at this level: ### Core SDK Changes -- Small-to-medium behavior changes with clear intent -- Bug fixes that require investigating existing logic -- Localized refactors for clarity or maintainability -- Improvements to existing APIs without breaking contracts +- Small-to-medium behavior changes with clear intent +- Bug fixes that require investigating existing logic +- Localized refactors for clarity or maintainability +- Improvements to existing APIs without breaking contracts ### Refactors & Code Quality -- Refining overly broad or imprecise type hints -- Reducing duplication or complexity -- Improving internal abstractions with clear justification +- Refining overly broad or imprecise type hints +- Reducing duplication or complexity +- Improving internal abstractions with clear justification ### Documentation & Guides -- Writing new documentation for existing features -- Clarifying developer guides based on real usage -- Documenting non-obvious workflows -- Updating docs to reflect recent changes +- Writing new documentation for existing features +- Clarifying developer guides based on real usage +- Documenting non-obvious workflows +- Updating docs to reflect recent changes ### Examples & Usability -- Creating new examples for existing features -- Improving examples based on user feedback -- Refactoring examples to demonstrate best practices +- Creating new examples for existing features +- Improving examples based on user feedback +- Refactoring examples to demonstrate best practices ### Tests -- Adding new tests for existing functionality -- Extending coverage for edge cases -- Refactoring tests for clarity and structure +- Adding new tests for existing functionality +- Extending coverage for edge cases +- Refactoring tests for clarity and structure --- ## Usually Not Good Fits -- Purely mechanical tasks -- Fully scripted changes -- Large architectural redesigns -- Long-term, multi-phase projects -- Work requiring deep protocol or DLT expertise +- Purely mechanical tasks +- Fully scripted changes +- Large architectural redesigns +- Long-term, multi-phase projects +- Work requiring deep protocol or DLT expertise These tasks may be a better fit for **Good First Issue** or **Beginner** labels. If a task: -- Involves major design decisions -- Affects core architecture or APIs +- Involves major design decisions +- Affects core architecture or APIs …it may be a better fit for the **Advanced** label. @@ -125,9 +125,9 @@ If a task: Intermediate Issues are usually: -- ⏱ **Estimated time:** 1–3 days -- 📄 **Scope:** Multiple related files -- 🧠 **Challenge level:** Investigation, reasoning, and ownership +- ⏱ **Estimated time:** 1–3 days +- 📄 **Scope:** Multiple related files +- 🧠 **Challenge level:** Investigation, reasoning, and ownership They’re designed to be achievable in a single pull request. @@ -156,10 +156,10 @@ Add an optional configuration flag that allows callers to request child receipts The change should: -- Be opt-in (default behavior stays the same) -- Reuse existing receipt parsing logic -- Follow existing query configuration patterns -- Avoid breaking public APIs +- Be opt-in (default behavior stays the same) +- Reuse existing receipt parsing logic +- Follow existing query configuration patterns +- Avoid breaking public APIs Example usage: @@ -201,4 +201,4 @@ Maintainer Guidance - Can be completed in a single PR Intermediate Issues are about growing skills — -through exploration, reasoning, and thoughtful ownership. \ No newline at end of file +through exploration, reasoning, and thoughtful ownership. diff --git a/docs/sdk_developers/automations/next-issue-recommendation-bot.md b/docs/sdk_developers/automations/next-issue-recommendation-bot.md index 1547645d9..c74453d58 100644 --- a/docs/sdk_developers/automations/next-issue-recommendation-bot.md +++ b/docs/sdk_developers/automations/next-issue-recommendation-bot.md @@ -21,7 +21,7 @@ The workflow only runs when: The bot parses the pull request body to find linked issues using regex patterns that match: - `Fixes #ISSUE_NUMBER` -- `Closes #ISSUE_NUMBER` +- `Closes #ISSUE_NUMBER` - `Resolves #ISSUE_NUMBER` - `Fix #ISSUE_NUMBER` - `Close #ISSUE_NUMBER` @@ -49,7 +49,7 @@ The bot posts a congratulatory comment that includes: - **Recommended issues**: List of up to 5 relevant issues with: - Issue title and direct link - Brief description (truncated to 150 characters) -- **Repository engagement**: +- **Repository engagement**: - Direct link to star the repository - Direct link to watch the repository for notifications - **Community resources**: Link to Discord community for questions diff --git a/docs/sdk_developers/checklist.md b/docs/sdk_developers/checklist.md index facffb729..af22cd28a 100644 --- a/docs/sdk_developers/checklist.md +++ b/docs/sdk_developers/checklist.md @@ -6,13 +6,13 @@ --- -## 1. Prior to Submission +## 1. Prior to Submission ## Required ✅ - [ ] All commits are signed (`-S`) and DCO signed-off (`-s`) - [ ] Changelog entry added under `[Unreleased]` -- [ ] Tests pass +- [ ] Tests pass - [ ] Only changes related to the issue are included ## Recommended 👍 @@ -56,4 +56,4 @@ The **Files Changed Tab** shows the exact **difference** between your branch and - **Signing issues?** → [Signing Guide](signing.md) - **Merge conflicts?** → [Merge Conflicts Guide](merge_conflicts.md) -- **Changelog format?** → [Changelog Guide](changelog_entry.md) \ No newline at end of file +- **Changelog format?** → [Changelog Guide](changelog_entry.md) diff --git a/docs/sdk_developers/examples.md b/docs/sdk_developers/examples.md index f8d08fda3..482dbd39d 100644 --- a/docs/sdk_developers/examples.md +++ b/docs/sdk_developers/examples.md @@ -43,6 +43,3 @@ pip install -e ".[eth]" ``` You'll need your environment variables and uv set up as outlined in /README.md [README](https://github.com/hiero-ledger/hiero-sdk-python/blob/main/README.md) - - - diff --git a/docs/sdk_developers/linting.md b/docs/sdk_developers/linting.md index 916e6663f..ff9da34b0 100644 --- a/docs/sdk_developers/linting.md +++ b/docs/sdk_developers/linting.md @@ -70,13 +70,13 @@ Search in extensions: ms-python.pylint #### Once downloaded, point to a Python interpreter. -Run ⇧⌘P → Python: Select Interpreter and pick the venv or interpreter you’re using. +Run ⇧⌘P → Python: Select Interpreter and pick the venv or interpreter you’re using. Be sure to point to the correct path or add the correct path. For example, if you're using a venv/virtualenv source .venv/bin/activate pip install pylint -Check /location, it should not be: +Check /location, it should not be: Location: /Library/Frameworks/Python.framework/Versions/3.10/lib/python3.10/site-packages It should be more like: Location: /Users/../../hedera_sdk_python/.venv/lib/python3.10/site-packages diff --git a/docs/sdk_developers/merge_conflicts.md b/docs/sdk_developers/merge_conflicts.md index 3ba6d459c..14ca6e1af 100644 --- a/docs/sdk_developers/merge_conflicts.md +++ b/docs/sdk_developers/merge_conflicts.md @@ -87,9 +87,9 @@ You will see sections like: ```text <<<<<<< HEAD code from main -======= +======= your branch’s code ->>>>>>> mybranch +>>>>>>> mybranch ``` ### 3. Decide what the final code should be @@ -113,7 +113,7 @@ Sometimes you'll be: Generally, you want to keep all changes that were merged to main, but additionally, layer on your changes. -> ✅ **Resolving Conflicts Requires Human Interpretation**. Merge conflicts require thinking about what the final piece should be, should it include both changes? some of each? none of one? +> ✅ **Resolving Conflicts Requires Human Interpretation**. Merge conflicts require thinking about what the final piece should be, should it include both changes? some of each? none of one? > ✅ **Resolving Conflicts Requires Human Edits**. Merge conflicts require manually editing the code. @@ -163,7 +163,7 @@ git rebase --continue ⚠️ Do NOT just click “Accept All Incoming” or “Accept All Current” — that usually **deletes** or **corrupts** important code. -Once the rebase operation completes, your commits will be layered on top of main. It means your commit history will look “different” and you may even see changes to commits from other authors — this is expected, since rebase rewrites history. +Once the rebase operation completes, your commits will be layered on top of main. It means your commit history will look “different” and you may even see changes to commits from other authors — this is expected, since rebase rewrites history. ##### 8. Push changes If you already have an open Pull Request, you will need to update it with a **force push**. @@ -253,4 +253,4 @@ git rebase upstream/main -S ### Helpful Resources - [Signing Guide](signing.md) - [Rebasing Guide](rebasing.md) -- [Discord](../discord.md) \ No newline at end of file +- [Discord](../discord.md) diff --git a/docs/sdk_developers/pylance.md b/docs/sdk_developers/pylance.md index 7d811973b..21483d087 100644 --- a/docs/sdk_developers/pylance.md +++ b/docs/sdk_developers/pylance.md @@ -377,8 +377,3 @@ Before every PR: --- - - - - - diff --git a/docs/sdk_developers/rebasing.md b/docs/sdk_developers/rebasing.md index 58f35138d..4e3cc6fae 100644 --- a/docs/sdk_developers/rebasing.md +++ b/docs/sdk_developers/rebasing.md @@ -18,7 +18,7 @@ git remote add upstream https://github.com/hiero-ledger/hiero-sdk-python.git #### 2. Sync your main on your fork Each time you want to sync: - Sync your fork's main with the upstream main changes: - + ```bash git checkout main git fetch upstream @@ -32,7 +32,7 @@ You can also do this by visiting your repository "https://github.com/YOUR_GITHUB Your fork’s `main` branch is now up to date, but **your working branch is not**. -To bring your branch in sync with the latest changes, apply a **rebase**. +To bring your branch in sync with the latest changes, apply a **rebase**. This keeps history clean and ensures your commits remain eligible for review. To rebase: @@ -42,8 +42,8 @@ git checkout mybranch git rebase main -S ``` -> ⚠️ **Always include the -S flag** -> ⚠️ **Do NOT merge `main` into your branch** +> ⚠️ **Always include the -S flag** +> ⚠️ **Do NOT merge `main` into your branch** ### 4. Verify Sign Status Verify after the rebase operation, your commits are still signed correctly: @@ -62,4 +62,4 @@ If conflicts occur during rebase, See [Merge Conflict Guide](./merge_conflicts.m ### Helpful Resources - [Signing Guide](signing.md) - [Merge Conflict Guide](./merge_conflicts.md) -- [Discord](../discord.md) \ No newline at end of file +- [Discord](../discord.md) diff --git a/docs/sdk_developers/setup.md b/docs/sdk_developers/setup.md index d916b8562..2bcabed43 100644 --- a/docs/sdk_developers/setup.md +++ b/docs/sdk_developers/setup.md @@ -174,6 +174,42 @@ Optional: To install all available extras (useful full-matrix testing): uv sync --dev --all-extras ``` +## Pre-Commit Tool Setup + +To maintain high code quality and security, this repository uses `re-commit` hooks. These hooks automatically run checks (like `Ruff` for linting and `Gitleaks` for security) every time you attempt to commit code. + +**Option 1: Using `uv` (Recommended)** + +`uv` is recommended because it manages pre-commit within your project’s locked environment, ensuring your local linting matches the CI exactly. + +1. **Install the git hooks:** +```bash +uv run pre-commit install +``` + +2. **Verify your setup (Optional):** +```bash +uv run pre-commit run --all-files +``` + +**Option 2: Using pip** + +If you are using a standard virtual environment: + +1. **Install the package:** +```bash + pip install pre-commit +``` + +2. **Install the git hooks:** +```bash +pre-commit install +``` + +Once installed, `git commit` will automatically trigger the checks. +- If they **pass**: Your commit is created normally. +- If they **fail**: The hooks will often fix the files for you (e.g., `Ruff` reformatting). Simply `git add` the changed files and commit again. + ## Generate Protocol Buffers The SDK uses protocol buffers to communicate with the Hedera network. Generate the Python code from the protobuf definitions: diff --git a/docs/sdk_developers/signing.md b/docs/sdk_developers/signing.md index a08dc487f..c67f438f8 100644 --- a/docs/sdk_developers/signing.md +++ b/docs/sdk_developers/signing.md @@ -188,4 +188,4 @@ git rebase main -S ## Still Need Help? * Refer to [GitHub's GPG Docs](https://docs.github.com/en/authentication/managing-commit-signature-verification) -* Ask on the **Hiero [Discord](../discord.md)** \ No newline at end of file +* Ask on the **Hiero [Discord](../discord.md)** diff --git a/docs/sdk_developers/testing.md b/docs/sdk_developers/testing.md index 939404560..120e46ed8 100644 --- a/docs/sdk_developers/testing.md +++ b/docs/sdk_developers/testing.md @@ -473,7 +473,7 @@ Let's walk through creating both unit and integration tests for a hypothetical n #### 1. Create Unit Test -**File:** +**File:** You may look at an already-created unit test file for better clarity: [token_pause_transaction_test.py](../../tests/unit/token_pause_transaction_test.py) @@ -509,10 +509,10 @@ def test_example(): # Arrange - Set up test data and preconditions account_id = AccountId(0, 0, 1001) initial_balance = Hbar(10) - + # Act - Perform the action being tested result = calculate_new_balance(account_id, initial_balance) - + # Assert - Verify the outcome assert result.to_tinybars() == 1_000_000_000 ``` @@ -586,10 +586,10 @@ def test_transaction_execution_calls_network(): """Test that transaction execution calls the network correctly.""" mock_client = Mock() mock_client.execute.return_value = {"status": "SUCCESS"} - + transaction = SomeTransaction() result = transaction.execute(mock_client) - + mock_client.execute.assert_called_once() assert result["status"] == "SUCCESS" ``` @@ -693,9 +693,9 @@ def _create_and_associate_token(env, account): """Helper to create token and associate it with account.""" token_receipt = TokenCreateTransaction().execute(env.client) token_id = token_receipt.token_id - + TokenAssociateTransaction().set_account_id(account.id).add_token_id(token_id).execute(env.client) - + return token_id @pytest.mark.integration @@ -1018,9 +1018,9 @@ def test_transaction_handles_network_error(): """Test that transaction properly handles network errors.""" mock_client = Mock() mock_client.execute.side_effect = Exception("Network timeout") - + transaction = SomeTransaction() - + with pytest.raises(Exception, match="Network timeout"): transaction.execute(mock_client) @@ -1032,10 +1032,10 @@ def test_transaction_retries_on_failure(): {"status": ResponseCode.BUSY}, {"status": ResponseCode.SUCCESS} ] - + transaction = SomeTransaction() result = transaction.execute_with_retry(mock_client, max_retries=2) - + assert result["status"] == ResponseCode.SUCCESS assert mock_client.execute.call_count == 2 ``` @@ -1052,10 +1052,10 @@ import asyncio async def test_async_transaction(): """Test asynchronous transaction execution.""" client = await create_async_client() - + transaction = AsyncTransaction() result = await transaction.execute(client) - + assert result.status == ResponseCode.SUCCESS ``` @@ -1072,7 +1072,7 @@ def test_hbar_conversion_property(amount): hbar = Hbar(amount) tinybars = hbar.to_tinybars() hbar2 = Hbar.from_tinybars(tinybars) - + assert hbar.to_tinybars() == hbar2.to_tinybars() ``` @@ -1085,7 +1085,7 @@ def test_transaction_serialization_snapshot(snapshot): """Test that transaction serialization hasn't changed.""" transaction = create_test_transaction() serialized = transaction.to_proto() - + # Compare against saved snapshot snapshot.assert_match(serialized) ``` @@ -1100,10 +1100,10 @@ import time def test_transaction_performance(): """Test that transaction creation is fast.""" start = time.time() - + for _ in range(1000): transaction = AccountCreateTransaction() - + duration = time.time() - start assert duration < 1.0, f"Transaction creation took {duration}s, expected < 1s" ``` @@ -1254,4 +1254,4 @@ Remember: 3. Run tests locally before pushing 4. All tests must pass in CI before merging -Thank you for contributing to the Hiero Python SDK! Your tests make the SDK better for everyone. \ No newline at end of file +Thank you for contributing to the Hiero Python SDK! Your tests make the SDK better for everyone. diff --git a/docs/sdk_developers/training/executable.md b/docs/sdk_developers/training/executable.md index 99422ae12..ae7bb3d8c 100644 --- a/docs/sdk_developers/training/executable.md +++ b/docs/sdk_developers/training/executable.md @@ -116,12 +116,12 @@ Key Steps: The response is checked via `_should_retry()` which returns one of four `Execution States`: - | State | Action | - | :--------------| :---------------------------------------| - | **RETRY** | `Wait (backoff), then loop again` | - | **FINISHED** | `Success! Return the response` | + | State | Action | + | :--------------| :---------------------------------------| + | **RETRY** | `Wait (backoff), then loop again` | + | **FINISHED** | `Success! Return the response` | | **ERROR** | `Permanent failure, raise exception` | - | **EXPIRED** | `Request expired, raise exception` | + | **EXPIRED** | `Request expired, raise exception` | @@ -354,4 +354,3 @@ This lets you quickly identify whether a failure is transient (network), permane * [Token Association Example](https://github.com/hiero-ledger/hiero-sdk-python/blob/main/examples/tokens/token_associate_transaction.py) * [Token Freeze Example](https://github.com/hiero-ledger/hiero-sdk-python/blob/main/examples/tokens/token_freeze_transaction.py) * [Token Account Info Query Example](https://github.com/hiero-ledger/hiero-sdk-python/blob/main/examples/query/account_info_query.py) - diff --git a/docs/sdk_developers/training/setup/01_introduction_hiero_python_sdk.md b/docs/sdk_developers/training/setup/01_introduction_hiero_python_sdk.md index bc3bee6a1..07cd25169 100644 --- a/docs/sdk_developers/training/setup/01_introduction_hiero_python_sdk.md +++ b/docs/sdk_developers/training/setup/01_introduction_hiero_python_sdk.md @@ -1,6 +1,6 @@ ## What is the Hiero Python SDK? -The Hiero Python SDK enables developers to use Python to interact with the Hedera Blockchain/DLT Network. +The Hiero Python SDK enables developers to use Python to interact with the Hedera Blockchain/DLT Network. For example, the Hiero Python SDK lets you: - Create a token on the Hedera network @@ -25,4 +25,3 @@ create_tx = ( token_id = create_tx.token_id print(f"🎉 Created a new token on the Hedera network with ID: {token_id}") ``` - diff --git a/docs/sdk_developers/training/setup/02_installing_hiero_python_sdk.md b/docs/sdk_developers/training/setup/02_installing_hiero_python_sdk.md index e508a4c05..7e6c7a540 100644 --- a/docs/sdk_developers/training/setup/02_installing_hiero_python_sdk.md +++ b/docs/sdk_developers/training/setup/02_installing_hiero_python_sdk.md @@ -29,4 +29,3 @@ Run: ```bash uv run python generate_proto.py ``` - diff --git a/docs/sdk_developers/training/setup/03_setting_up_env.md b/docs/sdk_developers/training/setup/03_setting_up_env.md index a4fc9496e..d9c07bfe8 100644 --- a/docs/sdk_developers/training/setup/03_setting_up_env.md +++ b/docs/sdk_developers/training/setup/03_setting_up_env.md @@ -23,5 +23,3 @@ NETWORK=testnet ``` We have added `.env` to `.gitignore` to help ensure its never committed. - - diff --git a/docs/sdk_developers/training/setup/04_using_env_variables.md b/docs/sdk_developers/training/setup/04_using_env_variables.md index 9448237a3..43800a457 100644 --- a/docs/sdk_developers/training/setup/04_using_env_variables.md +++ b/docs/sdk_developers/training/setup/04_using_env_variables.md @@ -8,7 +8,7 @@ Your credentials stored at .env are required to run transactions on Hedera testn We use both, for example: ```python -# Import dotenv and os +# Import dotenv and os from dotenv import load_dotenv from os import getenv @@ -24,4 +24,3 @@ print(f"Congratulations! We loaded your operator ID: {operator_id_string}.") print("Your operator key was loaded successfully (not printed for security).") ``` - diff --git a/docs/sdk_developers/training/setup/06_importing_hiero_files.md b/docs/sdk_developers/training/setup/06_importing_hiero_files.md index ae2a7ac6f..b9bbef9e6 100644 --- a/docs/sdk_developers/training/setup/06_importing_hiero_files.md +++ b/docs/sdk_developers/training/setup/06_importing_hiero_files.md @@ -19,7 +19,7 @@ from hiero_sdk_python.tokens import token_create_transaction ``` ### Advanced Example -You'll need to import everything you require. +You'll need to import everything you require. In this more advanced example, we are using imports to load env, to set up the client and network, and to form the Token Create Transaction and check the response: @@ -69,7 +69,7 @@ print(f"🎉 Created new token on the Hedera network with ID: {token_id}") ``` ## Extra Support -It takes time to be familiar with where everything is located to import correctly. +It takes time to be familiar with where everything is located to import correctly. - For reference, look at the [examples](../../../../examples) - For an explanation of the project structure read [project_structure.md](project_structure.md) diff --git a/docs/sdk_developers/training/setup/07_lab_set_up.md b/docs/sdk_developers/training/setup/07_lab_set_up.md index a741d68f4..6f86aeba5 100644 --- a/docs/sdk_developers/training/setup/07_lab_set_up.md +++ b/docs/sdk_developers/training/setup/07_lab_set_up.md @@ -99,4 +99,4 @@ def main(): if __name__ == "__main__": main() -``` \ No newline at end of file +``` diff --git a/docs/sdk_developers/training/setup/project_structure.md b/docs/sdk_developers/training/setup/project_structure.md index df3353131..10ee62477 100644 --- a/docs/sdk_developers/training/setup/project_structure.md +++ b/docs/sdk_developers/training/setup/project_structure.md @@ -111,4 +111,4 @@ A tool like Pylance (in VS Code) can help verify your import paths and flag any ## Conclusion -This guide outlines the basic file structure of the project. By understanding where different types of code live (`src/`, `tests/`, `examples/`, `docs/`), you can more easily find the classes you need to import and know where to place your new contributions. \ No newline at end of file +This guide outlines the basic file structure of the project. By understanding where different types of code live (`src/`, `tests/`, `examples/`, `docs/`), you can more easily find the classes you need to import and know where to place your new contributions. diff --git a/docs/sdk_developers/training/testing_forks.md b/docs/sdk_developers/training/testing_forks.md index e2766741e..796f43ecc 100644 --- a/docs/sdk_developers/training/testing_forks.md +++ b/docs/sdk_developers/training/testing_forks.md @@ -55,7 +55,7 @@ DAYS="${DAYS:-21}" **After (Testing Code):** ```bash # Set to 0 to treat everything as immediately stale for testing -DAYS="${DAYS:-0}" +DAYS="${DAYS:-0}" ``` ### 2. accelerating Cron Schedules @@ -112,4 +112,4 @@ Once you have verified the functionality works as expected: 1. Delete your test branches (`test/trigger-bot`, etc.). 2. Close any dummy Pull Requests and Issues in your fork. 3. **Revert the timescale changes** in your feature branch (e.g., change `DAYS=0` back to `DAYS=21`, remove the `*/5` cron). -4. Create a final Pull Request from your clean feature branch to the official upstream repository. \ No newline at end of file +4. Create a final Pull Request from your clean feature branch to the official upstream repository. diff --git a/docs/sdk_developers/training/transaction_lifecycle.md b/docs/sdk_developers/training/transaction_lifecycle.md index 53cf3e8ab..89cb9d2a4 100644 --- a/docs/sdk_developers/training/transaction_lifecycle.md +++ b/docs/sdk_developers/training/transaction_lifecycle.md @@ -178,4 +178,4 @@ transaction.set_account_id(new_id) # Error: Transaction is immutable - **Auto-freeze/sign:** Works for simple transactions but can hide issues in complex ones. - **Order dependency:** Construct → Freeze → Sign → Execute → Receipt. -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). \ No newline at end of file +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). diff --git a/docs/sdk_developers/training/workflow/01_supporting_infrastructure.md b/docs/sdk_developers/training/workflow/01_supporting_infrastructure.md index 776f069e3..2aad90b97 100644 --- a/docs/sdk_developers/training/workflow/01_supporting_infrastructure.md +++ b/docs/sdk_developers/training/workflow/01_supporting_infrastructure.md @@ -7,7 +7,7 @@ Recommended Tools - [ ] Visual Studio Code - [ ] Pylance -However, what may work best for you might be different. +However, what may work best for you might be different. ### GitHub Desktop GitHub Desktop is a free, user-friendly application that provides a visual interface for Git and GitHub. Instead of running Git commands in a terminal, GitHub Desktop lets you perform common tasks through an intuitive UI. @@ -44,6 +44,3 @@ from hiero_sdk_python.account.token_id import TokenId This is incorrect because token_id.py does not live in /account! Instead, it lives in /tokens Read our [Pylance Installation Guide](../../pylance.md) - - - diff --git a/docs/sdk_developers/training/workflow/03_staying_in_sync.md b/docs/sdk_developers/training/workflow/03_staying_in_sync.md index 83f642fb9..8d21958fb 100644 --- a/docs/sdk_developers/training/workflow/03_staying_in_sync.md +++ b/docs/sdk_developers/training/workflow/03_staying_in_sync.md @@ -29,4 +29,4 @@ git pull upstream main Then rebase on your working branch to apply the new changes: git checkout mybranch -git rebase main -S \ No newline at end of file +git rebase main -S diff --git a/docs/sdk_developers/training/workflow/05_working_branches.md b/docs/sdk_developers/training/workflow/05_working_branches.md index 2decbe54a..b9251fdba 100644 --- a/docs/sdk_developers/training/workflow/05_working_branches.md +++ b/docs/sdk_developers/training/workflow/05_working_branches.md @@ -17,5 +17,3 @@ git checkout -b my-new-branch-name ``` Commit all your changes on this new branch, then publish your branch and submit a pull request. - - diff --git a/docs/sdk_developers/training/workflow/06_commit_message_requirements.md b/docs/sdk_developers/training/workflow/06_commit_message_requirements.md index 4c1414717..8d78c33cf 100644 --- a/docs/sdk_developers/training/workflow/06_commit_message_requirements.md +++ b/docs/sdk_developers/training/workflow/06_commit_message_requirements.md @@ -32,4 +32,4 @@ git commit -S -s -m "feat: changelog entry to TokenCreateTransaction" This is incorrect: ```bash git commit -S -s -m "changelog entry to TokenCreateTransaction" -``` \ No newline at end of file +``` diff --git a/docs/sdk_developers/training/workflow/08_breaking_changes.md b/docs/sdk_developers/training/workflow/08_breaking_changes.md index f5809bd82..0ae7c65d2 100644 --- a/docs/sdk_developers/training/workflow/08_breaking_changes.md +++ b/docs/sdk_developers/training/workflow/08_breaking_changes.md @@ -53,4 +53,4 @@ Example changelog entry: `BREAKING CHANGE: transfer_tokens() now requires an AccountId object instead of a string.` -Breaking changes are typically scheduled for major releases, giving users time to prepare and migrate safely. \ No newline at end of file +Breaking changes are typically scheduled for major releases, giving users time to prepare and migrate safely. diff --git a/docs/sdk_developers/training/workflow/09_changelog_entry.md b/docs/sdk_developers/training/workflow/09_changelog_entry.md index 6ccedd24a..a429e62ae 100644 --- a/docs/sdk_developers/training/workflow/09_changelog_entry.md +++ b/docs/sdk_developers/training/workflow/09_changelog_entry.md @@ -14,4 +14,4 @@ For example: ### Added - Added `.github/workflows/merge-conflict-bot.yml` to automatically detect and notify users of merge conflicts in Pull Requests. -[Read more](../../changelog_entry.md) about creating changelog entries \ No newline at end of file +[Read more](../../changelog_entry.md) about creating changelog entries diff --git a/docs/sdk_developers/training/workflow/10_unit_and_integration_tests.md b/docs/sdk_developers/training/workflow/10_unit_and_integration_tests.md index cd564bbbb..c2d3b28ff 100644 --- a/docs/sdk_developers/training/workflow/10_unit_and_integration_tests.md +++ b/docs/sdk_developers/training/workflow/10_unit_and_integration_tests.md @@ -6,4 +6,4 @@ Integration tests will run as a github action on your copy of the repository onc Read more about creating and running tests here [Testing Guide](../../testing.md) -Similarly, most new functionality should have examples added at /examples/ \ No newline at end of file +Similarly, most new functionality should have examples added at /examples/ diff --git a/docs/sdk_developers/types.md b/docs/sdk_developers/types.md index bcadcf26b..714aa189d 100644 --- a/docs/sdk_developers/types.md +++ b/docs/sdk_developers/types.md @@ -5,12 +5,12 @@ This guide provides an overview of Python’s built-in types, explains how and w ## Table of Contents -- [What are Types in Python?](#what-are-types-in-python) -- [What are Type Hints?](#what-are-type-hints) -- [Why Type Hint?](#why-type-hint) -- [Typing Using the `typing` Module](#typing-using-the-typing-module) -- [Custom Types](#custom-types) -- [Installing and Using MyPy](#installing-and-using-mypy) +- [What are Types in Python?](#what-are-types-in-python) +- [What are Type Hints?](#what-are-type-hints) +- [Why Type Hint?](#why-type-hint) +- [Typing Using the `typing` Module](#typing-using-the-typing-module) +- [Custom Types](#custom-types) +- [Installing and Using MyPy](#installing-and-using-mypy) - [Configuring MyPy](#configuring-mypy) ## What are Types in Python? @@ -29,24 +29,24 @@ Python code can be written using various built-in types that define their behavi - `str`: immutable sequences of characters (e.g. `"hello"`) - **Binary** - - `bytes`: immutable byte sequences - - `bytearray`: mutable byte sequences + - `bytes`: immutable byte sequences + - `bytearray`: mutable byte sequences - `memoryview`: a view on another binary object - **Sequences** - - `list[T]`: mutable, ordered collections (e.g. `list[int]`) - - `tuple[T1, T2, …]`: immutable ordered collections (e.g. `tuple[int, str]`) + - `list[T]`: mutable, ordered collections (e.g. `list[int]`) + - `tuple[T1, T2, …]`: immutable ordered collections (e.g. `tuple[int, str]`) - `range`: immutable sequences of integers, typically used in loops - **Mappings** - `dict[K, V]`: mutable key→value stores (e.g. `dict[str, float]`) - **Sets** - - `set[T]`: mutable, unordered unique items + - `set[T]`: mutable, unordered unique items - `frozenset[T]`: immutable sets of unique items - **None** - - `None`: the singleton “no value” object (its type is `NoneType`). + - `None`: the singleton “no value” object (its type is `NoneType`). > _Note: `print()` always returns `None`. Functions return `None` unless an explicit `return` is provided._ --- @@ -111,10 +111,10 @@ Correct type handling is necessary for the proper functioning of code. Proper type hinting enables developers to: -- **Understand** the intended data structures and interfaces -- **Maintain** and navigate the codebase more easily -- **Refactor** with confidence, catching mismatches early -- **Extend** the code and documentation without introducing errors +- **Understand** the intended data structures and interfaces +- **Maintain** and navigate the codebase more easily +- **Refactor** with confidence, catching mismatches early +- **Extend** the code and documentation without introducing errors Additionally, type hints unlock the use of static analysis tools (like MyPy), allowing you to catch type-related bugs before runtime and ship more robust code. @@ -219,7 +219,7 @@ if __name__ == "__main__": Use MyPy to help check for correct typing in your code and its imports. You can adopt type hints gradually without impacting runtime. MyPy will not: -- Prevent or change the running of your code +- Prevent or change the running of your code This makes MyPy safe to introduce at your own pace. @@ -259,5 +259,5 @@ implicit_optional = True allow_untyped_calls = True ``` -For a full list of flags and options, see the MyPy command-line reference: +For a full list of flags and options, see the MyPy command-line reference: [MyPy command-line reference](https://mypy.readthedocs.io/en/stable/command_line.html#command-line) diff --git a/docs/sdk_developers/workflow.md b/docs/sdk_developers/workflow.md index 9d95c1670..30eced4d0 100644 --- a/docs/sdk_developers/workflow.md +++ b/docs/sdk_developers/workflow.md @@ -1,6 +1,6 @@ # Contribution Workflow -This guide explains the recommended workflow for contributing to the **Hiero Python SDK**. +This guide explains the recommended workflow for contributing to the **Hiero Python SDK**. It covers tooling, repository setup, branching, commit standards, testing, and submitting pull requests. --- @@ -11,8 +11,8 @@ For the best development experience and smoother support, we strongly recommend ### Recommended Tools -- [ ] GitHub Desktop -- [ ] Visual Studio Code +- [ ] GitHub Desktop +- [ ] Visual Studio Code - Pylance (extension) > These tools are recommendations, not requirements. You are free to use alternatives that fit your workflow. @@ -89,7 +89,7 @@ Click Create fork. Your new fork will appear at: `https://github.com//hiero-sdk-python` -This is your copy of the repository. You can work on this safely without fear of impacting the original repository. +This is your copy of the repository. You can work on this safely without fear of impacting the original repository. 3. Clone Your Fork Locally diff --git a/docs/sdk_users/running_examples.md b/docs/sdk_users/running_examples.md index 03bec4770..468a843a4 100644 --- a/docs/sdk_users/running_examples.md +++ b/docs/sdk_users/running_examples.md @@ -943,7 +943,7 @@ transaction.execute(client) #### Pythonic Syntax: ``` transaction = TokenAirdropTransaction( - token_transfers=token_transfers, + token_transfers=token_transfers, nft_transfers=nft_transfers ).freeze_with(client) diff --git a/docs/workflows/01-what-are-workflows.md b/docs/workflows/01-what-are-workflows.md index 650c6ab8b..7555b58c3 100644 --- a/docs/workflows/01-what-are-workflows.md +++ b/docs/workflows/01-what-are-workflows.md @@ -3,7 +3,7 @@ Welcome to the Hiero Python SDK! To ensure a smooth, safe, and professional contribution experience, this repository relies heavily on **GitHub Workflows**. This guide explains the foundational concepts of repository automation and how it helps you as a contributor. ### What is a Workflow? -A workflow is an automated process that is **event-driven**. It stays inactive until a specific "Trigger" occurs. +A workflow is an automated process that is **event-driven**. It stays inactive until a specific "Trigger" occurs. * **The Trigger:** This is the **"When"** (e.g., someone comments `/assign` on an issue). * **The Logic:** This is the **"How"** (e.g., the system checks if you are eligible and then assigns you). @@ -11,7 +11,7 @@ A workflow is an automated process that is **event-driven**. It stays inactive u --- ## The Anatomy of a Hiero Workflow -Every workflow in this repository follows a standardized design pattern to maintain security and consistency. +Every workflow in this repository follows a standardized design pattern to maintain security and consistency. #### 1. Job Title Every workflow defines one or more **Jobs**. These are the top-level tasks (like `lint`, `test`, or `assign`). You can see these names in the "Actions" tab of the repository to track the progress of your contribution. @@ -53,7 +53,7 @@ With over 30 workflows running in this repository, automation is the backbone of --- ## Case Study: The Auto-Assignment Bot -A great example of these concepts in action is our **Beginner Assignment Bot**. +A great example of these concepts in action is our **Beginner Assignment Bot**. 1. **The Trigger:** A contributor types `/assign` on an issue. 2. **The Logic:** The workflow calls `bot-beginner-assign-on-comment.js`. This "brain" checks if the issue is already taken and verifies the contributor's history. diff --git a/docs/workflows/02-architecture.md b/docs/workflows/02-architecture.md index 0ae244be0..2afae6011 100644 --- a/docs/workflows/02-architecture.md +++ b/docs/workflows/02-architecture.md @@ -2,18 +2,18 @@ Building on the basics from [01-what-are-workflows.md](./01-what-are-workflows.md), this document explains **exactly how** we structure workflows in the Hiero Python SDK repository. -We use a very deliberate pattern called **Orchestration vs. Logic separation**. +We use a very deliberate pattern called **Orchestration vs. Logic separation**. This makes it much easier for you (and future contributors) to **create**, **tweak**, **understand**, and **maintain** automations. ## The Simple Flow -1. Something happens on GitHub +1. Something happens on GitHub (someone comments `/assign`, a PR is opened, code is pushed…) 2. ↓ -3. The **workflow (.yml)** wakes up and quickly decides: - “Should this run right now?” +3. The **workflow (.yml)** wakes up and quickly decides: + “Should this run right now?” (based on event type, branch, labels, etc.) 4. ↓ @@ -22,12 +22,12 @@ This makes it much easier for you (and future contributors) to **create**, **twe 6. ↓ -7. The **script** does all the real work +7. The **script** does all the real work (reads details, thinks, decides, talks to GitHub, writes comments…) 8. ↓ -9. Results show up in GitHub +9. Results show up in GitHub (comment posted, label added, check passed/failed, log messages…) ## Two Folders – Two Very Different Jobs @@ -55,7 +55,7 @@ Responsible for: - Wiring **inputs**, **environment variables**, **secrets** - Calling the script (usually via `actions/github-script`) -Should contain **almost zero decision-making logic**. +Should contain **almost zero decision-making logic**. Complex `if:` conditions, string parsing, API calls, etc. do **not** belong here. ### Scripts (.github/scripts/*.js) – Business Logic @@ -78,16 +78,16 @@ We deliberately name workflows and their scripts **very similarly** so you can i Examples from the repository: -- `.github/workflows/bot-gfi-assign-on-comment.yml` +- `.github/workflows/bot-gfi-assign-on-comment.yml` → `.github/scripts/bot-gfi-assign-on-comment.js` This makes scanning `.github/` much faster when you want to understand or fix something. ## Best Practices Summary -- **Workflows** should have a **good, descriptive title** - (the `name:` field – it appears in the Actions tab and in PR checks) - Good: `Beginner Issues – Auto-assign when /assign is commented` +- **Workflows** should have a **good, descriptive title** + (the `name:` field – it appears in the Actions tab and in PR checks) + Good: `Beginner Issues – Auto-assign when /assign is commented` Bad: `assign` - **Scripts** should be **well documented**: @@ -98,4 +98,3 @@ This makes scanning `.github/` much faster when you want to understand or fix so // Purpose: Assigns a good-first-issue only if it's still free and correctly labeled // Allowed only via /assign command in issue comments ``` - \ No newline at end of file diff --git a/examples/account/account_allowance_approve_transaction_hbar.py b/examples/account/account_allowance_approve_transaction_hbar.py index 525478948..c86083cc0 100644 --- a/examples/account/account_allowance_approve_transaction_hbar.py +++ b/examples/account/account_allowance_approve_transaction_hbar.py @@ -30,6 +30,7 @@ Hedera allowance documentation. This example focuses solely on HBAR allowances and does not demonstrate revoking allowances or token/NFT usage. """ + import os import sys @@ -43,6 +44,7 @@ from hiero_sdk_python.response_code import ResponseCode from hiero_sdk_python.transaction.transfer_transaction import TransferTransaction + load_dotenv() network_name = os.getenv("NETWORK", "testnet").lower() @@ -75,10 +77,7 @@ def create_account(client: Client): ) if account_receipt.status != ResponseCode.SUCCESS: - print( - "Account creation failed with status: " - f"{ResponseCode(account_receipt.status).name}" - ) + print(f"Account creation failed with status: {ResponseCode(account_receipt.status).name}") sys.exit(1) account_account_id = account_receipt.account_id @@ -100,10 +99,7 @@ def approve_hbar_allowance( ) if receipt.status != ResponseCode.SUCCESS: - print( - "Hbar allowance approval failed with status: " - f"{ResponseCode(receipt.status).name}" - ) + print(f"Hbar allowance approval failed with status: {ResponseCode(receipt.status).name}") sys.exit(1) print(f"Hbar allowance of {amount} approved for spender {spender_account_id}") @@ -134,10 +130,7 @@ def transfer_hbar_with_allowance( print(f"Hbar transfer failed with status: {ResponseCode(receipt.status).name}") sys.exit(1) - print( - f"Successfully transferred {amount} from {owner_account_id} " - f"to {receiver_account_id} using allowance" - ) + print(f"Successfully transferred {amount} from {owner_account_id} to {receiver_account_id} using allowance") return receipt diff --git a/examples/account/account_allowance_approve_transaction_nft.py b/examples/account/account_allowance_approve_transaction_nft.py index bdca17b56..f8e7a1971 100644 --- a/examples/account/account_allowance_approve_transaction_nft.py +++ b/examples/account/account_allowance_approve_transaction_nft.py @@ -28,6 +28,7 @@ Usage: uv run examples/account/account_allowance_approve_transaction_nft.py """ + import os import sys @@ -51,6 +52,7 @@ ) from hiero_sdk_python.account.account_create_transaction import AccountCreateTransaction + load_dotenv() network_name = os.getenv("NETWORK", "testnet").lower() @@ -129,21 +131,14 @@ def create_nft_token(client, owner_id, owner_key): def mint_nft(client, token_id, metadata_list): """Mint NFT(s) with metadata.""" - tx = ( - TokenMintTransaction() - .set_token_id(token_id) - .set_metadata(metadata_list) - .execute(client) - ) + tx = TokenMintTransaction().set_token_id(token_id).set_metadata(metadata_list).execute(client) if tx.status != ResponseCode.SUCCESS: print(f"Mint failed: {ResponseCode(tx.status).name}") sys.exit(1) serials = tx.serial_numbers - print( - f"NFT Owner ({client.operator_account_id}) minted {len(serials)} NFT(s) for Token {token_id}: {serials}" - ) + print(f"NFT Owner ({client.operator_account_id}) minted {len(serials)} NFT(s) for Token {token_id}: {serials}") return [NftId(token_id, s) for s in serials] @@ -179,22 +174,14 @@ def approve_nft_allowance(client, nft_id, owner_id, spender_id, owner_key): print(f"Approval failed: {ResponseCode(tx.status).name}") sys.exit(1) - print( - f"NFT Owner ({owner_id}) approved Spender ({spender_id}) for NFT {nft_id.token_id} (all serials)" - ) + print(f"NFT Owner ({owner_id}) approved Spender ({spender_id}) for NFT {nft_id.token_id} (all serials)") def transfer_nft_using_allowance(spender_client, nft_id, owner_id, receiver_id): """Transfer an NFT using approved allowance via the spender client.""" - print( - f"Spender ({spender_client.operator_account_id}) transferring NFT {nft_id} from Owner ({owner_id})..." - ) + print(f"Spender ({spender_client.operator_account_id}) transferring NFT {nft_id} from Owner ({owner_id})...") - tx = ( - TransferTransaction() - .add_approved_nft_transfer(nft_id, owner_id, receiver_id) - .execute(spender_client) - ) + tx = TransferTransaction().add_approved_nft_transfer(nft_id, owner_id, receiver_id).execute(spender_client) if tx.status != ResponseCode.SUCCESS: print(f"Transfer failed: {ResponseCode(tx.status).name}") diff --git a/examples/account/account_allowance_delete_transaction_hbar.py b/examples/account/account_allowance_delete_transaction_hbar.py index c8e331bfc..0e727e618 100644 --- a/examples/account/account_allowance_delete_transaction_hbar.py +++ b/examples/account/account_allowance_delete_transaction_hbar.py @@ -13,6 +13,7 @@ from hiero_sdk_python.response_code import ResponseCode from hiero_sdk_python.transaction.transfer_transaction import TransferTransaction + load_dotenv() network_name = os.getenv("NETWORK", "testnet").lower() @@ -45,10 +46,7 @@ def create_account(client: Client): ) if account_receipt.status != ResponseCode.SUCCESS: - print( - "Account creation failed with status: " - f"{ResponseCode(account_receipt.status).name}" - ) + print(f"Account creation failed with status: {ResponseCode(account_receipt.status).name}") sys.exit(1) account_account_id = account_receipt.account_id @@ -70,10 +68,7 @@ def approve_hbar_allowance( ) if receipt.status != ResponseCode.SUCCESS: - print( - "Hbar allowance approval failed with status: " - f"{ResponseCode(receipt.status).name}" - ) + print(f"Hbar allowance approval failed with status: {ResponseCode(receipt.status).name}") sys.exit(1) print(f"Hbar allowance of {amount} approved for spender {spender_account_id}") @@ -93,10 +88,7 @@ def delete_hbar_allowance( ) if receipt.status != ResponseCode.SUCCESS: - print( - "Hbar allowance deletion failed with status: " - f"{ResponseCode(receipt.status).name}" - ) + print(f"Hbar allowance deletion failed with status: {ResponseCode(receipt.status).name}") sys.exit(1) print(f"Hbar allowance deleted for spender {spender_account_id}") @@ -126,10 +118,7 @@ def transfer_hbar_with_allowance( print(f"Hbar transfer failed with status: {ResponseCode(receipt.status).name}") sys.exit(1) - print( - f"Successfully transferred {amount} from {owner_account_id} " - f"to {receiver_account_id} using allowance" - ) + print(f"Successfully transferred {amount} from {owner_account_id} to {receiver_account_id} using allowance") return receipt @@ -165,10 +154,7 @@ def transfer_hbar_without_allowance( f"{ResponseCode(receipt.status).name}" ) else: - print( - "Hbar transfer successfully failed with " - f"{ResponseCode(receipt.status).name} status" - ) + print(f"Hbar transfer successfully failed with {ResponseCode(receipt.status).name} status") def main(): diff --git a/examples/account/account_allowance_delete_transaction_nft.py b/examples/account/account_allowance_delete_transaction_nft.py index edd4042ae..421595e64 100644 --- a/examples/account/account_allowance_delete_transaction_nft.py +++ b/examples/account/account_allowance_delete_transaction_nft.py @@ -13,6 +13,7 @@ python examples/account/account_allowance_delete_transaction_nft.py uv run examples/account/account_allowance_delete_transaction_nft.py """ + import os import sys @@ -37,6 +38,7 @@ ) from hiero_sdk_python.account.account_create_transaction import AccountCreateTransaction + load_dotenv() network_name = os.getenv("NETWORK", "testnet").lower() @@ -81,9 +83,7 @@ def create_account(client, memo="Test Account"): try: receipt = tx if receipt.status != ResponseCode.SUCCESS: - print( - f"Account creation failed ({memo}): {ResponseCode(receipt.status).name}" - ) + print(f"Account creation failed ({memo}): {ResponseCode(receipt.status).name}") sys.exit(1) account_id = receipt.account_id @@ -128,12 +128,7 @@ def create_nft_token(client, owner_id, owner_key): def mint_nft(client, token_id, metadata_list): """Mint NFT(s).""" try: - tx = ( - TokenMintTransaction() - .set_token_id(token_id) - .set_metadata(metadata_list) - .execute(client) - ) + tx = TokenMintTransaction().set_token_id(token_id).set_metadata(metadata_list).execute(client) receipt = tx if receipt.status != ResponseCode.SUCCESS: @@ -141,9 +136,7 @@ def mint_nft(client, token_id, metadata_list): sys.exit(1) serials = receipt.serial_numbers - print( - f"NFT Owner ({client.operator_account_id}) minted {len(serials)} NFT(s) for Token {token_id}: {serials}" - ) + print(f"NFT Owner ({client.operator_account_id}) minted {len(serials)} NFT(s) for Token {token_id}: {serials}") return [NftId(token_id, s) for s in serials] except Exception as e: print(f"NFT minting exception: {e}") @@ -164,9 +157,7 @@ def associate_token_with_account(client, account_id, private_key, token_id): receipt = tx if receipt.status != ResponseCode.SUCCESS: - print( - f"Token association failed for {account_id}: {ResponseCode(receipt.status).name}" - ) + print(f"Token association failed for {account_id}: {ResponseCode(receipt.status).name}") sys.exit(1) print(f"Associated token {token_id} with Receiver account {account_id}") @@ -197,9 +188,7 @@ def approve_nft_allowance_all_serials( print(f"Allowance approval failed: {ResponseCode(receipt.status).name}") sys.exit(1) - print( - f"NFT Owner ({owner_id}) approved Spender ({spender_id}) for ALL serials of token {token_id}" - ) + print(f"NFT Owner ({owner_id}) approved Spender ({spender_id}) for ALL serials of token {token_id}") except Exception as e: print(f"Allowance approval exception: {e}") sys.exit(1) @@ -213,9 +202,7 @@ def delete_nft_allowance_all_serials( owner_key: PrivateKey, ): """Revokes an "approve for all serials" NFT allowance from a spender.""" - print( - f"NFT Owner ({owner_id}) deleting 'approve for all' allowance for {token_id} from Spender ({spender_id})..." - ) + print(f"NFT Owner ({owner_id}) deleting 'approve for all' allowance for {token_id} from Spender ({spender_id})...") try: tx = ( @@ -237,29 +224,19 @@ def delete_nft_allowance_all_serials( sys.exit(1) -def verify_allowance_removed( - spender_client, nft_id: NftId, owner_id: AccountId, receiver_id: AccountId -): +def verify_allowance_removed(spender_client, nft_id: NftId, owner_id: AccountId, receiver_id: AccountId): """ Try to transfer NFT after allowance removal (should fail). This transaction is paid for and signed by the SPENDER. """ - print( - f"\nVerifying allowance removal by Spender ({spender_client.operator_account_id}) attempting transfer..." - ) + print(f"\nVerifying allowance removal by Spender ({spender_client.operator_account_id}) attempting transfer...") try: - receipt = ( - TransferTransaction() - .add_approved_nft_transfer(nft_id, owner_id, receiver_id) - .execute(spender_client) - ) + receipt = TransferTransaction().add_approved_nft_transfer(nft_id, owner_id, receiver_id).execute(spender_client) if receipt.status == ResponseCode.SPENDER_DOES_NOT_HAVE_ALLOWANCE: - print( - "Verification SUCCEEDED: Transfer failed with SPENDER_DOES_NOT_HAVE_ALLOWANCE as expected." - ) + print("Verification SUCCEEDED: Transfer failed with SPENDER_DOES_NOT_HAVE_ALLOWANCE as expected.") elif receipt.status == ResponseCode.SUCCESS: print("Verification FAILED: Transfer succeeded unexpectedly!") sys.exit(1) @@ -291,14 +268,10 @@ def main(): associate_token_with_account(owner_client, receiver_id, receiver_key, token_id) # 4. Approve allowance (for all serials) - approve_nft_allowance_all_serials( - owner_client, token_id, owner_id, spender_id, owner_key - ) + approve_nft_allowance_all_serials(owner_client, token_id, owner_id, spender_id, owner_key) # 5. Delete the "all serials" allowance - delete_nft_allowance_all_serials( - owner_client, token_id, owner_id, spender_id, owner_key - ) + delete_nft_allowance_all_serials(owner_client, token_id, owner_id, spender_id, owner_key) # 6. Verify deletion print("\nSetting up client for the Spender...") diff --git a/examples/account/account_create_transaction.py b/examples/account/account_create_transaction.py index bf92f361c..10abbe7af 100644 --- a/examples/account/account_create_transaction.py +++ b/examples/account/account_create_transaction.py @@ -96,9 +96,7 @@ def create_new_account(client: Client) -> None: print(f" New Account Private Key: {new_account_private_key.to_string()}") print(f" New Account Public Key: {new_account_public_key.to_string()}") else: - raise Exception( - "AccountID not found in receipt. Account may not have been created." - ) + raise Exception("AccountID not found in receipt. Account may not have been created.") except Exception as e: print(f"❌ Account creation failed: {str(e)}") diff --git a/examples/account/account_create_transaction_create_with_alias.py b/examples/account/account_create_transaction_create_with_alias.py index 093c2cf2d..da99c00e0 100644 --- a/examples/account/account_create_transaction_create_with_alias.py +++ b/examples/account/account_create_transaction_create_with_alias.py @@ -25,6 +25,7 @@ PrivateKey, ) + load_dotenv() diff --git a/examples/account/account_create_transaction_evm_alias.py b/examples/account/account_create_transaction_evm_alias.py index 9e151a798..63b26c672 100644 --- a/examples/account/account_create_transaction_evm_alias.py +++ b/examples/account/account_create_transaction_evm_alias.py @@ -12,6 +12,7 @@ uv run examples/account/account_create_transaction_evm_alias.py python examples/account/account_create_transaction_evm_alias.py """ + import sys from dotenv import load_dotenv @@ -28,6 +29,7 @@ PublicKey, ) + load_dotenv() diff --git a/examples/account/account_create_transaction_with_fallback_alias.py b/examples/account/account_create_transaction_with_fallback_alias.py index 3f526f3d2..b22063adb 100644 --- a/examples/account/account_create_transaction_with_fallback_alias.py +++ b/examples/account/account_create_transaction_with_fallback_alias.py @@ -10,6 +10,7 @@ uv run examples/account/account_create_transaction_with_fallback_alias.py python examples/account/account_create_transaction_with_fallback_alias.py """ + import sys from dotenv import load_dotenv @@ -24,6 +25,7 @@ PrivateKey, ) + load_dotenv() diff --git a/examples/account/account_create_transaction_without_alias.py b/examples/account/account_create_transaction_without_alias.py index 4c5544419..d522ccacf 100644 --- a/examples/account/account_create_transaction_without_alias.py +++ b/examples/account/account_create_transaction_without_alias.py @@ -11,6 +11,7 @@ - uv run python examples/account/account_create_transaction_without_alias.py - python examples/account/account_create_transaction_without_alias.py """ + import sys from hiero_sdk_python import ( @@ -33,6 +34,7 @@ def setup_client() -> Client: print(f"Client set up with operator id {client.operator_account_id}") return client + def generate_account_key() -> tuple[PrivateKey, PublicKey]: """Generate a key pair for the account.""" print("\nSTEP 1: Generating a key pair for the account (no alias)...") @@ -41,10 +43,13 @@ def generate_account_key() -> tuple[PrivateKey, PublicKey]: print(f"✅ Account public key (no alias): {account_public_key}") return account_private_key, account_public_key -def create_account_without_alias(client: Client, account_public_key: PublicKey, account_private_key: PrivateKey) -> AccountId: + +def create_account_without_alias( + client: Client, account_public_key: PublicKey, account_private_key: PrivateKey +) -> AccountId: """Create an account without setting any alias.""" print("\nSTEP 2: Creating the account without setting any alias...") - + transaction = ( AccountCreateTransaction( initial_balance=Hbar(5), @@ -58,27 +63,21 @@ def create_account_without_alias(client: Client, account_public_key: PublicKey, response = transaction.execute(client) if response.status != ResponseCode.SUCCESS: - raise RuntimeError( - f"Transaction failed with status: {response.status.name}" - ) + raise RuntimeError(f"Transaction failed with status: {response.status.name}") new_account_id = response.account_id if new_account_id is None: - raise RuntimeError( - "AccountID not found in receipt. Account may not have been created." - ) + raise RuntimeError("AccountID not found in receipt. Account may not have been created.") print(f"✅ Account created with ID: {new_account_id}\n") return new_account_id + def fetch_account_info(client: Client, account_id: AccountId) -> AccountInfo: """Fetch account information.""" - return ( - AccountInfoQuery() - .set_account_id(account_id) - .execute(client) - ) + return AccountInfoQuery().set_account_id(account_id).execute(client) + def main() -> None: """Main entry point.""" @@ -89,13 +88,11 @@ def main() -> None: account_info = fetch_account_info(client, new_account_id) print("\nAccount Info:") print(account_info) - print( - "\n✅ contract_account_id (no alias, zero-padded): " - f"{account_info.contract_account_id}" - ) + print(f"\n✅ contract_account_id (no alias, zero-padded): {account_info.contract_account_id}") except Exception as error: print(f"❌ Error: {error}") sys.exit(1) + if __name__ == "__main__": main() diff --git a/examples/account/account_delete_transaction.py b/examples/account/account_delete_transaction.py index ba20ae856..97d2027fa 100644 --- a/examples/account/account_delete_transaction.py +++ b/examples/account/account_delete_transaction.py @@ -8,6 +8,7 @@ python examples/account/account_delete_transaction.py """ + import sys from hiero_sdk_python import ( @@ -36,9 +37,7 @@ def create_account(client): ) if receipt.status != ResponseCode.SUCCESS: - print( - f"Account creation failed with status: {ResponseCode(receipt.status).name}" - ) + print(f"Account creation failed with status: {ResponseCode(receipt.status).name}") sys.exit(1) account_id = receipt.account_id diff --git a/examples/account/account_id.py b/examples/account/account_id.py index 1088883e9..c03b62825 100644 --- a/examples/account/account_id.py +++ b/examples/account/account_id.py @@ -11,6 +11,7 @@ 3. Comparing AccountId instances 4. Creating an AccountId with a public key alias """ + from hiero_sdk_python import AccountId, PrivateKey @@ -81,9 +82,7 @@ def compare_account_ids(): print(f"\nHash of AccountId 1: {hash(account_id1)}") print(f"Hash of AccountId 2: {hash(account_id2)}") print(f"Hash of AccountId 3: {hash(account_id3)}") - print( - f"Are hashes of AccountId 1 and 2 equal? {hash(account_id1) == hash(account_id2)}" - ) + print(f"Are hashes of AccountId 1 and 2 equal? {hash(account_id1) == hash(account_id2)}") def create_account_id_with_alias(): diff --git a/examples/account/account_id_populate_from_mirror.py b/examples/account/account_id_populate_from_mirror.py index bee0edcba..96057d6fd 100644 --- a/examples/account/account_id_populate_from_mirror.py +++ b/examples/account/account_id_populate_from_mirror.py @@ -13,6 +13,7 @@ 3. Populate account number (num) from mirror node 4. Populate EVM address from mirror node """ + import sys import time @@ -90,11 +91,7 @@ def populate_account_num_example(client, evm_address, created_account_id): print(f" Num: {new_account_id.num}") if new_account_id.num != created_account_id.num: - print( - "Account number mismatch:\n" - f" Expected: {created_account_id.num}\n" - f" Got: {new_account_id.num}" - ) + print(f"Account number mismatch:\n Expected: {created_account_id.num}\n Got: {new_account_id.num}") sys.exit(1) @@ -115,11 +112,7 @@ def populate_evm_address_example(client, created_account_id, evm_address): print(f"After populate: evm_address = {new_account_id.evm_address}") if new_account_id.evm_address != evm_address: - print( - "EVM address mismatch:\n" - f" Expected: {evm_address}\n" - f" Got: {new_account_id.evm_address}" - ) + print(f"EVM address mismatch:\n Expected: {evm_address}\n Got: {new_account_id.evm_address}") sys.exit(1) diff --git a/examples/account/account_records_query.py b/examples/account/account_records_query.py index 9cf429101..86cf83a03 100644 --- a/examples/account/account_records_query.py +++ b/examples/account/account_records_query.py @@ -6,6 +6,7 @@ uv run examples/account/account_records_query.py python examples/account/account_records_query.py """ + import os import sys @@ -23,6 +24,7 @@ from hiero_sdk_python.account.account_records_query import AccountRecordsQuery from hiero_sdk_python.transaction.transfer_transaction import TransferTransaction + load_dotenv() network_name = os.getenv("NETWORK", "testnet").lower() @@ -57,9 +59,7 @@ def create_account(client): ) if receipt.status != ResponseCode.SUCCESS: - print( - f"Account creation failed with status: {ResponseCode(receipt.status).name}" - ) + print(f"Account creation failed with status: {ResponseCode(receipt.status).name}") sys.exit(1) account_id = receipt.account_id diff --git a/examples/account/account_update_transaction.py b/examples/account/account_update_transaction.py index 57c06d113..4628c1c4f 100644 --- a/examples/account/account_update_transaction.py +++ b/examples/account/account_update_transaction.py @@ -7,6 +7,7 @@ uv run examples/account/account_update_transaction.py python examples/account/account_update_transaction.py """ + import datetime import sys @@ -42,9 +43,7 @@ def create_account(client): ) if receipt.status != ResponseCode.SUCCESS: - print( - f"Account creation failed with status: {ResponseCode(receipt.status).name}" - ) + print(f"Account creation failed with status: {ResponseCode(receipt.status).name}") sys.exit(1) account_id = receipt.account_id diff --git a/examples/account/account_update_transaction_with_keylist.py b/examples/account/account_update_transaction_with_keylist.py index d63938214..8861e7173 100644 --- a/examples/account/account_update_transaction_with_keylist.py +++ b/examples/account/account_update_transaction_with_keylist.py @@ -6,6 +6,7 @@ python examples/account/account_update_transaction_with_keylist.py """ + import sys from hiero_sdk_python import Client, Hbar, PrivateKey @@ -40,9 +41,7 @@ def create_account(client): ) if receipt.status != ResponseCode.SUCCESS: - print( - f"Account creation failed with status: {ResponseCode(receipt.status).name}" - ) + print(f"Account creation failed with status: {ResponseCode(receipt.status).name}") sys.exit(1) account_id = receipt.account_id @@ -82,9 +81,7 @@ def account_update_with_keylist(): # Rotate from a single key to a threshold KeyList (2 of 2). threshold_key_1 = PrivateKey.generate_ed25519() threshold_key_2 = PrivateKey.generate_ed25519() - threshold_key = KeyList( - [threshold_key_1.public_key(), threshold_key_2.public_key()], threshold=2 - ) + threshold_key = KeyList([threshold_key_1.public_key(), threshold_key_2.public_key()], threshold=2) print("\nRotating account key to a 2-of-2 threshold KeyList...") key_list_receipt = ( @@ -99,9 +96,7 @@ def account_update_with_keylist(): ) if key_list_receipt.status != ResponseCode.SUCCESS: - print( - f"KeyList rotation failed with status: {ResponseCode(key_list_receipt.status).name}" - ) + print(f"KeyList rotation failed with status: {ResponseCode(key_list_receipt.status).name}") sys.exit(1) print("\nAccount info after KeyList update:") @@ -120,10 +115,7 @@ def account_update_with_keylist(): ) if memo_receipt.status != ResponseCode.SUCCESS: - print( - "Memo update with threshold key failed with status: " - f"{ResponseCode(memo_receipt.status).name}" - ) + print(f"Memo update with threshold key failed with status: {ResponseCode(memo_receipt.status).name}") sys.exit(1) print("\nAccount info after memo update:") @@ -133,4 +125,4 @@ def account_update_with_keylist(): if __name__ == "__main__": - account_update_with_keylist() \ No newline at end of file + account_update_with_keylist() diff --git a/examples/client/client.py b/examples/client/client.py index 552a938a5..83f70d5fa 100644 --- a/examples/client/client.py +++ b/examples/client/client.py @@ -11,12 +11,14 @@ uv run examples/client/client.py python examples/client/client.py """ + import os from dotenv import load_dotenv from hiero_sdk_python import AccountId, Client, Network, PrivateKey + load_dotenv() diff --git a/examples/consensus/topic_create_transaction.py b/examples/consensus/topic_create_transaction.py index 756974317..094dbf281 100644 --- a/examples/consensus/topic_create_transaction.py +++ b/examples/consensus/topic_create_transaction.py @@ -5,6 +5,7 @@ uv run examples/consensus/topic_create_transaction.py python examples/consensus/topic_create_transaction.py """ + from hiero_sdk_python import Client, PrivateKey, ResponseCode, TopicCreateTransaction @@ -19,12 +20,11 @@ def setup_client(): print(f"Client set up with operator id {client.operator_account_id}") return client, client.operator_private_key + def create_topic(client: Client, operator_key: PrivateKey): """Builds, signs, and executes a new topic creation transaction.""" transaction = ( - TopicCreateTransaction( - memo="Python SDK created topic", admin_key=operator_key.public_key() - ) + TopicCreateTransaction(memo="Python SDK created topic", admin_key=operator_key.public_key()) .freeze_with(client) .sign(operator_key) ) @@ -39,12 +39,14 @@ def create_topic(client: Client, operator_key: PrivateKey): print(f"Success! Topic created with ID: {receipt.topic_id}") except Exception as e: print(f"Topic creation failed: {str(e)}") - raise SystemExit(1) + raise SystemExit(1) from e + def main(): """Main workflow to set up the client and create a new topic.""" client, operator_key = setup_client() create_topic(client, operator_key) + if __name__ == "__main__": - main() \ No newline at end of file + main() diff --git a/examples/consensus/topic_create_transaction_revenue_generating.py b/examples/consensus/topic_create_transaction_revenue_generating.py index 0195c3973..22137914a 100644 --- a/examples/consensus/topic_create_transaction_revenue_generating.py +++ b/examples/consensus/topic_create_transaction_revenue_generating.py @@ -16,6 +16,7 @@ uv run examples/consensus/topic_create_transaction_revenue_generating.py python examples/consensus/topic_create_transaction_revenue_generating.py """ + import sys from hiero_sdk_python import ( @@ -45,6 +46,7 @@ def setup_client() -> Client: print(f"Client set up with operator id {client.operator_account_id}") return client + def create_account(client, name, initial_balance=Hbar(10)): """Create a test account.""" account_private_key = PrivateKey.generate_ed25519() @@ -58,9 +60,7 @@ def create_account(client, name, initial_balance=Hbar(10)): ) if receipt.status != ResponseCode.SUCCESS: - print( - f"Account creation failed with status: {ResponseCode(receipt.status).name}" - ) + print(f"Account creation failed with status: {ResponseCode(receipt.status).name}") sys.exit(1) account_id = receipt.account_id @@ -101,9 +101,7 @@ def submit_message_with_custom_fee_limit(client, topic_id, custom_fee_limit): receipt = tx.execute(client) if receipt.status != ResponseCode.SUCCESS: - print( - f"Message submission failed with status: {ResponseCode(receipt.status).name}" - ) + print(f"Message submission failed with status: {ResponseCode(receipt.status).name}") sys.exit(1) print("Message submitted successfully") @@ -117,9 +115,7 @@ def submit_message_without_custom_fee_limit(client, topic_id): receipt = tx.execute(client) if receipt.status != ResponseCode.SUCCESS: - print( - f"Message submission failed with status: {ResponseCode(receipt.status).name}" - ) + print(f"Message submission failed with status: {ResponseCode(receipt.status).name}") sys.exit(1) print("Message submitted successfully") @@ -171,9 +167,7 @@ def associate_token_with_account(client, account_id, token_id, account_key): ) if receipt.status != ResponseCode.SUCCESS: - print( - f"Token association failed with status: {ResponseCode(receipt.status).name}" - ) + print(f"Token association failed with status: {ResponseCode(receipt.status).name}") sys.exit(1) print("Token associated successfully") @@ -197,12 +191,7 @@ def transfer_tokens(client, token_id, from_account_id, to_account_id, amount): def update_topic_custom_fees(client, topic_id, custom_fees): """Update topic custom fees.""" - receipt = ( - TopicUpdateTransaction() - .set_topic_id(topic_id) - .set_custom_fees(custom_fees) - .execute(client) - ) + receipt = TopicUpdateTransaction().set_topic_id(topic_id).set_custom_fees(custom_fees).execute(client) if receipt.status != ResponseCode.SUCCESS: print(f"Topic update failed with status: {ResponseCode(receipt.status).name}") @@ -213,12 +202,7 @@ def update_topic_custom_fees(client, topic_id, custom_fees): def update_topic_fee_exempt_keys(client, topic_id, fee_exempt_keys): """Update topic fee exempt keys.""" - receipt = ( - TopicUpdateTransaction() - .set_topic_id(topic_id) - .set_fee_exempt_keys(fee_exempt_keys) - .execute(client) - ) + receipt = TopicUpdateTransaction().set_topic_id(topic_id).set_fee_exempt_keys(fee_exempt_keys).execute(client) if receipt.status != ResponseCode.SUCCESS: print(f"Topic update failed with status: {ResponseCode(receipt.status).name}") @@ -227,9 +211,7 @@ def update_topic_fee_exempt_keys(client, topic_id, fee_exempt_keys): print("Topic updated with fee exempt keys") -def test_hbar_fee_flow( - client, topic_id, alice_id, alice_key, operator_id, operator_key -): +def test_hbar_fee_flow(client, topic_id, alice_id, alice_key, operator_id, operator_key): """Test Steps 3-4: Submit message with custom fee limit and verify Hbar fee collection.""" print("Submitting a message as Alice to the topic") alice_balance_before = get_account_balance(client, alice_id) @@ -256,17 +238,11 @@ def test_hbar_fee_flow( print(f"Alice account Hbar balance before: {alice_balance_before.hbars}") print(f"Alice account Hbar balance after: {alice_balance_after.hbars}") - print( - f"Fee collector account Hbar balance before: {fee_collector_balance_before.hbars}" - ) - print( - f"Fee collector account Hbar balance after: {fee_collector_balance_after.hbars}" - ) + print(f"Fee collector account Hbar balance before: {fee_collector_balance_before.hbars}") + print(f"Fee collector account Hbar balance after: {fee_collector_balance_after.hbars}") -def setup_token_and_update_topic( - client, topic_id, alice_id, alice_key, operator_id, operator_key -): +def setup_token_and_update_topic(client, topic_id, alice_id, alice_key, operator_id, operator_key): """Test Steps 5-6: Create token, transfer to Alice, and update topic with token fee.""" print("Creating a token") token_id = create_fungible_token(client, operator_id, operator_key) @@ -290,9 +266,7 @@ def setup_token_and_update_topic( return token_id -def test_token_fee_flow( - client, topic_id, token_id, alice_id, alice_key, operator_id, operator_key -): +def test_token_fee_flow(client, topic_id, token_id, alice_id, alice_key, operator_id, operator_key): """Test Steps 7-8: Submit message without custom fee limit and verify token fee collection.""" print("Submitting a message as Alice to the topic") alice_balance_before = get_account_balance(client, alice_id) @@ -310,23 +284,11 @@ def test_token_fee_flow( fee_collector_balance_after = get_account_balance(client, operator_id) print(f"Alice account Hbar balance before: {alice_balance_before.hbars}") - print( - f"Alice account Token balance before:" - f"{alice_balance_before.token_balances.get(token_id, 0)}" - ) + print(f"Alice account Token balance before:{alice_balance_before.token_balances.get(token_id, 0)}") print(f"Alice account Hbar balance after: {alice_balance_after.hbars}") - print( - f"Alice account Token balance after:" - f"{alice_balance_after.token_balances.get(token_id, 0)}" - ) - print( - f"Fee collector account Token balance before:" - f"{fee_collector_balance_before.token_balances.get(token_id, 0)}" - ) - print( - f"Fee collector account Token balance after:" - f"{fee_collector_balance_after.token_balances.get(token_id, 0)}" - ) + print(f"Alice account Token balance after:{alice_balance_after.token_balances.get(token_id, 0)}") + print(f"Fee collector account Token balance before:{fee_collector_balance_before.token_balances.get(token_id, 0)}") + print(f"Fee collector account Token balance after:{fee_collector_balance_after.token_balances.get(token_id, 0)}") def test_fee_exempt_flow(client, topic_id, token_id, operator_id, operator_key): @@ -351,15 +313,9 @@ def test_fee_exempt_flow(client, topic_id, token_id, operator_id, operator_key): bob_balance_after = get_account_balance(client, bob_id) print(f"Bob account Hbar balance before: {bob_balance_before.hbars}") - print( - f"Bob account Token balance before:" - f"{bob_balance_before.token_balances.get(token_id, 0)}" - ) + print(f"Bob account Token balance before:{bob_balance_before.token_balances.get(token_id, 0)}") print(f"Bob account Hbar balance after: {bob_balance_after.hbars}") - print( - f"Bob account Token balance after:" - f"{bob_balance_after.token_balances.get(token_id, 0)}" - ) + print(f"Bob account Token balance after:{bob_balance_after.token_balances.get(token_id, 0)}") def revenue_generating_topics(): @@ -389,11 +345,7 @@ def revenue_generating_topics(): # STEP 2: Create a topic with Hbar custom fee print("Creating a topic with hbar custom fee") - custom_hbar_fee = ( - CustomFixedFee() - .set_hbar_amount(Hbar(1)) - .set_fee_collector_account_id(operator_id) - ) + custom_hbar_fee = CustomFixedFee().set_hbar_amount(Hbar(1)).set_fee_collector_account_id(operator_id) topic_id = create_revenue_generating_topic(client, [custom_hbar_fee]) @@ -401,14 +353,10 @@ def revenue_generating_topics(): test_hbar_fee_flow(client, topic_id, alice_id, alice_key, operator_id, operator_key) # STEPS 5-6: Setup token and update topic - token_id = setup_token_and_update_topic( - client, topic_id, alice_id, alice_key, operator_id, operator_key - ) + token_id = setup_token_and_update_topic(client, topic_id, alice_id, alice_key, operator_id, operator_key) # STEPS 7-8: Test token fee flow - test_token_fee_flow( - client, topic_id, token_id, alice_id, alice_key, operator_id, operator_key - ) + test_token_fee_flow(client, topic_id, token_id, alice_id, alice_key, operator_id, operator_key) # STEPS 9-12: Test fee exempt flow test_fee_exempt_flow(client, topic_id, token_id, operator_id, operator_key) diff --git a/examples/consensus/topic_delete_transaction.py b/examples/consensus/topic_delete_transaction.py index b09416191..0ca6a05c5 100644 --- a/examples/consensus/topic_delete_transaction.py +++ b/examples/consensus/topic_delete_transaction.py @@ -37,9 +37,7 @@ def create_topic(client, operator_key): print("\nSTEP 1: Creating a Topic...") try: topic_tx = ( - TopicCreateTransaction( - memo="Python SDK created topic", admin_key=operator_key.public_key() - ) + TopicCreateTransaction(memo="Python SDK created topic", admin_key=operator_key.public_key()) .freeze_with(client) .sign(operator_key) ) @@ -60,9 +58,7 @@ def topic_delete_transaction(client, operator_key, topic_id): Separated so it can be called independently in tests or other scripts. """ print("\nSTEP 2: Deleting Topic...") - transaction = ( - TopicDeleteTransaction(topic_id=topic_id).freeze_with(client).sign(operator_key) - ) + transaction = TopicDeleteTransaction(topic_id=topic_id).freeze_with(client).sign(operator_key) try: receipt = transaction.execute(client) diff --git a/examples/consensus/topic_message.py b/examples/consensus/topic_message.py index de7408447..85ea0f40d 100644 --- a/examples/consensus/topic_message.py +++ b/examples/consensus/topic_message.py @@ -13,6 +13,7 @@ python examples/consensus/topic_message.py """ + from hiero_sdk_python.consensus.topic_message import TopicMessage @@ -98,11 +99,7 @@ def mock_consensus_response( chunk_info = None if is_chunked: - tx_id = ( - MockTransactionID(MockAccountID(0, 0, 10), 1736539100, 1) - if has_tx_id - else None - ) + tx_id = MockTransactionID(MockAccountID(0, 0, 10), 1736539100, 1) if has_tx_id else None chunk_info = MockChunkInfo(seq, total_chunks, tx_id) return MockResponse(message, seq, timestamp, chunk_info) @@ -129,15 +126,9 @@ def demonstrate_multi_chunk(): print("\n--- 2. Multi-Chunk TopicMessage ---") responses = [ - mock_consensus_response( - b"This is the first part, ", seq=1, is_chunked=True, total_chunks=3 - ), - mock_consensus_response( - b"this is the second, ", seq=2, is_chunked=True, total_chunks=3 - ), - mock_consensus_response( - b"and this is the end.", seq=3, is_chunked=True, total_chunks=3 - ), + mock_consensus_response(b"This is the first part, ", seq=1, is_chunked=True, total_chunks=3), + mock_consensus_response(b"this is the second, ", seq=2, is_chunked=True, total_chunks=3), + mock_consensus_response(b"and this is the end.", seq=3, is_chunked=True, total_chunks=3), ] topic_msg = TopicMessage.of_many(responses) @@ -151,9 +142,7 @@ def demonstrate_multi_chunk(): print(" Inspecting individual chunks:") for chunk in topic_msg.chunks: - print( - f" - Chunk Seq: {chunk.sequence_number}, Size: {chunk.content_size} bytes" - ) + print(f" - Chunk Seq: {chunk.sequence_number}, Size: {chunk.content_size} bytes") def demonstrate_transaction_id(): diff --git a/examples/consensus/topic_message_submit_chunked_transaction.py b/examples/consensus/topic_message_submit_chunked_transaction.py index a6be1378c..d07ce971e 100644 --- a/examples/consensus/topic_message_submit_chunked_transaction.py +++ b/examples/consensus/topic_message_submit_chunked_transaction.py @@ -5,6 +5,7 @@ uv run examples/consensus/topic_message_submit_chunked.py python examples/consensus/topic_message_submit_chunked.py """ + import os import sys @@ -21,6 +22,7 @@ TopicMessageSubmitTransaction, ) + BIG_CONTENT = """ @@ -77,11 +79,7 @@ def create_topic(client): """Create a new topic.""" print("\nCreating a Topic...") try: - topic_receipt = ( - TopicCreateTransaction(memo="Python SDK created topic") - .freeze_with(client) - .execute(client) - ) + topic_receipt = TopicCreateTransaction(memo="Python SDK created topic").freeze_with(client).execute(client) topic_id = topic_receipt.topic_id print(f"Topic created: {topic_id}") @@ -105,18 +103,14 @@ def submit_topic_message_transaction(client, topic_id): ) if message_receipt.status != ResponseCode.SUCCESS: - print( - f"Failed to submit message status: {ResponseCode(message_receipt.status).name}" - ) + print(f"Failed to submit message status: {ResponseCode(message_receipt.status).name}") sys.exit(1) print( f"Message submitted (status={ResponseCode(message_receipt.status)}, txId={message_receipt.transaction_id})" ) print("Message size:", len(BIG_CONTENT), "bytes") - print( - f"Message Content: {(BIG_CONTENT[:140] + '...') if len(BIG_CONTENT) > 140 else BIG_CONTENT}" - ) + print(f"Message Content: {(BIG_CONTENT[:140] + '...') if len(BIG_CONTENT) > 140 else BIG_CONTENT}") except Exception as e: print(f"Error: Message submission failed: {str(e)}") diff --git a/examples/consensus/topic_message_submit_transaction.py b/examples/consensus/topic_message_submit_transaction.py index f03bf1a39..70a1040f1 100644 --- a/examples/consensus/topic_message_submit_transaction.py +++ b/examples/consensus/topic_message_submit_transaction.py @@ -5,6 +5,7 @@ uv run examples/consensus/topic_message_submit_transaction.py python examples/consensus/topic_message_submit_transaction.py """ + import os import sys @@ -20,6 +21,7 @@ TopicMessageSubmitTransaction, ) + load_dotenv() network_name = os.getenv("NETWORK", "testnet").lower() @@ -47,9 +49,7 @@ def create_topic(client, operator_key): print("\nSTEP 1: Creating a Topic...") try: topic_tx = ( - TopicCreateTransaction( - memo="Python SDK created topic", admin_key=operator_key.public_key() - ) + TopicCreateTransaction(memo="Python SDK created topic", admin_key=operator_key.public_key()) .freeze_with(client) .sign(operator_key) ) @@ -67,9 +67,7 @@ def submit_topic_message_transaction(client, topic_id, message, operator_key): """Submit a message to the specified topic.""" print("\nSTEP 2: Submitting message...") transaction = ( - TopicMessageSubmitTransaction(topic_id=topic_id, message=message) - .freeze_with(client) - .sign(operator_key) + TopicMessageSubmitTransaction(topic_id=topic_id, message=message).freeze_with(client).sign(operator_key) ) try: diff --git a/examples/consensus/topic_update_transaction.py b/examples/consensus/topic_update_transaction.py index 3f8525c7d..02d147201 100644 --- a/examples/consensus/topic_update_transaction.py +++ b/examples/consensus/topic_update_transaction.py @@ -5,6 +5,7 @@ uv run examples/consensus/topic_update_transaction.py python examples/consensus/topic_update_transaction.py """ + import os import sys @@ -20,6 +21,7 @@ TopicUpdateTransaction, ) + load_dotenv() network_name = os.getenv("NETWORK", "testnet").lower() @@ -47,9 +49,7 @@ def create_topic(client, operator_key): print("\nSTEP 1: Creating a Topic...") try: topic_tx = ( - TopicCreateTransaction( - memo="Python SDK created topic", admin_key=operator_key.public_key() - ) + TopicCreateTransaction(memo="Python SDK created topic", admin_key=operator_key.public_key()) .freeze_with(client) .sign(operator_key) ) @@ -73,11 +73,7 @@ def update_topic(new_memo): # Update the Topic print("\nSTEP 2: Updating Topic...") - transaction = ( - TopicUpdateTransaction(topic_id=topic_id, memo=new_memo) - .freeze_with(client) - .sign(operator_key) - ) + transaction = TopicUpdateTransaction(topic_id=topic_id, memo=new_memo).freeze_with(client).sign(operator_key) try: receipt = transaction.execute(client) diff --git a/examples/contract/contract_balance_query.py b/examples/contract/contract_balance_query.py index cdc925ff7..0d79eccaf 100644 --- a/examples/contract/contract_balance_query.py +++ b/examples/contract/contract_balance_query.py @@ -13,6 +13,7 @@ uv run -m examples.contract.contract_balance_query python -m examples.contract.contract_balance_query """ + import sys from dotenv import load_dotenv @@ -28,6 +29,7 @@ from .contracts import SIMPLE_CONTRACT_BYTECODE + load_dotenv() @@ -39,7 +41,7 @@ def setup_client() -> Client: except Exception as e: print(f"❌ Failed: {e}") sys.exit(1) - + return client diff --git a/examples/contract/contract_bytecode_query.py b/examples/contract/contract_bytecode_query.py index f43973657..85451df08 100644 --- a/examples/contract/contract_bytecode_query.py +++ b/examples/contract/contract_bytecode_query.py @@ -18,6 +18,7 @@ python -m examples.contract.contract_bytecode_query """ + import os import sys @@ -35,6 +36,7 @@ # The contract bytecode is pre-compiled from Solidity source code from .contracts import SIMPLE_CONTRACT_BYTECODE + load_dotenv() network_name = os.getenv("NETWORK", "testnet").lower() @@ -66,9 +68,7 @@ def create_contract_file(client): # Check if file creation was successful if file_receipt.status != ResponseCode.SUCCESS: - print( - f"File creation failed with status: {ResponseCode(file_receipt.status).name}" - ) + print(f"File creation failed with status: {ResponseCode(file_receipt.status).name}") sys.exit(1) return file_receipt.file_id @@ -87,9 +87,7 @@ def create_contract(client, file_id): # Check if contract creation was successful if receipt.status != ResponseCode.SUCCESS: - print( - f"Contract creation failed with status: {ResponseCode(receipt.status).name}" - ) + print(f"Contract creation failed with status: {ResponseCode(receipt.status).name}") sys.exit(1) print(f"Contract created with ID: {receipt.contract_id}") diff --git a/examples/contract/contract_call_query.py b/examples/contract/contract_call_query.py index 3dfc64b64..2e0db546f 100644 --- a/examples/contract/contract_call_query.py +++ b/examples/contract/contract_call_query.py @@ -18,6 +18,7 @@ python -m examples.contract.contract_call_query """ + import os import sys @@ -38,6 +39,7 @@ # The contract bytecode is pre-compiled from Solidity source code from .contracts import STATEFUL_CONTRACT_BYTECODE + load_dotenv() network_name = os.getenv("NETWORK", "testnet").lower() @@ -69,9 +71,7 @@ def create_contract_file(client): # Check if file creation was successful if file_receipt.status != ResponseCode.SUCCESS: - print( - f"File creation failed with status: {ResponseCode(file_receipt.status).name}" - ) + print(f"File creation failed with status: {ResponseCode(file_receipt.status).name}") sys.exit(1) return file_receipt.file_id @@ -93,9 +93,7 @@ def create_contract(client, file_id): # Check if contract creation was successful if receipt.status != ResponseCode.SUCCESS: - print( - f"Contract creation failed with status: {ResponseCode(receipt.status).name}" - ) + print(f"Contract creation failed with status: {ResponseCode(receipt.status).name}") sys.exit(1) return receipt.contract_id @@ -120,13 +118,11 @@ def query_contract_call(): ContractCallQuery() .set_contract_id(contract_id) .set_gas(2000000) - .set_function( - "getMessageAndOwner" - ) # Call the contract's getMessageAndOwner() function + .set_function("getMessageAndOwner") # Call the contract's getMessageAndOwner() function ) cost = query.get_cost(client) query.set_max_query_payment(cost) - + result = query.execute(client) # You can also use set_function_parameters() instead of set_function() e.g.: # .set_function_parameters(ContractFunctionParameters("getMessageAndOwner")) diff --git a/examples/contract/contract_create_transaction_no_constructor_parameters.py b/examples/contract/contract_create_transaction_no_constructor_parameters.py index 4797ced67..477ea5aa9 100644 --- a/examples/contract/contract_create_transaction_no_constructor_parameters.py +++ b/examples/contract/contract_create_transaction_no_constructor_parameters.py @@ -17,6 +17,7 @@ uv run -m examples.contract.contract_create_transaction_no_constructor_parameters """ + import os import sys @@ -36,6 +37,7 @@ # The contract bytecode is pre-compiled from Solidity source code from .contracts import STATEFUL_CONTRACT_BYTECODE + load_dotenv() network_name = os.getenv("NETWORK", "testnet").lower() @@ -67,9 +69,7 @@ def create_contract_file(client): # Check if file creation was successful if file_receipt.status != ResponseCode.SUCCESS: - print( - f"File creation failed with status: {ResponseCode(file_receipt.status).name}" - ) + print(f"File creation failed with status: {ResponseCode(file_receipt.status).name}") sys.exit(1) return file_receipt.file_id @@ -113,9 +113,7 @@ def contract_create_constructor(): # Check if contract creation was successful if receipt.status != ResponseCode.SUCCESS: - print( - f"Contract creation failed with status: {ResponseCode(receipt.status).name}" - ) + print(f"Contract creation failed with status: {ResponseCode(receipt.status).name}") sys.exit(1) contract_id = receipt.contract_id diff --git a/examples/contract/contract_create_transaction_with_bytecode.py b/examples/contract/contract_create_transaction_with_bytecode.py index 573f6c05d..cdc0048b6 100644 --- a/examples/contract/contract_create_transaction_with_bytecode.py +++ b/examples/contract/contract_create_transaction_with_bytecode.py @@ -16,6 +16,7 @@ python -m examples.contract.contract_create_transaction_with_bytecode """ + import os import sys @@ -31,6 +32,7 @@ # The contract bytecode is pre-compiled from Solidity source code from .contracts import SIMPLE_CONTRACT_BYTECODE + load_dotenv() network_name = os.getenv("NETWORK", "testnet").lower() @@ -74,9 +76,7 @@ def contract_create_with_bytecode(): # Check if contract creation was successful if receipt.status != ResponseCode.SUCCESS: - print( - f"Contract creation failed with status: {ResponseCode(receipt.status).name}" - ) + print(f"Contract creation failed with status: {ResponseCode(receipt.status).name}") sys.exit(1) contract_id = receipt.contract_id diff --git a/examples/contract/contract_create_transaction_with_constructor_parameters.py b/examples/contract/contract_create_transaction_with_constructor_parameters.py index 4db6c3721..87b53c599 100644 --- a/examples/contract/contract_create_transaction_with_constructor_parameters.py +++ b/examples/contract/contract_create_transaction_with_constructor_parameters.py @@ -16,6 +16,7 @@ uv run -m examples.contract.contract_create_transaction_with_constructor_parameters python -m examples.contract.contract_create_transaction_with_constructor_parameters """ + import os import sys @@ -32,6 +33,7 @@ # The contract bytecode is pre-compiled from Solidity source code from .contracts import SIMPLE_CONTRACT_BYTECODE + load_dotenv() network_name = os.getenv("NETWORK", "testnet").lower() @@ -63,9 +65,7 @@ def create_contract_file(client): # Check if file creation was successful if file_receipt.status != ResponseCode.SUCCESS: - print( - f"File creation failed with status: {ResponseCode(file_receipt.status).name}" - ) + print(f"File creation failed with status: {ResponseCode(file_receipt.status).name}") sys.exit(1) return file_receipt.file_id @@ -94,9 +94,7 @@ def contract_create(): # Check if contract creation was successful if receipt.status != ResponseCode.SUCCESS: - print( - f"Contract creation failed with status: {ResponseCode(receipt.status).name}" - ) + print(f"Contract creation failed with status: {ResponseCode(receipt.status).name}") sys.exit(1) contract_id = receipt.contract_id diff --git a/examples/contract/contract_delete_transaction.py b/examples/contract/contract_delete_transaction.py index c9f4a1a8a..af9cd4078 100644 --- a/examples/contract/contract_delete_transaction.py +++ b/examples/contract/contract_delete_transaction.py @@ -23,10 +23,7 @@ import sys - - from hiero_sdk_python import Client - from hiero_sdk_python.contract.contract_create_transaction import ( ContractCreateTransaction, ) @@ -43,7 +40,6 @@ from .contracts import SIMPLE_CONTRACT_BYTECODE - def setup_client() -> Client: """ Set up and configure the client by loading OPERATOR_ID and OPERATOR_KEY with Client.from_env(). @@ -66,9 +62,7 @@ def create_contract_file(client): # Check if file creation was successful if file_receipt.status != ResponseCode.SUCCESS: - print( - f"File creation failed with status: {ResponseCode(file_receipt.status).name}" - ) + print(f"File creation failed with status: {ResponseCode(file_receipt.status).name}") sys.exit(1) return file_receipt.file_id @@ -88,9 +82,7 @@ def create_contract(client, file_id, initial_balance): # Check if contract creation was successful if receipt.status != ResponseCode.SUCCESS: - print( - f"Contract creation failed with status: {ResponseCode(receipt.status).name}" - ) + print(f"Contract creation failed with status: {ResponseCode(receipt.status).name}") sys.exit(1) print(f"Contract created with ID: {receipt.contract_id}") @@ -130,9 +122,7 @@ def contract_delete(): ) if receipt.status != ResponseCode.SUCCESS: - print( - f"Contract deletion failed with status: {ResponseCode(receipt.status).name}" - ) + print(f"Contract deletion failed with status: {ResponseCode(receipt.status).name}") sys.exit(1) print("\nSuccessfully deleted contract and transferred hbars to transfer contract") @@ -143,9 +133,7 @@ def contract_delete(): # Check if the transfer contract has the hbars transfer_info = ContractInfoQuery(transfer_contract_id).execute(client) - print( - f"Check transfer contract balance: {Hbar.from_tinybars(transfer_info.balance)}" - ) + print(f"Check transfer contract balance: {Hbar.from_tinybars(transfer_info.balance)}") # Delete the transfer contract and transfer the hbars to the operator account receipt = ( @@ -156,14 +144,10 @@ def contract_delete(): ) if receipt.status != ResponseCode.SUCCESS: - print( - f"Transfer contract deletion failed with status: {ResponseCode(receipt.status).name}" - ) + print(f"Transfer contract deletion failed with status: {ResponseCode(receipt.status).name}") sys.exit(1) - print( - "\nSuccessfully deleted transfer contract and transferred hbars to operator account" - ) + print("\nSuccessfully deleted transfer contract and transferred hbars to operator account") # Check if the transfer contract is deleted transfer_info = ContractInfoQuery(transfer_contract_id).execute(client) diff --git a/examples/contract/contract_execute_transaction.py b/examples/contract/contract_execute_transaction.py index 7daf30ae5..a555ca5fa 100644 --- a/examples/contract/contract_execute_transaction.py +++ b/examples/contract/contract_execute_transaction.py @@ -17,6 +17,7 @@ uv run -m examples.contract.contract_execute_transaction python -m examples.contract.contract_execute_transaction """ + import os import sys @@ -40,6 +41,7 @@ # The contract bytecode is pre-compiled from Solidity source code from .contracts import STATEFUL_CONTRACT_BYTECODE + load_dotenv() network_name = os.getenv("NETWORK", "testnet").lower() @@ -71,9 +73,7 @@ def create_contract_file(client): # Check if file creation was successful if file_receipt.status != ResponseCode.SUCCESS: - print( - f"File creation failed with status: {ResponseCode(file_receipt.status).name}" - ) + print(f"File creation failed with status: {ResponseCode(file_receipt.status).name}") sys.exit(1) return file_receipt.file_id @@ -95,9 +95,7 @@ def create_contract(client, file_id): # Check if contract creation was successful if receipt.status != ResponseCode.SUCCESS: - print( - f"Contract creation failed with status: {ResponseCode(receipt.status).name}" - ) + print(f"Contract creation failed with status: {ResponseCode(receipt.status).name}") sys.exit(1) print(f"Contract created with ID: {receipt.contract_id}") @@ -108,16 +106,11 @@ def create_contract(client, file_id): def get_contract_message(client, contract_id): """Get the message from the contract.""" # Query the contract function to verify that the message was set - query = ( - ContractCallQuery() - .set_contract_id(contract_id) - .set_gas(2000000) - .set_function("getMessage") - ) + query = ContractCallQuery().set_contract_id(contract_id).set_gas(2000000).set_function("getMessage") cost = query.get_cost(client) query.set_max_query_payment(cost) - + result = query.execute(client) # The contract returns bytes32, which we decode to string @@ -162,15 +155,10 @@ def execute_contract(): ) if receipt.status != ResponseCode.SUCCESS: - print( - f"Contract execution failed with status: {ResponseCode(receipt.status).name}" - ) + print(f"Contract execution failed with status: {ResponseCode(receipt.status).name}") sys.exit(1) - print( - f"Successfully executed setMessage() on {contract_id} with new message: " - f"'{new_message_string}'" - ) + print(f"Successfully executed setMessage() on {contract_id} with new message: '{new_message_string}'") # Query the contract function to verify that the message was set updated_message = get_contract_message(client, contract_id) diff --git a/examples/contract/contract_execute_transaction_with_value.py b/examples/contract/contract_execute_transaction_with_value.py index 723a065c9..2bbee9c34 100644 --- a/examples/contract/contract_execute_transaction_with_value.py +++ b/examples/contract/contract_execute_transaction_with_value.py @@ -41,6 +41,7 @@ - A receive() function for plain HBAR transfers - A setMessageAndPay() function that accepts HBAR while updating a message """ + import os import sys @@ -65,6 +66,7 @@ # The contract bytecode is pre-compiled from Solidity source code from .contracts import STATEFUL_CONTRACT_BYTECODE + load_dotenv() network_name = os.getenv("NETWORK", "testnet").lower() @@ -96,9 +98,7 @@ def create_contract_file(client): # Check if file creation was successful if file_receipt.status != ResponseCode.SUCCESS: - print( - f"File creation failed with status: {ResponseCode(file_receipt.status).name}" - ) + print(f"File creation failed with status: {ResponseCode(file_receipt.status).name}") sys.exit(1) return file_receipt.file_id @@ -120,9 +120,7 @@ def create_contract(client, file_id): # Check if contract creation was successful if receipt.status != ResponseCode.SUCCESS: - print( - f"Contract creation failed with status: {ResponseCode(receipt.status).name}" - ) + print(f"Contract creation failed with status: {ResponseCode(receipt.status).name}") sys.exit(1) print(f"Contract created with ID: {receipt.contract_id}") @@ -168,9 +166,7 @@ def execute_contract_with_value(): ) if receipt.status != ResponseCode.SUCCESS: - print( - f"Contract execution failed with status: {ResponseCode(receipt.status).name}" - ) + print(f"Contract execution failed with status: {ResponseCode(receipt.status).name}") sys.exit(1) print(f"Successfully executed contract {contract_id} with HBAR value transfer") diff --git a/examples/contract/contract_info_query.py b/examples/contract/contract_info_query.py index bdcf9c5a8..8e8e7d4f4 100644 --- a/examples/contract/contract_info_query.py +++ b/examples/contract/contract_info_query.py @@ -18,6 +18,7 @@ python -m examples.contract.contract_info_query """ + import sys from hiero_sdk_python import Client, Duration @@ -45,9 +46,7 @@ def create_contract_file(client): # Check if file creation was successful if file_receipt.status != ResponseCode.SUCCESS: - print( - f"File creation failed with status: {ResponseCode(file_receipt.status).name}" - ) + print(f"File creation failed with status: {ResponseCode(file_receipt.status).name}") sys.exit(1) return file_receipt.file_id @@ -70,9 +69,7 @@ def create_contract(client, file_id): # Check if contract creation was successful if receipt.status != ResponseCode.SUCCESS: - print( - f"Contract creation failed with status: {ResponseCode(receipt.status).name}" - ) + print(f"Contract creation failed with status: {ResponseCode(receipt.status).name}") sys.exit(1) print(f"Contract created with ID: {receipt.contract_id}") diff --git a/examples/contract/contract_update_transaction.py b/examples/contract/contract_update_transaction.py index b03791f34..959cbecb6 100644 --- a/examples/contract/contract_update_transaction.py +++ b/examples/contract/contract_update_transaction.py @@ -16,6 +16,7 @@ uv run -m examples.contract.contract_update_transaction python -m examples.contract.contract_update_transaction """ + import datetime import os import sys @@ -39,6 +40,7 @@ # The contract bytecode is pre-compiled from Solidity source code from .contracts import SIMPLE_CONTRACT_BYTECODE + load_dotenv() network_name = os.getenv("NETWORK", "testnet").lower() @@ -70,9 +72,7 @@ def create_contract_file(client): # Check if file creation was successful if file_receipt.status != ResponseCode.SUCCESS: - print( - f"File creation failed with status: {ResponseCode(file_receipt.status).name}" - ) + print(f"File creation failed with status: {ResponseCode(file_receipt.status).name}") sys.exit(1) return file_receipt.file_id @@ -91,9 +91,7 @@ def create_initial_contract(client, file_id): # Check if contract creation was successful if receipt.status != ResponseCode.SUCCESS: - print( - f"Contract creation failed with status: {ResponseCode(receipt.status).name}" - ) + print(f"Contract creation failed with status: {ResponseCode(receipt.status).name}") sys.exit(1) return receipt.contract_id @@ -140,9 +138,7 @@ def contract_update(): # Check if contract update was successful if receipt.status != ResponseCode.SUCCESS: - print( - f"Contract update failed with status: {ResponseCode(receipt.status).name}" - ) + print(f"Contract update failed with status: {ResponseCode(receipt.status).name}") sys.exit(1) print(f"\nContract updated successfully with ID: {contract_id}") diff --git a/examples/contract/contracts/ConstructorTestContract/ConstructorTestContract.bin b/examples/contract/contracts/ConstructorTestContract/ConstructorTestContract.bin index 7db4301ff..6a69c48d5 100644 --- a/examples/contract/contracts/ConstructorTestContract/ConstructorTestContract.bin +++ b/examples/contract/contracts/ConstructorTestContract/ConstructorTestContract.bin @@ -1 +1 @@ -608060405234801561000f575f5ffd5b50604051610ad5380380610ad58339818101604052810190610031919061058a565b865f908161003f919061088b565b50856001819055508460025f6101000a81548160ff02191690835f0b60ff16021790555083600260016101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555082600260156101000a81548160ff02191690831515021790555081600390816100cd91906109bc565b5080600490805190602001906100e49291906100f1565b5050505050505050610a8b565b828054828255905f5260205f2090601f01602090048101928215610182579160200282015f5b8382111561015457835183826101000a81548160ff021916908360ff16021790555092602001926001016020815f01049283019260010302610117565b80156101805782816101000a81549060ff02191690556001016020815f01049283019260010302610154565b505b50905061018f9190610193565b5090565b5b808211156101aa575f815f905550600101610194565b5090565b5f604051905090565b5f5ffd5b5f5ffd5b5f5ffd5b5f5ffd5b5f601f19601f8301169050919050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52604160045260245ffd5b61020d826101c7565b810181811067ffffffffffffffff8211171561022c5761022b6101d7565b5b80604052505050565b5f61023e6101ae565b905061024a8282610204565b919050565b5f67ffffffffffffffff821115610269576102686101d7565b5b610272826101c7565b9050602081019050919050565b8281835e5f83830152505050565b5f61029f61029a8461024f565b610235565b9050828152602081018484840111156102bb576102ba6101c3565b5b6102c684828561027f565b509392505050565b5f82601f8301126102e2576102e16101bf565b5b81516102f284826020860161028d565b91505092915050565b5f819050919050565b61030d816102fb565b8114610317575f5ffd5b50565b5f8151905061032881610304565b92915050565b5f815f0b9050919050565b6103428161032e565b811461034c575f5ffd5b50565b5f8151905061035d81610339565b92915050565b5f73ffffffffffffffffffffffffffffffffffffffff82169050919050565b5f61038c82610363565b9050919050565b61039c81610382565b81146103a6575f5ffd5b50565b5f815190506103b781610393565b92915050565b5f8115159050919050565b6103d1816103bd565b81146103db575f5ffd5b50565b5f815190506103ec816103c8565b92915050565b5f67ffffffffffffffff82111561040c5761040b6101d7565b5b610415826101c7565b9050602081019050919050565b5f61043461042f846103f2565b610235565b9050828152602081018484840111156104505761044f6101c3565b5b61045b84828561027f565b509392505050565b5f82601f830112610477576104766101bf565b5b8151610487848260208601610422565b91505092915050565b5f67ffffffffffffffff8211156104aa576104a96101d7565b5b602082029050602081019050919050565b5f5ffd5b5f60ff82169050919050565b6104d4816104bf565b81146104de575f5ffd5b50565b5f815190506104ef816104cb565b92915050565b5f61050761050284610490565b610235565b9050808382526020820190506020840283018581111561052a576105296104bb565b5b835b81811015610553578061053f88826104e1565b84526020840193505060208101905061052c565b5050509392505050565b5f82601f830112610571576105706101bf565b5b81516105818482602086016104f5565b91505092915050565b5f5f5f5f5f5f5f60e0888a0312156105a5576105a46101b7565b5b5f88015167ffffffffffffffff8111156105c2576105c16101bb565b5b6105ce8a828b016102ce565b97505060206105df8a828b0161031a565b96505060406105f08a828b0161034f565b95505060606106018a828b016103a9565b94505060806106128a828b016103de565b93505060a088015167ffffffffffffffff811115610633576106326101bb565b5b61063f8a828b01610463565b92505060c088015167ffffffffffffffff8111156106605761065f6101bb565b5b61066c8a828b0161055d565b91505092959891949750929550565b5f81519050919050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52602260045260245ffd5b5f60028204905060018216806106c957607f821691505b6020821081036106dc576106db610685565b5b50919050565b5f819050815f5260205f209050919050565b5f6020601f8301049050919050565b5f82821b905092915050565b5f6008830261073e7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff82610703565b6107488683610703565b95508019841693508086168417925050509392505050565b5f819050919050565b5f819050919050565b5f61078c61078761078284610760565b610769565b610760565b9050919050565b5f819050919050565b6107a583610772565b6107b96107b182610793565b84845461070f565b825550505050565b5f5f905090565b6107d06107c1565b6107db81848461079c565b505050565b5b818110156107fe576107f35f826107c8565b6001810190506107e1565b5050565b601f82111561084357610814816106e2565b61081d846106f4565b8101602085101561082c578190505b610840610838856106f4565b8301826107e0565b50505b505050565b5f82821c905092915050565b5f6108635f1984600802610848565b1980831691505092915050565b5f61087b8383610854565b9150826002028217905092915050565b6108948261067b565b67ffffffffffffffff8111156108ad576108ac6101d7565b5b6108b782546106b2565b6108c2828285610802565b5f60209050601f8311600181146108f3575f84156108e1578287015190505b6108eb8582610870565b865550610952565b601f198416610901866106e2565b5f5b8281101561092857848901518255600182019150602085019450602081019050610903565b868310156109455784890151610941601f891682610854565b8355505b6001600288020188555050505b505050505050565b5f81519050919050565b5f819050815f5260205f209050919050565b601f8211156109b75761098881610964565b610991846106f4565b810160208510156109a0578190505b6109b46109ac856106f4565b8301826107e0565b50505b505050565b6109c58261095a565b67ffffffffffffffff8111156109de576109dd6101d7565b5b6109e882546106b2565b6109f3828285610976565b5f60209050601f831160018114610a24575f8415610a12578287015190505b610a1c8582610870565b865550610a83565b601f198416610a3286610964565b5f5b82811015610a5957848901518255600182019150602085019450602081019050610a34565b86831015610a765784890151610a72601f891682610854565b8355505b6001600288020188555050505b505050505050565b603e80610a975f395ff3fe60806040525f5ffdfea264697066735822122063eaf411cd2e2c644a3eba9bbfd335b5245f7eb58f55ddb4754a66d83fd3697764736f6c634300081e0033 \ No newline at end of file +608060405234801561000f575f5ffd5b50604051610ad5380380610ad58339818101604052810190610031919061058a565b865f908161003f919061088b565b50856001819055508460025f6101000a81548160ff02191690835f0b60ff16021790555083600260016101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555082600260156101000a81548160ff02191690831515021790555081600390816100cd91906109bc565b5080600490805190602001906100e49291906100f1565b5050505050505050610a8b565b828054828255905f5260205f2090601f01602090048101928215610182579160200282015f5b8382111561015457835183826101000a81548160ff021916908360ff16021790555092602001926001016020815f01049283019260010302610117565b80156101805782816101000a81549060ff02191690556001016020815f01049283019260010302610154565b505b50905061018f9190610193565b5090565b5b808211156101aa575f815f905550600101610194565b5090565b5f604051905090565b5f5ffd5b5f5ffd5b5f5ffd5b5f5ffd5b5f601f19601f8301169050919050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52604160045260245ffd5b61020d826101c7565b810181811067ffffffffffffffff8211171561022c5761022b6101d7565b5b80604052505050565b5f61023e6101ae565b905061024a8282610204565b919050565b5f67ffffffffffffffff821115610269576102686101d7565b5b610272826101c7565b9050602081019050919050565b8281835e5f83830152505050565b5f61029f61029a8461024f565b610235565b9050828152602081018484840111156102bb576102ba6101c3565b5b6102c684828561027f565b509392505050565b5f82601f8301126102e2576102e16101bf565b5b81516102f284826020860161028d565b91505092915050565b5f819050919050565b61030d816102fb565b8114610317575f5ffd5b50565b5f8151905061032881610304565b92915050565b5f815f0b9050919050565b6103428161032e565b811461034c575f5ffd5b50565b5f8151905061035d81610339565b92915050565b5f73ffffffffffffffffffffffffffffffffffffffff82169050919050565b5f61038c82610363565b9050919050565b61039c81610382565b81146103a6575f5ffd5b50565b5f815190506103b781610393565b92915050565b5f8115159050919050565b6103d1816103bd565b81146103db575f5ffd5b50565b5f815190506103ec816103c8565b92915050565b5f67ffffffffffffffff82111561040c5761040b6101d7565b5b610415826101c7565b9050602081019050919050565b5f61043461042f846103f2565b610235565b9050828152602081018484840111156104505761044f6101c3565b5b61045b84828561027f565b509392505050565b5f82601f830112610477576104766101bf565b5b8151610487848260208601610422565b91505092915050565b5f67ffffffffffffffff8211156104aa576104a96101d7565b5b602082029050602081019050919050565b5f5ffd5b5f60ff82169050919050565b6104d4816104bf565b81146104de575f5ffd5b50565b5f815190506104ef816104cb565b92915050565b5f61050761050284610490565b610235565b9050808382526020820190506020840283018581111561052a576105296104bb565b5b835b81811015610553578061053f88826104e1565b84526020840193505060208101905061052c565b5050509392505050565b5f82601f830112610571576105706101bf565b5b81516105818482602086016104f5565b91505092915050565b5f5f5f5f5f5f5f60e0888a0312156105a5576105a46101b7565b5b5f88015167ffffffffffffffff8111156105c2576105c16101bb565b5b6105ce8a828b016102ce565b97505060206105df8a828b0161031a565b96505060406105f08a828b0161034f565b95505060606106018a828b016103a9565b94505060806106128a828b016103de565b93505060a088015167ffffffffffffffff811115610633576106326101bb565b5b61063f8a828b01610463565b92505060c088015167ffffffffffffffff8111156106605761065f6101bb565b5b61066c8a828b0161055d565b91505092959891949750929550565b5f81519050919050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52602260045260245ffd5b5f60028204905060018216806106c957607f821691505b6020821081036106dc576106db610685565b5b50919050565b5f819050815f5260205f209050919050565b5f6020601f8301049050919050565b5f82821b905092915050565b5f6008830261073e7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff82610703565b6107488683610703565b95508019841693508086168417925050509392505050565b5f819050919050565b5f819050919050565b5f61078c61078761078284610760565b610769565b610760565b9050919050565b5f819050919050565b6107a583610772565b6107b96107b182610793565b84845461070f565b825550505050565b5f5f905090565b6107d06107c1565b6107db81848461079c565b505050565b5b818110156107fe576107f35f826107c8565b6001810190506107e1565b5050565b601f82111561084357610814816106e2565b61081d846106f4565b8101602085101561082c578190505b610840610838856106f4565b8301826107e0565b50505b505050565b5f82821c905092915050565b5f6108635f1984600802610848565b1980831691505092915050565b5f61087b8383610854565b9150826002028217905092915050565b6108948261067b565b67ffffffffffffffff8111156108ad576108ac6101d7565b5b6108b782546106b2565b6108c2828285610802565b5f60209050601f8311600181146108f3575f84156108e1578287015190505b6108eb8582610870565b865550610952565b601f198416610901866106e2565b5f5b8281101561092857848901518255600182019150602085019450602081019050610903565b868310156109455784890151610941601f891682610854565b8355505b6001600288020188555050505b505050505050565b5f81519050919050565b5f819050815f5260205f209050919050565b601f8211156109b75761098881610964565b610991846106f4565b810160208510156109a0578190505b6109b46109ac856106f4565b8301826107e0565b50505b505050565b6109c58261095a565b67ffffffffffffffff8111156109de576109dd6101d7565b5b6109e882546106b2565b6109f3828285610976565b5f60209050601f831160018114610a24575f8415610a12578287015190505b610a1c8582610870565b865550610a83565b601f198416610a3286610964565b5f5b82811015610a5957848901518255600182019150602085019450602081019050610a34565b86831015610a765784890151610a72601f891682610854565b8355505b6001600288020188555050505b505050505050565b603e80610a975f395ff3fe60806040525f5ffdfea264697066735822122063eaf411cd2e2c644a3eba9bbfd335b5245f7eb58f55ddb4754a66d83fd3697764736f6c634300081e0033 diff --git a/examples/contract/contracts/SimpleContract/SimpleContract.bin b/examples/contract/contracts/SimpleContract/SimpleContract.bin index f19a1748e..b1c0bdd07 100644 --- a/examples/contract/contracts/SimpleContract/SimpleContract.bin +++ b/examples/contract/contracts/SimpleContract/SimpleContract.bin @@ -1 +1 @@ -6080604052335f5f6101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055506103b2806100505f395ff3fe608060405260043610610037575f3560e01c806324600fc3146100425780638da5cb5b14610058578063cfae3217146100825761003e565b3661003e57005b5f5ffd5b34801561004d575f5ffd5b506100566100ac565b005b348015610063575f5ffd5b5061006c6101a5565b6040516100799190610245565b60405180910390f35b34801561008d575f5ffd5b506100966101c9565b6040516100a391906102ce565b60405180910390f35b5f5f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff161461013a576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016101319061035e565b60405180910390fd5b5f4790505f5f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166108fc8290811502906040515f60405180830381858888f193505050501580156101a1573d5f5f3e3d5ffd5b5050565b5f5f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b60606040518060400160405280600d81526020017f48656c6c6f2c20776f726c642100000000000000000000000000000000000000815250905090565b5f73ffffffffffffffffffffffffffffffffffffffff82169050919050565b5f61022f82610206565b9050919050565b61023f81610225565b82525050565b5f6020820190506102585f830184610236565b92915050565b5f81519050919050565b5f82825260208201905092915050565b8281835e5f83830152505050565b5f601f19601f8301169050919050565b5f6102a08261025e565b6102aa8185610268565b93506102ba818560208601610278565b6102c381610286565b840191505092915050565b5f6020820190508181035f8301526102e68184610296565b905092915050565b7f4f6e6c7920746865206f776e65722063616e2077697468647261772066756e645f8201527f732e000000000000000000000000000000000000000000000000000000000000602082015250565b5f610348602283610268565b9150610353826102ee565b604082019050919050565b5f6020820190508181035f8301526103758161033c565b905091905056fea2646970667358221220447b4ca6263e05fc8a3c04c000141022b65950a601fddb3057a0673d53426c4764736f6c634300081e0033 \ No newline at end of file +6080604052335f5f6101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055506103b2806100505f395ff3fe608060405260043610610037575f3560e01c806324600fc3146100425780638da5cb5b14610058578063cfae3217146100825761003e565b3661003e57005b5f5ffd5b34801561004d575f5ffd5b506100566100ac565b005b348015610063575f5ffd5b5061006c6101a5565b6040516100799190610245565b60405180910390f35b34801561008d575f5ffd5b506100966101c9565b6040516100a391906102ce565b60405180910390f35b5f5f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff161461013a576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016101319061035e565b60405180910390fd5b5f4790505f5f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166108fc8290811502906040515f60405180830381858888f193505050501580156101a1573d5f5f3e3d5ffd5b5050565b5f5f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b60606040518060400160405280600d81526020017f48656c6c6f2c20776f726c642100000000000000000000000000000000000000815250905090565b5f73ffffffffffffffffffffffffffffffffffffffff82169050919050565b5f61022f82610206565b9050919050565b61023f81610225565b82525050565b5f6020820190506102585f830184610236565b92915050565b5f81519050919050565b5f82825260208201905092915050565b8281835e5f83830152505050565b5f601f19601f8301169050919050565b5f6102a08261025e565b6102aa8185610268565b93506102ba818560208601610278565b6102c381610286565b840191505092915050565b5f6020820190508181035f8301526102e68184610296565b905092915050565b7f4f6e6c7920746865206f776e65722063616e2077697468647261772066756e645f8201527f732e000000000000000000000000000000000000000000000000000000000000602082015250565b5f610348602283610268565b9150610353826102ee565b604082019050919050565b5f6020820190508181035f8301526103758161033c565b905091905056fea2646970667358221220447b4ca6263e05fc8a3c04c000141022b65950a601fddb3057a0673d53426c4764736f6c634300081e0033 diff --git a/examples/contract/contracts/SimpleContract/SimpleContract.bin-runtime b/examples/contract/contracts/SimpleContract/SimpleContract.bin-runtime index f54b1e206..c85d74df7 100644 --- a/examples/contract/contracts/SimpleContract/SimpleContract.bin-runtime +++ b/examples/contract/contracts/SimpleContract/SimpleContract.bin-runtime @@ -1 +1 @@ -608060405260043610610037575f3560e01c806324600fc3146100425780638da5cb5b14610058578063cfae3217146100825761003e565b3661003e57005b5f5ffd5b34801561004d575f5ffd5b506100566100ac565b005b348015610063575f5ffd5b5061006c6101a5565b6040516100799190610245565b60405180910390f35b34801561008d575f5ffd5b506100966101c9565b6040516100a391906102ce565b60405180910390f35b5f5f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff161461013a576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016101319061035e565b60405180910390fd5b5f4790505f5f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166108fc8290811502906040515f60405180830381858888f193505050501580156101a1573d5f5f3e3d5ffd5b5050565b5f5f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b60606040518060400160405280600d81526020017f48656c6c6f2c20776f726c642100000000000000000000000000000000000000815250905090565b5f73ffffffffffffffffffffffffffffffffffffffff82169050919050565b5f61022f82610206565b9050919050565b61023f81610225565b82525050565b5f6020820190506102585f830184610236565b92915050565b5f81519050919050565b5f82825260208201905092915050565b8281835e5f83830152505050565b5f601f19601f8301169050919050565b5f6102a08261025e565b6102aa8185610268565b93506102ba818560208601610278565b6102c381610286565b840191505092915050565b5f6020820190508181035f8301526102e68184610296565b905092915050565b7f4f6e6c7920746865206f776e65722063616e2077697468647261772066756e645f8201527f732e000000000000000000000000000000000000000000000000000000000000602082015250565b5f610348602283610268565b9150610353826102ee565b604082019050919050565b5f6020820190508181035f8301526103758161033c565b905091905056fea2646970667358221220447b4ca6263e05fc8a3c04c000141022b65950a601fddb3057a0673d53426c4764736f6c634300081e0033 \ No newline at end of file +608060405260043610610037575f3560e01c806324600fc3146100425780638da5cb5b14610058578063cfae3217146100825761003e565b3661003e57005b5f5ffd5b34801561004d575f5ffd5b506100566100ac565b005b348015610063575f5ffd5b5061006c6101a5565b6040516100799190610245565b60405180910390f35b34801561008d575f5ffd5b506100966101c9565b6040516100a391906102ce565b60405180910390f35b5f5f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff161461013a576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016101319061035e565b60405180910390fd5b5f4790505f5f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166108fc8290811502906040515f60405180830381858888f193505050501580156101a1573d5f5f3e3d5ffd5b5050565b5f5f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b60606040518060400160405280600d81526020017f48656c6c6f2c20776f726c642100000000000000000000000000000000000000815250905090565b5f73ffffffffffffffffffffffffffffffffffffffff82169050919050565b5f61022f82610206565b9050919050565b61023f81610225565b82525050565b5f6020820190506102585f830184610236565b92915050565b5f81519050919050565b5f82825260208201905092915050565b8281835e5f83830152505050565b5f601f19601f8301169050919050565b5f6102a08261025e565b6102aa8185610268565b93506102ba818560208601610278565b6102c381610286565b840191505092915050565b5f6020820190508181035f8301526102e68184610296565b905092915050565b7f4f6e6c7920746865206f776e65722063616e2077697468647261772066756e645f8201527f732e000000000000000000000000000000000000000000000000000000000000602082015250565b5f610348602283610268565b9150610353826102ee565b604082019050919050565b5f6020820190508181035f8301526103758161033c565b905091905056fea2646970667358221220447b4ca6263e05fc8a3c04c000141022b65950a601fddb3057a0673d53426c4764736f6c634300081e0033 diff --git a/examples/contract/contracts/StatefulContract/StatefulContract.bin b/examples/contract/contracts/StatefulContract/StatefulContract.bin index b2fb686b5..d0d449dee 100644 --- a/examples/contract/contracts/StatefulContract/StatefulContract.bin +++ b/examples/contract/contracts/StatefulContract/StatefulContract.bin @@ -1 +1 @@ -608060405260405161076838038061076883398181016040528101906023919060a0565b805f819055503360015f6101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055505060c6565b5f5ffd5b5f819050919050565b6082816072565b8114608b575f5ffd5b50565b5f81519050609a81607b565b92915050565b5f6020828403121560b25760b1606e565b5b5f60bd84828501608e565b91505092915050565b610695806100d35f395ff3fe608060405260043610610073575f3560e01c80639a8ac05f1161004d5780639a8ac05f146100e6578063b32fead514610111578063ce6d41de14610139578063e5679494146101635761007a565b806324600fc31461007e5780638d085382146100945780638da5cb5b146100bc5761007a565b3661007a57005b5f5ffd5b348015610089575f5ffd5b5061009261017f565b005b34801561009f575f5ffd5b506100ba60048036038101906100b591906103e3565b61027a565b005b3480156100c7575f5ffd5b506100d061027e565b6040516100dd919061046d565b60405180910390f35b3480156100f1575f5ffd5b506100fa6102a3565b60405161010892919061049e565b60405180910390f35b34801561011c575f5ffd5b50610137600480360381019061013291906104ef565b6102d1565b005b348015610144575f5ffd5b5061014d6102da565b60405161015a919061051a565b60405180910390f35b61017d600480360381019061017891906104ef565b6102e2565b005b60015f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff161461020e576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610205906105b3565b60405180910390fd5b5f47905060015f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166108fc8290811502906040515f60405180830381858888f19350505050158015610276573d5f5f3e3d5ffd5b5050565b5050565b60015f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b5f5f5f5460015f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff16915091509091565b805f8190555050565b5f5f54905090565b60015f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614610371576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161036890610641565b60405180910390fd5b805f8190555050565b5f5ffd5b5f5ffd5b5f5ffd5b5f5ffd5b5f5ffd5b5f5f83601f8401126103a3576103a2610382565b5b8235905067ffffffffffffffff8111156103c0576103bf610386565b5b6020830191508360018202830111156103dc576103db61038a565b5b9250929050565b5f5f602083850312156103f9576103f861037a565b5b5f83013567ffffffffffffffff8111156104165761041561037e565b5b6104228582860161038e565b92509250509250929050565b5f73ffffffffffffffffffffffffffffffffffffffff82169050919050565b5f6104578261042e565b9050919050565b6104678161044d565b82525050565b5f6020820190506104805f83018461045e565b92915050565b5f819050919050565b61049881610486565b82525050565b5f6040820190506104b15f83018561048f565b6104be602083018461045e565b9392505050565b6104ce81610486565b81146104d8575f5ffd5b50565b5f813590506104e9816104c5565b92915050565b5f602082840312156105045761050361037a565b5b5f610511848285016104db565b91505092915050565b5f60208201905061052d5f83018461048f565b92915050565b5f82825260208201905092915050565b7f4f6e6c7920746865206f776e65722063616e2077697468647261772066756e645f8201527f732e000000000000000000000000000000000000000000000000000000000000602082015250565b5f61059d602283610533565b91506105a882610543565b604082019050919050565b5f6020820190508181035f8301526105ca81610591565b9050919050565b7f4f6e6c7920746865206f776e65722063616e2075706461746520746865206d655f8201527f73736167652e0000000000000000000000000000000000000000000000000000602082015250565b5f61062b602683610533565b9150610636826105d1565b604082019050919050565b5f6020820190508181035f8301526106588161061f565b905091905056fea26469706673582212206cb7ba5c01fc3dc3a193327d7d4ece4a36b4c617ff1773b7e55bbbe38cebb02464736f6c634300081e0033 \ No newline at end of file +608060405260405161076838038061076883398181016040528101906023919060a0565b805f819055503360015f6101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055505060c6565b5f5ffd5b5f819050919050565b6082816072565b8114608b575f5ffd5b50565b5f81519050609a81607b565b92915050565b5f6020828403121560b25760b1606e565b5b5f60bd84828501608e565b91505092915050565b610695806100d35f395ff3fe608060405260043610610073575f3560e01c80639a8ac05f1161004d5780639a8ac05f146100e6578063b32fead514610111578063ce6d41de14610139578063e5679494146101635761007a565b806324600fc31461007e5780638d085382146100945780638da5cb5b146100bc5761007a565b3661007a57005b5f5ffd5b348015610089575f5ffd5b5061009261017f565b005b34801561009f575f5ffd5b506100ba60048036038101906100b591906103e3565b61027a565b005b3480156100c7575f5ffd5b506100d061027e565b6040516100dd919061046d565b60405180910390f35b3480156100f1575f5ffd5b506100fa6102a3565b60405161010892919061049e565b60405180910390f35b34801561011c575f5ffd5b50610137600480360381019061013291906104ef565b6102d1565b005b348015610144575f5ffd5b5061014d6102da565b60405161015a919061051a565b60405180910390f35b61017d600480360381019061017891906104ef565b6102e2565b005b60015f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff161461020e576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610205906105b3565b60405180910390fd5b5f47905060015f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166108fc8290811502906040515f60405180830381858888f19350505050158015610276573d5f5f3e3d5ffd5b5050565b5050565b60015f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b5f5f5f5460015f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff16915091509091565b805f8190555050565b5f5f54905090565b60015f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614610371576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161036890610641565b60405180910390fd5b805f8190555050565b5f5ffd5b5f5ffd5b5f5ffd5b5f5ffd5b5f5ffd5b5f5f83601f8401126103a3576103a2610382565b5b8235905067ffffffffffffffff8111156103c0576103bf610386565b5b6020830191508360018202830111156103dc576103db61038a565b5b9250929050565b5f5f602083850312156103f9576103f861037a565b5b5f83013567ffffffffffffffff8111156104165761041561037e565b5b6104228582860161038e565b92509250509250929050565b5f73ffffffffffffffffffffffffffffffffffffffff82169050919050565b5f6104578261042e565b9050919050565b6104678161044d565b82525050565b5f6020820190506104805f83018461045e565b92915050565b5f819050919050565b61049881610486565b82525050565b5f6040820190506104b15f83018561048f565b6104be602083018461045e565b9392505050565b6104ce81610486565b81146104d8575f5ffd5b50565b5f813590506104e9816104c5565b92915050565b5f602082840312156105045761050361037a565b5b5f610511848285016104db565b91505092915050565b5f60208201905061052d5f83018461048f565b92915050565b5f82825260208201905092915050565b7f4f6e6c7920746865206f776e65722063616e2077697468647261772066756e645f8201527f732e000000000000000000000000000000000000000000000000000000000000602082015250565b5f61059d602283610533565b91506105a882610543565b604082019050919050565b5f6020820190508181035f8301526105ca81610591565b9050919050565b7f4f6e6c7920746865206f776e65722063616e2075706461746520746865206d655f8201527f73736167652e0000000000000000000000000000000000000000000000000000602082015250565b5f61062b602683610533565b9150610636826105d1565b604082019050919050565b5f6020820190508181035f8301526106588161061f565b905091905056fea26469706673582212206cb7ba5c01fc3dc3a193327d7d4ece4a36b4c617ff1773b7e55bbbe38cebb02464736f6c634300081e0033 diff --git a/examples/contract/contracts/__init__.py b/examples/contract/contracts/__init__.py index 66d35c3ee..f178b31d5 100644 --- a/examples/contract/contracts/__init__.py +++ b/examples/contract/contracts/__init__.py @@ -5,6 +5,7 @@ and configuration constants for the contracts. """ + from .contract_utils import ( # Bytecode constants; Configuration constants CONSTRUCTOR_TEST_CONTRACT_BYTECODE, CONTRACT_DEPLOY_GAS, @@ -13,6 +14,7 @@ STATEFUL_CONTRACT_BYTECODE, ) + __all__ = [ # Bytecode constants "SIMPLE_CONTRACT_BYTECODE", diff --git a/examples/contract/contracts/contract_utils.py b/examples/contract/contracts/contract_utils.py index cc5483858..c46efb443 100644 --- a/examples/contract/contracts/contract_utils.py +++ b/examples/contract/contracts/contract_utils.py @@ -28,6 +28,7 @@ - Each contract's bytecode is loaded into a constant (e.g. SIMPLE_CONTRACT_BYTECODE) - The _load_contract_bytecode() utility handles loading and validation """ + from pathlib import Path @@ -48,14 +49,10 @@ def _load_contract_bytecode(contract_name: str, extension: str = "bin") -> str: """ try: # Look for contract in the main contracts directory (relative to project root) - contract_path = Path(__file__).parent.joinpath( - contract_name, f"{contract_name}.{extension}" - ) + contract_path = Path(__file__).parent.joinpath(contract_name, f"{contract_name}.{extension}") if not contract_path.exists(): - raise FileNotFoundError( - f"Contract bytecode file not found: {contract_path}" - ) + raise FileNotFoundError(f"Contract bytecode file not found: {contract_path}") bytecode = contract_path.read_bytes().decode("utf-8").strip() @@ -65,9 +62,7 @@ def _load_contract_bytecode(contract_name: str, extension: str = "bin") -> str: return bytecode except Exception as e: - raise RuntimeError( - f"Failed to load contract bytecode for {contract_name}: {e}" - ) from e + raise RuntimeError(f"Failed to load contract bytecode for {contract_name}: {e}") from e # Contract bytecode constants — loaded from hex-encoded .bin files @@ -78,9 +73,7 @@ def _load_contract_bytecode(contract_name: str, extension: str = "bin") -> str: SIMPLE_CONTRACT_BYTECODE = _load_contract_bytecode("SimpleContract") # The deployed (runtime) bytecode for SimpleContract, loaded from its .bin-runtime file. -SIMPLE_CONTRACT_RUNTIME_BYTECODE = _load_contract_bytecode( - "SimpleContract", "bin-runtime" -) +SIMPLE_CONTRACT_RUNTIME_BYTECODE = _load_contract_bytecode("SimpleContract", "bin-runtime") # StatefulContract: # Initializes a bytes32 message via constructor, stores it on-chain, diff --git a/examples/contract/ethereum_transaction.py b/examples/contract/ethereum_transaction.py index 413e0d477..90c7eec68 100644 --- a/examples/contract/ethereum_transaction.py +++ b/examples/contract/ethereum_transaction.py @@ -18,6 +18,7 @@ # Run from the project root directory python -m examples.contract.ethereum_transaction """ + import os import sys @@ -42,6 +43,7 @@ # The contract bytecode is pre-compiled from Solidity source code from .contracts import STATEFUL_CONTRACT_BYTECODE + load_dotenv() network_name = os.getenv("NETWORK", "testnet").lower() @@ -75,13 +77,9 @@ def create_alias_account(client): alias_private_key = PrivateKey.generate_ecdsa() # Create an alias account ID for this key - alias_account_id = AccountId( - shard=0, realm=0, num=0, alias_key=alias_private_key.public_key() - ) + alias_account_id = AccountId(shard=0, realm=0, num=0, alias_key=alias_private_key.public_key()) - print( - f"\nCreating alias account with public key: {alias_private_key.public_key().to_string()}" - ) + print(f"\nCreating alias account with public key: {alias_private_key.public_key().to_string()}") # Transfer HBAR to create a shallow account for the ECDSA key receipt = ( @@ -111,9 +109,7 @@ def create_contract_file(client): # Check if file creation was successful if file_receipt.status != ResponseCode.SUCCESS: - print( - f"File creation failed with status: {ResponseCode(file_receipt.status).name}" - ) + print(f"File creation failed with status: {ResponseCode(file_receipt.status).name}") sys.exit(1) return file_receipt.file_id @@ -135,9 +131,7 @@ def create_contract(client, file_id): # Check if contract creation was successful if receipt.status != ResponseCode.SUCCESS: - print( - f"Contract creation failed with status: {ResponseCode(receipt.status).name}" - ) + print(f"Contract creation failed with status: {ResponseCode(receipt.status).name}") sys.exit(1) print(f"Contract created with ID: {receipt.contract_id}") @@ -148,12 +142,7 @@ def create_contract(client, file_id): def get_contract_message(client, contract_id): """Get the message from the contract.""" # Query the contract function to verify that the message was set - query = ( - ContractCallQuery() - .set_contract_id(contract_id) - .set_gas(2000000) - .set_function("getMessage") - ) + query = ContractCallQuery().set_contract_id(contract_id).set_gas(2000000).set_function("getMessage") cost = query.get_cost(client) query.set_max_query_payment(cost) @@ -179,14 +168,10 @@ def create_ethereum_transaction_data(contract_id, new_message, alias_private_key bytes: The signed Ethereum transaction data """ # Prepare function call data using ContractFunctionParameters - call_data_bytes = ( - ContractFunctionParameters("setMessage").add_bytes32(new_message).to_bytes() - ) + call_data_bytes = ContractFunctionParameters("setMessage").add_bytes32(new_message).to_bytes() # Ethereum transaction fields - hardcoded for example simplicity - chain_id_bytes = bytes.fromhex( - os.getenv("CHAIN_ID", "0128") - ) # Chain ID 296 (Testnet) + chain_id_bytes = bytes.fromhex(os.getenv("CHAIN_ID", "0128")) # Chain ID 296 (Testnet) max_priority_gas_bytes = bytes.fromhex("00") # Zero for simplicity nonce_bytes = bytes.fromhex("00") # Zero nonce max_gas_bytes = bytes.fromhex("d1385c7bf0") # Max fee per gas @@ -258,17 +243,13 @@ def execute_ethereum_transaction(): print(f"\nPreparing Ethereum transaction to set message: '{new_message_string}'") # Create Ethereum transaction data - transaction_data = create_ethereum_transaction_data( - contract_id, new_message, alias_private_key - ) + transaction_data = create_ethereum_transaction_data(contract_id, new_message, alias_private_key) # Execute the Ethereum transaction receipt = EthereumTransaction().set_ethereum_data(transaction_data).execute(client) if receipt.status != ResponseCode.SUCCESS: - print( - f"Ethereum transaction failed with status: {ResponseCode(receipt.status).name}" - ) + print(f"Ethereum transaction failed with status: {ResponseCode(receipt.status).name}") sys.exit(1) print("Successfully executed Ethereum transaction") @@ -281,9 +262,7 @@ def execute_ethereum_transaction(): if updated_message == new_message_string: print("\nSuccess! Message was updated via Ethereum transaction.") else: - print( - f"\nMessage update failed. Expected: '{new_message_string}', got: '{updated_message}'" - ) + print(f"\nMessage update failed. Expected: '{new_message_string}', got: '{updated_message}'") if __name__ == "__main__": diff --git a/examples/crypto/private_key_der.py b/examples/crypto/private_key_der.py index 7695f2f3d..5a2723d17 100644 --- a/examples/crypto/private_key_der.py +++ b/examples/crypto/private_key_der.py @@ -10,6 +10,7 @@ python examples/crypto/private_key_der.py """ + from hiero_sdk_python.crypto.private_key import PrivateKey diff --git a/examples/crypto/private_key_ecdsa.py b/examples/crypto/private_key_ecdsa.py index 7af2fefa8..c3e1db6ec 100644 --- a/examples/crypto/private_key_ecdsa.py +++ b/examples/crypto/private_key_ecdsa.py @@ -9,6 +9,7 @@ python examples/crypto/private_key_ecdsa.py """ + from cryptography.exceptions import InvalidSignature from hiero_sdk_python.crypto.private_key import PrivateKey diff --git a/examples/crypto/private_key_ed25519.py b/examples/crypto/private_key_ed25519.py index a9afe7cf6..e907d50e6 100644 --- a/examples/crypto/private_key_ed25519.py +++ b/examples/crypto/private_key_ed25519.py @@ -9,6 +9,7 @@ python examples/crypto/private_key_ed25519.py """ + from cryptography.exceptions import InvalidSignature from hiero_sdk_python.crypto.private_key import PrivateKey @@ -110,10 +111,7 @@ def example_load_ed25519_der() -> None: print("=== Ed25519: Load from DER ===") # This DER encodes a small example Ed25519 key whose raw seed might be all 0x01. # 46 bytes in total; for demonstration only. - der_hex = ( - "302e020100300506032b657004220420" - "0101010101010101010101010101010101010101010101010101010101010101" - ) + der_hex = "302e020100300506032b6570042204200101010101010101010101010101010101010101010101010101010101010101" # Create private and public key privkey = PrivateKey.from_string_der(der_hex) diff --git a/examples/crypto/public_key_der.py b/examples/crypto/public_key_der.py index 45e8715be..df0f1db73 100644 --- a/examples/crypto/public_key_der.py +++ b/examples/crypto/public_key_der.py @@ -7,6 +7,7 @@ python examples/crypto/public_key_der.py """ + from cryptography.exceptions import InvalidSignature from cryptography.hazmat.primitives import hashes, serialization from cryptography.hazmat.primitives.asymmetric import ec, ed25519, utils @@ -71,9 +72,7 @@ def example_verify_der_signature() -> None: # Sign and verify, specifying hash algorithm for ECDSA data = b"Hello DER" - signature = private_key.sign( - keccak256(data), ec.ECDSA(utils.Prehashed(hashes.SHA256())) - ) + signature = private_key.sign(keccak256(data), ec.ECDSA(utils.Prehashed(hashes.SHA256()))) try: pubk_obj.verify(signature, data) print("DER: ECDSA signature verified!") diff --git a/examples/crypto/public_key_ecdsa.py b/examples/crypto/public_key_ecdsa.py index 67290cce8..b4fcf2cc9 100644 --- a/examples/crypto/public_key_ecdsa.py +++ b/examples/crypto/public_key_ecdsa.py @@ -7,6 +7,7 @@ python examples/crypto/public_key_ecdsa.py """ + from cryptography.exceptions import InvalidSignature from cryptography.hazmat.primitives import hashes from cryptography.hazmat.primitives.asymmetric import ec, utils @@ -17,9 +18,7 @@ def example_load_compressed_ecdsa() -> None: """Demonstrate creating a PublicKey object from a compressed 33-byte ECDSA hex.""" # A mock 33-byte compressed hex: - compressed_pubkey = bytes.fromhex( - "0281c2e57fecef82ff4f546dece3684acb6e2fe12a97af066348de81ccaf05d0a4" - ) + compressed_pubkey = bytes.fromhex("0281c2e57fecef82ff4f546dece3684acb6e2fe12a97af066348de81ccaf05d0a4") # 1) Construct via the specialized from_bytes_ecdsa() pubk_obj = PublicKey.from_bytes_ecdsa(compressed_pubkey) # or from_bytes @@ -45,9 +44,7 @@ def example_load_uncompressed_ecdsa_from_hex() -> None: # 2) Convert to compressed raw bytes or hex: compressed_bytes = pubk_obj.to_bytes_ecdsa() # or to_bytes_raw - print( - f"Compressed ECDSA bytes (len={len(compressed_bytes)}): {compressed_bytes.hex()}" - ) + print(f"Compressed ECDSA bytes (len={len(compressed_bytes)}): {compressed_bytes.hex()}") def example_verify_ecdsa_signature() -> None: @@ -61,9 +58,7 @@ def example_verify_ecdsa_signature() -> None: # 2) Sign some data data = b"Hello ECDSA" - signature = private_key.sign( - keccak256(data), ec.ECDSA(utils.Prehashed(hashes.SHA256())) - ) + signature = private_key.sign(keccak256(data), ec.ECDSA(utils.Prehashed(hashes.SHA256()))) # 3) Verify with pubk_obj try: diff --git a/examples/crypto/public_key_ed25519.py b/examples/crypto/public_key_ed25519.py index 941e184ad..3dee8452e 100644 --- a/examples/crypto/public_key_ed25519.py +++ b/examples/crypto/public_key_ed25519.py @@ -6,6 +6,7 @@ uv run examples/crypto/public_key_ed25519.py python examples/crypto/public_key_ed25519.py """ + from cryptography.exceptions import InvalidSignature from cryptography.hazmat.primitives.asymmetric import ed25519 @@ -20,9 +21,7 @@ def example_load_ed25519_from_raw() -> None: """ # Ed25519 public key bytes are always 32 bytes. # Most random 32 bytes will be a valid key, so here is a random example: - raw_pub = bytes.fromhex( - "8baa5f735dbf40f275283bed504cb752b1ce58a7118476d28f528ecd265c5f58" - ) + raw_pub = bytes.fromhex("8baa5f735dbf40f275283bed504cb752b1ce58a7118476d28f528ecd265c5f58") # 1) Construct via from_bytes_ed25519 pubk_obj = PublicKey.from_bytes_ed25519(raw_pub) # or from_bytes diff --git a/examples/errors/max_attempts_error.py b/examples/errors/max_attempts_error.py index faeeb176c..9e4dd683f 100644 --- a/examples/errors/max_attempts_error.py +++ b/examples/errors/max_attempts_error.py @@ -8,6 +8,7 @@ uv run examples/errors/max_attempts_error.py python examples/errors/max_attempts_error.py """ + from hiero_sdk_python import ( Client, TransactionGetReceiptQuery, @@ -33,7 +34,6 @@ def main() -> None: # By forcing max_attempts=1, we prevent retries. # Note: Triggering a pure MaxAttemptsError usually requires a timeout or busy node. # This example demonstrates the structure of handling the error. - # Using a generated TransactionId tx_id = TransactionId.generate(operator_id) @@ -47,10 +47,10 @@ def main() -> None: print(f"Node ID: {e.node_id}") print(f"Message: {e.message}") print("This error means the SDK gave up after reaching the maximum number of retry attempts.") - + except Exception as e: - # Note: In a real network test with a made-up ID, we might get ReceiptStatusError - # or PrecheckError (RECEIPT_NOT_FOUND). MaxAttemptsError typically happens + # Note: In a real network test with a made-up ID, we might get ReceiptStatusError + # or PrecheckError (RECEIPT_NOT_FOUND). MaxAttemptsError typically happens # on network timeouts or BUSY responses. print(f"\nCaught unexpected error (expected for this specific simulation): {type(e).__name__}") print(f"Details: {e}") @@ -58,4 +58,4 @@ def main() -> None: if __name__ == "__main__": - main() \ No newline at end of file + main() diff --git a/examples/errors/precheck_error.py b/examples/errors/precheck_error.py index 7f9e7da65..18aa68aca 100644 --- a/examples/errors/precheck_error.py +++ b/examples/errors/precheck_error.py @@ -8,6 +8,7 @@ uv run examples/errors/precheck_error.py python examples/errors/precheck_error.py """ + from hiero_sdk_python import AccountId, Client, TransferTransaction from hiero_sdk_python.exceptions import PrecheckError @@ -27,7 +28,7 @@ def main() -> None: TransferTransaction() .add_hbar_transfer(operator_id, -1) .add_hbar_transfer(AccountId(0, 0, 3), 1) - .set_transaction_valid_duration(1) + .set_transaction_valid_duration(1) .freeze_with(client) .sign(operator_key) ) @@ -46,5 +47,6 @@ def main() -> None: except Exception as e: print(f"\nAn unexpected error occurred: {type(e).__name__}: {e}") + if __name__ == "__main__": - main() \ No newline at end of file + main() diff --git a/examples/errors/receipt_status_error.py b/examples/errors/receipt_status_error.py index c7bbff3fd..d834bd054 100644 --- a/examples/errors/receipt_status_error.py +++ b/examples/errors/receipt_status_error.py @@ -8,13 +8,14 @@ uv run examples/errors/receipt_status_error.py python examples/errors/receipt_status_error.py """ + from hiero_sdk_python import Client, ResponseCode, TokenAssociateTransaction, TokenId from hiero_sdk_python.exceptions import ReceiptStatusError def main() -> None: client = Client.from_env() - + operator_id = client.operator_account_id operator_key = client.operator_private_key @@ -45,9 +46,7 @@ def main() -> None: print("\nCaught ReceiptStatusError!") print(f"Status: {e.status.name} ({e.status})") print(f"Transaction ID: {e.transaction_id}") - print( - "This error means the transaction reached consensus but failed logic execution." - ) + print("This error means the transaction reached consensus but failed logic execution.") # Catch all for unexpected errors except Exception as e: @@ -55,4 +54,4 @@ def main() -> None: if __name__ == "__main__": - main() \ No newline at end of file + main() diff --git a/examples/file/file_append_transaction.py b/examples/file/file_append_transaction.py index 220eda5d5..92a3aa675 100644 --- a/examples/file/file_append_transaction.py +++ b/examples/file/file_append_transaction.py @@ -9,14 +9,12 @@ uv run examples/file_append_transaction.py python examples/file_append_transaction.py """ + import os import sys from dotenv import load_dotenv -# Load environment variables from .env file -load_dotenv() - from hiero_sdk_python import ( AccountId, Client, @@ -27,6 +25,10 @@ ResponseCode, ) + +# Load environment variables from .env file +load_dotenv() + network_name = os.getenv("NETWORK", "testnet").lower() @@ -36,7 +38,6 @@ def setup_client(): print(f"Connecting to Hedera {network_name} network!") client = Client(network) - operator_id = AccountId.from_string(os.getenv("OPERATOR_ID", "")) operator_key = PrivateKey.from_string(os.getenv("OPERATOR_KEY", "")) client.set_operator(operator_id, operator_key) @@ -59,9 +60,7 @@ def create_file(client, file_private_key): ) if create_receipt.status != ResponseCode.SUCCESS: - print( - f"File creation failed with status: {ResponseCode(create_receipt.status).name}" - ) + print(f"File creation failed with status: {ResponseCode(create_receipt.status).name}") sys.exit(1) file_id = create_receipt.file_id @@ -83,9 +82,7 @@ def append_file_single(client, file_id, file_private_key): ) if append_receipt.status != ResponseCode.SUCCESS: - print( - f"File append failed with status: {ResponseCode(append_receipt.status).name}" - ) + print(f"File append failed with status: {ResponseCode(append_receipt.status).name}") sys.exit(1) print("Content appended successfully!") @@ -108,15 +105,11 @@ def append_file_large(client, file_id, file_private_key): ) if large_append_receipt.status != ResponseCode.SUCCESS: - print( - f"Large file append failed with status: {ResponseCode(large_append_receipt.status).name}" - ) + print(f"Large file append failed with status: {ResponseCode(large_append_receipt.status).name}") sys.exit(1) print("Large content appended successfully!") - print( - f"Total chunks used: {FileAppendTransaction().set_contents(large_content).get_required_chunks()}" - ) + print(f"Total chunks used: {FileAppendTransaction().set_contents(large_content).get_required_chunks()}") def main(): diff --git a/examples/file/file_contents_query.py b/examples/file/file_contents_query.py index 6d70b90a3..f31840baf 100644 --- a/examples/file/file_contents_query.py +++ b/examples/file/file_contents_query.py @@ -6,6 +6,7 @@ uv run examples/file/file_contents_query.py python examples/file/file_contents_query.py """ + import os import sys @@ -16,6 +17,7 @@ from hiero_sdk_python.file.file_create_transaction import FileCreateTransaction from hiero_sdk_python.response_code import ResponseCode + load_dotenv() network_name = os.getenv("NETWORK", "testnet").lower() @@ -41,9 +43,7 @@ def create_file(client: Client): receipt = ( FileCreateTransaction() - .set_keys( - [file_private_key.public_key(), client.operator_private_key.public_key()] - ) + .set_keys([file_private_key.public_key(), client.operator_private_key.public_key()]) .set_contents(b"Test contents to be queried!") .set_file_memo("Test file for query") .freeze_with(client) diff --git a/examples/file/file_create_transaction.py b/examples/file/file_create_transaction.py index fc596576e..434f0fe6c 100644 --- a/examples/file/file_create_transaction.py +++ b/examples/file/file_create_transaction.py @@ -9,19 +9,15 @@ """ import sys -import os - -from dotenv import load_dotenv from hiero_sdk_python import ( - AccountId, Client, - Network, PrivateKey, ) from hiero_sdk_python.file.file_create_transaction import FileCreateTransaction from hiero_sdk_python.response_code import ResponseCode + def setup_client(): """Initialize and set up the client with operator account.""" client = Client.from_env() @@ -46,9 +42,7 @@ def file_create(): # Create file receipt = ( FileCreateTransaction() - .set_keys( - file_private_key.public_key() - ) # Set the keys required to sign any modifications to this file + .set_keys(file_private_key.public_key()) # Set the keys required to sign any modifications to this file .set_contents(b"Hello, this is the content of my file on Hedera!") .set_file_memo("My first file on Hedera") .freeze_with(client) diff --git a/examples/file/file_delete_transaction.py b/examples/file/file_delete_transaction.py index deec59360..730219a0c 100644 --- a/examples/file/file_delete_transaction.py +++ b/examples/file/file_delete_transaction.py @@ -8,6 +8,7 @@ python examples/file_delete_transaction.py """ + import os import sys @@ -20,6 +21,7 @@ from hiero_sdk_python.file.file_info_query import FileInfoQuery from hiero_sdk_python.response_code import ResponseCode + load_dotenv() network_name = os.getenv("NETWORK", "testnet").lower() diff --git a/examples/file/file_info_query.py b/examples/file/file_info_query.py index 6441d6c91..18931429d 100644 --- a/examples/file/file_info_query.py +++ b/examples/file/file_info_query.py @@ -6,6 +6,7 @@ uv run examples/file/file_info_query.py python examples/file/file_info_query.py """ + import os import sys @@ -16,6 +17,7 @@ from hiero_sdk_python.file.file_info_query import FileInfoQuery from hiero_sdk_python.response_code import ResponseCode + load_dotenv() network_name = os.getenv("NETWORK", "testnet").lower() @@ -41,9 +43,7 @@ def create_file(client: Client): receipt = ( FileCreateTransaction() - .set_keys( - [file_private_key.public_key(), client.operator_private_key.public_key()] - ) + .set_keys([file_private_key.public_key(), client.operator_private_key.public_key()]) .set_contents(b"Hello, this is a test file for querying!") .set_file_memo("Test file for query") .freeze_with(client) diff --git a/examples/file/file_update_transaction.py b/examples/file/file_update_transaction.py index 4cea7492f..9e1160adc 100644 --- a/examples/file/file_update_transaction.py +++ b/examples/file/file_update_transaction.py @@ -6,6 +6,7 @@ uv run examples/file_update_transaction.py python examples/file_update_transaction.py """ + import os import sys @@ -17,6 +18,7 @@ from hiero_sdk_python.file.file_update_transaction import FileUpdateTransaction from hiero_sdk_python.response_code import ResponseCode + load_dotenv() network_name = os.getenv("NETWORK", "testnet").lower() diff --git a/examples/hbar.py b/examples/hbar.py index b9cf3c78d..0b0570a70 100644 --- a/examples/hbar.py +++ b/examples/hbar.py @@ -5,6 +5,7 @@ uv run examples/hbar.py python examples/hbar.py """ + from hiero_sdk_python.hbar import Hbar from hiero_sdk_python.hbar_unit import HbarUnit diff --git a/examples/logger/logging_example.py b/examples/logger/logging_example.py index 9808190ad..9cd4a20e5 100644 --- a/examples/logger/logging_example.py +++ b/examples/logger/logging_example.py @@ -12,6 +12,7 @@ uv run examples/logger/logging_example.py python examples/logger/logging_example.py """ + import os from dotenv import load_dotenv @@ -26,6 +27,7 @@ PrivateKey, ) + load_dotenv() diff --git a/examples/mypy.ini b/examples/mypy.ini index 760d6d2f3..e4d619756 100644 --- a/examples/mypy.ini +++ b/examples/mypy.ini @@ -10,4 +10,4 @@ follow_imports = skip # Prevent attribute errors on untyped modules allow_any_expr = True -allow_any_generics = True \ No newline at end of file +allow_any_generics = True diff --git a/examples/nodes/node_create_transaction.py b/examples/nodes/node_create_transaction.py index 1f0c9a940..b9f2afca9 100644 --- a/examples/nodes/node_create_transaction.py +++ b/examples/nodes/node_create_transaction.py @@ -17,6 +17,7 @@ 1. GitHub repository with full setup instructions: https://github.com/hiero-ledger/solo 2. Official documentation with step-by-step guide: https://solo.hiero.org/v0.43.0/docs/step-by-step-guide/ """ + import sys from dotenv import load_dotenv @@ -26,6 +27,7 @@ from hiero_sdk_python.nodes.node_create_transaction import NodeCreateTransaction from hiero_sdk_python.response_code import ResponseCode + # Gossip certificate is a DER-encoded x509 certificate used for secure communication between nodes. # This certificate authenticates the node's identity during gossip protocol communication. # Information about x509 certificates: https://www.ssl.com/faqs/what-is-an-x-509-certificate/ @@ -50,8 +52,7 @@ def setup_client(): # The private key is intentionally public for local development. # Note: This setup only works on solo network and will not work on testnet/mainnet. original_operator_key = PrivateKey.from_string_der( - "302e020100300506032b65700422042091132178e7" - "2057a1d7528025956fe39b0b847f200ab59b2fdd367017f3087137" + "302e020100300506032b65700422042091132178e72057a1d7528025956fe39b0b847f200ab59b2fdd367017f3087137" ) client.set_operator(AccountId(0, 0, 2), original_operator_key) diff --git a/examples/nodes/node_delete_transaction.py b/examples/nodes/node_delete_transaction.py index 471e8cc71..14b65cf7d 100644 --- a/examples/nodes/node_delete_transaction.py +++ b/examples/nodes/node_delete_transaction.py @@ -17,6 +17,7 @@ 1. GitHub repository with full setup instructions: https://github.com/hiero-ledger/solo 2. Official documentation with step-by-step guide: https://solo.hiero.org/v0.43.0/docs/step-by-step-guide/ """ + import sys from dotenv import load_dotenv @@ -27,6 +28,7 @@ from hiero_sdk_python.nodes.node_delete_transaction import NodeDeleteTransaction from hiero_sdk_python.response_code import ResponseCode + # Gossip certificate is a DER-encoded x509 certificate used for secure communication between nodes. # This certificate authenticates the node's identity during gossip protocol communication. # Information about x509 certificates: https://www.ssl.com/faqs/what-is-an-x-509-certificate/ @@ -51,8 +53,7 @@ def setup_client(): # The private key is intentionally public for local development. # Note: This setup only works on solo network and will not work on testnet/mainnet. original_operator_key = PrivateKey.from_string_der( - "302e020100300506032b65700422042091132178e7" - "2057a1d7528025956fe39b0b847f200ab59b2fdd367017f3087137" + "302e020100300506032b65700422042091132178e72057a1d7528025956fe39b0b847f200ab59b2fdd367017f3087137" ) client.set_operator(AccountId(0, 0, 2), original_operator_key) diff --git a/examples/nodes/node_update_transaction.py b/examples/nodes/node_update_transaction.py index bd1ec83cf..89fbd73fd 100644 --- a/examples/nodes/node_update_transaction.py +++ b/examples/nodes/node_update_transaction.py @@ -17,6 +17,7 @@ 1. GitHub repository with full setup instructions: https://github.com/hiero-ledger/solo 2. Official documentation with step-by-step guide: https://solo.hiero.org/v0.43.0/docs/step-by-step-guide/ """ + import sys from dotenv import load_dotenv @@ -27,6 +28,7 @@ from hiero_sdk_python.nodes.node_update_transaction import NodeUpdateTransaction from hiero_sdk_python.response_code import ResponseCode + # Gossip certificate is a DER-encoded x509 certificate used for secure communication between nodes. # This certificate authenticates the node's identity during gossip protocol communication. # Information about x509 certificates: https://www.ssl.com/faqs/what-is-an-x-509-certificate/ @@ -51,8 +53,7 @@ def setup_client(): # The private key is intentionally public for local development. # Note: This setup only works on solo network and will not work on testnet/mainnet. original_operator_key = PrivateKey.from_string_der( - "302e020100300506032b65700422042091132178e7" - "2057a1d7528025956fe39b0b847f200ab59b2fdd367017f3087137" + "302e020100300506032b65700422042091132178e72057a1d7528025956fe39b0b847f200ab59b2fdd367017f3087137" ) client.set_operator(AccountId(0, 0, 2), original_operator_key) @@ -116,21 +117,11 @@ def update_node(client, node_id, admin_key): updated_description = "Updated example node" # Updated endpoints for node services - updated_gossip_endpoint1 = Endpoint( - domain_name="updated-gossip1.example.com", port=50311 - ) - updated_gossip_endpoint2 = Endpoint( - domain_name="updated-gossip2.example.com", port=50312 - ) - updated_service_endpoint1 = Endpoint( - domain_name="updated-service1.example.com", port=50311 - ) - updated_service_endpoint2 = Endpoint( - domain_name="updated-service2.example.com", port=50312 - ) - updated_grpc_proxy_endpoint = Endpoint( - domain_name="updated-grpc.example.com", port=50313 - ) + updated_gossip_endpoint1 = Endpoint(domain_name="updated-gossip1.example.com", port=50311) + updated_gossip_endpoint2 = Endpoint(domain_name="updated-gossip2.example.com", port=50312) + updated_service_endpoint1 = Endpoint(domain_name="updated-service1.example.com", port=50311) + updated_service_endpoint2 = Endpoint(domain_name="updated-service2.example.com", port=50312) + updated_grpc_proxy_endpoint = Endpoint(domain_name="updated-grpc.example.com", port=50313) # DER encoded x509 certificate updated_gossip_ca_cert = bytes.fromhex(GOSSIP_CERTIFICATE) diff --git a/examples/prng_transaction.py b/examples/prng_transaction.py index 30fdb6011..b152e5ea7 100644 --- a/examples/prng_transaction.py +++ b/examples/prng_transaction.py @@ -8,6 +8,7 @@ You can set the range for the PRNG transaction to generate a pseudo-random number within a range. If no range is set, the PRNG transaction will generate a 48 byte unsigned pseudo-random number. """ + import os import sys @@ -18,6 +19,7 @@ from hiero_sdk_python.query.transaction_record_query import TransactionRecordQuery from hiero_sdk_python.response_code import ResponseCode + load_dotenv() network_name = os.getenv("NETWORK", "testnet").lower() @@ -42,9 +44,7 @@ def prng_with_range(client, range_value): receipt = PrngTransaction().set_range(range_value).execute(client) if receipt.status != ResponseCode.SUCCESS: - print( - f"PRNG transaction failed with status: {ResponseCode(receipt.status).name}" - ) + print(f"PRNG transaction failed with status: {ResponseCode(receipt.status).name}") sys.exit(1) # Get the transaction record to see the generated number @@ -59,9 +59,7 @@ def prng_without_range(client): receipt = PrngTransaction().execute(client) if receipt.status != ResponseCode.SUCCESS: - print( - f"PRNG transaction failed with status: {ResponseCode(receipt.status).name}" - ) + print(f"PRNG transaction failed with status: {ResponseCode(receipt.status).name}") sys.exit(1) # Get the transaction record to see the generated bytes @@ -86,9 +84,7 @@ def prng_transaction(): receipt = PrngTransaction().set_range(range_value).execute(client) if receipt.status != ResponseCode.SUCCESS: - print( - f"PRNG transaction failed with status: {ResponseCode(receipt.status).name}" - ) + print(f"PRNG transaction failed with status: {ResponseCode(receipt.status).name}") sys.exit(1) # Get the transaction record to see the generated number @@ -103,9 +99,7 @@ def prng_transaction(): # receipt = PrngTransaction().set_range(None).execute(client) if receipt.status != ResponseCode.SUCCESS: - print( - f"PRNG transaction failed with status: {ResponseCode(receipt.status).name}" - ) + print(f"PRNG transaction failed with status: {ResponseCode(receipt.status).name}") sys.exit(1) # Get the transaction record to see the generated number in bytes diff --git a/examples/protobuf_round_trip.py b/examples/protobuf_round_trip.py index 01c5c2737..c0e37467d 100644 --- a/examples/protobuf_round_trip.py +++ b/examples/protobuf_round_trip.py @@ -12,6 +12,7 @@ This example accompanies the ProtoBuff Training documentation. """ + from hiero_sdk_python.hapi.services import ( basic_types_pb2, crypto_get_info_pb2, diff --git a/examples/query/account_balance_query.py b/examples/query/account_balance_query.py index 001f448d6..cadc3b8ac 100644 --- a/examples/query/account_balance_query.py +++ b/examples/query/account_balance_query.py @@ -14,6 +14,7 @@ python examples/query/account_balance_query.py """ + import sys import time @@ -80,9 +81,7 @@ def create_account(client, operator_key, initial_balance=Hbar(10)): print("✓ Account created successfully") print(f" Account ID: {new_account_id}") - print( - f" Initial balance: {initial_balance.to_hbars()} hbars ({initial_balance.to_tinybars()} tinybars)\n" - ) + print(f" Initial balance: {initial_balance.to_hbars()} hbars ({initial_balance.to_tinybars()} tinybars)\n") return new_account_id, new_account_private_key @@ -151,9 +150,7 @@ def main(): client, operator_id, operator_key = setup_client() # Create a new account with initial balance - new_account_id, new_account_private_key = create_account( - client, operator_key, initial_balance=Hbar(10) - ) + new_account_id, new_account_private_key = create_account(client, operator_key, initial_balance=Hbar(10)) # Query and display the initial balance print("=" * 60) @@ -168,9 +165,7 @@ def main(): print("EXECUTING TRANSFER") print("=" * 60) transfer_amount = Hbar(5) - transfer_status = transfer_hbars( - client, operator_id, operator_key, new_account_id, transfer_amount - ) + transfer_status = transfer_hbars(client, operator_id, operator_key, new_account_id, transfer_amount) print(f"Transfer transaction status: {transfer_status}") print("=" * 60 + "\n") diff --git a/examples/query/account_balance_query_2.py b/examples/query/account_balance_query_2.py index c5c30e588..071d5c819 100644 --- a/examples/query/account_balance_query_2.py +++ b/examples/query/account_balance_query_2.py @@ -7,6 +7,7 @@ HBAR and token balances, including minting NFTs to the account. """ + import os import sys @@ -25,6 +26,7 @@ from hiero_sdk_python.query.account_balance_query import CryptoGetAccountBalanceQuery from hiero_sdk_python.tokens.token_id import TokenId + key_type = os.getenv("KEY_TYPE", "ecdsa") @@ -52,9 +54,7 @@ def create_account(client, name, initial_balance=Hbar(10)): ) if receipt.status != ResponseCode.SUCCESS: - print( - f"Account creation failed with status: {ResponseCode(receipt.status).name}" - ) + print(f"Account creation failed with status: {ResponseCode(receipt.status).name}") sys.exit(1) account_id = receipt.account_id @@ -83,13 +83,11 @@ def create_and_mint_token(treasury_account_id, treasury_account_key, client): .execute(client) ).token_id - TokenMintTransaction().set_token_id(token_id).set_metadata( - metadata_list - ).freeze_with(client).sign(supply_key).execute(client) + TokenMintTransaction().set_token_id(token_id).set_metadata(metadata_list).freeze_with(client).sign( + supply_key + ).execute(client) - total_supply = ( - TokenInfoQuery().set_token_id(token_id).execute(client).total_supply - ) + total_supply = TokenInfoQuery().set_token_id(token_id).execute(client).total_supply print(f"✅ Created NFT {token_id} — total supply: {total_supply}") return token_id except (ValueError, TypeError, RuntimeError, ConnectionError) as error: @@ -102,9 +100,7 @@ def get_account_balance(client: Client, account_id: AccountId): print(f"Retrieving account balance for account id: {account_id} ...") try: # Use CryptoGetAccountBalanceQuery to get the account balance - account_balance = ( - CryptoGetAccountBalanceQuery().set_account_id(account_id).execute(client) - ) + account_balance = CryptoGetAccountBalanceQuery().set_account_id(account_id).execute(client) print("✅ Account balance retrieved successfully!") # Print account balance with account_id context print(f"💰 HBAR Balance for {account_id}: {account_balance.hbars} hbars") @@ -116,14 +112,9 @@ def get_account_balance(client: Client, account_id: AccountId): # OPTIONAL comparison function -def compare_token_balances( - client, treasury_id: AccountId, receiver_id: AccountId, token_id: TokenId -): +def compare_token_balances(client, treasury_id: AccountId, receiver_id: AccountId, token_id: TokenId): """Compare token balances between two accounts.""" - print( - f"\n🔎 Comparing token balances for Token ID {token_id} " - f"between accounts {treasury_id} and {receiver_id}..." - ) + print(f"\n🔎 Comparing token balances for Token ID {token_id} between accounts {treasury_id} and {receiver_id}...") # retrieve balances for both accounts treasury_balance = get_account_balance(client, treasury_id) receiver_balance = get_account_balance(client, receiver_id) @@ -154,9 +145,7 @@ def main(): # Retrieve and display account balance for the test account get_account_balance(client, test_account_id) # OPTIONAL comparison of token balances between test account and operator account - compare_token_balances( - client, test_account_id, client.operator_account_id, token_id - ) + compare_token_balances(client, test_account_id, client.operator_account_id, token_id) if __name__ == "__main__": diff --git a/examples/query/account_info_query.py b/examples/query/account_info_query.py index 9afb6c66e..9ec697010 100644 --- a/examples/query/account_info_query.py +++ b/examples/query/account_info_query.py @@ -5,6 +5,7 @@ uv run examples/query/account_info_query.py python examples/query/account_info_query.py """ + import sys from hiero_sdk_python import ( @@ -56,9 +57,7 @@ def create_test_account(client, operator_key): ) if receipt.status != ResponseCode.SUCCESS: - print( - f"Account creation failed with status: {ResponseCode(receipt.status).name}" - ) + print(f"Account creation failed with status: {ResponseCode(receipt.status).name}") sys.exit(1) new_account_id = receipt.account_id @@ -159,9 +158,7 @@ def associate_token_with_account(client, token_id, account_id, account_key): ) if receipt.status != ResponseCode.SUCCESS: - print( - f"Token association failed with status: {ResponseCode(receipt.status).name}" - ) + print(f"Token association failed with status: {ResponseCode(receipt.status).name}") sys.exit(1) print(f"Token {token_id} associated with account {account_id}") @@ -169,12 +166,7 @@ def associate_token_with_account(client, token_id, account_id, account_key): def grant_kyc_for_token(client, account_id, token_id): """Grant KYC for the token to the account.""" - receipt = ( - TokenGrantKycTransaction() - .set_account_id(account_id) - .set_token_id(token_id) - .execute(client) - ) + receipt = TokenGrantKycTransaction().set_account_id(account_id).set_token_id(token_id).execute(client) if receipt.status != ResponseCode.SUCCESS: print(f"KYC grant failed with status: {ResponseCode(receipt.status).name}") @@ -203,9 +195,7 @@ def display_account_info(info): def display_token_relationships(info): """Display token relationships information.""" - print( - f"\nToken Relationships ({len(info.token_relationships)} total) for account {info.account_id}:" - ) + print(f"\nToken Relationships ({len(info.token_relationships)} total) for account {info.account_id}:") if info.token_relationships: for i, relationship in enumerate(info.token_relationships, 1): print(f" Token {i}:") diff --git a/examples/query/payment_query.py b/examples/query/payment_query.py index 40725e95a..0eebee3d6 100644 --- a/examples/query/payment_query.py +++ b/examples/query/payment_query.py @@ -5,6 +5,7 @@ uv run examples/query/payment_query.py python examples/query/payment_query.py """ + import sys from hiero_sdk_python import ( @@ -51,9 +52,7 @@ def create_fungible_token(client, operator_id, operator_key): ) if receipt.status != ResponseCode.SUCCESS: - print( - f"Fungible token creation failed with status: {ResponseCode(receipt.status).name}" - ) + print(f"Fungible token creation failed with status: {ResponseCode(receipt.status).name}") sys.exit(1) token_id = receipt.token_id @@ -90,11 +89,7 @@ def demonstrate_zero_cost_balance_query(client, account_id): # Case 2: Payment set - should return the set payment amount print("\nWhen custom payment is set:") custom_payment = Hbar(2) - query_with_payment = ( - CryptoGetAccountBalanceQuery() - .set_account_id(account_id) - .set_query_payment(custom_payment) - ) + query_with_payment = CryptoGetAccountBalanceQuery().set_account_id(account_id).set_query_payment(custom_payment) cost_with_payment = query_with_payment.get_cost(client) print(f"Cost: {cost_with_payment} Hbar") @@ -136,9 +131,7 @@ def demonstrate_payment_required_queries(client, token_id): # Case 2: Payment set - should return the set payment amount print("\nWhen custom payment is set:") custom_payment = Hbar(2) - query_with_payment = ( - TokenInfoQuery().set_token_id(token_id).set_query_payment(custom_payment) - ) + query_with_payment = TokenInfoQuery().set_token_id(token_id).set_query_payment(custom_payment) cost_with_payment = query_with_payment.get_cost(client) print(f"Cost: {cost_with_payment} Hbar") diff --git a/examples/query/token_info_query_fungible.py b/examples/query/token_info_query_fungible.py index d63beb992..2a52d356a 100644 --- a/examples/query/token_info_query_fungible.py +++ b/examples/query/token_info_query_fungible.py @@ -5,6 +5,7 @@ uv run examples/query/token_info_query_fungible.py python examples/query/token_info_query_fungible.py """ + import sys from hiero_sdk_python import ( @@ -51,9 +52,7 @@ def create_fungible_token(client, operator_id, operator_key): # Check if token creation was successful if receipt.status != ResponseCode.SUCCESS: - print( - f"Fungible token creation failed with status: {ResponseCode(receipt.status).name}" - ) + print(f"Fungible token creation failed with status: {ResponseCode(receipt.status).name}") sys.exit(1) # Get token ID from receipt diff --git a/examples/query/token_info_query_nft.py b/examples/query/token_info_query_nft.py index 6c924f71e..6ec697471 100644 --- a/examples/query/token_info_query_nft.py +++ b/examples/query/token_info_query_nft.py @@ -5,6 +5,7 @@ uv run examples/query/token_info_query_nft.py python examples/token_info_query_nft.py """ + import sys from hiero_sdk_python import ( diff --git a/examples/query/token_nft_info_query.py b/examples/query/token_nft_info_query.py index 8496eab66..470df53db 100644 --- a/examples/query/token_nft_info_query.py +++ b/examples/query/token_nft_info_query.py @@ -5,6 +5,7 @@ uv run examples/query/token_nft_info_query.py python examples/query/token_nft_info_query.py """ + import sys from hiero_sdk_python import ( @@ -65,12 +66,7 @@ def create_nft(client, operator_id, operator_key): def mint_nft(client, nft_token_id): """Mint a non-fungible token.""" - receipt = ( - TokenMintTransaction() - .set_token_id(nft_token_id) - .set_metadata(b"My NFT Metadata 1") - .execute(client) - ) + receipt = TokenMintTransaction().set_token_id(nft_token_id).set_metadata(b"My NFT Metadata 1").execute(client) if receipt.status != ResponseCode.SUCCESS: print(f"NFT minting failed with status: {ResponseCode(receipt.status).name}") diff --git a/examples/query/topic_info_query.py b/examples/query/topic_info_query.py index 7d6f3ec34..2245f0e49 100644 --- a/examples/query/topic_info_query.py +++ b/examples/query/topic_info_query.py @@ -5,6 +5,7 @@ uv run examples/query/topic_info_query.py python examples/query/topic_info_query.py """ + import sys from hiero_sdk_python import ( @@ -19,9 +20,7 @@ def create_topic(client, operator_key): print("\nSTEP 1: Creating a Topic...") try: topic_tx = ( - TopicCreateTransaction( - memo="Python SDK created topic", admin_key=operator_key.public_key() - ) + TopicCreateTransaction(memo="Python SDK created topic", admin_key=operator_key.public_key()) .freeze_with(client) .sign(operator_key) ) diff --git a/examples/query/topic_message_query.py b/examples/query/topic_message_query.py index 0198a403c..27735a16b 100644 --- a/examples/query/topic_message_query.py +++ b/examples/query/topic_message_query.py @@ -5,6 +5,7 @@ uv run examples/query/topic_message_query.py python examples/query/topic_message_query.py """ + import sys import time from datetime import datetime, timezone @@ -35,9 +36,7 @@ def create_topic(client, operator_key): print("\nSTEP 1: Creating a Topic...") try: topic_tx = ( - TopicCreateTransaction( - memo="Python SDK created topic", admin_key=operator_key.public_key() - ) + TopicCreateTransaction(memo="Python SDK created topic", admin_key=operator_key.public_key()) .freeze_with(client) .sign(operator_key) ) @@ -75,9 +74,7 @@ def on_error_handler(e): chunking_enabled=True, ) - handle = query.subscribe( - client, on_message=on_message_handler, on_error=on_error_handler - ) + handle = query.subscribe(client, on_message=on_message_handler, on_error=on_error_handler) print("Subscription started. Will auto-cancel after 10 seconds or on Ctrl+C...") try: diff --git a/examples/query/transaction_get_receipt_query.py b/examples/query/transaction_get_receipt_query.py index 535a5853c..4e33c3b4b 100644 --- a/examples/query/transaction_get_receipt_query.py +++ b/examples/query/transaction_get_receipt_query.py @@ -5,6 +5,7 @@ uv run examples/query/transaction_get_receipt_query.py python examples/query/transaction_get_receipt_query.py """ + import sys from hiero_sdk_python import ( @@ -57,9 +58,7 @@ def _print_receipt_children(queried_receipt): children = queried_receipt.children if not children: - print( - "No child receipts returned (this can be normal depending on transaction type)." - ) + print("No child receipts returned (this can be normal depending on transaction type).") return print(f"Child receipts count: {len(children)}") @@ -74,9 +73,7 @@ def _print_receipt_duplicates(queried_receipt): duplicates = queried_receipt.duplicates if not duplicates: - print( - "No duplicate receipts returned (this can be normal depending on transaction type)." - ) + print("No duplicate receipts returned (this can be normal depending on transaction type).") return print(f"Duplicate receipts count: {len(duplicates)}") @@ -112,9 +109,7 @@ def query_receipt(): receipt = transaction.execute(client) transaction_id = transaction.transaction_id print(f"Transaction ID: {transaction_id}") - print( - f"✅ Success! Transfer transaction status: {ResponseCode(receipt.status).name}" - ) + print(f"✅ Success! Transfer transaction status: {ResponseCode(receipt.status).name}") # Query Transaction Receipt print("\nSTEP 3: Querying transaction receipt (include child receipts)...") @@ -125,9 +120,7 @@ def query_receipt(): .set_include_duplicates(True) ) queried_receipt = receipt_query.execute(client) - print( - f"✅ Success! Queried transaction status: {ResponseCode(queried_receipt.status).name}" - ) + print(f"✅ Success! Queried transaction status: {ResponseCode(queried_receipt.status).name}") _print_receipt_children(queried_receipt) _print_receipt_duplicates(queried_receipt) diff --git a/examples/query/transaction_get_receipt_query_validate_status.py b/examples/query/transaction_get_receipt_query_validate_status.py index 5291bb43a..37e5513bf 100644 --- a/examples/query/transaction_get_receipt_query_validate_status.py +++ b/examples/query/transaction_get_receipt_query_validate_status.py @@ -4,17 +4,19 @@ uv run examples/query/transaction_get_receipt_query_validate_status.py python examples/query/transaction_get_receipt_query_validate_status.py """ + import sys from hiero_sdk_python import ( AccountDeleteTransaction, AccountId, Client, + ReceiptStatusError, ResponseCode, TransactionGetReceiptQuery, - ReceiptStatusError, ) + def setup_client(): """Initialize the Hiero client from environment variables.""" try: @@ -25,11 +27,12 @@ def setup_client(): print(f"Error setting up client: {e}") sys.exit(1) + def submit_failing_transaction(client): """Submit a transaction designed to fail to demonstrate receipt handling.""" tx = ( AccountDeleteTransaction() - .set_account_id(AccountId(0, 0, 9999999)) + .set_account_id(AccountId(0, 0, 9999999)) .set_transfer_account_id(client.operator_account_id) .freeze_with(client) ) @@ -38,33 +41,26 @@ def submit_failing_transaction(client): response = tx.execute(client, wait_for_receipt=False) print(f"Transaction submitted: {response.transaction_id}") - + return response.transaction_id + def run_manual_validation(client, transaction_id): """Demonstrate manual status checking (the default behavior).""" print("\n--- Option A: Manual Validation (Default) ---") - query = ( - TransactionGetReceiptQuery() - .set_transaction_id(transaction_id) - .set_validate_status(False) - ) + query = TransactionGetReceiptQuery().set_transaction_id(transaction_id).set_validate_status(False) print("Executing query with validate_status=False") receipt = query.execute(client) status_name = ResponseCode(receipt.status).name print(f"Query returned receipt with status: {status_name}") - + def run_automatic_validation(client, transaction_id): """Demonstrate automatic validation using ReceiptStatusError.""" print("\n--- Option B: Automatic Validation ---") - query = ( - TransactionGetReceiptQuery() - .set_transaction_id(transaction_id) - .set_validate_status(True) - ) - + query = TransactionGetReceiptQuery().set_transaction_id(transaction_id).set_validate_status(True) + try: print("Executing query with validate_status=True") query.execute(client) @@ -72,9 +68,10 @@ def run_automatic_validation(client, transaction_id): status_name = ResponseCode(e.status).name print(f"Query raises expected exception: {status_name}") + def main(): client = setup_client() - + # Get a transaction ID to query tx_id = submit_failing_transaction(client) @@ -84,5 +81,6 @@ def main(): # Receipt query with validate_status run_automatic_validation(client, tx_id) + if __name__ == "__main__": - main() \ No newline at end of file + main() diff --git a/examples/query/transaction_record_query.py b/examples/query/transaction_record_query.py index 7cd77594a..92e06e6e3 100644 --- a/examples/query/transaction_record_query.py +++ b/examples/query/transaction_record_query.py @@ -5,6 +5,7 @@ uv run examples/query/transaction_record_query.py python examples/query/transaction_record_query.py """ + import sys from hiero_sdk_python import ( @@ -53,9 +54,7 @@ def create_account_transaction(client): # Check if account creation was successful if receipt.status != ResponseCode.SUCCESS: - print( - f"Account creation failed with status: {ResponseCode(receipt.status).name}" - ) + print(f"Account creation failed with status: {ResponseCode(receipt.status).name}") sys.exit(1) # Get the new account ID and transaction ID from receipt @@ -67,7 +66,7 @@ def create_account_transaction(client): return new_account_id, new_account_key, transaction_id -def create_fungible_token(client: "Client", account_id, account_private_key): +def create_fungible_token(client: Client, account_id, account_private_key): """Create a fungible token.""" receipt = ( TokenCreateTransaction() @@ -85,9 +84,7 @@ def create_fungible_token(client: "Client", account_id, account_private_key): ) if receipt.status != ResponseCode.SUCCESS: - print( - f"Fungible token creation failed with status: {ResponseCode(receipt.status).name}" - ) + print(f"Fungible token creation failed with status: {ResponseCode(receipt.status).name}") sys.exit(1) token_id = receipt.token_id @@ -109,17 +106,13 @@ def associate_token(client, token_id, receiver_id, receiver_private_key): ) if receipt.status != ResponseCode.SUCCESS: - print( - f"Token association failed with status: {ResponseCode(receipt.status).name}" - ) + print(f"Token association failed with status: {ResponseCode(receipt.status).name}") sys.exit(1) print(f"Token successfully associated with account: {receiver_id}") -def transfer_tokens( - client, treasury_id, treasury_private_key, receiver_id, token_id, amount=10 -): +def transfer_tokens(client, treasury_id, treasury_private_key, receiver_id, token_id, amount=10): """Transfer tokens to the receiver account so we can later reject them.""" # Transfer tokens to the receiver account receipt = ( @@ -175,15 +168,9 @@ def query_record(): token_id = create_fungible_token(client, operator_id, operator_key) associate_token(client, token_id, new_account_id, new_account_key) - transfer_receipt = transfer_tokens( - client, operator_id, operator_key, new_account_id, token_id - ) + transfer_receipt = transfer_tokens(client, operator_id, operator_key, new_account_id, token_id) - transfer_record = ( - TransactionRecordQuery() - .set_transaction_id(transfer_receipt.transaction_id) - .execute(client) - ) + transfer_record = TransactionRecordQuery().set_transaction_id(transfer_receipt.transaction_id).execute(client) print("Transaction record for token transfer:") print_transaction_record(transfer_record) diff --git a/examples/query/transaction_record_query_with_children.py b/examples/query/transaction_record_query_with_children.py index a14df1329..85d12f5e1 100644 --- a/examples/query/transaction_record_query_with_children.py +++ b/examples/query/transaction_record_query_with_children.py @@ -23,16 +23,14 @@ def submit_alias_auto_create_transfer(client): """Transfer HBAR to a fresh EVM alias to trigger auto-account creation.""" try: alias_key = PrivateKey.generate_ecdsa() - alias_account_id = AccountId.from_evm_address( - alias_key.public_key().to_evm_address(), 0, 0 - ) + alias_account_id = AccountId.from_evm_address(alias_key.public_key().to_evm_address(), 0, 0) transaction = ( TransferTransaction() .add_hbar_transfer(alias_account_id, Hbar(1).to_tinybars()) .add_hbar_transfer(client.operator_account_id, Hbar(-1).to_tinybars()) ) - receipt = transaction.execute(client) + transaction.execute(client) return transaction.transaction_id except Exception as e: @@ -60,28 +58,19 @@ def print_child_records(record): if not record.children: sys.exit(1) - print_transaction_record(record.children[0], f"Child record") + print_transaction_record(record.children[0], "Child record") def main(): try: client = Client.from_env() - print( - "\nSTEP 1: Create a parent transaction with child records" - ) + print("\nSTEP 1: Create a parent transaction with child records") transaction_id = submit_alias_auto_create_transfer(client) print(f"Parent transaction ID: {transaction_id}") - print( - "\nSTEP 2: Querying parent transaction record with include_children=True..." - ) - record = ( - TransactionRecordQuery() - .set_transaction_id(transaction_id) - .set_include_children(True) - .execute(client) - ) + print("\nSTEP 2: Querying parent transaction record with include_children=True...") + record = TransactionRecordQuery().set_transaction_id(transaction_id).set_include_children(True).execute(client) print_transaction_record(record, "Parent Transaction Record") print_child_records(record) diff --git a/examples/query/transaction_record_query_with_duplicates.py b/examples/query/transaction_record_query_with_duplicates.py index 2e433d2e0..c5b25b214 100644 --- a/examples/query/transaction_record_query_with_duplicates.py +++ b/examples/query/transaction_record_query_with_duplicates.py @@ -12,6 +12,7 @@ Do NOT expect to see non-empty duplicates in this example — that's intentional. """ + import sys import time @@ -55,7 +56,7 @@ def submit_duplicates(tx, client, count=3): def print_record_info(record): """Print summary of the main record and any duplicates (usually none).""" main_status = ResponseCode(record.receipt.status).name - memo = record.transaction_memo or '(none)' + memo = record.transaction_memo or "(none)" print("\nMain record:") print(f" Status : {main_status}") @@ -66,7 +67,7 @@ def print_record_info(record): print("\nDuplicates (rare in normal operation):") for i, dup in enumerate(record.duplicates, 1): dup_status = ResponseCode(dup.receipt.status).name - dup_memo = dup.transaction_memo or '(none)' + dup_memo = dup.transaction_memo or "(none)" print(f" #{i:2} | Status: {dup_status:18} | Memo: {dup_memo}") else: print(" (No duplicate records — this is normal when duplicates are rejected at precheck)") @@ -104,12 +105,7 @@ def _run(): time.sleep(5) # Usually enough on testnet; increase if needed print("\nQuerying record with include_duplicates=True...") - record = ( - TransactionRecordQuery() - .set_transaction_id(tx_id) - .set_include_duplicates(True) - .execute(client) - ) + record = TransactionRecordQuery().set_transaction_id(tx_id).set_include_duplicates(True).execute(client) print_record_info(record) diff --git a/examples/response_code.py b/examples/response_code.py index e0b40097a..cd1e5ea88 100644 --- a/examples/response_code.py +++ b/examples/response_code.py @@ -7,7 +7,7 @@ def __init__(self, status_code: int): self.status = status_code -def response_code(receipt: "TransactionReceipt"): +def response_code(receipt: TransactionReceipt): status_code = ResponseCode(receipt.status) print(f"The response code is {status_code}") @@ -19,7 +19,7 @@ def response_code(receipt: "TransactionReceipt"): print("❌ Transaction failed!") -def response_name(receipt: "TransactionReceipt"): +def response_name(receipt: TransactionReceipt): status_code = ResponseCode(receipt.status) status_name = status_code.name print(f"The response name is {status_name}") diff --git a/examples/schedule/schedule_create_transaction.py b/examples/schedule/schedule_create_transaction.py index f7aed4e68..e9c87f997 100644 --- a/examples/schedule/schedule_create_transaction.py +++ b/examples/schedule/schedule_create_transaction.py @@ -6,6 +6,7 @@ uv run examples/schedule/schedule_create_transaction.py python examples/schedule/schedule_create_transaction.py """ + import datetime import os import sys @@ -21,6 +22,7 @@ from hiero_sdk_python.timestamp import Timestamp from hiero_sdk_python.transaction.transfer_transaction import TransferTransaction + load_dotenv() network_name = os.getenv("NETWORK", "testnet").lower() @@ -56,9 +58,7 @@ def create_account(client): ) if receipt.status != ResponseCode.SUCCESS: - print( - f"Account creation failed with status: {ResponseCode(receipt.status).name}" - ) + print(f"Account creation failed with status: {ResponseCode(receipt.status).name}") sys.exit(1) account_id = receipt.account_id @@ -110,25 +110,17 @@ def schedule_account_create(): expiration_time = datetime.datetime.now() + datetime.timedelta(seconds=5) receipt = ( - schedule_tx.set_payer_account_id( - client.operator_account_id - ) # payer of the transaction fee - .set_admin_key( - client.operator_private_key.public_key() - ) # delete/modify the transaction + schedule_tx.set_payer_account_id(client.operator_account_id) # payer of the transaction fee + .set_admin_key(client.operator_private_key.public_key()) # delete/modify the transaction .set_expiration_time(Timestamp.from_date(expiration_time)) .set_wait_for_expiry(True) # wait to expire to execute .freeze_with(client) - .sign( - account_private_key - ) # sign with the account private key as it transfers money + .sign(account_private_key) # sign with the account private key as it transfers money .execute(client) ) if receipt.status != ResponseCode.SUCCESS: - print( - f"Schedule creation failed with status: {ResponseCode(receipt.status).name}" - ) + print(f"Schedule creation failed with status: {ResponseCode(receipt.status).name}") sys.exit(1) print("Transaction scheduled successfully!") @@ -145,12 +137,8 @@ def schedule_account_create(): account_balance(client, account_id) print("\nQuerying transaction record to check if it was executed...") - record_query = ( - TransactionRecordQuery().set_transaction_id(scheduled_tx_id).execute(client) - ) - print( - f"Transaction Record receipt status: {ResponseCode(record_query.receipt.status).name}" - ) + record_query = TransactionRecordQuery().set_transaction_id(scheduled_tx_id).execute(client) + print(f"Transaction Record receipt status: {ResponseCode(record_query.receipt.status).name}") if __name__ == "__main__": diff --git a/examples/schedule/schedule_delete_transaction.py b/examples/schedule/schedule_delete_transaction.py index b33b94ff4..96d49ffa8 100644 --- a/examples/schedule/schedule_delete_transaction.py +++ b/examples/schedule/schedule_delete_transaction.py @@ -6,6 +6,7 @@ uv run examples/schedule/schedule_delete_transaction.py python examples/schedule/schedule_delete_transaction.py """ + import datetime import os import sys @@ -26,6 +27,7 @@ from hiero_sdk_python.timestamp import Timestamp from hiero_sdk_python.transaction.transfer_transaction import TransferTransaction + load_dotenv() network_name = os.getenv("NETWORK", "testnet").lower() @@ -61,9 +63,7 @@ def create_account(client): ) if receipt.status != ResponseCode.SUCCESS: - print( - f"Account creation failed with status: {ResponseCode(receipt.status).name}" - ) + print(f"Account creation failed with status: {ResponseCode(receipt.status).name}") sys.exit(1) account_id = receipt.account_id @@ -91,25 +91,17 @@ def create_schedule(client, account_id, account_private_key): expiration_time = datetime.datetime.now() + datetime.timedelta(seconds=90) receipt = ( - schedule_tx.set_payer_account_id( - client.operator_account_id - ) # payer of the transaction fee - .set_admin_key( - client.operator_private_key.public_key() - ) # delete/modify the transaction + schedule_tx.set_payer_account_id(client.operator_account_id) # payer of the transaction fee + .set_admin_key(client.operator_private_key.public_key()) # delete/modify the transaction .set_expiration_time(Timestamp.from_date(expiration_time)) .set_wait_for_expiry(True) # wait to expire to execute .freeze_with(client) - .sign( - account_private_key - ) # sign with the account private key as it transfers money + .sign(account_private_key) # sign with the account private key as it transfers money .execute(client) ) if receipt.status != ResponseCode.SUCCESS: - print( - f"Schedule creation failed with status: {ResponseCode(receipt.status).name}" - ) + print(f"Schedule creation failed with status: {ResponseCode(receipt.status).name}") sys.exit(1) print(f"Schedule created with ID: {receipt.schedule_id}") @@ -139,9 +131,7 @@ def schedule_delete(): receipt = ScheduleDeleteTransaction().set_schedule_id(schedule_id).execute(client) if receipt.status != ResponseCode.SUCCESS: - print( - f"Schedule deletion failed with status: {ResponseCode(receipt.status).name}" - ) + print(f"Schedule deletion failed with status: {ResponseCode(receipt.status).name}") sys.exit(1) info = ScheduleInfoQuery().set_schedule_id(schedule_id).execute(client) diff --git a/examples/schedule/schedule_info_query.py b/examples/schedule/schedule_info_query.py index 3463330be..1d1633063 100644 --- a/examples/schedule/schedule_info_query.py +++ b/examples/schedule/schedule_info_query.py @@ -6,6 +6,7 @@ uv run examples/schedule/schedule_info_query.py python examples/schedule/schedule_info_query.py """ + import datetime import os import sys @@ -25,6 +26,7 @@ TransferTransaction, ) + load_dotenv() network_name = os.getenv("NETWORK", "testnet").lower() @@ -60,9 +62,7 @@ def create_account(client): ) if receipt.status != ResponseCode.SUCCESS: - print( - f"Account creation failed with status: {ResponseCode(receipt.status).name}" - ) + print(f"Account creation failed with status: {ResponseCode(receipt.status).name}") sys.exit(1) account_id = receipt.account_id @@ -90,24 +90,16 @@ def create_schedule(client, account_id, account_private_key): expiration_time = datetime.datetime.now() + datetime.timedelta(seconds=90) receipt = ( - schedule_tx.set_payer_account_id( - client.operator_account_id - ) # payer of the transaction fee - .set_admin_key( - client.operator_private_key.public_key() - ) # delete/modify the transaction + schedule_tx.set_payer_account_id(client.operator_account_id) # payer of the transaction fee + .set_admin_key(client.operator_private_key.public_key()) # delete/modify the transaction .set_expiration_time(Timestamp.from_date(expiration_time)) .freeze_with(client) - .sign( - account_private_key - ) # sign with the account private key as it transfers money + .sign(account_private_key) # sign with the account private key as it transfers money .execute(client) ) if receipt.status != ResponseCode.SUCCESS: - print( - f"Schedule creation failed with status: {ResponseCode(receipt.status).name}" - ) + print(f"Schedule creation failed with status: {ResponseCode(receipt.status).name}") sys.exit(1) print(f"Schedule created with ID: {receipt.schedule_id}") @@ -147,7 +139,7 @@ def query_schedule_info(): print(f"Admin Key: {info.admin_key}") print(f"Signers: {len(info.signers)} signer(s)") for i, signer in enumerate(info.signers): - print(f" Signer {i+1}: {signer}") + print(f" Signer {i + 1}: {signer}") print(f"Ledger ID: {info.ledger_id}") print(f"Wait For Expiry: {info.wait_for_expiry}") diff --git a/examples/schedule/schedule_sign_transaction.py b/examples/schedule/schedule_sign_transaction.py index 2bd8334aa..33e794957 100644 --- a/examples/schedule/schedule_sign_transaction.py +++ b/examples/schedule/schedule_sign_transaction.py @@ -17,6 +17,7 @@ uv run examples/schedule/schedule_sign_transaction.py python examples/schedule/schedule_sign_transaction.py """ + import datetime import os import sys @@ -31,6 +32,7 @@ from hiero_sdk_python.timestamp import Timestamp from hiero_sdk_python.transaction.transfer_transaction import TransferTransaction + load_dotenv() network_name = os.getenv("NETWORK", "testnet").lower() @@ -66,9 +68,7 @@ def create_account(client): ) if receipt.status != ResponseCode.SUCCESS: - print( - f"Account creation failed with status: {ResponseCode(receipt.status).name}" - ) + print(f"Account creation failed with status: {ResponseCode(receipt.status).name}") sys.exit(1) account_id = receipt.account_id @@ -96,12 +96,8 @@ def create_schedule(client, account_id): expiration_time = datetime.datetime.now() + datetime.timedelta(seconds=90) receipt = ( - schedule_tx.set_payer_account_id( - client.operator_account_id - ) # payer of the transaction fee - .set_admin_key( - client.operator_private_key.public_key() - ) # delete/modify the transaction + schedule_tx.set_payer_account_id(client.operator_account_id) # payer of the transaction fee + .set_admin_key(client.operator_private_key.public_key()) # delete/modify the transaction .set_expiration_time(Timestamp.from_date(expiration_time)) .set_wait_for_expiry(False) # don't wait for expiry, execute when signed .set_schedule_memo("Test schedule for signing") @@ -109,9 +105,7 @@ def create_schedule(client, account_id): ) if receipt.status != ResponseCode.SUCCESS: - print( - f"Schedule creation failed with status: {ResponseCode(receipt.status).name}" - ) + print(f"Schedule creation failed with status: {ResponseCode(receipt.status).name}") sys.exit(1) print(f"Schedule created with ID: {receipt.schedule_id}") @@ -149,7 +143,7 @@ def query_schedule_info(client, schedule_id, required_inner_keys=None): print(f"Wait For Expiry: {info.wait_for_expiry}") print(f"Collected Signers: {len(info.signers)}") for i, signer in enumerate(info.signers): - print(f" Signer {i+1}: {signer}") + print(f" Signer {i + 1}: {signer}") # Show which signatures are still missing for the inner txn, if provided if required_inner_keys: diff --git a/examples/tls_query_balance.py b/examples/tls_query_balance.py index 7a1741999..10e72452c 100644 --- a/examples/tls_query_balance.py +++ b/examples/tls_query_balance.py @@ -14,6 +14,7 @@ Run with: uv run examples/tls_query_balance.py """ + import os from dotenv import load_dotenv diff --git a/examples/tokens/account_allowance_approve_transaction.py b/examples/tokens/account_allowance_approve_transaction.py index d1c31bcb5..c5b821e90 100644 --- a/examples/tokens/account_allowance_approve_transaction.py +++ b/examples/tokens/account_allowance_approve_transaction.py @@ -7,6 +7,7 @@ python examples/tokens/account_allowance_approve_transaction.py """ + import sys from hiero_sdk_python import Client, Hbar, PrivateKey, TransactionId @@ -45,9 +46,7 @@ def create_account(client): ) if account_receipt.status != ResponseCode.SUCCESS: - print( - f"Account creation failed with status: {ResponseCode(account_receipt.status).name}" - ) + print(f"Account creation failed with status: {ResponseCode(account_receipt.status).name}") sys.exit(1) account_account_id = account_receipt.account_id @@ -92,17 +91,13 @@ def associate_token_with_account(client, account_id, account_private_key, token_ ) if receipt.status != ResponseCode.SUCCESS: - print( - f"Token association failed with status: {ResponseCode(receipt.status).name}" - ) + print(f"Token association failed with status: {ResponseCode(receipt.status).name}") sys.exit(1) print(f"Token {token_id} associated with account {account_id}") -def approve_token_allowance( - client, token_id, owner_account_id, spender_account_id, amount -): +def approve_token_allowance(client, token_id, owner_account_id, spender_account_id, amount): """Approve token allowance for spender.""" receipt = ( AccountAllowanceApproveTransaction() @@ -111,9 +106,7 @@ def approve_token_allowance( ) if receipt.status != ResponseCode.SUCCESS: - print( - f"Token allowance approval failed with status: {ResponseCode(receipt.status).name}" - ) + print(f"Token allowance approval failed with status: {ResponseCode(receipt.status).name}") sys.exit(1) print(f"Token allowance of {amount} approved for spender {spender_account_id}") @@ -129,9 +122,7 @@ def delete_token_allowance(client, token_id, owner_account_id, spender_account_i ) if receipt.status != ResponseCode.SUCCESS: - print( - f"Token allowance deletion failed with status: {ResponseCode(receipt.status).name}" - ) + print(f"Token allowance deletion failed with status: {ResponseCode(receipt.status).name}") sys.exit(1) print(f"Token allowance deleted for spender {spender_account_id}") @@ -149,9 +140,7 @@ def transfer_token_without_allowance( """Transfer tokens without allowance.""" print("Trying to transfer tokens without allowance...") owner_account_id = client.operator_account_id - client.set_operator( - spender_account_id, spender_private_key - ) # Set operator to spender + client.set_operator(spender_account_id, spender_private_key) # Set operator to spender receipt = ( TransferTransaction() @@ -166,9 +155,7 @@ def transfer_token_without_allowance( f"status but got: {ResponseCode(receipt.status).name}" ) - print( - f"Token transfer successfully failed with {ResponseCode(receipt.status).name} status" - ) + print(f"Token transfer successfully failed with {ResponseCode(receipt.status).name} status") def token_allowance(): @@ -201,17 +188,13 @@ def token_allowance(): # Approve token allowance for spender allowance_amount = 20 - approve_token_allowance( - client, token_id, client.operator_account_id, spender_id, allowance_amount - ) + approve_token_allowance(client, token_id, client.operator_account_id, spender_id, allowance_amount) # Transfer tokens using the allowance receipt = ( TransferTransaction() .set_transaction_id(TransactionId.generate(spender_id)) - .add_approved_token_transfer( - token_id, client.operator_account_id, -allowance_amount - ) + .add_approved_token_transfer(token_id, client.operator_account_id, -allowance_amount) .add_approved_token_transfer(token_id, receiver_id, allowance_amount) .freeze_with(client) .sign(spender_private_key) @@ -229,9 +212,7 @@ def token_allowance(): delete_token_allowance(client, token_id, client.operator_account_id, spender_id) # Try to transfer tokens without allowance - transfer_token_without_allowance( - client, spender_id, spender_private_key, allowance_amount, receiver_id, token_id - ) + transfer_token_without_allowance(client, spender_id, spender_private_key, allowance_amount, receiver_id, token_id) if __name__ == "__main__": diff --git a/examples/tokens/custom_fee_fixed.py b/examples/tokens/custom_fee_fixed.py index f833b0a53..d7a96ca44 100644 --- a/examples/tokens/custom_fee_fixed.py +++ b/examples/tokens/custom_fee_fixed.py @@ -6,6 +6,7 @@ uv run examples/tokens/custom_fixed_fee.py python examples/tokens/custom_fixed_fee.py """ + import sys from hiero_sdk_python.client.client import Client @@ -24,6 +25,7 @@ def setup_client(): print(f"Client set up with operator id {client.operator_account_id}") return client + def custom_fixed_fee_example(): """Demonstrates how to create a token with a Custom Fixed Fee.""" client = setup_client() @@ -59,9 +61,7 @@ def custom_fixed_fee_example(): # Check if the status is explicitly SUCCESS if receipt.status != ResponseCode.SUCCESS: - print( - f"Transaction failed with status: {ResponseCode(receipt.status).name}" - ) + print(f"Transaction failed with status: {ResponseCode(receipt.status).name}") token_id = receipt.token_id print(f"Token created successfully with ID: {token_id}") diff --git a/examples/tokens/custom_fee_fractional.py b/examples/tokens/custom_fee_fractional.py index b46b0e231..0191f80f1 100644 --- a/examples/tokens/custom_fee_fractional.py +++ b/examples/tokens/custom_fee_fractional.py @@ -6,6 +6,7 @@ python examples/tokens/custom_fractional_fee.py uv run examples/tokens/custom_fractional_fee.py """ + import sys from hiero_sdk_python.account.account_id import AccountId diff --git a/examples/tokens/custom_royalty_fee.py b/examples/tokens/custom_royalty_fee.py index 33cec8243..40addb460 100644 --- a/examples/tokens/custom_royalty_fee.py +++ b/examples/tokens/custom_royalty_fee.py @@ -6,6 +6,7 @@ uv run examples/tokens/custom_royalty_fee.py python examples/tokens/custom_royalty_fee.py """ + from hiero_sdk_python.client.client import Client from hiero_sdk_python.hbar import Hbar from hiero_sdk_python.query.token_info_query import TokenInfoQuery @@ -67,9 +68,7 @@ def create_token_with_fee(client, royalty_fee): receipt = transaction.execute(client) if receipt.status != ResponseCode.SUCCESS: print(f"Token creation failed: {ResponseCode(receipt.status).name}") - raise RuntimeError( - f"Token creation failed: {ResponseCode(receipt.status).name}" - ) + raise RuntimeError(f"Token creation failed: {ResponseCode(receipt.status).name}") token_id = receipt.token_id print(f"Token created successfully with ID: {token_id}") diff --git a/examples/tokens/token_airdrop_claim_auto.py b/examples/tokens/token_airdrop_claim_auto.py index 5b60f486f..91bfbe425 100644 --- a/examples/tokens/token_airdrop_claim_auto.py +++ b/examples/tokens/token_airdrop_claim_auto.py @@ -55,9 +55,8 @@ def setup_client(): print(f"Client set up with operator id {client.operator_account_id}") return client -def create_receiver( - client: Client, signature_required: bool = False, max_auto_assoc: int = 10 -): + +def create_receiver(client: Client, signature_required: bool = False, max_auto_assoc: int = 10): """Create and return a configured Hedera client.""" receiver_key = PrivateKey.generate() receiver_public_key = receiver_key.public_key() @@ -197,19 +196,13 @@ def log_balances( print(f"\n===== {prefix} Balances =====") try: - operator_balance = ( - CryptoGetAccountBalanceQuery().set_account_id(operator_id).execute(client) - ) - receiver_balance = ( - CryptoGetAccountBalanceQuery().set_account_id(receiver_id).execute(client) - ) + operator_balance = CryptoGetAccountBalanceQuery().set_account_id(operator_id).execute(client) + receiver_balance = CryptoGetAccountBalanceQuery().set_account_id(receiver_id).execute(client) except Exception as e: # pylint: disable=broad-exception-caught print(f"❌ Failed to fetch balances: {e}") return - def log_fungible( - _account_id: AccountId, balances: dict, token_ids: Iterable[TokenId] - ): + def log_fungible(_account_id: AccountId, balances: dict, token_ids: Iterable[TokenId]): print(" Fungible tokens:") for token_id in token_ids: print(f" {token_id}: {balances.get(token_id, 0)}") @@ -258,10 +251,7 @@ def perform_airdrop( tx.add_token_transfer(fungible_id, operator_id, -ft_amount) tx.add_token_transfer(fungible_id, receiver_id, ft_amount) - message = ( - f"📤 Transferring {ft_amount} of fungible token {fungible_id} " - f"from {operator_id} → {receiver_id}" - ) + message = f"📤 Transferring {ft_amount} of fungible token {fungible_id} from {operator_id} → {receiver_id}" print(message) for nft_id in nft_ids: @@ -273,13 +263,9 @@ def perform_airdrop( if receipt.status != ResponseCode.SUCCESS: status_message = ResponseCode(receipt.status).name - raise RuntimeError( - f"Airdrop transaction failed with status: {status_message}" - ) + raise RuntimeError(f"Airdrop transaction failed with status: {status_message}") - print( - f"✅ Airdrop executed successfully! Transaction ID: {receipt.transaction_id}" - ) + print(f"✅ Airdrop executed successfully! Transaction ID: {receipt.transaction_id}") except Exception as e: print(f"❌ Airdrop failed: {e}") @@ -337,14 +323,9 @@ def main(): ) # Initiate airdrop of 20 fungible tokens and 1 nft token id - perform_airdrop( - client, operator_id, operator_key, receiver_id, [fungible_id], [nft_serial], 20 - ) + perform_airdrop(client, operator_id, operator_key, receiver_id, [fungible_id], [nft_serial], 20) - print( - "\n🔍 Verifying receiver has received airdrop contents automatically" - "and sender has sent:" - ) + print("\n🔍 Verifying receiver has received airdrop contents automaticallyand sender has sent:") log_balances( client, operator_id, @@ -354,14 +335,8 @@ def main(): prefix="After airdrop", ) - print( - "✅ Auto-association successful: Receiver accepted airdropped tokens " - "without pre-association." - ) - print( - "✅ Airdrop successful: Receiver accepted new fungible tokens " - "without pre-association." - ) + print("✅ Auto-association successful: Receiver accepted airdropped tokens without pre-association.") + print("✅ Airdrop successful: Receiver accepted new fungible tokens without pre-association.") if __name__ == "__main__": diff --git a/examples/tokens/token_airdrop_claim_signature_required.py b/examples/tokens/token_airdrop_claim_signature_required.py index 4b3feb2cb..8f7e8afa2 100644 --- a/examples/tokens/token_airdrop_claim_signature_required.py +++ b/examples/tokens/token_airdrop_claim_signature_required.py @@ -23,6 +23,7 @@ uv run examples/tokens/token_airdrop_claim_signature_required.py python examples/tokens/token_airdrop_claim_signature_required.py """ + # pylint: disable=import-error, # pylint: disable=too-many-arguments, # pylint: disable=protected-access, @@ -60,9 +61,7 @@ def setup_client(): return client -def create_receiver( - client: Client, signature_required: bool = True, max_auto_assoc: int = 0 -): +def create_receiver(client: Client, signature_required: bool = True, max_auto_assoc: int = 0): """Creates a receiver account with specific configurations.""" receiver_key = PrivateKey.generate() receiver_public_key = receiver_key.public_key() @@ -197,14 +196,10 @@ def get_token_association_status( """Checks if the receiver account is associated with the given tokens.""" try: # Query the receiver's balance, which includes token associations - balance = ( - CryptoGetAccountBalanceQuery().set_account_id(receiver_id).execute(client) - ) + balance = CryptoGetAccountBalanceQuery().set_account_id(receiver_id).execute(client) associated_tokens = set(balance.token_balances.keys()) - association_status = { - token_id: token_id in associated_tokens for token_id in token_ids - } + association_status = {token_id: token_id in associated_tokens for token_id in token_ids} print(f"✅ Association status for account {receiver_id}:") for tid, associated in association_status.items(): @@ -256,12 +251,8 @@ def log_balances( print(f"\n===== {prefix} Balances =====") try: - operator_balance = ( - CryptoGetAccountBalanceQuery().set_account_id(operator_id).execute(client) - ) - receiver_balance = ( - CryptoGetAccountBalanceQuery().set_account_id(receiver_id).execute(client) - ) + operator_balance = CryptoGetAccountBalanceQuery().set_account_id(operator_id).execute(client) + receiver_balance = CryptoGetAccountBalanceQuery().set_account_id(receiver_id).execute(client) except Exception as exc: print(f"❌ Failed to fetch balances: {exc}") return @@ -314,9 +305,7 @@ def perform_airdrop( if receipt.status != ResponseCode.SUCCESS: status_message = ResponseCode(receipt.status).name - raise RuntimeError( - f"Airdrop transaction failed with status: {status_message}" - ) + raise RuntimeError(f"Airdrop transaction failed with status: {status_message}") transaction_id = receipt.transaction_id print(f"✅ Airdrop executed successfully! Transaction ID: {transaction_id}") @@ -327,9 +316,7 @@ def perform_airdrop( raise RuntimeError("Airdrop execution failed") from exc -def fetch_pending_airdrops( - client: Client, transaction_id: TransactionId -) -> list[PendingAirdropId]: +def fetch_pending_airdrops(client: Client, transaction_id: TransactionId) -> list[PendingAirdropId]: """ Retrieve all pending airdrop IDs generated by a specific transaction. @@ -338,9 +325,7 @@ def fetch_pending_airdrops( `new_pending_airdrops` field. """ try: - record: TransactionRecord = TransactionRecordQuery(transaction_id).execute( - client - ) + record: TransactionRecord = TransactionRecordQuery(transaction_id).execute(client) pending_airdrops = record.new_pending_airdrops # List of PendingAirdropRecord pending_airdrop_ids = [p.pending_airdrop_id for p in pending_airdrops] @@ -352,15 +337,11 @@ def fetch_pending_airdrops( return pending_airdrop_ids except Exception as exc: - print( - f"❌ Failed to fetch pending airdrops for transaction {transaction_id}: {exc}" - ) + print(f"❌ Failed to fetch pending airdrops for transaction {transaction_id}: {exc}") return [] -def claim_airdrop( - client: Client, receiver_key: PrivateKey, pending_airdrops: list[PendingAirdropId] -): +def claim_airdrop(client: Client, receiver_key: PrivateKey, pending_airdrops: list[PendingAirdropId]): """ Claims one or more pending airdrops on behalf of the receiver. @@ -431,9 +412,7 @@ def main(): # Verify this receiver does NOT have any of the fungible or NFT tokens associated # Claim airdrop will work regardless token_ids_to_check = [fungible_id, nft_token_id] - association_status = get_token_association_status( - client, receiver_id, token_ids_to_check - ) + association_status = get_token_association_status(client, receiver_id, token_ids_to_check) print(association_status) # Check pre-airdrop balances @@ -448,9 +427,7 @@ def main(): ) # Initiate airdrop of 20 fungible tokens and 1 nft token id - transaction_id = perform_airdrop( - client, operator_id, operator_key, receiver_id, [fungible_id], [nft_serial], 20 - ) + transaction_id = perform_airdrop(client, operator_id, operator_key, receiver_id, [fungible_id], [nft_serial], 20) print("\n🔍 Verifying no balance change as airdrop is not yet claimed:") log_balances( @@ -471,9 +448,7 @@ def main(): # Claiming will work even without association and available auto-association slots # This is because the signing itself causes the Hedera network to associate the tokens. print("Claiming airdrop:") - claim_airdrop( - client, receiver_key, pending_airdrop_ids - ) # Pass the receiver key which is needed to sign + claim_airdrop(client, receiver_key, pending_airdrop_ids) # Pass the receiver key which is needed to sign # Check airdrop has resulted in transfers print("\n🔍 Verifying balances have now changed after claim:") @@ -488,9 +463,7 @@ def main(): # Check Hedera network has associated these tokens behind the scenes token_ids_to_check = [fungible_id, nft_token_id] - association_status = get_token_association_status( - client, receiver_id, token_ids_to_check - ) + association_status = get_token_association_status(client, receiver_id, token_ids_to_check) print(association_status) diff --git a/examples/tokens/token_airdrop_transaction.py b/examples/tokens/token_airdrop_transaction.py index f43295d97..91bc758bd 100644 --- a/examples/tokens/token_airdrop_transaction.py +++ b/examples/tokens/token_airdrop_transaction.py @@ -5,6 +5,7 @@ uv run examples/tokens/token_airdrop_transaction.py python examples/tokens/token_airdrop_transaction.py """ + import sys from hiero_sdk_python import ( @@ -43,9 +44,7 @@ def create_account(client, operator_key): .set_key_without_alias(recipient_key.public_key()) .set_initial_balance(Hbar.from_tinybars(100_000_000)) ) - account_receipt = ( - account_tx.freeze_with(client).sign(operator_key).execute(client) - ) + account_receipt = account_tx.freeze_with(client).sign(operator_key).execute(client) recipient_id = account_receipt.account_id print(f"✅ Success! Created a new recipient account with ID: {recipient_id}") @@ -111,7 +110,7 @@ def mint_nft(client, operator_key, nft_id): mint_receipt = mint_tx.execute(client) serial_number = mint_receipt.serial_numbers[0] - print(f"✅ Success! Nft minted serial: { serial_number }.") + print(f"✅ Success! Nft minted serial: {serial_number}.") return serial_number except Exception as e: print(f"❌ Error minting nft: {e}") @@ -122,9 +121,7 @@ def associate_tokens(client, recipient_id, recipient_key, tokens): """Associate the token and nft with the recipient.""" print("\nAssociating tokens to recipient...") try: - assocciate_tx = TokenAssociateTransaction( - account_id=recipient_id, token_ids=tokens - ) + assocciate_tx = TokenAssociateTransaction(account_id=recipient_id, token_ids=tokens) assocciate_tx.freeze_with(client) assocciate_tx.sign(recipient_key) assocciate_tx.execute(client) @@ -147,9 +144,7 @@ def associate_tokens(client, recipient_id, recipient_key, tokens): sys.exit(1) -def airdrop_tokens( - client, operator_id, operator_key, recipient_id, token_id, nft_id, serial_number -): +def airdrop_tokens(client, operator_id, operator_key, recipient_id, token_id, nft_id, serial_number): """ Build and execute a TokenAirdropTransaction that transfers one fungible token. @@ -157,9 +152,7 @@ def airdrop_tokens( """ print(f"\nStep 6: Airdropping tokens to recipient {recipient_id}:") print(f" - 1 fungible token TKA ({token_id})") - print( - f" - NFT from NFTA collection ({nft_id}) with serial number #{serial_number}" - ) + print(f" - NFT from NFTA collection ({nft_id}) with serial number #{serial_number}") try: airdrop_tx = ( TokenAirdropTransaction() @@ -195,11 +188,7 @@ def _check_token_transfer_for_pair(record, token_id, operator_id, recipient_id): (v for k, v in token_transfers.items() if k == token_id), None ) # Validate shape and compare expected amounts in a single expression - return ( - isinstance(transfers, dict) - and transfers.get(operator_id) == -1 - and transfers.get(recipient_id) == 1 - ) + return isinstance(transfers, dict) and transfers.get(operator_id) == -1 and transfers.get(recipient_id) == 1 except Exception: return False @@ -216,9 +205,7 @@ def _extract_nft_transfers(record): if nft_transfers is None: continue # Skip non-iterable or string-like values - if not hasattr(nft_transfers, "__iter__") or isinstance( - nft_transfers, (str, bytes) - ): + if not hasattr(nft_transfers, "__iter__") or isinstance(nft_transfers, (str, bytes)): continue for nft_transfer in nft_transfers: @@ -251,9 +238,7 @@ def verify_transaction_record( } try: - record = TransactionRecordQuery( - transaction_id=airdrop_receipt.transaction_id - ).execute(client) + record = TransactionRecordQuery(transaction_id=airdrop_receipt.transaction_id).execute(client) except Exception as e: print(f"❌ Error fetching transaction record: {e}") return result @@ -261,9 +246,7 @@ def verify_transaction_record( result["record"] = record # Token transfer check (delegated) - result["expected_token_transfer"] = _check_token_transfer_for_pair( - record, token_id, operator_id, recipient_id - ) + result["expected_token_transfer"] = _check_token_transfer_for_pair(record, token_id, operator_id, recipient_id) # NFT transfers: extract and then look for a matching entry nft_serials = _extract_nft_transfers(record) @@ -271,10 +254,7 @@ def verify_transaction_record( # Single-pass confirmation using any() to keep branching minimal result["nft_transfer_confirmed"] = any( - token_key == nft_id - and sender == operator_id - and receiver == recipient_id - and serial == serial_number + token_key == nft_id and sender == operator_id and receiver == recipient_id and serial == serial_number for token_key, serial, sender, receiver in nft_serials ) @@ -285,7 +265,6 @@ def verify_post_airdrop_balances( client: Client, operator_id, recipient_id, - token_id, nft_id, serial_number, balances_before, @@ -314,9 +293,7 @@ def verify_post_airdrop_balances( # Get current account info and balances using AccountInfoQuery (more robust) try: sender_info = AccountInfoQuery(account_id=operator_id).execute(client) - sender_current = { - rel.token_id: rel.balance for rel in sender_info.token_relationships - } + sender_current = {rel.token_id: rel.balance for rel in sender_info.token_relationships} except Exception as e: details["error"] = f"Error fetching sender account info: {e}" details["fully_verified"] = False @@ -324,9 +301,7 @@ def verify_post_airdrop_balances( try: recipient_info = AccountInfoQuery(account_id=recipient_id).execute(client) - recipient_current = { - rel.token_id: rel.balance for rel in recipient_info.token_relationships - } + recipient_current = {rel.token_id: rel.balance for rel in recipient_info.token_relationships} except Exception as e: details["error"] = f"Error fetching recipient account info: {e}" details["fully_verified"] = False @@ -378,12 +353,8 @@ def _log_balances_before(client, operator_id, recipient_id, token_id, nft_id): recipient_info = AccountInfoQuery(account_id=recipient_id).execute(client) # Build dictionaries for balances from token_relationships (more robust than balance query) - sender_balances_before = { - rel.token_id: rel.balance for rel in sender_info.token_relationships - } - recipient_balances_before = { - rel.token_id: rel.balance for rel in recipient_info.token_relationships - } + sender_balances_before = {rel.token_id: rel.balance for rel in sender_info.token_relationships} + recipient_balances_before = {rel.token_id: rel.balance for rel in recipient_info.token_relationships} print(f"Sender ({operator_id}) balances before airdrop:") print(f" {token_id}: {sender_balances_before.get(token_id, 0)}") @@ -407,15 +378,9 @@ def _print_verification_summary( print( f" - Token transfer verification (fungible): {'OK' if record_result.get('expected_token_transfer') else 'MISSING'}" ) - print( - f" - NFT transfer seen in record: {'OK' if record_result.get('nft_transfer_confirmed') else 'MISSING'}" - ) - print( - f" - NFT owner according to TokenNftInfoQuery: {verification_details.get('nft_owner')}" - ) - print( - f" - NFT owner matches recipient: {'YES' if verification_details.get('owner_matches') else 'NO'}" - ) + print(f" - NFT transfer seen in record: {'OK' if record_result.get('nft_transfer_confirmed') else 'MISSING'}") + print(f" - NFT owner according to TokenNftInfoQuery: {verification_details.get('nft_owner')}") + print(f" - NFT owner matches recipient: {'YES' if verification_details.get('owner_matches') else 'NO'}") if verification_details.get("fully_verified"): print( @@ -440,17 +405,13 @@ def _print_final_summary( # Use AccountInfoQuery for accurate balance retrieval if not already cached if verification_details.get("sender_balances_after") is None: sender_info = AccountInfoQuery(account_id=operator_id).execute(client) - sender_balances_after = { - rel.token_id: rel.balance for rel in sender_info.token_relationships - } + sender_balances_after = {rel.token_id: rel.balance for rel in sender_info.token_relationships} else: sender_balances_after = verification_details.get("sender_balances_after") if verification_details.get("recipient_balances_after") is None: recipient_info = AccountInfoQuery(account_id=recipient_id).execute(client) - recipient_balances_after = { - rel.token_id: rel.balance for rel in recipient_info.token_relationships - } + recipient_balances_after = {rel.token_id: rel.balance for rel in recipient_info.token_relationships} else: recipient_balances_after = verification_details.get("recipient_balances_after") @@ -516,9 +477,7 @@ def token_airdrop(): ) # Airdrop the tokens (separated into its own function) - airdrop_receipt = airdrop_tokens( - client, operator_id, operator_key, recipient_id, token_id, nft_id, serial_number - ) + airdrop_receipt = airdrop_tokens(client, operator_id, operator_key, recipient_id, token_id, nft_id, serial_number) # 1) Verify record contents (token transfers and nft transfers) record_result = verify_transaction_record( @@ -540,7 +499,6 @@ def token_airdrop(): client, operator_id, recipient_id, - token_id, nft_id, serial_number, balances_before, diff --git a/examples/tokens/token_airdrop_transaction_cancel.py b/examples/tokens/token_airdrop_transaction_cancel.py index 2429d2f99..300a0d61a 100644 --- a/examples/tokens/token_airdrop_transaction_cancel.py +++ b/examples/tokens/token_airdrop_transaction_cancel.py @@ -5,6 +5,7 @@ uv run examples/tokens/token_airdrop_transaction_cancel.py python examples/tokens/token_airdrop_transaction_cancel.py """ + import sys from hiero_sdk_python import ( @@ -20,6 +21,7 @@ TransactionRecordQuery, ) + # Load environment variables from .env file @@ -30,9 +32,7 @@ def setup_client(): return client -def create_account( - client, operator_key, initial_balance=Hbar.from_tinybars(100_000_000) -): +def create_account(client, operator_key, initial_balance=Hbar.from_tinybars(100_000_000)): """Create a new account with the given initial balance.""" print("\nCreating a new account...") recipient_key = PrivateKey.generate("ed25519") @@ -51,9 +51,7 @@ def create_account( sys.exit(1) -def create_token( - client, operator_account_id, operator_account_key, token_name, token_symbol, initial_supply=1 -): +def create_token(client, operator_account_id, operator_account_key, token_name, token_symbol, initial_supply=1): """Create a new token and return its token ID.""" print(f"\nCreating token: {token_name} ({token_symbol})...") try: @@ -75,22 +73,14 @@ def create_token( def airdrop_tokens(client, operator_account_id, operator_account_key, recipient_id, token_ids): """Airdrop the provided tokens to a recipient account.""" - print( - f"\nAirdropping tokens {', '.join([str(t) for t in token_ids])} to recipient {recipient_id}..." - ) + print(f"\nAirdropping tokens {', '.join([str(t) for t in token_ids])} to recipient {recipient_id}...") try: # Balances before airdrop sender_balances_before = ( - CryptoGetAccountBalanceQuery(account_id=operator_account_id) - .execute(client) - .token_balances - ) - recipient_balances_before = ( - CryptoGetAccountBalanceQuery(account_id=recipient_id) - .execute(client) - .token_balances + CryptoGetAccountBalanceQuery(account_id=operator_account_id).execute(client).token_balances ) + recipient_balances_before = CryptoGetAccountBalanceQuery(account_id=recipient_id).execute(client).token_balances print("\nBalances before airdrop:") for t in token_ids: @@ -104,10 +94,7 @@ def airdrop_tokens(client, operator_account_id, operator_account_key, recipient_ tx.add_token_transfer(token_id=token_id, account_id=recipient_id, amount=1) receipt = tx.freeze_with(client).sign(operator_account_key).execute(client) - print( - f"Token airdrop executed: status={receipt.status} " - f"transaction_id={receipt.transaction_id}" - ) + print(f"Token airdrop executed: status={receipt.status} transaction_id={receipt.transaction_id}") # Get record to inspect pending airdrops airdrop_record = TransactionRecordQuery(receipt.transaction_id).execute(client) @@ -115,15 +102,9 @@ def airdrop_tokens(client, operator_account_id, operator_account_key, recipient_ # Balances after airdrop sender_balances_after = ( - CryptoGetAccountBalanceQuery(account_id=operator_account_id) - .execute(client) - .token_balances - ) - recipient_balances_after = ( - CryptoGetAccountBalanceQuery(account_id=recipient_id) - .execute(client) - .token_balances + CryptoGetAccountBalanceQuery(account_id=operator_account_id).execute(client).token_balances ) + recipient_balances_after = CryptoGetAccountBalanceQuery(account_id=recipient_id).execute(client).token_balances print("\nBalances after airdrop:") for t in token_ids: @@ -157,9 +138,7 @@ def cancel_airdrops(client, operator_key, pending_airdrops): cancel_airdrop_receipt = cancel_airdrop_tx.execute(client) if cancel_airdrop_receipt.status != ResponseCode.SUCCESS: - print( - f"Failed to cancel airdrop: " f"Status: {cancel_airdrop_receipt.status}" - ) + print(f"Failed to cancel airdrop: Status: {cancel_airdrop_receipt.status}") sys.exit(1) print("Airdrop cancel transaction successful") @@ -179,9 +158,7 @@ def token_airdrop_cancel(): token_id_2 = create_token(client, operator_id, operator_key, "Second Token", "TKB") # Airdrop tokens - pending_airdrops = airdrop_tokens( - client, operator_id, operator_key, recipient_id, [token_id_1, token_id_2] - ) + pending_airdrops = airdrop_tokens(client, operator_id, operator_key, recipient_id, [token_id_1, token_id_2]) # Cancel airdrops cancel_airdrops(client, operator_key, pending_airdrops) diff --git a/examples/tokens/token_associate_transaction.py b/examples/tokens/token_associate_transaction.py index e423d18c2..76b7194c0 100644 --- a/examples/tokens/token_associate_transaction.py +++ b/examples/tokens/token_associate_transaction.py @@ -9,6 +9,7 @@ This script shows the complete workflow: client setup, account creation, token creation, token association, and verification. """ + import sys from hiero_sdk_python import ( @@ -59,9 +60,7 @@ def create_test_account(client, operator_key): ) if receipt.status != ResponseCode.SUCCESS: - print( - f"❌ Account creation failed with status: {ResponseCode(receipt.status).name}" - ) + print(f"❌ Account creation failed with status: {ResponseCode(receipt.status).name}") sys.exit(1) new_account_id = receipt.account_id print(f"✅ Success! Created new account with ID: {new_account_id}") @@ -100,9 +99,7 @@ def create_fungible_token(client, operator_id, operator_key): ) if receipt.status != ResponseCode.SUCCESS: - print( - f"❌ Token creation failed with status: {ResponseCode(receipt.status).name}" - ) + print(f"❌ Token creation failed with status: {ResponseCode(receipt.status).name}") sys.exit(1) token_id = receipt.token_id print(f"✅ Success! Created token with ID: {token_id}") @@ -137,9 +134,7 @@ def associate_token_with_account(client, token_id, account_id, account_key): ) if receipt.status != ResponseCode.SUCCESS: - print( - f"❌ Token association failed with status: {ResponseCode(receipt.status).name}" - ) + print(f"❌ Token association failed with status: {ResponseCode(receipt.status).name}") sys.exit(1) print("✅ Success! Token association complete.") print(f" Account {account_id} can now hold and transfer token {token_id}") @@ -148,9 +143,7 @@ def associate_token_with_account(client, token_id, account_id, account_key): sys.exit(1) -def associate_two_tokens_mixed_types_with_set_token_ids( - client, token_id_1, token_id_2, account_id, account_key -): +def associate_two_tokens_mixed_types_with_set_token_ids(client, token_id_1, token_id_2, account_id, account_key): """ Associate two tokens using set_token_ids() with mixed types: @@ -173,16 +166,11 @@ def associate_two_tokens_mixed_types_with_set_token_ids( ) if receipt.status != ResponseCode.SUCCESS: - print( - f"❌ Token association (mixed types) failed with status: " - f"{ResponseCode(receipt.status).name}" - ) + print(f"❌ Token association (mixed types) failed with status: {ResponseCode(receipt.status).name}") sys.exit(1) print("✅ Success! Token association completed.") - print( - f" Account {account_id} can now hold and transfer tokens {token_id_1} and {token_id_2}" - ) + print(f" Account {account_id} can now hold and transfer tokens {token_id_1} and {token_id_2}") except Exception as e: print(f"❌ Error in while associating tokens: {e}") sys.exit(1) @@ -209,19 +197,12 @@ def demonstrate_invalid_set_token_ids_usage(client, account_id, account_key): ) if receipt.status != ResponseCode.SUCCESS: - print( - f"❌ Token creation failed with status: {ResponseCode(receipt.status).name}" - ) + print(f"❌ Token creation failed with status: {ResponseCode(receipt.status).name}") sys.exit(1) except Exception as e: - if ( - type(e) == TypeError - and "Invalid token_id type: expected TokenId or str, got" in e.args[0] - ): - print( - "✅ Correct behavior: invalid token_id type was rejected from `set_token_ids`" - ) + if type(e) is TypeError and "Invalid token_id type: expected TokenId or str, got" in e.args[0]: + print("✅ Correct behavior: invalid token_id type was rejected from `set_token_ids`") print(f" Error: {e}") else: print(f"❌ Unexpected error while creating transaction: {e}") @@ -249,9 +230,7 @@ def verify_token_association(client, account_id, token_id): for relationship in info.token_relationships: if str(relationship.token_id) == str(token_id): print("✅ Verification Successful!") - print( - f" Token {token_id} is associated with account {account_id}" - ) + print(f" Token {token_id} is associated with account {account_id}") print(f" Balance: {relationship.balance}") return True print("❌ Verification Failed!") @@ -306,12 +285,8 @@ def main(): associate_token_with_account(client, token_id_0, account_id, account_private_key) # Step 6: Associate multiple tokens with the new account - print( - f"\nSTEP 6: Associating token {token_id_1} and token {token_id_2} with account {account_id}..." - ) - associate_two_tokens_mixed_types_with_set_token_ids( - client, token_id_1, token_id_2, account_id, account_private_key - ) + print(f"\nSTEP 6: Associating token {token_id_1} and token {token_id_2} with account {account_id}...") + associate_two_tokens_mixed_types_with_set_token_ids(client, token_id_1, token_id_2, account_id, account_private_key) # Step 7: Verify the token association print("\nSTEP 7: Verifying token association...") diff --git a/examples/tokens/token_burn_transaction_fungible.py b/examples/tokens/token_burn_transaction_fungible.py index 6b9e99574..bd672d57f 100644 --- a/examples/tokens/token_burn_transaction_fungible.py +++ b/examples/tokens/token_burn_transaction_fungible.py @@ -5,6 +5,7 @@ uv run examples/tokens/token_burn_transaction_fungible.py python examples/tokens/token_burn_transaction_fungible.py """ + import sys from hiero_sdk_python import Client @@ -41,9 +42,7 @@ def create_fungible_token(client, operator_id, operator_key): ) if receipt.status != ResponseCode.SUCCESS: - print( - f"Fungible token creation failed with status: {ResponseCode(receipt.status).name}" - ) + print(f"Fungible token creation failed with status: {ResponseCode(receipt.status).name}") sys.exit(1) token_id = receipt.token_id @@ -83,12 +82,7 @@ def token_burn_fungible(): burn_amount = 40 # Burn 40 tokens out of 100 - receipt = ( - TokenBurnTransaction() - .set_token_id(token_id) - .set_amount(burn_amount) - .execute(client) - ) + receipt = TokenBurnTransaction().set_token_id(token_id).set_amount(burn_amount).execute(client) if receipt.status != ResponseCode.SUCCESS: print(f"Token burn failed with status: {ResponseCode(receipt.status).name}") diff --git a/examples/tokens/token_burn_transaction_nft.py b/examples/tokens/token_burn_transaction_nft.py index e68ae8a7d..e108f88f1 100644 --- a/examples/tokens/token_burn_transaction_nft.py +++ b/examples/tokens/token_burn_transaction_nft.py @@ -5,6 +5,7 @@ uv run examples/tokens/token_burn_transaction_nft.py python examples/tokens/token_burn_transaction_nft.py """ + import sys from hiero_sdk_python import Client @@ -55,12 +56,7 @@ def create_nft(client, operator_id, operator_key): def mint_nfts(client, nft_token_id, metadata_list): """Mint a non-fungible token.""" - receipt = ( - TokenMintTransaction() - .set_token_id(nft_token_id) - .set_metadata(metadata_list) - .execute(client) - ) + receipt = TokenMintTransaction().set_token_id(nft_token_id).set_metadata(metadata_list).execute(client) if receipt.status != ResponseCode.SUCCESS: print(f"NFT minting failed with status: {ResponseCode(receipt.status).name}") @@ -105,20 +101,13 @@ def token_burn_nft(): get_token_info(client, token_id) # Burn first 2 NFTs from the minted collection (serials 1 and 2) - receipt = ( - TokenBurnTransaction() - .set_token_id(token_id) - .set_serials(serial_numbers[0:2]) - .execute(client) - ) + receipt = TokenBurnTransaction().set_token_id(token_id).set_serials(serial_numbers[0:2]).execute(client) if receipt.status != ResponseCode.SUCCESS: print(f"NFT burn failed with status: {ResponseCode(receipt.status).name}") sys.exit(1) - print( - f"Successfully burned NFTs with serial numbers {serial_numbers[0:2]} from {token_id}" - ) + print(f"Successfully burned NFTs with serial numbers {serial_numbers[0:2]} from {token_id}") # Get and print token balances after burn to show the final state print("\nToken balances after burn:") diff --git a/examples/tokens/token_create_transaction_admin_key.py b/examples/tokens/token_create_transaction_admin_key.py index 1e9edc06e..1b2afa330 100644 --- a/examples/tokens/token_create_transaction_admin_key.py +++ b/examples/tokens/token_create_transaction_admin_key.py @@ -17,6 +17,7 @@ uv run examples/tokens/token_create_transaction_admin_key.py python examples/tokens/token_create_transaction_admin_key.py """ + import sys from hiero_sdk_python import ( @@ -38,6 +39,7 @@ def setup_client(): print(f"Client set up with operator id {client.operator_account_id}") return client + def generate_admin_key(): """Generate a new admin key for the token.""" print("\nGenerating a new admin key for the token...") @@ -123,12 +125,8 @@ def demonstrate_failed_supply_key_addition(client, token_id, admin_key): try: receipt = transaction.execute(client) if receipt.status != ResponseCode.SUCCESS: - print( - f"❌ As expected, adding supply key failed: {ResponseCode(receipt.status).name}" - ) - print( - " Admin key cannot authorize adding keys that were not present during token creation." - ) + print(f"❌ As expected, adding supply key failed: {ResponseCode(receipt.status).name}") + print(" Admin key cannot authorize adding keys that were not present during token creation.") return True # Expected failure print("⚠️ Unexpectedly succeeded - this shouldn't happen") return False @@ -156,9 +154,7 @@ def demonstrate_admin_key_update(client, token_id, admin_key, operator_key): receipt = transaction.execute(client) if receipt.status != ResponseCode.SUCCESS: - print( - f"Admin key update failed with status: {ResponseCode(receipt.status).name}" - ) + print(f"Admin key update failed with status: {ResponseCode(receipt.status).name}") return False print("✅ Admin key updated successfully") diff --git a/examples/tokens/token_create_transaction_freeze_key.py b/examples/tokens/token_create_transaction_freeze_key.py index f20e07bfd..c55b872f0 100644 --- a/examples/tokens/token_create_transaction_freeze_key.py +++ b/examples/tokens/token_create_transaction_freeze_key.py @@ -11,6 +11,7 @@ and verifying how transfers behave while frozen 3. (Bonus) Showing that tokens created with freezeDefault=True start accounts frozen """ + import sys from dataclasses import dataclass @@ -28,6 +29,7 @@ TransferTransaction, ) + TRANSFER_AMOUNT = 10 # Small token transfer for demonstrations @@ -52,9 +54,7 @@ def generate_freeze_key() -> PrivateKey: return freeze_key -def create_token_without_freeze_key( - client: Client, operator_id: AccountId, operator_key: PrivateKey -): +def create_token_without_freeze_key(client: Client, operator_id: AccountId, operator_key: PrivateKey): """Create a fungible token that does not include a freeze key.""" print("\nSTEP 1️⃣ Creating token WITHOUT a freeze key...") receipt = ( @@ -77,9 +77,7 @@ def create_token_without_freeze_key( return token_id -def demonstrate_missing_freeze_key( - client: Client, token_id, operator_id: AccountId, operator_key: PrivateKey -): +def demonstrate_missing_freeze_key(client: Client, token_id, operator_id: AccountId, operator_key: PrivateKey): """Attempt freeze/unfreeze operations when no freeze key exists.""" print("\nSTEP 2️⃣ Demonstrating that freeze operations fail without a freeze key...") try: @@ -183,9 +181,7 @@ def create_demo_account(client: Client, operator_key: PrivateKey) -> DemoAccount def associate_token(client: Client, token_id, account: DemoAccount, signer: PrivateKey): """Associate a token with a given account.""" - print( - f"\nSTEP 5️⃣ Associating token {token_id} with account {account.account_id}..." - ) + print(f"\nSTEP 5️⃣ Associating token {token_id} with account {account.account_id}...") receipt = ( TokenAssociateTransaction() .set_account_id(account.account_id) @@ -236,9 +232,7 @@ def attempt_transfer( return False -def freeze_account( - client: Client, token_id, account_id: AccountId, freeze_key: PrivateKey -): +def freeze_account(client: Client, token_id, account_id: AccountId, freeze_key: PrivateKey): """Freeze an account for the given token.""" print(f"\n🧊 Freezing account {account_id} for token {token_id}...") receipt = ( @@ -256,9 +250,7 @@ def freeze_account( print("✅ Account frozen.") -def unfreeze_account( - client: Client, token_id, account_id: AccountId, freeze_key: PrivateKey -): +def unfreeze_account(client: Client, token_id, account_id: AccountId, freeze_key: PrivateKey): """Unfreeze an account for the given token.""" print(f"\n🔥 Unfreezing account {account_id} for token {token_id}...") receipt = ( @@ -294,9 +286,7 @@ def demonstrate_freeze_default_flow( symbol="FDF", ) default_frozen_account = create_demo_account(client, operator_key) - associate_token( - client, token_id, default_frozen_account, default_frozen_account.private_key - ) + associate_token(client, token_id, default_frozen_account, default_frozen_account.private_key) success_before_unfreeze = attempt_transfer( client, @@ -307,13 +297,9 @@ def demonstrate_freeze_default_flow( note="freezeDefault=True (should FAIL)", ) if success_before_unfreeze: - print( - "⚠️ Unexpected success: account should start frozen when freezeDefault=True." - ) + print("⚠️ Unexpected success: account should start frozen when freezeDefault=True.") else: - print( - "✅ Transfer blocked as expected because the account was frozen by default." - ) + print("✅ Transfer blocked as expected because the account was frozen by default.") unfreeze_account(client, token_id, default_frozen_account.account_id, freeze_key) attempt_transfer( @@ -335,16 +321,12 @@ def main(): operator_key = client.operator_private_key # Token without a freeze key - token_without_key = create_token_without_freeze_key( - client, operator_id, operator_key - ) + token_without_key = create_token_without_freeze_key(client, operator_id, operator_key) demonstrate_missing_freeze_key(client, token_without_key, operator_id, operator_key) # Token with a freeze key freeze_key = generate_freeze_key() - token_with_freeze = create_token_with_freeze_key( - client, operator_id, operator_key, freeze_key - ) + token_with_freeze = create_token_with_freeze_key(client, operator_id, operator_key, freeze_key) demo_account = create_demo_account(client, operator_key) associate_token(client, token_with_freeze, demo_account, demo_account.private_key) @@ -385,9 +367,7 @@ def main(): # Bonus behavior demonstrate_freeze_default_flow(client, operator_id, operator_key, freeze_key) - print( - "\n🎉 Freeze key demonstration completed! Review the log above for each step." - ) + print("\n🎉 Freeze key demonstration completed! Review the log above for each step.") if __name__ == "__main__": diff --git a/examples/tokens/token_create_transaction_fungible_finite.py b/examples/tokens/token_create_transaction_fungible_finite.py index 26e495dfd..0eb4e7e98 100644 --- a/examples/tokens/token_create_transaction_fungible_finite.py +++ b/examples/tokens/token_create_transaction_fungible_finite.py @@ -20,6 +20,7 @@ uv run examples/tokens/token_create_transaction_fungible_finite.py python examples/tokens/token_create_transaction_fungible_finite.py """ + import os import sys @@ -42,15 +43,13 @@ def parse_optional_key(key_str): return None - - - def setup_client(): client = Client.from_env() print(f"Network: {client.network.network}") print(f"Client set up with operator id {client.operator_account_id}") return client + def load_optional_keys(): """Load optional keys (admin, supply, freeze, pause).""" admin_key = parse_optional_key(os.getenv("ADMIN_KEY")) @@ -100,9 +99,7 @@ def execute_transaction(transaction, client, operator_key, admin_key): if receipt and receipt.token_id: print(f"Finite fungible token created with ID: {receipt.token_id}") else: - print( - "Finite fungible token creation failed: Token ID not returned in receipt." - ) + print("Finite fungible token creation failed: Token ID not returned in receipt.") sys.exit(1) except Exception as e: print(f"Token creation failed: {str(e)}") diff --git a/examples/tokens/token_create_transaction_fungible_infinite.py b/examples/tokens/token_create_transaction_fungible_infinite.py index 4c2b3117e..138c24d4b 100644 --- a/examples/tokens/token_create_transaction_fungible_infinite.py +++ b/examples/tokens/token_create_transaction_fungible_infinite.py @@ -16,6 +16,7 @@ uv run examples/tokens/token_create_transaction_fungible_infinite.py python examples/tokens/token_create_transaction_fungible_infinite.py """ + import sys from hiero_sdk_python import ( @@ -33,6 +34,7 @@ def setup_client(): print(f"Client set up with operator id {client.operator_account_id}") return client + def generate_keys(): """Generate new admin and supply keys.""" print("\nGenerating new admin and supply keys for the token...") @@ -71,9 +73,7 @@ def execute_transaction(transaction, client, operator_key, admin_key, supply_key try: receipt = transaction.execute(client) if receipt and receipt.token_id: - print( - f"Success! Infinite fungible token created with ID: {receipt.token_id}" - ) + print(f"Success! Infinite fungible token created with ID: {receipt.token_id}") else: print("Token creation failed: Token ID not returned in receipt.") sys.exit(1) diff --git a/examples/tokens/token_create_transaction_kyc_key.py b/examples/tokens/token_create_transaction_kyc_key.py index 61ee96ae6..1b960f5e2 100644 --- a/examples/tokens/token_create_transaction_kyc_key.py +++ b/examples/tokens/token_create_transaction_kyc_key.py @@ -21,6 +21,7 @@ python examples/tokens/token_create_transaction_kyc_key.py """ + import sys import time @@ -51,6 +52,7 @@ def setup_client(): print(f"Client set up with operator id {client.operator_account_id}") return client + def create_account(client, operator_key, initial_balance=Hbar(2)): """ Create a new test account on the Hedera network. @@ -77,9 +79,7 @@ def create_account(client, operator_key, initial_balance=Hbar(2)): receipt = transaction.execute(client) if receipt.status != ResponseCode.SUCCESS: - print( - f" Account creation failed with status: {ResponseCode(receipt.status).name}" - ) + print(f" Account creation failed with status: {ResponseCode(receipt.status).name}") sys.exit(1) account_id = receipt.account_id @@ -123,9 +123,7 @@ def create_token_without_kyc_key(client, operator_id, operator_key): ) if receipt.status != ResponseCode.SUCCESS: - print( - f" Token creation failed with status: {ResponseCode(receipt.status).name}" - ) + print(f" Token creation failed with status: {ResponseCode(receipt.status).name}") sys.exit(1) token_id = receipt.token_id @@ -207,9 +205,7 @@ def create_token_with_kyc_key(client, operator_id, operator_key, kyc_private_key ) if receipt.status != ResponseCode.SUCCESS: - print( - f" Token creation failed with status: {ResponseCode(receipt.status).name}" - ) + print(f" Token creation failed with status: {ResponseCode(receipt.status).name}") sys.exit(1) token_id = receipt.token_id @@ -238,9 +234,7 @@ def associate_token_to_account(client, token_id, account_id, account_private_key receipt = associate_transaction.execute(client) if receipt.status != ResponseCode.SUCCESS: - print( - f" Token association failed with status: {ResponseCode(receipt.status).name}" - ) + print(f" Token association failed with status: {ResponseCode(receipt.status).name}") sys.exit(1) print(f" Token {token_id} associated with account {account_id}") @@ -249,9 +243,7 @@ def associate_token_to_account(client, token_id, account_id, account_private_key sys.exit(1) -def attempt_transfer_without_kyc( - client, token_id, operator_id, recipient_id, operator_key -): +def attempt_transfer_without_kyc(client, token_id, operator_id, recipient_id, operator_key): """ Attempt to transfer tokens to an account that has not been granted KYC. @@ -266,11 +258,7 @@ def attempt_transfer_without_kyc( try: # Check balance before transfer - balance_before = ( - CryptoGetAccountBalanceQuery(account_id=recipient_id) - .execute(client) - .token_balances - ) + balance_before = CryptoGetAccountBalanceQuery(account_id=recipient_id).execute(client).token_balances recipient_balance_before = balance_before.get(token_id, 0) print(f"Recipient's token balance before transfer: {recipient_balance_before}") @@ -292,15 +280,9 @@ def attempt_transfer_without_kyc( return False print(f" Transfer succeeded with status: {status_name}") # Check balance after transfer - balance_after = ( - CryptoGetAccountBalanceQuery(account_id=recipient_id) - .execute(client) - .token_balances - ) + balance_after = CryptoGetAccountBalanceQuery(account_id=recipient_id).execute(client).token_balances recipient_balance_after = balance_after.get(token_id, 0) - print( - f"Recipient's token balance after transfer: {recipient_balance_after}\n" - ) + print(f"Recipient's token balance after transfer: {recipient_balance_after}\n") return True except Exception as e: print(f" Error during transfer attempt: {e}\n") @@ -349,11 +331,7 @@ def transfer_token_after_kyc(client, token_id, operator_id, recipient_id, operat try: # Check balance before transfer - balance_before = ( - CryptoGetAccountBalanceQuery(account_id=recipient_id) - .execute(client) - .token_balances - ) + balance_before = CryptoGetAccountBalanceQuery(account_id=recipient_id).execute(client).token_balances recipient_balance_before = balance_before.get(token_id, 0) print(f"Recipient's token balance before transfer: {recipient_balance_before}") @@ -376,11 +354,7 @@ def transfer_token_after_kyc(client, token_id, operator_id, recipient_id, operat print(f" Transfer succeeded with status: {status_name}") # Check balance after transfer - balance_after = ( - CryptoGetAccountBalanceQuery(account_id=recipient_id) - .execute(client) - .token_balances - ) + balance_after = CryptoGetAccountBalanceQuery(account_id=recipient_id).execute(client).token_balances recipient_balance_after = balance_after.get(token_id, 0) print(f"Recipient's token balance after transfer: {recipient_balance_after}\n") except Exception as e: @@ -409,9 +383,7 @@ def revoke_kyc_from_account(client, token_id, account_id, kyc_private_key): ) if receipt.status != ResponseCode.SUCCESS: - print( - f" KYC revoke failed with status: {ResponseCode(receipt.status).name}" - ) + print(f" KYC revoke failed with status: {ResponseCode(receipt.status).name}") return False print(f" KYC revoked for account {account_id} on token {token_id}") @@ -445,37 +417,27 @@ def main(): print(" KYC key generated\n") # ===== PART 1: Token WITHOUT KYC Key ===== - token_without_kyc = create_token_without_kyc_key( - client, operator_id, operator_key - ) + token_without_kyc = create_token_without_kyc_key(client, operator_id, operator_key) # Create test account for failed KYC attempt test_account_1, test_account_key_1 = create_account(client, operator_key) - associate_token_to_account( - client, token_without_kyc, test_account_1, test_account_key_1 - ) + associate_token_to_account(client, token_without_kyc, test_account_1, test_account_key_1) # Try to grant KYC (should fail) attempt_kyc_without_key(client, token_without_kyc, test_account_1, operator_key) # ===== PART 2: Token WITH KYC Key ===== - token_with_kyc = create_token_with_kyc_key( - client, operator_id, operator_key, kyc_private_key - ) + token_with_kyc = create_token_with_kyc_key(client, operator_id, operator_key, kyc_private_key) # Create and associate an account for KYC testing print("\n" + "=" * 70) print("STEP 4: Creating a new account for KYC testing") print("=" * 70) test_account_2, test_account_key_2 = create_account(client, operator_key) - associate_token_to_account( - client, token_with_kyc, test_account_2, test_account_key_2 - ) + associate_token_to_account(client, token_with_kyc, test_account_2, test_account_key_2) # Try to transfer without KYC (may fail) - attempt_transfer_without_kyc( - client, token_with_kyc, operator_id, test_account_2, operator_key - ) + attempt_transfer_without_kyc(client, token_with_kyc, operator_id, test_account_2, operator_key) # Grant KYC grant_kyc_to_account(client, token_with_kyc, test_account_2, kyc_private_key) @@ -484,9 +446,7 @@ def main(): time.sleep(1) # Transfer after KYC (should succeed) - transfer_token_after_kyc( - client, token_with_kyc, operator_id, test_account_2, operator_key - ) + transfer_token_after_kyc(client, token_with_kyc, operator_id, test_account_2, operator_key) # ===== BONUS: Revoke KYC ===== revoke_kyc_from_account(client, token_with_kyc, test_account_2, kyc_private_key) diff --git a/examples/tokens/token_create_transaction_max_automatic_token_associations_0.py b/examples/tokens/token_create_transaction_max_automatic_token_associations_0.py index cd18b25e5..e291c6f8e 100644 --- a/examples/tokens/token_create_transaction_max_automatic_token_associations_0.py +++ b/examples/tokens/token_create_transaction_max_automatic_token_associations_0.py @@ -12,6 +12,7 @@ Run with: uv run examples/tokens/token_create_transaction_max_automatic_token_associations_0. """ + import sys from hiero_sdk_python import ( @@ -30,6 +31,7 @@ from hiero_sdk_python.exceptions import PrecheckError from hiero_sdk_python.transaction.transaction_receipt import TransactionReceipt + TOKENS_TO_TRANSFER = 10 @@ -39,9 +41,8 @@ def setup_client(): print(f"Client set up with operator id {client.operator_account_id}") return client -def create_demo_token( - client: Client, operator_id: AccountId, operator_key: PrivateKey -) -> TokenId: + +def create_demo_token(client: Client, operator_id: AccountId, operator_key: PrivateKey) -> TokenId: """Create a fungible token whose treasury is the operator.""" print("\nSTEP 1: Creating the fungible demo token...") # Build and sign the fungible token creation transaction using the operator as treasury. @@ -65,13 +66,9 @@ def create_demo_token( return receipt.token_id -def create_max_account( - client: Client, operator_key: PrivateKey -) -> tuple[AccountId, PrivateKey]: +def create_max_account(client: Client, operator_key: PrivateKey) -> tuple[AccountId, PrivateKey]: """Create an account whose max automatic associations equals zero.""" - print( - "\nSTEP 2: Creating account 'max' with max automatic associations set to 0..." - ) + print("\nSTEP 2: Creating account 'max' with max automatic associations set to 0...") max_key = PrivateKey.generate() # Configure the new account to require explicit associations before accepting tokens. tx = ( @@ -98,10 +95,7 @@ def show_account_settings(client: Client, account_id: AccountId) -> None: print("\nSTEP 3: Querying account info...") # Fetch account information to verify configuration before attempting transfers. info = AccountInfoQuery(account_id).execute(client) - print( - f"Account {account_id} max_automatic_token_associations: " - f"{info.max_automatic_token_associations}" - ) + print(f"Account {account_id} max_automatic_token_associations: {info.max_automatic_token_associations}") print(f"Token relationships currently tracked: {len(info.token_relationships)}") @@ -137,16 +131,14 @@ def try_transfer( print(f"Transfer failed with status: {status.name}") return success except PrecheckError as err: - print(f"Precheck failed with status { _response_code_name(err.status) }") + print(f"Precheck failed with status {_response_code_name(err.status)}") return False except Exception as exc: # pragma: no cover - unexpected runtime/network failures print(f"Unexpected error while transferring tokens: {exc}") return False -def associate_token( - client: Client, account_id: AccountId, account_key: PrivateKey, token_id: TokenId -) -> None: +def associate_token(client: Client, account_id: AccountId, account_key: PrivateKey, token_id: TokenId) -> None: """Explicitly associate the token so the account can hold balances.""" print("\nSTEP 5: Associating the token for account 'max'...") # Submit the token association signed by the new account's private key. diff --git a/examples/tokens/token_create_transaction_nft_finite.py b/examples/tokens/token_create_transaction_nft_finite.py index 216b4277d..d276256da 100644 --- a/examples/tokens/token_create_transaction_nft_finite.py +++ b/examples/tokens/token_create_transaction_nft_finite.py @@ -16,6 +16,7 @@ uv run examples/tokens/token_create_transaction_nft_finite python examples/tokens/token_create_transaction_nft_finite """ + import sys from hiero_sdk_python import ( @@ -33,6 +34,7 @@ def setup_client(): print(f"Client set up with operator id {client.operator_account_id}") return client + def generate_keys(): """Generate new admin and supply keys.""" print("\nGenerating new admin and supply keys for the NFT...") @@ -71,9 +73,7 @@ def execute_transaction(transaction, client, operator_key, admin_key, supply_key try: receipt = transaction.execute(client) if receipt and receipt.token_id: - print( - f"Success! Finite non-fungible token created with ID: {receipt.token_id}" - ) + print(f"Success! Finite non-fungible token created with ID: {receipt.token_id}") else: print("Token creation failed: Token ID not returned in receipt.") sys.exit(1) diff --git a/examples/tokens/token_create_transaction_nft_infinite.py b/examples/tokens/token_create_transaction_nft_infinite.py index f7b6f71a5..510556f0e 100644 --- a/examples/tokens/token_create_transaction_nft_infinite.py +++ b/examples/tokens/token_create_transaction_nft_infinite.py @@ -6,6 +6,7 @@ uv run examples/tokens/token_create_transaction_nft_infinite.py python examples/tokens/token_create_transaction_nft_infinite.py """ + import sys from hiero_sdk_python import ( @@ -23,9 +24,12 @@ def setup_client(): print(f"Client set up with operator id {client.operator_account_id}") return client + """ 2. Generate Keys On-the-Fly. """ + + def keys_on_fly(): print("\nGenerating new admin and supply keys for the NFT...") admin_key = PrivateKey.generate_ed25519() @@ -37,6 +41,8 @@ def keys_on_fly(): """ 3. Build and Execute Transaction. """ + + def transaction(client, operator_id, operator_key, admin_key, supply_key): try: print("\nBuilding transaction to create an infinite NFT...") @@ -64,9 +70,7 @@ def transaction(client, operator_id, operator_key, admin_key, supply_key): receipt = transaction.execute(client) if receipt and receipt.token_id: - print( - f"Success! Infinite non-fungible token created with ID: {receipt.token_id}" - ) + print(f"Success! Infinite non-fungible token created with ID: {receipt.token_id}") return receipt.token_id print("Token creation failed: Token ID not returned in receipt.") sys.exit(1) @@ -79,6 +83,8 @@ def transaction(client, operator_id, operator_key, admin_key, supply_key): """ Creates an infinite NFT by generating admin and supply keys on the fly. """ + + def create_token_nft_infinite(): client = setup_client() operator_id = client.operator_account_id diff --git a/examples/tokens/token_create_transaction_pause_key.py b/examples/tokens/token_create_transaction_pause_key.py index 029878470..dffbd0906 100644 --- a/examples/tokens/token_create_transaction_pause_key.py +++ b/examples/tokens/token_create_transaction_pause_key.py @@ -17,6 +17,7 @@ Usage: uv run examples/token_create_transaction_pause_key.py """ + import sys from hiero_sdk_python import ( @@ -43,6 +44,7 @@ def setup_client(): print(f"Client set up with operator id {client.operator_account_id}") return client + # ------------------------------------------------------- # TOKEN CREATION (NO PAUSE KEY) # ------------------------------------------------------- @@ -75,19 +77,12 @@ def create_token_without_pause_key(client, operator_id, operator_key): def attempt_pause_should_fail(client, token_id, operator_key): print("🔹 Attempting to pause token WITHOUT a pause key... (expected failure)") - tx = ( - TokenPauseTransaction() - .set_token_id(token_id) - .freeze_with(client) - .sign(operator_key) - ) + tx = TokenPauseTransaction().set_token_id(token_id).freeze_with(client).sign(operator_key) receipt = tx.execute(client) if receipt.status == ResponseCode.TOKEN_HAS_NO_PAUSE_KEY: - print( - "✅ Expected failure: token cannot be paused because no pause key exists.\n" - ) + print("✅ Expected failure: token cannot be paused because no pause key exists.\n") else: print(f"❌ Unexpected status: {ResponseCode(receipt.status).name}\n") @@ -130,12 +125,7 @@ def create_token_with_pause_key(client, operator_id, operator_key, pause_key): def pause_token(client, token_id, pause_key): print("🔹 Pausing token...") - tx = ( - TokenPauseTransaction() - .set_token_id(token_id) - .freeze_with(client) - .sign(pause_key) - ) + tx = TokenPauseTransaction().set_token_id(token_id).freeze_with(client).sign(pause_key) receipt = tx.execute(client) if receipt.status == ResponseCode.SUCCESS: @@ -147,12 +137,7 @@ def pause_token(client, token_id, pause_key): def unpause_token(client, token_id, pause_key): print("🔹 Unpausing token...") - tx = ( - TokenUnpauseTransaction() - .set_token_id(token_id) - .freeze_with(client) - .sign(pause_key) - ) + tx = TokenUnpauseTransaction().set_token_id(token_id).freeze_with(client).sign(pause_key) receipt = tx.execute(client) if receipt.status == ResponseCode.SUCCESS: @@ -191,9 +176,7 @@ def create_temp_account(client, operator_key): return account_id, new_key -def test_transfer_while_paused( - client, operator_id, operator_key, recipient_id, token_id -): +def test_transfer_while_paused(client, operator_id, operator_key, recipient_id, token_id): print("🔹 Attempting transfer WHILE token is paused (expected failure)...") tx = ( @@ -227,16 +210,12 @@ def main(): print("\n==================== PART 2 — WITH PAUSE KEY ====================\n") pause_key = PrivateKey.generate_ed25519() - token_with_pause = create_token_with_pause_key( - client, operator_id, operator_key, pause_key - ) + token_with_pause = create_token_with_pause_key(client, operator_id, operator_key, pause_key) pause_token(client, token_with_pause, pause_key) recipient_id, _ = create_temp_account(client, operator_key) - test_transfer_while_paused( - client, operator_id, operator_key, recipient_id, token_with_pause - ) + test_transfer_while_paused(client, operator_id, operator_key, recipient_id, token_with_pause) unpause_token(client, token_with_pause, pause_key) diff --git a/examples/tokens/token_create_transaction_supply_key.py b/examples/tokens/token_create_transaction_supply_key.py index 9f234c341..e16f0fcfc 100644 --- a/examples/tokens/token_create_transaction_supply_key.py +++ b/examples/tokens/token_create_transaction_supply_key.py @@ -16,6 +16,7 @@ Usage: uv run examples/tokens/token_create_transaction_supply_key.py """ + import sys from hiero_sdk_python import ( @@ -36,6 +37,7 @@ def setup_client(): print(f"Client set up with operator id {client.operator_account_id}") return client + def create_token_no_supply_key(client, operator_id, operator_key): """ Create a FUNGIBLE token WITHOUT a supply key. @@ -62,9 +64,7 @@ def create_token_no_supply_key(client, operator_id, operator_key): try: reciept = transaction.execute(client) if reciept.status != ResponseCode.SUCCESS: - print( - f"Token creation failed with status: {ResponseCode(reciept.status).name}" - ) + print(f"Token creation failed with status: {ResponseCode(reciept.status).name}") sys.exit(1) token_id = reciept.token_id @@ -94,9 +94,7 @@ def demonstrate_mint_fail(client, token_id): try: receipt = transaction.execute(client) if receipt.status == ResponseCode.TOKEN_HAS_NO_SUPPLY_KEY: - print( - f" --> Mint failed as expected! Status: {ResponseCode(receipt.status).name}" - ) + print(f" --> Mint failed as expected! Status: {ResponseCode(receipt.status).name}") else: print(f"Mint failed with status: {ResponseCode(receipt.status).name}") @@ -133,9 +131,7 @@ def create_token_with_supply_key(client, operator_id, operator_key): try: receipt = transaction.execute(client) if receipt.status != ResponseCode.SUCCESS: - print( - f"Token creation failed with status: {ResponseCode(receipt.status).name}" - ) + print(f"Token creation failed with status: {ResponseCode(receipt.status).name}") sys.exit(1) token_id = receipt.token_id @@ -195,9 +191,7 @@ def main(): demonstrate_mint_fail(client, token_id_no_key) # 2. Demonstrate Success (With Supply Key) - token_id_with_key, supply_key = create_token_with_supply_key( - client, operator_id, operator_key - ) + token_id_with_key, supply_key = create_token_with_supply_key(client, operator_id, operator_key) demonstrate_mint_success(client, token_id_with_key, supply_key) verify_token_info(client, token_id_with_key) diff --git a/examples/tokens/token_create_transaction_token_fee_schedule_key.py b/examples/tokens/token_create_transaction_token_fee_schedule_key.py index ae417cdd6..bd9dcd7ab 100644 --- a/examples/tokens/token_create_transaction_token_fee_schedule_key.py +++ b/examples/tokens/token_create_transaction_token_fee_schedule_key.py @@ -16,6 +16,7 @@ Then, it attempts to update the custom fees for both tokens to demonstrate the difference. """ + import sys from hiero_sdk_python import Client, PrivateKey @@ -33,6 +34,7 @@ ) from hiero_sdk_python.tokens.token_type import TokenType + # Load environment variables @@ -42,6 +44,7 @@ def setup_client(): print(f"Client set up with operator id {client.operator_account_id}") return client + def create_token_with_fee_key(client, operator_id): """Create a fungible token with a fee_schedule_key.""" print("Creating fungible token with fee_schedule_key...") @@ -116,15 +119,9 @@ def query_token_fees(client, token_id, description): def attempt_fee_update(client, token_id, fee_schedule_key, description): """Attempt to update custom fees for a token.""" print(f"\nAttempting to update fees for {description}...") - new_fees = [ - CustomFixedFee(amount=200, fee_collector_account_id=client.operator_account_id) - ] - - tx = ( - TokenFeeScheduleUpdateTransaction() - .set_token_id(token_id) - .set_custom_fees(new_fees) - ) + new_fees = [CustomFixedFee(amount=200, fee_collector_account_id=client.operator_account_id)] + + tx = TokenFeeScheduleUpdateTransaction().set_token_id(token_id).set_custom_fees(new_fees) if fee_schedule_key: tx.freeze_with(client).sign(fee_schedule_key) @@ -147,31 +144,19 @@ def main(): try: # Create token with fee_schedule_key token_with_key, fee_key = create_token_with_fee_key(client, operator_id) - query_token_fees( - client, token_with_key, "Token with fee_schedule_key (initial)" - ) + query_token_fees(client, token_with_key, "Token with fee_schedule_key (initial)") # Create token without fee_schedule_key token_without_key = create_token_without_fee_key(client, operator_id) - query_token_fees( - client, token_without_key, "Token without fee_schedule_key (initial)" - ) + query_token_fees(client, token_without_key, "Token without fee_schedule_key (initial)") # Attempt updates - attempt_fee_update( - client, token_with_key, fee_key, "token with fee_schedule_key" - ) - attempt_fee_update( - client, token_without_key, None, "token without fee_schedule_key" - ) + attempt_fee_update(client, token_with_key, fee_key, "token with fee_schedule_key") + attempt_fee_update(client, token_without_key, None, "token without fee_schedule_key") # Query final fees - query_token_fees( - client, token_with_key, "Token with fee_schedule_key (after update)" - ) - query_token_fees( - client, token_without_key, "Token without fee_schedule_key (after update)" - ) + query_token_fees(client, token_with_key, "Token with fee_schedule_key (after update)") + query_token_fees(client, token_without_key, "Token without fee_schedule_key (after update)") except Exception as e: print(f"Error during operations: {e}") diff --git a/examples/tokens/token_create_transaction_token_metadata.py b/examples/tokens/token_create_transaction_token_metadata.py index 4ffb3f08f..8be399d72 100644 --- a/examples/tokens/token_create_transaction_token_metadata.py +++ b/examples/tokens/token_create_transaction_token_metadata.py @@ -16,6 +16,7 @@ uv run examples/tokens/token_create_transaction_token_metadata.py python examples/tokens/token_create_transaction_token_metadata.py """ + import sys from hiero_sdk_python import ( @@ -35,6 +36,7 @@ def setup_client(): print(f"Client set up with operator id {client.operator_account_id}") return client + def generate_metadata_key(): """Generate a new metadata key for the token.""" print("\nGenerating a new metadata key for the token...") @@ -79,10 +81,7 @@ def try_update_metadata_without_key(client, operator_key, token_id): updated_metadata = b"updated metadata (without metadata_key)" try: update_transaction = ( - TokenUpdateTransaction() - .set_token_id(token_id) - .set_metadata(updated_metadata) - .freeze_with(client) + TokenUpdateTransaction().set_token_id(token_id).set_metadata(updated_metadata).freeze_with(client) ) update_transaction.sign(operator_key) receipt = update_transaction.execute(client) @@ -129,9 +128,7 @@ def create_token_with_metadata_key(client, metadata_key, operator_id, operator_k sys.exit(1) if receipt.status != ResponseCode.SUCCESS: - print( - f"❌ Token creation failed with status: {ResponseCode(receipt.status).name}" - ) + print(f"❌ Token creation failed with status: {ResponseCode(receipt.status).name}") sys.exit(1) token_id = receipt.token_id @@ -140,7 +137,7 @@ def create_token_with_metadata_key(client, metadata_key, operator_id, operator_k return token_id, metadata_key -def update_metadata_with_key(client, token_id, metadata_key, operator_key): +def update_metadata_with_key(client, token_id, metadata_key): """Update token metadata with metadata_key.""" print(f"\nUpdating token {token_id} metadata WITH metadata_key...") updated_metadata = b"Updated metadata (with key)" @@ -155,9 +152,7 @@ def update_metadata_with_key(client, token_id, metadata_key, operator_key): ) receipt = update_transaction.execute(client) if receipt.status != ResponseCode.SUCCESS: - print( - f"❌ Token update failed with status: {ResponseCode(receipt.status).name}" - ) + print(f"❌ Token update failed with status: {ResponseCode(receipt.status).name}") sys.exit(1) except Exception as e: print(f"Error while freezing update transaction: {e}") @@ -190,9 +185,7 @@ def demonstrate_metadata_length_validation(client, operator_key, operator_id): if receipt.status == ResponseCode.SUCCESS: print("❌ Unexpected success for this operation!") else: - print( - "Error: Expected ValueError for metadata > 100 bytes, but none was raised." - ) + print("Error: Expected ValueError for metadata > 100 bytes, but none was raised.") sys.exit(1) except ValueError as exc: @@ -216,10 +209,8 @@ def create_token_with_metadata(): token_a = create_token_without_metadata_key(client, operator_key, operator_id) try_update_metadata_without_key(client, operator_key, token_a) - token_b, metadata_key = create_token_with_metadata_key( - client, metadata_key, operator_id, operator_key - ) - update_metadata_with_key(client, token_b, metadata_key, operator_key) + token_b, metadata_key = create_token_with_metadata_key(client, metadata_key, operator_id, operator_key) + update_metadata_with_key(client, token_b, metadata_key) demonstrate_metadata_length_validation(client, operator_key, operator_id) diff --git a/examples/tokens/token_create_transaction_wipe_key.py b/examples/tokens/token_create_transaction_wipe_key.py index e1c6688b5..8f5879db7 100644 --- a/examples/tokens/token_create_transaction_wipe_key.py +++ b/examples/tokens/token_create_transaction_wipe_key.py @@ -19,6 +19,7 @@ Usage: uv run examples/tokens/token_create_transaction_wipe_key.py """ + import sys from hiero_sdk_python import ( @@ -44,19 +45,14 @@ def setup_client(): print(f"Client set up with operator id {client.operator_account_id}") return client + def create_recipient_account(client): """Helper: Create a new account to hold tokens(wiped ones).""" private_key = PrivateKey.generate_ed25519() - tx = ( - AccountCreateTransaction() - .set_key_without_alias(private_key.public_key()) - .set_initial_balance(Hbar(2)) - ) + tx = AccountCreateTransaction().set_key_without_alias(private_key.public_key()).set_initial_balance(Hbar(2)) receipt = tx.execute(client) if receipt.status != ResponseCode.SUCCESS: - print( - f"❌ Account creation failed with status: {ResponseCode(receipt.status).name}" - ) + print(f"❌ Account creation failed with status: {ResponseCode(receipt.status).name}") sys.exit(1) print(f"✅ Account created: {receipt.account_id}") @@ -75,9 +71,7 @@ def associate_and_transfer(client, token_id, recipient_id, recipient_key, amount receipt_associate = associate_tx.execute(client) if receipt_associate.status != ResponseCode.SUCCESS: - print( - f"❌ Token association failed with status: {ResponseCode(receipt_associate.status).name}" - ) + print(f"❌ Token association failed with status: {ResponseCode(receipt_associate.status).name}") sys.exit(1) print(f" --> Associated token {token_id} to account {recipient_id}.") @@ -90,9 +84,7 @@ def associate_and_transfer(client, token_id, recipient_id, recipient_key, amount receipt_transfer = transfer_tx.execute(client) if receipt_transfer.status != ResponseCode.SUCCESS: - print( - f"❌ Token transfer failed with status: {ResponseCode(receipt_transfer.status).name}" - ) + print(f"❌ Token transfer failed with status: {ResponseCode(receipt_transfer.status).name}") sys.exit(1) print(f" --> Transferred {amount} tokens to account {recipient_id}.") @@ -117,9 +109,7 @@ def create_token_no_wipe_key(client, operator_id, operator_key): try: receipt = transaction.execute(client) if receipt.status != ResponseCode.SUCCESS: - print( - f"❌ Token creation failed with status: {ResponseCode(receipt.status).name}" - ) + print(f"❌ Token creation failed with status: {ResponseCode(receipt.status).name}") sys.exit(1) print(f"✅ Token created: {receipt.token_id}") @@ -150,9 +140,7 @@ def demonstrate_wipe_fail(client, token_id, target_account_id): f"✅ Wipe failed as expected! Token has no wipe key with status: {ResponseCode(receipt.status).name}." ) else: - print( - f"❌ Wipe unexpectedly succeeded or failed with status: {ResponseCode(receipt.status).name}" - ) + print(f"❌ Wipe unexpectedly succeeded or failed with status: {ResponseCode(receipt.status).name}") except Exception as e: print(f"✅ Wipe failed as expected with error: {e}") @@ -179,9 +167,7 @@ def create_token_with_wipe_key(client, operator_id, operator_key): try: receipt = transaction.execute(client) if receipt.status != ResponseCode.SUCCESS: - print( - f"❌ Token creation failed with status: {ResponseCode(receipt.status).name}" - ) + print(f"❌ Token creation failed with status: {ResponseCode(receipt.status).name}") sys.exit(1) print(f"✅ Token created: {receipt.token_id}") @@ -256,10 +242,7 @@ def demonstrate_nft_wipe_scenario(client, operator_id, operator_key, user_id, us # 2. Mint an NFT (Serial #1) print("Minting NFT Serial #1...") mint_tx = ( - TokenMintTransaction() - .set_token_id(nft_token_id) - .set_metadata([b"Metadata for NFT 1"]) - .freeze_with(client) + TokenMintTransaction().set_token_id(nft_token_id).set_metadata([b"Metadata for NFT 1"]).freeze_with(client) ) mint_tx.sign(supply_key) mint_receipt = mint_tx.execute(client) @@ -310,9 +293,7 @@ def demonstrate_nft_wipe_scenario(client, operator_id, operator_key, user_id, us wipe_receipt = wipe_tx.execute(client) if wipe_receipt.status == ResponseCode.SUCCESS: - print( - "✅ NFT Wipe Successful! The NFT has been effectively burned from the user's account." - ) + print("✅ NFT Wipe Successful! The NFT has been effectively burned from the user's account.") else: print(f"❌ NFT Wipe Failed: {ResponseCode(wipe_receipt.status).name}") @@ -342,9 +323,7 @@ def main(): demonstrate_wipe_fail(client, token_id_no_key, user_id) # --- Scenario 2: With Wipe Key (Fungible) --- - token_id_with_key, wipe_key = create_token_with_wipe_key( - client, operator_id, operator_key - ) + token_id_with_key, wipe_key = create_token_with_wipe_key(client, operator_id, operator_key) associate_and_transfer(client, token_id_with_key, user_id, user_key, 50) if demonstrate_wipe_success(client, token_id_with_key, user_id, wipe_key): diff --git a/examples/tokens/token_delete_transaction.py b/examples/tokens/token_delete_transaction.py index 1c46c2ce0..b64f76ff7 100644 --- a/examples/tokens/token_delete_transaction.py +++ b/examples/tokens/token_delete_transaction.py @@ -19,6 +19,7 @@ def setup_client(): print(f"Client set up with operator id {client.operator_account_id}") return client + def generate_admin_key(): """ diff --git a/examples/tokens/token_dissociate_transaction.py b/examples/tokens/token_dissociate_transaction.py index a08803d3a..29b2087f6 100644 --- a/examples/tokens/token_dissociate_transaction.py +++ b/examples/tokens/token_dissociate_transaction.py @@ -1,6 +1,7 @@ # uv run examples/tokens/token_dissociate_transaction.py # python examples/tokens/token_dissociate_transaction.py """A full example that creates an account, two tokens, associates them, and finally dissociates them.""" + import os import sys @@ -26,8 +27,6 @@ def setup_client(): return client - - def create_new_account(client, operator_id, operator_key): """Create a new account to associate/dissociate with tokens.""" print("\nSTEP 1: Creating a new account...") @@ -79,14 +78,10 @@ def create_token(client, operator_key, recipient_id, recipient_key, operator_id) .set_initial_supply(1) .set_treasury_account_id(operator_id) ) - fungible_receipt = ( - fungible_tx.freeze_with(client).sign(operator_key).execute(client) - ) + fungible_receipt = fungible_tx.freeze_with(client).sign(operator_key).execute(client) fungible_token_id = fungible_receipt.token_id - print( - f"✅ Success! Created NFT token: {nft_token_id} and fungible token: {fungible_token_id}" - ) + print(f"✅ Success! Created NFT token: {nft_token_id} and fungible token: {fungible_token_id}") return client, nft_token_id, fungible_token_id, recipient_id, recipient_key except Exception as e: @@ -94,21 +89,15 @@ def create_token(client, operator_key, recipient_id, recipient_key, operator_id) sys.exit(1) -def token_associate( - client, nft_token_id, fungible_token_id, recipient_id, recipient_key -): +def token_associate(client, nft_token_id, fungible_token_id, recipient_id, recipient_key): """ Associate the tokens with the new account. Note: Tokens must be associated with an account before they can be used or dissociated. Association is a prerequisite for holding, transferring, or later dissociating tokens. """ - print( - f"\nSTEP 3: Associating NFT and fungible tokens with account {recipient_id}..." - ) - print( - "Note: Tokens must be associated with an account before they can be used or dissociated." - ) + print(f"\nSTEP 3: Associating NFT and fungible tokens with account {recipient_id}...") + print("Note: Tokens must be associated with an account before they can be used or dissociated.") try: receipt = ( TokenAssociateTransaction() @@ -130,21 +119,14 @@ def verify_dissociation(client, nft_token_id, fungible_token_id, recipient_id): """Verify that the specified tokens are dissociated from the account.""" print("\nVerifying token dissociation...") info = AccountInfoQuery().set_account_id(recipient_id).execute(client) - associated_tokens = [ - rel.token_id for rel in getattr(info, "token_relationships", []) - ] - if ( - nft_token_id not in associated_tokens - and fungible_token_id not in associated_tokens - ): + associated_tokens = [rel.token_id for rel in getattr(info, "token_relationships", [])] + if nft_token_id not in associated_tokens and fungible_token_id not in associated_tokens: print("✅ Verified: Both tokens are dissociated from the account.") else: print("❌ Verification failed: Some tokens are still associated.") -def token_dissociate( - client, nft_token_id, fungible_token_id, recipient_id, recipient_key -): +def token_dissociate(client, nft_token_id, fungible_token_id, recipient_id, recipient_key): """ Dissociate the tokens from the new account. @@ -154,9 +136,7 @@ def token_dissociate( - For security or privacy reasons - To comply with business or regulatory requirements """ - print( - f"\nSTEP 4: Dissociating NFT and fungible tokens from account {recipient_id}..." - ) + print(f"\nSTEP 4: Dissociating NFT and fungible tokens from account {recipient_id}...") try: receipt = ( TokenDissociateTransaction() @@ -194,12 +174,8 @@ def main(): client, nft_token_id, fungible_token_id, recipient_id, recipient_key = create_token( client, operator_key, recipient_id, recipient_key, operator_id ) - token_associate( - client, nft_token_id, fungible_token_id, recipient_id, recipient_key - ) - token_dissociate( - client, nft_token_id, fungible_token_id, recipient_id, recipient_key - ) + token_associate(client, nft_token_id, fungible_token_id, recipient_id, recipient_key) + token_dissociate(client, nft_token_id, fungible_token_id, recipient_id, recipient_key) # Optional: Verify dissociation verify_dissociation(client, nft_token_id, fungible_token_id, recipient_id) diff --git a/examples/tokens/token_fee_schedule_update_transaction_fungible.py b/examples/tokens/token_fee_schedule_update_transaction_fungible.py index 573060802..f556e06db 100644 --- a/examples/tokens/token_fee_schedule_update_transaction_fungible.py +++ b/examples/tokens/token_fee_schedule_update_transaction_fungible.py @@ -5,6 +5,7 @@ uv run examples/tokens/token_fee_schedule_update_transaction_fungible.py python examples/tokens/token_fee_schedule_update_transaction_fungible.py """ + import sys from hiero_sdk_python import Client @@ -29,6 +30,7 @@ def setup_client(): print(f"Client set up with operator id {client.operator_account_id}") return client + def create_fungible_token(client, operator_id, fee_schedule_key): """Create a fungible token with only a fee schedule key.""" print(" Creating fungible token...") @@ -69,11 +71,7 @@ def update_custom_fixed_fee(client, token_id, fee_schedule_key, treasury_account CustomFixedFee(amount=150, fee_collector_account_id=treasury_account_id) ] print(f" Defined {len(new_fees)} new custom fees.\n") - tx = ( - TokenFeeScheduleUpdateTransaction() - .set_token_id(token_id) - .set_custom_fees(new_fees) - ) + tx = TokenFeeScheduleUpdateTransaction().set_token_id(token_id).set_custom_fees(new_fees) # The transaction MUST be signed by the fee_schedule_key tx.freeze_with(client).sign(fee_schedule_key) @@ -110,9 +108,7 @@ def query_token_info(client, token_id): print(f"Found {len(custom_fees)} custom fee(s):") for i, fee in enumerate(custom_fees, 1): print(f" Fee #{i}: {type(fee).__name__}") - print( - f" Collector: {getattr(fee, 'fee_collector_account_id', 'N/A')}" - ) + print(f" Collector: {getattr(fee, 'fee_collector_account_id', 'N/A')}") if isinstance(fee, CustomFixedFee): print(f" Amount: {getattr(fee, 'amount', 'N/A')}") else: diff --git a/examples/tokens/token_fee_schedule_update_transaction_nft.py b/examples/tokens/token_fee_schedule_update_transaction_nft.py index c27d09f43..275eb4f1b 100644 --- a/examples/tokens/token_fee_schedule_update_transaction_nft.py +++ b/examples/tokens/token_fee_schedule_update_transaction_nft.py @@ -5,6 +5,7 @@ uv run examples/tokens/token_fee_schedule_update_transaction_nft.py python examples/tokens/token_fee_schedule_update_transaction_nft.py """ + import sys from hiero_sdk_python import Client @@ -29,6 +30,7 @@ def setup_client(): print(f"Client set up with operator id {client.operator_account_id}") return client + def create_nft(client, operator_id, supply_key, fee_schedule_key): """Create an NFT with supply and fee schedule keys.""" print(" Creating NFT...") @@ -76,11 +78,7 @@ def update_custom_royalty_fee(client, token_id, fee_schedule_key, collector_acco ) ] print(f" Defined {len(new_fees)} new custom fees.\n") - tx = ( - TokenFeeScheduleUpdateTransaction() - .set_token_id(token_id) - .set_custom_fees(new_fees) - ) + tx = TokenFeeScheduleUpdateTransaction().set_token_id(token_id).set_custom_fees(new_fees) tx.freeze_with(client).sign(fee_schedule_key) @@ -116,9 +114,7 @@ def query_token_info(client, token_id): print(f"Found {len(custom_fees)} custom fee(s):") for i, fee in enumerate(custom_fees, 1): print(f" Fee #{i}: {type(fee).__name__}") - print( - f" Collector: {getattr(fee, 'fee_collector_account_id', 'N/A')}" - ) + print(f" Collector: {getattr(fee, 'fee_collector_account_id', 'N/A')}") if isinstance(fee, CustomRoyaltyFee): print(f" Royalty: {fee.numerator}/{fee.denominator}") else: @@ -132,7 +128,7 @@ def query_token_info(client, token_id): def main(): - + client = setup_client() operator_id = client.operator_account_id operator_key = client.operator_private_key diff --git a/examples/tokens/token_freeze_transaction.py b/examples/tokens/token_freeze_transaction.py index 6365dd758..ae7c1eb2c 100644 --- a/examples/tokens/token_freeze_transaction.py +++ b/examples/tokens/token_freeze_transaction.py @@ -8,6 +8,7 @@ uv run examples/tokens/token_freeze_transaction.py python examples/tokens/token_freeze_transaction.py """ + import os import sys @@ -56,12 +57,7 @@ def create_freezeable_token(client, operator_id, operator_key): .set_freeze_key(freeze_key) ) - receipt = ( - tx.freeze_with(client) - .sign(operator_key) - .sign(freeze_key) - .execute(client) - ) + receipt = tx.freeze_with(client).sign(operator_key).sign(freeze_key).execute(client) token_id = receipt.token_id print(f"✅ Success! Created token with ID: {token_id}") @@ -87,9 +83,7 @@ def freeze_token(token_id, client, operator_id, freeze_key): .execute(client) ) - print( - f"✅ Success! Token freeze complete. Status: {ResponseCode(receipt.status).name}" - ) + print(f"✅ Success! Token freeze complete. Status: {ResponseCode(receipt.status).name}") except Exception as e: print(f"❌ Error freezing token: {e}") @@ -113,13 +107,9 @@ def verify_freeze(token_id, client, operator_id, operator_key): status_name = ResponseCode(transfer_receipt.status).name if status_name == "ACCOUNT_FROZEN_FOR_TOKEN": - print( - f"✅ Verified: Transfer blocked as expected due to freeze. Status: {status_name}" - ) + print(f"✅ Verified: Transfer blocked as expected due to freeze. Status: {status_name}") elif status_name == "SUCCESS": - print( - "❌ Error: Transfer succeeded, but should have failed because the account is frozen." - ) + print("❌ Error: Transfer succeeded, but should have failed because the account is frozen.") else: print(f"❌ Unexpected transfer result. Status: {status_name}") @@ -137,9 +127,7 @@ def main(): """ client, operator_id, operator_key = setup_client() - freeze_key, token_id, client, operator_id, operator_key = create_freezeable_token( - client, operator_id, operator_key - ) + freeze_key, token_id, client, operator_id, operator_key = create_freezeable_token(client, operator_id, operator_key) freeze_token(token_id, client, operator_id, freeze_key) verify_freeze(token_id, client, operator_id, operator_key) diff --git a/examples/tokens/token_grant_kyc_transaction.py b/examples/tokens/token_grant_kyc_transaction.py index 8bcc3fd92..1f54ffca2 100644 --- a/examples/tokens/token_grant_kyc_transaction.py +++ b/examples/tokens/token_grant_kyc_transaction.py @@ -5,6 +5,7 @@ uv run examples/tokens/token_grant_kyc_transaction.py python examples/tokens/token_grant_kyc_transaction.py """ + import sys from hiero_sdk_python import ( @@ -29,6 +30,7 @@ def setup_client(): print(f"Client set up with operator id {client.operator_account_id}") return client + def create_fungible_token(client, operator_id, operator_key, kyc_private_key): """Create a fungible token.""" receipt = ( @@ -43,16 +45,12 @@ def create_fungible_token(client, operator_id, operator_key, kyc_private_key): .set_max_supply(1000) .set_admin_key(operator_key) .set_supply_key(operator_key) - .set_kyc_key( - kyc_private_key - ) # Required key for granting KYC approval to accounts + .set_kyc_key(kyc_private_key) # Required key for granting KYC approval to accounts .execute(client) ) if receipt.status != ResponseCode.SUCCESS: - print( - f"Fungible token creation failed with status: {ResponseCode(receipt.status).name}" - ) + print(f"Fungible token creation failed with status: {ResponseCode(receipt.status).name}") sys.exit(1) token_id = receipt.token_id @@ -74,9 +72,7 @@ def associate_token(client, token_id, account_id, account_private_key): receipt = associate_transaction.execute(client) if receipt.status != ResponseCode.SUCCESS: - print( - f"Token association failed with status: {ResponseCode(receipt.status).name}" - ) + print(f"Token association failed with status: {ResponseCode(receipt.status).name}") sys.exit(1) print("Token successfully associated with account") @@ -100,9 +96,7 @@ def create_test_account(client): # Check if account creation was successful if receipt.status != ResponseCode.SUCCESS: - print( - f"Account creation failed with status: {ResponseCode(receipt.status).name}" - ) + print(f"Account creation failed with status: {ResponseCode(receipt.status).name}") sys.exit(1) # Get account ID from receipt @@ -150,9 +144,7 @@ def token_grant_kyc(): # Check if the transaction was successful if receipt.status != ResponseCode.SUCCESS: - print( - f"Token grant KYC failed with status: {ResponseCode(receipt.status).name}" - ) + print(f"Token grant KYC failed with status: {ResponseCode(receipt.status).name}") sys.exit(1) print(f"Granted KYC for account {account_id} on token {token_id}") diff --git a/examples/tokens/token_mint_transaction_fungible.py b/examples/tokens/token_mint_transaction_fungible.py index ffa81f798..41071e724 100644 --- a/examples/tokens/token_mint_transaction_fungible.py +++ b/examples/tokens/token_mint_transaction_fungible.py @@ -6,6 +6,7 @@ python examples/tokens/token_mint_transaction_fungible.py Creates a mintable fungible token and then mints additional supply. """ + import os import sys @@ -25,6 +26,7 @@ def setup_client(): print(f"Client set up with operator id {client.operator_account_id}") return client + def generate_supply_key(): """Generate a new supply key for the token.""" print("\nSTEP 1: Generating a new supply key...") @@ -103,9 +105,7 @@ def token_mint_fungible(client, token_id, supply_key): .sign(supply_key) # Must be signed by the supply key .execute(client) ) - print( - f"✅ Success! Token minting complete, Status: {ResponseCode(receipt.status).name}" - ) + print(f"✅ Success! Token minting complete, Status: {ResponseCode(receipt.status).name}") # Confirm total supply after minting info_after = TokenInfoQuery().set_token_id(token_id).execute(client) diff --git a/examples/tokens/token_mint_transaction_non_fungible.py b/examples/tokens/token_mint_transaction_non_fungible.py index e19b84a8b..59cfb51a9 100644 --- a/examples/tokens/token_mint_transaction_non_fungible.py +++ b/examples/tokens/token_mint_transaction_non_fungible.py @@ -7,6 +7,7 @@ uv run examples/token_mint_transaction_non_fungible.py python examples/token_mint_transaction_non_fungible.py """ + import os import sys @@ -98,9 +99,7 @@ def token_mint_non_fungible(client, token_id, supply_key): ) # THE FIX: The receipt confirms status, it does not contain serial numbers. - print( - f"✅ Success! NFT minting complete, Status: {ResponseCode(receipt.status).name}" - ) + print(f"✅ Success! NFT minting complete, Status: {ResponseCode(receipt.status).name}") # Confirm total supply after minting info_after = TokenInfoQuery().set_token_id(token_id).execute(client) print(f"Total supply after minting: {info_after.total_supply}") diff --git a/examples/tokens/token_pause_transaction.py b/examples/tokens/token_pause_transaction.py index 4e6e84668..65e9ab9b9 100644 --- a/examples/tokens/token_pause_transaction.py +++ b/examples/tokens/token_pause_transaction.py @@ -5,6 +5,7 @@ uv run examples/tokens/token_pause_transaction.py python examples/tokens/token_pause_transaction.py """ + from hiero_sdk_python import Client from hiero_sdk_python.query.token_info_query import TokenInfoQuery from hiero_sdk_python.response_code import ResponseCode @@ -21,6 +22,7 @@ def setup_client(): print(f"Client set up with operator id {client.operator_account_id}") return client + def assert_success(receipt, action: str): """ @@ -70,12 +72,7 @@ def create_token(client, operator_id, admin_key, pause_key): def pause_token(client, token_id, pause_key): """Pause token.""" # Note: This requires the pause key that was specified during token creation - pause_transaction = ( - TokenPauseTransaction() - .set_token_id(token_id) - .freeze_with(client) - .sign(pause_key) - ) + pause_transaction = TokenPauseTransaction().set_token_id(token_id).freeze_with(client).sign(pause_key) receipt = pause_transaction.execute(client) assert_success(receipt, "Token pause") @@ -92,12 +89,7 @@ def check_pause_status(client, token_id): def delete_token(client, token_id, admin_key): """Delete token.""" # Note: This requires the admin key that was specified during token creation - delete_transaction = ( - TokenDeleteTransaction() - .set_token_id(token_id) - .freeze_with(client) - .sign(admin_key) - ) + delete_transaction = TokenDeleteTransaction().set_token_id(token_id).freeze_with(client).sign(admin_key) receipt = delete_transaction.execute(client) assert_success(receipt, "Token delete") @@ -133,9 +125,7 @@ def token_pause(): # Try deleting token with admin key – should fail with TOKEN_IS_PAUSED try: delete_token(client, token_id, admin_key) - print( - "❌ Whoops, delete succeeded—but it should have failed on a paused token!" - ) + print("❌ Whoops, delete succeeded—but it should have failed on a paused token!") except RuntimeError as e: print(f"✅ Unable to delete token as expected as it is paused: {e}") diff --git a/examples/tokens/token_reject_transaction_fungible_token.py b/examples/tokens/token_reject_transaction_fungible_token.py index e552e4f6e..5ac93180a 100644 --- a/examples/tokens/token_reject_transaction_fungible_token.py +++ b/examples/tokens/token_reject_transaction_fungible_token.py @@ -5,6 +5,7 @@ uv run examples/tokens/token_reject_transaction_fungible_token.py python examples/tokens/token_reject_transaction_fungible_token.py """ + import sys from hiero_sdk_python import ( @@ -31,6 +32,7 @@ def setup_client(): print(f"Client set up with operator id {client.operator_account_id}") return client + def create_test_account(client): """Create a new account for testing.""" # Generate private key for new account @@ -47,9 +49,7 @@ def create_test_account(client): # Check if account creation was successful if receipt.status != ResponseCode.SUCCESS: - print( - f"Account creation failed with status: {ResponseCode(receipt.status).name}" - ) + print(f"Account creation failed with status: {ResponseCode(receipt.status).name}") sys.exit(1) # Get account ID from receipt @@ -59,7 +59,7 @@ def create_test_account(client): return account_id, new_account_private_key -def create_fungible_token(client: "Client", treasury_id, treasury_private_key): +def create_fungible_token(client: Client, treasury_id, treasury_private_key): """Create a fungible token.""" receipt = ( TokenCreateTransaction() @@ -82,9 +82,7 @@ def create_fungible_token(client: "Client", treasury_id, treasury_private_key): ) if receipt.status != ResponseCode.SUCCESS: - print( - f"Fungible token creation failed with status: {ResponseCode(receipt.status).name}" - ) + print(f"Fungible token creation failed with status: {ResponseCode(receipt.status).name}") sys.exit(1) token_id = receipt.token_id @@ -106,17 +104,13 @@ def associate_token(client, receiver_id, token_id, receiver_private_key): ) if receipt.status != ResponseCode.SUCCESS: - print( - f"Token association failed with status: {ResponseCode(receipt.status).name}" - ) + print(f"Token association failed with status: {ResponseCode(receipt.status).name}") sys.exit(1) print(f"Token successfully associated with account: {receiver_id}") -def transfer_tokens( - client, treasury_id, treasury_private_key, receiver_id, token_id, amount=10 -): +def transfer_tokens(client, treasury_id, treasury_private_key, receiver_id, token_id, amount=10): """Transfer tokens to the receiver account so we can later reject them.""" # Transfer tokens to the receiver account receipt = ( @@ -138,19 +132,11 @@ def transfer_tokens( def get_token_balances(client, treasury_id, receiver_id, token_id): """Get token balances for both accounts.""" - token_balance = ( - CryptoGetAccountBalanceQuery().set_account_id(treasury_id).execute(client) - ) - print( - f"Token balance of treasury {treasury_id}: {token_balance.token_balances[token_id]}" - ) + token_balance = CryptoGetAccountBalanceQuery().set_account_id(treasury_id).execute(client) + print(f"Token balance of treasury {treasury_id}: {token_balance.token_balances[token_id]}") - receiver_token_balance = ( - CryptoGetAccountBalanceQuery().set_account_id(receiver_id).execute(client) - ) - print( - f"Token balance of receiver {receiver_id}: {receiver_token_balance.token_balances[token_id]}" - ) + receiver_token_balance = CryptoGetAccountBalanceQuery().set_account_id(receiver_id).execute(client) + print(f"Token balance of receiver {receiver_id}: {receiver_token_balance.token_balances[token_id]}") def token_reject_fungible(): @@ -194,9 +180,7 @@ def token_reject_fungible(): ) if receipt.status != ResponseCode.SUCCESS: - print( - f"Token rejection failed with status: {ResponseCode(receipt.status).name}" - ) + print(f"Token rejection failed with status: {ResponseCode(receipt.status).name}") sys.exit(1) print(f"Successfully rejected token {token_id} from account {receiver_id}") diff --git a/examples/tokens/token_reject_transaction_nft.py b/examples/tokens/token_reject_transaction_nft.py index 7ba6b91e4..e28bed83f 100644 --- a/examples/tokens/token_reject_transaction_nft.py +++ b/examples/tokens/token_reject_transaction_nft.py @@ -5,6 +5,7 @@ uv run examples/tokens/token_reject_transaction_nft.py python examples/tokens/token_reject_transaction_nft.py """ + import sys from hiero_sdk_python import ( @@ -33,6 +34,7 @@ def setup_client(): print(f"Client set up with operator id {client.operator_account_id}") return client + def create_test_account(client): """Create a new account for testing.""" # Generate private key for new account @@ -49,9 +51,7 @@ def create_test_account(client): # Check if account creation was successful if receipt.status != ResponseCode.SUCCESS: - print( - f"Account creation failed with status: {ResponseCode(receipt.status).name}" - ) + print(f"Account creation failed with status: {ResponseCode(receipt.status).name}") sys.exit(1) # Get account ID from receipt @@ -102,9 +102,7 @@ def mint_nfts(client, nft_token_id, metadata_list, treasury_private_key): .set_token_id(nft_token_id) .set_metadata(metadata_list) .freeze_with(client) - .sign( - treasury_private_key - ) # Has to be signed here by treasury's key because they own the supply key + .sign(treasury_private_key) # Has to be signed here by treasury's key because they own the supply key .execute(client) ) @@ -114,9 +112,7 @@ def mint_nfts(client, nft_token_id, metadata_list, treasury_private_key): print(f"NFT minted with serial numbers: {receipt.serial_numbers}") - return [ - NftId(nft_token_id, serial_number) for serial_number in receipt.serial_numbers - ] + return [NftId(nft_token_id, serial_number) for serial_number in receipt.serial_numbers] def associate_token(client, receiver_id, nft_token_id, receiver_private_key): @@ -132,9 +128,7 @@ def associate_token(client, receiver_id, nft_token_id, receiver_private_key): ) if receipt.status != ResponseCode.SUCCESS: - print( - f"Token association failed with status: {ResponseCode(receipt.status).name}" - ) + print(f"Token association failed with status: {ResponseCode(receipt.status).name}") sys.exit(1) print(f"Token successfully associated with account: {receiver_id}") @@ -162,19 +156,11 @@ def transfer_nfts(client, treasury_id, treasury_private_key, receiver_id, nft_id def get_nft_balances(client, treasury_id, receiver_id, nft_token_id): """Get NFT balances for both accounts.""" - token_balance = ( - CryptoGetAccountBalanceQuery().set_account_id(treasury_id).execute(client) - ) - print( - f"NFT balance of treasury {treasury_id}: {token_balance.token_balances[nft_token_id]}" - ) + token_balance = CryptoGetAccountBalanceQuery().set_account_id(treasury_id).execute(client) + print(f"NFT balance of treasury {treasury_id}: {token_balance.token_balances[nft_token_id]}") - receiver_token_balance = ( - CryptoGetAccountBalanceQuery().set_account_id(receiver_id).execute(client) - ) - print( - f"NFT balance of receiver {receiver_id}: {receiver_token_balance.token_balances[nft_token_id]}" - ) + receiver_token_balance = CryptoGetAccountBalanceQuery().set_account_id(receiver_id).execute(client) + print(f"NFT balance of receiver {receiver_id}: {receiver_token_balance.token_balances[nft_token_id]}") def token_reject_nft(): @@ -229,9 +215,7 @@ def token_reject_nft(): print(f"NFT rejection failed with status: {ResponseCode(receipt.status).name}") sys.exit(1) - print( - f"Successfully rejected NFTs {nft_ids[0]} and {nft_ids[1]} from account {receiver_id}" - ) + print(f"Successfully rejected NFTs {nft_ids[0]} and {nft_ids[1]} from account {receiver_id}") # Get and print NFT balances after rejection to show the final state print("\nNFT balances after rejection:") diff --git a/examples/tokens/token_revoke_kyc_transaction.py b/examples/tokens/token_revoke_kyc_transaction.py index 358e9b1c1..6e446d140 100644 --- a/examples/tokens/token_revoke_kyc_transaction.py +++ b/examples/tokens/token_revoke_kyc_transaction.py @@ -5,6 +5,7 @@ uv run examples/tokens/token_revoke_kyc_transaction.py python examples/tokens/token_revoke_kyc_transaction.py """ + import sys from hiero_sdk_python import ( @@ -32,6 +33,7 @@ def setup_client(): print(f"Client set up with operator id {client.operator_account_id}") return client + def create_fungible_token(client, operator_id, operator_key, kyc_private_key): """Create a fungible token.""" receipt = ( @@ -46,16 +48,12 @@ def create_fungible_token(client, operator_id, operator_key, kyc_private_key): .set_max_supply(1000) .set_admin_key(operator_key) .set_supply_key(operator_key) - .set_kyc_key( - kyc_private_key - ) # Required key for granting/revoking KYC approval to accounts + .set_kyc_key(kyc_private_key) # Required key for granting/revoking KYC approval to accounts .execute(client) ) if receipt.status != ResponseCode.SUCCESS: - print( - f"Fungible token creation failed with status: {ResponseCode(receipt.status).name}" - ) + print(f"Fungible token creation failed with status: {ResponseCode(receipt.status).name}") sys.exit(1) token_id = receipt.token_id @@ -77,9 +75,7 @@ def associate_token(client, token_id, account_id, account_private_key): receipt = associate_transaction.execute(client) if receipt.status != ResponseCode.SUCCESS: - print( - f"Token association failed with status: {ResponseCode(receipt.status).name}" - ) + print(f"Token association failed with status: {ResponseCode(receipt.status).name}") sys.exit(1) print("Token successfully associated with account") @@ -103,9 +99,7 @@ def create_test_account(client): # Check if account creation was successful if receipt.status != ResponseCode.SUCCESS: - print( - f"Account creation failed with status: {ResponseCode(receipt.status).name}" - ) + print(f"Account creation failed with status: {ResponseCode(receipt.status).name}") sys.exit(1) # Get account ID from receipt @@ -127,9 +121,7 @@ def grant_kyc(client, token_id, account_id, kyc_private_key): ) if receipt.status != ResponseCode.SUCCESS: - print( - f"Token grant KYC failed with status: {ResponseCode(receipt.status).name}" - ) + print(f"Token grant KYC failed with status: {ResponseCode(receipt.status).name}") sys.exit(1) print(f"Granted KYC for account {account_id} on token {token_id}") @@ -177,9 +169,7 @@ def token_revoke_kyc(): # Check if the transaction was successful if receipt.status != ResponseCode.SUCCESS: - print( - f"Token revoke KYC failed with status: {ResponseCode(receipt.status).name}" - ) + print(f"Token revoke KYC failed with status: {ResponseCode(receipt.status).name}") sys.exit(1) print(f"Revoked KYC for account {account_id} on token {token_id}") diff --git a/examples/tokens/token_unfreeze_transaction.py b/examples/tokens/token_unfreeze_transaction.py index 61f7b4510..d5c68d076 100644 --- a/examples/tokens/token_unfreeze_transaction.py +++ b/examples/tokens/token_unfreeze_transaction.py @@ -8,6 +8,7 @@ python examples/tokens/token_unfreeze_transaction.py """ + import os import sys @@ -28,6 +29,7 @@ def setup_client(): print(f"Client set up with operator id {client.operator_account_id}") return client + def generate_freeze_key(): """Generate a Freeze Key on the fly.""" print("\nSTEP 1: Generating a new freeze key...") @@ -54,9 +56,7 @@ def create_freezable_token(client): ) # FIX: The .execute() method returns the receipt directly. - receipt = ( - tx.freeze_with(client).sign(operator_key).sign(freeze_key).execute(client) - ) + receipt = tx.freeze_with(client).sign(operator_key).sign(freeze_key).execute(client) token_id = receipt.token_id print(f"✅ Success! Created token with ID: {token_id}") return token_id, operator_id, freeze_key, operator_key @@ -77,9 +77,7 @@ def freeze_token(token_id, client, operator_id, freeze_key): .sign(freeze_key) .execute(client) ) - print( - f"✅ Success! Token freeze complete, Status: {ResponseCode(receipt.status).name}" - ) + print(f"✅ Success! Token freeze complete, Status: {ResponseCode(receipt.status).name}") except (RuntimeError, ValueError) as e: print(f"❌ Error freezing token: {e}") sys.exit(1) @@ -88,9 +86,7 @@ def freeze_token(token_id, client, operator_id, freeze_key): def unfreeze_token(token_id, client, operator_id, freeze_key, operator_key): """Unfreeze the token for the operator account.""" # Step 1: Unfreeze the token for the operator account - print( - f"\nSTEP 4: Unfreezing token {token_id} for operator account {operator_id}..." - ) + print(f"\nSTEP 4: Unfreezing token {token_id} for operator account {operator_id}...") try: receipt = ( TokenUnfreezeTransaction() @@ -100,9 +96,7 @@ def unfreeze_token(token_id, client, operator_id, freeze_key, operator_key): .sign(freeze_key) .execute(client) ) - print( - f"✅ Success! Token unfreeze complete, Status: {ResponseCode(receipt.status).name}" - ) + print(f"✅ Success! Token unfreeze complete, Status: {ResponseCode(receipt.status).name}") # Step 2: Attempt a test transfer of 1 unit of token to self print(f"Attempting a test transfer of 1 unit of token {token_id} to self...") diff --git a/examples/tokens/token_unpause_transaction.py b/examples/tokens/token_unpause_transaction.py index 0d2160965..0a39a737f 100644 --- a/examples/tokens/token_unpause_transaction.py +++ b/examples/tokens/token_unpause_transaction.py @@ -5,6 +5,7 @@ uv run examples/tokens/token_unpause_transaction.py python examples/tokens/token_unpause_transaction.py """ + import sys from hiero_sdk_python import ( @@ -27,6 +28,7 @@ def setup_client(): print(f"Client set up with operator id {client.operator_account_id}") return client + def create_token( client: Client, operator_id: AccountId, @@ -64,12 +66,7 @@ def pause_token(client: Client, token_id: TokenId, pause_key: PrivateKey): print("\nAttempting to pause the token...") try: - pause_tx = ( - TokenPauseTransaction() - .set_token_id(token_id) - .freeze_with(client) - .sign(pause_key) - ) + pause_tx = TokenPauseTransaction().set_token_id(token_id).freeze_with(client).sign(pause_key) receipt = pause_tx.execute(client) @@ -102,12 +99,7 @@ def unpause_token(): print("\nAttempting to Unpause the token...") - unpause_tx = ( - TokenUnpauseTransaction() - .set_token_id(token_id) - .freeze_with(client) - .sign(pause_key) - ) + unpause_tx = TokenUnpauseTransaction().set_token_id(token_id).freeze_with(client).sign(pause_key) receipt = unpause_tx.execute(client) try: diff --git a/examples/tokens/token_update_nfts_transaction_nfts.py b/examples/tokens/token_update_nfts_transaction_nfts.py index 8d95ca99d..ab399e097 100644 --- a/examples/tokens/token_update_nfts_transaction_nfts.py +++ b/examples/tokens/token_update_nfts_transaction_nfts.py @@ -5,6 +5,7 @@ uv run examples/tokens/token_update_transaction_nfts.py python examples/tokens/token_update_transaction_nfts.py """ + import sys from hiero_sdk_python import ( @@ -29,6 +30,7 @@ def setup_client(): print(f"Client set up with operator id {client.operator_account_id}") return client + def create_nft(client, operator_id, operator_key, metadata_key): """Create a non-fungible token.""" receipt = ( @@ -62,12 +64,7 @@ def create_nft(client, operator_id, operator_key, metadata_key): def mint_nfts(client, nft_token_id, metadata_list): """Mint a non-fungible token.""" - receipt = ( - TokenMintTransaction() - .set_token_id(nft_token_id) - .set_metadata(metadata_list) - .execute(client) - ) + receipt = TokenMintTransaction().set_token_id(nft_token_id).set_metadata(metadata_list).execute(client) if receipt.status != ResponseCode.SUCCESS: print(f"NFT minting failed with status: {ResponseCode(receipt.status).name}") @@ -75,9 +72,7 @@ def mint_nfts(client, nft_token_id, metadata_list): print(f"NFT minted with serial numbers: {receipt.serial_numbers}") - return [ - NftId(nft_token_id, serial_number) for serial_number in receipt.serial_numbers - ], receipt.serial_numbers + return [NftId(nft_token_id, serial_number) for serial_number in receipt.serial_numbers], receipt.serial_numbers def get_nft_info(client, nft_id): @@ -85,10 +80,7 @@ def get_nft_info(client, nft_id): return TokenNftInfoQuery().set_nft_id(nft_id).execute(client) - -def update_nft_metadata( - client, nft_token_id, serial_numbers, new_metadata, metadata_private_key -): +def update_nft_metadata(client, nft_token_id, serial_numbers, new_metadata, metadata_private_key): """Update metadata for NFTs in a collection.""" receipt = ( TokenUpdateNftsTransaction() @@ -101,14 +93,10 @@ def update_nft_metadata( ) if receipt.status != ResponseCode.SUCCESS: - print( - f"NFT metadata update failed with status: {ResponseCode(receipt.status).name}" - ) + print(f"NFT metadata update failed with status: {ResponseCode(receipt.status).name}") sys.exit(1) - print( - f"Successfully updated metadata for NFTs with serial numbers: {serial_numbers}" - ) + print(f"Successfully updated metadata for NFTs with serial numbers: {serial_numbers}") def token_update_nfts(): diff --git a/examples/tokens/token_update_transaction_fungible.py b/examples/tokens/token_update_transaction_fungible.py index cd313294c..2f8563183 100644 --- a/examples/tokens/token_update_transaction_fungible.py +++ b/examples/tokens/token_update_transaction_fungible.py @@ -5,6 +5,7 @@ uv run examples/tokens/token_update_transaction_fungible.py python examples/tokens/token_update_transaction_fungible.py """ + import sys from hiero_sdk_python import ( @@ -25,6 +26,7 @@ def setup_client(): print(f"Client set up with operator id {client.operator_account_id}") return client + def create_fungible_token(client, operator_id, operator_key, metadata_key): """ Create a fungible token. @@ -55,9 +57,7 @@ def create_fungible_token(client, operator_id, operator_key, metadata_key): # Check if token creation was successful if receipt.status != ResponseCode.SUCCESS: - print( - f"Fungible token creation failed with status: {ResponseCode(receipt.status).name}" - ) + print(f"Fungible token creation failed with status: {ResponseCode(receipt.status).name}") sys.exit(1) # Get token ID from receipt @@ -72,7 +72,6 @@ def get_token_info(client, token_id): return TokenInfoQuery().set_token_id(token_id).execute(client) - def update_token_data( client, token_id, @@ -93,9 +92,7 @@ def update_token_data( ) if receipt.status != ResponseCode.SUCCESS: - print( - f"Token metadata update failed with status: {ResponseCode(receipt.status).name}" - ) + print(f"Token metadata update failed with status: {ResponseCode(receipt.status).name}") sys.exit(1) print("Successfully updated token data") @@ -118,9 +115,7 @@ def token_update_fungible(): # Create metadata key metadata_private_key = PrivateKey.generate_ed25519() - token_id = create_fungible_token( - client, operator_id, operator_key, metadata_private_key - ) + token_id = create_fungible_token(client, operator_id, operator_key, metadata_private_key) print("\nToken info before update:") token_info = get_token_info(client, token_id) diff --git a/examples/tokens/token_update_transaction_key.py b/examples/tokens/token_update_transaction_key.py index a3bbe6f6f..8ea2e2704 100644 --- a/examples/tokens/token_update_transaction_key.py +++ b/examples/tokens/token_update_transaction_key.py @@ -5,6 +5,7 @@ uv run examples/tokens/token_update_transaction_key.py python examples/tokens/token_update_transaction_key.py """ + import sys from hiero_sdk_python import ( @@ -26,6 +27,7 @@ def setup_client(): print(f"Client set up with operator id {client.operator_account_id}") return client + def create_fungible_token(client, operator_id, admin_key, wipe_key): """Create a fungible token.""" receipt = ( @@ -47,9 +49,7 @@ def create_fungible_token(client, operator_id, admin_key, wipe_key): # Check if token creation was successful if receipt.status != ResponseCode.SUCCESS: - print( - f"Fungible token creation failed with status: {ResponseCode(receipt.status).name}" - ) + print(f"Fungible token creation failed with status: {ResponseCode(receipt.status).name}") sys.exit(1) # Get token ID from receipt @@ -64,7 +64,6 @@ def get_token_info(client, token_id): return TokenInfoQuery().set_token_id(token_id).execute(client) - def update_wipe_key_full_validation(client, token_id, old_wipe_key): """ Update token wipe key with full validation mode. diff --git a/examples/tokens/token_update_transaction_nft.py b/examples/tokens/token_update_transaction_nft.py index 8801a9229..5e2526c11 100644 --- a/examples/tokens/token_update_transaction_nft.py +++ b/examples/tokens/token_update_transaction_nft.py @@ -5,6 +5,7 @@ uv run examples/tokens/token_update_transaction_nft.py python examples/tokens/token_update_transaction_nft.py """ + import sys from hiero_sdk_python import ( @@ -25,6 +26,7 @@ def setup_client(): print(f"Client set up with operator id {client.operator_account_id}") return client + def create_nft(client, operator_id, operator_key, metadata_key): """ Create a non-fungible token. @@ -70,7 +72,6 @@ def get_nft_info(client, nft_token_id): return TokenInfoQuery().set_token_id(nft_token_id).execute(client) - def update_nft_data( client, nft_token_id, @@ -91,9 +92,7 @@ def update_nft_data( ) if receipt.status != ResponseCode.SUCCESS: - print( - f"NFT data update failed with status: {ResponseCode(receipt.status).name}" - ) + print(f"NFT data update failed with status: {ResponseCode(receipt.status).name}") sys.exit(1) print("Successfully updated NFT data") diff --git a/examples/tokens/token_wipe_transaction.py b/examples/tokens/token_wipe_transaction.py index e3d070121..5fc70e473 100644 --- a/examples/tokens/token_wipe_transaction.py +++ b/examples/tokens/token_wipe_transaction.py @@ -5,6 +5,7 @@ uv run examples/tokens/token_wipe_transaction.py python examples/tokens/token_wipe_transaction.py """ + import sys from hiero_sdk_python import ( @@ -28,6 +29,7 @@ def setup_client(): print(f"Client set up with operator id {client.operator_account_id}") return client + def create_test_account(client): """Create a new account for testing.""" # Generate private key for new account @@ -46,9 +48,7 @@ def create_test_account(client): # Check if account creation was successful if receipt.status != ResponseCode.SUCCESS: - print( - f"Account creation failed with status: {ResponseCode(receipt.status).name}" - ) + print(f"Account creation failed with status: {ResponseCode(receipt.status).name}") sys.exit(1) # Get account ID from receipt @@ -108,9 +108,7 @@ def associate_token(client, account_id, token_id, account_private_key): receipt = associate_transaction.execute(client) if receipt.status != ResponseCode.SUCCESS: - print( - f"Token association failed with status: {ResponseCode(receipt.status).name}" - ) + print(f"Token association failed with status: {ResponseCode(receipt.status).name}") sys.exit(1) print("Token successfully associated with account") @@ -142,11 +140,7 @@ def wipe_tokens(client, token_id, account_id, amount): # Wipe the tokens from the account # Note: This requires the wipe key that was specified during token creation transaction = ( - TokenWipeTransaction() - .set_token_id(token_id) - .set_account_id(account_id) - .set_amount(amount) - .freeze_with(client) + TokenWipeTransaction().set_token_id(token_id).set_account_id(account_id).set_amount(amount).freeze_with(client) ) receipt = transaction.execute(client) diff --git a/examples/topic_info.py b/examples/topic_info.py index 1c52c0356..f5192c091 100644 --- a/examples/topic_info.py +++ b/examples/topic_info.py @@ -25,6 +25,7 @@ uv run examples/topic_info.py python examples/topic_info.py """ + from hiero_sdk_python.account.account_id import AccountId from hiero_sdk_python.consensus.topic_info import TopicInfo from hiero_sdk_python.crypto.private_key import PrivateKey diff --git a/examples/transaction/batch_transaction.py b/examples/transaction/batch_transaction.py index e25ef331f..0316692fa 100644 --- a/examples/transaction/batch_transaction.py +++ b/examples/transaction/batch_transaction.py @@ -26,15 +26,12 @@ TransferTransaction, ) + load_dotenv() def get_balance(client, account_id, token_id): - tokens_balance = ( - CryptoGetAccountBalanceQuery(account_id=account_id) - .execute(client) - .token_balances - ) + tokens_balance = CryptoGetAccountBalanceQuery(account_id=account_id).execute(client).token_balances print(f"Account: {account_id}: {tokens_balance[token_id] if tokens_balance else 0}") @@ -68,9 +65,7 @@ def create_account(client): tx = ( AccountCreateTransaction() .set_key_without_alias(key.public_key()) - .set_max_automatic_token_associations( - 2 - ) # to transfer token without associating it + .set_max_automatic_token_associations(2) # to transfer token without associating it .set_initial_balance(1) ) @@ -154,9 +149,7 @@ def transfer_token(client, sender, recipient, token_id): def perform_batch_tx(client, sender, recipient, token_id, freeze_key): """Perform a batch transaction using PrivateKey as batch_key.""" - print( - "\nPerforming batch transaction with PrivateKey (unfreeze → transfer → freeze)..." - ) + print("\nPerforming batch transaction with PrivateKey (unfreeze → transfer → freeze)...") batch_key = PrivateKey.generate() unfreeze_tx = ( @@ -202,9 +195,7 @@ def perform_batch_tx_with_public_key(client, sender, recipient, token_id, freeze Demonstrates that batch_key can accept both PrivateKey and PublicKey. """ - print( - "\n✨ Performing batch transaction with PublicKey (unfreeze → transfer → freeze)..." - ) + print("\n✨ Performing batch transaction with PublicKey (unfreeze → transfer → freeze)...") # Generate a key pair - we'll use the PublicKey as batch_key batch_private_key = PrivateKey.generate() @@ -247,12 +238,8 @@ def perform_batch_tx_with_public_key(client, sender, recipient, token_id, freeze ) receipt = batch.execute(client) - print( - f"Batch transaction with PublicKey status: {ResponseCode(receipt.status).name}" - ) - print( - " This demonstrates that batch_key now accepts both PrivateKey and PublicKey!" - ) + print(f"Batch transaction with PublicKey status: {ResponseCode(receipt.status).name}") + print(" This demonstrates that batch_key now accepts both PrivateKey and PublicKey!") def main(): @@ -279,9 +266,7 @@ def main(): get_balance(client, recipient_id, token_id) # Batch unfreeze → transfer → freeze (using PrivateKey) - perform_batch_tx( - client, client.operator_account_id, recipient_id, token_id, freeze_key - ) + perform_batch_tx(client, client.operator_account_id, recipient_id, token_id, freeze_key) print("\nBalances after first batch:") get_balance(client, client.operator_account_id, token_id) @@ -300,9 +285,7 @@ def main(): print("Demonstrating PublicKey support for batch_key") print("=" * 80) - perform_batch_tx_with_public_key( - client, client.operator_account_id, recipient_id, token_id, freeze_key - ) + perform_batch_tx_with_public_key(client, client.operator_account_id, recipient_id, token_id, freeze_key) print("\nBalances after second batch (with PublicKey):") get_balance(client, client.operator_account_id, token_id) @@ -311,9 +294,7 @@ def main(): # Verify that token is frozen again receipt = transfer_token(client, client.operator_account_id, recipient_id, token_id) if receipt.status == ResponseCode.ACCOUNT_FROZEN_FOR_TOKEN: - print( - "\n✅ Success! Account is frozen again, PublicKey batch_key works correctly!" - ) + print("\n✅ Success! Account is frozen again, PublicKey batch_key works correctly!") else: print("\nAccount should be frozen again!") sys.exit(1) diff --git a/examples/transaction/custom_fee_limit.py b/examples/transaction/custom_fee_limit.py index 3dc1de06a..642335548 100644 --- a/examples/transaction/custom_fee_limit.py +++ b/examples/transaction/custom_fee_limit.py @@ -7,6 +7,7 @@ - Submits a message with a CustomFeeLimit specifying how much the payer is willing to pay in custom fees for that message. """ + import os import sys @@ -85,9 +86,7 @@ def create_revenue_generating_topic(client: Client, operator_id: AccountId): return None -def submit_message_with_custom_fee_limit( - client: Client, topic_id, operator_id: AccountId -) -> None: +def submit_message_with_custom_fee_limit(client: Client, topic_id, operator_id: AccountId) -> None: """ Submit a message to the topic with a CustomFeeLimit applied. @@ -106,10 +105,7 @@ def submit_message_with_custom_fee_limit( fee_limit.set_payer_id(operator_id) fee_limit.add_custom_fee(limit_fee) - print( - f"Setting fee limit: max {limit_fee.amount} tinybars " - f"in custom fees for payer {operator_id}" - ) + print(f"Setting fee limit: max {limit_fee.amount} tinybars in custom fees for payer {operator_id}") try: submit_tx = TopicMessageSubmitTransaction() diff --git a/examples/transaction/transaction_freeze_manually.py b/examples/transaction/transaction_freeze_manually.py index 73f848bc2..887ad3343 100644 --- a/examples/transaction/transaction_freeze_manually.py +++ b/examples/transaction/transaction_freeze_manually.py @@ -8,6 +8,7 @@ uv run examples/transaction/transaction_freeze_manually.py python examples/transaction/transaction_freeze_manually.py """ + import os import sys @@ -24,6 +25,7 @@ TransactionId, ) + load_dotenv() NETWORK_NAME = os.getenv("NETWORK", "testnet").lower() @@ -31,6 +33,7 @@ OPERATOR_KEY = os.getenv("OPERATOR_KEY") NODE_ACCOUNT_ID = AccountId.from_string("0.0.3") + def setup_client(): """Initialize and return a Hedera Client using operator credentials.""" if not OPERATOR_ID or not OPERATOR_KEY: @@ -52,15 +55,12 @@ def setup_client(): print(f"Client initialized with operator {client.operator_account_id}") return client + def build_unsigned_tx(executor_client): """Build a Transaction, manually freeze it for a specific node, and return serialized unsigned bytes.""" tx_id = TransactionId.generate(executor_client.operator_account_id) - tx = ( - TopicCreateTransaction() - .set_memo("Test Topic Creation") - .set_transaction_id(tx_id) - ) + tx = TopicCreateTransaction().set_memo("Test Topic Creation").set_transaction_id(tx_id) # Explicit node binding (important for deterministic freeze) tx.node_account_id = NODE_ACCOUNT_ID @@ -71,6 +71,7 @@ def build_unsigned_tx(executor_client): print(f"Transaction frozen for node {NODE_ACCOUNT_ID}") return tx.to_bytes() + def sign_and_execute(unsigned_bytes, executor_client): """Deserialize, sign, and execute a transaction.""" try: @@ -89,9 +90,10 @@ def sign_and_execute(unsigned_bytes, executor_client): print("Transaction executed successfully.") print("Receipt:", receipt) - + except Exception as exc: - raise RuntimeError(f"Transaction execution failed: {exc}") from exc + raise RuntimeError(f"Transaction execution failed: {exc}") from exc + def main(): """ diff --git a/examples/transaction/transaction_freeze_secondary_client.py b/examples/transaction/transaction_freeze_secondary_client.py index 93656e0bd..ba1c7ee52 100644 --- a/examples/transaction/transaction_freeze_secondary_client.py +++ b/examples/transaction/transaction_freeze_secondary_client.py @@ -6,8 +6,9 @@ and executing a Hedera transaction using hiero_sdk_python. uv run examples/transaction/transaction_freeze_secondary_client.py -python examples/transaction/transaction_freeze_secondary_client.py +python examples/transaction/transaction_freeze_secondary_client.py """ + import os import sys @@ -25,6 +26,7 @@ TransactionId, ) + load_dotenv() NETWORK_NAME = os.getenv("NETWORK", "testnet").lower() @@ -53,6 +55,7 @@ def setup_client(): print(f"Client initialized with operator {client.operator_account_id}") return client + def create_secondary_client(executor_client): """Create a secondary account and client.""" private_key = PrivateKey.generate() @@ -73,6 +76,7 @@ def create_secondary_client(executor_client): return secondary_client + def build_unsigned_bytes(executor_client, secondary_client): """ Build a TopicCreateTransaction, manually freeze it using a secondary client,. @@ -81,11 +85,7 @@ def build_unsigned_bytes(executor_client, secondary_client): """ tx_id = TransactionId.generate(executor_client.operator_account_id) - tx = ( - TopicCreateTransaction() - .set_memo("Test Topic Creation") - .set_transaction_id(tx_id) - ) + tx = TopicCreateTransaction().set_memo("Test Topic Creation").set_transaction_id(tx_id) # Manually freeze the transaction using the secondary client tx.freeze_with(secondary_client) @@ -95,6 +95,7 @@ def build_unsigned_bytes(executor_client, secondary_client): return unsigned_bytes + def sign_and_execute(unsigned_bytes, executor_client): """ Deserialize a transaction from bytes, sign it using the executor client,. @@ -111,12 +112,12 @@ def sign_and_execute(unsigned_bytes, executor_client): receipt = tx.execute(executor_client) if receipt.status != ResponseCode.SUCCESS: raise RuntimeError(f"Transaction failed with status: {ResponseCode(receipt.status).name}") - + print("Transaction executed successfully.") print("Receipt:", receipt) except Exception as exc: - raise RuntimeError(f"Transaction execution failed: {exc}") from exc + raise RuntimeError(f"Transaction execution failed: {exc}") from exc def main(): diff --git a/examples/transaction/transaction_freeze_without_operator.py b/examples/transaction/transaction_freeze_without_operator.py index f20577d70..8ae21d115 100644 --- a/examples/transaction/transaction_freeze_without_operator.py +++ b/examples/transaction/transaction_freeze_without_operator.py @@ -8,6 +8,7 @@ uv run examples/transaction/transaction_freeze_without_operator.py python examples/transaction/transaction_freeze_without_operator.py """ + import os import sys @@ -24,6 +25,7 @@ TransactionId, ) + load_dotenv() NETWORK_NAME = os.getenv("NETWORK", "testnet").lower() @@ -53,7 +55,6 @@ def setup_client(): return client - def create_client_without_operator(): """Create a client without an operator.""" return Client(Network(NETWORK_NAME)) @@ -67,11 +68,7 @@ def build_unsigned_bytes(executor_client, secondary_client): """ tx_id = TransactionId.generate(executor_client.operator_account_id) - tx = ( - TopicCreateTransaction() - .set_memo("Test Topic Creation") - .set_transaction_id(tx_id) - ) + tx = TopicCreateTransaction().set_memo("Test Topic Creation").set_transaction_id(tx_id) # Manually freeze the transaction using the secondary client having no operator tx.freeze_with(secondary_client) @@ -81,6 +78,7 @@ def build_unsigned_bytes(executor_client, secondary_client): return unsigned_bytes + def sign_and_execute(unsigned_bytes, executor_client): """ Deserialize a transaction from bytes, sign it using the executor client,. @@ -97,12 +95,12 @@ def sign_and_execute(unsigned_bytes, executor_client): receipt = tx.execute(executor_client) if receipt.status != ResponseCode.SUCCESS: raise RuntimeError(f"Transaction failed with status: {ResponseCode(receipt.status).name}") - + print("Transaction executed successfully.") print("Receipt:", receipt) except Exception as exc: - raise RuntimeError(f"Transaction execution failed: {exc}") from exc + raise RuntimeError(f"Transaction execution failed: {exc}") from exc def main(): diff --git a/examples/transaction/transaction_record.py b/examples/transaction/transaction_record.py index c98f790f5..130634526 100644 --- a/examples/transaction/transaction_record.py +++ b/examples/transaction/transaction_record.py @@ -33,19 +33,16 @@ def create_mock_record(): tx_id = TransactionId.from_string("0.0.1234@1698765432.000000000") receipt_proto = transaction_receipt_pb2.TransactionReceipt() - receipt_proto.status = ResponseCode.SUCCESS.value + receipt_proto.status = ResponseCode.SUCCESS.value - receipt = TransactionReceipt( - receipt_proto=receipt_proto, - transaction_id=tx_id - ) + receipt = TransactionReceipt(receipt_proto=receipt_proto, transaction_id=tx_id) ts = Timestamp(seconds=1698765432, nanos=123456789) sched = ScheduleId(0, 0, 9999) record = TransactionRecord( transaction_id=tx_id, - transaction_hash=b'\x01\x02\x03\x04' * 12, + transaction_hash=b"\x01\x02\x03\x04" * 12, transaction_memo="Hello from example!", transaction_fee=50000, receipt=receipt, @@ -63,26 +60,25 @@ def create_mock_record(): AssessedCustomFee( amount=1000000, fee_collector_account_id=AccountId(shard=0, realm=0, num=98), - effective_payer_account_ids=[AccountId(shard=0, realm=0, num=100)] + effective_payer_account_ids=[AccountId(shard=0, realm=0, num=100)], ) ], automatic_token_associations=[ TokenAssociation( - token_id=TokenId(shard=0, realm=0, num=5678), - account_id=AccountId(shard=0, realm=0, num=1234) + token_id=TokenId(shard=0, realm=0, num=5678), account_id=AccountId(shard=0, realm=0, num=1234) ) ], parent_consensus_timestamp=ts, - alias=b'\x12\x34\x56\x78\x9a\xbc', - ethereum_hash=b'\xab' * 32, + alias=b"\x12\x34\x56\x78\x9a\xbc", + ethereum_hash=b"\xab" * 32, paid_staking_rewards=[ (AccountId(shard=0, realm=0, num=456), 500000), - (AccountId(shard=0, realm=0, num=789), 250000) + (AccountId(shard=0, realm=0, num=789), 250000), ], - evm_address=b'\xef' * 20, + evm_address=b"\xef" * 20, contract_create_result=ContractFunctionResult( contract_id=ContractId(shard=0, realm=0, contract=1000), - contract_call_result=b"Contract created successfully!" + contract_call_result=b"Contract created successfully!", ), ) @@ -91,6 +87,7 @@ def create_mock_record(): return record + def _print_basic_fields(record): print("Basic:") print(f" Transaction ID: {record.transaction_id}") @@ -112,7 +109,9 @@ def _print_basic_fields(record): def _print_transfer_fields(record): print(f" HBAR Transfers: {dict(record.transfers) if record.transfers else 'None'}") print(f" Token Transfers: {dict(record.token_transfers) if record.token_transfers else 'None'}") - print(f" NFT Transfers: { {k: len(v) for k, v in record.nft_transfers.items()} if record.nft_transfers else 'None'}") + print( + f" NFT Transfers: { {k: len(v) for k, v in record.nft_transfers.items()} if record.nft_transfers else 'None' }" + ) print(f" Pending Airdrops: {len(record.new_pending_airdrops)}") @@ -123,7 +122,9 @@ def _print_new_fields(record): print(f" Assessed Custom Fees ({len(record.assessed_custom_fees)}):") for fee in record.assessed_custom_fees: token = fee.token_id if fee.token_id else "HBAR" - payers = ", ".join(str(p) for p in fee.effective_payer_account_ids) if fee.effective_payer_account_ids else "N/A" + payers = ( + ", ".join(str(p) for p in fee.effective_payer_account_ids) if fee.effective_payer_account_ids else "N/A" + ) print(f" - {fee.amount} {token} → Collector: {fee.fee_collector_account_id}, Payers: {payers}") print(f" Automatic Token Associations ({len(record.automatic_token_associations)}):") for assoc in record.automatic_token_associations: @@ -136,7 +137,9 @@ def _print_new_fields(record): print(f" EVM Address (hex): {record.evm_address.hex() if record.evm_address else 'None'}") if record.contract_create_result: print(f" Contract Create Result: {record.contract_create_result.contract_id}") - print(f" Result bytes (first 32): {record.contract_create_result.contract_call_result[:32].hex() if record.contract_create_result.contract_call_result else 'None'}...") + print( + f" Result bytes (first 32): {record.contract_create_result.contract_call_result[:32].hex() if record.contract_create_result.contract_call_result else 'None'}..." + ) else: print(" Contract Create Result: None") @@ -147,7 +150,8 @@ def print_all_fields(record): _print_basic_fields(record) _print_transfer_fields(record) _print_new_fields(record) - + + def main(): """Run the TransactionRecord example.""" print("Creating mock TransactionRecord...\n") @@ -157,4 +161,3 @@ def main(): if __name__ == "__main__": main() - \ No newline at end of file diff --git a/examples/transaction/transaction_to_bytes.py b/examples/transaction/transaction_to_bytes.py index f1ce84c86..c7f979966 100644 --- a/examples/transaction/transaction_to_bytes.py +++ b/examples/transaction/transaction_to_bytes.py @@ -13,6 +13,7 @@ uv run examples/transaction/transaction_to_bytes.py python examples/transaction/transaction_to_bytes.py """ + import os import sys @@ -27,6 +28,7 @@ TransferTransaction, ) + load_dotenv() NETWORK = os.getenv("NETWORK", "testnet").lower() OPERATOR_ID = os.getenv("OPERATOR_ID", "") @@ -52,9 +54,7 @@ def setup_client() -> Client: sys.exit(1) -def create_and_freeze_transaction( - client: Client, sender: AccountId, receiver: AccountId -): +def create_and_freeze_transaction(client: Client, sender: AccountId, receiver: AccountId): """Create and freeze a simple HBAR transfer transaction.""" tx = ( TransferTransaction() diff --git a/examples/transaction/transaction_without_wait_for_receipt.py b/examples/transaction/transaction_without_wait_for_receipt.py index 03151f289..c4e980ca1 100644 --- a/examples/transaction/transaction_without_wait_for_receipt.py +++ b/examples/transaction/transaction_without_wait_for_receipt.py @@ -8,12 +8,7 @@ import sys -from hiero_sdk_python import ( - Client, - AccountCreateTransaction, - PrivateKey, - ResponseCode -) +from hiero_sdk_python import AccountCreateTransaction, Client, PrivateKey, ResponseCode def build_transaction(): @@ -38,7 +33,7 @@ def print_transaction_receipt(receipt): """Print the transaction receipt.""" if receipt.status != ResponseCode.SUCCESS: raise RuntimeError(f"Receipt Query failed with status: {ResponseCode(receipt.status).name}") - + print(f"Transaction Receipt Status: {ResponseCode(receipt.status).name}") print(f"Transaction Account ID: {receipt.account_id}") @@ -57,7 +52,7 @@ def main(): print("Executing transaction...") # Execute the transaction without waiting for receipt immediately response = tx.execute(client, wait_for_receipt=False) - + print("Transaction executed successfully!") print(f"Transaction submitted with ID: {response.transaction_id}") @@ -65,8 +60,7 @@ def main(): print("\n1. Getting Transaction Receipt using Transaction Response...") receipt = response.get_receipt(client) print_transaction_receipt(receipt) - - + print("\n2. Getting Transaction Record using Transaction Response...") record = response.get_record(client) print_transaction_record(record) diff --git a/examples/transaction/transfer_transaction_fungible.py b/examples/transaction/transfer_transaction_fungible.py index a14a51b71..928f84f55 100644 --- a/examples/transaction/transfer_transaction_fungible.py +++ b/examples/transaction/transfer_transaction_fungible.py @@ -5,6 +5,7 @@ uv run examples/transaction/transfer_transaction_fungible.py python examples/transaction/transfer_transaction_fungible.py """ + import os import sys @@ -23,6 +24,7 @@ TransferTransaction, ) + load_dotenv() network_name = os.getenv("NETWORK", "testnet").lower() diff --git a/examples/transaction/transfer_transaction_gigabar.py b/examples/transaction/transfer_transaction_gigabar.py index 150269e41..4f9846ffd 100644 --- a/examples/transaction/transfer_transaction_gigabar.py +++ b/examples/transaction/transfer_transaction_gigabar.py @@ -6,6 +6,7 @@ uv run examples/transaction/transfer_transaction_gigabar.py python examples/transaction/transfer_transaction_gigabar.py """ + import os import sys @@ -24,6 +25,7 @@ TransferTransaction, ) + load_dotenv() network_name = os.getenv("NETWORK", "testnet").lower() @@ -54,18 +56,12 @@ def create_account(client, operator_key): print("\nSTEP 1: Creating a new recipient account...") recipient_key = PrivateKey.generate() try: - tx = ( - AccountCreateTransaction() - .set_key(recipient_key.public_key()) - .set_initial_balance(Hbar.from_tinybars(0)) - ) + tx = AccountCreateTransaction().set_key(recipient_key.public_key()).set_initial_balance(Hbar.from_tinybars(0)) receipt = tx.freeze_with(client).sign(operator_key).execute(client) if receipt.status != ResponseCode.SUCCESS: - print( - f"❌ Account creation failed with status: {ResponseCode(receipt.status).name}" - ) + print(f"❌ Account creation failed with status: {ResponseCode(receipt.status).name}") sys.exit(1) recipient_id = receipt.account_id @@ -108,9 +104,7 @@ def transfer_gigabars(client, operator_id, recipient_id, operator_key): def get_balance(client, account_id, when=""): """Query and display account balance.""" try: - balance = ( - CryptoGetAccountBalanceQuery(account_id=account_id).execute(client).hbars - ) + balance = CryptoGetAccountBalanceQuery(account_id=account_id).execute(client).hbars print(f"Recipient account balance{when}: {balance} hbars") return balance except Exception as e: diff --git a/examples/transaction/transfer_transaction_hbar.py b/examples/transaction/transfer_transaction_hbar.py index 49167ab8c..d844157ad 100644 --- a/examples/transaction/transfer_transaction_hbar.py +++ b/examples/transaction/transfer_transaction_hbar.py @@ -6,6 +6,7 @@ uv run examples/transaction/transfer_transaction_hbar.py python examples/transaction/transfer_transaction_hbar.py """ + import os import sys @@ -23,6 +24,7 @@ TransferTransaction, ) + load_dotenv() network_name = os.getenv("NETWORK", "testnet").lower() @@ -60,9 +62,7 @@ def create_account(client, operator_key): receipt = tx.freeze_with(client).sign(operator_key).execute(client) if receipt.status != ResponseCode.SUCCESS: - print( - f"❌ Account creation failed with status: {ResponseCode(receipt.status).name}" - ) + print(f"❌ Account creation failed with status: {ResponseCode(receipt.status).name}") sys.exit(1) recipient_id = receipt.account_id @@ -105,9 +105,7 @@ def transfer_hbar(client, operator_id, recipient_id, operator_key): def get_balance(client, account_id, when=""): """Query and display account balance.""" try: - balance = ( - CryptoGetAccountBalanceQuery(account_id=account_id).execute(client).hbars - ) + balance = CryptoGetAccountBalanceQuery(account_id=account_id).execute(client).hbars print(f"Recipient account balance{when}: {balance} hbars") return balance except Exception as e: diff --git a/examples/transaction/transfer_transaction_nft.py b/examples/transaction/transfer_transaction_nft.py index 4625145bc..7899ca36e 100644 --- a/examples/transaction/transfer_transaction_nft.py +++ b/examples/transaction/transfer_transaction_nft.py @@ -5,6 +5,7 @@ uv run examples/transaction/transfer_transaction_nft.py python examples/transaction/transfer_transaction_nft.py """ + import os import sys @@ -29,6 +30,7 @@ from hiero_sdk_python.tokens.token_mint_transaction import TokenMintTransaction from hiero_sdk_python.tokens.token_type import TokenType + load_dotenv() network_name = os.getenv("NETWORK", "testnet").lower() @@ -67,9 +69,7 @@ def create_test_account(client): # Check if account creation was successful if receipt.status != ResponseCode.SUCCESS: - print( - f"Account creation failed with status: {ResponseCode(receipt.status).name}" - ) + print(f"Account creation failed with status: {ResponseCode(receipt.status).name}") sys.exit(1) # Get account ID from receipt @@ -111,13 +111,10 @@ def create_nft(client, operator_id, operator_key): return nft_token_id -def mint_nft(client, nft_token_id, operator_key): +def mint_nft(client, nft_token_id): """Mint a non-fungible token.""" transaction = ( - TokenMintTransaction() - .set_token_id(nft_token_id) - .set_metadata(b"My NFT Metadata 1") - .freeze_with(client) + TokenMintTransaction().set_token_id(nft_token_id).set_metadata(b"My NFT Metadata 1").freeze_with(client) ) receipt = transaction.execute(client) @@ -145,9 +142,7 @@ def associate_nft(client, account_id, token_id, account_private_key): receipt = associate_transaction.execute(client) if receipt.status != ResponseCode.SUCCESS: - print( - f"NFT association failed with status: {ResponseCode(receipt.status).name}" - ) + print(f"NFT association failed with status: {ResponseCode(receipt.status).name}") sys.exit(1) print("NFT successfully associated with account") @@ -156,11 +151,7 @@ def associate_nft(client, account_id, token_id, account_private_key): def transfer_nft_token(client, nft_id, sender_id, receiver_id): """Transfer the NFT from the sender to the receiver account.""" # Transfer nft to the new account - transfer_transaction = ( - TransferTransaction() - .add_nft_transfer(nft_id, sender_id, receiver_id) - .freeze_with(client) - ) + transfer_transaction = TransferTransaction().add_nft_transfer(nft_id, sender_id, receiver_id).freeze_with(client) receipt = transfer_transaction.execute(client) @@ -185,7 +176,7 @@ def main(): client, operator_id, operator_key = setup_client() account_id, new_account_private_key = create_test_account(client) token_id = create_nft(client, operator_id, operator_key) - nft_id = mint_nft(client, token_id, operator_key) + nft_id = mint_nft(client, token_id) associate_nft(client, account_id, token_id, new_account_private_key) # Transfer the NFT to the new account diff --git a/examples/transaction/transfer_transaction_tinybar.py b/examples/transaction/transfer_transaction_tinybar.py index 4de758162..c33035170 100644 --- a/examples/transaction/transfer_transaction_tinybar.py +++ b/examples/transaction/transfer_transaction_tinybar.py @@ -6,6 +6,7 @@ uv run examples/transaction/transfer_transaction_tinybar.py python examples/transaction/transfer_transaction_tinybar.py """ + import os import sys @@ -23,6 +24,7 @@ TransferTransaction, ) + load_dotenv() network_name = os.getenv("NETWORK", "testnet").lower() @@ -60,9 +62,7 @@ def create_account(client, operator_key): receipt = tx.freeze_with(client).sign(operator_key).execute(client) if receipt.status != ResponseCode.SUCCESS: - print( - f"❌ Account creation failed with status: {ResponseCode(receipt.status).name}" - ) + print(f"❌ Account creation failed with status: {ResponseCode(receipt.status).name}") sys.exit(1) recipient_id = receipt.account_id @@ -129,9 +129,7 @@ def transfer_hbar_with_object(client, operator_id, recipient_id, operator_key): def get_balance(client, account_id, when=""): """Query and display account balance.""" try: - balance = ( - CryptoGetAccountBalanceQuery(account_id=account_id).execute(client).hbars - ) + balance = CryptoGetAccountBalanceQuery(account_id=account_id).execute(client).hbars print(f"Recipient account balance{when}: {balance} hbars") return balance except Exception as e: diff --git a/generate_proto.py b/generate_proto.py index dd935c9f3..e87c3578b 100644 --- a/generate_proto.py +++ b/generate_proto.py @@ -21,35 +21,39 @@ * TRACE (custom) for verbose details such as per-file rewrites and protoc args. Run: python generate_proto.py -vv or with trace logs: python generate_proto.py -vvv """ + +from __future__ import annotations + import logging +import re import shutil import tarfile -from urllib.parse import urlparse import urllib.request -import re from dataclasses import dataclass, field from pathlib import Path +from urllib.parse import urlparse + -VERSION="v0.72.0-rc.2" +VERSION = "v0.72.0-rc.2" SOURCES = [ { "name": "hedera-protobufs", "url": "https://github.com/hashgraph/hedera-protobufs", "version": VERSION, "strip_count": 1, - "modules": ("mirror",) + "modules": ("mirror",), }, { "name": "hiero-consensus-node", "url": "https://github.com/hiero-ledger/hiero-consensus-node", "version": VERSION, - "strip_count": 6, - "modules": ("services", "platform", "fee", "sdk", "block", "streams", "blocks") - } + "strip_count": 6, + "modules": ("services", "platform", "fee", "sdk", "block", "streams", "blocks"), + }, ] -OUTPUT_DIR="src/hiero_sdk_python/hapi" -CACHE_DIR=".protos" +OUTPUT_DIR = "src/hiero_sdk_python/hapi" +CACHE_DIR = ".protos" # Map common broken imports in mirror/platform proto REPLACEMENTS = { @@ -70,16 +74,19 @@ class Config: name: str url: str version: str - strip_count: int + strip_count: int modules: tuple = field(default_factory=tuple) def setup_logging(verbosity: int) -> None: level = logging.WARNING - if verbosity == 1: level = logging.INFO - elif verbosity == 2: level = logging.DEBUG - elif verbosity >= 3: level = logging.TRACE_LEVEL - + if verbosity == 1: + level = logging.INFO + elif verbosity == 2: + level = logging.DEBUG + elif verbosity >= 3: + level = logging.TRACE_LEVEL + logging.basicConfig(level=level, format="%(levelname)s: %(message)s") @@ -93,11 +100,11 @@ def download_protos(config: Config, cache_path: Path) -> None: try: # URL scheme and host validated above - with urllib.request.urlopen(url, timeout=30) as resp: # nosec B310 + with urllib.request.urlopen(url, timeout=30) as resp: # nosec B310 safe_extract_tar_stream(resp, config, cache_path) except Exception as e: - raise RuntimeError(f"Download failed for {config.name}: {e}") - + raise RuntimeError(f"Download failed for {config.name}: {e}") from e + def is_safe_tar_member(member: tarfile.TarInfo, base: Path) -> bool: name = member.name @@ -111,8 +118,8 @@ def is_safe_tar_member(member: tarfile.TarInfo, base: Path) -> bool: dest.relative_to(base.resolve()) except ValueError: return False - - return (member.isdir() or member.isreg()) + + return member.isdir() or member.isreg() def safe_extract_tar_stream(resp, config: Config, cache_path: Path): @@ -120,9 +127,10 @@ def safe_extract_tar_stream(resp, config: Config, cache_path: Path): for member in tar: parts = Path(member.name).parts - if len(parts) <= config.strip_count: continue - member.name = "/".join(parts[config.strip_count:]) - + if len(parts) <= config.strip_count: + continue + member.name = "/".join(parts[config.strip_count :]) + if not any(member.name.startswith(p) for p in config.modules): continue @@ -132,24 +140,23 @@ def safe_extract_tar_stream(resp, config: Config, cache_path: Path): if member.isdir(): (cache_path / member.name).mkdir(parents=True, exist_ok=True) continue - + target = cache_path / member.name target.parent.mkdir(parents=True, exist_ok=True) with tar.extractfile(member) as src, target.open("wb") as dst: shutil.copyfileobj(src, dst) - def patch_proto_imports(proto_root: Path): logging.info("Patching proto files for consistent import paths...") for proto_file in proto_root.rglob("*.proto"): content = proto_file.read_text(encoding="utf-8") new_content = content - + for broken, fixed in REPLACEMENTS.items(): new_content = new_content.replace(broken, fixed) - + if "platform" in proto_file.parts: new_content = re.sub(r'import "event/', 'import "platform/event/', new_content) @@ -158,8 +165,9 @@ def patch_proto_imports(proto_root: Path): def run_protoc(proto_root: Path, output_root: Path) -> None: - from grpc_tools import protoc import grpc_tools + from grpc_tools import protoc + google_include = str(Path(grpc_tools.__file__).parent / "_proto") all_protos = [p.as_posix() for p in proto_root.rglob("*.proto")] @@ -184,19 +192,23 @@ def fix_imports(output_root: Path): if py_file.name == "__init__.py": continue - py_file.write_text("\n".join( - process_file_lines(py_file.read_text(encoding="utf-8").splitlines(), pattern, py_file.relative_to(output_root).parents) - ) + "\n", encoding="utf-8") + py_file.write_text( + "\n".join( + process_file_lines( + py_file.read_text(encoding="utf-8").splitlines(), pattern, py_file.relative_to(output_root).parents + ) + ) + + "\n", + encoding="utf-8", + ) + def process_file_lines(lines, pattern, parents): """Process each line in a file and fix imports if needed.""" depth = len(parents) - 1 dots = "." * (depth + 1) - new_lines = [] - for line in lines: - new_lines.append(fix_line_import(line, pattern, dots)) - return new_lines + return [fix_line_import(line, pattern, dots) for line in lines] def fix_line_import(line, pattern, dots): @@ -205,7 +217,7 @@ def fix_line_import(line, pattern, dots): if not match: return line - ptype, ppath, psuffix = match.group('type'), match.group('path'), match.group('suffix') + ptype, ppath, psuffix = match.group("type"), match.group("path"), match.group("suffix") full_module = ppath + psuffix if ptype == "from": @@ -217,16 +229,15 @@ def fix_line_import(line, pattern, dots): if " as " in line: alias = line.split(" as ", 1)[1].strip() return ( - f"from {dots}{module_parts[0]} import {module_parts[1]} as {alias}" - if len(module_parts) > 1 + f"from {dots}{module_parts[0]} import {module_parts[1]} as {alias}" + if len(module_parts) > 1 else f"from {dots} import {module_parts[0]} as {alias}" ) - else: - return ( - f"from {dots}{module_parts[0]} import {module_parts[1]}" - if len(module_parts) > 1 - else f"from {dots} import {module_parts[0]}" - ) + return ( + f"from {dots}{module_parts[0]} import {module_parts[1]}" + if len(module_parts) > 1 + else f"from {dots} import {module_parts[0]}" + ) def main(): @@ -234,8 +245,11 @@ def main(): cache_path = Path(CACHE_DIR) out_path = Path(OUTPUT_DIR) - if cache_path.exists(): shutil.rmtree(cache_path) - if out_path.exists(): shutil.rmtree(out_path) + if cache_path.exists(): + shutil.rmtree(cache_path) + + if out_path.exists(): + shutil.rmtree(out_path) cache_path.mkdir(parents=True) out_path.mkdir(parents=True) @@ -245,18 +259,19 @@ def main(): download_protos(src, cache_path) patch_proto_imports(cache_path) - + logging.info("Running protoc...") run_protoc(cache_path, out_path) - + logging.info("Fixing imports...") fix_imports(out_path) - + for d in [out_path, *out_path.rglob("*")]: - if d.is_dir(): (d / "__init__.py").touch() + if d.is_dir(): + (d / "__init__.py").touch() print(f"✅ Successfully merged and generated HAPI at {out_path}") if __name__ == "__main__": - main() \ No newline at end of file + main() diff --git a/mypy.ini b/mypy.ini index 85af713f8..3dc7fe5e6 100644 --- a/mypy.ini +++ b/mypy.ini @@ -19,4 +19,4 @@ allow_untyped_defs = True follow_imports = silent # Turn off strict None‐checking—allows assigning X | None to X without error -strict_optional = False \ No newline at end of file +strict_optional = False diff --git a/pyproject.toml b/pyproject.toml index 9b11b7f45..47b0cd173 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -48,7 +48,8 @@ dev = [ "grpcio-tools>=1.76.0,<2", "pytest>=8.3.4,<10", "pytest-cov>=7.0.0,<8", - "hypothesis>=6.137.2" + "hypothesis>=6.137.2", + "pre-commit>=4.0.0", ] lint = [ @@ -81,31 +82,68 @@ testpaths = ["tests"] [tool.ruff] line-length = 120 -target-version = "py314" +target-version = "py310" [tool.ruff.lint] select = [ - "D", # docstrings "E", # pycodestyle errors + "W", # pycodestyle warnings "F", # pyflakes - "I", # import sorting - "B", # bugbear (real bugs) - "UP", # pyupgrade (modern syntax) - "SIM", # simplify logic + "I", # isort (Import sorting) + "B", # flake8-bugbear (Real logic bugs) + "UP", # pyupgrade (Modern syntax) + "SIM", # flake8-simplify "PERF", # performance traps "ARG", # unused function arguments "RET", # return consistency "RSE", # exception correctness ] + ignore = [ - "D212", # disable "summary on same line" - "E501", # line length -> formatter - "B008", # function calls in argument defaults + "D", # Docstrings handled by pydocstyle + "E501", # Ignore line-length in linter (let the formatter handle it) + "B008", # Allow function calls in defaults + "UP017", # Keep timezone.utc shorthand + "SIM105", # Allow 'try-except-pass' +] + +[tool.ruff.lint.isort] +known-first-party = ["hiero_sdk_python"] +required-imports = ["from __future__ import annotations"] +combine-as-imports = true +split-on-trailing-comma = true +lines-after-imports = 2 + +[tool.ruff.lint.per-file-ignores] +"**/__init__.py" = [ + "F401", # Unused imports in __init__ + "I002", # No future annotations in __init__ +] + +"tests/**/*.py" = [ + "S101", # Asserts for pytest + "PLR2004", # Magic numbers in test data + "ARG001", # Unused args for fixtures + "ANN", # No type hints required in tests + "PERF", # Disable list compression not found + "E712", # Allow '== True' in tests for explicit assertions + "D", # No docstrings required in tests + "ERA001", # commeneted code found +] + +"examples/**/*.py" = [ + "T201", # Print statements are fine here + "T203", # pprint is fine + "D100", # No module docstring + "I002", # Allow skipping future annotations + "PERF203", # try-except inside loops found + "ERA001", # commeneted code found ] [tool.ruff.format] quote-style = "double" indent-style = "space" +skip-magic-trailing-comma = false [tool.ruff.lint.pydocstyle] convention = "google" diff --git a/pytest.ini b/pytest.ini index 0fb601499..6a69537cb 100644 --- a/pytest.ini +++ b/pytest.ini @@ -4,5 +4,5 @@ pythonpath = src markers = integration: mark a test as an integration test. - unit: mark a test as a unit test. + unit: mark a test as a unit test. fuzz: mark a test as fuzz test. diff --git a/scripts/examples/match_examples_src.py b/scripts/examples/match_examples_src.py index de0c9de7c..d120b1ade 100644 --- a/scripts/examples/match_examples_src.py +++ b/scripts/examples/match_examples_src.py @@ -1,7 +1,10 @@ +from __future__ import annotations + import os from collections import defaultdict -EXCLUDE_DIRS = ['hapi', '__pycache__'] + +EXCLUDE_DIRS = ["hapi", "__pycache__"] # ----------------------------- @@ -16,7 +19,7 @@ def tokenize_filename(path): def longest_shared_prefix_tokens(a, b): """Return longest shared prefix between two token lists.""" prefix = [] - for x, y in zip(a, b): + for x, y in zip(a, b, strict=True): if x == y: prefix.append(x) else: @@ -67,7 +70,6 @@ def list_files(base_path, exclude_dirs=None): return all_files - # ----------------------------- # Matching helpers # ----------------------------- @@ -81,7 +83,7 @@ def match_exact_folder(norm_ex, folder_ex, src_map, unmatched_src_set): def match_by_filename_only(norm_ex, src_map, unmatched_src_set): - for (src_folder, src_norm), src_list in src_map.items(): + for (_, src_norm), src_list in src_map.items(): if src_norm == norm_ex: for src_file in src_list: unmatched_src_set.discard(src_file) diff --git a/src/hiero_sdk_python/Duration.py b/src/hiero_sdk_python/Duration.py index a1965a908..518601157 100644 --- a/src/hiero_sdk_python/Duration.py +++ b/src/hiero_sdk_python/Duration.py @@ -1,9 +1,14 @@ +from __future__ import annotations + from dataclasses import dataclass + from hiero_sdk_python.hapi.services.duration_pb2 import Duration as proto_Duration + @dataclass(frozen=True, init=True) class Duration: """A frozen dataclass representing a duration in seconds.""" + seconds: int def __post_init__(self) -> None: @@ -15,7 +20,7 @@ def _to_proto(self) -> proto_Duration: return proto_Duration(seconds=self.seconds) @classmethod - def _from_proto(cls, proto: proto_Duration) -> 'Duration': + def _from_proto(cls, proto: proto_Duration) -> Duration: if isinstance(proto, Duration): raise ValueError("Invalid duration proto") return cls(seconds=proto.seconds) @@ -29,4 +34,4 @@ def __repr__(self) -> str: def __eq__(self, other: object) -> bool: if not isinstance(other, Duration): return False - return self.seconds == other.seconds \ No newline at end of file + return self.seconds == other.seconds diff --git a/src/hiero_sdk_python/__init__.py b/src/hiero_sdk_python/__init__.py index 513044aed..329f6f0a4 100644 --- a/src/hiero_sdk_python/__init__.py +++ b/src/hiero_sdk_python/__init__.py @@ -1,165 +1,164 @@ -# Client and Network -from .client.client import Client -from .client.network import Network - # Account -from .account.account_id import AccountId -from .account.account_create_transaction import AccountCreateTransaction -from .account.account_update_transaction import AccountUpdateTransaction -from .account.account_info import AccountInfo -from .account.account_delete_transaction import AccountDeleteTransaction from .account.account_allowance_approve_transaction import AccountAllowanceApproveTransaction from .account.account_allowance_delete_transaction import AccountAllowanceDeleteTransaction +from .account.account_create_transaction import AccountCreateTransaction +from .account.account_delete_transaction import AccountDeleteTransaction +from .account.account_id import AccountId +from .account.account_info import AccountInfo from .account.account_records_query import AccountRecordsQuery +from .account.account_update_transaction import AccountUpdateTransaction + +# Address book +from .address_book.endpoint import Endpoint +from .address_book.node_address import NodeAddress + +# Client and Network +from .client.client import Client +from .client.network import Network + +# Consensus +from .consensus.topic_create_transaction import TopicCreateTransaction +from .consensus.topic_delete_transaction import TopicDeleteTransaction +from .consensus.topic_id import TopicId +from .consensus.topic_message_submit_transaction import TopicMessageSubmitTransaction +from .consensus.topic_update_transaction import TopicUpdateTransaction + +# Contract +from .contract.contract_bytecode_query import ContractBytecodeQuery +from .contract.contract_call_query import ContractCallQuery +from .contract.contract_create_transaction import ContractCreateTransaction +from .contract.contract_delete_transaction import ContractDeleteTransaction +from .contract.contract_execute_transaction import ContractExecuteTransaction +from .contract.contract_function_parameters import ContractFunctionParameters +from .contract.contract_function_result import ContractFunctionResult +from .contract.contract_info import ContractInfo +from .contract.contract_info_query import ContractInfoQuery +from .contract.contract_update_transaction import ContractUpdateTransaction +from .contract.ethereum_transaction import EthereumTransaction # Crypto +from .crypto.evm_address import EvmAddress from .crypto.private_key import PrivateKey from .crypto.public_key import PublicKey -from .crypto.evm_address import EvmAddress -# Tokens -from .tokens.token_create_transaction import TokenCreateTransaction -from .tokens.token_associate_transaction import TokenAssociateTransaction -from .tokens.token_dissociate_transaction import TokenDissociateTransaction -from .tokens.token_delete_transaction import TokenDeleteTransaction -from .tokens.token_info import TokenInfo -from .tokens.token_mint_transaction import TokenMintTransaction -from .tokens.token_freeze_transaction import TokenFreezeTransaction -from .tokens.token_unfreeze_transaction import TokenUnfreezeTransaction -from .tokens.token_wipe_transaction import TokenWipeTransaction -from .tokens.token_reject_transaction import TokenRejectTransaction -from .tokens.token_update_nfts_transaction import TokenUpdateNftsTransaction -from .tokens.token_burn_transaction import TokenBurnTransaction -from .tokens.token_grant_kyc_transaction import TokenGrantKycTransaction -from .tokens.token_revoke_kyc_transaction import TokenRevokeKycTransaction -from .tokens.token_update_transaction import TokenUpdateTransaction -from .tokens.token_airdrop_transaction import TokenAirdropTransaction -from .tokens.token_airdrop_transaction_cancel import TokenCancelAirdropTransaction -from .tokens.token_airdrop_pending_id import PendingAirdropId -from .tokens.token_airdrop_pending_record import PendingAirdropRecord -from .tokens.token_id import TokenId -from .tokens.token_type import TokenType -from .tokens.supply_type import SupplyType -from .tokens.nft_id import NftId -from .tokens.token_nft_transfer import TokenNftTransfer -from .tokens.token_nft_info import TokenNftInfo -from .tokens.token_relationship import TokenRelationship -from .tokens.token_allowance import TokenAllowance -from .tokens.token_nft_allowance import TokenNftAllowance -from .tokens.hbar_allowance import HbarAllowance -from .tokens.hbar_transfer import HbarTransfer -from .tokens.token_unpause_transaction import TokenUnpauseTransaction -from .tokens.token_pause_transaction import TokenPauseTransaction -from .tokens.token_airdrop_claim import TokenClaimAirdropTransaction -from .tokens.assessed_custom_fee import AssessedCustomFee -from .tokens.token_association import TokenAssociation +# Duration +from .Duration import Duration -# Transaction -from .transaction.transaction import Transaction -from .transaction.transfer_transaction import TransferTransaction -from .transaction.transaction_id import TransactionId -from .transaction.transaction_receipt import TransactionReceipt -from .transaction.transaction_response import TransactionResponse -from .transaction.transaction_record import TransactionRecord -from .transaction.batch_transaction import BatchTransaction +# Errors +from .exceptions import PrecheckError, ReceiptStatusError -# Response / Codes -from .response_code import ResponseCode +# File +from .file.file_append_transaction import FileAppendTransaction +from .file.file_contents_query import FileContentsQuery +from .file.file_create_transaction import FileCreateTransaction +from .file.file_delete_transaction import FileDeleteTransaction +from .file.file_info import FileInfo +from .file.file_info_query import FileInfoQuery +from .file.file_update_transaction import FileUpdateTransaction # HBAR from .hbar import Hbar from .hbar_unit import HbarUnit -# Timestamp -from .timestamp import Timestamp -from .staking_info import StakingInfo +# Logger +from .logger.log_level import LogLevel +from .logger.logger import Logger -# Duration -from .Duration import Duration +# Nodes +from .nodes.node_create_transaction import NodeCreateTransaction +from .nodes.node_delete_transaction import NodeDeleteTransaction +from .nodes.node_update_transaction import NodeUpdateTransaction -# Consensus -from .consensus.topic_create_transaction import TopicCreateTransaction -from .consensus.topic_message_submit_transaction import TopicMessageSubmitTransaction -from .consensus.topic_update_transaction import TopicUpdateTransaction -from .consensus.topic_delete_transaction import TopicDeleteTransaction -from .consensus.topic_id import TopicId +# PRNG +from .prng_transaction import PrngTransaction # Queries +from .query.account_balance_query import CryptoGetAccountBalanceQuery +from .query.account_info_query import AccountInfoQuery +from .query.token_info_query import TokenInfoQuery +from .query.token_nft_info_query import TokenNftInfoQuery from .query.topic_info_query import TopicInfoQuery from .query.topic_message_query import TopicMessageQuery from .query.transaction_get_receipt_query import TransactionGetReceiptQuery from .query.transaction_record_query import TransactionRecordQuery -from .query.account_balance_query import CryptoGetAccountBalanceQuery -from .query.token_nft_info_query import TokenNftInfoQuery -from .query.token_info_query import TokenInfoQuery -from .query.account_info_query import AccountInfoQuery - -# Address book -from .address_book.endpoint import Endpoint -from .address_book.node_address import NodeAddress - -# Logger -from .logger.logger import Logger -from .logger.log_level import LogLevel -# File -from .file.file_create_transaction import FileCreateTransaction -from .file.file_append_transaction import FileAppendTransaction -from .file.file_info_query import FileInfoQuery -from .file.file_info import FileInfo -from .file.file_contents_query import FileContentsQuery -from .file.file_update_transaction import FileUpdateTransaction -from .file.file_delete_transaction import FileDeleteTransaction - -# Contract -from .contract.contract_create_transaction import ContractCreateTransaction -from .contract.contract_call_query import ContractCallQuery -from .contract.contract_info_query import ContractInfoQuery -from .contract.contract_bytecode_query import ContractBytecodeQuery -from .contract.contract_execute_transaction import ContractExecuteTransaction -from .contract.contract_delete_transaction import ContractDeleteTransaction -from .contract.contract_function_parameters import ContractFunctionParameters -from .contract.contract_function_result import ContractFunctionResult -from .contract.contract_info import ContractInfo -from .contract.contract_update_transaction import ContractUpdateTransaction -from .contract.ethereum_transaction import EthereumTransaction +# Response / Codes +from .response_code import ResponseCode # Schedule from .schedule.schedule_create_transaction import ScheduleCreateTransaction +from .schedule.schedule_delete_transaction import ScheduleDeleteTransaction from .schedule.schedule_id import ScheduleId from .schedule.schedule_info import ScheduleInfo from .schedule.schedule_info_query import ScheduleInfoQuery from .schedule.schedule_sign_transaction import ScheduleSignTransaction -from .schedule.schedule_delete_transaction import ScheduleDeleteTransaction +from .staking_info import StakingInfo -# Nodes -from .nodes.node_create_transaction import NodeCreateTransaction -from .nodes.node_update_transaction import NodeUpdateTransaction -from .nodes.node_delete_transaction import NodeDeleteTransaction +# System +from .system.freeze_transaction import FreezeTransaction +from .system.freeze_type import FreezeType -# PRNG -from .prng_transaction import PrngTransaction +# Timestamp +from .timestamp import Timestamp +from .tokens.assessed_custom_fee import AssessedCustomFee # Custom Fees from .tokens.custom_fee import CustomFee from .tokens.custom_fixed_fee import CustomFixedFee from .tokens.custom_fractional_fee import CustomFractionalFee from .tokens.custom_royalty_fee import CustomRoyaltyFee -from .transaction.custom_fee_limit import CustomFeeLimit +from .tokens.hbar_allowance import HbarAllowance +from .tokens.hbar_transfer import HbarTransfer +from .tokens.nft_id import NftId +from .tokens.supply_type import SupplyType +from .tokens.token_airdrop_claim import TokenClaimAirdropTransaction +from .tokens.token_airdrop_pending_id import PendingAirdropId +from .tokens.token_airdrop_pending_record import PendingAirdropRecord +from .tokens.token_airdrop_transaction import TokenAirdropTransaction +from .tokens.token_airdrop_transaction_cancel import TokenCancelAirdropTransaction +from .tokens.token_allowance import TokenAllowance +from .tokens.token_associate_transaction import TokenAssociateTransaction +from .tokens.token_association import TokenAssociation +from .tokens.token_burn_transaction import TokenBurnTransaction -# System -from .system.freeze_transaction import FreezeTransaction -from .system.freeze_type import FreezeType +# Tokens +from .tokens.token_create_transaction import TokenCreateTransaction +from .tokens.token_delete_transaction import TokenDeleteTransaction +from .tokens.token_dissociate_transaction import TokenDissociateTransaction +from .tokens.token_freeze_transaction import TokenFreezeTransaction +from .tokens.token_grant_kyc_transaction import TokenGrantKycTransaction +from .tokens.token_id import TokenId +from .tokens.token_info import TokenInfo +from .tokens.token_mint_transaction import TokenMintTransaction +from .tokens.token_nft_allowance import TokenNftAllowance +from .tokens.token_nft_info import TokenNftInfo +from .tokens.token_nft_transfer import TokenNftTransfer +from .tokens.token_pause_transaction import TokenPauseTransaction +from .tokens.token_reject_transaction import TokenRejectTransaction +from .tokens.token_relationship import TokenRelationship +from .tokens.token_revoke_kyc_transaction import TokenRevokeKycTransaction +from .tokens.token_type import TokenType +from .tokens.token_unfreeze_transaction import TokenUnfreezeTransaction +from .tokens.token_unpause_transaction import TokenUnpauseTransaction +from .tokens.token_update_nfts_transaction import TokenUpdateNftsTransaction +from .tokens.token_update_transaction import TokenUpdateTransaction +from .tokens.token_wipe_transaction import TokenWipeTransaction +from .transaction.batch_transaction import BatchTransaction + +# Transaction +from .transaction.custom_fee_limit import CustomFeeLimit +from .transaction.transaction import Transaction +from .transaction.transaction_id import TransactionId +from .transaction.transaction_receipt import TransactionReceipt +from .transaction.transaction_record import TransactionRecord +from .transaction.transaction_response import TransactionResponse +from .transaction.transfer_transaction import TransferTransaction -# Errors -from .exceptions import ReceiptStatusError -from .exceptions import PrecheckError __all__ = [ # Client "Client", "Network", - # Account "AccountId", "AccountCreateTransaction", @@ -169,12 +168,10 @@ "AccountAllowanceApproveTransaction", "AccountAllowanceDeleteTransaction", "AccountRecordsQuery", - # Crypto "PrivateKey", "PublicKey", "EvmAddress", - # Tokens "TokenCreateTransaction", "TokenAssociateTransaction", @@ -209,8 +206,8 @@ "HbarTransfer", "TokenPauseTransaction", "TokenUnpauseTransaction", + "TokenRevokeKycTransaction", "AssessedCustomFee", - # Transaction "Transaction", "TransferTransaction", @@ -219,17 +216,14 @@ "TransactionResponse", "TransactionRecord", "BatchTransaction", - # Response "ResponseCode", - # Consensus "TopicCreateTransaction", "TopicMessageSubmitTransaction", "TopicUpdateTransaction", "TopicDeleteTransaction", "TopicId", - # Queries "TopicInfoQuery", "TopicMessageQuery", @@ -239,15 +233,12 @@ "TokenNftInfoQuery", "TokenInfoQuery", "AccountInfoQuery", - # Address book "Endpoint", "NodeAddress", - # Logger "Logger", "LogLevel", - # HBAR "Hbar", "HbarUnit", @@ -255,7 +246,6 @@ "Timestamp", "Duration", "StakingInfo", - # File "FileCreateTransaction", "FileAppendTransaction", @@ -264,7 +254,6 @@ "FileContentsQuery", "FileUpdateTransaction", "FileDeleteTransaction", - # Contract "ContractCreateTransaction", "ContractCallQuery", @@ -277,7 +266,6 @@ "ContractInfo", "ContractUpdateTransaction", "EthereumTransaction", - # Schedule "ScheduleCreateTransaction", "ScheduleId", @@ -285,27 +273,22 @@ "ScheduleInfo", "ScheduleSignTransaction", "ScheduleDeleteTransaction", - # Nodes "NodeCreateTransaction", "NodeUpdateTransaction", "NodeDeleteTransaction", - # PRNG "PrngTransaction", - # Custom Fees "CustomFee", "CustomFixedFee", "CustomFractionalFee", "CustomRoyaltyFee", "CustomFeeLimit", - # System "FreezeTransaction", "FreezeType", - # Errors "ReceiptStatusError", - "PrecheckError" + "PrecheckError", ] diff --git a/src/hiero_sdk_python/account/account_allowance_approve_transaction.py b/src/hiero_sdk_python/account/account_allowance_approve_transaction.py index 6e8099ebe..056893792 100644 --- a/src/hiero_sdk_python/account/account_allowance_approve_transaction.py +++ b/src/hiero_sdk_python/account/account_allowance_approve_transaction.py @@ -1,8 +1,6 @@ -""" -AccountAllowanceApproveTransaction class for approving account allowances. -""" +"""AccountAllowanceApproveTransaction class for approving account allowances.""" -from typing import List, Optional +from __future__ import annotations from hiero_sdk_python.account.account_id import AccountId from hiero_sdk_python.channels import _Channel @@ -21,6 +19,7 @@ from hiero_sdk_python.tokens.token_nft_allowance import TokenNftAllowance from hiero_sdk_python.transaction.transaction import Transaction + DEFAULT_TRANSACTION_FEE = Hbar(1).to_tinybars() @@ -37,35 +36,29 @@ class AccountAllowanceApproveTransaction(Transaction): to be the owner for that particular allowance. Setting the amount to zero in HbarAllowance or TokenAllowance will remove the respective allowance for the spender. - NOTE: + Note: - If account 0.0.X pays for this transaction and owner is not specified in the allowance, then at consensus each spender account will have new allowances to spend HBAR or tokens from 0.0.X. """ def __init__( self, - hbar_allowances: Optional[List[HbarAllowance]] = None, - token_allowances: Optional[List[TokenAllowance]] = None, - nft_allowances: Optional[List[TokenNftAllowance]] = None, + hbar_allowances: list[HbarAllowance] | None = None, + token_allowances: list[TokenAllowance] | None = None, + nft_allowances: list[TokenNftAllowance] | None = None, ) -> None: """ Initializes a new AccountAllowanceApproveTransaction instance. Args: - hbar_allowances (Optional[List[HbarAllowance]]): Initial HBAR allowances. - token_allowances (Optional[List[TokenAllowance]]): Initial token allowances. - nft_allowances (Optional[List[TokenNftAllowance]]): Initial NFT allowances. + hbar_allowances (list[HbarAllowance], optional): Initial HBAR allowances. + token_allowances (list[TokenAllowance], optional): Initial token allowances. + nft_allowances (list[TokenNftAllowance], optional): Initial NFT allowances. """ super().__init__() - self.hbar_allowances: List[HbarAllowance] = ( - list(hbar_allowances) if hbar_allowances is not None else [] - ) - self.token_allowances: List[TokenAllowance] = ( - list(token_allowances) if token_allowances is not None else [] - ) - self.nft_allowances: List[TokenNftAllowance] = ( - list(nft_allowances) if nft_allowances is not None else [] - ) + self.hbar_allowances: list[HbarAllowance] = list(hbar_allowances) if hbar_allowances is not None else [] + self.token_allowances: list[TokenAllowance] = list(token_allowances) if token_allowances is not None else [] + self.nft_allowances: list[TokenNftAllowance] = list(nft_allowances) if nft_allowances is not None else [] self._default_transaction_fee = DEFAULT_TRANSACTION_FEE def approve_hbar_allowance( @@ -73,7 +66,7 @@ def approve_hbar_allowance( owner_account_id: AccountId, spender_account_id: AccountId, amount: Hbar, - ) -> "AccountAllowanceApproveTransaction": + ) -> AccountAllowanceApproveTransaction: """ Approves allowance of HBAR transfers for a spender. @@ -101,7 +94,7 @@ def approve_token_allowance( owner_account_id: AccountId, spender_account_id: AccountId, amount: int, - ) -> "AccountAllowanceApproveTransaction": + ) -> AccountAllowanceApproveTransaction: """ Approves allowance of fungible token transfers for a spender. @@ -131,7 +124,7 @@ def _approve_token_nft_approval( owner_account_id: AccountId, spender_account_id: AccountId, delegating_spender: AccountId, - ) -> "AccountAllowanceApproveTransaction": + ) -> AccountAllowanceApproveTransaction: """ Internal method to approve allowance of non-fungible token transfers for a spender. @@ -148,10 +141,7 @@ def _approve_token_nft_approval( # Check if there's already an allowance for this token and spender for allowance in self.nft_allowances: - if ( - allowance.token_id == nft_id.token_id - and allowance.spender_account_id == spender_account_id - ): + if allowance.token_id == nft_id.token_id and allowance.spender_account_id == spender_account_id: # Add the serial number if it's not already present if nft_id.serial_number not in allowance.serial_numbers: allowance.serial_numbers.append(nft_id.serial_number) @@ -175,7 +165,7 @@ def approve_token_nft_allowance( nft_id: NftId, owner_account_id: AccountId, spender_account_id: AccountId, - ) -> "AccountAllowanceApproveTransaction": + ) -> AccountAllowanceApproveTransaction: """ Approves allowance of non-fungible token transfers for a spender. @@ -195,7 +185,7 @@ def approve_token_nft_allowance_with_delegating_spender( owner_account_id: AccountId, spender_account_id: AccountId, delegating_spender: AccountId, - ) -> "AccountAllowanceApproveTransaction": + ) -> AccountAllowanceApproveTransaction: """ Approves allowance of non-fungible token transfers for a spender. @@ -208,16 +198,14 @@ def approve_token_nft_allowance_with_delegating_spender( Returns: AccountAllowanceApproveTransaction: This transaction instance. """ - return self._approve_token_nft_approval( - nft_id, owner_account_id, spender_account_id, delegating_spender - ) + return self._approve_token_nft_approval(nft_id, owner_account_id, spender_account_id, delegating_spender) def _approve_token_nft_allowance_all_serials( self, token_id: TokenId, owner_account_id: AccountId, spender_account_id: AccountId, - ) -> "AccountAllowanceApproveTransaction": + ) -> AccountAllowanceApproveTransaction: """ Internal method to approve allowance of non-fungible token transfers for a spender. @@ -233,10 +221,7 @@ def _approve_token_nft_allowance_all_serials( # Check if there's already an allowance for this token and spender for allowance in self.nft_allowances: - if ( - allowance.token_id == token_id - and allowance.spender_account_id == spender_account_id - ): + if allowance.token_id == token_id and allowance.spender_account_id == spender_account_id: allowance.serial_numbers = [] allowance.approved_for_all = True return self @@ -258,7 +243,7 @@ def approve_token_nft_allowance_all_serials( token_id: TokenId, owner_account_id: AccountId, spender_account_id: AccountId, - ) -> "AccountAllowanceApproveTransaction": + ) -> AccountAllowanceApproveTransaction: """ Approves allowance of non-fungible token transfers for a spender. Spender has access to all of the owner's NFT units of type tokenId (currently @@ -272,16 +257,14 @@ def approve_token_nft_allowance_all_serials( Returns: AccountAllowanceApproveTransaction: This transaction instance. """ - return self._approve_token_nft_allowance_all_serials( - token_id, owner_account_id, spender_account_id - ) + return self._approve_token_nft_allowance_all_serials(token_id, owner_account_id, spender_account_id) def delete_token_nft_allowance_all_serials( self, token_id: TokenId, owner_account_id: AccountId, spender_account_id: AccountId, - ) -> "AccountAllowanceApproveTransaction": + ) -> AccountAllowanceApproveTransaction: """ Revokes an allowance that permits a spender to transfer all of the owner's non-fungible tokens (NFTs) of a specific type (tokenId). @@ -299,10 +282,7 @@ def delete_token_nft_allowance_all_serials( self._require_not_frozen() for allowance in self.nft_allowances: - if ( - allowance.token_id == token_id - and allowance.spender_account_id == spender_account_id - ): + if allowance.token_id == token_id and allowance.spender_account_id == spender_account_id: allowance.serial_numbers = [] allowance.approved_for_all = True return self @@ -322,7 +302,7 @@ def add_all_token_nft_approval( self, token_id: TokenId, spender_account_id: AccountId, - ) -> "AccountAllowanceApproveTransaction": + ) -> AccountAllowanceApproveTransaction: """ Approve allowance of non-fungible token transfers for a spender. Spender has access to all of the owner's NFT units of type tokenId (currently @@ -348,14 +328,12 @@ def _build_proto_body(self) -> CryptoApproveAllowanceTransactionBody: Returns: CryptoApproveAllowanceTransactionBody: The protobuf body for this transaction. """ - body = CryptoApproveAllowanceTransactionBody( + return CryptoApproveAllowanceTransactionBody( cryptoAllowances=[allowance._to_proto() for allowance in self.hbar_allowances], tokenAllowances=[allowance._to_proto() for allowance in self.token_allowances], nftAllowances=[allowance._to_proto() for allowance in self.nft_allowances], ) - return body - def build_transaction_body(self): """ Builds the transaction body for this account allowance approve transaction. diff --git a/src/hiero_sdk_python/account/account_allowance_delete_transaction.py b/src/hiero_sdk_python/account/account_allowance_delete_transaction.py index 298a41f18..6603cac6f 100644 --- a/src/hiero_sdk_python/account/account_allowance_delete_transaction.py +++ b/src/hiero_sdk_python/account/account_allowance_delete_transaction.py @@ -1,8 +1,6 @@ -""" -AccountAllowanceDeleteTransaction class for deleting account allowances. -""" +"""AccountAllowanceDeleteTransaction class for deleting account allowances.""" -from typing import List, Optional +from __future__ import annotations from hiero_sdk_python.account.account_id import AccountId from hiero_sdk_python.channels import _Channel @@ -18,6 +16,7 @@ from hiero_sdk_python.tokens.token_nft_allowance import TokenNftAllowance from hiero_sdk_python.transaction.transaction import Transaction + DEFAULT_TRANSACTION_FEE = Hbar(1).to_tinybars() @@ -35,23 +34,23 @@ class AccountAllowanceDeleteTransaction(Transaction): def __init__( self, - nft_wipe: Optional[List[TokenNftAllowance]] = None, + nft_wipe: list[TokenNftAllowance] | None = None, ) -> None: """ Initializes a new AccountAllowanceDeleteTransaction instance. Args: - nft_wipe (Optional[List[TokenNftAllowance]]): Initial NFT allowances to delete. + nft_wipe (list[TokenNftAllowance], optional): Initial NFT allowances to delete. """ super().__init__() - self.nft_wipe: List[TokenNftAllowance] = list(nft_wipe) if nft_wipe is not None else [] + self.nft_wipe: list[TokenNftAllowance] = list(nft_wipe) if nft_wipe is not None else [] self._default_transaction_fee = DEFAULT_TRANSACTION_FEE def delete_all_token_nft_allowances( self, nft_id: NftId, owner_account_id: AccountId, - ) -> "AccountAllowanceDeleteTransaction": + ) -> AccountAllowanceDeleteTransaction: """ Deletes non-fungible token allowance/allowances to remove. @@ -66,10 +65,7 @@ def delete_all_token_nft_allowances( # Check if there's already a wipe entry for this token and owner for wipe_entry in self.nft_wipe: - if ( - wipe_entry.token_id == nft_id.token_id - and wipe_entry.owner_account_id == owner_account_id - ): + if wipe_entry.token_id == nft_id.token_id and wipe_entry.owner_account_id == owner_account_id: # Add the serial number if it's not already present if nft_id.serial_number not in wipe_entry.serial_numbers: wipe_entry.serial_numbers.append(nft_id.serial_number) @@ -93,12 +89,10 @@ def _build_proto_body(self) -> CryptoDeleteAllowanceTransactionBody: Returns: CryptoDeleteAllowanceTransactionBody: The protobuf body for this transaction. """ - body = CryptoDeleteAllowanceTransactionBody( + return CryptoDeleteAllowanceTransactionBody( nftAllowances=[allowance._to_wipe_proto() for allowance in self.nft_wipe], ) - return body - def build_transaction_body(self): """ Builds the transaction body for this account allowance delete transaction. diff --git a/src/hiero_sdk_python/account/account_balance.py b/src/hiero_sdk_python/account/account_balance.py index 12af2e170..b0afab550 100644 --- a/src/hiero_sdk_python/account/account_balance.py +++ b/src/hiero_sdk_python/account/account_balance.py @@ -1,8 +1,6 @@ -""" -AccountBalance class. -""" +"""AccountBalance class.""" -from typing import Dict +from __future__ import annotations from hiero_sdk_python.hapi.services.crypto_get_account_balance_pb2 import ( CryptoGetAccountBalanceResponse, @@ -20,7 +18,7 @@ class AccountBalance: token_balances (dict): A dictionary mapping TokenId to token balances. """ - def __init__(self, hbars: Hbar, token_balances: Dict[TokenId, int] = None) -> None: + def __init__(self, hbars: Hbar, token_balances: dict[TokenId, int] = None) -> None: """ Initializes the AccountBalance with the given hbar balance and token balances. @@ -32,7 +30,7 @@ def __init__(self, hbars: Hbar, token_balances: Dict[TokenId, int] = None) -> No self.token_balances = token_balances or {} @classmethod - def _from_proto(cls, proto: CryptoGetAccountBalanceResponse) -> "AccountBalance": + def _from_proto(cls, proto: CryptoGetAccountBalanceResponse) -> AccountBalance: """ Creates an AccountBalance instance from a protobuf response. @@ -44,7 +42,7 @@ def _from_proto(cls, proto: CryptoGetAccountBalanceResponse) -> "AccountBalance" """ hbars: Hbar = Hbar.from_tinybars(tinybars=proto.balance) - token_balances: Dict[TokenId, int] = {} + token_balances: dict[TokenId, int] = {} if proto.tokenBalances: for token_balance in proto.tokenBalances: token_id: TokenId = TokenId._from_proto(token_balance.tokenId) @@ -56,7 +54,7 @@ def _from_proto(cls, proto: CryptoGetAccountBalanceResponse) -> "AccountBalance" def __str__(self) -> str: """ Returns a human-friendly string representation of the account balance. - + Returns: str: A string showing HBAR balance and token balances. """ @@ -70,7 +68,7 @@ def __str__(self) -> str: def __repr__(self) -> str: """ Returns a developer-friendly string representation of the account balance. - + Returns: str: A string representation that shows the key attributes. """ diff --git a/src/hiero_sdk_python/account/account_create_transaction.py b/src/hiero_sdk_python/account/account_create_transaction.py index 02e8b0cb4..0aeac467c 100644 --- a/src/hiero_sdk_python/account/account_create_transaction.py +++ b/src/hiero_sdk_python/account/account_create_transaction.py @@ -1,9 +1,8 @@ -""" -AccountCreateTransaction class. -""" +"""AccountCreateTransaction class.""" + +from __future__ import annotations import ctypes -from typing import Optional, Union import warnings from hiero_sdk_python.account.account_id import AccountId @@ -20,60 +19,59 @@ from hiero_sdk_python.hbar import Hbar from hiero_sdk_python.transaction.transaction import Transaction + AUTO_RENEW_PERIOD = Duration(7890000) # around 90 days in seconds DEFAULT_TRANSACTION_FEE = Hbar(3).to_tinybars() # 3 Hbars class AccountCreateTransaction(Transaction): - """ - Represents an account creation transaction on the Hedera network. - """ + """Represents an account creation transaction on the Hedera network.""" def __init__( self, - key: Optional[Key] = None, - initial_balance: Union[Hbar, int] = 0, - receiver_signature_required: Optional[bool] = None, - auto_renew_period: Optional[Duration] = AUTO_RENEW_PERIOD, - memo: Optional[str] = None, - max_automatic_token_associations: Optional[int] = 0, - alias: Optional[EvmAddress] = None, - staked_account_id: Optional[AccountId] = None, - staked_node_id: Optional[int] = None, - decline_staking_reward: Optional[bool] = False + key: Key | None = None, + initial_balance: Hbar | int = 0, + receiver_signature_required: bool | None = None, + auto_renew_period: Duration | None = AUTO_RENEW_PERIOD, + memo: str | None = None, + max_automatic_token_associations: int | None = 0, + alias: EvmAddress | None = None, + staked_account_id: AccountId | None = None, + staked_node_id: int | None = None, + decline_staking_reward: bool | None = False, ) -> None: """ Initializes a new AccountCreateTransaction instance with default values or specified keyword arguments. Attributes: - key (Optional[PublicKey]): The public key for the new account. - initial_balance (Union[Hbar, int]): Initial balance in Hbar or tinybars. - receiver_signature_required (Optional[bool]): Whether receiver signature is required. + key (PublicKey, optional): The public key for the new account. + initial_balance (Hbar | int, optional): Initial balance in Hbar or tinybars. + receiver_signature_required (bool, optional): Whether receiver signature is required. auto_renew_period (Duration): Auto-renew period in seconds (default is ~90 days). - memo (Optional[str]): Memo for the account. - max_automatic_token_associations (Optional[int]): The maximum number of tokens that + memo (str, optional): Memo for the account. + max_automatic_token_associations (int, optional): The maximum number of tokens that can be auto-associated. - alias (Optional[EvmAddress]): The 20-byte EVM address to be used as the account's alias. - staked_account_id (Optional[AccountId]): The account to which this account will stake. - staked_node_id (Optional[int]): ID of the node this account is staked to. - decline_staking_reward (Optional[bool]): If true, the account declines receiving a + alias (EvmAddress, optional): The 20-byte EVM address to be used as the account's alias. + staked_account_id (AccountId, optional): The account to which this account will stake. + staked_node_id (int, optional): ID of the node this account is staked to. + decline_staking_reward (bool, optional): If true, the account declines receiving a staking reward (default is False). """ super().__init__() - self.key: Optional[Key] = key - self.initial_balance: Union[Hbar, int] = initial_balance - self.receiver_signature_required: Optional[bool] = receiver_signature_required - self.auto_renew_period: Optional[Duration] = auto_renew_period - self.account_memo: Optional[str] = memo - self.max_automatic_token_associations: Optional[int] = max_automatic_token_associations + self.key: Key | None = key + self.initial_balance: Hbar | int = initial_balance + self.receiver_signature_required: bool | None = receiver_signature_required + self.auto_renew_period: Duration | None = auto_renew_period + self.account_memo: str | None = memo + self.max_automatic_token_associations: int | None = max_automatic_token_associations self._default_transaction_fee = DEFAULT_TRANSACTION_FEE - self.alias: Optional[EvmAddress] = alias - self.staked_account_id: Optional[AccountId] = staked_account_id - self.staked_node_id: Optional[int] = staked_node_id + self.alias: EvmAddress | None = alias + self.staked_account_id: AccountId | None = staked_account_id + self.staked_node_id: int | None = staked_node_id self.decline_staking_reward = decline_staking_reward - def set_key(self, key: Key) -> "AccountCreateTransaction": + def set_key(self, key: Key) -> AccountCreateTransaction: """ Sets the key for the new account (accepts both PrivateKey or PublicKey). @@ -86,12 +84,13 @@ def set_key(self, key: Key) -> "AccountCreateTransaction": warnings.warn( "The 'set_key' method is deprecated, Use `set_key_without_alias` instead.", DeprecationWarning, + stacklevel=2, ) self._require_not_frozen() self.key = key return self - def set_key_without_alias(self, key: Key) -> "AccountCreateTransaction": + def set_key_without_alias(self, key: Key) -> AccountCreateTransaction: """ Sets the key for the new account without alias (accepts both PrivateKey or PublicKey). @@ -106,20 +105,16 @@ def set_key_without_alias(self, key: Key) -> "AccountCreateTransaction": self.alias = None return self - def set_key_with_alias( - self, - key: Key, - ecdsa_key: Optional[Key]=None - ) -> "AccountCreateTransaction": + def set_key_with_alias(self, key: Key, ecdsa_key: Key | None = None) -> AccountCreateTransaction: """ Sets the key for the new account and assigns an alias derived from an ECDSA key. - + If `ecdsa_key` is provided, its corresponding EVM address will be used as the account alias. Otherwise, the alias will be derived from the provided `key`. Args: key (Key): The key to assign to the account (PrivateKey or PublicKey). - ecdsa_key (Optional[PublicKey]): An optional ECDSA public key used + ecdsa_key (PublicKey, optional): An optional ECDSA public key used to derive the account alias. Returns: @@ -134,7 +129,7 @@ def set_key_with_alias( self.alias = evm_source_key.to_evm_address() return self - def set_initial_balance(self, balance: Union[Hbar, int]) -> "AccountCreateTransaction": + def set_initial_balance(self, balance: Hbar | int) -> AccountCreateTransaction: """ Sets the initial balance for the new account. @@ -146,13 +141,11 @@ def set_initial_balance(self, balance: Union[Hbar, int]) -> "AccountCreateTransa """ self._require_not_frozen() if not isinstance(balance, (Hbar, int)): - raise TypeError( - "initial_balance must be either an instance of Hbar or an integer (tinybars)." - ) + raise TypeError("initial_balance must be either an instance of Hbar or an integer (tinybars).") self.initial_balance = balance return self - def set_receiver_signature_required(self, required: bool) -> "AccountCreateTransaction": + def set_receiver_signature_required(self, required: bool) -> AccountCreateTransaction: """ Sets whether a receiver signature is required. @@ -166,7 +159,7 @@ def set_receiver_signature_required(self, required: bool) -> "AccountCreateTrans self.receiver_signature_required = required return self - def set_auto_renew_period(self, seconds: Union[int, Duration]) -> "AccountCreateTransaction": + def set_auto_renew_period(self, seconds: int | Duration) -> AccountCreateTransaction: """ Sets the auto-renew period in seconds. @@ -185,7 +178,7 @@ def set_auto_renew_period(self, seconds: Union[int, Duration]) -> "AccountCreate raise TypeError("Duration of invalid type") return self - def set_account_memo(self, memo: str) -> "AccountCreateTransaction": + def set_account_memo(self, memo: str) -> AccountCreateTransaction: """ Sets the memo for the new account. @@ -199,12 +192,12 @@ def set_account_memo(self, memo: str) -> "AccountCreateTransaction": self.account_memo = memo return self - def set_max_automatic_token_associations(self, max_assoc: int) -> "AccountCreateTransaction": + def set_max_automatic_token_associations(self, max_assoc: int) -> AccountCreateTransaction: """ Sets the maximum number of automatic token associations for the account. Args: - max_assoc (int): The maximum number of automatic + max_assoc (int): The maximum number of automatic token associations to allow (default 0). Returns: @@ -217,12 +210,12 @@ def set_max_automatic_token_associations(self, max_assoc: int) -> "AccountCreate self.max_automatic_token_associations = max_assoc return self - def set_alias(self, alias_evm_address: Union[EvmAddress, str]) -> "AccountCreateTransaction": + def set_alias(self, alias_evm_address: EvmAddress | str) -> AccountCreateTransaction: """ Sets the EVM Address alias for the account. Args: - alias_evm_address (Union[EvmAddress, str]): The 20-byte EVM address to + alias_evm_address (EvmAddress | str): The 20-byte EVM address to be used as the account's alias. Returns: @@ -243,15 +236,12 @@ def set_alias(self, alias_evm_address: Union[EvmAddress, str]) -> "AccountCreate return self - def set_staked_account_id( - self, - account_id: Union[AccountId, str] - ) -> "AccountCreateTransaction": + def set_staked_account_id(self, account_id: AccountId | str) -> AccountCreateTransaction: """ Sets the staked account id for the account. Args: - account_id (Union[AccountId, str]): The account to which this account will stake. + account_id (AccountId | str): The account to which this account will stake. Returns: AccountCreateTransaction: The current transaction instance for method chaining. @@ -261,12 +251,12 @@ def set_staked_account_id( account_id = AccountId.from_string(account_id) elif not isinstance(account_id, AccountId): raise TypeError("account_id must be of type str or AccountId") - + self.staked_account_id = account_id self.staked_node_id = None return self - def set_staked_node_id(self, node_id: int) -> "AccountCreateTransaction": + def set_staked_node_id(self, node_id: int) -> AccountCreateTransaction: """ Sets the staked node id for the account. @@ -284,15 +274,12 @@ def set_staked_node_id(self, node_id: int) -> "AccountCreateTransaction": self.staked_account_id = None return self - def set_decline_staking_reward( - self, - decline_staking_reward: bool - ) -> "AccountCreateTransaction": + def set_decline_staking_reward(self, decline_staking_reward: bool) -> AccountCreateTransaction: """ Sets the decline staking reward for the account. Args: - decline_staking_reward (bool): If true, the account declines + decline_staking_reward (bool): If true, the account declines receiving a staking reward (default is False) Returns: @@ -316,14 +303,13 @@ def _build_proto_body(self) -> crypto_create_pb2.CryptoCreateTransactionBody: ValueError: If required fields are missing. TypeError: If initial_balance is an invalid type. """ - if isinstance(self.initial_balance, Hbar): initial_balance_tinybars = self.initial_balance.to_tinybars() elif isinstance(self.initial_balance, int): initial_balance_tinybars = self.initial_balance else: raise TypeError("initial_balance must be Hbar or int (tinybars).") - + # Check for overflow if initial_balance_tinybars >= (2**64): raise OverflowError(f"Value {initial_balance_tinybars} exceeds 64-bit unsigned integer limit.") @@ -337,7 +323,7 @@ def _build_proto_body(self) -> crypto_create_pb2.CryptoCreateTransactionBody: memo=self.account_memo, max_automatic_token_associations=self.max_automatic_token_associations, alias=self.alias.address_bytes if self.alias else None, - decline_reward=self.decline_staking_reward + decline_reward=self.decline_staking_reward, ) if self.staked_node_id is not None and self.staked_account_id is not None: @@ -377,8 +363,10 @@ def build_scheduled_body(self) -> SchedulableTransactionBody: def _get_method(self, channel: _Channel) -> _Method: """ Returns the method for executing the account creation transaction. + Args: channel (_Channel): The channel to use for the transaction. + Returns: _Method: An instance of _Method containing the transaction and query functions. """ diff --git a/src/hiero_sdk_python/account/account_delete_transaction.py b/src/hiero_sdk_python/account/account_delete_transaction.py index e9ab6d497..cb84097a3 100644 --- a/src/hiero_sdk_python/account/account_delete_transaction.py +++ b/src/hiero_sdk_python/account/account_delete_transaction.py @@ -1,8 +1,6 @@ -""" -AccountDeleteTransaction class. -""" +"""AccountDeleteTransaction class.""" -from typing import Optional +from __future__ import annotations from hiero_sdk_python.account.account_id import AccountId from hiero_sdk_python.channels import _Channel @@ -14,6 +12,7 @@ from hiero_sdk_python.hbar import Hbar from hiero_sdk_python.transaction.transaction import Transaction + DEFAULT_TRANSACTION_FEE = Hbar(2).to_tinybars() @@ -24,35 +23,35 @@ class AccountDeleteTransaction(Transaction): This transaction can be used to delete an existing account from the network. Args: - account_id (Optional[AccountId]): The ID of the account to delete. - transfer_account_id (Optional[AccountId]): The account ID to transfer + account_id (AccountId, optional): The ID of the account to delete. + transfer_account_id (AccountId, optional): The account ID to transfer remaining balance to. """ def __init__( self, - account_id: Optional[AccountId] = None, - transfer_account_id: Optional[AccountId] = None, + account_id: AccountId | None = None, + transfer_account_id: AccountId | None = None, ): """ Initializes a new AccountDeleteTransaction instance. Args: - account_id (Optional[AccountId]): The ID of the account to delete. - transfer_account_id (Optional[AccountId]): The account ID to transfer + account_id (AccountId, optional): The ID of the account to delete. + transfer_account_id (AccountId, optional): The account ID to transfer remaining balance to. """ super().__init__() - self.account_id: Optional[AccountId] = account_id - self.transfer_account_id: Optional[AccountId] = transfer_account_id + self.account_id: AccountId | None = account_id + self.transfer_account_id: AccountId | None = transfer_account_id self._default_transaction_fee = DEFAULT_TRANSACTION_FEE - def set_account_id(self, account_id: Optional[AccountId]) -> "AccountDeleteTransaction": + def set_account_id(self, account_id: AccountId | None) -> AccountDeleteTransaction: """ Sets the ID of the account to delete. Args: - account_id (Optional[AccountId]): The ID of the account to delete. + account_id (AccountId | None): The ID of the account to delete. Returns: AccountDeleteTransaction: This transaction instance. @@ -61,9 +60,7 @@ def set_account_id(self, account_id: Optional[AccountId]) -> "AccountDeleteTrans self.account_id = account_id return self - def set_transfer_account_id( - self, transfer_account_id: Optional[AccountId] - ) -> "AccountDeleteTransaction": + def set_transfer_account_id(self, transfer_account_id: AccountId | None) -> AccountDeleteTransaction: """ Sets the account ID to transfer the remaining balance to. @@ -71,7 +68,7 @@ def set_transfer_account_id( to another account. This method sets the target account for the balance transfer. Args: - transfer_account_id (Optional[AccountId]): The account ID to transfer + transfer_account_id (AccountId | None): The account ID to transfer remaining balance to. Returns: @@ -99,9 +96,7 @@ def _build_proto_body(self): return CryptoDeleteTransactionBody( deleteAccountID=self.account_id._to_proto(), - transferAccountID=( - self.transfer_account_id._to_proto() if self.transfer_account_id else None - ), + transferAccountID=(self.transfer_account_id._to_proto() if self.transfer_account_id else None), ) def build_transaction_body(self): diff --git a/src/hiero_sdk_python/account/account_id.py b/src/hiero_sdk_python/account/account_id.py index 35535689a..7070fdbd7 100644 --- a/src/hiero_sdk_python/account/account_id.py +++ b/src/hiero_sdk_python/account/account_id.py @@ -1,21 +1,22 @@ -""" -AccountId class. -""" +"""AccountId class.""" + +from __future__ import annotations import re -from typing import TYPE_CHECKING, Optional, Union +from typing import TYPE_CHECKING from hiero_sdk_python.crypto.evm_address import EvmAddress from hiero_sdk_python.crypto.public_key import PublicKey from hiero_sdk_python.hapi.services import basic_types_pb2 from hiero_sdk_python.utils.entity_id_helper import ( - parse_from_string, - validate_checksum, format_to_string_with_checksum, + parse_from_string, perform_query_to_mirror_node, to_solidity_address, + validate_checksum, ) + if TYPE_CHECKING: from hiero_sdk_python.client.client import Client @@ -41,11 +42,12 @@ def __init__( shard: int = 0, realm: int = 0, num: int = 0, - alias_key: Optional[PublicKey] = None, - evm_address: Optional[EvmAddress] = None, + alias_key: PublicKey | None = None, + evm_address: EvmAddress | None = None, ) -> None: """ Initialize a new AccountId instance. + Args: shard (int): The shard number of the account. realm (int): The realm number of the account. @@ -61,14 +63,14 @@ def __init__( self.__checksum: str | None = None @classmethod - def from_string(cls, account_id_str: str) -> "AccountId": + def from_string(cls, account_id_str: str) -> AccountId: """ Creates an AccountId instance from a string. Supported formats: - shard.realm.num - shard.realm.num-checksum - shard.realm. - - 0x-prefixed or raw 20-byte hex EVM address + - 0x-prefixed or raw 20-byte hex EVM address. Args: account_id_str (str): Account ID string @@ -80,9 +82,7 @@ def from_string(cls, account_id_str: str) -> "AccountId": ValueError: If the string format is invalid """ if account_id_str is None or not isinstance(account_id_str, str): - raise TypeError( - f"account_id_str must be a string, got {type(account_id_str).__name__}." - ) + raise TypeError(f"account_id_str must be a string, got {type(account_id_str).__name__}.") if cls._is_evm_address(account_id_str): # Detect EVM address input (raw 20-byte hex or 0x-prefixed). @@ -94,9 +94,7 @@ def from_string(cls, account_id_str: str) -> "AccountId": try: shard, realm, num, checksum = parse_from_string(account_id_str) - account_id: AccountId = cls( - shard=int(shard), realm=int(realm), num=int(num) - ) + account_id: AccountId = cls(shard=int(shard), realm=int(realm), num=int(num)) account_id.__checksum = checksum return account_id @@ -115,14 +113,8 @@ def from_string(cls, account_id_str: str) -> "AccountId": shard=int(shard), realm=int(realm), num=0, - alias_key=( - PublicKey.from_bytes(alias_bytes) - if not is_evm_address - else None - ), - evm_address=( - EvmAddress.from_bytes(alias_bytes) if is_evm_address else None - ), + alias_key=(PublicKey.from_bytes(alias_bytes) if not is_evm_address else None), + evm_address=(EvmAddress.from_bytes(alias_bytes) if is_evm_address else None), ) raise ValueError( @@ -135,12 +127,10 @@ def from_string(cls, account_id_str: str) -> "AccountId": ) from e @classmethod - def from_evm_address( - cls, evm_address: Union[str, EvmAddress], shard: int, realm: int - ) -> "AccountId": + def from_evm_address(cls, evm_address: str | EvmAddress, shard: int, realm: int) -> AccountId: """ Create an AccountId from an EVM address. - In case shard and realm are unknown, they should be set to zero + In case shard and realm are unknown, they should be set to zero. Args: evm_address (Union[str, EvmAddress]): EVM address string or object @@ -160,9 +150,7 @@ def from_evm_address( raise ValueError(f"Invalid EVM address string: {evm_address}") from e elif not isinstance(evm_address, EvmAddress): - raise TypeError( - f"evm_address must be a str or EvmAddress, got {type(evm_address).__name__}" - ) + raise TypeError(f"evm_address must be a str or EvmAddress, got {type(evm_address).__name__}") return cls( shard=shard, @@ -173,7 +161,7 @@ def from_evm_address( ) @classmethod - def from_bytes(cls, data: bytes) -> "AccountId": + def from_bytes(cls, data: bytes) -> AccountId: """ Deserialize an AccountId from protobuf-encoded bytes. @@ -186,7 +174,7 @@ def from_bytes(cls, data: bytes) -> "AccountId": return cls._from_proto(basic_types_pb2.AccountID.FromString(data)) @classmethod - def _from_proto(cls, account_id_proto: basic_types_pb2.AccountID) -> "AccountId": + def _from_proto(cls, account_id_proto: basic_types_pb2.AccountID) -> AccountId: """ Creates an AccountId instance from a protobuf AccountID object. @@ -234,15 +222,13 @@ def _to_proto(self) -> basic_types_pb2.AccountID: @property def checksum(self) -> str | None: - """Checksum of the accountId""" + """Checksum of the accountId.""" return self.__checksum - def validate_checksum(self, client: "Client") -> None: - """Validate the checksum for the accountId""" + def validate_checksum(self, client: Client) -> None: + """Validate the checksum for the accountId.""" if self.alias_key is not None or self.evm_address is not None: - raise ValueError( - "Cannot calculate checksum with an account ID that has a aliasKey or evmAddress" - ) + raise ValueError("Cannot calculate checksum with an account ID that has a aliasKey or evmAddress") validate_checksum( self.shard, @@ -254,7 +240,7 @@ def validate_checksum(self, client: "Client") -> None: @staticmethod def _is_evm_address(value: str) -> bool: - """Check if the given string value is an evm_address""" + """Check if the given string value is an evm_address.""" if value.startswith("0x"): value = value[2:] @@ -269,28 +255,24 @@ def _is_evm_address(value: str) -> bool: return True def __str__(self) -> str: - """ - Returns the string representation of the AccountId in 'shard.realm.num' format. - """ + """Returns the string representation of the AccountId in 'shard.realm.num' format.""" if self.alias_key: return f"{self.shard}.{self.realm}.{self.alias_key.to_string()}" if self.evm_address: return f"{self.shard}.{self.realm}.{self.evm_address.to_string()}" return f"{self.shard}.{self.realm}.{self.num}" - def to_string_with_checksum(self, client: "Client") -> str: + def to_string_with_checksum(self, client: Client) -> str: """ Returns the string representation of the AccountId with checksum in 'shard.realm.num-checksum' format. """ if self.alias_key is not None or self.evm_address is not None: - raise ValueError( - "Cannot calculate checksum with an account ID that has a aliasKey or evmAddress" - ) + raise ValueError("Cannot calculate checksum with an account ID that has a aliasKey or evmAddress") return format_to_string_with_checksum(self.shard, self.realm, self.num, client) - def populate_account_num(self, client: "Client") -> "AccountId": + def populate_account_num(self, client: Client) -> AccountId: """ Populate the numeric account ID using the Mirror Node. Intended for AccountIds created from EVM addresses. @@ -319,8 +301,7 @@ def populate_account_num(self, client: "Client") -> "AccountId": except RuntimeError as e: raise RuntimeError( - "Failed to populate account number from mirror node for evm_address " - f"{self.evm_address.to_string()}" + f"Failed to populate account number from mirror node for evm_address {self.evm_address.to_string()}" ) from e try: @@ -334,7 +315,7 @@ def populate_account_num(self, client: "Client") -> "AccountId": except (ValueError, AttributeError) as e: raise ValueError(f"Invalid account format received: {account_id}") from e - def populate_evm_address(self, client: "Client") -> "AccountId": + def populate_evm_address(self, client: Client) -> AccountId: """ Populate the EVM address using the Mirror Node. @@ -362,17 +343,13 @@ def populate_evm_address(self, client: "Client") -> "AccountId": raise ValueError("Mirror node response missing 'evm_address'") except RuntimeError as e: - raise RuntimeError( - f"Failed to populate evm_address from mirror node for account {self.num}" - ) from e + raise RuntimeError(f"Failed to populate evm_address from mirror node for account {self.num}") from e evm_address = EvmAddress.from_string(evm_addr) - return AccountId( - shard=self.shard, realm=self.realm, num=self.num, evm_address=evm_address - ) + return AccountId(shard=self.shard, realm=self.realm, num=self.num, evm_address=evm_address) def to_evm_address(self) -> str: - """Return the EVM-compatible address for this account. Using account num""" + """Return the EVM-compatible address for this account. Using account num.""" if self.evm_address: return self.evm_address.to_string() @@ -383,26 +360,20 @@ def to_bytes(self) -> bytes: return self._to_proto().SerializeToString() def __repr__(self) -> str: - """ - Returns the repr representation of the AccountId. - """ + """Returns the repr representation of the AccountId.""" if self.alias_key: - return ( - f"AccountId(shard={self.shard}, realm={self.realm}, " - f"alias_key={self.alias_key.to_string_raw()})" - ) + return f"AccountId(shard={self.shard}, realm={self.realm}, alias_key={self.alias_key.to_string_raw()})" if self.evm_address: - return ( - f"AccountId(shard={self.shard}, realm={self.realm}, " - f"evm_address={self.evm_address.to_string()})" - ) + return f"AccountId(shard={self.shard}, realm={self.realm}, evm_address={self.evm_address.to_string()})" return f"AccountId(shard={self.shard}, realm={self.realm}, num={self.num})" def __eq__(self, other: object) -> bool: """ Checks equality between two AccountId instances. + Args: other (object): The object to compare with. + Returns: bool: True if both instances are equal, False otherwise. """ diff --git a/src/hiero_sdk_python/account/account_info.py b/src/hiero_sdk_python/account/account_info.py index 477da0351..f32d48f03 100644 --- a/src/hiero_sdk_python/account/account_info.py +++ b/src/hiero_sdk_python/account/account_info.py @@ -1,18 +1,17 @@ # pylint: disable=too-many-instance-attributes -""" -AccountInfo class. -""" +"""AccountInfo class.""" + +from __future__ import annotations import warnings from dataclasses import dataclass, field -from typing import Optional from hiero_sdk_python.account.account_id import AccountId -from hiero_sdk_python.staking_info import StakingInfo from hiero_sdk_python.crypto.key import Key from hiero_sdk_python.Duration import Duration from hiero_sdk_python.hapi.services.crypto_get_info_pb2 import CryptoGetInfoResponse from hiero_sdk_python.hbar import Hbar +from hiero_sdk_python.staking_info import StakingInfo from hiero_sdk_python.timestamp import Timestamp from hiero_sdk_python.tokens.token_relationship import TokenRelationship @@ -23,60 +22,63 @@ class AccountInfo: Contains information about an account. Attributes: - account_id (Optional[AccountId]): The ID of this account. - contract_account_id (Optional[str]): The contract account ID. - is_deleted (Optional[bool]): Whether the account has been deleted. - proxy_received (Optional[Hbar]): The total number of tinybars proxy staked to this account. - key (Optional[Key]): The key for this account. - balance (Optional[Hbar]): The current balance of account in hbar. + account_id (AccountId, optional): The ID of this account. + contract_account_id (str, optional): The contract account ID. + is_deleted (bool, optional): Whether the account has been deleted. + proxy_received (Hbar, optional): The total number of tinybars proxy staked to this account. + key (Key, optional): The key for this account. + balance (Hbar, optional): The current balance of account in hbar. receiver_signature_required (Optional[bool]): If true, this account's key must sign any transaction depositing into this account. - expiration_time (Optional[Timestamp]): The timestamp at which this account + expiration_time (Timestamp, optional): The timestamp at which this account is set to expire. - auto_renew_period (Optional[Duration]): The duration for which this account + auto_renew_period (Duration, optional): The duration for which this account will automatically renew. - token_relationships (list[TokenRelationship]): List of token relationships + token_relationships (list[TokenRelationship], optional): List of token relationships associated with this account. - account_memo (Optional[str]): The memo associated with this account. - owned_nfts (Optional[int]): The number of NFTs owned by this account. - staking_info (Optional[StakingInfo]): The staking information for this account. + account_memo (str, optional): The memo associated with this account. + owned_nfts (int, optional): The number of NFTs owned by this account. + staking_info (StakingInfo, optional): The staking information for this account. """ - account_id: Optional[AccountId] = None - contract_account_id: Optional[str] = None - is_deleted: Optional[bool] = None - proxy_received: Optional[Hbar] = None - key: Optional[Key] = None - balance: Optional[Hbar] = None - receiver_signature_required: Optional[bool] = None - expiration_time: Optional[Timestamp] = None - auto_renew_period: Optional[Duration] = None + account_id: AccountId | None = None + contract_account_id: str | None = None + is_deleted: bool | None = None + proxy_received: Hbar | None = None + key: Key | None = None + balance: Hbar | None = None + receiver_signature_required: bool | None = None + expiration_time: Timestamp | None = None + auto_renew_period: Duration | None = None token_relationships: list[TokenRelationship] = field(default_factory=list) - account_memo: Optional[str] = None - owned_nfts: Optional[int] = None - max_automatic_token_associations: Optional[int] = None - staking_info: Optional[StakingInfo] = None + account_memo: str | None = None + owned_nfts: int | None = None + max_automatic_token_associations: int | None = None + staking_info: StakingInfo | None = None @classmethod - def _from_proto(cls, proto: CryptoGetInfoResponse.AccountInfo) -> "AccountInfo": + def _from_proto(cls, proto: CryptoGetInfoResponse.AccountInfo) -> AccountInfo: """Creates an AccountInfo instance from its protobuf representation. Deserializes a `CryptoGetInfoResponse.AccountInfo` message into this SDK's `AccountInfo` object. This method handles the conversion of protobuf types to their corresponding SDK types (e.g., tinybars to `Hbar`, proto `Timestamp` to SDK `Timestamp`). + Args: proto (CryptoGetInfoResponse.AccountInfo): The source protobuf message containing account information. + Returns: AccountInfo: A new `AccountInfo` instance populated with data from the protobuf message. + Raises: ValueError: If the input `proto` is None. """ if proto is None: raise ValueError("Account info proto is None") - account_info: "AccountInfo" = cls( + account_info: AccountInfo = cls( account_id=AccountId._from_proto(proto.accountID) if proto.accountID else None, contract_account_id=proto.contractAccountID, is_deleted=proto.deleted, @@ -84,23 +86,15 @@ def _from_proto(cls, proto: CryptoGetInfoResponse.AccountInfo) -> "AccountInfo": key=Key.from_proto_key(proto.key) if proto.key else None, balance=Hbar.from_tinybars(proto.balance), receiver_signature_required=proto.receiverSigRequired, - expiration_time=( - Timestamp._from_protobuf(proto.expirationTime) if proto.expirationTime else None - ), - auto_renew_period=( - Duration._from_proto(proto.autoRenewPeriod) if proto.autoRenewPeriod else None - ), + expiration_time=(Timestamp._from_protobuf(proto.expirationTime) if proto.expirationTime else None), + auto_renew_period=(Duration._from_proto(proto.autoRenewPeriod) if proto.autoRenewPeriod else None), token_relationships=[ - TokenRelationship._from_proto(relationship) - for relationship in proto.tokenRelationships + TokenRelationship._from_proto(relationship) for relationship in proto.tokenRelationships ], account_memo=proto.memo, owned_nfts=proto.ownedNfts, max_automatic_token_associations=proto.max_automatic_token_associations, - staking_info=( - StakingInfo._from_proto(proto.staking_info) - if proto.HasField('staking_info') else None - ), + staking_info=(StakingInfo._from_proto(proto.staking_info) if proto.HasField("staking_info") else None), ) return account_info @@ -111,10 +105,12 @@ def _to_proto(self) -> CryptoGetInfoResponse.AccountInfo: `CryptoGetInfoResponse.AccountInfo` message. This method handles the conversion of SDK types back to their protobuf equivalents (e.g., `Hbar` to tinybars, SDK `Timestamp` to proto `Timestamp`). + Note: SDK fields that are `None` will be serialized as their default protobuf values (e.g., 0 for integers, False for booleans, empty strings/bytes). + Returns: CryptoGetInfoResponse.AccountInfo: The protobuf message representation of this `AccountInfo` object. @@ -129,9 +125,7 @@ def _to_proto(self) -> CryptoGetInfoResponse.AccountInfo: receiverSigRequired=self.receiver_signature_required, expirationTime=self.expiration_time._to_protobuf() if self.expiration_time else None, autoRenewPeriod=self.auto_renew_period._to_proto() if self.auto_renew_period else None, - tokenRelationships=[ - relationship._to_proto() for relationship in self.token_relationships - ], + tokenRelationships=[relationship._to_proto() for relationship in self.token_relationships], memo=self.account_memo, ownedNfts=self.owned_nfts, max_automatic_token_associations=self.max_automatic_token_associations, @@ -192,8 +186,7 @@ def __repr__(self) -> str: def staked_account_id(self): """Deprecated: use staking_info.staked_account_id instead.""" warnings.warn( - "AccountInfo.staked_account_id is deprecated, " - "use AccountInfo.staking_info.staked_account_id instead", + "AccountInfo.staked_account_id is deprecated, use AccountInfo.staking_info.staked_account_id instead", DeprecationWarning, stacklevel=2, ) @@ -203,30 +196,32 @@ def staked_account_id(self): def staked_account_id(self, value): """Deprecated setter: use staking_info.staked_account_id instead.""" warnings.warn( - "AccountInfo.staked_account_id setter is deprecated, " - "use AccountInfo.staking_info instead", + "AccountInfo.staked_account_id setter is deprecated, use AccountInfo.staking_info instead", DeprecationWarning, stacklevel=2, ) if self.staking_info is None: - object.__setattr__(self, 'staking_info', StakingInfo(staked_account_id=value)) + object.__setattr__(self, "staking_info", StakingInfo(staked_account_id=value)) else: # Reconstruct StakingInfo with updated field, clearing staked_node_id oneof conflict - object.__setattr__(self, 'staking_info', StakingInfo( - pending_reward=self.staking_info.pending_reward, - staked_to_me=self.staking_info.staked_to_me, - stake_period_start=self.staking_info.stake_period_start, - staked_account_id=value, - staked_node_id=None, # Clear oneof conflict - decline_reward=self.staking_info.decline_reward, - )) + object.__setattr__( + self, + "staking_info", + StakingInfo( + pending_reward=self.staking_info.pending_reward, + staked_to_me=self.staking_info.staked_to_me, + stake_period_start=self.staking_info.stake_period_start, + staked_account_id=value, + staked_node_id=None, # Clear oneof conflict + decline_reward=self.staking_info.decline_reward, + ), + ) @property def staked_node_id(self): """Deprecated: use staking_info.staked_node_id instead.""" warnings.warn( - "AccountInfo.staked_node_id is deprecated, " - "use AccountInfo.staking_info.staked_node_id instead", + "AccountInfo.staked_node_id is deprecated, use AccountInfo.staking_info.staked_node_id instead", DeprecationWarning, stacklevel=2, ) @@ -236,30 +231,32 @@ def staked_node_id(self): def staked_node_id(self, value): """Deprecated setter: use staking_info.staked_node_id instead.""" warnings.warn( - "AccountInfo.staked_node_id setter is deprecated, " - "use AccountInfo.staking_info instead", + "AccountInfo.staked_node_id setter is deprecated, use AccountInfo.staking_info instead", DeprecationWarning, stacklevel=2, ) if self.staking_info is None: - object.__setattr__(self, 'staking_info', StakingInfo(staked_node_id=value)) + object.__setattr__(self, "staking_info", StakingInfo(staked_node_id=value)) else: # Reconstruct StakingInfo with updated field, clearing staked_account_id oneof conflict - object.__setattr__(self, 'staking_info', StakingInfo( - pending_reward=self.staking_info.pending_reward, - staked_to_me=self.staking_info.staked_to_me, - stake_period_start=self.staking_info.stake_period_start, - staked_account_id=None, # Clear oneof conflict - staked_node_id=value, - decline_reward=self.staking_info.decline_reward, - )) + object.__setattr__( + self, + "staking_info", + StakingInfo( + pending_reward=self.staking_info.pending_reward, + staked_to_me=self.staking_info.staked_to_me, + stake_period_start=self.staking_info.stake_period_start, + staked_account_id=None, # Clear oneof conflict + staked_node_id=value, + decline_reward=self.staking_info.decline_reward, + ), + ) @property def decline_staking_reward(self): """Deprecated: use staking_info.decline_reward instead.""" warnings.warn( - "AccountInfo.decline_staking_reward is deprecated, " - "use AccountInfo.staking_info.decline_reward instead", + "AccountInfo.decline_staking_reward is deprecated, use AccountInfo.staking_info.decline_reward instead", DeprecationWarning, stacklevel=2, ) @@ -269,23 +266,26 @@ def decline_staking_reward(self): def decline_staking_reward(self, value): """Deprecated setter: use staking_info.decline_reward instead.""" warnings.warn( - "AccountInfo.decline_staking_reward setter is deprecated, " - "use AccountInfo.staking_info instead", + "AccountInfo.decline_staking_reward setter is deprecated, use AccountInfo.staking_info instead", DeprecationWarning, stacklevel=2, ) if self.staking_info is None: - object.__setattr__(self, 'staking_info', StakingInfo(decline_reward=value)) + object.__setattr__(self, "staking_info", StakingInfo(decline_reward=value)) else: # Reconstruct StakingInfo with updated field - object.__setattr__(self, 'staking_info', StakingInfo( - pending_reward=self.staking_info.pending_reward, - staked_to_me=self.staking_info.staked_to_me, - stake_period_start=self.staking_info.stake_period_start, - staked_account_id=self.staking_info.staked_account_id, - staked_node_id=self.staking_info.staked_node_id, - decline_reward=value, - )) + object.__setattr__( + self, + "staking_info", + StakingInfo( + pending_reward=self.staking_info.pending_reward, + staked_to_me=self.staking_info.staked_to_me, + stake_period_start=self.staking_info.stake_period_start, + staked_account_id=self.staking_info.staked_account_id, + staked_node_id=self.staking_info.staked_node_id, + decline_reward=value, + ), + ) # --------------------------------------------------------------------------- @@ -305,7 +305,8 @@ def _wrapped_account_info_init( ): _orig_account_info_init(self, *args, **kwargs) _legacy = [ - k for k, v in [ + k + for k, v in [ ("staked_account_id", staked_account_id), ("staked_node_id", staked_node_id), ("decline_staking_reward", decline_staking_reward), @@ -314,8 +315,7 @@ def _wrapped_account_info_init( ] if _legacy: warnings.warn( - f"Passing {', '.join(_legacy)} to AccountInfo() is deprecated; " - "use staking_info=StakingInfo(...) instead.", + f"Passing {', '.join(_legacy)} to AccountInfo() is deprecated; use staking_info=StakingInfo(...) instead.", DeprecationWarning, stacklevel=2, ) @@ -329,4 +329,4 @@ def _wrapped_account_info_init( self.decline_staking_reward = decline_staking_reward -AccountInfo.__init__ = _wrapped_account_info_init \ No newline at end of file +AccountInfo.__init__ = _wrapped_account_info_init diff --git a/src/hiero_sdk_python/account/account_records_query.py b/src/hiero_sdk_python/account/account_records_query.py index 72d13a1ab..539f7cf25 100644 --- a/src/hiero_sdk_python/account/account_records_query.py +++ b/src/hiero_sdk_python/account/account_records_query.py @@ -1,9 +1,8 @@ -""" -Query to get records about a specific account on the network. -""" +"""Query to get records about a specific account on the network.""" + +from __future__ import annotations import traceback -from typing import List, Optional, Union from hiero_sdk_python.account.account_id import AccountId from hiero_sdk_python.channels import _Channel @@ -25,22 +24,22 @@ class AccountRecordsQuery(Query): against the specified account within the last 25 hours. """ - def __init__(self, account_id: Optional[AccountId] = None): + def __init__(self, account_id: AccountId | None = None): """ Initializes a new AccountRecordsQuery instance with an optional account_id. Args: - account_id (Optional[AccountId]): The ID of the account to query. + account_id (AccountId, optional): The ID of the account to query. """ super().__init__() - self.account_id: Optional[AccountId] = account_id + self.account_id: AccountId | None = account_id - def set_account_id(self, account_id: Optional[AccountId]) -> "AccountRecordsQuery": + def set_account_id(self, account_id: AccountId | None) -> AccountRecordsQuery: """ Sets the ID of the account to query. Args: - account_id (Optional[AccountId]): The ID of the account. + account_id (AccountId | None): The ID of the account. Returns: AccountRecordsQuery: Returns self for method chaining. @@ -96,7 +95,7 @@ def _get_method(self, channel: _Channel) -> _Method: """ return _Method(transaction_func=None, query_func=channel.crypto.getAccountRecords) - def execute(self, client: Client, timeout: Optional[Union[int, float]] = None) -> List[TransactionRecord]: + def execute(self, client: Client, timeout: int | float | None = None) -> list[TransactionRecord]: """ Executes the account records query. @@ -108,7 +107,7 @@ def execute(self, client: Client, timeout: Optional[Union[int, float]] = None) - Args: client (Client): The client instance to use for execution - timeout (Optional[Union[int, float]]): The total execution timeout (in seconds) for this execution. + timeout (int | float, optional): The total execution timeout (in seconds) for this execution. Returns: List[TransactionRecord]: The account records from the network @@ -121,15 +120,9 @@ def execute(self, client: Client, timeout: Optional[Union[int, float]] = None) - self._before_execute(client) response = self._execute(client, timeout) - records = [] - for record in response.cryptoGetAccountRecords.records: - records.append(TransactionRecord._from_proto(record)) - - return records + return [TransactionRecord._from_proto(record) for record in response.cryptoGetAccountRecords.records] - def _get_query_response( - self, response: response_pb2.Response - ) -> CryptoGetAccountRecordsResponse: + def _get_query_response(self, response: response_pb2.Response) -> CryptoGetAccountRecordsResponse: """ Extracts the account records response from the full response. diff --git a/src/hiero_sdk_python/account/account_update_transaction.py b/src/hiero_sdk_python/account/account_update_transaction.py index acca7e3bb..7fd7cb9ef 100644 --- a/src/hiero_sdk_python/account/account_update_transaction.py +++ b/src/hiero_sdk_python/account/account_update_transaction.py @@ -1,10 +1,9 @@ # pylint: disable=too-many-instance-attributes -""" -AccountUpdateTransaction class, which is used to update an account on the network. -""" +"""AccountUpdateTransaction class, which is used to update an account on the network.""" + +from __future__ import annotations from dataclasses import dataclass -from typing import Optional from google.protobuf.wrappers_pb2 import BoolValue, Int32Value, StringValue @@ -20,6 +19,7 @@ from hiero_sdk_python.timestamp import Timestamp from hiero_sdk_python.transaction.transaction import Transaction + AUTO_RENEW_PERIOD = Duration(7890000) # around 90 days in seconds @@ -29,32 +29,32 @@ class AccountUpdateParams: Represents account attributes that can be updated. Attributes: - account_id (Optional[AccountId]): The account ID to update. - key (Optional[Key]): The new key for the account. + account_id (AccountId, optional): The account ID to update. + key (Key, optional): The new key for the account. auto_renew_period (Duration): The new auto-renew period. - account_memo (Optional[str]): The new memo for the account. - receiver_signature_required (Optional[bool]): Whether receiver signature is required. - expiration_time (Optional[Timestamp]): The new expiration time for the account. - max_automatic_token_associations (Optional[int]): The maximum number of tokens that + account_memo (str, optional): The new memo for the account. + receiver_signature_required (bool, optional): Whether receiver signature is required. + expiration_time (Timestamp, optional): The new expiration time for the account. + max_automatic_token_associations (int, optional): The maximum number of tokens that can be auto-associated with this account. Use -1 for unlimited, 0 for none. - staked_account_id (Optional[AccountId]): The account to which this account is staking + staked_account_id (AccountId, optional): The account to which this account is staking its balances. Mutually exclusive with staked_node_id. - staked_node_id (Optional[int]): The node ID to which this account is staking + staked_node_id (int, optional): The node ID to which this account is staking its balances. Mutually exclusive with staked_account_id. - decline_staking_reward (Optional[bool]): If true, the account declines receiving + decline_staking_reward (bool, optional): If true, the account declines receiving staking rewards. """ - account_id: Optional[AccountId] = None - key: Optional[Key] = None + account_id: AccountId | None = None + key: Key | None = None auto_renew_period: Duration = AUTO_RENEW_PERIOD - account_memo: Optional[str] = None - receiver_signature_required: Optional[bool] = None - expiration_time: Optional[Timestamp] = None - max_automatic_token_associations: Optional[int] = None - staked_account_id: Optional[AccountId] = None - staked_node_id: Optional[int] = None - decline_staking_reward: Optional[bool] = None + account_memo: str | None = None + receiver_signature_required: bool | None = None + expiration_time: Timestamp | None = None + max_automatic_token_associations: int | None = None + staked_account_id: AccountId | None = None + staked_node_id: int | None = None + decline_staking_reward: bool | None = None class AccountUpdateTransaction(Transaction): @@ -66,12 +66,12 @@ class AccountUpdateTransaction(Transaction): attribute remains unchanged. Only appropriate signers may update account state. """ - def __init__(self, account_params: Optional[AccountUpdateParams] = None): + def __init__(self, account_params: AccountUpdateParams | None = None): """ Initialize a new `AccountUpdateTransaction`. Args: - account_params (Optional[AccountUpdateParams]): Optional bag of parameters + account_params (AccountUpdateParams, optional): Optional bag of parameters to pre-populate the transaction. You may also set fields via setters. """ super().__init__() @@ -87,7 +87,7 @@ def __init__(self, account_params: Optional[AccountUpdateParams] = None): self.staked_node_id = params.staked_node_id self.decline_staking_reward = params.decline_staking_reward - def set_account_id(self, account_id: Optional[AccountId]) -> "AccountUpdateTransaction": + def set_account_id(self, account_id: AccountId | None) -> AccountUpdateTransaction: """ Sets the `AccountId` that will be updated. @@ -101,7 +101,7 @@ def set_account_id(self, account_id: Optional[AccountId]) -> "AccountUpdateTrans self.account_id = account_id return self - def set_key(self, key: Optional[Key]) -> "AccountUpdateTransaction": + def set_key(self, key: Key | None) -> AccountUpdateTransaction: """ Sets the new account key (Optional[Key]) for key rotation. @@ -115,9 +115,7 @@ def set_key(self, key: Optional[Key]) -> "AccountUpdateTransaction": self.key = key return self - def set_auto_renew_period( - self, auto_renew_period: Optional[Duration] - ) -> "AccountUpdateTransaction": + def set_auto_renew_period(self, auto_renew_period: Duration | None) -> AccountUpdateTransaction: """ Sets the auto-renew period for the account. @@ -131,7 +129,7 @@ def set_auto_renew_period( self.auto_renew_period = auto_renew_period return self - def set_account_memo(self, account_memo: Optional[str]) -> "AccountUpdateTransaction": + def set_account_memo(self, account_memo: str | None) -> AccountUpdateTransaction: """ Sets the account memo (UTF-8, network enforced size limits apply). @@ -145,9 +143,7 @@ def set_account_memo(self, account_memo: Optional[str]) -> "AccountUpdateTransac self.account_memo = account_memo return self - def set_receiver_signature_required( - self, receiver_signature_required: Optional[bool] - ) -> "AccountUpdateTransaction": + def set_receiver_signature_required(self, receiver_signature_required: bool | None) -> AccountUpdateTransaction: """ Sets whether the account requires receiver signatures for transfers. @@ -161,9 +157,7 @@ def set_receiver_signature_required( self.receiver_signature_required = receiver_signature_required return self - def set_expiration_time( - self, expiration_time: Optional[Timestamp] - ) -> "AccountUpdateTransaction": + def set_expiration_time(self, expiration_time: Timestamp | None) -> AccountUpdateTransaction: """ Sets the account expiration time. @@ -178,8 +172,8 @@ def set_expiration_time( return self def set_max_automatic_token_associations( - self, max_automatic_token_associations: Optional[int] - ) -> "AccountUpdateTransaction": + self, max_automatic_token_associations: int | None + ) -> AccountUpdateTransaction: """ Sets the maximum number of tokens that can be auto-associated with this account. @@ -196,15 +190,11 @@ def set_max_automatic_token_associations( """ self._require_not_frozen() if max_automatic_token_associations is not None and max_automatic_token_associations < -1: - raise ValueError( - "max_automatic_token_associations must be -1 (unlimited) or a non-negative integer." - ) + raise ValueError("max_automatic_token_associations must be -1 (unlimited) or a non-negative integer.") self.max_automatic_token_associations = max_automatic_token_associations return self - def set_staked_account_id( - self, staked_account_id: Optional[AccountId] - ) -> "AccountUpdateTransaction": + def set_staked_account_id(self, staked_account_id: AccountId | None) -> AccountUpdateTransaction: """ Sets the account to which this account is staking its balances. @@ -227,9 +217,7 @@ def set_staked_account_id( self.staked_node_id = None # Clear the other field in the oneOf return self - def set_staked_node_id( - self, staked_node_id: Optional[int] - ) -> "AccountUpdateTransaction": + def set_staked_node_id(self, staked_node_id: int | None) -> AccountUpdateTransaction: """ Sets the node ID to which this account is staking its balances. @@ -251,7 +239,7 @@ def set_staked_node_id( self.staked_account_id = None # Clear the other field in the oneOf return self - def clear_staked_account_id(self) -> "AccountUpdateTransaction": + def clear_staked_account_id(self) -> AccountUpdateTransaction: """ Clears staking to an account by setting the sentinel AccountId (0.0.0). @@ -263,7 +251,7 @@ def clear_staked_account_id(self) -> "AccountUpdateTransaction": self.staked_node_id = None return self - def clear_staked_node_id(self) -> "AccountUpdateTransaction": + def clear_staked_node_id(self) -> AccountUpdateTransaction: """ Clears staking to a node by setting the sentinel node ID (-1). @@ -275,9 +263,7 @@ def clear_staked_node_id(self) -> "AccountUpdateTransaction": self.staked_account_id = None return self - def set_decline_staking_reward( - self, decline_staking_reward: Optional[bool] - ) -> "AccountUpdateTransaction": + def set_decline_staking_reward(self, decline_staking_reward: bool | None) -> AccountUpdateTransaction: """ Sets whether the account declines receiving staking rewards. @@ -309,9 +295,7 @@ def _build_proto_body(self): accountIDToUpdate=self.account_id._to_proto(), key=self.key.to_proto_key() if self.key else None, memo=StringValue(value=self.account_memo) if self.account_memo is not None else None, - autoRenewPeriod=( - self.auto_renew_period._to_proto() if self.auto_renew_period else None - ), + autoRenewPeriod=(self.auto_renew_period._to_proto() if self.auto_renew_period else None), expirationTime=self.expiration_time._to_protobuf() if self.expiration_time else None, receiverSigRequiredWrapper=( BoolValue(value=self.receiver_signature_required) @@ -324,9 +308,7 @@ def _build_proto_body(self): else None ), decline_reward=( - BoolValue(value=self.decline_staking_reward) - if self.decline_staking_reward is not None - else None + BoolValue(value=self.decline_staking_reward) if self.decline_staking_reward is not None else None ), ) diff --git a/src/hiero_sdk_python/address_book/endpoint.py b/src/hiero_sdk_python/address_book/endpoint.py index 7f6ae9302..5bb623f23 100644 --- a/src/hiero_sdk_python/address_book/endpoint.py +++ b/src/hiero_sdk_python/address_book/endpoint.py @@ -1,35 +1,35 @@ +from __future__ import annotations + from typing import TypedDict from hiero_sdk_python.hapi.services.basic_types_pb2 import ServiceEndpoint + class EndpointDict(TypedDict): """ A TypedDict representing the structure of an endpoint in JSON format. - + Attributes: ip_address_v4 (str): The IPv4 address of the endpoint. port (int): The port number of the endpoint. domain_name (str): The domain name of the endpoint. """ + ip_address_v4: str port: int domain_name: str + class Endpoint: """ Represents an endpoint with address, port, and domain name information. This class is used to handle service endpoints in the Hedera network. """ - - def __init__( - self, - address: bytes = None, - port: int = None, - domain_name: str = None - ) -> None: + + def __init__(self, address: bytes = None, port: int = None, domain_name: str = None) -> None: """ Initialize a new Endpoint instance. - + Args: address (bytes, optional): The IP address in bytes format. port (int, optional): The port number. @@ -38,131 +38,121 @@ def __init__( self._address: bytes = address self._port: int = port self._domain_name: str = domain_name - - def set_address(self, address: bytes) -> "Endpoint": + + def set_address(self, address: bytes) -> Endpoint: """ Set the IP address of the endpoint. - + Args: address (bytes): The IP address in bytes format. - + Returns: Endpoint: This instance for method chaining. """ self._address = address return self - + def get_address(self) -> bytes: """ Get the IP address of the endpoint. - + Returns: bytes: The IP address in bytes format. """ return self._address - - def set_port(self, port: int) -> "Endpoint": + + def set_port(self, port: int) -> Endpoint: """ Set the port of the endpoint. - + Args: port (int): The port number. - + Returns: Endpoint: This instance for method chaining. """ self._port = port return self - + def get_port(self) -> int: """ Get the port of the endpoint. - + Returns: int: The port number. """ return self._port - - def set_domain_name(self, domain_name: str) -> "Endpoint": + + def set_domain_name(self, domain_name: str) -> Endpoint: """ Set the domain name of the endpoint. - + Args: domain_name (str): The domain name. - + Returns: Endpoint: This instance for method chaining. """ self._domain_name = domain_name return self - + def get_domain_name(self) -> str: """ Get the domain name of the endpoint. - + Returns: str: The domain name. """ return self._domain_name - + @classmethod - def _from_proto(cls, service_endpoint: ServiceEndpoint) -> "Endpoint": + def _from_proto(cls, service_endpoint: ServiceEndpoint) -> Endpoint: """ Create an Endpoint from a protobuf ServiceEndpoint. - + Args: service_endpoint: The protobuf ServiceEndpoint object. - + Returns: Endpoint: A new Endpoint instance. """ port = service_endpoint.port - + if port == 0 or port == 50111: port = 50211 - - return cls( - address=service_endpoint.ipAddressV4, - port=port, - domain_name=service_endpoint.domain_name - ) - + + return cls(address=service_endpoint.ipAddressV4, port=port, domain_name=service_endpoint.domain_name) + def _to_proto(self) -> ServiceEndpoint: """ Convert this Endpoint to a protobuf ServiceEndpoint. - + Returns: ServiceEndpoint: A protobuf ServiceEndpoint object. """ - - return ServiceEndpoint( - ipAddressV4=self._address, - port=self._port, - domain_name=self._domain_name - ) - + return ServiceEndpoint(ipAddressV4=self._address, port=self._port, domain_name=self._domain_name) + def __str__(self) -> str: """ Get a string representation of the Endpoint. - + Returns: str: The string representation in the format 'domain:port' or 'ip:port'. """ - return f"{self._address.decode('utf-8')}:{self._port}" @classmethod - def from_dict(cls, json_data: EndpointDict) -> "Endpoint": + def from_dict(cls, json_data: EndpointDict) -> Endpoint: """ Create an Endpoint from a JSON object. - + Args: json_data: The JSON object. """ - if 'ip_address_v4' not in json_data or 'port' not in json_data or 'domain_name' not in json_data: + if "ip_address_v4" not in json_data or "port" not in json_data or "domain_name" not in json_data: raise ValueError("JSON data must contain 'ip_address_v4', 'port', and 'domain_name' keys.") return cls( - address=bytes(json_data.get('ip_address_v4', ''), 'utf-8'), - port=json_data.get('port'), - domain_name=json_data.get('domain_name') - ) \ No newline at end of file + address=bytes(json_data.get("ip_address_v4", ""), "utf-8"), + port=json_data.get("port"), + domain_name=json_data.get("domain_name"), + ) diff --git a/src/hiero_sdk_python/address_book/node_address.py b/src/hiero_sdk_python/address_book/node_address.py index da658d7f0..dbe2dc03c 100644 --- a/src/hiero_sdk_python/address_book/node_address.py +++ b/src/hiero_sdk_python/address_book/node_address.py @@ -1,13 +1,16 @@ -from typing import List, TypedDict +from __future__ import annotations + +from typing import TypedDict from hiero_sdk_python.account.account_id import AccountId from hiero_sdk_python.address_book.endpoint import Endpoint, EndpointDict from hiero_sdk_python.hapi.services.basic_types_pb2 import NodeAddress as NodeAddressProto + class NodeDict(TypedDict): """ A TypedDict representing the structure of a node address in JSON format. - + Attributes: public_key (str): The RSA public key of the node. node_account_id (str): The account ID of the node in 'shard.realm.num' format. @@ -16,30 +19,30 @@ class NodeDict(TypedDict): service_endpoints (list[EndpointDict]): List of service endpoints for the node. description (str): Description of the node. """ + public_key: str node_account_id: str node_id: int node_cert_hash: str - service_endpoints: List[EndpointDict] + service_endpoints: list[EndpointDict] description: str + class NodeAddress: - """ - Represents the address of a node on the Hedera network. - """ - + """Represents the address of a node on the Hedera network.""" + def __init__( self, public_key: str = None, account_id: AccountId = None, node_id: int = None, cert_hash: bytes = None, - addresses: List[Endpoint] = None, - description: str = None + addresses: list[Endpoint] = None, + description: str = None, ): """ Initialize a new NodeAddress instance. - + Args: public_key (str, optional): The RSA public key of the node. account_id (AccountId, optional): The account ID of the node. @@ -52,42 +55,41 @@ def __init__( self._account_id: AccountId = account_id self._node_id: int = node_id self._cert_hash: bytes = cert_hash - self._addresses: List[Endpoint] = addresses + self._addresses: list[Endpoint] = addresses self._description: str = description - + @classmethod - def _from_proto(cls, node_address_proto: NodeAddressProto) -> "NodeAddress": + def _from_proto(cls, node_address_proto: NodeAddressProto) -> NodeAddress: """ Create a NodeAddress from a protobuf NodeAddress. - + Args: node_address_proto: The protobuf NodeAddress object. - + Returns: NodeAddress: A new NodeAddress instance. """ - addresses: List[Endpoint] = [] - - for endpoint_proto in node_address_proto.serviceEndpoint: - addresses.append(Endpoint._from_proto(endpoint_proto)) - + addresses: list[Endpoint] = [ + Endpoint._from_proto(endpoint_proto) for endpoint_proto in node_address_proto.serviceEndpoint + ] + account_id: AccountId = None if node_address_proto.nodeAccountId: account_id = AccountId._from_proto(node_address_proto.nodeAccountId) - + return cls( public_key=node_address_proto.RSA_PubKey, account_id=account_id, node_id=node_address_proto.nodeId, cert_hash=node_address_proto.nodeCertHash, addresses=addresses, - description=node_address_proto.description + description=node_address_proto.description, ) - + def _to_proto(self): """ Convert this NodeAddress to a protobuf NodeAddress. - + Returns: NodeAddressProto: A protobuf NodeAddress object. """ @@ -95,21 +97,21 @@ def _to_proto(self): RSA_PubKey=self._public_key, nodeId=self._node_id, nodeCertHash=self._cert_hash, - description=self._description + description=self._description, ) - + if self._account_id: node_address_proto.nodeAccountId.CopyFrom(self._account_id._to_proto()) - + for endpoint in self._addresses: node_address_proto.serviceEndpoint.append(endpoint._to_proto()) - + return node_address_proto - + def __str__(self): """ Get a string representation of the NodeAddress. - + Returns: str: The string representation of the NodeAddress. """ @@ -119,7 +121,7 @@ def __str__(self): cert_hash_str: str = self._cert_hash.hex() node_id_str: str = str(self._node_id) account_id_str: str = str(self._account_id) - + return ( f"NodeAccountId: {account_id_str} {addresses_str}\n" f"CertHash: {cert_hash_str}\n" @@ -128,28 +130,23 @@ def __str__(self): ) @classmethod - def _from_dict(cls, node: NodeDict) -> 'NodeAddress': - """ - Create a NodeAddress from a dictionary. - """ - - service_endpoints: List[EndpointDict] = node.get('service_endpoints', []) - public_key: str = node.get('public_key') - account_id: AccountId = AccountId.from_string(node.get('node_account_id')) - node_id: int = node.get('node_id') + def _from_dict(cls, node: NodeDict) -> NodeAddress: + """Create a NodeAddress from a dictionary.""" + service_endpoints: list[EndpointDict] = node.get("service_endpoints", []) + public_key: str = node.get("public_key") + account_id: AccountId = AccountId.from_string(node.get("node_account_id")) + node_id: int = node.get("node_id") # Get the hash from the node, remove the 0x prefix and convert to bytes - cert_hash: bytes = bytes.fromhex(node.get('node_cert_hash').removeprefix('0x')) - description: str = node.get('description') - - endpoints: List[Endpoint] = [] - for endpoint in service_endpoints: - endpoints.append(Endpoint.from_dict(endpoint)) - + cert_hash: bytes = bytes.fromhex(node.get("node_cert_hash").removeprefix("0x")) + description: str = node.get("description") + + endpoints: list[Endpoint] = [Endpoint.from_dict(endpoint) for endpoint in service_endpoints] + return cls( public_key=public_key, account_id=account_id, node_id=node_id, cert_hash=cert_hash, description=description, - addresses=endpoints + addresses=endpoints, ) diff --git a/src/hiero_sdk_python/channels.py b/src/hiero_sdk_python/channels.py index 3ba98f8be..8963303a3 100644 --- a/src/hiero_sdk_python/channels.py +++ b/src/hiero_sdk_python/channels.py @@ -1,24 +1,30 @@ -from hiero_sdk_python.hapi.services import crypto_service_pb2_grpc -from hiero_sdk_python.hapi.services import file_service_pb2_grpc -from hiero_sdk_python.hapi.services import network_service_pb2_grpc -from hiero_sdk_python.hapi.services import smart_contract_service_pb2_grpc -from hiero_sdk_python.hapi.services import token_service_pb2_grpc -from hiero_sdk_python.hapi.services import consensus_service_pb2_grpc -from hiero_sdk_python.hapi.services import freeze_service_pb2_grpc -from hiero_sdk_python.hapi.services import schedule_service_pb2_grpc -from hiero_sdk_python.hapi.services import util_service_pb2_grpc -from hiero_sdk_python.hapi.services import address_book_service_pb2_grpc +from __future__ import annotations + +from hiero_sdk_python.hapi.services import ( + address_book_service_pb2_grpc, + consensus_service_pb2_grpc, + crypto_service_pb2_grpc, + file_service_pb2_grpc, + freeze_service_pb2_grpc, + network_service_pb2_grpc, + schedule_service_pb2_grpc, + smart_contract_service_pb2_grpc, + token_service_pb2_grpc, + util_service_pb2_grpc, +) + class _Channel: """ - The _Channel class is a wrapper around gRPC channels that provides access to various + The _Channel class is a wrapper around gRPC channels that provides access to various Hedera service stubs. - + """ + def __init__(self, grpc_channel=None): """ Initialize a new _Channel instance. - + Args: grpc_channel: The gRPC channel to wrap, obtained from a Client instance. If None, service stub properties will return None when accessed. @@ -40,166 +46,166 @@ def __init__(self, grpc_channel=None): def crypto(self): """ Provides access to the CryptoService stub for cryptocurrency operations. - + This stub handles operations like: - Creating accounts (createAccount) - Transferring cryptocurrency (cryptoTransfer) - Updating accounts (updateAccount) - Deleting accounts (cryptoDelete) - + Returns: CryptoServiceStub: The gRPC stub for crypto operations, or None if no channel exists. """ if self._crypto is None and self.channel is not None: self._crypto = crypto_service_pb2_grpc.CryptoServiceStub(self.channel) return self._crypto - + @property def file(self): """ Provides access to the FileService stub for file operations. - + This stub handles operations like: - Creating files (createFile) - Updating files (updateFile) - Deleting files (deleteFile) - + Returns: FileServiceStub: The gRPC stub for file operations, or None if no channel exists. """ if self._file is None and self.channel is not None: self._file = file_service_pb2_grpc.FileServiceStub(self.channel) return self._file - + @property def smart_contract(self): """ Provides access to the SmartContractService stub for smart contract operations. - + This stub handles operations like: - Creating contracts (createContract) - Updating contracts (updateContract) - Deleting contracts (deleteContract) - Calling contracts (contractCallMethod) - + Returns: SmartContractServiceStub: The gRPC stub for contract operations, or None if no channel exists. """ if self._smart_contract is None and self.channel is not None: self._smart_contract = smart_contract_service_pb2_grpc.SmartContractServiceStub(self.channel) return self._smart_contract - + @property def topic(self): """ Provides access to the ConsensusService stub for consensus topic operations. - + This stub handles operations like: - Creating topics (createTopic) - Updating topics (updateTopic) - Deleting topics (deleteTopic) - Submitting messages to topics (submitMessage) - Querying topic info (getTopicInfo) - + Returns: ConsensusServiceStub: The gRPC stub for consensus operations, or None if no channel exists. """ if self._topic is None and self.channel is not None: self._topic = consensus_service_pb2_grpc.ConsensusServiceStub(self.channel) return self._topic - + @property def freeze(self): """ Provides access to the FreezeService stub for network freeze operations. - + This stub handles operations like: - Freezing the network (freezeNetwork) - Administrative operations on the network - + Returns: FreezeServiceStub: The gRPC stub for freeze operations, or None if no channel exists. """ if self._freeze is None and self.channel is not None: self._freeze = freeze_service_pb2_grpc.FreezeServiceStub(self.channel) return self._freeze - + @property def network(self): """ Provides access to the NetworkService stub for network-related operations. - + This stub handles operations like: - Getting network version info (getVersionInfo) - Querying network fees (getFeeSchedule) - Querying exchange rates (getExchangeRate) - + Returns: NetworkServiceStub: The gRPC stub for network operations, or None if no channel exists. """ if self._network is None and self.channel is not None: self._network = network_service_pb2_grpc.NetworkServiceStub(self.channel) return self._network - + @property def token(self): """ Provides access to the TokenService stub for token operations. - + This stub handles operations like: - Creating tokens (createToken) - Updating tokens (updateToken) - Minting tokens (mintToken) - Burning tokens (burnToken) - + Returns: TokenServiceStub: The gRPC stub for token operations, or None if no channel exists. """ if self._token is None and self.channel is not None: self._token = token_service_pb2_grpc.TokenServiceStub(self.channel) return self._token - + @property def schedule(self): """ Provides access to the ScheduleService stub for scheduled transaction operations. - + This stub handles operations like: - Creating scheduled transactions (createSchedule) - Signing scheduled transactions (signSchedule) - Deleting scheduled transactions (deleteSchedule) - + Returns: ScheduleServiceStub: The gRPC stub for scheduled transaction operations, or None if no channel exists. """ if self._schedule is None and self.channel is not None: self._schedule = schedule_service_pb2_grpc.ScheduleServiceStub(self.channel) return self._schedule - + @property def util(self): """ Provides access to the UtilService stub for utility operations. - + This stub handles operations like: - Pinging nodes (ping) - Getting transaction records (getTxRecordByTxID) - + Returns: UtilServiceStub: The gRPC stub for utility operations, or None if no channel exists. """ if self._util is None and self.channel is not None: self._util = util_service_pb2_grpc.UtilServiceStub(self.channel) return self._util - + @property def address_book(self): """ Provides access to the AddressBookService stub for node address operations. - + This stub handles operations like: - Getting the network address book (getAddressBook) - + Returns: AddressBookServiceStub: The gRPC stub for address book operations, or None if no channel exists. """ diff --git a/src/hiero_sdk_python/client/client.py b/src/hiero_sdk_python/client/client.py index 928c2e475..44e3266f5 100644 --- a/src/hiero_sdk_python/client/client.py +++ b/src/hiero_sdk_python/client/client.py @@ -1,27 +1,28 @@ -""" -Client module for interacting with the Hedera network. -""" +"""Client module for interacting with the Hedera network.""" + +from __future__ import annotations -from decimal import Decimal import math import os -from typing import NamedTuple, List, Union, Optional, Literal import warnings -from dotenv import load_dotenv +from decimal import Decimal +from typing import Literal, NamedTuple + import grpc +from dotenv import load_dotenv -from hiero_sdk_python.hbar import Hbar -from hiero_sdk_python.logger.logger import Logger, LogLevel +from hiero_sdk_python.account.account_id import AccountId +from hiero_sdk_python.crypto.private_key import PrivateKey from hiero_sdk_python.hapi.mirror import ( consensus_service_pb2_grpc as mirror_consensus_grpc, ) - +from hiero_sdk_python.hbar import Hbar +from hiero_sdk_python.logger.logger import Logger, LogLevel from hiero_sdk_python.transaction.transaction_id import TransactionId -from hiero_sdk_python.account.account_id import AccountId -from hiero_sdk_python.crypto.private_key import PrivateKey from .network import Network + DEFAULT_MAX_QUERY_PAYMENT = Hbar(1) DEFAULT_GRPC_DEADLINE = 10 # seconds @@ -40,9 +41,7 @@ class Operator(NamedTuple): class Client: - """ - Client to interact with Hedera network services including mirror nodes and transactions. - """ + """Client to interact with Hedera network services including mirror nodes and transactions.""" def __init__(self, network: Network = None) -> None: """ @@ -74,7 +73,7 @@ def __init__(self, network: Network = None) -> None: self.logger: Logger = Logger(LogLevel.from_env(), "hiero_sdk_python") @classmethod - def from_env(cls, network: Optional[NetworkName] = None) -> "Client": + def from_env(cls, network: NetworkName | None = None) -> Client: """ Initialize client from environment variables. Automatically loads .env file if present. @@ -93,29 +92,22 @@ def from_env(cls, network: Optional[NetworkName] = None) -> "Client": """ load_dotenv() - if network: - network_name = network - else: - network_name = os.getenv("NETWORK") or "testnet" + network_name = network or (os.getenv("NETWORK") or "testnet") network_name = network_name.lower() try: client = cls(Network(network_name)) - except ValueError: - raise ValueError(f"Invalid network name: {network_name}") + except ValueError as e: + raise ValueError(f"Invalid network name: {network_name}") from e operator_id_str = os.getenv("OPERATOR_ID") operator_key_str = os.getenv("OPERATOR_KEY") if not operator_id_str: - raise ValueError( - "OPERATOR_ID environment variable is required for Client.from_env()" - ) + raise ValueError("OPERATOR_ID environment variable is required for Client.from_env()") if not operator_key_str: - raise ValueError( - "OPERATOR_KEY environment variable is required for Client.from_env()" - ) + raise ValueError("OPERATOR_KEY environment variable is required for Client.from_env()") operator_id = AccountId.from_string(operator_id_str) operator_key = PrivateKey.from_string(operator_key_str) @@ -125,7 +117,7 @@ def from_env(cls, network: Optional[NetworkName] = None) -> "Client": return client @classmethod - def for_testnet(cls) -> "Client": + def for_testnet(cls) -> Client: """ Create a Client configured for Hedera Testnet. @@ -137,7 +129,7 @@ def for_testnet(cls) -> "Client": return cls(Network("testnet")) @classmethod - def for_mainnet(cls) -> "Client": + def for_mainnet(cls) -> Client: """ Create a Client configured for Hedera Mainnet. @@ -149,7 +141,7 @@ def for_mainnet(cls) -> "Client": return cls(Network("mainnet")) @classmethod - def for_previewnet(cls) -> "Client": + def for_previewnet(cls) -> Client: """ Create a Client configured for Hedera Previewnet. @@ -168,24 +160,18 @@ def _init_mirror_stub(self) -> None: """ mirror_address = self.network.get_mirror_address() if mirror_address.endswith(":50212") or mirror_address.endswith(":443"): - self.mirror_channel = grpc.secure_channel( - mirror_address, grpc.ssl_channel_credentials() - ) + self.mirror_channel = grpc.secure_channel(mirror_address, grpc.ssl_channel_credentials()) else: self.mirror_channel = grpc.insecure_channel(mirror_address) - self.mirror_stub = mirror_consensus_grpc.ConsensusServiceStub( - self.mirror_channel - ) + self.mirror_stub = mirror_consensus_grpc.ConsensusServiceStub(self.mirror_channel) def set_operator(self, account_id: AccountId, private_key: PrivateKey) -> None: - """ - Sets the operator credentials (account ID and private key). - """ + """Sets the operator credentials (account ID and private key).""" self.operator_account_id = account_id self.operator_private_key = private_key @property - def operator(self) -> Union[Operator, None]: + def operator(self) -> Operator | None: """ Returns an Operator namedtuple if both account ID and private key are set, otherwise None. @@ -198,23 +184,15 @@ def operator(self) -> Union[Operator, None]: return None def generate_transaction_id(self) -> TransactionId: - """ - Generates a new transaction ID, requiring that the operator_account_id is set. - """ + """Generates a new transaction ID, requiring that the operator_account_id is set.""" if self.operator_account_id is None: - raise ValueError( - "Operator account ID must be set to generate transaction ID." - ) + raise ValueError("Operator account ID must be set to generate transaction ID.") return TransactionId.generate(self.operator_account_id) - def get_node_account_ids(self) -> List[AccountId]: - """ - Returns a list of node AccountIds that the client can use to send queries and transactions. - """ + def get_node_account_ids(self) -> list[AccountId]: + """Returns a list of node AccountIds that the client can use to send queries and transactions.""" if self.network and self.network.nodes: - return [ - node._account_id for node in self.network.nodes - ] # pylint: disable=W0212 + return [node._account_id for node in self.network.nodes] # pylint: disable=W0212 raise ValueError("No nodes available in the network configuration.") def close(self) -> None: @@ -222,14 +200,13 @@ def close(self) -> None: Closes any open gRPC channels and frees resources. Call this when you are done using the Client to ensure a clean shutdown. """ - if self.mirror_channel is not None: self.mirror_channel.close() self.mirror_channel = None self.mirror_stub = None - def set_transport_security(self, enabled: bool) -> "Client": + def set_transport_security(self, enabled: bool) -> Client: """ Enable or disable TLS for consensus node connections. @@ -242,12 +219,10 @@ def set_transport_security(self, enabled: bool) -> "Client": return self def is_transport_security(self) -> bool: - """ - Determine if TLS is enabled for consensus node connections. - """ + """Determine if TLS is enabled for consensus node connections.""" return self.network.is_transport_security() - def set_verify_certificates(self, verify: bool) -> "Client": + def set_verify_certificates(self, verify: bool) -> Client: """ Enable or disable verification of server certificates when TLS is enabled. @@ -259,27 +234,19 @@ def set_verify_certificates(self, verify: bool) -> "Client": return self def is_verify_certificates(self) -> bool: - """ - Determine if certificate verification is enabled. - """ + """Determine if certificate verification is enabled.""" return self.network.is_verify_certificates() - def set_tls_root_certificates(self, root_certificates: Optional[bytes]) -> "Client": - """ - Provide custom root certificates for TLS connections. - """ + def set_tls_root_certificates(self, root_certificates: bytes | None) -> Client: + """Provide custom root certificates for TLS connections.""" self.network.set_tls_root_certificates(root_certificates) return self - def get_tls_root_certificates(self) -> Optional[bytes]: - """ - Retrieve the configured root certificates for TLS connections. - """ + def get_tls_root_certificates(self) -> bytes | None: + """Retrieve the configured root certificates for TLS connections.""" return self.network.get_tls_root_certificates() - def set_default_max_query_payment( - self, max_query_payment: Union[int, float, Decimal, Hbar] - ) -> "Client": + def set_default_max_query_payment(self, max_query_payment: int | float | Decimal | Hbar) -> Client: """ Sets the default maximum Hbar amount allowed for any query executed by this client. @@ -287,24 +254,18 @@ def set_default_max_query_payment( Individual queries may override this value via `Query.set_max_query_payment()`. Args: - max_query_payment (Union[int, float, Decimal, Hbar]): + max_query_payment (int | float | Decimal | Hbar): The maximum amount of Hbar that any single query is allowed to cost. + Returns: Client: The current client instance for method chaining. """ - if isinstance(max_query_payment, bool) or not isinstance( - max_query_payment, (int, float, Decimal, Hbar) - ): + if isinstance(max_query_payment, bool) or not isinstance(max_query_payment, (int, float, Decimal, Hbar)): raise TypeError( - "max_query_payment must be int, float, Decimal, or Hbar, " - f"got {type(max_query_payment).__name__}" + f"max_query_payment must be int, float, Decimal, or Hbar, got {type(max_query_payment).__name__}" ) - value = ( - max_query_payment - if isinstance(max_query_payment, Hbar) - else Hbar(max_query_payment) - ) + value = max_query_payment if isinstance(max_query_payment, Hbar) else Hbar(max_query_payment) if value < Hbar(0): raise ValueError("max_query_payment must be non-negative") @@ -312,7 +273,7 @@ def set_default_max_query_payment( self.default_max_query_payment = value return self - def set_max_attempts(self, max_attempts: int) -> "Client": + def set_max_attempts(self, max_attempts: int) -> Client: """ Set the maximum number of execution attempts for all transactions and queries executed by this client. @@ -324,9 +285,7 @@ def set_max_attempts(self, max_attempts: int) -> "Client": Client: This client instance for fluent chaining. """ if isinstance(max_attempts, bool) or not isinstance(max_attempts, int): - raise TypeError( - f"max_attempts must be of type int, got {(type(max_attempts).__name__)}" - ) + raise TypeError(f"max_attempts must be of type int, got {(type(max_attempts).__name__)}") if max_attempts <= 0: raise ValueError("max_attempts must be greater than 0") @@ -334,7 +293,7 @@ def set_max_attempts(self, max_attempts: int) -> "Client": self.max_attempts = max_attempts return self - def set_grpc_deadline(self, grpc_deadline: Union[int, float]) -> "Client": + def set_grpc_deadline(self, grpc_deadline: int | float) -> Client: """ Set the gRPC deadline (per-request timeout) used for all network calls made by this client. @@ -343,18 +302,14 @@ def set_grpc_deadline(self, grpc_deadline: Union[int, float]) -> "Client": individual gRPC request to complete before it is cancelled by the client. Args: - grpc_deadline (Union[int, float]): gRPC deadline in seconds. + grpc_deadline (int | float): gRPC deadline in seconds. Must be greater than zero. Returns: Client: This client instance for fluent chaining. """ - if isinstance(grpc_deadline, bool) or not isinstance( - grpc_deadline, (float, int) - ): - raise TypeError( - f"grpc_deadline must be of type Union[int, float], got {type(grpc_deadline).__name__}" - ) + if isinstance(grpc_deadline, bool) or not isinstance(grpc_deadline, (float, int)): + raise TypeError(f"grpc_deadline must be of type Union[int, float], got {type(grpc_deadline).__name__}") if not math.isfinite(grpc_deadline) or grpc_deadline <= 0: raise ValueError("grpc_deadline must be a finite value greater than 0") @@ -364,12 +319,13 @@ def set_grpc_deadline(self, grpc_deadline: Union[int, float]) -> "Client": "grpc_deadline should be smaller than request_timeout. " "This configuration may cause operations to fail unexpectedly.", UserWarning, + stacklevel=2, ) self._grpc_deadline = float(grpc_deadline) return self - def set_request_timeout(self, request_timeout: Union[int, float]) -> "Client": + def set_request_timeout(self, request_timeout: int | float) -> Client: """ Set the total execution timeout for a single transaction or query made by this client. @@ -379,18 +335,14 @@ def set_request_timeout(self, request_timeout: Union[int, float]) -> "Client": Once exceeded, the request fails with a TimeoutError. Args: - request_timeout (Union[int, float]): Total execution timeout in seconds. + request_timeout (int | float): Total execution timeout in seconds. Must be greater than zero. Returns: Client: This client instance for fluent chaining. """ - if isinstance(request_timeout, bool) or not isinstance( - request_timeout, (float, int) - ): - raise TypeError( - f"request_timeout must be of type Union[int, float], got {type(request_timeout).__name__}" - ) + if isinstance(request_timeout, bool) or not isinstance(request_timeout, (float, int)): + raise TypeError(f"request_timeout must be of type Union[int, float], got {type(request_timeout).__name__}") if not math.isfinite(request_timeout) or request_timeout <= 0: raise ValueError("request_timeout must be a finite value greater than 0") @@ -400,26 +352,25 @@ def set_request_timeout(self, request_timeout: Union[int, float]) -> "Client": "request_timeout should be larger than grpc_deadline. " "This configuration may cause operations to fail unexpectedly.", UserWarning, + stacklevel=2, ) self._request_timeout = float(request_timeout) return self - def set_min_backoff(self, min_backoff: Union[int, float]) -> "Client": + def set_min_backoff(self, min_backoff: int | float) -> Client: """ Set the minimum backoff delay used between retry attempts. Args: - min_backoff (Union[int, float]): Minimum backoff delay in seconds. + min_backoff (int | float): Minimum backoff delay in seconds. Must be finite and non-negative. Returns: Client: This client instance for fluent chaining. """ if isinstance(min_backoff, bool) or not isinstance(min_backoff, (int, float)): - raise TypeError( - f"min_backoff must be of type int or float, got {(type(min_backoff).__name__)}" - ) + raise TypeError(f"min_backoff must be of type int or float, got {(type(min_backoff).__name__)}") if not math.isfinite(min_backoff) or min_backoff < 0: raise ValueError("min_backoff must be a finite value >= 0") @@ -430,21 +381,19 @@ def set_min_backoff(self, min_backoff: Union[int, float]) -> "Client": self._min_backoff = float(min_backoff) return self - def set_max_backoff(self, max_backoff: Union[int, float]) -> "Client": + def set_max_backoff(self, max_backoff: int | float) -> Client: """ Set the maximum backoff delay used between retry attempts. Args: - max_backoff (Union[int, float]): Maximum backoff delay in seconds. + max_backoff (int | float): Maximum backoff delay in seconds. Must be finite and greater than or equal to min_backoff. Returns: Client: This client instance for fluent chaining. """ if isinstance(max_backoff, bool) or not isinstance(max_backoff, (int, float)): - raise TypeError( - f"max_backoff must be of type int or float, got {(type(max_backoff).__name__)}" - ) + raise TypeError(f"max_backoff must be of type int or float, got {(type(max_backoff).__name__)}") if not math.isfinite(max_backoff) or max_backoff < 0: raise ValueError("max_backoff must be a finite value >= 0") @@ -455,14 +404,12 @@ def set_max_backoff(self, max_backoff: Union[int, float]) -> "Client": self._max_backoff = float(max_backoff) return self - def update_network(self) -> "Client": - """ - Refresh the network node list from the mirror node. - """ + def update_network(self) -> Client: + """Refresh the network node list from the mirror node.""" self.network._set_network_nodes() return self - def __enter__(self) -> "Client": + def __enter__(self) -> Client: """ Allows the Client to be used in a 'with' statement for automatic resource management. This ensures that channels are closed properly when the block is exited. @@ -470,7 +417,5 @@ def __enter__(self) -> "Client": return self def __exit__(self, exc_type, exc_value, traceback) -> None: - """ - Automatically close channels when exiting 'with' block. - """ + """Automatically close channels when exiting 'with' block.""" self.close() diff --git a/src/hiero_sdk_python/client/network.py b/src/hiero_sdk_python/client/network.py index 406a8cc5e..13450b075 100644 --- a/src/hiero_sdk_python/client/network.py +++ b/src/hiero_sdk_python/client/network.py @@ -1,8 +1,10 @@ """Network module for managing Hedera SDK connections.""" +from __future__ import annotations + import secrets import time -from typing import Dict, List, Optional, Any, Tuple +from typing import Any import requests @@ -12,12 +14,10 @@ class Network: - """ - Manages the network configuration for connecting to the Hedera network. - """ + """Manages the network configuration for connecting to the Hedera network.""" # Mirror node gRPC addresses (always use TLS, port 443 for HTTPS) - MIRROR_ADDRESS_DEFAULT: Dict[str, str] = { + MIRROR_ADDRESS_DEFAULT: dict[str, str] = { "mainnet": "mainnet.mirrornode.hedera.com:443", "testnet": "testnet.mirrornode.hedera.com:443", "previewnet": "previewnet.mirrornode.hedera.com:443", @@ -25,14 +25,14 @@ class Network: } # Mirror node REST API base URLs (HTTPS for production networks, HTTP for localhost) - MIRROR_NODE_URLS: Dict[str, str] = { + MIRROR_NODE_URLS: dict[str, str] = { "mainnet": "https://mainnet-public.mirrornode.hedera.com", "testnet": "https://testnet.mirrornode.hedera.com", "previewnet": "https://previewnet.mirrornode.hedera.com", "solo": "http://localhost:5551", # Local development only } - DEFAULT_NODES: Dict[str, List[_Node]] = { + DEFAULT_NODES: dict[str, list[_Node]] = { "mainnet": [ ("35.237.200.180:50211", AccountId(0, 0, 3)), ("35.186.191.247:50211", AccountId(0, 0, 4)), @@ -64,7 +64,7 @@ class Network: "local": [("localhost:50211", AccountId(0, 0, 3))], } - LEDGER_ID: Dict[str, bytes] = { + LEDGER_ID: dict[str, bytes] = { "mainnet": bytes.fromhex("00"), "testnet": bytes.fromhex("01"), "previewnet": bytes.fromhex("02"), @@ -74,8 +74,8 @@ class Network: def __init__( self, network: str = "testnet", - nodes: Optional[List[_Node]] = None, - mirror_address: Optional[str] = None, + nodes: list[_Node] | None = None, + mirror_address: str | None = None, ledger_id: bytes | None = None, ) -> None: """ @@ -97,9 +97,7 @@ def __init__( Use Client.set_transport_security() and Client.set_verify_certificates() to customize. """ self.network: str = network or "testnet" - self.mirror_address: str = mirror_address or self.MIRROR_ADDRESS_DEFAULT.get( - network, "localhost:5600" - ) + self.mirror_address: str = mirror_address or self.MIRROR_ADDRESS_DEFAULT.get(network, "localhost:5600") self.ledger_id = ledger_id or self.LEDGER_ID.get(network, bytes.fromhex("03")) @@ -107,10 +105,10 @@ def __init__( hosted_networks = ("mainnet", "testnet", "previewnet") self._transport_security: bool = self.network in hosted_networks self._verify_certificates: bool = True # Always enabled by default - self._root_certificates: Optional[bytes] = None + self._root_certificates: bytes | None = None - self.nodes: List[_Node] = [] - self._healthy_nodes: List[_Node] = [] + self.nodes: list[_Node] = [] + self._healthy_nodes: list[_Node] = [] self._set_network_nodes(nodes) @@ -124,23 +122,15 @@ def __init__( self._node_index: int = secrets.randbelow(len(self._healthy_nodes)) self.current_node: _Node = self._healthy_nodes[self._node_index] - def _set_network_nodes(self, nodes: Optional[List[_Node]] = None): - """ - Configure the consensus nodes used by this network. - """ + def _set_network_nodes(self, nodes: list[_Node] | None = None): + """Configure the consensus nodes used by this network.""" final_nodes = self._resolve_nodes(nodes) # Apply TLS configuration to all nodes for node in final_nodes: - node._apply_transport_security( - self._transport_security - ) # pylint: disable=protected-access - node._set_verify_certificates( - self._verify_certificates - ) # pylint: disable=protected-access - node._set_root_certificates( - self._root_certificates - ) # pylint: disable=protected-access + node._apply_transport_security(self._transport_security) # pylint: disable=protected-access + node._set_verify_certificates(self._verify_certificates) # pylint: disable=protected-access + node._set_root_certificates(self._root_certificates) # pylint: disable=protected-access self.nodes = final_nodes self._healthy_nodes = [] @@ -150,7 +140,7 @@ def _set_network_nodes(self, nodes: Optional[List[_Node]] = None): continue self._healthy_nodes.append(node) - def _resolve_nodes(self, nodes: Optional[List[_Node]]) -> List[_Node]: + def _resolve_nodes(self, nodes: list[_Node] | None) -> list[_Node]: if nodes: return nodes @@ -166,29 +156,26 @@ def _resolve_nodes(self, nodes: Optional[List[_Node]]) -> List[_Node]: raise ValueError(f"No nodes available for network='{self.network}'") - def _fetch_nodes_from_mirror_node(self) -> List[_Node]: + def _fetch_nodes_from_mirror_node(self) -> list[_Node]: """ Fetches the list of nodes from the Hedera Mirror Node REST API. + Returns: list: A list of _Node objects. """ - base_url: Optional[str] = self.MIRROR_NODE_URLS.get(self.network) + base_url: str | None = self.MIRROR_NODE_URLS.get(self.network) if not base_url: - print( - f"No known mirror node URL for network='{self.network}'. Skipping fetch." - ) + print(f"No known mirror node URL for network='{self.network}'. Skipping fetch.") return [] url: str = f"{base_url}/api/v1/network/nodes?limit=100&order=desc" try: - response: requests.Response = requests.get( - url, timeout=30 - ) # Add 30 second timeout + response: requests.Response = requests.get(url, timeout=30) # Add 30 second timeout response.raise_for_status() - data: Dict[str, Any] = response.json() + data: dict[str, Any] = response.json() - nodes: List[_Node] = [] + nodes: list[_Node] = [] # Process each node from the mirror node API response for node in data.get("nodes", []): address_book: NodeAddress = NodeAddress._from_dict(node) @@ -202,14 +189,9 @@ def _fetch_nodes_from_mirror_node(self) -> List[_Node]: print(f"Error fetching nodes from mirror node API: {e}") return [] - def _fetch_nodes_from_default_nodes(self) -> List[_Node]: - """ - Fetches the list of nodes from the default nodes for the network. - """ - nodes: List[_Node] = [] - for node in self.DEFAULT_NODES[self.network]: - nodes.append(_Node(node[1], node[0], None)) - return nodes + def _fetch_nodes_from_default_nodes(self) -> list[_Node]: + """Fetches the list of nodes from the default nodes for the network.""" + return [_Node(node[1], node[0], None) for node in self.DEFAULT_NODES[self.network]] def _select_node(self) -> _Node: """ @@ -235,7 +217,7 @@ def _select_node(self) -> _Node: self.current_node = self._healthy_nodes[self._node_index] return self.current_node - def _get_node(self, account_id: AccountId) -> Optional[_Node]: + def _get_node(self, account_id: AccountId) -> _Node | None: """ Get a node matching the given account ID. @@ -243,7 +225,7 @@ def _get_node(self, account_id: AccountId) -> Optional[_Node]: account_id (AccountId): The account ID of the node to locate. Returns: - Optional[_Node]: The matching node, or None if not found. + _Node | None: The matching node, or None if not found. """ self._readmit_nodes() for node in self.nodes: @@ -258,12 +240,12 @@ def get_mirror_address(self) -> str: """ return self.mirror_address - def _parse_mirror_address(self) -> Tuple[str, int]: + def _parse_mirror_address(self) -> tuple[str, int]: """ Parse mirror_address into host and port. Returns: - Tuple[str, int]: (host, port) tuple + tuple[str, int]: (host, port) tuple """ mirror_addr = self.mirror_address if ":" in mirror_addr: @@ -277,7 +259,7 @@ def _parse_mirror_address(self) -> Tuple[str, int]: port = 443 return (host, port) - def _determine_scheme_and_port(self, host: str, port: int) -> Tuple[str, int]: + def _determine_scheme_and_port(self, host: str, port: int) -> tuple[str, int]: """ Determine the scheme (http/https) and port for the REST URL. @@ -286,7 +268,7 @@ def _determine_scheme_and_port(self, host: str, port: int) -> Tuple[str, int]: port: The port number Returns: - Tuple[str, int]: (scheme, port) tuple + tuple[str, int]: (scheme, port) tuple """ is_localhost = host in ("localhost", "127.0.0.1") @@ -313,9 +295,7 @@ def _build_rest_url(self, scheme: str, host: str, port: int) -> str: Returns: str: Complete REST URL with /api/v1 suffix """ - is_default_port = (scheme == "https" and port == 443) or ( - scheme == "http" and port == 80 - ) + is_default_port = (scheme == "https" and port == 443) or (scheme == "http" and port == 80) if is_default_port: return f"{scheme}://{host}/api/v1" @@ -338,9 +318,7 @@ def get_mirror_rest_url(self) -> str: return self._build_rest_url(scheme, host, port) def set_transport_security(self, enabled: bool) -> None: - """ - Enable or disable TLS for consensus node connections. - """ + """Enable or disable TLS for consensus node connections.""" if self._transport_security == enabled: return for node in self.nodes: @@ -348,47 +326,33 @@ def set_transport_security(self, enabled: bool) -> None: self._transport_security = enabled def is_transport_security(self) -> bool: - """ - Determine if TLS is enabled for consensus node connections. - """ + """Determine if TLS is enabled for consensus node connections.""" return self._transport_security def set_verify_certificates(self, verify: bool) -> None: - """ - Enable or disable server certificate verification when TLS is enabled. - """ + """Enable or disable server certificate verification when TLS is enabled.""" if self._verify_certificates == verify: return for node in self.nodes: node._set_verify_certificates(verify) # pylint: disable=protected-access self._verify_certificates = verify - def set_tls_root_certificates(self, root_certificates: Optional[bytes]) -> None: - """ - Provide custom root certificates to use when establishing TLS channels. - """ + def set_tls_root_certificates(self, root_certificates: bytes | None) -> None: + """Provide custom root certificates to use when establishing TLS channels.""" self._root_certificates = root_certificates for node in self.nodes: - node._set_root_certificates( - root_certificates - ) # pylint: disable=protected-access + node._set_root_certificates(root_certificates) # pylint: disable=protected-access - def get_tls_root_certificates(self) -> Optional[bytes]: - """ - Retrieve the configured root certificates used for TLS channels. - """ + def get_tls_root_certificates(self) -> bytes | None: + """Retrieve the configured root certificates used for TLS channels.""" return self._root_certificates def is_verify_certificates(self) -> bool: - """ - Determine if certificate verification is enabled. - """ + """Determine if certificate verification is enabled.""" return self._verify_certificates def _readmit_nodes(self) -> None: - """ - Re-admit nodes whose backoff period has expired. - """ + """Re-admit nodes whose backoff period has expired.""" now = time.monotonic() if self._earliest_readmit_time > now: @@ -414,9 +378,7 @@ def _readmit_nodes(self) -> None: self._earliest_readmit_time = now + delay def _increase_backoff(self, node: _Node) -> None: - """ - Increase the node's backoff duration after a failure and remove node from healthy node. - """ + """Increase the node's backoff duration after a failure and remove node from healthy node.""" if not isinstance(node, _Node): raise TypeError("node must be of type _Node") @@ -424,9 +386,7 @@ def _increase_backoff(self, node: _Node) -> None: self._mark_node_unhealthy(node) def _decrease_backoff(self, node: _Node) -> None: - """ - Decrease the node's backoff duration after a successful operation. - """ + """Decrease the node's backoff duration after a successful operation.""" if not isinstance(node, _Node): raise TypeError("node must be of type _Node") diff --git a/src/hiero_sdk_python/consensus/topic_create_transaction.py b/src/hiero_sdk_python/consensus/topic_create_transaction.py index e62ab17f6..82be5de98 100644 --- a/src/hiero_sdk_python/consensus/topic_create_transaction.py +++ b/src/hiero_sdk_python/consensus/topic_create_transaction.py @@ -7,71 +7,72 @@ transaction body for submission to the Hedera network . """ -from typing import List, Union, Optional +from __future__ import annotations + +from hiero_sdk_python.account.account_id import AccountId +from hiero_sdk_python.channels import _Channel from hiero_sdk_python.Duration import Duration -from hiero_sdk_python.tokens.custom_fixed_fee import CustomFixedFee -from hiero_sdk_python.transaction.transaction import Transaction -from hiero_sdk_python.hapi.services import ( - consensus_create_topic_pb2, - transaction_pb2, - basic_types_pb2) +from hiero_sdk_python.executable import _Method +from hiero_sdk_python.hapi.services import consensus_create_topic_pb2, transaction_pb2 from hiero_sdk_python.hapi.services.schedulable_transaction_body_pb2 import ( SchedulableTransactionBody, ) -from hiero_sdk_python.channels import _Channel -from hiero_sdk_python.executable import _Method -from hiero_sdk_python.account.account_id import AccountId +from hiero_sdk_python.tokens.custom_fixed_fee import CustomFixedFee +from hiero_sdk_python.transaction.transaction import Transaction from hiero_sdk_python.utils.key_utils import Key, key_to_proto class TopicCreateTransaction(Transaction): """ - Represents a transaction to create a new topic in the Hedera - Consensus Service (HCS). + Represents a transaction to create a new topic in the Hedera + Consensus Service (HCS). - This transaction can optionally define an admin key, submit key, - auto-renew period, auto-renew account, and memo. + This transaction can optionally define an admin key, submit key, + auto-renew period, auto-renew account, and memo. """ + def __init__( self, - memo: Optional[str] = None, - admin_key: Optional[Key] = None, - submit_key: Optional[Key] = None, - auto_renew_period: Optional[Duration] = None, - auto_renew_account: Optional[AccountId] = None, - custom_fees: Optional[List[CustomFixedFee]] = None, - fee_schedule_key: Optional[Key] = None, - fee_exempt_keys: Optional[List[Key]] = None, + memo: str | None = None, + admin_key: Key | None = None, + submit_key: Key | None = None, + auto_renew_period: Duration | None = None, + auto_renew_account: AccountId | None = None, + custom_fees: list[CustomFixedFee] | None = None, + fee_schedule_key: Key | None = None, + fee_exempt_keys: list[Key] | None = None, ) -> None: """ Initializes a new instance of the TopicCreateTransaction class. Args: - memo (Optional[str]): Optional memo for the topic. - admin_key (Optional[Key]): Optional admin key for the topic (PrivateKey or PublicKey). - submit_key (Optional[Key]): Optional submit key for the topic (PrivateKey or PublicKey). - auto_renew_period (Optional[Duration]): Optional auto-renew period for the topic. - auto_renew_account (Optional[AccountId]): Optional account ID for auto-renewal. + memo (str, optional): Optional memo for the topic. + admin_key (Key, optional): Optional admin key for the topic (PrivateKey or PublicKey). + submit_key (Key, optional): Optional submit key for the topic (PrivateKey or PublicKey). + auto_renew_period (Duration, optional): Optional auto-renew period for the topic. + auto_renew_account (AccountId, optional): Optional account ID for auto-renewal. custom_fees (list[CustomFixedFee]): Optional list of custom fees for the topic. - fee_schedule_key (Optional[Key]): Optional fee schedule key for the topic (PrivateKey or PublicKey). - fee_exempt_keys (Optional[List[Key]]): Optional list of fee exempt keys for the topic (PrivateKey or PublicKey). + fee_schedule_key (Key, optional): Optional fee schedule key for the topic (PrivateKey or PublicKey). + fee_exempt_keys (list[Key], optional): Optional list of fee exempt keys for the topic (PrivateKey or PublicKey). """ super().__init__() self.memo: str = memo or "" - self.admin_key: Optional[Key] = admin_key - self.submit_key: Optional[Key] = submit_key + self.admin_key: Key | None = admin_key + self.submit_key: Key | None = submit_key self.auto_renew_period: Duration = auto_renew_period or Duration(7890000) - self.auto_renew_account: Optional[AccountId] = auto_renew_account - self.transaction_fee: Optional[int] = 2_000_000_000 # 20 Hbars - self.custom_fees: List[CustomFixedFee] = custom_fees or [] - self.fee_exempt_keys: List[Key] = fee_exempt_keys or [] - self.fee_schedule_key: Optional[Key] = fee_schedule_key + self.auto_renew_account: AccountId | None = auto_renew_account + self.transaction_fee: int | None = 2_000_000_000 # 20 Hbars + self.custom_fees: list[CustomFixedFee] = custom_fees or [] + self.fee_exempt_keys: list[Key] = fee_exempt_keys or [] + self.fee_schedule_key: Key | None = fee_schedule_key - def set_memo(self, memo: str) -> "TopicCreateTransaction": + def set_memo(self, memo: str) -> TopicCreateTransaction: """ Sets the memo for the topic creation transaction. + Args: memo (str): The memo to set for the topic. + Returns: TopicCreateTransaction: The current instance for method chaining. """ @@ -79,11 +80,13 @@ def set_memo(self, memo: str) -> "TopicCreateTransaction": self.memo = memo return self - def set_admin_key(self, key: Key) -> "TopicCreateTransaction": + def set_admin_key(self, key: Key) -> TopicCreateTransaction: """ Sets the admin key for the topic creation transaction. + Args: key (Key): The admin key to set for the topic (PrivateKey or PublicKey). + Returns: TopicCreateTransaction: The current instance for method chaining. """ @@ -91,11 +94,13 @@ def set_admin_key(self, key: Key) -> "TopicCreateTransaction": self.admin_key = key return self - def set_submit_key(self, key: Key) -> "TopicCreateTransaction": + def set_submit_key(self, key: Key) -> TopicCreateTransaction: """ Sets the submit key for the topic creation transaction. + Args: key (Key): The submit key to set for the topic (PrivateKey or PublicKey). + Returns: TopicCreateTransaction: The current instance for method chaining. """ @@ -103,13 +108,16 @@ def set_submit_key(self, key: Key) -> "TopicCreateTransaction": self.submit_key = key return self - def set_auto_renew_period(self, seconds: Union[Duration, int]) -> "TopicCreateTransaction": + def set_auto_renew_period(self, seconds: Duration | int) -> TopicCreateTransaction: """ Sets the auto-renew period for the topic creation transaction. + Args: seconds (Union[Duration, int]): The auto-renew period in seconds or a Duration object. + Returns: TopicCreateTransaction: The current instance for method chaining. + Raises: TypeError: If the provided duration is of an invalid type. """ @@ -122,11 +130,13 @@ def set_auto_renew_period(self, seconds: Union[Duration, int]) -> "TopicCreateTr raise TypeError("Duration of invalid type") return self - def set_auto_renew_account(self, account_id: AccountId) -> "TopicCreateTransaction": + def set_auto_renew_account(self, account_id: AccountId) -> TopicCreateTransaction: """ Sets the account ID for auto-renewal of the topic. + Args: account_id (AccountId): The account ID to set for auto-renewal. + Returns: TopicCreateTransaction: The current instance for method chaining. """ @@ -134,11 +144,13 @@ def set_auto_renew_account(self, account_id: AccountId) -> "TopicCreateTransacti self.auto_renew_account = account_id return self - def set_custom_fees(self, custom_fees: List[CustomFixedFee]) -> "TopicCreateTransaction": + def set_custom_fees(self, custom_fees: list[CustomFixedFee]) -> TopicCreateTransaction: """ Sets the custom fees for the topic creation transaction. + Args: - custom_fees (List[CustomFixedFee]): The custom fees to set for the topic. + custom_fees (list[CustomFixedFee]): The custom fees to set for the topic. + Returns: TopicCreateTransaction: The current instance for method chaining. """ @@ -146,12 +158,13 @@ def set_custom_fees(self, custom_fees: List[CustomFixedFee]) -> "TopicCreateTran self.custom_fees = custom_fees return self - - def set_fee_schedule_key(self, key: Key) -> "TopicCreateTransaction": + def set_fee_schedule_key(self, key: Key) -> TopicCreateTransaction: """ Sets the fee schedule key for the topic creation transaction. + Args: key (Key): The fee schedule key to set for the topic (PrivateKey or PublicKey). + Returns: TopicCreateTransaction: The current instance for method chaining. """ @@ -159,11 +172,13 @@ def set_fee_schedule_key(self, key: Key) -> "TopicCreateTransaction": self.fee_schedule_key = key return self - def set_fee_exempt_keys(self, keys: List[Key]) -> "TopicCreateTransaction": + def set_fee_exempt_keys(self, keys: list[Key]) -> TopicCreateTransaction: """ Sets the fee exempt keys for the topic creation transaction. + Args: - keys (List[Key]): The fee exempt keys to set for the topic (PrivateKey or PublicKey). + keys (list[Key]): The fee exempt keys to set for the topic (PrivateKey or PublicKey). + Returns: TopicCreateTransaction: The current instance for method chaining. """ @@ -171,7 +186,7 @@ def set_fee_exempt_keys(self, keys: List[Key]) -> "TopicCreateTransaction": self.fee_exempt_keys = keys return self - def _to_proto_key(self, key: Optional[Key]): + def _to_proto_key(self, key: Key | None): """ Backwards-compatible wrapper around `key_to_proto` for converting SDK keys (PrivateKey or PublicKey) to protobuf `Key` messages. @@ -185,21 +200,15 @@ def _to_proto_key(self, key: Optional[Key]): def _build_proto_body(self) -> consensus_create_topic_pb2.ConsensusCreateTopicTransactionBody: """ Returns the protobuf body for the topic create transaction. - + Returns: ConsensusCreateTopicTransactionBody: The protobuf body for this transaction. """ return consensus_create_topic_pb2.ConsensusCreateTopicTransactionBody( adminKey=key_to_proto(self.admin_key), submitKey=key_to_proto(self.submit_key), - autoRenewPeriod=( - self.auto_renew_period._to_proto() - if self.auto_renew_period is not None - else None), - autoRenewAccount=( - self.auto_renew_account._to_proto() - if self.auto_renew_account is not None - else None), + autoRenewPeriod=(self.auto_renew_period._to_proto() if self.auto_renew_period is not None else None), + autoRenewAccount=(self.auto_renew_account._to_proto() if self.auto_renew_account is not None else None), memo=self.memo, custom_fees=[custom_fee._to_topic_fee_proto() for custom_fee in self.custom_fees], fee_schedule_key=key_to_proto(self.fee_schedule_key), @@ -217,6 +226,7 @@ def build_transaction_body(self) -> transaction_pb2.TransactionBody: transaction_body = self.build_base_transaction_body() transaction_body.consensusCreateTopic.CopyFrom(consensus_create_body) return transaction_body + def build_scheduled_body(self) -> SchedulableTransactionBody: """ Builds the scheduled transaction body for this topic create transaction. @@ -232,12 +242,11 @@ def build_scheduled_body(self) -> SchedulableTransactionBody: def _get_method(self, channel: _Channel) -> _Method: """ Returns the method to be used for executing the transaction. + Args: channel (_Channel): The channel to be used for the transaction. + Returns: _Method: The method for executing the transaction. """ - return _Method( - transaction_func=channel.topic.createTopic, - query_func=None - ) + return _Method(transaction_func=channel.topic.createTopic, query_func=None) diff --git a/src/hiero_sdk_python/consensus/topic_delete_transaction.py b/src/hiero_sdk_python/consensus/topic_delete_transaction.py index 70e083372..fb789a9d6 100644 --- a/src/hiero_sdk_python/consensus/topic_delete_transaction.py +++ b/src/hiero_sdk_python/consensus/topic_delete_transaction.py @@ -6,36 +6,34 @@ defining the execution method required to perform the deletion transaction. """ -from typing import Optional +from __future__ import annotations + +from hiero_sdk_python.channels import _Channel from hiero_sdk_python.consensus.topic_id import TopicId -from hiero_sdk_python.transaction.transaction import Transaction -from hiero_sdk_python.hapi.services import ( - consensus_delete_topic_pb2, - transaction_pb2 -) +from hiero_sdk_python.executable import _Method +from hiero_sdk_python.hapi.services import consensus_delete_topic_pb2, transaction_pb2 from hiero_sdk_python.hapi.services.schedulable_transaction_body_pb2 import ( SchedulableTransactionBody, ) -from hiero_sdk_python.channels import _Channel -from hiero_sdk_python.executable import _Method +from hiero_sdk_python.transaction.transaction import Transaction class TopicDeleteTransaction(Transaction): """ - Represents a transaction to delete an existing topic in the Hedera - Consensus Service (HCS). + Represents a transaction to delete an existing topic in the Hedera + Consensus Service (HCS). """ - def __init__(self, topic_id: Optional[TopicId] = None) -> None: + def __init__(self, topic_id: TopicId | None = None) -> None: super().__init__() - self.topic_id: Optional[TopicId] = topic_id + self.topic_id: TopicId | None = topic_id self.transaction_fee: int = 10_000_000 - def set_topic_id(self, topic_id:TopicId ) -> "TopicDeleteTransaction": + def set_topic_id(self, topic_id: TopicId) -> TopicDeleteTransaction: """ Sets the topic ID for the transaction. - + Args: topic_id: The topic ID to delete. @@ -49,19 +47,17 @@ def set_topic_id(self, topic_id:TopicId ) -> "TopicDeleteTransaction": def _build_proto_body(self) -> consensus_delete_topic_pb2.ConsensusDeleteTopicTransactionBody: """ Returns the protobuf body for the topic delete transaction. - + Returns: ConsensusDeleteTopicTransactionBody: The protobuf body for this transaction. - + Raises: ValueError: If required fields are missing. """ if self.topic_id is None: raise ValueError("Missing required fields: topic_id") - return consensus_delete_topic_pb2.ConsensusDeleteTopicTransactionBody( - topicID=self.topic_id._to_proto() - ) + return consensus_delete_topic_pb2.ConsensusDeleteTopicTransactionBody(topicID=self.topic_id._to_proto()) def build_transaction_body(self) -> transaction_pb2.TransactionBody: """ @@ -90,12 +86,11 @@ def build_scheduled_body(self) -> SchedulableTransactionBody: def _get_method(self, channel: _Channel) -> _Method: """ Returns the method for executing the topic delete transaction. + Args: channel (_Channel): The channel to use for the transaction. + Returns: _Method: The method to execute the transaction. """ - return _Method( - transaction_func=channel.topic.deleteTopic, - query_func=None - ) + return _Method(transaction_func=channel.topic.deleteTopic, query_func=None) diff --git a/src/hiero_sdk_python/consensus/topic_id.py b/src/hiero_sdk_python/consensus/topic_id.py index dde808ec3..3a04644d2 100644 --- a/src/hiero_sdk_python/consensus/topic_id.py +++ b/src/hiero_sdk_python/consensus/topic_id.py @@ -7,15 +7,14 @@ formats within the Hiero SDK. """ +from __future__ import annotations + from dataclasses import dataclass, field -from hiero_sdk_python.hapi.services import basic_types_pb2 from hiero_sdk_python.client.client import Client -from hiero_sdk_python.utils.entity_id_helper import ( - parse_from_string, - validate_checksum, - format_to_string_with_checksum -) +from hiero_sdk_python.hapi.services import basic_types_pb2 +from hiero_sdk_python.utils.entity_id_helper import format_to_string_with_checksum, parse_from_string, validate_checksum + @dataclass(frozen=True) class TopicId: @@ -31,13 +30,14 @@ class TopicId: realm (int): The realm number of the topic. Defaults to 0. num (int): The topic number. Defaults to 0. """ + shard: int = 0 realm: int = 0 num: int = 0 checksum: str | None = field(default=None, init=False) @classmethod - def _from_proto(cls, topic_id_proto: basic_types_pb2.TopicID) -> "TopicId": + def _from_proto(cls, topic_id_proto: basic_types_pb2.TopicID) -> TopicId: """ Creates a TopicId instance from a protobuf TopicID object. @@ -47,11 +47,7 @@ def _from_proto(cls, topic_id_proto: basic_types_pb2.TopicID) -> "TopicId": Returns: TopicId: A new TopicId instance. """ - return cls( - shard=topic_id_proto.shardNum, - realm=topic_id_proto.realmNum, - num=topic_id_proto.topicNum - ) + return cls(shard=topic_id_proto.shardNum, realm=topic_id_proto.realmNum, num=topic_id_proto.topicNum) def _to_proto(self) -> basic_types_pb2.TopicID: """ @@ -85,7 +81,7 @@ def __repr__(self) -> str: return f"TopicId(shard={self.shard}, realm={self.realm}, num={self.num})" @classmethod - def from_string(cls, topic_id_str: str) -> "TopicId": + def from_string(cls, topic_id_str: str) -> TopicId: """ Parses a string in the format 'shard.realm.num' to create a TopicId instance. @@ -101,21 +97,15 @@ def from_string(cls, topic_id_str: str) -> "TopicId": try: shard, realm, num, checksum = parse_from_string(topic_id_str) - topic_id: TopicId = cls( - shard=int(shard), - realm=int(realm), - num=int(num) - ) + topic_id: TopicId = cls(shard=int(shard), realm=int(realm), num=int(num)) object.__setattr__(topic_id, "checksum", checksum) return topic_id except Exception as e: - raise ValueError( - f"Invalid topic ID string '{topic_id_str}'. Expected format 'shard.realm.num'." - ) from e + raise ValueError(f"Invalid topic ID string '{topic_id_str}'. Expected format 'shard.realm.num'.") from e def validate_checksum(self, client: Client) -> None: - """Validate the checksum for the topicId""" + """Validate the checksum for the topicId.""" validate_checksum( self.shard, self.realm, @@ -126,12 +116,7 @@ def validate_checksum(self, client: Client) -> None: def to_string_with_checksum(self, client: Client) -> str: """ - Returns the string representation of the TopicId with checksum + Returns the string representation of the TopicId with checksum in 'shard.realm.num-checksum' format. """ - return format_to_string_with_checksum( - self.shard, - self.realm, - self.num, - client - ) + return format_to_string_with_checksum(self.shard, self.realm, self.num, client) diff --git a/src/hiero_sdk_python/consensus/topic_info.py b/src/hiero_sdk_python/consensus/topic_info.py index bfec28fa4..8b50fde0a 100644 --- a/src/hiero_sdk_python/consensus/topic_info.py +++ b/src/hiero_sdk_python/consensus/topic_info.py @@ -6,80 +6,76 @@ optional fields, and providing a readable string representation of the topic state. """ + +from __future__ import annotations + from datetime import datetime, timezone -from typing import List, Optional from hiero_sdk_python.crypto.public_key import PublicKey -from hiero_sdk_python.hapi.services.basic_types_pb2 import Key, AccountID -from hiero_sdk_python.hapi.services.timestamp_pb2 import Timestamp -from hiero_sdk_python.hapi.services import consensus_topic_info_pb2 from hiero_sdk_python.Duration import Duration +from hiero_sdk_python.hapi.services import consensus_topic_info_pb2 +from hiero_sdk_python.hapi.services.basic_types_pb2 import AccountID, Key +from hiero_sdk_python.hapi.services.timestamp_pb2 import Timestamp from hiero_sdk_python.tokens.custom_fixed_fee import CustomFixedFee from hiero_sdk_python.utils.key_format import format_key class TopicInfo: """ - Represents consensus topic information on the Hedera network. + Represents consensus topic information on the Hedera network. - It wraps the `ConsensusTopicInfo` protobuf message, exposing attributes - such as memo, running hash, sequence number, expiration time, admin key, - submit key, auto-renewal configuration, and ledger ID. + It wraps the `ConsensusTopicInfo` protobuf message, exposing attributes + such as memo, running hash, sequence number, expiration time, admin key, + submit key, auto-renewal configuration, and ledger ID. """ def __init__( - self, - memo: str, - running_hash: bytes, - sequence_number: int, - expiration_time: Optional[Timestamp], - admin_key: Optional[Key], - submit_key: Optional[Key], - auto_renew_period: Optional[Duration], - auto_renew_account: Optional[AccountID], - ledger_id: Optional[bytes], - fee_schedule_key: Optional[PublicKey], - fee_exempt_keys: Optional[List[PublicKey]], - custom_fees: Optional[List[CustomFixedFee]], + self, + memo: str, + running_hash: bytes, + sequence_number: int, + expiration_time: Timestamp | None, + admin_key: Key | None, + submit_key: Key | None, + auto_renew_period: Duration | None, + auto_renew_account: AccountID | None, + ledger_id: bytes | None, + fee_schedule_key: PublicKey | None, + fee_exempt_keys: list[PublicKey] | None, + custom_fees: list[CustomFixedFee] | None, ) -> None: """ Initializes a new instance of the TopicInfo class. + Args: memo (str): The memo associated with the topic. running_hash (bytes): The current running hash of the topic. sequence_number (int): The sequence number of the topic. - expiration_time (Optional[Timestamp]): The expiration time of the topic. - admin_key (Optional[Key]): The admin key for the topic. - submit_key (Optional[Key]): The submit key for the topic. - auto_renew_period (Optional[Duration]): The auto-renew period for the topic. - auto_renew_account (Optional[AccountID]): The account ID for auto-renewal. - ledger_id (Optional[bytes]): The ledger ID associated with the topic. + expiration_time (Timestamp, optional): The expiration time of the topic. + admin_key (Key, optional): The admin key for the topic. + submit_key (Key, optional): The submit key for the topic. + auto_renew_period (Duration, optional): The auto-renew period for the topic. + auto_renew_account (AccountID, optional): The account ID for auto-renewal. + ledger_id (bytes, optional): The ledger ID associated with the topic. fee_schedule_key (PublicKey): The fee schedule key for the topic. - fee_exempt_keys (List[PublicKey]): The fee exempt keys for the topic. - custom_fees (List[CustomFixedFee]): The custom fees for the topic. + fee_exempt_keys (list[PublicKey]): The fee exempt keys for the topic. + custom_fees (list[CustomFixedFee]): The custom fees for the topic. """ self.memo: str = memo self.running_hash: bytes = running_hash self.sequence_number: int = sequence_number - self.expiration_time: Optional[Timestamp] = expiration_time - self.admin_key: Optional[Key] = admin_key - self.submit_key: Optional[Key] = submit_key - self.auto_renew_period: Optional[Duration] = auto_renew_period - self.auto_renew_account: Optional[AccountID] = auto_renew_account - self.ledger_id: Optional[bytes] = ledger_id + self.expiration_time: Timestamp | None = expiration_time + self.admin_key: Key | None = admin_key + self.submit_key: Key | None = submit_key + self.auto_renew_period: Duration | None = auto_renew_period + self.auto_renew_account: AccountID | None = auto_renew_account + self.ledger_id: bytes | None = ledger_id self.fee_schedule_key: PublicKey = fee_schedule_key - self.fee_exempt_keys: List[PublicKey] = ( - list(fee_exempt_keys) if fee_exempt_keys is not None else [] - ) - self.custom_fees: List[CustomFixedFee] = ( - list(custom_fees) if custom_fees is not None else [] - ) + self.fee_exempt_keys: list[PublicKey] = list(fee_exempt_keys) if fee_exempt_keys is not None else [] + self.custom_fees: list[CustomFixedFee] = list(custom_fees) if custom_fees is not None else [] @classmethod - def _from_proto( - cls, - topic_info_proto: consensus_topic_info_pb2.ConsensusTopicInfo - ) -> "TopicInfo": + def _from_proto(cls, topic_info_proto: consensus_topic_info_pb2.ConsensusTopicInfo) -> TopicInfo: """ Constructs a TopicInfo object from a protobuf ConsensusTopicInfo message. @@ -93,30 +89,22 @@ def _from_proto( memo=topic_info_proto.memo, running_hash=topic_info_proto.runningHash, sequence_number=topic_info_proto.sequenceNumber, - expiration_time=( - topic_info_proto.expirationTime - if topic_info_proto.HasField("expirationTime") else None - ), - admin_key=( - topic_info_proto.adminKey - if topic_info_proto.HasField("adminKey") else None - ), - submit_key=( - topic_info_proto.submitKey - if topic_info_proto.HasField("submitKey") else None - ), + expiration_time=(topic_info_proto.expirationTime if topic_info_proto.HasField("expirationTime") else None), + admin_key=(topic_info_proto.adminKey if topic_info_proto.HasField("adminKey") else None), + submit_key=(topic_info_proto.submitKey if topic_info_proto.HasField("submitKey") else None), auto_renew_period=( Duration._from_proto(proto=topic_info_proto.autoRenewPeriod) - if topic_info_proto.HasField("autoRenewPeriod") else None + if topic_info_proto.HasField("autoRenewPeriod") + else None ), auto_renew_account=( - topic_info_proto.autoRenewAccount - if topic_info_proto.HasField("autoRenewAccount") else None + topic_info_proto.autoRenewAccount if topic_info_proto.HasField("autoRenewAccount") else None ), ledger_id=getattr(topic_info_proto, "ledger_id", None), fee_schedule_key=( PublicKey._from_proto(topic_info_proto.fee_schedule_key) - if topic_info_proto.HasField("fee_schedule_key") else None + if topic_info_proto.HasField("fee_schedule_key") + else None ), fee_exempt_keys=[PublicKey._from_proto(key) for key in topic_info_proto.fee_exempt_key_list], custom_fees=[CustomFixedFee._from_proto(fee) for fee in topic_info_proto.custom_fees], @@ -138,25 +126,21 @@ def __str__(self) -> str: Returns: str: A nicely formatted string representation of the topic. """ - exp_dt: Optional[str] = None + exp_dt: str | None = None if self.expiration_time and hasattr(self.expiration_time, "seconds"): - utc_dt = datetime.fromtimestamp( - self.expiration_time.seconds, tz=timezone.utc - ) + utc_dt = datetime.fromtimestamp(self.expiration_time.seconds, tz=timezone.utc) exp_dt = utc_dt.strftime("%Y-%m-%d %H:%M:%S") - running_hash_str: Optional[str] = f"0x{self.running_hash.hex()}" if self.running_hash else "None" - + running_hash_str: str | None = f"0x{self.running_hash.hex()}" if self.running_hash else "None" + # shows 0x{hex} when present, or "None" as a string when absent (previously could be 0xNone) - ledger_id_hex: Optional[str] = None + ledger_id_hex: str | None = None if self.ledger_id and isinstance(self.ledger_id, (bytes, bytearray)): ledger_id_hex = self.ledger_id.hex() ledger_id_str = f"0x{ledger_id_hex}" if ledger_id_hex else "None" # extracts and displays just the seconds value (e.g., 7776000) from Duration - auto_renew_seconds = ( - self.auto_renew_period.seconds if self.auto_renew_period else None - ) + auto_renew_seconds = self.auto_renew_period.seconds if self.auto_renew_period else None if self.auto_renew_account is None: auto_renew_account_str = "None" @@ -187,4 +171,4 @@ def __str__(self) -> str: f" fee_exempt_keys={fee_exempt_keys_formatted},\n" f" custom_fees={self.custom_fees},\n" ")" - ) \ No newline at end of file + ) diff --git a/src/hiero_sdk_python/consensus/topic_message.py b/src/hiero_sdk_python/consensus/topic_message.py index 4f25ebb54..ee87a7244 100644 --- a/src/hiero_sdk_python/consensus/topic_message.py +++ b/src/hiero_sdk_python/consensus/topic_message.py @@ -3,11 +3,12 @@ Hedera Consensus Service topic messages using the Hiero SDK. """ +from __future__ import annotations + from datetime import datetime -from typing import Optional, List, Union, Dict -from hiero_sdk_python.timestamp import Timestamp from hiero_sdk_python.hapi.mirror import consensus_service_pb2 as mirror_proto +from hiero_sdk_python.timestamp import Timestamp from hiero_sdk_python.transaction.transaction_id import TransactionId @@ -20,60 +21,55 @@ class TopicMessageChunk: def __init__(self, response: mirror_proto.ConsensusTopicResponse) -> None: # type: ignore """ Initializes a TopicMessageChunk from a ConsensusTopicResponse. + Args: response: The ConsensusTopicResponse containing chunk data. """ - self.consensus_timestamp: datetime = Timestamp._from_protobuf( - response.consensusTimestamp - ).to_date() + self.consensus_timestamp: datetime = Timestamp._from_protobuf(response.consensusTimestamp).to_date() self.content_size: int = len(response.message) - self.running_hash: Union[bytes, int] = response.runningHash - self.sequence_number: Union[bytes, int] = response.sequenceNumber + self.running_hash: bytes | int = response.runningHash + self.sequence_number: bytes | int = response.sequenceNumber class TopicMessage: - """ - Represents a Hedera TopicMessage, possibly composed of multiple chunks. - """ + """Represents a Hedera TopicMessage, possibly composed of multiple chunks.""" def __init__( - self, - consensus_timestamp: datetime, - message_data: Dict[str, Union[bytes, int]], - chunks: List[TopicMessageChunk], - transaction_id: Optional[TransactionId] = None, + self, + consensus_timestamp: datetime, + message_data: dict[str, bytes | int], + chunks: list[TopicMessageChunk], + transaction_id: TransactionId | None = None, ) -> None: """ Args: consensus_timestamp (datetime): The final consensus timestamp. - message_data (Dict[str, Union[bytes, int]]): Dict with required fields: + message_data (dict[str, bytes | int]): Dict with required fields: { "contents": bytes, "running_hash": bytes, "sequence_number": int } - chunks (List[TopicMessageChunk]): All individual chunks that form this message. - transaction_id (Optional[Transaction]): The transaction ID if available. + chunks (list[TopicMessageChunk]): All individual chunks that form this message. + transaction_id (Transaction, optional): The transaction ID if available. """ self.consensus_timestamp: datetime = consensus_timestamp - self.contents: Union[bytes, int] = message_data["contents"] - self.running_hash: Union[bytes, int] = message_data["running_hash"] - self.sequence_number: Union[bytes, int] = message_data["sequence_number"] - self.chunks: List[TopicMessageChunk] = chunks - self.transaction_id: Optional[TransactionId] = transaction_id + self.contents: bytes | int = message_data["contents"] + self.running_hash: bytes | int = message_data["running_hash"] + self.sequence_number: bytes | int = message_data["sequence_number"] + self.chunks: list[TopicMessageChunk] = chunks + self.transaction_id: TransactionId | None = transaction_id @classmethod - def of_single(cls, response: mirror_proto.ConsensusTopicResponse) -> "TopicMessage": # type: ignore - """ - Build a TopicMessage from a single-chunk response. - """ + def of_single(cls, response: mirror_proto.ConsensusTopicResponse) -> TopicMessage: # type: ignore + """Build a TopicMessage from a single-chunk response.""" chunk: TopicMessageChunk = TopicMessageChunk(response) consensus_timestamp: datetime = chunk.consensus_timestamp - contents: Union[bytes, int] = response.message - running_hash: Union[bytes, int] = response.runningHash - sequence_number: Union[bytes, int] = chunk.sequence_number + contents: bytes | int = response.message + running_hash: bytes | int = response.runningHash + sequence_number: bytes | int = chunk.sequence_number - transaction_id: Optional[TransactionId] = None + transaction_id: TransactionId | None = None if response.HasField("chunkInfo") and response.chunkInfo.HasField("initialTransactionID"): transaction_id = TransactionId._from_proto(response.chunkInfo.initialTransactionID) @@ -85,38 +81,31 @@ def of_single(cls, response: mirror_proto.ConsensusTopicResponse) -> "TopicMessa "sequence_number": sequence_number, }, [chunk], - transaction_id + transaction_id, ) @classmethod - def of_many(cls, responses: List[mirror_proto.ConsensusTopicResponse]) -> "TopicMessage": # type: ignore - """ - Reassemble multiple chunk responses into a single TopicMessage. - """ - sorted_responses: List[mirror_proto.ConsensusTopicResponse] = sorted( + def of_many(cls, responses: list[mirror_proto.ConsensusTopicResponse]) -> TopicMessage: # type: ignore + """Reassemble multiple chunk responses into a single TopicMessage.""" + sorted_responses: list[mirror_proto.ConsensusTopicResponse] = sorted( responses, key=lambda r: r.chunkInfo.number ) - chunks: List[TopicMessageChunk] = [] + chunks: list[TopicMessageChunk] = [] total_size: int = 0 - transaction_id: Optional[TransactionId] = None - + transaction_id: TransactionId | None = None + for r in sorted_responses: c = TopicMessageChunk(r) chunks.append(c) - + total_size += len(r.message) - - if ( - transaction_id is None - and r.HasField("chunkInfo") - and r.chunkInfo.HasField("initialTransactionID") - ): + if transaction_id is None and r.HasField("chunkInfo") and r.chunkInfo.HasField("initialTransactionID"): transaction_id = TransactionId._from_proto(r.chunkInfo.initialTransactionID) contents = bytearray(total_size) - + offset: int = 0 for r in sorted_responses: end = offset + len(r.message) @@ -124,9 +113,7 @@ def of_many(cls, responses: List[mirror_proto.ConsensusTopicResponse]) -> "Topic offset = end last_r: mirror_proto.ConsensusTopicResponse = sorted_responses[-1] - consensus_timestamp: datetime = Timestamp._from_protobuf( - last_r.consensusTimestamp - ).to_date() + consensus_timestamp: datetime = Timestamp._from_protobuf(last_r.consensusTimestamp).to_date() running_hash: bytes = last_r.runningHash sequence_number: int = last_r.sequenceNumber @@ -138,22 +125,19 @@ def of_many(cls, responses: List[mirror_proto.ConsensusTopicResponse]) -> "Topic "sequence_number": sequence_number, }, chunks, - transaction_id + transaction_id, ) @classmethod def _from_proto( - cls, - response_or_responses: Union[ - mirror_proto.ConsensusTopicResponse, - List[mirror_proto.ConsensusTopicResponse] - ], - chunking_enabled: bool = False - ) -> "TopicMessage": + cls, + response_or_responses: mirror_proto.ConsensusTopicResponse | list[mirror_proto.ConsensusTopicResponse], + chunking_enabled: bool = False, + ) -> TopicMessage: """ Creates a TopicMessage from either: - A single ConsensusTopicResponse - - A list of responses (for multi-chunk) + - A list of responses (for multi-chunk). If chunking is enabled and multiple chunks are detected, they are reassembled into one combined TopicMessage. Otherwise, a single chunk is returned as-is. @@ -169,10 +153,7 @@ def _from_proto( response: mirror_proto.ConsensusTopicResponse = response_or_responses if chunking_enabled and response.HasField("chunkInfo") and response.chunkInfo.total > 1: - raise ValueError( - "Cannot handle multi-chunk in a single response." - " Pass all chunk responses in a list." - ) + raise ValueError("Cannot handle multi-chunk in a single response. Pass all chunk responses in a list.") return cls.of_single(response) def __str__(self) -> str: diff --git a/src/hiero_sdk_python/consensus/topic_message_submit_transaction.py b/src/hiero_sdk_python/consensus/topic_message_submit_transaction.py index 48ae989e9..960a1e17a 100644 --- a/src/hiero_sdk_python/consensus/topic_message_submit_transaction.py +++ b/src/hiero_sdk_python/consensus/topic_message_submit_transaction.py @@ -1,17 +1,19 @@ +from __future__ import annotations + import math -from typing import List, Literal, Optional, Union, overload +from typing import Literal, overload + +from hiero_sdk_python.channels import _Channel from hiero_sdk_python.client.client import Client from hiero_sdk_python.consensus.topic_id import TopicId from hiero_sdk_python.crypto.private_key import PrivateKey -from hiero_sdk_python.transaction.transaction import Transaction -from hiero_sdk_python.transaction.custom_fee_limit import CustomFeeLimit -from hiero_sdk_python.hapi.services import consensus_submit_message_pb2, timestamp_pb2 -from hiero_sdk_python.hapi.services import transaction_pb2 +from hiero_sdk_python.executable import _Method +from hiero_sdk_python.hapi.services import consensus_submit_message_pb2, timestamp_pb2, transaction_pb2 from hiero_sdk_python.hapi.services.schedulable_transaction_body_pb2 import ( SchedulableTransactionBody, ) -from hiero_sdk_python.channels import _Channel -from hiero_sdk_python.executable import _Method +from hiero_sdk_python.transaction.custom_fee_limit import CustomFeeLimit +from hiero_sdk_python.transaction.transaction import Transaction from hiero_sdk_python.transaction.transaction_id import TransactionId from hiero_sdk_python.transaction.transaction_receipt import TransactionReceipt from hiero_sdk_python.transaction.transaction_response import TransactionResponse @@ -19,43 +21,44 @@ class TopicMessageSubmitTransaction(Transaction): """ - Represents a transaction that submits a message to a Hedera Consensus Service topic. + Represents a transaction that submits a message to a Hedera Consensus Service topic. - Allows setting the target topic ID and message, building the transaction body, - and executing the submission through a network channel. + Allows setting the target topic ID and message, building the transaction body, + and executing the submission through a network channel. """ def __init__( self, - topic_id: Optional[TopicId] = None, - message: Optional[str] = None, - chunk_size: Optional[int] = None, - max_chunks: Optional[int] = None + topic_id: TopicId | None = None, + message: str | None = None, + chunk_size: int | None = None, + max_chunks: int | None = None, ) -> None: """ Initializes a new TopicMessageSubmitTransaction instance. + Args: - topic_id (Optional[TopicId]): The ID of the topic. - message (Optional[str]): The message to submit. - chunk_size (Optional[int]): The maximum chunk size in bytes, Default: 1024. - max_chunks (Optional[int]): The maximum number of chunks allowed, Default: 20. + topic_id (TopicId, optional): The ID of the topic. + message (str, optional): The message to submit. + chunk_size (int, optional): The maximum chunk size in bytes, Default: 1024. + max_chunks (int, optional): The maximum number of chunks allowed, Default: 20. """ super().__init__() - self.topic_id: Optional[TopicId] = topic_id - self.message: Optional[str] = message + self.topic_id: TopicId | None = topic_id + self.message: str | None = message self.chunk_size: int = chunk_size or 1024 self.max_chunks: int = max_chunks or 20 self._current_index = 0 self._total_chunks = self.get_required_chunks() - self._initial_transaction_id: Optional[TransactionId] = None - self._transaction_ids: List[TransactionId] = [] - self._signing_keys: List["PrivateKey"] = [] + self._initial_transaction_id: TransactionId | None = None + self._transaction_ids: list[TransactionId] = [] + self._signing_keys: list[PrivateKey] = [] def get_required_chunks(self) -> int: """ Returns the number of chunks required for the current message. - + Returns: int: Number of chunks required. """ @@ -65,9 +68,7 @@ def get_required_chunks(self) -> int: content = self.message.encode("utf-8") return math.ceil(len(content) / self.chunk_size) - def set_topic_id( - self, topic_id: TopicId - ) -> "TopicMessageSubmitTransaction": + def set_topic_id(self, topic_id: TopicId) -> TopicMessageSubmitTransaction: """ Sets the topic ID for the message submission. @@ -81,7 +82,7 @@ def set_topic_id( self.topic_id = topic_id return self - def set_message(self, message: str) -> "TopicMessageSubmitTransaction": + def set_message(self, message: str) -> TopicMessageSubmitTransaction: """ Sets the message to submit to the topic. @@ -96,10 +97,10 @@ def set_message(self, message: str) -> "TopicMessageSubmitTransaction": self._total_chunks = self.get_required_chunks() return self - def set_chunk_size(self, chunk_size: int) -> "TopicMessageSubmitTransaction": + def set_chunk_size(self, chunk_size: int) -> TopicMessageSubmitTransaction: """ Set maximum chunk size in bytes. - + Args: chunk_size (int): The size of each chunk in bytes. @@ -114,7 +115,7 @@ def set_chunk_size(self, chunk_size: int) -> "TopicMessageSubmitTransaction": self._total_chunks = self.get_required_chunks() return self - def set_max_chunks(self, max_chunks: int) -> "TopicMessageSubmitTransaction": + def set_max_chunks(self, max_chunks: int) -> TopicMessageSubmitTransaction: """ Set maximum allowed chunks. @@ -131,14 +132,12 @@ def set_max_chunks(self, max_chunks: int) -> "TopicMessageSubmitTransaction": self.max_chunks = max_chunks return self - def set_custom_fee_limits( - self, custom_fee_limits: list["CustomFeeLimit"] - ) -> "TopicMessageSubmitTransaction": + def set_custom_fee_limits(self, custom_fee_limits: list[CustomFeeLimit]) -> TopicMessageSubmitTransaction: """ Sets the maximum custom fees that the user is willing to pay for the message. Args: - custom_fee_limits (List[CustomFeeLimit]): The list of custom fee limits to set. + custom_fee_limits (list[CustomFeeLimit]): The list of custom fee limits to set. Returns: TopicMessageSubmitTransaction: This transaction instance (for chaining). @@ -147,9 +146,7 @@ def set_custom_fee_limits( self.custom_fee_limits = custom_fee_limits return self - def add_custom_fee_limit( - self, custom_fee_limit: "CustomFeeLimit" - ) -> "TopicMessageSubmitTransaction": + def add_custom_fee_limit(self, custom_fee_limit: CustomFeeLimit) -> TopicMessageSubmitTransaction: """ Adds a maximum custom fee that the user is willing to pay for the message. @@ -181,10 +178,10 @@ def _validate_chunking(self) -> None: def _build_proto_body(self) -> consensus_submit_message_pb2.ConsensusSubmitMessageTransactionBody: """ Returns the protobuf body for the topic message submit transaction. - + Returns: ConsensusSubmitMessageTransactionBody: The protobuf body for this transaction. - + Raises: ValueError: If required fields (topic_id, message) are missing. """ @@ -199,19 +196,19 @@ def _build_proto_body(self) -> consensus_submit_message_pb2.ConsensusSubmitMessa end_index = min(start_index + self.chunk_size, len(content)) chunk_content = content[start_index:end_index] - body = consensus_submit_message_pb2.ConsensusSubmitMessageTransactionBody( - topicID=self.topic_id._to_proto(), - message=chunk_content + topicID=self.topic_id._to_proto(), message=chunk_content ) # Multi-chunk metadata if self._total_chunks > 1: - body.chunkInfo.CopyFrom(consensus_submit_message_pb2.ConsensusMessageChunkInfo( - initialTransactionID=self._initial_transaction_id._to_proto(), - total=self._total_chunks, - number=self._current_index + 1 - )) + body.chunkInfo.CopyFrom( + consensus_submit_message_pb2.ConsensusMessageChunkInfo( + initialTransactionID=self._initial_transaction_id._to_proto(), + total=self._total_chunks, + number=self._current_index + 1, + ) + ) return body @@ -220,7 +217,7 @@ def build_transaction_body(self) -> transaction_pb2.TransactionBody: Builds and returns the protobuf transaction body for message submission. Returns: - TransactionBody: The protobuf transaction body containing + TransactionBody: The protobuf transaction body containing the message submission details. """ consensus_submit_message_body = self._build_proto_body() @@ -250,12 +247,9 @@ def _get_method(self, channel: _Channel) -> _Method: Returns: _Method: The method object with bound transaction execution. """ - return _Method( - transaction_func=channel.topic.submitMessage, - query_func=None - ) + return _Method(transaction_func=channel.topic.submitMessage, query_func=None) - def freeze_with(self, client: "Client") -> "TopicMessageSubmitTransaction": + def freeze_with(self, client: Client) -> TopicMessageSubmitTransaction: if self._transaction_body_bytes: return self @@ -273,91 +267,85 @@ def freeze_with(self, client: "Client") -> "TopicMessageSubmitTransaction": chunk_transaction_id = self.transaction_id else: chunk_valid_start = timestamp_pb2.Timestamp( - seconds=base_timestamp.seconds, - nanos=base_timestamp.nanos + i + 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 + account_id=self.transaction_id.account_id, valid_start=chunk_valid_start ) self._transaction_ids.append(chunk_transaction_id) return super().freeze_with(client) - + @overload def execute( self, - client: "Client", + client: Client, timeout: int | float | None = None, wait_for_receipt: Literal[True] = True, - validate_status: bool = False - ) -> "TransactionReceipt": - ... + validate_status: bool = False, + ) -> TransactionReceipt: ... @overload def execute( self, - client: "Client", + client: Client, timeout: int | float | None = None, wait_for_receipt: Literal[False] = False, - validate_status: bool = False - ) -> "TransactionResponse": - ... + validate_status: bool = False, + ) -> TransactionResponse: ... def execute( self, - client: "Client", + client: Client, timeout: int | float | None = None, wait_for_receipt: bool = True, - validate_status: bool = False + validate_status: bool = False, ) -> TransactionReceipt | TransactionResponse: """ Executes the topic message submit transaction. - + For multi-chunk transactions, this method will execute all chunks sequentially and return first response. - + Args: client: The client to execute the transaction with. timeout (int | float | None, optional): The total execution timeout (in seconds) for this execution. wait_for_receipt (bool, optional): Whether to wait for consensus and return the receipt. If False, the method returns a TransactionResponse immediately after submission. validate_status: (bool): Whether the query should automatically validate the transaction status (default False). - + Returns: TransactionReceipt: If wait_for_receipt is True (default) TransactionResponse: If wait_for_receipt is False """ # Return the first response as the JS SDK does return self.execute_all(client, timeout, wait_for_receipt, validate_status)[0] - + @overload def execute_all( self, - client: "Client", + client: Client, timeout: int | float | None = None, wait_for_receipt: Literal[True] = True, - validate_status: bool = False - ) -> List["TransactionReceipt"]: - ... + validate_status: bool = False, + ) -> list[TransactionReceipt]: ... @overload def execute_all( self, - client: "Client", + client: Client, timeout: int | float | None = None, wait_for_receipt: Literal[False] = False, - validate_status: bool = False - ) -> List["TransactionResponse"]: - ... - + validate_status: bool = False, + ) -> list[TransactionResponse]: ... + def execute_all( self, - client: "Client", + client: Client, timeout: int | float | None = None, wait_for_receipt: bool = True, - validate_status: bool = False - ) -> List["TransactionReceipt"] | List["TransactionResponse"]: + validate_status: bool = False, + ) -> list[TransactionReceipt] | list[TransactionResponse]: """ Executes the topic message submit transaction. @@ -369,7 +357,7 @@ def execute_all( wait_for_receipt (bool, optional): Whether to wait for consensus and return the receipt. If False, the method returns a TransactionResponse immediately after submission. validate_status: (bool): Whether the query should automatically validate the transaction status (default False). - + Returns: List[TransactionReceipt]: If wait_for_receipt is True (default) List[TransactionResponse]: If wait_for_receipt is False @@ -399,16 +387,15 @@ def execute_all( # Execute the chunk response = super().execute(client, timeout, wait_for_receipt, validate_status) responses.append(response) - + return responses - - def sign(self, private_key: "PrivateKey"): + def sign(self, private_key: PrivateKey): """ Signs the transaction using the provided private key. - + For multi-chunk transactions, this stores the signing key for later use. - + Args: private_key (PrivateKey): The private key to sign the transaction with. """ diff --git a/src/hiero_sdk_python/consensus/topic_update_transaction.py b/src/hiero_sdk_python/consensus/topic_update_transaction.py index 01c6a0849..a886073bf 100644 --- a/src/hiero_sdk_python/consensus/topic_update_transaction.py +++ b/src/hiero_sdk_python/consensus/topic_update_transaction.py @@ -2,46 +2,46 @@ This module provides the `TopicUpdateTransaction` class for updating consensus topics on the Hedera network using the Hiero SDK. """ -from typing import Union, Optional, List + +from __future__ import annotations + from google.protobuf import wrappers_pb2 as _wrappers_pb2 -from hiero_sdk_python.Duration import Duration + +from hiero_sdk_python.account.account_id import AccountId from hiero_sdk_python.channels import _Channel from hiero_sdk_python.consensus.topic_id import TopicId +from hiero_sdk_python.crypto.public_key import PublicKey +from hiero_sdk_python.Duration import Duration from hiero_sdk_python.executable import _Method -from hiero_sdk_python.hapi.services.custom_fees_pb2 import FeeExemptKeyList -from hiero_sdk_python.hapi.services.custom_fees_pb2 import FixedCustomFeeList -from hiero_sdk_python.timestamp import Timestamp -from hiero_sdk_python.transaction.transaction import Transaction -from hiero_sdk_python.hapi.services import ( - consensus_update_topic_pb2, - duration_pb2, - timestamp_pb2, - transaction_pb2 -) +from hiero_sdk_python.hapi.services import consensus_update_topic_pb2, duration_pb2, timestamp_pb2, transaction_pb2 +from hiero_sdk_python.hapi.services.custom_fees_pb2 import FeeExemptKeyList, FixedCustomFeeList from hiero_sdk_python.hapi.services.schedulable_transaction_body_pb2 import ( SchedulableTransactionBody, ) -from hiero_sdk_python.account.account_id import AccountId -from hiero_sdk_python.crypto.public_key import PublicKey +from hiero_sdk_python.timestamp import Timestamp from hiero_sdk_python.tokens.custom_fixed_fee import CustomFixedFee +from hiero_sdk_python.transaction.transaction import Transaction + class TopicUpdateTransaction(Transaction): """Represents a transaction to update a consensus topic.""" + def __init__( self, - topic_id: Optional[TopicId] = None, - memo: Optional[str] = None, - admin_key: Optional[PublicKey] = None, - submit_key: Optional[PublicKey] = None, - auto_renew_period: Optional[Duration] = Duration(7890000), - auto_renew_account: Optional[AccountId] = None, - expiration_time: Optional[Timestamp] = None, - custom_fees: Optional[List[CustomFixedFee]] = None, - fee_schedule_key: Optional[PublicKey] = None, - fee_exempt_keys: Optional[List[PublicKey]] = None, + topic_id: TopicId | None = None, + memo: str | None = None, + admin_key: PublicKey | None = None, + submit_key: PublicKey | None = None, + auto_renew_period: Duration | None = Duration(7890000), + auto_renew_account: AccountId | None = None, + expiration_time: Timestamp | None = None, + custom_fees: list[CustomFixedFee] | None = None, + fee_schedule_key: PublicKey | None = None, + fee_exempt_keys: list[PublicKey] | None = None, ) -> None: """ Initializes a new instance of the TopicUpdateTransaction class. + Args: topic_id (TopicId): The ID of the topic to update. memo (str): The memo associated with the topic. @@ -52,19 +52,19 @@ def __init__( expiration_time (Timestamp): The expiration time of the topic. """ super().__init__() - self.topic_id: Optional[TopicId] = topic_id + self.topic_id: TopicId | None = topic_id self.memo: str = memo or "" - self.admin_key: Optional[PublicKey] = admin_key - self.submit_key: Optional[PublicKey] = submit_key - self.auto_renew_period: Optional[Duration] = auto_renew_period - self.auto_renew_account: Optional[AccountId] = auto_renew_account - self.expiration_time: Optional[Timestamp] = expiration_time + self.admin_key: PublicKey | None = admin_key + self.submit_key: PublicKey | None = submit_key + self.auto_renew_period: Duration | None = auto_renew_period + self.auto_renew_account: AccountId | None = auto_renew_account + self.expiration_time: Timestamp | None = expiration_time self.transaction_fee: int = 10_000_000 - self.custom_fees: Optional[List[CustomFixedFee]] = custom_fees - self.fee_schedule_key: Optional[PublicKey] = fee_schedule_key - self.fee_exempt_keys: Optional[List[PublicKey]] = fee_exempt_keys + self.custom_fees: list[CustomFixedFee] | None = custom_fees + self.fee_schedule_key: PublicKey | None = fee_schedule_key + self.fee_exempt_keys: list[PublicKey] | None = fee_exempt_keys - def set_topic_id(self, topic_id: TopicId) -> "TopicUpdateTransaction": + def set_topic_id(self, topic_id: TopicId) -> TopicUpdateTransaction: """ Sets the topic ID for the transaction. @@ -78,7 +78,7 @@ def set_topic_id(self, topic_id: TopicId) -> "TopicUpdateTransaction": self.topic_id = topic_id return self - def set_memo(self, memo: str) -> "TopicUpdateTransaction": + def set_memo(self, memo: str) -> TopicUpdateTransaction: """ Sets the memo for the topic. @@ -92,7 +92,7 @@ def set_memo(self, memo: str) -> "TopicUpdateTransaction": self.memo = memo return self - def set_admin_key(self, key: PublicKey) -> "TopicUpdateTransaction": + def set_admin_key(self, key: PublicKey) -> TopicUpdateTransaction: """ Sets the public admin key for the topic. @@ -106,7 +106,7 @@ def set_admin_key(self, key: PublicKey) -> "TopicUpdateTransaction": self.admin_key = key return self - def set_submit_key(self, key: PublicKey) -> "TopicUpdateTransaction": + def set_submit_key(self, key: PublicKey) -> TopicUpdateTransaction: """ Sets the public submit key for the topic. @@ -120,7 +120,7 @@ def set_submit_key(self, key: PublicKey) -> "TopicUpdateTransaction": self.submit_key = key return self - def set_auto_renew_period(self, seconds: Union[Duration, int]) -> "TopicUpdateTransaction": + def set_auto_renew_period(self, seconds: Duration | int) -> TopicUpdateTransaction: """ Sets the auto-renew period for the topic. @@ -139,7 +139,7 @@ def set_auto_renew_period(self, seconds: Union[Duration, int]) -> "TopicUpdateTr raise TypeError("Duration of invalid type") return self - def set_auto_renew_account(self, account_id: AccountId) -> "TopicUpdateTransaction": + def set_auto_renew_account(self, account_id: AccountId) -> TopicUpdateTransaction: """ Sets the auto-renew account for the topic. @@ -153,10 +153,7 @@ def set_auto_renew_account(self, account_id: AccountId) -> "TopicUpdateTransacti self.auto_renew_account = account_id return self - def set_expiration_time( - self, - expiration_time: timestamp_pb2.Timestamp - ) -> "TopicUpdateTransaction": + def set_expiration_time(self, expiration_time: timestamp_pb2.Timestamp) -> TopicUpdateTransaction: """ Sets the expiration time for the topic. @@ -170,13 +167,13 @@ def set_expiration_time( self.expiration_time = expiration_time return self - def set_custom_fees(self, custom_fees: List[CustomFixedFee]) -> "TopicUpdateTransaction": + def set_custom_fees(self, custom_fees: list[CustomFixedFee]) -> TopicUpdateTransaction: """ Sets the custom fees for the topic update transaction. - + Args: - custom_fees (List[CustomFixedFee]): The custom fees to set for the topic. - + custom_fees (list[CustomFixedFee]): The custom fees to set for the topic. + Returns: TopicUpdateTransaction: The current instance for method chaining. """ @@ -184,14 +181,13 @@ def set_custom_fees(self, custom_fees: List[CustomFixedFee]) -> "TopicUpdateTran self.custom_fees = custom_fees return self - - def set_fee_schedule_key(self, key: PublicKey) -> "TopicUpdateTransaction": + def set_fee_schedule_key(self, key: PublicKey) -> TopicUpdateTransaction: """ Sets the fee schedule key for the topic update transaction. - + Args: key (PublicKey): The fee schedule key to set for the topic. - + Returns: TopicUpdateTransaction: The current instance for method chaining. """ @@ -199,13 +195,13 @@ def set_fee_schedule_key(self, key: PublicKey) -> "TopicUpdateTransaction": self.fee_schedule_key = key return self - def set_fee_exempt_keys(self, keys: List[PublicKey]) -> "TopicUpdateTransaction": + def set_fee_exempt_keys(self, keys: list[PublicKey]) -> TopicUpdateTransaction: """ Sets the fee exempt keys for the topic update transaction. - + Args: - keys (List[PublicKey]): The fee exempt keys to set for the topic. - + keys (list[PublicKey]): The fee exempt keys to set for the topic. + Returns: TopicUpdateTransaction: The current instance for method chaining. """ @@ -213,23 +209,23 @@ def set_fee_exempt_keys(self, keys: List[PublicKey]) -> "TopicUpdateTransaction" self.fee_exempt_keys = keys return self - def clear_custom_fees(self) -> "TopicUpdateTransaction": + def clear_custom_fees(self) -> TopicUpdateTransaction: """ - Clears the custom fees for the topic update transaction and + Clears the custom fees for the topic update transaction and removes them from the network state. - + Returns: TopicUpdateTransaction: The current instance for method chaining. """ self._require_not_frozen() self.custom_fees = [] return self - - def clear_fee_exempt_keys(self) -> "TopicUpdateTransaction": + + def clear_fee_exempt_keys(self) -> TopicUpdateTransaction: """ - Clears the fee exempt keys for the topic update transaction and + Clears the fee exempt keys for the topic update transaction and removes them from the network state. - + Returns: TopicUpdateTransaction: The current instance for method chaining. """ @@ -240,10 +236,10 @@ def clear_fee_exempt_keys(self) -> "TopicUpdateTransaction": def _build_proto_body(self) -> consensus_update_topic_pb2.ConsensusUpdateTopicTransactionBody: """ Returns the protobuf body for the topic update transaction. - + Returns: ConsensusUpdateTopicTransactionBody: The protobuf body for this transaction. - + Raises: ValueError: If required fields are missing. """ @@ -251,9 +247,7 @@ def _build_proto_body(self) -> consensus_update_topic_pb2.ConsensusUpdateTopicTr raise ValueError("Missing required fields: topic_id") custom_fees = ( - FixedCustomFeeList( - fees=[custom_fee._to_topic_fee_proto() for custom_fee in self.custom_fees] - ) + FixedCustomFeeList(fees=[custom_fee._to_topic_fee_proto() for custom_fee in self.custom_fees]) if self.custom_fees is not None else None ) @@ -269,13 +263,9 @@ def _build_proto_body(self) -> consensus_update_topic_pb2.ConsensusUpdateTopicTr adminKey=self.admin_key._to_proto() if self.admin_key else None, submitKey=self.submit_key._to_proto() if self.submit_key else None, autoRenewPeriod=( - duration_pb2.Duration(seconds=self.auto_renew_period.seconds) - if self.auto_renew_period else None - ), - autoRenewAccount=( - self.auto_renew_account._to_proto() - if self.auto_renew_account else None + duration_pb2.Duration(seconds=self.auto_renew_period.seconds) if self.auto_renew_period else None ), + autoRenewAccount=(self.auto_renew_account._to_proto() if self.auto_renew_account else None), expirationTime=self.expiration_time._to_protobuf() if self.expiration_time else None, memo=_wrappers_pb2.StringValue(value=self.memo) if self.memo is not None else None, custom_fees=custom_fees, @@ -310,12 +300,11 @@ def build_scheduled_body(self) -> SchedulableTransactionBody: def _get_method(self, channel: _Channel) -> _Method: """ Returns the method for executing the topic update transaction. + Args: channel (_Channel): The channel to use for the transaction. + Returns: _Method: The method to execute the transaction. """ - return _Method( - transaction_func=channel.topic.updateTopic, - query_func=None - ) + return _Method(transaction_func=channel.topic.updateTopic, query_func=None) diff --git a/src/hiero_sdk_python/contract/contract_bytecode_query.py b/src/hiero_sdk_python/contract/contract_bytecode_query.py index 6d8056fce..7fc112933 100644 --- a/src/hiero_sdk_python/contract/contract_bytecode_query.py +++ b/src/hiero_sdk_python/contract/contract_bytecode_query.py @@ -1,9 +1,8 @@ -""" -Query to get the bytecode of a contract on the network. -""" +"""Query to get the bytecode of a contract on the network.""" + +from __future__ import annotations import traceback -from typing import Optional, Union from hiero_sdk_python.channels import _Channel from hiero_sdk_python.client.client import Client @@ -28,24 +27,22 @@ class ContractBytecodeQuery(Query): of a contract on the network. """ - def __init__(self, contract_id: Optional[ContractId] = None) -> None: + def __init__(self, contract_id: ContractId | None = None) -> None: """ Initializes a new ContractBytecodeQuery instance with an optional contract_id. Args: - contract_id (Optional[ContractId]): The ID of the contract to query. + contract_id (ContractId, optional): The ID of the contract to query. """ super().__init__() - self.contract_id: Optional[ContractId] = contract_id + self.contract_id: ContractId | None = contract_id - def set_contract_id( - self, contract_id: Optional[ContractId] - ) -> "ContractBytecodeQuery": + def set_contract_id(self, contract_id: ContractId | None) -> ContractBytecodeQuery: """ Sets the ID of the contract to query. Args: - contract_id (Optional[ContractId]): The ID of the contract. + contract_id (ContractId): The ID of the contract. Returns: ContractBytecodeQuery: Returns self for method chaining. @@ -73,11 +70,9 @@ def _make_request(self) -> query_pb2.Query: query_header = self._make_request_header() - contract_bytecode_query = ( - contract_get_bytecode_pb2.ContractGetBytecodeQuery( - header=query_header, - contractID=self.contract_id._to_proto(), - ) + contract_bytecode_query = contract_get_bytecode_pb2.ContractGetBytecodeQuery( + header=query_header, + contractID=self.contract_id._to_proto(), ) query = query_pb2.Query() @@ -102,11 +97,9 @@ def _get_method(self, channel: _Channel) -> _Method: Returns: _Method: The method wrapper containing the query function """ - return _Method( - transaction_func=None, query_func=channel.smart_contract.ContractGetBytecode - ) + return _Method(transaction_func=None, query_func=channel.smart_contract.ContractGetBytecode) - def execute(self, client: Client, timeout: Optional[Union[int, float]] = None) -> bytes: + def execute(self, client: Client, timeout: int | float | None = None) -> bytes: """ Executes the contract bytecode query. @@ -118,7 +111,7 @@ def execute(self, client: Client, timeout: Optional[Union[int, float]] = None) - Args: client (Client): The client instance to use for execution - timeout (Optional[Union[int, float]]): The total execution timeout (in seconds) for this execution. + timeout (int | float, optional ): The total execution timeout (in seconds) for this execution. Returns: bytes: The bytecode of the contract from the network @@ -133,9 +126,7 @@ def execute(self, client: Client, timeout: Optional[Union[int, float]] = None) - return response.contractGetBytecodeResponse.bytecode - def _get_query_response( - self, response: response_pb2.Response - ) -> ContractGetBytecodeResponse: + def _get_query_response(self, response: response_pb2.Response) -> ContractGetBytecodeResponse: """ Extracts the contract bytecode response from the full response. diff --git a/src/hiero_sdk_python/contract/contract_call_query.py b/src/hiero_sdk_python/contract/contract_call_query.py index 11d4a63fd..19ad64b9f 100644 --- a/src/hiero_sdk_python/contract/contract_call_query.py +++ b/src/hiero_sdk_python/contract/contract_call_query.py @@ -1,11 +1,10 @@ # pylint: disable=too-many-positional-arguments # pylint: disable=too-many-arguments -""" -Query to call a contract on the network. -""" +"""Query to call a contract on the network.""" + +from __future__ import annotations import traceback -from typing import Optional, Union from hiero_sdk_python.account.account_id import AccountId from hiero_sdk_python.channels import _Channel @@ -33,69 +32,67 @@ class ContractCallQuery(Query): def __init__( self, - contract_id: Optional[ContractId] = None, - gas: Optional[int] = None, - max_result_size: Optional[int] = None, - function_parameters: Optional[bytes] = None, - sender: Optional[AccountId] = None, + contract_id: ContractId | None = None, + gas: int | None = None, + max_result_size: int | None = None, + function_parameters: bytes | None = None, + sender: AccountId | None = None, ) -> None: """ Initializes a new ContractCallQuery instance with an optional contract_id. Args: - contract_id (Optional[ContractId]): The ID of the contract to call. - gas (Optional[int]): The gas to use for the contract call. - max_result_size (Optional[int]): The maximum size of the result to return. - function_parameters (Optional[bytes]): The parameters to pass to the contract function. - sender (Optional[AccountId]): The account to use for the contract call. + contract_id (ContractId, optional): The ID of the contract to call. + gas (int, optional): The gas to use for the contract call. + max_result_size (int, optional): The maximum size of the result to return. + function_parameters (bytes, optional): The parameters to pass to the contract function. + sender (AccountId, optional): The account to use for the contract call. """ super().__init__() - self.contract_id: Optional[ContractId] = contract_id - self.gas: Optional[int] = gas - self.max_result_size: Optional[int] = max_result_size - self.function_parameters: Optional[bytes] = function_parameters - self.sender: Optional[AccountId] = sender + self.contract_id: ContractId | None = contract_id + self.gas: int | None = gas + self.max_result_size: int | None = max_result_size + self.function_parameters: bytes | None = function_parameters + self.sender: AccountId | None = sender - def set_contract_id(self, contract_id: Optional[ContractId]) -> "ContractCallQuery": + def set_contract_id(self, contract_id: ContractId | None) -> ContractCallQuery: """ Sets the ID of the contract to call. Args: - contract_id (Optional[ContractId]): The ID of the contract to call. + contract_id (ContractId | None): The ID of the contract to call. """ self.contract_id = contract_id return self - def set_gas(self, gas: Optional[int]) -> "ContractCallQuery": + def set_gas(self, gas: int | None) -> ContractCallQuery: """ Sets the gas to use for the contract call. Args: - gas (Optional[int]): The gas to use for the contract call. + gas (int | None): The gas to use for the contract call. """ self.gas = gas return self - def set_max_result_size( - self, max_result_size: Optional[int] - ) -> "ContractCallQuery": + def set_max_result_size(self, max_result_size: int | None) -> ContractCallQuery: """ Sets the maximum size of the result to return. Args: - max_result_size (Optional[int]): The maximum size of the result to return. + max_result_size (int | None): The maximum size of the result to return. """ self.max_result_size = max_result_size return self def set_function_parameters( - self, function_parameters: Optional[ContractFunctionParameters | bytes] - ) -> "ContractCallQuery": + self, function_parameters: ContractFunctionParameters | bytes | None + ) -> ContractCallQuery: """ Sets the parameters to pass to the contract function. Args: - function_parameters (Optional[ContractFunctionParameters | bytes]): The parameters to + function_parameters (ContractFunctionParameters | bytes): The parameters to pass to the contract function. """ if isinstance(function_parameters, ContractFunctionParameters): @@ -104,15 +101,13 @@ def set_function_parameters( self.function_parameters = function_parameters return self - def set_function( - self, name: str, params: Optional[ContractFunctionParameters] = None - ) -> "ContractCallQuery": + def set_function(self, name: str, params: ContractFunctionParameters | None = None) -> ContractCallQuery: """ Sets the contract function to call and the parameters to pass to it. Args: name (str): The name of the contract function to call. - params (Optional[ContractFunctionParameters]): The parameters to pass to the function. + params (ContractFunctionParameters | None): The parameters to pass to the function. If not provided, the function is called with no parameters. """ if params is None: @@ -123,12 +118,12 @@ def set_function( self.function_parameters = params.to_bytes() return self - def set_sender(self, sender: Optional[AccountId]) -> "ContractCallQuery": + def set_sender(self, sender: AccountId | None) -> ContractCallQuery: """ Sets the account to use for the contract call. Args: - sender (Optional[AccountId]): The account to use for the contract call. + sender (AccountId): The account to use for the contract call. """ self.sender = sender return self @@ -187,7 +182,7 @@ def _get_method(self, channel: _Channel) -> _Method: query_func=channel.smart_contract.contractCallLocalMethod, ) - def execute(self, client: Client, timeout: Optional[Union[int, float]] = None) -> ContractFunctionResult: + def execute(self, client: Client, timeout: int | float | None = None) -> ContractFunctionResult: """ Executes the contract call query. @@ -199,7 +194,7 @@ def execute(self, client: Client, timeout: Optional[Union[int, float]] = None) - Args: client (Client): The client instance to use for execution - timeout (Optional[Union[int, float]]): The total execution timeout (in seconds) for this execution. + timeout (int | float, optional): The total execution timeout (in seconds) for this execution. Returns: ContractFunctionResult: The result of the contract call @@ -212,13 +207,9 @@ def execute(self, client: Client, timeout: Optional[Union[int, float]] = None) - self._before_execute(client) response = self._execute(client, timeout) - return ContractFunctionResult._from_proto( - response.contractCallLocal.functionResult - ) + return ContractFunctionResult._from_proto(response.contractCallLocal.functionResult) - def _get_query_response( - self, response: response_pb2.Response - ) -> contract_call_local_pb2.ContractCallLocalResponse: + def _get_query_response(self, response: response_pb2.Response) -> contract_call_local_pb2.ContractCallLocalResponse: """ Extracts the contract call response from the full response. diff --git a/src/hiero_sdk_python/contract/contract_create_transaction.py b/src/hiero_sdk_python/contract/contract_create_transaction.py index 2bc7715a6..795c64a70 100644 --- a/src/hiero_sdk_python/contract/contract_create_transaction.py +++ b/src/hiero_sdk_python/contract/contract_create_transaction.py @@ -1,10 +1,9 @@ # pylint: disable=too-many-instance-attributes -""" -ContractCreateTransaction class. -""" +"""ContractCreateTransaction class.""" + +from __future__ import annotations from dataclasses import dataclass -from typing import Optional from hiero_sdk_python.account.account_id import AccountId from hiero_sdk_python.channels import _Channel @@ -24,6 +23,7 @@ from hiero_sdk_python.hbar import Hbar from hiero_sdk_python.transaction.transaction import Transaction + DEFAULT_AUTO_RENEW_PERIOD = 90 * 24 * 60 * 60 # 90 days in seconds @@ -33,41 +33,41 @@ class ContractCreateParams: Represents contract creation parameters. Attributes: - bytecode_file_id (Optional[FileId]): The FileId of the file containing + bytecode_file_id (FileId, optional): The FileId of the file containing the contract bytecode. - proxy_account_id (Optional[AccountId]): The AccountId of the proxy account. - admin_key (Optional[PublicKey]): The admin key for the contract. - gas (Optional[int]): The gas limit for contract creation. - initial_balance (Optional[int]): The initial balance for the contract + proxy_account_id (AccountId, optional): The AccountId of the proxy account. + admin_key (PublicKey, optional): The admin key for the contract. + gas (int, optional): The gas limit for contract creation. + initial_balance (int, optional): The initial balance for the contract in tinybars. auto_renew_period (Duration): The auto-renewal period for the contract. - parameters (Optional[bytes]): ABI-encoded constructor parameters to be + parameters (bytes, optional): ABI-encoded constructor parameters to be passed to the smart contract upon creation. - contract_memo (Optional[str]): The memo for the contract. - bytecode (Optional[bytes]): The bytecode for the contract. - auto_renew_account_id (Optional[AccountId]): The AccountId that will pay + contract_memo (str, optional): The memo for the contract. + bytecode (bytes, optional): The bytecode for the contract. + auto_renew_account_id (AccountId, optional): The AccountId that will pay for auto-renewal. - max_automatic_token_associations (Optional[int]): Maximum number of + max_automatic_token_associations (int, optional): Maximum number of automatic token associations. - staked_account_id (Optional[AccountId]): The AccountId to stake to. - staked_node_id (Optional[int]): The node ID to stake to. - decline_reward (Optional[bool]): Whether to decline staking rewards. + staked_account_id (AccountId, optional): The AccountId to stake to. + staked_node_id (int, optional): The node ID to stake to. + decline_reward (bool, optional): Whether to decline staking rewards. """ - bytecode_file_id: Optional[FileId] = None - proxy_account_id: Optional[AccountId] = None - admin_key: Optional[PublicKey] = None - gas: Optional[int] = None - initial_balance: Optional[int] = None + bytecode_file_id: FileId | None = None + proxy_account_id: AccountId | None = None + admin_key: PublicKey | None = None + gas: int | None = None + initial_balance: int | None = None auto_renew_period: Duration = Duration(DEFAULT_AUTO_RENEW_PERIOD) - parameters: Optional[bytes] = None - contract_memo: Optional[str] = None - bytecode: Optional[bytes] = None - auto_renew_account_id: Optional[AccountId] = None - max_automatic_token_associations: Optional[int] = None - staked_account_id: Optional[AccountId] = None - staked_node_id: Optional[int] = None - decline_reward: Optional[bool] = None + parameters: bytes | None = None + contract_memo: str | None = None + bytecode: bytes | None = None + auto_renew_account_id: AccountId | None = None + max_automatic_token_associations: int | None = None + staked_account_id: AccountId | None = None + staked_node_id: int | None = None + decline_reward: bool | None = None class ContractCreateTransaction(Transaction): @@ -83,7 +83,7 @@ class ContractCreateTransaction(Transaction): contract creation. """ - def __init__(self, contract_params: Optional[ContractCreateParams] = None): + def __init__(self, contract_params: ContractCreateParams | None = None): """ Initializes a new ContractCreateTransaction instance. @@ -94,33 +94,29 @@ def __init__(self, contract_params: Optional[ContractCreateParams] = None): super().__init__() params = contract_params or ContractCreateParams() - self.bytecode_file_id: Optional[FileId] = params.bytecode_file_id - self.proxy_account_id: Optional[AccountId] = params.proxy_account_id - self.admin_key: Optional[PublicKey] = params.admin_key - self.gas: Optional[int] = params.gas - self.initial_balance: Optional[int] = params.initial_balance + self.bytecode_file_id: FileId | None = params.bytecode_file_id + self.proxy_account_id: AccountId | None = params.proxy_account_id + self.admin_key: PublicKey | None = params.admin_key + self.gas: int | None = params.gas + self.initial_balance: int | None = params.initial_balance self.auto_renew_period: Duration = params.auto_renew_period - self.parameters: Optional[bytes] = params.parameters - self.contract_memo: Optional[str] = params.contract_memo - self.bytecode: Optional[bytes] = params.bytecode - self.auto_renew_account_id: Optional[AccountId] = params.auto_renew_account_id - self.max_automatic_token_associations: Optional[int] = ( - params.max_automatic_token_associations - ) - self.staked_account_id: Optional[AccountId] = params.staked_account_id - self.staked_node_id: Optional[int] = params.staked_node_id - self.decline_reward: Optional[bool] = params.decline_reward + self.parameters: bytes | None = params.parameters + self.contract_memo: str | None = params.contract_memo + self.bytecode: bytes | None = params.bytecode + self.auto_renew_account_id: AccountId | None = params.auto_renew_account_id + self.max_automatic_token_associations: int | None = params.max_automatic_token_associations + self.staked_account_id: AccountId | None = params.staked_account_id + self.staked_node_id: int | None = params.staked_node_id + self.decline_reward: bool | None = params.decline_reward self._default_transaction_fee = Hbar(20).to_tinybars() - def set_bytecode_file_id( - self, bytecode_file_id: Optional[FileId] - ) -> "ContractCreateTransaction": + def set_bytecode_file_id(self, bytecode_file_id: FileId | None) -> ContractCreateTransaction: """ Sets the FileID of the file containing the contract bytecode. Args: - bytecode_file_id (Optional[FileId]): The FileID of the + bytecode_file_id (FileId | None): The FileID of the bytecode file. Returns: @@ -130,7 +126,7 @@ def set_bytecode_file_id( self.bytecode_file_id = bytecode_file_id return self - def set_bytecode(self, code: Optional[bytes]) -> "ContractCreateTransaction": + def set_bytecode(self, code: bytes | None) -> ContractCreateTransaction: """ Sets the bytecode for the contract. @@ -138,7 +134,7 @@ def set_bytecode(self, code: Optional[bytes]) -> "ContractCreateTransaction": transaction, otherwise it should be stored in a file. Args: - code (Optional[bytes]): The contract bytecode. + code (bytes | None): The contract bytecode. Returns: ContractCreateTransaction: This transaction instance. @@ -148,14 +144,12 @@ def set_bytecode(self, code: Optional[bytes]) -> "ContractCreateTransaction": self.bytecode_file_id = None return self - def set_proxy_account_id( - self, proxy_account_id: Optional[AccountId] - ) -> "ContractCreateTransaction": + def set_proxy_account_id(self, proxy_account_id: AccountId | None) -> ContractCreateTransaction: """ Sets the proxy account ID for the contract. Args: - proxy_account_id (Optional[AccountId]): The proxy account ID. + proxy_account_id (AccountId | None): The proxy account ID. Returns: ContractCreateTransaction: This transaction instance. @@ -164,14 +158,12 @@ def set_proxy_account_id( self.proxy_account_id = proxy_account_id return self - def set_admin_key( - self, admin_key: Optional[PublicKey] - ) -> "ContractCreateTransaction": + def set_admin_key(self, admin_key: PublicKey | None) -> ContractCreateTransaction: """ Sets the admin key for the contract. Args: - admin_key (Optional[PublicKey]): The admin key. + admin_key (PublicKey | None): The admin key. Returns: ContractCreateTransaction: This transaction instance. @@ -180,12 +172,12 @@ def set_admin_key( self.admin_key = admin_key return self - def set_gas(self, gas: Optional[int]) -> "ContractCreateTransaction": + def set_gas(self, gas: int | None) -> ContractCreateTransaction: """ Sets the gas limit for contract creation. Args: - gas (Optional[int]): The gas limit. + gas (int | None): The gas limit. Returns: ContractCreateTransaction: This transaction instance. @@ -194,14 +186,12 @@ def set_gas(self, gas: Optional[int]) -> "ContractCreateTransaction": self.gas = gas return self - def set_initial_balance( - self, initial_balance: Optional[int] - ) -> "ContractCreateTransaction": + def set_initial_balance(self, initial_balance: int | None) -> ContractCreateTransaction: """ Sets the initial balance for the contract in tinybars. Args: - initial_balance (Optional[int]): The initial balance in tinybars. + initial_balance (int | None): The initial balance in tinybars. Returns: ContractCreateTransaction: This transaction instance. @@ -210,9 +200,7 @@ def set_initial_balance( self.initial_balance = initial_balance return self - def set_auto_renew_period( - self, auto_renew_period: Duration - ) -> "ContractCreateTransaction": + def set_auto_renew_period(self, auto_renew_period: Duration) -> ContractCreateTransaction: """ Sets the auto-renewal period for the contract. @@ -227,13 +215,13 @@ def set_auto_renew_period( return self def set_constructor_parameters( - self, parameters: Optional[ContractFunctionParameters | bytes] - ) -> "ContractCreateTransaction": + self, parameters: ContractFunctionParameters | bytes | None + ) -> ContractCreateTransaction: """ Sets the constructor parameters for the contract. Args: - parameters (Optional[ContractFunctionParameters | bytes]): The + parameters (ContractFunctionParameters | bytes | None): The constructor parameters. Returns: @@ -246,14 +234,12 @@ def set_constructor_parameters( self.parameters = parameters return self - def set_contract_memo( - self, contract_memo: Optional[str] - ) -> "ContractCreateTransaction": + def set_contract_memo(self, contract_memo: str | None) -> ContractCreateTransaction: """ Sets the contract_memo for the contract. Args: - contract_memo (Optional[str]): The contract_memo. + contract_memo (str | None): The contract_memo. Returns: ContractCreateTransaction: This transaction instance. @@ -262,14 +248,12 @@ def set_contract_memo( self.contract_memo = contract_memo return self - def set_auto_renew_account_id( - self, auto_renew_account_id: Optional[AccountId] - ) -> "ContractCreateTransaction": + def set_auto_renew_account_id(self, auto_renew_account_id: AccountId | None) -> ContractCreateTransaction: """ Sets the account ID that will pay for auto-renewal. Args: - auto_renew_account_id (Optional[AccountId]): The auto-renewal + auto_renew_account_id (AccountId | None): The auto-renewal account ID. Returns: @@ -280,13 +264,13 @@ def set_auto_renew_account_id( return self def set_max_automatic_token_associations( - self, max_automatic_token_associations: Optional[int] - ) -> "ContractCreateTransaction": + self, max_automatic_token_associations: int | None + ) -> ContractCreateTransaction: """ Sets the maximum number of automatic token associations. Args: - max_automatic_token_associations (Optional[int]): The maximum + max_automatic_token_associations (int | None): The maximum number of automatic token associations. Returns: @@ -296,14 +280,12 @@ def set_max_automatic_token_associations( self.max_automatic_token_associations = max_automatic_token_associations return self - def set_staked_account_id( - self, staked_account_id: Optional[AccountId] - ) -> "ContractCreateTransaction": + def set_staked_account_id(self, staked_account_id: AccountId | None) -> ContractCreateTransaction: """ Sets the account ID to stake to. Args: - staked_account_id (Optional[AccountId]): The staked account ID. + staked_account_id (AccountId | None): The staked account ID. Returns: ContractCreateTransaction: This transaction instance. @@ -312,14 +294,12 @@ def set_staked_account_id( self.staked_account_id = staked_account_id return self - def set_staked_node_id( - self, staked_node_id: Optional[int] - ) -> "ContractCreateTransaction": + def set_staked_node_id(self, staked_node_id: int | None) -> ContractCreateTransaction: """ Sets the node ID to stake to. Args: - staked_node_id (Optional[int]): The staked node ID. + staked_node_id (int | None): The staked node ID. Returns: ContractCreateTransaction: This transaction instance. @@ -328,14 +308,12 @@ def set_staked_node_id( self.staked_node_id = staked_node_id return self - def set_decline_reward( - self, decline_reward: Optional[bool] - ) -> "ContractCreateTransaction": + def set_decline_reward(self, decline_reward: bool | None) -> ContractCreateTransaction: """ Sets whether to decline staking rewards. Args: - decline_reward (Optional[bool]): Whether to decline staking + decline_reward (bool | None): Whether to decline staking rewards. Returns: @@ -346,9 +324,7 @@ def set_decline_reward( return self def _validate_parameters(self): - """ - Validates the parameters for the contract creation transaction. - """ + """Validates the parameters for the contract creation transaction.""" if self.bytecode_file_id is None and self.bytecode is None: raise ValueError("Either bytecode_file_id or bytecode must be provided") @@ -373,22 +349,12 @@ def _build_proto_body(self): constructorParameters=self.parameters, memo=self.contract_memo, max_automatic_token_associations=self.max_automatic_token_associations, - decline_reward=( - self.decline_reward if self.decline_reward is not None else False - ), - auto_renew_account_id=( - self.auto_renew_account_id._to_proto() - if self.auto_renew_account_id - else None - ), - staked_account_id=( - self.staked_account_id._to_proto() if self.staked_account_id else None - ), + decline_reward=(self.decline_reward if self.decline_reward is not None else False), + auto_renew_account_id=(self.auto_renew_account_id._to_proto() if self.auto_renew_account_id else None), + staked_account_id=(self.staked_account_id._to_proto() if self.staked_account_id else None), staked_node_id=self.staked_node_id, autoRenewPeriod=self.auto_renew_period._to_proto(), - proxyAccountID=( - self.proxy_account_id._to_proto() if self.proxy_account_id else None - ), + proxyAccountID=(self.proxy_account_id._to_proto() if self.proxy_account_id else None), adminKey=(self.admin_key._to_proto() if self.admin_key else None), fileID=self.bytecode_file_id._to_proto() if self.bytecode_file_id else None, initcode=self.bytecode, @@ -432,6 +398,4 @@ def _get_method(self, channel: _Channel) -> _Method: _Method: An object containing the transaction function to create contracts. """ - return _Method( - transaction_func=channel.smart_contract.createContract, query_func=None - ) + return _Method(transaction_func=channel.smart_contract.createContract, query_func=None) diff --git a/src/hiero_sdk_python/contract/contract_delete_transaction.py b/src/hiero_sdk_python/contract/contract_delete_transaction.py index da2e23771..2ae52ef8e 100644 --- a/src/hiero_sdk_python/contract/contract_delete_transaction.py +++ b/src/hiero_sdk_python/contract/contract_delete_transaction.py @@ -1,8 +1,6 @@ -""" -ContractDeleteTransaction class. -""" +"""ContractDeleteTransaction class.""" -from typing import Optional +from __future__ import annotations from hiero_sdk_python.account.account_id import AccountId from hiero_sdk_python.channels import _Channel @@ -25,49 +23,47 @@ class ContractDeleteTransaction(Transaction): from the network state. Args: - contract_id (Optional[ContractId]): The ID of the contract to delete. - transfer_contract_id (Optional[ContractId]): The contract ID to transfer + contract_id (ContractId, optional): The ID of the contract to delete. + transfer_contract_id (ContractId, optional): The contract ID to transfer remaining balance to. - transfer_account_id (Optional[AccountId]): The account ID to transfer + transfer_account_id (AccountId, optional): The account ID to transfer remaining balance to. - permanent_removal (Optional[bool]): Whether to permanently remove the + permanent_removal (bool, optional): Whether to permanently remove the contract from network state. """ def __init__( self, - contract_id: Optional[ContractId] = None, - transfer_contract_id: Optional[ContractId] = None, - transfer_account_id: Optional[AccountId] = None, - permanent_removal: Optional[bool] = None, + contract_id: ContractId | None = None, + transfer_contract_id: ContractId | None = None, + transfer_account_id: AccountId | None = None, + permanent_removal: bool | None = None, ): """ Initializes a new ContractDeleteTransaction instance. Args: - contract_id (Optional[ContractId]): The ID of the contract to delete. - transfer_contract_id (Optional[ContractId]): The contract ID to transfer + contract_id (ContractId, optional): The ID of the contract to delete. + transfer_contract_id (ContractId, optional): The contract ID to transfer remaining balance to. - transfer_account_id (Optional[AccountId]): The account ID to transfer + transfer_account_id (AccountId, optional): The account ID to transfer remaining balance to. - permanent_removal (Optional[bool]): Whether to permanently remove the + permanent_removal (bool, optional): Whether to permanently remove the contract from network state. """ super().__init__() - self.contract_id: Optional[ContractId] = contract_id - self.transfer_contract_id: Optional[ContractId] = transfer_contract_id - self.transfer_account_id: Optional[AccountId] = transfer_account_id - self.permanent_removal: Optional[bool] = permanent_removal + self.contract_id: ContractId | None = contract_id + self.transfer_contract_id: ContractId | None = transfer_contract_id + self.transfer_account_id: AccountId | None = transfer_account_id + self.permanent_removal: bool | None = permanent_removal self._default_transaction_fee = Hbar(2).to_tinybars() - def set_contract_id( - self, contract_id: Optional[ContractId] - ) -> "ContractDeleteTransaction": + def set_contract_id(self, contract_id: ContractId | None) -> ContractDeleteTransaction: """ Sets the ID of the contract to delete. Args: - contract_id (Optional[ContractId]): The ID of the contract to delete. + contract_id (ContractId | None): The ID of the contract to delete. Returns: ContractDeleteTransaction: This transaction instance. @@ -76,9 +72,7 @@ def set_contract_id( self.contract_id = contract_id return self - def set_transfer_contract_id( - self, transfer_contract_id: Optional[ContractId] - ) -> "ContractDeleteTransaction": + def set_transfer_contract_id(self, transfer_contract_id: ContractId | None) -> ContractDeleteTransaction: """ Sets the contract ID to transfer the remaining balance to. @@ -87,7 +81,7 @@ def set_transfer_contract_id( contract for the balance transfer. Args: - transfer_contract_id (Optional[ContractId]): The contract ID to transfer + transfer_contract_id (ContractId | None): The contract ID to transfer remaining balance to. Returns: @@ -97,9 +91,7 @@ def set_transfer_contract_id( self.transfer_contract_id = transfer_contract_id return self - def set_transfer_account_id( - self, transfer_account_id: Optional[AccountId] - ) -> "ContractDeleteTransaction": + def set_transfer_account_id(self, transfer_account_id: AccountId | None) -> ContractDeleteTransaction: """ Sets the account ID to transfer the remaining balance to. @@ -108,7 +100,7 @@ def set_transfer_account_id( account for the balance transfer. Args: - transfer_account_id (Optional[AccountId]): The account ID to transfer + transfer_account_id (AccountId | None): The account ID to transfer remaining balance to. Returns: @@ -118,9 +110,7 @@ def set_transfer_account_id( self.transfer_account_id = transfer_account_id return self - def set_permanent_removal( - self, permanent_removal: Optional[bool] - ) -> "ContractDeleteTransaction": + def set_permanent_removal(self, permanent_removal: bool | None) -> ContractDeleteTransaction: """ Sets whether to permanently remove the contract from network state. @@ -129,7 +119,7 @@ def set_permanent_removal( be recoverable depending on network configuration. Args: - permanent_removal (Optional[bool]): Whether to permanently remove the + permanent_removal (bool | None): Whether to permanently remove the contract from network state. Returns: @@ -154,16 +144,8 @@ def build_transaction_body(self): contract_delete_body = ContractDeleteTransactionBody( contractID=self.contract_id._to_proto(), - transferContractID=( - self.transfer_contract_id._to_proto() - if self.transfer_contract_id - else None - ), - transferAccountID=( - self.transfer_account_id._to_proto() - if self.transfer_account_id - else None - ), + transferContractID=(self.transfer_contract_id._to_proto() if self.transfer_contract_id else None), + transferAccountID=(self.transfer_account_id._to_proto() if self.transfer_account_id else None), permanent_removal=self.permanent_removal, ) @@ -182,6 +164,4 @@ def _get_method(self, channel: _Channel) -> _Method: _Method: An object containing the transaction function to delete contracts. """ - return _Method( - transaction_func=channel.smart_contract.deleteContract, query_func=None - ) + return _Method(transaction_func=channel.smart_contract.deleteContract, query_func=None) diff --git a/src/hiero_sdk_python/contract/contract_execute_transaction.py b/src/hiero_sdk_python/contract/contract_execute_transaction.py index 0b42fcfa9..14229fd89 100644 --- a/src/hiero_sdk_python/contract/contract_execute_transaction.py +++ b/src/hiero_sdk_python/contract/contract_execute_transaction.py @@ -1,8 +1,6 @@ -""" -ContractExecuteTransaction class. -""" +"""ContractExecuteTransaction class.""" -from typing import Optional +from __future__ import annotations from hiero_sdk_python.channels import _Channel from hiero_sdk_python.contract.contract_function_parameters import ( @@ -29,62 +27,56 @@ class ContractExecuteTransaction(Transaction): def __init__( self, - contract_id: Optional[ContractId] = None, - gas: Optional[int] = None, - amount: Optional[int | Hbar] = None, - function_parameters: Optional[bytes] = None, + contract_id: ContractId | None = None, + gas: int | None = None, + amount: int | Hbar | None = None, + function_parameters: bytes | None = None, ): """ Initializes a new ContractExecuteTransaction instance. Args: - contract_id (Optional[ContractId]): The ID of the contract to execute. - gas (Optional[int]): The gas to use for the contract execution. - amount (Optional[int | Hbar]): The amount of hbar to send with the call. + contract_id (ContractId, optional): The ID of the contract to execute. + gas (int, optional): The gas to use for the contract execution. + amount (int | Hbar, optional): The amount of hbar to send with the call. You may pass an integer (tinybars) or an Hbar object. The value is always stored internally as an integer (tinybars). - function_parameters (Optional[bytes]): The parameters to pass to the contract function. + function_parameters (bytes, optional): The parameters to pass to the contract function. """ super().__init__() - self.contract_id: Optional[ContractId] = contract_id - self.gas: Optional[int] = gas - self.amount: Optional[int] = ( - amount.to_tinybars() if isinstance(amount, Hbar) else amount - ) - self.function_parameters: Optional[bytes] = function_parameters + self.contract_id: ContractId | None = contract_id + self.gas: int | None = gas + self.amount: int | None = amount.to_tinybars() if isinstance(amount, Hbar) else amount + self.function_parameters: bytes | None = function_parameters - def set_contract_id( - self, contract_id: Optional[ContractId] - ) -> "ContractExecuteTransaction": + def set_contract_id(self, contract_id: ContractId | None) -> ContractExecuteTransaction: """ Sets the contract ID for the transaction. Args: - contract_id (Optional[ContractId]): The ID of the contract to execute. + contract_id (ContractId | None): The ID of the contract to execute. """ self._require_not_frozen() self.contract_id = contract_id return self - def set_gas(self, gas: Optional[int]) -> "ContractExecuteTransaction": + def set_gas(self, gas: int | None) -> ContractExecuteTransaction: """ Sets the gas for the transaction. Args: - gas (Optional[int]): The gas to use for the contract execution. + gas (int | None): The gas to use for the contract execution. """ self._require_not_frozen() self.gas = gas return self - def set_payable_amount( - self, amount: Optional[int | Hbar] - ) -> "ContractExecuteTransaction": + def set_payable_amount(self, amount: int | Hbar | None) -> ContractExecuteTransaction: """ Sets the amount of HBAR to send with the call. Args: - amount (Optional[int | Hbar]): The amount of HBAR to send with the call. + amount (int | Hbar | None): The amount of HBAR to send with the call. You may pass an integer (tinybars) or an Hbar object. The value is always stored internally as an integer (tinybars). """ @@ -93,13 +85,13 @@ def set_payable_amount( return self def set_function_parameters( - self, function_parameters: Optional[ContractFunctionParameters | bytes] - ) -> "ContractExecuteTransaction": + self, function_parameters: ContractFunctionParameters | bytes | None + ) -> ContractExecuteTransaction: """ Sets the parameters to pass to the contract function. Args: - function_parameters (Optional[ContractFunctionParameters | bytes]): The parameters to + function_parameters (ContractFunctionParameters | bytes | None): The parameters to pass to the contract function. """ self._require_not_frozen() @@ -109,15 +101,13 @@ def set_function_parameters( self.function_parameters = function_parameters return self - def set_function( - self, name: str, params: Optional[ContractFunctionParameters] = None - ) -> "ContractExecuteTransaction": + def set_function(self, name: str, params: ContractFunctionParameters | None = None) -> ContractExecuteTransaction: """ Sets the function to call and the parameters to pass to it. Args: name (str): The name of the function to call. - params (Optional[ContractFunctionParameters]): The parameters to pass to the function. + params (ContractFunctionParameters | None): The parameters to pass to the function. """ self._require_not_frozen() if params is None: diff --git a/src/hiero_sdk_python/contract/contract_function_parameters.py b/src/hiero_sdk_python/contract/contract_function_parameters.py index 9d794e1af..192e74a83 100644 --- a/src/hiero_sdk_python/contract/contract_function_parameters.py +++ b/src/hiero_sdk_python/contract/contract_function_parameters.py @@ -4,7 +4,9 @@ encoding. Parameters can be encoded into a bytes format suitable for smart contract function calls. """ -from typing import Any, List, Optional, Union +from __future__ import annotations + +from typing import Any import eth_abi from eth_utils import function_signature_to_4byte_selector @@ -18,7 +20,7 @@ class ContractFunctionParameters: smart contract function calls, using the eth-abi library to handle the encoding logic. """ - def __init__(self, function_name: Optional[str] = None): + def __init__(self, function_name: str | None = None): """ Initialize a new ContractFunctionParameters instance. @@ -26,10 +28,10 @@ def __init__(self, function_name: Optional[str] = None): function_name: Optional function name to use for the function selector """ self.function_name = function_name - self._types: List[str] = [] - self._values: List[Any] = [] + self._types: list[str] = [] + self._values: list[Any] = [] - def _add_param(self, type_name: str, value: Any) -> "ContractFunctionParameters": + def _add_param(self, type_name: str, value: Any) -> ContractFunctionParameters: """ Internal helper to add a parameter. @@ -45,7 +47,7 @@ def _add_param(self, type_name: str, value: Any) -> "ContractFunctionParameters" return self # Basic types - explicitly define method signatures for IDE support - def add_bool(self, value: bool) -> "ContractFunctionParameters": + def add_bool(self, value: bool) -> ContractFunctionParameters: """ Add a boolean parameter. @@ -57,7 +59,7 @@ def add_bool(self, value: bool) -> "ContractFunctionParameters": """ return self._add_param("bool", value) - def add_address(self, value: Union[str, bytes]) -> "ContractFunctionParameters": + def add_address(self, value: str | bytes) -> ContractFunctionParameters: """ Add an address parameter. @@ -69,7 +71,7 @@ def add_address(self, value: Union[str, bytes]) -> "ContractFunctionParameters": """ return self._add_param("address", value) - def add_string(self, value: str) -> "ContractFunctionParameters": + def add_string(self, value: str) -> ContractFunctionParameters: """ Add a string parameter. @@ -81,7 +83,7 @@ def add_string(self, value: str) -> "ContractFunctionParameters": """ return self._add_param("string", value) - def add_bytes(self, value: bytes) -> "ContractFunctionParameters": + def add_bytes(self, value: bytes) -> ContractFunctionParameters: """ Add a bytes parameter. @@ -93,7 +95,7 @@ def add_bytes(self, value: bytes) -> "ContractFunctionParameters": """ return self._add_param("bytes", value) - def add_bytes32(self, value: bytes) -> "ContractFunctionParameters": + def add_bytes32(self, value: bytes) -> ContractFunctionParameters: """ Add a bytes32 parameter. @@ -106,7 +108,7 @@ def add_bytes32(self, value: bytes) -> "ContractFunctionParameters": return self._add_param("bytes32", value) # Array type methods for basic types - def add_bool_array(self, value: List[bool]) -> "ContractFunctionParameters": + def add_bool_array(self, value: list[bool]) -> ContractFunctionParameters: """ Add a boolean array parameter. @@ -118,9 +120,7 @@ def add_bool_array(self, value: List[bool]) -> "ContractFunctionParameters": """ return self._add_param("bool[]", value) - def add_address_array( - self, value: List[Union[str, bytes]] - ) -> "ContractFunctionParameters": + def add_address_array(self, value: list[str | bytes]) -> ContractFunctionParameters: """ Add an address array parameter. @@ -132,7 +132,7 @@ def add_address_array( """ return self._add_param("address[]", value) - def add_string_array(self, value: List[str]) -> "ContractFunctionParameters": + def add_string_array(self, value: list[str]) -> ContractFunctionParameters: """ Add a string array parameter. @@ -144,7 +144,7 @@ def add_string_array(self, value: List[str]) -> "ContractFunctionParameters": """ return self._add_param("string[]", value) - def add_bytes_array(self, value: List[bytes]) -> "ContractFunctionParameters": + def add_bytes_array(self, value: list[bytes]) -> ContractFunctionParameters: """ Add a bytes array parameter. @@ -156,7 +156,7 @@ def add_bytes_array(self, value: List[bytes]) -> "ContractFunctionParameters": """ return self._add_param("bytes[]", value) - def add_bytes32_array(self, value: List[bytes]) -> "ContractFunctionParameters": + def add_bytes32_array(self, value: list[bytes]) -> ContractFunctionParameters: """ Add a bytes32 array parameter. @@ -216,7 +216,7 @@ def __bytes__(self) -> bytes: """Allow conversion to bytes using bytes() function.""" return self.to_bytes() - def clear(self) -> "ContractFunctionParameters": + def clear(self) -> ContractFunctionParameters: """ Clear all parameters. @@ -241,9 +241,7 @@ def _create_method(class_type, method_name, type_name, doc, is_array=False): is_array: Whether this is an array type method """ - def method( - self: Any, value: List[int] if is_array else int - ) -> "ContractFunctionParameters": + def method(self: Any, value: list[int] if is_array else int) -> ContractFunctionParameters: """ Dynamically created method to add an integer parameter or array of integers. Takes either a single integer value or a list of integers depending on is_array flag. @@ -284,9 +282,7 @@ def _generate_int_type_methods(size: int): Returns: This instance for method chaining """ - _create_method( - ContractFunctionParameters, f"add_uint{size}", f"uint{size}", uint_doc - ) + _create_method(ContractFunctionParameters, f"add_uint{size}", f"uint{size}", uint_doc) def _generate_int_array_methods(size: int): diff --git a/src/hiero_sdk_python/contract/contract_function_parameters.pyi b/src/hiero_sdk_python/contract/contract_function_parameters.pyi index 38c23169c..08326ef15 100644 --- a/src/hiero_sdk_python/contract/contract_function_parameters.pyi +++ b/src/hiero_sdk_python/contract/contract_function_parameters.pyi @@ -1,6 +1,6 @@ # pylint: disable=[too-many-lines] """ -ContractFunctionParameters Type Stub File +ContractFunctionParameters Type Stub File. This module provides comprehensive type hints for the ContractFunctionParameters class, which handles Ethereum ABI encoding for smart contract function calls. @@ -13,7 +13,9 @@ Key Features: - Method chaining for fluent API """ -from typing import Any, List, Optional, Union +from __future__ import annotations + +from typing import Any class ContractFunctionParameters: """ @@ -22,11 +24,11 @@ class ContractFunctionParameters: dynamic type checking and IDE support. """ - function_name: Optional[str] - _types: List[str] - _values: List[Any] + function_name: str | None + _types: list[str] + _values: list[Any] - def __init__(self, function_name: Optional[str] = None) -> None: + def __init__(self, function_name: str | None = None) -> None: """ Initialize a new ContractFunctionParameters instance. @@ -34,7 +36,7 @@ class ContractFunctionParameters: function_name: Optional function name to use for the function selector """ - def _add_param(self, type_name: str, value: Any) -> "ContractFunctionParameters": + def _add_param(self, type_name: str, value: Any) -> ContractFunctionParameters: """ Internal helper to add a parameter. @@ -46,7 +48,7 @@ class ContractFunctionParameters: This instance for method chaining """ # Explicitly defined methods - def add_bool(self, value: bool) -> "ContractFunctionParameters": + def add_bool(self, value: bool) -> ContractFunctionParameters: """ Add a boolean parameter. @@ -57,7 +59,7 @@ class ContractFunctionParameters: This instance for method chaining """ - def add_address(self, value: Union[str, bytes]) -> "ContractFunctionParameters": + def add_address(self, value: str | bytes) -> ContractFunctionParameters: """ Add an address parameter. @@ -68,7 +70,7 @@ class ContractFunctionParameters: This instance for method chaining """ - def add_string(self, value: str) -> "ContractFunctionParameters": + def add_string(self, value: str) -> ContractFunctionParameters: """ Add a string parameter. @@ -79,7 +81,7 @@ class ContractFunctionParameters: This instance for method chaining """ - def add_bytes(self, value: bytes) -> "ContractFunctionParameters": + def add_bytes(self, value: bytes) -> ContractFunctionParameters: """ Add a bytes parameter. @@ -90,7 +92,7 @@ class ContractFunctionParameters: This instance for method chaining """ - def add_bytes32(self, value: bytes) -> "ContractFunctionParameters": + def add_bytes32(self, value: bytes) -> ContractFunctionParameters: """ Add a bytes32 parameter. @@ -105,7 +107,7 @@ class ContractFunctionParameters: TypeError: If value is not bytes type """ # Array methods for basic types - def add_bool_array(self, value: List[bool]) -> "ContractFunctionParameters": + def add_bool_array(self, value: list[bool]) -> ContractFunctionParameters: """ Add a boolean array parameter. @@ -116,9 +118,7 @@ class ContractFunctionParameters: This instance for method chaining """ - def add_address_array( - self, value: List[Union[str, bytes]] - ) -> "ContractFunctionParameters": + def add_address_array(self, value: list[str | bytes]) -> ContractFunctionParameters: """ Add an address array parameter. @@ -129,7 +129,7 @@ class ContractFunctionParameters: This instance for method chaining """ - def add_string_array(self, value: List[str]) -> "ContractFunctionParameters": + def add_string_array(self, value: list[str]) -> ContractFunctionParameters: """ Add a string array parameter. @@ -140,7 +140,7 @@ class ContractFunctionParameters: This instance for method chaining """ - def add_bytes_array(self, value: List[bytes]) -> "ContractFunctionParameters": + def add_bytes_array(self, value: list[bytes]) -> ContractFunctionParameters: """ Add a bytes array parameter. @@ -151,7 +151,7 @@ class ContractFunctionParameters: This instance for method chaining """ - def add_bytes32_array(self, value: List[bytes]) -> "ContractFunctionParameters": + def add_bytes32_array(self, value: list[bytes]) -> ContractFunctionParameters: """ Add a bytes32 array parameter. @@ -199,7 +199,7 @@ class ContractFunctionParameters: def __bytes__(self) -> bytes: """Allow conversion to bytes using bytes() function.""" - def clear(self) -> "ContractFunctionParameters": + def clear(self) -> ContractFunctionParameters: """ Clear all parameters. @@ -208,7 +208,7 @@ class ContractFunctionParameters: """ # Comprehensive integer type support (int8-256, uint8-256) # Each type has its own method for explicit type checking and IDE support - def add_int8(self, value: int) -> "ContractFunctionParameters": + def add_int8(self, value: int) -> ContractFunctionParameters: """ Add an int8 parameter. @@ -219,7 +219,7 @@ class ContractFunctionParameters: This instance for method chaining """ - def add_int16(self, value: int) -> "ContractFunctionParameters": + def add_int16(self, value: int) -> ContractFunctionParameters: """ Add an int16 parameter. @@ -230,7 +230,7 @@ class ContractFunctionParameters: This instance for method chaining """ - def add_int24(self, value: int) -> "ContractFunctionParameters": + def add_int24(self, value: int) -> ContractFunctionParameters: """ Add an int24 parameter. @@ -241,7 +241,7 @@ class ContractFunctionParameters: This instance for method chaining """ - def add_int32(self, value: int) -> "ContractFunctionParameters": + def add_int32(self, value: int) -> ContractFunctionParameters: """ Add an int32 parameter. @@ -252,7 +252,7 @@ class ContractFunctionParameters: This instance for method chaining """ - def add_int40(self, value: int) -> "ContractFunctionParameters": + def add_int40(self, value: int) -> ContractFunctionParameters: """ Add an int40 parameter. @@ -263,7 +263,7 @@ class ContractFunctionParameters: This instance for method chaining """ - def add_int48(self, value: int) -> "ContractFunctionParameters": + def add_int48(self, value: int) -> ContractFunctionParameters: """ Add an int48 parameter. @@ -274,7 +274,7 @@ class ContractFunctionParameters: This instance for method chaining """ - def add_int56(self, value: int) -> "ContractFunctionParameters": + def add_int56(self, value: int) -> ContractFunctionParameters: """ Add an int56 parameter. @@ -285,7 +285,7 @@ class ContractFunctionParameters: This instance for method chaining """ - def add_int64(self, value: int) -> "ContractFunctionParameters": + def add_int64(self, value: int) -> ContractFunctionParameters: """ Add an int64 parameter. @@ -296,7 +296,7 @@ class ContractFunctionParameters: This instance for method chaining """ - def add_int72(self, value: int) -> "ContractFunctionParameters": + def add_int72(self, value: int) -> ContractFunctionParameters: """ Add an int72 parameter. @@ -307,7 +307,7 @@ class ContractFunctionParameters: This instance for method chaining """ - def add_int80(self, value: int) -> "ContractFunctionParameters": + def add_int80(self, value: int) -> ContractFunctionParameters: """ Add an int80 parameter. @@ -318,7 +318,7 @@ class ContractFunctionParameters: This instance for method chaining """ - def add_int88(self, value: int) -> "ContractFunctionParameters": + def add_int88(self, value: int) -> ContractFunctionParameters: """ Add an int88 parameter. @@ -329,7 +329,7 @@ class ContractFunctionParameters: This instance for method chaining """ - def add_int96(self, value: int) -> "ContractFunctionParameters": + def add_int96(self, value: int) -> ContractFunctionParameters: """ Add an int96 parameter. @@ -340,7 +340,7 @@ class ContractFunctionParameters: This instance for method chaining """ - def add_int104(self, value: int) -> "ContractFunctionParameters": + def add_int104(self, value: int) -> ContractFunctionParameters: """ Add an int104 parameter. @@ -351,7 +351,7 @@ class ContractFunctionParameters: This instance for method chaining """ - def add_int112(self, value: int) -> "ContractFunctionParameters": + def add_int112(self, value: int) -> ContractFunctionParameters: """ Add an int112 parameter. @@ -362,7 +362,7 @@ class ContractFunctionParameters: This instance for method chaining """ - def add_int120(self, value: int) -> "ContractFunctionParameters": + def add_int120(self, value: int) -> ContractFunctionParameters: """ Add an int120 parameter. @@ -373,7 +373,7 @@ class ContractFunctionParameters: This instance for method chaining """ - def add_int128(self, value: int) -> "ContractFunctionParameters": + def add_int128(self, value: int) -> ContractFunctionParameters: """ Add an int128 parameter. @@ -384,7 +384,7 @@ class ContractFunctionParameters: This instance for method chaining """ - def add_int136(self, value: int) -> "ContractFunctionParameters": + def add_int136(self, value: int) -> ContractFunctionParameters: """ Add an int136 parameter. @@ -395,7 +395,7 @@ class ContractFunctionParameters: This instance for method chaining """ - def add_int144(self, value: int) -> "ContractFunctionParameters": + def add_int144(self, value: int) -> ContractFunctionParameters: """ Add an int144 parameter. @@ -406,7 +406,7 @@ class ContractFunctionParameters: This instance for method chaining """ - def add_int152(self, value: int) -> "ContractFunctionParameters": + def add_int152(self, value: int) -> ContractFunctionParameters: """ Add an int152 parameter. @@ -417,7 +417,7 @@ class ContractFunctionParameters: This instance for method chaining """ - def add_int160(self, value: int) -> "ContractFunctionParameters": + def add_int160(self, value: int) -> ContractFunctionParameters: """ Add an int160 parameter. @@ -428,7 +428,7 @@ class ContractFunctionParameters: This instance for method chaining """ - def add_int168(self, value: int) -> "ContractFunctionParameters": + def add_int168(self, value: int) -> ContractFunctionParameters: """ Add an int168 parameter. @@ -439,7 +439,7 @@ class ContractFunctionParameters: This instance for method chaining """ - def add_int176(self, value: int) -> "ContractFunctionParameters": + def add_int176(self, value: int) -> ContractFunctionParameters: """ Add an int176 parameter. @@ -450,7 +450,7 @@ class ContractFunctionParameters: This instance for method chaining """ - def add_int184(self, value: int) -> "ContractFunctionParameters": + def add_int184(self, value: int) -> ContractFunctionParameters: """ Add an int184 parameter. @@ -461,7 +461,7 @@ class ContractFunctionParameters: This instance for method chaining """ - def add_int192(self, value: int) -> "ContractFunctionParameters": + def add_int192(self, value: int) -> ContractFunctionParameters: """ Add an int192 parameter. @@ -472,7 +472,7 @@ class ContractFunctionParameters: This instance for method chaining """ - def add_int200(self, value: int) -> "ContractFunctionParameters": + def add_int200(self, value: int) -> ContractFunctionParameters: """ Add an int200 parameter. @@ -483,7 +483,7 @@ class ContractFunctionParameters: This instance for method chaining """ - def add_int208(self, value: int) -> "ContractFunctionParameters": + def add_int208(self, value: int) -> ContractFunctionParameters: """ Add an int208 parameter. @@ -494,7 +494,7 @@ class ContractFunctionParameters: This instance for method chaining """ - def add_int216(self, value: int) -> "ContractFunctionParameters": + def add_int216(self, value: int) -> ContractFunctionParameters: """ Add an int216 parameter. @@ -505,7 +505,7 @@ class ContractFunctionParameters: This instance for method chaining """ - def add_int224(self, value: int) -> "ContractFunctionParameters": + def add_int224(self, value: int) -> ContractFunctionParameters: """ Add an int224 parameter. @@ -516,7 +516,7 @@ class ContractFunctionParameters: This instance for method chaining """ - def add_int232(self, value: int) -> "ContractFunctionParameters": + def add_int232(self, value: int) -> ContractFunctionParameters: """ Add an int232 parameter. @@ -527,7 +527,7 @@ class ContractFunctionParameters: This instance for method chaining """ - def add_int240(self, value: int) -> "ContractFunctionParameters": + def add_int240(self, value: int) -> ContractFunctionParameters: """ Add an int240 parameter. @@ -538,7 +538,7 @@ class ContractFunctionParameters: This instance for method chaining """ - def add_int248(self, value: int) -> "ContractFunctionParameters": + def add_int248(self, value: int) -> ContractFunctionParameters: """ Add an int248 parameter. @@ -549,7 +549,7 @@ class ContractFunctionParameters: This instance for method chaining """ - def add_int256(self, value: int) -> "ContractFunctionParameters": + def add_int256(self, value: int) -> ContractFunctionParameters: """ Add an int256 parameter. @@ -560,7 +560,7 @@ class ContractFunctionParameters: This instance for method chaining """ - def add_uint8(self, value: int) -> "ContractFunctionParameters": + def add_uint8(self, value: int) -> ContractFunctionParameters: """ Add a uint8 parameter. @@ -571,7 +571,7 @@ class ContractFunctionParameters: This instance for method chaining """ - def add_uint16(self, value: int) -> "ContractFunctionParameters": + def add_uint16(self, value: int) -> ContractFunctionParameters: """ Add a uint16 parameter. @@ -582,7 +582,7 @@ class ContractFunctionParameters: This instance for method chaining """ - def add_uint24(self, value: int) -> "ContractFunctionParameters": + def add_uint24(self, value: int) -> ContractFunctionParameters: """ Add a uint24 parameter. @@ -593,7 +593,7 @@ class ContractFunctionParameters: This instance for method chaining """ - def add_uint32(self, value: int) -> "ContractFunctionParameters": + def add_uint32(self, value: int) -> ContractFunctionParameters: """ Add a uint32 parameter. @@ -604,7 +604,7 @@ class ContractFunctionParameters: This instance for method chaining """ - def add_uint40(self, value: int) -> "ContractFunctionParameters": + def add_uint40(self, value: int) -> ContractFunctionParameters: """ Add a uint40 parameter. @@ -615,7 +615,7 @@ class ContractFunctionParameters: This instance for method chaining """ - def add_uint48(self, value: int) -> "ContractFunctionParameters": + def add_uint48(self, value: int) -> ContractFunctionParameters: """ Add a uint48 parameter. @@ -626,7 +626,7 @@ class ContractFunctionParameters: This instance for method chaining """ - def add_uint56(self, value: int) -> "ContractFunctionParameters": + def add_uint56(self, value: int) -> ContractFunctionParameters: """ Add a uint56 parameter. @@ -637,7 +637,7 @@ class ContractFunctionParameters: This instance for method chaining """ - def add_uint64(self, value: int) -> "ContractFunctionParameters": + def add_uint64(self, value: int) -> ContractFunctionParameters: """ Add a uint64 parameter. @@ -648,7 +648,7 @@ class ContractFunctionParameters: This instance for method chaining """ - def add_uint72(self, value: int) -> "ContractFunctionParameters": + def add_uint72(self, value: int) -> ContractFunctionParameters: """ Add a uint72 parameter. @@ -659,7 +659,7 @@ class ContractFunctionParameters: This instance for method chaining """ - def add_uint80(self, value: int) -> "ContractFunctionParameters": + def add_uint80(self, value: int) -> ContractFunctionParameters: """ Add a uint80 parameter. @@ -670,7 +670,7 @@ class ContractFunctionParameters: This instance for method chaining """ - def add_uint88(self, value: int) -> "ContractFunctionParameters": + def add_uint88(self, value: int) -> ContractFunctionParameters: """ Add a uint88 parameter. @@ -681,7 +681,7 @@ class ContractFunctionParameters: This instance for method chaining """ - def add_uint96(self, value: int) -> "ContractFunctionParameters": + def add_uint96(self, value: int) -> ContractFunctionParameters: """ Add a uint96 parameter. @@ -692,7 +692,7 @@ class ContractFunctionParameters: This instance for method chaining """ - def add_uint104(self, value: int) -> "ContractFunctionParameters": + def add_uint104(self, value: int) -> ContractFunctionParameters: """ Add a uint104 parameter. @@ -703,7 +703,7 @@ class ContractFunctionParameters: This instance for method chaining """ - def add_uint112(self, value: int) -> "ContractFunctionParameters": + def add_uint112(self, value: int) -> ContractFunctionParameters: """ Add a uint112 parameter. @@ -714,7 +714,7 @@ class ContractFunctionParameters: This instance for method chaining """ - def add_uint120(self, value: int) -> "ContractFunctionParameters": + def add_uint120(self, value: int) -> ContractFunctionParameters: """ Add a uint120 parameter. @@ -725,7 +725,7 @@ class ContractFunctionParameters: This instance for method chaining """ - def add_uint128(self, value: int) -> "ContractFunctionParameters": + def add_uint128(self, value: int) -> ContractFunctionParameters: """ Add a uint128 parameter. @@ -736,7 +736,7 @@ class ContractFunctionParameters: This instance for method chaining """ - def add_uint136(self, value: int) -> "ContractFunctionParameters": + def add_uint136(self, value: int) -> ContractFunctionParameters: """ Add a uint136 parameter. @@ -747,7 +747,7 @@ class ContractFunctionParameters: This instance for method chaining """ - def add_uint144(self, value: int) -> "ContractFunctionParameters": + def add_uint144(self, value: int) -> ContractFunctionParameters: """ Add a uint144 parameter. @@ -758,7 +758,7 @@ class ContractFunctionParameters: This instance for method chaining """ - def add_uint152(self, value: int) -> "ContractFunctionParameters": + def add_uint152(self, value: int) -> ContractFunctionParameters: """ Add a uint152 parameter. @@ -769,7 +769,7 @@ class ContractFunctionParameters: This instance for method chaining """ - def add_uint160(self, value: int) -> "ContractFunctionParameters": + def add_uint160(self, value: int) -> ContractFunctionParameters: """ Add a uint160 parameter. @@ -780,7 +780,7 @@ class ContractFunctionParameters: This instance for method chaining """ - def add_uint168(self, value: int) -> "ContractFunctionParameters": + def add_uint168(self, value: int) -> ContractFunctionParameters: """ Add a uint168 parameter. @@ -791,7 +791,7 @@ class ContractFunctionParameters: This instance for method chaining """ - def add_uint176(self, value: int) -> "ContractFunctionParameters": + def add_uint176(self, value: int) -> ContractFunctionParameters: """ Add a uint176 parameter. @@ -802,7 +802,7 @@ class ContractFunctionParameters: This instance for method chaining """ - def add_uint184(self, value: int) -> "ContractFunctionParameters": + def add_uint184(self, value: int) -> ContractFunctionParameters: """ Add a uint184 parameter. @@ -813,7 +813,7 @@ class ContractFunctionParameters: This instance for method chaining """ - def add_uint192(self, value: int) -> "ContractFunctionParameters": + def add_uint192(self, value: int) -> ContractFunctionParameters: """ Add a uint192 parameter. @@ -824,7 +824,7 @@ class ContractFunctionParameters: This instance for method chaining """ - def add_uint200(self, value: int) -> "ContractFunctionParameters": + def add_uint200(self, value: int) -> ContractFunctionParameters: """ Add a uint200 parameter. @@ -835,7 +835,7 @@ class ContractFunctionParameters: This instance for method chaining """ - def add_uint208(self, value: int) -> "ContractFunctionParameters": + def add_uint208(self, value: int) -> ContractFunctionParameters: """ Add a uint208 parameter. @@ -846,7 +846,7 @@ class ContractFunctionParameters: This instance for method chaining """ - def add_uint216(self, value: int) -> "ContractFunctionParameters": + def add_uint216(self, value: int) -> ContractFunctionParameters: """ Add a uint216 parameter. @@ -857,7 +857,7 @@ class ContractFunctionParameters: This instance for method chaining """ - def add_uint224(self, value: int) -> "ContractFunctionParameters": + def add_uint224(self, value: int) -> ContractFunctionParameters: """ Add a uint224 parameter. @@ -868,7 +868,7 @@ class ContractFunctionParameters: This instance for method chaining """ - def add_uint232(self, value: int) -> "ContractFunctionParameters": + def add_uint232(self, value: int) -> ContractFunctionParameters: """ Add a uint232 parameter. @@ -879,7 +879,7 @@ class ContractFunctionParameters: This instance for method chaining """ - def add_uint240(self, value: int) -> "ContractFunctionParameters": + def add_uint240(self, value: int) -> ContractFunctionParameters: """ Add a uint240 parameter. @@ -890,7 +890,7 @@ class ContractFunctionParameters: This instance for method chaining """ - def add_uint248(self, value: int) -> "ContractFunctionParameters": + def add_uint248(self, value: int) -> ContractFunctionParameters: """ Add a uint248 parameter. @@ -901,7 +901,7 @@ class ContractFunctionParameters: This instance for method chaining """ - def add_uint256(self, value: int) -> "ContractFunctionParameters": + def add_uint256(self, value: int) -> ContractFunctionParameters: """ Add a uint256 parameter. @@ -912,7 +912,7 @@ class ContractFunctionParameters: This instance for method chaining """ - def add_int8_array(self, value: List[int]) -> "ContractFunctionParameters": + def add_int8_array(self, value: list[int]) -> ContractFunctionParameters: """ Add an int8 array parameter. @@ -923,7 +923,7 @@ class ContractFunctionParameters: This instance for method chaining """ - def add_int16_array(self, value: List[int]) -> "ContractFunctionParameters": + def add_int16_array(self, value: list[int]) -> ContractFunctionParameters: """ Add an int16 array parameter. @@ -934,7 +934,7 @@ class ContractFunctionParameters: This instance for method chaining """ - def add_int24_array(self, value: List[int]) -> "ContractFunctionParameters": + def add_int24_array(self, value: list[int]) -> ContractFunctionParameters: """ Add an int24 array parameter. @@ -945,7 +945,7 @@ class ContractFunctionParameters: This instance for method chaining """ - def add_int32_array(self, value: List[int]) -> "ContractFunctionParameters": + def add_int32_array(self, value: list[int]) -> ContractFunctionParameters: """ Add an int32 array parameter. @@ -956,7 +956,7 @@ class ContractFunctionParameters: This instance for method chaining """ - def add_int40_array(self, value: List[int]) -> "ContractFunctionParameters": + def add_int40_array(self, value: list[int]) -> ContractFunctionParameters: """ Add an int40 array parameter. @@ -967,7 +967,7 @@ class ContractFunctionParameters: This instance for method chaining """ - def add_int48_array(self, value: List[int]) -> "ContractFunctionParameters": + def add_int48_array(self, value: list[int]) -> ContractFunctionParameters: """ Add an int48 array parameter. @@ -978,7 +978,7 @@ class ContractFunctionParameters: This instance for method chaining """ - def add_int56_array(self, value: List[int]) -> "ContractFunctionParameters": + def add_int56_array(self, value: list[int]) -> ContractFunctionParameters: """ Add an int56 array parameter. @@ -989,7 +989,7 @@ class ContractFunctionParameters: This instance for method chaining """ - def add_int64_array(self, value: List[int]) -> "ContractFunctionParameters": + def add_int64_array(self, value: list[int]) -> ContractFunctionParameters: """ Add an int64 array parameter. @@ -1000,7 +1000,7 @@ class ContractFunctionParameters: This instance for method chaining """ - def add_int72_array(self, value: List[int]) -> "ContractFunctionParameters": + def add_int72_array(self, value: list[int]) -> ContractFunctionParameters: """ Add an int72 array parameter. @@ -1011,7 +1011,7 @@ class ContractFunctionParameters: This instance for method chaining """ - def add_int80_array(self, value: List[int]) -> "ContractFunctionParameters": + def add_int80_array(self, value: list[int]) -> ContractFunctionParameters: """ Add an int80 array parameter. @@ -1022,7 +1022,7 @@ class ContractFunctionParameters: This instance for method chaining """ - def add_int88_array(self, value: List[int]) -> "ContractFunctionParameters": + def add_int88_array(self, value: list[int]) -> ContractFunctionParameters: """ Add an int88 array parameter. @@ -1033,7 +1033,7 @@ class ContractFunctionParameters: This instance for method chaining """ - def add_int96_array(self, value: List[int]) -> "ContractFunctionParameters": + def add_int96_array(self, value: list[int]) -> ContractFunctionParameters: """ Add an int96 array parameter. @@ -1044,7 +1044,7 @@ class ContractFunctionParameters: This instance for method chaining """ - def add_int104_array(self, value: List[int]) -> "ContractFunctionParameters": + def add_int104_array(self, value: list[int]) -> ContractFunctionParameters: """ Add an int104 array parameter. @@ -1055,7 +1055,7 @@ class ContractFunctionParameters: This instance for method chaining """ - def add_int112_array(self, value: List[int]) -> "ContractFunctionParameters": + def add_int112_array(self, value: list[int]) -> ContractFunctionParameters: """ Add an int112 array parameter. @@ -1066,7 +1066,7 @@ class ContractFunctionParameters: This instance for method chaining """ - def add_int120_array(self, value: List[int]) -> "ContractFunctionParameters": + def add_int120_array(self, value: list[int]) -> ContractFunctionParameters: """ Add an int120 array parameter. @@ -1077,7 +1077,7 @@ class ContractFunctionParameters: This instance for method chaining """ - def add_int128_array(self, value: List[int]) -> "ContractFunctionParameters": + def add_int128_array(self, value: list[int]) -> ContractFunctionParameters: """ Add an int128 array parameter. @@ -1088,7 +1088,7 @@ class ContractFunctionParameters: This instance for method chaining """ - def add_int136_array(self, value: List[int]) -> "ContractFunctionParameters": + def add_int136_array(self, value: list[int]) -> ContractFunctionParameters: """ Add an int136 array parameter. @@ -1099,7 +1099,7 @@ class ContractFunctionParameters: This instance for method chaining """ - def add_int144_array(self, value: List[int]) -> "ContractFunctionParameters": + def add_int144_array(self, value: list[int]) -> ContractFunctionParameters: """ Add an int144 array parameter. @@ -1110,7 +1110,7 @@ class ContractFunctionParameters: This instance for method chaining """ - def add_int152_array(self, value: List[int]) -> "ContractFunctionParameters": + def add_int152_array(self, value: list[int]) -> ContractFunctionParameters: """ Add an int152 array parameter. @@ -1121,7 +1121,7 @@ class ContractFunctionParameters: This instance for method chaining """ - def add_int160_array(self, value: List[int]) -> "ContractFunctionParameters": + def add_int160_array(self, value: list[int]) -> ContractFunctionParameters: """ Add an int160 array parameter. @@ -1132,7 +1132,7 @@ class ContractFunctionParameters: This instance for method chaining """ - def add_int168_array(self, value: List[int]) -> "ContractFunctionParameters": + def add_int168_array(self, value: list[int]) -> ContractFunctionParameters: """ Add an int168 array parameter. @@ -1143,7 +1143,7 @@ class ContractFunctionParameters: This instance for method chaining """ - def add_int176_array(self, value: List[int]) -> "ContractFunctionParameters": + def add_int176_array(self, value: list[int]) -> ContractFunctionParameters: """ Add an int176 array parameter. @@ -1154,7 +1154,7 @@ class ContractFunctionParameters: This instance for method chaining """ - def add_int184_array(self, value: List[int]) -> "ContractFunctionParameters": + def add_int184_array(self, value: list[int]) -> ContractFunctionParameters: """ Add an int184 array parameter. @@ -1165,7 +1165,7 @@ class ContractFunctionParameters: This instance for method chaining """ - def add_int192_array(self, value: List[int]) -> "ContractFunctionParameters": + def add_int192_array(self, value: list[int]) -> ContractFunctionParameters: """ Add an int192 array parameter. @@ -1176,7 +1176,7 @@ class ContractFunctionParameters: This instance for method chaining """ - def add_int200_array(self, value: List[int]) -> "ContractFunctionParameters": + def add_int200_array(self, value: list[int]) -> ContractFunctionParameters: """ Add an int200 array parameter. @@ -1187,7 +1187,7 @@ class ContractFunctionParameters: This instance for method chaining """ - def add_int208_array(self, value: List[int]) -> "ContractFunctionParameters": + def add_int208_array(self, value: list[int]) -> ContractFunctionParameters: """ Add an int208 array parameter. @@ -1198,7 +1198,7 @@ class ContractFunctionParameters: This instance for method chaining """ - def add_int216_array(self, value: List[int]) -> "ContractFunctionParameters": + def add_int216_array(self, value: list[int]) -> ContractFunctionParameters: """ Add an int216 array parameter. @@ -1209,7 +1209,7 @@ class ContractFunctionParameters: This instance for method chaining """ - def add_int224_array(self, value: List[int]) -> "ContractFunctionParameters": + def add_int224_array(self, value: list[int]) -> ContractFunctionParameters: """ Add an int224 array parameter. @@ -1220,7 +1220,7 @@ class ContractFunctionParameters: This instance for method chaining """ - def add_int232_array(self, value: List[int]) -> "ContractFunctionParameters": + def add_int232_array(self, value: list[int]) -> ContractFunctionParameters: """ Add an int232 array parameter. @@ -1231,7 +1231,7 @@ class ContractFunctionParameters: This instance for method chaining """ - def add_int240_array(self, value: List[int]) -> "ContractFunctionParameters": + def add_int240_array(self, value: list[int]) -> ContractFunctionParameters: """ Add an int240 array parameter. @@ -1242,7 +1242,7 @@ class ContractFunctionParameters: This instance for method chaining """ - def add_int248_array(self, value: List[int]) -> "ContractFunctionParameters": + def add_int248_array(self, value: list[int]) -> ContractFunctionParameters: """ Add an int248 array parameter. @@ -1253,7 +1253,7 @@ class ContractFunctionParameters: This instance for method chaining """ - def add_int256_array(self, value: List[int]) -> "ContractFunctionParameters": + def add_int256_array(self, value: list[int]) -> ContractFunctionParameters: """ Add an int256 array parameter. @@ -1264,7 +1264,7 @@ class ContractFunctionParameters: This instance for method chaining """ - def add_uint8_array(self, value: List[int]) -> "ContractFunctionParameters": + def add_uint8_array(self, value: list[int]) -> ContractFunctionParameters: """ Add a uint8 array parameter. @@ -1275,7 +1275,7 @@ class ContractFunctionParameters: This instance for method chaining """ - def add_uint16_array(self, value: List[int]) -> "ContractFunctionParameters": + def add_uint16_array(self, value: list[int]) -> ContractFunctionParameters: """ Add a uint16 array parameter. @@ -1286,7 +1286,7 @@ class ContractFunctionParameters: This instance for method chaining """ - def add_uint24_array(self, value: List[int]) -> "ContractFunctionParameters": + def add_uint24_array(self, value: list[int]) -> ContractFunctionParameters: """ Add a uint24 array parameter. @@ -1297,7 +1297,7 @@ class ContractFunctionParameters: This instance for method chaining """ - def add_uint32_array(self, value: List[int]) -> "ContractFunctionParameters": + def add_uint32_array(self, value: list[int]) -> ContractFunctionParameters: """ Add a uint32 array parameter. @@ -1308,7 +1308,7 @@ class ContractFunctionParameters: This instance for method chaining """ - def add_uint40_array(self, value: List[int]) -> "ContractFunctionParameters": + def add_uint40_array(self, value: list[int]) -> ContractFunctionParameters: """ Add a uint40 array parameter. @@ -1319,7 +1319,7 @@ class ContractFunctionParameters: This instance for method chaining """ - def add_uint48_array(self, value: List[int]) -> "ContractFunctionParameters": + def add_uint48_array(self, value: list[int]) -> ContractFunctionParameters: """ Add a uint48 array parameter. @@ -1330,7 +1330,7 @@ class ContractFunctionParameters: This instance for method chaining """ - def add_uint56_array(self, value: List[int]) -> "ContractFunctionParameters": + def add_uint56_array(self, value: list[int]) -> ContractFunctionParameters: """ Add a uint56 array parameter. @@ -1341,7 +1341,7 @@ class ContractFunctionParameters: This instance for method chaining """ - def add_uint64_array(self, value: List[int]) -> "ContractFunctionParameters": + def add_uint64_array(self, value: list[int]) -> ContractFunctionParameters: """ Add a uint64 array parameter. @@ -1352,7 +1352,7 @@ class ContractFunctionParameters: This instance for method chaining """ - def add_uint72_array(self, value: List[int]) -> "ContractFunctionParameters": + def add_uint72_array(self, value: list[int]) -> ContractFunctionParameters: """ Add a uint72 array parameter. @@ -1363,7 +1363,7 @@ class ContractFunctionParameters: This instance for method chaining """ - def add_uint80_array(self, value: List[int]) -> "ContractFunctionParameters": + def add_uint80_array(self, value: list[int]) -> ContractFunctionParameters: """ Add a uint80 array parameter. @@ -1374,7 +1374,7 @@ class ContractFunctionParameters: This instance for method chaining """ - def add_uint88_array(self, value: List[int]) -> "ContractFunctionParameters": + def add_uint88_array(self, value: list[int]) -> ContractFunctionParameters: """ Add a uint88 array parameter. @@ -1385,7 +1385,7 @@ class ContractFunctionParameters: This instance for method chaining """ - def add_uint96_array(self, value: List[int]) -> "ContractFunctionParameters": + def add_uint96_array(self, value: list[int]) -> ContractFunctionParameters: """ Add a uint96 array parameter. @@ -1396,7 +1396,7 @@ class ContractFunctionParameters: This instance for method chaining """ - def add_uint104_array(self, value: List[int]) -> "ContractFunctionParameters": + def add_uint104_array(self, value: list[int]) -> ContractFunctionParameters: """ Add a uint104 array parameter. @@ -1407,7 +1407,7 @@ class ContractFunctionParameters: This instance for method chaining """ - def add_uint112_array(self, value: List[int]) -> "ContractFunctionParameters": + def add_uint112_array(self, value: list[int]) -> ContractFunctionParameters: """ Add a uint112 array parameter. @@ -1418,7 +1418,7 @@ class ContractFunctionParameters: This instance for method chaining """ - def add_uint120_array(self, value: List[int]) -> "ContractFunctionParameters": + def add_uint120_array(self, value: list[int]) -> ContractFunctionParameters: """ Add a uint120 array parameter. @@ -1429,7 +1429,7 @@ class ContractFunctionParameters: This instance for method chaining """ - def add_uint128_array(self, value: List[int]) -> "ContractFunctionParameters": + def add_uint128_array(self, value: list[int]) -> ContractFunctionParameters: """ Add a uint128 array parameter. @@ -1440,7 +1440,7 @@ class ContractFunctionParameters: This instance for method chaining """ - def add_uint136_array(self, value: List[int]) -> "ContractFunctionParameters": + def add_uint136_array(self, value: list[int]) -> ContractFunctionParameters: """ Add a uint136 array parameter. @@ -1451,7 +1451,7 @@ class ContractFunctionParameters: This instance for method chaining """ - def add_uint144_array(self, value: List[int]) -> "ContractFunctionParameters": + def add_uint144_array(self, value: list[int]) -> ContractFunctionParameters: """ Add a uint144 array parameter. @@ -1462,7 +1462,7 @@ class ContractFunctionParameters: This instance for method chaining """ - def add_uint152_array(self, value: List[int]) -> "ContractFunctionParameters": + def add_uint152_array(self, value: list[int]) -> ContractFunctionParameters: """ Add a uint152 array parameter. @@ -1473,7 +1473,7 @@ class ContractFunctionParameters: This instance for method chaining """ - def add_uint160_array(self, value: List[int]) -> "ContractFunctionParameters": + def add_uint160_array(self, value: list[int]) -> ContractFunctionParameters: """ Add a uint160 array parameter. @@ -1484,7 +1484,7 @@ class ContractFunctionParameters: This instance for method chaining """ - def add_uint168_array(self, value: List[int]) -> "ContractFunctionParameters": + def add_uint168_array(self, value: list[int]) -> ContractFunctionParameters: """ Add a uint168 array parameter. @@ -1495,7 +1495,7 @@ class ContractFunctionParameters: This instance for method chaining """ - def add_uint176_array(self, value: List[int]) -> "ContractFunctionParameters": + def add_uint176_array(self, value: list[int]) -> ContractFunctionParameters: """ Add a uint176 array parameter. @@ -1506,7 +1506,7 @@ class ContractFunctionParameters: This instance for method chaining """ - def add_uint184_array(self, value: List[int]) -> "ContractFunctionParameters": + def add_uint184_array(self, value: list[int]) -> ContractFunctionParameters: """ Add a uint184 array parameter. @@ -1517,7 +1517,7 @@ class ContractFunctionParameters: This instance for method chaining """ - def add_uint192_array(self, value: List[int]) -> "ContractFunctionParameters": + def add_uint192_array(self, value: list[int]) -> ContractFunctionParameters: """ Add a uint192 array parameter. @@ -1528,7 +1528,7 @@ class ContractFunctionParameters: This instance for method chaining """ - def add_uint200_array(self, value: List[int]) -> "ContractFunctionParameters": + def add_uint200_array(self, value: list[int]) -> ContractFunctionParameters: """ Add a uint200 array parameter. @@ -1539,7 +1539,7 @@ class ContractFunctionParameters: This instance for method chaining """ - def add_uint208_array(self, value: List[int]) -> "ContractFunctionParameters": + def add_uint208_array(self, value: list[int]) -> ContractFunctionParameters: """ Add a uint208 array parameter. @@ -1550,7 +1550,7 @@ class ContractFunctionParameters: This instance for method chaining """ - def add_uint216_array(self, value: List[int]) -> "ContractFunctionParameters": + def add_uint216_array(self, value: list[int]) -> ContractFunctionParameters: """ Add a uint216 array parameter. @@ -1561,7 +1561,7 @@ class ContractFunctionParameters: This instance for method chaining """ - def add_uint224_array(self, value: List[int]) -> "ContractFunctionParameters": + def add_uint224_array(self, value: list[int]) -> ContractFunctionParameters: """ Add a uint224 array parameter. @@ -1572,7 +1572,7 @@ class ContractFunctionParameters: This instance for method chaining """ - def add_uint232_array(self, value: List[int]) -> "ContractFunctionParameters": + def add_uint232_array(self, value: list[int]) -> ContractFunctionParameters: """ Add a uint232 array parameter. @@ -1583,7 +1583,7 @@ class ContractFunctionParameters: This instance for method chaining """ - def add_uint240_array(self, value: List[int]) -> "ContractFunctionParameters": + def add_uint240_array(self, value: list[int]) -> ContractFunctionParameters: """ Add a uint240 array parameter. @@ -1594,7 +1594,7 @@ class ContractFunctionParameters: This instance for method chaining """ - def add_uint248_array(self, value: List[int]) -> "ContractFunctionParameters": + def add_uint248_array(self, value: list[int]) -> ContractFunctionParameters: """ Add a uint248 array parameter. @@ -1605,7 +1605,7 @@ class ContractFunctionParameters: This instance for method chaining """ - def add_uint256_array(self, value: List[int]) -> "ContractFunctionParameters": + def add_uint256_array(self, value: list[int]) -> ContractFunctionParameters: """ Add a uint256 array parameter. diff --git a/src/hiero_sdk_python/contract/contract_function_result.py b/src/hiero_sdk_python/contract/contract_function_result.py index aab9761a5..6bae2986b 100644 --- a/src/hiero_sdk_python/contract/contract_function_result.py +++ b/src/hiero_sdk_python/contract/contract_function_result.py @@ -6,8 +6,10 @@ decoding. Results can be accessed through type-specific getter methods. """ +from __future__ import annotations + from dataclasses import dataclass, field -from typing import Any, List, Optional +from typing import Any import eth_abi from google.protobuf.wrappers_pb2 import BytesValue, Int64Value @@ -27,20 +29,20 @@ class ContractFunctionResult: Hedera smart contract function calls, using the eth-abi library to handle the decoding logic. """ - contract_id: Optional[ContractId] = None - contract_call_result: Optional[bytes] = None - error_message: Optional[str] = None - bloom: Optional[bytes] = None - gas_used: Optional[int] = None - log_info: List[ContractLogInfo] = field(default_factory=list) - evm_address: Optional[ContractId] = None - gas_available: Optional[int] = None - amount: Optional[int] = None - function_parameters: Optional[bytes] = None - contract_nonces: List[ContractNonceInfo] = field(default_factory=list) - signer_nonce: Optional[int] = None - - def get_result(self, output_types: List[str]) -> List[Any]: + contract_id: ContractId | None = None + contract_call_result: bytes | None = None + error_message: str | None = None + bloom: bytes | None = None + gas_used: int | None = None + log_info: list[ContractLogInfo] = field(default_factory=list) + evm_address: ContractId | None = None + gas_available: int | None = None + amount: int | None = None + function_parameters: bytes | None = None + contract_nonces: list[ContractNonceInfo] = field(default_factory=list) + signer_nonce: int | None = None + + def get_result(self, output_types: list[str]) -> list[Any]: """ Decode the result bytes according to the provided Solidity output types. @@ -450,9 +452,7 @@ def get_string(self, index: int) -> str: return self.get_bytes(index).decode("utf-8") @classmethod - def _from_proto( - cls, proto: contract_types_pb2.ContractFunctionResult - ) -> "ContractFunctionResult": + def _from_proto(cls, proto: contract_types_pb2.ContractFunctionResult) -> ContractFunctionResult: """ Deserializes a ContractFunctionResult from a protobuf message. @@ -468,24 +468,14 @@ def _from_proto( if proto is None: raise ValueError("Contract function result proto is None") - logs = [] - for log_info in proto.logInfo: - logs.append(ContractLogInfo._from_proto(log_info)) + logs = [ContractLogInfo._from_proto(log_info) for log_info in proto.logInfo] - evm_address = ( - ContractId(evm_address=proto.evm_address.value) - if len(proto.evm_address.value) > 0 - else None - ) + evm_address = ContractId(evm_address=proto.evm_address.value) if len(proto.evm_address.value) > 0 else None - contract_nonces = [] - for contract_nonce in proto.contract_nonces: - contract_nonces.append(ContractNonceInfo._from_proto(contract_nonce)) + contract_nonces = [ContractNonceInfo._from_proto(contract_nonce) for contract_nonce in proto.contract_nonces] return cls( - contract_id=( - ContractId._from_proto(proto.contractID) if proto.contractID else None - ), + contract_id=(ContractId._from_proto(proto.contractID) if proto.contractID else None), contract_call_result=proto.contractCallResult, error_message=proto.errorMessage, bloom=proto.bloom, @@ -513,17 +503,10 @@ def _to_proto(self) -> contract_types_pb2.ContractFunctionResult: bloom=self.bloom, gasUsed=self.gas_used, logInfo=[log_info._to_proto() for log_info in self.log_info or []], - evm_address=( - BytesValue(value=self.evm_address.evm_address) - if self.evm_address - else None - ), + evm_address=(BytesValue(value=self.evm_address.evm_address) if self.evm_address else None), gas=self.gas_available, amount=self.amount, functionParameters=self.function_parameters, signer_nonce=Int64Value(value=self.signer_nonce), - contract_nonces=[ - contract_nonce._to_proto() - for contract_nonce in self.contract_nonces or [] - ], + contract_nonces=[contract_nonce._to_proto() for contract_nonce in self.contract_nonces or []], ) diff --git a/src/hiero_sdk_python/contract/contract_id.py b/src/hiero_sdk_python/contract/contract_id.py index 2e41b787b..2dd4b2b16 100644 --- a/src/hiero_sdk_python/contract/contract_id.py +++ b/src/hiero_sdk_python/contract/contract_id.py @@ -5,21 +5,24 @@ format, and validating checksums associated with a contract. """ +from __future__ import annotations + import re from dataclasses import dataclass, field -from typing import TYPE_CHECKING, Optional +from typing import TYPE_CHECKING from hiero_sdk_python.crypto.evm_address import EvmAddress from hiero_sdk_python.crypto.key import Key from hiero_sdk_python.hapi.services import basic_types_pb2 from hiero_sdk_python.utils.entity_id_helper import ( - parse_from_string, - validate_checksum, format_to_string_with_checksum, - to_solidity_address, + parse_from_string, perform_query_to_mirror_node, + to_solidity_address, + validate_checksum, ) + if TYPE_CHECKING: from hiero_sdk_python.client.client import Client @@ -48,11 +51,11 @@ class ContractId(Key): shard: int = 0 realm: int = 0 contract: int = 0 - evm_address: Optional[bytes] = None + evm_address: bytes | None = None checksum: str | None = field(default=None, init=False) @classmethod - def _from_proto(cls, contract_id_proto: basic_types_pb2.ContractID) -> "ContractId": + def _from_proto(cls, contract_id_proto: basic_types_pb2.ContractID) -> ContractId: """ Creates a ContractId instance from a protobuf ContractID object. @@ -91,7 +94,7 @@ def _to_proto(self): contractNum=self.contract, evm_address=self.evm_address, ) - + def to_proto_key(self) -> basic_types_pb2.Key: """ Convert the ContractId instance to a protobuf Key object. @@ -102,7 +105,7 @@ def to_proto_key(self) -> basic_types_pb2.Key: return basic_types_pb2.Key(contractID=self._to_proto()) @classmethod - def from_string(cls, contract_id_str: str) -> "ContractId": + def from_string(cls, contract_id_str: str) -> ContractId: """ Parses a string to create a ContractId instance. @@ -122,9 +125,7 @@ def from_string(cls, contract_id_str: str) -> "ContractId": or in an invalid format. """ if contract_id_str is None or not isinstance(contract_id_str, str): - raise TypeError( - f"contract_id_str must be of type str, got {type(contract_id_str).__name__}" - ) + raise TypeError(f"contract_id_str must be of type str, got {type(contract_id_str).__name__}") evm_address_match = EVM_ADDRESS_REGEX.match(contract_id_str) @@ -136,24 +137,20 @@ def from_string(cls, contract_id_str: str) -> "ContractId": evm_address=bytes.fromhex(evm_address), ) - else: - try: - shard, realm, contract, checksum = parse_from_string(contract_id_str) + try: + shard, realm, contract, checksum = parse_from_string(contract_id_str) - contract_id: ContractId = cls( - shard=int(shard), realm=int(realm), contract=int(contract) - ) - object.__setattr__(contract_id, "checksum", checksum) - return contract_id + contract_id: ContractId = cls(shard=int(shard), realm=int(realm), contract=int(contract)) + object.__setattr__(contract_id, "checksum", checksum) + return contract_id - except Exception as e: - raise ValueError( - f"Invalid contract ID string '{contract_id_str}'. " - f"Expected format 'shard.realm.contract'." - ) from e + except Exception as e: + raise ValueError( + f"Invalid contract ID string '{contract_id_str}'. Expected format 'shard.realm.contract'." + ) from e @classmethod - def from_evm_address(cls, shard: int, realm: int, evm_address: str) -> "ContractId": + def from_evm_address(cls, shard: int, realm: int, evm_address: str) -> ContractId: """ Create a ContractId from an EVM address string. @@ -170,9 +167,7 @@ def from_evm_address(cls, shard: int, realm: int, evm_address: str) -> "Contract ValueError: If shard or realm are negative, or the EVM address is invalid. """ if not isinstance(evm_address, str): - raise TypeError( - f"evm_address must be of type str, got {type(evm_address).__name__}" - ) + raise TypeError(f"evm_address must be of type str, got {type(evm_address).__name__}") for name, value in (("shard", shard), ("realm", realm)): if isinstance(value, bool) or not isinstance(value, int): @@ -188,7 +183,7 @@ def from_evm_address(cls, shard: int, realm: int, evm_address: str) -> "Contract raise ValueError(f"Invalid EVM address: {evm_address}") from e @classmethod - def from_bytes(cls, data: bytes) -> "ContractId": + def from_bytes(cls, data: bytes) -> ContractId: """ Deserialize an ContractId from protobuf-encoded bytes. @@ -212,7 +207,7 @@ def from_bytes(cls, data: bytes) -> "ContractId": return cls._from_proto(proto) - def to_bytes(self) -> "bytes": + def to_bytes(self) -> bytes: """ Serialize this ContractId to protobuf bytes. @@ -221,7 +216,7 @@ def to_bytes(self) -> "bytes": """ return self._to_proto().SerializeToString() - def populate_contract_num(self, client: "Client") -> "ContractId": + def populate_contract_num(self, client: Client) -> ContractId: """ Resolve and populate the numeric contract ID using the Mirror Node. @@ -250,8 +245,7 @@ def populate_contract_num(self, client: "Client") -> "ContractId": except RuntimeError as e: raise RuntimeError( - "Failed to populate contract num from mirror node for evm_address " - f"{self.evm_address.hex()}" + f"Failed to populate contract num from mirror node for evm_address {self.evm_address.hex()}" ) from e try: @@ -309,7 +303,7 @@ def to_evm_address(self) -> str: return to_solidity_address(self.shard, self.realm, self.contract) - def validate_checksum(self, client: "Client") -> None: + def validate_checksum(self, client: Client) -> None: """ Validates the checksum (if present) against a client's network. @@ -332,7 +326,7 @@ def validate_checksum(self, client: "Client") -> None: client, ) - def to_string_with_checksum(self, client: "Client") -> str: + def to_string_with_checksum(self, client: Client) -> str: """ Generates a string representation with a network-specific checksum. @@ -350,11 +344,6 @@ def to_string_with_checksum(self, client: "Client") -> str: as checksums cannot be applied to EVM addresses. """ if self.evm_address is not None: - raise ValueError( - "to_string_with_checksum cannot be applied to ContractId with evm_address" - ) - - return format_to_string_with_checksum( - self.shard, self.realm, self.contract, client - ) + raise ValueError("to_string_with_checksum cannot be applied to ContractId with evm_address") + return format_to_string_with_checksum(self.shard, self.realm, self.contract, client) diff --git a/src/hiero_sdk_python/contract/contract_info.py b/src/hiero_sdk_python/contract/contract_info.py index aeb04fc1a..a6cb7b006 100644 --- a/src/hiero_sdk_python/contract/contract_info.py +++ b/src/hiero_sdk_python/contract/contract_info.py @@ -1,11 +1,11 @@ # pylint: disable=too-many-instance-attributes -""" -This module contains the ContractInfo class, which is used to store information about a contract. -""" +"""This module contains the ContractInfo class, which is used to store information about a contract.""" + +from __future__ import annotations import datetime +from collections.abc import Callable from dataclasses import dataclass, field -from typing import Callable, Optional from hiero_sdk_python.account.account_id import AccountId from hiero_sdk_python.contract.contract_id import ContractId @@ -23,43 +23,43 @@ class ContractInfo: Information about a contract stored on the network. Attributes: - contract_id (Optional[ContractId]): The ID of the contract - account_id (Optional[AccountId]): The ID of the account owned by the contract - contract_account_id (Optional[str]): The contract's EVM address (hex). - admin_key (Optional[PublicKey]): The key that can modify this contract - expiration_time (Optional[Timestamp]): When the contract will expire - auto_renew_period (Optional[Duration]): The period for which the contract will auto-renew - auto_renew_account_id (Optional[AccountId]): + contract_id (ContractId, optional): The ID of the contract + account_id (AccountId, optional): The ID of the account owned by the contract + contract_account_id (str, optional): The contract's EVM address (hex). + admin_key (PublicKey, optional): The key that can modify this contract + expiration_time (Timestamp, optional): When the contract will expire + auto_renew_period (Duration, optional): The period for which the contract will auto-renew + auto_renew_account_id (AccountId, optional): The ID of the account that will auto-renew the contract - storage (Optional[int]): The storage used by the contract - contract_memo (Optional[str]): The memo associated with the contract - balance (Optional[int]): The balance of the contract - is_deleted (Optional[bool]): Whether the contract has been deleted - ledger_id (Optional[bytes]): The ID of the ledger this contract exists in - max_automatic_token_associations (Optional[int]): + storage (int, optional): The storage used by the contract + contract_memo (str, optional): The memo associated with the contract + balance (int, optional): The balance of the contract + is_deleted (bool, optional): Whether the contract has been deleted + ledger_id (bytes, optional): The ID of the ledger this contract exists in + max_automatic_token_associations (int, optional): The maximum number of token associations that can be automatically renewed token_relationships (list[TokenRelationship]): The token relationships of the contract - staking_info (Optional[StakingInfo]): The staking information for this contract + staking_info (StakingInfo, optional): The staking information for this contract """ - contract_id: Optional[ContractId] = None - account_id: Optional[AccountId] = None - contract_account_id: Optional[str] = None - admin_key: Optional[PublicKey] = None - expiration_time: Optional[Timestamp] = None - auto_renew_period: Optional[Duration] = None - auto_renew_account_id: Optional[AccountId] = None - storage: Optional[int] = None - contract_memo: Optional[str] = None - balance: Optional[int] = None - is_deleted: Optional[bool] = None - ledger_id: Optional[bytes] = None - max_automatic_token_associations: Optional[int] = None + contract_id: ContractId | None = None + account_id: AccountId | None = None + contract_account_id: str | None = None + admin_key: PublicKey | None = None + expiration_time: Timestamp | None = None + auto_renew_period: Duration | None = None + auto_renew_account_id: AccountId | None = None + storage: int | None = None + contract_memo: str | None = None + balance: int | None = None + is_deleted: bool | None = None + ledger_id: bytes | None = None + max_automatic_token_associations: int | None = None token_relationships: list[TokenRelationship] = field(default_factory=list) - staking_info: Optional[StakingInfo] = None + staking_info: StakingInfo | None = None @classmethod - def _from_proto(cls, proto: ContractGetInfoResponse.ContractInfo) -> "ContractInfo": + def _from_proto(cls, proto: ContractGetInfoResponse.ContractInfo) -> ContractInfo: """ Creates a ContractInfo instance from its protobuf representation. @@ -72,26 +72,14 @@ def _from_proto(cls, proto: ContractGetInfoResponse.ContractInfo) -> "ContractIn if proto is None: raise ValueError("Contract info proto is None") - contract_info = cls( - contract_id=( - cls._from_proto_field(proto, "contractID", ContractId._from_proto) - ), - account_id=( - cls._from_proto_field(proto, "accountID", AccountId._from_proto) - ), + return cls( + contract_id=(cls._from_proto_field(proto, "contractID", ContractId._from_proto)), + account_id=(cls._from_proto_field(proto, "accountID", AccountId._from_proto)), contract_account_id=proto.contractAccountID, admin_key=(cls._from_proto_field(proto, "adminKey", PublicKey._from_proto)), - expiration_time=( - cls._from_proto_field(proto, "expirationTime", Timestamp._from_protobuf) - ), - auto_renew_period=( - cls._from_proto_field(proto, "autoRenewPeriod", Duration._from_proto) - ), - auto_renew_account_id=( - cls._from_proto_field( - proto, "auto_renew_account_id", AccountId._from_proto - ) - ), + expiration_time=(cls._from_proto_field(proto, "expirationTime", Timestamp._from_protobuf)), + auto_renew_period=(cls._from_proto_field(proto, "autoRenewPeriod", Duration._from_proto)), + auto_renew_account_id=(cls._from_proto_field(proto, "auto_renew_account_id", AccountId._from_proto)), storage=proto.storage, contract_memo=proto.memo, balance=proto.balance, @@ -99,18 +87,11 @@ def _from_proto(cls, proto: ContractGetInfoResponse.ContractInfo) -> "ContractIn ledger_id=proto.ledger_id, max_automatic_token_associations=proto.max_automatic_token_associations, token_relationships=[ - TokenRelationship._from_proto(relationship) - for relationship in proto.tokenRelationships + TokenRelationship._from_proto(relationship) for relationship in proto.tokenRelationships ], - staking_info=( - StakingInfo._from_proto(proto.staking_info) - if proto.HasField('staking_info') - else None - ), + staking_info=(StakingInfo._from_proto(proto.staking_info) if proto.HasField("staking_info") else None), ) - return contract_info - def _to_proto(self) -> ContractGetInfoResponse.ContractInfo: """ Converts this ContractInfo instance to its protobuf representation. @@ -123,25 +104,15 @@ def _to_proto(self) -> ContractGetInfoResponse.ContractInfo: contractID=self.contract_id._to_proto() if self.contract_id else None, contractAccountID=self.contract_account_id, adminKey=self.admin_key._to_proto() if self.admin_key else None, - expirationTime=( - self.expiration_time._to_protobuf() if self.expiration_time else None - ), - autoRenewPeriod=( - self.auto_renew_period._to_proto() if self.auto_renew_period else None - ), + expirationTime=(self.expiration_time._to_protobuf() if self.expiration_time else None), + autoRenewPeriod=(self.auto_renew_period._to_proto() if self.auto_renew_period else None), storage=self.storage, memo=self.contract_memo, balance=self.balance, deleted=self.is_deleted, - tokenRelationships=[ - relationship._to_proto() for relationship in self.token_relationships - ], + tokenRelationships=[relationship._to_proto() for relationship in self.token_relationships], ledger_id=self.ledger_id, - auto_renew_account_id=( - self.auto_renew_account_id._to_proto() - if self.auto_renew_account_id - else None - ), + auto_renew_account_id=(self.auto_renew_account_id._to_proto() if self.auto_renew_account_id else None), max_automatic_token_associations=self.max_automatic_token_associations, staking_info=self.staking_info._to_proto() if self.staking_info else None, ) @@ -156,9 +127,7 @@ def __repr__(self) -> str: return self.__str__() def __str__(self) -> str: - """ - Pretty-print the ContractInfo. - """ + """Pretty-print the ContractInfo.""" # Format expiration time as datetime if available exp_dt = ( datetime.datetime.fromtimestamp(self.expiration_time.seconds) @@ -168,16 +137,12 @@ def __str__(self) -> str: # Format keys as readable strings token_relationships_str = ( - [str(relationship) for relationship in self.token_relationships] - if self.token_relationships - else [] + [str(relationship) for relationship in self.token_relationships] if self.token_relationships else [] ) # Format ledger_id as hex if it's bytes ledger_id_display = ( - f"0x{self.ledger_id.hex()}" - if isinstance(self.ledger_id, (bytes, bytearray)) - else self.ledger_id + f"0x{self.ledger_id.hex()}" if isinstance(self.ledger_id, (bytes, bytearray)) else self.ledger_id ) return ( diff --git a/src/hiero_sdk_python/contract/contract_info_query.py b/src/hiero_sdk_python/contract/contract_info_query.py index 479b5e97c..03370ef6b 100644 --- a/src/hiero_sdk_python/contract/contract_info_query.py +++ b/src/hiero_sdk_python/contract/contract_info_query.py @@ -1,9 +1,8 @@ -""" -Query to get information about a contract on the network. -""" +"""Query to get information about a contract on the network.""" + +from __future__ import annotations import traceback -from typing import Optional, Union from hiero_sdk_python.channels import _Channel from hiero_sdk_python.client.client import Client @@ -28,22 +27,22 @@ class ContractInfoQuery(Query): """ - def __init__(self, contract_id: Optional[ContractId] = None) -> None: + def __init__(self, contract_id: ContractId | None = None) -> None: """ Initializes a new ContractInfoQuery instance with an optional contract_id. Args: - contract_id (Optional[ContractId]): The ID of the contract to query. + contract_id (ContractId, optional): The ID of the contract to query. """ super().__init__() - self.contract_id: Optional[ContractId] = contract_id + self.contract_id: ContractId | None = contract_id - def set_contract_id(self, contract_id: Optional[ContractId]) -> "ContractInfoQuery": + def set_contract_id(self, contract_id: ContractId | None) -> ContractInfoQuery: """ Sets the ID of the contract to query. Args: - contract_id (Optional[ContractId]): The ID of the contract. + contract_id (ContractId | None): The ID of the contract. """ self.contract_id = contract_id return self @@ -94,11 +93,9 @@ def _get_method(self, channel: _Channel) -> _Method: Returns: _Method: The method wrapper containing the query function """ - return _Method( - transaction_func=None, query_func=channel.smart_contract.getContractInfo - ) + return _Method(transaction_func=None, query_func=channel.smart_contract.getContractInfo) - def execute(self, client: Client, timeout: Optional[Union[int, float]] = None) -> ContractInfo: + def execute(self, client: Client, timeout: int | float | None = None) -> ContractInfo: """ Executes the contract info query. @@ -110,7 +107,7 @@ def execute(self, client: Client, timeout: Optional[Union[int, float]] = None) - Args: client (Client): The client instance to use for execution - timeout (Optional[Union[int, float]]): The total execution timeout (in seconds) for this execution. + timeout (int | float, optional): The total execution timeout (in seconds) for this execution. Returns: ContractInfo: The contract info from the network @@ -125,9 +122,7 @@ def execute(self, client: Client, timeout: Optional[Union[int, float]] = None) - return ContractInfo._from_proto(response.contractGetInfo.contractInfo) - def _get_query_response( - self, response: response_pb2.Response - ) -> ContractGetInfoResponse.ContractInfo: + def _get_query_response(self, response: response_pb2.Response) -> ContractGetInfoResponse.ContractInfo: """ Extracts the contract info response from the full response. diff --git a/src/hiero_sdk_python/contract/contract_log_info.py b/src/hiero_sdk_python/contract/contract_log_info.py index 947accef3..774a0a813 100644 --- a/src/hiero_sdk_python/contract/contract_log_info.py +++ b/src/hiero_sdk_python/contract/contract_log_info.py @@ -3,8 +3,9 @@ from a contract execution. """ +from __future__ import annotations + from dataclasses import dataclass, field -from typing import List, Optional from hiero_sdk_python.contract.contract_id import ContractId from hiero_sdk_python.hapi.services import contract_types_pb2 @@ -16,21 +17,19 @@ class ContractLogInfo: Represents a log entry from a contract execution. Attributes: - contract_id (Optional[ContractId]): The contract ID. - bloom (Optional[bytes]): The bloom filter. - topics (List[bytes]): The topics. - data (Optional[bytes]): The data. + contract_id (ContractId, optional): The contract ID. + bloom (bytes, optional): The bloom filter. + topics (list[bytes]): The topics. + data (bytes, optional): The data. """ - contract_id: Optional[ContractId] = None - bloom: Optional[bytes] = None - topics: List[bytes] = field(default_factory=list) - data: Optional[bytes] = None + contract_id: ContractId | None = None + bloom: bytes | None = None + topics: list[bytes] = field(default_factory=list) + data: bytes | None = None @classmethod - def _from_proto( - cls, proto: contract_types_pb2.ContractLoginfo - ) -> "ContractLogInfo": + def _from_proto(cls, proto: contract_types_pb2.ContractLoginfo) -> ContractLogInfo: """ Creates a ContractLogInfo instance from its protobuf representation. @@ -47,9 +46,7 @@ def _from_proto( raise ValueError("Contract log info proto is None") return cls( - contract_id=( - ContractId._from_proto(proto.contractID) if proto.contractID else None - ), + contract_id=(ContractId._from_proto(proto.contractID) if proto.contractID else None), bloom=proto.bloom, topics=proto.topic, data=proto.data, @@ -79,9 +76,7 @@ def __repr__(self) -> str: return self.__str__() def __str__(self) -> str: - """ - Pretty-print the ContractLogInfo. - """ + """Pretty-print the ContractLogInfo.""" topics_str = [topic.hex() for topic in self.topics] if self.topics else [] return ( diff --git a/src/hiero_sdk_python/contract/contract_nonce_info.py b/src/hiero_sdk_python/contract/contract_nonce_info.py index c8bef65f6..3fb4ed3bc 100644 --- a/src/hiero_sdk_python/contract/contract_nonce_info.py +++ b/src/hiero_sdk_python/contract/contract_nonce_info.py @@ -5,8 +5,9 @@ attacks and ensure transaction uniqueness. """ +from __future__ import annotations + from dataclasses import dataclass -from typing import Optional from hiero_sdk_python.contract.contract_id import ContractId from hiero_sdk_python.hapi.services import contract_types_pb2 @@ -18,17 +19,15 @@ class ContractNonceInfo: Represents the nonce information for a specific contract. Attributes: - contract_id (Optional[ContractId]): The contract identifier. + contract_id (ContractId, optional): The contract identifier. nonce (int): The nonce value associated with the contract. """ - contract_id: Optional[ContractId] = None + contract_id: ContractId | None = None nonce: int = 0 @classmethod - def _from_proto( - cls, proto: contract_types_pb2.ContractNonceInfo - ) -> "ContractNonceInfo": + def _from_proto(cls, proto: contract_types_pb2.ContractNonceInfo) -> ContractNonceInfo: """ Creates a ContractNonceInfo instance from its protobuf representation. @@ -44,9 +43,7 @@ def _from_proto( if proto is None: raise ValueError("Contract nonce info proto is None") - return cls( - contract_id=ContractId._from_proto(proto.contract_id), nonce=proto.nonce - ) + return cls(contract_id=ContractId._from_proto(proto.contract_id), nonce=proto.nonce) def _to_proto(self) -> contract_types_pb2.ContractNonceInfo: """ @@ -55,9 +52,7 @@ def _to_proto(self) -> contract_types_pb2.ContractNonceInfo: Returns: contract_types_pb2.ContractNonceInfo: The protobuf object representing this instance. """ - return contract_types_pb2.ContractNonceInfo( - contract_id=self.contract_id._to_proto(), nonce=self.nonce - ) + return contract_types_pb2.ContractNonceInfo(contract_id=self.contract_id._to_proto(), nonce=self.nonce) def __repr__(self) -> str: """ @@ -69,12 +64,5 @@ def __repr__(self) -> str: return self.__str__() def __str__(self) -> str: - """ - Pretty-print the ContractNonceInfo. - """ - return ( - "ContractNonceInfo(" - f"contract_id={self.contract_id}, " - f"nonce={self.nonce}" - ")" - ) + """Pretty-print the ContractNonceInfo.""" + return f"ContractNonceInfo(contract_id={self.contract_id}, nonce={self.nonce})" diff --git a/src/hiero_sdk_python/contract/contract_update_transaction.py b/src/hiero_sdk_python/contract/contract_update_transaction.py index 905d31c7d..8909f618f 100644 --- a/src/hiero_sdk_python/contract/contract_update_transaction.py +++ b/src/hiero_sdk_python/contract/contract_update_transaction.py @@ -1,10 +1,10 @@ # pylint: disable=too-many-instance-attributes -""" -Transaction to update a contract's properties, metadata, or keys on the network. -""" +"""Transaction to update a contract's properties, metadata, or keys on the network.""" + +from __future__ import annotations from dataclasses import dataclass -from typing import Any, Optional +from typing import Any from google.protobuf.wrappers_pb2 import BoolValue, Int32Value, StringValue @@ -29,30 +29,30 @@ class ContractUpdateParams: Represents contract attributes that can be updated. Attributes: - contract_id (Optional[ContractId]): The contract ID to update. - expiration_time (Optional[Timestamp]): The new expiration time for the contract. - admin_key (Optional[PublicKey]): The new admin key for the contract. - auto_renew_period (Optional[Duration]): The new auto-renew period for the contract. - contract_memo (Optional[str]): The new memo for the contract. - max_automatic_token_associations (Optional[int]): The new maximum number of + contract_id (ContractId, optional): The contract ID to update. + expiration_time (Timestamp, optional): The new expiration time for the contract. + admin_key (PublicKey, optional): The new admin key for the contract. + auto_renew_period (Duration, optional): The new auto-renew period for the contract. + contract_memo (str, optional): The new memo for the contract. + max_automatic_token_associations (int, optional): The new maximum number of automatic token associations for the contract. - auto_renew_account_id (Optional[AccountId]): The new account to be charged for auto-renewal. - staked_node_id (Optional[int]): The node ID to which the contract will be staked. - staked_account_id (Optional[AccountId]): The account ID to which + auto_renew_account_id (AccountId, optional): The new account to be charged for auto-renewal. + staked_node_id (int, optional): The node ID to which the contract will be staked. + staked_account_id (AccountId, optional): The account ID to which the contract will be staked. - decline_reward (Optional[bool]): Whether the contract should decline staking rewards. + decline_reward (bool, optional): Whether the contract should decline staking rewards. """ - contract_id: Optional[ContractId] = None - expiration_time: Optional[Timestamp] = None - admin_key: Optional[PublicKey] = None - auto_renew_period: Optional[Duration] = None - contract_memo: Optional[str] = None - max_automatic_token_associations: Optional[int] = None - auto_renew_account_id: Optional[AccountId] = None - staked_node_id: Optional[int] = None - staked_account_id: Optional[AccountId] = None - decline_reward: Optional[bool] = None + contract_id: ContractId | None = None + expiration_time: Timestamp | None = None + admin_key: PublicKey | None = None + auto_renew_period: Duration | None = None + contract_memo: str | None = None + max_automatic_token_associations: int | None = None + auto_renew_account_id: AccountId | None = None + staked_node_id: int | None = None + staked_account_id: AccountId | None = None + decline_reward: bool | None = None class ContractUpdateTransaction(Transaction): @@ -72,33 +72,31 @@ class ContractUpdateTransaction(Transaction): def __init__( self, - contract_params: Optional[ContractUpdateParams] = None, + contract_params: ContractUpdateParams | None = None, ) -> None: """ Initializes a new ContractUpdateTransaction instance with the specified parameters. Args: - contract_params (Optional[ContractUpdateParams]): The contract update + contract_params (ContractUpdateParams, optional): The contract update parameters containing all the fields that can be updated. If not provided, defaults to an empty ContractUpdateParams instance. """ super().__init__() params = contract_params or ContractUpdateParams() - self.contract_id: Optional[ContractId] = params.contract_id - self.expiration_time: Optional[Timestamp] = params.expiration_time - self.admin_key: Optional[PublicKey] = params.admin_key - self.auto_renew_period: Optional[Duration] = params.auto_renew_period - self.contract_memo: Optional[str] = params.contract_memo - self.max_automatic_token_associations: Optional[int] = ( - params.max_automatic_token_associations - ) - self.auto_renew_account_id: Optional[AccountId] = params.auto_renew_account_id - self.staked_node_id: Optional[int] = params.staked_node_id - self.staked_account_id: Optional[AccountId] = params.staked_account_id - self.decline_reward: Optional[bool] = params.decline_reward + self.contract_id: ContractId | None = params.contract_id + self.expiration_time: Timestamp | None = params.expiration_time + self.admin_key: PublicKey | None = params.admin_key + self.auto_renew_period: Duration | None = params.auto_renew_period + self.contract_memo: str | None = params.contract_memo + self.max_automatic_token_associations: int | None = params.max_automatic_token_associations + self.auto_renew_account_id: AccountId | None = params.auto_renew_account_id + self.staked_node_id: int | None = params.staked_node_id + self.staked_account_id: AccountId | None = params.staked_account_id + self.decline_reward: bool | None = params.decline_reward self._default_transaction_fee = Hbar(20).to_tinybars() - def set_contract_id(self, contract_id: ContractId) -> "ContractUpdateTransaction": + def set_contract_id(self, contract_id: ContractId) -> ContractUpdateTransaction: """ Sets the ContractID to be updated. @@ -112,14 +110,12 @@ def set_contract_id(self, contract_id: ContractId) -> "ContractUpdateTransaction self.contract_id = contract_id return self - def set_expiration_time( - self, expiration_time: Optional[Timestamp] - ) -> "ContractUpdateTransaction": + def set_expiration_time(self, expiration_time: Timestamp | None) -> ContractUpdateTransaction: """ Sets the new expiration time for the contract. Args: - expiration_time (Optional[Timestamp]): The new expiration time for the contract. + expiration_time (Timestamp | None): The new expiration time for the contract. Returns: ContractUpdateTransaction: This transaction instance. @@ -128,14 +124,12 @@ def set_expiration_time( self.expiration_time = expiration_time return self - def set_admin_key( - self, admin_key: Optional[PublicKey] - ) -> "ContractUpdateTransaction": + def set_admin_key(self, admin_key: PublicKey | None) -> ContractUpdateTransaction: """ Sets the new admin key for the contract. Args: - admin_key (Optional[PublicKey]): The new admin key for the contract. + admin_key (PublicKey | None): The new admin key for the contract. This key can update or delete the contract. Returns: @@ -145,14 +139,12 @@ def set_admin_key( self.admin_key = admin_key return self - def set_auto_renew_period( - self, auto_renew_period: Optional[Duration] - ) -> "ContractUpdateTransaction": + def set_auto_renew_period(self, auto_renew_period: Duration | None) -> ContractUpdateTransaction: """ Sets the new auto-renewal period for the contract. Args: - auto_renew_period (Optional[Duration]): The new auto-renewal period for the contract. + auto_renew_period (Duration | None): The new auto-renewal period for the contract. The contract will be automatically renewed for this duration upon expiration. Returns: @@ -162,14 +154,12 @@ def set_auto_renew_period( self.auto_renew_period = auto_renew_period return self - def set_contract_memo( - self, contract_memo: Optional[str] - ) -> "ContractUpdateTransaction": + def set_contract_memo(self, contract_memo: str | None) -> ContractUpdateTransaction: """ Sets the new memo associated with the contract. Args: - contract_memo (Optional[str]): The new memo for the contract. + contract_memo (str | None): The new memo for the contract. Returns: ContractUpdateTransaction: This transaction instance. @@ -179,14 +169,14 @@ def set_contract_memo( return self def set_max_automatic_token_associations( - self, max_automatic_token_associations: Optional[int] - ) -> "ContractUpdateTransaction": + self, max_automatic_token_associations: int | None + ) -> ContractUpdateTransaction: """ Sets the new maximum number of tokens that can be automatically associated with the contract. Args: - max_automatic_token_associations (Optional[int]): The new maximum number of tokens + max_automatic_token_associations (int | None): The new maximum number of tokens that can be automatically associated with the contract. Returns: @@ -196,14 +186,12 @@ def set_max_automatic_token_associations( self.max_automatic_token_associations = max_automatic_token_associations return self - def set_auto_renew_account_id( - self, auto_renew_account_id: Optional[AccountId] - ) -> "ContractUpdateTransaction": + def set_auto_renew_account_id(self, auto_renew_account_id: AccountId | None) -> ContractUpdateTransaction: """ Sets the new account ID that will be charged for the contract's auto-renewal. Args: - auto_renew_account_id (Optional[AccountId]): The new account ID that will be + auto_renew_account_id (AccountId | None): The new account ID that will be charged for the contract's auto-renewal. Returns: @@ -213,14 +201,12 @@ def set_auto_renew_account_id( self.auto_renew_account_id = auto_renew_account_id return self - def set_staked_node_id( - self, staked_node_id: Optional[int] - ) -> "ContractUpdateTransaction": + def set_staked_node_id(self, staked_node_id: int | None) -> ContractUpdateTransaction: """ Sets the new node ID to which the contract stakes. Args: - staked_node_id (Optional[int]): The new node ID to which the contract stakes. + staked_node_id (int | None): The new node ID to which the contract stakes. Returns: ContractUpdateTransaction: This transaction instance. @@ -229,14 +215,12 @@ def set_staked_node_id( self.staked_node_id = staked_node_id return self - def set_decline_reward( - self, decline_reward: Optional[bool] - ) -> "ContractUpdateTransaction": + def set_decline_reward(self, decline_reward: bool | None) -> ContractUpdateTransaction: """ Sets whether the contract declines staking rewards. Args: - decline_reward (Optional[bool]): Whether the contract declines staking rewards. + decline_reward (bool | None): Whether the contract declines staking rewards. Returns: ContractUpdateTransaction: This transaction instance. @@ -245,14 +229,12 @@ def set_decline_reward( self.decline_reward = decline_reward return self - def set_staked_account_id( - self, staked_account_id: Optional[AccountId] - ) -> "ContractUpdateTransaction": + def set_staked_account_id(self, staked_account_id: AccountId | None) -> ContractUpdateTransaction: """ Sets the new account ID to which the contract stakes. Args: - staked_account_id (Optional[AccountId]): The new account ID to which the contract + staked_account_id (AccountId | None): The new account ID to which the contract stakes. Returns: @@ -262,8 +244,8 @@ def set_staked_account_id( self.staked_account_id = staked_account_id return self - def _convert_to_proto(self, obj: Optional[Any]) -> Any: - """Convert object to proto if it exists, otherwise return None""" + def _convert_to_proto(self, obj: Any | None) -> Any: + """Convert object to proto if it exists, otherwise return None.""" return obj._to_proto() if obj else None def _build_proto_body(self): @@ -281,17 +263,11 @@ def _build_proto_body(self): return contract_update_pb2.ContractUpdateTransactionBody( contractID=self.contract_id._to_proto(), - expirationTime=( - self.expiration_time._to_protobuf() if self.expiration_time else None - ), + expirationTime=(self.expiration_time._to_protobuf() if self.expiration_time else None), adminKey=self._convert_to_proto(self.admin_key), autoRenewPeriod=self._convert_to_proto(self.auto_renew_period), staked_node_id=self.staked_node_id, - memoWrapper=( - StringValue(value=self.contract_memo) - if self.contract_memo is not None - else None - ), + memoWrapper=(StringValue(value=self.contract_memo) if self.contract_memo is not None else None), max_automatic_token_associations=( Int32Value(value=self.max_automatic_token_associations) if self.max_automatic_token_associations is not None @@ -299,11 +275,7 @@ def _build_proto_body(self): ), staked_account_id=self._convert_to_proto(self.staked_account_id), auto_renew_account_id=self._convert_to_proto(self.auto_renew_account_id), - decline_reward=( - BoolValue(value=self.decline_reward) - if self.decline_reward is not None - else None - ), + decline_reward=(BoolValue(value=self.decline_reward) if self.decline_reward is not None else None), ) def build_transaction_body(self) -> transaction_pb2.TransactionBody: @@ -343,6 +315,4 @@ def _get_method(self, channel: _Channel) -> _Method: Returns: _Method: An object containing the transaction function to update a contract. """ - return _Method( - transaction_func=channel.smart_contract.updateContract, query_func=None - ) + return _Method(transaction_func=channel.smart_contract.updateContract, query_func=None) diff --git a/src/hiero_sdk_python/contract/delegate_contract_id.py b/src/hiero_sdk_python/contract/delegate_contract_id.py index 69aa2ea57..49d3d7c9b 100644 --- a/src/hiero_sdk_python/contract/delegate_contract_id.py +++ b/src/hiero_sdk_python/contract/delegate_contract_id.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from dataclasses import dataclass from typing import TYPE_CHECKING @@ -5,77 +7,71 @@ from hiero_sdk_python.hapi.services import basic_types_pb2 from hiero_sdk_python.utils.entity_id_helper import format_to_string_with_checksum + if TYPE_CHECKING: - from hiero_sdk_python.client.client import Client + from hiero_sdk_python.client.client import Client + @dataclass(frozen=True) class DelegateContractId(ContractId): - """ - Represents a delegatable contract identifier used as a key in the Hiero network. - - A DelegateContractId is a permissive key type that designates a smart contract - authorized to sign a transaction if it is the recipient of the active message - frame. Unlike a standard ContractID, this key type does not require the code - executing in the current frame to belong to the specified contract. - """ - - def to_proto_key(self) -> basic_types_pb2.Key: - return basic_types_pb2.Key(delegatable_contract_id=self._to_proto()) - - - - def __str__(self) -> str: """ - Returns the string representation of the DelegateContractId. - - Format will be 'shard.realm.contract' or 'shard.realm.evm_address_hex' - if evm_address is set. Does not include a checksum. + Represents a delegatable contract identifier used as a key in the Hiero network. - Returns: - str: The string representation of the ContractId. + A DelegateContractId is a permissive key type that designates a smart contract + authorized to sign a transaction if it is the recipient of the active message + frame. Unlike a standard ContractID, this key type does not require the code + executing in the current frame to belong to the specified contract. """ - if self.evm_address is not None: - return f"{self.shard}.{self.realm}.{self.evm_address.hex()}" - return f"{self.shard}.{self.realm}.{self.contract}" + def to_proto_key(self) -> basic_types_pb2.Key: + return basic_types_pb2.Key(delegatable_contract_id=self._to_proto()) - def __repr__(self) -> str: - """ - Returns a detailed string representation of the ContractId for debugging. + def __str__(self) -> str: + """ + Returns the string representation of the DelegateContractId. - Returns: - str: DelegateContractId(shard=X, realm=Y, contract=Z) or - DelegateContractId(shard=X, realm=Y, evm_address=...) if evm_address is set. - """ - if self.evm_address is not None: - return f"DelegateContractId(shard={self.shard}, realm={self.realm}, evm_address={self.evm_address.hex()})" + Format will be 'shard.realm.contract' or 'shard.realm.evm_address_hex' + if evm_address is set. Does not include a checksum. - return f"DelegateContractId(shard={self.shard}, realm={self.realm}, contract={self.contract})" + Returns: + str: The string representation of the ContractId. + """ + if self.evm_address is not None: + return f"{self.shard}.{self.realm}.{self.evm_address.hex()}" + return f"{self.shard}.{self.realm}.{self.contract}" - def to_string_with_checksum(self, client: "Client") -> str: - """ - Generates a string representation with a network-specific checksum. + def __repr__(self) -> str: + """ + Returns a detailed string representation of the ContractId for debugging. - Format: 'shard.realm.contract-checksum' (e.g., "0.0.123-vfmkw"). + Returns: + str: DelegateContractId(shard=X, realm=Y, contract=Z) or + DelegateContractId(shard=X, realm=Y, evm_address=...) if evm_address is set. + """ + if self.evm_address is not None: + return f"DelegateContractId(shard={self.shard}, realm={self.realm}, evm_address={self.evm_address.hex()})" - Args: - client (Client): The client instance used to generate the - network-specific checksum. + return f"DelegateContractId(shard={self.shard}, realm={self.realm}, contract={self.contract})" - Returns: - str: The string representation with checksum. + def to_string_with_checksum(self, client: Client) -> str: + """ + Generates a string representation with a network-specific checksum. - Raises: - ValueError: If the DelegateContractId has an `evm_address` set, - as checksums cannot be applied to EVM addresses. - """ - if self.evm_address is not None: - raise ValueError( - "to_string_with_checksum cannot be applied to DelegateContractId with evm_address" - ) + Format: 'shard.realm.contract-checksum' (e.g., "0.0.123-vfmkw"). + + Args: + client (Client): The client instance used to generate the + network-specific checksum. + + Returns: + str: The string representation with checksum. - return format_to_string_with_checksum( - self.shard, self.realm, self.contract, client - ) + Raises: + ValueError: If the DelegateContractId has an `evm_address` set, + as checksums cannot be applied to EVM addresses. + """ + if self.evm_address is not None: + raise ValueError("to_string_with_checksum cannot be applied to DelegateContractId with evm_address") + return format_to_string_with_checksum(self.shard, self.realm, self.contract, client) diff --git a/src/hiero_sdk_python/contract/ethereum_transaction.py b/src/hiero_sdk_python/contract/ethereum_transaction.py index e663414c8..fdcda5220 100644 --- a/src/hiero_sdk_python/contract/ethereum_transaction.py +++ b/src/hiero_sdk_python/contract/ethereum_transaction.py @@ -1,8 +1,6 @@ -""" -EthereumTransaction class. -""" +"""EthereumTransaction class.""" -from typing import Optional +from __future__ import annotations from hiero_sdk_python.channels import _Channel from hiero_sdk_python.executable import _Method @@ -28,31 +26,31 @@ class EthereumTransaction(Transaction): def __init__( self, - ethereum_data: Optional[bytes] = None, - call_data_file_id: Optional[FileId] = None, - max_gas_allowed: Optional[int] = None, + ethereum_data: bytes | None = None, + call_data_file_id: FileId | None = None, + max_gas_allowed: int | None = None, ): """ Initializes a new EthereumTransaction instance. Args: - ethereum_data (Optional[bytes]): The Ethereum transaction data to execute. - call_data_file_id (Optional[FileId]): + ethereum_data (bytes, optional): The Ethereum transaction data to execute. + call_data_file_id (FileId, optional): The FileId containing call data for the transaction. - max_gas_allowed (Optional[int]): The maximum gas allowed for the transaction execution. + max_gas_allowed (int, optional): The maximum gas allowed for the transaction execution. """ super().__init__() - self.ethereum_data: Optional[bytes] = ethereum_data - self.call_data: Optional[FileId] = call_data_file_id - self.max_gas_allowed: Optional[int] = max_gas_allowed + self.ethereum_data: bytes | None = ethereum_data + self.call_data: FileId | None = call_data_file_id + self.max_gas_allowed: int | None = max_gas_allowed - def set_ethereum_data(self, ethereum_data: Optional[bytes]) -> "EthereumTransaction": + def set_ethereum_data(self, ethereum_data: bytes | None) -> EthereumTransaction: """ Sets the Ethereum transaction data to execute. Args: - ethereum_data (Optional[bytes]): The Ethereum transaction data to execute. + ethereum_data (bytes, None): The Ethereum transaction data to execute. Returns: EthereumTransaction: This transaction instance. @@ -61,14 +59,12 @@ def set_ethereum_data(self, ethereum_data: Optional[bytes]) -> "EthereumTransact self.ethereum_data = ethereum_data return self - def set_call_data_file_id( - self, call_data_file_id: Optional[FileId] - ) -> "EthereumTransaction": + def set_call_data_file_id(self, call_data_file_id: FileId | None) -> EthereumTransaction: """ Sets the call data for the transaction. Args: - call_data_file_id (Optional[FileId]): The FileId containing call data for the transaction. + call_data_file_id (FileId | None): The FileId containing call data for the transaction. Returns: EthereumTransaction: This transaction instance. @@ -77,14 +73,12 @@ def set_call_data_file_id( self.call_data = call_data_file_id return self - def set_max_gas_allowed( - self, max_gas_allowed: Optional[int] - ) -> "EthereumTransaction": + def set_max_gas_allowed(self, max_gas_allowed: int | None) -> EthereumTransaction: """ Sets the maximum gas allowed for the transaction execution. Args: - max_gas_allowed (Optional[int]): The maximum gas allowed for the transaction execution. + max_gas_allowed (int | None): The maximum gas allowed for the transaction execution. Returns: EthereumTransaction: This transaction instance. diff --git a/src/hiero_sdk_python/crypto/evm_address.py b/src/hiero_sdk_python/crypto/evm_address.py index 596356859..b776776b9 100644 --- a/src/hiero_sdk_python/crypto/evm_address.py +++ b/src/hiero_sdk_python/crypto/evm_address.py @@ -1,15 +1,18 @@ +from __future__ import annotations + from hiero_sdk_python.crypto.key import Key class EvmAddress(Key): """ - Represents a 20-byte EVM address derived from the rightmost 20 bytes of + Represents a 20-byte EVM address derived from the rightmost 20 bytes of 32 byte Keccak-256 hash of an ECDSA public key. """ + def __init__(self, address_bytes: bytes) -> None: """ Initialize an EvmAddress instance from bytes. - + Args: address_bytes (bytes): A 20-byte sequence representing the EVM address. """ @@ -19,14 +22,12 @@ def __init__(self, address_bytes: bytes) -> None: self.address_bytes: bytes = address_bytes @classmethod - def from_string(cls, evm_address: str) -> "EvmAddress": - """ - Create an EvmAddress from a hex string (with or without '0x' prefix). - """ + def from_string(cls, evm_address: str) -> EvmAddress: + """Create an EvmAddress from a hex string (with or without '0x' prefix).""" if not isinstance(evm_address, str): raise TypeError("evm_address must be a of type string.") - address = evm_address[2:] if evm_address.startswith('0x') else evm_address + address = evm_address[2:] if evm_address.startswith("0x") else evm_address if len(address) == 40: return cls(address_bytes=bytes.fromhex(address)) @@ -34,16 +35,15 @@ def from_string(cls, evm_address: str) -> "EvmAddress": raise ValueError("Invalid hex string for evm_address.") @classmethod - def from_bytes(cls, address_bytes: "bytes") -> "EvmAddress": + def from_bytes(cls, address_bytes: bytes) -> EvmAddress: """Create an EvmAddress from raw bytes.""" return cls(address_bytes) - def to_proto_key(self): raise RuntimeError("to_proto_key() not implemented for EvmAddress") def to_string(self) -> str: - """Return the EVM address as a hex string""" + """Return the EVM address as a hex string.""" return bytes.hex(self.address_bytes) def __str__(self) -> str: diff --git a/src/hiero_sdk_python/crypto/key.py b/src/hiero_sdk_python/crypto/key.py index d08b63620..b85643cf9 100644 --- a/src/hiero_sdk_python/crypto/key.py +++ b/src/hiero_sdk_python/crypto/key.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from abc import ABC, abstractmethod from hiero_sdk_python.hapi.services import basic_types_pb2 @@ -12,7 +14,7 @@ class Key(ABC): """ @classmethod - def from_proto_key(cls, proto: basic_types_pb2.Key) -> "Key": + def from_proto_key(cls, proto: basic_types_pb2.Key) -> Key: """ Convert a protobuf Key message into the appropriate SDK Key object. @@ -37,11 +39,11 @@ def from_proto_key(cls, proto: basic_types_pb2.Key) -> "Key": TypeError: If proto is not a Key protobuf message. ValueError: If the key type is unknown. """ - from hiero_sdk_python.crypto.public_key import PublicKey - from hiero_sdk_python.crypto.evm_address import EvmAddress from hiero_sdk_python.contract.contract_id import ContractId from hiero_sdk_python.contract.delegate_contract_id import DelegateContractId + from hiero_sdk_python.crypto.evm_address import EvmAddress from hiero_sdk_python.crypto.key_list import KeyList + from hiero_sdk_python.crypto.public_key import PublicKey if not isinstance(proto, basic_types_pb2.Key): raise TypeError("proto must be an instance of basic_types_pb2.Key") @@ -77,7 +79,7 @@ def from_proto_key(cls, proto: basic_types_pb2.Key) -> "Key": raise ValueError(f"Unknown key type: {key_type}") @classmethod - def from_bytes(cls, data: bytes) -> "Key": + def from_bytes(cls, data: bytes) -> Key: """ Deserialize a Key object from protobuf-encoded bytes. diff --git a/src/hiero_sdk_python/crypto/key_list.py b/src/hiero_sdk_python/crypto/key_list.py index c0bf8ae00..3eb60e9eb 100644 --- a/src/hiero_sdk_python/crypto/key_list.py +++ b/src/hiero_sdk_python/crypto/key_list.py @@ -1,4 +1,4 @@ -from typing import List +from __future__ import annotations from hiero_sdk_python.crypto.key import Key from hiero_sdk_python.hapi.services import basic_types_pb2 @@ -12,12 +12,12 @@ class KeyList(Key): keys must sign for the transaction to be valid. """ - def __init__(self, keys: List[Key] = None, threshold: int | None = None) -> None: + def __init__(self, keys: list[Key] = None, threshold: int | None = None) -> None: """ Initialize a KeyList. Args: - keys (List[Key], optional): A list of keys that belong to this KeyList. + keys (list[Key], optional): A list of keys that belong to this KeyList. threshold (int, optional): The minimum number of keys required to sign. Raises: @@ -34,15 +34,15 @@ def __init__(self, keys: List[Key] = None, threshold: int | None = None) -> None if threshold is not None and not isinstance(threshold, int): raise TypeError("threshold must be an integer") - self.keys: List[Key] = keys or [] + self.keys: list[Key] = keys or [] self.threshold: int | None = threshold - def set_keys(self, keys: List[Key]) -> "KeyList": + def set_keys(self, keys: list[Key]) -> KeyList: """ Set the keys contained in this KeyList. Args: - keys (List[Key]): The new list of keys. + keys (list[Key]): The new list of keys. Returns: KeyList: The current instance for method chaining. @@ -60,7 +60,7 @@ def set_keys(self, keys: List[Key]) -> "KeyList": self.keys = keys return self - def add_key(self, key: Key) -> "KeyList": + def add_key(self, key: Key) -> KeyList: """ Add a key to the KeyList. @@ -79,7 +79,7 @@ def add_key(self, key: Key) -> "KeyList": self.keys.append(key) return self - def set_threshold(self, threshold: int | None) -> "KeyList": + def set_threshold(self, threshold: int | None) -> KeyList: """ Set the threshold for this KeyList. @@ -99,9 +99,7 @@ def set_threshold(self, threshold: int | None) -> "KeyList": return self @classmethod - def from_proto( - cls, proto: basic_types_pb2.KeyList, threshold: int = None - ) -> "KeyList": + def from_proto(cls, proto: basic_types_pb2.KeyList, threshold: int = None) -> KeyList: """ Create a KeyList from a protobuf representation. @@ -112,10 +110,7 @@ def from_proto( Returns: KeyList: The constructed KeyList instance. """ - keys = [] - for key in proto.keys: - keys.append(Key.from_proto_key(key)) - + keys = [Key.from_proto_key(key) for key in proto.keys] return cls(keys=keys, threshold=threshold) def to_proto(self) -> basic_types_pb2.KeyList: @@ -125,9 +120,7 @@ def to_proto(self) -> basic_types_pb2.KeyList: Returns: basic_types_pb2.KeyList: The protobuf KeyList object. """ - proto_keys = [] - for key in self.keys: - proto_keys.append(key.to_proto_key()) + proto_keys = [key.to_proto_key() for key in self.keys] return basic_types_pb2.KeyList(keys=proto_keys) @@ -141,10 +134,7 @@ def to_proto_key(self) -> basic_types_pb2.Key: Returns: basic_types_pb2.Key: The protobuf Key representation. """ - proto_key_list = [] - - for key in self.keys: - proto_key_list.append(key.to_proto_key()) + proto_key_list = [key.to_proto_key() for key in self.keys] if self.threshold is not None: threshold_key = basic_types_pb2.ThresholdKey( diff --git a/src/hiero_sdk_python/crypto/private_key.py b/src/hiero_sdk_python/crypto/private_key.py index 5f65ade14..5d8f32124 100644 --- a/src/hiero_sdk_python/crypto/private_key.py +++ b/src/hiero_sdk_python/crypto/private_key.py @@ -1,15 +1,22 @@ """This module handles private key operations for ECDSA and Ed25519.""" + +from __future__ import annotations + import warnings -from typing import Optional, Union from cryptography.hazmat.primitives import hashes, serialization -from cryptography.hazmat.primitives.asymmetric import ed25519, ec -from cryptography.hazmat.primitives.asymmetric import utils as asym_utils +from cryptography.hazmat.primitives.asymmetric import ( + ec, + ed25519, + utils as asym_utils, +) + from hiero_sdk_python.crypto.key import Key from hiero_sdk_python.crypto.public_key import PublicKey from hiero_sdk_python.hapi.services import basic_types_pb2 from hiero_sdk_python.utils.crypto_utils import keccak256 + _LEGACY_ECDSA_PRIVATE_KEY_PREFIX = "3030020100300706052b8104000a04220420" @@ -20,16 +27,9 @@ class PrivateKey(Key): Allows generation, signing, and public key derivation. """ - def __init__( - self, - private_key: Union[ec.EllipticCurvePrivateKey, ed25519.Ed25519PrivateKey] - ) -> None: - """ - Initializes a PrivateKey from a cryptography PrivateKey object. - """ - self._private_key: Union[ - ec.EllipticCurvePrivateKey, ed25519.Ed25519PrivateKey - ] = private_key + def __init__(self, private_key: ec.EllipticCurvePrivateKey | ed25519.Ed25519PrivateKey) -> None: + """Initializes a PrivateKey from a cryptography PrivateKey object.""" + self._private_key: ec.EllipticCurvePrivateKey | ed25519.Ed25519PrivateKey = private_key # # --------------------------------- @@ -38,7 +38,7 @@ def __init__( # @classmethod - def from_string(cls, key_str: str) -> "PrivateKey": + def from_string(cls, key_str: str) -> PrivateKey: """ Catch all method. Load a private key from a hex-encoded string. @@ -61,7 +61,7 @@ def from_string(cls, key_str: str) -> "PrivateKey": return cls.from_bytes(key_bytes) @classmethod - def from_string_ed25519(cls, hex_str: str) -> "PrivateKey": + def from_string_ed25519(cls, hex_str: str) -> PrivateKey: """ Interpret the given string as hex-encoded 32-byte Ed25519 private seed. Specific method to aid clarity. @@ -74,7 +74,7 @@ def from_string_ed25519(cls, hex_str: str) -> "PrivateKey": return cls.from_bytes_ed25519(key_bytes) @classmethod - def from_string_ecdsa(cls, hex_str: str) -> "PrivateKey": + def from_string_ecdsa(cls, hex_str: str) -> PrivateKey: """ Interpret the given string as hex-encoded 32-byte ECDSA (secp256k1) private scalar. Specific method to aid clarity. @@ -87,7 +87,7 @@ def from_string_ecdsa(cls, hex_str: str) -> "PrivateKey": return cls.from_bytes_ecdsa(key_bytes) @classmethod - def from_string_der(cls, hex_str: str) -> "PrivateKey": + def from_string_der(cls, hex_str: str) -> PrivateKey: """ Interpret the given string as hex-encoded DER data. Specific method to aid clarity. @@ -106,10 +106,8 @@ def from_string_der(cls, hex_str: str) -> "PrivateKey": # @classmethod - def generate(cls, key_type: str = "ed25519") -> "PrivateKey": - """ - Generate a new private key, defaulting to ed25519. key_type can be "ed25519" or "ecdsa". - """ + def generate(cls, key_type: str = "ed25519") -> PrivateKey: + """Generate a new private key, defaulting to ed25519. key_type can be "ed25519" or "ecdsa".""" if key_type.lower() == "ed25519": return cls.generate_ed25519() if key_type.lower() == "ecdsa": @@ -117,17 +115,13 @@ def generate(cls, key_type: str = "ed25519") -> "PrivateKey": raise ValueError("Invalid key_type. Use 'ed25519' or 'ecdsa'.") @classmethod - def generate_ed25519(cls) -> "PrivateKey": - """ - Generate a new Ed25519 private key. - """ + def generate_ed25519(cls) -> PrivateKey: + """Generate a new Ed25519 private key.""" return cls(ed25519.Ed25519PrivateKey.generate()) @classmethod - def generate_ecdsa(cls) -> "PrivateKey": - """ - Generate a new ECDSA (secp256k1) private key. - """ + def generate_ecdsa(cls) -> PrivateKey: + """Generate a new ECDSA (secp256k1) private key.""" private_key = ec.generate_private_key(ec.SECP256K1()) return cls(private_key) @@ -138,12 +132,12 @@ def generate_ecdsa(cls) -> "PrivateKey": # @classmethod - def from_bytes(cls, key_bytes: bytes) -> "PrivateKey": + def from_bytes(cls, key_bytes: bytes) -> PrivateKey: """ Attempt to load a private key from: - 32-byte Ed25519 seed - 32-byte ECDSA scalar - - DER-encoded private key + - DER-encoded private key. """ # If exactly 32 bytes, we have an ambiguity. Let's try Ed25519, then ECDSA, then DER. if len(key_bytes) == 32: @@ -152,7 +146,7 @@ def from_bytes(cls, key_bytes: bytes) -> "PrivateKey": "If your data is 32 bytes, there's no guaranteed way to distinguish which type. " "Consider using from_bytes_ed25519() or from_bytes_ecdsa() for clarity.", UserWarning, - stacklevel=2 + stacklevel=2, ) ed_priv = cls._try_load_ed25519(key_bytes) @@ -169,12 +163,10 @@ def from_bytes(cls, key_bytes: bytes) -> "PrivateKey": return cls(der_key) # If all attempts failed, raise an error - raise ValueError( - "Failed to load private key from bytes (not Ed25519 seed, ECDSA scalar, or valid DER)." - ) + raise ValueError("Failed to load private key from bytes (not Ed25519 seed, ECDSA scalar, or valid DER).") @staticmethod - def _try_load_ed25519(key_bytes: bytes) -> Optional[ed25519.Ed25519PrivateKey]: + def _try_load_ed25519(key_bytes: bytes) -> ed25519.Ed25519PrivateKey | None: """ Attempt to interpret the given 32 bytes as an Ed25519 private seed. Return the key object on success, or None on failure. @@ -185,7 +177,7 @@ def _try_load_ed25519(key_bytes: bytes) -> Optional[ed25519.Ed25519PrivateKey]: return None @staticmethod - def _try_load_ecdsa(key_bytes: bytes) -> Optional[ec.EllipticCurvePrivateKey]: + def _try_load_ecdsa(key_bytes: bytes) -> ec.EllipticCurvePrivateKey | None: """ Attempt to interpret the given 32 bytes as an ECDSA (secp256k1) private scalar. Return the key object on success, or None on failure. @@ -199,9 +191,7 @@ def _try_load_ecdsa(key_bytes: bytes) -> Optional[ec.EllipticCurvePrivateKey]: return None @staticmethod - def _try_load_der(key_bytes: bytes) -> Optional[Union[ - ed25519.Ed25519PrivateKey, ec.EllipticCurvePrivateKey - ]]: + def _try_load_der(key_bytes: bytes) -> ed25519.Ed25519PrivateKey | ec.EllipticCurvePrivateKey | None: """ Attempt to parse the bytes as a DER-encoded private key. Auto-detect Ed25519 vs. ECDSA(secp256k1). Return None on failure. @@ -218,18 +208,15 @@ def _try_load_der(key_bytes: bytes) -> Optional[Union[ if isinstance(private_key, ed25519.Ed25519PrivateKey): return private_key # Check ECDSA (secp256k1 only) - if isinstance(private_key, ec.EllipticCurvePrivateKey): - if isinstance(private_key.curve, ec.SECP256K1): - return private_key + if isinstance(private_key, ec.EllipticCurvePrivateKey) and isinstance(private_key.curve, ec.SECP256K1): + return private_key return None except Exception: return None @classmethod - def from_bytes_ed25519(cls, seed_32: bytes) -> "PrivateKey": - """ - Interpret 32 bytes as an Ed25519 seed. - """ + def from_bytes_ed25519(cls, seed_32: bytes) -> PrivateKey: + """Interpret 32 bytes as an Ed25519 seed.""" if len(seed_32) != 32: raise ValueError("Ed25519 private key seed must be 32 bytes.") try: @@ -238,10 +225,8 @@ def from_bytes_ed25519(cls, seed_32: bytes) -> "PrivateKey": raise ValueError(f"Could not load Ed25519 private key from seed: {e}") from e @classmethod - def from_bytes_ecdsa(cls, scalar_32: bytes) -> "PrivateKey": - """ - Interpret 32 bytes as an ECDSA secp256k1 private scalar. - """ + def from_bytes_ecdsa(cls, scalar_32: bytes) -> PrivateKey: + """Interpret 32 bytes as an ECDSA secp256k1 private scalar.""" if len(scalar_32) != 32: raise ValueError("ECDSA private key scalar must be 32 bytes.") try: @@ -255,7 +240,7 @@ def from_bytes_ecdsa(cls, scalar_32: bytes) -> "PrivateKey": raise ValueError(f"Could not load ECDSA private key from scalar: {e}") from e @classmethod - def from_der(cls, der_data: bytes) -> "PrivateKey": + def from_der(cls, der_data: bytes) -> PrivateKey: """ Interpret bytes as a DER-encoded private key. Auto-detect Ed25519 vs. ECDSA(secp256k1). @@ -297,20 +282,16 @@ def sign(self, data: bytes) -> bytes: if isinstance(self._private_key, ed25519.Ed25519PrivateKey): # Ed25519 automatically handles the hashing internally return self._private_key.sign(data) - + data_hash = keccak256(data) signature_der = self._private_key.sign(data_hash, ec.ECDSA(asym_utils.Prehashed(hashes.SHA256()))) r, s = asym_utils.decode_dss_signature(signature_der) - signature = r.to_bytes(32, "big") + s.to_bytes(32, "big") - return signature + return r.to_bytes(32, "big") + s.to_bytes(32, "big") def public_key(self) -> PublicKey: - """ - Derive the public key from this private key. - """ + """Derive the public key from this private key.""" return PublicKey(self._private_key.public_key()) - # # --------------------------------- # Serialization @@ -318,9 +299,7 @@ def public_key(self) -> PublicKey: # def to_bytes_raw(self) -> bytes: - """ - Return the raw 32-byte seed (Ed25519) or 32-byte scalar (ECDSA). - """ + """Return the raw 32-byte seed (Ed25519) or 32-byte scalar (ECDSA).""" if self.is_ed25519(): return self.to_bytes_ed25519_raw() if self.is_ecdsa(): @@ -328,9 +307,7 @@ def to_bytes_raw(self) -> bytes: raise ValueError("Unknown key type; cannot extract raw bytes.") def to_bytes_ed25519_raw(self) -> bytes: - """ - Return the raw 32-byte Ed25519 seed. - """ + """Return the raw 32-byte Ed25519 seed.""" if not self.is_ed25519(): raise ValueError("Not an Ed25519 key.") return self._private_key.private_bytes( @@ -340,55 +317,42 @@ def to_bytes_ed25519_raw(self) -> bytes: ) def to_bytes_ecdsa_raw(self) -> bytes: - """ - Return the raw 32-byte ECDSA (secp256k1) scalar. - """ + """Return the raw 32-byte ECDSA (secp256k1) scalar.""" if not isinstance(self._private_key, ec.EllipticCurvePrivateKey): raise TypeError("Not an ECDSA (secp256k1) key.") - return self._private_key.private_numbers()\ - .private_value.to_bytes(32, "big") + return self._private_key.private_numbers().private_value.to_bytes(32, "big") def to_bytes_der(self) -> bytes: - """ - Return the DER-encoded private key. - """ + """Return the DER-encoded private key.""" if self.is_ed25519(): # Ed25519 only supports PKCS#8 for DER exports return self._private_key.private_bytes( encoding=serialization.Encoding.DER, format=serialization.PrivateFormat.PKCS8, - encryption_algorithm=serialization.NoEncryption() + encryption_algorithm=serialization.NoEncryption(), ) # ECDSA can be exported in Traditional OpenSSL or PKCS#8 return self._private_key.private_bytes( encoding=serialization.Encoding.DER, format=serialization.PrivateFormat.TraditionalOpenSSL, - encryption_algorithm=serialization.NoEncryption() - ) + encryption_algorithm=serialization.NoEncryption(), + ) def to_string_raw(self) -> str: - """ - Returns the private key as a hex string (raw). - """ + """Returns the private key as a hex string (raw).""" return self.to_bytes_raw().hex() def to_string_ed25519_raw(self) -> str: - """ - Returns the Ed25519 private key as a hex string (raw). - """ + """Returns the Ed25519 private key as a hex string (raw).""" return self.to_bytes_ed25519_raw().hex() def to_string_ecdsa_raw(self) -> str: - """ - Returns the ECDSA private key as a hex string (raw). - """ + """Returns the ECDSA private key as a hex string (raw).""" return self.to_bytes_ecdsa_raw().hex() def to_string_der(self) -> str: - """ - Returns the DER-encoded private key as a hex string. - """ + """Returns the DER-encoded private key as a hex string.""" return self.to_bytes_der().hex() def to_string(self) -> str: @@ -397,6 +361,7 @@ def to_string(self) -> str: Matches old usage that calls to_string(). """ return self.to_string_raw() + # # --------------------------------- # is_ed25519 / is_ecdsa checks @@ -428,7 +393,7 @@ def __repr__(self) -> str: # --------------------------------- # @staticmethod - def _parse_legacy_ecdsa_der_key(key_bytes: bytes) -> "ec.EllipticCurvePrivateKey": + def _parse_legacy_ecdsa_der_key(key_bytes: bytes) -> ec.EllipticCurvePrivateKey: """ Parse a legacy ECDSA private key from DER-encoded bytes. @@ -447,9 +412,7 @@ def _parse_legacy_ecdsa_der_key(key_bytes: bytes) -> "ec.EllipticCurvePrivateKey raise ValueError("Missing legacy ECDSA prefix") # Remove the legacy prefix - hex_without_prefix = key_bytes.hex().removeprefix( - _LEGACY_ECDSA_PRIVATE_KEY_PREFIX - ) + hex_without_prefix = key_bytes.hex().removeprefix(_LEGACY_ECDSA_PRIVATE_KEY_PREFIX) try: raw_key_bytes = bytes.fromhex(hex_without_prefix) @@ -458,9 +421,7 @@ def _parse_legacy_ecdsa_der_key(key_bytes: bytes) -> "ec.EllipticCurvePrivateKey # ECDSA private keys must be exactly 32 bytes if len(raw_key_bytes) != 32: - raise ValueError( - f"Invalid key length: {len(raw_key_bytes)} bytes (expected 32)" - ) + raise ValueError(f"Invalid key length: {len(raw_key_bytes)} bytes (expected 32)") private_int = int.from_bytes(raw_key_bytes, "big") @@ -471,20 +432,18 @@ def _parse_legacy_ecdsa_der_key(key_bytes: bytes) -> "ec.EllipticCurvePrivateKey return ec.derive_private_key(private_int, ec.SECP256K1()) except Exception as exc: raise ValueError(f"Failed to derive ECDSA private key: {exc}") from exc - + def to_proto_key(self) -> basic_types_pb2.Key: """ Convert the instance of PrivateKey to the protobuf object of Key. - + Returns: basic_types_pb2.Key: The protobuf object of Key. """ return self.public_key().to_proto_key() - + def __eq__(self, other: object) -> bool: - """ - Compare two PrivateKey objects for equality. - """ + """Compare two PrivateKey objects for equality.""" if not isinstance(other, PrivateKey): return NotImplemented @@ -493,8 +452,7 @@ def __eq__(self, other: object) -> bool: return False return self.to_bytes_raw() == other.to_bytes_raw() - + def __hash__(self) -> int: """Returns the hash value for the private key.""" return hash((self.is_ed25519(), self.to_bytes_raw())) - \ No newline at end of file diff --git a/src/hiero_sdk_python/crypto/public_key.py b/src/hiero_sdk_python/crypto/public_key.py index 7142d7b11..18269f379 100644 --- a/src/hiero_sdk_python/crypto/public_key.py +++ b/src/hiero_sdk_python/crypto/public_key.py @@ -1,40 +1,43 @@ -"""This module handles Public key operations""" +"""This module handles Public key operations.""" + +from __future__ import annotations + import warnings -from typing import Union -from cryptography.hazmat.primitives.asymmetric import ed25519, ec -from cryptography.hazmat.primitives import serialization, hashes -from cryptography.hazmat.primitives.asymmetric import utils as asym_utils +from cryptography.hazmat.primitives import hashes, serialization +from cryptography.hazmat.primitives.asymmetric import ( + ec, + ed25519, + utils as asym_utils, +) + from hiero_sdk_python.contract.contract_id import ContractId from hiero_sdk_python.crypto.evm_address import EvmAddress from hiero_sdk_python.crypto.key import Key from hiero_sdk_python.hapi.services import basic_types_pb2 from hiero_sdk_python.utils.crypto_utils import keccak256 + def _warn_ed25519_ambiguity(caller_name: str) -> None: warnings.warn( f"{caller_name}: cannot distinguish Ed25519 private seeds from public keys. " "If using Ed25519, ensure you truly have a public key.", UserWarning, - stacklevel=3 + stacklevel=3, ) + class PublicKey(Key): """ Represents a public key. Supports multiple key formats: raw bytes, DER-encoded keys, hex strings, and a protobuf (“proto”) representation. - + """ - def __init__( - self, - public_key: Union[ec.EllipticCurvePublicKey, ed25519.Ed25519PublicKey] - ) -> None: - """ - Initializes a PublicKey from a cryptography PublicKey object. - """ - self._public_key: Union[ec.EllipticCurvePublicKey, ed25519.Ed25519PublicKey] = public_key + def __init__(self, public_key: ec.EllipticCurvePublicKey | ed25519.Ed25519PublicKey) -> None: + """Initializes a PublicKey from a cryptography PublicKey object.""" + self._public_key: ec.EllipticCurvePublicKey | ed25519.Ed25519PublicKey = public_key # # --------------------------------- @@ -45,10 +48,8 @@ def __init__( # @classmethod - def from_bytes(cls, pub: bytes) -> "PublicKey": - """ - Catch-all method to load public keys from bytes (Ed25519, ECDSA, or DER). - """ + def from_bytes(cls, pub: bytes) -> PublicKey: + """Catch-all method to load public keys from bytes (Ed25519, ECDSA, or DER).""" _warn_ed25519_ambiguity("PublicKey.from_bytes") # 1) 32-byte raw ⇒ Ed25519 @@ -66,7 +67,7 @@ def from_bytes(cls, pub: bytes) -> "PublicKey": raise ValueError("Failed to load public key") from exc @classmethod - def from_bytes_ed25519(cls, pub: bytes) -> "PublicKey": + def from_bytes_ed25519(cls, pub: bytes) -> PublicKey: """ Load an Ed25519 public key from public raw bytes. @@ -84,7 +85,7 @@ def from_bytes_ed25519(cls, pub: bytes) -> "PublicKey": return cls._from_bytes_ed25519(pub) @classmethod - def _from_bytes_ed25519(cls, pub: bytes) -> "PublicKey": + def _from_bytes_ed25519(cls, pub: bytes) -> PublicKey: """ Load an Ed25519 public key from public raw bytes. @@ -110,9 +111,8 @@ def _from_bytes_ed25519(cls, pub: bytes) -> "PublicKey": # 3) Return the validated public key return cls(ed_pub) - @classmethod - def from_bytes_ecdsa(cls, pub: bytes) -> "PublicKey": + def from_bytes_ecdsa(cls, pub: bytes) -> PublicKey: """ Load a secp256k1 ECDSA public key from raw bytes. @@ -135,9 +135,7 @@ def from_bytes_ecdsa(cls, pub: bytes) -> "PublicKey": # 1) Enforce valid public bytes point lengths # (does not allow private-key scalars which are 32 bytes) if len(pub) not in (33, 65): - raise ValueError( - f"ECDSA (secp256k1) public key must be 33 or 65 bytes, got {len(pub)}." - ) + raise ValueError(f"ECDSA (secp256k1) public key must be 33 or 65 bytes, got {len(pub)}.") # 2) Delegate to cryptography ec library for point decoding and validation try: @@ -150,31 +148,31 @@ def from_bytes_ecdsa(cls, pub: bytes) -> "PublicKey": return cls(ec_pub) @classmethod - def from_der(cls, der_bytes: bytes) -> "PublicKey": + def from_der(cls, der_bytes: bytes) -> PublicKey: """ Load a public key from DER-encoded SubjectPublicKeyInfo. - This method automatically detects Ed25519 vs. secp256k1 ECDSA - based on the OID found in the DER bytes. It strictly expects + This method automatically detects Ed25519 vs. secp256k1 ECDSA + based on the OID found in the DER bytes. It strictly expects a DER-encoded **public** key (SubjectPublicKeyInfo). - - The `cryptography` library's `load_der_public_key` function + + The `cryptography` library's `load_der_public_key` function rejects DER-encoded private keys, as their structure differs. *Warning* a ed25519 private key incorrectly passed as a public key - and became DER-encoded will still be processed as a public key. + and became DER-encoded will still be processed as a public key. Args: der_bytes (bytes): DER-encoded SubjectPublicKeyInfo. Returns: - PublicKey: A wrapped key of the detected algorithm + PublicKey: A wrapped key of the detected algorithm (Ed25519 or secp256k1 ECDSA). Raises: - ValueError: If the DER bytes cannot be parsed as a public - key, or if the curve/algorithm is unsupported. This - includes cases where a private key is passed instead of a + ValueError: If the DER bytes cannot be parsed as a public + key, or if the curve/algorithm is unsupported. This + includes cases where a private key is passed instead of a public key. """ try: @@ -202,10 +200,8 @@ def from_der(cls, der_bytes: bytes) -> "PublicKey": # @classmethod - def from_string_ed25519(cls, hex_str: str) -> "PublicKey": - """ - Interpret the given string as a hex-encoded 32-byte Ed25519 public key. - """ + def from_string_ed25519(cls, hex_str: str) -> PublicKey: + """Interpret the given string as a hex-encoded 32-byte Ed25519 public key.""" _warn_ed25519_ambiguity("PublicKey.from_string_ed25519") # Sanitizing: The "0x" prefix denotes that a string represents a hex number. @@ -221,15 +217,16 @@ def from_string_ed25519(cls, hex_str: str) -> "PublicKey": return cls._from_bytes_ed25519(pub) @classmethod - def from_string_ecdsa(cls, hex_str: str) -> "PublicKey": + def from_string_ecdsa(cls, hex_str: str) -> PublicKey: """ - Interpret the given string as a hex-encoded compressed/uncompressed + Interpret the given string as a hex-encoded compressed/uncompressed ECDSA pubkey (33/65 bytes). - + Sanitizing. The "0x" prefix is used to denote that a string - represents a hexadecimal number. - The "0x" itself isn't part of the binary or numerical - value represented by the hexadecimal string""" + The "0x" itself isn't part of the binary or numerical + value represented by the hexadecimal string + """ hex_str = hex_str.removeprefix("0x") try: # Python's .fromhex will throw if the hex string is malformed @@ -239,10 +236,8 @@ def from_string_ecdsa(cls, hex_str: str) -> "PublicKey": return cls.from_bytes_ecdsa(pub) @classmethod - def from_string_der(cls, hex_str: str) -> "PublicKey": - """ - Interpret the given string as hex-encoded DER bytes containing a public key. - """ + def from_string_der(cls, hex_str: str) -> PublicKey: + """Interpret the given string as hex-encoded DER bytes containing a public key.""" # Sanitizing. The "0x" prefix is used to denote that a string # represents a hexadecimal number. # The "0x" itself isn't part of the binary or @@ -263,15 +258,15 @@ def from_string_der(cls, hex_str: str) -> "PublicKey": # @classmethod - def from_string(cls, hex_str: str) -> "PublicKey": + def from_string(cls, hex_str: str) -> PublicKey: """ Load a *public* key from a hex-encoded string. Catch-all method supporting: - + - Ed25519 raw (32 bytes → 64 hex chars) - secp256k1 ECDSA compressed (33 bytes → 66 hex chars) - secp256k1 ECDSA uncompressed (65 bytes → 130 hex chars) - DER-encoded SPKI (variable length) - + *Warning*: Raw Ed25519 private seeds are also 32 bytes and will be treated as valid public keys here. """ @@ -307,10 +302,8 @@ def from_string(cls, hex_str: str) -> "PublicKey": # @classmethod - def _from_proto(cls, proto: basic_types_pb2.Key) -> "PublicKey": - """ - Load a public key from a protobuf Key message. - """ + def _from_proto(cls, proto: basic_types_pb2.Key) -> PublicKey: + """Load a public key from a protobuf Key message.""" if proto.ed25519: return cls._from_bytes_ed25519(proto.ed25519) if proto.ECDSA_secp256k1: @@ -335,18 +328,17 @@ def _to_proto(self) -> Key: Returns: Key: The protobuf Key message. """ - # get the raw public-key bytes (32/33/65 bytes depending on type) pub_bytes = self.to_bytes_raw() if self.is_ed25519(): return basic_types_pb2.Key(ed25519=pub_bytes) return basic_types_pb2.Key(ECDSA_secp256k1=pub_bytes) - + def to_proto_key(self) -> basic_types_pb2.Key: """ Convert the instance of PublicKey to the protobuf object of Key. - + Returns: basic_types_pb2.Key: The protobuf object of Key. """ @@ -359,15 +351,11 @@ def to_proto_key(self) -> basic_types_pb2.Key: # def is_ed25519(self) -> bool: - """ - Checks if this key (private or public) is Ed25519. - """ + """Checks if this key (private or public) is Ed25519.""" return isinstance(self._public_key, ed25519.Ed25519PublicKey) def is_ecdsa(self) -> bool: - """ - Checks if this public key is ECDSA (secp256k1). - """ + """Checks if this public key is ECDSA (secp256k1).""" return isinstance(self._public_key, ec.EllipticCurvePublicKey) # @@ -378,7 +366,7 @@ def is_ecdsa(self) -> bool: def to_bytes_raw(self) -> bytes: """ - Catch-all for convenience. + Catch-all for convenience. Serialize this PublicKey into its raw, algorithm-specific byte form. Ed25519 keys @@ -389,12 +377,12 @@ def to_bytes_raw(self) -> bytes: ECDSA (secp256k1) keys ------------------------ - Returns the **compressed SEC1** form (33 bytes): - 1. A 1-byte prefix (0x02 or 0x03) indicating the parity of the Y coordinate - 2. A 32-byte big-endian X coordinate + 1. A 1-byte prefix (0x02 or 0x03) indicating the parity of the Y coordinate + 2. A 32-byte big-endian X coordinate Returns: - bytes: - - If `is_ed25519() == True`, a 32-byte Ed25519 point. + bytes: + - If `is_ed25519() == True`, a 32-byte Ed25519 point. - Otherwise, a 33-byte compressed secp256k1 point. """ if self.is_ed25519(): @@ -407,31 +395,22 @@ def to_bytes_ed25519(self) -> bytes: Specific name for clarity. Returns the Ed25519 public key in 32-bytes raw form. """ - return self._public_key.public_bytes( - encoding=serialization.Encoding.Raw, - format=serialization.PublicFormat.Raw - ) + return self._public_key.public_bytes(encoding=serialization.Encoding.Raw, format=serialization.PublicFormat.Raw) def to_bytes_ecdsa(self, compressed: bool = True) -> bytes: """ Specific name for clarity. Returns the ECDSA public key in compressed or uncompressed form. """ - format_ = (serialization.PublicFormat.CompressedPoint - if compressed - else serialization.PublicFormat.UncompressedPoint) - return self._public_key.public_bytes( - encoding=serialization.Encoding.X962, - format=format_ + format_ = ( + serialization.PublicFormat.CompressedPoint if compressed else serialization.PublicFormat.UncompressedPoint ) + return self._public_key.public_bytes(encoding=serialization.Encoding.X962, format=format_) def to_bytes_der(self) -> bytes: - """ - Returns the DER-encoded public key. - """ + """Returns the DER-encoded public key.""" return self._public_key.public_bytes( - encoding=serialization.Encoding.DER, - format=serialization.PublicFormat.SubjectPublicKeyInfo + encoding=serialization.Encoding.DER, format=serialization.PublicFormat.SubjectPublicKeyInfo ) # @@ -443,14 +422,14 @@ def to_bytes_der(self) -> bytes: def to_string_ed25519(self) -> str: """ Specific naming for clarity. - Returns the Ed25519 public key as raw hex-encoded public key + Returns the Ed25519 public key as raw hex-encoded public key. """ return self.to_bytes_ed25519().hex() def to_string_ecdsa(self) -> str: """ Specific naming for clarity. - Returns the ECDSA public key as raw hex-encoded public key + Returns the ECDSA public key as raw hex-encoded public key. """ return self.to_bytes_ecdsa().hex() @@ -479,23 +458,23 @@ def to_string(self) -> str: Matches old usage that calls to_string(). """ return self.to_string_raw() - + def to_evm_address(self): """ Derives the EVM address corresponding to this ECDSA public key. - + Note: This address derivation is valid only for ECDSA secp256k1 keys. Calling this method on an Ed25519 key will raise a ValueError. - + Returns: EvmAddress: The derived EVM address. """ if self.is_ed25519(): raise ValueError("Cannot derive an EVM address from an Ed25519 key.") - + uncompressed_bytes = self.to_bytes_ecdsa(compressed=False) - keccak_bytes = keccak256(uncompressed_bytes[1:]) + keccak_bytes = keccak256(uncompressed_bytes[1:]) evm_address = keccak_bytes[-20:] return EvmAddress.from_bytes(evm_address) @@ -523,9 +502,7 @@ def verify(self, signature: bytes, data: bytes) -> None: return self.verify_ecdsa(signature, data) def verify_ed25519(self, signature: bytes, data: bytes) -> None: - """ - Verify an Ed25519 signature for clarity purposes. Raises InvalidSignature on failure. - """ + """Verify an Ed25519 signature for clarity purposes. Raises InvalidSignature on failure.""" if not isinstance(self._public_key, ed25519.Ed25519PublicKey): raise TypeError("Not an Ed25519 key") # Ed25519 has no external hash; the library does it internally. @@ -544,7 +521,7 @@ def verify_ecdsa(self, signature: bytes, data: bytes) -> None: """ if not isinstance(self._public_key, ec.EllipticCurvePublicKey): raise TypeError("Not an ECDSA key") - + # Convert raw 64-byte signature to DER format if len(signature) == 64: r = int.from_bytes(signature[:32], "big") @@ -552,22 +529,18 @@ def verify_ecdsa(self, signature: bytes, data: bytes) -> None: signature_der = asym_utils.encode_dss_signature(r, s) else: signature_der = signature - + data_hash = keccak256(data) self._public_key.verify(signature_der, data_hash, ec.ECDSA(asym_utils.Prehashed(hashes.SHA256()))) def __repr__(self) -> str: - """ - Returns a string representation of the PublicKey. - """ + """Returns a string representation of the PublicKey.""" if self.is_ed25519(): return f"" return f"" - + def __eq__(self, other: object) -> bool: - """ - Compare two PublicKey objects for equality. - """ + """Compare two PublicKey objects for equality.""" if not isinstance(other, PublicKey): return NotImplemented @@ -576,7 +549,7 @@ def __eq__(self, other: object) -> bool: return False return self.to_bytes_raw() == other.to_bytes_raw() - + def __hash__(self) -> int: """Returns the hash value for the public key.""" return hash((self.is_ed25519(), self.to_bytes_raw())) diff --git a/src/hiero_sdk_python/exceptions.py b/src/hiero_sdk_python/exceptions.py index c02927f70..52ff7eb67 100644 --- a/src/hiero_sdk_python/exceptions.py +++ b/src/hiero_sdk_python/exceptions.py @@ -1,20 +1,20 @@ -from __future__ import annotations -from typing import TYPE_CHECKING, Optional, Union +from __future__ import annotations + +from typing import TYPE_CHECKING from hiero_sdk_python.response_code import ResponseCode + if TYPE_CHECKING: - from hiero_sdk_python import ( - TransactionId, - TransactionReceipt - ) + from hiero_sdk_python import TransactionId, TransactionReceipt + class PrecheckError(Exception): """ Exception thrown when a transaction fails its precheck validation. - + This occurs before the transaction reaches consensus. - + Attributes: status (ResponseCode): The precheck status code. transaction_id (TransactionId): The ID of the transaction that failed. @@ -23,9 +23,9 @@ class PrecheckError(Exception): def __init__( self, - status: Union[ResponseCode, int], - transaction_id: Optional[TransactionId] = None, - message: Optional[str] = None, + status: ResponseCode | int, + transaction_id: TransactionId | None = None, + message: str | None = None, ) -> None: self.status = ResponseCode(status) self.transaction_id = transaction_id @@ -57,7 +57,7 @@ class MaxAttemptsError(Exception): last_error (BaseException): The last error that occurred during the final attempt """ - def __init__(self, message: str, node_id: str, last_error: Optional[BaseException] = None) -> None: + def __init__(self, message: str, node_id: str, last_error: BaseException | None = None) -> None: self.node_id = node_id self.last_error = last_error @@ -79,25 +79,25 @@ def __repr__(self) -> str: class ReceiptStatusError(Exception): """ Exception raised when a transaction receipt contains an error status. - + Attributes: status (ResponseCode): The error status code from the receipt transaction_id (TransactionId): The ID of the transaction that failed (Optional) transaction_receipt (TransactionReceipt): The receipt containing the error status message (str): The error message describing the failure """ - + def __init__( - self, - status: Union[ResponseCode, int], - transaction_id: Optional[TransactionId], - transaction_receipt: TransactionReceipt, - message: Optional[str] = None + self, + status: ResponseCode | int, + transaction_id: TransactionId | None, + transaction_receipt: TransactionReceipt, + message: str | None = None, ) -> None: self.status = ResponseCode(status) self.transaction_id = transaction_id self.transaction_receipt = transaction_receipt - + # Build a default message if none provided if message is None: status_name = self.status.name @@ -105,12 +105,12 @@ def __init__( message = f"Receipt for transaction {transaction_id} contained error status: {status_name} ({int(self.status)})" else: message = f"Receipt contained error status: {status_name} ({int(self.status)})" - + self.message = message super().__init__(self.message) - + def __str__(self) -> str: return self.message - + def __repr__(self) -> str: - return f"ReceiptStatusError(status={self.status}, transaction_id={self.transaction_id})" \ No newline at end of file + return f"ReceiptStatusError(status={self.status}, transaction_id={self.transaction_id})" diff --git a/src/hiero_sdk_python/executable.py b/src/hiero_sdk_python/executable.py index 0a81893fa..145311d46 100644 --- a/src/hiero_sdk_python/executable.py +++ b/src/hiero_sdk_python/executable.py @@ -1,20 +1,24 @@ +from __future__ import annotations + import math import re import time -from typing import Callable, Optional, Any, TYPE_CHECKING, List, Union +import warnings from abc import ABC, abstractmethod +from collections.abc import Callable from enum import IntEnum -import warnings +from typing import TYPE_CHECKING, Any import grpc +from hiero_sdk_python.account.account_id import AccountId from hiero_sdk_python.channels import _Channel from hiero_sdk_python.exceptions import MaxAttemptsError -from hiero_sdk_python.account.account_id import AccountId from hiero_sdk_python.hapi.services import query_pb2, transaction_pb2 from hiero_sdk_python.logger.logger import Logger from hiero_sdk_python.response_code import ResponseCode + if TYPE_CHECKING: from hiero_sdk_python.client.client import Client @@ -34,8 +38,8 @@ class _Method: def __init__( self, - query_func: Optional[Callable[..., Any]] = None, - transaction_func: Optional[Callable[..., Any]] = None, + query_func: Callable[..., Any] | None = None, + transaction_func: Callable[..., Any] | None = None, ): """ Initialize a Method instance with the appropriate callable functions. @@ -76,24 +80,24 @@ class _Executable(ABC): """ def __init__(self): - self._max_attempts: Optional[int] = None - self._max_backoff: Optional[float] = None - self._min_backoff: Optional[float] = None - self._grpc_deadline: Optional[float] = None - self._request_timeout: Optional[float] = None + self._max_attempts: int | None = None + self._max_backoff: float | None = None + self._min_backoff: float | None = None + self._grpc_deadline: float | None = None + self._request_timeout: float | None = None - self.node_account_id: Optional[AccountId] = None - self.node_account_ids: List[AccountId] = [] + self.node_account_id: AccountId | None = None + self.node_account_ids: list[AccountId] = [] - self._used_node_account_id: Optional[AccountId] = None + self._used_node_account_id: AccountId | None = None self._node_account_ids_index: int = 0 - def set_node_account_ids(self, node_account_ids: List[AccountId]): + def set_node_account_ids(self, node_account_ids: list[AccountId]): """ Explicitly set the node account IDs to execute against. Args: - node_account_ids (List[AccountId]): List of node account IDs + node_account_ids (list[AccountId]): List of node account IDs Returns: The current instance of the class for chaining. @@ -125,9 +129,7 @@ def set_max_attempts(self, max_attempts: int): The current instance of the class for chaining. """ if isinstance(max_attempts, bool) or not isinstance(max_attempts, int): - raise TypeError( - f"max_attempts must be of type int, got {(type(max_attempts).__name__)}" - ) + raise TypeError(f"max_attempts must be of type int, got {(type(max_attempts).__name__)}") if max_attempts <= 0: raise ValueError("max_attempts must be greater than 0") @@ -135,23 +137,19 @@ def set_max_attempts(self, max_attempts: int): self._max_attempts = max_attempts return self - def set_grpc_deadline(self, grpc_deadline: Union[int, float]): + def set_grpc_deadline(self, grpc_deadline: int | float): """ Set the gRPC call deadline (per attempt). Args: - grpc_deadline (Union[int,float]): gRPC deadline in seconds. + grpc_deadline (int | float): gRPC deadline in seconds. Must be greater than zero. Returns: The current instance of the class for chaining. """ - if isinstance(grpc_deadline, bool) or not isinstance( - grpc_deadline, (float, int) - ): - raise TypeError( - f"grpc_deadline must be of type Union[int, float], got {type(grpc_deadline).__name__}" - ) + if isinstance(grpc_deadline, bool) or not isinstance(grpc_deadline, (float, int)): + raise TypeError(f"grpc_deadline must be of type Union[int, float], got {type(grpc_deadline).__name__}") if not math.isfinite(grpc_deadline) or grpc_deadline <= 0: raise ValueError("grpc_deadline must be a finite value greater than 0") @@ -161,12 +159,13 @@ def set_grpc_deadline(self, grpc_deadline: Union[int, float]): "grpc_deadline should be smaller than request_timeout. " "This configuration may cause operations to fail unexpectedly.", UserWarning, + stacklevel=2, ) self._grpc_deadline = float(grpc_deadline) return self - def set_request_timeout(self, request_timeout: Union[int, float]): + def set_request_timeout(self, request_timeout: int | float): """ Set the total execution timeout for this operation. @@ -177,12 +176,8 @@ def set_request_timeout(self, request_timeout: Union[int, float]): Returns: The current instance of the class for chaining. """ - if isinstance(request_timeout, bool) or not isinstance( - request_timeout, (float, int) - ): - raise TypeError( - f"request_timeout must be of type Union[int, float], got {type(request_timeout).__name__}" - ) + if isinstance(request_timeout, bool) or not isinstance(request_timeout, (float, int)): + raise TypeError(f"request_timeout must be of type Union[int, float], got {type(request_timeout).__name__}") if not math.isfinite(request_timeout) or request_timeout <= 0: raise ValueError("request_timeout must be a finite value greater than 0") @@ -192,26 +187,25 @@ def set_request_timeout(self, request_timeout: Union[int, float]): "request_timeout should be larger than grpc_deadline. " "This configuration may cause operations to fail unexpectedly.", UserWarning, + stacklevel=2, ) self._request_timeout = float(request_timeout) return self - def set_min_backoff(self, min_backoff: Union[int, float]): + def set_min_backoff(self, min_backoff: int | float): """ Set the minimum backoff delay between retries. Args: - min_backoff ((Union[int,float]): Minimum backoff delay in seconds. + min_backoff (int | float): Minimum backoff delay in seconds. Must be finite and non-negative. Returns: The current instance of the class for chaining. """ if isinstance(min_backoff, bool) or not isinstance(min_backoff, (int, float)): - raise TypeError( - f"min_backoff must be of type int or float, got {(type(min_backoff).__name__)}" - ) + raise TypeError(f"min_backoff must be of type int or float, got {(type(min_backoff).__name__)}") if not math.isfinite(min_backoff) or min_backoff < 0: raise ValueError("min_backoff must be a finite value >= 0") @@ -222,21 +216,19 @@ def set_min_backoff(self, min_backoff: Union[int, float]): self._min_backoff = float(min_backoff) return self - def set_max_backoff(self, max_backoff: Union[int, float]): + def set_max_backoff(self, max_backoff: int | float): """ Set the maximum backoff delay between retries. Args: - max_backoff (Union[int,float]): Maximum backoff delay in seconds. + max_backoff (int | float): Maximum backoff delay in seconds. Must be finite and greater than or equal to min_backoff. Returns: The current instance of the class for chaining. """ if isinstance(max_backoff, bool) or not isinstance(max_backoff, (int, float)): - raise TypeError( - f"max_backoff must be of type int or float, got {(type(max_backoff).__name__)}" - ) + raise TypeError(f"max_backoff must be of type int or float, got {(type(max_backoff).__name__)}") if not math.isfinite(max_backoff) or max_backoff < 0: raise ValueError("max_backoff must be a finite value >= 0") @@ -247,7 +239,7 @@ def set_max_backoff(self, max_backoff: Union[int, float]): self._max_backoff = float(max_backoff) return self - def _select_node_account_id(self) -> Optional[AccountId]: + def _select_node_account_id(self) -> AccountId | None: """ Select the next node account ID from node_account_ids in a round-robin fashion. @@ -256,9 +248,7 @@ def _select_node_account_id(self) -> Optional[AccountId]: """ if self.node_account_ids: # Use modulo to cycle through the list - selected = self.node_account_ids[ - self._node_account_ids_index % len(self.node_account_ids) - ] + selected = self.node_account_ids[self._node_account_ids_index % len(self.node_account_ids)] self._used_node_account_id = selected return selected return None @@ -333,15 +323,11 @@ def _map_response(self, response, node_id, proto_request): raise NotImplementedError("_map_response must be implemented by subclasses") def _get_request_id(self): - """ - Format the request ID for the logger. - """ + """Format the request ID for the logger.""" return f"{self.__class__.__name__}:{time.time_ns()}" - def _resolve_execution_config(self, client: "Client", timeout: Optional[Union[int, float]]) -> None: - """ - Resolve unset execution configuration from the Client defaults. - """ + def _resolve_execution_config(self, client: Client, timeout: int | float | None) -> None: + """Resolve unset execution configuration from the Client defaults.""" # Set request_timeout explicitly set via set_request_timeout() # If not set use timeout passed to execute() # Else clients default request_timeout @@ -362,9 +348,7 @@ def _resolve_execution_config(self, client: "Client", timeout: Optional[Union[in # nodes to which the executaion must be run against, if not provided used nodes from client if not self.node_account_ids: - self.node_account_ids = [ - node._account_id for node in client.network._healthy_nodes - ] + self.node_account_ids = [node._account_id for node in client.network._healthy_nodes] if not self.node_account_ids: raise RuntimeError("No healthy nodes available for execution") @@ -379,19 +363,16 @@ def _should_retry_exponentially(self, err: Exception) -> bool: grpc.StatusCode.DEADLINE_EXCEEDED, grpc.StatusCode.UNAVAILABLE, grpc.StatusCode.RESOURCE_EXHAUSTED, - ) or ( - err.code() == grpc.StatusCode.INTERNAL - and bool(RST_STREAM.search(err.details())) - ) + ) or (err.code() == grpc.StatusCode.INTERNAL and bool(RST_STREAM.search(err.details()))) return True def _calculate_backoff(self, attempt: int): - """Calculate backoff for the given attempt, attempt start from 0""" + """Calculate backoff for the given attempt, attempt start from 0.""" return min(self._max_backoff, self._min_backoff * (2 ** (attempt + 1))) def _handle_unhealthy_node(self, proto_request, attempt, logger, err) -> bool: - """Handle node switching and backoff for unhealthy node""" + """Handle node switching and backoff for unhealthy node.""" # Check if the request is a transaction receipt or record because they are single node requests if _is_transaction_receipt_or_record_request(proto_request): _delay_for_attempt( @@ -409,13 +390,13 @@ def _handle_unhealthy_node(self, proto_request, attempt, logger, err) -> bool: self._advance_node_index() return True - def _execute(self, client: "Client", timeout: Optional[Union[int, float]] = None): + def _execute(self, client: Client, timeout: int | float | None = None): """ Execute a transaction or query with retry logic. Args: client (Client): The client instance to use for execution - timeout (Optional[Union[int, float]): The total execution timeout (in seconds) for this execution. + timeout (int | float, optional): The total execution timeout (in seconds) for this execution. Precedence as follow: 1. Explicitly set via set_request_timeout() 2. Timeout passed to execute() @@ -448,9 +429,7 @@ def _execute(self, client: "Client", timeout: Optional[Union[int, float]] = None node = client.network._get_node(node_id) if node is None: - raise RuntimeError( - f"No node found for node_account_id: {self.node_account_id}" - ) + raise RuntimeError(f"No node found for node_account_id: {self.node_account_id}") # Store for logging and receipts self.node_account_id = node._account_id @@ -458,7 +437,17 @@ def _execute(self, client: "Client", timeout: Optional[Union[int, float]] = None # Create a channel wrapper from the client's channel channel = node._get_channel() - logger.trace("Executing", "requestId", self._get_request_id(), "nodeAccountID", self.node_account_id, "attempt", attempt + 1, "maxAttempts", self._max_attempts,) + logger.trace( + "Executing", + "requestId", + self._get_request_id(), + "nodeAccountID", + self.node_account_id, + "attempt", + attempt + 1, + "maxAttempts", + self._max_attempts, + ) # Get the appropriate gRPC method to call method = self._get_method(channel) @@ -467,9 +456,7 @@ def _execute(self, client: "Client", timeout: Optional[Union[int, float]] = None proto_request = self._make_request() if not node.is_healthy(): - self._handle_unhealthy_node( - proto_request, attempt, logger, err_persistant - ) + self._handle_unhealthy_node(proto_request, attempt, logger, err_persistant) continue # Execute the GRPC call @@ -493,7 +480,17 @@ def _execute(self, client: "Client", timeout: Optional[Union[int, float]] = None # Determine if we should retry based on the response execution_state = self._should_retry(response) - logger.trace(f"{self.__class__.__name__} status received", "nodeAccountID", self.node_account_id, "network", client.network.network, "state", execution_state.name, "txID", tx_id,) + logger.trace( + f"{self.__class__.__name__} status received", + "nodeAccountID", + self.node_account_id, + "network", + client.network.network, + "state", + execution_state.name, + "txID", + tx_id, + ) # Handle the execution state match execution_state: @@ -521,11 +518,15 @@ def _execute(self, client: "Client", timeout: Optional[Union[int, float]] = None case _ExecutionState.FINISHED: # If the transaction completed successfully, map the response and return it logger.trace(f"{self.__class__.__name__} finished execution") - return self._map_response( - response, self.node_account_id, proto_request - ) + return self._map_response(response, self.node_account_id, proto_request) - logger.error("Exceeded maximum attempts for request", "requestId", self._get_request_id(), "last exception being", err_persistant,) + logger.error( + "Exceeded maximum attempts for request", + "requestId", + self._get_request_id(), + "last exception being", + err_persistant, + ) raise MaxAttemptsError( "Exceeded maximum attempts or request timeout", self.node_account_id, @@ -534,14 +535,12 @@ def _execute(self, client: "Client", timeout: Optional[Union[int, float]] = None def _is_transaction_receipt_or_record_request( - request: Union[transaction_pb2.Transaction, query_pb2.Query], + request: transaction_pb2.Transaction | query_pb2.Query, ) -> bool: if not isinstance(request, query_pb2.Query): return False - return request.HasField("transactionGetReceipt") or request.HasField( - "transactionGetRecord" - ) + return request.HasField("transactionGetReceipt") or request.HasField("transactionGetRecord") def _delay_for_attempt(request_id: str, backoff: float, attempt: int, logger: Logger, error) -> None: @@ -552,7 +551,17 @@ def _delay_for_attempt(request_id: str, backoff: float, attempt: int, logger: Lo attempt (int): The current attempt number (0-based) backoff (float): The current backoff period in seconds """ - logger.trace("Retrying request attempt", "requestId", request_id, "delay", backoff, "attempt", attempt, "error", error,) + logger.trace( + "Retrying request attempt", + "requestId", + request_id, + "delay", + backoff, + "attempt", + attempt, + "error", + error, + ) time.sleep(backoff) @@ -573,6 +582,6 @@ def _execute_method(method, proto_request, timeout: float): """ if method.transaction is not None: return method.transaction(proto_request, timeout=timeout) - elif method.query is not None: + if method.query is not None: return method.query(proto_request, timeout=timeout) raise Exception("No method to execute") diff --git a/src/hiero_sdk_python/file/file_append_transaction.py b/src/hiero_sdk_python/file/file_append_transaction.py index 3265f9884..aebb4ad9b 100644 --- a/src/hiero_sdk_python/file/file_append_transaction.py +++ b/src/hiero_sdk_python/file/file_append_transaction.py @@ -1,5 +1,3 @@ -from __future__ import annotations - """ Represents a file append transaction on the network. @@ -13,56 +11,66 @@ to build and execute a file append transaction. """ +from __future__ import annotations + import math -from typing import TYPE_CHECKING, Any, List, Literal, Optional, Union, overload +from typing import TYPE_CHECKING, Any, Literal, overload + from hiero_sdk_python.file.file_id import FileId -from hiero_sdk_python.hbar import Hbar -from hiero_sdk_python.transaction.transaction import Transaction -from hiero_sdk_python.transaction.transaction_id import TransactionId from hiero_sdk_python.hapi.services import file_append_pb2, timestamp_pb2 from hiero_sdk_python.hapi.services.schedulable_transaction_body_pb2 import ( SchedulableTransactionBody, ) +from hiero_sdk_python.hbar import Hbar +from hiero_sdk_python.transaction.transaction import Transaction +from hiero_sdk_python.transaction.transaction_id import TransactionId + # Use TYPE_CHECKING to avoid circular import errors if TYPE_CHECKING: + from hiero_sdk_python.channels import _Channel from hiero_sdk_python.client import Client from hiero_sdk_python.crypto.private_key import PrivateKey - from hiero_sdk_python.channels import _Channel from hiero_sdk_python.executable import _Method from hiero_sdk_python.transaction.transaction import TransactionReceipt from hiero_sdk_python.transaction.transaction_response import TransactionResponse - - + + # pylint: disable=too-many-instance-attributes class FileAppendTransaction(Transaction): """ Represents a file append transaction on the network. - + This transaction appends data to an existing file on the network. If a file has multiple keys, all keys must sign to modify its contents. - + The transaction supports chunking for large files, automatically breaking content into smaller chunks if the content exceeds the chunk size limit. - + Inherits from the base Transaction class and implements the required methods to build and execute a file append transaction. """ - def __init__(self, file_id: Optional[FileId] = None, contents: Optional[str | bytes] = None, - max_chunks: Optional[int] = None, chunk_size: Optional[int] = None): + + def __init__( + self, + file_id: FileId | None = None, + contents: str | bytes | None = None, + max_chunks: int | None = None, + chunk_size: int | None = None, + ): """ Initializes a new FileAppendTransaction instance with the specified parameters. Args: file_id (Optional[FileId], optional): The ID of the file to append to. - contents (Optional[str | bytes], optional): The contents to append to the file. + contents (Optional[str | bytes], optional): The contents to append to the file. Strings will be automatically encoded as UTF-8 bytes. max_chunks (Optional[int], optional): Maximum number of chunks allowed. Defaults to 20. chunk_size (Optional[int], optional): Size of each chunk in bytes. Defaults to 4096. """ super().__init__() - self.file_id: Optional[FileId] = file_id - self.contents: Optional[bytes] = self._encode_contents(contents) + self.file_id: FileId | None = file_id + self.contents: bytes | None = self._encode_contents(contents) self.max_chunks: int = max_chunks if max_chunks is not None else 20 self.chunk_size: int = chunk_size if chunk_size is not None else 4096 self._default_transaction_fee = Hbar(5).to_tinybars() @@ -70,29 +78,29 @@ def __init__(self, file_id: Optional[FileId] = None, contents: Optional[str | by # Internal tracking for chunking self._current_chunk_index: int = 0 self._total_chunks: int = self._calculate_total_chunks() - self._transaction_ids: List[TransactionId] = [] - self._signing_keys: List["PrivateKey"] = [] # Use string annotation to avoid import issues + self._transaction_ids: list[TransactionId] = [] + self._signing_keys: list[PrivateKey] = [] # Use string annotation to avoid import issues - def _encode_contents(self, contents: Optional[str | bytes]) -> Optional[bytes]: + def _encode_contents(self, contents: str | bytes | None) -> bytes | None: """ Helper method to encode string contents to UTF-8 bytes. - + Args: contents (Optional[str | bytes]): The contents to encode. - + Returns: Optional[bytes]: The encoded contents or None if input is None. """ if contents is None: return None if isinstance(contents, str): - return contents.encode('utf-8') + return contents.encode("utf-8") return contents def _calculate_total_chunks(self) -> int: """ Calculates the total number of chunks needed for the current contents. - + Returns: int: The total number of chunks needed. """ @@ -103,7 +111,7 @@ def _calculate_total_chunks(self) -> int: def get_required_chunks(self) -> int: """ Gets the number of chunks required for the current contents. - + Returns: int: The number of chunks required. """ @@ -123,12 +131,12 @@ def set_file_id(self, file_id: FileId) -> FileAppendTransaction: self.file_id = file_id return self - def set_contents(self, contents: Optional[str | bytes]) -> FileAppendTransaction: + def set_contents(self, contents: str | bytes | None) -> FileAppendTransaction: """ Sets the contents for this file append transaction. Args: - contents (Optional[str | bytes]): The contents to append to the file. + contents (Optional[str | bytes]): The contents to append to the file. Strings will be automatically encoded as UTF-8 bytes. Returns: @@ -181,17 +189,16 @@ def _build_proto_body(self) -> file_append_pb2.FileAppendTransactionBody: # Calculate the current chunk's content if self.file_id is None: raise ValueError("Missing required FileID") - + if self.contents is None: - chunk_contents = b'' + chunk_contents = b"" else: start_index = self._current_chunk_index * self.chunk_size end_index = min(start_index + self.chunk_size, len(self.contents)) chunk_contents = self.contents[start_index:end_index] return file_append_pb2.FileAppendTransactionBody( - fileID=self.file_id._to_proto() if self.file_id else None, - contents=chunk_contents + fileID=self.file_id._to_proto() if self.file_id else None, contents=chunk_contents ) def build_transaction_body(self) -> Any: @@ -218,7 +225,7 @@ def build_scheduled_body(self) -> SchedulableTransactionBody: schedulable_body.fileAppend.CopyFrom(file_append_body) return schedulable_body - def _get_method(self, channel: "_Channel") -> "_Method": + def _get_method(self, channel: _Channel) -> _Method: """ Gets the method to execute the file append transaction. @@ -227,15 +234,13 @@ def _get_method(self, channel: "_Channel") -> "_Method": Args: channel (_Channel): The channel containing service stubs - + Returns: _Method: An object containing the transaction function to append to a file. """ from hiero_sdk_python.executable import _Method - return _Method( - transaction_func=channel.file.appendContent, - query_func=None - ) + + return _Method(transaction_func=channel.file.appendContent, query_func=None) def _from_proto(self, proto: file_append_pb2.FileAppendTransactionBody) -> FileAppendTransaction: """ @@ -247,7 +252,6 @@ def _from_proto(self, proto: file_append_pb2.FileAppendTransactionBody) -> FileA Returns: FileAppendTransaction: This transaction instance. """ - self.file_id = FileId._from_proto(proto.fileID) if proto.fileID else None self.contents = proto.contents self._total_chunks = self._calculate_total_chunks() @@ -256,7 +260,7 @@ def _from_proto(self, proto: file_append_pb2.FileAppendTransactionBody) -> FileA def _validate_chunking(self) -> None: """ Validates that the transaction doesn't exceed the maximum number of chunks. - + Raises: ValueError: If the transaction exceeds the maximum number of chunks. """ @@ -266,11 +270,10 @@ def _validate_chunking(self) -> None: f"Required: {self.get_required_chunks()}" ) - - def freeze_with(self, client: "Client") -> FileAppendTransaction: + def freeze_with(self, client: Client) -> FileAppendTransaction: """ Freezes the transaction by building the transaction body and setting necessary IDs. - + For multi-chunk transactions, this method generates multiple transaction IDs with incremented timestamps based on the chunk interval. @@ -283,7 +286,6 @@ def freeze_with(self, client: "Client") -> FileAppendTransaction: if self._transaction_body_bytes: return self - if self.transaction_id is None: self.transaction_id = client.generate_transaction_id() @@ -299,12 +301,10 @@ def freeze_with(self, client: "Client") -> FileAppendTransaction: # 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 + 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 + account_id=self.transaction_id.account_id, valid_start=chunk_valid_start ) self._transaction_ids.append(chunk_transaction_id) @@ -324,88 +324,84 @@ def freeze_with(self, client: "Client") -> FileAppendTransaction: @overload def execute( self, - client: "Client", + client: Client, timeout: int | float | None = None, wait_for_receipt: Literal[True] = True, - validate_status: bool = False - ) -> "TransactionReceipt": - ... + validate_status: bool = False, + ) -> TransactionReceipt: ... @overload def execute( self, - client: "Client", + client: Client, timeout: int | float | None = None, wait_for_receipt: Literal[False] = False, - validate_status: bool = False - ) -> "TransactionResponse": - ... + validate_status: bool = False, + ) -> TransactionResponse: ... def execute( self, - client: "Client", + client: Client, timeout: int | float | None = None, wait_for_receipt: bool = True, - validate_status: bool = False - ) -> "TransactionReceipt" | "TransactionResponse": + validate_status: bool = False, + ) -> TransactionReceipt | TransactionResponse: """ Executes the file append transaction. - + For multi-chunk transactions, this method will execute all chunks sequentially and return first response. - + Args: client: The client to execute the transaction with. timeout (int | float | None, optional): The total execution timeout (in seconds) for this execution. wait_for_receipt (bool, optional): Whether to wait for consensus and return the receipt. If False, the method returns a TransactionResponse immediately after submission. validate_status: (bool): Whether the query should automatically validate the transaction status (default False). - + Returns: TransactionReceipt: If wait_for_receipt is True (default) TransactionResponse: If wait_for_receipt is False """ # Return the first response (as per JavaScript implementation) return self.execute_all(client, timeout, wait_for_receipt, validate_status)[0] - + @overload def execute_all( self, - client: "Client", + client: Client, timeout: int | float | None = None, wait_for_receipt: Literal[True] = True, - validate_status: bool = False - ) -> List["TransactionReceipt"]: - ... + validate_status: bool = False, + ) -> list[TransactionReceipt]: ... @overload def execute_all( self, - client: "Client", + client: Client, timeout: int | float | None = None, wait_for_receipt: Literal[False] = False, - validate_status: bool = False - ) -> List["TransactionResponse"]: - ... - + validate_status: bool = False, + ) -> list[TransactionResponse]: ... + def execute_all( self, - client: "Client", + client: Client, timeout: int | float | None = None, wait_for_receipt: bool = True, - validate_status: bool = False - ) -> List["TransactionReceipt"] | List["TransactionResponse"]: + validate_status: bool = False, + ) -> list[TransactionReceipt] | list[TransactionResponse]: """ Executes the file append transaction. - + This method will execute all chunks sequentially and return list of response for each chunked - + Args: client: The client to execute the transaction with. timeout (int | float | None, optional): The total execution timeout (in seconds) for this execution. wait_for_receipt (bool, optional): Whether to wait for consensus and return the receipt. If False, the method returns a TransactionResponse immediately after submission. validate_status: (bool): Whether the query should automatically validate the transaction status (default False). - + Returns: List[TransactionReceipt]: If wait_for_receipt is True (default) List[TransactionResponse]: If wait_for_receipt is False @@ -443,12 +439,12 @@ def execute_all( return responses - def sign(self, private_key: "PrivateKey") -> FileAppendTransaction: + def sign(self, private_key: PrivateKey) -> FileAppendTransaction: """ Signs the transaction using the provided private key. - + For multi-chunk transactions, this stores the signing key for later use. - + Args: private_key (PrivateKey): The private key to sign the transaction with. @@ -461,4 +457,4 @@ def sign(self, private_key: "PrivateKey") -> FileAppendTransaction: # Call the parent sign method for the current transaction super().sign(private_key) - return self \ No newline at end of file + return self diff --git a/src/hiero_sdk_python/file/file_contents_query.py b/src/hiero_sdk_python/file/file_contents_query.py index 0457b5f99..7d99b4f0e 100644 --- a/src/hiero_sdk_python/file/file_contents_query.py +++ b/src/hiero_sdk_python/file/file_contents_query.py @@ -1,8 +1,6 @@ -""" -Query to get the contents of a file on the network. -""" +"""Query to get the contents of a file on the network.""" -from typing import Optional, Union +from __future__ import annotations from hiero_sdk_python.channels import _Channel from hiero_sdk_python.client.client import Client @@ -26,7 +24,7 @@ class FileContentsQuery(Query): """ - def __init__(self, file_id: Optional[FileId] = None) -> None: + def __init__(self, file_id: FileId | None = None) -> None: """ Initializes a new FileContentsQuery instance with an optional file_id. @@ -36,7 +34,7 @@ def __init__(self, file_id: Optional[FileId] = None) -> None: super().__init__() self.file_id = file_id - def set_file_id(self, file_id: Optional[FileId]) -> "FileContentsQuery": + def set_file_id(self, file_id: FileId | None) -> FileContentsQuery: """ Sets the ID of the file to query. @@ -96,7 +94,7 @@ def _get_method(self, channel: _Channel) -> _Method: """ return _Method(transaction_func=None, query_func=channel.file.getFileContent) - def execute(self, client: Client, timeout: Optional[Union[int, float]] = None) -> str: + def execute(self, client: Client, timeout: int | float | None = None) -> str: """ Executes the file contents query. @@ -123,9 +121,7 @@ def execute(self, client: Client, timeout: Optional[Union[int, float]] = None) - return response.fileGetContents.fileContents.contents - def _get_query_response( - self, response: response_pb2.Response - ) -> FileGetContentsResponse: + def _get_query_response(self, response: response_pb2.Response) -> FileGetContentsResponse: """ Extracts the file contents response from the full response. diff --git a/src/hiero_sdk_python/file/file_create_transaction.py b/src/hiero_sdk_python/file/file_create_transaction.py index d1e7f0277..0275469ea 100644 --- a/src/hiero_sdk_python/file/file_create_transaction.py +++ b/src/hiero_sdk_python/file/file_create_transaction.py @@ -1,6 +1,10 @@ +from __future__ import annotations + import time -from typing import Optional + +from hiero_sdk_python.channels import _Channel from hiero_sdk_python.crypto.public_key import PublicKey +from hiero_sdk_python.executable import _Method from hiero_sdk_python.hapi.services import file_create_pb2 from hiero_sdk_python.hapi.services.basic_types_pb2 import KeyList as KeyListProto from hiero_sdk_python.hapi.services.schedulable_transaction_body_pb2 import ( @@ -9,24 +13,29 @@ from hiero_sdk_python.hbar import Hbar from hiero_sdk_python.timestamp import Timestamp from hiero_sdk_python.transaction.transaction import Transaction -from hiero_sdk_python.channels import _Channel -from hiero_sdk_python.executable import _Method + class FileCreateTransaction(Transaction): """ Represents a file create transaction on the network. - + This transaction creates a new file on the network with the specified keys, contents, expiration time and memo. - + Inherits from the base Transaction class and implements the required methods to build and execute a file create transaction. """ - + # 90 days in seconds is the default expiration time DEFAULT_EXPIRY_SECONDS = 90 * 24 * 60 * 60 # 7776000 - - def __init__(self, keys: Optional[list[PublicKey]] = None, contents: Optional[str | bytes] = None, expiration_time: Optional[Timestamp] = None, file_memo: Optional[str] = None): + + def __init__( + self, + keys: list[PublicKey] | None = None, + contents: str | bytes | None = None, + expiration_time: Timestamp | None = None, + file_memo: str | None = None, + ): """ Initializes a new FileCreateTransaction instance with the specified parameters. @@ -37,29 +46,31 @@ def __init__(self, keys: Optional[list[PublicKey]] = None, contents: Optional[st file_memo (Optional[str], optional): A memo to include with the file. """ super().__init__() - self.keys: Optional[list[PublicKey]] = keys or [] - self.contents: Optional[bytes] = self._encode_contents(contents) - self.expiration_time: Optional[Timestamp] = expiration_time if expiration_time else Timestamp(int(time.time()) + self.DEFAULT_EXPIRY_SECONDS, 0) - self.file_memo: Optional[str] = file_memo + self.keys: list[PublicKey] | None = keys or [] + self.contents: bytes | None = self._encode_contents(contents) + self.expiration_time: Timestamp | None = ( + expiration_time if expiration_time else Timestamp(int(time.time()) + self.DEFAULT_EXPIRY_SECONDS, 0) + ) + self.file_memo: str | None = file_memo self._default_transaction_fee = Hbar(5).to_tinybars() - def _encode_contents(self, contents: Optional[str | bytes]) -> Optional[bytes]: + def _encode_contents(self, contents: str | bytes | None) -> bytes | None: """ Helper method to encode string contents to UTF-8 bytes. - + Args: contents (Optional[str | bytes]): The contents to encode. - + Returns: Optional[bytes]: The encoded contents or None if input is None. """ if contents is None: return None if isinstance(contents, str): - return contents.encode('utf-8') + return contents.encode("utf-8") return contents - def set_keys(self, keys: Optional[list[PublicKey]] | PublicKey) -> 'FileCreateTransaction': + def set_keys(self, keys: list[PublicKey] | None | PublicKey) -> FileCreateTransaction: """ Sets the keys for this file create transaction. @@ -76,7 +87,7 @@ def set_keys(self, keys: Optional[list[PublicKey]] | PublicKey) -> 'FileCreateTr self.keys = keys or [] return self - def set_contents(self, contents: Optional[str | bytes]) -> 'FileCreateTransaction': + def set_contents(self, contents: str | bytes | None) -> FileCreateTransaction: """ Sets the contents for this file create transaction. @@ -90,7 +101,7 @@ def set_contents(self, contents: Optional[str | bytes]) -> 'FileCreateTransactio self.contents = self._encode_contents(contents) return self - def set_expiration_time(self, expiration_time: Optional[Timestamp]) -> 'FileCreateTransaction': + def set_expiration_time(self, expiration_time: Timestamp | None) -> FileCreateTransaction: """ Sets the expiration time for this file create transaction. @@ -103,8 +114,8 @@ def set_expiration_time(self, expiration_time: Optional[Timestamp]) -> 'FileCrea self._require_not_frozen() self.expiration_time = expiration_time return self - - def set_file_memo(self, file_memo: Optional[str]) -> 'FileCreateTransaction': + + def set_file_memo(self, file_memo: str | None) -> FileCreateTransaction: """ Sets the memo for this file create transaction. @@ -127,9 +138,9 @@ def _build_proto_body(self): """ return file_create_pb2.FileCreateTransactionBody( keys=KeyListProto(keys=[key._to_proto() for key in self.keys or []]), - contents=self.contents if self.contents is not None else b'', + contents=self.contents if self.contents is not None else b"", expirationTime=self.expiration_time._to_protobuf() if self.expiration_time else None, - memo=self.file_memo if self.file_memo is not None else '' + memo=self.file_memo if self.file_memo is not None else "", ) def build_transaction_body(self): @@ -165,16 +176,13 @@ def _get_method(self, channel: _Channel) -> _Method: Args: channel (_Channel): The channel containing service stubs - + Returns: _Method: An object containing the transaction function to create a file. """ - return _Method( - transaction_func=channel.file.createFile, - query_func=None - ) - - def _from_proto(self, proto: file_create_pb2.FileCreateTransactionBody) -> 'FileCreateTransaction': + return _Method(transaction_func=channel.file.createFile, query_func=None) + + def _from_proto(self, proto: file_create_pb2.FileCreateTransactionBody) -> FileCreateTransaction: """ Initializes a new FileCreateTransaction instance from a protobuf object. @@ -185,7 +193,7 @@ def _from_proto(self, proto: file_create_pb2.FileCreateTransactionBody) -> 'File FileCreateTransaction: This transaction instance. """ self.keys = [PublicKey._from_proto(key) for key in proto.keys.keys] if proto.keys.keys else [] - self.contents = proto.contents + self.contents = proto.contents self.expiration_time = Timestamp._from_protobuf(proto.expirationTime) if proto.expirationTime else None - self.file_memo = proto.memo - return self \ No newline at end of file + self.file_memo = proto.memo + return self diff --git a/src/hiero_sdk_python/file/file_delete_transaction.py b/src/hiero_sdk_python/file/file_delete_transaction.py index f3f2d7e38..b4ab5c7d8 100644 --- a/src/hiero_sdk_python/file/file_delete_transaction.py +++ b/src/hiero_sdk_python/file/file_delete_transaction.py @@ -1,8 +1,6 @@ -""" -Transaction to delete a file on the network. -""" +"""Transaction to delete a file on the network.""" -from typing import Optional +from __future__ import annotations from hiero_sdk_python.channels import _Channel from hiero_sdk_python.executable import _Method @@ -14,6 +12,7 @@ from hiero_sdk_python.hbar import Hbar from hiero_sdk_python.transaction.transaction import Transaction + DEFAULT_TRANSACTION_FEE = Hbar(2).to_tinybars() @@ -27,7 +26,7 @@ class FileDeleteTransaction(Transaction): to build and execute a file deletion transaction. """ - def __init__(self, file_id: Optional[FileId] = None): + def __init__(self, file_id: FileId | None = None): """ Initializes a new FileDeleteTransaction instance with optional file_id. @@ -38,7 +37,7 @@ def __init__(self, file_id: Optional[FileId] = None): self.file_id = file_id self._default_transaction_fee = DEFAULT_TRANSACTION_FEE - def set_file_id(self, file_id: FileId) -> "FileDeleteTransaction": + def set_file_id(self, file_id: FileId) -> FileDeleteTransaction: """ Sets the ID of the file to be deleted. diff --git a/src/hiero_sdk_python/file/file_id.py b/src/hiero_sdk_python/file/file_id.py index c804258b2..cbe66d6fd 100644 --- a/src/hiero_sdk_python/file/file_id.py +++ b/src/hiero_sdk_python/file/file_id.py @@ -5,16 +5,14 @@ files stored on the Hedera network. It supports string parsing, checksum validation, and conversion to and from protobuf structures. """ + +from __future__ import annotations + from dataclasses import dataclass, field -from typing import Optional from hiero_sdk_python.client.client import Client from hiero_sdk_python.hapi.services import basic_types_pb2 -from hiero_sdk_python.utils.entity_id_helper import ( - parse_from_string, - validate_checksum, - format_to_string_with_checksum -) +from hiero_sdk_python.utils.entity_id_helper import format_to_string_with_checksum, parse_from_string, validate_checksum @dataclass(frozen=True) @@ -31,13 +29,14 @@ class FileId: file (int): The file number. Defaults to 0. checksum (Optional[str]): The calculated checksum for the file ID. """ + shard: int = 0 realm: int = 0 file: int = 0 - checksum: Optional[str] = field(default=None, init=False) + checksum: str | None = field(default=None, init=False) @classmethod - def _from_proto(cls, file_id_proto: basic_types_pb2.FileID) -> 'FileId': + def _from_proto(cls, file_id_proto: basic_types_pb2.FileID) -> FileId: """ Creates a FileId instance from a protobuf FileID object. @@ -48,11 +47,7 @@ def _from_proto(cls, file_id_proto: basic_types_pb2.FileID) -> 'FileId': Returns: FileId: A new instance of the FileId class. """ - return cls( - shard=file_id_proto.shardNum, - realm=file_id_proto.realmNum, - file=file_id_proto.fileNum - ) + return cls(shard=file_id_proto.shardNum, realm=file_id_proto.realmNum, file=file_id_proto.fileNum) def _to_proto(self) -> basic_types_pb2.FileID: """ @@ -61,16 +56,12 @@ def _to_proto(self) -> basic_types_pb2.FileID: Returns: basic_types_pb2.FileID: The protobuf representation of the FileId. """ - return basic_types_pb2.FileID( - shardNum=self.shard, - realmNum=self.realm, - fileNum=self.file - ) + return basic_types_pb2.FileID(shardNum=self.shard, realmNum=self.realm, fileNum=self.file) @classmethod - def from_string(cls, file_id_str: str) -> 'FileId': + def from_string(cls, file_id_str: str) -> FileId: """ - Parses a string in the format 'shard.realm.file' (with optional checksum) + Parses a string in the format 'shard.realm.file' (with optional checksum) to create a FileId instance. Args: @@ -85,18 +76,13 @@ def from_string(cls, file_id_str: str) -> 'FileId': try: shard, realm, file, checksum = parse_from_string(file_id_str) - file_id: FileId = cls( - shard=int(shard), - realm=int(realm), - file=int(file) - ) - object.__setattr__(file_id, 'checksum', checksum) + file_id: FileId = cls(shard=int(shard), realm=int(realm), file=int(file)) + object.__setattr__(file_id, "checksum", checksum) return file_id - except Exception as e: - raise ValueError( - f"Invalid file ID string '{file_id_str}'. Expected format 'shard.realm.file'." - ) from e + except Exception as e: + raise ValueError(f"Invalid file ID string '{file_id_str}'. Expected format 'shard.realm.file'.") from e + def __str__(self) -> str: """ Returns the string representation of the FileId in the format 'shard.realm.file'. @@ -120,35 +106,24 @@ def validate_checksum(self, client: Client) -> None: Validates the stored checksum against the calculated checksum using the provided client. Args: - client (Client): The client instance used to retrieve network information + client (Client): The client instance used to retrieve network information necessary for checksum calculation. Raises: ValueError: If the stored checksum is invalid or does not match the calculated value. """ - validate_checksum( - self.shard, - self.realm, - self.file, - self.checksum, - client - ) + validate_checksum(self.shard, self.realm, self.file, self.checksum, client) def to_string_with_checksum(self, client: Client) -> str: """ - Returns the string representation of the FileId with its calculated checksum + Returns the string representation of the FileId with its calculated checksum in 'shard.realm.file-checksum' format. Args: - client (Client): The client instance used to retrieve network information + client (Client): The client instance used to retrieve network information necessary for checksum calculation. Returns: str: The file ID formatted with its checksum. """ - return format_to_string_with_checksum( - self.shard, - self.realm, - self.file, - client - ) \ No newline at end of file + return format_to_string_with_checksum(self.shard, self.realm, self.file, client) diff --git a/src/hiero_sdk_python/file/file_info.py b/src/hiero_sdk_python/file/file_info.py index 6402276f5..3293ddd8b 100644 --- a/src/hiero_sdk_python/file/file_info.py +++ b/src/hiero_sdk_python/file/file_info.py @@ -1,36 +1,40 @@ -from dataclasses import dataclass, field +from __future__ import annotations + import datetime -from typing import Optional +from dataclasses import dataclass, field + from hiero_sdk_python.crypto.public_key import PublicKey from hiero_sdk_python.file.file_id import FileId -from hiero_sdk_python.timestamp import Timestamp -from hiero_sdk_python.hapi.services.file_get_info_pb2 import FileGetInfoResponse from hiero_sdk_python.hapi.services.basic_types_pb2 import KeyList as KeyListProto +from hiero_sdk_python.hapi.services.file_get_info_pb2 import FileGetInfoResponse +from hiero_sdk_python.timestamp import Timestamp + @dataclass class FileInfo: """ Information about a file stored on the Hedera network. - + Attributes: - file_id (Optional[FileId]): The ID of the file - size (Optional[int]): The size of the file in bytes - expiration_time (Optional[Timestamp]): When the file will expire - is_deleted (Optional[bool]): Whether the file has been deleted + file_id (FileId, optional): The ID of the file + size (int, optional): The size of the file in bytes + expiration_time (Timestamp, optional): When the file will expire + is_deleted (bool, optional): Whether the file has been deleted keys (list[PublicKey]): The keys that can modify this file - file_memo (Optional[str]): The memo associated with the file - ledger_id (Optional[bytes]): The ID of the ledger this file exists in + file_memo (str, optional): The memo associated with the file + ledger_id (bytes, optional): The ID of the ledger this file exists in """ - file_id: Optional[FileId] = None - size: Optional[int] = None - expiration_time: Optional[Timestamp] = None - is_deleted: Optional[bool] = None + + file_id: FileId | None = None + size: int | None = None + expiration_time: Timestamp | None = None + is_deleted: bool | None = None keys: list[PublicKey] = field(default_factory=list) - file_memo: Optional[str] = None - ledger_id: Optional[bytes] = None + file_memo: str | None = None + ledger_id: bytes | None = None @classmethod - def _from_proto(cls, proto: FileGetInfoResponse.FileInfo) -> 'FileInfo': + def _from_proto(cls, proto: FileGetInfoResponse.FileInfo) -> FileInfo: """ Creates a FileInfo instance from its protobuf representation. @@ -42,7 +46,7 @@ def _from_proto(cls, proto: FileGetInfoResponse.FileInfo) -> 'FileInfo': """ if proto is None: raise ValueError("File info proto is None") - + return cls( file_id=FileId._from_proto(proto.fileID), size=proto.size, @@ -50,7 +54,7 @@ def _from_proto(cls, proto: FileGetInfoResponse.FileInfo) -> 'FileInfo': is_deleted=proto.deleted, keys=[PublicKey._from_proto(key) for key in proto.keys.keys], file_memo=proto.memo, - ledger_id=proto.ledger_id + ledger_id=proto.ledger_id, ) def _to_proto(self) -> FileGetInfoResponse.FileInfo: @@ -67,9 +71,9 @@ def _to_proto(self) -> FileGetInfoResponse.FileInfo: deleted=self.is_deleted, keys=KeyListProto(keys=[key._to_proto() for key in self.keys or []]), memo=self.file_memo, - ledger_id=self.ledger_id + ledger_id=self.ledger_id, ) - + def __repr__(self) -> str: """ Returns a string representation of the FileInfo object. @@ -80,9 +84,7 @@ def __repr__(self) -> str: return self.__str__() def __str__(self) -> str: - """ - Pretty-print the FileInfo. - """ + """Pretty-print the FileInfo.""" # Format expiration time as datetime if available exp_dt = ( datetime.datetime.fromtimestamp(self.expiration_time.seconds) @@ -95,9 +97,7 @@ def __str__(self) -> str: # Format ledger_id as hex if it's bytes ledger_id_display = ( - f"0x{self.ledger_id.hex()}" - if isinstance(self.ledger_id, (bytes, bytearray)) - else self.ledger_id + f"0x{self.ledger_id.hex()}" if isinstance(self.ledger_id, (bytes, bytearray)) else self.ledger_id ) return ( @@ -110,4 +110,4 @@ def __str__(self) -> str: f" file_memo='{self.file_memo}',\n" f" ledger_id={ledger_id_display}\n" ")" - ) \ No newline at end of file + ) diff --git a/src/hiero_sdk_python/file/file_info_query.py b/src/hiero_sdk_python/file/file_info_query.py index 8f27058cf..9ce48bfb2 100644 --- a/src/hiero_sdk_python/file/file_info_query.py +++ b/src/hiero_sdk_python/file/file_info_query.py @@ -1,9 +1,8 @@ -""" -Query to get information about a file on the network. -""" +"""Query to get information about a file on the network.""" + +from __future__ import annotations import traceback -from typing import Optional, Union from hiero_sdk_python.channels import _Channel from hiero_sdk_python.client.client import Client @@ -24,7 +23,7 @@ class FileInfoQuery(Query): """ - def __init__(self, file_id: Optional[FileId] = None) -> None: + def __init__(self, file_id: FileId | None = None) -> None: """ Initializes a new FileInfoQuery instance with an optional file_id. @@ -32,9 +31,9 @@ def __init__(self, file_id: Optional[FileId] = None) -> None: file_id (Optional[FileId], optional): The ID of the file to query. """ super().__init__() - self.file_id: Optional[FileId] = file_id + self.file_id: FileId | None = file_id - def set_file_id(self, file_id: Optional[FileId]) -> "FileInfoQuery": + def set_file_id(self, file_id: FileId | None) -> FileInfoQuery: """ Sets the ID of the file to query. @@ -95,7 +94,7 @@ def _get_method(self, channel: _Channel) -> _Method: """ return _Method(transaction_func=None, query_func=channel.file.getFileInfo) - def execute(self, client: Client, timeout: Optional[Union[int, float]] = None) -> FileInfo: + def execute(self, client: Client, timeout: int | float | None = None) -> FileInfo: """ Executes the file info query. @@ -107,7 +106,7 @@ def execute(self, client: Client, timeout: Optional[Union[int, float]] = None) - Args: client (Client): The client instance to use for execution - timeout (Optional[Union[int, float]]): The total execution timeout (in seconds) for this execution. + timeout (int | float, optional): The total execution timeout (in seconds) for this execution. Returns: FileInfo: The file info from the network @@ -122,9 +121,7 @@ def execute(self, client: Client, timeout: Optional[Union[int, float]] = None) - return FileInfo._from_proto(response.fileGetInfo.fileInfo) - def _get_query_response( - self, response: response_pb2.Response - ) -> FileGetInfoResponse.FileInfo: + def _get_query_response(self, response: response_pb2.Response) -> FileGetInfoResponse.FileInfo: """ Extracts the file info response from the full response. diff --git a/src/hiero_sdk_python/file/file_update_transaction.py b/src/hiero_sdk_python/file/file_update_transaction.py index 88ea6549e..f0daa894a 100644 --- a/src/hiero_sdk_python/file/file_update_transaction.py +++ b/src/hiero_sdk_python/file/file_update_transaction.py @@ -1,8 +1,6 @@ -""" -Transaction to update a file's contents, metadata, or keys on the network. -""" +"""Transaction to update a file's contents, metadata, or keys on the network.""" -from typing import Optional +from __future__ import annotations # pylint: disable=no-name-in-module from google.protobuf.wrappers_pb2 import StringValue @@ -20,6 +18,7 @@ from hiero_sdk_python.timestamp import Timestamp from hiero_sdk_python.transaction.transaction import Transaction + DEFAULT_TRANSACTION_FEE = Hbar(2).to_tinybars() @@ -39,38 +38,38 @@ class FileUpdateTransaction(Transaction): def __init__( self, - file_id: Optional[FileId] = None, - keys: Optional[list[PublicKey]] = None, - contents: Optional[str | bytes] = None, - expiration_time: Optional[Timestamp] = None, - file_memo: Optional[str] = None, + file_id: FileId | None = None, + keys: list[PublicKey] | None = None, + contents: str | bytes | None = None, + expiration_time: Timestamp | None = None, + file_memo: str | None = None, ): # pylint: [disable=too-many-arguments, disable=too-many-positional-arguments] """ Initializes a new FileUpdateTransaction instance with the specified parameters. Args: - file_id (Optional[FileId], optional): The ID of the file to update. + file_id (FileId, optional): The ID of the file to update. keys (Optional[list[PublicKey]], optional): The new keys that are allowed to update/delete the file. - contents (Optional[str | bytes], optional): The new contents of the file. + contents (str | bytes, optional): The new contents of the file. Strings will be automatically encoded as UTF-8 bytes. - expiration_time (Optional[Timestamp], optional): The new expiration time for the file. - file_memo (Optional[str], optional): The new memo for the file. + expiration_time (Timestamp, optional): The new expiration time for the file. + file_memo (str, optional): The new memo for the file. """ super().__init__() - self.file_id: Optional[FileId] = file_id - self.keys: Optional[list[PublicKey]] = keys - self.contents: Optional[bytes] = self._encode_contents(contents) - self.expiration_time: Optional[Timestamp] = expiration_time - self.file_memo: Optional[str] = file_memo + self.file_id: FileId | None = file_id + self.keys: list[PublicKey] | None = keys + self.contents: bytes | None = self._encode_contents(contents) + self.expiration_time: Timestamp | None = expiration_time + self.file_memo: str | None = file_memo self._default_transaction_fee = DEFAULT_TRANSACTION_FEE - def _encode_contents(self, contents: Optional[str | bytes]) -> Optional[bytes]: + def _encode_contents(self, contents: str | bytes | None) -> bytes | None: """ Helper method to encode string contents to UTF-8 bytes. Args: - contents (Optional[str | bytes]): The contents to encode. + contents (str | bytes | None): The contents to encode. Returns: Optional[bytes]: The encoded contents or None if input is None. @@ -81,12 +80,12 @@ def _encode_contents(self, contents: Optional[str | bytes]) -> Optional[bytes]: return contents.encode("utf-8") return contents - def set_file_id(self, file_id: Optional[FileId]) -> "FileUpdateTransaction": + def set_file_id(self, file_id: FileId | None) -> FileUpdateTransaction: """ Sets the FileID to be updated. Args: - file_id (Optional[FileId]): The ID of the file to update. + file_id (FileId | None): The ID of the file to update. Returns: FileUpdateTransaction: This transaction instance. @@ -95,14 +94,12 @@ def set_file_id(self, file_id: Optional[FileId]) -> "FileUpdateTransaction": self.file_id = file_id return self - def set_keys( - self, keys: Optional[list[PublicKey]] | PublicKey - ) -> "FileUpdateTransaction": + def set_keys(self, keys: list[PublicKey] | None | PublicKey) -> FileUpdateTransaction: """ Sets the new list of keys that can modify or delete the file. Args: - keys (Optional[list[PublicKey]] | PublicKey): The new keys to set for the file. + keys (list[PublicKey] | PublicKey | None): The new keys to set for the file. Can be a list of PublicKey objects, a single PublicKey, or None. Returns: @@ -115,14 +112,12 @@ def set_keys( self.keys = keys return self - def set_expiration_time( - self, expiration_time: Optional[Timestamp] - ) -> "FileUpdateTransaction": + def set_expiration_time(self, expiration_time: Timestamp | None) -> FileUpdateTransaction: """ Sets the new expiry time for the file. Args: - expiration_time (Optional[Timestamp]): The new expiration time for the file. + expiration_time (Timestamp | None): The new expiration time for the file. Returns: FileUpdateTransaction: This transaction instance. @@ -131,12 +126,12 @@ def set_expiration_time( self.expiration_time = expiration_time return self - def set_contents(self, contents: Optional[bytes | str]) -> "FileUpdateTransaction": + def set_contents(self, contents: bytes | str | None) -> FileUpdateTransaction: """ Sets the new contents that should overwrite the file's current contents. Args: - contents (Optional[bytes | str]): The new contents for the file. + contents (bytes | str | None): The new contents for the file. Strings will be automatically encoded as UTF-8 bytes. Returns: @@ -146,12 +141,12 @@ def set_contents(self, contents: Optional[bytes | str]) -> "FileUpdateTransactio self.contents = self._encode_contents(contents) return self - def set_file_memo(self, file_memo: Optional[str]) -> "FileUpdateTransaction": + def set_file_memo(self, file_memo: str | None) -> FileUpdateTransaction: """ Sets the new memo to be associated with the file (UTF-8 encoding max 100 bytes). Args: - file_memo (Optional[str]): The new memo for the file. + file_memo (str | None): The new memo for the file. Returns: FileUpdateTransaction: This transaction instance. @@ -175,20 +170,10 @@ def _build_proto_body(self): return FileUpdateTransactionBody( fileID=self.file_id._to_proto(), - keys=( - KeyListProto(keys=[key._to_proto() for key in self.keys]) - if self.keys - else None - ), + keys=(KeyListProto(keys=[key._to_proto() for key in self.keys]) if self.keys else None), contents=self.contents if self.contents is not None else b"", - expirationTime=( - self.expiration_time._to_protobuf() if self.expiration_time else None - ), - memo=( - StringValue(value=self.file_memo) - if self.file_memo is not None - else None - ), + expirationTime=(self.expiration_time._to_protobuf() if self.expiration_time else None), + memo=(StringValue(value=self.file_memo) if self.file_memo is not None else None), ) def build_transaction_body(self): diff --git a/src/hiero_sdk_python/hbar.py b/src/hiero_sdk_python/hbar.py index fbdc2ad57..22e3d03a6 100644 --- a/src/hiero_sdk_python/hbar.py +++ b/src/hiero_sdk_python/hbar.py @@ -1,44 +1,43 @@ """ -hiero_sdk_python.hbar.py +hiero_sdk_python.hbar.py. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Defines the Hbar, a value object for representing, converting, and validating amounts of the network utility token (HBAR). """ +from __future__ import annotations + import math import re -import warnings from decimal import Decimal -from typing import ClassVar, Union +from typing import ClassVar from hiero_sdk_python.hbar_unit import HbarUnit + FROM_STRING_PATTERN = re.compile(r"^((?:\+|\-)?\d+(?:\.\d+)?)(\ (tℏ|μℏ|mℏ|ℏ|kℏ|Mℏ|Gℏ))?$") + class Hbar: """ Represents the network utility token. - For historical purposes this is referred to as an hbar in the SDK because that is the + For historical purposes this is referred to as an hbar in the SDK because that is the native currency of the Hedera network, but for other Hiero networks, it represents the network utility token, whatever its designation may be. """ - ZERO: ClassVar["Hbar"] - MAX: ClassVar["Hbar"] - MIN: ClassVar["Hbar"] + ZERO: ClassVar[Hbar] + MAX: ClassVar[Hbar] + MIN: ClassVar[Hbar] - def __init__( - self, - amount: Union[int, float, Decimal], - unit: HbarUnit=HbarUnit.HBAR - ) -> None: + def __init__(self, amount: int | float | Decimal, unit: HbarUnit = HbarUnit.HBAR) -> None: """ Create an Hbar instance with the given amount designated either in hbars or tinybars. Args: - amount: The numeric amount of hbar or tinybar. - unit: Unit of the provided amount. + amount (int | float | Decimal): The numeric amount of hbar or tinybar. + unit (HbarUnit, optional): Unit of the provided amount (Default HBAR). """ if isinstance(amount, bool) or not isinstance(amount, (int, float, Decimal)): raise TypeError("Amount must be of type int, float, or Decimal") @@ -48,7 +47,7 @@ def __init__( if isinstance(amount, Decimal) and not amount.is_finite(): raise ValueError("Hbar amount must be finite") - if unit == HbarUnit.TINYBAR: + if unit == HbarUnit.TINYBAR: if not isinstance(amount, int): raise ValueError("Fractional tinybar value not allowed") self._amount_in_tinybar = int(amount) @@ -72,12 +71,10 @@ def to_tinybars(self) -> int: return self._amount_in_tinybar def to_hbars(self) -> float: - """ - Return the amount of hbars as a floating-point number. - """ + """Return the amount of hbars as a floating-point number.""" return self.to(HbarUnit.HBAR) - def negated(self) -> "Hbar": + def negated(self) -> Hbar: """ Return a new Hbar representing the negated value. @@ -87,21 +84,21 @@ def negated(self) -> "Hbar": return Hbar.from_tinybars(-self._amount_in_tinybar) @classmethod - def of(cls, amount: Union[int, float, Decimal], unit: HbarUnit) -> "Hbar": + def of(cls, amount: int | float | Decimal, unit: HbarUnit) -> Hbar: """ Create an Hbar instance from a given amount and unit. Args: - amount (Union[int, float, Decimal]): The amount to represent. + amount (int | float | Decimal): The amount to represent. unit (HbarUnit): The unit of the given amount. - + Returns: Hbar: A new Hbar instance. """ return cls(amount, unit=unit) @classmethod - def from_tinybars(cls, tinybars: int) -> "Hbar": + def from_tinybars(cls, tinybars: int) -> Hbar: """ Create an Hbar instance from the given amount in tinybars. @@ -114,64 +111,64 @@ def from_tinybars(cls, tinybars: int) -> "Hbar": if isinstance(tinybars, bool) or not isinstance(tinybars, int): raise TypeError("tinybars must be an int.") return cls(tinybars, unit=HbarUnit.TINYBAR) - + @classmethod - def from_microbars(cls, amount: Union[int, float, Decimal]) -> "Hbar": + def from_microbars(cls, amount: int | float | Decimal) -> Hbar: """ Create an Hbar object representing the specified amount of microbars. - Example: Hbar.from_microbars(100) -> 100 μℏ + Example: Hbar.from_microbars(100) -> 100 μℏ. """ return cls(amount, unit=HbarUnit.MICROBAR) @classmethod - def from_millibars(cls, amount: Union[int, float, Decimal]) -> "Hbar": + def from_millibars(cls, amount: int | float | Decimal) -> Hbar: """ Create an Hbar object representing the specified amount of millibars. - Example: Hbar.from_millibars(500) -> 500 mℏ + Example: Hbar.from_millibars(500) -> 500 mℏ. """ return cls(amount, unit=HbarUnit.MILLIBAR) @classmethod - def from_hbars(cls, amount: Union[int, float, Decimal]) -> "Hbar": + def from_hbars(cls, amount: int | float | Decimal) -> Hbar: """ Create an Hbar object representing the specified amount of hbars. - Example: Hbar.from_hbars(10) -> 10 ℏ + Example: Hbar.from_hbars(10) -> 10 ℏ. """ return cls(amount, unit=HbarUnit.HBAR) @classmethod - def from_kilobars(cls, amount: Union[int, float, Decimal]) -> "Hbar": + def from_kilobars(cls, amount: int | float | Decimal) -> Hbar: """ Create an Hbar object representing the specified amount of kilobars. - Example: Hbar.from_kilobars(1) -> 1 kℏ + Example: Hbar.from_kilobars(1) -> 1 kℏ. """ return cls(amount, unit=HbarUnit.KILOBAR) @classmethod - def from_megabars(cls, amount: Union[int, float, Decimal]) -> "Hbar": + def from_megabars(cls, amount: int | float | Decimal) -> Hbar: """ Create an Hbar object representing the specified amount of megabars. - Example: Hbar.from_megabars(1) -> 1 Mℏ + Example: Hbar.from_megabars(1) -> 1 Mℏ. """ return cls(amount, unit=HbarUnit.MEGABAR) @classmethod - def from_gigabars(cls, amount: Union[int, float, Decimal]) -> "Hbar": + def from_gigabars(cls, amount: int | float | Decimal) -> Hbar: """ Create an Hbar object representing the specified amount of gigabars. - Example: Hbar.from_gigabars(1) -> 1 Gℏ + Example: Hbar.from_gigabars(1) -> 1 Gℏ. """ return cls(amount, unit=HbarUnit.GIGABAR) @classmethod - def from_string(cls, amount: str, unit: HbarUnit = HbarUnit.HBAR) -> "Hbar": + def from_string(cls, amount: str, unit: HbarUnit = HbarUnit.HBAR) -> Hbar: """ Create an Hbar instance from a string like "10 ℏ", "5000 tℏ", "-0.25 Mℏ", or "10". - + Args: amount (str): The string to parse (e.g., "1.5 ℏ", "1000 tℏ", or just "10"). unit (HbarUnit): The unit to use if the string does not include one (default: HBAR). - + Returns: Hbar: A new Hbar instance. """ @@ -180,7 +177,7 @@ def from_string(cls, amount: str, unit: HbarUnit = HbarUnit.HBAR) -> "Hbar": if not match: raise ValueError(f"Invalid Hbar format: '{amount}'") - parts = amount.split(' ') + parts = amount.split(" ") value = Decimal(parts[0]) unit = HbarUnit.from_string(parts[1]) if len(parts) == 2 else unit return cls(value, unit=unit) diff --git a/src/hiero_sdk_python/hbar_unit.py b/src/hiero_sdk_python/hbar_unit.py index dd2f9c947..40521ca90 100644 --- a/src/hiero_sdk_python/hbar_unit.py +++ b/src/hiero_sdk_python/hbar_unit.py @@ -1,36 +1,38 @@ +from __future__ import annotations + from enum import Enum + class HbarUnit(Enum): - """ - Represents the various denominations of the HBAR (or network utility token). - """ + """Represents the various denominations of the HBAR (or network utility token).""" + # 1 tinybar = base unit - TINYBAR = ('tℏ', 1) + TINYBAR = ("tℏ", 1) # 1 microbar = 100 tinybars - MICROBAR = ('μℏ', 10**2) - + MICROBAR = ("μℏ", 10**2) + # 1 millibar = 100,000 tinybars - MILLIBAR = ('mℏ', 10**5) - + MILLIBAR = ("mℏ", 10**5) + # 1 hbar = 100,000,000 tinybars - HBAR = ('ℏ', 10**8) + HBAR = ("ℏ", 10**8) # 1 kilobar = 100,000,000,000 tinybars - KILOBAR = ('kℏ', 10**11) - + KILOBAR = ("kℏ", 10**11) + # 1 megabar = 100,000,000,000,000 tinybars - MEGABAR = ('Mℏ', 10**14) - + MEGABAR = ("Mℏ", 10**14) + # 1 gigabar = 100,000,000,000,000,000 tinybars - GIGABAR = ('Gℏ', 10**17) + GIGABAR = ("Gℏ", 10**17) def __init__(self, symbol: str, tinybar: int): self.symbol = symbol self.tinybar = tinybar @classmethod - def from_string(cls, symbol: str) -> "HbarUnit": + def from_string(cls, symbol: str) -> HbarUnit: """ Convert a unit symbol string into the corresponding `HbarUnit`. diff --git a/src/hiero_sdk_python/logger/log_level.py b/src/hiero_sdk_python/logger/log_level.py index 85fecdcaf..e8b65071c 100644 --- a/src/hiero_sdk_python/logger/log_level.py +++ b/src/hiero_sdk_python/logger/log_level.py @@ -4,13 +4,15 @@ This module defines the log levels used throughout the SDK. """ +from __future__ import annotations + import os from enum import IntEnum + class LogLevel(IntEnum): - """ - Enumeration of log levels - """ + """Enumeration of log levels.""" + TRACE = 5 DEBUG = 10 INFO = 20 @@ -19,24 +21,24 @@ class LogLevel(IntEnum): CRITICAL = 50 DISABLED = 60 - #Old warn method will be depreciated + # Old warn method will be depreciated WARN = WARNING def to_python_level(self) -> int: - """Convert to Python's logging level - + """Convert to Python's logging level. + Returns: int: The Python logging level """ return self.value @classmethod - def from_string(cls, level_str: str) -> "LogLevel": - """Convert a string to a LogLevel - + def from_string(cls, level_str: str) -> LogLevel: + """Convert a string to a LogLevel. + Args: level_str: The string to convert - + Returns: LogLevel: The LogLevel enum value """ @@ -45,16 +47,16 @@ def from_string(cls, level_str: str) -> "LogLevel": try: return cls[level_str.upper()] - except KeyError: - raise ValueError(f"Invalid log level: {level_str}") - + except KeyError as e: + raise ValueError(f"Invalid log level: {level_str}") from e + @classmethod - def from_env(cls) -> "LogLevel": + def from_env(cls) -> LogLevel: """ - Get log level from environment variable - + Get log level from environment variable. + Returns: LogLevel: The LogLevel enum value """ - level_str = os.getenv('LOG_LEVEL') - return cls.from_string(level_str) \ No newline at end of file + level_str = os.getenv("LOG_LEVEL") + return cls.from_string(level_str) diff --git a/src/hiero_sdk_python/logger/logger.py b/src/hiero_sdk_python/logger/logger.py index 828743217..8a162a0e4 100644 --- a/src/hiero_sdk_python/logger/logger.py +++ b/src/hiero_sdk_python/logger/logger.py @@ -6,17 +6,22 @@ configuration within the SDK. """ +from __future__ import annotations + import logging import sys -from typing import Optional, Union, Sequence +from collections.abc import Sequence + from hiero_sdk_python.logger.log_level import LogLevel + # Register custom levels on import _DISABLED_LEVEL = LogLevel.DISABLED.value _TRACE_LEVEL = LogLevel.TRACE.value logging.addLevelName(_DISABLED_LEVEL, "DISABLED") logging.addLevelName(_TRACE_LEVEL, "TRACE") + class Logger: """ Custom logger that wraps Python's logging module for Hiero SDK use. @@ -24,16 +29,15 @@ class Logger: This class handles configuration, formatting, and logging operations across various log levels, including custom SDK levels (TRACE, DISABLED). """ - - def __init__(self, level: Optional[LogLevel] = None, name: Optional[str] = None) -> None: + + def __init__(self, level: LogLevel | None = None, name: str | None = None) -> None: """ Initializes the Logger instance for the Hiero SDK. - + Args: level (LogLevel, optional): The current minimum log level. Defaults to TRACE. name (str, optional): The logger name. Defaults to "hiero_sdk_python". """ - # Get logger name if name is None: name = "hiero_sdk_python" @@ -41,42 +45,42 @@ def __init__(self, level: Optional[LogLevel] = None, name: Optional[str] = None) self.name: str = name self.internal_logger: logging.Logger = logging.getLogger(name) self.level: LogLevel = level or LogLevel.TRACE - + # Add handler if needed if not self.internal_logger.handlers: handler = logging.StreamHandler(sys.stdout) # Configure formatter to structure log output with logger name, timestamp, level and message - formatter = logging.Formatter('[%(name)s] [%(asctime)s] %(levelname)-8s %(message)s') + formatter = logging.Formatter("[%(name)s] [%(asctime)s] %(levelname)-8s %(message)s") handler.setFormatter(formatter) self.internal_logger.addHandler(handler) - + # Set level self.set_level(self.level) - - def set_level(self, level: Union[LogLevel, str]) -> "Logger": + + def set_level(self, level: LogLevel | str) -> Logger: """ Sets the current logging level for the SDK logger. Args: - level (Union[LogLevel, str]): The new minimum log level. Can be a LogLevel enum or a string (e.g., "INFO"). + level (LogLevel | str): The new minimum log level. Can be a LogLevel enum or a string (e.g., "INFO"). Returns: Logger: The current Logger instance (for chaining). """ if isinstance(level, str): level = LogLevel.from_string(level) - + self.level = level - + # If level is DISABLED, turn off logging by disabling the logger if level == LogLevel.DISABLED: self.internal_logger.disabled = True else: self.internal_logger.disabled = False - + self.internal_logger.setLevel(level.to_python_level()) return self - + def get_level(self) -> LogLevel: """ Retrieves the current logging level set for the SDK logger. @@ -85,8 +89,8 @@ def get_level(self) -> LogLevel: LogLevel: The current minimum logging level. """ return self.level - - def set_silent(self, is_silent: bool) -> "Logger": + + def set_silent(self, is_silent: bool) -> Logger: """ Enables or disables silent mode (disabling all logging output). @@ -102,7 +106,7 @@ def set_silent(self, is_silent: bool) -> "Logger": self.internal_logger.disabled = False return self - + def _format_args(self, message: str, args: Sequence[object]) -> str: """ Formats a message with optional key-value pairs into a clean string format. @@ -118,11 +122,9 @@ def _format_args(self, message: str, args: Sequence[object]) -> str: """ if not args or len(args) % 2 != 0: return message - pairs = [] - for i in range(0, len(args), 2): - pairs.append(f"{args[i]} = {args[i+1]}") + pairs = [f"{args[i]} = {args[i + 1]}" for i in range(0, len(args), 2)] return f"{message}: {', '.join(pairs)}" - + def trace(self, message: str, *args: object) -> None: """ Logs a message at the TRACE level (lowest verbosity). @@ -133,7 +135,7 @@ def trace(self, message: str, *args: object) -> None: """ if self.internal_logger.isEnabledFor(_TRACE_LEVEL): self.internal_logger.log(_TRACE_LEVEL, self._format_args(message, args)) - + def debug(self, message: str, *args: object) -> None: """ Logs a message at the DEBUG level. @@ -144,7 +146,7 @@ def debug(self, message: str, *args: object) -> None: """ if self.internal_logger.isEnabledFor(LogLevel.DEBUG.value): self.internal_logger.debug(self._format_args(message, args)) - + def info(self, message: str, *args: object) -> None: """ Logs a message at the INFO level. @@ -178,8 +180,9 @@ def error(self, message: str, *args: object) -> None: if self.internal_logger.isEnabledFor(LogLevel.ERROR.value): self.internal_logger.error(self._format_args(message, args)) + def get_logger( - level: Optional[LogLevel] = None, - name: Optional[str] = None, + level: LogLevel | None = None, + name: str | None = None, ) -> Logger: return Logger(level, name) diff --git a/src/hiero_sdk_python/managed_node_address.py b/src/hiero_sdk_python/managed_node_address.py index 6ecf8abff..d06b1c158 100644 --- a/src/hiero_sdk_python/managed_node_address.py +++ b/src/hiero_sdk_python/managed_node_address.py @@ -1,65 +1,69 @@ +from __future__ import annotations + import re + class _ManagedNodeAddress: """ Represents a managed consensus node address with a host and port. This class is used to handle consensus node addresses in the Hedera network. Note: Mirror nodes are handled separately and do not use this class. """ + PORT_NODE_PLAIN = 50211 PORT_NODE_TLS = 50212 TLS_PORTS = {PORT_NODE_TLS} PLAIN_PORTS = {PORT_NODE_PLAIN} - + # Regular expression to parse a host:port string - HOST_PORT_PATTERN = re.compile(r'^(\S+):(\d+)$') - + HOST_PORT_PATTERN = re.compile(r"^(\S+):(\d+)$") + def __init__(self, address=None, port=None): """ Initialize a new ManagedNodeAddress instance. - + Args: address (str, optional): The host address. port (int, optional): The port number. """ self._address = address self._port = port - + @classmethod def _from_string(cls, address_str): """ Create a ManagedNodeAddress from a string in the format 'host:port'. - + Args: address_str (str): A string in the format 'host:port'. - + Returns: ManagedNodeAddress: A new ManagedNodeAddress instance. - + Raises: ValueError: If the address string is not in the correct format. """ match = cls.HOST_PORT_PATTERN.match(address_str) - + if match: host = match.group(1) try: port = int(match.group(2)) return cls(address=host, port=port) - except ValueError: - raise ValueError(f"Failed to parse port number: {match.group(2)}") - + except ValueError as e: + raise ValueError(f"Failed to parse port number: {match.group(2)}") from e + raise ValueError("Failed to parse node address. Format should be 'host:port'") - + def _is_transport_security(self): """ Check if the address uses a secure port. - + Returns: bool: True if the port is a secure port (50212), False otherwise. """ return self._port in self.TLS_PORTS - + def _to_secure(self): """ Return a new ManagedNodeAddress that uses the secure port when possible. @@ -67,12 +71,12 @@ def _to_secure(self): """ if self._is_transport_security(): return self - + port = self._port if port == self.PORT_NODE_PLAIN: port = self.PORT_NODE_TLS return _ManagedNodeAddress(self._address, port) - + def _to_insecure(self): """ Return a new ManagedNodeAddress that uses the plaintext port when possible. @@ -80,32 +84,28 @@ def _to_insecure(self): """ if not self._is_transport_security(): return self - + port = self._port if port == self.PORT_NODE_TLS: port = self.PORT_NODE_PLAIN return _ManagedNodeAddress(self._address, port) - + def _get_host(self): - """ - Return the host component of the address. - """ + """Return the host component of the address.""" return self._address def _get_port(self): - """ - Return the port component of the address. - """ + """Return the port component of the address.""" return self._port def __str__(self): """ Get a string representation of the ManagedNodeAddress. - + Returns: str: The string representation in the format 'host:port'. """ if self._address is not None: return f"{self._address}:{self._port}" - + return "" diff --git a/src/hiero_sdk_python/node.py b/src/hiero_sdk_python/node.py index 7ffaf574f..a83918508 100644 --- a/src/hiero_sdk_python/node.py +++ b/src/hiero_sdk_python/node.py @@ -1,14 +1,18 @@ +from __future__ import annotations + import hashlib import socket -import ssl # Python's ssl module implements TLS (despite the name) +import ssl # Python's ssl module implements TLS (despite the name) import time + import grpc -from typing import Optional + from hiero_sdk_python.account.account_id import AccountId -from hiero_sdk_python.channels import _Channel from hiero_sdk_python.address_book.node_address import NodeAddress +from hiero_sdk_python.channels import _Channel from hiero_sdk_python.managed_node_address import _ManagedNodeAddress + # Timeout for fetching server certificates during TLS validation CERT_FETCH_TIMEOUT_SECONDS = 10 @@ -20,7 +24,7 @@ class _HederaTrustManager: against expected hashes from the address book. """ - def __init__(self, cert_hash: Optional[bytes], verify_certificate: bool): + def __init__(self, cert_hash: bytes | None, verify_certificate: bool): """ Initialize the trust manager. @@ -74,7 +78,6 @@ def check_server_trusted(self, pem_cert: bytes) -> bool: class _Node: - def __init__(self, account_id: AccountId, address: str, address_book: NodeAddress): """ Initialize a new Node instance. @@ -84,14 +87,13 @@ def __init__(self, account_id: AccountId, address: str, address_book: NodeAddres address (str): The address of the node. min_backoff (int): The minimum backoff time in seconds. """ - self._account_id: AccountId = account_id - self._channel: Optional[_Channel] = None + self._channel: _Channel | None = None self._address_book: NodeAddress = address_book self._address: _ManagedNodeAddress = _ManagedNodeAddress._from_string(address) self._verify_certificates: bool = True - self._root_certificates: Optional[bytes] = None - self._node_pem_cert: Optional[bytes] = None + self._root_certificates: bytes | None = None + self._node_pem_cert: bytes | None = None self._min_backoff: float = 8 # seconds self._max_backoff: float = 3600 # seconds @@ -141,9 +143,7 @@ def _get_channel(self): private_key=None, certificate_chain=None, ) - channel = grpc.secure_channel( - str(self._address), credentials, options=options - ) + channel = grpc.secure_channel(str(self._address), credentials, options=options) else: channel = grpc.insecure_channel(str(self._address)) @@ -152,9 +152,7 @@ def _get_channel(self): return self._channel def _apply_transport_security(self, enabled: bool): - """ - Update the node's address to use secure or insecure transport. - """ + """Update the node's address to use secure or insecure transport.""" if enabled and self._address._is_transport_security(): return if not enabled and not self._address._is_transport_security(): @@ -167,18 +165,14 @@ def _apply_transport_security(self, enabled: bool): else: self._address = self._address._to_insecure() - def _set_root_certificates(self, root_certificates: Optional[bytes]): - """ - Assign custom root certificates used for TLS verification. - """ + def _set_root_certificates(self, root_certificates: bytes | None): + """Assign custom root certificates used for TLS verification.""" self._root_certificates = root_certificates if self._channel and self._address._is_transport_security(): self._close() def _set_verify_certificates(self, verify: bool): - """ - Set whether TLS certificates should be verified. - """ + """Set whether TLS certificates should be verified.""" if self._verify_certificates == verify: return @@ -207,7 +201,7 @@ def _build_channel_options(self): communicating with the correct Hedera node regardless of the hostname or IP address used to connect. """ - options = [ + return [ ("grpc.default_authority", "127.0.0.1"), ("grpc.ssl_target_name_override", "127.0.0.1"), ("grpc.keepalive_time_ms", 100000), @@ -215,8 +209,6 @@ def _build_channel_options(self): ("grpc.keepalive_permit_without_calls", 1), ] - return options - def _validate_tls_certificate_with_trust_manager(self): """ Validate the remote TLS certificate using HederaTrustManager. @@ -231,9 +223,7 @@ def _validate_tls_certificate_with_trust_manager(self): cert_hash = None if self._address_book: # pylint: disable=protected-access - cert_hash = ( - self._address_book._cert_hash - ) # pylint: disable=protected-access + cert_hash = self._address_book._cert_hash # pylint: disable=protected-access # Skip validation if no cert hash is available (e.g., in unit tests) # This allows tests to run without address books while still enabling @@ -247,9 +237,7 @@ def _validate_tls_certificate_with_trust_manager(self): @staticmethod def _normalize_cert_hash(cert_hash: bytes) -> str: - """ - Normalize the certificate hash to a lowercase hex string. - """ + """Normalize the certificate hash to a lowercase hex string.""" try: decoded = cert_hash.decode("utf-8").strip().lower() if decoded.startswith("0x"): @@ -285,17 +273,14 @@ def _fetch_server_certificate_pem(self) -> bytes: context.check_hostname = False context.verify_mode = ssl.CERT_NONE - with socket.create_connection( - (host, port), timeout=CERT_FETCH_TIMEOUT_SECONDS - ) as sock: - with context.wrap_socket( - sock, server_hostname=server_hostname - ) as tls_socket: - der_cert = tls_socket.getpeercert(True) + with ( + socket.create_connection((host, port), timeout=CERT_FETCH_TIMEOUT_SECONDS) as sock, + context.wrap_socket(sock, server_hostname=server_hostname) as tls_socket, + ): + der_cert = tls_socket.getpeercert(True) # Convert DER to PEM format (matching Java's PEM encoding) - pem_cert = ssl.DER_cert_to_PEM_cert(der_cert).encode("utf-8") - return pem_cert + return ssl.DER_cert_to_PEM_cert(der_cert).encode("utf-8") def is_healthy(self) -> bool: """ @@ -307,16 +292,11 @@ def is_healthy(self) -> bool: return self._readmit_time <= time.monotonic() def _increase_backoff(self) -> None: - """ - Increase the node's backoff duration after a failure. - """ + """Increase the node's backoff duration after a failure.""" self._bad_grpc_response_count += 1 self._current_backoff = min(self._current_backoff * 2, self._max_backoff) self._readmit_time = time.monotonic() + self._current_backoff def _decrease_backoff(self) -> None: - """ - Decrease the node's backoff duration after a successful operation. - """ + """Decrease the node's backoff duration after a successful operation.""" self._current_backoff = max(self._current_backoff / 2, self._min_backoff) - diff --git a/src/hiero_sdk_python/nodes/node_create_transaction.py b/src/hiero_sdk_python/nodes/node_create_transaction.py index 342b25fad..0431a1f8f 100644 --- a/src/hiero_sdk_python/nodes/node_create_transaction.py +++ b/src/hiero_sdk_python/nodes/node_create_transaction.py @@ -1,9 +1,8 @@ -""" -NodeCreateTransaction class. -""" +"""NodeCreateTransaction class.""" + +from __future__ import annotations from dataclasses import dataclass, field -from typing import List, Optional from hiero_sdk_python.account.account_id import AccountId from hiero_sdk_python.address_book.endpoint import Endpoint @@ -11,11 +10,11 @@ from hiero_sdk_python.crypto.public_key import PublicKey from hiero_sdk_python.executable import _Method from hiero_sdk_python.hapi.services.node_create_pb2 import NodeCreateTransactionBody -from hiero_sdk_python.hapi.services.transaction_pb2 import TransactionBody -from hiero_sdk_python.transaction.transaction import Transaction from hiero_sdk_python.hapi.services.schedulable_transaction_body_pb2 import ( SchedulableTransactionBody, ) +from hiero_sdk_python.hapi.services.transaction_pb2 import TransactionBody +from hiero_sdk_python.transaction.transaction import Transaction @dataclass @@ -24,26 +23,26 @@ class NodeCreateParams: Represents node attributes that can be set on creation. Attributes: - account_id (Optional[AccountId]): The account ID of the node. - description (Optional[str]): The description of the node. - gossip_endpoints (List[Endpoint]): The gossip endpoints of the node. - service_endpoints (List[Endpoint]): The service endpoints of the node. - gossip_ca_certificate (Optional[bytes]): The gossip ca certificate of the node. - grpc_certificate_hash (Optional[bytes]): The grpc certificate hash of the node. - admin_key (Optional[PublicKey]): The admin key of the node. - decline_reward (Optional[bool]): The decline reward of the node. - grpc_web_proxy_endpoint (Optional[Endpoint]): The grpc web proxy endpoint of the node. + account_id (AccountId, optional): The account ID of the node. + description (str, optional): The description of the node. + gossip_endpoints (list[Endpoint]): The gossip endpoints of the node. + service_endpoints (list[Endpoint]): The service endpoints of the node. + gossip_ca_certificate (bytes, optional): The gossip ca certificate of the node. + grpc_certificate_hash (bytes, optional): The grpc certificate hash of the node. + admin_key (PublicKey, optional): The admin key of the node. + decline_reward (bool, optional): The decline reward of the node. + grpc_web_proxy_endpoint (Endpoint, optional): The grpc web proxy endpoint of the node. """ - account_id: Optional[AccountId] = None - description: Optional[str] = None - gossip_endpoints: List[Endpoint] = field(default_factory=list) - service_endpoints: List[Endpoint] = field(default_factory=list) - gossip_ca_certificate: Optional[bytes] = None - grpc_certificate_hash: Optional[bytes] = None - admin_key: Optional[PublicKey] = None - decline_reward: Optional[bool] = None - grpc_web_proxy_endpoint: Optional[Endpoint] = None + account_id: AccountId | None = None + description: str | None = None + gossip_endpoints: list[Endpoint] = field(default_factory=list) + service_endpoints: list[Endpoint] = field(default_factory=list) + gossip_ca_certificate: bytes | None = None + grpc_certificate_hash: bytes | None = None + admin_key: PublicKey | None = None + decline_reward: bool | None = None + grpc_web_proxy_endpoint: Endpoint | None = None class NodeCreateTransaction(Transaction): @@ -57,33 +56,27 @@ class NodeCreateTransaction(Transaction): to build and execute a node create transaction. """ - def __init__(self, node_create_params: Optional[NodeCreateParams] = None): + def __init__(self, node_create_params: NodeCreateParams | None = None): """ Initializes a new NodeCreateTransaction instance with the specified parameters. Args: - node_create_params (Optional[NodeCreateParams]): + node_create_params (NodeCreateParams, optional): The parameters for the node create transaction. """ super().__init__() node_create_params = node_create_params or NodeCreateParams() - self.account_id: Optional[AccountId] = node_create_params.account_id - self.description: Optional[str] = node_create_params.description - self.gossip_endpoints: List[Endpoint] = node_create_params.gossip_endpoints - self.service_endpoints: List[Endpoint] = node_create_params.service_endpoints - self.gossip_ca_certificate: Optional[bytes] = ( - node_create_params.gossip_ca_certificate - ) - self.grpc_certificate_hash: Optional[bytes] = ( - node_create_params.grpc_certificate_hash - ) - self.admin_key: Optional[PublicKey] = node_create_params.admin_key - self.decline_reward: Optional[bool] = node_create_params.decline_reward - self.grpc_web_proxy_endpoint: Optional[Endpoint] = ( - node_create_params.grpc_web_proxy_endpoint - ) - - def set_account_id(self, account_id: Optional[AccountId]) -> "NodeCreateTransaction": + self.account_id: AccountId | None = node_create_params.account_id + self.description: str | None = node_create_params.description + self.gossip_endpoints: list[Endpoint] = node_create_params.gossip_endpoints + self.service_endpoints: list[Endpoint] = node_create_params.service_endpoints + self.gossip_ca_certificate: bytes | None = node_create_params.gossip_ca_certificate + self.grpc_certificate_hash: bytes | None = node_create_params.grpc_certificate_hash + self.admin_key: PublicKey | None = node_create_params.admin_key + self.decline_reward: bool | None = node_create_params.decline_reward + self.grpc_web_proxy_endpoint: Endpoint | None = node_create_params.grpc_web_proxy_endpoint + + def set_account_id(self, account_id: AccountId | None) -> NodeCreateTransaction: """ Sets the account id for this node create transaction. @@ -98,7 +91,7 @@ def set_account_id(self, account_id: Optional[AccountId]) -> "NodeCreateTransact self.account_id = account_id return self - def set_description(self, description: Optional[str]) -> "NodeCreateTransaction": + def set_description(self, description: str | None) -> NodeCreateTransaction: """ Sets the description for this node create transaction. @@ -113,14 +106,12 @@ def set_description(self, description: Optional[str]) -> "NodeCreateTransaction" self.description = description return self - def set_gossip_endpoints( - self, gossip_endpoints: Optional[List[Endpoint]] - ) -> "NodeCreateTransaction": + def set_gossip_endpoints(self, gossip_endpoints: list[Endpoint] | None) -> NodeCreateTransaction: """ Sets the gossip endpoints for this node create transaction. Args: - gossip_endpoints (List[Endpoint]): + gossip_endpoints (list[Endpoint] | None): The gossip endpoints of the node. Returns: @@ -130,14 +121,12 @@ def set_gossip_endpoints( self.gossip_endpoints = gossip_endpoints return self - def set_service_endpoints( - self, service_endpoints: Optional[List[Endpoint]] - ) -> "NodeCreateTransaction": + def set_service_endpoints(self, service_endpoints: list[Endpoint] | None) -> NodeCreateTransaction: """ Sets the service endpoints for this node create transaction. Args: - service_endpoints (List[Endpoint]): + service_endpoints (list[Endpoint] | None): The service endpoints of the node. Returns: @@ -147,9 +136,7 @@ def set_service_endpoints( self.service_endpoints = service_endpoints return self - def set_gossip_ca_certificate( - self, gossip_ca_certificate: Optional[bytes] - ) -> "NodeCreateTransaction": + def set_gossip_ca_certificate(self, gossip_ca_certificate: bytes | None) -> NodeCreateTransaction: """ Sets the gossip ca certificate for this node create transaction. @@ -164,9 +151,7 @@ def set_gossip_ca_certificate( self.gossip_ca_certificate = gossip_ca_certificate return self - def set_grpc_certificate_hash( - self, grpc_certificate_hash: Optional[bytes] - ) -> "NodeCreateTransaction": + def set_grpc_certificate_hash(self, grpc_certificate_hash: bytes | None) -> NodeCreateTransaction: """ Sets the grpc certificate hash for this node create transaction. @@ -181,7 +166,7 @@ def set_grpc_certificate_hash( self.grpc_certificate_hash = grpc_certificate_hash return self - def set_admin_key(self, admin_key: Optional[PublicKey]) -> "NodeCreateTransaction": + def set_admin_key(self, admin_key: PublicKey | None) -> NodeCreateTransaction: """ Sets the admin key for this node create transaction. @@ -196,9 +181,7 @@ def set_admin_key(self, admin_key: Optional[PublicKey]) -> "NodeCreateTransactio self.admin_key = admin_key return self - def set_decline_reward( - self, decline_reward: Optional[bool] - ) -> "NodeCreateTransaction": + def set_decline_reward(self, decline_reward: bool | None) -> NodeCreateTransaction: """ Sets the decline reward for this node create transaction. @@ -213,9 +196,7 @@ def set_decline_reward( self.decline_reward = decline_reward return self - def set_grpc_web_proxy_endpoint( - self, grpc_web_proxy_endpoint: Optional[Endpoint] - ) -> "NodeCreateTransaction": + def set_grpc_web_proxy_endpoint(self, grpc_web_proxy_endpoint: Endpoint | None) -> NodeCreateTransaction: """ Sets the grpc web proxy endpoint for this node create transaction. @@ -240,21 +221,13 @@ def _build_proto_body(self) -> NodeCreateTransactionBody: return NodeCreateTransactionBody( account_id=self.account_id._to_proto() if self.account_id else None, description=self.description, - gossip_endpoint=[ - endpoint._to_proto() for endpoint in self.gossip_endpoints or [] - ], - service_endpoint=[ - endpoint._to_proto() for endpoint in self.service_endpoints or [] - ], + gossip_endpoint=[endpoint._to_proto() for endpoint in self.gossip_endpoints or []], + service_endpoint=[endpoint._to_proto() for endpoint in self.service_endpoints or []], gossip_ca_certificate=self.gossip_ca_certificate, grpc_certificate_hash=self.grpc_certificate_hash, admin_key=self.admin_key._to_proto() if self.admin_key else None, decline_reward=self.decline_reward, - grpc_proxy_endpoint=( - self.grpc_web_proxy_endpoint._to_proto() - if self.grpc_web_proxy_endpoint - else None - ), + grpc_proxy_endpoint=(self.grpc_web_proxy_endpoint._to_proto() if self.grpc_web_proxy_endpoint else None), ) def build_transaction_body(self) -> TransactionBody: @@ -268,7 +241,7 @@ def build_transaction_body(self) -> TransactionBody: transaction_body = self.build_base_transaction_body() transaction_body.nodeCreate.CopyFrom(node_create_body) return transaction_body - + def build_scheduled_body(self) -> SchedulableTransactionBody: """ Builds the scheduled transaction body for node create transaction. @@ -281,7 +254,6 @@ def build_scheduled_body(self) -> SchedulableTransactionBody: scheduled_body.nodeCreate.CopyFrom(node_create_body) return scheduled_body - def _get_method(self, channel: _Channel) -> _Method: """ Gets the method to execute the node create transaction. diff --git a/src/hiero_sdk_python/nodes/node_delete_transaction.py b/src/hiero_sdk_python/nodes/node_delete_transaction.py index df7f688cb..ab8d5231a 100644 --- a/src/hiero_sdk_python/nodes/node_delete_transaction.py +++ b/src/hiero_sdk_python/nodes/node_delete_transaction.py @@ -1,8 +1,6 @@ -""" -NodeDeleteTransaction class. -""" +"""NodeDeleteTransaction class.""" -from typing import Optional +from __future__ import annotations from hiero_sdk_python.channels import _Channel from hiero_sdk_python.executable import _Method @@ -24,23 +22,23 @@ class NodeDeleteTransaction(Transaction): to build and execute a node delete transaction. """ - def __init__(self, node_id: Optional[int] = None): + def __init__(self, node_id: int | None = None): """ Initializes a new NodeDeleteTransaction instance with the specified parameters. Args: - node_id (Optional[int]): + node_id (int, optional): The parameters for the node delete transaction. """ super().__init__() - self.node_id: Optional[int] = node_id + self.node_id: int | None = node_id - def set_node_id(self, node_id: Optional[int]) -> "NodeDeleteTransaction": + def set_node_id(self, node_id: int | None) -> NodeDeleteTransaction: """ Sets the node id for this node delete transaction. Args: - node_id (Optional[int]): + node_id (int | None): The node id of the node. Returns: @@ -107,6 +105,4 @@ def _get_method(self, channel: _Channel) -> _Method: Returns: _Method: An object containing the transaction function to delete a node. """ - return _Method( - transaction_func=channel.address_book.deleteNode, query_func=None - ) + return _Method(transaction_func=channel.address_book.deleteNode, query_func=None) diff --git a/src/hiero_sdk_python/nodes/node_update_transaction.py b/src/hiero_sdk_python/nodes/node_update_transaction.py index f4c356f4e..da9e169ae 100644 --- a/src/hiero_sdk_python/nodes/node_update_transaction.py +++ b/src/hiero_sdk_python/nodes/node_update_transaction.py @@ -1,9 +1,9 @@ -""" -NodeUpdateTransaction class. -""" +"""NodeUpdateTransaction class.""" + +from __future__ import annotations from dataclasses import dataclass, field -from typing import Any, List, Optional +from typing import Any from google.protobuf.wrappers_pb2 import BoolValue, BytesValue, StringValue @@ -26,28 +26,28 @@ class NodeUpdateParams: Represents node attributes that can be set on update. Attributes: - node_id (Optional[int]): The node ID of the node. - account_id (Optional[AccountId]): The account ID of the node. - description (Optional[str]): The description of the node. - gossip_endpoints (List[Endpoint]): The gossip endpoints of the node. - service_endpoints (List[Endpoint]): The service endpoints of the node. - gossip_ca_certificate (Optional[bytes]): The gossip ca certificate of the node. - grpc_certificate_hash (Optional[bytes]): The grpc certificate hash of the node. - admin_key (Optional[PublicKey]): The admin key of the node. - decline_reward (Optional[bool]): The decline reward of the node. - grpc_web_proxy_endpoint (Optional[Endpoint]): The grpc web proxy endpoint of the node. + node_id (int, optional): The node ID of the node. + account_id (AccountId, optional): The account ID of the node. + description (str, optional): The description of the node. + gossip_endpoints (list[Endpoint]): The gossip endpoints of the node. + service_endpoints (list[Endpoint]): The service endpoints of the node. + gossip_ca_certificate (bytes, None): The gossip ca certificate of the node. + grpc_certificate_hash (bytes, None): The grpc certificate hash of the node. + admin_key (PublicKey, optional): The admin key of the node. + decline_reward (bool, optional): The decline reward of the node. + grpc_web_proxy_endpoint (Endpoint, optional): The grpc web proxy endpoint of the node. """ - node_id: Optional[int] = None - account_id: Optional[AccountId] = None - description: Optional[str] = None - gossip_endpoints: List[Endpoint] = field(default_factory=list) - service_endpoints: List[Endpoint] = field(default_factory=list) - gossip_ca_certificate: Optional[bytes] = None - grpc_certificate_hash: Optional[bytes] = None - admin_key: Optional[PublicKey] = None - decline_reward: Optional[bool] = None - grpc_web_proxy_endpoint: Optional[Endpoint] = None + node_id: int | None = None + account_id: AccountId | None = None + description: str | None = None + gossip_endpoints: list[Endpoint] = field(default_factory=list) + service_endpoints: list[Endpoint] = field(default_factory=list) + gossip_ca_certificate: bytes | None = None + grpc_certificate_hash: bytes | None = None + admin_key: PublicKey | None = None + decline_reward: bool | None = None + grpc_web_proxy_endpoint: Endpoint | None = None class NodeUpdateTransaction(Transaction): @@ -61,39 +61,33 @@ class NodeUpdateTransaction(Transaction): to build and execute a node update transaction. """ - def __init__(self, node_update_params: Optional[NodeUpdateParams] = None): + def __init__(self, node_update_params: NodeUpdateParams | None = None): """ Initializes a new NodeUpdateTransaction instance with the specified parameters. Args: - node_update_params (Optional[NodeUpdateParams]): + node_update_params (NodeUpdateParams, optional): The parameters for the node update transaction. """ super().__init__() node_update_params = node_update_params or NodeUpdateParams() - self.node_id: Optional[int] = node_update_params.node_id - self.account_id: Optional[AccountId] = node_update_params.account_id - self.description: Optional[str] = node_update_params.description - self.gossip_endpoints: List[Endpoint] = node_update_params.gossip_endpoints - self.service_endpoints: List[Endpoint] = node_update_params.service_endpoints - self.gossip_ca_certificate: Optional[bytes] = ( - node_update_params.gossip_ca_certificate - ) - self.grpc_certificate_hash: Optional[bytes] = ( - node_update_params.grpc_certificate_hash - ) - self.admin_key: Optional[PublicKey] = node_update_params.admin_key - self.decline_reward: Optional[bool] = node_update_params.decline_reward - self.grpc_web_proxy_endpoint: Optional[Endpoint] = ( - node_update_params.grpc_web_proxy_endpoint - ) - - def set_node_id(self, node_id: Optional[int]) -> "NodeUpdateTransaction": + self.node_id: int | None = node_update_params.node_id + self.account_id: AccountId | None = node_update_params.account_id + self.description: str | None = node_update_params.description + self.gossip_endpoints: list[Endpoint] = node_update_params.gossip_endpoints + self.service_endpoints: list[Endpoint] = node_update_params.service_endpoints + self.gossip_ca_certificate: bytes | None = node_update_params.gossip_ca_certificate + self.grpc_certificate_hash: bytes | None = node_update_params.grpc_certificate_hash + self.admin_key: PublicKey | None = node_update_params.admin_key + self.decline_reward: bool | None = node_update_params.decline_reward + self.grpc_web_proxy_endpoint: Endpoint | None = node_update_params.grpc_web_proxy_endpoint + + def set_node_id(self, node_id: int | None) -> NodeUpdateTransaction: """ Sets the node id for this node update transaction. Args: - node_id (Optional[int]): + node_id (int): The node id of the node. Returns: @@ -103,7 +97,7 @@ def set_node_id(self, node_id: Optional[int]) -> "NodeUpdateTransaction": self.node_id = node_id return self - def set_account_id(self, account_id: Optional[AccountId]) -> "NodeUpdateTransaction": + def set_account_id(self, account_id: AccountId | None) -> NodeUpdateTransaction: """ Sets the account id for this node update transaction. @@ -118,7 +112,7 @@ def set_account_id(self, account_id: Optional[AccountId]) -> "NodeUpdateTransact self.account_id = account_id return self - def set_description(self, description: Optional[str]) -> "NodeUpdateTransaction": + def set_description(self, description: str | None) -> NodeUpdateTransaction: """ Sets the description for this node update transaction. @@ -133,14 +127,12 @@ def set_description(self, description: Optional[str]) -> "NodeUpdateTransaction" self.description = description return self - def set_gossip_endpoints( - self, gossip_endpoints: Optional[List[Endpoint]] - ) -> "NodeUpdateTransaction": + def set_gossip_endpoints(self, gossip_endpoints: list[Endpoint] | None) -> NodeUpdateTransaction: """ Sets the gossip endpoints for this node update transaction. Args: - gossip_endpoints (List[Endpoint]): + gossip_endpoints (list[Endpoint]): The gossip endpoints of the node. Returns: @@ -150,14 +142,12 @@ def set_gossip_endpoints( self.gossip_endpoints = gossip_endpoints return self - def set_service_endpoints( - self, service_endpoints: Optional[List[Endpoint]] - ) -> "NodeUpdateTransaction": + def set_service_endpoints(self, service_endpoints: list[Endpoint] | None) -> NodeUpdateTransaction: """ Sets the service endpoints for this node update transaction. Args: - service_endpoints (List[Endpoint]): + service_endpoints (list[Endpoint]): The service endpoints of the node. Returns: @@ -167,9 +157,7 @@ def set_service_endpoints( self.service_endpoints = service_endpoints return self - def set_gossip_ca_certificate( - self, gossip_ca_certificate: Optional[bytes] - ) -> "NodeUpdateTransaction": + def set_gossip_ca_certificate(self, gossip_ca_certificate: bytes | None) -> NodeUpdateTransaction: """ Sets the gossip ca certificate for this node update transaction. @@ -184,9 +172,7 @@ def set_gossip_ca_certificate( self.gossip_ca_certificate = gossip_ca_certificate return self - def set_grpc_certificate_hash( - self, grpc_certificate_hash: Optional[bytes] - ) -> "NodeUpdateTransaction": + def set_grpc_certificate_hash(self, grpc_certificate_hash: bytes | None) -> NodeUpdateTransaction: """ Sets the grpc certificate hash for this node update transaction. @@ -201,7 +187,7 @@ def set_grpc_certificate_hash( self.grpc_certificate_hash = grpc_certificate_hash return self - def set_admin_key(self, admin_key: Optional[PublicKey]) -> "NodeUpdateTransaction": + def set_admin_key(self, admin_key: PublicKey | None) -> NodeUpdateTransaction: """ Sets the admin key for this node update transaction. @@ -216,9 +202,7 @@ def set_admin_key(self, admin_key: Optional[PublicKey]) -> "NodeUpdateTransactio self.admin_key = admin_key return self - def set_decline_reward( - self, decline_reward: Optional[bool] - ) -> "NodeUpdateTransaction": + def set_decline_reward(self, decline_reward: bool | None) -> NodeUpdateTransaction: """ Sets the decline reward for this node update transaction. @@ -233,9 +217,7 @@ def set_decline_reward( self.decline_reward = decline_reward return self - def set_grpc_web_proxy_endpoint( - self, grpc_web_proxy_endpoint: Optional[Endpoint] - ) -> "NodeUpdateTransaction": + def set_grpc_web_proxy_endpoint(self, grpc_web_proxy_endpoint: Endpoint | None) -> NodeUpdateTransaction: """ Sets the grpc web proxy endpoint for this node update transaction. @@ -250,12 +232,12 @@ def set_grpc_web_proxy_endpoint( self.grpc_web_proxy_endpoint = grpc_web_proxy_endpoint return self - def _convert_to_proto(self, obj: Optional[Any]) -> Any: - """Convert object to proto if it exists, otherwise return None""" + def _convert_to_proto(self, obj: Any | None) -> Any: + """Convert object to proto if it exists, otherwise return None.""" return obj._to_proto() if obj else None - def _convert_to_proto_list(self, obj: Optional[List[Any]]) -> Any: - """Convert list of objects to proto if it exists, otherwise return empty list""" + def _convert_to_proto_list(self, obj: list[Any] | None) -> Any: + """Convert list of objects to proto if it exists, otherwise return empty list.""" return [obj._to_proto() for obj in obj or []] def _build_proto_body(self) -> NodeUpdateTransactionBody: @@ -268,29 +250,17 @@ def _build_proto_body(self) -> NodeUpdateTransactionBody: return NodeUpdateTransactionBody( node_id=self.node_id, account_id=self._convert_to_proto(self.account_id), - description=( - StringValue(value=self.description) - if self.description is not None - else None - ), + description=(StringValue(value=self.description) if self.description is not None else None), gossip_endpoint=self._convert_to_proto_list(self.gossip_endpoints), service_endpoint=self._convert_to_proto_list(self.service_endpoints), gossip_ca_certificate=( - BytesValue(value=self.gossip_ca_certificate) - if self.gossip_ca_certificate is not None - else None + BytesValue(value=self.gossip_ca_certificate) if self.gossip_ca_certificate is not None else None ), grpc_certificate_hash=( - BytesValue(value=self.grpc_certificate_hash) - if self.grpc_certificate_hash is not None - else None + BytesValue(value=self.grpc_certificate_hash) if self.grpc_certificate_hash is not None else None ), admin_key=self._convert_to_proto(self.admin_key), - decline_reward=( - BoolValue(value=self.decline_reward) - if self.decline_reward is not None - else None - ), + decline_reward=(BoolValue(value=self.decline_reward) if self.decline_reward is not None else None), grpc_proxy_endpoint=self._convert_to_proto(self.grpc_web_proxy_endpoint), ) diff --git a/src/hiero_sdk_python/prng_transaction.py b/src/hiero_sdk_python/prng_transaction.py index 1ce28d924..26c142e78 100644 --- a/src/hiero_sdk_python/prng_transaction.py +++ b/src/hiero_sdk_python/prng_transaction.py @@ -1,8 +1,6 @@ -""" -PrngTransaction class. -""" +"""PrngTransaction class.""" -from typing import Optional +from __future__ import annotations from hiero_sdk_python.channels import _Channel from hiero_sdk_python.executable import _Method @@ -16,7 +14,7 @@ class PrngTransaction(Transaction): This transaction can be used to request a pseudo-random number. You can specify the range of the pseudo-random number. - + If no range is specified, the transaction will return a 48 byte unsigned pseudo-random number. @@ -24,22 +22,22 @@ class PrngTransaction(Transaction): to build and execute a prng transaction. """ - def __init__(self, range: Optional[int] = None): + def __init__(self, range: int | None = None): """ Initializes a new PrngTransaction instance. Args: - range (Optional[int]): The range of the pseudo-random number. + range (int | None): The range of the pseudo-random number. """ super().__init__() - self.range: Optional[int] = range + self.range: int | None = range - def set_range(self, range: Optional[int]) -> "PrngTransaction": + def set_range(self, range: int | None) -> PrngTransaction: """ Sets the range for the transaction. Args: - range (Optional[int]): The range of the pseudo-random number. + range (int | None): The range of the pseudo-random number. """ self._require_not_frozen() self.range = range @@ -51,13 +49,13 @@ def _build_proto_body(self): Returns: UtilPrngTransactionBody: The protobuf body for the prng transaction. - + Raises: ValueError: If the range is negative. """ if self.range is not None and self.range < 0: raise ValueError("Range can't be negative.") - + return UtilPrngTransactionBody(range=self.range) def build_transaction_body(self): @@ -67,7 +65,7 @@ def build_transaction_body(self): Returns: TransactionBody: The protobuf transaction body containing the prng details. - + Raises: ValueError: If the range is negative. """ diff --git a/src/hiero_sdk_python/query/account_balance_query.py b/src/hiero_sdk_python/query/account_balance_query.py index 3de2c27b6..82e3b6578 100644 --- a/src/hiero_sdk_python/query/account_balance_query.py +++ b/src/hiero_sdk_python/query/account_balance_query.py @@ -1,13 +1,16 @@ +from __future__ import annotations + import traceback -from typing import Optional, Any, Union -from hiero_sdk_python.client.client import Client -from hiero_sdk_python.query.query import Query -from hiero_sdk_python.hapi.services import crypto_get_account_balance_pb2, query_pb2 -from hiero_sdk_python.account.account_id import AccountId +from typing import Any + from hiero_sdk_python.account.account_balance import AccountBalance -from hiero_sdk_python.executable import _Method +from hiero_sdk_python.account.account_id import AccountId from hiero_sdk_python.channels import _Channel +from hiero_sdk_python.client.client import Client from hiero_sdk_python.contract.contract_id import ContractId +from hiero_sdk_python.executable import _Method +from hiero_sdk_python.hapi.services import crypto_get_account_balance_pb2, query_pb2 +from hiero_sdk_python.query.query import Query class CryptoGetAccountBalanceQuery(Query): @@ -20,8 +23,8 @@ class CryptoGetAccountBalanceQuery(Query): def __init__( self, - account_id: Optional[AccountId] = None, - contract_id: Optional[ContractId] = None, + account_id: AccountId | None = None, + contract_id: ContractId | None = None, ) -> None: """ Initializes a new instance of the CryptoGetAccountBalanceQuery class. @@ -31,15 +34,15 @@ def __init__( contract_id (ContractId, optional): The ID of the contract to retrieve the balance for. """ super().__init__() - self.account_id: Optional[AccountId] = None - self.contract_id: Optional[ContractId] = None + self.account_id: AccountId | None = None + self.contract_id: ContractId | None = None if account_id is not None: self.set_account_id(account_id) if contract_id is not None: self.set_contract_id(contract_id) - def set_account_id(self, account_id: AccountId) -> "CryptoGetAccountBalanceQuery": + def set_account_id(self, account_id: AccountId) -> CryptoGetAccountBalanceQuery: """ Sets the account ID for which to retrieve the balance. Resets to None the contract ID. @@ -56,7 +59,7 @@ def set_account_id(self, account_id: AccountId) -> "CryptoGetAccountBalanceQuery self.account_id = account_id return self - def set_contract_id(self, contract_id: ContractId) -> "CryptoGetAccountBalanceQuery": + def set_contract_id(self, contract_id: ContractId) -> CryptoGetAccountBalanceQuery: """ Sets the contract ID for which to retrieve the balance. Resets to None the account ID. @@ -94,9 +97,7 @@ def _make_request(self) -> query_pb2.Query: raise ValueError("Specify either account_id or contract_id, not both.") query_header = self._make_request_header() - crypto_get_balance = ( - crypto_get_account_balance_pb2.CryptoGetAccountBalanceQuery() - ) + crypto_get_balance = crypto_get_account_balance_pb2.CryptoGetAccountBalanceQuery() crypto_get_balance.header.CopyFrom(query_header) if self.account_id: @@ -106,9 +107,7 @@ def _make_request(self) -> query_pb2.Query: query = query_pb2.Query() if not hasattr(query, "cryptogetAccountBalance"): - raise AttributeError( - "Query object has no attribute 'cryptogetAccountBalance'" - ) + raise AttributeError("Query object has no attribute 'cryptogetAccountBalance'") query.cryptogetAccountBalance.CopyFrom(crypto_get_balance) return query @@ -130,11 +129,9 @@ def _get_method(self, channel: _Channel) -> _Method: Returns: _Method: The method wrapper containing the query function """ - return _Method( - transaction_func=None, query_func=channel.crypto.cryptoGetBalance - ) + return _Method(transaction_func=None, query_func=channel.crypto.cryptoGetBalance) - def execute(self, client: Client, timeout: Optional[Union[int, float]] = None) -> AccountBalance: + def execute(self, client: Client, timeout: int | float | None = None) -> AccountBalance: """ Executes the account balance query. @@ -160,9 +157,7 @@ def execute(self, client: Client, timeout: Optional[Union[int, float]] = None) - return AccountBalance._from_proto(response.cryptogetAccountBalance) - def _get_query_response( - self, response: Any - ) -> crypto_get_account_balance_pb2.CryptoGetAccountBalanceResponse: + def _get_query_response(self, response: Any) -> crypto_get_account_balance_pb2.CryptoGetAccountBalanceResponse: """ Extracts the account balance response from the full response. diff --git a/src/hiero_sdk_python/query/account_info_query.py b/src/hiero_sdk_python/query/account_info_query.py index db701a74f..a1198f99e 100644 --- a/src/hiero_sdk_python/query/account_info_query.py +++ b/src/hiero_sdk_python/query/account_info_query.py @@ -1,49 +1,50 @@ -from typing import Optional, Union -from hiero_sdk_python.query.query import Query -from hiero_sdk_python.hapi.services import query_pb2, crypto_get_info_pb2 -from hiero_sdk_python.executable import _Method -from hiero_sdk_python.channels import _Channel +from __future__ import annotations from hiero_sdk_python.account.account_id import AccountId from hiero_sdk_python.account.account_info import AccountInfo +from hiero_sdk_python.channels import _Channel +from hiero_sdk_python.executable import _Method +from hiero_sdk_python.hapi.services import crypto_get_info_pb2, query_pb2 +from hiero_sdk_python.query.query import Query class AccountInfoQuery(Query): """ A query to retrieve information about a specific Account. - - This class constructs and executes a query to retrieve information - about an account on the network, including the account's properties + + This class constructs and executes a query to retrieve information + about an account on the network, including the account's properties and settings. - + """ - def __init__(self, account_id: Optional[AccountId] = None): + + def __init__(self, account_id: AccountId | None = None): """ Initializes a new AccountInfoQuery instance with an optional account_id. Args: - account_id (Optional[AccountId], optional): The ID of the account to query. + account_id (AccountId, optional): The ID of the account to query. """ super().__init__() - self.account_id : Optional[AccountId] = account_id - + self.account_id: AccountId | None = account_id + def set_account_id(self, account_id: AccountId): """ - Sets the ID of the account to query. + Sets the ID of the account to query. - Args: - account_id (AccountId): The ID of the account. + Args: + account_id (AccountId): The ID of the account. Returns: - AccountInfoQuery: Returns self for method chaining. + AccountInfoQuery: Returns self for method chaining. """ self.account_id = account_id return self - + def _make_request(self): """ Constructs the protobuf request for the query. - + Builds a CryptoGetInfoQuery protobuf message with the appropriate header and account ID. @@ -66,16 +67,16 @@ def _make_request(self): query = query_pb2.Query() query.cryptoGetInfo.CopyFrom(crypto_info_query) - + return query except Exception as e: print(f"Exception in _make_request: {e}") raise - + def _get_method(self, channel: _Channel) -> _Method: """ Returns the appropriate gRPC method for the account info query. - + Implements the abstract method from Query to provide the specific gRPC method for getting account information. @@ -85,15 +86,12 @@ def _get_method(self, channel: _Channel) -> _Method: Returns: _Method: The method wrapper containing the query function """ - return _Method( - transaction_func=None, - query_func=channel.crypto.getAccountInfo - ) + return _Method(transaction_func=None, query_func=channel.crypto.getAccountInfo) - def execute(self, client, timeout: Optional[Union[int, float]] = None): + def execute(self, client, timeout: int | float | None = None): """ Executes the account info query. - + Sends the query to the Hedera network and processes the response to return an AccountInfo object. @@ -101,7 +99,7 @@ def execute(self, client, timeout: Optional[Union[int, float]] = None): Args: client (Client): The client instance to use for execution - timeout (Optional[Union[int, float]]): The total execution timeout (in seconds) for this execution. + timeout (int | float, optional): The total execution timeout (in seconds) for this execution. Returns: AccountInfo: The account info from the network @@ -119,14 +117,14 @@ def execute(self, client, timeout: Optional[Union[int, float]] = None): def _get_query_response(self, response): """ Extracts the account info response from the full response. - + Implements the abstract method from Query to extract the specific account info response object. - + Args: response: The full response from the network - + Returns: The crypto get info response object """ - return response.cryptoGetInfo \ No newline at end of file + return response.cryptoGetInfo diff --git a/src/hiero_sdk_python/query/query.py b/src/hiero_sdk_python/query/query.py index d4f85df1e..6a9f093bc 100644 --- a/src/hiero_sdk_python/query/query.py +++ b/src/hiero_sdk_python/query/query.py @@ -1,10 +1,10 @@ -""" -Base class for all network queries. -""" +"""Base class for all network queries.""" + +from __future__ import annotations -from decimal import Decimal import time -from typing import Any, Optional, Union +from decimal import Decimal +from typing import Any from hiero_sdk_python.account.account_id import AccountId from hiero_sdk_python.channels import _Channel @@ -18,8 +18,8 @@ duration_pb2, query_header_pb2, query_pb2, + transaction_contents_pb2, transaction_pb2, - transaction_contents_pb2 ) from hiero_sdk_python.hbar import Hbar from hiero_sdk_python.response_code import ResponseCode @@ -50,14 +50,13 @@ def __init__(self) -> None: Sets timestamp, node account IDs, operator, query payment settings, and other properties needed for Hedera queries. """ - super().__init__() self.timestamp: int = int(time.time()) - self.operator: Optional[Operator] = None + self.operator: Operator | None = None self.node_index: int = 0 - self.payment_amount: Optional[Hbar] = None - self.max_query_payment: Optional[Hbar] = None + self.payment_amount: Hbar | None = None + self.max_query_payment: Hbar | None = None def _get_query_response(self, response: Any) -> query_pb2.Query: """ @@ -77,7 +76,7 @@ def _get_query_response(self, response: Any) -> query_pb2.Query: """ raise NotImplementedError("_get_query_response must be implemented by subclasses.") - def set_query_payment(self, payment_amount: Hbar) -> "Query": + def set_query_payment(self, payment_amount: Hbar) -> Query: """ Sets the payment amount for this query. @@ -92,8 +91,8 @@ def set_query_payment(self, payment_amount: Hbar) -> "Query": """ self.payment_amount = payment_amount return self - - def set_max_query_payment(self, max_query_payment: Union[int, float, Decimal, Hbar]) -> "Query": + + def set_max_query_payment(self, max_query_payment: int | float | Decimal | Hbar) -> Query: """ Sets the maximum Hbar amount that this query is allowed to cost. @@ -101,25 +100,20 @@ def set_max_query_payment(self, max_query_payment: Union[int, float, Decimal, Hb from the network and compare it against this value. If the cost exceeds the specified maximum, execution will fail early with an error instead of submitting the query. - + Args: - max_query_payment (Union[int, float, Decimal, Hbar]): + max_query_payment (int | float | Decimal | Hbar): The maximum amount of Hbar that any single query is allowed to cost. - + Returns: Query: The current query instance for method chaining. """ if isinstance(max_query_payment, bool) or not isinstance(max_query_payment, (int, float, Decimal, Hbar)): raise TypeError( - "max_query_payment must be int, float, Decimal, or Hbar, " - f"got {type(max_query_payment).__name__}" + f"max_query_payment must be int, float, Decimal, or Hbar, got {type(max_query_payment).__name__}" ) - - value = ( - max_query_payment - if isinstance(max_query_payment, Hbar) - else Hbar(max_query_payment) - ) + + value = max_query_payment if isinstance(max_query_payment, Hbar) else Hbar(max_query_payment) if value < Hbar(0): raise ValueError("max_query_payment must be non-negative") @@ -146,20 +140,17 @@ def _before_execute(self, client: Client) -> None: # get the cost from the network and set it as the payment amount if self.payment_amount is None and self._is_payment_required(): self.payment_amount = self.get_cost(client) - + # if max_query_payment not set fall back to the client-level default max query payment max_payment = ( - self.max_query_payment - if self.max_query_payment is not None - else client.default_max_query_payment + self.max_query_payment if self.max_query_payment is not None else client.default_max_query_payment ) - + if self.payment_amount > max_payment: raise ValueError( f"Query cost ℏ{self.payment_amount.to_hbars()} HBAR " f"exceeds max set query payment: ℏ{max_payment.to_hbars()} HBAR" ) - def _make_request_header(self) -> query_header_pb2.QueryHeader: """ @@ -188,11 +179,7 @@ def _make_request_header(self) -> query_header_pb2.QueryHeader: header.responseType = query_header_pb2.ResponseType.COST_ANSWER return header - if ( - self.operator is not None - and self.node_account_id is not None - and self.payment_amount is not None - ): + if self.operator is not None and self.node_account_id is not None and self.payment_amount is not None: payment_tx = self._build_query_payment_transaction( payer_account_id=self.operator.account_id, payer_private_key=self.operator.private_key, @@ -259,28 +246,18 @@ def _build_query_payment_transaction( # Create signature pair if payer_private_key.is_ed25519(): - sig_pair = basic_types_pb2.SignaturePair( - pubKeyPrefix=public_key_bytes, - ed25519=signature - ) + sig_pair = basic_types_pb2.SignaturePair(pubKeyPrefix=public_key_bytes, ed25519=signature) else: - sig_pair = basic_types_pb2.SignaturePair( - pubKeyPrefix=public_key_bytes, - ECDSA_secp256k1=signature - ) + sig_pair = basic_types_pb2.SignaturePair(pubKeyPrefix=public_key_bytes, ECDSA_secp256k1=signature) # Create signature map signature_map = basic_types_pb2.SignatureMap(sigPair=[sig_pair]) # Create signed transaction - signed_transaction = transaction_contents_pb2.SignedTransaction( - bodyBytes=body_bytes, sigMap=signature_map - ) + signed_transaction = transaction_contents_pb2.SignedTransaction(bodyBytes=body_bytes, sigMap=signature_map) # Return final transaction - return transaction_pb2.Transaction( - signedTransactionBytes=signed_transaction.SerializeToString() - ) + return transaction_pb2.Transaction(signedTransactionBytes=signed_transaction.SerializeToString()) def get_cost(self, client: Client) -> Hbar: """ @@ -356,9 +333,7 @@ def _make_request(self) -> query_pb2.Query: """ raise NotImplementedError("_make_request must be implemented by subclasses.") - def _map_response( - self, response: Any, node_id: int, proto_request: query_pb2.Query - ) -> query_pb2.Query: + def _map_response(self, response: Any, node_id: int, proto_request: query_pb2.Query) -> query_pb2.Query: # noqa: ARG002 """ Maps the network response to the appropriate response object. @@ -400,9 +375,7 @@ def _should_retry(self, response: Any) -> _ExecutionState: return _ExecutionState.FINISHED return _ExecutionState.ERROR - def _map_status_error( - self, response: Any - ) -> Union[PrecheckError, ReceiptStatusError]: + def _map_status_error(self, response: Any) -> PrecheckError | ReceiptStatusError: """ Maps a response status code to an appropriate error object. @@ -423,4 +396,3 @@ def _is_payment_required(self) -> bool: bool: True if payment is required, False otherwise """ return True - \ No newline at end of file diff --git a/src/hiero_sdk_python/query/token_info_query.py b/src/hiero_sdk_python/query/token_info_query.py index 11a326b5a..c383fb5a8 100644 --- a/src/hiero_sdk_python/query/token_info_query.py +++ b/src/hiero_sdk_python/query/token_info_query.py @@ -1,23 +1,25 @@ -from typing import Optional, Union -from hiero_sdk_python.query.query import Query -from hiero_sdk_python.hapi.services import query_pb2, token_get_info_pb2, response_pb2 -from hiero_sdk_python.executable import _Method +from __future__ import annotations + from hiero_sdk_python.channels import _Channel from hiero_sdk_python.client.client import Client - +from hiero_sdk_python.executable import _Method +from hiero_sdk_python.hapi.services import query_pb2, response_pb2, token_get_info_pb2 +from hiero_sdk_python.query.query import Query from hiero_sdk_python.tokens.token_id import TokenId from hiero_sdk_python.tokens.token_info import TokenInfo + class TokenInfoQuery(Query): """ A query to retrieve information about a specific Token. - - This class constructs and executes a query to retrieve information + + This class constructs and executes a query to retrieve information about a fungible or non-fungible token on the network, including the token's properties and settings. - + """ - def __init__(self, token_id: Optional[TokenId] = None) -> None: + + def __init__(self, token_id: TokenId | None = None) -> None: """ Initializes a new TokenInfoQuery instance with an optional token_id. @@ -25,17 +27,17 @@ def __init__(self, token_id: Optional[TokenId] = None) -> None: token_id (TokenId, optional): The ID of the token to query. """ super().__init__() - self.token_id: Optional[TokenId] = token_id + self.token_id: TokenId | None = token_id - def set_token_id(self, token_id: TokenId) -> "TokenInfoQuery": + def set_token_id(self, token_id: TokenId) -> TokenInfoQuery: """ - Sets the ID of the token to query. + Sets the ID of the token to query. - Args: - token_id (TokenID): The ID of the token. + Args: + token_id (TokenID): The ID of the token. Returns: - TokenInfoQuery: Returns self for method chaining. + TokenInfoQuery: Returns self for method chaining. """ self.token_id = token_id return self @@ -43,7 +45,7 @@ def set_token_id(self, token_id: TokenId) -> "TokenInfoQuery": def _make_request(self) -> query_pb2.Query: """ Constructs the protobuf request for the query. - + Builds a TokenGetInfoQuery protobuf message with the appropriate header and token ID. @@ -66,7 +68,7 @@ def _make_request(self) -> query_pb2.Query: query = query_pb2.Query() query.tokenGetInfo.CopyFrom(token_info_query) - + return query except Exception as e: print(f"Exception in _make_request: {e}") @@ -75,7 +77,7 @@ def _make_request(self) -> query_pb2.Query: def _get_method(self, channel: _Channel) -> _Method: """ Returns the appropriate gRPC method for the token info query. - + Implements the abstract method from Query to provide the specific gRPC method for getting token information. @@ -85,15 +87,12 @@ def _get_method(self, channel: _Channel) -> _Method: Returns: _Method: The method wrapper containing the query function """ - return _Method( - transaction_func=None, - query_func=channel.token.getTokenInfo - ) + return _Method(transaction_func=None, query_func=channel.token.getTokenInfo) - def execute(self, client: Client, timeout: Optional[Union[int, float]] = None) -> TokenInfo: + def execute(self, client: Client, timeout: int | float | None = None) -> TokenInfo: """ Executes the token info query. - + Sends the query to the Hedera network and processes the response to return a TokenInfo object. @@ -101,7 +100,7 @@ def execute(self, client: Client, timeout: Optional[Union[int, float]] = None) - Args: client (Client): The client instance to use for execution - timeout (Optional[Union[int, float]]): The total execution timeout (in seconds) for this execution. + timeout (int | float, optional): The total execution timeout (in seconds) for this execution. Returns: TokenInfo: The token info from the network @@ -119,14 +118,14 @@ def execute(self, client: Client, timeout: Optional[Union[int, float]] = None) - def _get_query_response(self, response: response_pb2.Response) -> token_get_info_pb2.TokenGetInfoResponse: """ Extracts the token info response from the full response. - + Implements the abstract method from Query to extract the specific token info response object. - + Args: response: The full response from the network - + Returns: The token get info response object """ - return response.tokenGetInfo \ No newline at end of file + return response.tokenGetInfo diff --git a/src/hiero_sdk_python/query/token_nft_info_query.py b/src/hiero_sdk_python/query/token_nft_info_query.py index 082d1d690..d31e4680c 100644 --- a/src/hiero_sdk_python/query/token_nft_info_query.py +++ b/src/hiero_sdk_python/query/token_nft_info_query.py @@ -1,23 +1,26 @@ -from typing import Optional, Any, Union -from hiero_sdk_python.query.query import Query -from hiero_sdk_python.hapi.services import query_pb2, response_pb2, token_get_nft_info_pb2 -from hiero_sdk_python.executable import _Method -from hiero_sdk_python.channels import _Channel +from __future__ import annotations + import traceback +from hiero_sdk_python.channels import _Channel from hiero_sdk_python.client.client import Client +from hiero_sdk_python.executable import _Method +from hiero_sdk_python.hapi.services import query_pb2, response_pb2, token_get_nft_info_pb2 +from hiero_sdk_python.query.query import Query from hiero_sdk_python.tokens.nft_id import NftId from hiero_sdk_python.tokens.token_nft_info import TokenNftInfo + class TokenNftInfoQuery(Query): """ A query to retrieve information about a specific Hedera NFT. - + This class constructs and executes a query to retrieve information about a NFT on the Hedera network, including the NFT's properties and settings. - + """ - def __init__(self, nft_id: Optional[NftId] = None) -> None: + + def __init__(self, nft_id: NftId | None = None) -> None: """ Initializes a new TokenNftInfoQuery instance with an optional nft_id. @@ -25,9 +28,9 @@ def __init__(self, nft_id: Optional[NftId] = None) -> None: nft_id (NftId, optional): The ID of the NFT to query. """ super().__init__() - self.nft_id: Optional[NftId] = nft_id + self.nft_id: NftId | None = nft_id - def set_nft_id(self, nft_id: NftId) -> "TokenNftInfoQuery": + def set_nft_id(self, nft_id: NftId) -> TokenNftInfoQuery: """ Sets the ID of the NFT to query. @@ -40,7 +43,7 @@ def set_nft_id(self, nft_id: NftId) -> "TokenNftInfoQuery": def _make_request(self) -> query_pb2.Query: """ Constructs the protobuf request for the query. - + Builds a TokenGetNftInfoQuery protobuf message with the appropriate header and nft ID. @@ -63,7 +66,7 @@ def _make_request(self) -> query_pb2.Query: query = query_pb2.Query() query.tokenGetNftInfo.CopyFrom(nft_info_query) - + return query except Exception as e: print(f"Exception in _make_request: {e}") @@ -73,7 +76,7 @@ def _make_request(self) -> query_pb2.Query: def _get_method(self, channel: _Channel) -> _Method: """ Returns the appropriate gRPC method for the nft info query. - + Implements the abstract method from Query to provide the specific gRPC method for getting nft information. @@ -83,15 +86,12 @@ def _get_method(self, channel: _Channel) -> _Method: Returns: _Method: The method wrapper containing the query function """ - return _Method( - transaction_func=None, - query_func=channel.token.getTokenNftInfo - ) + return _Method(transaction_func=None, query_func=channel.token.getTokenNftInfo) - def execute(self, client: Client, timeout: Optional[Union[int, float]] = None) -> TokenNftInfo: + def execute(self, client: Client, timeout: int | float | None = None) -> TokenNftInfo: """ Executes the nft info query. - + Sends the query to the Hedera network and processes the response to return a TokenNftInfo object. @@ -99,7 +99,7 @@ def execute(self, client: Client, timeout: Optional[Union[int, float]] = None) - Args: client (Client): The client instance to use for execution - timeout (Optional[Union[int, float]]): The total execution timeout (in seconds) for this execution. + timeout (int | float, optional): The total execution timeout (in seconds) for this execution. Returns: TokenNftInfo: The token nft info from the network @@ -117,14 +117,14 @@ def execute(self, client: Client, timeout: Optional[Union[int, float]] = None) - def _get_query_response(self, response: response_pb2.Response) -> token_get_nft_info_pb2.TokenGetNftInfoResponse: """ Extracts the nft info response from the full response. - + Implements the abstract method from Query to extract the specific nft info response object. - + Args: response: The full response from the network - + Returns: The token get nft info response object """ - return response.tokenGetNftInfo \ No newline at end of file + return response.tokenGetNftInfo diff --git a/src/hiero_sdk_python/query/topic_info_query.py b/src/hiero_sdk_python/query/topic_info_query.py index ffaa23a99..7d8912994 100644 --- a/src/hiero_sdk_python/query/topic_info_query.py +++ b/src/hiero_sdk_python/query/topic_info_query.py @@ -1,25 +1,28 @@ -from typing import Optional, Any, Union -from hiero_sdk_python.query.query import Query -from hiero_sdk_python.hapi.services import query_pb2, consensus_get_topic_info_pb2, response_pb2 +from __future__ import annotations + +import traceback +from typing import Any + +from hiero_sdk_python.channels import _Channel from hiero_sdk_python.client.client import Client from hiero_sdk_python.consensus.topic_id import TopicId from hiero_sdk_python.consensus.topic_info import TopicInfo -from hiero_sdk_python.executable import _Method, _ExecutionState -from hiero_sdk_python.channels import _Channel +from hiero_sdk_python.executable import _ExecutionState, _Method +from hiero_sdk_python.hapi.services import consensus_get_topic_info_pb2, query_pb2 +from hiero_sdk_python.query.query import Query from hiero_sdk_python.response_code import ResponseCode -from hiero_sdk_python.exceptions import PrecheckError -import traceback + class TopicInfoQuery(Query): """ A query to retrieve information about a specific Hedera topic. - + This class constructs and executes a query to retrieve information about a topic on the Hedera network, including the topic's properties and settings. """ - def __init__(self, topic_id: Optional[TopicId] = None) -> None: + def __init__(self, topic_id: TopicId | None = None) -> None: """ Initializes a new TopicInfoQuery instance with an optional topic_id. @@ -27,20 +30,20 @@ def __init__(self, topic_id: Optional[TopicId] = None) -> None: topic_id (TopicId, optional): The ID of the topic to query. """ super().__init__() - self.topic_id: Optional[TopicId] = topic_id - self._frozen: bool = False + self.topic_id: TopicId | None = topic_id + self._frozen: bool = False def _require_not_frozen(self) -> None: """ Ensures the query is not frozen before making changes. - + Raises: ValueError: If the query is frozen and cannot be modified. """ if self._frozen: raise ValueError("This query is frozen and cannot be modified.") - def set_topic_id(self, topic_id: TopicId) -> "TopicInfoQuery": + def set_topic_id(self, topic_id: TopicId) -> TopicInfoQuery: """ Sets the ID of the topic to query. @@ -49,7 +52,7 @@ def set_topic_id(self, topic_id: TopicId) -> "TopicInfoQuery": Returns: TopicInfoQuery: Returns self for method chaining. - + Raises: ValueError: If the query is frozen and cannot be modified. """ @@ -57,10 +60,10 @@ def set_topic_id(self, topic_id: TopicId) -> "TopicInfoQuery": self.topic_id = topic_id return self - def freeze(self) -> "TopicInfoQuery": + def freeze(self) -> TopicInfoQuery: """ Marks the query as frozen, preventing further modification. - + Once frozen, properties like topic_id cannot be changed. Returns: @@ -72,7 +75,7 @@ def freeze(self) -> "TopicInfoQuery": def _make_request(self) -> query_pb2.Query: """ Constructs the protobuf request for the query. - + Builds a ConsensusGetTopicInfoQuery protobuf message with the appropriate header and topic ID. @@ -95,9 +98,9 @@ def _make_request(self) -> query_pb2.Query: query = query_pb2.Query() query.consensusGetTopicInfo.CopyFrom(topic_info_query) - + return query - + except Exception as e: print(f"Exception in _make_request: {e}") traceback.print_exc() @@ -106,7 +109,7 @@ def _make_request(self) -> query_pb2.Query: def _get_method(self, channel: _Channel) -> _Method: """ Returns the appropriate gRPC method for the topic info query. - + Implements the abstract method from Query to provide the specific gRPC method for getting topic information. @@ -116,15 +119,12 @@ def _get_method(self, channel: _Channel) -> _Method: Returns: _Method: The method wrapper containing the query function """ - return _Method( - transaction_func=None, - query_func=channel.topic.getTopicInfo - ) + return _Method(transaction_func=None, query_func=channel.topic.getTopicInfo) def _should_retry(self, response: Any) -> _ExecutionState: """ Determines whether the query should be retried based on the response. - + Implements the abstract method from Query to decide whether to retry the query based on the response status code. @@ -136,23 +136,18 @@ def _should_retry(self, response: Any) -> _ExecutionState: """ status = response.consensusGetTopicInfo.header.nodeTransactionPrecheckCode - retryable_statuses = { - ResponseCode.UNKNOWN, - ResponseCode.BUSY, - ResponseCode.PLATFORM_NOT_ACTIVE - } - + retryable_statuses = {ResponseCode.UNKNOWN, ResponseCode.BUSY, ResponseCode.PLATFORM_NOT_ACTIVE} + if status == ResponseCode.OK: return _ExecutionState.FINISHED - elif status in retryable_statuses: + if status in retryable_statuses: return _ExecutionState.RETRY - else: - return _ExecutionState.ERROR + return _ExecutionState.ERROR - def execute(self, client: Client, timeout: Optional[Union[int, float]] = None) -> TopicInfo: + def execute(self, client: Client, timeout: int | float | None = None) -> TopicInfo: """ Executes the topic info query. - + Sends the query to the Hedera network and processes the response to return a TopicInfo object. @@ -160,7 +155,7 @@ def execute(self, client: Client, timeout: Optional[Union[int, float]] = None) - Args: client (Client): The client instance to use for execution - timeout (Optional[Union[int, float]]): The total execution timeout (in seconds) for this execution. + timeout (int | float, optional): The total execution timeout (in seconds) for this execution. Returns: TopicInfo: The topic info from the network @@ -172,19 +167,19 @@ def execute(self, client: Client, timeout: Optional[Union[int, float]] = None) - """ self._before_execute(client) response = self._execute(client, timeout) - + return TopicInfo._from_proto(response.consensusGetTopicInfo.topicInfo) def _get_query_response(self, response: Any) -> consensus_get_topic_info_pb2.ConsensusGetTopicInfoResponse: """ Extracts the topic info response from the full response. - + Implements the abstract method from Query to extract the specific topic info response object. - + Args: response: The full response from the network - + Returns: The consensus get topic info response object """ diff --git a/src/hiero_sdk_python/query/topic_message_query.py b/src/hiero_sdk_python/query/topic_message_query.py index ea431821a..fca9f9472 100644 --- a/src/hiero_sdk_python/query/topic_message_query.py +++ b/src/hiero_sdk_python/query/topic_message_query.py @@ -1,15 +1,17 @@ -import time +from __future__ import annotations + import threading +import time +from collections.abc import Callable from datetime import datetime -from typing import Optional, Callable, Union, Dict, List -from hiero_sdk_python.hapi.mirror import consensus_service_pb2 as mirror_proto -from hiero_sdk_python.hapi.services import basic_types_pb2, timestamp_pb2 +from hiero_sdk_python.client.client import Client from hiero_sdk_python.consensus.topic_id import TopicId from hiero_sdk_python.consensus.topic_message import TopicMessage +from hiero_sdk_python.hapi.mirror import consensus_service_pb2 as mirror_proto +from hiero_sdk_python.hapi.services import basic_types_pb2, timestamp_pb2 from hiero_sdk_python.transaction.transaction_id import TransactionId from hiero_sdk_python.utils.subscription_handle import SubscriptionHandle -from hiero_sdk_python.client.client import Client class TopicMessageQuery: @@ -22,54 +24,49 @@ class TopicMessageQuery: def __init__( self, - topic_id: Optional[Union[str, TopicId]] = None, - start_time: Optional[datetime] = None, - end_time: Optional[datetime] = None, - limit: Optional[int] = None, + topic_id: str | TopicId | None = None, + start_time: datetime | None = None, + end_time: datetime | None = None, + limit: int | None = None, chunking_enabled: bool = False, ) -> None: - """ - Initializes a TopicMessageQuery. - """ - self._topic_id: Optional[TopicId] = self._parse_topic_id(topic_id) if topic_id else None - self._start_time: Optional[timestamp_pb2.Timestamp] = self._parse_timestamp(start_time) if start_time else None - self._end_time: Optional[timestamp_pb2.Timestamp] = self._parse_timestamp(end_time) if end_time else None - self._limit: Optional[int] = limit + """Initializes a TopicMessageQuery.""" + self._topic_id: TopicId | None = self._parse_topic_id(topic_id) if topic_id else None + self._start_time: timestamp_pb2.Timestamp | None = self._parse_timestamp(start_time) if start_time else None + self._end_time: timestamp_pb2.Timestamp | None = self._parse_timestamp(end_time) if end_time else None + self._limit: int | None = limit self._chunking_enabled: bool = chunking_enabled - self._completion_handler: Optional[Callable[[], None]] = None + self._completion_handler: Callable[[], None] | None = None self._max_attempts: int = 10 self._max_backoff: float = 8.0 - def set_max_attempts(self, attempts: int) -> "TopicMessageQuery": + def set_max_attempts(self, attempts: int) -> TopicMessageQuery: """Sets the maximum number of attempts to reconnect on failure.""" self._max_attempts = attempts return self - def set_max_backoff(self, backoff: float) -> "TopicMessageQuery": + def set_max_backoff(self, backoff: float) -> TopicMessageQuery: """Sets the maximum backoff time in seconds for reconnection attempts.""" self._max_backoff = backoff return self - def set_completion_handler(self, handler: Callable[[], None]) -> "TopicMessageQuery": + def set_completion_handler(self, handler: Callable[[], None]) -> TopicMessageQuery: """Sets a completion handler that is called when the subscription completes.""" self._completion_handler = handler return self - def _parse_topic_id(self, topic_id: Union[str, TopicId]) -> basic_types_pb2.TopicID: - """ - Parses a topic ID from a string or TopicId object into a protobuf TopicID. - """ + def _parse_topic_id(self, topic_id: str | TopicId) -> basic_types_pb2.TopicID: + """Parses a topic ID from a string or TopicId object into a protobuf TopicID.""" if isinstance(topic_id, str): parts = topic_id.strip().split(".") if len(parts) != 3: raise ValueError(f"Invalid topic ID string: {topic_id}") shard, realm, topic = map(int, parts) return basic_types_pb2.TopicID(shardNum=shard, realmNum=realm, topicNum=topic) - elif isinstance(topic_id, TopicId): + if isinstance(topic_id, TopicId): return topic_id._to_proto() - else: - raise TypeError("Invalid topic_id format. Must be a string or TopicId.") + raise TypeError("Invalid topic_id format. Must be a string or TopicId.") def _parse_timestamp(self, dt: datetime) -> timestamp_pb2.Timestamp: """Converts a datetime object to a protobuf Timestamp.""" @@ -77,27 +74,27 @@ def _parse_timestamp(self, dt: datetime) -> timestamp_pb2.Timestamp: nanos = int((dt.timestamp() - seconds) * 1e9) return timestamp_pb2.Timestamp(seconds=seconds, nanos=nanos) - def set_topic_id(self, topic_id: Union[str, TopicId]) -> "TopicMessageQuery": + def set_topic_id(self, topic_id: str | TopicId) -> TopicMessageQuery: """Sets the topic ID for the query.""" self._topic_id = self._parse_topic_id(topic_id) return self - def set_start_time(self, dt: datetime) -> "TopicMessageQuery": + def set_start_time(self, dt: datetime) -> TopicMessageQuery: """Sets the start time for the query.""" self._start_time = self._parse_timestamp(dt) return self - def set_end_time(self, dt: datetime) -> "TopicMessageQuery": + def set_end_time(self, dt: datetime) -> TopicMessageQuery: """Sets the end time for the query.""" self._end_time = self._parse_timestamp(dt) return self - def set_limit(self, limit: int) -> "TopicMessageQuery": + def set_limit(self, limit: int) -> TopicMessageQuery: """Sets the maximum number of messages to return in the query.""" self._limit = limit return self - def set_chunking_enabled(self, enabled: bool) -> "TopicMessageQuery": + def set_chunking_enabled(self, enabled: bool) -> TopicMessageQuery: """Enables or disables chunking for multi-chunk messages.""" self._chunking_enabled = enabled return self @@ -106,10 +103,9 @@ def subscribe( self, client: Client, on_message: Callable[[TopicMessage], None], - on_error: Optional[Callable[[Exception], None]] = None, + on_error: Callable[[Exception], None] | None = None, ) -> SubscriptionHandle: """Subscribes to messages from the specified topic.""" - if not self._topic_id: raise ValueError("Topic ID must be set before subscribing.") if not client.mirror_stub: @@ -125,7 +121,7 @@ def subscribe( subscription_handle = SubscriptionHandle() - pending_chunks: Dict[str, List[mirror_proto.ConsensusTopicResponse]] = {} + pending_chunks: dict[str, list[mirror_proto.ConsensusTopicResponse]] = {} def run_stream(): attempt = 0 @@ -137,9 +133,11 @@ def run_stream(): if subscription_handle.is_cancelled(): return - if (not self._chunking_enabled - or not response.HasField("chunkInfo") - or response.chunkInfo.total <= 1): + if ( + not self._chunking_enabled + or not response.HasField("chunkInfo") + or response.chunkInfo.total <= 1 + ): msg_obj = TopicMessage.of_single(response) on_message(msg_obj) continue diff --git a/src/hiero_sdk_python/query/transaction_get_receipt_query.py b/src/hiero_sdk_python/query/transaction_get_receipt_query.py index 0b8ab52a5..73b751032 100644 --- a/src/hiero_sdk_python/query/transaction_get_receipt_query.py +++ b/src/hiero_sdk_python/query/transaction_get_receipt_query.py @@ -1,11 +1,18 @@ +from __future__ import annotations + import traceback -from typing import List, Optional, Union from hiero_sdk_python.channels import _Channel from hiero_sdk_python.client.client import Client from hiero_sdk_python.exceptions import PrecheckError, ReceiptStatusError from hiero_sdk_python.executable import _ExecutionState, _Method -from hiero_sdk_python.hapi.services import query_header_pb2, query_pb2, response_pb2, transaction_get_receipt_pb2, transaction_receipt_pb2 +from hiero_sdk_python.hapi.services import ( + query_header_pb2, + query_pb2, + response_pb2, + transaction_get_receipt_pb2, + transaction_receipt_pb2, +) from hiero_sdk_python.query.query import Query from hiero_sdk_python.response_code import ResponseCode from hiero_sdk_python.transaction.transaction_id import TransactionId @@ -26,10 +33,10 @@ class TransactionGetReceiptQuery(Query): def __init__( self, - transaction_id: Optional[TransactionId] = None, + transaction_id: TransactionId | None = None, include_children: bool = False, include_duplicates: bool = False, - validate_status: bool = False + validate_status: bool = False, ) -> None: """ Initializes a new instance of the TransactionGetReceiptQuery class. @@ -41,11 +48,11 @@ def __init__( validate_status: (bool): Whether the query should automatically validate the transaction status. """ super().__init__() - self.transaction_id: Optional[TransactionId] = transaction_id + self.transaction_id: TransactionId | None = transaction_id self._frozen: bool = False self.include_children = include_children self.include_duplicates = include_duplicates - self.validate_status = validate_status # To keep backward compatible + self.validate_status = validate_status # To keep backward compatible def _require_not_frozen(self) -> None: """ @@ -57,7 +64,7 @@ def _require_not_frozen(self) -> None: if self._frozen: raise ValueError("This query is frozen and cannot be modified.") - def set_transaction_id(self, transaction_id: TransactionId) -> "TransactionGetReceiptQuery": + def set_transaction_id(self, transaction_id: TransactionId) -> TransactionGetReceiptQuery: """ Sets the transaction ID for which to retrieve the receipt. @@ -74,9 +81,7 @@ def set_transaction_id(self, transaction_id: TransactionId) -> "TransactionGetRe self.transaction_id = transaction_id return self - def set_include_children( - self, include_children: bool - ) -> "TransactionGetReceiptQuery": + def set_include_children(self, include_children: bool) -> TransactionGetReceiptQuery: """ Sets include_children for which to retrieve the child transaction receipts. @@ -93,9 +98,7 @@ def set_include_children( self.include_children = include_children return self - def set_include_duplicates( - self, include_duplicates: bool - ) -> "TransactionGetReceiptQuery": + def set_include_duplicates(self, include_duplicates: bool) -> TransactionGetReceiptQuery: """ Sets include_duplicates for which to retrieve the duplicate transaction receipts. @@ -104,28 +107,28 @@ def set_include_duplicates( Returns: TransactionGetReceiptQuery: The current instance for method chaining. - + Raises: ValueError: If the query is frozen and cannot be modified. """ self._require_not_frozen() self.include_duplicates = include_duplicates return self - - def set_validate_status(self, validate_status: bool) -> "TransactionGetReceiptQuery": + + def set_validate_status(self, validate_status: bool) -> TransactionGetReceiptQuery: """ Sets whether the query should automatically validate the transaction status. - - When set to True, the execute() method will raise a ReceiptStatusError if + + When set to True, the execute() method will raise a ReceiptStatusError if the transaction receipt status is anything other than SUCCESS. Args: - validate_status (bool): True to enable automatic error raising on failure statuses; + validate_status (bool): True to enable automatic error raising on failure statuses; False to return the receipt regardless of outcome. (default False) - + Returns: TransactionGetReceiptQuery: The current instance for method chaining. - + Raises: ValueError: If the query is frozen and cannot be modified. """ @@ -133,7 +136,7 @@ def set_validate_status(self, validate_status: bool) -> "TransactionGetReceiptQu self.validate_status = validate_status return self - def freeze(self) -> "TransactionGetReceiptQuery": + def freeze(self) -> TransactionGetReceiptQuery: """ Marks the query as frozen, preventing further modification. @@ -235,14 +238,13 @@ def _should_retry(self, response: response_pb2.Response) -> _ExecutionState: if status in retryable_statuses or status == ResponseCode.OK: return _ExecutionState.RETRY - elif status == ResponseCode.SUCCESS: - return _ExecutionState.FINISHED - else: - if self.validate_status: - return _ExecutionState.ERROR + if status == ResponseCode.SUCCESS: return _ExecutionState.FINISHED + if self.validate_status: + return _ExecutionState.ERROR + return _ExecutionState.FINISHED - def _map_status_error(self, response: response_pb2.Response) -> Union[PrecheckError, ReceiptStatusError]: + def _map_status_error(self, response: response_pb2.Response) -> PrecheckError | ReceiptStatusError: """ Maps a response status code to an appropriate error object. @@ -275,7 +277,7 @@ def _map_status_error(self, response: response_pb2.Response) -> Union[PrecheckEr TransactionReceipt._from_proto(response.transactionGetReceipt.receipt, self.transaction_id), ) - def _map_receipt_list(self, receipts: List[transaction_receipt_pb2.TransactionReceipt]) -> List["TransactionReceipt"]: + def _map_receipt_list(self, receipts: list[transaction_receipt_pb2.TransactionReceipt]) -> list[TransactionReceipt]: """ Maps a list of protobuf transaction receipts to TransactionReceipt objects. @@ -285,13 +287,9 @@ def _map_receipt_list(self, receipts: List[transaction_receipt_pb2.TransactionR Returns: A list of TransactionReceipt objects """ - return [ - TransactionReceipt._from_proto(receipt_proto, self.transaction_id) - for receipt_proto in receipts - ] + return [TransactionReceipt._from_proto(receipt_proto, self.transaction_id) for receipt_proto in receipts] - - def execute(self, client: Client, timeout: Optional[Union[int, float]] = None) -> TransactionReceipt: + def execute(self, client: Client, timeout: int | float | None = None) -> TransactionReceipt: """ Executes the transaction receipt query. @@ -302,7 +300,7 @@ def execute(self, client: Client, timeout: Optional[Union[int, float]] = None) - Args: client (Client): The client instance to use for execution - timeout (Optional[Union[int, float]]): The total execution timeout (in seconds) for this execution. + timeout (int | float, optional): The total execution timeout (in seconds) for this execution. Returns: TransactionReceipt: The transaction receipt from the network @@ -317,20 +315,16 @@ def execute(self, client: Client, timeout: Optional[Union[int, float]] = None) - parent = TransactionReceipt._from_proto(response.transactionGetReceipt.receipt, self.transaction_id) if self.include_children: - children = self._map_receipt_list( - response.transactionGetReceipt.child_transaction_receipts - ) + children = self._map_receipt_list(response.transactionGetReceipt.child_transaction_receipts) parent._set_children(children) - + if self.include_duplicates: - duplicates = self._map_receipt_list( - response.transactionGetReceipt.duplicateTransactionReceipts - ) + duplicates = self._map_receipt_list(response.transactionGetReceipt.duplicateTransactionReceipts) parent._set_duplicates(duplicates) - return parent + return parent def _get_query_response( self, response: response_pb2.Response diff --git a/src/hiero_sdk_python/query/transaction_record_query.py b/src/hiero_sdk_python/query/transaction_record_query.py index 6cca69946..5965f80af 100644 --- a/src/hiero_sdk_python/query/transaction_record_query.py +++ b/src/hiero_sdk_python/query/transaction_record_query.py @@ -1,30 +1,30 @@ -from typing import Optional, Any, Union +from __future__ import annotations + +from typing import Any + +from hiero_sdk_python.channels import _Channel +from hiero_sdk_python.client.client import Client +from hiero_sdk_python.exceptions import PrecheckError, ReceiptStatusError +from hiero_sdk_python.executable import _ExecutionState, _Method from hiero_sdk_python.hapi.services import ( query_header_pb2, - transaction_get_record_pb2, query_pb2, + transaction_get_record_pb2, transaction_record_pb2, ) -from hiero_sdk_python.client.client import Client from hiero_sdk_python.query.query import Query from hiero_sdk_python.response_code import ResponseCode from hiero_sdk_python.transaction.transaction_id import TransactionId -from hiero_sdk_python.channels import _Channel -from hiero_sdk_python.executable import _Method -from hiero_sdk_python.exceptions import PrecheckError, ReceiptStatusError from hiero_sdk_python.transaction.transaction_receipt import TransactionReceipt from hiero_sdk_python.transaction.transaction_record import TransactionRecord -from hiero_sdk_python.executable import _ExecutionState class TransactionRecordQuery(Query): - """ - Represents a query for a transaction record on the Hedera network. - """ + """Represents a query for a transaction record on the Hedera network.""" def __init__( self, - transaction_id: Optional[TransactionId] = None, + transaction_id: TransactionId | None = None, include_children: bool = False, include_duplicates: bool = False, ) -> None: @@ -43,22 +43,16 @@ def __init__( ) if transaction_id is not None and not isinstance(transaction_id, TransactionId): - raise TypeError( - f"transaction_id must be TransactionId or None, got {type(transaction_id).__name__}" - ) + raise TypeError(f"transaction_id must be TransactionId or None, got {type(transaction_id).__name__}") if not isinstance(include_children, bool): - raise TypeError( - f"include_children must be a bool (True or False), got {type(include_children).__name__}" - ) + raise TypeError(f"include_children must be a bool (True or False), got {type(include_children).__name__}") - self.transaction_id: Optional[TransactionId] = transaction_id + self.transaction_id: TransactionId | None = transaction_id self.include_children: bool = bool(include_children) self.include_duplicates: bool = bool(include_duplicates) - def set_include_duplicates( - self, include_duplicates: bool - ) -> "TransactionRecordQuery": + def set_include_duplicates(self, include_duplicates: bool) -> TransactionRecordQuery: """ Sets whether to include duplicate transaction records in the query results. @@ -69,16 +63,14 @@ def set_include_duplicates( TransactionRecordQuery: The current instance for method chaining. """ if not isinstance(include_duplicates, bool): - raise TypeError( - f"include_duplicates must be a boolean, got {type(include_duplicates).__name__}" - ) + raise TypeError(f"include_duplicates must be a boolean, got {type(include_duplicates).__name__}") self.include_duplicates = include_duplicates return self def set_transaction_id( self, - transaction_id: Optional[TransactionId], - ) -> "TransactionRecordQuery": + transaction_id: TransactionId | None, + ) -> TransactionRecordQuery: """ Sets the ID of the transaction whose record is to be queried. @@ -90,16 +82,13 @@ def set_transaction_id( Returns: TransactionRecordQuery: This query instance for chaining. """ - if transaction_id is not None and not isinstance(transaction_id, TransactionId): - raise TypeError( - f"transaction_id must be TransactionId or None, got {type(transaction_id).__name__}" - ) + raise TypeError(f"transaction_id must be TransactionId or None, got {type(transaction_id).__name__}") self.transaction_id = transaction_id return self - def set_include_children(self, include_children: bool) -> "TransactionRecordQuery": + def set_include_children(self, include_children: bool) -> TransactionRecordQuery: """ Sets include_children for which to retrieve the child transaction records. @@ -110,9 +99,7 @@ def set_include_children(self, include_children: bool) -> "TransactionRecordQuer TransactionRecordQuery: The current instance for method chaining. """ if not isinstance(include_children, bool): - raise TypeError( - f"include_children must be a boolean, got {type(include_children).__name__}" - ) + raise TypeError(f"include_children must be a boolean, got {type(include_children).__name__}") self.include_children = include_children return self @@ -169,9 +156,7 @@ def _map_record_list( records: list[TransactionRecord] = [] for proto_record in proto_records: # We pass the same transaction_id as the main record - record = TransactionRecord._from_proto( - proto_record, transaction_id=self.transaction_id - ) + record = TransactionRecord._from_proto(proto_record, transaction_id=self.transaction_id) records.append(record) return records @@ -188,9 +173,7 @@ def _get_method(self, channel: _Channel) -> _Method: Returns: _Method: The method wrapper containing the query function """ - return _Method( - transaction_func=None, query_func=channel.crypto.getTxRecordByTxID - ) + return _Method(transaction_func=None, query_func=channel.crypto.getTxRecordByTxID) def _should_retry(self, response: Any) -> _ExecutionState: """ @@ -217,15 +200,9 @@ def _should_retry(self, response: Any) -> _ExecutionState: } if status == ResponseCode.OK: - if ( - response.transactionGetRecord.header.responseType - == query_header_pb2.ResponseType.COST_ANSWER - ): + if response.transactionGetRecord.header.responseType == query_header_pb2.ResponseType.COST_ANSWER: return _ExecutionState.FINISHED - elif ( - status in retryable_statuses - or status == ResponseCode.PLATFORM_TRANSACTION_NOT_CREATED - ): + elif status in retryable_statuses or status == ResponseCode.PLATFORM_TRANSACTION_NOT_CREATED: return _ExecutionState.RETRY else: return _ExecutionState.ERROR @@ -234,14 +211,11 @@ def _should_retry(self, response: Any) -> _ExecutionState: if status in retryable_statuses or status == ResponseCode.OK: return _ExecutionState.RETRY - elif status == ResponseCode.SUCCESS: + if status == ResponseCode.SUCCESS: return _ExecutionState.FINISHED - else: - return _ExecutionState.ERROR + return _ExecutionState.ERROR - def _map_status_error( - self, response: Any - ) -> Union[PrecheckError, ReceiptStatusError]: + def _map_status_error(self, response: Any) -> PrecheckError | ReceiptStatusError: """ Maps a response status code to an appropriate error object. @@ -276,7 +250,7 @@ def _map_status_error( TransactionReceipt._from_proto(receipt, self.transaction_id), ) - def execute(self, client: Client, timeout: Optional[Union[int, float]] = None): + def execute(self, client: Client, timeout: int | float | None = None): """ Executes the transaction record query. @@ -287,7 +261,7 @@ def execute(self, client: Client, timeout: Optional[Union[int, float]] = None): Args: client (Client): The client instance to use for execution - timeout (Optional[Union[int, float]]): The total execution timeout (in seconds) for this execution. + timeout (int | float, optional): The total execution timeout (in seconds) for this execution. Returns: TransactionRecord: The transaction record from the network @@ -302,16 +276,12 @@ def execute(self, client: Client, timeout: Optional[Union[int, float]] = None): primary_proto = response.transactionGetRecord.transactionRecord children = [] if self.include_duplicates: - duplicates = self._map_record_list( - response.transactionGetRecord.duplicateTransactionRecords - ) + duplicates = self._map_record_list(response.transactionGetRecord.duplicateTransactionRecords) else: duplicates = [] if self.include_children: - children = self._map_record_list( - response.transactionGetRecord.child_transaction_records - ) + children = self._map_record_list(response.transactionGetRecord.child_transaction_records) return TransactionRecord._from_proto( primary_proto, diff --git a/src/hiero_sdk_python/response_code.py b/src/hiero_sdk_python/response_code.py index 29b479118..f233644dc 100644 --- a/src/hiero_sdk_python/response_code.py +++ b/src/hiero_sdk_python/response_code.py @@ -1,6 +1,9 @@ +from __future__ import annotations + import warnings from enum import IntEnum + class ResponseCode(IntEnum): OK = 0 INVALID_TRANSACTION = 1 @@ -88,8 +91,8 @@ class ResponseCode(IntEnum): CONTRACT_FILE_EMPTY = 83 CONTRACT_BYTECODE_EMPTY = 84 INVALID_INITIAL_BALANCE = 85 - INVALID_RECEIVE_RECORD_THRESHOLD = 86 # [Deprecated] - INVALID_SEND_RECORD_THRESHOLD = 87 # [Deprecated] + INVALID_RECEIVE_RECORD_THRESHOLD = 86 # [Deprecated] + INVALID_SEND_RECORD_THRESHOLD = 87 # [Deprecated] ACCOUNT_IS_NOT_GENESIS_ACCOUNT = 88 PAYER_ACCOUNT_UNAUTHORIZED = 89 INVALID_FREEZE_TRANSACTION_BODY = 90 @@ -244,25 +247,25 @@ class ResponseCode(IntEnum): MAX_STORAGE_IN_PRICE_REGIME_HAS_BEEN_USED = 281 INVALID_ALIAS_KEY = 282 UNEXPECTED_TOKEN_DECIMALS = 283 - INVALID_PROXY_ACCOUNT_ID = 284 # [Deprecated] + INVALID_PROXY_ACCOUNT_ID = 284 # [Deprecated] INVALID_TRANSFER_ACCOUNT_ID = 285 INVALID_FEE_COLLECTOR_ACCOUNT_ID = 286 ALIAS_IS_IMMUTABLE = 287 SPENDER_ACCOUNT_SAME_AS_OWNER = 288 AMOUNT_EXCEEDS_TOKEN_MAX_SUPPLY = 289 NEGATIVE_ALLOWANCE_AMOUNT = 290 - CANNOT_APPROVE_FOR_ALL_FUNGIBLE_COMMON = 291 # [Deprecated] + CANNOT_APPROVE_FOR_ALL_FUNGIBLE_COMMON = 291 # [Deprecated] SPENDER_DOES_NOT_HAVE_ALLOWANCE = 292 AMOUNT_EXCEEDS_ALLOWANCE = 293 MAX_ALLOWANCES_EXCEEDED = 294 EMPTY_ALLOWANCES = 295 - SPENDER_ACCOUNT_REPEATED_IN_ALLOWANCES = 296 # [Deprecated] - REPEATED_SERIAL_NUMS_IN_NFT_ALLOWANCES = 297 # [Deprecated] + SPENDER_ACCOUNT_REPEATED_IN_ALLOWANCES = 296 # [Deprecated] + REPEATED_SERIAL_NUMS_IN_NFT_ALLOWANCES = 297 # [Deprecated] FUNGIBLE_TOKEN_IN_NFT_ALLOWANCES = 298 NFT_IN_FUNGIBLE_TOKEN_ALLOWANCES = 299 INVALID_ALLOWANCE_OWNER_ID = 300 INVALID_ALLOWANCE_SPENDER_ID = 301 - REPEATED_ALLOWANCES_TO_DELETE = 302 # [Deprecated] + REPEATED_ALLOWANCES_TO_DELETE = 302 # [Deprecated] INVALID_DELEGATING_SPENDER = 303 DELEGATING_SPENDER_CANNOT_GRANT_APPROVE_FOR_ALL = 304 DELEGATING_SPENDER_DOES_NOT_HAVE_APPROVE_FOR_ALL = 305 @@ -357,7 +360,7 @@ class ResponseCode(IntEnum): INVALID_BATCH_KEY = 394 @classmethod - def _missing_(cls, value: object) -> "ResponseCode": + def _missing_(cls, value: object) -> ResponseCode: """ Handles cases where an integer value does not match any ResponseCode member and returns 'UNKNOWN_CODE_'. @@ -366,7 +369,7 @@ def _missing_(cls, value: object) -> "ResponseCode": raise ValueError(f"{value!r} is not a valid {cls.__name__}") unknown = int.__new__(cls, value) - unknown._name_ = f'UNKNOWN_CODE_{value}' + unknown._name_ = f"UNKNOWN_CODE_{value}" unknown._value_ = value return unknown @@ -375,13 +378,11 @@ def is_unknown(self) -> bool: return self.name.startswith("UNKNOWN_CODE_") @classmethod - def get_name(cls,code: int) -> str: - """ - Returns the name of the response code. - """ + def get_name(cls, code: int) -> str: + """Returns the name of the response code.""" warnings.warn( - "The `get_name` method to be deprecated in v0.1.4. " - "Please use `ResponseCode(code).name` instead.", - FutureWarning + "The `get_name` method to be deprecated in v0.1.4. Please use `ResponseCode(code).name` instead.", + FutureWarning, + stacklevel=2, ) return cls(code).name diff --git a/src/hiero_sdk_python/schedule/schedule_create_transaction.py b/src/hiero_sdk_python/schedule/schedule_create_transaction.py index 6999f82c9..e9b7d8b9d 100644 --- a/src/hiero_sdk_python/schedule/schedule_create_transaction.py +++ b/src/hiero_sdk_python/schedule/schedule_create_transaction.py @@ -1,9 +1,8 @@ -""" -ScheduleCreateTransaction class. -""" +"""ScheduleCreateTransaction class.""" + +from __future__ import annotations from dataclasses import dataclass -from typing import Optional from hiero_sdk_python.account.account_id import AccountId from hiero_sdk_python.channels import _Channel @@ -26,24 +25,24 @@ class ScheduleCreateParams: Represents schedule attributes that can be set on creation. Attributes: - payer_account_id (Optional[AccountId]): The account ID of the payer + payer_account_id (AccountId, optional): The account ID of the payer for the scheduled transaction. - admin_key (Optional[PublicKey]): The key that can delete or sign the schedule. - schedulable_body (Optional[SchedulableTransactionBody]): The body of the transaction + admin_key (PublicKey, optional): The key that can delete or sign the schedule. + schedulable_body (SchedulableTransactionBody, optional): The body of the transaction to be scheduled. - schedule_memo (Optional[str]): A memo to include with the schedule. - expiration_time (Optional[Timestamp]): The time at which the schedule should expire. - wait_for_expiry (Optional[bool]): If True, the transaction will execute only at expiration time, + schedule_memo (str, optional): A memo to include with the schedule. + expiration_time (Timestamp, optional): The time at which the schedule should expire. + wait_for_expiry (bool, optional): If True, the transaction will execute only at expiration time, even if all required signatures are collected before then. If False or unset, the transaction will execute as soon as all required signatures are received. """ - payer_account_id: Optional[AccountId] = None - admin_key: Optional[PublicKey] = None - schedulable_body: Optional[SchedulableTransactionBody] = None - schedule_memo: Optional[str] = None - expiration_time: Optional[Timestamp] = None - wait_for_expiry: Optional[bool] = None + payer_account_id: AccountId | None = None + admin_key: PublicKey | None = None + schedulable_body: SchedulableTransactionBody | None = None + schedule_memo: str | None = None + expiration_time: Timestamp | None = None + wait_for_expiry: bool | None = None class ScheduleCreateTransaction(Transaction): @@ -59,35 +58,31 @@ class ScheduleCreateTransaction(Transaction): def __init__( self, - schedule_params: Optional[ScheduleCreateParams] = None, + schedule_params: ScheduleCreateParams | None = None, ): """ Initializes a new ScheduleCreateTransaction instance with the specified parameters. Args: - schedule_params (Optional[ScheduleCreateParams]): + schedule_params (ScheduleCreateParams, optional): The parameters for the schedule create transaction. """ super().__init__() schedule_params = schedule_params or ScheduleCreateParams() - self.payer_account_id: Optional[AccountId] = schedule_params.payer_account_id - self.admin_key: Optional[PublicKey] = schedule_params.admin_key - self.schedulable_body: Optional[SchedulableTransactionBody] = ( - schedule_params.schedulable_body - ) - self.schedule_memo: Optional[str] = schedule_params.schedule_memo - self.expiration_time: Optional[Timestamp] = schedule_params.expiration_time - self.wait_for_expiry: Optional[bool] = schedule_params.wait_for_expiry + self.payer_account_id: AccountId | None = schedule_params.payer_account_id + self.admin_key: PublicKey | None = schedule_params.admin_key + self.schedulable_body: SchedulableTransactionBody | None = schedule_params.schedulable_body + self.schedule_memo: str | None = schedule_params.schedule_memo + self.expiration_time: Timestamp | None = schedule_params.expiration_time + self.wait_for_expiry: bool | None = schedule_params.wait_for_expiry self._default_transaction_fee = Hbar(5).to_tinybars() - def _set_schedulable_body( - self, schedulable_body: Optional[SchedulableTransactionBody] - ) -> "ScheduleCreateTransaction": + def _set_schedulable_body(self, schedulable_body: SchedulableTransactionBody | None) -> ScheduleCreateTransaction: """ Sets the schedulable body for this schedule create transaction. Args: - schedulable_body (Optional[SchedulableTransactionBody]): + schedulable_body (SchedulableTransactionBody | None): The body of the schedulable transaction. Returns: @@ -97,9 +92,7 @@ def _set_schedulable_body( self.schedulable_body = schedulable_body return self - def set_scheduled_transaction( - self, transaction: "Transaction" - ) -> "ScheduleCreateTransaction": + def set_scheduled_transaction(self, transaction: Transaction) -> ScheduleCreateTransaction: """ Sets the scheduled transaction for this schedule create transaction. @@ -115,14 +108,12 @@ def set_scheduled_transaction( return self - def set_schedule_memo( - self, schedule_memo: Optional[str] - ) -> "ScheduleCreateTransaction": + def set_schedule_memo(self, schedule_memo: str | None) -> ScheduleCreateTransaction: """ Sets the schedule memo for this schedule create transaction. Args: - schedule_memo (Optional[str]): The schedule memo for the schedule. + schedule_memo (str | None): The schedule memo for the schedule. Returns: ScheduleCreateTransaction: This transaction instance. @@ -131,14 +122,12 @@ def set_schedule_memo( self.schedule_memo = schedule_memo return self - def set_payer_account_id( - self, payer_account_id: Optional[AccountId] - ) -> "ScheduleCreateTransaction": + def set_payer_account_id(self, payer_account_id: AccountId | None) -> ScheduleCreateTransaction: """ Sets the payer account ID for this schedule create transaction. Args: - payer_account_id (Optional[AccountId]): The payer account ID for the schedule. + payer_account_id (AccountId | None): The payer account ID for the schedule. Returns: ScheduleCreateTransaction: This transaction instance. @@ -147,14 +136,12 @@ def set_payer_account_id( self.payer_account_id = payer_account_id return self - def set_expiration_time( - self, expiration_time: Optional[Timestamp] - ) -> "ScheduleCreateTransaction": + def set_expiration_time(self, expiration_time: Timestamp | None) -> ScheduleCreateTransaction: """ Sets the expiration time for this schedule create transaction. Args: - expiration_time (Optional[Timestamp]): The expiration time for the schedule. + expiration_time (Timestamp | None): The expiration time for the schedule. Returns: ScheduleCreateTransaction: This transaction instance. @@ -163,14 +150,12 @@ def set_expiration_time( self.expiration_time = expiration_time return self - def set_wait_for_expiry( - self, wait_for_expiry: Optional[bool] - ) -> "ScheduleCreateTransaction": + def set_wait_for_expiry(self, wait_for_expiry: bool | None) -> ScheduleCreateTransaction: """ Sets the wait for expiry for this schedule create transaction. Args: - wait_for_expiry (Optional[bool]): Whether to wait for the schedule to expire. + wait_for_expiry (bool | None): Whether to wait for the schedule to expire. Returns: ScheduleCreateTransaction: This transaction instance. @@ -179,14 +164,12 @@ def set_wait_for_expiry( self.wait_for_expiry = wait_for_expiry return self - def set_admin_key( - self, admin_key: Optional[PublicKey] - ) -> "ScheduleCreateTransaction": + def set_admin_key(self, admin_key: PublicKey | None) -> ScheduleCreateTransaction: """ Sets the admin key for this schedule create transaction. Args: - admin_key (Optional[PublicKey]): The admin key for the schedule. + admin_key (PublicKey | None): The admin key for the schedule. Returns: ScheduleCreateTransaction: This transaction instance. @@ -207,12 +190,8 @@ def _build_proto_body(self): memo=self.schedule_memo, adminKey=self.admin_key._to_proto() if self.admin_key else None, scheduledTransactionBody=self.schedulable_body, - expiration_time=( - self.expiration_time._to_protobuf() if self.expiration_time else None - ), - payerAccountID=( - self.payer_account_id._to_proto() if self.payer_account_id else None - ), + expiration_time=(self.expiration_time._to_protobuf() if self.expiration_time else None), + payerAccountID=(self.payer_account_id._to_proto() if self.payer_account_id else None), ) def build_transaction_body(self): diff --git a/src/hiero_sdk_python/schedule/schedule_delete_transaction.py b/src/hiero_sdk_python/schedule/schedule_delete_transaction.py index 27e7018a0..b5e5c9048 100644 --- a/src/hiero_sdk_python/schedule/schedule_delete_transaction.py +++ b/src/hiero_sdk_python/schedule/schedule_delete_transaction.py @@ -1,8 +1,6 @@ -""" -ScheduleDeleteTransaction class. -""" +"""ScheduleDeleteTransaction class.""" -from typing import Optional +from __future__ import annotations from hiero_sdk_python.channels import _Channel from hiero_sdk_python.executable import _Method @@ -27,25 +25,23 @@ class ScheduleDeleteTransaction(Transaction): to build and execute a schedule delete transaction. """ - def __init__(self, schedule_id: Optional[ScheduleId] = None): + def __init__(self, schedule_id: ScheduleId | None = None): """ Initializes a new ScheduleDeleteTransaction instance with the specified parameters. Args: - schedule_id (Optional[ScheduleId]): The ID of the schedule to delete. + schedule_id (ScheduleId, optional): The ID of the schedule to delete. """ super().__init__() - self.schedule_id: Optional[ScheduleId] = schedule_id + self.schedule_id: ScheduleId | None = schedule_id self._default_transaction_fee = Hbar(5).to_tinybars() - def set_schedule_id( - self, schedule_id: Optional[ScheduleId] - ) -> "ScheduleDeleteTransaction": + def set_schedule_id(self, schedule_id: ScheduleId | None) -> ScheduleDeleteTransaction: """ Sets the ID of the schedule to delete. Args: - schedule_id (Optional[ScheduleId]): The ID of the schedule to delete. + schedule_id (ScheduleId | None): The ID of the schedule to delete. Returns: ScheduleDeleteTransaction: This transaction instance. @@ -106,6 +102,4 @@ def _get_method(self, channel: _Channel) -> _Method: Returns: _Method: An object containing the transaction function to delete a schedule. """ - return _Method( - transaction_func=channel.schedule.deleteSchedule, query_func=None - ) + return _Method(transaction_func=channel.schedule.deleteSchedule, query_func=None) diff --git a/src/hiero_sdk_python/schedule/schedule_id.py b/src/hiero_sdk_python/schedule/schedule_id.py index f783c7b27..66c1d2b59 100644 --- a/src/hiero_sdk_python/schedule/schedule_id.py +++ b/src/hiero_sdk_python/schedule/schedule_id.py @@ -1,16 +1,12 @@ -""" -ScheduleId class. -""" +"""ScheduleId class.""" + +from __future__ import annotations from dataclasses import dataclass, field -from hiero_sdk_python.hapi.services.basic_types_pb2 import ScheduleID as ProtoScheduleID from hiero_sdk_python.client.client import Client -from hiero_sdk_python.utils.entity_id_helper import ( - parse_from_string, - validate_checksum, - format_to_string_with_checksum -) +from hiero_sdk_python.hapi.services.basic_types_pb2 import ScheduleID as ProtoScheduleID +from hiero_sdk_python.utils.entity_id_helper import format_to_string_with_checksum, parse_from_string, validate_checksum @dataclass(frozen=True) @@ -33,7 +29,7 @@ class ScheduleId: checksum: str | None = field(default=None, init=False) @classmethod - def from_string(cls, id_str: str) -> "ScheduleId": + def from_string(cls, id_str: str) -> ScheduleId: """ Creates a ScheduleId instance from a string representation. @@ -63,9 +59,7 @@ def from_string(cls, id_str: str) -> "ScheduleId": return schedule_id except Exception as e: - raise ValueError( - f"Invalid schedule ID string '{id_str}'. Expected format 'shard.realm.schedule'." - ) from e + raise ValueError(f"Invalid schedule ID string '{id_str}'. Expected format 'shard.realm.schedule'.") from e def __str__(self) -> str: """ @@ -103,11 +97,7 @@ def __eq__(self, other: object) -> bool: """ if not isinstance(other, ScheduleId): return NotImplemented - return ( - self.shard == other.shard - and self.realm == other.realm - and self.schedule == other.schedule - ) + return self.shard == other.shard and self.realm == other.realm and self.schedule == other.schedule def _to_proto(self) -> ProtoScheduleID: """ @@ -127,7 +117,7 @@ def _to_proto(self) -> ProtoScheduleID: ) @classmethod - def _from_proto(cls, proto: ProtoScheduleID) -> "ScheduleId": + def _from_proto(cls, proto: ProtoScheduleID) -> ScheduleId: """ Creates a ScheduleId instance from a protobuf ScheduleID object. @@ -141,12 +131,10 @@ def _from_proto(cls, proto: ProtoScheduleID) -> "ScheduleId": Returns: ScheduleId: A new ScheduleId instance with the values from the protobuf object. """ - return cls( - shard=proto.shardNum, realm=proto.realmNum, schedule=proto.scheduleNum - ) + return cls(shard=proto.shardNum, realm=proto.realmNum, schedule=proto.scheduleNum) def validate_checksum(self, client: Client) -> None: - """Validate the checksum for the scheduleId""" + """Validate the checksum for the scheduleId.""" validate_checksum( self.shard, self.realm, @@ -157,12 +145,7 @@ def validate_checksum(self, client: Client) -> None: def to_string_with_checksum(self, client: Client) -> str: """ - Returns the string representation of the ScheduleId with checksum + Returns the string representation of the ScheduleId with checksum in 'shard.realm.schedule-checksum' format. """ - return format_to_string_with_checksum( - self.shard, - self.realm, - self.schedule, - client - ) + return format_to_string_with_checksum(self.shard, self.realm, self.schedule, client) diff --git a/src/hiero_sdk_python/schedule/schedule_info.py b/src/hiero_sdk_python/schedule/schedule_info.py index 092cb31d8..475eba120 100644 --- a/src/hiero_sdk_python/schedule/schedule_info.py +++ b/src/hiero_sdk_python/schedule/schedule_info.py @@ -1,10 +1,11 @@ -""" -ScheduleInfo class -""" +"""ScheduleInfo class.""" + +from __future__ import annotations import datetime +from collections.abc import Callable from dataclasses import dataclass, field -from typing import Any, Callable, Optional +from typing import Any from hiero_sdk_python.account.account_id import AccountId from hiero_sdk_python.crypto.public_key import PublicKey @@ -26,40 +27,40 @@ class ScheduleInfo: Information about a scheduled transaction on the network. Attributes: - schedule_id (Optional[ScheduleId]): The unique identifier for the schedule. - creator_account_id (Optional[AccountId]): The account that created the schedule. - payer_account_id (Optional[AccountId]): The account responsible for paying + schedule_id (ScheduleId, optional): The unique identifier for the schedule. + creator_account_id (AccountId, optional): The account that created the schedule. + payer_account_id (AccountId, optional): The account responsible for paying for the scheduled transaction. - deleted_at (Optional[Timestamp]): The time at which the schedule was deleted. - executed_at (Optional[Timestamp]): The time at which the scheduled transaction was executed. - expiration_time (Optional[Timestamp]): The time at which the schedule will expire. - scheduled_transaction_id (Optional[TransactionId]): + deleted_at (Timestamp, optional): The time at which the schedule was deleted. + executed_at (Timestamp, optional): The time at which the scheduled transaction was executed. + expiration_time (Timestamp, optional): The time at which the schedule will expire. + scheduled_transaction_id (TransactionId, optional): The transaction ID of the scheduled transaction. - scheduled_transaction_body (Optional[SchedulableTransactionBody]): + scheduled_transaction_body (SchedulableTransactionBody, optional): The body of the scheduled transaction. - schedule_memo (Optional[str]): The memo associated with the schedule. - admin_key (Optional[PublicKey]): The key that can delete or update the schedule. + schedule_memo (str, optional): The memo associated with the schedule. + admin_key (PublicKey, optional): The key that can delete or update the schedule. signers (list[PublicKey]): The list of public keys that have signed the schedule. - ledger_id (Optional[bytes]): The ID of the ledger this schedule exists in. - wait_for_expiry (Optional[bool]): Whether the schedule is set to wait for expiry. + ledger_id (bytes, optional): The ID of the ledger this schedule exists in. + wait_for_expiry (bool, optional): Whether the schedule is set to wait for expiry. """ - schedule_id: Optional[ScheduleId] = None - creator_account_id: Optional[AccountId] = None - payer_account_id: Optional[AccountId] = None - deleted_at: Optional[Timestamp] = None - executed_at: Optional[Timestamp] = None - expiration_time: Optional[Timestamp] = None - scheduled_transaction_id: Optional[TransactionId] = None - scheduled_transaction_body: Optional[SchedulableTransactionBody] = None - schedule_memo: Optional[str] = None - admin_key: Optional[PublicKey] = None + schedule_id: ScheduleId | None = None + creator_account_id: AccountId | None = None + payer_account_id: AccountId | None = None + deleted_at: Timestamp | None = None + executed_at: Timestamp | None = None + expiration_time: Timestamp | None = None + scheduled_transaction_id: TransactionId | None = None + scheduled_transaction_body: SchedulableTransactionBody | None = None + schedule_memo: str | None = None + admin_key: PublicKey | None = None signers: list[PublicKey] = field(default_factory=list) - ledger_id: Optional[bytes] = None - wait_for_expiry: Optional[bool] = None + ledger_id: bytes | None = None + wait_for_expiry: bool | None = None @classmethod - def _from_proto(cls, proto: ScheduleInfoProto) -> "ScheduleInfo": + def _from_proto(cls, proto: ScheduleInfoProto) -> ScheduleInfo: """ Creates a ScheduleInfo instance from its protobuf representation. @@ -74,28 +75,14 @@ def _from_proto(cls, proto: ScheduleInfoProto) -> "ScheduleInfo": raise ValueError("Schedule info proto is None") return cls( - schedule_id=( - cls._from_proto_field(proto, "scheduleID", ScheduleId._from_proto) - ), - creator_account_id=( - cls._from_proto_field(proto, "creatorAccountID", AccountId._from_proto) - ), - payer_account_id=( - cls._from_proto_field(proto, "payerAccountID", AccountId._from_proto) - ), - deleted_at=( - cls._from_proto_field(proto, "deletion_time", Timestamp._from_protobuf) - ), - executed_at=( - cls._from_proto_field(proto, "execution_time", Timestamp._from_protobuf) - ), - expiration_time=( - cls._from_proto_field(proto, "expirationTime", Timestamp._from_protobuf) - ), + schedule_id=(cls._from_proto_field(proto, "scheduleID", ScheduleId._from_proto)), + creator_account_id=(cls._from_proto_field(proto, "creatorAccountID", AccountId._from_proto)), + payer_account_id=(cls._from_proto_field(proto, "payerAccountID", AccountId._from_proto)), + deleted_at=(cls._from_proto_field(proto, "deletion_time", Timestamp._from_protobuf)), + executed_at=(cls._from_proto_field(proto, "execution_time", Timestamp._from_protobuf)), + expiration_time=(cls._from_proto_field(proto, "expirationTime", Timestamp._from_protobuf)), scheduled_transaction_id=( - cls._from_proto_field( - proto, "scheduledTransactionID", TransactionId._from_proto - ) + cls._from_proto_field(proto, "scheduledTransactionID", TransactionId._from_proto) ), scheduled_transaction_body=proto.scheduledTransactionBody, schedule_memo=proto.memo, @@ -114,33 +101,16 @@ def _to_proto(self) -> ScheduleInfoProto: """ return ScheduleInfoProto( scheduleID=self._convert_to_proto(self.schedule_id, ScheduleId._to_proto), - creatorAccountID=( - self._convert_to_proto(self.creator_account_id, AccountId._to_proto) - ), - payerAccountID=( - self._convert_to_proto(self.payer_account_id, AccountId._to_proto) - ), + creatorAccountID=(self._convert_to_proto(self.creator_account_id, AccountId._to_proto)), + payerAccountID=(self._convert_to_proto(self.payer_account_id, AccountId._to_proto)), deletion_time=self._convert_to_proto(self.deleted_at, Timestamp._to_protobuf), - execution_time=( - self._convert_to_proto(self.executed_at, Timestamp._to_protobuf) - ), - expirationTime=( - self._convert_to_proto(self.expiration_time, Timestamp._to_protobuf) - ), - scheduledTransactionID=( - self._convert_to_proto( - self.scheduled_transaction_id, TransactionId._to_proto - ) - ), + execution_time=(self._convert_to_proto(self.executed_at, Timestamp._to_protobuf)), + expirationTime=(self._convert_to_proto(self.expiration_time, Timestamp._to_protobuf)), + scheduledTransactionID=(self._convert_to_proto(self.scheduled_transaction_id, TransactionId._to_proto)), scheduledTransactionBody=self.scheduled_transaction_body, memo=self.schedule_memo, adminKey=self._convert_to_proto(self.admin_key, PublicKey._to_proto), - signers=KeyListProto( - keys=[ - self._convert_to_proto(key, PublicKey._to_proto) - for key in self.signers or [] - ] - ), + signers=KeyListProto(keys=[self._convert_to_proto(key, PublicKey._to_proto) for key in self.signers or []]), ledger_id=self.ledger_id, wait_for_expiry=self.wait_for_expiry, ) @@ -155,10 +125,7 @@ def __repr__(self) -> str: return self.__str__() def __str__(self) -> str: - """ - Pretty-print the ScheduleInfo. - """ - + """Pretty-print the ScheduleInfo.""" exp_dt = ( datetime.datetime.fromtimestamp(self.expiration_time.seconds) if self.expiration_time and hasattr(self.expiration_time, "seconds") @@ -170,9 +137,7 @@ def __str__(self) -> str: # Format ledger_id as hex if it's bytes ledger_id_display = ( - f"0x{self.ledger_id.hex()}" - if isinstance(self.ledger_id, (bytes, bytearray)) - else self.ledger_id + f"0x{self.ledger_id.hex()}" if isinstance(self.ledger_id, (bytes, bytearray)) else self.ledger_id ) return ( @@ -217,6 +182,6 @@ def _from_proto_field( value = getattr(proto, field_name) return from_proto(value) - def _convert_to_proto(self, obj: Optional[Any], to_proto: Callable) -> Any: - """Convert object to proto if it exists, otherwise return None""" + def _convert_to_proto(self, obj: Any | None, to_proto: Callable) -> Any: + """Convert object to proto if it exists, otherwise return None.""" return to_proto(obj) if obj else None diff --git a/src/hiero_sdk_python/schedule/schedule_info_query.py b/src/hiero_sdk_python/schedule/schedule_info_query.py index 83067be0b..625668b25 100644 --- a/src/hiero_sdk_python/schedule/schedule_info_query.py +++ b/src/hiero_sdk_python/schedule/schedule_info_query.py @@ -1,9 +1,8 @@ -""" -Query to get information about a schedule on the network. -""" +"""Query to get information about a schedule on the network.""" + +from __future__ import annotations import traceback -from typing import Optional, Union from hiero_sdk_python.channels import _Channel from hiero_sdk_python.client.client import Client @@ -23,22 +22,22 @@ class ScheduleInfoQuery(Query): about a schedule on the network, including the schedule's properties and settings. """ - def __init__(self, schedule_id: Optional[ScheduleId] = None) -> None: + def __init__(self, schedule_id: ScheduleId | None = None) -> None: """ Initializes a new ScheduleInfoQuery instance with an optional schedule_id. Args: - schedule_id (Optional[ScheduleId]): The ID of the schedule to query. + schedule_id (ScheduleId, optional): The ID of the schedule to query. """ super().__init__() - self.schedule_id: Optional[ScheduleId] = schedule_id + self.schedule_id: ScheduleId | None = schedule_id - def set_schedule_id(self, schedule_id: Optional[ScheduleId]) -> "ScheduleInfoQuery": + def set_schedule_id(self, schedule_id: ScheduleId | None) -> ScheduleInfoQuery: """ Sets the ID of the schedule to query. Args: - schedule_id (Optional[ScheduleId]): The ID of the schedule. + schedule_id (ScheduleId | None): The ID of the schedule. """ self.schedule_id = schedule_id return self @@ -91,7 +90,7 @@ def _get_method(self, channel: _Channel) -> _Method: """ return _Method(transaction_func=None, query_func=channel.schedule.getScheduleInfo) - def execute(self, client: Client, timeout: Optional[Union[int, float]] = None) -> ScheduleInfo: + def execute(self, client: Client, timeout: int | float | None = None) -> ScheduleInfo: """ Executes the schedule info query. @@ -103,7 +102,7 @@ def execute(self, client: Client, timeout: Optional[Union[int, float]] = None) - Args: client (Client): The client instance to use for execution - timeout (Optional[Union[int, float]]): The total execution timeout (in seconds) for this execution. + timeout (int | float, optional): The total execution timeout (in seconds) for this execution. Returns: ScheduleInfo: The schedule info from the network @@ -118,9 +117,7 @@ def execute(self, client: Client, timeout: Optional[Union[int, float]] = None) - return ScheduleInfo._from_proto(response.scheduleGetInfo.scheduleInfo) - def _get_query_response( - self, response: response_pb2.Response - ) -> ScheduleGetInfoResponse: + def _get_query_response(self, response: response_pb2.Response) -> ScheduleGetInfoResponse: """ Extracts the schedule info response from the full response. diff --git a/src/hiero_sdk_python/schedule/schedule_sign_transaction.py b/src/hiero_sdk_python/schedule/schedule_sign_transaction.py index 431914d14..c2b677953 100644 --- a/src/hiero_sdk_python/schedule/schedule_sign_transaction.py +++ b/src/hiero_sdk_python/schedule/schedule_sign_transaction.py @@ -1,8 +1,6 @@ -""" -ScheduleSignTransaction class. -""" +"""ScheduleSignTransaction class.""" -from typing import Optional +from __future__ import annotations from hiero_sdk_python.channels import _Channel from hiero_sdk_python.executable import _Method @@ -33,25 +31,23 @@ class ScheduleSignTransaction(Transaction): """ - def __init__(self, schedule_id: Optional[ScheduleId] = None): + def __init__(self, schedule_id: ScheduleId | None = None): """ Initializes a new ScheduleSignTransaction instance with the specified parameters. Args: - schedule_id (Optional[ScheduleId]): The ID of the schedule to sign. + schedule_id (ScheduleId, optional): The ID of the schedule to sign. """ super().__init__() - self.schedule_id: Optional[ScheduleId] = schedule_id + self.schedule_id: ScheduleId | None = schedule_id self._default_transaction_fee = Hbar(5).to_tinybars() - def set_schedule_id( - self, schedule_id: Optional[ScheduleId] - ) -> "ScheduleSignTransaction": + def set_schedule_id(self, schedule_id: ScheduleId | None) -> ScheduleSignTransaction: """ Sets the schedule ID for this schedule sign transaction. Args: - schedule_id (Optional[ScheduleId]): + schedule_id (ScheduleId | None): The ID of the schedule to sign. Returns: diff --git a/src/hiero_sdk_python/staking_info.py b/src/hiero_sdk_python/staking_info.py index 04998e62c..65c8548d1 100644 --- a/src/hiero_sdk_python/staking_info.py +++ b/src/hiero_sdk_python/staking_info.py @@ -1,9 +1,8 @@ -""" -StakingInfo class. -""" +"""StakingInfo class.""" + +from __future__ import annotations from dataclasses import dataclass -from typing import Optional from hiero_sdk_python.account.account_id import AccountId from hiero_sdk_python.hapi.services.basic_types_pb2 import StakingInfo as StakingInfoProto @@ -17,41 +16,37 @@ class StakingInfo: Represents staking-related information for an account. Attributes: - decline_reward (Optional[bool]): Whether rewards are declined. - stake_period_start (Optional[Timestamp]): Start of the staking period. - pending_reward (Optional[Hbar]): Pending staking reward in Hbar. - staked_to_me (Optional[Hbar]): Amount staked to this account in Hbar. - staked_account_id (Optional[AccountId]): Account ID this account is staked to. - staked_node_id (Optional[int]): Node ID this account is staked to. + decline_reward (bool, optional): Whether rewards are declined. + stake_period_start (Timestamp, optional): Start of the staking period. + pending_reward (Hbar, optional): Pending staking reward in Hbar. + staked_to_me (Hbar, optional): Amount staked to this account in Hbar. + staked_account_id (AccountId, optional): Account ID this account is staked to. + staked_node_id (int, optional): Node ID this account is staked to. """ - decline_reward: Optional[bool] = None - stake_period_start: Optional[Timestamp] = None - pending_reward: Optional[Hbar] = None - staked_to_me: Optional[Hbar] = None - staked_account_id: Optional[AccountId] = None - staked_node_id: Optional[int] = None + decline_reward: bool | None = None + stake_period_start: Timestamp | None = None + pending_reward: Hbar | None = None + staked_to_me: Hbar | None = None + staked_account_id: AccountId | None = None + staked_node_id: int | None = None def __post_init__(self) -> None: if self.staked_account_id is not None and self.staked_node_id is not None: raise ValueError("Only one of staked_account_id or staked_node_id can be set.") @classmethod - def _from_proto(cls, proto: StakingInfoProto) -> "StakingInfo": - """ - Creates a StakingInfo instance from its protobuf representation. - """ + def _from_proto(cls, proto: StakingInfoProto) -> StakingInfo: + """Creates a StakingInfo instance from its protobuf representation.""" if proto is None: raise ValueError("Staking info proto is None") - decline_reward = proto.decline_reward - stake_period_start = None if proto.HasField("stake_period_start"): stake_period_start = Timestamp._from_protobuf(proto.stake_period_start) - pending_reward=Hbar.from_tinybars(proto.pending_reward) - staked_to_me=Hbar.from_tinybars(proto.staked_to_me) + pending_reward = Hbar.from_tinybars(proto.pending_reward) + staked_to_me = Hbar.from_tinybars(proto.staked_to_me) staked_account_id = None if proto.HasField("staked_account_id"): @@ -71,9 +66,7 @@ def _from_proto(cls, proto: StakingInfoProto) -> "StakingInfo": ) def _to_proto(self) -> StakingInfoProto: - """ - Converts this StakingInfo instance to its protobuf representation. - """ + """Converts this StakingInfo instance to its protobuf representation.""" proto = StakingInfoProto() if self.decline_reward is not None: @@ -92,10 +85,8 @@ def _to_proto(self) -> StakingInfoProto: return proto @classmethod - def from_bytes(cls, data: bytes) -> "StakingInfo": - """ - Creates a StakingInfo instance from protobuf-encoded bytes. - """ + def from_bytes(cls, data: bytes) -> StakingInfo: + """Creates a StakingInfo instance from protobuf-encoded bytes.""" if not isinstance(data, bytes): raise TypeError("data must be bytes") if len(data) == 0: @@ -109,9 +100,7 @@ def from_bytes(cls, data: bytes) -> "StakingInfo": return cls._from_proto(proto) def to_bytes(self) -> bytes: - """ - Serializes this StakingInfo instance to protobuf-encoded bytes. - """ + """Serializes this StakingInfo instance to protobuf-encoded bytes.""" return self._to_proto().SerializeToString() def __str__(self) -> str: diff --git a/src/hiero_sdk_python/system/freeze_transaction.py b/src/hiero_sdk_python/system/freeze_transaction.py index bad258254..16467aaf2 100644 --- a/src/hiero_sdk_python/system/freeze_transaction.py +++ b/src/hiero_sdk_python/system/freeze_transaction.py @@ -1,8 +1,6 @@ -""" -FreezeTransaction class for freezing the network. -""" +"""FreezeTransaction class for freezing the network.""" -from typing import Optional +from __future__ import annotations from hiero_sdk_python.channels import _Channel from hiero_sdk_python.executable import _Method @@ -30,32 +28,32 @@ class FreezeTransaction(Transaction): def __init__( self, - start_time: Optional[Timestamp] = None, - file_id: Optional[FileId] = None, - file_hash: Optional[bytes] = None, - freeze_type: Optional[FreezeType] = None, + start_time: Timestamp | None = None, + file_id: FileId | None = None, + file_hash: bytes | None = None, + freeze_type: FreezeType | None = None, ): """ Initializes a new FreezeTransaction instance with the specified parameters. Args: - start_time (Optional[Timestamp]): The start time for the freeze. - file_id (Optional[FileId]): The file ID containing the upgrade data. - file_hash (Optional[bytes]): Hash of the file for verification. - freeze_type (Optional[FreezeType]): The type of freeze to perform. + start_time (Timestamp, optional): The start time for the freeze. + file_id (FileId, optional): The file ID containing the upgrade data. + file_hash (bytes, optional): Hash of the file for verification. + freeze_type (FreezeType, optional): The type of freeze to perform. """ super().__init__() - self.start_time: Optional[Timestamp] = start_time - self.file_id: Optional[FileId] = file_id - self.file_hash: Optional[bytes] = file_hash - self.freeze_type: Optional[FreezeType] = freeze_type + self.start_time: Timestamp | None = start_time + self.file_id: FileId | None = file_id + self.file_hash: bytes | None = file_hash + self.freeze_type: FreezeType | None = freeze_type - def set_start_time(self, start_time: Optional[Timestamp]) -> "FreezeTransaction": + def set_start_time(self, start_time: Timestamp | None) -> FreezeTransaction: """ Sets the start time for this freeze transaction. Args: - start_time (Optional[Timestamp]): The start time for the freeze. + start_time (Timestamp | None): The start time for the freeze. Returns: FreezeTransaction: This transaction instance. @@ -64,12 +62,12 @@ def set_start_time(self, start_time: Optional[Timestamp]) -> "FreezeTransaction" self.start_time = start_time return self - def set_file_id(self, file_id: Optional[FileId]) -> "FreezeTransaction": + def set_file_id(self, file_id: FileId | None) -> FreezeTransaction: """ Sets the file ID for this freeze transaction. Args: - file_id (Optional[FileId]): The file ID containing the upgrade data. + file_id (FileId | None): The file ID containing the upgrade data. Returns: FreezeTransaction: This transaction instance. @@ -78,12 +76,12 @@ def set_file_id(self, file_id: Optional[FileId]) -> "FreezeTransaction": self.file_id = file_id return self - def set_file_hash(self, file_hash: Optional[bytes]) -> "FreezeTransaction": + def set_file_hash(self, file_hash: bytes | None) -> FreezeTransaction: """ Sets the file hash for this freeze transaction. Args: - file_hash (Optional[bytes]): Hash of the file for verification. + file_hash (bytes | None): Hash of the file for verification. Returns: FreezeTransaction: This transaction instance. @@ -92,12 +90,12 @@ def set_file_hash(self, file_hash: Optional[bytes]) -> "FreezeTransaction": self.file_hash = file_hash return self - def set_freeze_type(self, freeze_type: Optional[FreezeType]) -> "FreezeTransaction": + def set_freeze_type(self, freeze_type: FreezeType | None) -> FreezeTransaction: """ Sets the freeze type for this freeze transaction. Args: - freeze_type (Optional[FreezeType]): The type of freeze to perform. + freeze_type (FreezeType | None): The type of freeze to perform. Returns: FreezeTransaction: This transaction instance. diff --git a/src/hiero_sdk_python/system/freeze_type.py b/src/hiero_sdk_python/system/freeze_type.py index b9a3120d6..45b772a66 100644 --- a/src/hiero_sdk_python/system/freeze_type.py +++ b/src/hiero_sdk_python/system/freeze_type.py @@ -1,6 +1,7 @@ -""" -Defines FreezeType enum for representing freeze types -""" +"""Defines FreezeType enum for representing freeze types.""" + +from __future__ import annotations + from enum import Enum from hiero_sdk_python.hapi.services.freeze_type_pb2 import ( @@ -28,53 +29,53 @@ class FreezeType(Enum): TELEMETRY_UPGRADE = 5 @staticmethod - def _from_proto(proto_obj: proto_FreezeType) -> "FreezeType": + def _from_proto(proto_obj: proto_FreezeType) -> FreezeType: """ Converts a protobuf FreezeType to a FreezeType enum. - + Args: proto_obj (proto_FreezeType): The protobuf FreezeType object. - + Returns: FreezeType: The corresponding FreezeType enum value. """ if proto_obj == proto_FreezeType.FREEZE_ONLY: return FreezeType.FREEZE_ONLY - elif proto_obj == proto_FreezeType.PREPARE_UPGRADE: + if proto_obj == proto_FreezeType.PREPARE_UPGRADE: return FreezeType.PREPARE_UPGRADE - elif proto_obj == proto_FreezeType.FREEZE_UPGRADE: + if proto_obj == proto_FreezeType.FREEZE_UPGRADE: return FreezeType.FREEZE_UPGRADE - elif proto_obj == proto_FreezeType.FREEZE_ABORT: + if proto_obj == proto_FreezeType.FREEZE_ABORT: return FreezeType.FREEZE_ABORT - elif proto_obj == proto_FreezeType.TELEMETRY_UPGRADE: + if proto_obj == proto_FreezeType.TELEMETRY_UPGRADE: return FreezeType.TELEMETRY_UPGRADE return FreezeType.UNKNOWN_FREEZE_TYPE def _to_proto(self) -> proto_FreezeType: """ Converts a FreezeType enum to a protobuf FreezeType object. - + Args: self (FreezeType): The FreezeType enum value. - + Returns: proto_FreezeType: The corresponding protobuf FreezeType object. """ if self == FreezeType.FREEZE_ONLY: return proto_FreezeType.FREEZE_ONLY - elif self == FreezeType.PREPARE_UPGRADE: + if self == FreezeType.PREPARE_UPGRADE: return proto_FreezeType.PREPARE_UPGRADE - elif self == FreezeType.FREEZE_UPGRADE: + if self == FreezeType.FREEZE_UPGRADE: return proto_FreezeType.FREEZE_UPGRADE - elif self == FreezeType.FREEZE_ABORT: + if self == FreezeType.FREEZE_ABORT: return proto_FreezeType.FREEZE_ABORT - elif self == FreezeType.TELEMETRY_UPGRADE: + if self == FreezeType.TELEMETRY_UPGRADE: return proto_FreezeType.TELEMETRY_UPGRADE return proto_FreezeType.UNKNOWN_FREEZE_TYPE def __eq__(self, other: object) -> bool: if isinstance(other, FreezeType): return self.value == other.value - elif isinstance(other, int): + if isinstance(other, int): return self.value == other return False diff --git a/src/hiero_sdk_python/timestamp.py b/src/hiero_sdk_python/timestamp.py index f135994b1..49a6bda7b 100644 --- a/src/hiero_sdk_python/timestamp.py +++ b/src/hiero_sdk_python/timestamp.py @@ -1,14 +1,14 @@ -from datetime import datetime, timedelta, timezone +from __future__ import annotations + import random import time +from datetime import datetime, timedelta, timezone from hiero_sdk_python.hapi.services.timestamp_pb2 import Timestamp as TimestampProto class Timestamp: - """ - Represents a specific moment in time with nanosecond precision. - """ + """Represents a specific moment in time with nanosecond precision.""" MAX_NS = 1_000_000_000 @@ -24,7 +24,7 @@ def __init__(self, seconds: int, nanos: int): self.nanos = nanos @staticmethod - def generate(has_jitter=True) -> "Timestamp": + def generate(has_jitter=True) -> Timestamp: """ Generate a `Timestamp` with optional jitter. @@ -42,7 +42,7 @@ def generate(has_jitter=True) -> "Timestamp": return Timestamp(seconds, nanos) @staticmethod - def from_date(date) -> "Timestamp": + def from_date(date) -> Timestamp: """ Create a `Timestamp` from a Python `datetime` object, timestamp, or string. @@ -74,11 +74,9 @@ def to_date(self) -> datetime: Returns: datetime: A `datetime` instance. """ - return datetime.fromtimestamp(self.seconds, tz=timezone.utc) + timedelta( - microseconds=self.nanos // 1000 - ) + return datetime.fromtimestamp(self.seconds, tz=timezone.utc) + timedelta(microseconds=self.nanos // 1000) - def plus_nanos(self, nanos: int) -> "Timestamp": + def plus_nanos(self, nanos: int) -> Timestamp: """ Add nanoseconds to the current `Timestamp`. @@ -104,7 +102,7 @@ def _to_protobuf(self) -> TimestampProto: return TimestampProto(seconds=self.seconds, nanos=self.nanos) @staticmethod - def _from_protobuf(pb_obj: TimestampProto) -> "Timestamp": + def _from_protobuf(pb_obj: TimestampProto) -> Timestamp: """ Create a `Timestamp` from a protobuf object. @@ -114,7 +112,6 @@ def _from_protobuf(pb_obj: TimestampProto) -> "Timestamp": Returns: Timestamp: A `Timestamp` instance. """ - return Timestamp(pb_obj.seconds, pb_obj.nanos) def __str__(self) -> str: @@ -126,7 +123,7 @@ def __str__(self) -> str: """ return f"{self.seconds}.{str(self.nanos).zfill(9)}" - def compare(self, other: "Timestamp") -> int: + def compare(self, other: Timestamp) -> int: """ Compare the current `Timestamp` with another. diff --git a/src/hiero_sdk_python/tokens/abstract_token_transfer_transaction.py b/src/hiero_sdk_python/tokens/abstract_token_transfer_transaction.py index cd3910217..a423b51d3 100644 --- a/src/hiero_sdk_python/tokens/abstract_token_transfer_transaction.py +++ b/src/hiero_sdk_python/tokens/abstract_token_transfer_transaction.py @@ -1,16 +1,19 @@ -"""hiero_sdk_python.tokens.abstract_token_transfer_transaction.py +"""hiero_sdk_python.tokens.abstract_token_transfer_transaction.py. Abstract base transaction for fungible token and NFT transfers on Hedera. This module provides the `AbstractTokenTransferTransaction` class, which encapsulates common logic for grouping and validating multiple token and NFT transfer operations into Hedera-compatible protobuf messages. -It handles the collection of token and NFT transfers before they are aggregated +It handles the collection of token and NFT transfers before they are aggregated for building the transaction body. """ + +from __future__ import annotations + from abc import ABC from collections import defaultdict -from typing import Any, Dict, Generic, Optional, List, Tuple, TypeVar, Union +from typing import Any, Generic, TypeVar from hiero_sdk_python.account.account_id import AccountId from hiero_sdk_python.hapi.services import basic_types_pb2 @@ -21,41 +24,41 @@ from hiero_sdk_python.tokens.token_transfer_list import TokenTransferList from hiero_sdk_python.transaction.transaction import Transaction + T = TypeVar("T", bound="AbstractTokenTransferTransaction[Any]") + class AbstractTokenTransferTransaction(Transaction, ABC, Generic[T]): """ Base transaction class for executing multiple token and NFT transfers. Collects fungible and non-fungible token transfers, ensures balance - rules, and builds the corresponding Hedera protobuf messages. This class + rules, and builds the corresponding Hedera protobuf messages. This class is typically inherited by concrete transaction types like `TransferTransaction`. """ + def __init__(self) -> None: """ Initializes a new AbstractTokenTransferTransaction instance. - Sets up empty lists for token and NFT transfers and defines the default + Sets up empty lists for token and NFT transfers and defines the default transaction fee. """ super().__init__() - self.token_transfers: Dict[TokenId, List[TokenTransfer]] = defaultdict(list) - self.nft_transfers: Dict[TokenId, List[TokenNftTransfer]] = defaultdict(list) + self.token_transfers: dict[TokenId, list[TokenTransfer]] = defaultdict(list) + self.nft_transfers: dict[TokenId, list[TokenNftTransfer]] = defaultdict(list) self._default_transaction_fee: int = 100_000_000 - def _init_token_transfers( - self, - token_transfers: Union[Dict[TokenId, Dict[AccountId, int]], List[TokenTransfer]] - ) -> None: + def _init_token_transfers(self, token_transfers: dict[TokenId, dict[AccountId, int]] | list[TokenTransfer]) -> None: """Initializes the transaction with a list of fungible token transfers. - Iterates through the provided list and adds each transfer using the + Iterates through the provided list and adds each transfer using the private `_add_token_transfer` method. Args: - token_transfers (Union[Dict[TokenId, Dict[AccountId, int]], List[TokenTransfer]]): + token_transfers (dict[TokenId, dict[AccountId, int] | list[TokenTransfer]): A list of initialized TokenTransfer objects. - + Raises: TypeError: If `token_transfers` is neither a list nor a dictionary. """ @@ -66,7 +69,7 @@ def _init_token_transfers( transfer.account_id, transfer.amount, transfer.expected_decimals, - transfer.is_approved + transfer.is_approved, ) elif isinstance(token_transfers, dict): for token_id, account_transfers in token_transfers.items(): @@ -79,21 +82,16 @@ def _init_token_transfers( ) def _init_nft_transfers( - self, - nft_transfers: Union[ - Dict[TokenId, List[Tuple[AccountId, AccountId, int, bool]]], - List[TokenNftTransfer] - ] - ) -> None: + self, nft_transfers: dict[TokenId, list[tuple[AccountId, AccountId, int, bool]]] | list[TokenNftTransfer] + ) -> None: """Initializes the transaction with a list of NFT transfers. - Iterates through the provided list and adds each transfer using the + Iterates through the provided list and adds each transfer using the private `_add_nft_transfer` method. Args: - nft_transfers (Union[ - Dict[TokenId, List[Tuple[AccountId, AccountId, int, bool]]], List[TokenNftTransfer]] - ):A list or dictionary describing NFT transfers. + nft_transfers (dict[TokenId, list[tuple[AccountId, AccountId, int, bool]]] | list[TokenNftTransfer]): + A list or dictionary describing NFT transfers. Raises: TypeError: If `nft_transfers` is neither a list nor a dictionary. @@ -105,39 +103,37 @@ def _init_nft_transfers( transfer.sender_id, transfer.receiver_id, transfer.serial_number, - transfer.is_approved + transfer.is_approved, ) elif isinstance(nft_transfers, dict): for token_id, transfers in nft_transfers.items(): for sender_id, receiver_id, serial_number, is_approved in transfers: - self._add_nft_transfer( - token_id, sender_id, receiver_id, serial_number, is_approved - ) + self._add_nft_transfer(token_id, sender_id, receiver_id, serial_number, is_approved) else: raise TypeError( "Invalid type for `nft_transfers`. Expected a list of TokenNftTransfer " - "or a dict[TokenId, List[Tuple[AccountId, AccountId, int, bool]]]." + "or a dict[TokenId, list[tuple[AccountId, AccountId, int, bool]]]." ) def _add_token_transfer( - self, - token_id: TokenId, - account_id: AccountId, - amount: int, - expected_decimals: Optional[int]=None, - is_approved: bool=False - ) -> None: + self, + token_id: TokenId, + account_id: AccountId, + amount: int, + expected_decimals: int | None = None, + is_approved: bool = False, + ) -> None: """Adds a fungible token transfer to the transaction's list. Args: token_id (TokenId): The ID of the fungible token being transferred. - account_id (AccountId): The account ID of the sender (negative amount) + account_id (AccountId): The account ID of the sender (negative amount) or receiver (positive amount). amount (int): The amount of the token to transfer (in smallest denomination). Must be a non-zero integer. - expected_decimals (Optional[int], optional): The number of decimals + expected_decimals (int, optional): The number of decimals expected for the token. Defaults to None. - is_approved (bool, optional): Whether the transfer is approved. + is_approved (bool, optional): Whether the transfer is approved. Defaults to False. Raises: @@ -166,13 +162,13 @@ def _add_token_transfer( ) def _add_nft_transfer( - self, - token_id: TokenId, - sender_id: AccountId, - receiver_id: AccountId, - serial_number: int, - is_approved: bool=False - ) -> None: + self, + token_id: TokenId, + sender_id: AccountId, + receiver_id: AccountId, + serial_number: int, + is_approved: bool = False, + ) -> None: """Adds an NFT (Non-Fungible Token) transfer to the transaction's list. Args: @@ -180,7 +176,7 @@ def _add_nft_transfer( sender (AccountId): The sender's account ID. receiver (AccountId): The receiver's account ID. serial_number (int): The unique serial number of the NFT being transferred. - is_approved (bool, optional): Whether the transfer is approved. + is_approved (bool, optional): Whether the transfer is approved. Defaults to False. Raises: @@ -199,14 +195,9 @@ def _add_nft_transfer( TokenNftTransfer(token_id, sender_id, receiver_id, serial_number, is_approved) ) - def add_token_transfer( - self: T, - token_id: TokenId, - account_id: AccountId, - amount: int - ) -> T: + def add_token_transfer(self: T, token_id: TokenId, account_id: AccountId, amount: int) -> T: """ - Adds a transfer to token_transfers list + Adds a transfer to token_transfers list Args: token_id (TokenId): The ID of the token being transferred. account_id (AccountId): The accountId of sender/receiver. @@ -220,12 +211,8 @@ def add_token_transfer( return self def add_token_transfer_with_decimals( - self: T, - token_id: TokenId, - account_id: AccountId, - amount: int, - decimals: int - ) -> T: + self: T, token_id: TokenId, account_id: AccountId, amount: int, decimals: int + ) -> T: """ Adds a transfer with expected_decimals to token_transfers list Args: @@ -241,14 +228,9 @@ def add_token_transfer_with_decimals( self._add_token_transfer(token_id, account_id, amount, expected_decimals=decimals) return self - def add_approved_token_transfer( - self: T, - token_id: TokenId, - account_id: AccountId, - amount: int - ) -> T: + def add_approved_token_transfer(self: T, token_id: TokenId, account_id: AccountId, amount: int) -> T: """ - Adds a transfer with approve allowance to token_transfers list + Adds a transfer with approve allowance to token_transfers list Args: token_id (TokenId): The ID of the token being transferred. account_id (AccountId): The accountId of sender/receiver. @@ -262,12 +244,8 @@ def add_approved_token_transfer( return self def add_approved_token_transfer_with_decimals( - self: T, - token_id: TokenId, - account_id: AccountId, - amount: int, - decimals: int - ) -> T: + self: T, token_id: TokenId, account_id: AccountId, amount: int, decimals: int + ) -> T: """ Adds a transfer with expected_decimals and approve allowance to token_transfers list Args: @@ -284,20 +262,16 @@ def add_approved_token_transfer_with_decimals( return self def add_nft_transfer( - self: T, - nft_id: NftId, - sender_id: AccountId, - receiver_id: AccountId, - is_approved: bool = False - ) -> T: + self: T, nft_id: NftId, sender_id: AccountId, receiver_id: AccountId, is_approved: bool = False + ) -> T: """ - Adds a transfer to the nft_transfers + Adds a transfer to the nft_transfers. Args: nft_id (NftId): The ID of the NFT being transferred. sender_id (AccountId): The sender's account ID. receiver_id (AccountId): The receiver's account ID. - is_approved (bool): Whether the transfer is approved. + is_approved (bool): Whether the transfer is approved. Returns: Self: The current instance of the transaction for chaining. @@ -307,19 +281,12 @@ def add_nft_transfer( if not isinstance(nft_id, NftId): raise TypeError("nft_id must be a NftId instance.") - self._add_nft_transfer( - nft_id.token_id, sender_id, receiver_id, nft_id.serial_number, is_approved - ) + self._add_nft_transfer(nft_id.token_id, sender_id, receiver_id, nft_id.serial_number, is_approved) return self - def add_approved_nft_transfer( - self: T, - nft_id: NftId, - sender_id: AccountId, - receiver_id: AccountId - ) -> T: + def add_approved_nft_transfer(self: T, nft_id: NftId, sender_id: AccountId, receiver_id: AccountId) -> T: """ - Adds a transfer to the nft_transfers with approved allowance + Adds a transfer to the nft_transfers with approved allowance. Args: nft_id (NftId): The ID of the NFT being transferred. @@ -337,13 +304,13 @@ def add_approved_nft_transfer( self._add_nft_transfer(nft_id.token_id, sender_id, receiver_id, nft_id.serial_number, True) return self - def build_token_transfers(self) -> 'List[basic_types_pb2.TokenTransferList]': + def build_token_transfers(self) -> list[basic_types_pb2.TokenTransferList]: """ Aggregates all individual fungible token transfers and NFT transfers into a list of TokenTransferList objects, where each TokenTransferList groups transfers for a specific token ID. - Performs validation to ensure all fungible token transfers for a given + Performs validation to ensure all fungible token transfers for a given token ID are balanced (net change must be zero). Returns: @@ -353,14 +320,11 @@ def build_token_transfers(self) -> 'List[basic_types_pb2.TokenTransferList]': Raises: ValueError: If fungible transfers for any token ID are not balanced. """ - token_transfer_list: List[TokenTransferList] = [] + token_transfer_list: list[TokenTransferList] = [] # Tokens for token_id, token_transfers in self.token_transfers.items(): - token_list = TokenTransferList( - token=token_id, - expected_decimals=token_transfers[0].expected_decimals - ) + token_list = TokenTransferList(token=token_id, expected_decimals=token_transfers[0].expected_decimals) for token_transfer in token_transfers: token_list.add_token_transfer(token_transfer) @@ -385,9 +349,7 @@ def build_token_transfers(self) -> 'List[basic_types_pb2.TokenTransferList]': net_amount += token_transfer.amount if net_amount != 0: - raise ValueError( - "All fungible token transfers must be balanced, debits must equal credits." - ) + raise ValueError("All fungible token transfers must be balanced, debits must equal credits.") token_transfer_proto.append(transfer._to_proto()) diff --git a/src/hiero_sdk_python/tokens/assessed_custom_fee.py b/src/hiero_sdk_python/tokens/assessed_custom_fee.py index 2866f93b5..800606fe4 100644 --- a/src/hiero_sdk_python/tokens/assessed_custom_fee.py +++ b/src/hiero_sdk_python/tokens/assessed_custom_fee.py @@ -1,5 +1,6 @@ +from __future__ import annotations + from dataclasses import dataclass, field -from typing import Optional from hiero_sdk_python.account.account_id import AccountId from hiero_sdk_python.hapi.services.custom_fees_pb2 import ( @@ -32,10 +33,10 @@ class AssessedCustomFee: amount: int """The amount of the fee assessed, in the smallest units of the token (or tinybars for HBAR).""" - token_id: Optional[TokenId] = None + token_id: TokenId | None = None """The ID of the token used to pay the fee; None if paid in HBAR.""" - fee_collector_account_id: Optional[AccountId] = None + fee_collector_account_id: AccountId | None = None """The account ID that collects/receives this assessed custom fee (required field).""" effective_payer_account_ids: list[AccountId] = field(default_factory=list) @@ -43,31 +44,22 @@ class AssessedCustomFee: def __post_init__(self) -> None: if self.fee_collector_account_id is None: - raise ValueError( - "fee_collector_account_id is required for AssessedCustomFee" - ) + raise ValueError("fee_collector_account_id is required for AssessedCustomFee") @classmethod - def _from_proto(cls, proto: AssessedCustomFeeProto) -> "AssessedCustomFee": + def _from_proto(cls, proto: AssessedCustomFeeProto) -> AssessedCustomFee: """Create an AssessedCustomFee instance from the protobuf message.""" - token_id = ( - TokenId._from_proto(proto.token_id) if proto.HasField("token_id") else None - ) + token_id = TokenId._from_proto(proto.token_id) if proto.HasField("token_id") else None if not proto.HasField("fee_collector_account_id"): - raise ValueError( - "fee_collector_account_id is required in AssessedCustomFee proto" - ) + raise ValueError("fee_collector_account_id is required in AssessedCustomFee proto") return cls( amount=proto.amount, token_id=token_id, - fee_collector_account_id=AccountId._from_proto( - proto.fee_collector_account_id - ), + fee_collector_account_id=AccountId._from_proto(proto.fee_collector_account_id), effective_payer_account_ids=[ - AccountId._from_proto(payer_proto) - for payer_proto in proto.effective_payer_account_id + AccountId._from_proto(payer_proto) for payer_proto in proto.effective_payer_account_id ], ) diff --git a/src/hiero_sdk_python/tokens/custom_fee.py b/src/hiero_sdk_python/tokens/custom_fee.py index 9d53fb4ab..087c523e6 100644 --- a/src/hiero_sdk_python/tokens/custom_fee.py +++ b/src/hiero_sdk_python/tokens/custom_fee.py @@ -11,9 +11,11 @@ """ from __future__ import annotations + import typing from abc import ABC, abstractmethod + if typing.TYPE_CHECKING: from hiero_sdk_python.account.account_id import AccountId from hiero_sdk_python.client.client import Client @@ -32,13 +34,13 @@ class CustomFee(ABC): def __init__( self, - fee_collector_account_id: typing.Optional[AccountId] = None, + fee_collector_account_id: AccountId | None = None, all_collectors_are_exempt: bool = False, ): """Initializes a CustomFee instance. Args: - fee_collector_account_id (Optional[AccountId]): The account ID that collects + fee_collector_account_id (AccountId, optional): The account ID that collects the fee. If None, no collector is specified. all_collectors_are_exempt (bool): If True, all collectors are exempt from this fee. Defaults to False. @@ -46,7 +48,7 @@ def __init__( self.fee_collector_account_id = fee_collector_account_id self.all_collectors_are_exempt = all_collectors_are_exempt - def set_fee_collector_account_id(self, account_id: AccountId) -> "CustomFee": + def set_fee_collector_account_id(self, account_id: AccountId) -> CustomFee: """Sets the fee collector account ID. Args: @@ -58,7 +60,7 @@ def set_fee_collector_account_id(self, account_id: AccountId) -> "CustomFee": self.fee_collector_account_id = account_id return self - def set_all_collectors_are_exempt(self, exempt: bool) -> "CustomFee": + def set_all_collectors_are_exempt(self, exempt: bool) -> CustomFee: """Sets the exemption status for all collectors. Args: @@ -71,7 +73,7 @@ def set_all_collectors_are_exempt(self, exempt: bool) -> "CustomFee": return self @staticmethod - def _from_proto(custom_fee: "CustomFeeProto") -> "CustomFee": # Changed from _from_protobuf + def _from_proto(custom_fee: CustomFeeProto) -> CustomFee: # Changed from _from_protobuf """Creates a CustomFee instance from a protobuf message. This factory method dynamically instantiates the appropriate subclass based on the @@ -100,21 +102,17 @@ def _from_proto(custom_fee: "CustomFeeProto") -> "CustomFee": # Changed from _f raise ValueError(f"Unrecognized fee case: {fee_case}") - def _get_fee_collector_account_id_protobuf(self) -> typing.Optional[AccountID]: + def _get_fee_collector_account_id_protobuf(self) -> AccountID | None: """Retrieves the fee collector account ID in protobuf format. Returns: - Optional[AccountID]: The protobuf AccountID if the fee collector is set, + AccountID | None: The protobuf AccountID if the fee collector is set, otherwise None. """ - return ( - self.fee_collector_account_id._to_proto() - if self.fee_collector_account_id is not None - else None - ) + return self.fee_collector_account_id._to_proto() if self.fee_collector_account_id is not None else None @abstractmethod - def _to_proto(self) -> "CustomFeeProto": # Changed from _to_protobuf + def _to_proto(self) -> CustomFeeProto: # Changed from _to_protobuf """Converts this CustomFee to its protobuf representation. Subclasses must implement this method to serialize their specific fee details. @@ -147,5 +145,8 @@ def __eq__(self, other: object) -> bool: """ if not isinstance(other, CustomFee): return NotImplemented - - return self.fee_collector_account_id == other.fee_collector_account_id and self.all_collectors_are_exempt == other.all_collectors_are_exempt \ No newline at end of file + + return ( + self.fee_collector_account_id == other.fee_collector_account_id + and self.all_collectors_are_exempt == other.all_collectors_are_exempt + ) diff --git a/src/hiero_sdk_python/tokens/custom_fixed_fee.py b/src/hiero_sdk_python/tokens/custom_fixed_fee.py index 5408db151..cd3daa03b 100644 --- a/src/hiero_sdk_python/tokens/custom_fixed_fee.py +++ b/src/hiero_sdk_python/tokens/custom_fixed_fee.py @@ -1,16 +1,20 @@ from __future__ import annotations + import typing import warnings -from hiero_sdk_python.tokens.custom_fee import CustomFee + from hiero_sdk_python.hbar import Hbar +from hiero_sdk_python.tokens.custom_fee import CustomFee + if typing.TYPE_CHECKING: from hiero_sdk_python.client.client import Client from hiero_sdk_python.hapi.services import custom_fees_pb2 from hiero_sdk_python.account.account_id import AccountId -from hiero_sdk_python.tokens.token_id import TokenId from hiero_sdk_python.hapi.services import custom_fees_pb2 +from hiero_sdk_python.tokens.token_id import TokenId + """Manages custom fixed fees assessed during transactions on the Hedera network. @@ -19,6 +23,7 @@ amounts, denominating tokens, and converting to/from protobuf formats. """ + class CustomFixedFee(CustomFee): """Represents a fixed fee assessed as part of a custom fee schedule. @@ -40,8 +45,8 @@ class CustomFixedFee(CustomFee): def __init__( self, amount: int = 0, - denominating_token_id: typing.Optional["TokenId"] = None, - fee_collector_account_id: typing.Optional["AccountId"] = None, + denominating_token_id: TokenId | None = None, + fee_collector_account_id: AccountId | None = None, all_collectors_are_exempt: bool = False, ): """Initializes the CustomFixedFee. @@ -62,18 +67,14 @@ def __init__( super().__init__(fee_collector_account_id, all_collectors_are_exempt) self.amount = amount self.denominating_token_id = denominating_token_id - + def __str__(self) -> str: """Return a user-friendly string representation of the CustomFixedFee. Displays all fields in insertion order with aligned formatting. TokenId and AccountId objects are displayed in Hedera notation (e.g., 0.0.123). """ - - fields = { - key.replace("_", " ").title(): value - for key, value in self.__dict__.items() - } + fields = {key.replace("_", " ").title(): value for key, value in self.__dict__.items()} if not fields: return f"{self.__class__.__name__}()" @@ -86,11 +87,10 @@ def __str__(self) -> str: return "\n".join(lines) - - def set_amount_in_tinybars(self, amount: int) -> "CustomFixedFee": + def set_amount_in_tinybars(self, amount: int) -> CustomFixedFee: """Sets the fee amount in tinybars. - .. deprecated:: + .. deprecated:: Use :meth:`set_hbar_amount` with :meth:`Hbar.from_tinybars` instead. For example: ``set_hbar_amount(Hbar.from_tinybars(amount))`` @@ -106,13 +106,13 @@ def set_amount_in_tinybars(self, amount: int) -> "CustomFixedFee": "set_amount_in_tinybars() is deprecated and will be removed in a future release. " "Use set_hbar_amount(Hbar.from_tinybars(amount)) instead.", DeprecationWarning, - stacklevel=2 + stacklevel=2, ) self.denominating_token_id = None self.amount = amount return self - def set_hbar_amount(self, amount: Hbar) -> "CustomFixedFee": + def set_hbar_amount(self, amount: Hbar) -> CustomFixedFee: """Sets the fee amount using an Hbar object. Converts the Hbar amount to tinybars and clears any previously set @@ -128,7 +128,7 @@ def set_hbar_amount(self, amount: Hbar) -> "CustomFixedFee": self.amount = amount.to_tinybars() return self - def set_denominating_token_id(self, token_id: typing.Optional["TokenId"]) -> "CustomFixedFee": + def set_denominating_token_id(self, token_id: TokenId | None) -> CustomFixedFee: """Sets the fungible token used to pay the fee. If set to None, the fee defaults to being paid in HBAR. @@ -142,8 +142,8 @@ def set_denominating_token_id(self, token_id: typing.Optional["TokenId"]) -> "Cu """ self.denominating_token_id = token_id return self - - def set_denominating_token_to_same_token(self) -> "CustomFixedFee": + + def set_denominating_token_to_same_token(self) -> CustomFixedFee: """Configures the fee to be paid in the same token the custom fee is attached to. This sets the denominating_token_id to the sentinel value 0.0.0. @@ -152,11 +152,12 @@ def set_denominating_token_to_same_token(self) -> "CustomFixedFee": CustomFixedFee: This CustomFixedFee instance for chaining. """ from hiero_sdk_python.tokens.token_id import TokenId + self.denominating_token_id = TokenId(0, 0, 0) return self @staticmethod - def _from_fixed_fee_proto(fixed_fee: "custom_fees_pb2.FixedFee") -> "CustomFixedFee": + def _from_fixed_fee_proto(fixed_fee: custom_fees_pb2.FixedFee) -> CustomFixedFee: """Creates a CustomFixedFee instance from a FixedFee protobuf object. Internal helper method. @@ -168,15 +169,14 @@ def _from_fixed_fee_proto(fixed_fee: "custom_fees_pb2.FixedFee") -> "CustomFixed CustomFixedFee: The corresponding CustomFixedFee object. """ from hiero_sdk_python.tokens.token_id import TokenId + fee = CustomFixedFee() fee.amount = fixed_fee.amount if fixed_fee.HasField("denominating_token_id"): - fee.denominating_token_id = TokenId._from_proto( - fixed_fee.denominating_token_id - ) + fee.denominating_token_id = TokenId._from_proto(fixed_fee.denominating_token_id) return fee - def _to_proto(self) -> "custom_fees_pb2.CustomFee": + def _to_proto(self) -> custom_fees_pb2.CustomFee: """Converts this CustomFixedFee object to its protobuf representation. Builds the `FixedFee` part and integrates it with the common fields @@ -207,7 +207,7 @@ def _to_proto(self) -> "custom_fees_pb2.CustomFee": cf.all_collectors_are_exempt = self.all_collectors_are_exempt return cf - def _to_topic_fee_proto(self) -> "custom_fees_pb2.FixedCustomFee": + def _to_topic_fee_proto(self) -> custom_fees_pb2.FixedCustomFee: """Converts this CustomFixedFee object to a FixedCustomFee protobuf object. Specifically used for fee schedules related to Hedera Consensus Service topics. @@ -220,7 +220,7 @@ def _to_topic_fee_proto(self) -> "custom_fees_pb2.FixedCustomFee": (Note: Current implementation doesn't explicitly check amount >= 0). """ from hiero_sdk_python.hapi.services import custom_fees_pb2 - + return custom_fees_pb2.FixedCustomFee( fixed_fee=custom_fees_pb2.FixedFee( amount=self.amount, @@ -231,7 +231,7 @@ def _to_topic_fee_proto(self) -> "custom_fees_pb2.FixedCustomFee": fee_collector_account_id=self._get_fee_collector_account_id_protobuf(), ) - def _validate_checksums(self, client: "Client") -> None: + def _validate_checksums(self, client: Client) -> None: """Validates checksums for configured account and token IDs. Ensures that the fee collector account ID and the denominating token ID @@ -248,7 +248,7 @@ def _validate_checksums(self, client: "Client") -> None: self.denominating_token_id.validate_checksum(client) @classmethod - def _from_proto(cls, proto_fee: custom_fees_pb2.CustomFee) -> "CustomFixedFee": + def _from_proto(cls, proto_fee: custom_fees_pb2.CustomFee) -> CustomFixedFee: """Creates a CustomFixedFee instance from a CustomFee protobuf object. Extracts the fixed fee details and common fee properties from the @@ -264,27 +264,26 @@ def _from_proto(cls, proto_fee: custom_fees_pb2.CustomFee) -> "CustomFixedFee": Raises: ValueError: If the `fixed_fee` field is not set in the protobuf message. """ - fixed_fee_proto = proto_fee.fixed_fee - + denominating_token_id = None if fixed_fee_proto.HasField("denominating_token_id"): denominating_token_id = TokenId._from_proto(fixed_fee_proto.denominating_token_id) - + fee_collector_account_id = None if proto_fee.HasField("fee_collector_account_id"): fee_collector_account_id = AccountId._from_proto(proto_fee.fee_collector_account_id) - - collectors_are_exempt = getattr(proto_fee, 'all_collectors_are_exempt', False) - + + collectors_are_exempt = getattr(proto_fee, "all_collectors_are_exempt", False) + return cls( amount=fixed_fee_proto.amount, denominating_token_id=denominating_token_id, fee_collector_account_id=fee_collector_account_id, - all_collectors_are_exempt=collectors_are_exempt + all_collectors_are_exempt=collectors_are_exempt, ) - - def __eq__(self, other: "CustomFixedFee") -> bool: + + def __eq__(self, other: CustomFixedFee) -> bool: """Compares this CustomFixedFee instance with another object for equality. Checks if the other object is also a CustomFixedFee and if all @@ -297,4 +296,8 @@ def __eq__(self, other: "CustomFixedFee") -> bool: Returns: bool: True if the objects are considered equal, False otherwise. """ - return super().__eq__(other) and self.amount == other.amount and self.denominating_token_id == other.denominating_token_id \ No newline at end of file + return ( + super().__eq__(other) + and self.amount == other.amount + and self.denominating_token_id == other.denominating_token_id + ) diff --git a/src/hiero_sdk_python/tokens/custom_fractional_fee.py b/src/hiero_sdk_python/tokens/custom_fractional_fee.py index d48e1d125..b432a6f1e 100644 --- a/src/hiero_sdk_python/tokens/custom_fractional_fee.py +++ b/src/hiero_sdk_python/tokens/custom_fractional_fee.py @@ -7,10 +7,13 @@ """ from __future__ import annotations + import typing + from hiero_sdk_python.tokens.custom_fee import CustomFee from hiero_sdk_python.tokens.fee_assessment_method import FeeAssessmentMethod + if typing.TYPE_CHECKING: from hiero_sdk_python.account.account_id import AccountId from hiero_sdk_python.hapi.services import custom_fees_pb2 @@ -31,7 +34,7 @@ def __init__( min_amount: int = 0, max_amount: int = 0, assessment_method: FeeAssessmentMethod = FeeAssessmentMethod.INCLUSIVE, - fee_collector_account_id: typing.Optional["AccountId"] = None, + fee_collector_account_id: AccountId | None = None, all_collectors_are_exempt: bool = False, ): """Initialize a CustomFractionalFee instance. @@ -54,13 +57,12 @@ def __init__( def __str__(self) -> str: """Return a string representation of the CustomFractionalFee.""" - max_len = max(len(k.replace('_', ' ').title()) for k in self.__dict__) + max_len = max(len(k.replace("_", " ").title()) for k in self.__dict__) return f"{self.__class__.__name__}:\n" + "".join( - f" {key.replace('_', ' ').title():<{max_len}} = {value}\n" - for key, value in self.__dict__.items() - ) + f" {key.replace('_', ' ').title():<{max_len}} = {value}\n" for key, value in self.__dict__.items() + ) - def set_numerator(self, numerator: int) -> "CustomFractionalFee": + def set_numerator(self, numerator: int) -> CustomFractionalFee: """Set the numerator for the fractional fee. Args: @@ -72,7 +74,7 @@ def set_numerator(self, numerator: int) -> "CustomFractionalFee": self.numerator = numerator return self - def set_denominator(self, denominator: int) -> "CustomFractionalFee": + def set_denominator(self, denominator: int) -> CustomFractionalFee: """Set the denominator for the fractional fee. Args: @@ -84,7 +86,7 @@ def set_denominator(self, denominator: int) -> "CustomFractionalFee": self.denominator = denominator return self - def set_min_amount(self, min_amount: int) -> "CustomFractionalFee": + def set_min_amount(self, min_amount: int) -> CustomFractionalFee: """Set the minimum fee amount. Args: @@ -96,7 +98,7 @@ def set_min_amount(self, min_amount: int) -> "CustomFractionalFee": self.min_amount = min_amount return self - def set_max_amount(self, max_amount: int) -> "CustomFractionalFee": + def set_max_amount(self, max_amount: int) -> CustomFractionalFee: """Set the maximum fee amount. Args: @@ -108,7 +110,7 @@ def set_max_amount(self, max_amount: int) -> "CustomFractionalFee": self.max_amount = max_amount return self - def set_assessment_method(self, assessment_method: FeeAssessmentMethod) -> "CustomFractionalFee": + def set_assessment_method(self, assessment_method: FeeAssessmentMethod) -> CustomFractionalFee: """Set the assessment method for calculating the fee. Args: @@ -121,7 +123,7 @@ def set_assessment_method(self, assessment_method: FeeAssessmentMethod) -> "Cust self.assessment_method = assessment_method return self - def _to_proto(self) -> "custom_fees_pb2.CustomFee": + def _to_proto(self) -> custom_fees_pb2.CustomFee: """Convert this CustomFractionalFee to its protobuf representation. Returns: @@ -145,7 +147,7 @@ def _to_proto(self) -> "custom_fees_pb2.CustomFee": ) @classmethod - def _from_proto(cls, proto_fee) -> "CustomFractionalFee": + def _from_proto(cls, proto_fee) -> CustomFractionalFee: """Create a CustomFractionalFee object from a protobuf CustomFee message. Args: @@ -155,7 +157,7 @@ def _from_proto(cls, proto_fee) -> "CustomFractionalFee": CustomFractionalFee: A new instance created from the protobuf data. """ # Moved the import here to avoid a blank line issue - from hiero_sdk_python.account.account_id import AccountId + from hiero_sdk_python.account.account_id import AccountId fractional_fee_proto = proto_fee.fractional_fee diff --git a/src/hiero_sdk_python/tokens/custom_royalty_fee.py b/src/hiero_sdk_python/tokens/custom_royalty_fee.py index 8827f7583..9c2e44643 100644 --- a/src/hiero_sdk_python/tokens/custom_royalty_fee.py +++ b/src/hiero_sdk_python/tokens/custom_royalty_fee.py @@ -1,15 +1,18 @@ from __future__ import annotations + import typing + from hiero_sdk_python.tokens.custom_fee import CustomFee + if typing.TYPE_CHECKING: from hiero_sdk_python.account.account_id import AccountId - from hiero_sdk_python.tokens.custom_fixed_fee import CustomFixedFee from hiero_sdk_python.hapi.services import custom_fees_pb2 + from hiero_sdk_python.tokens.custom_fixed_fee import CustomFixedFee """Manages custom royalty fees for Non-Fungible Token (NFT) transactions. -This module defines the CustomRoyaltyFee class, which allows a percentage-based +This module defines the CustomRoyaltyFee class, which allows a percentage-based fee (with an optional fixed fee fallback) to be collected upon NFT transfer. """ @@ -17,8 +20,8 @@ class CustomRoyaltyFee(CustomFee): """Represents a custom royalty fee assessed during NFT transfers. - The royalty fee is defined by a fractional exchange value (numerator/denominator) - and an optional fixed fee that applies if the NFT is exchanged for HBAR + The royalty fee is defined by a fractional exchange value (numerator/denominator) + and an optional fixed fee that applies if the NFT is exchanged for HBAR or a token not specified in the fee schedule. Inherits common properties like fee_collector_account_id from CustomFee. @@ -28,23 +31,23 @@ def __init__( self, numerator: int = 0, denominator: int = 1, - fallback_fee: typing.Optional["CustomFixedFee"] = None, - fee_collector_account_id: typing.Optional["AccountId"] = None, + fallback_fee: CustomFixedFee | None = None, + fee_collector_account_id: AccountId | None = None, all_collectors_are_exempt: bool = False, ): """Initializes the CustomRoyaltyFee. Args: - numerator (int): The numerator of the fraction defining the royalty amount. + numerator (int): The numerator of the fraction defining the royalty amount. Defaults to 0. - denominator (int): The denominator of the fraction defining the royalty + denominator (int): The denominator of the fraction defining the royalty amount. Defaults to 1. - fallback_fee (typing.Optional[CustomFixedFee]): The fixed fee to be - collected if the exchange is not in the token's unit (e.g., if sold + fallback_fee (typing.Optional[CustomFixedFee]): The fixed fee to be + collected if the exchange is not in the token's unit (e.g., if sold for HBAR). Defaults to None. - fee_collector_account_id (typing.Optional[AccountId]): The account ID of + fee_collector_account_id (typing.Optional[AccountId]): The account ID of the fee collector. Inherited from CustomFee. Defaults to None. - all_collectors_are_exempt (bool): If true, all collectors for this fee + all_collectors_are_exempt (bool): If true, all collectors for this fee are exempt from custom fees. Inherited from CustomFee. Defaults to False. """ super().__init__(fee_collector_account_id, all_collectors_are_exempt) @@ -52,7 +55,7 @@ def __init__( self.denominator = denominator self.fallback_fee = fallback_fee - def set_numerator(self, numerator: int) -> "CustomRoyaltyFee": + def set_numerator(self, numerator: int) -> CustomRoyaltyFee: """Sets the numerator of the royalty fraction. Args: @@ -64,7 +67,7 @@ def set_numerator(self, numerator: int) -> "CustomRoyaltyFee": self.numerator = numerator return self - def set_denominator(self, denominator: int) -> "CustomRoyaltyFee": + def set_denominator(self, denominator: int) -> CustomRoyaltyFee: """Sets the denominator of the royalty fraction. Args: @@ -76,11 +79,11 @@ def set_denominator(self, denominator: int) -> "CustomRoyaltyFee": self.denominator = denominator return self - def set_fallback_fee(self, fallback_fee: typing.Optional["CustomFixedFee"]) -> "CustomRoyaltyFee": + def set_fallback_fee(self, fallback_fee: CustomFixedFee | None) -> CustomRoyaltyFee: """Sets the optional fixed fee that applies if the royalty is paid in HBAR. Args: - fallback_fee (typing.Optional[CustomFixedFee]): A CustomFixedFee object + fallback_fee (typing.Optional[CustomFixedFee]): A CustomFixedFee object to use as the fixed fallback fee, or None to remove it. Returns: @@ -89,7 +92,7 @@ def set_fallback_fee(self, fallback_fee: typing.Optional["CustomFixedFee"]) -> " self.fallback_fee = fallback_fee return self - def _to_proto(self) -> "custom_fees_pb2.CustomFee": + def _to_proto(self) -> custom_fees_pb2.CustomFee: """Converts this CustomRoyaltyFee object to its protobuf representation. Builds the RoyaltyFee message and integrates it with the common fields @@ -116,16 +119,16 @@ def _to_proto(self) -> "custom_fees_pb2.CustomFee": fallback_fee=fallback_fee_proto, ), ) - + @classmethod - def _from_proto(cls, proto_fee) -> "CustomRoyaltyFee": + def _from_proto(cls, proto_fee) -> CustomRoyaltyFee: """Creates a CustomRoyaltyFee instance from a CustomFee protobuf message. - Extracts the royalty fee details, optional fallback fee, and common fee + Extracts the royalty fee details, optional fallback fee, and common fee properties from the protobuf message. Args: - proto_fee: The protobuf CustomFee message. It is expected that the + proto_fee: The protobuf CustomFee message. It is expected that the `royalty_fee` field is set. Returns: @@ -133,23 +136,23 @@ def _from_proto(cls, proto_fee) -> "CustomRoyaltyFee": """ from hiero_sdk_python.account.account_id import AccountId from hiero_sdk_python.tokens.custom_fixed_fee import CustomFixedFee - + royalty_fee_proto = proto_fee.royalty_fee - + fallback_fee = None if royalty_fee_proto.HasField("fallback_fee"): fallback_fee = CustomFixedFee._from_fixed_fee_proto(royalty_fee_proto.fallback_fee) - + fee_collector_account_id = None if proto_fee.HasField("fee_collector_account_id"): fee_collector_account_id = AccountId._from_proto(proto_fee.fee_collector_account_id) - + return cls( numerator=royalty_fee_proto.exchange_value_fraction.numerator, denominator=royalty_fee_proto.exchange_value_fraction.denominator, fallback_fee=fallback_fee, fee_collector_account_id=fee_collector_account_id, - all_collectors_are_exempt=proto_fee.all_collectors_are_exempt + all_collectors_are_exempt=proto_fee.all_collectors_are_exempt, ) def __str__(self) -> str: @@ -165,7 +168,7 @@ def __str__(self) -> str: fallback_fee_token_id = self.fallback_fee.denominating_token_id if self.fallback_fee else "None" lines = [ - f"CustomRoyaltyFee:", + "CustomRoyaltyFee:", f" Numerator = {self.numerator}", f" Denominator = {self.denominator}", f" Fallback Fee Amount = {fallback_fee_amount}", @@ -173,4 +176,4 @@ def __str__(self) -> str: f" Fee Collector Account ID = {self.fee_collector_account_id}", f" All Collectors Are Exempt = {self.all_collectors_are_exempt}", ] - return "\n".join(lines) \ No newline at end of file + return "\n".join(lines) diff --git a/src/hiero_sdk_python/tokens/fee_assessment_method.py b/src/hiero_sdk_python/tokens/fee_assessment_method.py index a1e39e66a..5134b4c91 100644 --- a/src/hiero_sdk_python/tokens/fee_assessment_method.py +++ b/src/hiero_sdk_python/tokens/fee_assessment_method.py @@ -1,29 +1,32 @@ +from __future__ import annotations + from enum import Enum + class FeeAssessmentMethod(Enum): """ Fee assessment method for custom token fees. - + Determines whether custom fees are deducted from the transferred amount or charged separately from the payer's account. - + Attributes: INCLUSIVE: Fee is deducted from the transferred amount. The recipient receives the transferred amount minus the fee. Used when the fee should be included in the transfer amount. - + EXCLUSIVE: Fee is charged in addition to the transferred amount. The recipient receives the full transferred amount, and the payer pays the fee on top of that. Used when the fee should be charged separately from the transfer. - + Example: >>> # Using inclusive fee assessment >>> assessment = FeeAssessmentMethod.INCLUSIVE >>> print(f"Fee type: {assessment}") Fee type: FeeAssessmentMethod.INCLUSIVE - - >>> # Using exclusive fee assessment + + >>> # Using exclusive fee assessment >>> assessment = FeeAssessmentMethod.EXCLUSIVE >>> print(f"Fee type: {assessment}") Fee type: FeeAssessmentMethod.EXCLUSIVE diff --git a/src/hiero_sdk_python/tokens/hbar_allowance.py b/src/hiero_sdk_python/tokens/hbar_allowance.py index 12c60473a..96f228116 100644 --- a/src/hiero_sdk_python/tokens/hbar_allowance.py +++ b/src/hiero_sdk_python/tokens/hbar_allowance.py @@ -1,9 +1,10 @@ -""" -HbarAllowance class for handling HBAR allowances. -""" +"""HbarAllowance class for handling HBAR allowances.""" +from __future__ import annotations + +from collections.abc import Callable from dataclasses import dataclass -from typing import Any, Callable, Optional +from typing import Any from hiero_sdk_python.account.account_id import AccountId from hiero_sdk_python.hapi.services.crypto_approve_allowance_pb2 import ( @@ -20,17 +21,17 @@ class HbarAllowance: spender account, and amount. Attributes: - owner_account_id (Optional[AccountId]): The account that owns the HBAR. - spender_account_id (Optional[AccountId]): The account permitted to transfer the HBAR. - amount (int): The amount of HBAR allowed for transfer (in tinybars). + owner_account_id (AccountId, optional): The account that owns the HBAR. + spender_account_id (AccountId, optional): The account permitted to transfer the HBAR. + amount (int, optional): The amount of HBAR allowed for transfer (in tinybars). """ - owner_account_id: Optional[AccountId] = None - spender_account_id: Optional[AccountId] = None + owner_account_id: AccountId | None = None + spender_account_id: AccountId | None = None amount: int = 0 @classmethod - def _from_proto(cls, proto: CryptoAllowanceProto) -> "HbarAllowance": + def _from_proto(cls, proto: CryptoAllowanceProto) -> HbarAllowance: """ Creates a HbarAllowance instance from its protobuf representation. @@ -86,22 +87,11 @@ def __str__(self) -> str: f"amount={self.amount}" f")" ) - elif self.owner_account_id is not None: - return ( - f"HbarAllowance(" - f"owner_account_id={self.owner_account_id}, " - f"amount={self.amount}" - f")" - ) - elif self.spender_account_id is not None: - return ( - f"HbarAllowance(" - f"spender_account_id={self.spender_account_id}, " - f"amount={self.amount}" - f")" - ) - else: - return f"HbarAllowance(amount={self.amount})" + if self.owner_account_id is not None: + return f"HbarAllowance(owner_account_id={self.owner_account_id}, amount={self.amount})" + if self.spender_account_id is not None: + return f"HbarAllowance(spender_account_id={self.spender_account_id}, amount={self.amount})" + return f"HbarAllowance(amount={self.amount})" def __repr__(self) -> str: """ diff --git a/src/hiero_sdk_python/tokens/hbar_transfer.py b/src/hiero_sdk_python/tokens/hbar_transfer.py index c84846ecf..1fa1b9c27 100644 --- a/src/hiero_sdk_python/tokens/hbar_transfer.py +++ b/src/hiero_sdk_python/tokens/hbar_transfer.py @@ -3,7 +3,7 @@ (account, amount, approval) to and from protobuf messages. """ -from typing import Optional +from __future__ import annotations from hiero_sdk_python.account.account_id import AccountId from hiero_sdk_python.hapi.services import basic_types_pb2 @@ -19,8 +19,8 @@ class HbarTransfer: def __init__( self, - account_id: Optional[AccountId] = None, - amount: Optional[int] = None, + account_id: AccountId | None = None, + amount: int | None = None, is_approved: bool = False, ) -> None: """ @@ -31,8 +31,8 @@ def __init__( amount (int): The amount of HBAR to transfer (in tinybars). is_approved (bool, optional): Whether the transfer is approved. Defaults to False. """ - self.account_id: Optional[AccountId] = account_id - self.amount: Optional[int] = amount + self.account_id: AccountId | None = account_id + self.amount: int | None = amount self.is_approved: bool = is_approved def _to_proto(self) -> basic_types_pb2.AccountAmount: @@ -49,7 +49,7 @@ def _to_proto(self) -> basic_types_pb2.AccountAmount: ) @classmethod - def _from_proto(cls, proto: basic_types_pb2.AccountAmount) -> "HbarTransfer": + def _from_proto(cls, proto: basic_types_pb2.AccountAmount) -> HbarTransfer: """ Creates a HbarTransfer from a protobuf representation. @@ -75,13 +75,7 @@ def __str__(self) -> str: Returns: str: A string representation of this HBAR transfer. """ - return ( - "HbarTransfer(" - f"account_id={self.account_id}, " - f"amount={self.amount}, " - f"is_approved={self.is_approved}" - ")" - ) + return f"HbarTransfer(account_id={self.account_id}, amount={self.amount}, is_approved={self.is_approved})" def __repr__(self) -> str: """ diff --git a/src/hiero_sdk_python/tokens/nft_id.py b/src/hiero_sdk_python/tokens/nft_id.py index f3e28e94e..def8b7c70 100644 --- a/src/hiero_sdk_python/tokens/nft_id.py +++ b/src/hiero_sdk_python/tokens/nft_id.py @@ -1,5 +1,5 @@ """ -hiero_sdk_python.tokens.nft_id.py +hiero_sdk_python.tokens.nft_id.py. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Defines NftId, a value object for representing and validating a unique @@ -7,9 +7,11 @@ Protobuf and string serialization. """ +from __future__ import annotations + import re from dataclasses import dataclass, field -from typing import Optional + from hiero_sdk_python.client.client import Client from hiero_sdk_python.hapi.services import basic_types_pb2 from hiero_sdk_python.tokens.token_id import TokenId @@ -27,8 +29,8 @@ class NftId: def __init__( self, - token_id: Optional[TokenId] = None, - serial_number: Optional[int] = None, + token_id: TokenId | None = None, + serial_number: int | None = None, ) -> None: # Validate presence if token_id is None: @@ -48,20 +50,14 @@ def __post_init__(self) -> None: if self.token_id is None: raise TypeError("token_id is required") if not isinstance(self.token_id, TokenId): - raise TypeError( - f"token_id must be of type TokenId, got {type(self.token_id)}" - ) + raise TypeError(f"token_id must be of type TokenId, got {type(self.token_id)}") if not isinstance(self.serial_number, int): - raise TypeError( - f"serial_number must be an integer, got {type(self.serial_number)}" - ) + raise TypeError(f"serial_number must be an integer, got {type(self.serial_number)}") if self.serial_number < 0: raise ValueError("serial_number must be non-negative") @classmethod - def _from_proto( - cls, nft_id_proto: Optional[basic_types_pb2.NftID] = None - ) -> "NftId": + def _from_proto(cls, nft_id_proto: basic_types_pb2.NftID | None = None) -> NftId: """ :param nft_id_proto: the proto NftID object :return: an NftId object @@ -74,26 +70,21 @@ def _from_proto( ) def _to_proto(self) -> basic_types_pb2.NftID: - """ - :return: a protobuf NftID object representation of this NftId object - """ - nft_id_proto = basic_types_pb2.NftID( + """:return: a protobuf NftID object representation of this NftId object""" + return basic_types_pb2.NftID( token_ID=self.token_id._to_proto(), serial_number=self.serial_number, ) - return nft_id_proto @classmethod - def from_string(cls, nft_id_str: str) -> "NftId": + def from_string(cls, nft_id_str: str) -> NftId: """ :param nft_id_str: a string NftId representation :return: returns the NftId parsed from the string input """ parts = re.split(r"/", nft_id_str) if len(parts) != 2: - raise ValueError( - "nft_id_str must be formatted as: shard.realm.number/serial_number" - ) + raise ValueError("nft_id_str must be formatted as: shard.realm.number/serial_number") token_part, serial_part = parts return cls( token_id=TokenId.from_string(token_part), @@ -103,14 +94,12 @@ def from_string(cls, nft_id_str: str) -> "NftId": def to_string_with_checksum(self, client: Client) -> str: """ Returns the string representation of the NftId with - checksum in the format 'shard.realm.num-checksum/serial' + checksum in the format 'shard.realm.num-checksum/serial'. """ return f"{self.token_id.to_string_with_checksum(client)}/{self.serial_number}" def __str__(self) -> str: - """ - :return: a human-readable representation of the NftId - """ + """:return: a human-readable representation of the NftId""" return f"{self.token_id}/{self.serial_number}" def __repr__(self) -> str: diff --git a/src/hiero_sdk_python/tokens/supply_type.py b/src/hiero_sdk_python/tokens/supply_type.py index 64f4f51f2..7cb910744 100644 --- a/src/hiero_sdk_python/tokens/supply_type.py +++ b/src/hiero_sdk_python/tokens/supply_type.py @@ -1,13 +1,16 @@ """ -hiero_sdk_python.tokens.supply_type.py +hiero_sdk_python.tokens.supply_type.py. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Defines SupplyType, an enumeration of possible token supply behaviors for Non-Fungible Tokens (NFTs). """ +from __future__ import annotations + from enum import Enum + class SupplyType(Enum): """ Enumeration of NFT supply models: @@ -15,5 +18,6 @@ class SupplyType(Enum): - INFINITE: Tokens can be minted without limit. - FINITE: Tokens have a fixed maximum supply. """ + INFINITE = 0 - FINITE = 1 + FINITE = 1 diff --git a/src/hiero_sdk_python/tokens/token_airdrop_claim.py b/src/hiero_sdk_python/tokens/token_airdrop_claim.py index 554e4baa8..cbfd93c61 100644 --- a/src/hiero_sdk_python/tokens/token_airdrop_claim.py +++ b/src/hiero_sdk_python/tokens/token_airdrop_claim.py @@ -7,20 +7,22 @@ - No duplicate PendingAirdropId entries """ -from typing import Optional, List, Any +from __future__ import annotations + +from typing import Any -from hiero_sdk_python.transaction.transaction import Transaction -from hiero_sdk_python.tokens.token_airdrop_pending_id import PendingAirdropId -from hiero_sdk_python.hapi.services.token_claim_airdrop_pb2 import ( # pylint: disable=no-name-in-module - TokenClaimAirdropTransactionBody, -) -from hiero_sdk_python.hapi.services import transaction_pb2 from hiero_sdk_python.channels import _Channel from hiero_sdk_python.executable import _Method +from hiero_sdk_python.hapi.services import transaction_pb2 +from hiero_sdk_python.hapi.services.token_claim_airdrop_pb2 import ( # pylint: disable=no-name-in-module + TokenClaimAirdropTransactionBody, +) +from hiero_sdk_python.tokens.token_airdrop_pending_id import PendingAirdropId +from hiero_sdk_python.transaction.transaction import Transaction class TokenClaimAirdropTransaction(Transaction): - """Claim 1–10 unique pending airdrops via TokenClaimAirdropTransactionBody. + """Claim 1-10 unique pending airdrops via TokenClaimAirdropTransactionBody. TokenClaimAirdropTransaction is only required if the receiver account has required signing to claim airdrops. This transaction MUST be signed by the receiver for each PendingAirdropId to claim. """ @@ -28,19 +30,16 @@ class TokenClaimAirdropTransaction(Transaction): MAX_IDS: int = 10 MIN_IDS: int = 1 - def __init__( - self, - pending_airdrop_ids: Optional[List[PendingAirdropId]] = None - ) -> None: + def __init__(self, pending_airdrop_ids: list[PendingAirdropId] | None = None) -> None: """Initialize the TokenClaimAirdropTransaction. Args: pending_airdrop_ids: Optional list of pending airdrop IDs. """ super().__init__() - self._pending_airdrop_ids: List[PendingAirdropId] = list(pending_airdrop_ids or []) + self._pending_airdrop_ids: list[PendingAirdropId] = list(pending_airdrop_ids or []) - def _validate_all(self, ids: List[PendingAirdropId]) -> None: + def _validate_all(self, ids: list[PendingAirdropId]) -> None: """Validate a candidate list of pending airdrop IDs. Ensures the list contains no more than ``MAX_IDS`` entries and has no @@ -76,10 +75,7 @@ def _validate_final(self) -> None: raise ValueError(f"You must claim at least {self.MIN_IDS} airdrop (got {n}).") self._validate_all(self._pending_airdrop_ids) - def add_pending_airdrop_id( - self, - pending_airdrop_id: PendingAirdropId - ) -> "TokenClaimAirdropTransaction": + def add_pending_airdrop_id(self, pending_airdrop_id: PendingAirdropId) -> TokenClaimAirdropTransaction: """Append a single PendingAirdropId. Args: @@ -90,10 +86,7 @@ def add_pending_airdrop_id( """ return self.add_pending_airdrop_ids([pending_airdrop_id]) - def add_pending_airdrop_ids( - self, - pending_airdrop_ids: List[PendingAirdropId] - ) -> "TokenClaimAirdropTransaction": + def add_pending_airdrop_ids(self, pending_airdrop_ids: list[PendingAirdropId]) -> TokenClaimAirdropTransaction: """Add many pending airdrop IDs. Args: @@ -111,7 +104,7 @@ def add_pending_airdrop_ids( self._pending_airdrop_ids = candidate return self - def _pending_airdrop_ids_to_proto(self) -> List[Any]: + def _pending_airdrop_ids_to_proto(self) -> list[Any]: """Convert the current list of PendingAirdropId to protobuf messages. Returns: @@ -123,10 +116,7 @@ def _pending_airdrop_ids_to_proto(self) -> List[Any]: ] @classmethod - def _from_proto( - cls, - proto: TokenClaimAirdropTransactionBody - ) -> "TokenClaimAirdropTransaction": + def _from_proto(cls, proto: TokenClaimAirdropTransactionBody) -> TokenClaimAirdropTransaction: """Construct a TokenClaimAirdropTransaction from a TokenClaimAirdropTransactionBody. Args: @@ -147,11 +137,11 @@ def _from_proto( return inst - def build_transaction_body(self) -> transaction_pb2.TransactionBody: # pylint: disable=no-member + def build_transaction_body(self) -> transaction_pb2.TransactionBody: # pylint: disable=no-member """Build the TransactionBody for this claim. Returns: - transaction_body_pb2.TransactionBody: + transaction_body_pb2.TransactionBody: A TransactionBody with TokenClaimAirdrop populated. Raises: @@ -162,7 +152,7 @@ def build_transaction_body(self) -> transaction_pb2.TransactionBody: # pylint: d pending_airdrop_claim_body = TokenClaimAirdropTransactionBody( pending_airdrops=self._pending_airdrop_ids_to_proto() ) - transaction_body: transaction_pb2.TransactionBody = self.build_base_transaction_body() # pylint: disable=no-member + transaction_body: transaction_pb2.TransactionBody = self.build_base_transaction_body() # pylint: disable=no-member transaction_body.tokenClaimAirdrop.CopyFrom(pending_airdrop_claim_body) return transaction_body @@ -176,35 +166,22 @@ def _get_method(self, channel: _Channel) -> _Method: Returns: _Method: Wraps the gRPC method for TokenClaimAirdrop. """ - return _Method( - transaction_func=channel.token.claimAirdrop, - query_func=None - ) + return _Method(transaction_func=channel.token.claimAirdrop, query_func=None) - def get_pending_airdrop_ids(self) -> List[PendingAirdropId]: - """Returns a copy of the list of pending airdrop IDs currently stored inside TokenClaimAirdropTransaction object""" + def get_pending_airdrop_ids(self) -> list[PendingAirdropId]: + """Returns a copy of the list of pending airdrop IDs currently stored inside TokenClaimAirdropTransaction object.""" return list(self._pending_airdrop_ids) def __repr__(self) -> str: """Developer-friendly representation with class name and pending IDs.""" - return ( - f"{self.__class__.__name__}(" - f"pending_airdrop_ids={self._pending_airdrop_ids!r})" - ) + return f"{self.__class__.__name__}(pending_airdrop_ids={self._pending_airdrop_ids!r})" def __str__(self) -> str: - """Human-readable summary showing each pending airdrop on its own line""" + """Human-readable summary showing each pending airdrop on its own line.""" if not self._pending_airdrop_ids: return "No pending airdrops in this transaction." - lines = [ - f" → {aid}" - for aid in self._pending_airdrop_ids - ] + lines = [f" → {aid}" for aid in self._pending_airdrop_ids] ids_block = "\n".join(lines) - summary = ( - f"Pending Airdrops to claim:\n" - f"{ids_block}\n" - ) - return summary \ No newline at end of file + return f"Pending Airdrops to claim:\n{ids_block}\n" diff --git a/src/hiero_sdk_python/tokens/token_airdrop_pending_id.py b/src/hiero_sdk_python/tokens/token_airdrop_pending_id.py index 2798bda7b..1578c3e30 100644 --- a/src/hiero_sdk_python/tokens/token_airdrop_pending_id.py +++ b/src/hiero_sdk_python/tokens/token_airdrop_pending_id.py @@ -1,7 +1,6 @@ """ PendingAirdropId module. - Defines the PendingAirdropId class used to uniquely identify a specific pending token airdrop. @@ -13,26 +12,32 @@ This class supports safe construction, validation, and conversion to/from protobuf for use within the Hiero SDK. """ -from typing import Optional + +from __future__ import annotations + from hiero_sdk_python.account.account_id import AccountId from hiero_sdk_python.hapi.services import basic_types_pb2 from hiero_sdk_python.tokens.nft_id import NftId from hiero_sdk_python.tokens.token_id import TokenId + class PendingAirdropId: """ Represents a pending airdrop id, containing sender_id and receiver_id of an airdrop, along with the specific token being airdropped, which can be either a fungible_token_id (TokenId) or a nft_id (NftId). """ - def __init__(self, sender_id: AccountId, receiver_id: AccountId, token_id: Optional[TokenId]=None, nft_id: Optional[NftId]=None) -> None: + + def __init__( + self, sender_id: AccountId, receiver_id: AccountId, token_id: TokenId | None = None, nft_id: NftId | None = None + ) -> None: """ Initializes a new PendingAirdropId instance. Args: sender_id (AccountId): The ID of the account initiating the airdrop. receiver_id (AccountId): The account ID of the intended recipient of the airdrop. - token_id (Optional[TokenId]): The ID of the fungible token being airdropped. - nft_id (Optional[NftId]): The ID of the non-fungible token being airdropped. + token_id (TokenId. optional): The ID of the fungible token being airdropped. + nft_id (NftId, optional): The ID of the non-fungible token being airdropped. """ if (token_id is None) == (nft_id is None): raise ValueError("Exactly one of 'token_id' or 'nft_id' must be required.") @@ -43,18 +48,17 @@ def __init__(self, sender_id: AccountId, receiver_id: AccountId, token_id: Optio self.nft_id = nft_id @classmethod - def _from_proto(cls, proto: basic_types_pb2.PendingAirdropId) -> "PendingAirdropId": + def _from_proto(cls, proto: basic_types_pb2.PendingAirdropId) -> PendingAirdropId: """ Create a PendingAirdropId instance from protobuf message. Args: - proto (basic_types_pb2.PendingAirdropId): + proto (basic_types_pb2.PendingAirdropId): The protobuf message containing PendingAirdropId information. Returns: PendingAirdropId: A new PendingAirdropId instance populated with data from the protobuf message. """ - fungible_token_type = None if proto.HasField("fungible_token_type"): fungible_token_type = TokenId._from_proto(proto.fungible_token_type) @@ -67,7 +71,7 @@ def _from_proto(cls, proto: basic_types_pb2.PendingAirdropId) -> "PendingAirdrop sender_id=AccountId._from_proto(proto.sender_id), receiver_id=AccountId._from_proto(proto.receiver_id), token_id=fungible_token_type, - nft_id=non_fungible_token + nft_id=non_fungible_token, ) def _to_proto(self) -> basic_types_pb2.PendingAirdropId: @@ -89,17 +93,13 @@ def _to_proto(self) -> basic_types_pb2.PendingAirdropId: sender_id=self.sender_id._to_proto(), receiver_id=self.receiver_id._to_proto(), fungible_token_type=fungible_token_type, - non_fungible_token=non_fungible_token + non_fungible_token=non_fungible_token, ) def __str__(self) -> str: - """Readable summary""" + """Readable summary.""" if self.nft_id: - return ( - f"PendingAirdropId(" - f"sender_id={self.sender_id}, receiver_id={self.receiver_id}, " - f"nft_id={self.nft_id})" - ) + return f"PendingAirdropId(sender_id={self.sender_id}, receiver_id={self.receiver_id}, nft_id={self.nft_id})" return ( f"PendingAirdropId(" f"sender_id={self.sender_id}, receiver_id={self.receiver_id}, " @@ -120,14 +120,12 @@ def __eq__(self, other: object) -> bool: if not isinstance(other, PendingAirdropId): return NotImplemented return ( - self.sender_id == other.sender_id and - self.receiver_id == other.receiver_id and - self.token_id == other.token_id and - self.nft_id == other.nft_id + self.sender_id == other.sender_id + and self.receiver_id == other.receiver_id + and self.token_id == other.token_id + and self.nft_id == other.nft_id ) def __hash__(self) -> int: - """ - Returns a hash value so this object can be used in sets and as dictionary keys. - """ - return hash((self.sender_id, self.receiver_id, self.token_id, self.nft_id)) \ No newline at end of file + """Returns a hash value so this object can be used in sets and as dictionary keys.""" + return hash((self.sender_id, self.receiver_id, self.token_id, self.nft_id)) diff --git a/src/hiero_sdk_python/tokens/token_airdrop_pending_record.py b/src/hiero_sdk_python/tokens/token_airdrop_pending_record.py index 280338cf7..fdc847695 100644 --- a/src/hiero_sdk_python/tokens/token_airdrop_pending_record.py +++ b/src/hiero_sdk_python/tokens/token_airdrop_pending_record.py @@ -1,11 +1,12 @@ +from __future__ import annotations + from hiero_sdk_python.hapi.services import basic_types_pb2, transaction_record_pb2 from hiero_sdk_python.tokens.token_airdrop_pending_id import PendingAirdropId class PendingAirdropRecord: - """ - Represents a record of a pending airdrop, retrieved from a transaction. - """ + """Represents a record of a pending airdrop, retrieved from a transaction.""" + def __init__(self, pending_airdrop_id: PendingAirdropId, amount: int) -> None: """ Initializes a new PendingAirdropRecord. @@ -16,9 +17,9 @@ def __init__(self, pending_airdrop_id: PendingAirdropId, amount: int) -> None: """ self.pending_airdrop_id = pending_airdrop_id self.amount = amount - + @classmethod - def _from_proto(cls, proto: transaction_record_pb2.PendingAirdropRecord) -> "PendingAirdropRecord": + def _from_proto(cls, proto: transaction_record_pb2.PendingAirdropRecord) -> PendingAirdropRecord: """ Creates a PendingAirdropRecord instance from a protobuf message. @@ -30,24 +31,22 @@ def _from_proto(cls, proto: transaction_record_pb2.PendingAirdropRecord) -> "Pen """ return cls( pending_airdrop_id=PendingAirdropId._from_proto(proto.pending_airdrop_id), - amount=proto.pending_airdrop_value.amount + amount=proto.pending_airdrop_value.amount, ) - + def _to_proto(self) -> transaction_record_pb2.PendingAirdropRecord: """ Converts the PendingAirdropRecord instance to its protobuf message. - Returns: + Returns: transaction_record_pb2.PendingAirdropRecord: The protobuf representation of the PendingAirdropRecord. """ return transaction_record_pb2.PendingAirdropRecord( pending_airdrop_id=self.pending_airdrop_id._to_proto(), - pending_airdrop_value=basic_types_pb2.PendingAirdropValue(amount=self.amount) + pending_airdrop_value=basic_types_pb2.PendingAirdropValue(amount=self.amount), ) - + def __str__(self): - """ - Returns a string representation of this PendingAirdropRecord instance. - """ - return f"PendingAirdropRecord(pending_airdrop_id={self.pending_airdrop_id}, amount={self.amount})" \ No newline at end of file + """Returns a string representation of this PendingAirdropRecord instance.""" + return f"PendingAirdropRecord(pending_airdrop_id={self.pending_airdrop_id}, amount={self.amount})" diff --git a/src/hiero_sdk_python/tokens/token_airdrop_transaction.py b/src/hiero_sdk_python/tokens/token_airdrop_transaction.py index 55bbde3db..da1e74360 100644 --- a/src/hiero_sdk_python/tokens/token_airdrop_transaction.py +++ b/src/hiero_sdk_python/tokens/token_airdrop_transaction.py @@ -1,22 +1,24 @@ """ -hiero_sdk_python.tokens.token_airdrop_transaction.py +hiero_sdk_python.tokens.token_airdrop_transaction.py. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Provides TokenAirdropTransaction, a concrete transaction class for distributing both fungible tokens and NFTs to multiple accounts on the Hedera network via Hedera Token Service (HTS) airdrop functionality. """ -from typing import Optional, List + +from __future__ import annotations from hiero_sdk_python.channels import _Channel from hiero_sdk_python.executable import _Method -from hiero_sdk_python.tokens.token_nft_transfer import TokenNftTransfer -from hiero_sdk_python.tokens.token_transfer import TokenTransfer -from hiero_sdk_python.tokens.abstract_token_transfer_transaction import AbstractTokenTransferTransaction from hiero_sdk_python.hapi.services import token_airdrop_pb2, transaction_pb2 from hiero_sdk_python.hapi.services.schedulable_transaction_body_pb2 import ( SchedulableTransactionBody, ) +from hiero_sdk_python.tokens.abstract_token_transfer_transaction import AbstractTokenTransferTransaction +from hiero_sdk_python.tokens.token_nft_transfer import TokenNftTransfer +from hiero_sdk_python.tokens.token_transfer import TokenTransfer + class TokenAirdropTransaction(AbstractTokenTransferTransaction["TokenAirdropTransaction"]): """ @@ -25,16 +27,15 @@ class TokenAirdropTransaction(AbstractTokenTransferTransaction["TokenAirdropTran The TokenAirdropTransaction allows users to transfer tokens to multiple accounts, handling both fungible tokens and NFTs. """ + def __init__( - self, - token_transfers: Optional[List[TokenTransfer]] = None, - nft_transfers: Optional[List[TokenNftTransfer]] = None - ) -> None: + self, token_transfers: list[TokenTransfer] | None = None, nft_transfers: list[TokenNftTransfer] | None = None + ) -> None: """ Initializes a new TokenAirdropTransaction instance. Args: - token_transfers (list[TokenTransfer], optional): + token_transfers (list[TokenTransfer], optional): Initial list of fungible token transfers. nft_transfers (list[TokenNftTransfer], optional): Initial list of NFT transfers. """ @@ -43,57 +44,48 @@ def __init__( self._init_token_transfers(token_transfers) if nft_transfers: self._init_nft_transfers(nft_transfers) - + def _build_proto_body(self) -> token_airdrop_pb2.TokenAirdropTransactionBody: """ Returns the protobuf body for the token airdrop transaction. - + Returns: TokenAirdropTransactionBody: The protobuf body for this transaction. - + Raises: ValueError: If transfer list is invalid. """ token_transfers = self.build_token_transfers() - if (len(token_transfers) < 1 or len(token_transfers) > 10): - raise ValueError( - "Airdrop transfer list must contain minimum 1 and maximum 10 transfers." - ) + if len(token_transfers) < 1 or len(token_transfers) > 10: + raise ValueError("Airdrop transfer list must contain minimum 1 and maximum 10 transfers.") - return token_airdrop_pb2.TokenAirdropTransactionBody( - token_transfers=token_transfers - ) + return token_airdrop_pb2.TokenAirdropTransactionBody(token_transfers=token_transfers) @classmethod - def _from_proto(cls, proto: token_airdrop_pb2.TokenAirdropTransactionBody) -> "TokenAirdropTransaction": + def _from_proto(cls, proto: token_airdrop_pb2.TokenAirdropTransactionBody) -> TokenAirdropTransaction: """ Construct an instance of TokenAirdropTransaction from protobuf TokenAirdropTransactionBody. Args: proto (TokenAirdropTransactionBody): The protobuf TokenAirdropTransctionBody """ - token_transfers: List[TokenTransfer] = [] - nft_transfers: List[TokenNftTransfer] = [] + token_transfers: list[TokenTransfer] = [] + nft_transfers: list[TokenNftTransfer] = [] for transfer in proto.token_transfers: if transfer.transfers: - for token_transfer in TokenTransfer._from_proto(transfer): - token_transfers.append(token_transfer) + token_transfers.extend(TokenTransfer._from_proto(transfer)) elif transfer.nftTransfers: - for nft_transfer in TokenNftTransfer._from_proto(transfer): - nft_transfers.append(nft_transfer) + nft_transfers.extend(TokenNftTransfer._from_proto(transfer)) - return cls( - token_transfers=token_transfers, - nft_transfers=nft_transfers - ) + return cls(token_transfers=token_transfers, nft_transfers=nft_transfers) - def build_transaction_body(self) -> transaction_pb2.TransactionBody : + def build_transaction_body(self) -> transaction_pb2.TransactionBody: """ Builds and returns the protobuf transaction body for token airdrop. - + Returns: TransactionBody: The protobuf transaction body containing the token airdrop details. """ @@ -101,7 +93,7 @@ def build_transaction_body(self) -> transaction_pb2.TransactionBody : transaction_body = self.build_base_transaction_body() transaction_body.tokenAirdrop.CopyFrom(token_airdrop_body) return transaction_body - + def build_scheduled_body(self) -> SchedulableTransactionBody: """ Builds the scheduled transaction body for this token airdrop transaction. @@ -119,7 +111,4 @@ def _get_method(self, channel: _Channel) -> _Method: if token_service is None: raise ValueError("Token service not available on channel") - return _Method( - transaction_func=token_service.airdropTokens, - query_func=None - ) + return _Method(transaction_func=token_service.airdropTokens, query_func=None) diff --git a/src/hiero_sdk_python/tokens/token_airdrop_transaction_cancel.py b/src/hiero_sdk_python/tokens/token_airdrop_transaction_cancel.py index 2a28d37a7..1e8e95429 100644 --- a/src/hiero_sdk_python/tokens/token_airdrop_transaction_cancel.py +++ b/src/hiero_sdk_python/tokens/token_airdrop_transaction_cancel.py @@ -1,4 +1,5 @@ -from typing import Optional +from __future__ import annotations + from hiero_sdk_python.executable import _Method from hiero_sdk_python.hapi.services import basic_types_pb2, token_cancel_airdrop_pb2 from hiero_sdk_python.hapi.services.schedulable_transaction_body_pb2 import ( @@ -7,23 +8,25 @@ from hiero_sdk_python.tokens.token_airdrop_pending_id import PendingAirdropId from hiero_sdk_python.transaction.transaction import Transaction + class TokenCancelAirdropTransaction(Transaction): """ Represents a transaction to cancel token airdrops on the Hedera network. This transaction allows users to cancel one or more airdrops for both fungible tokens and NFTs. """ - def __init__(self, pending_airdrops: Optional[list[PendingAirdropId]] = None) -> None: + + def __init__(self, pending_airdrops: list[PendingAirdropId] | None = None) -> None: """ Initializes a new TokenCancelAirdropTransaction instance. Args: - pending_airdrops (Optional[list[PendingAirdropId]]): An optional list of pending airdrop IDs. + pending_airdrops (list[PendingAirdropId], optional): An optional list of pending airdrop IDs. """ super().__init__() self.pending_airdrops: list[PendingAirdropId] = pending_airdrops or [] - def set_pending_airdrops(self, pending_airdrops: list[PendingAirdropId]) -> "TokenCancelAirdropTransaction": + def set_pending_airdrops(self, pending_airdrops: list[PendingAirdropId]) -> TokenCancelAirdropTransaction: """ Sets the list of pending airdrops IDs. @@ -36,7 +39,7 @@ def set_pending_airdrops(self, pending_airdrops: list[PendingAirdropId]) -> "Tok self.pending_airdrops = pending_airdrops return self - def add_pending_airdrop(self, pending_airdrop: PendingAirdropId) -> "TokenCancelAirdropTransaction": + def add_pending_airdrop(self, pending_airdrop: PendingAirdropId) -> TokenCancelAirdropTransaction: """ Adds a single pending airdrop ID to the pending_airdrops list. @@ -48,8 +51,8 @@ def add_pending_airdrop(self, pending_airdrop: PendingAirdropId) -> "TokenCancel """ self.pending_airdrops.append(pending_airdrop) return self - - def clear_pending_airdrops(self) -> "TokenCancelAirdropTransaction": + + def clear_pending_airdrops(self) -> TokenCancelAirdropTransaction: """ Clears all pending airdrop IDs from the list. @@ -58,38 +61,33 @@ def clear_pending_airdrops(self) -> "TokenCancelAirdropTransaction": """ self.pending_airdrops.clear() return self - + def _build_proto_body(self): """ Returns the protobuf body for the token cancel airdrop transaction. - + Returns: TokenCancelAirdropTransactionBody: The protobuf body for this transaction. - + Raises: ValueError: If pending airdrops list is invalid. """ - pending_airdrops_proto: list[basic_types_pb2.PendingAirdropId] = [] + pending_airdrops_proto: list[basic_types_pb2.PendingAirdropId] = [ + pending_airdrop._to_proto() for pending_airdrop in self.pending_airdrops + ] - for pending_airdrop in self.pending_airdrops: - pending_airdrops_proto.append(pending_airdrop._to_proto()) + if len(pending_airdrops_proto) < 1 or len(pending_airdrops_proto) > 10: + raise ValueError("Pending airdrops list must contain mininum 1 and maximum 10 pendingAirdrop.") - if (len(pending_airdrops_proto) < 1 or len(pending_airdrops_proto) > 10): - raise ValueError("Pending airdrops list must contain mininum 1 and maximum 10 pendingAirdrop.") + return token_cancel_airdrop_pb2.TokenCancelAirdropTransactionBody(pending_airdrops=pending_airdrops_proto) - return token_cancel_airdrop_pb2.TokenCancelAirdropTransactionBody( - pending_airdrops=pending_airdrops_proto - ) - def build_transaction_body(self): - """ - Builds and returns the protobuf transaction body for canceling a token airdrop. - """ + """Builds and returns the protobuf transaction body for canceling a token airdrop.""" token_airdrop_cancel_body = self._build_proto_body() transaction_body = self.build_base_transaction_body() transaction_body.tokenCancelAirdrop.CopyFrom(token_airdrop_cancel_body) return transaction_body - + def build_scheduled_body(self) -> SchedulableTransactionBody: """ Builds the scheduled transaction body for this token cancel airdrop transaction. @@ -101,9 +99,6 @@ def build_scheduled_body(self) -> SchedulableTransactionBody: schedulable_body = self.build_base_scheduled_body() schedulable_body.tokenCancelAirdrop.CopyFrom(token_airdrop_cancel_body) return schedulable_body - + def _get_method(self, channel): - return _Method( - transaction_func=channel.token.cancelAirdrop, - query_func=None - ) \ No newline at end of file + return _Method(transaction_func=channel.token.cancelAirdrop, query_func=None) diff --git a/src/hiero_sdk_python/tokens/token_allowance.py b/src/hiero_sdk_python/tokens/token_allowance.py index 4bbe502c0..62cb6db23 100644 --- a/src/hiero_sdk_python/tokens/token_allowance.py +++ b/src/hiero_sdk_python/tokens/token_allowance.py @@ -1,9 +1,10 @@ -""" -TokenAllowance class for handling fungible token allowances. -""" +"""TokenAllowance class for handling fungible token allowances.""" +from __future__ import annotations + +from collections.abc import Callable from dataclasses import dataclass -from typing import Any, Callable, Optional +from typing import Any from hiero_sdk_python.account.account_id import AccountId from hiero_sdk_python.hapi.services.crypto_approve_allowance_pb2 import ( @@ -21,19 +22,19 @@ class TokenAllowance: owner account, spender account, and amount. Attributes: - token_id (Optional[TokenId]): The ID of the fungible token. - owner_account_id (Optional[AccountId]): The account that owns the tokens. - spender_account_id (Optional[AccountId]): The account permitted to transfer the tokens. - amount (int): The amount of tokens allowed for transfer. + token_id (TokenId, optional): The ID of the fungible token. + owner_account_id (AccountId, optional): The account that owns the tokens. + spender_account_id (AccountId, optional): The account permitted to transfer the tokens. + amount (int, optional): The amount of tokens allowed for transfer. """ - token_id: Optional[TokenId] = None - owner_account_id: Optional[AccountId] = None - spender_account_id: Optional[AccountId] = None + token_id: TokenId | None = None + owner_account_id: AccountId | None = None + spender_account_id: AccountId | None = None amount: int = 0 @classmethod - def _from_proto(cls, proto: TokenAllowanceProto) -> "TokenAllowance": + def _from_proto(cls, proto: TokenAllowanceProto) -> TokenAllowance: """ Creates a TokenAllowance instance from its protobuf representation. diff --git a/src/hiero_sdk_python/tokens/token_associate_transaction.py b/src/hiero_sdk_python/tokens/token_associate_transaction.py index 26c6414c9..13a14d328 100644 --- a/src/hiero_sdk_python/tokens/token_associate_transaction.py +++ b/src/hiero_sdk_python/tokens/token_associate_transaction.py @@ -1,23 +1,23 @@ """ -hiero_sdk_python.tokens.token_associate_transaction.py +hiero_sdk_python.tokens.token_associate_transaction.py. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Provides TokenAssociateTransaction, a subclass of Transaction for associating tokens with accounts on the Hedera network using the Hedera Token Service (HTS) API. """ -from typing import Optional, List, Union +from __future__ import annotations from hiero_sdk_python.account.account_id import AccountId from hiero_sdk_python.channels import _Channel from hiero_sdk_python.executable import _Method from hiero_sdk_python.hapi.services import token_associate_pb2, transaction_pb2 from hiero_sdk_python.hapi.services.schedulable_transaction_body_pb2 import SchedulableTransactionBody - from hiero_sdk_python.tokens.token_id import TokenId from hiero_sdk_python.transaction.transaction import Transaction -TokenIdLike = Union[TokenId, str] + +TokenIdLike = TokenId | str class TokenAssociateTransaction(Transaction): @@ -31,28 +31,26 @@ class TokenAssociateTransaction(Transaction): to build and execute a token association transaction. """ - def __init__( - self, - account_id: Optional[AccountId] = None, - token_ids: Optional[List[TokenId]] = None - ) -> None: + def __init__(self, account_id: AccountId | None = None, token_ids: list[TokenId] | None = None) -> None: """ Initializes a new TokenAssociateTransaction instance with optional keyword arguments. Args: account_id (AccountId, optional): The account to associate tokens with. - token_ids (list of TokenId, optional): The tokens to associate with the account. + token_ids (list[TokenId], optional): The tokens to associate with the account. """ super().__init__() - self.account_id: Optional[AccountId] = account_id - self.token_ids: List[TokenId] = list(token_ids) if token_ids is not None else [] + self.account_id: AccountId | None = account_id + self.token_ids: list[TokenId] = list(token_ids) if token_ids is not None else [] self._default_transaction_fee: int = 500_000_000 - def set_account_id(self, account_id: AccountId) -> "TokenAssociateTransaction": + def set_account_id(self, account_id: AccountId) -> TokenAssociateTransaction: """ Sets the account ID for the token association transaction. + Args: account_id (AccountId): The account ID to associate tokens with. + Returns: TokenAssociateTransaction: The current instance for method chaining. """ @@ -60,13 +58,13 @@ def set_account_id(self, account_id: AccountId) -> "TokenAssociateTransaction": self.account_id = account_id return self - def add_token_id(self, token_id: TokenId) -> "TokenAssociateTransaction": + def add_token_id(self, token_id: TokenId) -> TokenAssociateTransaction: """Add a token ID to the association list.""" self._require_not_frozen() self.token_ids.append(token_id) return self - def set_token_ids(self, token_ids: List[TokenId]) -> "TokenAssociateTransaction": + def set_token_ids(self, token_ids: list[TokenId]) -> TokenAssociateTransaction: """ Sets the list of token IDs for the token association transaction. @@ -80,16 +78,14 @@ def set_token_ids(self, token_ids: List[TokenId]) -> "TokenAssociateTransaction" TokenAssociateTransaction. """ self._require_not_frozen() - tokens_to_add: List[TokenId] = [] + tokens_to_add: list[TokenId] = [] for token_id in token_ids: if isinstance(token_id, TokenId): tokens_to_add.append(token_id) elif isinstance(token_id, str): tokens_to_add.append(TokenId.from_string(token_id)) else: - raise TypeError( - f"Invalid token_id type: expected TokenId or str, got {type(token_id).__name__}" - ) + raise TypeError(f"Invalid token_id type: expected TokenId or str, got {type(token_id).__name__}") self.token_ids = tokens_to_add return self @@ -108,19 +104,14 @@ def _build_proto_body(self) -> token_associate_pb2.TokenAssociateTransactionBody raise ValueError("Account ID and token IDs must be set.") return token_associate_pb2.TokenAssociateTransactionBody( - account=self.account_id._to_proto(), - tokens=[token_id._to_proto() for token_id in self.token_ids] + account=self.account_id._to_proto(), tokens=[token_id._to_proto() for token_id in self.token_ids] ) @classmethod - def _from_proto( - cls, body: token_associate_pb2.TokenAssociateTransactionBody - ) -> "TokenAssociateTransaction": - """ - Construct a TokenAssociateTransaction from its protobuf. - """ + def _from_proto(cls, body: token_associate_pb2.TokenAssociateTransactionBody) -> TokenAssociateTransaction: + """Construct a TokenAssociateTransaction from its protobuf.""" account_id = AccountId._from_proto(body.account) - token_ids: List[TokenId] = [] + token_ids: list[TokenId] = [] for proto_token in body.tokens: token_id = TokenId._from_proto(proto_token) @@ -170,7 +161,4 @@ def _validate_checksums(self, client) -> None: token_id.validate_checksum(client) def _get_method(self, channel: _Channel) -> _Method: - return _Method( - transaction_func=channel.token.associateTokens, - query_func=None - ) + return _Method(transaction_func=channel.token.associateTokens, query_func=None) diff --git a/src/hiero_sdk_python/tokens/token_association.py b/src/hiero_sdk_python/tokens/token_association.py index 5c8848049..1b233f6ac 100644 --- a/src/hiero_sdk_python/tokens/token_association.py +++ b/src/hiero_sdk_python/tokens/token_association.py @@ -1,4 +1,5 @@ """Dataclass for automatic token associations in Hedera transaction records.""" + from __future__ import annotations from dataclasses import dataclass @@ -30,16 +31,8 @@ class TokenAssociation: def _from_proto(cls, proto: TokenAssociationProto) -> TokenAssociation: """Create a TokenAssociation instance from the protobuf message.""" return cls( - token_id=( - TokenId._from_proto(proto.token_id) - if proto.HasField("token_id") - else None - ), - account_id=( - AccountId._from_proto(proto.account_id) - if proto.HasField("account_id") - else None - ), + token_id=(TokenId._from_proto(proto.token_id) if proto.HasField("token_id") else None), + account_id=(AccountId._from_proto(proto.account_id) if proto.HasField("account_id") else None), ) def _to_proto(self) -> TokenAssociationProto: @@ -64,7 +57,7 @@ def from_bytes(cls, data: bytes) -> TokenAssociation: proto = TokenAssociationProto() proto.ParseFromString(data) return cls._from_proto(proto) - + def __repr__(self) -> str: """Returns an unambiguous string representation for debugging.""" return f"TokenAssociation(token_id={self.token_id!r}, account_id={self.account_id!r})" @@ -72,4 +65,3 @@ def __repr__(self) -> str: def __str__(self) -> str: """Returns a human-readable string representation.""" return self.__repr__() - \ No newline at end of file diff --git a/src/hiero_sdk_python/tokens/token_burn_transaction.py b/src/hiero_sdk_python/tokens/token_burn_transaction.py index 94f60359f..362d6e14d 100644 --- a/src/hiero_sdk_python/tokens/token_burn_transaction.py +++ b/src/hiero_sdk_python/tokens/token_burn_transaction.py @@ -1,37 +1,37 @@ """ -hiero_sdk_python.tokens.token_burn_transaction.py +hiero_sdk_python.tokens.token_burn_transaction.py. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Provides TokenBurnTransaction, a subclass of Transaction for burning fungible and non-fungible tokens on the Hedera network using the Hedera Token Service (HTS) API. """ -from typing import List, Optional -from hiero_sdk_python.hapi.services.token_burn_pb2 import TokenBurnTransactionBody -from hiero_sdk_python.hapi.services import transaction_pb2, token_burn_pb2 +from __future__ import annotations + +from hiero_sdk_python.channels import _Channel +from hiero_sdk_python.executable import _Method +from hiero_sdk_python.hapi.services import token_burn_pb2, transaction_pb2 from hiero_sdk_python.hapi.services.schedulable_transaction_body_pb2 import ( SchedulableTransactionBody, ) -from hiero_sdk_python.transaction.transaction import Transaction -from hiero_sdk_python.channels import _Channel -from hiero_sdk_python.executable import _Method +from hiero_sdk_python.hapi.services.token_burn_pb2 import TokenBurnTransactionBody from hiero_sdk_python.tokens.token_id import TokenId +from hiero_sdk_python.transaction.transaction import Transaction + class TokenBurnTransaction(Transaction): """ Represents a token burn transaction on the network. - + This transaction burns tokens, effectively removing them from circulation. Can burn fungible tokens by amount or non-fungible tokens by serial numbers. - + Inherits from the base Transaction class and implements the required methods to build and execute a token burn transaction. """ + def __init__( - self, - token_id: Optional[TokenId] = None, - amount: Optional[int] = None, - serials: Optional[List[int]] = None + self, token_id: TokenId | None = None, amount: int | None = None, serials: list[int] | None = None ) -> None: """ Initializes a new TokenBurnTransaction instance with optional token_id, amount, and serials. @@ -42,11 +42,11 @@ def __init__( serials (list[int], optional): The serial numbers of non-fungible tokens to burn. """ super().__init__() - self.token_id: Optional[TokenId] = token_id - self.amount: Optional[int] = amount - self.serials: List[int] = serials if serials is not None else [] + self.token_id: TokenId | None = token_id + self.amount: int | None = amount + self.serials: list[int] = serials if serials is not None else [] - def set_token_id(self, token_id: TokenId) -> "TokenBurnTransaction": + def set_token_id(self, token_id: TokenId) -> TokenBurnTransaction: """ Sets the token ID for this burn transaction. @@ -60,7 +60,7 @@ def set_token_id(self, token_id: TokenId) -> "TokenBurnTransaction": self.token_id = token_id return self - def set_amount(self, amount: int) -> "TokenBurnTransaction": + def set_amount(self, amount: int) -> TokenBurnTransaction: """ Sets the amount of fungible tokens to burn. @@ -74,7 +74,7 @@ def set_amount(self, amount: int) -> "TokenBurnTransaction": self.amount = amount return self - def set_serials(self, serials: List[int]) -> "TokenBurnTransaction": + def set_serials(self, serials: list[int]) -> TokenBurnTransaction: """ Sets the list of serial numbers of non-fungible tokens to burn. @@ -88,7 +88,7 @@ def set_serials(self, serials: List[int]) -> "TokenBurnTransaction": self.serials = serials return self - def add_serial(self, serial: int) -> "TokenBurnTransaction": + def add_serial(self, serial: int) -> TokenBurnTransaction: """ Adds a single serial number to the list of non-fungible tokens to burn. @@ -106,10 +106,10 @@ def add_serial(self, serial: int) -> "TokenBurnTransaction": def _build_proto_body(self) -> token_burn_pb2.TokenBurnTransactionBody: """ Returns the protobuf body for the token burn transaction. - + Returns: TokenBurnTransactionBody: The protobuf body for this transaction. - + Raises: ValueError: If the token ID is not set or if both amount and serials are provided. """ @@ -119,12 +119,8 @@ def _build_proto_body(self) -> token_burn_pb2.TokenBurnTransactionBody: if self.amount and self.serials: raise ValueError("Cannot burn both amount and serial in the same transaction") - return TokenBurnTransactionBody( - token=self.token_id._to_proto(), - amount=self.amount, - serialNumbers=self.serials - ) - + return TokenBurnTransactionBody(token=self.token_id._to_proto(), amount=self.amount, serialNumbers=self.serials) + def build_transaction_body(self) -> transaction_pb2.TransactionBody: """ Builds the transaction body for this token burn transaction. @@ -136,7 +132,7 @@ def build_transaction_body(self) -> transaction_pb2.TransactionBody: transaction_body: transaction_pb2.TransactionBody = self.build_base_transaction_body() transaction_body.tokenBurn.CopyFrom(token_burn_body) return transaction_body - + def build_scheduled_body(self) -> SchedulableTransactionBody: """ Builds the scheduled transaction body for this token burn transaction. @@ -158,16 +154,13 @@ def _get_method(self, channel: _Channel) -> _Method: Args: channel (_Channel): The channel containing service stubs - + Returns: _Method: An object containing the transaction function to burn tokens. """ - return _Method( - transaction_func=channel.token.burnToken, - query_func=None - ) + return _Method(transaction_func=channel.token.burnToken, query_func=None) - def _from_proto(self, proto: TokenBurnTransactionBody) -> "TokenBurnTransaction": + def _from_proto(self, proto: TokenBurnTransactionBody) -> TokenBurnTransaction: """ Deserializes a TokenBurnTransactionBody from a protobuf object. diff --git a/src/hiero_sdk_python/tokens/token_create_transaction.py b/src/hiero_sdk_python/tokens/token_create_transaction.py index c8873d93a..1f8e363e5 100644 --- a/src/hiero_sdk_python/tokens/token_create_transaction.py +++ b/src/hiero_sdk_python/tokens/token_create_transaction.py @@ -1,5 +1,5 @@ """ -hiero_sdk_python.tokens.token_create_transaction.py +hiero_sdk_python.tokens.token_create_transaction.py. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Module for creating and validating Hedera token transactions. @@ -11,43 +11,52 @@ - TokenCreateTransaction: Handles token creation transactions on Hedera. """ +from __future__ import annotations + from dataclasses import dataclass, field -from typing import Optional, Any, List, Union +from typing import Any -from hiero_sdk_python.Duration import Duration +from hiero_sdk_python.account.account_id import AccountId from hiero_sdk_python.channels import _Channel +from hiero_sdk_python.Duration import Duration from hiero_sdk_python.executable import _Method -from hiero_sdk_python.timestamp import Timestamp -from hiero_sdk_python.transaction.transaction import Transaction -from hiero_sdk_python.hapi.services import token_create_pb2, basic_types_pb2, transaction_pb2 +from hiero_sdk_python.hapi.services import token_create_pb2, transaction_pb2 from hiero_sdk_python.hapi.services.schedulable_transaction_body_pb2 import ( SchedulableTransactionBody, ) -from hiero_sdk_python.tokens.token_type import TokenType -from hiero_sdk_python.tokens.supply_type import SupplyType -from hiero_sdk_python.account.account_id import AccountId +from hiero_sdk_python.timestamp import Timestamp from hiero_sdk_python.tokens.custom_fee import CustomFee +from hiero_sdk_python.tokens.supply_type import SupplyType +from hiero_sdk_python.tokens.token_type import TokenType +from hiero_sdk_python.transaction.transaction import Transaction from hiero_sdk_python.utils.key_utils import Key, key_to_proto + AUTO_RENEW_PERIOD = Duration(7890000) # around 90 days in seconds DEFAULT_TRANSACTION_FEE = 3_000_000_000 + @dataclass class TokenParams: """ Represents token attributes such as name, symbol, decimals, and type. Attributes: - token_name (required): The name of the token. - token_symbol (required): The symbol of the token. - treasury_account_id (required): The treasury account ID. - decimals (optional): The number of decimals for the token. This must be zero for NFTs. - initial_supply (optional): The initial supply of the token. - token_type (optional): The type of the token, defaulting to fungible. - max_supply (optional): The max tokens or NFT serial numbers. - supply_type (optional): The token supply status as finite or infinite. - freeze_default (optional): An initial Freeze status for accounts associated to this token. - metadata (optional): The on-ledger token metadata as bytes (max 100 bytes). + token_name (str, required): The name of the token. + token_symbol (str, required): The symbol of the token. + treasury_account_id (AccountId, required): The treasury account ID. + decimals (int, optional): The number of decimals for the token. This must be zero for NFTs. + initial_supply (int, optional): The initial supply of the token. + token_type (TokenType, optional): The type of the token, defaulting to fungible. + max_supply (int, optional): The max tokens or NFT serial numbers. + supply_type (SupplyType, optional): The token supply status as finite or infinite. + freeze_default (bool, optional): An initial Freeze status for accounts associated to this token. + custom_fees (list[CustomFee], optional): The custom fees for the token. + expiration_time (Timestamp, optional): The expiration time for token. + auto_renew_account_id (AccountId, optional): The auto renew account id for the token. + auto_renew_period (Duration, optional): The auto renew period for the token. + memo (str, optional): The memo for the token. + metadata (bytes, optional): The on-ledger token metadata as bytes (max 100 bytes). """ token_name: str @@ -56,50 +65,50 @@ class TokenParams: decimals: int = 0 # Default to zero decimals initial_supply: int = 0 # Default to zero initial supply token_type: TokenType = TokenType.FUNGIBLE_COMMON # Default to Fungible Common - max_supply: int = 0 # Since defaulting to infinite - supply_type: SupplyType = SupplyType.INFINITE # Default to infinite + max_supply: int = 0 # Since defaulting to infinite + supply_type: SupplyType = SupplyType.INFINITE # Default to infinite freeze_default: bool = False - custom_fees: List[CustomFee] = field(default_factory=list) - expiration_time: Optional[Timestamp] = None - auto_renew_account_id: Optional[AccountId] = None - auto_renew_period: Optional[Duration] = AUTO_RENEW_PERIOD # Default around ~90 days - memo: Optional[str] = None - metadata: Optional[bytes] = None + custom_fees: list[CustomFee] = field(default_factory=list) + expiration_time: Timestamp | None = None + auto_renew_account_id: AccountId | None = None + auto_renew_period: Duration | None = AUTO_RENEW_PERIOD # Default around ~90 days + memo: str | None = None + metadata: bytes | None = None @dataclass class TokenKeys: """ - Represents cryptographic keys associated with a token. + Represents cryptographic keys associated with a token. Does not include treasury_key which is for transaction signing. Attributes: - admin_key: The admin key for the token to update and delete. - supply_key: The supply key for the token to mint and burn. - freeze_key: The freeze key for the token to freeze and unfreeze. - wipe_key: The wipe key for the token to wipe tokens from an account. - pause_key: The pause key for the token to be paused. - metadata_key: The metadata key for the token to update NFT metadata. - kyc_key: The KYC key for the token to grant KYC to an account. + admin_key (Key, optional): The admin key for the token to update and delete. + supply_key (Key, optional): The supply key for the token to mint and burn. + freeze_key (Key, optional): The freeze key for the token to freeze and unfreeze. + wipe_key (Key, optional): The wipe key for the token to wipe tokens from an account. + pause_key (Key, optional): The pause key for the token to be paused. + metadata_key (Key, optional): The metadata key for the token to update NFT metadata. + kyc_key (Key, optional): The KYC key for the token to grant KYC to an account. + fee_schedule_key (Key, optional): The fee schedule key for the token. """ - admin_key: Optional[Key] = None - supply_key: Optional[Key] = None - freeze_key: Optional[Key] = None - wipe_key: Optional[Key] = None - metadata_key: Optional[Key] = None - pause_key: Optional[Key] = None - kyc_key: Optional[Key] = None - fee_schedule_key: Optional[Key] = None + admin_key: Key | None = None + supply_key: Key | None = None + freeze_key: Key | None = None + wipe_key: Key | None = None + metadata_key: Key | None = None + pause_key: Key | None = None + kyc_key: Key | None = None + fee_schedule_key: Key | None = None + class TokenCreateValidator: - """Token, key and freeze checks for creating a token as per the proto""" + """Token, key and freeze checks for creating a token as per the proto.""" @staticmethod def _validate_token_params(token_params: TokenParams) -> None: - """ - Ensure valid values for the token characteristics. - """ + """Ensure valid values for the token characteristics.""" TokenCreateValidator._validate_required_fields(token_params) TokenCreateValidator._validate_name_and_symbol(token_params) TokenCreateValidator._validate_initial_supply(token_params) @@ -109,31 +118,26 @@ def _validate_token_params(token_params: TokenParams) -> None: @staticmethod def _validate_token_freeze_status(keys: TokenKeys, token_params: TokenParams) -> None: """Ensure account is not frozen for this token.""" - if token_params.freeze_default: - if not keys.freeze_key: - raise ValueError("Token is permanently frozen. Unable to proceed.") + if token_params.freeze_default and not keys.freeze_key: + raise ValueError("Token is permanently frozen. Unable to proceed.") # freezeDefault=True simply starts accounts frozen; allow creation as long as # a freeze key exists so the treasury (and others) can be unfrozen later. @staticmethod def _validate_required_fields(token_params: TokenParams) -> None: - """ - Ensure all required fields are present and not empty. - """ + """Ensure all required fields are present and not empty.""" required_fields: dict[str, Any] = { "Token name": token_params.token_name, "Token symbol": token_params.token_symbol, "Treasury account ID": token_params.treasury_account_id, } - for field, value in required_fields.items(): - if not value: - raise ValueError(f"{field} is required") + for _field, _value in required_fields.items(): + if not _value: + raise ValueError(f"{_field} is required") @staticmethod def _validate_name_and_symbol(token_params: TokenParams) -> None: - """ - Ensure the token name & symbol are valid in length and do not contain a NUL character. - """ + """Ensure the token name & symbol are valid in length and do not contain a NUL character.""" if len(token_params.token_name.encode()) > 100: raise ValueError("Token name must be between 1 and 100 bytes") if len(token_params.token_symbol.encode()) > 100: @@ -142,16 +146,11 @@ def _validate_name_and_symbol(token_params: TokenParams) -> None: # Ensure the token name and symbol do not contain a NUL character for attr in ["token_name", "token_symbol"]: if "\x00" in getattr(token_params, attr): - raise ValueError( - f"{attr.replace('_', ' ').capitalize()} must not " - "contain the Unicode NUL character" - ) + raise ValueError(f"{attr.replace('_', ' ').capitalize()} must not contain the Unicode NUL character") @staticmethod def _validate_initial_supply(token_params: TokenParams) -> None: - """ - Ensure initial supply is a non-negative integer and does not exceed max supply. - """ + """Ensure initial supply is a non-negative integer and does not exceed max supply.""" MAXIMUM_SUPPLY = 9_223_372_036_854_775_807 # 2^63 - 1 if token_params.initial_supply < 0: @@ -161,12 +160,9 @@ def _validate_initial_supply(token_params: TokenParams) -> None: if token_params.max_supply > MAXIMUM_SUPPLY: raise ValueError(f"Max supply cannot exceed {MAXIMUM_SUPPLY}") - @staticmethod def _validate_decimals_and_token_type(token_params: TokenParams) -> None: - """ - Ensure decimals and token_type align with either fungible or non-fungible constraints. - """ + """Ensure decimals and token_type align with either fungible or non-fungible constraints.""" if token_params.decimals < 0: raise ValueError("Decimals must be a non-negative integer") @@ -188,9 +184,8 @@ def _validate_supply_max_and_type(token_params: TokenParams) -> None: # An infinite token must have max supply = 0. # A finite token must have max supply > 0. # Setting a max supply is only approprite for a finite token. - if token_params.max_supply != 0: - if token_params.supply_type != SupplyType.FINITE: - raise ValueError("Setting a max supply field requires setting a finite supply type") + if token_params.max_supply != 0 and token_params.supply_type != SupplyType.FINITE: + raise ValueError("Setting a max supply field requires setting a finite supply type") # Finite tokens have the option to set a max supply >0. # A finite token must have max supply > 0. @@ -200,9 +195,8 @@ def _validate_supply_max_and_type(token_params: TokenParams) -> None: # Ensure max supply is greater than initial supply if token_params.initial_supply > token_params.max_supply: - raise ValueError( - "Initial supply cannot exceed the defined max supply for a finite token" - ) + raise ValueError("Initial supply cannot exceed the defined max supply for a finite token") + class TokenCreateTransaction(Transaction): """ @@ -215,11 +209,7 @@ class TokenCreateTransaction(Transaction): to build and execute a token creation transaction. """ - def __init__( - self, - token_params: Optional[TokenParams] = None, - keys: Optional[TokenKeys] = None - ) -> None: + def __init__(self, token_params: TokenParams | None = None, keys: TokenKeys | None = None) -> None: """ Initializes a new TokenCreateTransaction instance with token parameters and optional keys. @@ -230,11 +220,11 @@ def __init__( immediately if fields are missing at creation. Args: - token_params (TokenParams, Optional): The token parameters (name, symbol, decimals, etc.). + token_params (TokenParams, optional): The token parameters (name, symbol, decimals, etc.). If None, a default/blank TokenParams is created, expecting you to call setters later. - keys (TokenKeys, Optional): The token keys (admin, supply, freeze). - If None, an empty TokenKeys is created, + keys (TokenKeys, optional): The token keys (admin, supply, freeze). + If None, an empty TokenKeys is created, expecting you to call setter methods if needed. """ super().__init__() @@ -253,7 +243,7 @@ def __init__( supply_type=SupplyType.INFINITE, freeze_default=False, expiration_time=None, - auto_renew_period=AUTO_RENEW_PERIOD + auto_renew_period=AUTO_RENEW_PERIOD, ) # Store TokenParams and TokenKeys. @@ -271,7 +261,7 @@ def __init__( self._default_transaction_fee = DEFAULT_TRANSACTION_FEE - def set_token_params(self, token_params: TokenParams) -> "TokenCreateTransaction": + def set_token_params(self, token_params: TokenParams) -> TokenCreateTransaction: """ Replaces the current TokenParams object with the new one. Useful if you have a fully-formed TokenParams to override existing fields. @@ -280,7 +270,7 @@ def set_token_params(self, token_params: TokenParams) -> "TokenCreateTransaction self._token_params = token_params return self - def set_token_keys(self, keys: TokenKeys) -> "TokenCreateTransaction": + def set_token_keys(self, keys: TokenKeys) -> TokenCreateTransaction: """ Replaces the current TokenKeys object with the new one. Useful if you have a fully-formed TokenKeys to override existing fields. @@ -290,62 +280,63 @@ def set_token_keys(self, keys: TokenKeys) -> "TokenCreateTransaction": return self # These allow setting of individual fields - def set_token_name(self, name: str) -> "TokenCreateTransaction": - """ Sets the token name for the transaction.""" + def set_token_name(self, name: str) -> TokenCreateTransaction: + """Sets the token name for the transaction.""" self._require_not_frozen() self._token_params.token_name = name return self - def set_token_symbol(self, symbol: str) -> "TokenCreateTransaction": - """ Sets the token symbol for the transaction.""" + def set_token_symbol(self, symbol: str) -> TokenCreateTransaction: + """Sets the token symbol for the transaction.""" self._require_not_frozen() self._token_params.token_symbol = symbol return self - def set_treasury_account_id(self, account_id: AccountId) -> "TokenCreateTransaction": - """ Sets the treasury account ID for the token.""" + def set_treasury_account_id(self, account_id: AccountId) -> TokenCreateTransaction: + """Sets the treasury account ID for the token.""" self._require_not_frozen() self._token_params.treasury_account_id = account_id return self - def set_decimals(self, decimals: int) -> "TokenCreateTransaction": - """ Sets the number of decimals for the token.""" + def set_decimals(self, decimals: int) -> TokenCreateTransaction: + """Sets the number of decimals for the token.""" self._require_not_frozen() self._token_params.decimals = decimals return self - def set_initial_supply(self, initial_supply: int) -> "TokenCreateTransaction": - """ Sets the initial supply of the token.""" + def set_initial_supply(self, initial_supply: int) -> TokenCreateTransaction: + """Sets the initial supply of the token.""" self._require_not_frozen() self._token_params.initial_supply = initial_supply return self - def set_token_type(self, token_type: TokenType) -> "TokenCreateTransaction": - """ Sets the type of the token, such as fungible or non-fungible. """ + def set_token_type(self, token_type: TokenType) -> TokenCreateTransaction: + """Sets the type of the token, such as fungible or non-fungible.""" self._require_not_frozen() self._token_params.token_type = token_type return self - def set_max_supply(self, max_supply: int) -> "TokenCreateTransaction": - """ Sets the maximum supply of the token. - For fungible tokens, this is the max number of tokens that can be created.""" + def set_max_supply(self, max_supply: int) -> TokenCreateTransaction: + """Sets the maximum supply of the token. + For fungible tokens, this is the max number of tokens that can be created. + """ self._require_not_frozen() self._token_params.max_supply = max_supply return self - def set_supply_type(self, supply_type: SupplyType) -> "TokenCreateTransaction": - """ Sets the supply type of the token, such as finite or infinite.""" + def set_supply_type(self, supply_type: SupplyType) -> TokenCreateTransaction: + """Sets the supply type of the token, such as finite or infinite.""" self._require_not_frozen() self._token_params.supply_type = supply_type return self - def set_freeze_default(self, freeze_default: bool) -> "TokenCreateTransaction": - """ Sets the default freeze status for accounts associated with this token.""" + def set_freeze_default(self, freeze_default: bool) -> TokenCreateTransaction: + """Sets the default freeze status for accounts associated with this token.""" self._require_not_frozen() self._token_params.freeze_default = freeze_default return self - def set_expiration_time(self, expiration_time: Timestamp) -> "TokenCreateTransaction": + def set_expiration_time(self, expiration_time: Timestamp) -> TokenCreateTransaction: """Sets the explicit expiration time for the token.""" self._require_not_frozen() self._token_params.expiration_time = expiration_time @@ -353,80 +344,80 @@ def set_expiration_time(self, expiration_time: Timestamp) -> "TokenCreateTransac self._token_params.auto_renew_period = None return self - def set_auto_renew_period(self, auto_renew_period: Duration) -> "TokenCreateTransaction": + def set_auto_renew_period(self, auto_renew_period: Duration) -> TokenCreateTransaction: """Sets the auto-renew period for the token.""" self._require_not_frozen() self._token_params.auto_renew_period = auto_renew_period return self - def set_auto_renew_account_id(self, auto_renew_account_id: AccountId) -> "TokenCreateTransaction": + def set_auto_renew_account_id(self, auto_renew_account_id: AccountId) -> TokenCreateTransaction: """Sets the auto-renew account ID for the token.""" self._require_not_frozen() self._token_params.auto_renew_account_id = auto_renew_account_id return self - def set_memo(self, memo: str) -> "TokenCreateTransaction": + def set_memo(self, memo: str) -> TokenCreateTransaction: """Sets a short description (memo) for the token.""" self._require_not_frozen() self._token_params.memo = memo return self - def set_admin_key(self, key: Key) -> "TokenCreateTransaction": - """ Sets the admin key for the token, which allows updating and deleting the token.""" + def set_admin_key(self, key: Key) -> TokenCreateTransaction: + """Sets the admin key for the token, which allows updating and deleting the token.""" self._require_not_frozen() self._keys.admin_key = key return self - def set_supply_key(self, key: Key) -> "TokenCreateTransaction": - """ Sets the supply key for the token, which allows minting and burning tokens.""" + def set_supply_key(self, key: Key) -> TokenCreateTransaction: + """Sets the supply key for the token, which allows minting and burning tokens.""" self._require_not_frozen() self._keys.supply_key = key return self - def set_freeze_key(self, key: Key) -> "TokenCreateTransaction": - """ Sets the freeze key for the token, which allows freezing and unfreezing accounts.""" + def set_freeze_key(self, key: Key) -> TokenCreateTransaction: + """Sets the freeze key for the token, which allows freezing and unfreezing accounts.""" self._require_not_frozen() self._keys.freeze_key = key return self - def set_wipe_key(self, key: Key) -> "TokenCreateTransaction": - """ Sets the wipe key for the token, which allows wiping tokens from an account.""" + def set_wipe_key(self, key: Key) -> TokenCreateTransaction: + """Sets the wipe key for the token, which allows wiping tokens from an account.""" self._require_not_frozen() self._keys.wipe_key = key return self - def set_metadata_key(self, key: Key) -> "TokenCreateTransaction": - """ Sets the metadata key for the token, which allows updating NFT metadata.""" + def set_metadata_key(self, key: Key) -> TokenCreateTransaction: + """Sets the metadata key for the token, which allows updating NFT metadata.""" self._require_not_frozen() self._keys.metadata_key = key return self - def set_pause_key(self, key: Key) -> "TokenCreateTransaction": - """ Sets the pause key for the token, which allows pausing and unpausing the token.""" + def set_pause_key(self, key: Key) -> TokenCreateTransaction: + """Sets the pause key for the token, which allows pausing and unpausing the token.""" self._require_not_frozen() self._keys.pause_key = key return self - def set_kyc_key(self, key: Key) -> "TokenCreateTransaction": - """ Sets the KYC key for the token, which allows granting KYC to an account.""" + def set_kyc_key(self, key: Key) -> TokenCreateTransaction: + """Sets the KYC key for the token, which allows granting KYC to an account.""" self._require_not_frozen() self._keys.kyc_key = key return self - def set_custom_fees(self, custom_fees: List[CustomFee]) -> "TokenCreateTransaction": + def set_custom_fees(self, custom_fees: list[CustomFee]) -> TokenCreateTransaction: """Set the Custom Fees.""" self._require_not_frozen() self._token_params.custom_fees = custom_fees return self - def set_fee_schedule_key(self, key: Key) -> "TokenCreateTransaction": + def set_fee_schedule_key(self, key: Key) -> TokenCreateTransaction: """Sets the fee schedule key for the token.""" self._require_not_frozen() self._keys.fee_schedule_key = key return self - def set_metadata(self, metadata: bytes | str) -> "TokenCreateTransaction": - """Sets the metadata for the token (max 100 bytes)""" + def set_metadata(self, metadata: bytes | str) -> TokenCreateTransaction: + """Sets the metadata for the token (max 100 bytes).""" self._require_not_frozen() # accept stringt and converts to bytes @@ -443,7 +434,7 @@ def set_metadata(self, metadata: bytes | str) -> "TokenCreateTransaction": self._token_params.metadata = metadata return self - def freeze_with(self, client) -> "TokenCreateTransaction": + def freeze_with(self, client) -> TokenCreateTransaction: """ Freeze the transaction with the given client. @@ -453,20 +444,14 @@ def freeze_with(self, client) -> "TokenCreateTransaction": - Otherwise → fall back to the client's `operator_account_id`. """ - if ( - self._token_params.auto_renew_account_id is None - and self._token_params.auto_renew_period - ): + if self._token_params.auto_renew_account_id is None and self._token_params.auto_renew_period: self._token_params.auto_renew_account_id = ( - self.transaction_id.account_id - if self.transaction_id - else client.operator_account_id + self.transaction_id.account_id if self.transaction_id else client.operator_account_id ) return super().freeze_with(client) - - def _to_proto_key(self, key: Optional[Key]): + def _to_proto_key(self, key: Key | None): """ Backwards-compatible wrapper around `key_to_proto` for converting SDK keys (PrivateKey or PublicKey) to protobuf `Key` messages. @@ -480,10 +465,10 @@ def _to_proto_key(self, key: Optional[Key]): def _build_proto_body(self) -> token_create_pb2.TokenCreateTransactionBody: """ Returns the protobuf body for the token create transaction. - + Returns: TokenCreateTransactionBody: The protobuf body for this transaction. - + Raises: ValueError: If required fields are missing or invalid. """ @@ -526,20 +511,14 @@ def _build_proto_body(self) -> token_create_pb2.TokenCreateTransactionBody: maxSupply=self._token_params.max_supply, freezeDefault=self._token_params.freeze_default, treasury=self._token_params.treasury_account_id._to_proto(), - expiry=( - self._token_params.expiration_time._to_protobuf() - if self._token_params.expiration_time - else None - ), + expiry=(self._token_params.expiration_time._to_protobuf() if self._token_params.expiration_time else None), autoRenewAccount=( self._token_params.auto_renew_account_id._to_proto() if self._token_params.auto_renew_account_id else None ), autoRenewPeriod=( - self._token_params.auto_renew_period._to_proto() - if self._token_params.auto_renew_period - else None + self._token_params.auto_renew_period._to_proto() if self._token_params.auto_renew_period else None ), memo=self._token_params.memo, metadata=self._token_params.metadata, @@ -579,7 +558,4 @@ def build_scheduled_body(self) -> SchedulableTransactionBody: return schedulable_body def _get_method(self, channel: _Channel) -> _Method: - return _Method( - transaction_func=channel.token.createToken, - query_func=None - ) + return _Method(transaction_func=channel.token.createToken, query_func=None) diff --git a/src/hiero_sdk_python/tokens/token_delete_transaction.py b/src/hiero_sdk_python/tokens/token_delete_transaction.py index 0ec1ed6b7..b03169c36 100644 --- a/src/hiero_sdk_python/tokens/token_delete_transaction.py +++ b/src/hiero_sdk_python/tokens/token_delete_transaction.py @@ -1,20 +1,22 @@ """ -hiero_sdk_python.tokens.token_delete_transaction.py +hiero_sdk_python.tokens.token_delete_transaction.py. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Provides TokenDeleteTransaction, a subclass of Transaction for deleting tokens on the Hedera network using the Hedera Token Service (HTS) API. """ -from typing import Optional -from hiero_sdk_python.transaction.transaction import Transaction +from __future__ import annotations + +from hiero_sdk_python.channels import _Channel +from hiero_sdk_python.executable import _Method from hiero_sdk_python.hapi.services import token_delete_pb2, transaction_pb2 from hiero_sdk_python.hapi.services.schedulable_transaction_body_pb2 import ( SchedulableTransactionBody, ) -from hiero_sdk_python.channels import _Channel -from hiero_sdk_python.executable import _Method from hiero_sdk_python.tokens.token_id import TokenId +from hiero_sdk_python.transaction.transaction import Transaction + class TokenDeleteTransaction(Transaction): """ @@ -26,10 +28,7 @@ class TokenDeleteTransaction(Transaction): to build and execute a token deletion transaction. """ - def __init__( - self, - token_id: Optional[TokenId] = None - ) -> None: + def __init__(self, token_id: TokenId | None = None) -> None: """ Initializes a new TokenDeleteTransaction instance with optional token_id. @@ -37,10 +36,10 @@ def __init__( token_id (TokenId, optional): The ID of the token to be deleted. """ super().__init__() - self.token_id: Optional[TokenId] = token_id + self.token_id: TokenId | None = token_id self._default_transaction_fee: int = 3_000_000_000 - def set_token_id(self, token_id: TokenId) -> "TokenDeleteTransaction": + def set_token_id(self, token_id: TokenId) -> TokenDeleteTransaction: """ Sets the ID of the token to be deleted. @@ -57,20 +56,18 @@ def set_token_id(self, token_id: TokenId) -> "TokenDeleteTransaction": def _build_proto_body(self) -> token_delete_pb2.TokenDeleteTransactionBody: """ Returns the protobuf body for the token delete transaction. - + Returns: TokenDeleteTransactionBody: The protobuf body for this transaction. - + Raises: ValueError: If the token ID is missing. """ if not self.token_id: raise ValueError("Missing required TokenID.") - return token_delete_pb2.TokenDeleteTransactionBody( - token=self.token_id._to_proto() - ) - + return token_delete_pb2.TokenDeleteTransactionBody(token=self.token_id._to_proto()) + def build_transaction_body(self) -> transaction_pb2.TransactionBody: """ Builds and returns the protobuf transaction body for token deletion. @@ -82,7 +79,7 @@ def build_transaction_body(self) -> transaction_pb2.TransactionBody: transaction_body: transaction_pb2.TransactionBody = self.build_base_transaction_body() transaction_body.tokenDeletion.CopyFrom(token_delete_body) return transaction_body - + def build_scheduled_body(self) -> SchedulableTransactionBody: """ Builds the scheduled transaction body for this token delete transaction. @@ -96,7 +93,4 @@ def build_scheduled_body(self) -> SchedulableTransactionBody: return schedulable_body def _get_method(self, channel: _Channel) -> _Method: - return _Method( - transaction_func=channel.token.deleteToken, - query_func=None - ) + return _Method(transaction_func=channel.token.deleteToken, query_func=None) diff --git a/src/hiero_sdk_python/tokens/token_dissociate_transaction.py b/src/hiero_sdk_python/tokens/token_dissociate_transaction.py index 97bded7d9..1957de2c2 100644 --- a/src/hiero_sdk_python/tokens/token_dissociate_transaction.py +++ b/src/hiero_sdk_python/tokens/token_dissociate_transaction.py @@ -1,5 +1,5 @@ """ -hiero_sdk_python.tokens.token_dissociate_transaction.py +hiero_sdk_python.tokens.token_dissociate_transaction.py. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ This module provides the `TokenDissociateTransaction` class, which models @@ -11,18 +11,20 @@ from the base `Transaction` class and encapsulates all necessary fields and methods to perform a token dissociation on Hedera. """ -from typing import Optional, List + +from __future__ import annotations from hiero_sdk_python.account.account_id import AccountId -from hiero_sdk_python.transaction.transaction import Transaction +from hiero_sdk_python.channels import _Channel +from hiero_sdk_python.executable import _Method from hiero_sdk_python.hapi.services import token_dissociate_pb2, transaction_pb2 from hiero_sdk_python.hapi.services.schedulable_transaction_body_pb2 import ( SchedulableTransactionBody, ) -from hiero_sdk_python.channels import _Channel -from hiero_sdk_python.executable import _Method -from hiero_sdk_python.tokens.token_id import TokenId from hiero_sdk_python.hbar import Hbar +from hiero_sdk_python.tokens.token_id import TokenId +from hiero_sdk_python.transaction.transaction import Transaction + class TokenDissociateTransaction(Transaction): """ @@ -35,38 +37,34 @@ class TokenDissociateTransaction(Transaction): to build and execute a token dissociate transaction. """ - def __init__( - self, - account_id: Optional[AccountId] = None, - token_ids: Optional[List[TokenId]] = None - ) -> None: + def __init__(self, account_id: AccountId | None = None, token_ids: list[TokenId] | None = None) -> None: """ Initializes a new TokenDissociateTransaction instance with default values. + Args: account_id (AccountId, optional): The ID of the account to dissociate tokens from - token_ids (List[TokenId], optional): A list of token IDs to dissociate from the account + token_ids (list[TokenId], optional): A list of token IDs to dissociate from the account """ super().__init__() - self.account_id: Optional[AccountId] = account_id - self.token_ids: List[TokenId] = token_ids or [] + self.account_id: AccountId | None = account_id + self.token_ids: list[TokenId] = token_ids or [] self._default_transaction_fee = Hbar(2) - def set_account_id(self, account_id: AccountId) -> "TokenDissociateTransaction": - """ Sets the account ID for the token dissociation transaction. """ + def set_account_id(self, account_id: AccountId) -> TokenDissociateTransaction: + """Sets the account ID for the token dissociation transaction.""" self._require_not_frozen() self.account_id = account_id return self - def add_token_id(self, token_id: TokenId) -> "TokenDissociateTransaction": + def add_token_id(self, token_id: TokenId) -> TokenDissociateTransaction: """Adds a token ID to the list of tokens to dissociate from the account.""" self._require_not_frozen() self.token_ids.append(token_id) return self - def set_token_ids(self, token_ids: List[TokenId]) -> "TokenDissociateTransaction": - """Sets the list of token IDs to dissociate from the account. - """ + def set_token_ids(self, token_ids: list[TokenId]) -> TokenDissociateTransaction: + """Sets the list of token IDs to dissociate from the account.""" self._require_not_frozen() self.token_ids = token_ids return self @@ -75,13 +73,12 @@ def _validate_check_sum(self, client) -> None: """Validates the checksums of the account ID and token IDs against the provided client.""" if self.account_id is not None: self.account_id.validate_checksum(client) - for token_id in (self.token_ids or []): + for token_id in self.token_ids or []: if token_id is not None: token_id.validate_checksum(client) - @classmethod - def _from_proto(cls, proto: token_dissociate_pb2.TokenDissociateTransactionBody) -> "TokenDissociateTransaction": + def _from_proto(cls, proto: token_dissociate_pb2.TokenDissociateTransactionBody) -> TokenDissociateTransaction: """ Creates a TokenDissociateTransaction instance from a protobuf TokenDissociateTransactionBody object. @@ -93,20 +90,15 @@ def _from_proto(cls, proto: token_dissociate_pb2.TokenDissociateTransactionBody) account_id = AccountId._from_proto(proto.account) token_ids = [TokenId._from_proto(token_proto) for token_proto in proto.tokens] - transaction = cls( - account_id=account_id, - token_ids=token_ids - ) - - return transaction + return cls(account_id=account_id, token_ids=token_ids) def _build_proto_body(self) -> token_dissociate_pb2.TokenDissociateTransactionBody: """ Returns the protobuf body for the token dissociate transaction. - + Returns: TokenDissociateTransactionBody: The protobuf body for this transaction. - + Raises: ValueError: If account ID or token IDs are not set. """ @@ -114,10 +106,9 @@ def _build_proto_body(self) -> token_dissociate_pb2.TokenDissociateTransactionBo raise ValueError("Account ID and token IDs must be set.") return token_dissociate_pb2.TokenDissociateTransactionBody( - account=self.account_id._to_proto(), - tokens=[token_id._to_proto() for token_id in self.token_ids] + account=self.account_id._to_proto(), tokens=[token_id._to_proto() for token_id in self.token_ids] ) - + def build_transaction_body(self) -> transaction_pb2.TransactionBody: """ Builds and returns the protobuf transaction body for token dissociation. @@ -129,7 +120,7 @@ def build_transaction_body(self) -> transaction_pb2.TransactionBody: transaction_body: transaction_pb2.TransactionBody = self.build_base_transaction_body() transaction_body.tokenDissociate.CopyFrom(token_dissociate_body) return transaction_body - + def build_scheduled_body(self) -> SchedulableTransactionBody: """ Builds the scheduled transaction body for this token dissociate transaction. @@ -143,7 +134,4 @@ def build_scheduled_body(self) -> SchedulableTransactionBody: return schedulable_body def _get_method(self, channel: _Channel) -> _Method: - return _Method( - transaction_func=channel.token.dissociateTokens, - query_func=None - ) + return _Method(transaction_func=channel.token.dissociateTokens, query_func=None) diff --git a/src/hiero_sdk_python/tokens/token_fee_schedule_update_transaction.py b/src/hiero_sdk_python/tokens/token_fee_schedule_update_transaction.py index f0ef2e630..3cab108ef 100644 --- a/src/hiero_sdk_python/tokens/token_fee_schedule_update_transaction.py +++ b/src/hiero_sdk_python/tokens/token_fee_schedule_update_transaction.py @@ -1,11 +1,9 @@ -""" -Defines TokenFeeScheduleUpdateTransaction for updating custom fee schedules -""" +"""Defines TokenFeeScheduleUpdateTransaction for updating custom fee schedules.""" -from typing import TYPE_CHECKING, List, Optional +from __future__ import annotations + +from typing import TYPE_CHECKING -from hiero_sdk_python.transaction.transaction import Transaction -from hiero_sdk_python.tokens.token_id import TokenId from hiero_sdk_python.channels import _Channel from hiero_sdk_python.executable import _Method from hiero_sdk_python.hapi.services import ( @@ -16,6 +14,9 @@ SchedulableTransactionBody, ) from hiero_sdk_python.tokens.custom_fee import CustomFee +from hiero_sdk_python.tokens.token_id import TokenId +from hiero_sdk_python.transaction.transaction import Transaction + if TYPE_CHECKING: from hiero_sdk_python.client import Client @@ -26,35 +27,31 @@ class TokenFeeScheduleUpdateTransaction(Transaction): def __init__( self, - token_id: Optional[TokenId] = None, - custom_fees: Optional[List[CustomFee]] = None, + token_id: TokenId | None = None, + custom_fees: list[CustomFee] | None = None, ) -> None: """ Initializes a new TokenFeeScheduleUpdateTransaction instance. Sets a default transaction fee. """ super().__init__() - self._default_transaction_fee: int = 100_000_000 # 1 Hbar in tinybars - self.token_id: Optional[TokenId] = token_id - self.custom_fees: List[CustomFee] = custom_fees or [] + self._default_transaction_fee: int = 100_000_000 # 1 Hbar in tinybars + self.token_id: TokenId | None = token_id + self.custom_fees: list[CustomFee] = custom_fees or [] - def set_token_id( - self, token_id: TokenId - ) -> "TokenFeeScheduleUpdateTransaction": + def set_token_id(self, token_id: TokenId) -> TokenFeeScheduleUpdateTransaction: """Sets the token ID to update.""" self._require_not_frozen() self.token_id = token_id return self - def set_custom_fees( - self, custom_fees: List[CustomFee] - ) -> "TokenFeeScheduleUpdateTransaction": + def set_custom_fees(self, custom_fees: list[CustomFee]) -> TokenFeeScheduleUpdateTransaction: """Sets the new custom fee schedule for the token.""" self._require_not_frozen() self.custom_fees = custom_fees return self - def _validate_checksums(self, client: "Client") -> None: + def _validate_checksums(self, client: Client) -> None: """Validates checksums for token ID and account IDs within custom fees.""" if self.token_id: self.token_id.validate_checksum(client) @@ -78,9 +75,7 @@ def _build_proto_body( def build_transaction_body(self) -> transaction_pb2.TransactionBody: """Builds and returns the protobuf transaction body.""" token_fee_update_body = self._build_proto_body() - transaction_body: transaction_pb2.TransactionBody = ( - self.build_base_transaction_body() - ) + transaction_body: transaction_pb2.TransactionBody = self.build_base_transaction_body() transaction_body.token_fee_schedule_update.CopyFrom(token_fee_update_body) return transaction_body diff --git a/src/hiero_sdk_python/tokens/token_freeze_status.py b/src/hiero_sdk_python/tokens/token_freeze_status.py index 79781ebad..34820bc16 100644 --- a/src/hiero_sdk_python/tokens/token_freeze_status.py +++ b/src/hiero_sdk_python/tokens/token_freeze_status.py @@ -1,9 +1,12 @@ """ -hiero_sdk_python.tokens.token_freeze_status.py +hiero_sdk_python.tokens.token_freeze_status.py. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ TokenFreezeStatus shows whether or not an account can use a token in transactions. """ + +from __future__ import annotations + from enum import Enum from typing import Any @@ -11,14 +14,16 @@ TokenFreezeStatus as proto_TokenFreezeStatus, ) + class TokenFreezeStatus(Enum): - """Enum representing a token’s freeze status: not applicable, frozen, or unfrozen.""" + """Enum representing a token's freeze status: not applicable, frozen, or unfrozen.""" + FREEZE_NOT_APPLICABLE = 0 FROZEN = 1 UNFROZEN = 2 @staticmethod - def _from_proto(proto_obj: proto_TokenFreezeStatus) -> "TokenFreezeStatus": + def _from_proto(proto_obj: proto_TokenFreezeStatus) -> TokenFreezeStatus: """Converts a protobuf TokenFreezeStatus to a TokenFreezeStatus enum.""" if proto_obj == proto_TokenFreezeStatus.FreezeNotApplicable: return TokenFreezeStatus.FREEZE_NOT_APPLICABLE diff --git a/src/hiero_sdk_python/tokens/token_freeze_transaction.py b/src/hiero_sdk_python/tokens/token_freeze_transaction.py index 66abe147a..06b3e0fe0 100644 --- a/src/hiero_sdk_python/tokens/token_freeze_transaction.py +++ b/src/hiero_sdk_python/tokens/token_freeze_transaction.py @@ -1,21 +1,23 @@ """ -hiero_sdk_python.tokens.token_freeze_transaction.py +hiero_sdk_python.tokens.token_freeze_transaction.py. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Provides TokenFreezeTransaction, a subclass of Transaction for freezing a specified token for an account on the Hedera network using the Hedera Token Service (HTS) API. """ -from typing import Optional + +from __future__ import annotations from hiero_sdk_python.account.account_id import AccountId -from hiero_sdk_python.tokens.token_id import TokenId -from hiero_sdk_python.transaction.transaction import Transaction +from hiero_sdk_python.channels import _Channel +from hiero_sdk_python.executable import _Method from hiero_sdk_python.hapi.services import token_freeze_account_pb2, transaction_pb2 from hiero_sdk_python.hapi.services.schedulable_transaction_body_pb2 import ( SchedulableTransactionBody, ) -from hiero_sdk_python.channels import _Channel -from hiero_sdk_python.executable import _Method +from hiero_sdk_python.tokens.token_id import TokenId +from hiero_sdk_python.transaction.transaction import Transaction + class TokenFreezeTransaction(Transaction): """ @@ -27,11 +29,7 @@ class TokenFreezeTransaction(Transaction): to build and execute a token freeze transaction. """ - def __init__( - self, - token_id: Optional[TokenId] = None, - account_id: Optional[AccountId]=None - ) -> None: + def __init__(self, token_id: TokenId | None = None, account_id: AccountId | None = None) -> None: """ Initializes a new TokenFreezeTransaction instance with optional token_id and account_id. @@ -40,11 +38,11 @@ def __init__( account_id (AccountId, optional): The ID of the account to have their token frozen. """ super().__init__() - self.token_id: Optional[TokenId] = token_id - self.account_id: Optional[AccountId] = account_id + self.token_id: TokenId | None = token_id + self.account_id: AccountId | None = account_id self._default_transaction_fee: int = 3_000_000_000 - def set_token_id(self, token_id: TokenId) -> "TokenFreezeTransaction": + def set_token_id(self, token_id: TokenId) -> TokenFreezeTransaction: """ Sets the ID of the token to be frozen. @@ -58,7 +56,7 @@ def set_token_id(self, token_id: TokenId) -> "TokenFreezeTransaction": self.token_id = token_id return self - def set_account_id(self, account_id: AccountId) -> "TokenFreezeTransaction": + def set_account_id(self, account_id: AccountId) -> TokenFreezeTransaction: """ Sets the ID of the account to be frozen. @@ -75,10 +73,10 @@ def set_account_id(self, account_id: AccountId) -> "TokenFreezeTransaction": def _build_proto_body(self) -> token_freeze_account_pb2.TokenFreezeAccountTransactionBody: """ Returns the protobuf body for the token freeze transaction. - + Returns: TokenFreezeAccountTransactionBody: The protobuf body for this transaction. - + Raises: ValueError: If the token ID or account ID is missing. """ @@ -89,10 +87,9 @@ def _build_proto_body(self) -> token_freeze_account_pb2.TokenFreezeAccountTransa raise ValueError("Missing required AccountID.") return token_freeze_account_pb2.TokenFreezeAccountTransactionBody( - token=self.token_id._to_proto(), - account=self.account_id._to_proto() + token=self.token_id._to_proto(), account=self.account_id._to_proto() ) - + def build_transaction_body(self) -> transaction_pb2.TransactionBody: """ Builds and returns the protobuf transaction body for token freeze. @@ -104,7 +101,7 @@ def build_transaction_body(self) -> transaction_pb2.TransactionBody: transaction_body: transaction_pb2.TransactionBody = self.build_base_transaction_body() transaction_body.tokenFreeze.CopyFrom(token_freeze_body) return transaction_body - + def build_scheduled_body(self) -> SchedulableTransactionBody: """ Builds the scheduled transaction body for this token freeze transaction. @@ -117,9 +114,5 @@ def build_scheduled_body(self) -> SchedulableTransactionBody: schedulable_body.tokenFreeze.CopyFrom(token_freeze_body) return schedulable_body - def _get_method(self, channel: _Channel) -> _Method: - return _Method( - transaction_func=channel.token.freezeTokenAccount, - query_func=None - ) + return _Method(transaction_func=channel.token.freezeTokenAccount, query_func=None) diff --git a/src/hiero_sdk_python/tokens/token_grant_kyc_transaction.py b/src/hiero_sdk_python/tokens/token_grant_kyc_transaction.py index fd88e13dc..4e71bfa87 100644 --- a/src/hiero_sdk_python/tokens/token_grant_kyc_transaction.py +++ b/src/hiero_sdk_python/tokens/token_grant_kyc_transaction.py @@ -1,37 +1,36 @@ """ -hiero_sdk_python.tokens.token_grant_kyc_transaction.py +hiero_sdk_python.tokens.token_grant_kyc_transaction.py. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Provides TokenGrantKycTransaction, a subclass of Transaction for granting KYC status to accounts for specific tokens on the Hedera network via the Hedera Token Service (HTS) API. """ -from typing import Optional -from hiero_sdk_python.hapi.services.token_grant_kyc_pb2 import TokenGrantKycTransactionBody +from __future__ import annotations + +from hiero_sdk_python.account.account_id import AccountId +from hiero_sdk_python.channels import _Channel +from hiero_sdk_python.executable import _Method +from hiero_sdk_python.hapi.services import token_grant_kyc_pb2, transaction_pb2 from hiero_sdk_python.hapi.services.schedulable_transaction_body_pb2 import ( SchedulableTransactionBody, ) -from hiero_sdk_python.hapi.services import token_grant_kyc_pb2, transaction_pb2 -from hiero_sdk_python.transaction.transaction import Transaction -from hiero_sdk_python.channels import _Channel -from hiero_sdk_python.executable import _Method +from hiero_sdk_python.hapi.services.token_grant_kyc_pb2 import TokenGrantKycTransactionBody from hiero_sdk_python.tokens.token_id import TokenId -from hiero_sdk_python.account.account_id import AccountId +from hiero_sdk_python.transaction.transaction import Transaction + class TokenGrantKycTransaction(Transaction): """ Represents a token grant KYC transaction on the network. - + This transaction grants KYC (Know Your Customer) status to an account for a specific token. - + Inherits from the base Transaction class and implements the required methods to build and execute a token grant KYC transaction. """ - def __init__( - self, - token_id: Optional[TokenId] = None, - account_id: Optional[AccountId] = None - ) -> None: + + def __init__(self, token_id: TokenId | None = None, account_id: AccountId | None = None) -> None: """ Initializes a new TokenGrantKycTransaction instance with the token ID and account ID. @@ -40,10 +39,10 @@ def __init__( account_id (AccountId, optional): The ID of the account to grant KYC to. """ super().__init__() - self.token_id: Optional[TokenId] = token_id - self.account_id: Optional[AccountId] = account_id + self.token_id: TokenId | None = token_id + self.account_id: AccountId | None = account_id - def set_token_id(self, token_id: TokenId) -> "TokenGrantKycTransaction": + def set_token_id(self, token_id: TokenId) -> TokenGrantKycTransaction: """ Sets the token ID for this grant KYC transaction. @@ -57,7 +56,7 @@ def set_token_id(self, token_id: TokenId) -> "TokenGrantKycTransaction": self.token_id = token_id return self - def set_account_id(self, account_id: AccountId) -> "TokenGrantKycTransaction": + def set_account_id(self, account_id: AccountId) -> TokenGrantKycTransaction: """ Sets the account ID for this grant KYC transaction. @@ -74,10 +73,10 @@ def set_account_id(self, account_id: AccountId) -> "TokenGrantKycTransaction": def _build_proto_body(self) -> token_grant_kyc_pb2.TokenGrantKycTransactionBody: """ Returns the protobuf body for the token grant KYC transaction. - + Returns: TokenGrantKycTransactionBody: The protobuf body for this transaction. - + Raises: ValueError: If the token ID or account ID is not set. """ @@ -87,11 +86,8 @@ def _build_proto_body(self) -> token_grant_kyc_pb2.TokenGrantKycTransactionBody: if self.account_id is None: raise ValueError("Missing account ID") - return TokenGrantKycTransactionBody( - token=self.token_id._to_proto(), - account=self.account_id._to_proto() - ) - + return TokenGrantKycTransactionBody(token=self.token_id._to_proto(), account=self.account_id._to_proto()) + def build_transaction_body(self) -> transaction_pb2.TransactionBody: """ Builds the transaction body for this token grant KYC transaction. @@ -103,7 +99,7 @@ def build_transaction_body(self) -> transaction_pb2.TransactionBody: transaction_body: transaction_pb2.TransactionBody = self.build_base_transaction_body() transaction_body.tokenGrantKyc.CopyFrom(token_grant_kyc_body) return transaction_body - + def build_scheduled_body(self) -> SchedulableTransactionBody: """ Builds the scheduled transaction body for this token grant KYC transaction. @@ -125,24 +121,18 @@ def _get_method(self, channel: _Channel) -> _Method: Args: channel (_Channel): The channel containing service stubs - + Returns: _Method: An object containing the transaction function to grant KYC. """ - return _Method( - transaction_func=channel.token.grantKycToTokenAccount, - query_func=None - ) + return _Method(transaction_func=channel.token.grantKycToTokenAccount, query_func=None) - def _from_proto( - self, - proto: token_grant_kyc_pb2.TokenGrantKycTransactionBody - ) -> "TokenGrantKycTransaction": + def _from_proto(self, proto: token_grant_kyc_pb2.TokenGrantKycTransactionBody) -> TokenGrantKycTransaction: """ Initializes a new TokenGrantKycTransaction instance from a protobuf object. Args: - proto (token_grant_kyc_pb2.TokenGrantKycTransactionBody): + proto (token_grant_kyc_pb2.TokenGrantKycTransactionBody): The protobuf object to initialize from. Returns: diff --git a/src/hiero_sdk_python/tokens/token_id.py b/src/hiero_sdk_python/tokens/token_id.py index 39ca06d9c..308426fda 100644 --- a/src/hiero_sdk_python/tokens/token_id.py +++ b/src/hiero_sdk_python/tokens/token_id.py @@ -1,22 +1,25 @@ """ -hiero_sdk_python.tokens.token_id.py +hiero_sdk_python.tokens.token_id.py. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Defines TokenId, a frozen dataclass for representing Hedera token identifiers (shard, realm, num) with validation and protobuf conversion utilities. """ + +from __future__ import annotations + from dataclasses import dataclass, field -from typing import Optional -from hiero_sdk_python.hapi.services import basic_types_pb2 from hiero_sdk_python.client.client import Client +from hiero_sdk_python.hapi.services import basic_types_pb2 from hiero_sdk_python.utils.entity_id_helper import ( + format_to_string, + format_to_string_with_checksum, parse_from_string, validate_checksum, - format_to_string, - format_to_string_with_checksum ) + @dataclass(frozen=True, eq=True, init=True, repr=True) class TokenId: """Represents an immutable Hedera token identifier (shard, realm, num). @@ -32,6 +35,7 @@ class TokenId: when parsing from a string with a checksum. Not directly initializable. """ + shard: int realm: int num: int @@ -45,18 +49,18 @@ def __post_init__(self) -> None: ValueError: If shard, realm, or num is less than 0. """ if self.shard < 0: - raise ValueError('Shard must be >= 0') + raise ValueError("Shard must be >= 0") if self.realm < 0: - raise ValueError('Realm must be >= 0') + raise ValueError("Realm must be >= 0") if self.num < 0: - raise ValueError('Num must be >= 0') + raise ValueError("Num must be >= 0") @classmethod - def _from_proto(cls, token_id_proto: Optional[basic_types_pb2.TokenID] = None) -> "TokenId": + def _from_proto(cls, token_id_proto: basic_types_pb2.TokenID) -> TokenId: """Creates a TokenId instance from a protobuf TokenID object. Args: - token_id_proto (Optional[basic_types_pb2.TokenID]): The protobuf + token_id_proto (basic_types_pb2.TokenID): The protobuf TokenID object. Returns: @@ -66,13 +70,9 @@ def _from_proto(cls, token_id_proto: Optional[basic_types_pb2.TokenID] = None) - ValueError: If token_id_proto is None. """ if token_id_proto is None: - raise ValueError('TokenId is required') + raise ValueError("TokenId is required") - return cls( - shard=token_id_proto.shardNum, - realm=token_id_proto.realmNum, - num=token_id_proto.tokenNum - ) + return cls(shard=token_id_proto.shardNum, realm=token_id_proto.realmNum, num=token_id_proto.tokenNum) def _to_proto(self) -> basic_types_pb2.TokenID: """Converts the TokenId instance to a protobuf TokenID object. @@ -87,14 +87,14 @@ def _to_proto(self) -> basic_types_pb2.TokenID: return token_id_proto @classmethod - def from_string(cls, token_id_str: Optional[str] = None) -> "TokenId": + def from_string(cls, token_id_str: str) -> TokenId: """Parses a string to create a TokenId instance. The string can be in the format 'shard.realm.num' or 'shard.realm.num-checksum'. Args: - token_id_str (Optional[str]): The token ID string to parse. + token_id_str (str): The token ID string to parse. Returns: TokenId: The corresponding TokenId instance. @@ -104,22 +104,17 @@ def from_string(cls, token_id_str: Optional[str] = None) -> "TokenId": invalid format. """ if token_id_str is None: - raise ValueError("token_id_str cannot be None") + raise ValueError("token_id_str cannot be None") try: shard, realm, num, checksum = parse_from_string(token_id_str) - token_id = cls( - shard=int(shard), - realm=int(realm), - num=int(num)) - object.__setattr__(token_id, 'checksum', checksum) + token_id = cls(shard=int(shard), realm=int(realm), num=int(num)) + object.__setattr__(token_id, "checksum", checksum) return token_id except Exception as e: - raise ValueError( - f"Invalid token ID string '{token_id_str}'. Expected format 'shard.realm.num'." - )from e + raise ValueError(f"Invalid token ID string '{token_id_str}'. Expected format 'shard.realm.num'.") from e def validate_checksum(self, client: Client) -> None: """Validates the checksum (if present) against the client's network. @@ -135,15 +130,9 @@ def validate_checksum(self, client: Client) -> None: expected checksum for the client's network (e.g., "Checksum mismatch for 0.0.123"). """ - validate_checksum( - shard=self.shard, - realm=self.realm, - num=self.num, - checksum=self.checksum, - client=client - ) - - def to_string_with_checksum(self, client:Client) -> str: + validate_checksum(shard=self.shard, realm=self.realm, num=self.num, checksum=self.checksum, client=client) + + def to_string_with_checksum(self, client: Client) -> str: """Returns the string representation with a network-specific checksum. Generates a checksum based on the client's network and returns @@ -156,12 +145,7 @@ def to_string_with_checksum(self, client:Client) -> str: Returns: str: The token ID string with a calculated checksum. """ - return format_to_string_with_checksum( - shard=self.shard, - realm=self.realm, - num=self.num, - client=client - ) + return format_to_string_with_checksum(shard=self.shard, realm=self.realm, num=self.num, client=client) def __str__(self) -> str: """Returns the simple string representation 'shard.realm.num'. @@ -170,7 +154,7 @@ def __str__(self) -> str: str: The token ID string in 'shard.realm.num' format. """ return format_to_string(self.shard, self.realm, self.num) - + def __repr__(self) -> str: """ Returns a detailed representation of the TokenId suitable for debugging. diff --git a/src/hiero_sdk_python/tokens/token_info.py b/src/hiero_sdk_python/tokens/token_info.py index 557700256..aef332050 100644 --- a/src/hiero_sdk_python/tokens/token_info.py +++ b/src/hiero_sdk_python/tokens/token_info.py @@ -1,74 +1,68 @@ # pylint: disable=C901 # pylint: disable=too-many-arguments """ -hiero_sdk_python.tokens.token_info.py +hiero_sdk_python.tokens.token_info.py. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Provides TokenInfo, a dataclass representing Hedera token metadata (IDs, keys, statuses, supply details, and timing), with conversion to and from protobuf messages. """ +from __future__ import annotations + from dataclasses import dataclass, field -from typing import Optional, Any, List +from typing import Any -from hiero_sdk_python.tokens.token_id import TokenId from hiero_sdk_python.account.account_id import AccountId from hiero_sdk_python.crypto.public_key import PublicKey from hiero_sdk_python.Duration import Duration +from hiero_sdk_python.hapi.services import token_get_info_pb2 as hapi_pb from hiero_sdk_python.timestamp import Timestamp +from hiero_sdk_python.tokens.custom_fee import CustomFee +from hiero_sdk_python.tokens.custom_fixed_fee import CustomFixedFee +from hiero_sdk_python.tokens.custom_fractional_fee import CustomFractionalFee +from hiero_sdk_python.tokens.custom_royalty_fee import CustomRoyaltyFee from hiero_sdk_python.tokens.supply_type import SupplyType +from hiero_sdk_python.tokens.token_freeze_status import TokenFreezeStatus +from hiero_sdk_python.tokens.token_id import TokenId from hiero_sdk_python.tokens.token_kyc_status import TokenKycStatus from hiero_sdk_python.tokens.token_pause_status import TokenPauseStatus -from hiero_sdk_python.tokens.token_freeze_status import TokenFreezeStatus from hiero_sdk_python.tokens.token_type import TokenType -from hiero_sdk_python.tokens.custom_fixed_fee import CustomFixedFee -from hiero_sdk_python.tokens.custom_fractional_fee import CustomFractionalFee -from hiero_sdk_python.tokens.custom_royalty_fee import CustomRoyaltyFee -from hiero_sdk_python.tokens.custom_fee import CustomFee -from hiero_sdk_python.hapi.services import token_get_info_pb2 as hapi_pb @dataclass(frozen=True) class TokenInfo: """Data class for basic token details: ID, name, and symbol.""" - token_id: Optional[TokenId] = None - name: Optional[str] = None - symbol: Optional[str] = None - decimals: Optional[int] = None - total_supply: Optional[int] = None - treasury: Optional[AccountId] = None - is_deleted: Optional[bool] = None - memo: Optional[str] = None - token_type: Optional[TokenType] = None - max_supply: Optional[int] = None - ledger_id: Optional[bytes] = None - metadata: Optional[bytes] = None - custom_fees: List[Any] = field(default_factory=list) - - admin_key: Optional[PublicKey] = None - kyc_key: Optional[PublicKey] = None - freeze_key: Optional[PublicKey] = None - wipe_key: Optional[PublicKey] = None - supply_key: Optional[PublicKey] = None - metadata_key: Optional[PublicKey] = None - fee_schedule_key: Optional[PublicKey] = None - default_freeze_status: TokenFreezeStatus = field( - default_factory=lambda: TokenFreezeStatus.FREEZE_NOT_APPLICABLE - ) - default_kyc_status: TokenKycStatus = field( - default_factory=lambda: TokenKycStatus.KYC_NOT_APPLICABLE - ) - auto_renew_account: Optional[AccountId] = None - auto_renew_period: Optional[Duration] = None - expiry: Optional[Timestamp] = None - pause_key: Optional[PublicKey] = None - pause_status: TokenPauseStatus = field( - default_factory=lambda: TokenPauseStatus.PAUSE_NOT_APPLICABLE - ) - supply_type: SupplyType = field( - default_factory=lambda: SupplyType.FINITE - ) + token_id: TokenId | None = None + name: str | None = None + symbol: str | None = None + decimals: int | None = None + total_supply: int | None = None + treasury: AccountId | None = None + is_deleted: bool | None = None + memo: str | None = None + token_type: TokenType | None = None + max_supply: int | None = None + ledger_id: bytes | None = None + metadata: bytes | None = None + custom_fees: list[Any] = field(default_factory=list) + + admin_key: PublicKey | None = None + kyc_key: PublicKey | None = None + freeze_key: PublicKey | None = None + wipe_key: PublicKey | None = None + supply_key: PublicKey | None = None + metadata_key: PublicKey | None = None + fee_schedule_key: PublicKey | None = None + default_freeze_status: TokenFreezeStatus = field(default_factory=lambda: TokenFreezeStatus.FREEZE_NOT_APPLICABLE) + default_kyc_status: TokenKycStatus = field(default_factory=lambda: TokenKycStatus.KYC_NOT_APPLICABLE) + auto_renew_account: AccountId | None = None + auto_renew_period: Duration | None = None + expiry: Timestamp | None = None + pause_key: PublicKey | None = None + pause_status: TokenPauseStatus = field(default_factory=lambda: TokenPauseStatus.PAUSE_NOT_APPLICABLE) + supply_type: SupplyType = field(default_factory=lambda: SupplyType.FINITE) @staticmethod def _get(proto_obj, *names): @@ -79,23 +73,21 @@ def _get(proto_obj, *names): return None @staticmethod - def _public_key_from_oneof(key_msg) -> Optional[PublicKey]: - """ - Extract a PublicKey from a key oneof, or None if not present. - """ + def _public_key_from_oneof(key_msg) -> PublicKey | None: + """Extract a PublicKey from a key oneof, or None if not present.""" if key_msg is not None and hasattr(key_msg, "WhichOneof") and key_msg.WhichOneof("key"): return PublicKey._from_proto(key_msg) return None # === conversions === @classmethod - def _from_proto(cls, proto_obj: hapi_pb.TokenInfo) -> "TokenInfo": + def _from_proto(cls, proto_obj: hapi_pb.TokenInfo) -> TokenInfo: """ Creates a TokenInfo instance from a protobuf TokenInfo object. :param proto_obj: The token_get_info_pb2.TokenInfo object. :return: An instance of TokenInfo. """ - kwargs: Dict[str, Any] = { + kwargs: dict[str, Any] = { "token_id": TokenId._from_proto(proto_obj.tokenId), "name": proto_obj.name, "symbol": proto_obj.symbol, @@ -112,14 +104,14 @@ def _from_proto(cls, proto_obj: hapi_pb.TokenInfo) -> "TokenInfo": } key_sources = [ - (("adminKey",), "admin_key"), - (("kycKey",), "kyc_key"), - (("freezeKey",), "freeze_key"), - (("wipeKey",), "wipe_key"), - (("supplyKey",), "supply_key"), - (("metadataKey", "metadata_key"), "metadata_key"), - (("feeScheduleKey", "fee_schedule_key"),"fee_schedule_key"), - (("pauseKey", "pause_key"), "pause_key"), + (("adminKey",), "admin_key"), + (("kycKey",), "kyc_key"), + (("freezeKey",), "freeze_key"), + (("wipeKey",), "wipe_key"), + (("supplyKey",), "supply_key"), + (("metadataKey", "metadata_key"), "metadata_key"), + (("feeScheduleKey", "fee_schedule_key"), "fee_schedule_key"), + (("pauseKey", "pause_key"), "pause_key"), ] for names, attr_name in key_sources: key_msg = cls._get(proto_obj, *names) @@ -129,12 +121,12 @@ def _from_proto(cls, proto_obj: hapi_pb.TokenInfo) -> "TokenInfo": conv_map = [ (("defaultFreezeStatus",), "default_freeze_status", TokenFreezeStatus._from_proto), - (("defaultKycStatus",), "default_kyc_status", TokenKycStatus._from_proto), - (("autoRenewAccount",), "auto_renew_account", AccountId._from_proto), - (("autoRenewPeriod",), "auto_renew_period", Duration._from_proto), - (("expiry",), "expiry", Timestamp._from_protobuf), - (("pauseStatus", "pause_status"), "pause_status", TokenPauseStatus._from_proto), - (("supplyType",), "supply_type", SupplyType), + (("defaultKycStatus",), "default_kyc_status", TokenKycStatus._from_proto), + (("autoRenewAccount",), "auto_renew_account", AccountId._from_proto), + (("autoRenewPeriod",), "auto_renew_period", Duration._from_proto), + (("expiry",), "expiry", Timestamp._from_protobuf), + (("pauseStatus", "pause_status"), "pause_status", TokenPauseStatus._from_proto), + (("supplyType",), "supply_type", SupplyType), ] for names, attr_name, conv in conv_map: @@ -146,8 +138,8 @@ def _from_proto(cls, proto_obj: hapi_pb.TokenInfo) -> "TokenInfo": # === helpers === @staticmethod - def _parse_custom_fees(proto_obj) -> List[CustomFee]: - out: List[CustomFee] = [] + def _parse_custom_fees(proto_obj) -> list[CustomFee]: + out: list[CustomFee] = [] for fee_proto in getattr(proto_obj, "custom_fees", []): # snake_case matches your generated proto if fee_proto.HasField("fixed_fee"): out.append(CustomFixedFee._from_proto(fee_proto)) @@ -169,7 +161,7 @@ def _copy_msg_to_proto(src_obj, dst_proto, src_attr: str, dst_field: str) -> Non getattr(dst_proto, dst_field).CopyFrom(to_fn()) @staticmethod - def _set_bool(dst_proto, field_name: str, value: Optional[bool], default: bool = False) -> None: + def _set_bool(dst_proto, field_name: str, value: bool | None, default: bool = False) -> None: """Assign a boolean, using default when value is None.""" setattr(dst_proto, field_name, default if value is None else bool(value)) @@ -179,15 +171,13 @@ def _set_enum(dst_proto, field_name: str, enum_val) -> None: setattr(dst_proto, field_name, (enum_val.value if enum_val is not None else 0)) @staticmethod - def _append_custom_fees(dst_proto, fees: Optional[List[Any]]) -> None: + def _append_custom_fees(dst_proto, fees: list[Any] | None) -> None: """Append serialized custom fees if any.""" if fees: dst_proto.custom_fees.extend(fee._to_proto() for fee in fees) def _to_proto(self) -> hapi_pb.TokenInfo: - """ - Converts the TokenInfo instance to a protobuf TokenInfo object. - """ + """Converts the TokenInfo instance to a protobuf TokenInfo object.""" proto: hapi_pb.TokenInfo = hapi_pb.TokenInfo( tokenId=self.token_id._to_proto() if self.token_id else None, name=self.name, @@ -207,17 +197,17 @@ def _to_proto(self) -> hapi_pb.TokenInfo: self._append_custom_fees(proto, self.custom_fees) msg_fields = [ - ("admin_key", "adminKey"), - ("kyc_key", "kycKey"), - ("freeze_key", "freezeKey"), - ("wipe_key", "wipeKey"), - ("supply_key", "supplyKey"), - ("metadata_key", "metadata_key"), - ("fee_schedule_key", "fee_schedule_key"), - ("pause_key", "pause_key"), + ("admin_key", "adminKey"), + ("kyc_key", "kycKey"), + ("freeze_key", "freezeKey"), + ("wipe_key", "wipeKey"), + ("supply_key", "supplyKey"), + ("metadata_key", "metadata_key"), + ("fee_schedule_key", "fee_schedule_key"), + ("pause_key", "pause_key"), ("auto_renew_account", "autoRenewAccount"), - ("auto_renew_period", "autoRenewPeriod"), - ("expiry", "expiry"), + ("auto_renew_period", "autoRenewPeriod"), + ("expiry", "expiry"), ] for src_attr, dst_field in msg_fields: self._copy_msg_to_proto(self, proto, src_attr, dst_field) diff --git a/src/hiero_sdk_python/tokens/token_key_validation.py b/src/hiero_sdk_python/tokens/token_key_validation.py index 22074118e..bf5a26691 100644 --- a/src/hiero_sdk_python/tokens/token_key_validation.py +++ b/src/hiero_sdk_python/tokens/token_key_validation.py @@ -1,26 +1,32 @@ """ -hiero_sdk_python.tokens.token_key_validation.py +hiero_sdk_python.tokens.token_key_validation.py. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Defines TokenKeyValidation enum to control whether token key validation checks are performed during Hedera transaction processing. """ + +from __future__ import annotations + from enum import Enum from typing import Any + from hiero_sdk_python.hapi.services import basic_types_pb2 + class TokenKeyValidation(Enum): """ Enum for token key validation modes: - • FULL_VALIDATION – perform all validation checks - • NO_VALIDATION – skip validation checks + • FULL_VALIDATION - perform all validation checks + • NO_VALIDATION - skip validation checks """ + FULL_VALIDATION = 0 NO_VALIDATION = 1 @staticmethod - def _from_proto(proto_obj: basic_types_pb2.TokenKeyValidation) -> "TokenKeyValidation": + def _from_proto(proto_obj: basic_types_pb2.TokenKeyValidation) -> TokenKeyValidation: """Converts a proto TokenKeyValidation object to a TokenKeyValidation enum.""" if proto_obj == basic_types_pb2.TokenKeyValidation.FULL_VALIDATION: return TokenKeyValidation.FULL_VALIDATION diff --git a/src/hiero_sdk_python/tokens/token_kyc_status.py b/src/hiero_sdk_python/tokens/token_kyc_status.py index 0907e3ff4..ff4330659 100644 --- a/src/hiero_sdk_python/tokens/token_kyc_status.py +++ b/src/hiero_sdk_python/tokens/token_kyc_status.py @@ -1,28 +1,34 @@ """ -hiero_sdk_python.tokens.token_kyc_status.py +hiero_sdk_python.tokens.token_kyc_status.py. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Defines TokenKycStatus enum to represent Know-Your-Customer (KYC) status: not applicable, granted, or revoked. """ + +from __future__ import annotations + from enum import Enum from typing import Any + from hiero_sdk_python.hapi.services import basic_types_pb2 + class TokenKycStatus(Enum): """ KYC (Know Your Customer) status indicator: - • KYC_NOT_APPLICABLE – not applicable - • GRANTED – KYC granted - • REVOKED – KYC revoked + • KYC_NOT_APPLICABLE - not applicable + • GRANTED - KYC granted + • REVOKED - KYC revoked """ + KYC_NOT_APPLICABLE = 0 GRANTED = 1 REVOKED = 2 @staticmethod - def _from_proto(proto_obj: basic_types_pb2.TokenKycStatus) -> "TokenKycStatus": + def _from_proto(proto_obj: basic_types_pb2.TokenKycStatus) -> TokenKycStatus: """Converts a proto TokenKycStatus object to a TokenKycStatus enum.""" if proto_obj == basic_types_pb2.TokenKycStatus.KycNotApplicable: return TokenKycStatus.KYC_NOT_APPLICABLE diff --git a/src/hiero_sdk_python/tokens/token_mint_transaction.py b/src/hiero_sdk_python/tokens/token_mint_transaction.py index c3c3c0d59..e265c66a4 100644 --- a/src/hiero_sdk_python/tokens/token_mint_transaction.py +++ b/src/hiero_sdk_python/tokens/token_mint_transaction.py @@ -1,37 +1,35 @@ """ -hiero_sdk_python.tokens.token_mint_transaction.py +hiero_sdk_python.tokens.token_mint_transaction.py. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Provides TokenMintTransaction, a subclass of Transaction for minting fungible and non-fungible tokens on the Hedera network via the Hedera Token Service (HTS) API. """ -from typing import List, Optional, Union -from hiero_sdk_python.transaction.transaction import Transaction -from hiero_sdk_python.hapi.services.token_mint_pb2 import TokenMintTransactionBody +from __future__ import annotations + +from hiero_sdk_python.channels import _Channel +from hiero_sdk_python.executable import _Method from hiero_sdk_python.hapi.services import token_mint_pb2, transaction_pb2 from hiero_sdk_python.hapi.services.schedulable_transaction_body_pb2 import ( SchedulableTransactionBody, ) -from hiero_sdk_python.channels import _Channel -from hiero_sdk_python.executable import _Method from hiero_sdk_python.tokens.token_id import TokenId +from hiero_sdk_python.transaction.transaction import Transaction + class TokenMintTransaction(Transaction): """ Represents a token minting transaction on the Hedera network. Transaction mints tokens (fungible or non-fungible) to the token treasury. - + Inherits from the base Transaction class and implements the required methods to build and execute a token minting transaction. """ def __init__( - self, - token_id: Optional[TokenId] = None, - amount: Optional[int] = None, - metadata: Optional[Union[bytes, List[bytes]]] = None + self, token_id: TokenId | None = None, amount: int | None = None, metadata: bytes | list[bytes] | None = None ) -> None: """ Initializes a new TokenMintTransaction Custom instance with optional keyword arguments. @@ -39,24 +37,25 @@ def __init__( Args: token_id (TokenId, optional): The ID of the token to mint. amount (int, optional): The amount of a fungible token to mint. - metadata (Union[bytes, List[bytes]], optional): The non-fungible token metadata to mint. + metadata (bytes | list[bytes, optional): The non-fungible token metadata to mint. If a single bytes object is passed, it will be converted internally to [bytes]. """ - super().__init__() - self.token_id: Optional[TokenId] = token_id - self.amount: Optional[int] = amount - self.metadata: Optional[Union[bytes,List[bytes]]] = None + self.token_id: TokenId | None = token_id + self.amount: int | None = amount + self.metadata: bytes | list[bytes] | None = None if metadata is not None: self.set_metadata(metadata) self._default_transaction_fee: int = 3_000_000_000 - def set_token_id(self, token_id: TokenId) -> "TokenMintTransaction": + def set_token_id(self, token_id: TokenId) -> TokenMintTransaction: """ Sets the ID of the token to be minted. + Args: token_id (TokenId): The ID of the token to be minted. + Returns: TokenMintTransaction: Returns self for method chaining. """ @@ -64,11 +63,13 @@ def set_token_id(self, token_id: TokenId) -> "TokenMintTransaction": self.token_id = token_id return self - def set_amount(self, amount: int) -> "TokenMintTransaction": + def set_amount(self, amount: int) -> TokenMintTransaction: """ Sets the amount of fungible tokens to mint. + Args: amount (int): The amount of fungible tokens to mint. + Returns: TokenMintTransaction: Returns self for method chaining. """ @@ -76,12 +77,14 @@ def set_amount(self, amount: int) -> "TokenMintTransaction": self.amount = amount return self - def set_metadata(self, metadata: Union[bytes, List[bytes]]) -> "TokenMintTransaction": + def set_metadata(self, metadata: bytes | list[bytes]) -> TokenMintTransaction: """ Sets the metadata for non-fungible tokens to mint. + Args: - metadata (Union[bytes, List[bytes]]): The metadata for non-fungible tokens to mint. + metadata (bytes | list[bytes]): The metadata for non-fungible tokens to mint. If a single bytes object is passed, it will be converted internally to [bytes]. + Returns: TokenMintTransaction: Returns self for method chaining. """ @@ -92,17 +95,12 @@ def set_metadata(self, metadata: Union[bytes, List[bytes]]) -> "TokenMintTransac return self def _validate_parameters(self): - """ - Validates the parameters for the token mint transaction. - """ + """Validates the parameters for the token mint transaction.""" if self.token_id is None: raise ValueError("Token ID is required for minting.") if (self.amount is not None) and (self.metadata is not None): - raise ValueError( - "Specify either amount for fungible tokens or metadata " - "for NFTs, not both." - ) + raise ValueError("Specify either amount for fungible tokens or metadata for NFTs, not both.") def _build_proto_body(self) -> token_mint_pb2.TokenMintTransactionBody: """ @@ -130,21 +128,16 @@ def _build_proto_body(self) -> token_mint_pb2.TokenMintTransactionBody: if not self.metadata: raise ValueError("Metadata list cannot be empty for NFTs.") return token_mint_pb2.TokenMintTransactionBody( - token=self.token_id._to_proto(), - amount=0, - metadata=self.metadata + token=self.token_id._to_proto(), amount=0, metadata=self.metadata ) # Neither amount nor metadata is set - raise ValueError( - "Specify either amount for fungible tokens or metadata for NFTs." - ) + raise ValueError("Specify either amount for fungible tokens or metadata for NFTs.") - def build_transaction_body(self) -> transaction_pb2.TransactionBody: """ Builds and returns the protobuf transaction body for token minting. - + Returns: TransactionBody: The protobuf transaction body containing the token minting details. """ @@ -152,7 +145,7 @@ def build_transaction_body(self) -> transaction_pb2.TransactionBody: transaction_body = self.build_base_transaction_body() transaction_body.tokenMint.CopyFrom(token_mint_body) return transaction_body - + def build_scheduled_body(self) -> SchedulableTransactionBody: """ Builds the scheduled transaction body for this token mint transaction. @@ -166,7 +159,4 @@ def build_scheduled_body(self) -> SchedulableTransactionBody: return schedulable_body def _get_method(self, channel: _Channel) -> _Method: - return _Method( - transaction_func=channel.token.mintToken, - query_func=None - ) + return _Method(transaction_func=channel.token.mintToken, query_func=None) diff --git a/src/hiero_sdk_python/tokens/token_nft_allowance.py b/src/hiero_sdk_python/tokens/token_nft_allowance.py index db1f22246..31c449f75 100644 --- a/src/hiero_sdk_python/tokens/token_nft_allowance.py +++ b/src/hiero_sdk_python/tokens/token_nft_allowance.py @@ -1,9 +1,10 @@ -""" -TokenNftAllowance class for handling NFT token allowances. -""" +"""TokenNftAllowance class for handling NFT token allowances.""" +from __future__ import annotations + +from collections.abc import Callable from dataclasses import dataclass, field -from typing import Any, Callable, List, Optional +from typing import Any from google.protobuf.wrappers_pb2 import BoolValue @@ -27,23 +28,23 @@ class TokenNftAllowance: tokens flag, and optional delegating spender. Attributes: - token_id (Optional[TokenId]): The ID of the NFT token. - owner_account_id (Optional[AccountId]): The account that owns the NFTs. - spender_account_id (Optional[AccountId]): The account permitted to transfer the NFTs. - serial_numbers (List[int]): List of specific NFT serial numbers allowed for transfer. - approved_for_all (Optional[bool]): Whether the spender can transfer all NFTs of this type. - delegating_spender (Optional[AccountId]): Account that can delegate NFT transfers. + token_id (TokenId, optional): The ID of the NFT token. + owner_account_id (AccountId, optional): The account that owns the NFTs. + spender_account_id (AccountId, optional): The account permitted to transfer the NFTs. + serial_numbers (list[int], optional): List of specific NFT serial numbers allowed for transfer. + approved_for_all (bool, optional): Whether the spender can transfer all NFTs of this type. + delegating_spender (AccountId, optional): Account that can delegate NFT transfers. """ - token_id: Optional[TokenId] = None - owner_account_id: Optional[AccountId] = None - spender_account_id: Optional[AccountId] = None - serial_numbers: List[int] = field(default_factory=list) - approved_for_all: Optional[bool] = None - delegating_spender: Optional[AccountId] = None + token_id: TokenId | None = None + owner_account_id: AccountId | None = None + spender_account_id: AccountId | None = None + serial_numbers: list[int] = field(default_factory=list) + approved_for_all: bool | None = None + delegating_spender: AccountId | None = None @classmethod - def _from_proto(cls, proto: NftAllowanceProto) -> "TokenNftAllowance": + def _from_proto(cls, proto: NftAllowanceProto) -> TokenNftAllowance: """ Creates a TokenNftAllowance instance from its protobuf representation. @@ -64,16 +65,12 @@ def _from_proto(cls, proto: NftAllowanceProto) -> "TokenNftAllowance": owner_account_id=(cls._from_proto_field(proto, "owner", AccountId._from_proto)), spender_account_id=(cls._from_proto_field(proto, "spender", AccountId._from_proto)), serial_numbers=list(proto.serial_numbers), - approved_for_all=( - proto.approved_for_all.value if proto.HasField("approved_for_all") else False - ), - delegating_spender=( - cls._from_proto_field(proto, "delegating_spender", AccountId._from_proto) - ), + approved_for_all=(proto.approved_for_all.value if proto.HasField("approved_for_all") else False), + delegating_spender=(cls._from_proto_field(proto, "delegating_spender", AccountId._from_proto)), ) @classmethod - def _from_wipe_proto(cls, proto: NftRemoveAllowanceProto) -> "TokenNftAllowance": + def _from_wipe_proto(cls, proto: NftRemoveAllowanceProto) -> TokenNftAllowance: """ Creates a TokenNftAllowance instance from an NftRemoveAllowance protobuf. @@ -153,9 +150,7 @@ def __str__(self) -> str: spender_str = str(self.spender_account_id) if self.spender_account_id else "None" token_str = str(self.token_id) if self.token_id else "None" serials_str = str(self.serial_numbers) if self.serial_numbers else "[]" - delegating_str = ( - f", delegating_spender={self.delegating_spender}" if self.delegating_spender else "" - ) + delegating_str = f", delegating_spender={self.delegating_spender}" if self.delegating_spender else "" return ( f"TokenNftAllowance(" diff --git a/src/hiero_sdk_python/tokens/token_nft_info.py b/src/hiero_sdk_python/tokens/token_nft_info.py index dd8a10361..f9e4cc880 100644 --- a/src/hiero_sdk_python/tokens/token_nft_info.py +++ b/src/hiero_sdk_python/tokens/token_nft_info.py @@ -1,21 +1,25 @@ -from dataclasses import dataclass """ -hiero_sdk_python.tokens.token_nft_info.py +hiero_sdk_python.tokens.token_nft_info.py. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Defines TokenNftInfo, a class representing Non-Fungible Token details (ID, owner, creation time, metadata, spender) on the Hedera network, with protobuf conversion. """ -from typing import Optional + +from __future__ import annotations + +from dataclasses import dataclass + from hiero_sdk_python.account.account_id import AccountId -from hiero_sdk_python.tokens.nft_id import NftId from hiero_sdk_python.hapi.services import timestamp_pb2, token_get_nft_info_pb2 +from hiero_sdk_python.tokens.nft_id import NftId + @dataclass class TokenNftInfo: """ Represents information about a Non-Fungible Token (NFT) on the Hedera network. - + This dataclass encapsulates details about an NFT including its unique identifier, owner account, creation time, associated metadata, and any account with spending privileges. @@ -26,21 +30,22 @@ class TokenNftInfo: metadata (bytes, optional): The metadata associated with the NFT. spender_id (AccountId, optional): The account ID with spending privileges for this NFT """ - nft_id: Optional[NftId] = None - account_id: Optional[AccountId] = None - creation_time: Optional[int] = None - metadata: Optional[bytes] = None - spender_id: Optional[AccountId] = None + + nft_id: NftId | None = None + account_id: AccountId | None = None + creation_time: int | None = None + metadata: bytes | None = None + spender_id: AccountId | None = None @classmethod - def _from_proto(cls, proto: token_get_nft_info_pb2.TokenNftInfo) -> "TokenNftInfo": + def _from_proto(cls, proto: token_get_nft_info_pb2.TokenNftInfo) -> TokenNftInfo: """ Create a TokenNftInfo instance from a protobuf message. - + Args: - proto (token_get_nft_info_pb2.TokenNftInfo): + proto (token_get_nft_info_pb2.TokenNftInfo): The protobuf message containing NFT information. - + Returns: TokenNftInfo: A new instance populated with data from the protobuf message. """ @@ -49,15 +54,15 @@ def _from_proto(cls, proto: token_get_nft_info_pb2.TokenNftInfo) -> "TokenNftInf account_id=AccountId._from_proto(proto.accountID), creation_time=proto.creationTime.seconds, metadata=proto.metadata, - spender_id=AccountId._from_proto(proto.spender_id) + spender_id=AccountId._from_proto(proto.spender_id), ) def _to_proto(self) -> token_get_nft_info_pb2.TokenNftInfo: """ Convert this TokenNftInfo instance to a protobuf message. - + Returns: - token_get_nft_info_pb2.TokenNftInfo: + token_get_nft_info_pb2.TokenNftInfo: The protobuf representation of this NFT information. """ return token_get_nft_info_pb2.TokenNftInfo( @@ -65,19 +70,20 @@ def _to_proto(self) -> token_get_nft_info_pb2.TokenNftInfo: accountID=self.account_id._to_proto() if self.account_id else None, creationTime=timestamp_pb2.Timestamp(seconds=self.creation_time), metadata=self.metadata, - spender_id=self.spender_id._to_proto() if self.spender_id else None + spender_id=self.spender_id._to_proto() if self.spender_id else None, ) def __str__(self) -> str: """ Get a string representation of this TokenNftInfo instance. - + Returns: str: A string representation including all fields of this NFT information. """ - return (f"TokenNftInfo(nft_id={self.nft_id}, " - f"account_id={self.account_id}, " - f"creation_time={self.creation_time}, " - f"metadata={self.metadata!r}, " - f"spender_id={self.spender_id})") - + return ( + f"TokenNftInfo(nft_id={self.nft_id}, " + f"account_id={self.account_id}, " + f"creation_time={self.creation_time}, " + f"metadata={self.metadata!r}, " + f"spender_id={self.spender_id})" + ) diff --git a/src/hiero_sdk_python/tokens/token_nft_transfer.py b/src/hiero_sdk_python/tokens/token_nft_transfer.py index b7deb662d..77711005b 100644 --- a/src/hiero_sdk_python/tokens/token_nft_transfer.py +++ b/src/hiero_sdk_python/tokens/token_nft_transfer.py @@ -1,35 +1,37 @@ """ -hiero_sdk_python.tokens.token_nft_transfer.py +hiero_sdk_python.tokens.token_nft_transfer.py. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Defines TokenNftTransfer for representing and converting NFT transfer details (sender, receiver, serial number, approval) to and from protobuf messages. """ -from typing import List + +from __future__ import annotations + from hiero_sdk_python.account.account_id import AccountId from hiero_sdk_python.hapi.services import basic_types_pb2 from hiero_sdk_python.tokens.token_id import TokenId + class TokenNftTransfer: """ Represents a transfer of a non-fungible token (NFT) from one account to another. - + This class encapsulates the details of an NFT transfer, including the sender, receiver, serial number of the NFT, and whether the transfer is approved. """ - def __init__( self, token_id: TokenId, sender_id: AccountId, receiver_id: AccountId, serial_number: int, - is_approved: bool = False + is_approved: bool = False, ) -> None: """ Initializes a new TokenNftTransfer instance. - + Args: token_id (TokenId): The ID of the token being transferred. sender_id (AccountId): The account ID of the sender. @@ -38,15 +40,15 @@ def __init__( is_approved (bool, optional): Whether the transfer is approved. Defaults to False. """ self.token_id: TokenId = token_id - self.sender_id : AccountId = sender_id - self.receiver_id : AccountId = receiver_id - self.serial_number : int = serial_number - self.is_approved : bool = is_approved + self.sender_id: AccountId = sender_id + self.receiver_id: AccountId = receiver_id + self.serial_number: int = serial_number + self.is_approved: bool = is_approved def _to_proto(self) -> basic_types_pb2.NftTransfer: """ Converts this TokenNftTransfer instance to its protobuf representation. - + Returns: basic_type_pb2.NftTransfer: The protobuf representation of this NFT transfer. """ @@ -54,33 +56,27 @@ def _to_proto(self) -> basic_types_pb2.NftTransfer: senderAccountID=self.sender_id._to_proto(), receiverAccountID=self.receiver_id._to_proto(), serialNumber=self.serial_number, - is_approval=self.is_approved + is_approval=self.is_approved, ) @classmethod def _from_proto(cls, proto: basic_types_pb2.TokenTransferList): - """ - Creates a TokenNftTransfer from a protobuf representation. - """ - nftTransfers: List[TokenNftTransfer] = [] - - for nftTransfer in proto.nftTransfers: - nftTransfers.append( - cls( - token_id = TokenId._from_proto(proto.token), - sender_id=AccountId._from_proto(nftTransfer.senderAccountID), - receiver_id=AccountId._from_proto(nftTransfer.receiverAccountID), - serial_number=nftTransfer.serialNumber, - is_approved=nftTransfer.is_approval - ) + """Creates a TokenNftTransfer from a protobuf representation.""" + return [ + cls( + token_id=TokenId._from_proto(proto.token), + sender_id=AccountId._from_proto(nftTransfer.senderAccountID), + receiver_id=AccountId._from_proto(nftTransfer.receiverAccountID), + serial_number=nftTransfer.serialNumber, + is_approved=nftTransfer.is_approval, ) - - return nftTransfers + for nftTransfer in proto.nftTransfers + ] def __str__(self): """ Returns a string representation of this TokenNftTransfer instance. - + Returns: str: A string representation of this NFT transfer. """ diff --git a/src/hiero_sdk_python/tokens/token_pause_status.py b/src/hiero_sdk_python/tokens/token_pause_status.py index 61e9e3ed7..52ee79ad9 100644 --- a/src/hiero_sdk_python/tokens/token_pause_status.py +++ b/src/hiero_sdk_python/tokens/token_pause_status.py @@ -1,35 +1,44 @@ """ -hiero_sdk_python.tokens.token_pause_status.py +hiero_sdk_python.tokens.token_pause_status.py. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Defines TokenPauseStatus enum for representing token pause states: not applicable, paused, or unpaused. A Token's paused status shows whether or not a Token can be used or not in a transaction. """ + +from __future__ import annotations + from enum import Enum from typing import Any + from hiero_sdk_python.hapi.services import basic_types_pb2 + class TokenPauseStatus(Enum): """ Enumeration of token pause statuses: - • PAUSE_NOT_APPLICABLE – pause not relevant - • PAUSED – token is paused - • UNPAUSED – token is active + • PAUSE_NOT_APPLICABLE - pause not relevant + • PAUSED - token is paused + • UNPAUSED - token is active """ + PAUSE_NOT_APPLICABLE = 0 PAUSED = 1 UNPAUSED = 2 @staticmethod - def _from_proto(proto_obj: basic_types_pb2.TokenPauseStatus) -> "TokenPauseStatus": + def _from_proto(proto_obj: basic_types_pb2.TokenPauseStatus) -> TokenPauseStatus: """ Converts a protobuf TokenPauseStatus to a TokenPauseStatus enum. + Args: proto_obj (basic_types_pb2.TokenPauseStatus): The protobuf TokenPauseStatus object. + Returns: TokenPauseStatus: The corresponding TokenPauseStatus enum value. + Raises: ValueError: If the proto object does not match any known TokenPauseStatus. """ @@ -44,8 +53,10 @@ def _from_proto(proto_obj: basic_types_pb2.TokenPauseStatus) -> "TokenPauseStatu def __eq__(self, other: Any) -> bool: """ Checks equality with another TokenPauseStatus or an integer value. + Args: other (Any): The object to compare with. + Returns: bool: True if the values are equal, False otherwise. """ diff --git a/src/hiero_sdk_python/tokens/token_pause_transaction.py b/src/hiero_sdk_python/tokens/token_pause_transaction.py index f8eb8661b..a3b96b351 100644 --- a/src/hiero_sdk_python/tokens/token_pause_transaction.py +++ b/src/hiero_sdk_python/tokens/token_pause_transaction.py @@ -1,36 +1,39 @@ """ -hiero_sdk_python.tokens.token_pause_transaction.py +hiero_sdk_python.tokens.token_pause_transaction.py. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Provides TokenPauseTransaction, a subclass of Transaction for pausing a specified token on the Hedera network via the Hedera Token Service (HTS) API. """ -from typing import Optional -from hiero_sdk_python.tokens.token_id import TokenId -from hiero_sdk_python.transaction.transaction import Transaction -from hiero_sdk_python.hapi.services import transaction_pb2, token_pause_pb2 -from hiero_sdk_python.hapi.services.token_pause_pb2 import TokenPauseTransactionBody +from __future__ import annotations + +from hiero_sdk_python.channels import _Channel +from hiero_sdk_python.executable import _Method +from hiero_sdk_python.hapi.services import token_pause_pb2, transaction_pb2 from hiero_sdk_python.hapi.services.schedulable_transaction_body_pb2 import ( SchedulableTransactionBody, ) -from hiero_sdk_python.channels import _Channel -from hiero_sdk_python.executable import _Method +from hiero_sdk_python.hapi.services.token_pause_pb2 import TokenPauseTransactionBody +from hiero_sdk_python.tokens.token_id import TokenId +from hiero_sdk_python.transaction.transaction import Transaction + class TokenPauseTransaction(Transaction): """ - Represents a token pause transaction. - + Represents a token pause transaction. + A token pause transaction prevents a token from being involved in any operation. The token is required to have a pause key and the pause key must sign. - Once a token is paused, token status will update from unpaused to paused. + Once a token is paused, token status will update from unpaused to paused. Those without a pause key will state PauseNotApplicable. - + Inherits from the base Transaction class and implements the required methods to build and execute a token pause transaction. """ - def __init__(self, token_id: Optional[TokenId] = None) -> None: + + def __init__(self, token_id: TokenId | None = None) -> None: """ Initializes a new TokenPauseTransaction instance with optional token_id. @@ -38,9 +41,9 @@ def __init__(self, token_id: Optional[TokenId] = None) -> None: token_id (TokenId, optional): The ID of the token to be paused. """ super().__init__() - self.token_id: Optional[TokenId] = token_id + self.token_id: TokenId | None = token_id - def set_token_id(self, token_id: TokenId) -> "TokenPauseTransaction": + def set_token_id(self, token_id: TokenId) -> TokenPauseTransaction: """ Sets the ID of the token to be paused. @@ -57,20 +60,18 @@ def set_token_id(self, token_id: TokenId) -> "TokenPauseTransaction": def _build_proto_body(self) -> token_pause_pb2.TokenPauseTransactionBody: """ Returns the protobuf body for the token pause transaction. - + Returns: TokenPauseTransactionBody: The protobuf body for this transaction. - + Raises: ValueError: If no token_id has been set. """ if self.token_id is None or self.token_id.num == 0: raise ValueError("token_id must be set before building the transaction body") - return TokenPauseTransactionBody( - token=self.token_id._to_proto() - ) - + return TokenPauseTransactionBody(token=self.token_id._to_proto()) + def build_transaction_body(self) -> transaction_pb2.TransactionBody: """ Builds and returns the protobuf transaction body for token pause. @@ -82,7 +83,7 @@ def build_transaction_body(self) -> transaction_pb2.TransactionBody: transaction_body = self.build_base_transaction_body() transaction_body.token_pause.CopyFrom(token_pause_body) return transaction_body - + def build_scheduled_body(self) -> SchedulableTransactionBody: """ Builds the scheduled transaction body for this token pause transaction. @@ -96,15 +97,9 @@ def build_scheduled_body(self) -> SchedulableTransactionBody: return schedulable_body def _get_method(self, channel: _Channel) -> _Method: - return _Method( - transaction_func=channel.token.pauseToken, - query_func=None - ) - - def _from_proto( - self, - proto: token_pause_pb2.TokenPauseTransactionBody - ) -> "TokenPauseTransaction": + return _Method(transaction_func=channel.token.pauseToken, query_func=None) + + def _from_proto(self, proto: token_pause_pb2.TokenPauseTransactionBody) -> TokenPauseTransaction: """ Deserializes a TokenPauseTransactionBody from a protobuf object. diff --git a/src/hiero_sdk_python/tokens/token_reject_transaction.py b/src/hiero_sdk_python/tokens/token_reject_transaction.py index 59085cc91..863477947 100644 --- a/src/hiero_sdk_python/tokens/token_reject_transaction.py +++ b/src/hiero_sdk_python/tokens/token_reject_transaction.py @@ -1,42 +1,46 @@ """ -hiero_sdk_python.tokens.token_reject_transaction.py +hiero_sdk_python.tokens.token_reject_transaction.py. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Defines TokenRejectTransaction for rejecting fungible token and NFT transfers on the Hedera network via the Hedera Token Service (HTS) API. """ -from typing import Optional, List -from hiero_sdk_python.hapi.services.token_reject_pb2 import ( - TokenReference, - TokenRejectTransactionBody, -) + +from __future__ import annotations + +from hiero_sdk_python.account.account_id import AccountId +from hiero_sdk_python.channels import _Channel +from hiero_sdk_python.executable import _Method from hiero_sdk_python.hapi.services import transaction_pb2 from hiero_sdk_python.hapi.services.schedulable_transaction_body_pb2 import ( SchedulableTransactionBody, ) +from hiero_sdk_python.hapi.services.token_reject_pb2 import ( + TokenReference, + TokenRejectTransactionBody, +) from hiero_sdk_python.tokens.nft_id import NftId from hiero_sdk_python.tokens.token_id import TokenId -from hiero_sdk_python.account.account_id import AccountId from hiero_sdk_python.transaction.transaction import Transaction -from hiero_sdk_python.channels import _Channel -from hiero_sdk_python.executable import _Method + class TokenRejectTransaction(Transaction): """ Represents a token reject transaction on the Hedera network. - + This transaction rejects a token transfer. Allows users to reject and return unwanted airdrops to the treasury account without incurring custom fees. - + Inherits from the base Transaction class and implements the required methods to build and execute a token reject transaction. """ + def __init__( self, - owner_id: Optional[AccountId] = None, - token_ids: Optional[List[TokenId]] = None, - nft_ids: Optional[List[NftId]] = None + owner_id: AccountId | None = None, + token_ids: list[TokenId] | None = None, + nft_ids: list[NftId] | None = None, ) -> None: """ TokenRejectTransaction instance with optional owner_id, token_ids, and nft_ids. @@ -47,23 +51,23 @@ def __init__( nft_ids (list[NftId], optional): The IDs of the non-fungible tokens (NFTs) to reject. """ super().__init__() - self.owner_id: Optional[AccountId] = owner_id - self.token_ids: List[TokenId] = token_ids if token_ids else [] - self.nft_ids: List[NftId] = nft_ids if nft_ids else [] + self.owner_id: AccountId | None = owner_id + self.token_ids: list[TokenId] = token_ids if token_ids else [] + self.nft_ids: list[NftId] = nft_ids if nft_ids else [] - def set_owner_id(self, owner_id: AccountId) -> "TokenRejectTransaction": + def set_owner_id(self, owner_id: AccountId) -> TokenRejectTransaction: """Set the owner account ID for rejected tokens.""" self._require_not_frozen() self.owner_id = owner_id return self - def set_token_ids(self, token_ids: List[TokenId]) -> "TokenRejectTransaction": + def set_token_ids(self, token_ids: list[TokenId]) -> TokenRejectTransaction: """Set the list of fungible token IDs to reject.""" self._require_not_frozen() self.token_ids = token_ids return self - def set_nft_ids(self, nft_ids: List[NftId]) -> "TokenRejectTransaction": + def set_nft_ids(self, nft_ids: list[NftId]) -> TokenRejectTransaction: """Set the list of NFT IDs to reject.""" self._require_not_frozen() self.nft_ids = nft_ids @@ -72,21 +76,19 @@ def set_nft_ids(self, nft_ids: List[NftId]) -> "TokenRejectTransaction": def _build_proto_body(self): """ Returns the protobuf body for the token reject transaction. - + Returns: TokenRejectTransactionBody: The protobuf body for this transaction. """ - token_references: List[TokenReference] = [] - for token_id in self.token_ids: - token_references.append(TokenReference(fungible_token=token_id._to_proto())) - for nft_id in self.nft_ids: - token_references.append(TokenReference(nft=nft_id._to_proto())) + token_references: list[TokenReference] = [ + TokenReference(fungible_token=token_id._to_proto()) for token_id in self.token_ids + ] + token_references.extend(TokenReference(nft=nft_id._to_proto()) for nft_id in self.nft_ids) return TokenRejectTransactionBody( - owner=self.owner_id and self.owner_id._to_proto(), - rejections=token_references + owner=self.owner_id and self.owner_id._to_proto(), rejections=token_references ) - + def build_transaction_body(self) -> transaction_pb2.TransactionBody: """ Builds and returns the protobuf transaction body for token reject. @@ -98,7 +100,7 @@ def build_transaction_body(self) -> transaction_pb2.TransactionBody: transaction_body = self.build_base_transaction_body() transaction_body.tokenReject.CopyFrom(token_reject_body) return transaction_body - + def build_scheduled_body(self) -> SchedulableTransactionBody: """ Builds the scheduled transaction body for this token reject transaction. @@ -120,16 +122,13 @@ def _get_method(self, channel: _Channel) -> _Method: Args: channel (_Channel): The channel containing service stubs - + Returns: _Method: An object containing the transaction function to reject tokens. """ - return _Method( - transaction_func=channel.token.rejectToken, - query_func=None - ) + return _Method(transaction_func=channel.token.rejectToken, query_func=None) - def _from_proto(self, proto: TokenRejectTransactionBody) -> "TokenRejectTransaction": + def _from_proto(self, proto: TokenRejectTransactionBody) -> TokenRejectTransaction: """ Deserializes a TokenRejectTransactionBody from a protobuf object. @@ -143,16 +142,10 @@ def _from_proto(self, proto: TokenRejectTransactionBody) -> "TokenRejectTransact # Extract fungible token IDs self.token_ids = [ - TokenId._from_proto(entry.fungible_token) - for entry in proto.rejections - if entry.HasField("fungible_token") + TokenId._from_proto(entry.fungible_token) for entry in proto.rejections if entry.HasField("fungible_token") ] # Extract NFT IDs - self.nft_ids = [ - NftId._from_proto(entry.nft) - for entry in proto.rejections - if entry.HasField("nft") - ] + self.nft_ids = [NftId._from_proto(entry.nft) for entry in proto.rejections if entry.HasField("nft")] return self diff --git a/src/hiero_sdk_python/tokens/token_relationship.py b/src/hiero_sdk_python/tokens/token_relationship.py index 5a8d3151a..383d0127a 100644 --- a/src/hiero_sdk_python/tokens/token_relationship.py +++ b/src/hiero_sdk_python/tokens/token_relationship.py @@ -1,55 +1,59 @@ -"""hiero_sdk_python.tokens.token_relationship.py +"""hiero_sdk_python.tokens.token_relationship.py. Provides TokenRelationship, a dataclass modeling an account’s relationship to a token, including ID, symbol, balance, KYC status, freeze status, decimals, and auto-association flag. This class is primarily used for parsing and representing data returned by queries like CryptoGetAccountInfoQuery. """ + +from __future__ import annotations + from dataclasses import dataclass -from typing import Optional from hiero_sdk_python.hapi.services.basic_types_pb2 import ( - TokenRelationship as TokenRelationshipProto, - TokenFreezeStatus as TokenFreezeStatusProto, - TokenKycStatus as TokenKycStatusProto, + TokenFreezeStatus as TokenFreezeStatusProto, + TokenKycStatus as TokenKycStatusProto, + TokenRelationship as TokenRelationshipProto, ) from hiero_sdk_python.tokens.token_freeze_status import TokenFreezeStatus from hiero_sdk_python.tokens.token_id import TokenId from hiero_sdk_python.tokens.token_kyc_status import TokenKycStatus + @dataclass class TokenRelationship: """ Represents a relationship between an account and a token. Attributes: - token_id (Optional[TokenId]): The ID of the token. - symbol (Optional[str]): The symbol of the token. - balance (Optional[int]): The balance of tokens held by the account. - kyc_status (Optional[TokenKycStatus]): The KYC status of the account for this token. - freeze_status (Optional[TokenFreezeStatus]): + token_id (TokenId, optional): The ID of the token. + symbol (str, optional): The symbol of the token. + balance (int, optional): The balance of tokens held by the account. + kyc_status (TokenKycStatus, optional): The KYC status of the account for this token. + freeze_status (TokenFreezeStatus, optional): The freeze status of the account for this token. - decimals (Optional[int]): The number of decimal places used by the token. - automatic_association (Optional[bool]): + decimals (int, optional): The number of decimal places used by the token. + automatic_association (bool, optional): Whether the token was automatically associated with the account. """ - token_id: Optional[TokenId] = None - symbol: Optional[str] = None - balance: Optional[int] = None - kyc_status: Optional[TokenKycStatus] = None - freeze_status: Optional[TokenFreezeStatus] = None - decimals: Optional[int] = None - automatic_association: Optional[bool] = None + + token_id: TokenId | None = None + symbol: str | None = None + balance: int | None = None + kyc_status: TokenKycStatus | None = None + freeze_status: TokenFreezeStatus | None = None + decimals: int | None = None + automatic_association: bool | None = None @classmethod - def _from_proto(cls, proto: Optional[TokenRelationshipProto]) -> 'TokenRelationship': + def _from_proto(cls, proto: TokenRelationshipProto | None) -> TokenRelationship: """Creates a TokenRelationship instance from a protobuf TokenRelationship message. - Parses the protobuf fields, converting enum types (KYC, Freeze status) and + Parses the protobuf fields, converting enum types (KYC, Freeze status) and TokenId objects into their corresponding Python representations. Args: - proto (Optional[TokenRelationshipProto]): The protobuf TokenRelationship message. + proto (TokenRelationshipProto, optional): The protobuf TokenRelationship message. Returns: TokenRelationship: The corresponding Python dataclass instance. @@ -60,11 +64,7 @@ def _from_proto(cls, proto: Optional[TokenRelationshipProto]) -> 'TokenRelations if proto is None: raise ValueError("Token relationship proto is None") - token_id: Optional[TokenId] = ( - TokenId._from_proto(proto.tokenId) - if proto.tokenId - else None - ) + token_id: TokenId | None = TokenId._from_proto(proto.tokenId) if proto.tokenId else None # Convert HAPI protobuf enums to SDK enums kyc_status: TokenKycStatus = TokenKycStatus._from_proto(proto.kycStatus) freeze_status: TokenFreezeStatus = TokenFreezeStatus._from_proto(proto.freezeStatus) @@ -76,13 +76,13 @@ def _from_proto(cls, proto: Optional[TokenRelationshipProto]) -> 'TokenRelations kyc_status=kyc_status, freeze_status=freeze_status, decimals=proto.decimals, - automatic_association=proto.automatic_association + automatic_association=proto.automatic_association, ) def _to_proto(self) -> TokenRelationshipProto: """Converts this TokenRelationship instance into its protobuf representation. - Converts SDK enums (TokenFreezeStatus, TokenKycStatus) back into their + Converts SDK enums (TokenFreezeStatus, TokenKycStatus) back into their HAPI protobuf integer enum values for transmission. Returns: @@ -108,7 +108,7 @@ def _to_proto(self) -> TokenRelationshipProto: kycStatus=kyc_status, freezeStatus=freeze_status, decimals=self.decimals, - ) + ) if self.token_id: proto.tokenId.CopyFrom(self.token_id._to_proto()) @@ -116,4 +116,4 @@ def _to_proto(self) -> TokenRelationshipProto: if self.automatic_association is not None: proto.automatic_association = self.automatic_association - return proto \ No newline at end of file + return proto diff --git a/src/hiero_sdk_python/tokens/token_revoke_kyc_transaction.py b/src/hiero_sdk_python/tokens/token_revoke_kyc_transaction.py index aae882370..13b4f0e3c 100644 --- a/src/hiero_sdk_python/tokens/token_revoke_kyc_transaction.py +++ b/src/hiero_sdk_python/tokens/token_revoke_kyc_transaction.py @@ -1,37 +1,37 @@ """ -hiero_sdk_python.tokens.token_revoke_kyc_transaction.py +hiero_sdk_python.tokens.token_revoke_kyc_transaction.py. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Provides TokenRevokeKycTransaction, a subclass of Transaction for revoking Know-Your-Customer (KYC) status from an account for a specific token on the Hedera network via the Hedera Token Service (HTS) API. """ -from typing import Optional -from hiero_sdk_python.hapi.services.token_revoke_kyc_pb2 import TokenRevokeKycTransactionBody + +from __future__ import annotations + +from hiero_sdk_python.account.account_id import AccountId +from hiero_sdk_python.channels import _Channel +from hiero_sdk_python.executable import _Method from hiero_sdk_python.hapi.services import token_revoke_kyc_pb2, transaction_pb2 from hiero_sdk_python.hapi.services.schedulable_transaction_body_pb2 import ( SchedulableTransactionBody, ) -from hiero_sdk_python.transaction.transaction import Transaction -from hiero_sdk_python.channels import _Channel -from hiero_sdk_python.executable import _Method +from hiero_sdk_python.hapi.services.token_revoke_kyc_pb2 import TokenRevokeKycTransactionBody from hiero_sdk_python.tokens.token_id import TokenId -from hiero_sdk_python.account.account_id import AccountId +from hiero_sdk_python.transaction.transaction import Transaction + class TokenRevokeKycTransaction(Transaction): """ Represents a token revoke KYC transaction on the network. - + This transaction revokes KYC (Know Your Customer) status from an account for a specific token. - + Inherits from the base Transaction class and implements the required methods to build and execute a token revoke KYC transaction. """ - def __init__( - self, - token_id: Optional[TokenId] = None, - account_id: Optional[AccountId] = None - ) -> None: + + def __init__(self, token_id: TokenId | None = None, account_id: AccountId | None = None) -> None: """ Initializes a new TokenRevokeKycTransaction instance with the token ID and account ID. @@ -40,10 +40,10 @@ def __init__( account_id (AccountId, optional): The ID of the account to revoke KYC from. """ super().__init__() - self.token_id: Optional[TokenId] = token_id - self.account_id: Optional[AccountId] = account_id + self.token_id: TokenId | None = token_id + self.account_id: AccountId | None = account_id - def set_token_id(self, token_id: TokenId) -> "TokenRevokeKycTransaction": + def set_token_id(self, token_id: TokenId) -> TokenRevokeKycTransaction: """ Sets the token ID for this revoke KYC transaction. @@ -57,7 +57,7 @@ def set_token_id(self, token_id: TokenId) -> "TokenRevokeKycTransaction": self.token_id = token_id return self - def set_account_id(self, account_id: AccountId) -> "TokenRevokeKycTransaction": + def set_account_id(self, account_id: AccountId) -> TokenRevokeKycTransaction: """ Sets the account ID for this revoke KYC transaction. @@ -74,10 +74,10 @@ def set_account_id(self, account_id: AccountId) -> "TokenRevokeKycTransaction": def _build_proto_body(self) -> token_revoke_kyc_pb2.TokenRevokeKycTransactionBody: """ Returns the protobuf body for the token revoke KYC transaction. - + Returns: TokenRevokeKycTransactionBody: The protobuf body for this transaction. - + Raises: ValueError: If the token ID or account ID is not set. """ @@ -87,11 +87,8 @@ def _build_proto_body(self) -> token_revoke_kyc_pb2.TokenRevokeKycTransactionBod if self.account_id is None: raise ValueError("Missing account ID") - return TokenRevokeKycTransactionBody( - token=self.token_id._to_proto(), - account=self.account_id._to_proto() - ) - + return TokenRevokeKycTransactionBody(token=self.token_id._to_proto(), account=self.account_id._to_proto()) + def build_transaction_body(self) -> transaction_pb2.AtomicBatchTransactionBody: """ Builds the transaction body for this token revoke KYC transaction. @@ -103,7 +100,7 @@ def build_transaction_body(self) -> transaction_pb2.AtomicBatchTransactionBody: transaction_body = self.build_base_transaction_body() transaction_body.tokenRevokeKyc.CopyFrom(token_revoke_kyc_body) return transaction_body - + def build_scheduled_body(self) -> SchedulableTransactionBody: """ Builds the scheduled transaction body for this token revoke KYC transaction. @@ -125,19 +122,13 @@ def _get_method(self, channel: _Channel) -> _Method: Args: channel (_Channel): The channel containing service stubs - + Returns: _Method: An object containing the transaction function to revoke KYC. """ - return _Method( - transaction_func=channel.token.revokeKycFromTokenAccount, - query_func=None - ) + return _Method(transaction_func=channel.token.revokeKycFromTokenAccount, query_func=None) - def _from_proto( - self, - proto: token_revoke_kyc_pb2.TokenRevokeKycTransactionBody - ) -> "TokenRevokeKycTransaction": + def _from_proto(self, proto: token_revoke_kyc_pb2.TokenRevokeKycTransactionBody) -> TokenRevokeKycTransaction: """ Initializes a new TokenRevokeKycTransaction instance from a protobuf object. diff --git a/src/hiero_sdk_python/tokens/token_transfer.py b/src/hiero_sdk_python/tokens/token_transfer.py index 0a207ee13..2c75eff86 100644 --- a/src/hiero_sdk_python/tokens/token_transfer.py +++ b/src/hiero_sdk_python/tokens/token_transfer.py @@ -1,28 +1,31 @@ """ -hiero_sdk_python.tokens.token_transfer.py +hiero_sdk_python.tokens.token_transfer.py. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Defines TokenTransfer for representing Token transfer details. """ -from typing import List, Optional +from __future__ import annotations + from hiero_sdk_python.account.account_id import AccountId from hiero_sdk_python.hapi.services import basic_types_pb2 from hiero_sdk_python.tokens.token_id import TokenId + class TokenTransfer: """ Represents a single fungible token transfer, detailing the token, the account involved, the amount, and optional approval status and decimal expectations. """ + def __init__( - self, - token_id: TokenId, - account_id: AccountId, - amount: int, - expected_decimals: Optional[int]=None, - is_approved: bool=False - ) ->None: + self, + token_id: TokenId, + account_id: AccountId, + amount: int, + expected_decimals: int | None = None, + is_approved: bool = False, + ) -> None: """ Initializes a new TokenTransfer instance. @@ -30,14 +33,14 @@ def __init__( token_id (TokenId): The ID of the token being transferred. account_id (AccountId): The account ID of the sender or receiver. amount (int): The amount of the token to send or receive. - expected_decimals (optional, int): + expected_decimals (int, optional): The number specifying the amount in the smallest denomination. - is_approved (optional, bool): Indicates whether this transfer is an approved allowance. + is_approved (bool, optional): Indicates whether this transfer is an approved allowance. """ self.token_id: TokenId = token_id self.account_id: AccountId = account_id self.amount: int = amount - self.expected_decimals: Optional[int] = expected_decimals + self.expected_decimals: int | None = expected_decimals self.is_approved: bool = is_approved def _to_proto(self) -> basic_types_pb2.AccountAmount: @@ -48,38 +51,30 @@ def _to_proto(self) -> basic_types_pb2.AccountAmount: AccountAmount: The protobuf representation of this TokenTransfer. """ return basic_types_pb2.AccountAmount( - accountID=self.account_id._to_proto(), - amount=self.amount, - is_approval=self.is_approved + accountID=self.account_id._to_proto(), amount=self.amount, is_approval=self.is_approved ) @classmethod - def _from_proto(cls, proto: basic_types_pb2.TokenTransferList) -> List["TokenTransfer"]: + def _from_proto(cls, proto: basic_types_pb2.TokenTransferList) -> list[TokenTransfer]: """ Construct a list of TokenTransfer from the protobuf of TokenTransferList. Args: - proto (basic_types_pb2.TokenTransferList: + proto (basic_types_pb2.TokenTransferList: The protobuf representation of a TokenTransferList """ - token_transfer: List[TokenTransfer] = [] + expected_decimals = proto.expected_decimals.value if proto.HasField("expected_decimals") else None - expected_decimals = ( - proto.expected_decimals.value if proto.HasField('expected_decimals') else None - ) - - for transfer in proto.transfers: - token_transfer.append( - TokenTransfer( - token_id=TokenId._from_proto(proto.token), - account_id=AccountId._from_proto(transfer.accountID), - amount=transfer.amount, - expected_decimals=expected_decimals, - is_approved=transfer.is_approval - ) + return [ + TokenTransfer( + token_id=TokenId._from_proto(proto.token), + account_id=AccountId._from_proto(transfer.accountID), + amount=transfer.amount, + expected_decimals=expected_decimals, + is_approved=transfer.is_approval, ) - - return token_transfer + for transfer in proto.transfers + ] def __str__(self) -> str: """ diff --git a/src/hiero_sdk_python/tokens/token_transfer_list.py b/src/hiero_sdk_python/tokens/token_transfer_list.py index c46c315af..0b3ea1b86 100644 --- a/src/hiero_sdk_python/tokens/token_transfer_list.py +++ b/src/hiero_sdk_python/tokens/token_transfer_list.py @@ -1,49 +1,53 @@ """ -hiero_sdk_python.tokens.token_transfer_list.py +hiero_sdk_python.tokens.token_transfer_list.py. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Defines TokenTransferList for representing and converting Token transfer details. For fungible and non-fungible token transfers to and from protobuf messages. """ -from typing import Optional + +from __future__ import annotations + from hiero_sdk_python.hapi.services import basic_types_pb2 from hiero_sdk_python.tokens.token_id import TokenId from hiero_sdk_python.tokens.token_nft_transfer import TokenNftTransfer from hiero_sdk_python.tokens.token_transfer import TokenTransfer + class TokenTransferList: """ This class encapsulates the details of a list of token transfers, including fungible token transfers, non-fungible token (NFT) transfers, and expected decimal information. """ + def __init__( - self, - token: TokenId, - transfers: Optional[list[TokenTransfer]]=None, - nft_transfers: Optional[list[TokenNftTransfer]]=None, - expected_decimals: Optional[int]=None - ) -> None: + self, + token: TokenId, + transfers: list[TokenTransfer] | None = None, + nft_transfers: list[TokenNftTransfer] | None = None, + expected_decimals: int | None = None, + ) -> None: """ Initializes a new TokenTransferList instance. Args: token (TokenId): Thhe ID of the token being transferred. - transfers (optional, list[TokenTransfer]): A list of fungible token transfers. - nft_transfers (optional, list[TokenNftTransfer]): A list of NFT transfers. - expected_decimals (optional, int): + transfers (list[TokenTransfer], optional): A list of fungible token transfers. + nft_transfers (list[TokenNftTransfer], optional): A list of NFT transfers. + expected_decimals (int, optional): The number specifying the amount in the smallest denomination. """ self.token: TokenId = token self.transfers: list[TokenTransfer] = [] self.nft_transfers: list[TokenNftTransfer] = [] - self.expected_decimals: Optional[int] = expected_decimals + self.expected_decimals: int | None = expected_decimals if transfers: self.transfers = transfers if nft_transfers: self.nft_transfers = nft_transfers - def add_token_transfer(self, transfer: TokenTransfer) -> None: + def add_token_transfer(self, transfer: TokenTransfer) -> None: """ Adds a fungible token transfer to the list of transfers. @@ -52,7 +56,7 @@ def add_token_transfer(self, transfer: TokenTransfer) -> None: """ self.transfers.append(transfer) - def add_nft_transfer(self, transfer: TokenNftTransfer) -> None: + def add_nft_transfer(self, transfer: TokenNftTransfer) -> None: """ Adds an NFT transfer to the list of NFT transfers. @@ -70,7 +74,7 @@ def _to_proto(self) -> basic_types_pb2.TokenTransferList: """ proto = basic_types_pb2.TokenTransferList( token=self.token._to_proto(), - expected_decimals={'value':self.expected_decimals} if self.expected_decimals else None + expected_decimals={"value": self.expected_decimals} if self.expected_decimals else None, ) for fungible_transfer in self.transfers: @@ -84,13 +88,8 @@ def _to_proto(self) -> basic_types_pb2.TokenTransferList: def __str__(self) -> str: """ Returns a string representation of this TokenTransferList instance. - + Returns: str: A string representation of this TokenTransferList. """ - return ( - f"TokenTransferList(" - f"token={self.token}, " - f"transfers={self.transfers}, " - f"nft_transfers={self.nft_transfers})" - ) + return f"TokenTransferList(token={self.token}, transfers={self.transfers}, nft_transfers={self.nft_transfers})" diff --git a/src/hiero_sdk_python/tokens/token_type.py b/src/hiero_sdk_python/tokens/token_type.py index 02c4a553a..3a06c194c 100644 --- a/src/hiero_sdk_python/tokens/token_type.py +++ b/src/hiero_sdk_python/tokens/token_type.py @@ -1,43 +1,46 @@ """ -hiero_sdk_python.tokens.token_type.py +hiero_sdk_python.tokens.token_type.py. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Defines TokenType enum for distinguishing between fungible common tokens and non-fungible unique tokens on Hedera network. """ +from __future__ import annotations + from enum import Enum + class TokenType(Enum): """ Token type for Hedera tokens. - + Determines whether a token represents divisible, interchangeable units or unique, individually identifiable assets. - + Attributes: FUNGIBLE_COMMON: Interchangeable tokens where each unit is equal. All tokens are identical and can be divided into smaller units. Used for currencies, utility tokens, or any asset where individual tokens are indistinguishable from each other. - + NON_FUNGIBLE_UNIQUE: Unique tokens where each unit is distinct. Each token has its own metadata and identity. Used for NFTs, collectibles, or any asset where individual tokens have unique properties and cannot be replaced by other tokens of the same type. - + Example: >>> # Creating a fungible token (like a currency) >>> token_type = TokenType.FUNGIBLE_COMMON >>> print(f"Token type: {token_type}") Token type: TokenType.FUNGIBLE_COMMON - + >>> # Creating a non-fungible token (like an NFT) >>> token_type = TokenType.NON_FUNGIBLE_UNIQUE >>> print(f"Token type: {token_type}") Token type: TokenType.NON_FUNGIBLE_UNIQUE """ - + FUNGIBLE_COMMON = 0 NON_FUNGIBLE_UNIQUE = 1 diff --git a/src/hiero_sdk_python/tokens/token_unfreeze_transaction.py b/src/hiero_sdk_python/tokens/token_unfreeze_transaction.py index 5ae7ea5c6..e9bf2d624 100644 --- a/src/hiero_sdk_python/tokens/token_unfreeze_transaction.py +++ b/src/hiero_sdk_python/tokens/token_unfreeze_transaction.py @@ -1,53 +1,54 @@ """ -hiero_sdk_python.tokens.token_unfreeze_transaction.py +hiero_sdk_python.tokens.token_unfreeze_transaction.py. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Provides TokenUnfreezeTransaction, a subclass of Transaction for un-freezing a specified token for an account on the Hedera network using the Hedera Token Service (HTS) API. """ -from typing import Optional -from hiero_sdk_python.transaction.transaction import Transaction + +from __future__ import annotations + +from hiero_sdk_python.account.account_id import AccountId +from hiero_sdk_python.channels import _Channel +from hiero_sdk_python.executable import _Method from hiero_sdk_python.hapi.services import token_unfreeze_account_pb2, transaction_pb2 from hiero_sdk_python.hapi.services.schedulable_transaction_body_pb2 import ( SchedulableTransactionBody, ) - -from hiero_sdk_python.channels import _Channel -from hiero_sdk_python.executable import _Method from hiero_sdk_python.tokens.token_id import TokenId -from hiero_sdk_python.account.account_id import AccountId +from hiero_sdk_python.transaction.transaction import Transaction + class TokenUnfreezeTransaction(Transaction): """ Represents a token unfreeze transaction on the Hedera network. - + This transaction unfreezes specified tokens for a given account. - + Inherits from the base Transaction class and implements the required methods to build and execute a token unfreeze transaction. """ - def __init__( - self, - account_id: Optional[AccountId] = None, - token_id: Optional[TokenId] = None - ) -> None: + def __init__(self, account_id: AccountId | None = None, token_id: TokenId | None = None) -> None: """ Initializes a new TokenUnfreezeTransaction instance with default values. + Args: account_id (AccountId, optional): The ID of the account to unfreeze tokens for. token_id (TokenId, optional): The ID of the token to unfreeze. """ super().__init__() - self.token_id: Optional[TokenId] = token_id - self.account_id: Optional[AccountId] = account_id + self.token_id: TokenId | None = token_id + self.account_id: AccountId | None = account_id self._default_transaction_fee: int = 3_000_000_000 - def set_token_id(self, token_id: TokenId) -> "TokenUnfreezeTransaction": + def set_token_id(self, token_id: TokenId) -> TokenUnfreezeTransaction: """ Sets the token ID for this unfreeze transaction. + Args: token_id (TokenId): The ID of the token to unfreeze. + Returns: TokenUnfreezeTransaction: This transaction instance. """ @@ -55,11 +56,13 @@ def set_token_id(self, token_id: TokenId) -> "TokenUnfreezeTransaction": self.token_id = token_id return self - def set_account_id(self, account_id: AccountId) -> "TokenUnfreezeTransaction": + def set_account_id(self, account_id: AccountId) -> TokenUnfreezeTransaction: """ Sets the account ID for this unfreeze transaction. + Args: account_id (AccountId): The ID of the account to unfreeze tokens for. + Returns: TokenUnfreezeTransaction: This transaction instance. """ @@ -67,14 +70,13 @@ def set_account_id(self, account_id: AccountId) -> "TokenUnfreezeTransaction": self.account_id = account_id return self - def _build_proto_body(self) -> token_unfreeze_account_pb2.TokenUnfreezeAccountTransactionBody: """ Returns the protobuf body for the token unfreeze transaction. - + Returns: TokenUnfreezeAccountTransactionBody: The protobuf body for this transaction. - + Raises: ValueError: If account ID or token ID is not set. """ @@ -85,10 +87,9 @@ def _build_proto_body(self) -> token_unfreeze_account_pb2.TokenUnfreezeAccountTr raise ValueError("Missing required AccountID.") return token_unfreeze_account_pb2.TokenUnfreezeAccountTransactionBody( - account=self.account_id._to_proto(), - token=self.token_id._to_proto() + account=self.account_id._to_proto(), token=self.token_id._to_proto() ) - + def build_transaction_body(self) -> transaction_pb2.TransactionBody: """ Builds and returns the protobuf transaction body for token unfreeze. @@ -100,7 +101,7 @@ def build_transaction_body(self) -> transaction_pb2.TransactionBody: transaction_body: transaction_pb2.TransactionBody = self.build_base_transaction_body() transaction_body.tokenUnfreeze.CopyFrom(token_unfreeze_body) return transaction_body - + def build_scheduled_body(self) -> SchedulableTransactionBody: """ Builds the scheduled transaction body for this token unfreeze transaction. @@ -114,7 +115,4 @@ def build_scheduled_body(self) -> SchedulableTransactionBody: return schedulable_body def _get_method(self, channel: _Channel) -> _Method: - return _Method( - transaction_func=channel.token.unfreezeTokenAccount, - query_func=None - ) + return _Method(transaction_func=channel.token.unfreezeTokenAccount, query_func=None) diff --git a/src/hiero_sdk_python/tokens/token_unpause_transaction.py b/src/hiero_sdk_python/tokens/token_unpause_transaction.py index d9bc137a4..a30d1eea9 100644 --- a/src/hiero_sdk_python/tokens/token_unpause_transaction.py +++ b/src/hiero_sdk_python/tokens/token_unpause_transaction.py @@ -1,67 +1,67 @@ """ -hiero_sdk_python.tokens.token_unpause_transaction.py +hiero_sdk_python.tokens.token_unpause_transaction.py. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Provides the TokenUnpauseTransaction class a subclass of Transaction that facilitates unpausing a specified token on the Hedera network using the Hedera Token Service (HTS) API. """ -from typing import Optional -from hiero_sdk_python.hapi.services.schedulable_transaction_body_pb2 import ( - SchedulableTransactionBody -) -from hiero_sdk_python.hapi.services.token_unpause_pb2 import TokenUnpauseTransactionBody -from hiero_sdk_python.hapi.services.transaction_pb2 import TransactionBody + +from __future__ import annotations from hiero_sdk_python.channels import _Channel -from hiero_sdk_python.executable import _Method from hiero_sdk_python.client.client import Client +from hiero_sdk_python.executable import _Method +from hiero_sdk_python.hapi.services.schedulable_transaction_body_pb2 import SchedulableTransactionBody +from hiero_sdk_python.hapi.services.token_unpause_pb2 import TokenUnpauseTransactionBody +from hiero_sdk_python.hapi.services.transaction_pb2 import TransactionBody from hiero_sdk_python.tokens.token_id import TokenId from hiero_sdk_python.transaction.transaction import Transaction + class TokenUnpauseTransaction(Transaction): """ Represents a token unpause transaction on the Hedera network. - + This transaction unpauses specified tokens. - + Inherits from the base Transaction class and implements the required methods to build and execute a token unpause transaction. """ - def __init__(self, token_id: Optional[TokenId] = None) -> None: + def __init__(self, token_id: TokenId | None = None) -> None: """ Initializes a new TokenUnpauseTransaction instance with default values. Args: - token_id (Optional[TokenId]): The ID of the token to unpause. + token_id (TokenId, optional): The ID of the token to unpause. """ super().__init__() - self.token_id: Optional[TokenId] = None + self.token_id: TokenId | None = None if token_id is not None: self.set_token_id(token_id) - def set_token_id(self, token_id: TokenId) -> "TokenUnpauseTransaction": + def set_token_id(self, token_id: TokenId) -> TokenUnpauseTransaction: """ Sets the token ID for this unpause transaction. Args: token_id (TokenId): The ID of the token to unpause. - + Returns: TokenUnpauseTransaction: This current instance of transaction. """ self._require_not_frozen() - #Check if the tokenId is instance of TokenId + # Check if the tokenId is instance of TokenId if not isinstance(token_id, TokenId): raise TypeError("token_id must be an instance of TokenId") self.token_id = token_id return self - def _validate_checksum(self, client: "Client") -> None: + def _validate_checksum(self, client: Client) -> None: """ Validates the checksum for the token ID associated with this transaction. @@ -72,13 +72,13 @@ def _validate_checksum(self, client: "Client") -> None: self.token_id.validate_checksum(client) @classmethod - def _from_proto(cls, proto: TokenUnpauseTransactionBody) -> "TokenUnpauseTransaction": + def _from_proto(cls, proto: TokenUnpauseTransactionBody) -> TokenUnpauseTransaction: """ Construct TokenUnpauseTransaction from TokenUnpauseTransactionBody. Args: proto (TokenUnpauseTransactionBody): The protobuf body of TokenUnpauseTransaction - + Returns: TokenUnpauseTransaction: A new instance of TokenUnpauseTransaction """ @@ -88,19 +88,17 @@ def _from_proto(cls, proto: TokenUnpauseTransactionBody) -> "TokenUnpauseTransac def _build_proto_body(self) -> TokenUnpauseTransactionBody: """ Returns the protobuf body for the token unpause transaction. - + Returns: TokenUnpauseTransactionBody: The protobuf body for this transaction. - + Raises: ValueError: If account ID or token ID is not set. """ if self.token_id is None: raise ValueError("Missing token ID") - return TokenUnpauseTransactionBody( - token=self.token_id._to_proto() - ) + return TokenUnpauseTransactionBody(token=self.token_id._to_proto()) def build_transaction_body(self) -> TransactionBody: """ @@ -127,7 +125,4 @@ def build_scheduled_body(self) -> SchedulableTransactionBody: return schedulable_body def _get_method(self, channel: _Channel) -> _Method: - return _Method( - transaction_func=channel.token.unpauseToken, - query_func=None - ) + return _Method(transaction_func=channel.token.unpauseToken, query_func=None) diff --git a/src/hiero_sdk_python/tokens/token_update_nfts_transaction.py b/src/hiero_sdk_python/tokens/token_update_nfts_transaction.py index 1978236a6..f5a90f6c6 100644 --- a/src/hiero_sdk_python/tokens/token_update_nfts_transaction.py +++ b/src/hiero_sdk_python/tokens/token_update_nfts_transaction.py @@ -1,39 +1,38 @@ """ -hiero_sdk_python.tokens.token_update_nfts_transaction.py +hiero_sdk_python.tokens.token_update_nfts_transaction.py. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Provides TokenUpdateNftsTransaction, a subclass of Transaction for updating metadata of non-fungible tokens (NFTs) on the Hedera network via HTS. """ -from typing import List, Optional + +from __future__ import annotations + from google.protobuf.wrappers_pb2 import BytesValue -from hiero_sdk_python.tokens.token_id import TokenId -from hiero_sdk_python.transaction.transaction import Transaction from hiero_sdk_python.channels import _Channel from hiero_sdk_python.executable import _Method -from hiero_sdk_python.hapi.services.token_update_nfts_pb2 import TokenUpdateNftsTransactionBody -from hiero_sdk_python.hapi.services import transaction_pb2 +from hiero_sdk_python.hapi.services import token_update_nfts_pb2, transaction_pb2 from hiero_sdk_python.hapi.services.schedulable_transaction_body_pb2 import ( SchedulableTransactionBody, ) -from google.protobuf.wrappers_pb2 import BytesValue -from hiero_sdk_python.hapi.services import token_update_nfts_pb2 +from hiero_sdk_python.hapi.services.token_update_nfts_pb2 import TokenUpdateNftsTransactionBody +from hiero_sdk_python.tokens.token_id import TokenId +from hiero_sdk_python.transaction.transaction import Transaction + class TokenUpdateNftsTransaction(Transaction): """ Represents a token update NFTs transaction on the Hedera network. - + This transaction updates the metadata of NFTs. - + Inherits from the base Transaction class and implements the required methods to build and execute a token update NFTs transaction. """ + def __init__( - self, - token_id: Optional[TokenId] = None, - serial_numbers: Optional[List[int]] = None, - metadata: Optional[bytes] = None + self, token_id: TokenId | None = None, serial_numbers: list[int] | None = None, metadata: bytes | None = None ) -> None: """ Initializes a new TokenUpdateNftsTransaction instance: @@ -45,15 +44,17 @@ def __init__( metadata (bytes, optional): The new metadata for the NFTs. """ super().__init__() - self.token_id: Optional[TokenId] = token_id - self.serial_numbers: List[int] = serial_numbers if serial_numbers else [] - self.metadata: Optional[bytes] = metadata + self.token_id: TokenId | None = token_id + self.serial_numbers: list[int] = serial_numbers if serial_numbers else [] + self.metadata: bytes | None = metadata - def set_token_id(self, token_id: TokenId) -> "TokenUpdateNftsTransaction": + def set_token_id(self, token_id: TokenId) -> TokenUpdateNftsTransaction: """ Sets the token ID for this update NFTs transaction. + Args: token_id (TokenId): The ID of the token whose NFTs will be updated. + Returns: TokenUpdateNftsTransaction: This transaction instance. """ @@ -61,11 +62,13 @@ def set_token_id(self, token_id: TokenId) -> "TokenUpdateNftsTransaction": self.token_id = token_id return self - def set_serial_numbers(self, serial_numbers: List[int]) -> "TokenUpdateNftsTransaction": + def set_serial_numbers(self, serial_numbers: list[int]) -> TokenUpdateNftsTransaction: """ Sets the serial numbers of the NFTs to update. + Args: serial_numbers (list[int]): A list of serial numbers for the NFTs to update. + Returns: TokenUpdateNftsTransaction: This transaction instance. """ @@ -73,11 +76,13 @@ def set_serial_numbers(self, serial_numbers: List[int]) -> "TokenUpdateNftsTrans self.serial_numbers = serial_numbers return self - def set_metadata(self, metadata: bytes) -> "TokenUpdateNftsTransaction": + def set_metadata(self, metadata: bytes) -> TokenUpdateNftsTransaction: """ Sets the metadata for the NFTs to update. + Args: metadata (bytes): The new metadata for the NFTs. + Returns: TokenUpdateNftsTransaction: This transaction instance. """ @@ -88,12 +93,12 @@ def set_metadata(self, metadata: bytes) -> "TokenUpdateNftsTransaction": def _build_proto_body(self): """ Returns the protobuf body for the token update NFTs transaction. - + Returns: TokenUpdateNftsTransactionBody: The protobuf body for this transaction. - + Raises: - ValueError: If the token ID and serial numbers are not set + ValueError: If the token ID and serial numbers are not set or metadata is greater than 100 bytes. """ if not self.token_id: @@ -108,9 +113,9 @@ def _build_proto_body(self): return TokenUpdateNftsTransactionBody( token=self.token_id._to_proto(), serial_numbers=self.serial_numbers, - metadata=BytesValue(value=self.metadata) + metadata=BytesValue(value=self.metadata), ) - + def build_transaction_body(self) -> transaction_pb2.TransactionBody: """ Builds and returns the protobuf transaction body for token update NFTs. @@ -122,7 +127,7 @@ def build_transaction_body(self) -> transaction_pb2.TransactionBody: transaction_body = self.build_base_transaction_body() transaction_body.token_update_nfts.CopyFrom(token_update_body) return transaction_body - + def build_scheduled_body(self) -> SchedulableTransactionBody: """ Builds the scheduled transaction body for this token update NFTs transaction. @@ -144,19 +149,13 @@ def _get_method(self, channel: _Channel) -> _Method: Args: channel (_Channel): The channel containing service stubs - + Returns: _Method: An object containing the transaction function to update NFTs. """ - return _Method( - transaction_func=channel.token.updateNfts, - query_func=None - ) + return _Method(transaction_func=channel.token.updateNfts, query_func=None) - def _from_proto( - self, - proto: token_update_nfts_pb2.TokenUpdateNftsTransactionBody - ) -> "TokenUpdateNftsTransaction": + def _from_proto(self, proto: token_update_nfts_pb2.TokenUpdateNftsTransactionBody) -> TokenUpdateNftsTransaction: """ Deserializes a TokenUpdateNftsTransactionBody from a protobuf object. diff --git a/src/hiero_sdk_python/tokens/token_update_transaction.py b/src/hiero_sdk_python/tokens/token_update_transaction.py index e0b867123..5f0af078d 100644 --- a/src/hiero_sdk_python/tokens/token_update_transaction.py +++ b/src/hiero_sdk_python/tokens/token_update_transaction.py @@ -1,49 +1,58 @@ """ -hiero_sdk_python.tokens.token_update_transaction.py +hiero_sdk_python.tokens.token_update_transaction.py. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Defines TokenUpdateParams, TokenUpdateKeys, and TokenUpdateTransaction for updating token properties (settings and keys) on the Hedera network via the HTS API. """ -from typing import Optional + +from __future__ import annotations + from dataclasses import dataclass -from google.protobuf.wrappers_pb2 import (BytesValue, StringValue) -from hiero_sdk_python.Duration import Duration -from hiero_sdk_python.hbar import Hbar -from hiero_sdk_python.timestamp import Timestamp -from hiero_sdk_python.tokens.token_id import TokenId -from hiero_sdk_python.tokens.token_key_validation import TokenKeyValidation -from hiero_sdk_python.transaction.transaction import Transaction +from google.protobuf.wrappers_pb2 import BytesValue, StringValue + from hiero_sdk_python.account.account_id import AccountId from hiero_sdk_python.channels import _Channel +from hiero_sdk_python.Duration import Duration from hiero_sdk_python.executable import _Method +from hiero_sdk_python.hapi.services import token_update_pb2, transaction_pb2 from hiero_sdk_python.hapi.services.schedulable_transaction_body_pb2 import ( SchedulableTransactionBody, ) -from hiero_sdk_python.hapi.services import token_update_pb2, transaction_pb2 +from hiero_sdk_python.hbar import Hbar +from hiero_sdk_python.timestamp import Timestamp +from hiero_sdk_python.tokens.token_id import TokenId +from hiero_sdk_python.tokens.token_key_validation import TokenKeyValidation +from hiero_sdk_python.transaction.transaction import Transaction from hiero_sdk_python.utils.key_utils import Key, key_to_proto + @dataclass class TokenUpdateParams: """ Represents token attributes that can be updated. Attributes: - treasury_account_id (optional): The new treasury account ID. - token_name (optional): The new name of the token. - token_symbol (optional): The new symbol of the token. - token_memo (optional): The new memo for the token. - metadata (optional): The new metadata for the token. + treasury_account_id (AccountId, optional): The new treasury account ID. + token_name (str, optional): The new name of the token. + token_symbol (str, optional): The new symbol of the token. + token_memo (str, optional): The new memo for the token. + metadata (bytes, optional): The new metadata for the token. + auto_renew_period (Duration, optional): The new auto renew period for token. + auto_renew_account_id (AccountId, optional): The new auto renew account ID. + expiration_time (Timestamp, optional): The new expiration times for token. """ - treasury_account_id: Optional[AccountId] = None - token_name: Optional[str] = None - token_symbol: Optional[str] = None - token_memo: Optional[str] = None - metadata: Optional[bytes] = None - auto_renew_period: Optional[Duration] = None - auto_renew_account_id: Optional[AccountId] = None - expiration_time: Optional[Timestamp] = None + + treasury_account_id: AccountId | None = None + token_name: str | None = None + token_symbol: str | None = None + token_memo: str | None = None + metadata: bytes | None = None + auto_renew_period: Duration | None = None + auto_renew_account_id: AccountId | None = None + expiration_time: Timestamp | None = None + @dataclass class TokenUpdateKeys: @@ -52,21 +61,24 @@ class TokenUpdateKeys: Does not include treasury_key which is for transaction signing. Attributes: - admin_key: The new admin key for the token. - supply_key: The new supply key for the token. - freeze_key: The new freeze key for the token. - wipe_key: The new wipe key for the token. - metadata_key: The new metadata key for the token. - pause_key: The new pause key for the token. + admin_key (Key, optional): The new admin key for the token. + supply_key (Key, optional): The new supply key for the token. + freeze_key: (Key, optional) The new freeze key for the token. + wipe_key (Key, optional): The new wipe key for the token. + metadata_key (Key, optional): The new metadata key for the token. + pause_key (Key, optional): The new pause key for the token. + kyc_key (Key, optional): The new kyc key for the token. + fee_schedule_key (Key, optional): The new schedule key for the token. """ - admin_key: Optional[Key] = None - supply_key: Optional[Key] = None - freeze_key: Optional[Key] = None - wipe_key: Optional[Key] = None - metadata_key: Optional[Key] = None - pause_key: Optional[Key] = None - kyc_key: Optional[Key] = None - fee_schedule_key: Optional[Key] = None + + admin_key: Key | None = None + supply_key: Key | None = None + freeze_key: Key | None = None + wipe_key: Key | None = None + metadata_key: Key | None = None + pause_key: Key | None = None + kyc_key: Key | None = None + fee_schedule_key: Key | None = None class TokenUpdateTransaction(Transaction): @@ -84,12 +96,13 @@ class TokenUpdateTransaction(Transaction): token_key_validation (TokenKeyValidation, optional): The validation mode for token keys. Defaults to FULL_VALIDATION. """ + def __init__( self, - token_id: Optional[TokenId] = None, - token_params: Optional[TokenUpdateParams] = None, - token_keys: Optional[TokenUpdateKeys] = None, - token_key_verification_mode: TokenKeyValidation = TokenKeyValidation.FULL_VALIDATION + token_id: TokenId | None = None, + token_params: TokenUpdateParams | None = None, + token_keys: TokenUpdateKeys | None = None, + token_key_verification_mode: TokenKeyValidation = TokenKeyValidation.FULL_VALIDATION, ) -> None: """ Initializes a new TokenUpdateTransaction instance with token parameters and optional keys. @@ -97,7 +110,7 @@ def __init__( This transaction can be built in two ways to support flexibility: 1) By passing a fully-formed TokenId, TokenUpdateParams and TokenUpdateKeys 2) By passing `None` (or partial) then using the `set_*` methods - Validation is deferred until build time (`build_transaction_body()`), + Validation is deferred until build time (`build_transaction_body()`), so you won't fail immediately if fields are missing at creation. Args: @@ -109,39 +122,36 @@ def __init__( """ super().__init__() - self.token_id: Optional[TokenId] = token_id + self.token_id: TokenId | None = token_id # Initialize params attributes params: TokenUpdateParams = token_params or TokenUpdateParams() - self.treasury_account_id: Optional[AccountId] = params.treasury_account_id - self.token_name: Optional[str] = params.token_name - self.token_symbol: Optional[str] = params.token_symbol - self.token_memo: Optional[str] = params.token_memo - self.metadata: Optional[bytes] = params.metadata - self.auto_renew_account_id: Optional[AccountId] = params.auto_renew_account_id - self.auto_renew_period: Optional[Duration] = params.auto_renew_period - self.expiration_time: Optional[Timestamp] = params.expiration_time + self.treasury_account_id: AccountId | None = params.treasury_account_id + self.token_name: str | None = params.token_name + self.token_symbol: str | None = params.token_symbol + self.token_memo: str | None = params.token_memo + self.metadata: bytes | None = params.metadata + self.auto_renew_account_id: AccountId | None = params.auto_renew_account_id + self.auto_renew_period: Duration | None = params.auto_renew_period + self.expiration_time: Timestamp | None = params.expiration_time # Initialize keys attributes keys: TokenUpdateKeys = token_keys or TokenUpdateKeys() - self.admin_key: Optional[Key] = keys.admin_key - self.freeze_key: Optional[Key] = keys.freeze_key - self.wipe_key: Optional[Key] = keys.wipe_key - self.supply_key: Optional[Key] = keys.supply_key - self.pause_key: Optional[Key] = keys.pause_key - self.metadata_key: Optional[Key] = keys.metadata_key - self.kyc_key: Optional[Key] = keys.kyc_key - self.fee_schedule_key: Optional[Key] = keys.fee_schedule_key + self.admin_key: Key | None = keys.admin_key + self.freeze_key: Key | None = keys.freeze_key + self.wipe_key: Key | None = keys.wipe_key + self.supply_key: Key | None = keys.supply_key + self.pause_key: Key | None = keys.pause_key + self.metadata_key: Key | None = keys.metadata_key + self.kyc_key: Key | None = keys.kyc_key + self.fee_schedule_key: Key | None = keys.fee_schedule_key self.token_key_verification_mode: TokenKeyValidation = token_key_verification_mode # Set default transaction fee to 2 HBAR for token update transactions self._default_transaction_fee: int = Hbar(2).to_tinybars() - def set_token_id( - self, - token_id: TokenId - ) -> "TokenUpdateTransaction": + def set_token_id(self, token_id: TokenId) -> TokenUpdateTransaction: """ Sets the token ID to update. @@ -155,10 +165,7 @@ def set_token_id( self.token_id = token_id return self - def set_treasury_account_id( - self, - treasury_account_id: AccountId - ) -> "TokenUpdateTransaction": + def set_treasury_account_id(self, treasury_account_id: AccountId) -> TokenUpdateTransaction: """ Sets the new treasury account ID for the token. @@ -172,10 +179,7 @@ def set_treasury_account_id( self.treasury_account_id = treasury_account_id return self - def set_token_name( - self, - token_name: str - ) -> "TokenUpdateTransaction": + def set_token_name(self, token_name: str) -> TokenUpdateTransaction: """ Sets the new name for the token. @@ -189,10 +193,7 @@ def set_token_name( self.token_name = token_name return self - def set_token_symbol( - self, - token_symbol: str - ) -> "TokenUpdateTransaction": + def set_token_symbol(self, token_symbol: str) -> TokenUpdateTransaction: """ Sets the new symbol for the token. @@ -206,10 +207,7 @@ def set_token_symbol( self.token_symbol = token_symbol return self - def set_token_memo( - self, - token_memo: str - ) -> "TokenUpdateTransaction": + def set_token_memo(self, token_memo: str) -> TokenUpdateTransaction: """ Sets the new memo for the token. @@ -223,10 +221,7 @@ def set_token_memo( self.token_memo = token_memo return self - def set_metadata( - self, - metadata: bytes - ) -> "TokenUpdateTransaction": + def set_metadata(self, metadata: bytes) -> TokenUpdateTransaction: """ Sets the new metadata for the token. @@ -240,7 +235,7 @@ def set_metadata( self.metadata = metadata return self - def set_auto_renew_account_id(self, auto_renew_account_id: AccountId) -> "TokenUpdateTransaction": + def set_auto_renew_account_id(self, auto_renew_account_id: AccountId) -> TokenUpdateTransaction: """ Sets the new auto renew account for the token. @@ -254,7 +249,7 @@ def set_auto_renew_account_id(self, auto_renew_account_id: AccountId) -> "TokenU self.auto_renew_account_id = auto_renew_account_id return self - def set_auto_renew_period(self, auto_renew_period: Duration) -> "TokenUpdateTransaction": + def set_auto_renew_period(self, auto_renew_period: Duration) -> TokenUpdateTransaction: """ Sets the new auto renew period for the token. @@ -268,7 +263,7 @@ def set_auto_renew_period(self, auto_renew_period: Duration) -> "TokenUpdateTran self.auto_renew_period = auto_renew_period return self - def set_expiration_time(self, expiration_time: Timestamp) -> "TokenUpdateTransaction": + def set_expiration_time(self, expiration_time: Timestamp) -> TokenUpdateTransaction: """ Sets the new expiration time for the token. @@ -282,10 +277,7 @@ def set_expiration_time(self, expiration_time: Timestamp) -> "TokenUpdateTransac self.expiration_time = expiration_time return self - def set_admin_key( - self, - admin_key: Key - ) -> "TokenUpdateTransaction": + def set_admin_key(self, admin_key: Key) -> TokenUpdateTransaction: """ Sets the new admin key for the token. @@ -299,10 +291,7 @@ def set_admin_key( self.admin_key = admin_key return self - def set_freeze_key( - self, - freeze_key: Key - ) -> "TokenUpdateTransaction": + def set_freeze_key(self, freeze_key: Key) -> TokenUpdateTransaction: """ Sets the new freeze key for the token. @@ -316,10 +305,7 @@ def set_freeze_key( self.freeze_key = freeze_key return self - def set_wipe_key( - self, - wipe_key: Key - ) -> "TokenUpdateTransaction": + def set_wipe_key(self, wipe_key: Key) -> TokenUpdateTransaction: """ Sets the new wipe key for the token. @@ -333,10 +319,7 @@ def set_wipe_key( self.wipe_key = wipe_key return self - def set_supply_key( - self, - supply_key: Key - ) -> "TokenUpdateTransaction": + def set_supply_key(self, supply_key: Key) -> TokenUpdateTransaction: """ Sets the new supply key for the token. @@ -350,10 +333,7 @@ def set_supply_key( self.supply_key = supply_key return self - def set_pause_key( - self, - pause_key: Key - ) -> "TokenUpdateTransaction": + def set_pause_key(self, pause_key: Key) -> TokenUpdateTransaction: """ Sets the new pause key for the token. @@ -367,10 +347,7 @@ def set_pause_key( self.pause_key = pause_key return self - def set_metadata_key( - self, - metadata_key: Key - ) -> "TokenUpdateTransaction": + def set_metadata_key(self, metadata_key: Key) -> TokenUpdateTransaction: """ Sets the new metadata key for the token. @@ -384,9 +361,9 @@ def set_metadata_key( self.metadata_key = metadata_key return self - def set_kyc_key(self, kyc_key: Key) -> "TokenUpdateTransaction": + def set_kyc_key(self, kyc_key: Key) -> TokenUpdateTransaction: """ - Sets the kyc key for the token + Sets the kyc key for the token. Args: kyc_key (Key): The new kyc_key to set (PrivateKey or PublicKey). @@ -398,9 +375,9 @@ def set_kyc_key(self, kyc_key: Key) -> "TokenUpdateTransaction": self.kyc_key = kyc_key return self - def set_fee_schedule_key(self, fee_schedule_key: Key) -> "TokenUpdateTransaction": + def set_fee_schedule_key(self, fee_schedule_key: Key) -> TokenUpdateTransaction: """ - Sets the fee schedule key for the token + Sets the fee schedule key for the token. Args: fee_schedule_key (Key): The new fee_schedule_key to set (PrivateKey or PublicKey). @@ -412,10 +389,7 @@ def set_fee_schedule_key(self, fee_schedule_key: Key) -> "TokenUpdateTransaction self.fee_schedule_key = fee_schedule_key return self - def set_key_verification_mode( - self, - key_verification_mode: TokenKeyValidation - ) -> "TokenUpdateTransaction": + def set_key_verification_mode(self, key_verification_mode: TokenKeyValidation) -> TokenUpdateTransaction: """ Sets the key verification mode for the token. @@ -432,10 +406,10 @@ def set_key_verification_mode( def _build_proto_body(self) -> token_update_pb2.TokenUpdateTransactionBody: """ Returns the protobuf body for the token update transaction. - + Returns: TokenUpdateTransactionBody: The protobuf body for this transaction. - + Raises: ValueError: If token_id is not set. """ @@ -452,7 +426,7 @@ def _build_proto_body(self) -> token_update_pb2.TokenUpdateTransactionBody: key_verification_mode=self.token_key_verification_mode._to_proto(), expiry=self.expiration_time._to_protobuf() if self.expiration_time else None, autoRenewAccount=self.auto_renew_account_id._to_proto() if self.auto_renew_account_id else None, - autoRenewPeriod=self.auto_renew_period._to_proto() if self.auto_renew_period else None + autoRenewPeriod=self.auto_renew_period._to_proto() if self.auto_renew_period else None, ) self._set_keys_to_proto(token_update_body) return token_update_body @@ -490,22 +464,14 @@ def _get_method(self, channel: _Channel) -> _Method: Args: channel (_Channel): The channel containing service stubs. - + Returns: _Method: An object containing the transaction function to update tokens. """ - return _Method( - transaction_func=channel.token.updateToken, - query_func=None - ) + return _Method(transaction_func=channel.token.updateToken, query_func=None) - def _set_keys_to_proto( - self, - token_update_body: token_update_pb2.TokenUpdateTransactionBody - ) -> None: - """ - Sets the keys to the protobuf transaction body. - """ + def _set_keys_to_proto(self, token_update_body: token_update_pb2.TokenUpdateTransactionBody) -> None: + """Sets the keys to the protobuf transaction body.""" if self.admin_key: token_update_body.adminKey.CopyFrom(key_to_proto(self.admin_key)) if self.freeze_key: diff --git a/src/hiero_sdk_python/tokens/token_wipe_transaction.py b/src/hiero_sdk_python/tokens/token_wipe_transaction.py index 624ec4b5a..86899bfc2 100644 --- a/src/hiero_sdk_python/tokens/token_wipe_transaction.py +++ b/src/hiero_sdk_python/tokens/token_wipe_transaction.py @@ -1,39 +1,41 @@ """ -hiero_sdk_python.tokens.token_wipe_transaction.py +hiero_sdk_python.tokens.token_wipe_transaction.py. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Provides TokenWipeTransaction, a subclass of Transaction for wiping fungible tokens and NFTs from accounts on the Hedera network via the Hedera Token Service (HTS) API. """ -from typing import Optional, List -from hiero_sdk_python.tokens.token_id import TokenId + +from __future__ import annotations + from hiero_sdk_python.account.account_id import AccountId -from hiero_sdk_python.transaction.transaction import Transaction -from hiero_sdk_python.hapi.services import token_wipe_account_pb2 -from hiero_sdk_python.hapi.services.token_wipe_account_pb2 import TokenWipeAccountTransactionBody +from hiero_sdk_python.channels import _Channel +from hiero_sdk_python.executable import _Method from hiero_sdk_python.hapi.services import transaction_pb2 from hiero_sdk_python.hapi.services.schedulable_transaction_body_pb2 import ( SchedulableTransactionBody, ) +from hiero_sdk_python.hapi.services.token_wipe_account_pb2 import TokenWipeAccountTransactionBody +from hiero_sdk_python.tokens.token_id import TokenId +from hiero_sdk_python.transaction.transaction import Transaction -from hiero_sdk_python.channels import _Channel -from hiero_sdk_python.executable import _Method class TokenWipeTransaction(Transaction): """ Represents a token wipe transaction on the Hedera network. - + This transaction wipes (removes) tokens from an account. - + Inherits from the base Transaction class and implements the required methods to build and execute a token wipe transaction. """ + def __init__( self, - token_id: Optional[TokenId] = None, - account_id: Optional[AccountId] = None, - amount: Optional[int] = None, - serial: Optional[List[int]] = None + token_id: TokenId | None = None, + account_id: AccountId | None = None, + amount: int | None = None, + serial: list[int] | None = None, ) -> None: """ Initializes a new TokenWipeTransaction instance with optional token_id and account_id. @@ -45,12 +47,12 @@ def __init__( serial (list[int], optional): The serial numbers of NFTs to wipe. """ super().__init__() - self.token_id: Optional[TokenId] = token_id - self.account_id: Optional[AccountId] = account_id - self.amount: Optional[int] = amount - self.serial: List[int] = serial if serial else [] + self.token_id: TokenId | None = token_id + self.account_id: AccountId | None = account_id + self.amount: int | None = amount + self.serial: list[int] = serial if serial else [] - def set_token_id(self, token_id: TokenId) -> "TokenWipeTransaction": + def set_token_id(self, token_id: TokenId) -> TokenWipeTransaction: """ Sets the ID of the token to be wiped. @@ -64,13 +66,13 @@ def set_token_id(self, token_id: TokenId) -> "TokenWipeTransaction": self.token_id = token_id return self - def set_account_id(self, account_id: AccountId) -> "TokenWipeTransaction": + def set_account_id(self, account_id: AccountId) -> TokenWipeTransaction: """ Sets the ID of the account to have their tokens wiped. Args: account_id (AccountId): The ID of the account to have their tokens wiped. - + Returns: TokenWipeTransaction: Returns self for method chaining. """ @@ -78,13 +80,13 @@ def set_account_id(self, account_id: AccountId) -> "TokenWipeTransaction": self.account_id = account_id return self - def set_amount(self, amount: int) -> "TokenWipeTransaction": + def set_amount(self, amount: int) -> TokenWipeTransaction: """ Sets the amount of tokens to wipe. Args: amount (int): The amount of tokens to wipe. - + Returns: TokenWipeTransaction: Returns self for method chaining. """ @@ -92,13 +94,13 @@ def set_amount(self, amount: int) -> "TokenWipeTransaction": self.amount = amount return self - def set_serial(self, serial: List[int]) -> "TokenWipeTransaction": + def set_serial(self, serial: list[int]) -> TokenWipeTransaction: """ Sets the serial numbers of NFTs to wipe. Args: - serial (List[int]): The serial numbers of the NFTs to wipe. - + serial (list[int]): The serial numbers of the NFTs to wipe. + Returns: TokenWipeTransaction: Returns self for method chaining. """ @@ -109,7 +111,7 @@ def set_serial(self, serial: List[int]) -> "TokenWipeTransaction": def _build_proto_body(self): """ Returns the protobuf body for the token wipe transaction. - + Returns: TokenWipeAccountTransactionBody: The protobuf body for this transaction. """ @@ -117,9 +119,9 @@ def _build_proto_body(self): token=self.token_id and self.token_id._to_proto(), account=self.account_id and self.account_id._to_proto(), amount=self.amount, - serialNumbers=self.serial + serialNumbers=self.serial, ) - + def build_transaction_body(self) -> transaction_pb2.TransactionBody: """ Builds and returns the protobuf transaction body for token wipe. @@ -131,7 +133,7 @@ def build_transaction_body(self) -> transaction_pb2.TransactionBody: transaction_body = self.build_base_transaction_body() transaction_body.tokenWipe.CopyFrom(token_wipe_body) return transaction_body - + def build_scheduled_body(self) -> SchedulableTransactionBody: """ Builds the scheduled transaction body for this token wipe transaction. @@ -145,15 +147,9 @@ def build_scheduled_body(self) -> SchedulableTransactionBody: return schedulable_body def _get_method(self, channel: _Channel) -> _Method: - return _Method( - transaction_func=channel.token.wipeTokenAccount, - query_func=None - ) + return _Method(transaction_func=channel.token.wipeTokenAccount, query_func=None) - def _from_proto( - self, - proto: TokenWipeAccountTransactionBody - ) -> "TokenWipeTransaction": + def _from_proto(self, proto: TokenWipeAccountTransactionBody) -> TokenWipeTransaction: """ Deserializes a TokenWipeAccountTransactionBody from a protobuf object. diff --git a/src/hiero_sdk_python/transaction/batch_transaction.py b/src/hiero_sdk_python/transaction/batch_transaction.py index b402fb2da..ede88c843 100644 --- a/src/hiero_sdk_python/transaction/batch_transaction.py +++ b/src/hiero_sdk_python/transaction/batch_transaction.py @@ -1,12 +1,13 @@ """ -hiero_sdk_python.transaction.batch_transaction.py +hiero_sdk_python.transaction.batch_transaction.py. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Defines the BatchTransaction class — a subclass of Transaction that enables grouping multiple frozen, signed transactions (with batch keys) into a single atomic operation on the Hedera network. """ -from typing import List, Optional + +from __future__ import annotations from hiero_sdk_python.channels import _Channel from hiero_sdk_python.executable import _Method @@ -16,32 +17,32 @@ from hiero_sdk_python.transaction.transaction import Transaction from hiero_sdk_python.transaction.transaction_id import TransactionId + class BatchTransaction(Transaction): - """ - Represents an atomic batch transaction on the Hedera network. - """ - def __init__(self, inner_transactions: Optional[List[Transaction]]=None) -> None: + """Represents an atomic batch transaction on the Hedera network.""" + + def __init__(self, inner_transactions: list[Transaction] | None = None) -> None: """ Initialize a new BatchTransaction. Args: - inner_transactions (Optional[List[Transaction]]): + inner_transactions (list[Transaction], optional): An optional list of transactions to include in the batch. """ super().__init__() - self.inner_transactions: List[Transaction] = [] + self.inner_transactions: list[Transaction] = [] if inner_transactions: self.set_inner_transactions(inner_transactions) - def set_inner_transactions(self, transactions: List[Transaction]) -> "BatchTransaction": + def set_inner_transactions(self, transactions: list[Transaction]) -> BatchTransaction: """ Set the inner_transaction for batch transaction. Args: - transactions (List[Transaction]): + transactions (list[Transaction]): A list of frozen transactions with a batch key already set. - + Returns: BatchTransaction: The current transaction instance for method chaining. """ @@ -52,14 +53,14 @@ def set_inner_transactions(self, transactions: List[Transaction]) -> "BatchTrans self.inner_transactions = transactions return self - def add_inner_transaction(self, transaction: Transaction) -> "BatchTransaction": + def add_inner_transaction(self, transaction: Transaction) -> BatchTransaction: """ Add the inner_transaction for batch transaction. Args: - transaction (Transaction): + transaction (Transaction): A frozen transaction with a batch key. - + Returns: BatchTransaction: The current transaction instance for method chaining. """ @@ -68,18 +69,14 @@ def add_inner_transaction(self, transaction: Transaction) -> "BatchTransaction": self.inner_transactions.append(transaction) return self - def get_inner_transaction_ids(self) -> List[TransactionId]: + def get_inner_transaction_ids(self) -> list[TransactionId]: """ Retrieve the TransactionIds of all inner batch transactions. Returns: - List[TransactionId]: IDs of all included transactions. + list[TransactionId]: IDs of all included transactions. """ - transaction_ids: List[TransactionId] = [] - for transaction in self.inner_transactions: - transaction_ids.append(transaction.transaction_id) - - return transaction_ids + return [transaction.transaction_id for transaction in self.inner_transactions] def _verify_inner_transaction(self, transaction: Transaction) -> None: """ @@ -89,10 +86,7 @@ def _verify_inner_transaction(self, transaction: Transaction) -> None: ValueError: If the transaction is invalid for batching. """ if isinstance(transaction, (FreezeTransaction, BatchTransaction)): - raise ValueError( - f"Transaction type {type(transaction).__name__} " - "is not allowed in a batch transaction" - ) + raise ValueError(f"Transaction type {type(transaction).__name__} is not allowed in a batch transaction") if not transaction._transaction_body_bytes: raise ValueError("Transaction must be frozen") @@ -101,7 +95,7 @@ def _verify_inner_transaction(self, transaction: Transaction) -> None: raise ValueError("Batch key needs to be set") @classmethod - def _from_protobuf(cls, transaction_body, body_bytes: bytes, sig_map) -> "BatchTransaction": + def _from_protobuf(cls, transaction_body, body_bytes: bytes, sig_map) -> BatchTransaction: """ Creates a BatchTransaction instance from protobuf components. @@ -115,24 +109,18 @@ def _from_protobuf(cls, transaction_body, body_bytes: bytes, sig_map) -> "BatchT """ transaction = super()._from_protobuf(transaction_body, body_bytes, sig_map) - if transaction_body.HasField('atomic_batch'): + if transaction_body.HasField("atomic_batch"): atomic_batch = transaction_body.atomic_batch for inner_transaction in atomic_batch.transactions: - inner_tx_proto = transaction_pb2.Transaction( - signedTransactionBytes=inner_transaction - ) + inner_tx_proto = transaction_pb2.Transaction(signedTransactionBytes=inner_transaction) - transaction.inner_transactions.append( - Transaction.from_bytes(inner_tx_proto.SerializeToString()) - ) + transaction.inner_transactions.append(Transaction.from_bytes(inner_tx_proto.SerializeToString())) return transaction def _build_proto_body(self) -> AtomicBatchTransactionBody: - """ - Returns the protobuf body for the batch transaction. - """ + """Returns the protobuf body for the batch transaction.""" if len(self.inner_transactions) == 0: raise ValueError("BatchTransaction requires at least one inner transaction.") @@ -158,7 +146,4 @@ def build_scheduled_body(self): raise ValueError("Cannot schedule Atomic Batch transaction.") def _get_method(self, channel: _Channel) -> _Method: - return _Method( - transaction_func=channel.util.atomicBatch, - query_func=None - ) + return _Method(transaction_func=channel.util.atomicBatch, query_func=None) diff --git a/src/hiero_sdk_python/transaction/custom_fee_limit.py b/src/hiero_sdk_python/transaction/custom_fee_limit.py index ed402459e..56b6439ef 100644 --- a/src/hiero_sdk_python/transaction/custom_fee_limit.py +++ b/src/hiero_sdk_python/transaction/custom_fee_limit.py @@ -1,9 +1,8 @@ -""" -This module contains the CustomFeeLimit class to store information about custom fee limits. -""" +"""This module contains the CustomFeeLimit class to store information about custom fee limits.""" + +from __future__ import annotations from dataclasses import dataclass, field -from typing import Optional from hiero_sdk_python.account.account_id import AccountId from hiero_sdk_python.hapi.services.custom_fees_pb2 import ( @@ -18,14 +17,14 @@ class CustomFeeLimit: Information about custom fee limits stored on the network. Attributes: - payer_id (Optional[AccountId]): The ID of the account that pays the custom fees + payer_id (AccountId, optional): The ID of the account that pays the custom fees custom_fees (list[CustomFixedFee]): The list of custom fixed fees associated with this limit """ - payer_id: Optional[AccountId] = None + payer_id: AccountId | None = None custom_fees: list[CustomFixedFee] = field(default_factory=list) - def set_payer_id(self, payer_id: Optional[AccountId]) -> "CustomFeeLimit": + def set_payer_id(self, payer_id: AccountId | None) -> CustomFeeLimit: """ Sets the payer account ID for this custom fee limit. @@ -38,7 +37,7 @@ def set_payer_id(self, payer_id: Optional[AccountId]) -> "CustomFeeLimit": self.payer_id = payer_id return self - def add_custom_fee(self, custom_fee: CustomFixedFee) -> "CustomFeeLimit": + def add_custom_fee(self, custom_fee: CustomFixedFee) -> CustomFeeLimit: """ Adds a custom fixed fee to this custom fee limit. @@ -51,7 +50,7 @@ def add_custom_fee(self, custom_fee: CustomFixedFee) -> "CustomFeeLimit": self.custom_fees.append(custom_fee) return self - def set_custom_fees(self, custom_fees: list[CustomFixedFee]) -> "CustomFeeLimit": + def set_custom_fees(self, custom_fees: list[CustomFixedFee]) -> CustomFeeLimit: """ Sets the list of custom fixed fees for this custom fee limit. @@ -64,7 +63,7 @@ def set_custom_fees(self, custom_fees: list[CustomFixedFee]) -> "CustomFeeLimit" self.custom_fees = custom_fees return self - def _to_proto(self) -> "CustomFeeLimitProto": + def _to_proto(self) -> CustomFeeLimitProto: """ Converts this CustomFeeLimit instance to its protobuf representation. @@ -77,7 +76,7 @@ def _to_proto(self) -> "CustomFeeLimitProto": ) @classmethod - def _from_proto(cls, proto: "CustomFeeLimitProto") -> "CustomFeeLimit": + def _from_proto(cls, proto: CustomFeeLimitProto) -> CustomFeeLimit: """ Creates a CustomFeeLimit instance from its protobuf representation. @@ -94,15 +93,8 @@ def _from_proto(cls, proto: "CustomFeeLimitProto") -> "CustomFeeLimit": raise ValueError("Custom fee limit proto is None") return cls( - payer_id=( - AccountId._from_proto(proto.account_id) - if proto.HasField("account_id") - else None - ), - custom_fees=[ - CustomFixedFee._from_fixed_fee_proto(custom_fee) - for custom_fee in proto.fees - ], + payer_id=(AccountId._from_proto(proto.account_id) if proto.HasField("account_id") else None), + custom_fees=[CustomFixedFee._from_fixed_fee_proto(custom_fee) for custom_fee in proto.fees], ) def __repr__(self) -> str: @@ -115,16 +107,7 @@ def __repr__(self) -> str: return self.__str__() def __str__(self) -> str: - """ - Pretty-print the CustomFeeLimit. - """ - custom_fees_str = ( - [str(fee) for fee in self.custom_fees] if self.custom_fees else [] - ) + """Pretty-print the CustomFeeLimit.""" + custom_fees_str = [str(fee) for fee in self.custom_fees] if self.custom_fees else [] - return ( - "CustomFeeLimit(\n" - f" payer_id={self.payer_id},\n" - f" custom_fees={custom_fees_str}\n" - ")" - ) + return f"CustomFeeLimit(\n payer_id={self.payer_id},\n custom_fees={custom_fees_str}\n)" diff --git a/src/hiero_sdk_python/transaction/query_payment.py b/src/hiero_sdk_python/transaction/query_payment.py index 9de883f7d..d96eb646e 100644 --- a/src/hiero_sdk_python/transaction/query_payment.py +++ b/src/hiero_sdk_python/transaction/query_payment.py @@ -1,27 +1,26 @@ +from __future__ import annotations + from hiero_sdk_python.account.account_id import AccountId from hiero_sdk_python.crypto.private_key import PrivateKey +from hiero_sdk_python.hapi.services import transaction_pb2 from hiero_sdk_python.hbar import Hbar -from hiero_sdk_python.transaction.transfer_transaction import TransferTransaction from hiero_sdk_python.transaction.transaction_id import TransactionId -from hiero_sdk_python.hapi.services import transaction_pb2 +from hiero_sdk_python.transaction.transfer_transaction import TransferTransaction + def build_query_payment_transaction( - payer_account_id: AccountId, - payer_private_key: PrivateKey, - node_account_id: AccountId, - amount: Hbar + payer_account_id: AccountId, payer_private_key: PrivateKey, node_account_id: AccountId, amount: Hbar ) -> transaction_pb2.TransactionBody: """ Build and sign a TransferTransaction that sends `amount` of HBAR from `payer_account_id` to `node_account_id`. Returns a Transaction proto that can be attached to QueryHeader.payment. """ - tx = TransferTransaction() tx.add_hbar_transfer(payer_account_id, -amount.to_tinybars()) tx.add_hbar_transfer(node_account_id, amount.to_tinybars()) - tx.transaction_fee = 100_000_000 + tx.transaction_fee = 100_000_000 tx.node_account_id = node_account_id tx.transaction_id = TransactionId.generate(payer_account_id) diff --git a/src/hiero_sdk_python/transaction/transaction.py b/src/hiero_sdk_python/transaction/transaction.py index 22a721491..7b1369de9 100644 --- a/src/hiero_sdk_python/transaction/transaction.py +++ b/src/hiero_sdk_python/transaction/transaction.py @@ -1,16 +1,15 @@ -import hashlib -from typing import Literal, Optional, overload - -from typing import TYPE_CHECKING +from __future__ import annotations +import hashlib +from typing import TYPE_CHECKING, Literal, overload from hiero_sdk_python.account.account_id import AccountId from hiero_sdk_python.client.client import Client from hiero_sdk_python.exceptions import PrecheckError from hiero_sdk_python.executable import _Executable, _ExecutionState -from hiero_sdk_python.hapi.services import (basic_types_pb2, transaction_pb2, transaction_contents_pb2) +from hiero_sdk_python.hapi.services import basic_types_pb2, transaction_contents_pb2, transaction_pb2 from hiero_sdk_python.hapi.services.schedulable_transaction_body_pb2 import SchedulableTransactionBody -from hiero_sdk_python.hapi.services.transaction_response_pb2 import (TransactionResponse as TransactionResponseProto) +from hiero_sdk_python.hapi.services.transaction_response_pb2 import TransactionResponse as TransactionResponseProto from hiero_sdk_python.hbar import Hbar from hiero_sdk_python.response_code import ResponseCode from hiero_sdk_python.transaction.transaction_id import TransactionId @@ -18,6 +17,7 @@ from hiero_sdk_python.transaction.transaction_response import TransactionResponse from hiero_sdk_python.utils.key_utils import Key, key_to_proto + if TYPE_CHECKING: from hiero_sdk_python.crypto.private_key import PrivateKey from hiero_sdk_python.schedule.schedule_create_transaction import ( @@ -41,15 +41,12 @@ class Transaction(_Executable): """ def __init__(self) -> None: - """ - Initializes a new Transaction instance with default values. - """ - + """Initializes a new Transaction instance with default values.""" super().__init__() self.transaction_id = None self.transaction_fee: int | None = None - self.transaction_valid_duration = 120 + self.transaction_valid_duration = 120 self.generate_record = False self.memo = "" self.custom_fee_limits: list[CustomFeeLimit] = [] @@ -59,15 +56,15 @@ def __init__(self) -> None: # Each transaction body has the AccountId of the node it's being submitted to. # If these do not match `INVALID_NODE_ACCOUNT` error will occur. self._transaction_body_bytes: dict[AccountId, bytes] = {} - + # Maps transaction body bytes to their associated signatures # This allows us to maintain the signatures for each unique transaction # and ensures that the correct signatures are used when submitting transactions self._signature_map: dict[bytes, basic_types_pb2.SignatureMap] = {} # changed from int: 2_000_000 to Hbar: 0.02 self._default_transaction_fee = Hbar(0.02) - self.operator_account_id = None - self.batch_key: Optional[Key] = None + self.operator_account_id = None + self.batch_key: Key | None = None def _make_request(self): """ @@ -81,11 +78,7 @@ def _make_request(self): """ return self._to_proto() - def _map_response( - self, - response, - node_id, - proto_request): + def _map_response(self, response, node_id, proto_request): # noqa: ARG002 """ Implements the Executable._map_response method to create a TransactionResponse. @@ -139,7 +132,7 @@ def _should_retry(self, response): ResponseCode.PLATFORM_TRANSACTION_NOT_CREATED, ResponseCode.PLATFORM_NOT_ACTIVE, ResponseCode.BUSY, - ResponseCode.INVALID_NODE_ACCOUNT + ResponseCode.INVALID_NODE_ACCOUNT, } if status in retryable_statuses: @@ -165,10 +158,10 @@ def _map_status_error(self, response): """ error_code = response.nodeTransactionPrecheckCode tx_id = self.transaction_id - + return PrecheckError(error_code, tx_id) - def sign(self, private_key: "PrivateKey") -> "Transaction": + def sign(self, private_key: PrivateKey) -> Transaction: """ Signs the transaction using the provided private key. @@ -183,7 +176,7 @@ def sign(self, private_key: "PrivateKey") -> "Transaction": """ # We require the transaction to be frozen before signing self._require_frozen() - + # We sign the bodies for each node in case we need to switch nodes during execution. for body_bytes in self._transaction_body_bytes.values(): signature = private_key.sign(body_bytes) @@ -191,22 +184,16 @@ def sign(self, private_key: "PrivateKey") -> "Transaction": public_key_bytes = private_key.public_key().to_bytes_raw() if private_key.is_ed25519(): - sig_pair = basic_types_pb2.SignaturePair( - pubKeyPrefix=public_key_bytes, - ed25519=signature - ) + sig_pair = basic_types_pb2.SignaturePair(pubKeyPrefix=public_key_bytes, ed25519=signature) else: - sig_pair = basic_types_pb2.SignaturePair( - pubKeyPrefix=public_key_bytes, - ECDSA_secp256k1=signature - ) + sig_pair = basic_types_pb2.SignaturePair(pubKeyPrefix=public_key_bytes, ECDSA_secp256k1=signature) # We initialize the signature map for this body_bytes if it doesn't exist yet self._signature_map.setdefault(body_bytes, basic_types_pb2.SignatureMap()) # Append the signature pair to the signature map for this transaction body self._signature_map[body_bytes].sigPair.append(sig_pair) - + return self def _to_proto(self): @@ -231,14 +218,9 @@ def _to_proto(self): if sig_map is None: sig_map = basic_types_pb2.SignatureMap() - signed_transaction = transaction_contents_pb2.SignedTransaction( - bodyBytes=body_bytes, - sigMap=sig_map - ) + signed_transaction = transaction_contents_pb2.SignedTransaction(bodyBytes=body_bytes, sigMap=sig_map) - return transaction_pb2.Transaction( - signedTransactionBytes=signed_transaction.SerializeToString() - ) + return transaction_pb2.Transaction(signedTransactionBytes=signed_transaction.SerializeToString()) def freeze(self): """ @@ -260,13 +242,17 @@ def freeze(self): """ if self._transaction_body_bytes: return self - + if self.transaction_id is None: - raise ValueError("Transaction ID must be set before freezing. Use freeze_with(client) or set_transaction_id().") - + raise ValueError( + "Transaction ID must be set before freezing. Use freeze_with(client) or set_transaction_id()." + ) + if self.node_account_id is None and len(self.node_account_ids) == 0: - raise ValueError("Node account ID must be set before freezing. Use freeze_with(client) or manually set node_account_ids.") - + raise ValueError( + "Node account ID must be set before freezing. Use freeze_with(client) or manually set node_account_ids." + ) + # Populate node_account_ids for backward compatibility if self.node_account_id: self.set_node_account_id(self.node_account_id) @@ -277,7 +263,7 @@ def freeze(self): for node_account_id in self.node_account_ids: self.node_account_id = node_account_id self._transaction_body_bytes[node_account_id] = self.build_transaction_body().SerializeToString() - + return self def freeze_with(self, client): @@ -295,26 +281,26 @@ def freeze_with(self, client): """ if self._transaction_body_bytes: return self - + if self.transaction_id is None: self.transaction_id = client.generate_transaction_id() - + # We iterate through every node in the client's network # For each node, set the node_account_id and build the transaction body # This allows the transaction to be submitted to any node in the network if self.batch_key: # For Inner Transaction of batch transaction node_account_id=0.0.0 - self.node_account_id = AccountId(0,0,0) - self._transaction_body_bytes[AccountId(0,0,0)] = self.build_transaction_body().SerializeToString() + self.node_account_id = AccountId(0, 0, 0) + self._transaction_body_bytes[AccountId(0, 0, 0)] = self.build_transaction_body().SerializeToString() return self - + # Single node if self.node_account_id: self.set_node_account_id(self.node_account_id) self._transaction_body_bytes[self.node_account_id] = self.build_transaction_body().SerializeToString() return self - + # Multiple node if len(self.node_account_ids) > 0: for node_account_id in self.node_account_ids: @@ -328,33 +314,31 @@ def freeze_with(self, client): self._transaction_body_bytes[node._account_id] = self.build_transaction_body().SerializeToString() return self - + @overload def execute( self, - client: "Client", + client: Client, timeout: int | float | None = None, wait_for_receipt: Literal[True] = True, - validate_status: bool = False - ) -> "TransactionReceipt": - ... + validate_status: bool = False, + ) -> TransactionReceipt: ... @overload def execute( self, - client: "Client", + client: Client, timeout: int | float | None = None, wait_for_receipt: Literal[False] = False, - validate_status: bool = False - ) -> "TransactionResponse": - ... + validate_status: bool = False, + ) -> TransactionResponse: ... def execute( - self, - client: "Client", - timeout: int | float | None = None, + self, + client: Client, + timeout: int | float | None = None, wait_for_receipt: bool = True, - validate_status: bool = False + validate_status: bool = False, ) -> TransactionReceipt | TransactionResponse: """ Executes the transaction on the Hedera network using the provided client. @@ -378,6 +362,7 @@ def execute( ReceiptStatusError: If the query fails with a receipt status error """ from hiero_sdk_python.transaction.batch_transaction import BatchTransaction + if self.batch_key and not isinstance(self, (BatchTransaction)): raise ValueError("Cannot execute batchified transaction outside of BatchTransaction.") @@ -399,7 +384,7 @@ def execute( if wait_for_receipt: return response.get_receipt(client, timeout=timeout, validate_status=validate_status) - + return response def is_signed_by(self, public_key): @@ -413,16 +398,13 @@ def is_signed_by(self, public_key): bool: True if signed by the given public key, False otherwise. """ public_key_bytes = public_key.to_bytes_raw() - + sig_map = self._signature_map.get(self._transaction_body_bytes.get(self.node_account_id)) - + if sig_map is None: return False - - for sig_pair in sig_map.sigPair: - if sig_pair.pubKeyPrefix == public_key_bytes: - return True - return False + + return any(sig_pair.pubKeyPrefix == public_key_bytes for sig_pair in sig_map.sigPair) def build_transaction_body(self): """ @@ -465,9 +447,9 @@ def build_base_transaction_body(self) -> transaction_pb2.TransactionBody: ValueError: If required IDs are not set. """ if self.transaction_id is None: - if self.operator_account_id is None: - raise ValueError("Operator account ID is not set.") - self.transaction_id = TransactionId.generate(self.operator_account_id) + if self.operator_account_id is None: + raise ValueError("Operator account ID is not set.") + self.transaction_id = TransactionId.generate(self.operator_account_id) transaction_id_proto = self.transaction_id._to_proto() @@ -518,7 +500,7 @@ def build_base_scheduled_body(self) -> SchedulableTransactionBody: return schedulable_body - def schedule(self) -> "ScheduleCreateTransaction": + def schedule(self) -> ScheduleCreateTransaction: """ Converts this transaction into a scheduled transaction. @@ -584,7 +566,7 @@ def set_transaction_memo(self, memo): self.memo = memo return self - def set_transaction_valid_duration(self, duration: int) -> "Transaction": + def set_transaction_valid_duration(self, duration: int) -> Transaction: """ Sets the valid duration for the transaction. @@ -653,10 +635,10 @@ def to_bytes(self) -> bytes: Exception: If the transaction has not been frozen yet. """ self._require_frozen() - + # Get the transaction protobuf transaction_proto = self._to_proto() - + # Serialize to bytes return transaction_proto.SerializeToString() @@ -731,19 +713,19 @@ def from_bytes(transaction_bytes: bytes): transaction_proto = transaction_pb2.Transaction() transaction_proto.ParseFromString(transaction_bytes) except Exception as e: - raise ValueError(f"Failed to parse transaction bytes: {e}") + raise ValueError(f"Failed to parse transaction bytes: {e}") from e try: signed_transaction = transaction_contents_pb2.SignedTransaction() signed_transaction.ParseFromString(transaction_proto.signedTransactionBytes) except Exception as e: - raise ValueError(f"Failed to parse signed transaction: {e}") + raise ValueError(f"Failed to parse signed transaction: {e}") from e try: transaction_body = transaction_pb2.TransactionBody() transaction_body.ParseFromString(signed_transaction.bodyBytes) except Exception as e: - raise ValueError(f"Failed to parse transaction body: {e}") + raise ValueError(f"Failed to parse transaction body: {e}") from e transaction_type = transaction_body.WhichOneof("data") @@ -755,12 +737,10 @@ def from_bytes(transaction_bytes: bytes): if transaction_class is None: raise ValueError(f"Unknown transaction type: {transaction_type}") - transaction_instance = transaction_class._from_protobuf( + return transaction_class._from_protobuf( transaction_body, signed_transaction.bodyBytes, signed_transaction.sigMap ) - return transaction_instance - @staticmethod def _get_transaction_class(transaction_type: str): """ @@ -823,7 +803,7 @@ def _get_transaction_class(transaction_type: str): "tokenReject": "hiero_sdk_python.tokens.token_reject_transaction.TokenRejectTransaction", "tokenAirdrop": "hiero_sdk_python.tokens.token_airdrop_transaction.TokenAirdropTransaction", "tokenCancelAirdrop": "hiero_sdk_python.tokens.token_cancel_airdrop_transaction.TokenCancelAirdropTransaction", - "atomic_batch": "hiero_sdk_python.transaction.batch_transaction.BatchTransaction" + "atomic_batch": "hiero_sdk_python.transaction.batch_transaction.BatchTransaction", } class_path = transaction_type_map.get(transaction_type) @@ -836,7 +816,7 @@ def _get_transaction_class(transaction_type: str): module = __import__(module_path, fromlist=[class_name]) return getattr(module, class_name) except (ImportError, AttributeError) as e: - raise ValueError(f"Failed to import transaction class for type '{transaction_type}': {e}") + raise ValueError(f"Failed to import transaction class for type '{transaction_type}': {e}") from e @classmethod def _from_protobuf(cls, transaction_body, body_bytes: bytes, sig_map): @@ -869,6 +849,7 @@ def _from_protobuf(cls, transaction_body, body_bytes: bytes, sig_map): if transaction_body.max_custom_fees: from hiero_sdk_python.transaction.custom_fee_limit import CustomFeeLimit + transaction.custom_fee_limits = [ CustomFeeLimit._from_proto(fee) for fee in transaction_body.max_custom_fees ] @@ -882,7 +863,7 @@ def _from_protobuf(cls, transaction_body, body_bytes: bytes, sig_map): transaction._signature_map[body_bytes] = sig_map return transaction - + def set_batch_key(self, key: Key): """ Set the batch key required for batch transaction. @@ -891,12 +872,12 @@ def set_batch_key(self, key: Key): batch_key (Key): Key to use as batch key (accepts both PrivateKey and PublicKey). Returns: - Transaction: A reconstructed transaction instance of the appropriate subclass. + Transaction: A reconstructed transaction instance of the appropriate subclass. """ self._require_not_frozen() self.batch_key = key return self - + def batchify(self, client: Client, batch_key: Key): """ Marks the current transaction as an inner (batched) transaction. @@ -904,7 +885,7 @@ def batchify(self, client: Client, batch_key: Key): Args: client (Client): The client instance to use for setting defaults. batch_key (Key): Key to use as batch key (accepts both PrivateKey and PublicKey). - + Returns: Transaction: A reconstructed transaction instance of the appropriate subclass. """ diff --git a/src/hiero_sdk_python/transaction/transaction_id.py b/src/hiero_sdk_python/transaction/transaction_id.py index 365076a86..e98d6f651 100644 --- a/src/hiero_sdk_python/transaction/transaction_id.py +++ b/src/hiero_sdk_python/transaction/transaction_id.py @@ -1,9 +1,12 @@ -import secrets +from __future__ import annotations +import secrets import time -from typing import Any, Optional -from hiero_sdk_python.hapi.services import basic_types_pb2, timestamp_pb2 +from typing import Any + from hiero_sdk_python.account.account_id import AccountId +from hiero_sdk_python.hapi.services import basic_types_pb2, timestamp_pb2 + class TransactionId: """ @@ -18,10 +21,10 @@ class TransactionId: """ def __init__( - self, - account_id: Optional[AccountId] = None, - valid_start: Optional[timestamp_pb2.Timestamp] = None, - scheduled: bool = False + self, + account_id: AccountId | None = None, + valid_start: timestamp_pb2.Timestamp | None = None, + scheduled: bool = False, ) -> None: """ Initializes a TransactionId with the given account ID and valid start timestamp. @@ -30,12 +33,12 @@ def __init__( account_id (AccountId, optional): The account ID initiating the transaction. valid_start (timestamp_pb2.Timestamp, optional): The valid start time of the transaction. """ - self.account_id: Optional[AccountId] = account_id - self.valid_start: Optional[timestamp_pb2.Timestamp] = valid_start + self.account_id: AccountId | None = account_id + self.valid_start: timestamp_pb2.Timestamp | None = valid_start self.scheduled = scheduled @classmethod - def generate(cls, account_id: AccountId) -> "TransactionId": + def generate(cls, account_id: AccountId) -> TransactionId: """ Generates a new TransactionId using the current time as the valid start, subtracting a random number of seconds to adjust for potential network delays. @@ -54,7 +57,7 @@ def generate(cls, account_id: AccountId) -> "TransactionId": return cls(account_id, valid_start, scheduled=False) @classmethod - def from_string(cls, transaction_id_str: str) -> "TransactionId": + def from_string(cls, transaction_id_str: str) -> TransactionId: """ Parses a TransactionId from a string in the format 'account_id@seconds.nanos[?scheduled]'. @@ -73,7 +76,7 @@ def from_string(cls, transaction_id_str: str) -> "TransactionId": ValueError: If the input string is not in the correct format or contains invalid suffixes. """ original_string = transaction_id_str - + try: scheduled = False if "?" in transaction_id_str: @@ -85,18 +88,18 @@ def from_string(cls, transaction_id_str: str) -> "TransactionId": if "@" not in transaction_id_str: raise ValueError(f"Invalid TransactionId string format: {original_string}") - account_id_str: Optional[str] = None - timestamp_str: Optional[str] = None - - account_id_str, timestamp_str = transaction_id_str.split('@') + account_id_str: str | None = None + timestamp_str: str | None = None + + account_id_str, timestamp_str = transaction_id_str.split("@") account_id = AccountId.from_string(account_id_str) - + if "." not in timestamp_str: - raise ValueError(f"Invalid TransactionId string format: {original_string}") - - seconds_str, nanos_str = timestamp_str.split('.') + raise ValueError(f"Invalid TransactionId string format: {original_string}") + + seconds_str, nanos_str = timestamp_str.split(".") valid_start = timestamp_pb2.Timestamp(seconds=int(seconds_str), nanos=int(nanos_str)) - + return cls(account_id, valid_start, scheduled=scheduled) except Exception as e: if isinstance(e, ValueError) and "suffix" in str(e): @@ -129,7 +132,7 @@ def _to_proto(self) -> basic_types_pb2.TransactionID: return transaction_id_proto @classmethod - def _from_proto(cls, transaction_id_proto: basic_types_pb2.TransactionID) -> "TransactionId": + def _from_proto(cls, transaction_id_proto: basic_types_pb2.TransactionID) -> TransactionId: """ Creates a TransactionId instance from a protobuf TransactionID object. @@ -155,11 +158,11 @@ def __eq__(self, other: Any) -> bool: bool: True if equal, False otherwise. """ return ( - isinstance(other, TransactionId) and - self.account_id == other.account_id and - self.valid_start.seconds == other.valid_start.seconds and - self.valid_start.nanos == other.valid_start.nanos and - self.scheduled == other.scheduled + isinstance(other, TransactionId) + and self.account_id == other.account_id + and self.valid_start.seconds == other.valid_start.seconds + and self.valid_start.nanos == other.valid_start.nanos + and self.scheduled == other.scheduled ) def __hash__(self) -> int: diff --git a/src/hiero_sdk_python/transaction/transaction_receipt.py b/src/hiero_sdk_python/transaction/transaction_receipt.py index 9d8c1f805..eaf7261d0 100644 --- a/src/hiero_sdk_python/transaction/transaction_receipt.py +++ b/src/hiero_sdk_python/transaction/transaction_receipt.py @@ -1,5 +1,5 @@ """ -transaction_receipt.py +transaction_receipt.py. ~~~~~~~~~~~~~~~~~~~~~~ Defines the TransactionReceipt class, which represents the outcome of a Hedera transaction. @@ -11,15 +11,20 @@ Classes: - TransactionReceipt: Parses and exposes fields from a transaction receipt protobuf. """ -from typing import Optional, cast -from hiero_sdk_python.file.file_id import FileId + +from __future__ import annotations + +from typing import cast + +from hiero_sdk_python.account.account_id import AccountId +from hiero_sdk_python.consensus.topic_id import TopicId from hiero_sdk_python.contract.contract_id import ContractId +from hiero_sdk_python.file.file_id import FileId +from hiero_sdk_python.hapi.services import response_code_pb2, transaction_receipt_pb2 from hiero_sdk_python.schedule.schedule_id import ScheduleId from hiero_sdk_python.tokens.token_id import TokenId from hiero_sdk_python.transaction.transaction_id import TransactionId -from hiero_sdk_python.hapi.services import transaction_receipt_pb2, response_code_pb2 -from hiero_sdk_python.account.account_id import AccountId -from hiero_sdk_python.consensus.topic_id import TopicId + class TransactionReceipt: """ @@ -37,9 +42,9 @@ class TransactionReceipt: def __init__( self, receipt_proto: transaction_receipt_pb2.TransactionReceipt, - transaction_id: Optional[TransactionId] = None, - children: Optional[list["TransactionReceipt"]] = None, - duplicates: Optional[list["TransactionReceipt"]] = None, + transaction_id: TransactionId | None = None, + children: list[TransactionReceipt] | None = None, + duplicates: list[TransactionReceipt] | None = None, ) -> None: """ Initializes the TransactionReceipt with the provided protobuf receipt. @@ -48,54 +53,45 @@ def __init__( receipt_proto (transaction_receipt_pb2.TransactionReceiptProto, optional): The protobuf transaction receipt. transaction_id (TransactionId, optional): The transaction ID associated with this receipt. """ - self._transaction_id: Optional[TransactionId] = transaction_id - self.status: Optional[response_code_pb2.ResponseCodeEnum] = receipt_proto.status + self._transaction_id: TransactionId | None = transaction_id + self.status: response_code_pb2.ResponseCodeEnum | None = receipt_proto.status self._receipt_proto: transaction_receipt_pb2.TransactionReceipt = receipt_proto - self._children: list["TransactionReceipt"] = children or [] - self._duplicates: list["TransactionReceipt"] = duplicates or [] + self._children: list[TransactionReceipt] = children or [] + self._duplicates: list[TransactionReceipt] = duplicates or [] @property - def token_id(self) -> Optional[TokenId]: + def token_id(self) -> TokenId | None: """ Retrieves the TokenId associated with the transaction receipt, if available. Returns: TokenId or None: The TokenId if present; otherwise, None. """ - if ( - self._receipt_proto.HasField("tokenID") - and self._receipt_proto.tokenID.tokenNum != 0 - ): + if self._receipt_proto.HasField("tokenID") and self._receipt_proto.tokenID.tokenNum != 0: return TokenId._from_proto(self._receipt_proto.tokenID) return None @property - def topic_id(self) -> Optional[TopicId]: + def topic_id(self) -> TopicId | None: """ Retrieves the TopicId associated with the transaction receipt, if available. Returns: TopicId or None: The TopicId if present; otherwise, None. """ - if ( - self._receipt_proto.HasField("topicID") - and self._receipt_proto.topicID.topicNum != 0 - ): + if self._receipt_proto.HasField("topicID") and self._receipt_proto.topicID.topicNum != 0: return TopicId._from_proto(self._receipt_proto.topicID) return None @property - def account_id(self) -> Optional[AccountId]: + def account_id(self) -> AccountId | None: """ Retrieves the AccountId associated with the transaction receipt, if available. Returns: AccountId or None: The AccountId if present; otherwise, None. """ - if ( - self._receipt_proto.HasField("accountID") - and self._receipt_proto.accountID.accountNum != 0 - ): + if self._receipt_proto.HasField("accountID") and self._receipt_proto.accountID.accountNum != 0: return AccountId._from_proto(self._receipt_proto.accountID) return None @@ -110,19 +106,14 @@ def serial_numbers(self) -> list[int]: return cast(list[int], self._receipt_proto.serialNumbers) @property - def file_id(self) -> Optional[FileId]: - """ - Returns the file ID associated with this receipt. - """ - if ( - self._receipt_proto.HasField("fileID") - and self._receipt_proto.fileID.fileNum != 0 - ): + def file_id(self) -> FileId | None: + """Returns the file ID associated with this receipt.""" + if self._receipt_proto.HasField("fileID") and self._receipt_proto.fileID.fileNum != 0: return FileId._from_proto(self._receipt_proto.fileID) return None @property - def transaction_id(self) -> Optional[TransactionId]: + def transaction_id(self) -> TransactionId | None: """ Returns the transaction ID associated with this receipt. @@ -139,10 +130,7 @@ def contract_id(self): Returns: ContractId or None: The ContractId if present; otherwise, None. """ - if ( - self._receipt_proto.HasField("contractID") - and self._receipt_proto.contractID.contractNum != 0 - ): + if self._receipt_proto.HasField("contractID") and self._receipt_proto.contractID.contractNum != 0: return ContractId._from_proto(self._receipt_proto.contractID) return None @@ -155,10 +143,7 @@ def schedule_id(self): Returns: ScheduleId or None: The ScheduleId if present; otherwise, None. """ - if ( - self._receipt_proto.HasField("scheduleID") - and self._receipt_proto.scheduleID.scheduleNum != 0 - ): + if self._receipt_proto.HasField("scheduleID") and self._receipt_proto.scheduleID.scheduleNum != 0: return ScheduleId._from_proto(self._receipt_proto.scheduleID) return None @@ -197,20 +182,20 @@ def topic_sequence_number(self) -> int: return self._receipt_proto.topicSequenceNumber @property - def topic_running_hash(self) -> Optional[bytes]: + def topic_running_hash(self) -> bytes | None: """ Returns the topic running hash associated with this receipt. Returns: int: The running hash of the topic if present, otherwise None. """ - if self._receipt_proto.HasField('topicRunningHash'): + if self._receipt_proto.HasField("topicRunningHash"): return self._receipt_proto.topicRunningHash return None @property - def children(self) -> list["TransactionReceipt"]: + def children(self) -> list[TransactionReceipt]: """ Returns the child transaction receipts associated with this receipt. @@ -219,7 +204,7 @@ def children(self) -> list["TransactionReceipt"]: """ return self._children - def _set_children(self, children: list["TransactionReceipt"]) -> None: + def _set_children(self, children: list[TransactionReceipt]) -> None: """ Internal setter for child receipts (used by receipt queries). @@ -229,7 +214,7 @@ def _set_children(self, children: list["TransactionReceipt"]) -> None: self._children = children @property - def duplicates(self) -> list["TransactionReceipt"]: + def duplicates(self) -> list[TransactionReceipt]: """ Returns the duplicate transaction receipts associated with this receipt. @@ -237,8 +222,8 @@ def duplicates(self) -> list["TransactionReceipt"]: list[TransactionReceipt]: Duplicate receipts (empty if not requested or none exist). """ return self._duplicates - - def _set_duplicates(self, duplicates: list["TransactionReceipt"]) -> None: + + def _set_duplicates(self, duplicates: list[TransactionReceipt]) -> None: """ Internal setter for duplicate receipts (used by receipt queries). @@ -257,12 +242,16 @@ def _to_proto(self): return self._receipt_proto @classmethod - def _from_proto(cls, proto: transaction_receipt_pb2.TransactionReceipt, transaction_id: TransactionId) -> "TransactionReceipt": + def _from_proto( + cls, proto: transaction_receipt_pb2.TransactionReceipt, transaction_id: TransactionId + ) -> TransactionReceipt: """ Creates a TransactionReceipt instance from a protobuf TransactionReceipt object. + Args: proto (transaction_receipt_pb2.TransactionReceipt): The protobuf TransactionReceipt object. transaction_id (TransactionId): The transaction ID associated with this receipt. + Returns: TransactionReceipt: A new instance of TransactionReceipt populated with data from the protobuf object. """ diff --git a/src/hiero_sdk_python/transaction/transaction_record.py b/src/hiero_sdk_python/transaction/transaction_record.py index 75bb7d6d1..704737bb7 100644 --- a/src/hiero_sdk_python/transaction/transaction_record.py +++ b/src/hiero_sdk_python/transaction/transaction_record.py @@ -19,6 +19,7 @@ """ from __future__ import annotations + from collections import defaultdict from dataclasses import dataclass, field @@ -40,47 +41,48 @@ class TransactionRecord: """ Represents a record of a completed transaction on the Hiero network. - + This class combines detailed information about the a transaction including the transaction ID, receipt, token and NFT transfers, fees & other - metadata such as pseudo-random number generation(PRNG) results and + metadata such as pseudo-random number generation(PRNG) results and pending airdrop records. Attributes: - transaction_id (Optional[TransactionId]): The unique identifier of the transaction. - transaction_hash (Optional[bytes]): The raw hash of the transaction as recorded on-chain. - transaction_memo (Optional[str]): A text memo associated with the transaction. - transaction_fee (Optional[int]): The total network fee (in tinybars) charged for the transaction. - receipt (Optional[TransactionReceipt]): The receipt summarizing the outcome and status of the transaction. - call_result (Optional[ContractFunctionResult]): The result of a contract call if the transaction was a smart contract execution. - token_transfers (defaultdict[TokenId, defaultdict[AccountId, int]]): - A mapping of token IDs to account-level transfer amounts. - Represents fungible token movements within the transaction. - nft_transfers (defaultdict[TokenId, list[TokenNftTransfer]]): + transaction_id (TransactionId, optional): The unique identifier of the transaction. + transaction_hash (bytes, optional): The raw hash of the transaction as recorded on-chain. + transaction_memo (str, optional): A text memo associated with the transaction. + transaction_fee (int, optional): The total network fee (in tinybars) charged for the transaction. + receipt (TransactionReceipt, optional): The receipt summarizing the outcome and status of the transaction. + call_result (ContractFunctionResult, optional): The result of a contract call if the transaction was a smart contract execution. + token_transfers (defaultdict[TokenId, defaultdict[AccountId, int]]): + A mapping of token IDs to account-level transfer amounts. + Represents fungible token movements within the transaction. + nft_transfers (defaultdict[TokenId, list[TokenNftTransfer]]): A mapping of token IDs to lists of NFT transfers for that token. transfers (defaultdict[AccountId, int]): A mapping of account IDs to hbar transfer amounts (positive for credit, negative for debit). new_pending_airdrops (list[PendingAirdropRecord]):A list of new airdrop records created by this transaction. - - prng_number (Optional[int]): A pseudo-random integer generated by the network (if applicable). - prng_bytes (Optional[bytes]): A pseudo-random byte array generated by the network (if applicable). + + prng_number (int, optional): A pseudo-random integer generated by the network (if applicable). + prng_bytes (bytes, optional): A pseudo-random byte array generated by the network (if applicable). duplicates (list[TransactionRecord]): A list of duplicate transaction records returned when queried with include_duplicates=True. Empty by default. children (list[TransactionRecord]): A list of children transaction records returned when queried - with include_children=True. Empty by default. + with include_children=True. Empty by default. + with include_children=True. Empty by default. consensus_timestamp (Optional[Timestamp]): Network-assigned consensus timestamp when the transaction was finalized. schedule_ref (Optional[ScheduleId]): Schedule ID if this transaction was executed via a schedule. assessed_custom_fees (list[AssessedCustomFee]): Custom fees that were assessed and charged during execution. - automatic_token_associations (list[TokenAssociation]): + automatic_token_associations (list[TokenAssociation]): Automatic token-account associations created by the network. parent_consensus_timestamp (Optional[Timestamp]): Consensus timestamp of the parent transaction (for child records). alias (Optional[bytes]): New account alias created (e.g., during account creation with alias). ethereum_hash (Optional[bytes]): Keccak-256 hash of the Ethereum-compatible transaction data. - paid_staking_rewards (list[tuple[AccountId, int]]): + paid_staking_rewards (list[tuple[AccountId, int]]): Staking rewards paid out (account ID → amount in tinybars). evm_address (Optional[bytes]): EVM address created or associated with the account. - contract_create_result (Optional[ContractFunctionResult]): - Result of a contract creation transaction (if applicable) + contract_create_result (Optional[ContractFunctionResult]): + Result of a contract creation transaction (if applicable) """ transaction_id: TransactionId | None = None @@ -90,8 +92,12 @@ class TransactionRecord: receipt: TransactionReceipt | None = None call_result: ContractFunctionResult | None = None - token_transfers: defaultdict[TokenId, defaultdict[AccountId, int]] = field(default_factory=lambda: defaultdict(lambda: defaultdict(int))) - nft_transfers: defaultdict[TokenId, list[TokenNftTransfer]] = field(default_factory=lambda: defaultdict(list[TokenNftTransfer])) + token_transfers: defaultdict[TokenId, defaultdict[AccountId, int]] = field( + default_factory=lambda: defaultdict(lambda: defaultdict(int)) + ) + nft_transfers: defaultdict[TokenId, list[TokenNftTransfer]] = field( + default_factory=lambda: defaultdict(list[TokenNftTransfer]) + ) transfers: defaultdict[AccountId, int] = field(default_factory=lambda: defaultdict(int)) new_pending_airdrops: list[PendingAirdropRecord] = field(default_factory=list) @@ -99,7 +105,11 @@ class TransactionRecord: prng_bytes: bytes | None = None duplicates: list[TransactionRecord] = field(default_factory=list) children: list[TransactionRecord] = field(default_factory=list) - + prng_number: int | None = None + prng_bytes: bytes | None = None + duplicates: list[TransactionRecord] = field(default_factory=list) + children: list[TransactionRecord] = field(default_factory=list) + consensus_timestamp: Timestamp | None = None schedule_ref: ScheduleId | None = None assessed_custom_fees: list[AssessedCustomFee] = field(default_factory=list) @@ -113,14 +123,14 @@ class TransactionRecord: def __repr__(self) -> str: """Returns a human-readable string representation of the TransactionRecord. - - This method constructs a detailed string containing all significant fields of the - transaction record including transaction ID, hash, memo, fees, status, transfers, - PRNG results, consensus timestamp, schedule ref, custom fees, automatic associations, - parent timestamp, alias, Ethereum hash, staking rewards, EVM address, contract create result. + + This method constructs a detailed string containing all significant fields of the + transaction record including transaction ID, hash, memo, fees, status, transfers, + PRNG results, consensus timestamp, schedule ref, custom fees, automatic associations, + parent timestamp, alias, Ethereum hash, staking rewards, EVM address, contract create result. For the receipt status, it attempts to resolve the numeric status to a human-readable ResponseCode name. - + Returns: str: A string representation showing all significant fields of the TransactionRecord. """ @@ -132,31 +142,34 @@ def __repr__(self) -> str: status = ResponseCode(self.receipt.status).name except (ValueError, AttributeError): status = self.receipt.status - return (f"TransactionRecord(transaction_id='{self.transaction_id}', " - f"transaction_hash={self.transaction_hash}, " - f"transaction_memo='{self.transaction_memo}', " - f"transaction_fee={self.transaction_fee}, " - f"receipt_status='{status}', " - f"token_transfers={dict(self.token_transfers)}, " - f"nft_transfers={dict(self.nft_transfers)}, " - f"transfers={dict(self.transfers)}, " - f"new_pending_airdrops={list(self.new_pending_airdrops)}, " - f"call_result={self.call_result}, " - f"prng_number={self.prng_number}, " - f"prng_bytes={self.prng_bytes}, " - f"duplicates_count={len(self.duplicates)}, " - f"children_count={len(self.children)}, " - f"consensus_timestamp={self.consensus_timestamp}, " - f"schedule_ref={self.schedule_ref}, " - f"assessed_custom_fees={self.assessed_custom_fees}, " - f"automatic_token_associations={self.automatic_token_associations}, " - f"parent_consensus_timestamp={self.parent_consensus_timestamp}, " - f"alias={self.alias}, " - f"ethereum_hash={self.ethereum_hash}, " - f"paid_staking_rewards={self.paid_staking_rewards}, " - f"evm_address={self.evm_address}, " - f"contract_create_result={self.contract_create_result})") - + + return ( + f"TransactionRecord(transaction_id='{self.transaction_id}', " + f"transaction_hash={self.transaction_hash}, " + f"transaction_memo='{self.transaction_memo}', " + f"transaction_fee={self.transaction_fee}, " + f"receipt_status='{status}', " + f"token_transfers={dict(self.token_transfers)}, " + f"nft_transfers={dict(self.nft_transfers)}, " + f"transfers={dict(self.transfers)}, " + f"new_pending_airdrops={list(self.new_pending_airdrops)}, " + f"call_result={self.call_result}, " + f"prng_number={self.prng_number}, " + f"prng_bytes={self.prng_bytes}, " + f"duplicates_count={len(self.duplicates)}, " + f"children_count={len(self.children)}, " + f"consensus_timestamp={self.consensus_timestamp}, " + f"schedule_ref={self.schedule_ref}, " + f"assessed_custom_fees={self.assessed_custom_fees}, " + f"automatic_token_associations={self.automatic_token_associations}, " + f"parent_consensus_timestamp={self.parent_consensus_timestamp}, " + f"alias={self.alias}, " + f"ethereum_hash={self.ethereum_hash}, " + f"paid_staking_rewards={self.paid_staking_rewards}, " + f"evm_address={self.evm_address}, " + f"contract_create_result={self.contract_create_result})" + ) + @classmethod def _from_proto( cls, @@ -179,7 +192,7 @@ def _from_proto( assessed custom fees, automatic token associations, parent consensus timestamp, alias, ethereum hash, paid staking rewards, EVM address, and contract create result - + The method maps all nested transfer data from the raw protobuf message into the structured format used by the TransactionRecord class & organizing them @@ -213,39 +226,32 @@ def _from_proto( contract_create_result = ContractFunctionResult._from_proto(proto.contractCreateResult) consensus_timestamp = ( - Timestamp._from_protobuf(proto.consensusTimestamp) - if proto.HasField("consensusTimestamp") else None + Timestamp._from_protobuf(proto.consensusTimestamp) if proto.HasField("consensusTimestamp") else None ) parent_consensus_timestamp = ( Timestamp._from_protobuf(proto.parent_consensus_timestamp) - if proto.HasField("parent_consensus_timestamp") else None + if proto.HasField("parent_consensus_timestamp") + else None ) - schedule_ref = ( - ScheduleId._from_proto(proto.scheduleRef) - if proto.HasField("scheduleRef") else None - ) - assessed_custom_fees = [ - AssessedCustomFee._from_proto(fee) for fee in proto.assessed_custom_fees - ] + schedule_ref = ScheduleId._from_proto(proto.scheduleRef) if proto.HasField("scheduleRef") else None + assessed_custom_fees = [AssessedCustomFee._from_proto(fee) for fee in proto.assessed_custom_fees] automatic_token_associations = [ TokenAssociation._from_proto(assoc) for assoc in proto.automatic_token_associations ] - paid_staking_rewards = [ - (AccountId._from_proto(r.accountID), r.amount) - for r in proto.paid_staking_rewards - ] + paid_staking_rewards = [(AccountId._from_proto(r.accountID), r.amount) for r in proto.paid_staking_rewards] contract_create_result = ( ContractFunctionResult._from_proto(proto.contractCreateResult) - if proto.HasField("contractCreateResult") else None + if proto.HasField("contractCreateResult") + else None ) alias = proto.alias if proto.alias else None ethereum_hash = proto.ethereum_hash if proto.ethereum_hash else None evm_address = proto.evm_address if proto.evm_address else None - + entropy_case = proto.WhichOneof("entropy") prng_number = None - prng_bytes = None + prng_bytes = None if entropy_case == "prng_number": prng_number = proto.prng_number @@ -287,10 +293,10 @@ def _resolve_transaction_id( """Resolves the transaction ID from proto or raises error if not available.""" if proto.HasField("transactionID"): return TransactionId._from_proto(proto.transactionID) - + if transaction_id is not None: return transaction_id - + raise ValueError("transaction_id is required when proto.transactionID is not present") @staticmethod @@ -310,9 +316,7 @@ def _parse_token_transfers( token_transfers[token_id][account_id] = transfer.amount # Non-fungible (NFT) transfers - nft_transfers[token_id].extend( - TokenNftTransfer._from_proto(ttl) - ) + nft_transfers[token_id].extend(TokenNftTransfer._from_proto(ttl)) return token_transfers, nft_transfers @@ -332,10 +336,7 @@ def _parse_pending_airdrops( proto: transaction_record_pb2.TransactionRecord, ) -> list: """Parses pending airdrop records from proto.""" - return [ - PendingAirdropRecord._from_proto(pending) - for pending in proto.new_pending_airdrops - ] + return [PendingAirdropRecord._from_proto(pending) for pending in proto.new_pending_airdrops] @staticmethod def _parse_contract_call_result( @@ -352,7 +353,7 @@ def _to_proto(self) -> transaction_record_pb2.TransactionRecord: Note: The 'duplicates' field is intentionally **not** serialized. Duplicate records are only present in query responses and are never included when sending TransactionRecord messages to the network. - + This method serializes all components of the transaction record into a protobuf message, including: - Basic transaction metadata (hash, memo, fees) @@ -380,26 +381,29 @@ def _to_proto(self) -> transaction_record_pb2.TransactionRecord: "contractCallResult and contractCreateResult are mutually exclusive " "proto oneof fields and cannot both be set simultaneously." ) - + if self.prng_number is not None and self.prng_bytes is not None: raise ValueError( "prng_number and prng_bytes are mutually exclusive " "proto oneof fields (entropy) and cannot both be set simultaneously." ) - + record_proto = transaction_record_pb2.TransactionRecord( transactionHash=self.transaction_hash, memo=self.transaction_memo, transactionFee=self.transaction_fee, receipt=self.receipt._to_proto() if self.receipt else None, + contractCallResult=(self.call_result._to_proto() if self.call_result else None), + prng_number=self.prng_number, + prng_bytes=self.prng_bytes, ) - + if self.call_result is not None: record_proto.contractCallResult.CopyFrom(self.call_result._to_proto()) elif self.contract_create_result is not None: record_proto.contractCreateResult.CopyFrom(self.contract_create_result._to_proto()) if self.prng_number is not None: - record_proto.prng_number = self.prng_number + record_proto.prng_number = self.prng_number if self.prng_bytes is not None: record_proto.prng_bytes = self.prng_bytes @@ -433,9 +437,7 @@ def _to_proto(self) -> transaction_record_pb2.TransactionRecord: if self.schedule_ref is not None: record_proto.scheduleRef.CopyFrom(self.schedule_ref._to_proto()) - record_proto.assessed_custom_fees.extend( - fee._to_proto() for fee in self.assessed_custom_fees - ) + record_proto.assessed_custom_fees.extend(fee._to_proto() for fee in self.assessed_custom_fees) record_proto.automatic_token_associations.extend( assoc._to_proto() for assoc in self.automatic_token_associations diff --git a/src/hiero_sdk_python/transaction/transaction_response.py b/src/hiero_sdk_python/transaction/transaction_response.py index e52835793..6dbab4aa8 100644 --- a/src/hiero_sdk_python/transaction/transaction_response.py +++ b/src/hiero_sdk_python/transaction/transaction_response.py @@ -1,11 +1,13 @@ """ -transaction_response.py +transaction_response.py. ~~~~~~~~~~~~~~~~~~~~~~~~ Represents the response from a transaction submitted to the Hedera network. Provides methods to retrieve the receipt and access core transaction details. """ -from typing import Optional, Union + +from __future__ import annotations + from typing import TYPE_CHECKING from hiero_sdk_python.account.account_id import AccountId @@ -14,65 +16,60 @@ from hiero_sdk_python.transaction.transaction_receipt import TransactionReceipt from hiero_sdk_python.transaction.transaction_record import TransactionRecord + if TYPE_CHECKING: from hiero_sdk_python.transaction.transaction import Transaction # pylint: disable=too-few-public-methods + class TransactionResponse: - """ - Represents the response from a transaction submitted to the network. - """ + """Represents the response from a transaction submitted to the network.""" def __init__(self) -> None: - """ - Initialize a new TransactionResponse instance with default values. - """ + """Initialize a new TransactionResponse instance with default values.""" self.transaction_id: TransactionId = TransactionId() self.node_id: AccountId = AccountId() - self.hash: bytes = bytes() + self.hash: bytes = b"" self.validate_status: bool = False - self.transaction: Optional["Transaction"] = None - + self.transaction: Transaction | None = None + def get_receipt_query(self, validate_status: bool = False): """ Create a receipt query for this transaction. Args: - validate_status (bool, Optional): The query should automatically validate the transaction status. (default False) + validate_status (bool, optional): The query should automatically validate the transaction status. (default False) Returns: TransactionGetReceiptQuery: A configured receipt query. """ from hiero_sdk_python.query.transaction_get_receipt_query import TransactionGetReceiptQuery + return ( TransactionGetReceiptQuery() .set_transaction_id(self.transaction_id) .set_node_account_id(self.node_id) .set_validate_status(validate_status) ) - + def get_receipt( - self, - client: "Client", - timeout: Optional[Union[int, float]] = None, - validate_status: bool = False - ) -> "TransactionReceipt": + self, client: Client, timeout: int | float | None = None, validate_status: bool = False + ) -> TransactionReceipt: """ Retrieves the receipt for this transaction from the network. Args: client (Client): The client instance to use for receipt retrieval. - timeout (Optional[Union[int, float]]): The total execution timeout (in seconds) for this execution. - validate_status (bool, Optional): The query should automatically validate the transaction status. (default False) + timeout (int | float, optional): The total execution timeout (in seconds) for this execution. + validate_status (bool, optional): The query should automatically validate the transaction status. (default False) Returns: TransactionReceipt: The receipt from the network, containing the status and any entities created by the transaction """ - receipt = self.get_receipt_query(validate_status=validate_status).execute(client, timeout) - return receipt - + return self.get_receipt_query(validate_status=validate_status).execute(client, timeout) + def get_record_query(self): """ Create a record query for this transaction. @@ -81,13 +78,10 @@ def get_record_query(self): TransactionRecordQuery: A configured record query. """ from hiero_sdk_python.query.transaction_record_query import TransactionRecordQuery - return ( - TransactionRecordQuery() - .set_transaction_id(self.transaction_id) - .set_node_account_ids([self.node_id]) - ) - - def get_record(self, client: "Client", timeout: Optional[Union[int, float]] = None) -> "TransactionRecord": + + return TransactionRecordQuery().set_transaction_id(self.transaction_id).set_node_account_ids([self.node_id]) + + def get_record(self, client: Client, timeout: int | float | None = None) -> TransactionRecord: """ Retrieve the transaction record from the network. @@ -98,5 +92,4 @@ def get_record(self, client: "Client", timeout: Optional[Union[int, float]] = No Returns: TransactionRecord: The full transaction record. """ - record = self.get_record_query().execute(client, timeout) - return record + return self.get_record_query().execute(client, timeout) diff --git a/src/hiero_sdk_python/transaction/transfer_transaction.py b/src/hiero_sdk_python/transaction/transfer_transaction.py index 20d971be8..300200d4f 100644 --- a/src/hiero_sdk_python/transaction/transfer_transaction.py +++ b/src/hiero_sdk_python/transaction/transfer_transaction.py @@ -1,20 +1,16 @@ -""" -Defines TransferTransaction for transferring HBAR or tokens between accounts. -""" +"""Defines TransferTransaction for transferring HBAR or tokens between accounts.""" -from typing import Dict, List, Optional, Tuple, Union +from __future__ import annotations from hiero_sdk_python.account.account_id import AccountId from hiero_sdk_python.channels import _Channel from hiero_sdk_python.executable import _Method -from hiero_sdk_python.hbar import Hbar from hiero_sdk_python.hapi.services import basic_types_pb2, crypto_transfer_pb2, transaction_pb2 from hiero_sdk_python.hapi.services.schedulable_transaction_body_pb2 import ( SchedulableTransactionBody, ) -from hiero_sdk_python.tokens.abstract_token_transfer_transaction import ( - AbstractTokenTransferTransaction -) +from hiero_sdk_python.hbar import Hbar +from hiero_sdk_python.tokens.abstract_token_transfer_transaction import AbstractTokenTransferTransaction from hiero_sdk_python.tokens.hbar_transfer import HbarTransfer from hiero_sdk_python.tokens.token_id import TokenId from hiero_sdk_python.tokens.token_nft_transfer import TokenNftTransfer @@ -22,16 +18,13 @@ class TransferTransaction(AbstractTokenTransferTransaction["TransferTransaction"]): - """ - Represents a transaction to transfer HBAR or tokens between accounts. - """ + """Represents a transaction to transfer HBAR or tokens between accounts.""" def __init__( self, - hbar_transfers: Optional[Dict[AccountId, int]] = None, - token_transfers: Optional[Dict[TokenId, Dict[AccountId, int]]] = None, - nft_transfers: Optional[Dict[TokenId, - List[Tuple[AccountId, AccountId, int, bool]]]] = None, + hbar_transfers: dict[AccountId, int] | None = None, + token_transfers: dict[TokenId, dict[AccountId, int]] | None = None, + nft_transfers: dict[TokenId, list[tuple[AccountId, AccountId, int, bool]]] | None = None, ) -> None: """ Initializes a new TransferTransaction instance. @@ -44,7 +37,7 @@ def __init__( Initial NFT transfers. """ super().__init__() - self.hbar_transfers: List[HbarTransfer] = [] + self.hbar_transfers: list[HbarTransfer] = [] if hbar_transfers: self._init_hbar_transfers(hbar_transfers) @@ -53,22 +46,20 @@ def __init__( if nft_transfers: self._init_nft_transfers(nft_transfers) - def _init_hbar_transfers(self, hbar_transfers: Dict[AccountId, int]) -> None: - """ - Initializes HBAR transfers from a dictionary. - """ + def _init_hbar_transfers(self, hbar_transfers: dict[AccountId, int]) -> None: + """Initializes HBAR transfers from a dictionary.""" for account_id, amount in hbar_transfers.items(): self.add_hbar_transfer(account_id, amount) def _add_hbar_transfer( - self, account_id: AccountId, amount: Union[int, Hbar], is_approved: bool = False - ) -> "TransferTransaction": + self, account_id: AccountId, amount: int | Hbar, is_approved: bool = False + ) -> TransferTransaction: """ Internal method to add a HBAR transfer to the transaction. Args: account_id (AccountId): The account ID of the sender or receiver. - amount (Union[int, Hbar]): The amount of the HBAR to transfer. + amount (int | Hbar): The amount of the HBAR to transfer. is_approved (bool, optional): Whether the transfer is approved. Defaults to False. Returns: @@ -91,17 +82,16 @@ def _add_hbar_transfer( transfer.amount += amount return self - self.hbar_transfers.append( - HbarTransfer(account_id, amount, is_approved)) + self.hbar_transfers.append(HbarTransfer(account_id, amount, is_approved)) return self - def add_hbar_transfer(self, account_id: AccountId, amount: Union[int, Hbar]) -> "TransferTransaction": + def add_hbar_transfer(self, account_id: AccountId, amount: int | Hbar) -> TransferTransaction: """ Adds a HBAR transfer to the transaction. Args: account_id (AccountId): The account ID of the sender or receiver. - amount (Union[int, Hbar]): The amount of the HBAR to transfer. + amount (int | Hbar): The amount of the HBAR to transfer. Returns: TransferTransaction: The current instance of the transaction for chaining. @@ -109,15 +99,13 @@ def add_hbar_transfer(self, account_id: AccountId, amount: Union[int, Hbar]) -> self._add_hbar_transfer(account_id, amount, False) return self - def add_approved_hbar_transfer( - self, account_id: AccountId, amount: Union[int, Hbar] - ) -> "TransferTransaction": + def add_approved_hbar_transfer(self, account_id: AccountId, amount: int | Hbar) -> TransferTransaction: """ Adds a HBAR transfer with approval to the transaction. Args: account_id (AccountId): The account ID of the sender or receiver. - amount (Union[int, Hbar]): The amount of the HBAR to transfer. + amount (int | Hbar): The amount of the HBAR to transfer. Returns: TransferTransaction: The current instance of the transaction for chaining. @@ -126,9 +114,7 @@ def add_approved_hbar_transfer( return self def _build_proto_body(self) -> crypto_transfer_pb2.CryptoTransferTransactionBody: - """ - Returns the protobuf body for the transfer transaction. - """ + """Returns the protobuf body for the transfer transaction.""" crypto_transfer_tx_body = crypto_transfer_pb2.CryptoTransferTransactionBody() # HBAR @@ -161,7 +147,7 @@ def build_transaction_body(self) -> transaction_pb2.TransactionBody: return transaction_body - def build_scheduled_body(self) -> "SchedulableTransactionBody": + def build_scheduled_body(self) -> SchedulableTransactionBody: """ Builds the transaction body for this transfer transaction. @@ -197,13 +183,10 @@ def _from_protobuf(cls, transaction_body, body_bytes: bytes, sig_map): if crypto_transfer.HasField("transfers"): for account_amount in crypto_transfer.transfers.accountAmounts: - account_id = AccountId._from_proto( - account_amount.accountID) + account_id = AccountId._from_proto(account_amount.accountID) amount = account_amount.amount is_approved = account_amount.is_approval - transaction.hbar_transfers.append( - HbarTransfer(account_id, amount, is_approved) - ) + transaction.hbar_transfers.append(HbarTransfer(account_id, amount, is_approved)) for token_transfer_list in crypto_transfer.tokenTransfers: token_id = TokenId._from_proto(token_transfer_list.token) @@ -218,21 +201,17 @@ def _from_protobuf(cls, transaction_body, body_bytes: bytes, sig_map): expected_decimals = token_transfer_list.expected_decimals.value transaction.token_transfers[token_id].append( - TokenTransfer(token_id, account_id, amount, - expected_decimals, is_approved) + TokenTransfer(token_id, account_id, amount, expected_decimals, is_approved) ) for nft_transfer in token_transfer_list.nftTransfers: - sender_id = AccountId._from_proto( - nft_transfer.senderAccountID) - receiver_id = AccountId._from_proto( - nft_transfer.receiverAccountID) + sender_id = AccountId._from_proto(nft_transfer.senderAccountID) + receiver_id = AccountId._from_proto(nft_transfer.receiverAccountID) serial_number = nft_transfer.serialNumber is_approved = nft_transfer.is_approval transaction.nft_transfers[token_id].append( - TokenNftTransfer( - token_id, sender_id, receiver_id, serial_number, is_approved) + TokenNftTransfer(token_id, sender_id, receiver_id, serial_number, is_approved) ) return transaction diff --git a/src/hiero_sdk_python/utils/crypto_utils.py b/src/hiero_sdk_python/utils/crypto_utils.py index 45e2e7044..1066539f9 100644 --- a/src/hiero_sdk_python/utils/crypto_utils.py +++ b/src/hiero_sdk_python/utils/crypto_utils.py @@ -1,10 +1,8 @@ -import hashlib -import math -from typing import Optional, Tuple +from __future__ import annotations -from cryptography.hazmat.primitives.asymmetric import ec -from cryptography.hazmat.backends import default_backend from cryptography.hazmat.primitives import serialization +from cryptography.hazmat.primitives.asymmetric import ec + try: from Crypto.Hash import keccak @@ -30,7 +28,6 @@ def keccak256(data: bytes) -> bytes: Raises: RuntimeError: If pycryptodome or similar keccak library is not installed. """ - global keccak if keccak is None: raise RuntimeError("Keccak not available. Install pycryptodome or similar.") @@ -55,11 +52,11 @@ def compress_point_unchecked(x: int, y: int) -> bytes: Returns: bytes: A 33-byte compressed point representation. """ - prefix = 0x02 | (y & 1) + prefix = 0x02 | (y & 1) return bytes([prefix]) + x.to_bytes(32, "big") -def decompress_point(data: bytes) -> Tuple[int, int]: +def decompress_point(data: bytes) -> tuple[int, int]: """ Decompress a 33-byte point for secp256k1 into (x, y). If 65 bytes, interpret as uncompressed and re-compress or decode, etc. @@ -68,7 +65,7 @@ def decompress_point(data: bytes) -> Tuple[int, int]: x = int.from_bytes(data[1:33], "big") y = int.from_bytes(data[33:], "big") return (x, y) - elif len(data) == 33 and (data[0] in (0x02, 0x03)): + if len(data) == 33 and (data[0] in (0x02, 0x03)): x = int.from_bytes(data[1:], "big") else: raise ValueError("Not recognized as compressed or uncompressed SEC1 point.") @@ -96,8 +93,7 @@ def compress_with_cryptography(encoded: bytes) -> bytes: ValueError: If the input is not a valid SEC1 encoded point. """ pub = ec.EllipticCurvePublicKey.from_encoded_point(SECP256K1_CURVE, encoded) - compressed = pub.public_bytes( + return pub.public_bytes( encoding=serialization.Encoding.X962, format=serialization.PublicFormat.CompressedPoint, ) - return compressed diff --git a/src/hiero_sdk_python/utils/entity_id_helper.py b/src/hiero_sdk_python/utils/entity_id_helper.py index 03f9a5134..e6c80975f 100644 --- a/src/hiero_sdk_python/utils/entity_id_helper.py +++ b/src/hiero_sdk_python/utils/entity_id_helper.py @@ -1,8 +1,11 @@ +from __future__ import annotations + import re import struct +from typing import TYPE_CHECKING, Any + import requests -from typing import TYPE_CHECKING, Any, Dict if TYPE_CHECKING: from hiero_sdk_python.client.client import Client @@ -13,9 +16,10 @@ P3 = 26**3 P5 = 26**5 + def parse_from_string(address: str) -> tuple[str, str, str, str | None]: """ - Parse an address string of the form: ..[-] + Parse an address string of the form: ..[-]. Args: address: The entity ID string to parse. @@ -38,7 +42,7 @@ def parse_from_string(address: str) -> tuple[str, str, str, str | None]: def generate_checksum(ledger_id: bytes, address: str) -> str: - """ + r""" Compute the 5-character checksum for a Hiero entity ID string (HIP-15). Args: @@ -88,9 +92,7 @@ def generate_checksum(ledger_id: bytes, address: str) -> str: return "".join(reversed(letter)) -def validate_checksum( - shard: int, realm: int, num: int, checksum: str | None, client: "Client" -) -> None: +def validate_checksum(shard: int, realm: int, num: int, checksum: str | None, client: Client) -> None: """ Validate a Hiero entity ID checksum against the current client's ledger. @@ -120,18 +122,12 @@ def validate_checksum( def format_to_string(shard: int, realm: int, num: int) -> str: - """ - Convert an entity ID into its standard string representation. - """ + """Convert an entity ID into its standard string representation.""" return f"{shard}.{realm}.{num}" -def format_to_string_with_checksum( - shard: int, realm: int, num: int, client: "Client" -) -> str: - """ - Convert an entity ID into its string representation with checksum. - """ +def format_to_string_with_checksum(shard: int, realm: int, num: int, client: Client) -> str: + """Convert an entity ID into its string representation with checksum.""" ledger_id = client.network.ledger_id if not ledger_id: raise ValueError("Missing ledger ID in client") @@ -140,7 +136,7 @@ def format_to_string_with_checksum( return f"{base_str}-{generate_checksum(ledger_id, format_to_string(shard, realm, num))}" -def perform_query_to_mirror_node(url: str, timeout: float = 10) -> Dict[str, Any]: +def perform_query_to_mirror_node(url: str, timeout: float = 10) -> dict[str, Any]: """Perform a GET request to the Hedera Mirror Node REST API.""" if not isinstance(url, str) or not url: raise ValueError("url must be a non-empty string") diff --git a/src/hiero_sdk_python/utils/key_format.py b/src/hiero_sdk_python/utils/key_format.py index d973e8800..0f574c65e 100644 --- a/src/hiero_sdk_python/utils/key_format.py +++ b/src/hiero_sdk_python/utils/key_format.py @@ -1,5 +1,8 @@ +from __future__ import annotations + from hiero_sdk_python.hapi.services.basic_types_pb2 import Key + def format_key(key: Key) -> str: """ Converts a protobuf Key into a nicely formatted string: @@ -9,19 +12,18 @@ def format_key(key: Key) -> str: """ if key is None: return "None" - # If PublicKey objects don't have certain method e.g., HasField() (protobuf-only) # check for _to_proto() method and convert before calling it - if hasattr(key, '_to_proto'): - key = key._to_proto() # Handle PublicKey objects (convert to proto first) + if hasattr(key, "_to_proto"): + key = key._to_proto() # Handle PublicKey objects (convert to proto first) if key.HasField("ed25519"): return f"ed25519({key.ed25519.hex()})" - elif key.HasField("thresholdKey"): + if key.HasField("thresholdKey"): return "thresholdKey(...)" - elif key.HasField("keyList"): + if key.HasField("keyList"): return "keyList(...)" - elif key.HasField("contractID"): + if key.HasField("contractID"): return f"contractID({key.contractID})" return str(key).replace("\n", " ") diff --git a/src/hiero_sdk_python/utils/key_utils.py b/src/hiero_sdk_python/utils/key_utils.py index e1b038c22..757a8aa53 100644 --- a/src/hiero_sdk_python/utils/key_utils.py +++ b/src/hiero_sdk_python/utils/key_utils.py @@ -1,21 +1,22 @@ """ -hiero_sdk_python.utils.key_utils +hiero_sdk_python.utils.key_utils. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Utility functions and type definitions for working with cryptographic keys. """ -from typing import Optional, Union +from __future__ import annotations from hiero_sdk_python.crypto.private_key import PrivateKey from hiero_sdk_python.crypto.public_key import PublicKey from hiero_sdk_python.hapi.services import basic_types_pb2 + # Type alias for keys that can be either PrivateKey or PublicKey -Key = Union[PrivateKey, PublicKey] +Key = PrivateKey | PublicKey -def key_to_proto(key: Optional[Key]) -> Optional[basic_types_pb2.Key]: +def key_to_proto(key: Key | None) -> basic_types_pb2.Key | None: """ Helper function to convert a key (PrivateKey or PublicKey) to protobuf Key format. @@ -26,10 +27,10 @@ def key_to_proto(key: Optional[Key]) -> Optional[basic_types_pb2.Key]: Args: key (Optional[Key]): The key to convert (PrivateKey or PublicKey), or None - + Returns: basic_types_pb2.Key (Optional): The protobuf key or None if key is None - + Raises: TypeError: If the provided key is not a PrivateKey, PublicKey, or None. """ @@ -39,11 +40,10 @@ def key_to_proto(key: Optional[Key]) -> Optional[basic_types_pb2.Key]: # If it's a PrivateKey, get the public key first, then convert to proto if isinstance(key, PrivateKey): return key.public_key()._to_proto() - + # If it's a PublicKey, convert directly to proto if isinstance(key, PublicKey): return key._to_proto() # Safety net: This will fail if a non-key is passed raise TypeError("Key must be of type PrivateKey or PublicKey") - diff --git a/src/hiero_sdk_python/utils/subscription_handle.py b/src/hiero_sdk_python/utils/subscription_handle.py index 6f24085d0..f4ad199c1 100644 --- a/src/hiero_sdk_python/utils/subscription_handle.py +++ b/src/hiero_sdk_python/utils/subscription_handle.py @@ -1,35 +1,32 @@ +from __future__ import annotations + import threading + class SubscriptionHandle: """ Represents a handle to an ongoing subscription. + Calling .cancel() will signal the subscription thread to stop. """ + def __init__(self): self._cancelled = threading.Event() - self._thread = None + self._thread = None def cancel(self): - """ - Signals to cancel the subscription. - """ + """Signals to cancel the subscription.""" self._cancelled.set() def is_cancelled(self) -> bool: - """ - Returns True if this subscription is already cancelled. - """ + """Returns True if this subscription is already cancelled.""" return self._cancelled.is_set() def set_thread(self, thread: threading.Thread): - """ - (Optional) Store the thread object for reference. - """ + """(Optional) Store the thread object for reference.""" self._thread = thread def join(self, timeout=None): - """ - (Optional) Wait for the subscription thread to end. - """ + """(Optional) Wait for the subscription thread to end.""" if self._thread: self._thread.join(timeout) diff --git a/tck/__init__.py b/tck/__init__.py index f34a652cb..b7eaa2e45 100644 --- a/tck/__init__.py +++ b/tck/__init__.py @@ -2,4 +2,5 @@ from .server import start_server + __all__ = ["start_server"] diff --git a/tck/__main__.py b/tck/__main__.py index 85a432b8d..119616eea 100644 --- a/tck/__main__.py +++ b/tck/__main__.py @@ -1,5 +1,7 @@ """Main module to start the TCK server.""" +from __future__ import annotations + from . import start_server diff --git a/tck/errors.py b/tck/errors.py index 781a6758a..d6656fd78 100644 --- a/tck/errors.py +++ b/tck/errors.py @@ -1,11 +1,14 @@ """Error code constants for the TCK server.""" -from functools import wraps +from __future__ import annotations + import logging +from functools import wraps from hiero_sdk_python.exceptions import MaxAttemptsError, PrecheckError, ReceiptStatusError from hiero_sdk_python.response_code import ResponseCode + # These constants can be used throughout the codebase to represent specific error conditions. PARSE_ERROR = -32700 INVALID_REQUEST = -32600 @@ -34,7 +37,7 @@ def to_dict(self): return error @classmethod - def parse_error(cls, data=None, message: str = "Parse error") -> "JsonRpcError": + def parse_error(cls, data=None, message: str = "Parse error") -> JsonRpcError: """Create a Parse Error JSON-RPC error. Args: @@ -44,9 +47,7 @@ def parse_error(cls, data=None, message: str = "Parse error") -> "JsonRpcError": return cls(PARSE_ERROR, message, data) @classmethod - def invalid_request_error( - cls, data=None, message: str = "Invalid Request" - ) -> "JsonRpcError": + def invalid_request_error(cls, data=None, message: str = "Invalid Request") -> JsonRpcError: """Create an Invalid Request JSON-RPC error. Args: @@ -56,9 +57,7 @@ def invalid_request_error( return cls(INVALID_REQUEST, message, data) @classmethod - def method_not_found_error( - cls, data=None, message: str = "Method not found" - ) -> "JsonRpcError": + def method_not_found_error(cls, data=None, message: str = "Method not found") -> JsonRpcError: """Create a Method Not Found JSON-RPC error. Args: @@ -68,9 +67,7 @@ def method_not_found_error( return cls(METHOD_NOT_FOUND, message, data) @classmethod - def invalid_params_error( - cls, data=None, message: str = "Invalid params" - ) -> "JsonRpcError": + def invalid_params_error(cls, data=None, message: str = "Invalid params") -> JsonRpcError: """Create an Invalid Params JSON-RPC error. Args: @@ -80,9 +77,7 @@ def invalid_params_error( return cls(INVALID_PARAMS, message, data) @classmethod - def internal_error( - cls, data=None, message: str = "Internal error" - ) -> "JsonRpcError": + def internal_error(cls, data=None, message: str = "Internal error") -> JsonRpcError: """Create an Internal Error JSON-RPC error. Args: @@ -92,7 +87,7 @@ def internal_error( return cls(INTERNAL_ERROR, message, data) @classmethod - def hiero_error(cls, data=None, message: str = "Hiero error") -> "JsonRpcError": + def hiero_error(cls, data=None, message: str = "Hiero error") -> JsonRpcError: """Create a Hiero-specific JSON-RPC error. Args: @@ -111,24 +106,18 @@ def wrapper(*args, **kwargs): raise except PrecheckError as e: logger.error(f"PrecheckError (status: {ResponseCode(e.status).name}, method: {func.__name__})") - raise JsonRpcError.hiero_error( - {"status": ResponseCode(e.status).name} - ) + raise JsonRpcError.hiero_error({"status": ResponseCode(e.status).name}) from e except ReceiptStatusError as e: logger.error(f"ReceiptStatusError (status: {ResponseCode(e.status).name}, method: {func.__name__})") - raise JsonRpcError.hiero_error( - {"status": ResponseCode(e.status).name} - ) - + raise JsonRpcError.hiero_error({"status": ResponseCode(e.status).name}) from e + except MaxAttemptsError as e: logger.error(f"MaxAttemptsError (method: {func.__name__})") - raise JsonRpcError.hiero_error( - message= str(e) - ) + raise JsonRpcError.hiero_error(message=str(e)) from e except Exception as e: logger.exception("Unhandled error in RPC handler") - raise JsonRpcError.internal_error(message="Internal error") + raise JsonRpcError.internal_error(message="Internal error") from e return wrapper diff --git a/tck/handlers/__init__.py b/tck/handlers/__init__.py index 93dad61ea..5b76afd46 100644 --- a/tck/handlers/__init__.py +++ b/tck/handlers/__init__.py @@ -1,19 +1,24 @@ """TCK handlers - auto-import all handler modules.""" # Import registry functions first to make them available +# Import all handler modules to trigger @rpc_method decorators +from . import ( + account, + key, + sdk, # setup, reset +) from .registry import ( - get_handler, get_all_handlers, + get_handler, safe_dispatch, ) -# Import all handler modules to trigger @rpc_method decorators -from . import sdk # setup, reset -from . import key -from . import account __all__ = [ "get_handler", "get_all_handlers", "safe_dispatch", + "account", + "key", + "sdk", ] diff --git a/tck/handlers/account.py b/tck/handlers/account.py index 521a6633e..1b11a0372 100644 --- a/tck/handlers/account.py +++ b/tck/handlers/account.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from hiero_sdk_python.account.account_create_transaction import AccountCreateTransaction from hiero_sdk_python.account.account_id import AccountId from hiero_sdk_python.hbar import Hbar @@ -24,9 +26,7 @@ def _build_create_account_transaction(params: CreateAccountParams) -> AccountCre transaction.set_receiver_signature_required(params.receiverSignatureRequired) if params.maxAutoTokenAssociations is not None: - transaction.set_max_automatic_token_associations( - params.maxAutoTokenAssociations - ) + transaction.set_max_automatic_token_associations(params.maxAutoTokenAssociations) if params.stakedAccountId is not None: transaction.set_staked_account_id(AccountId.from_string(params.stakedAccountId)) diff --git a/tck/handlers/key.py b/tck/handlers/key.py index a93c596bd..c198110b0 100644 --- a/tck/handlers/key.py +++ b/tck/handlers/key.py @@ -1,7 +1,8 @@ +from __future__ import annotations + from hiero_sdk_python.crypto.key_list import KeyList from hiero_sdk_python.crypto.private_key import PrivateKey from hiero_sdk_python.crypto.public_key import PublicKey -from hiero_sdk_python.hapi.services.basic_types_pb2 import Key from tck.errors import JsonRpcError from tck.handlers.registry import rpc_method from tck.param.key import KeyGenerationParams @@ -22,9 +23,7 @@ def generate_key(params: KeyGenerationParams) -> KeyGenerationResponse: ) if params.threshold is not None and params.type != KeyType.THRESHOLD_KEY: - raise JsonRpcError.invalid_params_error( - "invalid parameters: threshold is only allowed for thresholdKey types." - ) + raise JsonRpcError.invalid_params_error("invalid parameters: threshold is only allowed for thresholdKey types.") if params.type == KeyType.THRESHOLD_KEY and params.threshold is None: raise JsonRpcError.invalid_params_error( @@ -42,16 +41,12 @@ def generate_key(params: KeyGenerationParams) -> KeyGenerationResponse: ) response = KeyGenerationResponse() - response.key = _process_key_recursively( - params=params, response=response, is_list=False - ) + response.key = _process_key_recursively(params=params, response=response, is_list=False) return response -def _handle_private_key( - params: KeyGenerationParams, response: KeyGenerationResponse, is_list: bool -) -> str: +def _handle_private_key(params: KeyGenerationParams, response: KeyGenerationResponse, is_list: bool) -> str: if params.type == KeyType.ED25519_PRIVATE_KEY: private_key = PrivateKey.generate_ed25519() else: @@ -65,9 +60,7 @@ def _handle_private_key( return private_key_string -def _handle_public_key( - params: KeyGenerationParams, response: KeyGenerationResponse, is_list: bool -) -> str: +def _handle_public_key(params: KeyGenerationParams, response: KeyGenerationResponse, is_list: bool) -> str: if params.fromKey: return PrivateKey.from_string(params.fromKey).public_key().to_string_der() @@ -82,15 +75,11 @@ def _handle_public_key( return private_key.public_key().to_string_der() -def _handle_key_list( - params: KeyGenerationParams, response: KeyGenerationResponse, is_list: bool -) -> str: +def _handle_key_list(params: KeyGenerationParams, response: KeyGenerationResponse) -> str: key_list = KeyList() for key_params in params.keys: - key_string = _process_key_recursively( - params=key_params, response=response, is_list=True - ) + key_string = _process_key_recursively(params=key_params, response=response, is_list=True) key_list.add_key(get_key_from_string(key_string)) if params.type == KeyType.THRESHOLD_KEY: @@ -99,44 +88,34 @@ def _handle_key_list( return key_list.to_bytes().hex() -def _handle_evm_address( - params: KeyGenerationParams, response: KeyGenerationResponse, is_list: bool -) -> str: +def _handle_evm_address(params: KeyGenerationParams) -> str: if params.fromKey: key = get_key_from_string(params.fromKey) if isinstance(key, PrivateKey): return str(key.public_key().to_evm_address()) - elif isinstance(key, PublicKey): + if isinstance(key, PublicKey): return str(key.to_evm_address()) - else: - raise JsonRpcError.invalid_params_error( - "invalid parameters: fromKey for evmAddress is not ECDSAsecp256k1." - ) + raise JsonRpcError.invalid_params_error("invalid parameters: fromKey for evmAddress is not ECDSAsecp256k1.") return str(PrivateKey.generate_ecdsa().public_key().to_evm_address()) -def _process_key_recursively( - params: KeyGenerationParams, response: KeyGenerationResponse, is_list: bool -) -> str: +def _process_key_recursively(params: KeyGenerationParams, response: KeyGenerationResponse, is_list: bool) -> str: if params.type in { KeyType.ED25519_PRIVATE_KEY, KeyType.ECDSA_SECP256K1_PRIVATE_KEY, }: return _handle_private_key(params, response, is_list) - elif params.type in { + if params.type in { KeyType.ED25519_PUBLIC_KEY, KeyType.ECDSA_SECP256K1_PUBLIC_KEY, }: return _handle_public_key(params, response, is_list) - elif params.type in {KeyType.LIST_KEY, KeyType.THRESHOLD_KEY}: - return _handle_key_list(params, response, is_list) - elif params.type == KeyType.EVM_ADDRESS_KEY: - return _handle_evm_address(params, response, is_list) - else: - raise JsonRpcError.invalid_params_error( - "invalid request: key type not recognized." - ) + if params.type in {KeyType.LIST_KEY, KeyType.THRESHOLD_KEY}: + return _handle_key_list(params, response) + if params.type == KeyType.EVM_ADDRESS_KEY: + return _handle_evm_address(params) + raise JsonRpcError.invalid_params_error("invalid request: key type not recognized.") diff --git a/tck/handlers/registry.py b/tck/handlers/registry.py index 1960a2c4f..9dbdf6d76 100644 --- a/tck/handlers/registry.py +++ b/tck/handlers/registry.py @@ -1,14 +1,20 @@ """Build a flexible registry-based method routing system that can dispatch -requests to handlers and transform exceptions into JSON-RPC errors.""" +requests to handlers and transform exceptions into JSON-RPC errors. +""" + +from __future__ import annotations -from dataclasses import asdict import inspect -from typing import Any, Dict, Optional, Union, Callable +from collections.abc import Callable +from dataclasses import asdict +from typing import Any + from tck.errors import JsonRpcError, handle_sdk_errors from tck.protocol import build_json_rpc_error_response + # A global _HANDLERS dict to store method name -> handler function mappings -_HANDLERS: Dict[str, Callable] = {} +_HANDLERS: dict[str, Callable] = {} def rpc_method(method_name: str): @@ -22,12 +28,12 @@ def decorator(func: Callable) -> Callable: return decorator -def get_handler(method_name: str) -> Optional[Callable]: +def get_handler(method_name: str) -> Callable | None: """Retrieve a handler by method name.""" return _HANDLERS.get(method_name) -def get_all_handlers() -> Dict[str, Callable]: +def get_all_handlers() -> dict[str, Callable]: """Get all registered handlers.""" return _HANDLERS.copy() @@ -37,20 +43,18 @@ def dispatch(method_name: str, params: Any) -> Any: handler = get_handler(method_name) if handler is None: - raise JsonRpcError.method_not_found_error( - message=f"Method not found: {method_name}" - ) + raise JsonRpcError.method_not_found_error(message=f"Method not found: {method_name}") try: signature = inspect.signature(handler) parameters = list(signature.parameters.values()) param_type = parameters[0].annotation - + try: params = param_type.parse_json_params(params) except (TypeError, ValueError) as e: raise JsonRpcError.invalid_params_error(data=str(e)) from e - + result = handler(params) return parse_result(result) @@ -61,9 +65,7 @@ def dispatch(method_name: str, params: Any) -> Any: raise JsonRpcError.internal_error(data=str(e)) from e -def safe_dispatch( - method_name: str, params: Any, request_id: Optional[Union[str, int]] -) -> Union[Any, Dict[str, Any]]: +def safe_dispatch(method_name: str, params: Any, request_id: str | int | None) -> Any | dict[str, Any]: """Safely dispatch the request and handle exceptions.""" try: return dispatch(method_name, params) diff --git a/tck/handlers/sdk.py b/tck/handlers/sdk.py index 964cd37d9..4a169dccf 100644 --- a/tck/handlers/sdk.py +++ b/tck/handlers/sdk.py @@ -1,5 +1,7 @@ +from __future__ import annotations + +from hiero_sdk_python import AccountId, Client, Network, PrivateKey from hiero_sdk_python.node import _Node -from hiero_sdk_python import Client, AccountId, PrivateKey, Network from tck.handlers.registry import rpc_method from tck.param.base import BaseParams from tck.param.sdk import SetupParams @@ -15,9 +17,7 @@ def setup_handler(params: SetupParams) -> SetupResponse: if params.nodeIp and params.nodeAccountId and params.mirrorNetworkIp: client = Client() client.network = Network( - nodes=[ - _Node(AccountId.from_string(params.nodeAccountId), params.nodeIp, None) - ], + nodes=[_Node(AccountId.from_string(params.nodeAccountId), params.nodeIp, None)], mirror_address=params.mirrorNetworkIp, ) client.set_operator(operator_account_id, operator_private_key) diff --git a/tck/param/account.py b/tck/param/account.py index 5682e41a7..58087aece 100644 --- a/tck/param/account.py +++ b/tck/param/account.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from dataclasses import dataclass from tck.param.base import BaseTransactionParams @@ -23,7 +25,7 @@ class CreateAccountParams(BaseTransactionParams): alias: str | None = None @classmethod - def parse_json_params(cls, params: dict) -> "CreateAccountParams": + def parse_json_params(cls, params: dict) -> CreateAccountParams: return cls( key=params.get("key"), initialBalance=to_int(params.get("initialBalance")), diff --git a/tck/param/base.py b/tck/param/base.py index 4ca35089e..5b4a083e3 100644 --- a/tck/param/base.py +++ b/tck/param/base.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from dataclasses import dataclass from tck.param.common import CommonTransactionParams @@ -9,7 +11,7 @@ class BaseParams: sessionId: str = None @classmethod - def parse_json_params(cls, params: dict) -> "BaseParams": + def parse_json_params(cls, params: dict) -> BaseParams: return cls(parse_session_id(params)) diff --git a/tck/param/common.py b/tck/param/common.py index b9e3da87c..7e90038d5 100644 --- a/tck/param/common.py +++ b/tck/param/common.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from dataclasses import dataclass from hiero_sdk_python.account.account_id import AccountId @@ -18,7 +20,7 @@ class CommonTransactionParams: signers: list[str] | None = None @classmethod - def parse_json_params(cls, params: dict) -> "CommonTransactionParams": + def parse_json_params(cls, params: dict) -> CommonTransactionParams: return cls( transactionId=params.get("transactionId"), maxTransactionFee=to_int(params.get("maxTransactionFee")), @@ -32,13 +34,9 @@ def apply_common_params(self, transaction: Transaction, client: Client) -> None: """Apply commonTransactionParams to a given transaction.""" if self.transactionId is not None: try: - transaction.set_transaction_id( - TransactionId.from_string(self.transactionId) - ) - except: - transaction.set_transaction_id( - TransactionId.generate(AccountId.from_string(self.transactionId)) - ) + transaction.set_transaction_id(TransactionId.from_string(self.transactionId)) + except Exception: + transaction.set_transaction_id(TransactionId.generate(AccountId.from_string(self.transactionId))) # TODO add a max_transaction_fee sdk missing func diff --git a/tck/param/key.py b/tck/param/key.py index cf7998699..3453c24f9 100644 --- a/tck/param/key.py +++ b/tck/param/key.py @@ -1,5 +1,6 @@ +from __future__ import annotations + from dataclasses import dataclass -from typing import List from tck.util.key_utils import KeyType @@ -9,16 +10,14 @@ class KeyGenerationParams: type: KeyType = None fromKey: str | None = None threshold: int | None = None - keys: List["KeyGenerationParams"] | None = None + keys: list[KeyGenerationParams] | None = None @classmethod - def parse_json_params(cls, params: dict) -> "KeyGenerationParams": + def parse_json_params(cls, params: dict) -> KeyGenerationParams: key_list = params.get("keys") or [] return cls( - type=( - KeyType.from_string(params.get("type")) if params.get("type") else None - ), + type=(KeyType.from_string(params.get("type")) if params.get("type") else None), fromKey=params.get("fromKey"), threshold=params.get("threshold"), keys=[cls.parse_json_params(k) for k in key_list], diff --git a/tck/param/sdk.py b/tck/param/sdk.py index b9ce539aa..b62ae6687 100644 --- a/tck/param/sdk.py +++ b/tck/param/sdk.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from dataclasses import dataclass from tck.param.base import BaseParams @@ -13,7 +15,7 @@ class SetupParams(BaseParams): mirrorNetworkIp: str | None = None @classmethod - def parse_json_params(cls, params: dict) -> "SetupParams": + def parse_json_params(cls, params: dict) -> SetupParams: return cls( operatorAccountId=params.get("operatorAccountId"), operatorPrivateKey=params.get("operatorPrivateKey"), diff --git a/tck/protocol.py b/tck/protocol.py index a5e5a938d..4098a9445 100644 --- a/tck/protocol.py +++ b/tck/protocol.py @@ -1,9 +1,12 @@ +from __future__ import annotations + import json -from typing import Any, Dict, Optional, Union +from typing import Any + from tck.errors import JsonRpcError -def _normalize_request_input(request_in: Any) -> Union[Dict[str, Any], JsonRpcError]: +def _normalize_request_input(request_in: Any) -> dict[str, Any] | JsonRpcError: """Normalize request input to a dictionary Args: request_in: Either a JSON string or a pre-parsed dict @@ -23,8 +26,9 @@ def _normalize_request_input(request_in: Any) -> Union[Dict[str, Any], JsonRpcEr return JsonRpcError.invalid_request_error() -def _validate_json_rpc_structure(request: Dict[str, Any]) -> Optional[JsonRpcError]: +def _validate_json_rpc_structure(request: dict[str, Any]) -> JsonRpcError | None: """Validate the basic JSON-RPC 2.0 structure. + Args: request: The parsed request dictionary @@ -51,8 +55,9 @@ def _validate_json_rpc_structure(request: Dict[str, Any]) -> Optional[JsonRpcErr return None -def _extract_session_id(params: Any) -> Optional[str]: +def _extract_session_id(params: Any) -> str | None: """Extract session ID from params if present. + Args: params: Request parameters (dict, list, or None) @@ -64,7 +69,7 @@ def _extract_session_id(params: Any) -> Optional[str]: return None -def parse_json_rpc_request(request_in: Any) -> Union[Dict[str, Any], JsonRpcError]: +def parse_json_rpc_request(request_in: Any) -> dict[str, Any] | JsonRpcError: """Parse and validate a JSON-RPC 2.0 request. Accepts either a JSON string or a pre-parsed dict (e.g., Flask request body). @@ -92,27 +97,21 @@ def parse_json_rpc_request(request_in: Any) -> Union[Dict[str, Any], JsonRpcErro } -def build_json_rpc_success_response( - result: Any, request_id: Optional[Union[str, int]] -) -> Dict[str, Any]: +def build_json_rpc_success_response(result: Any, request_id: str | int | None) -> dict[str, Any]: """Build a JSON-RPC 2.0 success response.""" - response = { + return { "jsonrpc": "2.0", "id": request_id, "result": result, } - return response -def build_json_rpc_error_response( - error: JsonRpcError, request_id: Optional[Union[str, int]] -) -> Dict[str, Any]: +def build_json_rpc_error_response(error: JsonRpcError, request_id: str | int | None) -> dict[str, Any]: """Build a JSON-RPC 2.0 error response.""" error_obj = error.to_dict() - response = { + return { "jsonrpc": "2.0", "id": request_id, "error": error_obj, } - return response diff --git a/tck/response/account.py b/tck/response/account.py index 8cd41a469..73d4c72b3 100644 --- a/tck/response/account.py +++ b/tck/response/account.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from dataclasses import dataclass diff --git a/tck/response/key.py b/tck/response/key.py index 4413ef89d..77f512b3c 100644 --- a/tck/response/key.py +++ b/tck/response/key.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from dataclasses import dataclass, field diff --git a/tck/response/sdk.py b/tck/response/sdk.py index 3bfa835ab..591d8743c 100644 --- a/tck/response/sdk.py +++ b/tck/response/sdk.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from dataclasses import dataclass diff --git a/tck/server.py b/tck/server.py index 6d57fc9a1..af2bc0e52 100644 --- a/tck/server.py +++ b/tck/server.py @@ -1,9 +1,13 @@ """TCK Server implementation using Flask.""" +from __future__ import annotations + +import logging import os from dataclasses import dataclass, field -import logging + from flask import Flask, jsonify, request + from tck.errors import JsonRpcError from tck.handlers import safe_dispatch from tck.protocol import ( @@ -18,18 +22,14 @@ class ServerConfig: """Configuration for the TCK server.""" host: str = field(default_factory=lambda: os.getenv("TCK_HOST", "localhost")) - port: int = field( - default_factory=lambda: _parse_port(os.getenv("TCK_PORT", "8544")) - ) + port: int = field(default_factory=lambda: _parse_port(os.getenv("TCK_PORT", "8544"))) def _parse_port(port_str: str) -> int: try: port = int(port_str) except ValueError as exc: - raise ValueError( - f"TCK_PORT must be a valid integer, got: '{port_str}'" - ) from exc + raise ValueError(f"TCK_PORT must be a valid integer, got: '{port_str}'") from exc if not (1 <= port <= 65535): raise ValueError(f"TCK_PORT must be between 1 and 65535, got: {port}") return port @@ -43,11 +43,9 @@ def _parse_port(port_str: str) -> int: def json_rpc_endpoint(): """JSON-RPC 2.0 endpoint to handle requests.""" if request.mimetype != "application/json": - error = JsonRpcError.parse_error( - message="Parse error: Content-Type must be application/json" - ) + error = JsonRpcError.parse_error(message="Parse error: Content-Type must be application/json") return jsonify(build_json_rpc_error_response(error, None)) - + try: request_json = request.get_json(force=True) except Exception: @@ -70,7 +68,7 @@ def json_rpc_endpoint(): # If the response is already an error response, return it directly if isinstance(response, dict) and "jsonrpc" in response and "error" in response: return jsonify(response) - + return jsonify(build_json_rpc_success_response(response, request_id)) diff --git a/tck/util/client_utils.py b/tck/util/client_utils.py index b8ab1c8ef..ac214c973 100644 --- a/tck/util/client_utils.py +++ b/tck/util/client_utils.py @@ -1,6 +1,10 @@ -from hiero_sdk_python import Client +from __future__ import annotations + import threading +from hiero_sdk_python import Client + + _CLIENTS: dict[str, Client] = {} _LOCK = threading.Lock() diff --git a/tck/util/constants.py b/tck/util/constants.py index f200c1a67..2fc9b85b4 100644 --- a/tck/util/constants.py +++ b/tck/util/constants.py @@ -1 +1,4 @@ -DEFAULT_GRPC_TIMEOUT=30 +from __future__ import annotations + + +DEFAULT_GRPC_TIMEOUT = 30 diff --git a/tck/util/key_utils.py b/tck/util/key_utils.py index dbc9eef84..ca85eb767 100644 --- a/tck/util/key_utils.py +++ b/tck/util/key_utils.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from enum import Enum from hiero_sdk_python.crypto.key import Key @@ -27,7 +29,7 @@ def from_string(cls, key_type_str: str): def get_key_from_string(key_string: str) -> Key: """Helper to convert the str value to Key.""" key_bytes = bytes.fromhex(key_string) - + try: return Key.from_bytes(key_bytes) except Exception: diff --git a/tck/util/param_utils.py b/tck/util/param_utils.py index 0be500259..49a12b4db 100644 --- a/tck/util/param_utils.py +++ b/tck/util/param_utils.py @@ -1,3 +1,6 @@ +from __future__ import annotations + + def parse_session_id(params: dict) -> str: """Parse sessionId from the json rpc params.""" session_id = params.get("sessionId") @@ -16,9 +19,7 @@ def parse_common_transaction_params(params: dict): if common_params is None: return None - return CommonTransactionParams.parse_json_params( - params.get("commonTransactionParams") - ) + return CommonTransactionParams.parse_json_params(params.get("commonTransactionParams")) def to_int(value) -> int | None: diff --git a/tests/fuzz/__init__.py b/tests/fuzz/__init__.py index 2e839778b..3bb0f7bd8 100644 --- a/tests/fuzz/__init__.py +++ b/tests/fuzz/__init__.py @@ -1 +1 @@ -# with <3 from Anto \ No newline at end of file +# with <3 from Anto diff --git a/tests/fuzz/conftest.py b/tests/fuzz/conftest.py index 60048d26c..0c523f1fe 100644 --- a/tests/fuzz/conftest.py +++ b/tests/fuzz/conftest.py @@ -1,5 +1,7 @@ """Shared Hypothesis setup, fixtures, and compatibility re-exports for fuzz tests.""" +from __future__ import annotations + from tests.fuzz.support.classes import ( AccountIdAliasCase, ContractValueCase, @@ -16,6 +18,7 @@ get_strategy_fixture, ) + load_hypothesis_profile() __all__ = [ diff --git a/tests/fuzz/support/__init__.py b/tests/fuzz/support/__init__.py index fe7052834..f0eb86dfa 100644 --- a/tests/fuzz/support/__init__.py +++ b/tests/fuzz/support/__init__.py @@ -8,6 +8,7 @@ ) from tests.fuzz.support.registry import FUZZ_STRATEGIES, get_strategy + __all__ = [ "AccountIdAliasCase", "ContractValueCase", diff --git a/tests/fuzz/support/classes.py b/tests/fuzz/support/classes.py index 28e076888..72bc36fb1 100644 --- a/tests/fuzz/support/classes.py +++ b/tests/fuzz/support/classes.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from dataclasses import dataclass from decimal import Decimal from typing import Any diff --git a/tests/fuzz/support/helpers.py b/tests/fuzz/support/helpers.py index fa7453133..a5242095b 100644 --- a/tests/fuzz/support/helpers.py +++ b/tests/fuzz/support/helpers.py @@ -1,10 +1,11 @@ +from __future__ import annotations + from decimal import Decimal from hypothesis import strategies as st from hypothesis.strategies import SearchStrategy from hiero_sdk_python import AccountId, HbarUnit, PrivateKey, TransactionId, TransferTransaction - from tests.fuzz.support.classes import HbarConstructorCase, HbarStringCase @@ -49,11 +50,7 @@ def build_valid_transaction_bytes() -> tuple[bytes, bytes]: node_id = AccountId.from_string("0.0.3") receiver_id = AccountId.from_string("0.0.5678") - tx = ( - TransferTransaction() - .add_hbar_transfer(operator_id, -100_000_000) - .add_hbar_transfer(receiver_id, 100_000_000) - ) + tx = TransferTransaction().add_hbar_transfer(operator_id, -100_000_000).add_hbar_transfer(receiver_id, 100_000_000) tx.transaction_id = TransactionId.generate(operator_id) tx.node_account_id = node_id tx.freeze() diff --git a/tests/fuzz/support/profiles.py b/tests/fuzz/support/profiles.py index 5554550f2..75471f175 100644 --- a/tests/fuzz/support/profiles.py +++ b/tests/fuzz/support/profiles.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import os from hypothesis import HealthCheck, settings diff --git a/tests/fuzz/support/registry.py b/tests/fuzz/support/registry.py index a5b93c1f7..611df9b4e 100644 --- a/tests/fuzz/support/registry.py +++ b/tests/fuzz/support/registry.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import string from decimal import Decimal from typing import Any, NamedTuple @@ -8,7 +10,6 @@ from hypothesis.strategies import SearchStrategy from hiero_sdk_python import HbarUnit, PrivateKey - from tests.fuzz.support.classes import ( AccountIdAliasCase, ContractValueCase, @@ -23,6 +24,7 @@ with_optional_0x, ) + MAX_I64 = 2**63 - 1 MIN_I64 = -(2**63) @@ -55,7 +57,6 @@ class _KeySampleValues(NamedTuple): def _build_identifier_primitives() -> _IdentifierPrimitives: """Build the shared scalar strategies used to compose Hedera-style identifiers.""" - return _IdentifierPrimitives( shard=st.integers(min_value=0, max_value=4096), realm=st.integers(min_value=0, max_value=4096), @@ -66,7 +67,6 @@ def _build_identifier_primitives() -> _IdentifierPrimitives: def _build_key_sample_values() -> _KeySampleValues: """Build canonical valid key samples once so all dependent strategies stay consistent.""" - ed_private_raw = "01" * 32 ecdsa_private_raw = "00" * 31 + "01" @@ -95,7 +95,6 @@ def _build_entity_id_strategies( primitives: _IdentifierPrimitives, ) -> dict[str, SearchStrategy[Any]]: """Build valid dotted and checksum entity ID strategies with the current numeric bounds.""" - entity_id_valid_dotted = st.builds( lambda shard_value, realm_value, num_value: EntityIdCase( text=f"{shard_value}.{realm_value}.{num_value}", @@ -129,7 +128,6 @@ def _build_alias_identifier_strategies( primitives: _IdentifierPrimitives, key_samples: _KeySampleValues ) -> dict[str, SearchStrategy[Any]]: """Build valid account and contract alias strategies using canonical alias and EVM samples.""" - ed25519_alias_hex = st.just(key_samples.ed25519_alias_hex) ecdsa_alias_hex = st.just(key_samples.ecdsa_alias_hex) evm_hex = st.just(key_samples.evm_hex) @@ -147,9 +145,7 @@ def _build_alias_identifier_strategies( ) account_id_valid_evm = st.one_of( evm_hex.map(lambda value: AccountIdAliasCase(text=value, shard=0, realm=0, evm_hex=value)), - evm_hex.map( - lambda value: AccountIdAliasCase(text=f"0x{value}", shard=0, realm=0, evm_hex=value) - ), + evm_hex.map(lambda value: AccountIdAliasCase(text=f"0x{value}", shard=0, realm=0, evm_hex=value)), st.builds( lambda shard_value, realm_value, value: AccountIdAliasCase( text=f"{shard_value}.{realm_value}.{value}", @@ -183,7 +179,6 @@ def _build_alias_identifier_strategies( def _build_invalid_identifier_strategies() -> dict[str, SearchStrategy[Any]]: """Build malformed identifier strategies while preserving existing invalid-string coverage.""" - invalid_entity_strings = st.one_of( st.sampled_from( [ @@ -248,11 +243,8 @@ def _build_invalid_identifier_strategies() -> dict[str, SearchStrategy[Any]]: def _build_private_key_strategies(key_samples: _KeySampleValues) -> dict[str, SearchStrategy[Any]]: """Build the private key strategies from the canonical valid sample encodings.""" - private_key_valid_string = st.one_of( - with_optional_0x( - st.sampled_from([key_samples.ed_private_raw, key_samples.ed_private_der]) - ), + with_optional_0x(st.sampled_from([key_samples.ed_private_raw, key_samples.ed_private_der])), ) private_key_valid_bytes = st.sampled_from( [ @@ -290,7 +282,6 @@ def _build_private_key_strategies(key_samples: _KeySampleValues) -> dict[str, Se def _build_public_key_strategies(key_samples: _KeySampleValues) -> dict[str, SearchStrategy[Any]]: """Build the public key strategies from the canonical valid sample encodings.""" - public_key_valid_string = st.one_of( with_optional_0x( st.sampled_from( @@ -343,7 +334,6 @@ def _build_public_key_strategies(key_samples: _KeySampleValues) -> dict[str, Sea def _build_key_strategies(key_samples: _KeySampleValues) -> dict[str, SearchStrategy[Any]]: """Build the complete private and public key strategy registry entries.""" - strategies: dict[str, SearchStrategy[Any]] = {} strategies.update(_build_private_key_strategies(key_samples)) strategies.update(_build_public_key_strategies(key_samples)) @@ -352,7 +342,6 @@ def _build_key_strategies(key_samples: _KeySampleValues) -> dict[str, SearchStra def _build_hbar_strategies() -> dict[str, SearchStrategy[Any]]: """Build Hbar parsing and constructor strategies, including intentional invalid edge cases.""" - hbar_valid_tinybars = st.integers(min_value=-(10**12), max_value=10**12) hbar_valid_string = st.one_of( hbar_valid_tinybars.map(lambda tinybars: hbar_string_case(HbarUnit.HBAR, tinybars)), @@ -408,7 +397,6 @@ def _build_hbar_strategies() -> dict[str, SearchStrategy[Any]]: def _build_transaction_byte_strategies() -> dict[str, SearchStrategy[Any]]: """Build valid and intentionally broken transaction byte payload strategies.""" - unsigned_tx_bytes, signed_tx_bytes = build_valid_transaction_bytes() tx_valid_bytes = st.sampled_from([unsigned_tx_bytes, signed_tx_bytes]) tx_invalid_empty = st.just(b"") @@ -442,35 +430,20 @@ def _build_transaction_byte_strategies() -> dict[str, SearchStrategy[Any]]: def _build_contract_value_strategies() -> dict[str, SearchStrategy[Any]]: """Build valid and invalid contract parameter cases without changing ABI edge coverage.""" - valid_contract_value = st.one_of( st.booleans().map(lambda value: ContractValueCase("add_bool", value)), - st.integers(min_value=-(2**31), max_value=2**31 - 1).map( - lambda value: ContractValueCase("add_int32", value) - ), - st.integers(min_value=0, max_value=2**32 - 1).map( - lambda value: ContractValueCase("add_uint32", value) - ), - st.integers(min_value=MIN_I64, max_value=MAX_I64).map( - lambda value: ContractValueCase("add_int64", value) - ), - st.integers(min_value=0, max_value=2**64 - 1).map( - lambda value: ContractValueCase("add_uint64", value) - ), + st.integers(min_value=-(2**31), max_value=2**31 - 1).map(lambda value: ContractValueCase("add_int32", value)), + st.integers(min_value=0, max_value=2**32 - 1).map(lambda value: ContractValueCase("add_uint32", value)), + st.integers(min_value=MIN_I64, max_value=MAX_I64).map(lambda value: ContractValueCase("add_int64", value)), + st.integers(min_value=0, max_value=2**64 - 1).map(lambda value: ContractValueCase("add_uint64", value)), st.integers(min_value=-(2**255), max_value=2**255 - 1).map( lambda value: ContractValueCase("add_int256", value) ), - st.integers(min_value=0, max_value=2**256 - 1).map( - lambda value: ContractValueCase("add_uint256", value) - ), + st.integers(min_value=0, max_value=2**256 - 1).map(lambda value: ContractValueCase("add_uint256", value)), st.text(min_size=0, max_size=32).map(lambda value: ContractValueCase("add_string", value)), st.binary(min_size=0, max_size=64).map(lambda value: ContractValueCase("add_bytes", value)), - st.binary(min_size=0, max_size=32).map( - lambda value: ContractValueCase("add_bytes32", value) - ), - st.binary(min_size=20, max_size=20).map( - lambda value: ContractValueCase("add_address", value) - ), + st.binary(min_size=0, max_size=32).map(lambda value: ContractValueCase("add_bytes32", value)), + st.binary(min_size=20, max_size=20).map(lambda value: ContractValueCase("add_address", value)), with_optional_0x(sized_hex(20)).map(lambda value: ContractValueCase("add_address", value)), st.lists(st.integers(min_value=-(2**31), max_value=2**31 - 1), min_size=0, max_size=6).map( lambda value: ContractValueCase("add_int32_array", value) @@ -516,7 +489,6 @@ def _build_contract_value_strategies() -> dict[str, SearchStrategy[Any]]: def build_strategy_registry() -> dict[str, SearchStrategy[Any]]: """Assemble all domain-specific strategy groups into the shared fuzz registry.""" - primitives = _build_identifier_primitives() key_samples = _build_key_sample_values() @@ -536,7 +508,6 @@ def build_strategy_registry() -> dict[str, SearchStrategy[Any]]: def get_strategy(name: str) -> SearchStrategy[Any]: """Return the named fuzz strategy from the shared registry or raise a helpful KeyError.""" - try: return FUZZ_STRATEGIES[name] except KeyError as exc: @@ -547,12 +518,10 @@ def get_strategy(name: str) -> SearchStrategy[Any]: @pytest.fixture(name="fuzz_strategies") def fuzz_strategies_fixture() -> dict[str, SearchStrategy[Any]]: """Provide the shared fuzz strategy registry.""" - return FUZZ_STRATEGIES @pytest.fixture(name="get_strategy") def get_strategy_fixture() -> Any: """Provide the named strategy accessor.""" - return get_strategy diff --git a/tests/fuzz/test_contract_function_parameters_fuzz.py b/tests/fuzz/test_contract_function_parameters_fuzz.py index 22121bf5b..5e870ec82 100644 --- a/tests/fuzz/test_contract_function_parameters_fuzz.py +++ b/tests/fuzz/test_contract_function_parameters_fuzz.py @@ -1,11 +1,13 @@ +from __future__ import annotations + import pytest from eth_abi.exceptions import EncodingTypeError, ValueOutOfBounds from hypothesis import given from hiero_sdk_python import ContractFunctionParameters - from tests.fuzz.conftest import ContractValueCase, InvalidContractValueCase, get_strategy + pytestmark = pytest.mark.fuzz diff --git a/tests/fuzz/test_entity_id_fuzz.py b/tests/fuzz/test_entity_id_fuzz.py index df64eeb16..c3bd86f60 100644 --- a/tests/fuzz/test_entity_id_fuzz.py +++ b/tests/fuzz/test_entity_id_fuzz.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import warnings import pytest @@ -5,9 +7,9 @@ from hiero_sdk_python import AccountId, TokenId from hiero_sdk_python.contract.contract_id import ContractId - from tests.fuzz.conftest import AccountIdAliasCase, EntityIdCase, get_strategy + pytestmark = pytest.mark.fuzz diff --git a/tests/fuzz/test_hbar_fuzz.py b/tests/fuzz/test_hbar_fuzz.py index d84ae26f8..426228947 100644 --- a/tests/fuzz/test_hbar_fuzz.py +++ b/tests/fuzz/test_hbar_fuzz.py @@ -1,12 +1,14 @@ +from __future__ import annotations + import math import pytest from hypothesis import given from hiero_sdk_python import Hbar, HbarUnit - from tests.fuzz.conftest import HbarConstructorCase, HbarStringCase, get_strategy + pytestmark = pytest.mark.fuzz @@ -57,4 +59,4 @@ def test_hbar_rejects_fractional_tinybars(amount: object) -> None: def test_hbar_constructor_rejects_invalid_types(value: object) -> None: """The constructor must not silently coerce unsupported input types.""" with pytest.raises(TypeError): - Hbar(value) \ No newline at end of file + Hbar(value) diff --git a/tests/fuzz/test_keys_fuzz.py b/tests/fuzz/test_keys_fuzz.py index 51b5ddf83..49247dc27 100644 --- a/tests/fuzz/test_keys_fuzz.py +++ b/tests/fuzz/test_keys_fuzz.py @@ -1,12 +1,14 @@ +from __future__ import annotations + import warnings import pytest from hypothesis import given from hiero_sdk_python import PrivateKey, PublicKey - from tests.fuzz.conftest import get_strategy + pytestmark = pytest.mark.fuzz diff --git a/tests/fuzz/test_transaction_from_bytes_fuzz.py b/tests/fuzz/test_transaction_from_bytes_fuzz.py index f793dfffd..db104d7ec 100644 --- a/tests/fuzz/test_transaction_from_bytes_fuzz.py +++ b/tests/fuzz/test_transaction_from_bytes_fuzz.py @@ -1,10 +1,12 @@ +from __future__ import annotations + import pytest from hypothesis import given, settings from hiero_sdk_python import Transaction - from tests.fuzz.conftest import get_strategy + pytestmark = pytest.mark.fuzz diff --git a/tests/integration/account_allowance_e2e_test.py b/tests/integration/account_allowance_e2e_test.py index 4df17cd60..5cd6b4eda 100644 --- a/tests/integration/account_allowance_e2e_test.py +++ b/tests/integration/account_allowance_e2e_test.py @@ -2,6 +2,8 @@ Integration tests for Account Allowance functionality. """ +from __future__ import annotations + import pytest from hiero_sdk_python.account.account_allowance_approve_transaction import ( @@ -17,7 +19,7 @@ from hiero_sdk_python.tokens.token_mint_transaction import TokenMintTransaction from hiero_sdk_python.transaction.transaction_id import TransactionId from hiero_sdk_python.transaction.transfer_transaction import TransferTransaction -from tests.integration.utils import create_fungible_token, create_nft_token, env +from tests.integration.utils import create_fungible_token, create_nft_token def _create_spender_and_receiver_accounts(env): @@ -39,20 +41,16 @@ def _associate_token_with_account(env, account, token_id): .execute(env.client) ) - assert ( - receipt.status == ResponseCode.SUCCESS - ), f"Token association failed with status: {ResponseCode(receipt.status).name}" + assert receipt.status == ResponseCode.SUCCESS, ( + f"Token association failed with status: {ResponseCode(receipt.status).name}" + ) def _mint_nft(env, token_id, metadata): """Helper function to mint NFT""" - receipt = ( - TokenMintTransaction().set_token_id(token_id).set_metadata(metadata).execute(env.client) - ) + receipt = TokenMintTransaction().set_token_id(token_id).set_metadata(metadata).execute(env.client) - assert ( - receipt.status == ResponseCode.SUCCESS - ), f"NFT mint failed with status: {ResponseCode(receipt.status).name}" + assert receipt.status == ResponseCode.SUCCESS, f"NFT mint failed with status: {ResponseCode(receipt.status).name}" assert len(receipt.serial_numbers) > 0 nft_ids = [] @@ -120,9 +118,9 @@ def test_integration_can_transfer_on_behalf_of_spender_with_allowance_approval(e .execute(env.client) ) - assert ( - receipt.status == ResponseCode.SUCCESS - ), f"Allowance approval failed with status: {ResponseCode(receipt.status).name}" + assert receipt.status == ResponseCode.SUCCESS, ( + f"Allowance approval failed with status: {ResponseCode(receipt.status).name}" + ) transaction_id = TransactionId.generate(spender_account.id) # Now transfer NFT on behalf of operator with allowance approval @@ -139,9 +137,7 @@ def test_integration_can_transfer_on_behalf_of_spender_with_allowance_approval(e .execute(env.client) ) - assert ( - receipt.status == ResponseCode.SUCCESS - ), f"Transfer failed with status: {ResponseCode(receipt.status).name}" + assert receipt.status == ResponseCode.SUCCESS, f"Transfer failed with status: {ResponseCode(receipt.status).name}" @pytest.mark.integration @@ -155,9 +151,9 @@ def test_integration_hbar_allowance(env): .execute(env.client) ) # Approve HBAR allowance for spender - assert ( - receipt.status == ResponseCode.SUCCESS - ), f"HBAR allowance approval failed with status: {ResponseCode(receipt.status).name}" + assert receipt.status == ResponseCode.SUCCESS, ( + f"HBAR allowance approval failed with status: {ResponseCode(receipt.status).name}" + ) # Set operator to spender account env.client.set_operator(spender_account.id, spender_account.key) @@ -170,9 +166,9 @@ def test_integration_hbar_allowance(env): .execute(env.client) ) - assert ( - receipt.status == ResponseCode.SUCCESS - ), f"HBAR transfer failed with status: {ResponseCode(receipt.status).name}" + assert receipt.status == ResponseCode.SUCCESS, ( + f"HBAR transfer failed with status: {ResponseCode(receipt.status).name}" + ) # Reset operator back to original env.client.set_operator(env.operator_id, env.operator_key) @@ -183,9 +179,9 @@ def test_integration_hbar_allowance(env): .approve_hbar_allowance(env.operator_id, spender_account.id, Hbar(0)) .execute(env.client) ) - assert ( - receipt.status == ResponseCode.SUCCESS - ), f"HBAR allowance deletion failed with status: {ResponseCode(receipt.status).name}" + assert receipt.status == ResponseCode.SUCCESS, ( + f"HBAR allowance deletion failed with status: {ResponseCode(receipt.status).name}" + ) # Set operator to spender account env.client.set_operator(spender_account.id, spender_account.key) @@ -221,9 +217,9 @@ def test_integration_fungible_token_allowance(env): .execute(env.client) ) - assert ( - receipt.status == ResponseCode.SUCCESS - ), f"Token allowance approval failed with status: {ResponseCode(receipt.status).name}" + assert receipt.status == ResponseCode.SUCCESS, ( + f"Token allowance approval failed with status: {ResponseCode(receipt.status).name}" + ) env.client.set_operator(spender_account.id, spender_account.key) # Set operator to spender @@ -235,9 +231,9 @@ def test_integration_fungible_token_allowance(env): .execute(env.client) ) - assert ( - receipt.status == ResponseCode.SUCCESS - ), f"Token transfer failed with status: {ResponseCode(receipt.status).name}" + assert receipt.status == ResponseCode.SUCCESS, ( + f"Token transfer failed with status: {ResponseCode(receipt.status).name}" + ) env.client.set_operator(env.operator_id, env.operator_key) # Reset operator @@ -246,9 +242,9 @@ def test_integration_fungible_token_allowance(env): .approve_token_allowance(token_id, env.operator_id, spender_account.id, 0) .execute(env.client) ) # Delete allowance - assert ( - receipt.status == ResponseCode.SUCCESS - ), f"Token allowance deletion failed with status: {ResponseCode(receipt.status).name}" + assert receipt.status == ResponseCode.SUCCESS, ( + f"Token allowance deletion failed with status: {ResponseCode(receipt.status).name}" + ) # Set operator to spender account env.client.set_operator(spender_account.id, spender_account.key) @@ -287,18 +283,16 @@ def test_integration_cant_transfer_on_behalf_of_spender_after_removing_the_allow .approve_token_nft_allowance(nft2, env.operator_id, spender_account.id) .execute(env.client) ) # Approve allowance for both NFTs - assert ( - approve_receipt.status == ResponseCode.SUCCESS - ), f"Allowance approval failed with status: {ResponseCode(approve_receipt.status).name}" + assert approve_receipt.status == ResponseCode.SUCCESS, ( + f"Allowance approval failed with status: {ResponseCode(approve_receipt.status).name}" + ) delete_receipt = ( - AccountAllowanceDeleteTransaction() - .delete_all_token_nft_allowances(nft2, env.operator_id) - .execute(env.client) + AccountAllowanceDeleteTransaction().delete_all_token_nft_allowances(nft2, env.operator_id).execute(env.client) ) # Delete allowance for nft2 - assert ( - delete_receipt.status == ResponseCode.SUCCESS - ), f"Allowance deletion failed with status: {ResponseCode(delete_receipt.status).name}" + assert delete_receipt.status == ResponseCode.SUCCESS, ( + f"Allowance deletion failed with status: {ResponseCode(delete_receipt.status).name}" + ) # Transfer nft1 (should succeed - allowance still exists) transfer_receipt = ( @@ -309,9 +303,9 @@ def test_integration_cant_transfer_on_behalf_of_spender_after_removing_the_allow .sign(spender_account.key) .execute(env.client) ) - assert ( - transfer_receipt.status == ResponseCode.SUCCESS - ), f"Transfer failed with status: {ResponseCode(transfer_receipt.status).name}" + assert transfer_receipt.status == ResponseCode.SUCCESS, ( + f"Transfer failed with status: {ResponseCode(transfer_receipt.status).name}" + ) # Transfer nft2 (should fail - allowance was deleted) transfer_receipt2 = ( @@ -346,9 +340,9 @@ def test_integration_cant_remove_serial_allowance_when_all_serials_allowed(env): .approve_token_nft_allowance_all_serials(token_id, env.operator_id, spender_account.id) .execute(env.client) ) # Approve allowance for all serials - assert ( - approve_receipt.status == ResponseCode.SUCCESS - ), f"Allowance approval failed with status: {ResponseCode(approve_receipt.status).name}" + assert approve_receipt.status == ResponseCode.SUCCESS, ( + f"Allowance approval failed with status: {ResponseCode(approve_receipt.status).name}" + ) # Transfer nft1 (should succeed - all serials allowed) tx = TransferTransaction() @@ -359,19 +353,17 @@ def test_integration_cant_remove_serial_allowance_when_all_serials_allowed(env): .sign(spender_account.key) .execute(env.client) ) - assert ( - transfer_receipt.status == ResponseCode.SUCCESS - ), f"Transfer failed with status: {ResponseCode(transfer_receipt.status).name}" + assert transfer_receipt.status == ResponseCode.SUCCESS, ( + f"Transfer failed with status: {ResponseCode(transfer_receipt.status).name}" + ) # Try to delete allowance for nft2 (should not affect the all-serial allowance) delete_receipt = ( - AccountAllowanceDeleteTransaction() - .delete_all_token_nft_allowances(nft2, env.operator_id) - .execute(env.client) + AccountAllowanceDeleteTransaction().delete_all_token_nft_allowances(nft2, env.operator_id).execute(env.client) + ) + assert delete_receipt.status == ResponseCode.SUCCESS, ( + f"Allowance deletion failed with status: {ResponseCode(delete_receipt.status).name}" ) - assert ( - delete_receipt.status == ResponseCode.SUCCESS - ), f"Allowance deletion failed with status: {ResponseCode(delete_receipt.status).name}" # Transfer nft2 (should still succeed - all serials allowance still active) tx = TransferTransaction() @@ -382,9 +374,9 @@ def test_integration_cant_remove_serial_allowance_when_all_serials_allowed(env): .sign(spender_account.key) .execute(env.client) ) - assert ( - transfer_receipt2.status == ResponseCode.SUCCESS - ), f"Transfer failed with status: {ResponseCode(transfer_receipt2.status).name}" + assert transfer_receipt2.status == ResponseCode.SUCCESS, ( + f"Transfer failed with status: {ResponseCode(transfer_receipt2.status).name}" + ) @pytest.mark.integration @@ -408,9 +400,9 @@ def test_integration_can_delegate_single_nft_after_all_serials_allowance(env): .approve_token_nft_allowance_all_serials(token_id, env.operator_id, spender_account.id) .execute(env.client) ) # Approve allowance for all serials to spender - assert ( - approve_receipt.status == ResponseCode.SUCCESS - ), f"Allowance approval failed with status: {ResponseCode(approve_receipt.status).name}" + assert approve_receipt.status == ResponseCode.SUCCESS, ( + f"Allowance approval failed with status: {ResponseCode(approve_receipt.status).name}" + ) env.client.set_operator(spender_account.id, spender_account.key) # Set spender as operator @@ -421,9 +413,9 @@ def test_integration_can_delegate_single_nft_after_all_serials_allowance(env): ) .execute(env.client) ) # Approve delegation of nft1 to delegate spender - assert ( - receipt.status == ResponseCode.SUCCESS - ), f"Delegate allowance approval failed with status: {ResponseCode(receipt.status).name}" + assert receipt.status == ResponseCode.SUCCESS, ( + f"Delegate allowance approval failed with status: {ResponseCode(receipt.status).name}" + ) env.client.set_operator(delegate_spender_account.id, delegate_spender_account.key) @@ -434,9 +426,9 @@ def test_integration_can_delegate_single_nft_after_all_serials_allowance(env): .add_approved_nft_transfer(nft1, env.operator_id, receiver_account.id) .execute(env.client) ) - assert ( - transfer_receipt.status == ResponseCode.SUCCESS - ), f"Transfer failed with status: {ResponseCode(transfer_receipt.status).name}" + assert transfer_receipt.status == ResponseCode.SUCCESS, ( + f"Transfer failed with status: {ResponseCode(transfer_receipt.status).name}" + ) # Transfer nft2 using delegate spender (should fail - no delegation for nft2) transfer_receipt2 = ( @@ -470,9 +462,9 @@ def test_integration_cannot_send_deleted_token_nft_serials(env): .approve_token_nft_allowance_all_serials(token_id, env.operator_id, spender_account.id) .execute(env.client) ) - assert ( - receipt.status == ResponseCode.SUCCESS - ), f"Allowance approval failed with status: {ResponseCode(receipt.status).name}" + assert receipt.status == ResponseCode.SUCCESS, ( + f"Allowance approval failed with status: {ResponseCode(receipt.status).name}" + ) # Delete the allowance for all serials receipt = ( @@ -480,17 +472,15 @@ def test_integration_cannot_send_deleted_token_nft_serials(env): .delete_token_nft_allowance_all_serials(token_id, env.operator_id, spender_account.id) .execute(env.client) ) - assert ( - receipt.status == ResponseCode.SUCCESS - ), f"Allowance deletion failed with status: {ResponseCode(receipt.status).name}" + assert receipt.status == ResponseCode.SUCCESS, ( + f"Allowance deletion failed with status: {ResponseCode(receipt.status).name}" + ) env.client.set_operator(spender_account.id, spender_account.key) # Set spender as operator # Try to transfer nft (should fail - allowance was deleted) transfer_receipt = ( - TransferTransaction() - .add_approved_nft_transfer(nft, env.operator_id, receiver_account.id) - .execute(env.client) + TransferTransaction().add_approved_nft_transfer(nft, env.operator_id, receiver_account.id).execute(env.client) ) assert transfer_receipt.status == ResponseCode.SPENDER_DOES_NOT_HAVE_ALLOWANCE, ( f"Transfer should have failed with SPENDER_DOES_NOT_HAVE_ALLOWANCE" diff --git a/tests/integration/account_balance_query_e2e_test.py b/tests/integration/account_balance_query_e2e_test.py index b51f539cb..c92ab5bff 100644 --- a/tests/integration/account_balance_query_e2e_test.py +++ b/tests/integration/account_balance_query_e2e_test.py @@ -1,15 +1,15 @@ +from __future__ import annotations + import pytest -from hiero_sdk_python.account.account_id import AccountId -from hiero_sdk_python.contract.contract_id import ContractId -from hiero_sdk_python.query.account_balance_query import CryptoGetAccountBalanceQuery +from examples.contract.contracts import SIMPLE_CONTRACT_BYTECODE from hiero_sdk_python.contract.contract_create_transaction import ContractCreateTransaction from hiero_sdk_python.contract.contract_delete_transaction import ContractDeleteTransaction +from hiero_sdk_python.contract.contract_id import ContractId +from hiero_sdk_python.query.account_balance_query import CryptoGetAccountBalanceQuery from hiero_sdk_python.response_code import ResponseCode from tests.integration.utils import IntegrationTestEnv -from examples.contract.contracts import SIMPLE_CONTRACT_BYTECODE - pytestmark = pytest.mark.integration @@ -26,9 +26,7 @@ def _create_test_contract(env: IntegrationTestEnv) -> ContractId: ) if ResponseCode(receipt.status) != ResponseCode.SUCCESS: - raise RuntimeError( - f"ContractCreateTransaction failed with status: {ResponseCode(receipt.status).name}" - ) + raise RuntimeError(f"ContractCreateTransaction failed with status: {ResponseCode(receipt.status).name}") if receipt.contract_id is None: raise RuntimeError("ContractCreateTransaction succeeded but receipt.contract_id is None") diff --git a/tests/integration/account_create_transaction_e2e_test.py b/tests/integration/account_create_transaction_e2e_test.py index 0a69acacf..d9cce37b3 100644 --- a/tests/integration/account_create_transaction_e2e_test.py +++ b/tests/integration/account_create_transaction_e2e_test.py @@ -1,12 +1,13 @@ +from __future__ import annotations + import pytest + from hiero_sdk_python.account.account_create_transaction import AccountCreateTransaction -from hiero_sdk_python.account.account_update_transaction import AccountUpdateTransaction from hiero_sdk_python.crypto.private_key import PrivateKey from hiero_sdk_python.exceptions import PrecheckError from hiero_sdk_python.hbar import Hbar from hiero_sdk_python.query.account_info_query import AccountInfoQuery from hiero_sdk_python.response_code import ResponseCode -from tests.integration.utils import env @pytest.mark.integration @@ -15,46 +16,41 @@ def test_integration_account_create_transaction_can_execute(env): new_account_private_key = PrivateKey.generate() new_account_public_key = new_account_private_key.public_key() initial_balance = Hbar(2) - + assert initial_balance.to_tinybars() == 200000000 - + transaction = AccountCreateTransaction( - key=new_account_public_key, - initial_balance=initial_balance, - memo="Recipient Account" + key=new_account_public_key, initial_balance=initial_balance, memo="Recipient Account" ) - + transaction.freeze_with(env.client) receipt = transaction.execute(env.client) assert receipt.status == ResponseCode.SUCCESS assert receipt.account_id is not None, "AccountID not found in receipt. Account may not have been created." - - + + def test_create_account_without_alias(env): """Test account_create_transaction without alias.""" public_key = PrivateKey.generate().public_key() initial_balance = Hbar(2) tx = ( - AccountCreateTransaction( - initial_balance=initial_balance, - memo="Recipient Account Without alias" - ) + AccountCreateTransaction(initial_balance=initial_balance, memo="Recipient Account Without alias") .set_key_without_alias(public_key) .freeze_with(env.client) ) receipt = tx.execute(env.client) account_id = receipt.account_id - + assert receipt.status == ResponseCode.SUCCESS assert receipt.account_id is not None, "AccountID not found in receipt. Account may not have been created." info = AccountInfoQuery(account_id=account_id).execute(env.client) assert info.account_id == account_id - assert info.contract_account_id.startswith('00000000000000000000') + assert info.contract_account_id.startswith("00000000000000000000") def test_create_account_with_alias_derived_from_ecdsa_key(env): @@ -63,17 +59,14 @@ def test_create_account_with_alias_derived_from_ecdsa_key(env): initial_balance = Hbar(2) tx = ( - AccountCreateTransaction( - initial_balance=initial_balance, - memo="Recipient Account With alias" - ) + AccountCreateTransaction(initial_balance=initial_balance, memo="Recipient Account With alias") .set_key_with_alias(public_key) .freeze_with(env.client) ) - + receipt = tx.execute(env.client) account_id = receipt.account_id - + assert receipt.status == ResponseCode.SUCCESS assert receipt.account_id is not None, "AccountID not found in receipt. Account may not have been created." @@ -99,17 +92,14 @@ def test_create_account_with_alias_from_seperate_ecdsa_key(env): initial_balance = Hbar(2) tx = ( - AccountCreateTransaction( - initial_balance=initial_balance, - memo="Recipient Account With alias" - ) + AccountCreateTransaction(initial_balance=initial_balance, memo="Recipient Account With alias") .set_key_with_alias(public_key, alias_key.public_key()) .freeze_with(env.client) - ).sign(alias_key) # Need to sign the tx with alias_key - + ).sign(alias_key) # Need to sign the tx with alias_key + receipt = tx.execute(env.client) account_id = receipt.account_id - + assert receipt.status == ResponseCode.SUCCESS assert receipt.account_id is not None, "AccountID not found in receipt. Account may not have been created." @@ -126,7 +116,7 @@ def test_create_account_with_alias_from_seperate_non_ecdsa_key(): tx = AccountCreateTransaction() with pytest.raises(ValueError): - tx.set_key_with_alias(public_key,alias_key) + tx.set_key_with_alias(public_key, alias_key) def test_create_account_with_alias_from_seperate_ecdsa_key_when_not_sign(env): @@ -136,14 +126,11 @@ def test_create_account_with_alias_from_seperate_ecdsa_key_when_not_sign(env): initial_balance = Hbar(2) tx = ( - AccountCreateTransaction( - initial_balance=initial_balance, - memo="Recipient Account With alias" - ) + AccountCreateTransaction(initial_balance=initial_balance, memo="Recipient Account With alias") .set_key_with_alias(public_key, alias_key.public_key()) .freeze_with(env.client) ) - + receipt = tx.execute(env.client) assert receipt.status == ResponseCode.INVALID_SIGNATURE @@ -157,17 +144,15 @@ def test_create_account_with_same_alias(env): # Create an account with the alisa_evm_address tx1 = ( AccountCreateTransaction( - key=private_key.public_key(), - initial_balance=initial_balance, - memo="Recipient Account With alias" + key=private_key.public_key(), initial_balance=initial_balance, memo="Recipient Account With alias" ) .set_alias(alias_evm_address) .freeze_with(env.client) ) - + receipt1 = tx1.execute(env.client) account_id = receipt1.account_id - + assert receipt1.status == ResponseCode.SUCCESS assert receipt1.account_id is not None, "AccountID not found in receipt. Account may not have been created." @@ -179,9 +164,7 @@ def test_create_account_with_same_alias(env): # Verify that no account with same alias can be created again tx2 = ( AccountCreateTransaction( - initial_balance=initial_balance, - memo="Recipient Account With alias", - key=PrivateKey.generate().public_key() + initial_balance=initial_balance, memo="Recipient Account With alias", key=PrivateKey.generate().public_key() ) .set_alias(alias_evm_address) .freeze_with(env.client) @@ -198,17 +181,15 @@ def test_create_account_with_staked_account_id(env): tx = ( AccountCreateTransaction( - key=public_key, - initial_balance=initial_balance, - memo="Recipient Account With staked_account_id" + key=public_key, initial_balance=initial_balance, memo="Recipient Account With staked_account_id" ) .set_staked_account_id(env.operator_id) .freeze_with(env.client) ) - + receipt = tx.execute(env.client) account_id = receipt.account_id - + assert receipt.status == ResponseCode.SUCCESS assert receipt.account_id is not None, "AccountID not found in receipt. Account may not have been created." @@ -225,16 +206,14 @@ def test_create_account_with_staked_node_id(env): tx = ( AccountCreateTransaction( - key=public_key, - initial_balance=initial_balance, - memo="Recipient Account With Staked node_id" + key=public_key, initial_balance=initial_balance, memo="Recipient Account With Staked node_id" ) .set_staked_node_id(1) .freeze_with(env.client) ) - + receipt = tx.execute(env.client) - # This might succeed or fail depending on network state, but should not crash + # This might succeed or fail depending on network state, but should not crash assert receipt.status in [ ResponseCode.SUCCESS, ResponseCode.INVALID_STAKING_ID, @@ -248,18 +227,16 @@ def test_create_account_with_decline_reward(env): tx = ( AccountCreateTransaction( - key=public_key, - initial_balance=initial_balance, - memo="Recipient Account decline_reward" + key=public_key, initial_balance=initial_balance, memo="Recipient Account decline_reward" ) .set_staked_account_id(env.operator_id) .set_decline_staking_reward(True) .freeze_with(env.client) ) - + receipt = tx.execute(env.client) account_id = receipt.account_id - + assert receipt.status == ResponseCode.SUCCESS assert receipt.account_id is not None, "AccountID not found in receipt. Account may not have been created." @@ -270,6 +247,7 @@ def test_create_account_with_decline_reward(env): assert info.staking_info.staked_account_id == env.operator_id assert info.staking_info.decline_reward is True + def test_integration_account_create_transaction_can_execute_with_private_key(env): """Test AccountCreateTransaction can be executed when key is a PrivateKey.""" new_account_private_key = PrivateKey.generate() @@ -287,9 +265,8 @@ def test_integration_account_create_transaction_can_execute_with_private_key(env receipt = tx.execute(env.client) assert receipt.status == ResponseCode.SUCCESS - assert receipt.account_id is not None, ( - "AccountID not found in receipt. Account may not have been created." - ) + assert receipt.account_id is not None, "AccountID not found in receipt. Account may not have been created." + def test_proto_includes_alias_from_ecdsa_key(env): """Proto alias should come from the separate ECDSA key when provided.""" @@ -322,6 +299,7 @@ def test_proto_includes_alias_from_ecdsa_key(env): # Key in the proto must still be the main account key assert tx_body.cryptoCreateAccount.key == account_public_key._to_proto() + def test_proto_includes_alias_from_main_key(env): """Proto alias should be derived from the main ECDSA key when no separate alias key is provided.""" account_private_key = PrivateKey.generate_ecdsa() @@ -332,8 +310,7 @@ def test_proto_includes_alias_from_main_key(env): AccountCreateTransaction( initial_balance=Hbar(2), memo="Account with alias from main key", - ) - .set_key_with_alias(account_private_key) # no ecdsa_key param + ).set_key_with_alias(account_private_key) # no ecdsa_key param ) tx.freeze_with(env.client) @@ -346,19 +323,17 @@ def test_proto_includes_alias_from_main_key(env): # Key in proto is still the public key of the account assert tx_body.cryptoCreateAccount.key == account_public_key._to_proto() + def test_proto_excludes_alias_when_not_set(env): """Proto should not include alias when we use the 'without alias' path.""" account_private_key = PrivateKey.generate() account_public_key = account_private_key.public_key() - tx = ( - AccountCreateTransaction( - key=account_public_key, - initial_balance=Hbar(2), - memo="Account without alias", - ) - .set_key_without_alias(account_public_key) - ) + tx = AccountCreateTransaction( + key=account_public_key, + initial_balance=Hbar(2), + memo="Account without alias", + ).set_key_without_alias(account_public_key) tx.freeze_with(env.client) receipt = tx.execute(env.client) @@ -374,13 +349,9 @@ def test_proto_excludes_alias_when_not_set(env): def test_create_account_with_negative_initial_balance(env): """Test create_account_transaction raise precheck-error for negative initial balance""" public_key = PrivateKey.generate_ed25519().public_key() - tx = ( - AccountCreateTransaction() - .set_key_without_alias(public_key) - .set_initial_balance(-1) - ) + tx = AccountCreateTransaction().set_key_without_alias(public_key).set_initial_balance(-1) with pytest.raises(PrecheckError) as e: tx.execute(env.client) - + assert e.value.status == ResponseCode.INVALID_INITIAL_BALANCE diff --git a/tests/integration/account_delete_transaction_e2e_test.py b/tests/integration/account_delete_transaction_e2e_test.py index 2edeea94b..7faef6c22 100644 --- a/tests/integration/account_delete_transaction_e2e_test.py +++ b/tests/integration/account_delete_transaction_e2e_test.py @@ -2,11 +2,12 @@ Integration tests for AccountDeleteTransaction. """ +from __future__ import annotations + import pytest from hiero_sdk_python.account.account_delete_transaction import AccountDeleteTransaction from hiero_sdk_python.account.account_id import AccountId -from hiero_sdk_python.exceptions import PrecheckError from hiero_sdk_python.hbar import Hbar from hiero_sdk_python.query.account_info_query import AccountInfoQuery from hiero_sdk_python.query.transaction_record_query import TransactionRecordQuery @@ -14,11 +15,7 @@ from hiero_sdk_python.tokens.nft_id import NftId from hiero_sdk_python.tokens.token_airdrop_transaction import TokenAirdropTransaction from hiero_sdk_python.tokens.token_mint_transaction import TokenMintTransaction -from tests.integration.utils import ( - create_fungible_token, - create_nft_token, - env, -) +from tests.integration.utils import create_fungible_token, create_nft_token @pytest.mark.integration @@ -36,17 +33,15 @@ def test_integration_account_delete_transaction_can_execute(env): .execute(env.client) ) - assert ( - receipt.status == ResponseCode.SUCCESS - ), f"Delete account failed with status: {ResponseCode(receipt.status).name}" + assert receipt.status == ResponseCode.SUCCESS, ( + f"Delete account failed with status: {ResponseCode(receipt.status).name}" + ) transfer_account_info = AccountInfoQuery(transfer_account.id).execute(env.client) - assert ( - transfer_account_info.account_id == transfer_account.id - ), "Account ID should match" - assert ( - transfer_account_info.balance.to_tinybars() == Hbar(2).to_tinybars() - ), f"Account balance should be 2 HBAR but got {transfer_account_info.balance.to_tinybars()}" + assert transfer_account_info.account_id == transfer_account.id, "Account ID should match" + assert transfer_account_info.balance.to_tinybars() == Hbar(2).to_tinybars(), ( + f"Account balance should be 2 HBAR but got {transfer_account_info.balance.to_tinybars()}" + ) @pytest.mark.integration @@ -62,8 +57,7 @@ def test_integration_account_delete_transaction_fails_with_invalid_account_id(en ) assert receipt.status == ResponseCode.INVALID_ACCOUNT_ID, ( - f"Delete account should have failed with INVALID_ACCOUNT_ID status" - f" but got: {ResponseCode(receipt.status).name}" + f"Delete account should have failed with INVALID_ACCOUNT_ID status but got: {ResponseCode(receipt.status).name}" ) @@ -80,8 +74,7 @@ def test_integration_account_delete_transaction_fails_with_no_signature(env): ) assert receipt.status == ResponseCode.INVALID_SIGNATURE, ( - f"Delete account should have failed with INVALID_SIGNATURE" - f" but got: {ResponseCode(receipt.status).name}" + f"Delete account should have failed with INVALID_SIGNATURE but got: {ResponseCode(receipt.status).name}" ) @@ -94,15 +87,8 @@ def test_integration_account_delete_transaction_fails_with_pending_airdrops(env) # Mint two NFTs nft_metadata = [b"nft1", b"nft2"] - receipt = ( - TokenMintTransaction() - .set_token_id(nft_id) - .set_metadata(nft_metadata) - .execute(env.client) - ) - assert ( - receipt.status == ResponseCode.SUCCESS - ), f"Token mint failed: {ResponseCode(receipt.status).name}" + receipt = TokenMintTransaction().set_token_id(nft_id).set_metadata(nft_metadata).execute(env.client) + assert receipt.status == ResponseCode.SUCCESS, f"Token mint failed: {ResponseCode(receipt.status).name}" nft_serials = receipt.serial_numbers assert len(nft_serials) == 2, "Should have minted 2 NFTs" @@ -118,9 +104,7 @@ def test_integration_account_delete_transaction_fails_with_pending_airdrops(env) .add_token_transfer(token_id, env.operator_id, -100) .execute(env.client) ) - assert ( - receipt.status == ResponseCode.SUCCESS - ), f"Token airdrop failed: {ResponseCode(receipt.status).name}" + assert receipt.status == ResponseCode.SUCCESS, f"Token airdrop failed: {ResponseCode(receipt.status).name}" record = TransactionRecordQuery(receipt.transaction_id).execute(env.client) assert len(record.new_pending_airdrops) == 3, "Should have 3 pending airdrops" @@ -153,9 +137,9 @@ def test_integration_account_delete_transaction_fails_when_deleted_twice(env): .sign(account.key) .execute(env.client) ) - assert ( - receipt.status == ResponseCode.SUCCESS - ), f"First account deletion failed with status: {ResponseCode(receipt.status).name}" + assert receipt.status == ResponseCode.SUCCESS, ( + f"First account deletion failed with status: {ResponseCode(receipt.status).name}" + ) receipt = ( AccountDeleteTransaction() diff --git a/tests/integration/account_id_population_e2e_test.py b/tests/integration/account_id_population_e2e_test.py index 7dc149499..28711a8bd 100644 --- a/tests/integration/account_id_population_e2e_test.py +++ b/tests/integration/account_id_population_e2e_test.py @@ -2,6 +2,8 @@ Integration tests for AccountId. """ +from __future__ import annotations + import pytest from hiero_sdk_python.account.account_id import AccountId @@ -11,7 +13,7 @@ TransactionGetReceiptQuery, ) from hiero_sdk_python.transaction.transfer_transaction import TransferTransaction -from tests.integration.utils import env, wait_for_mirror_node +from tests.integration.utils import wait_for_mirror_node @pytest.fixture @@ -44,9 +46,7 @@ def test_populate_account_id_num(env, evm_address): ) assert transfer_receipt is not None - assert ( - len(transfer_receipt.children) > 0 - ), "Expected child transaction for auto-account creation" + assert len(transfer_receipt.children) > 0, "Expected child transaction for auto-account creation" created_account_id = transfer_receipt.children[0].account_id assert created_account_id is not None, f"AccountId not found in child transaction: {transfer_receipt.children[0]}" @@ -87,9 +87,7 @@ def test_populate_account_id_evm_address(env, evm_address): ) assert transfer_receipt is not None - assert ( - len(transfer_receipt.children) > 0 - ), "Expected child transaction for auto-account creation" + assert len(transfer_receipt.children) > 0, "Expected child transaction for auto-account creation" created_account_id = transfer_receipt.children[0].account_id assert created_account_id is not None, f"AccountId not found in child transaction: {transfer_receipt.children[0]}" diff --git a/tests/integration/account_info_query_e2e_test.py b/tests/integration/account_info_query_e2e_test.py index ba5032104..722b1d65a 100644 --- a/tests/integration/account_info_query_e2e_test.py +++ b/tests/integration/account_info_query_e2e_test.py @@ -1,31 +1,34 @@ +from __future__ import annotations + import pytest from hiero_sdk_python import Duration from hiero_sdk_python.account.account_create_transaction import AccountCreateTransaction +from hiero_sdk_python.account.account_id import AccountId from hiero_sdk_python.crypto.private_key import PrivateKey from hiero_sdk_python.exceptions import PrecheckError from hiero_sdk_python.hbar import Hbar -from hiero_sdk_python.account.account_id import AccountId from hiero_sdk_python.query.account_info_query import AccountInfoQuery from hiero_sdk_python.response_code import ResponseCode from hiero_sdk_python.tokens.token_associate_transaction import TokenAssociateTransaction from hiero_sdk_python.tokens.token_dissociate_transaction import TokenDissociateTransaction -from hiero_sdk_python.tokens.token_grant_kyc_transaction import TokenGrantKycTransaction from hiero_sdk_python.tokens.token_freeze_status import TokenFreezeStatus +from hiero_sdk_python.tokens.token_grant_kyc_transaction import TokenGrantKycTransaction from hiero_sdk_python.tokens.token_kyc_status import TokenKycStatus -from hiero_sdk_python.tokens.token_unfreeze_transaction import TokenUnfreezeTransaction from hiero_sdk_python.tokens.token_mint_transaction import TokenMintTransaction +from hiero_sdk_python.tokens.token_unfreeze_transaction import TokenUnfreezeTransaction from tests.integration.utils import IntegrationTestEnv, create_fungible_token, create_nft_token + @pytest.mark.integration def test_integration_account_info_query_can_execute(): env = IntegrationTestEnv() - + try: new_account_private_key = PrivateKey.generate_ed25519() new_account_public_key = new_account_private_key.public_key() account_memo = "Test account memo" - + receipt = ( AccountCreateTransaction() .set_key_without_alias(new_account_public_key) @@ -33,49 +36,65 @@ def test_integration_account_info_query_can_execute(): .set_account_memo(account_memo) .execute(env.client) ) - assert receipt.status == ResponseCode.SUCCESS, f"Account creation failed with status: {ResponseCode(receipt.status).name}" + assert receipt.status == ResponseCode.SUCCESS, ( + f"Account creation failed with status: {ResponseCode(receipt.status).name}" + ) new_account_id = receipt.account_id - + info = AccountInfoQuery(new_account_id).execute(env.client) - - assert str(info.account_id) == str(new_account_id), f"Expected account ID {new_account_id}, but got {info.account_id}" - assert info.balance.to_tinybars() == Hbar(1).to_tinybars(), f"Expected balance of 1 Hbar, but got {info.balance}" - assert info.key.to_bytes_raw() == new_account_public_key.to_bytes_raw(), f"Expected public key {new_account_public_key}, but got {info.key}" + + assert str(info.account_id) == str(new_account_id), ( + f"Expected account ID {new_account_id}, but got {info.account_id}" + ) + assert info.balance.to_tinybars() == Hbar(1).to_tinybars(), ( + f"Expected balance of 1 Hbar, but got {info.balance}" + ) + assert info.key.to_bytes_raw() == new_account_public_key.to_bytes_raw(), ( + f"Expected public key {new_account_public_key}, but got {info.key}" + ) assert info.receiver_signature_required == False, "Expected receiver signature to not be required, but it was" - assert info.auto_renew_period == Duration(7890000), f"Expected auto renew period of 7890000 seconds, but got {info.auto_renew_period}" + assert info.auto_renew_period == Duration(7890000), ( + f"Expected auto renew period of 7890000 seconds, but got {info.auto_renew_period}" + ) assert info.expiration_time is not None, "Expected expiration time to be set, but it was None" - assert info.account_memo == account_memo, f"Expected account memo '{account_memo}', but got '{info.account_memo}'" + assert info.account_memo == account_memo, ( + f"Expected account memo '{account_memo}', but got '{info.account_memo}'" + ) assert info.owned_nfts == 0, f"Expected 0 owned NFTs, but got {info.owned_nfts}" assert info.is_deleted == False, "Expected account to not be deleted, but it was" - assert info.proxy_received.to_tinybars() == 0, f"Expected 0 proxy received tinybars, but got {info.proxy_received.to_tinybars()}" + assert info.proxy_received.to_tinybars() == 0, ( + f"Expected 0 proxy received tinybars, but got {info.proxy_received.to_tinybars()}" + ) finally: env.close() + @pytest.mark.integration def test_integration_account_info_query_fails_with_invalid_account_id(): env = IntegrationTestEnv() - + try: # Use a non-existent account ID account_id = AccountId(0, 0, 123456789) - + with pytest.raises(PrecheckError, match="failed precheck with status: INVALID_ACCOUNT_ID"): AccountInfoQuery(account_id).execute(env.client) finally: env.close() - + + @pytest.mark.integration def test_integration_account_info_query_token_relationship_info(): env = IntegrationTestEnv() - + try: new_account = env.create_account() - + # Create token with kyc key and freeze default and associate it with the new account - token_id = create_fungible_token(env, - [lambda tx: tx.set_kyc_key(env.operator_key), - lambda tx: tx.set_freeze_default(False)]) #Must not be frozen for token operations - + token_id = create_fungible_token( + env, [lambda tx: tx.set_kyc_key(env.operator_key), lambda tx: tx.set_freeze_default(False)] + ) # Must not be frozen for token operations + receipt = ( TokenAssociateTransaction() .set_account_id(new_account.id) @@ -84,59 +103,72 @@ def test_integration_account_info_query_token_relationship_info(): .sign(new_account.key) .execute(env.client) ) - assert receipt.status == ResponseCode.SUCCESS, f"Token associate failed with status: {ResponseCode(receipt.status).name}" - + assert receipt.status == ResponseCode.SUCCESS, ( + f"Token associate failed with status: {ResponseCode(receipt.status).name}" + ) + # Get account info and verify token relationship info = AccountInfoQuery(new_account.id).execute(env.client) - - assert len(info.token_relationships) == 1, f"Expected 1 token relationship, but got {len(info.token_relationships)}" + + assert len(info.token_relationships) == 1, ( + f"Expected 1 token relationship, but got {len(info.token_relationships)}" + ) relationship = info.token_relationships[0] assert relationship.token_id == token_id, f"Expected token ID {token_id}, but got {relationship.token_id}" - assert relationship.freeze_status == TokenFreezeStatus.UNFROZEN, f"Expected freeze status to be UNFROZEN, but got {relationship.freeze_status}" - assert relationship.kyc_status == TokenKycStatus.REVOKED, f"Expected KYC status to be REVOKED, but got {relationship.kyc_status}" + assert relationship.freeze_status == TokenFreezeStatus.UNFROZEN, ( + f"Expected freeze status to be UNFROZEN, but got {relationship.freeze_status}" + ) + assert relationship.kyc_status == TokenKycStatus.REVOKED, ( + f"Expected KYC status to be REVOKED, but got {relationship.kyc_status}" + ) assert relationship.balance == 0, f"Expected balance to be 0, but got {relationship.balance}" assert relationship.symbol == "PTT34", f"Expected symbol 'PTT34', but got {relationship.symbol}" assert relationship.decimals == 2, f"Expected decimals to be 2, but got {relationship.decimals}" - assert relationship.automatic_association == False, f"Expected automatic association to be False, but got {relationship.automatic_association}" - + assert relationship.automatic_association == False, ( + f"Expected automatic association to be False, but got {relationship.automatic_association}" + ) + # Unfreeze account for token - receipt = ( - TokenUnfreezeTransaction() - .set_account_id(new_account.id) - .set_token_id(token_id) - .execute(env.client) + receipt = TokenUnfreezeTransaction().set_account_id(new_account.id).set_token_id(token_id).execute(env.client) + assert receipt.status == ResponseCode.SUCCESS, ( + f"Token freeze failed with status: {ResponseCode(receipt.status).name}" ) - assert receipt.status == ResponseCode.SUCCESS, f"Token freeze failed with status: {ResponseCode(receipt.status).name}" - + # Grant KYC to account - receipt = ( - TokenGrantKycTransaction() - .set_account_id(new_account.id) - .set_token_id(token_id) - .execute(env.client) + receipt = TokenGrantKycTransaction().set_account_id(new_account.id).set_token_id(token_id).execute(env.client) + assert receipt.status == ResponseCode.SUCCESS, ( + f"KYC grant failed with status: {ResponseCode(receipt.status).name}" ) - assert receipt.status == ResponseCode.SUCCESS, f"KYC grant failed with status: {ResponseCode(receipt.status).name}" - + # Get updated account info and verify changes info = AccountInfoQuery(new_account.id).execute(env.client) - - assert len(info.token_relationships) == 1, f"Expected 1 token relationship, but got {len(info.token_relationships)}" + + assert len(info.token_relationships) == 1, ( + f"Expected 1 token relationship, but got {len(info.token_relationships)}" + ) relationship = info.token_relationships[0] assert relationship.token_id == token_id, f"Expected token ID {token_id}, but got {relationship.token_id}" - assert relationship.freeze_status == TokenFreezeStatus.UNFROZEN, f"Expected freeze status to be UNFROZEN, but got {relationship.freeze_status}" - assert relationship.kyc_status == TokenKycStatus.GRANTED, f"Expected KYC status to be GRANTED, but got {relationship.kyc_status}" + assert relationship.freeze_status == TokenFreezeStatus.UNFROZEN, ( + f"Expected freeze status to be UNFROZEN, but got {relationship.freeze_status}" + ) + assert relationship.kyc_status == TokenKycStatus.GRANTED, ( + f"Expected KYC status to be GRANTED, but got {relationship.kyc_status}" + ) finally: env.close() - + + @pytest.mark.integration def test_integration_account_info_query_token_relationships_length(): env = IntegrationTestEnv() - + try: new_account = env.create_account() - + # Create first token with decimals set to 8 and associate it with the new account - decimals_token_id = create_fungible_token(env, [lambda tx: tx.set_decimals(8), lambda tx: tx.set_kyc_key(env.operator_key)]) + decimals_token_id = create_fungible_token( + env, [lambda tx: tx.set_decimals(8), lambda tx: tx.set_kyc_key(env.operator_key)] + ) receipt = ( TokenAssociateTransaction() .set_account_id(new_account.id) @@ -145,13 +177,19 @@ def test_integration_account_info_query_token_relationships_length(): .sign(new_account.key) .execute(env.client) ) - assert receipt.status == ResponseCode.SUCCESS, f"Token associate failed with status: {ResponseCode(receipt.status).name}" - + assert receipt.status == ResponseCode.SUCCESS, ( + f"Token associate failed with status: {ResponseCode(receipt.status).name}" + ) + info = AccountInfoQuery(new_account.id).execute(env.client) - - assert len(info.token_relationships) == 1, f"Expected 1 token relationship, but got {len(info.token_relationships)}" - assert info.token_relationships[0].decimals == 8, f"Expected decimals to be 8, but got {info.token_relationships[0].decimals}" - + + assert len(info.token_relationships) == 1, ( + f"Expected 1 token relationship, but got {len(info.token_relationships)}" + ) + assert info.token_relationships[0].decimals == 8, ( + f"Expected decimals to be 8, but got {info.token_relationships[0].decimals}" + ) + # Create second token with default decimals and associate it with the new account default_decimals_token_id = create_fungible_token(env) receipt = ( @@ -162,15 +200,23 @@ def test_integration_account_info_query_token_relationships_length(): .sign(new_account.key) .execute(env.client) ) - assert receipt.status == ResponseCode.SUCCESS, f"Token associate failed with status: {ResponseCode(receipt.status).name}" - + assert receipt.status == ResponseCode.SUCCESS, ( + f"Token associate failed with status: {ResponseCode(receipt.status).name}" + ) + # Check account info has two token relationships and the first relationship is the second token info = AccountInfoQuery(new_account.id).execute(env.client) - - assert len(info.token_relationships) == 2, f"Expected 2 token relationships, but got {len(info.token_relationships)}" - assert info.token_relationships[0].decimals == 2, f"Expected decimals to be 2, but got {info.token_relationships[0].decimals}" - assert info.token_relationships[1].decimals == 8, f"Expected decimals to be 8, but got {info.token_relationships[1].decimals}" - + + assert len(info.token_relationships) == 2, ( + f"Expected 2 token relationships, but got {len(info.token_relationships)}" + ) + assert info.token_relationships[0].decimals == 2, ( + f"Expected decimals to be 2, but got {info.token_relationships[0].decimals}" + ) + assert info.token_relationships[1].decimals == 8, ( + f"Expected decimals to be 8, but got {info.token_relationships[1].decimals}" + ) + # Dissociate both tokens receipt = ( TokenDissociateTransaction() @@ -181,28 +227,38 @@ def test_integration_account_info_query_token_relationships_length(): .sign(new_account.key) .execute(env.client) ) - assert receipt.status == ResponseCode.SUCCESS, f"Token dissociate failed with status: {ResponseCode(receipt.status).name}" - + assert receipt.status == ResponseCode.SUCCESS, ( + f"Token dissociate failed with status: {ResponseCode(receipt.status).name}" + ) + # Check account info has no token relationships info = AccountInfoQuery(new_account.id).execute(env.client) - assert str(info.account_id) == str(new_account.id), f"Expected account ID {new_account.id}, but got {info.account_id}" - assert len(info.token_relationships) == 0, f"Expected 0 token relationships, but got {len(info.token_relationships)}" + assert str(info.account_id) == str(new_account.id), ( + f"Expected account ID {new_account.id}, but got {info.account_id}" + ) + assert len(info.token_relationships) == 0, ( + f"Expected 0 token relationships, but got {len(info.token_relationships)}" + ) finally: env.close() + @pytest.mark.integration def test_integration_account_info_query_nft_owned(): env = IntegrationTestEnv() - + try: new_account = env.create_account() - + # Create NFT token for the new account - token_id = create_nft_token(env, [ - lambda tx: tx.set_treasury_account_id(new_account.id), - lambda tx: tx.freeze_with(env.client).sign(new_account.key) - ]) - + token_id = create_nft_token( + env, + [ + lambda tx: tx.set_treasury_account_id(new_account.id), + lambda tx: tx.freeze_with(env.client).sign(new_account.key), + ], + ) + # Check initial NFT count is 0 info = AccountInfoQuery(new_account.id).execute(env.client) assert info.owned_nfts == 0, f"Expected 0 owned NFTs, but got {info.owned_nfts}" @@ -216,8 +272,10 @@ def test_integration_account_info_query_nft_owned(): .sign(new_account.key) .execute(env.client) ) - - assert receipt.status == ResponseCode.SUCCESS, f"Token mint failed with status: {ResponseCode(receipt.status).name}" + + assert receipt.status == ResponseCode.SUCCESS, ( + f"Token mint failed with status: {ResponseCode(receipt.status).name}" + ) serial_numbers = receipt.serial_numbers assert len(serial_numbers) == 2, f"Expected 2 serial numbers, but got {len(serial_numbers)}" @@ -225,4 +283,4 @@ def test_integration_account_info_query_nft_owned(): info = AccountInfoQuery(new_account.id).execute(env.client) assert info.owned_nfts == 2, f"Expected 2 owned NFTs, but got {info.owned_nfts}" finally: - env.close() \ No newline at end of file + env.close() diff --git a/tests/integration/account_records_query_e2e_test.py b/tests/integration/account_records_query_e2e_test.py index bf1a765a5..f4cb39a3f 100644 --- a/tests/integration/account_records_query_e2e_test.py +++ b/tests/integration/account_records_query_e2e_test.py @@ -2,6 +2,8 @@ Integration tests for AccountRecordsQuery. """ +from __future__ import annotations + import pytest from hiero_sdk_python.account.account_id import AccountId @@ -10,7 +12,6 @@ from hiero_sdk_python.hbar import Hbar from hiero_sdk_python.response_code import ResponseCode from hiero_sdk_python.transaction.transfer_transaction import TransferTransaction -from tests.integration.utils import env @pytest.mark.integration @@ -25,9 +26,9 @@ def test_integration_account_record_query_can_execute(env): .add_hbar_transfer(env.operator_id, -Hbar(1).to_tinybars()) .execute(env.client) ) - assert ( - transfer_receipt.status == ResponseCode.SUCCESS - ), f"Transfer failed with status: {ResponseCode(transfer_receipt.status).name}" + assert transfer_receipt.status == ResponseCode.SUCCESS, ( + f"Transfer failed with status: {ResponseCode(transfer_receipt.status).name}" + ) # Query operator account records records = AccountRecordsQuery().set_account_id(env.operator_id).execute(env.client) @@ -56,9 +57,9 @@ def test_integration_account_record_query_get_cost(env): .execute(env.client) ) - assert ( - transfer_receipt.status == ResponseCode.SUCCESS - ), f"Transfer failed with status: {ResponseCode(transfer_receipt.status).name}" + assert transfer_receipt.status == ResponseCode.SUCCESS, ( + f"Transfer failed with status: {ResponseCode(transfer_receipt.status).name}" + ) records_query = AccountRecordsQuery().set_account_id(account.id) @@ -81,9 +82,9 @@ def test_integration_account_record_query_insufficient_payment(env): .execute(env.client) ) - assert ( - transfer_receipt.status == ResponseCode.SUCCESS - ), f"Transfer failed with status: {ResponseCode(transfer_receipt.status).name}" + assert transfer_receipt.status == ResponseCode.SUCCESS, ( + f"Transfer failed with status: {ResponseCode(transfer_receipt.status).name}" + ) records_query = AccountRecordsQuery().set_account_id(env.operator_id) diff --git a/tests/integration/account_update_transaction_e2e_test.py b/tests/integration/account_update_transaction_e2e_test.py index 41605fe08..fc597e358 100644 --- a/tests/integration/account_update_transaction_e2e_test.py +++ b/tests/integration/account_update_transaction_e2e_test.py @@ -2,8 +2,9 @@ Integration tests for the AccountUpdateTransaction class. """ +from __future__ import annotations + import pytest -import datetime from hiero_sdk_python.account.account_create_transaction import AccountCreateTransaction from hiero_sdk_python.account.account_id import AccountId @@ -15,7 +16,6 @@ from hiero_sdk_python.query.account_info_query import AccountInfoQuery from hiero_sdk_python.response_code import ResponseCode from hiero_sdk_python.timestamp import Timestamp -from tests.integration.utils import env @pytest.mark.integration @@ -32,9 +32,9 @@ def test_integration_account_update_transaction_can_execute(env): .set_receiver_signature_required(False) .execute(env.client) ) - assert ( - receipt.status == ResponseCode.SUCCESS - ), f"Account creation failed with status: {ResponseCode(receipt.status).name}" + assert receipt.status == ResponseCode.SUCCESS, ( + f"Account creation failed with status: {ResponseCode(receipt.status).name}" + ) account_id = receipt.account_id assert account_id is not None, "Account ID should not be None" @@ -57,23 +57,17 @@ def test_integration_account_update_transaction_can_execute(env): .sign(new_private_key) # Sign with new key .execute(env.client) ) - assert ( - receipt.status == ResponseCode.SUCCESS - ), f"Account update failed with status: {ResponseCode(receipt.status).name}" + assert receipt.status == ResponseCode.SUCCESS, ( + f"Account update failed with status: {ResponseCode(receipt.status).name}" + ) # Query account info to verify updates info = AccountInfoQuery(account_id).execute(env.client) assert str(info.account_id) == str(account_id), "Account ID should match" - assert ( - info.key.to_bytes_raw() == new_public_key.to_bytes_raw() - ), "Public key should be updated" + assert info.key.to_bytes_raw() == new_public_key.to_bytes_raw(), "Public key should be updated" assert info.account_memo == new_memo, "Account memo should be updated" - assert ( - info.receiver_signature_required is True - ), "Receiver signature requirement should be updated" - assert ( - info.auto_renew_period == new_auto_renew_period - ), "Auto renew period should be updated" + assert info.receiver_signature_required is True, "Receiver signature requirement should be updated" + assert info.auto_renew_period == new_auto_renew_period, "Auto renew period should be updated" @pytest.mark.integration @@ -86,9 +80,9 @@ def test_integration_account_update_transaction_set_key_with_threshold_keylist(e .set_initial_balance(Hbar(2)) .execute(env.client) ) - assert ( - receipt.status == ResponseCode.SUCCESS - ), f"Account creation failed with status: {ResponseCode(receipt.status).name}" + assert receipt.status == ResponseCode.SUCCESS, ( + f"Account creation failed with status: {ResponseCode(receipt.status).name}" + ) account_id = receipt.account_id assert account_id is not None, "Account ID should not be None" @@ -96,9 +90,7 @@ def test_integration_account_update_transaction_set_key_with_threshold_keylist(e # Build a 2-of-2 threshold KeyList and rotate the account key to it. key_1_private = PrivateKey.generate_ed25519() key_2_private = PrivateKey.generate_ed25519() - threshold_key = KeyList( - [key_1_private.public_key(), key_2_private.public_key()], threshold=2 - ) + threshold_key = KeyList([key_1_private.public_key(), key_2_private.public_key()], threshold=2) receipt = ( AccountUpdateTransaction() @@ -109,9 +101,9 @@ def test_integration_account_update_transaction_set_key_with_threshold_keylist(e .sign(key_2_private) .execute(env.client) ) - assert ( - receipt.status == ResponseCode.SUCCESS - ), f"Account key rotation to KeyList failed with status: {ResponseCode(receipt.status).name}" + assert receipt.status == ResponseCode.SUCCESS, ( + f"Account key rotation to KeyList failed with status: {ResponseCode(receipt.status).name}" + ) # Verify a follow-up update can be authorized with both threshold keys. new_memo = "Updated using threshold KeyList" @@ -124,9 +116,9 @@ def test_integration_account_update_transaction_set_key_with_threshold_keylist(e .sign(key_2_private) .execute(env.client) ) - assert ( - receipt.status == ResponseCode.SUCCESS - ), f"Account update signed by threshold keys failed with status: {ResponseCode(receipt.status).name}" + assert receipt.status == ResponseCode.SUCCESS, ( + f"Account update signed by threshold keys failed with status: {ResponseCode(receipt.status).name}" + ) @pytest.mark.integration @@ -135,14 +127,9 @@ def test_integration_account_update_transaction_fails_with_invalid_account_id(en # Create an account ID that doesn't exist on the network invalid_account_id = AccountId(0, 0, 999999999) - receipt = ( - AccountUpdateTransaction() - .set_account_id(invalid_account_id) - .execute(env.client) - ) + receipt = AccountUpdateTransaction().set_account_id(invalid_account_id).execute(env.client) assert receipt.status == ResponseCode.INVALID_ACCOUNT_ID, ( - f"Account update should have failed with status INVALID_ACCOUNT_ID, " - f"but got {ResponseCode(receipt.status).name}" + f"Account update should have failed with status INVALID_ACCOUNT_ID, but got {ResponseCode(receipt.status).name}" ) @@ -159,9 +146,9 @@ def test_integration_account_update_transaction_fails_with_invalid_signature(env .set_initial_balance(Hbar(1)) .execute(env.client) ) - assert ( - receipt.status == ResponseCode.SUCCESS - ), f"Account creation failed with status: {ResponseCode(receipt.status).name}" + assert receipt.status == ResponseCode.SUCCESS, ( + f"Account creation failed with status: {ResponseCode(receipt.status).name}" + ) account_id = receipt.account_id assert account_id is not None, "Account ID should not be None" @@ -169,16 +156,10 @@ def test_integration_account_update_transaction_fails_with_invalid_signature(env base_info = AccountInfoQuery(account_id).execute(env.client) # Try to update without signing with the account's key - receipt = ( - AccountUpdateTransaction() - .set_account_id(account_id) - .set_account_memo("New memo") - .execute(env.client) - ) + receipt = AccountUpdateTransaction().set_account_id(account_id).set_account_memo("New memo").execute(env.client) assert receipt.status == ResponseCode.INVALID_SIGNATURE, ( - f"Account update should have failed with status INVALID_SIGNATURE, " - f"but got {ResponseCode(receipt.status).name}" + f"Account update should have failed with status INVALID_SIGNATURE, but got {ResponseCode(receipt.status).name}" ) # Verify nothing changed on-chain @@ -199,36 +180,27 @@ def test_integration_account_update_transaction_partial_update(env): .set_receiver_signature_required(False) .execute(env.client) ) - assert ( - receipt.status == ResponseCode.SUCCESS - ), f"Account creation failed with status: {ResponseCode(receipt.status).name}" + assert receipt.status == ResponseCode.SUCCESS, ( + f"Account creation failed with status: {ResponseCode(receipt.status).name}" + ) account_id = receipt.account_id assert account_id is not None, "Account ID should not be None" # Update only the memo, leaving other fields unchanged new_memo = "Only memo updated" - receipt = ( - AccountUpdateTransaction() - .set_account_id(account_id) - .set_account_memo(new_memo) - .execute(env.client) + receipt = AccountUpdateTransaction().set_account_id(account_id).set_account_memo(new_memo).execute(env.client) + assert receipt.status == ResponseCode.SUCCESS, ( + f"Account update failed with status: {ResponseCode(receipt.status).name}" ) - assert ( - receipt.status == ResponseCode.SUCCESS - ), f"Account update failed with status: {ResponseCode(receipt.status).name}" # Query account info to verify only memo was updated info = AccountInfoQuery(account_id).execute(env.client) assert str(info.account_id) == str(account_id), "Account ID should match" - assert ( - info.key.to_bytes_raw() == env.operator_key.public_key().to_bytes_raw() - ), "Public key should remain unchanged" + assert info.key.to_bytes_raw() == env.operator_key.public_key().to_bytes_raw(), "Public key should remain unchanged" assert info.account_memo == new_memo, "Account memo should be updated" - assert ( - info.receiver_signature_required is False - ), "Receiver signature requirement should remain unchanged" + assert info.receiver_signature_required is False, "Receiver signature requirement should remain unchanged" @pytest.mark.integration @@ -241,9 +213,9 @@ def test_integration_account_update_transaction_invalid_auto_renew_period(env): .set_initial_balance(Hbar(1)) .execute(env.client) ) - assert ( - receipt.status == ResponseCode.SUCCESS - ), f"Account creation failed with status: {ResponseCode(receipt.status).name}" + assert receipt.status == ResponseCode.SUCCESS, ( + f"Account creation failed with status: {ResponseCode(receipt.status).name}" + ) account_id = receipt.account_id assert account_id is not None, "Account ID should not be None" @@ -254,10 +226,7 @@ def test_integration_account_update_transaction_invalid_auto_renew_period(env): # Try to update with invalid auto renew period receipt = ( - AccountUpdateTransaction() - .set_account_id(account_id) - .set_auto_renew_period(invalid_period) - .execute(env.client) + AccountUpdateTransaction().set_account_id(account_id).set_auto_renew_period(invalid_period).execute(env.client) ) assert receipt.status == ResponseCode.AUTORENEW_DURATION_NOT_IN_RANGE, ( @@ -269,6 +238,7 @@ def test_integration_account_update_transaction_invalid_auto_renew_period(env): info_after = AccountInfoQuery(account_id).execute(env.client) assert info_after.expiration_time == original_info.expiration_time + def _apply_tiny_max_fee_if_supported(tx, client) -> bool: # Try tx-level setters for attr in ("set_max_transaction_fee", "set_max_fee", "set_transaction_fee"): @@ -276,13 +246,18 @@ def _apply_tiny_max_fee_if_supported(tx, client) -> bool: getattr(tx, attr)(Hbar.from_tinybars(1)) return True # Try client-level default - for attr in ("set_default_max_transaction_fee", "set_max_transaction_fee", - "set_default_max_fee", "setMaxTransactionFee"): + for attr in ( + "set_default_max_transaction_fee", + "set_max_transaction_fee", + "set_default_max_fee", + "setMaxTransactionFee", + ): if hasattr(client, attr): getattr(client, attr)(Hbar.from_tinybars(1)) return True return False + @pytest.mark.integration def test_account_update_insufficient_fee_with_valid_expiration_bump(env): """If we can cap the fee, a small valid expiration bump should fail with INSUFFICIENT_TX_FEE; otherwise skip.""" @@ -303,11 +278,7 @@ def test_account_update_insufficient_fee_with_valid_expiration_bump(env): delta_seconds = 60 * 60 * 24 # +1 day; typically valid new_expiry = Timestamp(seconds=base_expiry_secs + delta_seconds, nanos=0) - tx = ( - AccountUpdateTransaction() - .set_account_id(account_id) - .set_expiration_time(new_expiry) - ) + tx = AccountUpdateTransaction().set_account_id(account_id).set_expiration_time(new_expiry) if not _apply_tiny_max_fee_if_supported(tx, env.client): pytest.skip("SDK lacks a max-fee API; cannot deterministically trigger INSUFFICIENT_TX_FEE.") @@ -321,6 +292,7 @@ def test_account_update_insufficient_fee_with_valid_expiration_bump(env): info_after = AccountInfoQuery(account_id).execute(env.client) assert int(info_after.expiration_time.seconds) == base_expiry_secs + @pytest.mark.integration def test_integration_account_update_transaction_with_only_account_id(env): """Test that AccountUpdateTransaction can execute with only account ID set.""" @@ -331,18 +303,18 @@ def test_integration_account_update_transaction_with_only_account_id(env): .set_initial_balance(Hbar(1)) .execute(env.client) ) - assert ( - receipt.status == ResponseCode.SUCCESS - ), f"Account creation failed with status: {ResponseCode(receipt.status).name}" + assert receipt.status == ResponseCode.SUCCESS, ( + f"Account creation failed with status: {ResponseCode(receipt.status).name}" + ) account_id = receipt.account_id assert account_id is not None, "Account ID should not be None" receipt = AccountUpdateTransaction().set_account_id(account_id).execute(env.client) - assert ( - receipt.status == ResponseCode.SUCCESS - ), f"Account update failed with status: {ResponseCode(receipt.status).name}" + assert receipt.status == ResponseCode.SUCCESS, ( + f"Account update failed with status: {ResponseCode(receipt.status).name}" + ) # Ensure no fields were unintentionally modified info = AccountInfoQuery(account_id).execute(env.client) @@ -360,9 +332,9 @@ def test_integration_account_update_transaction_with_max_automatic_token_associa .set_initial_balance(Hbar(2)) .execute(env.client) ) - assert ( - receipt.status == ResponseCode.SUCCESS - ), f"Account creation failed with status: {ResponseCode(receipt.status).name}" + assert receipt.status == ResponseCode.SUCCESS, ( + f"Account creation failed with status: {ResponseCode(receipt.status).name}" + ) account_id = receipt.account_id assert account_id is not None, "Account ID should not be None" @@ -375,15 +347,15 @@ def test_integration_account_update_transaction_with_max_automatic_token_associa .set_max_automatic_token_associations(new_max_associations) .execute(env.client) ) - assert ( - receipt.status == ResponseCode.SUCCESS - ), f"Account update failed with status: {ResponseCode(receipt.status).name}" + assert receipt.status == ResponseCode.SUCCESS, ( + f"Account update failed with status: {ResponseCode(receipt.status).name}" + ) # Query account info to verify the update persisted info = AccountInfoQuery(account_id).execute(env.client) - assert ( - info.max_automatic_token_associations == new_max_associations - ), "Max automatic token associations should be updated" + assert info.max_automatic_token_associations == new_max_associations, ( + "Max automatic token associations should be updated" + ) @pytest.mark.integration @@ -417,9 +389,9 @@ def test_integration_account_update_transaction_with_staking_fields(env): .set_decline_staking_reward(True) .execute(env.client) ) - assert ( - receipt.status == ResponseCode.SUCCESS - ), f"Account update with staking fields failed with status: {ResponseCode(receipt.status).name}" + assert receipt.status == ResponseCode.SUCCESS, ( + f"Account update with staking fields failed with status: {ResponseCode(receipt.status).name}" + ) # Verify staking info reflects the updated values info = AccountInfoQuery(account_id).execute(env.client) @@ -443,12 +415,7 @@ def test_integration_account_update_transaction_with_staked_node_id(env): # Update with staked_node_id (using node 0 as a test value) # Note: In a real scenario, you'd use a valid node ID - receipt = ( - AccountUpdateTransaction() - .set_account_id(account_id) - .set_staked_node_id(0) - .execute(env.client) - ) + receipt = AccountUpdateTransaction().set_account_id(account_id).set_staked_node_id(0).execute(env.client) # This might succeed or fail depending on network state, but should not crash assert receipt.status in [ ResponseCode.SUCCESS, @@ -459,4 +426,4 @@ def test_integration_account_update_transaction_with_staked_node_id(env): info = AccountInfoQuery(account_id).execute(env.client) assert info.staking_info is not None, "Staking info should be set" assert info.staking_info.staked_node_id == 0 - assert info.staking_info.staked_account_id is None \ No newline at end of file + assert info.staking_info.staked_account_id is None diff --git a/tests/integration/batch_transaction_e2e_test.py b/tests/integration/batch_transaction_e2e_test.py index 1071149c1..6961a46dd 100644 --- a/tests/integration/batch_transaction_e2e_test.py +++ b/tests/integration/batch_transaction_e2e_test.py @@ -1,5 +1,9 @@ +from __future__ import annotations + import datetime + import pytest + from hiero_sdk_python.account.account_create_transaction import AccountCreateTransaction from hiero_sdk_python.crypto.private_key import PrivateKey from hiero_sdk_python.crypto.public_key import PublicKey @@ -12,21 +16,18 @@ from hiero_sdk_python.timestamp import Timestamp from hiero_sdk_python.transaction.batch_transaction import BatchTransaction from hiero_sdk_python.transaction.transfer_transaction import TransferTransaction -from tests.integration.utils import env + def create_account_tx(key, client): """Helper transaction to create an account.""" - account_receipt = ( - AccountCreateTransaction() - .set_key_without_alias(key) - .set_initial_balance(1) - .execute(client) - ) + account_receipt = AccountCreateTransaction().set_key_without_alias(key).set_initial_balance(1).execute(client) return account_receipt.account_id + batch_key = PrivateKey.generate() + def test_batch_transaction_can_execute(env): """Test can create and execute batch transaction.""" receiver_id = create_account_tx(PrivateKey.generate().public_key(), env.client) @@ -38,12 +39,7 @@ def test_batch_transaction_can_execute(env): .batchify(env.client, batch_key) ) - batch_tx = ( - BatchTransaction() - .add_inner_transaction(transfer_tx) - .freeze_with(env.client) - .sign(batch_key) - ) + batch_tx = BatchTransaction().add_inner_transaction(transfer_tx).freeze_with(env.client).sign(batch_key) batch_receipt = batch_tx.execute(env.client) @@ -51,14 +47,11 @@ def test_batch_transaction_can_execute(env): # Inner Transaction Receipt transfer_tx_id = batch_tx.get_inner_transaction_ids()[0] - transfer_tx_receipt = ( - TransactionGetReceiptQuery() - .set_transaction_id(transfer_tx_id) - .execute(env.client) - ) + transfer_tx_receipt = TransactionGetReceiptQuery().set_transaction_id(transfer_tx_id).execute(env.client) assert transfer_tx_receipt.status == ResponseCode.SUCCESS + def test_batch_transaction_can_execute_from_bytes(env): """Test can create and execute batch transaction from bytes.""" receiver_id = create_account_tx(PrivateKey.generate().public_key(), env.client) @@ -70,12 +63,7 @@ def test_batch_transaction_can_execute_from_bytes(env): .batchify(env.client, batch_key) ) - batch_tx = ( - BatchTransaction() - .add_inner_transaction(transfer_tx) - .freeze_with(env.client) - .sign(batch_key) - ) + batch_tx = BatchTransaction().add_inner_transaction(transfer_tx).freeze_with(env.client).sign(batch_key) # Convert to bytes batch_bytes = batch_tx.to_bytes() @@ -87,18 +75,15 @@ def test_batch_transaction_can_execute_from_bytes(env): # Inner Transaction Receipt transfer_tx_id = batch_tx_from_bytes.get_inner_transaction_ids()[0] - transfer_tx_receipt = ( - TransactionGetReceiptQuery() - .set_transaction_id(transfer_tx_id) - .execute(env.client) - ) + transfer_tx_receipt = TransactionGetReceiptQuery().set_transaction_id(transfer_tx_id).execute(env.client) assert transfer_tx_receipt.status == ResponseCode.SUCCESS + def test_batch_transaction_can_execute_large_batch(env): """Test can execute a large batch transaction""" batch_tx = BatchTransaction() - + receiver_id = create_account_tx(PrivateKey.generate().public_key(), env.client) for _ in range(20): @@ -117,26 +102,19 @@ def test_batch_transaction_can_execute_large_batch(env): batch_receipt = batch_tx.execute(env.client) assert batch_receipt.status == ResponseCode.SUCCESS - + # Inner Transaction Receipt for tx_id in batch_tx.get_inner_transaction_ids(): - transfer_tx_receipt = ( - TransactionGetReceiptQuery() - .set_transaction_id(tx_id) - .execute(env.client) - ) - + transfer_tx_receipt = TransactionGetReceiptQuery().set_transaction_id(tx_id).execute(env.client) + assert transfer_tx_receipt.status == ResponseCode.SUCCESS + def test_batch_transaction_without_inner_transactions(env): """Test batch transaction with empty inner transaction's list should raise an error.""" with pytest.raises(ValueError, match="BatchTransaction requires at least one inner transaction."): - ( - BatchTransaction() - .freeze_with(env.client) - .sign(batch_key) - .execute(env.client) - ) + (BatchTransaction().freeze_with(env.client).sign(batch_key).execute(env.client)) + def test_batch_transaction_with_blacklisted_inner_transaction(env): """Test batch transaction with blacklisted inner transaction should raise an error.""" @@ -144,7 +122,7 @@ def test_batch_transaction_with_blacklisted_inner_transaction(env): freeze_tx = ( FreezeTransaction() .set_file_id(FileId.from_string("4.5.6")) - .set_file_hash(bytes.fromhex('1723904587120938954702349857')) + .set_file_hash(bytes.fromhex("1723904587120938954702349857")) .set_start_time(Timestamp.generate()) .set_freeze_type(FreezeType.FREEZE_ONLY) .batchify(env.client, batch_key) @@ -167,20 +145,11 @@ def test_batch_transaction_with_blacklisted_inner_transaction(env): .batchify(env.client, batch_key) ) - batch_tx = ( - BatchTransaction() - .add_inner_transaction(account_tx) - .batchify(env.client, batch_key) - ) + batch_tx = BatchTransaction().add_inner_transaction(account_tx).batchify(env.client, batch_key) with pytest.raises(ValueError, match="Transaction type BatchTransaction is not allowed in a batch transaction"): - ( - BatchTransaction() - .add_inner_transaction(batch_tx) - .freeze_with(env.client) - .sign(batch_key) - .execute(env.client) - ) + (BatchTransaction().add_inner_transaction(batch_tx).freeze_with(env.client).sign(batch_key).execute(env.client)) + def test_batch_transaction_with_invalid_batch_key(env): """Test invalid batch key set to inner transaction should raise error.""" @@ -194,17 +163,13 @@ def test_batch_transaction_with_invalid_batch_key(env): .batchify(env.client, invalid_batch_key) ) - batch_tx = ( - BatchTransaction() - .add_inner_transaction(transfer_tx) - .freeze_with(env.client) - .sign(batch_key) - ) + batch_tx = BatchTransaction().add_inner_transaction(transfer_tx).freeze_with(env.client).sign(batch_key) batch_receipt = batch_tx.execute(env.client) assert batch_receipt.status == ResponseCode.INVALID_SIGNATURE + def test_batch_transaction_can_execute_with_different_batch_key(env): """Test can execute batch transaction with different batch keys.""" batch_key1 = PrivateKey.generate() @@ -252,13 +217,10 @@ def test_batch_transaction_can_execute_with_different_batch_key(env): # Inner Transaction Receipt for transfer_tx_id in batch_tx.get_inner_transaction_ids(): - transfer_tx_receipt = ( - TransactionGetReceiptQuery() - .set_transaction_id(transfer_tx_id) - .execute(env.client) - ) + transfer_tx_receipt = TransactionGetReceiptQuery().set_transaction_id(transfer_tx_id).execute(env.client) assert transfer_tx_receipt.status == ResponseCode.SUCCESS + def test_execute_transaction_fail_when_batchified(env): """Test transaction should fail when batchified but not part of a batch.""" tx = ( @@ -271,11 +233,10 @@ def test_execute_transaction_fail_when_batchified(env): with pytest.raises(ValueError, match="Cannot execute batchified transaction outside of BatchTransaction."): tx.execute(env.client) + def test_successful_inner_transactions_should_incur_fees_even_though_one_fails(env): """Test successful inner transaction should incur fees even though one failed.""" - initial_balance = ( - AccountInfoQuery(account_id=env.operator_id).execute(env.client).balance - ) + initial_balance = AccountInfoQuery(account_id=env.operator_id).execute(env.client).balance tx1 = ( AccountCreateTransaction() @@ -312,22 +273,18 @@ def test_successful_inner_transactions_should_incur_fees_even_though_one_fails(e batch_receipt = batch_tx.execute(env.client) assert batch_receipt.status == ResponseCode.INNER_TRANSACTION_FAILED - final_balance = ( - AccountInfoQuery(account_id=env.operator_id).execute(env.client).balance - ) + final_balance = AccountInfoQuery(account_id=env.operator_id).execute(env.client).balance assert initial_balance > final_balance + def test_batch_transaction_with_inner_schedule_transaction(env): """Test batch_transaction with inner schedule transaction raise error.""" receiver_key = PrivateKey.generate() receiver_id = create_account_tx(receiver_key.public_key(), env.client) schedule_create_tx = ( - TransferTransaction() - .add_hbar_transfer(receiver_id, -1) - .add_hbar_transfer(env.operator_id, 1) - .schedule() + TransferTransaction().add_hbar_transfer(receiver_id, -1).add_hbar_transfer(env.operator_id, 1).schedule() ) current_time = datetime.datetime.now() @@ -344,12 +301,7 @@ def test_batch_transaction_with_inner_schedule_transaction(env): .sign(env.operator_key) ) - batch_tx = ( - BatchTransaction() - .add_inner_transaction(schedule_create_tx) - .freeze_with(env.client) - .sign(batch_key) - ) + batch_tx = BatchTransaction().add_inner_transaction(schedule_create_tx).freeze_with(env.client).sign(batch_key) batch_receipt = batch_tx.execute(env.client) assert batch_receipt.status == ResponseCode.BATCH_TRANSACTION_IN_BLACKLIST @@ -360,7 +312,7 @@ def test_batch_transaction_with_public_key_as_batch_key(env): # Generate a key pair - we'll use the PublicKey as batch_key batch_private_key = PrivateKey.generate() batch_public_key = batch_private_key.public_key() - + receiver_id = create_account_tx(PrivateKey.generate().public_key(), env.client) # Use PublicKey in batchify @@ -388,11 +340,7 @@ def test_batch_transaction_with_public_key_as_batch_key(env): # Inner Transaction Receipt transfer_tx_id = batch_tx.get_inner_transaction_ids()[0] - transfer_tx_receipt = ( - TransactionGetReceiptQuery() - .set_transaction_id(transfer_tx_id) - .execute(env.client) - ) + transfer_tx_receipt = TransactionGetReceiptQuery().set_transaction_id(transfer_tx_id).execute(env.client) assert transfer_tx_receipt.status == ResponseCode.SUCCESS @@ -456,11 +404,7 @@ def test_batch_transaction_with_mixed_public_and_private_keys(env): # Verify all inner transactions succeeded for transfer_tx_id in batch_tx.get_inner_transaction_ids(): - transfer_tx_receipt = ( - TransactionGetReceiptQuery() - .set_transaction_id(transfer_tx_id) - .execute(env.client) - ) + transfer_tx_receipt = TransactionGetReceiptQuery().set_transaction_id(transfer_tx_id).execute(env.client) assert transfer_tx_receipt.status == ResponseCode.SUCCESS @@ -469,7 +413,7 @@ def test_batch_transaction_set_batch_key_with_public_key(env): # Generate a key pair batch_private_key = PrivateKey.generate() batch_public_key = batch_private_key.public_key() - + receiver_id = create_account_tx(PrivateKey.generate().public_key(), env.client) # Use set_batch_key with PublicKey instead of batchify @@ -498,9 +442,5 @@ def test_batch_transaction_set_batch_key_with_public_key(env): # Inner Transaction Receipt transfer_tx_id = batch_tx.get_inner_transaction_ids()[0] - transfer_tx_receipt = ( - TransactionGetReceiptQuery() - .set_transaction_id(transfer_tx_id) - .execute(env.client) - ) + transfer_tx_receipt = TransactionGetReceiptQuery().set_transaction_id(transfer_tx_id).execute(env.client) assert transfer_tx_receipt.status == ResponseCode.SUCCESS diff --git a/tests/integration/client_integration_test.py b/tests/integration/client_integration_test.py index b30a24291..0491c38c0 100644 --- a/tests/integration/client_integration_test.py +++ b/tests/integration/client_integration_test.py @@ -1,46 +1,52 @@ -import pytest -from hiero_sdk_python import Client, PrivateKey, AccountId +from __future__ import annotations + +from hiero_sdk_python import AccountId, Client, PrivateKey + """Integration tests for factory methods with operator setup.""" + + def test_for_testnet_then_set_operator(): """Test for_testnet factory followed by set_operator.""" client = Client.for_testnet() - + # Generate valid credentials # Assuming AccountId constructor takes (shard, realm, num) operator_id = AccountId(0, 0, 12345) operator_key = PrivateKey.generate_ed25519() - + client.set_operator(operator_id, operator_key) - + # Verify operator was set correctly assert client.operator_account_id == operator_id assert client.operator_private_key.to_string() == operator_key.to_string() assert client.operator is not None assert client.network.network == "testnet" - + client.close() - + + def test_for_mainnet_then_set_operator(): """Test for_mainnet factory followed by set_operator.""" client = Client.for_mainnet() - + operator_id = AccountId(0, 0, 67890) operator_key = PrivateKey.generate_ecdsa() client.set_operator(operator_id, operator_key) - + assert client.operator_account_id == operator_id assert client.operator_private_key.to_string() == operator_key.to_string() assert client.network.network == "mainnet" - + client.close() - + + def test_factory_methods_return_different_instances(): """Test that factory methods return new instances.""" client1 = Client.for_testnet() client2 = Client.for_testnet() - + assert client1 is not client2 - + client1.close() - client2.close() \ No newline at end of file + client2.close() diff --git a/tests/integration/conftest.py b/tests/integration/conftest.py new file mode 100644 index 000000000..15564386b --- /dev/null +++ b/tests/integration/conftest.py @@ -0,0 +1,13 @@ +from __future__ import annotations + +import pytest + +from tests.integration.utils import IntegrationTestEnv + + +@pytest.fixture +def env(): + """Integration test environment with client/operator set up.""" + e = IntegrationTestEnv() + yield e + e.close() diff --git a/tests/integration/contract_bytecode_query_e2e_test.py b/tests/integration/contract_bytecode_query_e2e_test.py index ee643651d..2b302c2b7 100644 --- a/tests/integration/contract_bytecode_query_e2e_test.py +++ b/tests/integration/contract_bytecode_query_e2e_test.py @@ -2,6 +2,8 @@ Integration tests for ContractBytecodeQuery. """ +from __future__ import annotations + import pytest from examples.contract.contracts import ( @@ -17,7 +19,6 @@ from hiero_sdk_python.exceptions import PrecheckError from hiero_sdk_python.hbar import Hbar from hiero_sdk_python.response_code import ResponseCode -from tests.integration.utils import env @pytest.mark.integration @@ -33,9 +34,9 @@ def test_integration_contract_bytecode_query_can_execute(env): .execute(env.client) ) - assert ( - receipt.status == ResponseCode.SUCCESS - ), f"Contract creation failed with status: {ResponseCode(receipt.status).name}" + assert receipt.status == ResponseCode.SUCCESS, ( + f"Contract creation failed with status: {ResponseCode(receipt.status).name}" + ) contract_id = receipt.contract_id assert contract_id is not None, "Contract ID should not be None" @@ -60,9 +61,9 @@ def test_integration_contract_bytecode_query_get_cost(env): .execute(env.client) ) - assert ( - receipt.status == ResponseCode.SUCCESS - ), f"Contract creation failed with status: {ResponseCode(receipt.status).name}" + assert receipt.status == ResponseCode.SUCCESS, ( + f"Contract creation failed with status: {ResponseCode(receipt.status).name}" + ) contract_id = receipt.contract_id assert contract_id is not None, "Contract ID should not be None" @@ -89,18 +90,16 @@ def test_integration_contract_bytecode_query_insufficient_payment(env): .execute(env.client) ) - assert ( - receipt.status == ResponseCode.SUCCESS - ), f"Contract creation failed with status: {ResponseCode(receipt.status).name}" + assert receipt.status == ResponseCode.SUCCESS, ( + f"Contract creation failed with status: {ResponseCode(receipt.status).name}" + ) contract_id = receipt.contract_id assert contract_id is not None, "Contract ID should not be None" contract_bytecode = ContractBytecodeQuery().set_contract_id(contract_id) - with pytest.raises( - PrecheckError, match="failed precheck with status: INSUFFICIENT_TX_FEE" - ): + with pytest.raises(PrecheckError, match="failed precheck with status: INSUFFICIENT_TX_FEE"): contract_bytecode.set_query_payment(Hbar.from_tinybars(1)).execute(env.client) @@ -110,7 +109,5 @@ def test_integration_contract_bytecode_query_fails_with_invalid_contract_id(env) # Create a contract ID that doesn't exist on the network contract_id = ContractId(0, 0, 999999999) - with pytest.raises( - PrecheckError, match="failed precheck with status: INVALID_CONTRACT_ID" - ): + with pytest.raises(PrecheckError, match="failed precheck with status: INVALID_CONTRACT_ID"): ContractBytecodeQuery(contract_id).execute(env.client) diff --git a/tests/integration/contract_call_query_e2e_test.py b/tests/integration/contract_call_query_e2e_test.py index fd60ef5ed..ae5a9d223 100644 --- a/tests/integration/contract_call_query_e2e_test.py +++ b/tests/integration/contract_call_query_e2e_test.py @@ -2,6 +2,8 @@ Integration tests for ContractCallQuery. """ +from __future__ import annotations + import pytest from examples.contract.contracts.contract_utils import ( @@ -21,7 +23,6 @@ from hiero_sdk_python.file.file_create_transaction import FileCreateTransaction from hiero_sdk_python.hbar import Hbar from hiero_sdk_python.response_code import ResponseCode -from tests.integration.utils import env @pytest.mark.integration @@ -35,9 +36,9 @@ def test_integration_contract_call_query_can_execute_with_constructor(env): .execute(env.client) ) - assert ( - receipt.status == ResponseCode.SUCCESS - ), f"File creation failed with status: {ResponseCode(receipt.status).name}" + assert receipt.status == ResponseCode.SUCCESS, ( + f"File creation failed with status: {ResponseCode(receipt.status).name}" + ) file_id = receipt.file_id assert file_id is not None, "File ID should not be None" @@ -56,19 +57,14 @@ def test_integration_contract_call_query_can_execute_with_constructor(env): .execute(env.client) ) - assert ( - receipt.status == ResponseCode.SUCCESS - ), f"Contract creation failed with status: {ResponseCode(receipt.status).name}" + assert receipt.status == ResponseCode.SUCCESS, ( + f"Contract creation failed with status: {ResponseCode(receipt.status).name}" + ) contract_id = receipt.contract_id assert contract_id is not None, "Contract ID should not be None" - query = ( - ContractCallQuery() - .set_contract_id(contract_id) - .set_gas(10000000) - .set_function("getMessage") - ) + query = ContractCallQuery().set_contract_id(contract_id).set_gas(10000000).set_function("getMessage") cost = query.get_cost(env.client) query.set_max_query_payment(cost) @@ -90,9 +86,9 @@ def test_integration_contract_call_query_can_execute(env): .execute(env.client) ) - assert ( - receipt.status == ResponseCode.SUCCESS - ), f"File creation failed with status: {ResponseCode(receipt.status).name}" + assert receipt.status == ResponseCode.SUCCESS, ( + f"File creation failed with status: {ResponseCode(receipt.status).name}" + ) file_id = receipt.file_id assert file_id is not None, "File ID should not be None" @@ -106,19 +102,14 @@ def test_integration_contract_call_query_can_execute(env): .execute(env.client) ) - assert ( - receipt.status == ResponseCode.SUCCESS - ), f"Contract creation failed with status: {ResponseCode(receipt.status).name}" + assert receipt.status == ResponseCode.SUCCESS, ( + f"Contract creation failed with status: {ResponseCode(receipt.status).name}" + ) contract_id = receipt.contract_id assert contract_id is not None, "Contract ID should not be None" - query = ( - ContractCallQuery() - .set_contract_id(contract_id) - .set_gas(10000000) - .set_function("greet") - ) + query = ContractCallQuery().set_contract_id(contract_id).set_gas(10000000).set_function("greet") cost = query.get_cost(env.client) query.set_max_query_payment(cost) @@ -139,9 +130,9 @@ def test_integration_contract_call_query_get_cost(env): .set_file_memo("test contract bytecode file") .execute(env.client) ) - assert ( - receipt.status == ResponseCode.SUCCESS - ), f"File creation failed with status: {ResponseCode(receipt.status).name}" + assert receipt.status == ResponseCode.SUCCESS, ( + f"File creation failed with status: {ResponseCode(receipt.status).name}" + ) file_id = receipt.file_id assert file_id is not None, "File ID should not be None" @@ -154,19 +145,14 @@ def test_integration_contract_call_query_get_cost(env): .set_contract_memo("test contract deployment") .execute(env.client) ) - assert ( - receipt.status == ResponseCode.SUCCESS - ), f"Contract creation failed with status: {ResponseCode(receipt.status).name}" + assert receipt.status == ResponseCode.SUCCESS, ( + f"Contract creation failed with status: {ResponseCode(receipt.status).name}" + ) contract_id = receipt.contract_id assert contract_id is not None, "Contract ID should not be None" # Prepare the contract call query - contract_call_query = ( - ContractCallQuery() - .set_contract_id(contract_id) - .set_gas(10000000) - .set_function("greet") - ) + contract_call_query = ContractCallQuery().set_contract_id(contract_id).set_gas(10000000).set_function("greet") # Get the cost for the query cost = contract_call_query.get_cost(env.client) @@ -189,9 +175,9 @@ def test_integration_contract_call_query_insufficient_payment(env): .set_file_memo("test contract bytecode file") .execute(env.client) ) - assert ( - receipt.status == ResponseCode.SUCCESS - ), f"File creation failed with status: {ResponseCode(receipt.status).name}" + assert receipt.status == ResponseCode.SUCCESS, ( + f"File creation failed with status: {ResponseCode(receipt.status).name}" + ) file_id = receipt.file_id assert file_id is not None, "File ID should not be None" @@ -204,26 +190,17 @@ def test_integration_contract_call_query_insufficient_payment(env): .set_contract_memo("test contract deployment") .execute(env.client) ) - assert ( - receipt.status == ResponseCode.SUCCESS - ), f"Contract creation failed with status: {ResponseCode(receipt.status).name}" + assert receipt.status == ResponseCode.SUCCESS, ( + f"Contract creation failed with status: {ResponseCode(receipt.status).name}" + ) contract_id = receipt.contract_id assert contract_id is not None, "Contract ID should not be None" # Prepare the contract call query with insufficient payment - contract_call_query = ( - ContractCallQuery() - .set_contract_id(contract_id) - .set_gas(10000000) - .set_function("greet") - ) - contract_call_query.set_query_payment( - Hbar.from_tinybars(1) - ) # Intentionally insufficient + contract_call_query = ContractCallQuery().set_contract_id(contract_id).set_gas(10000000).set_function("greet") + contract_call_query.set_query_payment(Hbar.from_tinybars(1)) # Intentionally insufficient - with pytest.raises( - PrecheckError, match="failed precheck with status: INSUFFICIENT_TX_FEE" - ): + with pytest.raises(PrecheckError, match="failed precheck with status: INSUFFICIENT_TX_FEE"): contract_call_query.execute(env.client) @@ -233,15 +210,10 @@ def test_integration_contract_call_query_fails_with_invalid_contract_id(env): invalid_contract_id = ContractId(0, 0, 999999999) contract_call_query = ( - ContractCallQuery() - .set_contract_id(invalid_contract_id) - .set_gas(10000000) - .set_function("greet") + ContractCallQuery().set_contract_id(invalid_contract_id).set_gas(10000000).set_function("greet") ) - with pytest.raises( - PrecheckError, match="failed precheck with status: INVALID_CONTRACT_ID" - ): + with pytest.raises(PrecheckError, match="failed precheck with status: INVALID_CONTRACT_ID"): contract_call_query.execute(env.client) @@ -256,9 +228,9 @@ def test_integration_contract_call_query_fails_with_no_gas(env): .set_file_memo("test contract for no gas") .execute(env.client) ) - assert ( - receipt.status == ResponseCode.SUCCESS - ), f"File creation failed with status: {ResponseCode(receipt.status).name}" + assert receipt.status == ResponseCode.SUCCESS, ( + f"File creation failed with status: {ResponseCode(receipt.status).name}" + ) file_id = receipt.file_id assert file_id is not None, "File ID should not be None" @@ -270,19 +242,15 @@ def test_integration_contract_call_query_fails_with_no_gas(env): .set_contract_memo("test contract deployment for no gas") .execute(env.client) ) - assert ( - contract_receipt.status == ResponseCode.SUCCESS - ), f"Contract creation failed with status: {ResponseCode(contract_receipt.status).name}" + assert contract_receipt.status == ResponseCode.SUCCESS, ( + f"Contract creation failed with status: {ResponseCode(contract_receipt.status).name}" + ) contract_id = contract_receipt.contract_id assert contract_id is not None, "Contract ID should not be None" # Attempt to call the contract with no gas set - contract_call_query = ( - ContractCallQuery().set_contract_id(contract_id).set_function("greet") - ) + contract_call_query = ContractCallQuery().set_contract_id(contract_id).set_function("greet") - with pytest.raises( - PrecheckError, match="failed precheck with status: INSUFFICIENT_GAS" - ): + with pytest.raises(PrecheckError, match="failed precheck with status: INSUFFICIENT_GAS"): contract_call_query.execute(env.client) diff --git a/tests/integration/contract_create_transaction_e2e_test.py b/tests/integration/contract_create_transaction_e2e_test.py index 060ef97b8..8d821c56c 100644 --- a/tests/integration/contract_create_transaction_e2e_test.py +++ b/tests/integration/contract_create_transaction_e2e_test.py @@ -2,6 +2,8 @@ Integration tests for the ContractCreateTransaction class. """ +from __future__ import annotations + import pytest from examples.contract.contracts import ( @@ -17,7 +19,6 @@ ) from hiero_sdk_python.file.file_create_transaction import FileCreateTransaction from hiero_sdk_python.response_code import ResponseCode -from tests.integration.utils import env @pytest.mark.integration @@ -31,9 +32,9 @@ def test_integration_contract_create_transaction_can_execute(env): .execute(env.client) ) - assert ( - receipt.status == ResponseCode.SUCCESS - ), f"File creation failed with status: {ResponseCode(receipt.status).name}" + assert receipt.status == ResponseCode.SUCCESS, ( + f"File creation failed with status: {ResponseCode(receipt.status).name}" + ) file_id = receipt.file_id assert file_id is not None, "File ID should not be None" @@ -47,9 +48,9 @@ def test_integration_contract_create_transaction_can_execute(env): .execute(env.client) ) - assert ( - receipt.status == ResponseCode.SUCCESS - ), f"Contract creation failed with status: {ResponseCode(receipt.status).name}" + assert receipt.status == ResponseCode.SUCCESS, ( + f"Contract creation failed with status: {ResponseCode(receipt.status).name}" + ) contract_id = receipt.contract_id assert contract_id is not None, "Contract ID should not be None" @@ -65,15 +66,15 @@ def test_integration_contract_create_transaction_with_constructor(env): .set_file_memo("file create with constructor params") .execute(env.client) ) - assert ( - receipt.status == ResponseCode.SUCCESS - ), f"File creation failed with status: {ResponseCode(receipt.status).name}" + assert receipt.status == ResponseCode.SUCCESS, ( + f"File creation failed with status: {ResponseCode(receipt.status).name}" + ) file_id = receipt.file_id assert file_id is not None, "File ID should not be None" # Convert the message string to bytes32 format for the contract constructor. - message = "Initial message from constructor".encode("utf-8") + message = b"Initial message from constructor" params = ContractFunctionParameters().add_bytes32(message) @@ -87,9 +88,9 @@ def test_integration_contract_create_transaction_with_constructor(env): .execute(env.client) ) - assert ( - receipt.status == ResponseCode.SUCCESS - ), f"Contract creation failed with status: {ResponseCode(receipt.status).name}" + assert receipt.status == ResponseCode.SUCCESS, ( + f"Contract creation failed with status: {ResponseCode(receipt.status).name}" + ) contract_id = receipt.contract_id assert contract_id is not None, "Contract ID should not be None" @@ -108,9 +109,9 @@ def test_integration_contract_create_transaction_set_bytecode(env): .execute(env.client) ) - assert ( - receipt.status == ResponseCode.SUCCESS - ), f"Contract creation failed with status: {ResponseCode(receipt.status).name}" + assert receipt.status == ResponseCode.SUCCESS, ( + f"Contract creation failed with status: {ResponseCode(receipt.status).name}" + ) contract_id = receipt.contract_id assert contract_id is not None, "Contract ID should not be None" diff --git a/tests/integration/contract_delete_transaction_e2e_test.py b/tests/integration/contract_delete_transaction_e2e_test.py index 9998374ff..66a85e25f 100644 --- a/tests/integration/contract_delete_transaction_e2e_test.py +++ b/tests/integration/contract_delete_transaction_e2e_test.py @@ -2,6 +2,8 @@ Integration tests for ContractDeleteTransaction. """ +from __future__ import annotations + import pytest from examples.contract.contracts import CONTRACT_DEPLOY_GAS, SIMPLE_CONTRACT_BYTECODE @@ -18,7 +20,6 @@ from hiero_sdk_python.hbar import Hbar from hiero_sdk_python.query.account_info_query import AccountInfoQuery from hiero_sdk_python.response_code import ResponseCode -from tests.integration.utils import env @pytest.mark.integration @@ -33,9 +34,9 @@ def test_integration_contract_delete_transaction_can_transfer_balance_to_account .set_contract_memo("test contract delete transaction") .execute(env.client) ) - assert ( - receipt.status == ResponseCode.SUCCESS - ), f"Contract creation failed with status: {ResponseCode(receipt.status).name}" + assert receipt.status == ResponseCode.SUCCESS, ( + f"Contract creation failed with status: {ResponseCode(receipt.status).name}" + ) contract_id = receipt.contract_id assert contract_id is not None, "Contract ID should not be None" @@ -44,24 +45,21 @@ def test_integration_contract_delete_transaction_can_transfer_balance_to_account account = env.create_account() receipt = ( - ContractDeleteTransaction() - .set_contract_id(contract_id) - .set_transfer_account_id(account.id) - .execute(env.client) + ContractDeleteTransaction().set_contract_id(contract_id).set_transfer_account_id(account.id).execute(env.client) ) - assert ( - receipt.status == ResponseCode.SUCCESS - ), f"Delete contract failed with status: {ResponseCode(receipt.status).name}" + assert receipt.status == ResponseCode.SUCCESS, ( + f"Delete contract failed with status: {ResponseCode(receipt.status).name}" + ) contract_info = ContractInfoQuery(contract_id).execute(env.client) assert contract_info.is_deleted is True, "Contract should be deleted" account_info = AccountInfoQuery(account.id).execute(env.client) assert account_info.account_id == account.id, "Account ID should match" - assert ( - account_info.balance.to_tinybars() == Hbar(2).to_tinybars() - ), f"Account balance should be 2 HBAR but got {account_info.balance.to_tinybars()}" + assert account_info.balance.to_tinybars() == Hbar(2).to_tinybars(), ( + f"Account balance should be 2 HBAR but got {account_info.balance.to_tinybars()}" + ) @pytest.mark.integration @@ -76,9 +74,9 @@ def test_integration_contract_delete_transaction_can_transfer_balance_to_contrac .set_contract_memo("test contract delete transaction") .execute(env.client) ) - assert ( - receipt.status == ResponseCode.SUCCESS - ), f"Contract creation failed with status: {ResponseCode(receipt.status).name}" + assert receipt.status == ResponseCode.SUCCESS, ( + f"Contract creation failed with status: {ResponseCode(receipt.status).name}" + ) contract_id = receipt.contract_id assert contract_id is not None, "Contract ID should not be None" @@ -92,9 +90,9 @@ def test_integration_contract_delete_transaction_can_transfer_balance_to_contrac .set_contract_memo("contract to transfer balance to") .execute(env.client) ) - assert ( - receipt.status == ResponseCode.SUCCESS - ), f"Contract creation failed with status: {ResponseCode(receipt.status).name}" + assert receipt.status == ResponseCode.SUCCESS, ( + f"Contract creation failed with status: {ResponseCode(receipt.status).name}" + ) transfer_contract_id = receipt.contract_id assert transfer_contract_id is not None, "Contract ID should not be None" @@ -105,20 +103,16 @@ def test_integration_contract_delete_transaction_can_transfer_balance_to_contrac .set_transfer_contract_id(transfer_contract_id) .execute(env.client) ) - assert ( - receipt.status == ResponseCode.SUCCESS - ), f"Contract deletion failed with status: {ResponseCode(receipt.status).name}" + assert receipt.status == ResponseCode.SUCCESS, ( + f"Contract deletion failed with status: {ResponseCode(receipt.status).name}" + ) contract_info = ContractInfoQuery(contract_id).execute(env.client) assert contract_info.is_deleted is True, "Contract should be deleted" transfer_contract_info = ContractInfoQuery(transfer_contract_id).execute(env.client) - assert ( - transfer_contract_info.contract_id == transfer_contract_id - ), "Contract ID should match" - assert ( - transfer_contract_info.balance == Hbar(2).to_tinybars() - ), "Contract balance should be 2 HBAR" + assert transfer_contract_info.contract_id == transfer_contract_id, "Contract ID should match" + assert transfer_contract_info.balance == Hbar(2).to_tinybars(), "Contract balance should be 2 HBAR" @pytest.mark.integration @@ -133,9 +127,9 @@ def test_integration_contract_delete_transaction_fails_when_deleted_twice(env): .execute(env.client) ) - assert ( - receipt.status == ResponseCode.SUCCESS - ), f"Contract creation failed with status: {ResponseCode(receipt.status).name}" + assert receipt.status == ResponseCode.SUCCESS, ( + f"Contract creation failed with status: {ResponseCode(receipt.status).name}" + ) contract_id = receipt.contract_id assert contract_id is not None, "Contract ID should not be None" @@ -147,9 +141,9 @@ def test_integration_contract_delete_transaction_fails_when_deleted_twice(env): .set_transfer_account_id(env.operator_id) .execute(env.client) ) - assert ( - receipt.status == ResponseCode.SUCCESS - ), f"Contract deletion failed with status: {ResponseCode(receipt.status).name}" + assert receipt.status == ResponseCode.SUCCESS, ( + f"Contract deletion failed with status: {ResponseCode(receipt.status).name}" + ) # Try to delete again receipt = ( @@ -195,17 +189,15 @@ def test_integration_contract_delete_transaction_fails_with_obtainer_required(en .set_initial_balance(Hbar(1).to_tinybars()) .execute(env.client) ) - assert ( - receipt.status == ResponseCode.SUCCESS - ), f"Contract creation failed with status: {ResponseCode(receipt.status).name}" + assert receipt.status == ResponseCode.SUCCESS, ( + f"Contract creation failed with status: {ResponseCode(receipt.status).name}" + ) contract_id = receipt.contract_id assert contract_id is not None, "Contract ID should not be None" # Attempt to delete without setting transfer_account_id or transfer_contract_id - with pytest.raises( - PrecheckError, match="failed precheck with status: OBTAINER_REQUIRED" - ): + with pytest.raises(PrecheckError, match="failed precheck with status: OBTAINER_REQUIRED"): ContractDeleteTransaction().set_contract_id(contract_id).execute(env.client) @@ -222,9 +214,9 @@ def test_integration_contract_delete_transaction_fails_with_immutable_contract(e .set_initial_balance(Hbar(1).to_tinybars()) .execute(env.client) ) - assert ( - receipt.status == ResponseCode.SUCCESS - ), f"Contract creation failed with status: {ResponseCode(receipt.status).name}" + assert receipt.status == ResponseCode.SUCCESS, ( + f"Contract creation failed with status: {ResponseCode(receipt.status).name}" + ) contract_id = receipt.contract_id assert contract_id is not None, "Contract ID should not be None" @@ -258,9 +250,9 @@ def test_integration_contract_delete_transaction_fails_with_invalid_signature(en .sign(admin_private_key) .execute(env.client) ) - assert ( - receipt.status == ResponseCode.SUCCESS - ), f"Contract creation failed with status: {ResponseCode(receipt.status).name}" + assert receipt.status == ResponseCode.SUCCESS, ( + f"Contract creation failed with status: {ResponseCode(receipt.status).name}" + ) contract_id = receipt.contract_id assert contract_id is not None, "Contract ID should not be None" diff --git a/tests/integration/contract_execute_transaction_e2e_test.py b/tests/integration/contract_execute_transaction_e2e_test.py index 5b17f587f..30aa301a4 100644 --- a/tests/integration/contract_execute_transaction_e2e_test.py +++ b/tests/integration/contract_execute_transaction_e2e_test.py @@ -2,6 +2,8 @@ Integration test for ContractExecuteTransaction. """ +from __future__ import annotations + import pytest from examples.contract.contracts.contract_utils import ( @@ -22,7 +24,6 @@ from hiero_sdk_python.exceptions import PrecheckError from hiero_sdk_python.file.file_create_transaction import FileCreateTransaction from hiero_sdk_python.response_code import ResponseCode -from tests.integration.utils import env @pytest.mark.integration @@ -35,9 +36,9 @@ def test_integration_contract_execute_transaction_can_execute_function(env): .set_file_memo("test contract bytecode file") .execute(env.client) ) - assert ( - file_receipt.status == ResponseCode.SUCCESS - ), f"File creation failed with status: {ResponseCode(file_receipt.status).name}" + assert file_receipt.status == ResponseCode.SUCCESS, ( + f"File creation failed with status: {ResponseCode(file_receipt.status).name}" + ) file_id = file_receipt.file_id assert file_id is not None, "File ID should not be None" @@ -52,14 +53,14 @@ def test_integration_contract_execute_transaction_can_execute_function(env): .set_contract_memo("test contract deployment") .execute(env.client) ) - assert ( - contract_receipt.status == ResponseCode.SUCCESS - ), f"Contract creation failed with status: {ResponseCode(contract_receipt.status).name}" + assert contract_receipt.status == ResponseCode.SUCCESS, ( + f"Contract creation failed with status: {ResponseCode(contract_receipt.status).name}" + ) contract_id = contract_receipt.contract_id assert contract_id is not None, "Contract ID should not be None" - new_message = b"Updated message from execute".ljust(32, b"\x00") # pad the message to 32 bytes + new_message = b"Updated message from execute".ljust(32, b"\x00") # pad the message to 32 bytes execute_params = ContractFunctionParameters().add_bytes32(new_message) execute_receipt = ( ContractExecuteTransaction() @@ -68,16 +69,12 @@ def test_integration_contract_execute_transaction_can_execute_function(env): .set_function("setMessage", execute_params) .execute(env.client) ) - assert ( - execute_receipt.status == ResponseCode.SUCCESS - ), f"Contract execute failed with status: {ResponseCode(execute_receipt.status).name}" + assert execute_receipt.status == ResponseCode.SUCCESS, ( + f"Contract execute failed with status: {ResponseCode(execute_receipt.status).name}" + ) result = ( - ContractCallQuery() - .set_contract_id(contract_id) - .set_gas(1000000) - .set_function("getMessage") - .execute(env.client) + ContractCallQuery().set_contract_id(contract_id).set_gas(1000000).set_function("getMessage").execute(env.client) ) assert result is not None, "Contract call result should not be None" assert result.get_bytes32(0) == new_message @@ -93,9 +90,9 @@ def test_integration_contract_execute_fails_with_no_gas(env): .set_file_memo("test contract bytecode file") .execute(env.client) ) - assert ( - file_receipt.status == ResponseCode.SUCCESS - ), f"File creation failed with status: {ResponseCode(file_receipt.status).name}" + assert file_receipt.status == ResponseCode.SUCCESS, ( + f"File creation failed with status: {ResponseCode(file_receipt.status).name}" + ) file_id = file_receipt.file_id assert file_id is not None, "File ID should not be None" @@ -111,24 +108,18 @@ def test_integration_contract_execute_fails_with_no_gas(env): .set_contract_memo("test contract deployment") .execute(env.client) ) - assert ( - contract_receipt.status == ResponseCode.SUCCESS - ), f"Contract creation failed with status: {ResponseCode(contract_receipt.status).name}" + assert contract_receipt.status == ResponseCode.SUCCESS, ( + f"Contract creation failed with status: {ResponseCode(contract_receipt.status).name}" + ) contract_id = contract_receipt.contract_id assert contract_id is not None, "Contract ID should not be None" new_message = b"Updated message from execute" execute_params = ContractFunctionParameters().add_bytes32(new_message) - transaction = ( - ContractExecuteTransaction() - .set_contract_id(contract_id) - .set_function("setMessage", execute_params) - ) + transaction = ContractExecuteTransaction().set_contract_id(contract_id).set_function("setMessage", execute_params) - with pytest.raises( - PrecheckError, match="failed precheck with status: INSUFFICIENT_GAS" - ): + with pytest.raises(PrecheckError, match="failed precheck with status: INSUFFICIENT_GAS"): transaction.execute(env.client) @@ -142,9 +133,9 @@ def test_integration_contract_execute_fails_with_invalid_function(env): .set_file_memo("test contract bytecode file") .execute(env.client) ) - assert ( - file_receipt.status == ResponseCode.SUCCESS - ), f"File creation failed with status: {ResponseCode(file_receipt.status).name}" + assert file_receipt.status == ResponseCode.SUCCESS, ( + f"File creation failed with status: {ResponseCode(file_receipt.status).name}" + ) file_id = file_receipt.file_id assert file_id is not None, "File ID should not be None" @@ -160,9 +151,9 @@ def test_integration_contract_execute_fails_with_invalid_function(env): .set_contract_memo("test contract deployment") .execute(env.client) ) - assert ( - contract_receipt.status == ResponseCode.SUCCESS - ), f"Contract creation failed with status: {ResponseCode(contract_receipt.status).name}" + assert contract_receipt.status == ResponseCode.SUCCESS, ( + f"Contract creation failed with status: {ResponseCode(contract_receipt.status).name}" + ) contract_id = contract_receipt.contract_id assert contract_id is not None, "Contract ID should not be None" @@ -193,9 +184,9 @@ def test_integration_contract_execute_fails_with_missing_parameters(env): .set_file_memo("test contract bytecode file") .execute(env.client) ) - assert ( - file_receipt.status == ResponseCode.SUCCESS - ), f"File creation failed with status: {ResponseCode(file_receipt.status).name}" + assert file_receipt.status == ResponseCode.SUCCESS, ( + f"File creation failed with status: {ResponseCode(file_receipt.status).name}" + ) file_id = file_receipt.file_id assert file_id is not None, "File ID should not be None" @@ -210,9 +201,9 @@ def test_integration_contract_execute_fails_with_missing_parameters(env): .set_contract_memo("test contract deployment") .execute(env.client) ) - assert ( - contract_receipt.status == ResponseCode.SUCCESS - ), f"Contract creation failed with status: {ResponseCode(contract_receipt.status).name}" + assert contract_receipt.status == ResponseCode.SUCCESS, ( + f"Contract creation failed with status: {ResponseCode(contract_receipt.status).name}" + ) contract_id = contract_receipt.contract_id assert contract_id is not None, "Contract ID should not be None" @@ -241,9 +232,9 @@ def test_integration_contract_execute_with_amount(env): .set_file_memo("test contract bytecode file") .execute(env.client) ) - assert ( - file_receipt.status == ResponseCode.SUCCESS - ), f"File creation failed with status: {ResponseCode(file_receipt.status).name}" + assert file_receipt.status == ResponseCode.SUCCESS, ( + f"File creation failed with status: {ResponseCode(file_receipt.status).name}" + ) file_id = file_receipt.file_id assert file_id is not None, "File ID should not be None" @@ -259,9 +250,9 @@ def test_integration_contract_execute_with_amount(env): .set_contract_memo("test contract deployment") .execute(env.client) ) - assert ( - contract_receipt.status == ResponseCode.SUCCESS - ), f"Contract creation failed with status: {ResponseCode(contract_receipt.status).name}" + assert contract_receipt.status == ResponseCode.SUCCESS, ( + f"Contract creation failed with status: {ResponseCode(contract_receipt.status).name}" + ) contract_id = contract_receipt.contract_id assert contract_id is not None, "Contract ID should not be None" @@ -274,9 +265,9 @@ def test_integration_contract_execute_with_amount(env): .execute(env.client) ) - assert ( - receipt.status == ResponseCode.SUCCESS - ), f"Contract creation failed with status: {ResponseCode(receipt.status).name}" + assert receipt.status == ResponseCode.SUCCESS, ( + f"Contract creation failed with status: {ResponseCode(receipt.status).name}" + ) info = ContractInfoQuery().set_contract_id(contract_id).execute(env.client) assert info.contract_id == contract_id, "Contract ID should match" @@ -296,9 +287,9 @@ def test_integration_contract_execute_fails_on_nonpayable_with_payment(env): .set_file_memo("test contract bytecode file") .execute(env.client) ) - assert ( - file_receipt.status == ResponseCode.SUCCESS - ), f"File creation failed with status: {ResponseCode(file_receipt.status).name}" + assert file_receipt.status == ResponseCode.SUCCESS, ( + f"File creation failed with status: {ResponseCode(file_receipt.status).name}" + ) file_id = file_receipt.file_id assert file_id is not None, "File ID should not be None" @@ -314,9 +305,9 @@ def test_integration_contract_execute_fails_on_nonpayable_with_payment(env): .set_contract_memo("test contract deployment") .execute(env.client) ) - assert ( - contract_receipt.status == ResponseCode.SUCCESS - ), f"Contract creation failed with status: {ResponseCode(contract_receipt.status).name}" + assert contract_receipt.status == ResponseCode.SUCCESS, ( + f"Contract creation failed with status: {ResponseCode(contract_receipt.status).name}" + ) contract_id = contract_receipt.contract_id assert contract_id is not None, "Contract ID should not be None" @@ -346,9 +337,9 @@ def test_integration_contract_execute_payable_function_with_payment(env): .set_contents(STATEFUL_CONTRACT_BYTECODE) .execute(env.client) ) - assert ( - file_receipt.status == ResponseCode.SUCCESS - ), f"File creation failed with status: {ResponseCode(file_receipt.status).name}" + assert file_receipt.status == ResponseCode.SUCCESS, ( + f"File creation failed with status: {ResponseCode(file_receipt.status).name}" + ) file_id = file_receipt.file_id assert file_id is not None, "File ID should not be None" @@ -363,9 +354,9 @@ def test_integration_contract_execute_payable_function_with_payment(env): .set_bytecode_file_id(file_id) .execute(env.client) ) - assert ( - contract_receipt.status == ResponseCode.SUCCESS - ), f"Contract creation failed with status: {ResponseCode(contract_receipt.status).name}" + assert contract_receipt.status == ResponseCode.SUCCESS, ( + f"Contract creation failed with status: {ResponseCode(contract_receipt.status).name}" + ) contract_id = contract_receipt.contract_id assert contract_id is not None, "Contract ID should not be None" @@ -379,20 +370,16 @@ def test_integration_contract_execute_payable_function_with_payment(env): .execute(env.client) ) - assert ( - receipt.status == ResponseCode.SUCCESS - ), f"Contract execute failed with status: {ResponseCode(receipt.status).name}" + assert receipt.status == ResponseCode.SUCCESS, ( + f"Contract execute failed with status: {ResponseCode(receipt.status).name}" + ) info = ContractInfoQuery().set_contract_id(contract_id).execute(env.client) assert info.contract_id == contract_id, "Contract ID should match" assert info.balance == 100, "Balance should match" receipt = ( - ContractCallQuery() - .set_contract_id(contract_id) - .set_gas(1000000) - .set_function("getMessage") - .execute(env.client) + ContractCallQuery().set_contract_id(contract_id).set_gas(1000000).set_function("getMessage").execute(env.client) ) assert receipt is not None, "Contract call result should not be None" assert receipt.get_bytes32(0).strip(b"\x00") == b"test", "Message should match" diff --git a/tests/integration/contract_function_parameters_e2e_test.py b/tests/integration/contract_function_parameters_e2e_test.py index f035d91e1..97f0e7d4c 100644 --- a/tests/integration/contract_function_parameters_e2e_test.py +++ b/tests/integration/contract_function_parameters_e2e_test.py @@ -2,6 +2,8 @@ Integration tests for the ConstructorTestContract constructor parameters. """ +from __future__ import annotations + import pytest from examples.contract.contracts import CONSTRUCTOR_TEST_CONTRACT_BYTECODE, CONTRACT_DEPLOY_GAS @@ -14,7 +16,7 @@ from hiero_sdk_python.crypto.private_key import PrivateKey from hiero_sdk_python.file.file_create_transaction import FileCreateTransaction from hiero_sdk_python.response_code import ResponseCode -from tests.integration.utils import env + # Generate a new ECDSA key pair and extract the first 40 bytes of the public key # to use as a test address for the contract constructor @@ -32,9 +34,9 @@ def test_constructor_test_contract_parameters(env): .execute(env.client) ) - assert ( - receipt.status == ResponseCode.SUCCESS - ), f"File creation failed with status: {ResponseCode(receipt.status).name}" + assert receipt.status == ResponseCode.SUCCESS, ( + f"File creation failed with status: {ResponseCode(receipt.status).name}" + ) file_id = receipt.file_id assert file_id is not None, "File ID should not be None" @@ -69,9 +71,9 @@ def test_constructor_test_contract_parameters(env): .execute(env.client) ) - assert ( - receipt.status == ResponseCode.SUCCESS - ), f"Contract creation failed with status: {ResponseCode(receipt.status).name}" + assert receipt.status == ResponseCode.SUCCESS, ( + f"Contract creation failed with status: {ResponseCode(receipt.status).name}" + ) contract_id = receipt.contract_id assert contract_id is not None, "Contract ID should not be None" @@ -148,9 +150,7 @@ def test_constructor_test_contract_parameter_variations(env): .execute(env.client) ) - assert ( - receipt.status == ResponseCode.SUCCESS - ), f"Contract creation failed for test case '{test_case['name']}'" + assert receipt.status == ResponseCode.SUCCESS, f"Contract creation failed for test case '{test_case['name']}'" @pytest.mark.integration @@ -199,9 +199,7 @@ def test_constructor_test_contract_parameter_order_sensitivity(env): .execute(env.client) ) - assert ( - receipt.status == ResponseCode.SUCCESS - ), "Contract creation with correct parameter order failed" + assert receipt.status == ResponseCode.SUCCESS, "Contract creation with correct parameter order failed" # Try with incorrect order (should fail) # For example, swap string and bytes32 diff --git a/tests/integration/contract_id_population_e2e_test.py b/tests/integration/contract_id_population_e2e_test.py index 848e2ec9f..14be72d47 100644 --- a/tests/integration/contract_id_population_e2e_test.py +++ b/tests/integration/contract_id_population_e2e_test.py @@ -2,6 +2,8 @@ Integration tests for ContractId. """ +from __future__ import annotations + import pytest from examples.contract.contracts.contract_utils import ( @@ -18,7 +20,7 @@ from hiero_sdk_python.contract.contract_info_query import ContractInfoQuery from hiero_sdk_python.file.file_create_transaction import FileCreateTransaction from hiero_sdk_python.response_code import ResponseCode -from tests.integration.utils import env, wait_for_mirror_node +from tests.integration.utils import wait_for_mirror_node @pytest.mark.integration @@ -36,9 +38,7 @@ def test_populate_contract_id_num(env): file_id = file_receipt.file_id assert file_id is not None - constructor_params = ContractFunctionParameters().add_bytes32( - b"Initial message from constructor" - ) + constructor_params = ContractFunctionParameters().add_bytes32(b"Initial message from constructor") contract_receipt = ( ContractCreateTransaction() .set_admin_key(env.operator_key.public_key()) diff --git a/tests/integration/contract_info_query_e2e_test.py b/tests/integration/contract_info_query_e2e_test.py index f65eb2374..81c7bddd0 100644 --- a/tests/integration/contract_info_query_e2e_test.py +++ b/tests/integration/contract_info_query_e2e_test.py @@ -2,6 +2,8 @@ Integration tests for ContractInfoQuery. """ +from __future__ import annotations + import pytest from examples.contract.contracts import CONTRACT_DEPLOY_GAS, STATEFUL_CONTRACT_BYTECODE @@ -18,7 +20,6 @@ from hiero_sdk_python.file.file_create_transaction import FileCreateTransaction from hiero_sdk_python.hbar import Hbar from hiero_sdk_python.response_code import ResponseCode -from tests.integration.utils import env @pytest.mark.integration @@ -31,15 +32,15 @@ def test_integration_contract_info_query_can_execute(env): .set_file_memo("file create with constructor params") .execute(env.client) ) - assert ( - receipt.status == ResponseCode.SUCCESS - ), f"File creation failed with status: {ResponseCode(receipt.status).name}" + assert receipt.status == ResponseCode.SUCCESS, ( + f"File creation failed with status: {ResponseCode(receipt.status).name}" + ) file_id = receipt.file_id assert file_id is not None, "File ID should not be None" # Convert the message string to bytes32 format for the contract constructor. - message = "Initial message from constructor".encode("utf-8") + message = b"Initial message from constructor" params = ContractFunctionParameters().add_bytes32(message) auto_renew_period = Duration(seconds=5184000) # 60 days in seconds @@ -57,9 +58,9 @@ def test_integration_contract_info_query_can_execute(env): .execute(env.client) ) - assert ( - receipt.status == ResponseCode.SUCCESS - ), f"Contract creation failed with status: {ResponseCode(receipt.status).name}" + assert receipt.status == ResponseCode.SUCCESS, ( + f"Contract creation failed with status: {ResponseCode(receipt.status).name}" + ) contract_id = receipt.contract_id assert contract_id is not None, "Contract ID should not be None" @@ -67,17 +68,11 @@ def test_integration_contract_info_query_can_execute(env): info = ContractInfoQuery().set_contract_id(contract_id).execute(env.client) assert str(info.contract_id) == str(contract_id), "Contract ID mismatch" - assert ( - info.admin_key.to_bytes_raw() == env.operator_key.public_key().to_bytes_raw() - ), "Admin key mismatch" - assert ( - info.contract_memo == "contract create with constructor params" - ), "Contract memo mismatch" + assert info.admin_key.to_bytes_raw() == env.operator_key.public_key().to_bytes_raw(), "Admin key mismatch" + assert info.contract_memo == "contract create with constructor params", "Contract memo mismatch" assert info.balance == 1000, "Contract balance should be 1000" assert info.is_deleted is False, "Contract should not be deleted" - assert ( - info.max_automatic_token_associations == 10 - ), "Max automatic token associations should be 10" + assert info.max_automatic_token_associations == 10, "Max automatic token associations should be 10" assert not info.token_relationships, "Token relationships should be empty" assert info.auto_renew_account_id == env.operator_id, "Auto renew account ID mismatch" assert info.auto_renew_period == auto_renew_period, "Auto renew period mismatch" @@ -99,15 +94,15 @@ def test_integration_contract_info_query_get_cost(env): .set_file_memo("file create with constructor params") .execute(env.client) ) - assert ( - receipt.status == ResponseCode.SUCCESS - ), f"File creation failed with status: {ResponseCode(receipt.status).name}" + assert receipt.status == ResponseCode.SUCCESS, ( + f"File creation failed with status: {ResponseCode(receipt.status).name}" + ) file_id = receipt.file_id assert file_id is not None, "File ID should not be None" # Convert the message string to bytes32 format for the contract constructor. - message = "Initial message from constructor".encode("utf-8") + message = b"Initial message from constructor" params = ContractFunctionParameters().add_bytes32(message) @@ -121,9 +116,9 @@ def test_integration_contract_info_query_get_cost(env): .execute(env.client) ) - assert ( - receipt.status == ResponseCode.SUCCESS - ), f"Contract creation failed with status: {ResponseCode(receipt.status).name}" + assert receipt.status == ResponseCode.SUCCESS, ( + f"Contract creation failed with status: {ResponseCode(receipt.status).name}" + ) contract_id = receipt.contract_id assert contract_id is not None, "Contract ID should not be None" @@ -147,15 +142,15 @@ def test_integration_contract_info_query_insufficient_payment(env): .set_file_memo("file create with constructor params") .execute(env.client) ) - assert ( - receipt.status == ResponseCode.SUCCESS - ), f"File creation failed with status: {ResponseCode(receipt.status).name}" + assert receipt.status == ResponseCode.SUCCESS, ( + f"File creation failed with status: {ResponseCode(receipt.status).name}" + ) file_id = receipt.file_id assert file_id is not None, "File ID should not be None" # Convert the message string to bytes32 format for the contract constructor. - message = "Initial message from constructor".encode("utf-8") + message = b"Initial message from constructor" params = ContractFunctionParameters().add_bytes32(message) @@ -169,18 +164,16 @@ def test_integration_contract_info_query_insufficient_payment(env): .execute(env.client) ) - assert ( - receipt.status == ResponseCode.SUCCESS - ), f"Contract creation failed with status: {ResponseCode(receipt.status).name}" + assert receipt.status == ResponseCode.SUCCESS, ( + f"Contract creation failed with status: {ResponseCode(receipt.status).name}" + ) contract_id = receipt.contract_id assert contract_id is not None, "Contract ID should not be None" contract_info = ContractInfoQuery().set_contract_id(contract_id) - with pytest.raises( - PrecheckError, match="failed precheck with status: INSUFFICIENT_TX_FEE" - ): + with pytest.raises(PrecheckError, match="failed precheck with status: INSUFFICIENT_TX_FEE"): contract_info.set_query_payment(Hbar.from_tinybars(1)).execute(env.client) @@ -190,9 +183,7 @@ def test_integration_contract_info_query_fails_with_invalid_contract_id(env): # Create a contract ID that doesn't exist on the network contract_id = ContractId(0, 0, 999999999) - with pytest.raises( - PrecheckError, match="failed precheck with status: INVALID_CONTRACT_ID" - ): + with pytest.raises(PrecheckError, match="failed precheck with status: INVALID_CONTRACT_ID"): ContractInfoQuery(contract_id).execute(env.client) @@ -206,15 +197,15 @@ def test_integration_contract_info_query_can_execute_without_admin_key(env): .set_file_memo("file create with constructor params") .execute(env.client) ) - assert ( - receipt.status == ResponseCode.SUCCESS - ), f"File creation failed with status: {ResponseCode(receipt.status).name}" + assert receipt.status == ResponseCode.SUCCESS, ( + f"File creation failed with status: {ResponseCode(receipt.status).name}" + ) file_id = receipt.file_id assert file_id is not None, "File ID should not be None" # Convert the message string to bytes32 format for the contract constructor. - message = "Initial message from constructor".encode("utf-8") + message = b"Initial message from constructor" params = ContractFunctionParameters().add_bytes32(message) @@ -227,9 +218,9 @@ def test_integration_contract_info_query_can_execute_without_admin_key(env): .execute(env.client) ) - assert ( - receipt.status == ResponseCode.SUCCESS - ), f"Contract creation failed with status: {ResponseCode(receipt.status).name}" + assert receipt.status == ResponseCode.SUCCESS, ( + f"Contract creation failed with status: {ResponseCode(receipt.status).name}" + ) contract_id = receipt.contract_id assert contract_id is not None, "Contract ID should not be None" @@ -238,6 +229,4 @@ def test_integration_contract_info_query_can_execute_without_admin_key(env): assert str(info.contract_id) == str(contract_id), "Contract ID mismatch" assert isinstance(info.admin_key, ContractId), "Admin key should be a ContractId" - assert str(info.admin_key) == str( - contract_id - ), "Admin key should be the contract ID" + assert str(info.admin_key) == str(contract_id), "Admin key should be the contract ID" diff --git a/tests/integration/contract_update_transaction_e2e_test.py b/tests/integration/contract_update_transaction_e2e_test.py index 81809a28f..b785ee06c 100644 --- a/tests/integration/contract_update_transaction_e2e_test.py +++ b/tests/integration/contract_update_transaction_e2e_test.py @@ -2,6 +2,8 @@ Integration tests for the ContractUpdateTransaction class. """ +from __future__ import annotations + import datetime import pytest @@ -19,7 +21,6 @@ from hiero_sdk_python.crypto.private_key import PrivateKey from hiero_sdk_python.response_code import ResponseCode from hiero_sdk_python.timestamp import Timestamp -from tests.integration.utils import env @pytest.mark.integration @@ -34,9 +35,9 @@ def test_integration_contract_update_transaction_can_execute(env): .set_contract_memo("[e2e::ContractCreateTransaction]") .execute(env.client) ) - assert ( - contract_receipt.status == ResponseCode.SUCCESS - ), f"Contract creation failed with status: {ResponseCode(contract_receipt.status).name}" + assert contract_receipt.status == ResponseCode.SUCCESS, ( + f"Contract creation failed with status: {ResponseCode(contract_receipt.status).name}" + ) contract_id = contract_receipt.contract_id assert contract_id is not None, "Contract ID should not be None" @@ -55,28 +56,20 @@ def test_integration_contract_update_transaction_can_execute(env): .set_expiration_time(future_expiration) .execute(env.client) ) - assert ( - update_receipt.status == ResponseCode.SUCCESS - ), f"Contract update failed with status: {ResponseCode(update_receipt.status).name}" + assert update_receipt.status == ResponseCode.SUCCESS, ( + f"Contract update failed with status: {ResponseCode(update_receipt.status).name}" + ) info = ContractInfoQuery().set_contract_id(contract_id).execute(env.client) assert info.contract_id == contract_id, "Contract ID should be updated" - assert ( - info.admin_key.to_bytes_raw() == env.operator_key.public_key().to_bytes_raw() - ), "Admin key should be unchanged" + assert info.admin_key.to_bytes_raw() == env.operator_key.public_key().to_bytes_raw(), ( + "Admin key should be unchanged" + ) assert info.contract_memo == updated_memo, "Contract memo should be updated" - assert ( - info.auto_renew_account_id == env.operator_id - ), "Auto renew account ID should be updated" - assert ( - info.auto_renew_period == updated_duration - ), "Auto renew period should be updated" - assert ( - info.max_automatic_token_associations == 10 - ), "Max automatic token associations should be updated" - assert ( - info.expiration_time.seconds == future_expiration.seconds - ), "Expiration time should be updated" + assert info.auto_renew_account_id == env.operator_id, "Auto renew account ID should be updated" + assert info.auto_renew_period == updated_duration, "Auto renew period should be updated" + assert info.max_automatic_token_associations == 10, "Max automatic token associations should be updated" + assert info.expiration_time.seconds == future_expiration.seconds, "Expiration time should be updated" @pytest.mark.integration @@ -84,13 +77,10 @@ def test_integration_contract_update_transaction_fails_with_invalid_contract_id( """Test that contract update fails when contract ID is invalid.""" contract_id = ContractId(0, 0, 999999999) - receipt = ( - ContractUpdateTransaction().set_contract_id(contract_id).execute(env.client) - ) + receipt = ContractUpdateTransaction().set_contract_id(contract_id).execute(env.client) assert receipt.status == ResponseCode.INVALID_CONTRACT_ID, ( - f"Contract update should fail when contract ID is invalid, " - f"but got status: {ResponseCode(receipt.status).name}" + f"Contract update should fail when contract ID is invalid, but got status: {ResponseCode(receipt.status).name}" ) @@ -108,9 +98,9 @@ def test_integration_contract_update_transaction_with_admin_key(env): .set_contract_memo("[e2e::ContractCreateTransaction]") .execute(env.client) ) - assert ( - contract_receipt.status == ResponseCode.SUCCESS - ), f"Contract creation failed with status: {ResponseCode(contract_receipt.status).name}" + assert contract_receipt.status == ResponseCode.SUCCESS, ( + f"Contract creation failed with status: {ResponseCode(contract_receipt.status).name}" + ) contract_id = contract_receipt.contract_id assert contract_id is not None, "Contract ID should not be None" @@ -123,20 +113,16 @@ def test_integration_contract_update_transaction_with_admin_key(env): .sign(new_admin_key) .execute(env.client) ) - assert ( - update_receipt.status == ResponseCode.SUCCESS - ), f"Contract update failed with status: {ResponseCode(update_receipt.status).name}" + assert update_receipt.status == ResponseCode.SUCCESS, ( + f"Contract update failed with status: {ResponseCode(update_receipt.status).name}" + ) contract_info = ContractInfoQuery().set_contract_id(contract_id).execute(env.client) - assert ( - contract_info.admin_key.to_string() == new_admin_key.public_key().to_string() - ), "Admin key should be updated" - assert ( - contract_info.contract_memo == "[e2e::ContractCreateTransaction]" - ), "Auto renew account ID should be unchanged" - assert ( - contract_info.max_automatic_token_associations == 10 - ), "Max automatic token associations should be unchanged" + assert contract_info.admin_key.to_string() == new_admin_key.public_key().to_string(), "Admin key should be updated" + assert contract_info.contract_memo == "[e2e::ContractCreateTransaction]", ( + "Auto renew account ID should be unchanged" + ) + assert contract_info.max_automatic_token_associations == 10, "Max automatic token associations should be unchanged" @pytest.mark.integration @@ -151,9 +137,9 @@ def test_integration_contract_update_transaction_invalid_auto_renew_period(env): .set_contract_memo("[e2e::ContractCreateTransaction]") .execute(env.client) ) - assert ( - contract_receipt.status == ResponseCode.SUCCESS - ), f"Contract creation failed with status: {ResponseCode(contract_receipt.status).name}" + assert contract_receipt.status == ResponseCode.SUCCESS, ( + f"Contract creation failed with status: {ResponseCode(contract_receipt.status).name}" + ) contract_id = contract_receipt.contract_id assert contract_id is not None, "Contract ID should not be None" @@ -188,9 +174,9 @@ def test_integration_contract_update_transaction_fails_with_invalid_signature(en .set_contract_memo("[e2e::ContractCreateTransaction]") .execute(env.client) ) - assert ( - contract_receipt.status == ResponseCode.SUCCESS - ), f"Contract creation failed with status: {ResponseCode(contract_receipt.status).name}" + assert contract_receipt.status == ResponseCode.SUCCESS, ( + f"Contract creation failed with status: {ResponseCode(contract_receipt.status).name}" + ) contract_id = contract_receipt.contract_id assert contract_id is not None, "Contract ID should not be None" diff --git a/tests/integration/ethereum_transaction_e2e_test.py b/tests/integration/ethereum_transaction_e2e_test.py index aba1c5545..4bf226902 100644 --- a/tests/integration/ethereum_transaction_e2e_test.py +++ b/tests/integration/ethereum_transaction_e2e_test.py @@ -2,6 +2,8 @@ Integration tests for the EthereumTransaction class. """ +from __future__ import annotations + import pytest import rlp from eth_keys import keys @@ -23,7 +25,6 @@ from hiero_sdk_python.query.transaction_record_query import TransactionRecordQuery from hiero_sdk_python.response_code import ResponseCode from hiero_sdk_python.transaction.transfer_transaction import TransferTransaction -from tests.integration.utils import env @pytest.mark.integration @@ -33,10 +34,8 @@ def test_integration_ethereum_transaction_with_contract_execution(env): contract_id = _create_contract(env) - message = "Updated message bytes!".encode("utf-8") - call_data_bytes = ( - ContractFunctionParameters("setMessage").add_bytes32(message).to_bytes() - ) + message = b"Updated message bytes!" + call_data_bytes = ContractFunctionParameters("setMessage").add_bytes32(message).to_bytes() # Ethereum transaction fields chain_id_bytes = bytes.fromhex("012a") @@ -60,12 +59,10 @@ def test_integration_ethereum_transaction_with_contract_execution(env): alias_private_key, ) - receipt = ( - EthereumTransaction().set_ethereum_data(transaction_data).execute(env.client) + receipt = EthereumTransaction().set_ethereum_data(transaction_data).execute(env.client) + assert receipt.status == ResponseCode.SUCCESS, ( + f"Ethereum transaction failed with status: {ResponseCode(receipt.status).name}" ) - assert ( - receipt.status == ResponseCode.SUCCESS - ), f"Ethereum transaction failed with status: {ResponseCode(receipt.status).name}" info_query = ( ContractCallQuery() @@ -75,9 +72,7 @@ def test_integration_ethereum_transaction_with_contract_execution(env): .execute(env.client) ) - assert ( - info_query.get_bytes32(0).rstrip(b"\x00") == message - ), "Message should be updated" + assert info_query.get_bytes32(0).rstrip(b"\x00") == message, "Message should be updated" @pytest.mark.integration @@ -111,21 +106,13 @@ def test_integration_ethereum_transaction_with_contract_call(env): alias_private_key, ) - receipt = ( - EthereumTransaction().set_ethereum_data(transaction_data).execute(env.client) + receipt = EthereumTransaction().set_ethereum_data(transaction_data).execute(env.client) + assert receipt.status == ResponseCode.SUCCESS, ( + f"Ethereum transaction failed with status: {ResponseCode(receipt.status).name}" ) - assert ( - receipt.status == ResponseCode.SUCCESS - ), f"Ethereum transaction failed with status: {ResponseCode(receipt.status).name}" - record = ( - TransactionRecordQuery() - .set_transaction_id(receipt.transaction_id) - .execute(env.client) - ) - assert ( - record.call_result.contract_call_result == b"Initial message from constructor" - ) + record = TransactionRecordQuery().set_transaction_id(receipt.transaction_id).execute(env.client) + assert record.call_result.contract_call_result == b"Initial message from constructor" def test_integration_ethereum_transaction_jumbo_transaction(env): @@ -160,12 +147,10 @@ def test_integration_ethereum_transaction_jumbo_transaction(env): alias_private_key, ) - receipt = ( - EthereumTransaction().set_ethereum_data(transaction_data).execute(env.client) + receipt = EthereumTransaction().set_ethereum_data(transaction_data).execute(env.client) + assert receipt.status == ResponseCode.SUCCESS, ( + f"Ethereum transaction failed with status: {ResponseCode(receipt.status).name}" ) - assert ( - receipt.status == ResponseCode.SUCCESS - ), f"Ethereum transaction failed with status: {ResponseCode(receipt.status).name}" # pylint: disable=too-many-arguments, too-many-positional-arguments @@ -196,7 +181,6 @@ def _get_call_data( Returns: Complete transaction bytes with signature """ - # Create the transaction list without signature components # EIP-1559 transaction format: # [chainId, nonce, maxPriorityFeePerGas, maxFeePerGas, gasLimit, to, value, data, accessList] @@ -241,9 +225,7 @@ def _create_alias_account(env) -> PrivateKey: """ alias_private_key = PrivateKey.generate_ecdsa() - alias_account_id = AccountId( - shard=0, realm=0, num=0, alias_key=alias_private_key.public_key() - ) + alias_account_id = AccountId(shard=0, realm=0, num=0, alias_key=alias_private_key.public_key()) # Create a shallow account for the ECDSA key by transferring HBAR to the alias receipt = ( @@ -252,9 +234,9 @@ def _create_alias_account(env) -> PrivateKey: .add_hbar_transfer(alias_account_id, Hbar(5).to_tinybars()) .execute(env.client) ) - assert ( - receipt.status == ResponseCode.SUCCESS - ), f"Transfer transaction failed with status: {ResponseCode(receipt.status).name}" + assert receipt.status == ResponseCode.SUCCESS, ( + f"Transfer transaction failed with status: {ResponseCode(receipt.status).name}" + ) return alias_private_key @@ -274,15 +256,15 @@ def _create_contract(env) -> ContractId: .set_file_memo("file create with constructor params") .execute(env.client) ) - assert ( - receipt.status == ResponseCode.SUCCESS - ), f"Create file failed with status: {ResponseCode(receipt.status).name}" + assert receipt.status == ResponseCode.SUCCESS, ( + f"Create file failed with status: {ResponseCode(receipt.status).name}" + ) file_id = receipt.file_id assert file_id is not None, "File ID should not be None" # Convert the message string to bytes32 format for the contract constructor. - message = "Initial message from constructor".encode("utf-8") + message = b"Initial message from constructor" receipt = ( ContractCreateTransaction() .set_admin_key(env.operator_key.public_key()) @@ -293,9 +275,9 @@ def _create_contract(env) -> ContractId: .execute(env.client) ) - assert ( - receipt.status == ResponseCode.SUCCESS - ), f"Contract creation failed with status: {ResponseCode(receipt.status).name}" + assert receipt.status == ResponseCode.SUCCESS, ( + f"Contract creation failed with status: {ResponseCode(receipt.status).name}" + ) contract_id = receipt.contract_id assert contract_id is not None, "Contract ID should not be None" diff --git a/tests/integration/file_append_transaction_e2e_test.py b/tests/integration/file_append_transaction_e2e_test.py index eb507412a..e44d61e28 100644 --- a/tests/integration/file_append_transaction_e2e_test.py +++ b/tests/integration/file_append_transaction_e2e_test.py @@ -1,22 +1,23 @@ +from __future__ import annotations + import pytest from pytest import mark -from hiero_sdk_python.file.file_create_transaction import FileCreateTransaction from hiero_sdk_python.file.file_append_transaction import FileAppendTransaction from hiero_sdk_python.file.file_contents_query import FileContentsQuery -from hiero_sdk_python.file.file_id import FileId +from hiero_sdk_python.file.file_create_transaction import FileCreateTransaction from hiero_sdk_python.response_code import ResponseCode -from hiero_sdk_python.hbar import Hbar -from hiero_sdk_python.exceptions import PrecheckError -from tests.integration.utils import env, IntegrationTestEnv + # Generate big contents for chunking tests - similar to JavaScript bigContents BIG_CONTENTS = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. " * 250 # ~13,750 characters + def generate_uint8_array(length: int) -> bytes: """Generate a byte array of specified length filled with repeating pattern.""" return bytes([1] * length) + @mark.integration def test_integration_file_append_transaction_can_execute(env): """Test basic file append functionality and verify content is properly appended.""" @@ -24,13 +25,12 @@ def test_integration_file_append_transaction_can_execute(env): # Create a file first create_receipt = ( - FileCreateTransaction() - .set_keys(operator_key) - .set_contents(b"[e2e::FileCreateTransaction]") - .execute(env.client) + FileCreateTransaction().set_keys(operator_key).set_contents(b"[e2e::FileCreateTransaction]").execute(env.client) + ) + + assert create_receipt.status == ResponseCode.SUCCESS, ( + f"Create file failed with status: {ResponseCode(create_receipt.status).name}" ) - - assert create_receipt.status == ResponseCode.SUCCESS, f"Create file failed with status: {ResponseCode(create_receipt.status).name}" assert create_receipt.file_id is not None assert create_receipt.file_id.file > 0 @@ -38,19 +38,19 @@ def test_integration_file_append_transaction_can_execute(env): # Append to the file append_receipt = ( - FileAppendTransaction() - .set_file_id(file_id) - .set_contents(b"[e2e::FileAppendTransaction]") - .execute(env.client) + FileAppendTransaction().set_file_id(file_id).set_contents(b"[e2e::FileAppendTransaction]").execute(env.client) + ) + + assert append_receipt.status == ResponseCode.SUCCESS, ( + f"Append file failed with status: {ResponseCode(append_receipt.status).name}" ) - - assert append_receipt.status == ResponseCode.SUCCESS, f"Append file failed with status: {ResponseCode(append_receipt.status).name}" - + # Verify the content was properly appended file_contents = FileContentsQuery().set_file_id(file_id).execute(env.client) expected_content = b"[e2e::FileCreateTransaction][e2e::FileAppendTransaction]" assert file_contents == expected_content, f"Expected {expected_content}, but got {file_contents}" + @mark.integration def test_integration_file_append_transaction_chunk_contents(env): """Test file append with large content that requires chunking.""" @@ -58,55 +58,50 @@ def test_integration_file_append_transaction_chunk_contents(env): # Create a file first create_receipt = ( - FileCreateTransaction() - .set_keys(operator_key) - .set_contents(b"[e2e::FileCreateTransaction]") - .execute(env.client) + FileCreateTransaction().set_keys(operator_key).set_contents(b"[e2e::FileCreateTransaction]").execute(env.client) + ) + + assert create_receipt.status == ResponseCode.SUCCESS, ( + f"Create file failed with status: {ResponseCode(create_receipt.status).name}" ) - - assert create_receipt.status == ResponseCode.SUCCESS, f"Create file failed with status: {ResponseCode(create_receipt.status).name}" assert create_receipt.file_id is not None assert create_receipt.file_id.file > 0 file_id = create_receipt.file_id # Append large content that will be chunked - big_contents_bytes = BIG_CONTENTS.encode('utf-8') - append_receipt = ( - FileAppendTransaction() - .set_file_id(file_id) - .set_contents(big_contents_bytes) - .execute(env.client) + big_contents_bytes = BIG_CONTENTS.encode("utf-8") + append_receipt = FileAppendTransaction().set_file_id(file_id).set_contents(big_contents_bytes).execute(env.client) + + assert append_receipt.status == ResponseCode.SUCCESS, ( + f"Append large file failed with status: {ResponseCode(append_receipt.status).name}" ) - - assert append_receipt.status == ResponseCode.SUCCESS, f"Append large file failed with status: {ResponseCode(append_receipt.status).name}" -@mark.integration + +@mark.integration def test_integration_file_append_transaction_no_contents(env): """Test that FileAppendTransaction does not error with no contents appended.""" operator_key = env.operator_key.public_key() # Create a file first create_receipt = ( - FileCreateTransaction() - .set_keys(operator_key) - .set_contents(b"[e2e::FileCreateTransaction]") - .execute(env.client) + FileCreateTransaction().set_keys(operator_key).set_contents(b"[e2e::FileCreateTransaction]").execute(env.client) + ) + + assert create_receipt.status == ResponseCode.SUCCESS, ( + f"Create file failed with status: {ResponseCode(create_receipt.status).name}" ) - - assert create_receipt.status == ResponseCode.SUCCESS, f"Create file failed with status: {ResponseCode(create_receipt.status).name}" assert create_receipt.file_id is not None file_id = create_receipt.file_id # Append with no contents - this should not error - append_receipt = ( - FileAppendTransaction() - .set_file_id(file_id) - .execute(env.client) + append_receipt = FileAppendTransaction().set_file_id(file_id).execute(env.client) + + assert append_receipt.status == ResponseCode.SUCCESS, ( + f"Append with no contents failed with status: {ResponseCode(append_receipt.status).name}" ) - - assert append_receipt.status == ResponseCode.SUCCESS, f"Append with no contents failed with status: {ResponseCode(append_receipt.status).name}" + @mark.integration def test_integration_file_append_transaction_one_chunk(env): @@ -115,14 +110,11 @@ def test_integration_file_append_transaction_one_chunk(env): operator_key = env.operator_key.public_key() # Create a file first - create_receipt = ( - FileCreateTransaction() - .set_keys(operator_key) - .set_contents(b"") - .execute(env.client) + create_receipt = FileCreateTransaction().set_keys(operator_key).set_contents(b"").execute(env.client) + + assert create_receipt.status == ResponseCode.SUCCESS, ( + f"Create file failed with status: {ResponseCode(create_receipt.status).name}" ) - - assert create_receipt.status == ResponseCode.SUCCESS, f"Create file failed with status: {ResponseCode(create_receipt.status).name}" assert create_receipt.file_id is not None assert create_receipt.file_id.file > 0 @@ -137,8 +129,11 @@ def test_integration_file_append_transaction_one_chunk(env): .set_chunk_size(CHUNK_SIZE) .execute(env.client) ) - - assert append_receipt.status == ResponseCode.SUCCESS, f"Append one chunk failed with status: {ResponseCode(append_receipt.status).name}" + + assert append_receipt.status == ResponseCode.SUCCESS, ( + f"Append one chunk failed with status: {ResponseCode(append_receipt.status).name}" + ) + @mark.integration def test_integration_file_append_transaction_multiple_chunks(env): @@ -146,34 +141,34 @@ def test_integration_file_append_transaction_multiple_chunks(env): operator_key = env.operator_key.public_key() # Create a file first - create_receipt = ( - FileCreateTransaction() - .set_keys(operator_key) - .set_contents(b"") - .execute(env.client) + create_receipt = FileCreateTransaction().set_keys(operator_key).set_contents(b"").execute(env.client) + + assert create_receipt.status == ResponseCode.SUCCESS, ( + f"Create file failed with status: {ResponseCode(create_receipt.status).name}" ) - - assert create_receipt.status == ResponseCode.SUCCESS, f"Create file failed with status: {ResponseCode(create_receipt.status).name}" assert create_receipt.file_id is not None file_id = create_receipt.file_id # Create content larger than default chunk size to force multiple chunks large_content = generate_uint8_array(10000) # 10KB content - + file_append_tx = ( FileAppendTransaction() .set_file_id(file_id) .set_contents(large_content) .set_chunk_size(1024) # 1KB chunks = should require ~10 chunks ) - + # Verify it calculates the correct number of chunks required_chunks = file_append_tx.get_required_chunks() assert required_chunks > 1, f"Expected multiple chunks, got {required_chunks}" - + append_receipt = file_append_tx.execute(env.client) - assert append_receipt.status == ResponseCode.SUCCESS, f"Append multiple chunks failed with status: {ResponseCode(append_receipt.status).name}" + assert append_receipt.status == ResponseCode.SUCCESS, ( + f"Append multiple chunks failed with status: {ResponseCode(append_receipt.status).name}" + ) + @mark.integration def test_integration_file_append_transaction_custom_chunk_settings(env): @@ -181,37 +176,37 @@ def test_integration_file_append_transaction_custom_chunk_settings(env): operator_key = env.operator_key.public_key() # Create a file first - create_receipt = ( - FileCreateTransaction() - .set_keys(operator_key) - .set_contents(b"") - .execute(env.client) + create_receipt = FileCreateTransaction().set_keys(operator_key).set_contents(b"").execute(env.client) + + assert create_receipt.status == ResponseCode.SUCCESS, ( + f"Create file failed with status: {ResponseCode(create_receipt.status).name}" ) - - assert create_receipt.status == ResponseCode.SUCCESS, f"Create file failed with status: {ResponseCode(create_receipt.status).name}" file_id = create_receipt.file_id # Test with custom chunk settings test_content = generate_uint8_array(5000) # 5KB content - + append_tx = ( FileAppendTransaction() - .set_file_id(file_id) + .set_file_id(file_id) .set_contents(test_content) .set_chunk_size(1000) # 1KB chunks .set_max_chunks(10) # Max 10 chunks ) - + # Verify settings are applied assert append_tx.chunk_size == 1000 assert append_tx.max_chunks == 10 - + # Should require 5 chunks (5000 bytes / 1000 bytes per chunk) required_chunks = append_tx.get_required_chunks() assert required_chunks == 5, f"Expected 5 chunks, got {required_chunks}" - + append_receipt = append_tx.execute(env.client) - assert append_receipt.status == ResponseCode.SUCCESS, f"Append with custom settings failed with status: {ResponseCode(append_receipt.status).name}" + assert append_receipt.status == ResponseCode.SUCCESS, ( + f"Append with custom settings failed with status: {ResponseCode(append_receipt.status).name}" + ) + @mark.integration def test_integration_file_append_transaction_max_chunks_exceeded(env): @@ -219,57 +214,46 @@ def test_integration_file_append_transaction_max_chunks_exceeded(env): operator_key = env.operator_key.public_key() # Create a file first - create_receipt = ( - FileCreateTransaction() - .set_keys(operator_key) - .set_contents(b"") - .execute(env.client) - ) - + create_receipt = FileCreateTransaction().set_keys(operator_key).set_contents(b"").execute(env.client) + assert create_receipt.status == ResponseCode.SUCCESS file_id = create_receipt.file_id # Create content that would require more than max allowed chunks large_content = generate_uint8_array(10000) # 10KB content - + append_tx = ( FileAppendTransaction() .set_file_id(file_id) .set_contents(large_content) .set_chunk_size(100) # Small chunks = 100 chunks needed - .set_max_chunks(5) # But only allow 5 chunks + .set_max_chunks(5) # But only allow 5 chunks ) # Should fail with max chunks exceeded with pytest.raises(ValueError, match="more than.*chunks"): append_tx.execute(env.client) + @mark.integration def test_integration_file_append_transaction_string_contents(env): """Test file append with string contents (automatic UTF-8 encoding).""" operator_key = env.operator_key.public_key() # Create a file first - create_receipt = ( - FileCreateTransaction() - .set_keys(operator_key) - .set_contents(b"Initial content") - .execute(env.client) - ) - + create_receipt = FileCreateTransaction().set_keys(operator_key).set_contents(b"Initial content").execute(env.client) + assert create_receipt.status == ResponseCode.SUCCESS file_id = create_receipt.file_id # Append string content (should be automatically encoded to UTF-8) string_content = "This is a string that should be encoded as UTF-8 bytes 🌟" - append_receipt = ( - FileAppendTransaction() - .set_file_id(file_id) - .set_contents(string_content) - .execute(env.client) + append_receipt = FileAppendTransaction().set_file_id(file_id).set_contents(string_content).execute(env.client) + + assert append_receipt.status == ResponseCode.SUCCESS, ( + f"Append string content failed with status: {ResponseCode(append_receipt.status).name}" ) - - assert append_receipt.status == ResponseCode.SUCCESS, f"Append string content failed with status: {ResponseCode(append_receipt.status).name}" + @mark.integration def test_integration_file_append_transaction_method_chaining(env): @@ -277,13 +261,8 @@ def test_integration_file_append_transaction_method_chaining(env): operator_key = env.operator_key.public_key() # Create a file first - create_receipt = ( - FileCreateTransaction() - .set_keys(operator_key) - .set_contents(b"") - .execute(env.client) - ) - + create_receipt = FileCreateTransaction().set_keys(operator_key).set_contents(b"").execute(env.client) + assert create_receipt.status == ResponseCode.SUCCESS file_id = create_receipt.file_id @@ -295,12 +274,12 @@ def test_integration_file_append_transaction_method_chaining(env): .set_chunk_size(2048) .set_max_chunks(15) ) - + # Verify all properties were set correctly assert append_tx.file_id == file_id assert append_tx.contents == b"Method chaining test" assert append_tx.chunk_size == 2048 assert append_tx.max_chunks == 15 - + append_receipt = append_tx.execute(env.client) - assert append_receipt.status == ResponseCode.SUCCESS + assert append_receipt.status == ResponseCode.SUCCESS diff --git a/tests/integration/file_contents_query_e2e_test.py b/tests/integration/file_contents_query_e2e_test.py index cb0e0ebe8..77006bfc6 100644 --- a/tests/integration/file_contents_query_e2e_test.py +++ b/tests/integration/file_contents_query_e2e_test.py @@ -2,6 +2,8 @@ Integration tests for FileContentsQuery. """ +from __future__ import annotations + import pytest from hiero_sdk_python.exceptions import PrecheckError @@ -9,7 +11,7 @@ from hiero_sdk_python.file.file_create_transaction import FileCreateTransaction from hiero_sdk_python.file.file_id import FileId from hiero_sdk_python.hbar import Hbar -from tests.integration.utils import env + FILE_CONTENT = b"Hello, World" @@ -89,9 +91,7 @@ def test_integration_file_contents_query_insufficient_payment(env): file_contents = FileContentsQuery().set_file_id(file_id) file_contents.set_query_payment(Hbar.from_tinybars(1)) # Set very low query payment - with pytest.raises( - PrecheckError, match="failed precheck with status: INSUFFICIENT_TX_FEE" - ): + with pytest.raises(PrecheckError, match="failed precheck with status: INSUFFICIENT_TX_FEE"): file_contents.execute(env.client) @@ -101,7 +101,5 @@ def test_integration_file_contents_query_fails_with_invalid_file_id(env): # Create a file ID that doesn't exist on the network file_id = FileId(0, 0, 999999999) - with pytest.raises( - PrecheckError, match="failed precheck with status: INVALID_FILE_ID" - ): + with pytest.raises(PrecheckError, match="failed precheck with status: INVALID_FILE_ID"): FileContentsQuery(file_id).execute(env.client) diff --git a/tests/integration/file_create_transaction_e2e_test.py b/tests/integration/file_create_transaction_e2e_test.py index ae345cf28..1be9dd195 100644 --- a/tests/integration/file_create_transaction_e2e_test.py +++ b/tests/integration/file_create_transaction_e2e_test.py @@ -1,11 +1,13 @@ +from __future__ import annotations + import time -import pytest + from pytest import mark from hiero_sdk_python.file.file_create_transaction import FileCreateTransaction from hiero_sdk_python.response_code import ResponseCode from hiero_sdk_python.timestamp import Timestamp -from tests.integration.utils import env, IntegrationTestEnv + @mark.integration def test_integration_file_create_transaction_can_execute(env): @@ -16,26 +18,29 @@ def test_integration_file_create_transaction_can_execute(env): .set_file_memo("Test the memo of the file") .execute(env.client) ) - assert receipt.status == ResponseCode.SUCCESS, f"Create file failed with status: {ResponseCode(receipt.status).name}" - + assert receipt.status == ResponseCode.SUCCESS, ( + f"Create file failed with status: {ResponseCode(receipt.status).name}" + ) + file_id = receipt.file_id assert file_id is not None, "File ID is None" -@mark.integration + +@mark.integration def test_integration_file_create_transaction_no_key(env): - receipt = ( - FileCreateTransaction() - .execute(env.client) + receipt = FileCreateTransaction().execute(env.client) + assert receipt.status == ResponseCode.SUCCESS, ( + f"Create file failed with status: {ResponseCode(receipt.status).name}" ) - assert receipt.status == ResponseCode.SUCCESS, f"Create file failed with status: {ResponseCode(receipt.status).name}" - + file_id = receipt.file_id assert file_id is not None, "File ID is None" + @mark.integration def test_integration_file_create_transaction_too_large_expiration_fails(env): timestamp = Timestamp(int(time.time()) + 9999999999, 0) - + receipt = ( FileCreateTransaction() .set_keys(env.operator_key.public_key()) @@ -43,5 +48,6 @@ def test_integration_file_create_transaction_too_large_expiration_fails(env): .set_expiration_time(timestamp) .execute(env.client) ) - assert receipt.status == ResponseCode.AUTORENEW_DURATION_NOT_IN_RANGE, \ - f"FileCreateTransaction should have failed with AUTORENEW_DURATION_NOT_IN_RANGE but got: {ResponseCode(receipt.status).name}" \ No newline at end of file + assert receipt.status == ResponseCode.AUTORENEW_DURATION_NOT_IN_RANGE, ( + f"FileCreateTransaction should have failed with AUTORENEW_DURATION_NOT_IN_RANGE but got: {ResponseCode(receipt.status).name}" + ) diff --git a/tests/integration/file_delete_transaction_e2e_test.py b/tests/integration/file_delete_transaction_e2e_test.py index 5907a0b5d..3bf9aadd6 100644 --- a/tests/integration/file_delete_transaction_e2e_test.py +++ b/tests/integration/file_delete_transaction_e2e_test.py @@ -2,6 +2,8 @@ Integration tests for FileDeleteTransaction. """ +from __future__ import annotations + import pytest from hiero_sdk_python.crypto.private_key import PrivateKey @@ -10,7 +12,6 @@ from hiero_sdk_python.file.file_id import FileId from hiero_sdk_python.file.file_info_query import FileInfoQuery from hiero_sdk_python.response_code import ResponseCode -from tests.integration.utils import env @pytest.mark.integration @@ -24,18 +25,18 @@ def test_integration_file_delete_transaction_can_execute(env): .set_file_memo("go sdk e2e tests") .execute(env.client) ) - assert ( - receipt.status == ResponseCode.SUCCESS - ), f"Create file failed with status: {ResponseCode(receipt.status).name}" + assert receipt.status == ResponseCode.SUCCESS, ( + f"Create file failed with status: {ResponseCode(receipt.status).name}" + ) file_id = receipt.file_id assert file_id is not None, "File ID is None" # Then delete the file receipt = FileDeleteTransaction().set_file_id(file_id).execute(env.client) - assert ( - receipt.status == ResponseCode.SUCCESS - ), f"Delete file failed with status: {ResponseCode(receipt.status).name}" + assert receipt.status == ResponseCode.SUCCESS, ( + f"Delete file failed with status: {ResponseCode(receipt.status).name}" + ) # Query the file info info = FileInfoQuery().set_file_id(file_id).execute(env.client) @@ -64,8 +65,7 @@ def test_integration_file_delete_transaction_fails_when_deleted_twice(env): # Try to delete again receipt = FileDeleteTransaction().set_file_id(file_id).execute(env.client) assert receipt.status == ResponseCode.FILE_DELETED, ( - f"File deletion should have failed with FILE_DELETED status but got: " - f"{ResponseCode(receipt.status).name}" + f"File deletion should have failed with FILE_DELETED status but got: {ResponseCode(receipt.status).name}" ) @@ -77,8 +77,7 @@ def test_integration_file_delete_transaction_fails_when_file_does_not_exist(env) receipt = FileDeleteTransaction().set_file_id(file_id).execute(env.client) assert receipt.status == ResponseCode.INVALID_FILE_ID, ( - f"File deletion should have failed with INVALID_FILE_ID status but got: " - f"{ResponseCode(receipt.status).name}" + f"File deletion should have failed with INVALID_FILE_ID status but got: {ResponseCode(receipt.status).name}" ) @@ -103,6 +102,5 @@ def test_integration_file_delete_transaction_fails_when_key_is_invalid(env): # Try to delete the file without the required key signature receipt = FileDeleteTransaction().set_file_id(file_id).execute(env.client) assert receipt.status == ResponseCode.INVALID_SIGNATURE, ( - f"File deletion should have failed with INVALID_SIGNATURE status but got: " - f"{ResponseCode(receipt.status).name}" + f"File deletion should have failed with INVALID_SIGNATURE status but got: {ResponseCode(receipt.status).name}" ) diff --git a/tests/integration/file_info_query_e2e_test.py b/tests/integration/file_info_query_e2e_test.py index 6c92891df..021b37d79 100644 --- a/tests/integration/file_info_query_e2e_test.py +++ b/tests/integration/file_info_query_e2e_test.py @@ -2,6 +2,8 @@ Integration tests for FileInfoQuery. """ +from __future__ import annotations + import pytest from hiero_sdk_python.crypto.private_key import PrivateKey @@ -10,7 +12,7 @@ from hiero_sdk_python.file.file_id import FileId from hiero_sdk_python.file.file_info_query import FileInfoQuery from hiero_sdk_python.hbar import Hbar -from tests.integration.utils import env + FILE_CONTENT = b"Hello, World" FILE_MEMO = "python sdk e2e tests" @@ -99,9 +101,7 @@ def test_integration_file_info_query_multiple_keys(env): # Verify each key is present key_bytes = [key.to_bytes_raw() for key in info.keys] - assert ( - env.operator_key.public_key().to_bytes_raw() in key_bytes - ), "Operator key not found" + assert env.operator_key.public_key().to_bytes_raw() in key_bytes, "Operator key not found" assert key1.public_key().to_bytes_raw() in key_bytes, "Key1 not found" assert key2.public_key().to_bytes_raw() in key_bytes, "Key2 not found" @@ -118,9 +118,7 @@ def test_integration_file_info_query_insufficient_payment(env): file_info = FileInfoQuery().set_file_id(file_id) file_info.set_query_payment(Hbar.from_tinybars(1)) # Set very low query payment - with pytest.raises( - PrecheckError, match="failed precheck with status: INSUFFICIENT_TX_FEE" - ): + with pytest.raises(PrecheckError, match="failed precheck with status: INSUFFICIENT_TX_FEE"): file_info.execute(env.client) @@ -130,7 +128,5 @@ def test_integration_file_info_query_fails_with_invalid_file_id(env): # Create a file ID that doesn't exist on the network file_id = FileId(0, 0, 999999999) - with pytest.raises( - PrecheckError, match="failed precheck with status: INVALID_FILE_ID" - ): + with pytest.raises(PrecheckError, match="failed precheck with status: INVALID_FILE_ID"): FileInfoQuery(file_id).execute(env.client) diff --git a/tests/integration/file_update_transaction_e2e_test.py b/tests/integration/file_update_transaction_e2e_test.py index 27ea491b4..8885a7426 100644 --- a/tests/integration/file_update_transaction_e2e_test.py +++ b/tests/integration/file_update_transaction_e2e_test.py @@ -2,6 +2,8 @@ Integration tests for the FileUpdateTransaction class. """ +from __future__ import annotations + import pytest from hiero_sdk_python import PrivateKey @@ -10,7 +12,6 @@ from hiero_sdk_python.file.file_info_query import FileInfoQuery from hiero_sdk_python.file.file_update_transaction import FileUpdateTransaction from hiero_sdk_python.response_code import ResponseCode -from tests.integration.utils import env @pytest.mark.integration @@ -27,9 +28,9 @@ def test_integration_file_update_transaction_can_execute(env): .sign(file_private_key) .execute(env.client) ) - assert ( - receipt.status == ResponseCode.SUCCESS - ), f"File creation failed with status: {ResponseCode(receipt.status).name}" + assert receipt.status == ResponseCode.SUCCESS, ( + f"File creation failed with status: {ResponseCode(receipt.status).name}" + ) file_id = receipt.file_id assert file_id is not None, "File ID should not be None" @@ -51,9 +52,9 @@ def test_integration_file_update_transaction_can_execute(env): .sign(file_private_key) .execute(env.client) ) - assert ( - receipt.status == ResponseCode.SUCCESS - ), f"File update failed with status: {ResponseCode(receipt.status).name}" + assert receipt.status == ResponseCode.SUCCESS, ( + f"File update failed with status: {ResponseCode(receipt.status).name}" + ) # Query file info and check if everything is updated info = FileInfoQuery().set_file_id(file_id).execute(env.client) @@ -74,8 +75,7 @@ def test_integration_file_update_transaction_fails_with_invalid_file_id(env): receipt = FileUpdateTransaction().set_file_id(file_id).execute(env.client) assert receipt.status == ResponseCode.INVALID_FILE_ID, ( - f"File update should have failed with INVALID_FILE_ID status but got: " - f"{ResponseCode(receipt.status).name}" + f"File update should have failed with INVALID_FILE_ID status but got: {ResponseCode(receipt.status).name}" ) @@ -83,9 +83,9 @@ def test_integration_file_update_transaction_fails_with_invalid_file_id(env): def test_integration_file_update_transaction_cannot_update_immutable_file(env): """Test that the FileUpdateTransaction fails when updating an immutable file.""" receipt = FileCreateTransaction().set_contents("Immutable file").execute(env.client) - assert ( - receipt.status == ResponseCode.SUCCESS - ), f"File creation failed with status: {ResponseCode(receipt.status).name}" + assert receipt.status == ResponseCode.SUCCESS, ( + f"File creation failed with status: {ResponseCode(receipt.status).name}" + ) file_id = receipt.file_id assert file_id is not None, "File ID should not be None" @@ -101,8 +101,7 @@ def test_integration_file_update_transaction_cannot_update_immutable_file(env): .execute(env.client) ) assert receipt.status == ResponseCode.UNAUTHORIZED, ( - f"File update should have failed with UNAUTHORIZED status but got: " - f"{ResponseCode(receipt.status).name}" + f"File update should have failed with UNAUTHORIZED status but got: {ResponseCode(receipt.status).name}" ) @@ -119,21 +118,15 @@ def test_integration_file_update_transaction_fails_when_key_is_invalid(env): .sign(file_private_key) .execute(env.client) ) - assert ( - receipt.status == ResponseCode.SUCCESS - ), f"File creation failed with status: {ResponseCode(receipt.status).name}" + assert receipt.status == ResponseCode.SUCCESS, ( + f"File creation failed with status: {ResponseCode(receipt.status).name}" + ) file_id = receipt.file_id assert file_id is not None, "File ID should not be None" # Update file contents - receipt = ( - FileUpdateTransaction() - .set_file_id(file_id) - .set_contents("Update contents!") - .execute(env.client) - ) + receipt = FileUpdateTransaction().set_file_id(file_id).set_contents("Update contents!").execute(env.client) assert receipt.status == ResponseCode.INVALID_SIGNATURE, ( - f"File update should have failed with INVALID_SIGNATURE status but got: " - f"{ResponseCode(receipt.status).name}" + f"File update should have failed with INVALID_SIGNATURE status but got: {ResponseCode(receipt.status).name}" ) diff --git a/tests/integration/node_create_transaction_e2e_test.py b/tests/integration/node_create_transaction_e2e_test.py index 0c077951d..e326e170f 100644 --- a/tests/integration/node_create_transaction_e2e_test.py +++ b/tests/integration/node_create_transaction_e2e_test.py @@ -2,6 +2,8 @@ Integration tests for NodeCreateTransaction. """ +from __future__ import annotations + import pytest from hiero_sdk_python.account.account_id import AccountId @@ -12,6 +14,7 @@ from hiero_sdk_python.nodes.node_create_transaction import NodeCreateTransaction from hiero_sdk_python.response_code import ResponseCode + # Gossip certificate is a DER-encoded x509 certificate used for secure communication between nodes. # This certificate authenticates the node's identity during gossip protocol communication. # Information about x509 certificates: https://www.ssl.com/faqs/what-is-an-x-509-certificate/ @@ -34,8 +37,7 @@ def test_node_create_transaction_can_execute(): # The private key is intentionally public for local development. # Note: This setup only works on solo network and will not work on testnet/mainnet. original_operator_key = PrivateKey.from_string_der( - "302e020100300506032b65700422042091132178e7" - "2057a1d7528025956fe39b0b847f200ab59b2fdd367017f3087137" + "302e020100300506032b65700422042091132178e72057a1d7528025956fe39b0b847f200ab59b2fdd367017f3087137" ) client.set_operator(AccountId(0, 0, 2), original_operator_key) @@ -70,8 +72,6 @@ def test_node_create_transaction_can_execute(): .execute(client) ) - assert ( - receipt.status == ResponseCode.SUCCESS - ), f"Node create failed with status {ResponseCode(receipt.status).name}" + assert receipt.status == ResponseCode.SUCCESS, f"Node create failed with status {ResponseCode(receipt.status).name}" assert receipt.node_id is not None, "Node ID should not be None" diff --git a/tests/integration/node_delete_transaction_e2e_test.py b/tests/integration/node_delete_transaction_e2e_test.py index 058cf493a..ef6ed3082 100644 --- a/tests/integration/node_delete_transaction_e2e_test.py +++ b/tests/integration/node_delete_transaction_e2e_test.py @@ -2,6 +2,8 @@ Integration tests for NodeDeleteTransaction. """ +from __future__ import annotations + import pytest from hiero_sdk_python.account.account_id import AccountId @@ -13,6 +15,7 @@ from hiero_sdk_python.nodes.node_delete_transaction import NodeDeleteTransaction from hiero_sdk_python.response_code import ResponseCode + # Gossip certificate is a DER-encoded x509 certificate used for secure communication between nodes. # This certificate authenticates the node's identity during gossip protocol communication. # Information about x509 certificates: https://www.ssl.com/faqs/what-is-an-x-509-certificate/ @@ -32,8 +35,7 @@ def test_node_delete_transaction_can_execute(): # Account 0.0.2 is a special admin account with privileges for network management operations. original_operator_key = PrivateKey.from_string_der( - "302e020100300506032b65700422042091132178e7" - "2057a1d7528025956fe39b0b847f200ab59b2fdd367017f3087137" + "302e020100300506032b65700422042091132178e72057a1d7528025956fe39b0b847f200ab59b2fdd367017f3087137" ) client.set_operator(AccountId(0, 0, 2), original_operator_key) @@ -44,9 +46,7 @@ def test_node_delete_transaction_can_execute(): endpoint1 = Endpoint(domain_name="test.com", port=1234) grpc_proxy_endpoint = Endpoint(domain_name="testWeb.com", port=12345) - valid_gossip_cert = bytes.fromhex( - GOSSIP_CERTIFICATE - ) # DER encoded x509 certificate + valid_gossip_cert = bytes.fromhex(GOSSIP_CERTIFICATE) # DER encoded x509 certificate admin_key = PrivateKey.generate_ed25519() @@ -65,20 +65,12 @@ def test_node_delete_transaction_can_execute(): .sign(admin_key) .execute(client) ) - assert ( - receipt.status == ResponseCode.SUCCESS - ), f"Node create failed with status {ResponseCode(receipt.status).name}" + assert receipt.status == ResponseCode.SUCCESS, f"Node create failed with status {ResponseCode(receipt.status).name}" assert receipt.node_id is not None, "Node ID should not be None" - receipt = ( - NodeDeleteTransaction() - .set_node_id(receipt.node_id) - .execute(client) - ) + receipt = NodeDeleteTransaction().set_node_id(receipt.node_id).execute(client) - assert ( - receipt.status == ResponseCode.SUCCESS - ), f"Node delete failed with status {ResponseCode(receipt.status).name}" + assert receipt.status == ResponseCode.SUCCESS, f"Node delete failed with status {ResponseCode(receipt.status).name}" assert receipt.node_id is not None, "Node ID should not be None" diff --git a/tests/integration/node_update_transaction_e2e_test.py b/tests/integration/node_update_transaction_e2e_test.py index 257d8fd8e..0b930a5cc 100644 --- a/tests/integration/node_update_transaction_e2e_test.py +++ b/tests/integration/node_update_transaction_e2e_test.py @@ -2,6 +2,8 @@ Integration tests for NodeCreateTransaction. """ +from __future__ import annotations + import pytest from hiero_sdk_python.account.account_id import AccountId @@ -13,6 +15,7 @@ from hiero_sdk_python.nodes.node_update_transaction import NodeUpdateTransaction from hiero_sdk_python.response_code import ResponseCode + # Gossip certificate is a DER-encoded x509 certificate used for secure communication between nodes. # This certificate authenticates the node's identity during gossip protocol communication. # Information about x509 certificates: https://www.ssl.com/faqs/what-is-an-x-509-certificate/ @@ -32,8 +35,7 @@ def test_node_update_transaction_can_execute(): # Account 0.0.2 is a special admin account with privileges for network management operations. original_operator_key = PrivateKey.from_string_der( - "302e020100300506032b65700422042091132178e7" - "2057a1d7528025956fe39b0b847f200ab59b2fdd367017f3087137" + "302e020100300506032b65700422042091132178e72057a1d7528025956fe39b0b847f200ab59b2fdd367017f3087137" ) client.set_operator(AccountId(0, 0, 2), original_operator_key) @@ -62,9 +64,7 @@ def test_node_update_transaction_can_execute(): .sign(admin_key) .execute(client) ) - assert ( - receipt.status == ResponseCode.SUCCESS - ), f"Node create failed with status {ResponseCode(receipt.status).name}" + assert receipt.status == ResponseCode.SUCCESS, f"Node create failed with status {ResponseCode(receipt.status).name}" assert receipt.node_id is not None, "Node ID should not be None" @@ -82,8 +82,6 @@ def test_node_update_transaction_can_execute(): .execute(client) ) - assert ( - receipt.status == ResponseCode.SUCCESS - ), f"Node update failed with status {ResponseCode(receipt.status).name}" + assert receipt.status == ResponseCode.SUCCESS, f"Node update failed with status {ResponseCode(receipt.status).name}" assert receipt.node_id is not None, "Node ID should not be None" diff --git a/tests/integration/prng_transaction_e2e_test.py b/tests/integration/prng_transaction_e2e_test.py index ae9df7d83..88e796645 100644 --- a/tests/integration/prng_transaction_e2e_test.py +++ b/tests/integration/prng_transaction_e2e_test.py @@ -2,28 +2,28 @@ Integration tests for PRNG transaction. """ +from __future__ import annotations + import pytest from hiero_sdk_python.prng_transaction import PrngTransaction from hiero_sdk_python.query.transaction_record_query import TransactionRecordQuery from hiero_sdk_python.response_code import ResponseCode -from tests.integration.utils import env @pytest.mark.integration def test_integration_prng_transaction_can_execute(env): """Test that the PRNG transaction can be executed successfully.""" receipt = PrngTransaction().set_range(100).execute(env.client) - assert ( - receipt.status == ResponseCode.SUCCESS - ), f"Prng transaction failed with status: {ResponseCode(receipt.status).name}" + assert receipt.status == ResponseCode.SUCCESS, ( + f"Prng transaction failed with status: {ResponseCode(receipt.status).name}" + ) record = TransactionRecordQuery(receipt.transaction_id).execute(env.client) assert record.prng_number is not None, "PRNG number should not be None" - assert ( - record.prng_number >= 0 and record.prng_number <= 100 - ), "PRNG number should be between 0 and 100" + + assert record.prng_number >= 0 and record.prng_number <= 100, "PRNG number should be between 0 and 100" assert record.prng_bytes is None, "PRNG bytes should be None" @@ -31,9 +31,9 @@ def test_integration_prng_transaction_can_execute(env): def test_integration_prng_transaction_can_execute_without_range(env): """Test that the PRNG transaction can be executed successfully without a range.""" receipt = PrngTransaction().execute(env.client) - assert ( - receipt.status == ResponseCode.SUCCESS - ), f"Prng transaction failed with status: {ResponseCode(receipt.status).name}" + assert receipt.status == ResponseCode.SUCCESS, ( + f"Prng transaction failed with status: {ResponseCode(receipt.status).name}" + ) record = TransactionRecordQuery(receipt.transaction_id).execute(env.client) @@ -41,16 +41,17 @@ def test_integration_prng_transaction_can_execute_without_range(env): assert record.prng_bytes is not None, "PRNG bytes should not be None" assert len(record.prng_bytes) == 48, "PRNG bytes should be 48 bytes" + @pytest.mark.integration def test_integration_prng_transaction_can_execute_with_zero_range(env): """Test that the PRNG transaction can be executed successfully with a zero range.""" receipt = PrngTransaction().set_range(0).execute(env.client) - assert ( - receipt.status == ResponseCode.SUCCESS - ), f"Prng transaction failed with status: {ResponseCode(receipt.status).name}" + assert receipt.status == ResponseCode.SUCCESS, ( + f"Prng transaction failed with status: {ResponseCode(receipt.status).name}" + ) record = TransactionRecordQuery(receipt.transaction_id).execute(env.client) assert record.prng_number is None, "PRNG number should be None" assert record.prng_bytes is not None, "PRNG bytes should not be None" - assert len(record.prng_bytes) == 48, "PRNG bytes should be 48 bytes" \ No newline at end of file + assert len(record.prng_bytes) == 48, "PRNG bytes should be 48 bytes" diff --git a/tests/integration/query_e2e_test.py b/tests/integration/query_e2e_test.py index 5f087195c..11eedb54e 100644 --- a/tests/integration/query_e2e_test.py +++ b/tests/integration/query_e2e_test.py @@ -1,4 +1,5 @@ -import time +from __future__ import annotations + import pytest from hiero_sdk_python.account.account_create_transaction import AccountCreateTransaction @@ -9,17 +10,18 @@ from hiero_sdk_python.query.account_info_query import AccountInfoQuery from hiero_sdk_python.query.token_info_query import TokenInfoQuery from hiero_sdk_python.response_code import ResponseCode -from tests.integration.utils import IntegrationTestEnv, env, create_fungible_token +from tests.integration.utils import IntegrationTestEnv, create_fungible_token + @pytest.mark.integration def test_integration_free_query_no_cost(): """Test that free queries don't cost anything and don't deduct from account balance.""" env = IntegrationTestEnv() - + try: new_private_key = PrivateKey.generate_ed25519() new_account_public_key = new_private_key.public_key() - + initial_balance = Hbar(1) receipt = ( AccountCreateTransaction() @@ -30,46 +32,39 @@ def test_integration_free_query_no_cost(): assert receipt.status == ResponseCode.SUCCESS account_id = receipt.account_id assert account_id is not None - + env.client.set_operator(account_id, new_private_key) - + # Test free query (account balance query) query = CryptoGetAccountBalanceQuery(account_id) - + # Cost should be 0 for free queries cost = query.get_cost(env.client) assert cost.to_tinybars() == 0 - + # Execute query and verify balance wasn't deducted - balance_before = ( - CryptoGetAccountBalanceQuery() - .set_account_id(account_id) - .execute(env.client) - ) - + balance_before = CryptoGetAccountBalanceQuery().set_account_id(account_id).execute(env.client) + balance_result = query.execute(env.client) assert balance_result.hbars.to_tinybars() == initial_balance.to_tinybars() - - balance_after = ( - CryptoGetAccountBalanceQuery() - .set_account_id(account_id) - .execute(env.client) - ) - + + balance_after = CryptoGetAccountBalanceQuery().set_account_id(account_id).execute(env.client) + # Balance should remain unchanged after free query assert balance_before.hbars.to_tinybars() == balance_after.hbars.to_tinybars() finally: env.close() + @pytest.mark.integration def test_integration_free_query_with_manual_payment(): """Test that manually setting payment on free queries still results in no cost.""" env = IntegrationTestEnv() - + try: new_private_key = PrivateKey.generate_ed25519() new_account_public_key = new_private_key.public_key() - + initial_balance = Hbar(1) receipt = ( AccountCreateTransaction() @@ -77,57 +72,48 @@ def test_integration_free_query_with_manual_payment(): .set_initial_balance(initial_balance) .execute(env.client) ) - + assert receipt.status == ResponseCode.SUCCESS account_id = receipt.account_id assert account_id is not None - + env.client.set_operator(account_id, new_private_key) - + # Test free query with manual payment set query = ( - CryptoGetAccountBalanceQuery() - .set_account_id(account_id) - .set_query_payment(Hbar(2)) # Set a high payment + CryptoGetAccountBalanceQuery().set_account_id(account_id).set_query_payment(Hbar(2)) # Set a high payment ) - + # Cost should still be 0 for free queries even with manual payment cost = query.get_cost(env.client) assert cost.to_tinybars() == 0 - + # Execute and verify no balance deduction - balance_before = ( - CryptoGetAccountBalanceQuery() - .set_account_id(account_id) - .execute(env.client) - ) - + balance_before = CryptoGetAccountBalanceQuery().set_account_id(account_id).execute(env.client) + balance_result = query.execute(env.client) assert balance_result.hbars.to_tinybars() == initial_balance.to_tinybars() - - balance_after = ( - CryptoGetAccountBalanceQuery() - .set_account_id(account_id) - .execute(env.client) - ) - + + balance_after = CryptoGetAccountBalanceQuery().set_account_id(account_id).execute(env.client) + # Balance should remain unchanged assert balance_before.hbars.to_tinybars() == balance_after.hbars.to_tinybars() finally: env.close() + @pytest.mark.integration def test_integration_paid_query_network_cost(): """Test that paid queries get actual network cost and payment amount is set correctly.""" env = IntegrationTestEnv() - + try: token_id = create_fungible_token(env) assert token_id is not None - + new_private_key = PrivateKey.generate_ed25519() new_account_public_key = new_private_key.public_key() - + receipt = ( AccountCreateTransaction() .set_key_without_alias(new_account_public_key) @@ -137,124 +123,118 @@ def test_integration_paid_query_network_cost(): assert receipt.status == ResponseCode.SUCCESS account_id = receipt.account_id assert account_id is not None - + env.client.set_operator(account_id, new_private_key) - + # Test paid query (token info query) query = TokenInfoQuery(token_id) - + # Get the network cost network_cost = query.get_cost(env.client) assert network_cost.to_tinybars() > 0 - + # Execute the query query.execute(env.client) - + # Payment amount should be set to the network cost assert query.payment_amount.to_tinybars() == network_cost.to_tinybars() finally: env.close() + @pytest.mark.integration def test_integration_paid_query_manual_payment(): """Test that manually setting payment on paid queries uses the set amount.""" env = IntegrationTestEnv() - + try: token_id = create_fungible_token(env) assert token_id is not None - + new_private_key = PrivateKey.generate_ed25519() new_account_public_key = new_private_key.public_key() - + receipt = ( AccountCreateTransaction() .set_key_without_alias(new_account_public_key) .set_initial_balance(Hbar(1)) .execute(env.client) ) - + assert receipt.status == ResponseCode.SUCCESS account_id = receipt.account_id assert account_id is not None - + env.client.set_operator(account_id, new_private_key) - + # Set a custom payment amount custom_payment = Hbar.from_tinybars(5000000) # 0.05 Hbar - query = ( - TokenInfoQuery() - .set_token_id(token_id) - .set_query_payment(custom_payment) - ) - + query = TokenInfoQuery().set_token_id(token_id).set_query_payment(custom_payment) + # get_cost should return the manually set amount cost = query.get_cost(env.client) assert cost.to_tinybars() == custom_payment.to_tinybars() - + # Execute the query query.execute(env.client) assert query.payment_amount is not None - + # Payment amount should be the custom amount assert query.payment_amount.to_tinybars() == custom_payment.to_tinybars() finally: env.close() + @pytest.mark.integration def test_integration_paid_query_payment_too_high_fails(): """Test that setting payment too high on paid queries fails.""" env = IntegrationTestEnv() - + try: token_id = create_fungible_token(env) assert token_id is not None - + new_private_key = PrivateKey.generate_ed25519() new_account_public_key = new_private_key.public_key() - + receipt = ( AccountCreateTransaction() .set_key_without_alias(new_account_public_key) .set_initial_balance(Hbar(1)) .execute(env.client) ) - + assert receipt.status == ResponseCode.SUCCESS account_id = receipt.account_id assert account_id is not None - + env.client.set_operator(account_id, new_private_key) - + # Set an unreasonably high payment amount payment = Hbar(2) - query = ( - TokenInfoQuery() - .set_token_id(token_id) - .set_query_payment(payment) - ) + query = TokenInfoQuery().set_token_id(token_id).set_query_payment(payment) # Execute the query - should fail due to insufficient balance with pytest.raises(PrecheckError, match="failed precheck with status: INSUFFICIENT_PAYER_BALANCE"): query.execute(env.client) finally: env.close() + @pytest.mark.integration def test_integration_query_exceeds_max_payment(env): """Test that Query fails when cost exceeds max_query_payment.""" receipt = env.create_account(1) account_id = receipt.id - + # Set max payment below actual cost query = ( AccountInfoQuery() .set_account_id(account_id) .set_max_query_payment(Hbar.from_tinybars(1)) # Intentionally too low to fail ) - + with pytest.raises(ValueError) as e: query.execute(env.client) msg = str(e.value) assert "Query cost" in msg and "exceeds max set query payment:" in msg - \ No newline at end of file diff --git a/tests/integration/revenue_generating_topics_e2e_test.py b/tests/integration/revenue_generating_topics_e2e_test.py index f521e4cd1..1a2fdda7b 100644 --- a/tests/integration/revenue_generating_topics_e2e_test.py +++ b/tests/integration/revenue_generating_topics_e2e_test.py @@ -2,6 +2,8 @@ Integration tests for revenue generating topics. """ +from __future__ import annotations + import pytest from hiero_sdk_python.account.account_create_transaction import AccountCreateTransaction @@ -21,7 +23,8 @@ from hiero_sdk_python.tokens.token_associate_transaction import TokenAssociateTransaction from hiero_sdk_python.tokens.token_id import TokenId from hiero_sdk_python.transaction.custom_fee_limit import CustomFeeLimit -from tests.integration.utils import create_fungible_token, env +from tests.integration.utils import create_fungible_token + TOPIC_MEMO = "Python SDK revenue generating topic" MESSAGE = "test_message" @@ -49,9 +52,9 @@ def _create_topic(env, exempt_key1, exempt_key2, custom_fee1, custom_fee2): .execute(env.client) ) - assert ( - receipt.status == ResponseCode.SUCCESS - ), f"Topic creation failed with status: {ResponseCode(receipt.status).name}" + assert receipt.status == ResponseCode.SUCCESS, ( + f"Topic creation failed with status: {ResponseCode(receipt.status).name}" + ) topic_id = receipt.topic_id assert topic_id is not None @@ -132,9 +135,9 @@ def test_integration_revenue_generating_topic_can_update(env): .execute(env.client) ) - assert ( - receipt.status == ResponseCode.SUCCESS - ), f"Topic update failed with status: {ResponseCode(receipt.status).name}" + assert receipt.status == ResponseCode.SUCCESS, ( + f"Topic update failed with status: {ResponseCode(receipt.status).name}" + ) # Query topic info again to validate updates updated_topic_info = TopicInfoQuery(topic_id=topic_id).execute(env.client) @@ -209,9 +212,9 @@ def test_integration_revenue_generating_topic_cannot_update_fee_schedule_key(env # Create a revenue generating topic without fee schedule key receipt = TopicCreateTransaction().set_admin_key(env.public_operator_key).execute(env.client) - assert ( - receipt.status == ResponseCode.SUCCESS - ), f"Topic creation failed with status: {ResponseCode(receipt.status).name}" + assert receipt.status == ResponseCode.SUCCESS, ( + f"Topic creation failed with status: {ResponseCode(receipt.status).name}" + ) topic_id = receipt.topic_id assert topic_id is not None @@ -240,9 +243,9 @@ def test_integration_revenue_generating_topic_cannot_update_custom_fees(env): # Create a revenue generating topic without fee schedule key receipt = TopicCreateTransaction().set_admin_key(env.public_operator_key).execute(env.client) - assert ( - receipt.status == ResponseCode.SUCCESS - ), f"Topic creation failed with status: {ResponseCode(receipt.status).name}" + assert receipt.status == ResponseCode.SUCCESS, ( + f"Topic creation failed with status: {ResponseCode(receipt.status).name}" + ) topic_id = receipt.topic_id assert topic_id is not None @@ -265,8 +268,7 @@ def test_integration_revenue_generating_topic_cannot_update_custom_fees(env): ) assert receipt.status == ResponseCode.FEE_SCHEDULE_KEY_NOT_SET, ( - f"Topic update should have failed with FEE_SCHEDULE_KEY_NOT_SET" - f"but got {ResponseCode(receipt.status).name}" + f"Topic update should have failed with FEE_SCHEDULE_KEY_NOT_SETbut got {ResponseCode(receipt.status).name}" ) @@ -290,9 +292,9 @@ def test_integration_revenue_generating_topic_can_charge_hbars_with_limit(env): .execute(env.client) ) - assert ( - receipt.status == ResponseCode.SUCCESS - ), f"Topic creation failed with status: {ResponseCode(receipt.status).name}" + assert receipt.status == ResponseCode.SUCCESS, ( + f"Topic creation failed with status: {ResponseCode(receipt.status).name}" + ) topic_id = receipt.topic_id assert topic_id is not None @@ -319,9 +321,9 @@ def test_integration_revenue_generating_topic_can_charge_hbars_with_limit(env): message_transaction.transaction_fee = Hbar(2).to_tinybars() message_receipt = message_transaction.execute(env.client) - assert ( - message_receipt.status == ResponseCode.SUCCESS - ), f"Message submission failed with status: {ResponseCode(message_receipt.status).name}" + assert message_receipt.status == ResponseCode.SUCCESS, ( + f"Message submission failed with status: {ResponseCode(message_receipt.status).name}" + ) # Reset operator to original env.client.set_operator(env.operator_id, env.operator_key) @@ -350,9 +352,9 @@ def test_integration_revenue_generating_topic_can_charge_hbars_without_limit(env .execute(env.client) ) - assert ( - receipt.status == ResponseCode.SUCCESS - ), f"Topic creation failed with status: {ResponseCode(receipt.status).name}" + assert receipt.status == ResponseCode.SUCCESS, ( + f"Topic creation failed with status: {ResponseCode(receipt.status).name}" + ) topic_id = receipt.topic_id assert topic_id is not None @@ -362,16 +364,14 @@ def test_integration_revenue_generating_topic_can_charge_hbars_without_limit(env # Submit a message to the revenue generating topic without custom fee limit env.client.set_operator(payer_account.id, payer_account.key) - message_transaction = ( - TopicMessageSubmitTransaction().set_message(MESSAGE).set_topic_id(topic_id) - ) + message_transaction = TopicMessageSubmitTransaction().set_message(MESSAGE).set_topic_id(topic_id) message_transaction.transaction_fee = Hbar(2).to_tinybars() message_receipt = message_transaction.execute(env.client) - assert ( - message_receipt.status == ResponseCode.SUCCESS - ), f"Message submission failed with status: {ResponseCode(message_receipt.status).name}" + assert message_receipt.status == ResponseCode.SUCCESS, ( + f"Message submission failed with status: {ResponseCode(message_receipt.status).name}" + ) # Reset operator to original env.client.set_operator(env.operator_id, env.operator_key) @@ -397,9 +397,9 @@ def test_integration_revenue_generating_topic_can_charge_tokens_with_limit(env): .execute(env.client) ) - assert ( - receipt.status == ResponseCode.SUCCESS - ), f"Topic creation failed with status: {ResponseCode(receipt.status).name}" + assert receipt.status == ResponseCode.SUCCESS, ( + f"Topic creation failed with status: {ResponseCode(receipt.status).name}" + ) topic_id = receipt.topic_id assert topic_id is not None @@ -412,9 +412,7 @@ def test_integration_revenue_generating_topic_can_charge_tokens_with_limit(env): custom_fee_limit = ( CustomFeeLimit() .set_payer_id(payer_account.id) - .add_custom_fee( - CustomFixedFee().set_amount_in_tinybars(2).set_denominating_token_id(token_id) - ) + .add_custom_fee(CustomFixedFee().set_amount_in_tinybars(2).set_denominating_token_id(token_id)) ) # Set operator to payer @@ -430,17 +428,15 @@ def test_integration_revenue_generating_topic_can_charge_tokens_with_limit(env): message_transaction.transaction_fee = Hbar(2).to_tinybars() message_receipt = message_transaction.execute(env.client) - assert ( - message_receipt.status == ResponseCode.SUCCESS - ), f"Message submission failed with status: {ResponseCode(message_receipt.status).name}" + assert message_receipt.status == ResponseCode.SUCCESS, ( + f"Message submission failed with status: {ResponseCode(message_receipt.status).name}" + ) # Reset operator to original env.client.set_operator(env.operator_id, env.operator_key) # Verify the custom fee charged - account_balance = ( - CryptoGetAccountBalanceQuery().set_account_id(payer_account.id).execute(env.client) - ) + account_balance = CryptoGetAccountBalanceQuery().set_account_id(payer_account.id).execute(env.client) assert account_balance.token_balances.get(token_id) == 0 @@ -460,9 +456,9 @@ def test_integration_revenue_generating_topic_can_charge_tokens_without_limit(en .execute(env.client) ) - assert ( - receipt.status == ResponseCode.SUCCESS - ), f"Topic creation failed with status: {ResponseCode(receipt.status).name}" + assert receipt.status == ResponseCode.SUCCESS, ( + f"Topic creation failed with status: {ResponseCode(receipt.status).name}" + ) topic_id = receipt.topic_id assert topic_id is not None @@ -474,24 +470,20 @@ def test_integration_revenue_generating_topic_can_charge_tokens_without_limit(en # Set operator to payer env.client.set_operator(payer_account.id, payer_account.key) - message_transaction = ( - TopicMessageSubmitTransaction().set_message(MESSAGE).set_topic_id(topic_id) - ) + message_transaction = TopicMessageSubmitTransaction().set_message(MESSAGE).set_topic_id(topic_id) message_transaction.transaction_fee = Hbar(2).to_tinybars() message_receipt = message_transaction.execute(env.client) - assert ( - message_receipt.status == ResponseCode.SUCCESS - ), f"Message submission failed with status: {ResponseCode(message_receipt.status).name}" + assert message_receipt.status == ResponseCode.SUCCESS, ( + f"Message submission failed with status: {ResponseCode(message_receipt.status).name}" + ) # Reset operator to original env.client.set_operator(env.operator_id, env.operator_key) # Verify the custom fee charged - account_balance = ( - CryptoGetAccountBalanceQuery().set_account_id(payer_account.id).execute(env.client) - ) + account_balance = CryptoGetAccountBalanceQuery().set_account_id(payer_account.id).execute(env.client) assert account_balance.token_balances.get(token_id) == 0 @@ -518,9 +510,9 @@ def test_integration_revenue_generating_topic_does_not_charge_hbars_fee_exempt_k .execute(env.client) ) - assert ( - receipt.status == ResponseCode.SUCCESS - ), f"Topic creation failed with status: {ResponseCode(receipt.status).name}" + assert receipt.status == ResponseCode.SUCCESS, ( + f"Topic creation failed with status: {ResponseCode(receipt.status).name}" + ) topic_id = receipt.topic_id assert topic_id is not None @@ -532,25 +524,23 @@ def test_integration_revenue_generating_topic_does_not_charge_hbars_fee_exempt_k .set_initial_balance(Hbar(1)) .execute(env.client) ) - assert ( - receipt.status == ResponseCode.SUCCESS - ), f"Account creation failed with status: {ResponseCode(receipt.status).name}" + assert receipt.status == ResponseCode.SUCCESS, ( + f"Account creation failed with status: {ResponseCode(receipt.status).name}" + ) payer_account = receipt.account_id assert payer_account is not None # Submit a message to the revenue generating topic without custom fee limit env.client.set_operator(payer_account, fee_exempt_key1) - message_transaction = ( - TopicMessageSubmitTransaction().set_message(MESSAGE).set_topic_id(topic_id) - ) + message_transaction = TopicMessageSubmitTransaction().set_message(MESSAGE).set_topic_id(topic_id) message_transaction.transaction_fee = Hbar(2).to_tinybars() message_receipt = message_transaction.execute(env.client) - assert ( - message_receipt.status == ResponseCode.SUCCESS - ), f"Message submission failed with status: {ResponseCode(message_receipt.status).name}" + assert message_receipt.status == ResponseCode.SUCCESS, ( + f"Message submission failed with status: {ResponseCode(message_receipt.status).name}" + ) # Reset operator to original env.client.set_operator(env.operator_id, env.operator_key) @@ -582,9 +572,9 @@ def test_integration_revenue_generating_topic_does_not_charge_tokens_fee_exempt_ .execute(env.client) ) - assert ( - receipt.status == ResponseCode.SUCCESS - ), f"Topic creation failed with status: {ResponseCode(receipt.status).name}" + assert receipt.status == ResponseCode.SUCCESS, ( + f"Topic creation failed with status: {ResponseCode(receipt.status).name}" + ) topic_id = receipt.topic_id assert topic_id is not None @@ -595,9 +585,9 @@ def test_integration_revenue_generating_topic_does_not_charge_tokens_fee_exempt_ .set_initial_balance(Hbar(1)) .execute(env.client) ) - assert ( - receipt.status == ResponseCode.SUCCESS - ), f"Account creation failed with status: {ResponseCode(receipt.status).name}" + assert receipt.status == ResponseCode.SUCCESS, ( + f"Account creation failed with status: {ResponseCode(receipt.status).name}" + ) payer_account = receipt.account_id assert payer_account is not None @@ -605,23 +595,19 @@ def test_integration_revenue_generating_topic_does_not_charge_tokens_fee_exempt_ env.client.set_operator(payer_account, fee_exempt_key1) - message_transaction = ( - TopicMessageSubmitTransaction().set_message(MESSAGE).set_topic_id(topic_id) - ) + message_transaction = TopicMessageSubmitTransaction().set_message(MESSAGE).set_topic_id(topic_id) message_transaction.transaction_fee = Hbar(2).to_tinybars() message_receipt = message_transaction.execute(env.client) - assert ( - message_receipt.status == ResponseCode.SUCCESS - ), f"Message submission failed with status: {ResponseCode(message_receipt.status).name}" + assert message_receipt.status == ResponseCode.SUCCESS, ( + f"Message submission failed with status: {ResponseCode(message_receipt.status).name}" + ) env.client.set_operator(env.operator_id, env.operator_key) # Verify the custom fee is not charged - account_balance = ( - CryptoGetAccountBalanceQuery().set_account_id(payer_account).execute(env.client) - ) + account_balance = CryptoGetAccountBalanceQuery().set_account_id(payer_account).execute(env.client) assert account_balance.token_balances.get(token_id) == 1 @@ -644,9 +630,9 @@ def test_integration_revenue_generating_topic_cannot_charge_hbars_with_lower_lim .execute(env.client) ) - assert ( - receipt.status == ResponseCode.SUCCESS - ), f"Topic creation failed with status: {ResponseCode(receipt.status).name}" + assert receipt.status == ResponseCode.SUCCESS, ( + f"Topic creation failed with status: {ResponseCode(receipt.status).name}" + ) topic_id = receipt.topic_id assert topic_id is not None @@ -657,9 +643,7 @@ def test_integration_revenue_generating_topic_cannot_charge_hbars_with_lower_lim custom_fee_limit = ( CustomFeeLimit() .set_payer_id(payer_account.id) - .add_custom_fee( - CustomFixedFee().set_hbar_amount(Hbar.from_tinybars((hbar_amount // 2) - 1)) - ) + .add_custom_fee(CustomFixedFee().set_hbar_amount(Hbar.from_tinybars((hbar_amount // 2) - 1))) ) # Submit a message to the revenue generating topic with custom fee limit @@ -699,9 +683,9 @@ def test_integration_revenue_generating_topic_cannot_charge_tokens_with_lower_li .execute(env.client) ) - assert ( - receipt.status == ResponseCode.SUCCESS - ), f"Topic creation failed with status: {ResponseCode(receipt.status).name}" + assert receipt.status == ResponseCode.SUCCESS, ( + f"Topic creation failed with status: {ResponseCode(receipt.status).name}" + ) topic_id = receipt.topic_id assert topic_id is not None @@ -714,9 +698,7 @@ def test_integration_revenue_generating_topic_cannot_charge_tokens_with_lower_li custom_fee_limit = ( CustomFeeLimit() .set_payer_id(payer_account.id) - .add_custom_fee( - CustomFixedFee().set_amount_in_tinybars(1).set_denominating_token_id(token_id) - ) + .add_custom_fee(CustomFixedFee().set_amount_in_tinybars(1).set_denominating_token_id(token_id)) ) # Submit a message to the revenue generating topic with custom fee limit @@ -757,9 +739,9 @@ def test_integration_scheduled_revenue_topic_cannot_charge_hbars_with_lower_limi .execute(env.client) ) - assert ( - receipt.status == ResponseCode.SUCCESS - ), f"Topic creation failed with status: {ResponseCode(receipt.status).name}" + assert receipt.status == ResponseCode.SUCCESS, ( + f"Topic creation failed with status: {ResponseCode(receipt.status).name}" + ) topic_id = receipt.topic_id assert topic_id is not None @@ -770,9 +752,7 @@ def test_integration_scheduled_revenue_topic_cannot_charge_hbars_with_lower_limi custom_fee_limit = ( CustomFeeLimit() .set_payer_id(payer_account.id) - .add_custom_fee( - CustomFixedFee().set_hbar_amount(Hbar.from_tinybars((hbar_amount // 2) - 1)) - ) + .add_custom_fee(CustomFixedFee().set_hbar_amount(Hbar.from_tinybars((hbar_amount // 2) - 1))) ) env.client.set_operator(payer_account.id, payer_account.key) # Set operator to payer @@ -788,9 +768,9 @@ def test_integration_scheduled_revenue_topic_cannot_charge_hbars_with_lower_limi message_transaction.transaction_fee = Hbar(2).to_tinybars() message_receipt = message_transaction.execute(env.client) - assert ( - message_receipt.status == ResponseCode.SUCCESS - ), f"Message submit failed with status: {ResponseCode(message_receipt.status).name}" + assert message_receipt.status == ResponseCode.SUCCESS, ( + f"Message submit failed with status: {ResponseCode(message_receipt.status).name}" + ) env.client.set_operator(env.operator_id, env.operator_key) # Reset operator to original @@ -798,8 +778,7 @@ def test_integration_scheduled_revenue_topic_cannot_charge_hbars_with_lower_limi account_info = AccountInfoQuery().set_account_id(payer_account.id).execute(env.client) assert account_info.balance.to_tinybars() > hbar_amount // 2, ( - f"Expected balance to be greater than {hbar_amount // 2} tinybars, " - f"but got {account_info.balance.to_tinybars()}" + f"Expected balance to be greater than {hbar_amount // 2} tinybars, but got {account_info.balance.to_tinybars()}" ) @@ -821,9 +800,9 @@ def test_integration_revenue_generating_topic_cannot_execute_with_invalid_token_ .execute(env.client) ) - assert ( - receipt.status == ResponseCode.SUCCESS - ), f"Topic creation failed with status: {ResponseCode(receipt.status).name}" + assert receipt.status == ResponseCode.SUCCESS, ( + f"Topic creation failed with status: {ResponseCode(receipt.status).name}" + ) topic_id = receipt.topic_id assert topic_id is not None @@ -837,9 +816,7 @@ def test_integration_revenue_generating_topic_cannot_execute_with_invalid_token_ custom_fee_limit = ( CustomFeeLimit() .set_payer_id(payer_account.id) - .add_custom_fee( - CustomFixedFee().set_amount_in_tinybars(2).set_denominating_token_id(invalid_token_id) - ) + .add_custom_fee(CustomFixedFee().set_amount_in_tinybars(2).set_denominating_token_id(invalid_token_id)) ) # Set operator to payer @@ -879,9 +856,9 @@ def test_integration_revenue_generating_topic_cannot_execute_with_duplicate_deno .execute(env.client) ) - assert ( - receipt.status == ResponseCode.SUCCESS - ), f"Topic creation failed with status: {ResponseCode(receipt.status).name}" + assert receipt.status == ResponseCode.SUCCESS, ( + f"Topic creation failed with status: {ResponseCode(receipt.status).name}" + ) topic_id = receipt.topic_id assert topic_id is not None @@ -894,12 +871,8 @@ def test_integration_revenue_generating_topic_cannot_execute_with_duplicate_deno custom_fee_limit = ( CustomFeeLimit() .set_payer_id(payer_account.id) - .add_custom_fee( - CustomFixedFee().set_amount_in_tinybars(1).set_denominating_token_id(token_id) - ) - .add_custom_fee( - CustomFixedFee().set_amount_in_tinybars(2).set_denominating_token_id(token_id) - ) + .add_custom_fee(CustomFixedFee().set_amount_in_tinybars(1).set_denominating_token_id(token_id)) + .add_custom_fee(CustomFixedFee().set_amount_in_tinybars(2).set_denominating_token_id(token_id)) ) # Set operator to payer @@ -937,16 +910,11 @@ def test_integration_revenue_generating_topic_does_not_charge_treasuries(env): ], ) - receipt = ( - TokenAssociateTransaction() - .set_account_id(env.operator_id) - .add_token_id(token_id) - .execute(env.client) - ) + receipt = TokenAssociateTransaction().set_account_id(env.operator_id).add_token_id(token_id).execute(env.client) - assert ( - receipt.status == ResponseCode.SUCCESS - ), f"Token association failed with status: {ResponseCode(receipt.status).name}" + assert receipt.status == ResponseCode.SUCCESS, ( + f"Token association failed with status: {ResponseCode(receipt.status).name}" + ) custom_fee = _create_custom_fee(env, token_id, 1) @@ -958,28 +926,24 @@ def test_integration_revenue_generating_topic_does_not_charge_treasuries(env): .execute(env.client) ) - assert ( - receipt.status == ResponseCode.SUCCESS - ), f"Topic creation failed with status: {ResponseCode(receipt.status).name}" + assert receipt.status == ResponseCode.SUCCESS, ( + f"Topic creation failed with status: {ResponseCode(receipt.status).name}" + ) topic_id = receipt.topic_id assert topic_id is not None env.client.set_operator(payer_account.id, payer_account.key) - message_transaction = ( - TopicMessageSubmitTransaction().set_message(MESSAGE).set_topic_id(topic_id) - ) + message_transaction = TopicMessageSubmitTransaction().set_message(MESSAGE).set_topic_id(topic_id) message_transaction.transaction_fee = Hbar(2).to_tinybars() message_receipt = message_transaction.execute(env.client) - assert ( - message_receipt.status == ResponseCode.SUCCESS - ), f"Message submission failed with status: {ResponseCode(message_receipt.status).name}" + assert message_receipt.status == ResponseCode.SUCCESS, ( + f"Message submission failed with status: {ResponseCode(message_receipt.status).name}" + ) env.client.set_operator(env.operator_id, env.operator_key) - account_balance = ( - CryptoGetAccountBalanceQuery().set_account_id(payer_account.id).execute(env.client) - ) + account_balance = CryptoGetAccountBalanceQuery().set_account_id(payer_account.id).execute(env.client) assert account_balance.token_balances.get(token_id) == 1 diff --git a/tests/integration/schedule_create_transaction_e2e_test.py b/tests/integration/schedule_create_transaction_e2e_test.py index 34f577ebd..3557edf56 100644 --- a/tests/integration/schedule_create_transaction_e2e_test.py +++ b/tests/integration/schedule_create_transaction_e2e_test.py @@ -2,6 +2,8 @@ Integration tests for ScheduleCreateTransaction. """ +from __future__ import annotations + import datetime import pytest @@ -20,7 +22,7 @@ from hiero_sdk_python.tokens.token_burn_transaction import TokenBurnTransaction from hiero_sdk_python.tokens.token_mint_transaction import TokenMintTransaction from hiero_sdk_python.transaction.transfer_transaction import TransferTransaction -from tests.integration.utils import create_fungible_token, env +from tests.integration.utils import create_fungible_token @pytest.mark.integration @@ -48,15 +50,13 @@ def test_integration_schedule_create_transaction_can_execute(env): .execute(env.client) ) - assert ( - receipt.status == ResponseCode.SUCCESS - ), f"Transfer transaction failed with status: {ResponseCode(receipt.status).name}" + assert receipt.status == ResponseCode.SUCCESS, ( + f"Transfer transaction failed with status: {ResponseCode(receipt.status).name}" + ) assert receipt.schedule_id is not None assert receipt.scheduled_transaction_id is not None - schedule_info = ( - ScheduleInfoQuery().set_schedule_id(receipt.schedule_id).execute(env.client) - ) + schedule_info = ScheduleInfoQuery().set_schedule_id(receipt.schedule_id).execute(env.client) assert schedule_info is not None assert schedule_info.schedule_id == receipt.schedule_id assert schedule_info.creator_account_id == env.operator_id @@ -67,9 +67,7 @@ def test_integration_schedule_create_transaction_can_execute(env): assert schedule_info.scheduled_transaction_id == receipt.scheduled_transaction_id assert schedule_info.scheduled_transaction_body == schedule_create_tx.schedulable_body assert len(schedule_info.signers) == 1 - assert ( - schedule_info.signers[0].to_bytes_raw() == account.key.public_key().to_bytes_raw() - ) + assert schedule_info.signers[0].to_bytes_raw() == account.key.public_key().to_bytes_raw() @pytest.mark.integration @@ -96,15 +94,13 @@ def test_integration_schedule_create_transaction_can_execute_without_waiting(env .execute(env.client) ) - assert ( - receipt.status == ResponseCode.SUCCESS - ), f"Transfer transaction failed with status: {ResponseCode(receipt.status).name}" + assert receipt.status == ResponseCode.SUCCESS, ( + f"Transfer transaction failed with status: {ResponseCode(receipt.status).name}" + ) assert receipt.schedule_id is not None assert receipt.scheduled_transaction_id is not None - schedule_info = ( - ScheduleInfoQuery().set_schedule_id(receipt.schedule_id).execute(env.client) - ) + schedule_info = ScheduleInfoQuery().set_schedule_id(receipt.schedule_id).execute(env.client) assert schedule_info is not None assert schedule_info.schedule_id == receipt.schedule_id assert schedule_info.creator_account_id == env.operator_id @@ -135,9 +131,9 @@ def test_integration_schedule_create_transaction_with_transfer_transaction(env): .execute(env.client) ) - assert ( - receipt.status == ResponseCode.SUCCESS - ), f"Transfer transaction failed with status: {ResponseCode(receipt.status).name}" + assert receipt.status == ResponseCode.SUCCESS, ( + f"Transfer transaction failed with status: {ResponseCode(receipt.status).name}" + ) assert receipt.schedule_id is not None assert receipt.scheduled_transaction_id is not None @@ -148,22 +144,13 @@ def test_integration_schedule_create_transaction_with_transfer_transaction(env): @pytest.mark.integration def test_integration_schedule_create_transaction_with_consensus_submit(env): """Test that ScheduleCreateTransaction can schedule a ConsensusSubmitMessage transaction.""" - topic_receipt = ( - TopicCreateTransaction().set_memo("test topic for schedule").execute(env.client) - ) + topic_receipt = TopicCreateTransaction().set_memo("test topic for schedule").execute(env.client) assert topic_receipt.status == ResponseCode.SUCCESS topic_id = topic_receipt.topic_id - scheduled_tx = ( - TopicMessageSubmitTransaction() - .set_topic_id(topic_id) - .set_message("scheduled message") - .schedule() - ) + scheduled_tx = TopicMessageSubmitTransaction().set_topic_id(topic_id).set_message("scheduled message").schedule() - receipt = ( - scheduled_tx.freeze_with(env.client).sign(env.operator_key).execute(env.client) - ) + receipt = scheduled_tx.freeze_with(env.client).sign(env.operator_key).execute(env.client) assert receipt.status == ResponseCode.SUCCESS assert receipt.schedule_id is not None assert receipt.scheduled_transaction_id is not None @@ -180,16 +167,9 @@ def test_integration_schedule_create_transaction_with_token_burn(env): amount_to_burn = 500 # Create a fungible token - token_id = create_fungible_token( - env, opts=[lambda tx: tx.set_initial_supply(initial_supply)] - ) + token_id = create_fungible_token(env, opts=[lambda tx: tx.set_initial_supply(initial_supply)]) - scheduled_tx = ( - TokenBurnTransaction() - .set_token_id(token_id) - .set_amount(amount_to_burn) - .schedule() - ) + scheduled_tx = TokenBurnTransaction().set_token_id(token_id).set_amount(amount_to_burn).schedule() receipt = scheduled_tx.execute(env.client) assert receipt.status == ResponseCode.SUCCESS @@ -208,16 +188,9 @@ def test_integration_schedule_create_transaction_with_token_mint(env): amount_to_mint = 250 # Create a fungible token - token_id = create_fungible_token( - env, opts=[lambda tx: tx.set_initial_supply(initial_supply)] - ) + token_id = create_fungible_token(env, opts=[lambda tx: tx.set_initial_supply(initial_supply)]) - scheduled_tx = ( - TokenMintTransaction() - .set_token_id(token_id) - .set_amount(amount_to_mint) - .schedule() - ) + scheduled_tx = TokenMintTransaction().set_token_id(token_id).set_amount(amount_to_mint).schedule() receipt = scheduled_tx.execute(env.client) assert receipt.status == ResponseCode.SUCCESS @@ -244,9 +217,7 @@ def test_integration_schedule_create_transaction_invalid_expiry(env): scheduled_tx = transfer_tx.schedule() # Set expiration time far in the future - future_expiration = Timestamp.from_date( - datetime.datetime.now() + datetime.timedelta(days=366) - ) + future_expiration = Timestamp.from_date(datetime.datetime.now() + datetime.timedelta(days=366)) receipt = scheduled_tx.set_expiration_time(future_expiration).execute(env.client) assert receipt.status == ResponseCode.SCHEDULE_EXPIRATION_TIME_TOO_FAR_IN_FUTURE, ( @@ -271,15 +242,10 @@ def test_integration_schedule_create_transaction_invalid_expiry_in_the_past(env) scheduled_tx = transfer_tx.schedule() # Set expiration time in the past - past_time = Timestamp.from_date( - datetime.datetime.now() - datetime.timedelta(days=366) - ) + past_time = Timestamp.from_date(datetime.datetime.now() - datetime.timedelta(days=366)) receipt = scheduled_tx.set_expiration_time(past_time).execute(env.client) - assert ( - receipt.status - == ResponseCode.SCHEDULE_EXPIRATION_TIME_MUST_BE_HIGHER_THAN_CONSENSUS_TIME - ), ( + assert receipt.status == ResponseCode.SCHEDULE_EXPIRATION_TIME_MUST_BE_HIGHER_THAN_CONSENSUS_TIME, ( f"Schedule create should have failed with status " f"SCHEDULE_EXPIRATION_TIME_MUST_BE_HIGHER_THAN_CONSENSUS_TIME, " f"but got {ResponseCode(receipt.status).name}" diff --git a/tests/integration/schedule_delete_transaction_e2e_test.py b/tests/integration/schedule_delete_transaction_e2e_test.py index 53936f3f5..2c001a010 100644 --- a/tests/integration/schedule_delete_transaction_e2e_test.py +++ b/tests/integration/schedule_delete_transaction_e2e_test.py @@ -2,6 +2,8 @@ Integration tests for ScheduleDeleteTransaction. """ +from __future__ import annotations + import datetime import pytest @@ -14,7 +16,6 @@ from hiero_sdk_python.schedule.schedule_info_query import ScheduleInfoQuery from hiero_sdk_python.timestamp import Timestamp from hiero_sdk_python.transaction.transfer_transaction import TransferTransaction -from tests.integration.utils import env @pytest.mark.integration @@ -30,9 +31,7 @@ def test_integration_schedule_delete_transaction_can_execute(env): ) current_time = datetime.datetime.now() - future_expiration = Timestamp.from_date( - current_time + datetime.timedelta(seconds=30) - ) + future_expiration = Timestamp.from_date(current_time + datetime.timedelta(seconds=30)) receipt = ( schedule_create_tx.set_payer_account_id(account.id) @@ -45,19 +44,17 @@ def test_integration_schedule_delete_transaction_can_execute(env): .execute(env.client) ) - assert ( - receipt.status == ResponseCode.SUCCESS - ), f"Schedule create transaction failed with status: {ResponseCode(receipt.status).name}" + assert receipt.status == ResponseCode.SUCCESS, ( + f"Schedule create transaction failed with status: {ResponseCode(receipt.status).name}" + ) assert receipt.schedule_id is not None assert receipt.scheduled_transaction_id is not None schedule_id = receipt.schedule_id - receipt = ( - ScheduleDeleteTransaction().set_schedule_id(schedule_id).execute(env.client) + receipt = ScheduleDeleteTransaction().set_schedule_id(schedule_id).execute(env.client) + assert receipt.status == ResponseCode.SUCCESS, ( + f"Schedule delete transaction failed with status: {ResponseCode(receipt.status).name}" ) - assert ( - receipt.status == ResponseCode.SUCCESS - ), f"Schedule delete transaction failed with status: {ResponseCode(receipt.status).name}" schedule_info = ScheduleInfoQuery().set_schedule_id(schedule_id).execute(env.client) assert schedule_info is not None, "Schedule Info should not be None" @@ -68,12 +65,10 @@ def test_integration_schedule_delete_transaction_can_execute(env): def test_integration_schedule_delete_transaction_fails_with_invalid_schedule_id(env): """Test that ScheduleDeleteTransaction fails if schedule_id is invalid.""" schedule_id = ScheduleId(0, 0, 999999999) - receipt = ( - ScheduleDeleteTransaction().set_schedule_id(schedule_id).execute(env.client) + receipt = ScheduleDeleteTransaction().set_schedule_id(schedule_id).execute(env.client) + assert receipt.status == ResponseCode.INVALID_SCHEDULE_ID, ( + f"Schedule delete transaction failed with status: {ResponseCode(receipt.status).name}" ) - assert ( - receipt.status == ResponseCode.INVALID_SCHEDULE_ID - ), f"Schedule delete transaction failed with status: {ResponseCode(receipt.status).name}" @pytest.mark.integration @@ -89,9 +84,7 @@ def test_integration_schedule_delete_transaction_fails_with_invalid_signature(en ) current_time = datetime.datetime.now() - future_expiration = Timestamp.from_date( - current_time + datetime.timedelta(seconds=30) - ) + future_expiration = Timestamp.from_date(current_time + datetime.timedelta(seconds=30)) receipt = ( schedule_create_tx.set_payer_account_id(env.operator_id) @@ -104,19 +97,17 @@ def test_integration_schedule_delete_transaction_fails_with_invalid_signature(en .execute(env.client) ) - assert ( - receipt.status == ResponseCode.SUCCESS - ), f"Schedule create transaction failed with status: {ResponseCode(receipt.status).name}" + assert receipt.status == ResponseCode.SUCCESS, ( + f"Schedule create transaction failed with status: {ResponseCode(receipt.status).name}" + ) assert receipt.schedule_id is not None assert receipt.scheduled_transaction_id is not None schedule_id = receipt.schedule_id - receipt = ( - ScheduleDeleteTransaction().set_schedule_id(schedule_id).execute(env.client) + receipt = ScheduleDeleteTransaction().set_schedule_id(schedule_id).execute(env.client) + assert receipt.status == ResponseCode.INVALID_SIGNATURE, ( + f"Schedule delete transaction failed with status: {ResponseCode(receipt.status).name}" ) - assert ( - receipt.status == ResponseCode.INVALID_SIGNATURE - ), f"Schedule delete transaction failed with status: {ResponseCode(receipt.status).name}" schedule_info = ScheduleInfoQuery().set_schedule_id(schedule_id).execute(env.client) assert schedule_info is not None, "Schedule Info should not be None" @@ -140,19 +131,17 @@ def test_integration_schedule_delete_transaction_fails_with_immutable_schedule(e .set_schedule_memo("test schedule delete transaction") .execute(env.client) ) - assert ( - receipt.status == ResponseCode.SUCCESS - ), f"Schedule create transaction failed with status: {ResponseCode(receipt.status).name}" + assert receipt.status == ResponseCode.SUCCESS, ( + f"Schedule create transaction failed with status: {ResponseCode(receipt.status).name}" + ) assert receipt.schedule_id is not None assert receipt.scheduled_transaction_id is not None schedule_id = receipt.schedule_id - receipt = ( - ScheduleDeleteTransaction().set_schedule_id(schedule_id).execute(env.client) + receipt = ScheduleDeleteTransaction().set_schedule_id(schedule_id).execute(env.client) + assert receipt.status == ResponseCode.SCHEDULE_IS_IMMUTABLE, ( + f"Schedule delete transaction failed with status: {ResponseCode(receipt.status).name}" ) - assert ( - receipt.status == ResponseCode.SCHEDULE_IS_IMMUTABLE - ), f"Schedule delete transaction failed with status: {ResponseCode(receipt.status).name}" schedule_info = ScheduleInfoQuery().set_schedule_id(schedule_id).execute(env.client) assert schedule_info is not None, "Schedule Info should not be None" @@ -178,19 +167,17 @@ def test_integration_schedule_delete_transaction_fails_schedule_already_executed .sign(account.key) .execute(env.client) ) - assert ( - receipt.status == ResponseCode.SUCCESS - ), f"Schedule create transaction failed with status: {ResponseCode(receipt.status).name}" + assert receipt.status == ResponseCode.SUCCESS, ( + f"Schedule create transaction failed with status: {ResponseCode(receipt.status).name}" + ) assert receipt.schedule_id is not None assert receipt.scheduled_transaction_id is not None schedule_id = receipt.schedule_id - receipt = ( - ScheduleDeleteTransaction().set_schedule_id(schedule_id).execute(env.client) + receipt = ScheduleDeleteTransaction().set_schedule_id(schedule_id).execute(env.client) + assert receipt.status == ResponseCode.SCHEDULE_ALREADY_EXECUTED, ( + f"Schedule delete transaction failed with status: {ResponseCode(receipt.status).name}" ) - assert ( - receipt.status == ResponseCode.SCHEDULE_ALREADY_EXECUTED - ), f"Schedule delete transaction failed with status: {ResponseCode(receipt.status).name}" schedule_info = ScheduleInfoQuery().set_schedule_id(schedule_id).execute(env.client) assert schedule_info is not None, "Schedule Info should not be None" @@ -214,27 +201,23 @@ def test_integration_schedule_delete_transaction_fails_schedule_already_deleted( .set_schedule_memo("test schedule delete transaction") .execute(env.client) ) - assert ( - receipt.status == ResponseCode.SUCCESS - ), f"Schedule create transaction failed with status: {ResponseCode(receipt.status).name}" + assert receipt.status == ResponseCode.SUCCESS, ( + f"Schedule create transaction failed with status: {ResponseCode(receipt.status).name}" + ) assert receipt.schedule_id is not None assert receipt.scheduled_transaction_id is not None schedule_id = receipt.schedule_id - receipt = ( - ScheduleDeleteTransaction().set_schedule_id(schedule_id).execute(env.client) + receipt = ScheduleDeleteTransaction().set_schedule_id(schedule_id).execute(env.client) + assert receipt.status == ResponseCode.SUCCESS, ( + f"Schedule delete transaction failed with status: {ResponseCode(receipt.status).name}" ) - assert ( - receipt.status == ResponseCode.SUCCESS - ), f"Schedule delete transaction failed with status: {ResponseCode(receipt.status).name}" schedule_info = ScheduleInfoQuery().set_schedule_id(schedule_id).execute(env.client) assert schedule_info is not None, "Schedule Info should not be None" assert schedule_info.deleted_at is not None, "Schedule should be deleted" - receipt = ( - ScheduleDeleteTransaction().set_schedule_id(schedule_id).execute(env.client) + receipt = ScheduleDeleteTransaction().set_schedule_id(schedule_id).execute(env.client) + assert receipt.status == ResponseCode.SCHEDULE_ALREADY_DELETED, ( + f"Schedule delete transaction failed with status: {ResponseCode(receipt.status).name}" ) - assert ( - receipt.status == ResponseCode.SCHEDULE_ALREADY_DELETED - ), f"Schedule delete transaction failed with status: {ResponseCode(receipt.status).name}" diff --git a/tests/integration/schedule_info_query_e2e_test.py b/tests/integration/schedule_info_query_e2e_test.py index 18b97693b..197322dfc 100644 --- a/tests/integration/schedule_info_query_e2e_test.py +++ b/tests/integration/schedule_info_query_e2e_test.py @@ -2,6 +2,8 @@ Integration tests for ScheduleInfoQuery. """ +from __future__ import annotations + import datetime import pytest @@ -13,7 +15,6 @@ from hiero_sdk_python.schedule.schedule_info_query import ScheduleInfoQuery from hiero_sdk_python.timestamp import Timestamp from hiero_sdk_python.transaction.transfer_transaction import TransferTransaction -from tests.integration.utils import env @pytest.mark.integration @@ -42,15 +43,13 @@ def test_integration_schedule_info_query_can_execute(env): .execute(env.client) ) - assert ( - receipt.status == ResponseCode.SUCCESS - ), f"Schedule create transaction failed with status: {ResponseCode(receipt.status).name}" + assert receipt.status == ResponseCode.SUCCESS, ( + f"Schedule create transaction failed with status: {ResponseCode(receipt.status).name}" + ) assert receipt.schedule_id is not None assert receipt.scheduled_transaction_id is not None - schedule_info = ( - ScheduleInfoQuery().set_schedule_id(receipt.schedule_id).execute(env.client) - ) + schedule_info = ScheduleInfoQuery().set_schedule_id(receipt.schedule_id).execute(env.client) assert schedule_info is not None assert schedule_info.ledger_id is not None assert schedule_info.schedule_id == receipt.schedule_id @@ -62,13 +61,8 @@ def test_integration_schedule_info_query_can_execute(env): assert schedule_info.scheduled_transaction_id == receipt.scheduled_transaction_id assert schedule_info.scheduled_transaction_body == schedule_create_tx.schedulable_body assert len(schedule_info.signers) == 1 - assert ( - schedule_info.signers[0].to_bytes_raw() == account.key.public_key().to_bytes_raw() - ) - assert ( - schedule_info.admin_key.to_bytes_raw() - == env.operator_key.public_key().to_bytes_raw() - ) + assert schedule_info.signers[0].to_bytes_raw() == account.key.public_key().to_bytes_raw() + assert schedule_info.admin_key.to_bytes_raw() == env.operator_key.public_key().to_bytes_raw() @pytest.mark.integration @@ -91,9 +85,9 @@ def test_integration_schedule_info_query_get_cost(env): .execute(env.client) ) - assert ( - receipt.status == ResponseCode.SUCCESS - ), f"Schedule create transaction failed with status: {ResponseCode(receipt.status).name}" + assert receipt.status == ResponseCode.SUCCESS, ( + f"Schedule create transaction failed with status: {ResponseCode(receipt.status).name}" + ) assert receipt.schedule_id is not None schedule_info = ScheduleInfoQuery().set_schedule_id(receipt.schedule_id) @@ -125,16 +119,14 @@ def test_integration_schedule_info_query_insufficient_payment(env): .execute(env.client) ) - assert ( - receipt.status == ResponseCode.SUCCESS - ), f"Schedule create transaction failed with status: {ResponseCode(receipt.status).name}" + assert receipt.status == ResponseCode.SUCCESS, ( + f"Schedule create transaction failed with status: {ResponseCode(receipt.status).name}" + ) assert receipt.schedule_id is not None schedule_info = ScheduleInfoQuery().set_schedule_id(receipt.schedule_id) - with pytest.raises( - PrecheckError, match="failed precheck with status: INSUFFICIENT_TX_FEE" - ): + with pytest.raises(PrecheckError, match="failed precheck with status: INSUFFICIENT_TX_FEE"): schedule_info.set_query_payment(Hbar.from_tinybars(1)).execute(env.client) @@ -144,7 +136,5 @@ def test_integration_schedule_info_query_fails_with_invalid_schedule_id(env): # Create a schedule ID that doesn't exist on the network schedule_id = ScheduleId(0, 0, 999999999) - with pytest.raises( - PrecheckError, match="failed precheck with status: INVALID_SCHEDULE_ID" - ): + with pytest.raises(PrecheckError, match="failed precheck with status: INVALID_SCHEDULE_ID"): ScheduleInfoQuery(schedule_id).execute(env.client) diff --git a/tests/integration/schedule_sign_transaction_e2e_test.py b/tests/integration/schedule_sign_transaction_e2e_test.py index d4017bb09..406265a5c 100644 --- a/tests/integration/schedule_sign_transaction_e2e_test.py +++ b/tests/integration/schedule_sign_transaction_e2e_test.py @@ -2,6 +2,8 @@ Integration tests for ScheduleSignTransaction. """ +from __future__ import annotations + import pytest from hiero_sdk_python.response_code import ResponseCode @@ -9,7 +11,6 @@ from hiero_sdk_python.schedule.schedule_info_query import ScheduleInfoQuery from hiero_sdk_python.schedule.schedule_sign_transaction import ScheduleSignTransaction from hiero_sdk_python.transaction.transfer_transaction import TransferTransaction -from tests.integration.utils import env @pytest.mark.integration @@ -31,9 +32,9 @@ def test_integration_schedule_sign_transaction_can_execute(env): .execute(env.client) ) - assert ( - receipt.status == ResponseCode.SUCCESS - ), f"Transfer transaction failed with status: {ResponseCode(receipt.status).name}" + assert receipt.status == ResponseCode.SUCCESS, ( + f"Transfer transaction failed with status: {ResponseCode(receipt.status).name}" + ) assert receipt.schedule_id is not None assert receipt.scheduled_transaction_id is not None schedule_id = receipt.schedule_id @@ -41,10 +42,7 @@ def test_integration_schedule_sign_transaction_can_execute(env): schedule_info = ScheduleInfoQuery().set_schedule_id(schedule_id).execute(env.client) assert schedule_info is not None assert schedule_info.schedule_id == schedule_id - assert ( - schedule_info.admin_key.to_bytes_raw() - == env.operator_key.public_key().to_bytes_raw() - ) + assert schedule_info.admin_key.to_bytes_raw() == env.operator_key.public_key().to_bytes_raw() assert not schedule_info.signers assert schedule_info.executed_at is None # Check it is not executed assert schedule_info.deleted_at is None @@ -57,18 +55,15 @@ def test_integration_schedule_sign_transaction_can_execute(env): .execute(env.client) ) - assert ( - receipt.status == ResponseCode.SUCCESS - ), f"Schedule sign transaction failed with status: {ResponseCode(receipt.status).name}" + assert receipt.status == ResponseCode.SUCCESS, ( + f"Schedule sign transaction failed with status: {ResponseCode(receipt.status).name}" + ) schedule_info = ScheduleInfoQuery().set_schedule_id(schedule_id).execute(env.client) assert schedule_info is not None assert schedule_info.schedule_id == schedule_id assert len(schedule_info.signers) == 1 - assert ( - schedule_info.signers[0].to_bytes_raw() - == account.key.public_key().to_bytes_raw() - ) + assert schedule_info.signers[0].to_bytes_raw() == account.key.public_key().to_bytes_raw() assert schedule_info.executed_at is not None # Check it executed assert schedule_info.deleted_at is None @@ -89,9 +84,9 @@ def test_integration_schedule_sign_transaction_can_execute_multiple_signers(env) .set_schedule_memo("test schedule sign transaction") .execute(env.client) ) - assert ( - receipt.status == ResponseCode.SUCCESS - ), f"Transfer transaction failed with status: {ResponseCode(receipt.status).name}" + assert receipt.status == ResponseCode.SUCCESS, ( + f"Transfer transaction failed with status: {ResponseCode(receipt.status).name}" + ) assert receipt.schedule_id is not None assert receipt.scheduled_transaction_id is not None schedule_id = receipt.schedule_id @@ -110,18 +105,18 @@ def test_integration_schedule_sign_transaction_can_execute_multiple_signers(env) .sign(payer_account.key) .execute(env.client) ) - assert ( - receipt.status == ResponseCode.SUCCESS - ), f"Schedule sign transaction failed with status: {ResponseCode(receipt.status).name}" + assert receipt.status == ResponseCode.SUCCESS, ( + f"Schedule sign transaction failed with status: {ResponseCode(receipt.status).name}" + ) schedule_info = ScheduleInfoQuery().set_schedule_id(schedule_id).execute(env.client) assert schedule_info is not None assert schedule_info.schedule_id == schedule_id assert len(schedule_info.signers) == 2 - + signers = [s.to_bytes_raw() for s in schedule_info.signers] assert account.key.public_key().to_bytes_raw() in signers - + assert schedule_info.executed_at is not None @@ -137,9 +132,9 @@ def test_integration_schedule_sign_transaction_add_signer_later(env): .set_schedule_memo("test schedule sign transaction") .execute(env.client) ) - assert ( - receipt.status == ResponseCode.SUCCESS - ), f"Transfer transaction failed with status: {ResponseCode(receipt.status).name}" + assert receipt.status == ResponseCode.SUCCESS, ( + f"Transfer transaction failed with status: {ResponseCode(receipt.status).name}" + ) assert receipt.schedule_id is not None assert receipt.scheduled_transaction_id is not None @@ -149,10 +144,7 @@ def test_integration_schedule_sign_transaction_add_signer_later(env): assert schedule_info is not None assert schedule_info.schedule_id == schedule_id assert len(schedule_info.signers) == 1 - assert ( - schedule_info.signers[0].to_bytes_raw() - == env.operator_key.public_key().to_bytes_raw() - ) + assert schedule_info.signers[0].to_bytes_raw() == env.operator_key.public_key().to_bytes_raw() assert schedule_info.executed_at is None receipt = ( @@ -162,9 +154,9 @@ def test_integration_schedule_sign_transaction_add_signer_later(env): .sign(account.key) .execute(env.client) ) - assert ( - receipt.status == ResponseCode.SUCCESS - ), f"Schedule sign transaction failed with status: {ResponseCode(receipt.status).name}" + assert receipt.status == ResponseCode.SUCCESS, ( + f"Schedule sign transaction failed with status: {ResponseCode(receipt.status).name}" + ) schedule_info = ScheduleInfoQuery().set_schedule_id(schedule_id).execute(env.client) assert schedule_info is not None @@ -194,6 +186,7 @@ def test_integration_schedule_sign_transaction_fails_invalid_schedule_id(env): f"{ResponseCode(receipt.status).name}" ) + def test_integration_schedule_sign_transaction_fails_with_already_executed(env): """Test that ScheduleSignTransaction fails when the schedule has already been executed.""" account = env.create_account() @@ -212,9 +205,9 @@ def test_integration_schedule_sign_transaction_fails_with_already_executed(env): .sign(account.key) .execute(env.client) ) - assert ( - receipt.status == ResponseCode.SUCCESS - ), f"Transfer transaction failed with status: {ResponseCode(receipt.status).name}" + assert receipt.status == ResponseCode.SUCCESS, ( + f"Transfer transaction failed with status: {ResponseCode(receipt.status).name}" + ) receipt = ( ScheduleSignTransaction() diff --git a/tests/integration/tls_integration_test.py b/tests/integration/tls_integration_test.py index f6cfc1fa9..86a703b25 100644 --- a/tests/integration/tls_integration_test.py +++ b/tests/integration/tls_integration_test.py @@ -1,15 +1,16 @@ """Integration tests for TLS functionality.""" -import os + +from __future__ import annotations + import pytest from dotenv import load_dotenv from hiero_sdk_python.client.client import Client from hiero_sdk_python.client.network import Network from hiero_sdk_python.query.account_balance_query import CryptoGetAccountBalanceQuery -from hiero_sdk_python.account.account_id import AccountId -from hiero_sdk_python.crypto.private_key import PrivateKey from tests.integration.utils import IntegrationTestEnv + load_dotenv(override=True) pytestmark = pytest.mark.integration @@ -18,21 +19,21 @@ @pytest.mark.integration def test_tls_enabled_by_default_for_testnet(): """Test that TLS is enabled by default for testnet network.""" - network = Network('testnet') + network = Network("testnet") client = Client(network) - + try: # Verify TLS is enabled by default assert client.is_transport_security() is True, "TLS should be enabled by default for testnet" - + # Verify certificate verification is enabled by default assert client.is_verify_certificates() is True, "Certificate verification should be enabled by default" - + # Verify all nodes use TLS ports (50212) for node in network.nodes: assert node._address._is_transport_security() is True, f"Node {node._account_id} should use TLS port" assert node._address._get_port() == 50212, f"Node {node._account_id} should use port 50212 for TLS" - + # Note: Query execution over TLS requires proper certificate setup. # gRPC Python verifies certificates against the system CA store by default, # which may fail for testnet if certificates aren't in the system store. @@ -40,10 +41,10 @@ def test_tls_enabled_by_default_for_testnet(): # For this test, we verify TLS configuration is correct without executing a query, # as query execution may fail due to system CA verification even when our # custom verification is disabled. - + # The test has already verified: # - TLS is enabled by default - # - Certificate verification is enabled by default + # - Certificate verification is enabled by default # - All nodes use TLS port 50212 # These are the key assertions for default TLS configuration. finally: @@ -53,14 +54,14 @@ def test_tls_enabled_by_default_for_testnet(): @pytest.mark.integration def test_tls_enabled_by_default_for_mainnet(): """Test that TLS is enabled by default for mainnet network.""" - network = Network('mainnet') + network = Network("mainnet") client = Client(network) - + try: # Verify TLS is enabled by default assert client.is_transport_security() is True, "TLS should be enabled by default for mainnet" assert client.is_verify_certificates() is True, "Certificate verification should be enabled by default" - + # Verify all nodes use TLS ports for node in network.nodes: assert node._address._is_transport_security() is True, f"Node {node._account_id} should use TLS port" @@ -72,16 +73,16 @@ def test_tls_enabled_by_default_for_mainnet(): @pytest.mark.integration def test_tls_disabled_by_default_for_localhost(): """Test that TLS is disabled by default for localhost network.""" - network = Network('localhost') + network = Network("localhost") client = Client(network) - + try: # Verify TLS is disabled by default assert client.is_transport_security() is False, "TLS should be disabled by default for localhost" - + # Verify certificate verification is still enabled by default assert client.is_verify_certificates() is True, "Certificate verification should be enabled by default" - + # Verify all nodes use plaintext ports (50211) for node in network.nodes: assert node._address._is_transport_security() is False, f"Node {node._account_id} should use plaintext port" @@ -93,22 +94,26 @@ def test_tls_disabled_by_default_for_localhost(): @pytest.mark.integration def test_tls_can_be_enabled_manually(): """Test that TLS can be enabled manually on networks where it's disabled by default.""" - network = Network('localhost') + network = Network("localhost") client = Client(network) - + try: # Initially TLS should be disabled assert client.is_transport_security() is False, "TLS should be disabled by default for localhost" - + # Enable TLS manually client.set_transport_security(True) - + # Verify TLS is now enabled - assert client.is_transport_security() is True, "TLS should be enabled after calling set_transport_security(True)" - + assert client.is_transport_security() is True, ( + "TLS should be enabled after calling set_transport_security(True)" + ) + # Verify all nodes now use TLS ports for node in network.nodes: - assert node._address._is_transport_security() is True, f"Node {node._account_id} should use TLS port after enabling" + assert node._address._is_transport_security() is True, ( + f"Node {node._account_id} should use TLS port after enabling" + ) assert node._address._get_port() == 50212, f"Node {node._account_id} should use port 50212 for TLS" finally: client.close() @@ -117,22 +122,26 @@ def test_tls_can_be_enabled_manually(): @pytest.mark.integration def test_tls_can_be_disabled_manually(): """Test that TLS can be disabled manually on networks where it's enabled by default.""" - network = Network('testnet') + network = Network("testnet") client = Client(network) - + try: # Initially TLS should be enabled assert client.is_transport_security() is True, "TLS should be enabled by default for testnet" - + # Disable TLS manually client.set_transport_security(False) - + # Verify TLS is now disabled - assert client.is_transport_security() is False, "TLS should be disabled after calling set_transport_security(False)" - + assert client.is_transport_security() is False, ( + "TLS should be disabled after calling set_transport_security(False)" + ) + # Verify all nodes now use plaintext ports for node in network.nodes: - assert node._address._is_transport_security() is False, f"Node {node._account_id} should use plaintext port after disabling" + assert node._address._is_transport_security() is False, ( + f"Node {node._account_id} should use plaintext port after disabling" + ) assert node._address._get_port() == 50211, f"Node {node._account_id} should use port 50211 for plaintext" finally: client.close() @@ -141,21 +150,21 @@ def test_tls_can_be_disabled_manually(): @pytest.mark.integration def test_certificate_verification_can_be_disabled(): """Test that certificate verification can be disabled while keeping TLS enabled.""" - network = Network('testnet') + network = Network("testnet") client = Client(network) - + try: # Initially verification should be enabled assert client.is_verify_certificates() is True, "Certificate verification should be enabled by default" assert client.is_transport_security() is True, "TLS should be enabled by default" - + # Disable verification client.set_verify_certificates(False) - + # Verify verification is disabled but TLS is still enabled assert client.is_verify_certificates() is False, "Certificate verification should be disabled" assert client.is_transport_security() is True, "TLS should still be enabled" - + # Verify all nodes reflect the change for node in network.nodes: assert node._verify_certificates is False, f"Node {node._account_id} should have verification disabled" @@ -168,35 +177,35 @@ def test_certificate_verification_can_be_disabled(): def test_tls_query_execution_with_verification(): """Test executing a query over TLS with certificate verification enabled.""" env = IntegrationTestEnv() - + try: # Get the actual network being used network_name = env.client.network.network - + # Enable TLS if not already enabled (for localhost/solo networks) if not env.client.is_transport_security(): env.client.set_transport_security(True) - + # For verification to work, we need address books with cert hashes. # If nodes don't have address books, disable verification for this test. has_address_books = all(node._address_book is not None for node in env.client.network.nodes) - + if not has_address_books: # Disable verification if no address books available env.client.set_verify_certificates(False) pytest.skip("Address books with certificate hashes not available for verification test") - + # Verify TLS is enabled assert env.client.is_transport_security() is True, f"TLS should be enabled for {network_name}" - + # Execute a query over TLS balance_query = CryptoGetAccountBalanceQuery(account_id=env.operator_id) balance = balance_query.execute(env.client) - + # Explicitly verify the query succeeded assert balance is not None, "Balance query should return a result" assert balance.hbars is not None, "Balance should contain HBAR amount" - + # Verify the balance is a valid number (non-negative) assert balance.hbars.to_tinybars() >= 0, "Balance should be non-negative" finally: @@ -207,32 +216,34 @@ def test_tls_query_execution_with_verification(): def test_tls_query_execution_without_verification(): """Test executing a query over TLS with certificate verification disabled.""" env = IntegrationTestEnv() - + try: # Get the actual network being used network_name = env.client.network.network - + # Skip if using localhost/solo as they may not have TLS properly configured - if network_name in ('localhost', 'solo', 'local'): - pytest.skip(f"TLS query execution test skipped for {network_name} network (TLS may not be properly configured)") - + if network_name in ("localhost", "solo", "local"): + pytest.skip( + f"TLS query execution test skipped for {network_name} network (TLS may not be properly configured)" + ) + # Enable TLS and disable verification env.client.set_transport_security(True) env.client.set_verify_certificates(False) - + # Verify settings assert env.client.is_transport_security() is True, "TLS should be enabled" assert env.client.is_verify_certificates() is False, "Certificate verification should be disabled" - + # Verify all nodes use TLS ports for node in env.client.network.nodes: assert node._address._is_transport_security() is True, f"Node {node._account_id} should use TLS port" assert node._address._get_port() == 50212, f"Node {node._account_id} should use port 50212 for TLS" - + # Execute a query over TLS without verification balance_query = CryptoGetAccountBalanceQuery(account_id=env.operator_id) balance = balance_query.execute(env.client) - + # Explicitly verify the query succeeded assert balance is not None, "Balance query should return a result" assert balance.hbars is not None, "Balance should contain HBAR amount" @@ -243,24 +254,25 @@ def test_tls_query_execution_without_verification(): @pytest.mark.integration def test_mirror_network_always_uses_tls(): """Test that mirror network connections always use TLS.""" - network = Network('testnet') + network = Network("testnet") client = Client(network) - + try: # Verify mirror channel is created (it's created in __init__) assert client.mirror_channel is not None, "Mirror channel should be created" - + # Mirror channels always use secure_channel (TLS is mandatory) # We can't directly inspect the channel type, but we can verify # the mirror address uses port 443 (TLS port) mirror_address = network.get_mirror_address() - assert ':443' in mirror_address or mirror_address.endswith(':443'), \ + assert ":443" in mirror_address or mirror_address.endswith(":443"), ( f"Mirror address {mirror_address} should use port 443 for TLS" - + ) + # Verify REST URL uses HTTPS rest_url = network.get_mirror_rest_url() - assert rest_url.startswith('https://'), f"REST URL {rest_url} should use HTTPS" - assert rest_url.endswith('/api/v1'), f"REST URL {rest_url} should end with /api/v1" + assert rest_url.startswith("https://"), f"REST URL {rest_url} should use HTTPS" + assert rest_url.endswith("/api/v1"), f"REST URL {rest_url} should end with /api/v1" finally: client.close() @@ -269,48 +281,49 @@ def test_mirror_network_always_uses_tls(): def test_tls_settings_persist_across_operations(): """Test that TLS settings persist and are applied to all operations.""" env = IntegrationTestEnv() - + try: # Get the actual network being used network_name = env.client.network.network - + # Skip if using localhost/solo as they may not have TLS properly configured - if network_name in ('localhost', 'solo', 'local'): + if network_name in ("localhost", "solo", "local"): pytest.skip(f"TLS persistence test skipped for {network_name} network (TLS may not be properly configured)") - + # Check if nodes have address books for verification has_address_books = all(node._address_book is not None for node in env.client.network.nodes) - + # Set TLS configuration env.client.set_transport_security(True) - + # Only enable verification if address books are available if has_address_books: env.client.set_verify_certificates(True) else: env.client.set_verify_certificates(False) - + # Verify initial settings assert env.client.is_transport_security() is True, "TLS should be enabled" - + # Execute multiple queries to verify settings persist for i in range(3): balance_query = CryptoGetAccountBalanceQuery(account_id=env.operator_id) balance = balance_query.execute(env.client) - + # Verify each query succeeds - assert balance is not None, f"Balance query {i+1} should return a result" - assert balance.hbars is not None, f"Balance {i+1} should contain HBAR amount" - + assert balance is not None, f"Balance query {i + 1} should return a result" + assert balance.hbars is not None, f"Balance {i + 1} should contain HBAR amount" + # Verify TLS settings are still applied - assert env.client.is_transport_security() is True, f"TLS should remain enabled after query {i+1}" - + assert env.client.is_transport_security() is True, f"TLS should remain enabled after query {i + 1}" + # Verify nodes still use TLS ports for node in env.client.network.nodes: - assert node._address._is_transport_security() is True, \ - f"Node {node._account_id} should still use TLS port after query {i+1}" - assert node._address._get_port() == 50212, \ - f"Node {node._account_id} should use port 50212 after query {i+1}" + assert node._address._is_transport_security() is True, ( + f"Node {node._account_id} should still use TLS port after query {i + 1}" + ) + assert node._address._get_port() == 50212, ( + f"Node {node._account_id} should use port 50212 after query {i + 1}" + ) finally: env.close() - diff --git a/tests/integration/token_airdrop_transaction_cancel_e2e_test.py b/tests/integration/token_airdrop_transaction_cancel_e2e_test.py index 109428cca..cbb8764af 100644 --- a/tests/integration/token_airdrop_transaction_cancel_e2e_test.py +++ b/tests/integration/token_airdrop_transaction_cancel_e2e_test.py @@ -1,37 +1,36 @@ -from hiero_sdk_python.query.transaction_record_query import TransactionRecordQuery -from hiero_sdk_python.tokens.token_airdrop_transaction_cancel import TokenCancelAirdropTransaction +from __future__ import annotations + import pytest -from hiero_sdk_python.tokens.token_airdrop_transaction import TokenAirdropTransaction -from hiero_sdk_python.tokens.token_nft_transfer import TokenNftTransfer -from hiero_sdk_python.tokens.token_transfer import TokenTransfer from hiero_sdk_python.account.account_create_transaction import AccountCreateTransaction from hiero_sdk_python.crypto.private_key import PrivateKey from hiero_sdk_python.hbar import Hbar -from hiero_sdk_python.tokens.token_mint_transaction import TokenMintTransaction +from hiero_sdk_python.query.transaction_record_query import TransactionRecordQuery from hiero_sdk_python.response_code import ResponseCode +from hiero_sdk_python.tokens.token_airdrop_transaction import TokenAirdropTransaction +from hiero_sdk_python.tokens.token_airdrop_transaction_cancel import TokenCancelAirdropTransaction +from hiero_sdk_python.tokens.token_mint_transaction import TokenMintTransaction +from hiero_sdk_python.tokens.token_nft_transfer import TokenNftTransfer +from hiero_sdk_python.tokens.token_transfer import TokenTransfer from tests.integration.utils import IntegrationTestEnv, create_fungible_token, create_nft_token -#Mint NFT and return serial_number + +# Mint NFT and return serial_number def mint_nft(env: IntegrationTestEnv, nft_id): - token_mint_tx = TokenMintTransaction( - token_id=nft_id, - metadata=[b"NFT Token"] - ) + token_mint_tx = TokenMintTransaction(token_id=nft_id, metadata=[b"NFT Token"]) token_mint_tx.freeze_with(env.client) token_mint_receipt = token_mint_tx.execute(env.client) return token_mint_receipt.serial_numbers[0] + # Perform token airdrop_tx and return list of pending_airdop_records def airdrop_tokens(env: IntegrationTestEnv, account_id, token_id, nft_id, serial_number): airdrop_tx = TokenAirdropTransaction( token_transfers=[ TokenTransfer(token_id, env.client.operator_account_id, -1), - TokenTransfer(token_id, account_id, 1) + TokenTransfer(token_id, account_id, 1), ], - nft_transfers=[ - TokenNftTransfer(nft_id, env.client.operator_account_id, account_id, serial_number) - ] + nft_transfers=[TokenNftTransfer(nft_id, env.client.operator_account_id, account_id, serial_number)], ) airdrop_tx.freeze_with(env.client) airdrop_tx.sign(env.client.operator_private_key) @@ -39,6 +38,7 @@ def airdrop_tokens(env: IntegrationTestEnv, account_id, token_id, nft_id, serial airdrop_record = TransactionRecordQuery(airdrop_receipt.transaction_id).execute(env.client) return airdrop_record.new_pending_airdrops + @pytest.mark.integration def test_integration_token_cancel_airdrop_transaction_can_execute(): env = IntegrationTestEnv() @@ -47,11 +47,9 @@ def test_integration_token_cancel_airdrop_transaction_can_execute(): new_account_private_key = PrivateKey.generate() new_account_public_key = new_account_private_key.public_key() initial_balance = Hbar(2) - + account_transaction = AccountCreateTransaction( - key=new_account_public_key, - initial_balance=initial_balance, - memo="Recipient Account" + key=new_account_public_key, initial_balance=initial_balance, memo="Recipient Account" ) account_transaction.freeze_with(env.client) account_receipt = account_transaction.execute(env.client) @@ -79,6 +77,8 @@ def test_integration_token_cancel_airdrop_transaction_can_execute(): cancel_airdrop_tx.sign(env.client.operator_private_key) cancel_airdrop_receipt = cancel_airdrop_tx.execute(env.client) - assert cancel_airdrop_receipt.status == ResponseCode.SUCCESS, f"Token airdrop failed with status: {ResponseCode(cancel_airdrop_receipt.status).name}" + assert cancel_airdrop_receipt.status == ResponseCode.SUCCESS, ( + f"Token airdrop failed with status: {ResponseCode(cancel_airdrop_receipt.status).name}" + ) finally: - env.close() \ No newline at end of file + env.close() diff --git a/tests/integration/token_airdrop_transaction_claim_e2e_test.py b/tests/integration/token_airdrop_transaction_claim_e2e_test.py index 7559e2d9d..170987c47 100644 --- a/tests/integration/token_airdrop_transaction_claim_e2e_test.py +++ b/tests/integration/token_airdrop_transaction_claim_e2e_test.py @@ -1,17 +1,20 @@ +from __future__ import annotations + import pytest -from hiero_sdk_python.response_code import ResponseCode -from hiero_sdk_python.crypto.private_key import PrivateKey + +from hiero_sdk_python.account.account_id import AccountId from hiero_sdk_python.account.account_update_transaction import AccountUpdateTransaction -from hiero_sdk_python.tokens.token_associate_transaction import TokenAssociateTransaction -from hiero_sdk_python.tokens.token_airdrop_transaction import TokenAirdropTransaction -from hiero_sdk_python.tokens.token_transfer import TokenTransfer -from hiero_sdk_python.tokens.token_airdrop_pending_id import PendingAirdropId +from hiero_sdk_python.crypto.private_key import PrivateKey +from hiero_sdk_python.query.transaction_record_query import TransactionRecordQuery +from hiero_sdk_python.response_code import ResponseCode from hiero_sdk_python.tokens.token_airdrop_claim import TokenClaimAirdropTransaction -from hiero_sdk_python.account.account_id import AccountId +from hiero_sdk_python.tokens.token_airdrop_pending_id import PendingAirdropId +from hiero_sdk_python.tokens.token_airdrop_transaction import TokenAirdropTransaction +from hiero_sdk_python.tokens.token_associate_transaction import TokenAssociateTransaction from hiero_sdk_python.tokens.token_id import TokenId -from hiero_sdk_python.query.transaction_record_query import TransactionRecordQuery -from tests.integration.utils import env, create_fungible_token, create_nft_token -from typing import List +from hiero_sdk_python.tokens.token_transfer import TokenTransfer +from tests.integration.utils import create_fungible_token + pytestmark = pytest.mark.integration @@ -19,6 +22,7 @@ # --- Inline Helpers --- # ====================== + def set_receiver_signature_required(env, account_id: AccountId, account_key: PrivateKey, required: bool): receipt = ( AccountUpdateTransaction() @@ -30,6 +34,7 @@ def set_receiver_signature_required(env, account_id: AccountId, account_key: Pri ) assert receipt.status == ResponseCode.SUCCESS, f"Account update failed: {receipt.status}" + def associate_token(env, account_id: AccountId, account_key: PrivateKey, token_id: TokenId): receipt = ( TokenAssociateTransaction() @@ -41,32 +46,41 @@ def associate_token(env, account_id: AccountId, account_key: PrivateKey, token_i ) assert receipt.status == ResponseCode.SUCCESS, f"Association failed: {receipt.status}" + def submit_airdrop(env, receiver_id: AccountId, token_id: TokenId, amount=1): - tx = TokenAirdropTransaction( - token_transfers=[ - TokenTransfer(token_id, env.operator_id, -amount), - TokenTransfer(token_id, receiver_id, amount), - ] - ).freeze_with(env.client).sign(env.operator_key).execute(env.client) + tx = ( + TokenAirdropTransaction( + token_transfers=[ + TokenTransfer(token_id, env.operator_id, -amount), + TokenTransfer(token_id, receiver_id, amount), + ] + ) + .freeze_with(env.client) + .sign(env.operator_key) + .execute(env.client) + ) assert tx.status == ResponseCode.SUCCESS, f"Airdrop failed: {tx.status}" record = TransactionRecordQuery(tx.transaction_id).execute(env.client) assert record is not None return record + def has_immediate_credit(record, token_id: TokenId, account_id: AccountId, amount=1): token_map = record.token_transfers.get(token_id, {}) return token_map.get(account_id, 0) == amount + def has_new_pending(record): return bool(record.new_pending_airdrops) -def extract_pending_ids(record) -> List[PendingAirdropId]: + +def extract_pending_ids(record) -> list[PendingAirdropId]: """ Extract a list of SDK PendingAirdropId objects from a transaction record. Handles both protobuf objects and already instantiated SDK objects. """ - sdk_ids: List[PendingAirdropId] = [] + sdk_ids: list[PendingAirdropId] = [] for item in record.new_pending_airdrops: if isinstance(item, PendingAirdropId): @@ -91,15 +105,18 @@ def extract_pending_ids(record) -> List[PendingAirdropId]: return sdk_ids + def claim_pending(env, pending_ids, receiver_key): tx = TokenClaimAirdropTransaction().add_pending_airdrop_ids(pending_ids) tx.freeze_with(env.client).sign(receiver_key) return tx.execute(env.client) + # ====================== # --- Integration Tests --- # ====================== + def test_airdrop_becomes_pending_if_not_associated_no_sig_required(env): receiver = env.create_account(initial_hbar=2.0) token_id = create_fungible_token(env) @@ -111,6 +128,7 @@ def test_airdrop_becomes_pending_if_not_associated_no_sig_required(env): assert has_new_pending(record) assert not has_immediate_credit(record, token_id, receiver.id) + def test_immediate_airdrop_if_associated_and_no_sig_required(env): receiver = env.create_account(initial_hbar=2.0) token_id = create_fungible_token(env) @@ -122,6 +140,7 @@ def test_immediate_airdrop_if_associated_and_no_sig_required(env): assert not has_new_pending(record) assert has_immediate_credit(record, token_id, receiver.id) + def test_pending_airdrop_if_unassociated_and_no_sig_required(env): receiver = env.create_account(initial_hbar=2.0) token_id = create_fungible_token(env) @@ -133,6 +152,7 @@ def test_pending_airdrop_if_unassociated_and_no_sig_required(env): # Can't auto claim because not associated assert not has_immediate_credit(record, token_id, receiver.id) + def test_pending_airdrop_if_sig_required_even_if_associated(env): receiver = env.create_account(initial_hbar=2.0) token_id = create_fungible_token(env) @@ -143,6 +163,7 @@ def test_pending_airdrop_if_sig_required_even_if_associated(env): assert has_new_pending(record) assert not has_immediate_credit(record, token_id, receiver.id) + def test_claim_fungible_pending(env): receiver = env.create_account(initial_hbar=2.0) token_id = create_fungible_token(env) @@ -155,6 +176,7 @@ def test_claim_fungible_pending(env): claim_record = TransactionRecordQuery(receipt.transaction_id).execute(env.client) assert has_immediate_credit(claim_record, token_id, receiver.id) + def test_claim_multiple_pendings(env): receiver = env.create_account(initial_hbar=2.0) token_id = create_fungible_token(env) @@ -176,18 +198,18 @@ def test_claim_multiple_pendings(env): def test_claim_fails_without_signature(env): - receiver = env.create_account(initial_hbar=2.0) - token_id = create_fungible_token(env) + receiver = env.create_account(initial_hbar=2.0) + token_id = create_fungible_token(env) - record = submit_airdrop(env, receiver.id, token_id) - ids = extract_pending_ids(record) + record = submit_airdrop(env, receiver.id, token_id) + ids = extract_pending_ids(record) - tx = TokenClaimAirdropTransaction().add_pending_airdrop_ids(ids).freeze_with(env.client) - # Execute without signing - receipt = tx.execute(env.client) + tx = TokenClaimAirdropTransaction().add_pending_airdrop_ids(ids).freeze_with(env.client) + # Execute without signing + receipt = tx.execute(env.client) - # The status should indicate failure - assert receipt.status != ResponseCode.SUCCESS + # The status should indicate failure + assert receipt.status != ResponseCode.SUCCESS def test_cannot_claim_duplicate_ids(env): @@ -201,32 +223,34 @@ def test_cannot_claim_duplicate_ids(env): with pytest.raises(ValueError): tx.add_pending_airdrop_ids([pid, pid]) + def test_cannot_claim_same_pending_twice(env): - receiver = env.create_account(initial_hbar=2.0) - token_id = create_fungible_token(env) + receiver = env.create_account(initial_hbar=2.0) + token_id = create_fungible_token(env) + + record = submit_airdrop(env, receiver.id, token_id) + ids = extract_pending_ids(record) - record = submit_airdrop(env, receiver.id, token_id) - ids = extract_pending_ids(record) + # First claim should succeed + first = claim_pending(env, ids, receiver.key) + assert ResponseCode(first.status) == ResponseCode.SUCCESS - # First claim should succeed - first = claim_pending(env, ids, receiver.key) - assert ResponseCode(first.status) == ResponseCode.SUCCESS + # Second claim should fail (already claimed) + second = claim_pending(env, ids, receiver.key) + assert ResponseCode(second.status) != ResponseCode.SUCCESS - # Second claim should fail (already claimed) - second = claim_pending(env, ids, receiver.key) - assert ResponseCode(second.status) != ResponseCode.SUCCESS def test_claim_max_ids(env): receiver = env.create_account(initial_hbar=2.0) - + tokens = [create_fungible_token(env) for _ in range(TokenClaimAirdropTransaction.MAX_IDS)] - + pending_ids = [] for token_id in tokens: record = submit_airdrop(env, receiver.id, token_id) pending_ids.extend(extract_pending_ids(record)) - + assert len(pending_ids) == TokenClaimAirdropTransaction.MAX_IDS - + receipt = claim_pending(env, pending_ids, receiver.key) assert ResponseCode(receipt.status) == ResponseCode.SUCCESS diff --git a/tests/integration/token_airdrop_transaction_e2e_test.py b/tests/integration/token_airdrop_transaction_e2e_test.py index 2ecfc17a9..19a18b008 100644 --- a/tests/integration/token_airdrop_transaction_e2e_test.py +++ b/tests/integration/token_airdrop_transaction_e2e_test.py @@ -1,51 +1,48 @@ +from __future__ import annotations + import pytest +from hiero_sdk_python.account.account_create_transaction import AccountCreateTransaction +from hiero_sdk_python.crypto.private_key import PrivateKey +from hiero_sdk_python.hbar import Hbar from hiero_sdk_python.query.account_balance_query import CryptoGetAccountBalanceQuery +from hiero_sdk_python.response_code import ResponseCode from hiero_sdk_python.tokens.token_airdrop_transaction import TokenAirdropTransaction from hiero_sdk_python.tokens.token_associate_transaction import TokenAssociateTransaction +from hiero_sdk_python.tokens.token_mint_transaction import TokenMintTransaction from hiero_sdk_python.tokens.token_nft_transfer import TokenNftTransfer from hiero_sdk_python.tokens.token_transfer import TokenTransfer -from hiero_sdk_python.account.account_create_transaction import AccountCreateTransaction -from hiero_sdk_python.crypto.private_key import PrivateKey -from hiero_sdk_python.hbar import Hbar -from hiero_sdk_python.tokens.token_mint_transaction import TokenMintTransaction -from hiero_sdk_python.response_code import ResponseCode from tests.integration.utils import IntegrationTestEnv, create_fungible_token, create_nft_token + def _mint_nft(env: IntegrationTestEnv, nft_id): - token_mint_tx = TokenMintTransaction( - token_id=nft_id, - metadata=[b"NFT Token A"] - ) + token_mint_tx = TokenMintTransaction(token_id=nft_id, metadata=[b"NFT Token A"]) token_mint_tx.freeze_with(env.client) token_mint_receipt = token_mint_tx.execute(env.client) return token_mint_receipt.serial_numbers[0] + def _associate_token_to_account(env: IntegrationTestEnv, account_id, private_key, token_ids): - token_associate_tx = TokenAssociateTransaction( - account_id=account_id, - token_ids=token_ids - ) + token_associate_tx = TokenAssociateTransaction(account_id=account_id, token_ids=token_ids) token_associate_tx.freeze_with(env.client) token_associate_tx.sign(private_key) token_associate_tx.execute(env.client) + @pytest.mark.integration def test_integration_token_airdrop_transaction_can_execute(): env = IntegrationTestEnv() - + try: new_account_private_key = PrivateKey.generate() new_account_public_key = new_account_private_key.public_key() - + initial_balance = Hbar(2) - + account_transaction = AccountCreateTransaction( - key=new_account_public_key, - initial_balance=initial_balance, - memo="Recipient Account" + key=new_account_public_key, initial_balance=initial_balance, memo="Recipient Account" ) - + account_transaction.freeze_with(env.client) account_receipt = account_transaction.execute(env.client) new_account_id = account_receipt.account_id @@ -60,18 +57,16 @@ def test_integration_token_airdrop_transaction_can_execute(): nft_id = create_nft_token(env) assert nft_id is not None - serial_number =_mint_nft(env, nft_id) + serial_number = _mint_nft(env, nft_id) _associate_token_to_account(env, new_account_id, new_account_private_key, [token_id, nft_id]) - + airdrop_tx = TokenAirdropTransaction( token_transfers=[ TokenTransfer(token_id, env.client.operator_account_id, -1), - TokenTransfer(token_id, new_account_id, 1) + TokenTransfer(token_id, new_account_id, 1), ], - nft_transfers=[ - TokenNftTransfer(nft_id, env.client.operator_account_id, new_account_id, serial_number) - ] + nft_transfers=[TokenNftTransfer(nft_id, env.client.operator_account_id, new_account_id, serial_number)], ) airdrop_tx.freeze_with(env.client) airdrop_tx.sign(env.client.operator_private_key) @@ -79,15 +74,17 @@ def test_integration_token_airdrop_transaction_can_execute(): balance_after_tx = CryptoGetAccountBalanceQuery(account_id=new_account_id).execute(env.client) - assert airdrop_receipt.status == ResponseCode.SUCCESS, f"Token airdrop failed with status: {ResponseCode(airdrop_receipt.status).name}" - + assert airdrop_receipt.status == ResponseCode.SUCCESS, ( + f"Token airdrop failed with status: {ResponseCode(airdrop_receipt.status).name}" + ) + assert balance_before_tx.token_balances == {} assert len(balance_after_tx.token_balances) == 2 - + assert token_id in balance_after_tx.token_balances assert balance_after_tx.token_balances[token_id] == 1 assert nft_id in balance_after_tx.token_balances assert balance_after_tx.token_balances[nft_id] == 1 finally: - env.close() + env.close() diff --git a/tests/integration/token_associate_transaction_e2e_test.py b/tests/integration/token_associate_transaction_e2e_test.py index af2c3aeb0..d348f0281 100644 --- a/tests/integration/token_associate_transaction_e2e_test.py +++ b/tests/integration/token_associate_transaction_e2e_test.py @@ -1,43 +1,39 @@ +from __future__ import annotations + import pytest +from hiero_sdk_python.account.account_create_transaction import AccountCreateTransaction from hiero_sdk_python.crypto.private_key import PrivateKey from hiero_sdk_python.hbar import Hbar +from hiero_sdk_python.response_code import ResponseCode from hiero_sdk_python.tokens.token_associate_transaction import TokenAssociateTransaction from hiero_sdk_python.tokens.token_dissociate_transaction import TokenDissociateTransaction -from hiero_sdk_python.account.account_create_transaction import AccountCreateTransaction -from hiero_sdk_python.response_code import ResponseCode from tests.integration.utils import IntegrationTestEnv, create_fungible_token @pytest.mark.integration def test_integration_token_associate_transaction_can_execute(): env = IntegrationTestEnv() - + try: new_account_private_key = PrivateKey.generate() new_account_public_key = new_account_private_key.public_key() - + initial_balance = Hbar(2) assert initial_balance.to_tinybars() == 200000000 - + account_transaction = AccountCreateTransaction( - key=new_account_public_key, - initial_balance=initial_balance, - memo="Recipient Account" + key=new_account_public_key, initial_balance=initial_balance, memo="Recipient Account" ) - + account_transaction.freeze_with(env.client) account_receipt = account_transaction.execute(env.client) new_account_id = account_receipt.account_id - + token_id = create_fungible_token(env) assert token_id is not None, "TokenID not found in receipt. Token may not have been created." - - associate_transaction = ( - TokenAssociateTransaction() - .set_account_id(new_account_id) - .set_token_ids([token_id]) - ) + + associate_transaction = TokenAssociateTransaction().set_account_id(new_account_id).set_token_ids([token_id]) associate_transaction.freeze_with(env.client) @@ -54,18 +50,19 @@ def test_integration_token_associate_transaction_can_execute(): associate_transaction.sign(new_account_private_key) associate_receipt = associate_transaction.execute(env.client) - - assert associate_receipt.status == ResponseCode.SUCCESS, f"Token association failed with status: {ResponseCode(associate_receipt.status).name}" - - dissociate_transaction = TokenDissociateTransaction( - account_id=new_account_id, - token_ids=[token_id] + + assert associate_receipt.status == ResponseCode.SUCCESS, ( + f"Token association failed with status: {ResponseCode(associate_receipt.status).name}" ) - + + dissociate_transaction = TokenDissociateTransaction(account_id=new_account_id, token_ids=[token_id]) + dissociate_transaction.freeze_with(env.client) dissociate_transaction.sign(new_account_private_key) dissociate_receipt = dissociate_transaction.execute(env.client) - - assert dissociate_receipt.status == ResponseCode.SUCCESS, f"Token dissociation failed with status: {ResponseCode(dissociate_receipt.status).name}" + + assert dissociate_receipt.status == ResponseCode.SUCCESS, ( + f"Token dissociation failed with status: {ResponseCode(dissociate_receipt.status).name}" + ) finally: - env.close() + env.close() diff --git a/tests/integration/token_burn_transaction_e2e_test.py b/tests/integration/token_burn_transaction_e2e_test.py index 3aeff75ca..664d0d053 100644 --- a/tests/integration/token_burn_transaction_e2e_test.py +++ b/tests/integration/token_burn_transaction_e2e_test.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import pytest from hiero_sdk_python.query.token_info_query import TokenInfoQuery @@ -10,118 +12,105 @@ @pytest.mark.integration def test_integration_token_burn_transaction_can_execute(): env = IntegrationTestEnv() - + try: token_id = create_fungible_token(env) assert token_id is not None - + info = TokenInfoQuery(token_id).execute(env.client) assert info.total_supply == 1000, f"Total supply is not 1000, but {info.total_supply}" - - receipt = ( - TokenBurnTransaction() - .set_token_id(token_id) - .set_amount(10) - .execute(env.client) + + receipt = TokenBurnTransaction().set_token_id(token_id).set_amount(10).execute(env.client) + + assert receipt.status == ResponseCode.SUCCESS, ( + f"Token burn failed with status: {ResponseCode(receipt.status).name}" ) - - assert receipt.status == ResponseCode.SUCCESS, f"Token burn failed with status: {ResponseCode(receipt.status).name}" - + info = TokenInfoQuery(token_id).execute(env.client) assert info.total_supply == 990, f"Total supply is not 990, but {info.total_supply}" finally: env.close() + @pytest.mark.integration def test_integration_token_burn_transaction_no_amount(): env = IntegrationTestEnv() - + try: token_id = create_fungible_token(env) assert token_id is not None - + # Execute token burn with no amount specified - receipt = ( - TokenBurnTransaction() - .set_token_id(token_id) - .execute(env.client) + receipt = TokenBurnTransaction().set_token_id(token_id).execute(env.client) + + assert receipt.status == ResponseCode.SUCCESS, ( + f"Token burn failed with status: {ResponseCode(receipt.status).name}" ) - - assert receipt.status == ResponseCode.SUCCESS, f"Token burn failed with status: {ResponseCode(receipt.status).name}" - + info = TokenInfoQuery(token_id).execute(env.client) assert info.total_supply == 1000, f"Total supply should remain 1000, but is {info.total_supply}" finally: env.close() + @pytest.mark.integration def test_integration_token_burn_transaction_nft(): env = IntegrationTestEnv() - + try: # Create NFT token token_id = create_nft_token(env) assert token_id is not None - + # Mint an NFT - receipt = ( - TokenMintTransaction() - .set_token_id(token_id) - .set_metadata([b"Metadata"]) - .execute(env.client) + receipt = TokenMintTransaction().set_token_id(token_id).set_metadata([b"Metadata"]).execute(env.client) + assert receipt.status == ResponseCode.SUCCESS, ( + f"Token mint failed with status: {ResponseCode(receipt.status).name}" ) - assert receipt.status == ResponseCode.SUCCESS, f"Token mint failed with status: {ResponseCode(receipt.status).name}" - + # Try to burn the NFT - receipt = ( - TokenBurnTransaction() - .set_token_id(token_id) - .set_serials([1]) - .execute(env.client) + receipt = TokenBurnTransaction().set_token_id(token_id).set_serials([1]).execute(env.client) + assert receipt.status == ResponseCode.SUCCESS, ( + f"Token burn failed with status: {ResponseCode(receipt.status).name}" ) - assert receipt.status == ResponseCode.SUCCESS, f"Token burn failed with status: {ResponseCode(receipt.status).name}" - + info = TokenInfoQuery(token_id).execute(env.client) assert info.total_supply == 0, f"Total supply is not 0, but {info.totalSupply}" finally: env.close() + @pytest.mark.integration def test_integration_token_burn_transaction_fails_invalid_metadata(): env = IntegrationTestEnv() - + try: # Create NFT token token_id = create_nft_token(env) assert token_id is not None - + # Attempt to burn an NFT using set_amount() instead of set_serials() # This should fail since NFTs require serial numbers for burning - receipt = ( - TokenBurnTransaction() - .set_token_id(token_id) - .set_amount(1) - .execute(env.client) + receipt = TokenBurnTransaction().set_token_id(token_id).set_amount(1).execute(env.client) + assert receipt.status == ResponseCode.INVALID_TOKEN_BURN_METADATA, ( + f"Token burn should have failed with status: {ResponseCode(receipt.status).name}" ) - assert receipt.status == ResponseCode.INVALID_TOKEN_BURN_METADATA, f"Token burn should have failed with status: {ResponseCode(receipt.status).name}" finally: env.close() - + + @pytest.mark.integration def test_integration_token_burn_transaction_fails_no_supply_key(): env = IntegrationTestEnv() - + try: # Create fungible token without supply key token_id = create_fungible_token(env, [lambda tx: tx.set_supply_key(None)]) - + # Try to burn tokens - should fail without supply key - receipt = ( - TokenBurnTransaction() - .set_token_id(token_id) - .set_amount(10) - .execute(env.client) + receipt = TokenBurnTransaction().set_token_id(token_id).set_amount(10).execute(env.client) + assert receipt.status == ResponseCode.TOKEN_HAS_NO_SUPPLY_KEY, ( + f"Token burn should have failed with TOKEN_HAS_NO_SUPPLY_KEY but got: {ResponseCode(receipt.status).name}" ) - assert receipt.status == ResponseCode.TOKEN_HAS_NO_SUPPLY_KEY, f"Token burn should have failed with TOKEN_HAS_NO_SUPPLY_KEY but got: {ResponseCode(receipt.status).name}" finally: - env.close() \ No newline at end of file + env.close() diff --git a/tests/integration/token_create_transaction_e2e_test.py b/tests/integration/token_create_transaction_e2e_test.py index bf471cd78..54504741d 100644 --- a/tests/integration/token_create_transaction_e2e_test.py +++ b/tests/integration/token_create_transaction_e2e_test.py @@ -1,27 +1,29 @@ +from __future__ import annotations + import datetime + import pytest -from hiero_sdk_python.Duration import Duration from hiero_sdk_python.crypto.private_key import PrivateKey +from hiero_sdk_python.Duration import Duration +from hiero_sdk_python.query.token_info_query import TokenInfoQuery from hiero_sdk_python.response_code import ResponseCode +from hiero_sdk_python.timestamp import Timestamp from hiero_sdk_python.tokens.custom_fixed_fee import CustomFixedFee +from hiero_sdk_python.tokens.token_create_transaction import TokenCreateTransaction, TokenParams from hiero_sdk_python.tokens.token_fee_schedule_update_transaction import TokenFeeScheduleUpdateTransaction -from hiero_sdk_python.crypto.public_key import PublicKey -from hiero_sdk_python.transaction.transaction import Transaction from hiero_sdk_python.tokens.token_type import TokenType -from hiero_sdk_python.query.token_info_query import TokenInfoQuery -from hiero_sdk_python.timestamp import Timestamp -from hiero_sdk_python.tokens.token_create_transaction import TokenCreateTransaction, TokenParams +from hiero_sdk_python.transaction.transaction import Transaction from tests.integration.utils import IntegrationTestEnv, create_fungible_token, create_nft_token @pytest.mark.integration def test_integration_fungible_token_create_transaction_can_execute(): env = IntegrationTestEnv() - + try: token_id = create_fungible_token(env) - + assert token_id is not None, "TokenID not found in receipt. Token may not have been created." finally: env.close() @@ -30,14 +32,15 @@ def test_integration_fungible_token_create_transaction_can_execute(): @pytest.mark.integration def test_integration_nft_token_create_transaction_can_execute(): env = IntegrationTestEnv() - + try: token_id = create_nft_token(env) - + assert token_id is not None, "TokenID not found in receipt. Token may not have been created." finally: env.close() + @pytest.mark.integration def test_fungible_token_create_sets_default_autorenew_values(): """Test that when no expiration_time or auto_renew_account is explicitly provided default values are set""" @@ -56,12 +59,13 @@ def test_fungible_token_create_sets_default_autorenew_values(): token_id = receipt.token_id token_info = TokenInfoQuery(token_id=token_id).execute(env.client) - + assert token_info.auto_renew_period == Duration(7890000), "Token auto renew period mismatch" assert token_info.auto_renew_account == env.client.operator_account_id, "Token auto renew account mismatch" finally: env.close() + @pytest.mark.integration def test_fungible_token_create_with_expiration_time(): """Test create fungible token with expiration_time""" @@ -74,24 +78,25 @@ def test_fungible_token_create_with_expiration_time(): initial_supply=1, treasury_account_id=env.client.operator_account_id, expiration_time=expiration_time, - token_type=TokenType.FUNGIBLE_COMMON + token_type=TokenType.FUNGIBLE_COMMON, ) receipt = TokenCreateTransaction(params).freeze_with(env.client).execute(env.client) assert receipt.token_id is not None, "TokenID not found in receipt. Token may not have been created." - + token_id = receipt.token_id token_info = TokenInfoQuery(token_id=token_id).execute(env.client) - assert token_info.expiry.seconds == expiration_time.seconds, "Token expiry mismatch" + assert token_info.expiry.seconds == expiration_time.seconds, "Token expiry mismatch" assert token_info.auto_renew_period == Duration(0) finally: env.close() + @pytest.mark.integration def test_fungible_token_create_auto_assigns_account_if_autorenew_period_present(): """ - Test that if an auto_renew_period is set but auto_renew_account is not set + Test that if an auto_renew_period is set but auto_renew_account is not set it get automatically assigns the client's operator account or transaction_id account_id. """ env = IntegrationTestEnv() @@ -106,17 +111,20 @@ def test_fungible_token_create_auto_assigns_account_if_autorenew_period_present( ) receipt = TokenCreateTransaction(params).freeze_with(env.client).execute(env.client) - assert receipt.token_id is not None,"TokenID not found in receipt. Token may not have been created." + assert receipt.token_id is not None, "TokenID not found in receipt. Token may not have been created." token_id = receipt.token_id token_info = TokenInfoQuery(token_id=token_id).execute(env.client) - - assert token_info.auto_renew_period == Duration(7890000), "Token auto renew period mismatch" # Defaut around ~90 days + + assert token_info.auto_renew_period == Duration(7890000), ( + "Token auto renew period mismatch" + ) # Defaut around ~90 days # Client operator account if no auto_renew_account set assert token_info.auto_renew_account == env.client.operator_account_id, "Token auto renew account missmatch" finally: env.close() + @pytest.mark.integration def test_fungible_token_create_with_fee_schedule_key(): """ @@ -131,7 +139,7 @@ def test_fungible_token_create_with_fee_schedule_key(): token_symbol="HFT", initial_supply=1, treasury_account_id=env.client.operator_account_id, - token_type=TokenType.FUNGIBLE_COMMON + token_type=TokenType.FUNGIBLE_COMMON, ) receipt = ( @@ -144,8 +152,10 @@ def test_fungible_token_create_with_fee_schedule_key(): token_id = receipt.token_id token_info = TokenInfoQuery(token_id=token_id).execute(env.client) - - assert token_info.fee_schedule_key.to_string() == fee_schedule_key.public_key().to_string(), "Fee schedule key missmatch" + + assert token_info.fee_schedule_key.to_string() == fee_schedule_key.public_key().to_string(), ( + "Fee schedule key missmatch" + ) assert len(token_info.custom_fees) == 0 # Validate Fee schedule key @@ -160,7 +170,7 @@ def test_fungible_token_create_with_fee_schedule_key(): assert update_receipt.status == ResponseCode.SUCCESS token_info = TokenInfoQuery(token_id=token_id).execute(env.client) - + assert len(token_info.custom_fees) == 1 assert token_info.custom_fees[0].amount == 1 assert token_info.custom_fees[0].fee_collector_account_id == env.client.operator_account_id @@ -168,6 +178,7 @@ def test_fungible_token_create_with_fee_schedule_key(): finally: env.close() + @pytest.mark.integration def test_token_create_non_custodial_flow(): """ @@ -177,7 +188,6 @@ def test_token_create_non_custodial_flow(): 3. User (with the PrivateKey) signs the bytes. 4. Operator executes the signed transaction. """ - env = IntegrationTestEnv() client = env.client @@ -189,7 +199,7 @@ def test_token_create_non_custodial_flow(): # ================================================================= # STEP 1 & 2: OPERATOR (CLIENT) BUILDS THE TRANSACTION # ================================================================= - + tx = ( TokenCreateTransaction() .set_token_name("NonCustodialToken") @@ -206,37 +216,38 @@ def test_token_create_non_custodial_flow(): # ================================================================= # STEP 3: USER (SIGNER) SIGNS THE TRANSACTION # ================================================================= - + tx_from_bytes = Transaction.from_bytes(tx_bytes) tx_from_bytes.sign(user_private_key) # ================================================================= # STEP 4: OPERATOR (CLIENT) EXECUTES THE SIGNED TX # ================================================================= - + receipt = tx_from_bytes.execute(client) - + assert receipt is not None token_id = receipt.token_id assert token_id is not None - + # PROOF: Query the new token and check if the admin key matches token_info = TokenInfoQuery(token_id=token_id).execute(client) - + assert token_info.admin_key is not None - + # This is the STRONG assertion: # Compare the bytes of the key from the network # with the bytes of the key we originally used. admin_key_bytes = token_info.admin_key.to_bytes_raw() public_key_bytes = user_public_key.to_bytes_raw() - + assert admin_key_bytes == public_key_bytes finally: # Clean up the environment env.close() + def test_fungible_token_create_with_metadata(): """ Test creating a fungible token with on-ledger metadata and verifying @@ -257,12 +268,7 @@ def test_fungible_token_create_with_metadata(): ) # Build, freeze and execute the token creation transaction with metadata - receipt = ( - TokenCreateTransaction(params) - .set_metadata(metadata) - .freeze_with(env.client) - .execute(env.client) - ) + receipt = TokenCreateTransaction(params).set_metadata(metadata).freeze_with(env.client).execute(env.client) assert receipt.token_id is not None, "TokenID not found in receipt. Token may not have been created." diff --git a/tests/integration/token_create_with_custom_fee_e2e_test.py b/tests/integration/token_create_with_custom_fee_e2e_test.py index 329f7b099..9ec559c3f 100644 --- a/tests/integration/token_create_with_custom_fee_e2e_test.py +++ b/tests/integration/token_create_with_custom_fee_e2e_test.py @@ -1,12 +1,16 @@ +from __future__ import annotations + import pytest -from hiero_sdk_python.tokens.token_create_transaction import TokenCreateTransaction, TokenParams, TokenKeys -from hiero_sdk_python.tokens.token_type import TokenType -from hiero_sdk_python.tokens.supply_type import SupplyType + +from hiero_sdk_python.query.token_info_query import TokenInfoQuery from hiero_sdk_python.tokens.custom_fixed_fee import CustomFixedFee +from hiero_sdk_python.tokens.supply_type import SupplyType +from hiero_sdk_python.tokens.token_create_transaction import TokenCreateTransaction, TokenKeys, TokenParams from hiero_sdk_python.tokens.token_id import TokenId -from hiero_sdk_python.query.token_info_query import TokenInfoQuery +from hiero_sdk_python.tokens.token_type import TokenType from tests.integration.utils import IntegrationTestEnv + @pytest.mark.integration def test_token_create_with_custom_fee_e2e(): env = IntegrationTestEnv() @@ -32,9 +36,7 @@ def test_token_create_with_custom_fee_e2e(): keys = TokenKeys(admin_key=env.operator_key, supply_key=env.operator_key) transaction = ( - TokenCreateTransaction(token_params=token_params, keys=keys) - .freeze_with(env.client) - .sign(env.operator_key) + TokenCreateTransaction(token_params=token_params, keys=keys).freeze_with(env.client).sign(env.operator_key) ) receipt = transaction.execute(env.client) @@ -52,4 +54,4 @@ def test_token_create_with_custom_fee_e2e(): assert retrieved_fee.amount == 10 assert retrieved_fee.fee_collector_account_id == env.operator_id finally: - env.close() \ No newline at end of file + env.close() diff --git a/tests/integration/token_delete_transaction_e2e_test.py b/tests/integration/token_delete_transaction_e2e_test.py index a7ddadc87..4e959d40f 100644 --- a/tests/integration/token_delete_transaction_e2e_test.py +++ b/tests/integration/token_delete_transaction_e2e_test.py @@ -1,22 +1,27 @@ +from __future__ import annotations + import pytest -from tests.integration.utils import IntegrationTestEnv, create_fungible_token -from hiero_sdk_python.tokens.token_delete_transaction import TokenDeleteTransaction from hiero_sdk_python.response_code import ResponseCode +from hiero_sdk_python.tokens.token_delete_transaction import TokenDeleteTransaction +from tests.integration.utils import IntegrationTestEnv, create_fungible_token + @pytest.mark.integration def test_integration_token_delete_transaction_can_execute(): env = IntegrationTestEnv() - + try: token_id = create_fungible_token(env) - + assert token_id is not None, "TokenID not found in receipt. Token may not have been created." - + transaction = TokenDeleteTransaction(token_id=token_id) transaction.freeze_with(env.client) receipt = transaction.execute(env.client) - - assert receipt.status == ResponseCode.SUCCESS, f"Token deletion failed with status: {ResponseCode(receipt.status).name}" + + assert receipt.status == ResponseCode.SUCCESS, ( + f"Token deletion failed with status: {ResponseCode(receipt.status).name}" + ) finally: - env.close() \ No newline at end of file + env.close() diff --git a/tests/integration/token_dissociate_transaction_e2e_test.py b/tests/integration/token_dissociate_transaction_e2e_test.py index 99a7df68b..894b371db 100644 --- a/tests/integration/token_dissociate_transaction_e2e_test.py +++ b/tests/integration/token_dissociate_transaction_e2e_test.py @@ -1,58 +1,56 @@ +from __future__ import annotations + import pytest from hiero_sdk_python.account.account_create_transaction import AccountCreateTransaction from hiero_sdk_python.crypto.private_key import PrivateKey from hiero_sdk_python.hbar import Hbar +from hiero_sdk_python.response_code import ResponseCode from hiero_sdk_python.tokens.token_associate_transaction import TokenAssociateTransaction from hiero_sdk_python.tokens.token_dissociate_transaction import TokenDissociateTransaction -from hiero_sdk_python.response_code import ResponseCode from tests.integration.utils import IntegrationTestEnv, create_fungible_token @pytest.mark.integration def test_integration_token_dissociate_transaction_can_execute(): env = IntegrationTestEnv() - + try: new_account_private_key = PrivateKey.generate() new_account_public_key = new_account_private_key.public_key() - + initial_balance = Hbar(2) - + assert initial_balance.to_tinybars() == 200000000 - + account_transaction = AccountCreateTransaction( - key=new_account_public_key, - initial_balance=initial_balance, - memo="Recipient Account" + key=new_account_public_key, initial_balance=initial_balance, memo="Recipient Account" ) - + account_transaction.freeze_with(env.client) account_receipt = account_transaction.execute(env.client) new_account_id = account_receipt.account_id - + token_id = create_fungible_token(env) - - associate_transaction = TokenAssociateTransaction( - account_id=new_account_id, - token_ids=[token_id] - ) + + associate_transaction = TokenAssociateTransaction(account_id=new_account_id, token_ids=[token_id]) associate_transaction.freeze_with(env.client) associate_transaction.sign(new_account_private_key) - + receipt = associate_transaction.execute(env.client) - - assert receipt.status == ResponseCode.SUCCESS, f"Token association failed with status: {ResponseCode(receipt.status).name}" - - dissociate_transaction = TokenDissociateTransaction( - account_id=new_account_id, - token_ids=[token_id] + + assert receipt.status == ResponseCode.SUCCESS, ( + f"Token association failed with status: {ResponseCode(receipt.status).name}" ) + + dissociate_transaction = TokenDissociateTransaction(account_id=new_account_id, token_ids=[token_id]) dissociate_transaction.freeze_with(env.client) dissociate_transaction.sign(new_account_private_key) - + receipt = dissociate_transaction.execute(env.client) - - assert receipt.status == ResponseCode.SUCCESS, f"Token dissociation failed with status: {ResponseCode(receipt.status).name}" + + assert receipt.status == ResponseCode.SUCCESS, ( + f"Token dissociation failed with status: {ResponseCode(receipt.status).name}" + ) finally: - env.close() \ No newline at end of file + env.close() diff --git a/tests/integration/token_fee_schedule_update_transaction_e2e_test.py b/tests/integration/token_fee_schedule_update_transaction_e2e_test.py index 13a33c1d4..14a7bfd0d 100644 --- a/tests/integration/token_fee_schedule_update_transaction_e2e_test.py +++ b/tests/integration/token_fee_schedule_update_transaction_e2e_test.py @@ -1,25 +1,20 @@ +from __future__ import annotations + import pytest -from hiero_sdk_python.hbar import Hbar -from tests.integration.utils import IntegrationTestEnv, create_fungible_token, create_nft_token -from hiero_sdk_python.tokens.token_create_transaction import ( - TokenCreateTransaction, - TokenParams, - TokenKeys, -) -from hiero_sdk_python.tokens.token_type import TokenType -from hiero_sdk_python.tokens.supply_type import SupplyType -from hiero_sdk_python.tokens.custom_fixed_fee import CustomFixedFee -from hiero_sdk_python.tokens.custom_royalty_fee import CustomRoyaltyFee -from hiero_sdk_python.tokens.custom_fractional_fee import CustomFractionalFee +from hiero_sdk_python.crypto.private_key import PrivateKey from hiero_sdk_python.query.token_info_query import TokenInfoQuery from hiero_sdk_python.response_code import ResponseCode +from hiero_sdk_python.tokens.custom_fixed_fee import CustomFixedFee +from hiero_sdk_python.tokens.custom_fractional_fee import CustomFractionalFee +from hiero_sdk_python.tokens.custom_royalty_fee import CustomRoyaltyFee +from hiero_sdk_python.tokens.token_delete_transaction import TokenDeleteTransaction from hiero_sdk_python.tokens.token_fee_schedule_update_transaction import ( TokenFeeScheduleUpdateTransaction, ) -from hiero_sdk_python.crypto.private_key import PrivateKey from hiero_sdk_python.tokens.token_id import TokenId -from hiero_sdk_python.tokens.token_delete_transaction import TokenDeleteTransaction +from tests.integration.utils import IntegrationTestEnv, create_fungible_token, create_nft_token + @pytest.mark.integration def test_token_fee_schedule_update_e2e_fungible(): @@ -27,10 +22,7 @@ def test_token_fee_schedule_update_e2e_fungible(): env = IntegrationTestEnv() try: fee_schedule_key = env.operator_key - token_id = create_fungible_token( - env, - opts=[lambda tx: tx.set_fee_schedule_key(fee_schedule_key)] - ) + token_id = create_fungible_token(env, opts=[lambda tx: tx.set_fee_schedule_key(fee_schedule_key)]) assert token_id is not None new_fee = CustomFixedFee( @@ -44,8 +36,9 @@ def test_token_fee_schedule_update_e2e_fungible(): update_tx.sign(fee_schedule_key) update_receipt = update_tx.execute(env.client) - assert update_receipt.status == ResponseCode.SUCCESS, \ + assert update_receipt.status == ResponseCode.SUCCESS, ( f"Fee schedule update failed: {ResponseCode(update_receipt.status).name}" + ) token_info = TokenInfoQuery().set_token_id(token_id).execute(env.client) assert token_info.custom_fees and len(token_info.custom_fees) == 1 @@ -54,21 +47,19 @@ def test_token_fee_schedule_update_e2e_fungible(): finally: env.close() + @pytest.mark.integration def test_token_fee_schedule_update_e2e_nft(): """Test updating fee schedule successfully for an NFT.""" env = IntegrationTestEnv() try: fee_schedule_key = env.operator_key - token_id = create_nft_token( - env, - opts=[lambda tx: tx.set_fee_schedule_key(fee_schedule_key)] - ) + token_id = create_nft_token(env, opts=[lambda tx: tx.set_fee_schedule_key(fee_schedule_key)]) assert token_id is not None new_fee = CustomRoyaltyFee( numerator=1, - denominator=10, # 10% royalty + denominator=10, # 10% royalty fee_collector_account_id=env.operator_id, ) update_tx = TokenFeeScheduleUpdateTransaction() @@ -78,8 +69,9 @@ def test_token_fee_schedule_update_e2e_nft(): update_tx.sign(fee_schedule_key) update_receipt = update_tx.execute(env.client) - assert update_receipt.status == ResponseCode.SUCCESS, \ + assert update_receipt.status == ResponseCode.SUCCESS, ( f"Fee schedule update failed: {ResponseCode(update_receipt.status).name}" + ) token_info = TokenInfoQuery().set_token_id(token_id).execute(env.client) assert token_info.custom_fees and len(token_info.custom_fees) == 1 @@ -87,27 +79,21 @@ def test_token_fee_schedule_update_e2e_nft(): finally: env.close() + @pytest.mark.integration def test_token_fee_schedule_update_fails_with_invalid_signature(): """Test failure with an incorrect signature.""" env = IntegrationTestEnv() try: - fee_schedule_key = PrivateKey.generate() # Must be a new key - token_id = create_fungible_token( - env, - opts=[lambda tx: tx.set_fee_schedule_key(fee_schedule_key)] - ) + fee_schedule_key = PrivateKey.generate() # Must be a new key + token_id = create_fungible_token(env, opts=[lambda tx: tx.set_fee_schedule_key(fee_schedule_key)]) assert token_id is not None wrong_key = PrivateKey.generate() new_fee = CustomFixedFee(amount=50, fee_collector_account_id=env.operator_id) - update_tx = ( - TokenFeeScheduleUpdateTransaction() - .set_token_id(token_id) - .set_custom_fees([new_fee]) - ) + update_tx = TokenFeeScheduleUpdateTransaction().set_token_id(token_id).set_custom_fees([new_fee]) update_tx.freeze_with(env.client) - update_tx.sign(wrong_key) # Sign with the wrong key + update_tx.sign(wrong_key) # Sign with the wrong key update_receipt = update_tx.execute(env.client) assert update_receipt.status == ResponseCode.INVALID_SIGNATURE, ( @@ -124,11 +110,7 @@ def test_token_fee_schedule_update_fails_with_invalid_token_id(): try: invalid_token_id = TokenId(0, 0, 9999999) new_fee = CustomFixedFee(amount=50, fee_collector_account_id=env.operator_id) - update_tx = ( - TokenFeeScheduleUpdateTransaction() - .set_token_id(invalid_token_id) - .set_custom_fees([new_fee]) - ) + update_tx = TokenFeeScheduleUpdateTransaction().set_token_id(invalid_token_id).set_custom_fees([new_fee]) update_receipt = update_tx.execute(env.client) assert update_receipt.status == ResponseCode.INVALID_TOKEN_ID, ( f"Expected INVALID_TOKEN_ID, but got {ResponseCode(update_receipt.status).name}" @@ -145,11 +127,7 @@ def test_token_fee_schedule_update_fails_for_deleted_token(): admin_key = env.operator_key fee_schedule_key = env.operator_key token_id = create_fungible_token( - env, - opts=[ - lambda tx: tx.set_admin_key(admin_key), - lambda tx: tx.set_fee_schedule_key(fee_schedule_key) - ] + env, opts=[lambda tx: tx.set_admin_key(admin_key), lambda tx: tx.set_fee_schedule_key(fee_schedule_key)] ) assert token_id is not None @@ -157,11 +135,7 @@ def test_token_fee_schedule_update_fails_for_deleted_token(): assert delete_receipt.status == ResponseCode.SUCCESS, "Token deletion failed" new_fee = CustomFixedFee(amount=50, fee_collector_account_id=env.operator_id) - update_tx = ( - TokenFeeScheduleUpdateTransaction() - .set_token_id(token_id) - .set_custom_fees([new_fee]) - ) + update_tx = TokenFeeScheduleUpdateTransaction().set_token_id(token_id).set_custom_fees([new_fee]) update_receipt = update_tx.execute(env.client) assert update_receipt.status == ResponseCode.TOKEN_WAS_DELETED, ( f"Expected TOKEN_WAS_DELETED, but got {ResponseCode(update_receipt.status).name}" @@ -169,63 +143,49 @@ def test_token_fee_schedule_update_fails_for_deleted_token(): finally: env.close() + @pytest.mark.integration def test_token_fee_schedule_update_fails_royalty_on_fungible(): """Test failure when adding a royalty fee to a fungible token.""" env = IntegrationTestEnv() try: fee_schedule_key = env.operator_key - token_id = create_fungible_token( - env, - opts=[lambda tx: tx.set_fee_schedule_key(fee_schedule_key)] - ) + token_id = create_fungible_token(env, opts=[lambda tx: tx.set_fee_schedule_key(fee_schedule_key)]) assert token_id is not None new_fee = CustomRoyaltyFee(numerator=1, denominator=10, fee_collector_account_id=env.operator_id) - update_tx = ( - TokenFeeScheduleUpdateTransaction() - .set_token_id(token_id) - .set_custom_fees([new_fee]) - ) + update_tx = TokenFeeScheduleUpdateTransaction().set_token_id(token_id).set_custom_fees([new_fee]) update_tx.freeze_with(env.client).sign(fee_schedule_key) update_receipt = update_tx.execute(env.client) - - assert update_receipt.status == ResponseCode.CUSTOM_ROYALTY_FEE_ONLY_ALLOWED_FOR_NON_FUNGIBLE_UNIQUE, \ + + assert update_receipt.status == ResponseCode.CUSTOM_ROYALTY_FEE_ONLY_ALLOWED_FOR_NON_FUNGIBLE_UNIQUE, ( f"Expected CUSTOM_ROYALTY_FEE_ONLY_ALLOWED_FOR_NON_FUNGIBLE_UNIQUE, but got {ResponseCode(update_receipt.status).name}" + ) finally: env.close() + @pytest.mark.integration def test_token_fee_schedule_update_fails_fractional_on_nft(): """Test failure when adding a fractional fee to an NFT.""" env = IntegrationTestEnv() try: fee_schedule_key = env.operator_key - token_id = create_nft_token( - env, - opts=[lambda tx: tx.set_fee_schedule_key(fee_schedule_key)] - ) + token_id = create_nft_token(env, opts=[lambda tx: tx.set_fee_schedule_key(fee_schedule_key)]) assert token_id is not None new_fee = CustomFractionalFee( - numerator=1, - denominator=100, - min_amount=1, - max_amount=10, - fee_collector_account_id=env.operator_id - ) - update_tx = ( - TokenFeeScheduleUpdateTransaction() - .set_token_id(token_id) - .set_custom_fees([new_fee]) + numerator=1, denominator=100, min_amount=1, max_amount=10, fee_collector_account_id=env.operator_id ) + update_tx = TokenFeeScheduleUpdateTransaction().set_token_id(token_id).set_custom_fees([new_fee]) update_tx.freeze_with(env.client).sign(fee_schedule_key) update_receipt = update_tx.execute(env.client) - - assert update_receipt.status == ResponseCode.CUSTOM_FRACTIONAL_FEE_ONLY_ALLOWED_FOR_FUNGIBLE_COMMON, \ + + assert update_receipt.status == ResponseCode.CUSTOM_FRACTIONAL_FEE_ONLY_ALLOWED_FOR_FUNGIBLE_COMMON, ( f"Expected CUSTOM_FRACTIONAL_FEE_ONLY_ALLOWED_FOR_FUNGIBLE_COMMON, but got {ResponseCode(update_receipt.status).name}" - - #Additional check to ensure the string representation works as expected + ) + + # Additional check to ensure the string representation works as expected fee_str = new_fee.__str__() assert "Numerator" in fee_str and "1" in fee_str assert "Denominator" in fee_str and "100" in fee_str @@ -233,6 +193,7 @@ def test_token_fee_schedule_update_fails_fractional_on_nft(): finally: env.close() + @pytest.mark.integration def test_token_fee_schedule_update_clears_fees(): """Test successfully clearing all fees by passing an empty list.""" @@ -240,31 +201,30 @@ def test_token_fee_schedule_update_clears_fees(): try: admin_key = env.operator_key fee_schedule_key = env.operator_key - + initial_fee = CustomFixedFee(amount=10, fee_collector_account_id=env.operator_id) token_id = create_fungible_token( env, opts=[ lambda tx: tx.set_custom_fees([initial_fee]), lambda tx: tx.set_admin_key(admin_key), - lambda tx: tx.set_fee_schedule_key(fee_schedule_key) - ] + lambda tx: tx.set_fee_schedule_key(fee_schedule_key), + ], ) assert token_id is not None - + token_info = TokenInfoQuery().set_token_id(token_id).execute(env.client) assert len(token_info.custom_fees) == 1 update_tx = ( - TokenFeeScheduleUpdateTransaction() - .set_token_id(token_id) - .set_custom_fees([]) # Pass empty list + TokenFeeScheduleUpdateTransaction().set_token_id(token_id).set_custom_fees([]) # Pass empty list ) update_tx.freeze_with(env.client).sign(fee_schedule_key) update_receipt = update_tx.execute(env.client) - - assert update_receipt.status == ResponseCode.SUCCESS, \ + + assert update_receipt.status == ResponseCode.SUCCESS, ( f"Fee schedule update (clear) failed: {ResponseCode(update_receipt.status).name}" + ) token_info_cleared = TokenInfoQuery().set_token_id(token_id).execute(env.client) assert len(token_info_cleared.custom_fees) == 0 diff --git a/tests/integration/token_freeze_transaction_e2e_test.py b/tests/integration/token_freeze_transaction_e2e_test.py index 51b0d5c53..591931e36 100644 --- a/tests/integration/token_freeze_transaction_e2e_test.py +++ b/tests/integration/token_freeze_transaction_e2e_test.py @@ -1,60 +1,59 @@ +from __future__ import annotations + import pytest +from hiero_sdk_python.account.account_create_transaction import AccountCreateTransaction from hiero_sdk_python.crypto.private_key import PrivateKey from hiero_sdk_python.hbar import Hbar +from hiero_sdk_python.response_code import ResponseCode from hiero_sdk_python.tokens.token_associate_transaction import TokenAssociateTransaction - from hiero_sdk_python.tokens.token_freeze_transaction import TokenFreezeTransaction -from hiero_sdk_python.account.account_create_transaction import AccountCreateTransaction -from hiero_sdk_python.response_code import ResponseCode from tests.integration.utils import IntegrationTestEnv, create_fungible_token - + @pytest.mark.integration def test_integration_token_freeze_transaction_can_execute(): env = IntegrationTestEnv() - + try: new_account_private_key = PrivateKey.generate() new_account_public_key = new_account_private_key.public_key() - + initial_balance = Hbar(2) - + assert initial_balance.to_tinybars() == 200000000 - + account_transaction = AccountCreateTransaction( - key=new_account_public_key, - initial_balance=initial_balance, - memo="Recipient Account" + key=new_account_public_key, initial_balance=initial_balance, memo="Recipient Account" ) - + account_transaction.freeze_with(env.client) receipt = account_transaction.execute(env.client) - - assert receipt.status == ResponseCode.SUCCESS, f"Account creation failed with status: {ResponseCode(receipt.status).name}" - + + assert receipt.status == ResponseCode.SUCCESS, ( + f"Account creation failed with status: {ResponseCode(receipt.status).name}" + ) + account_id = receipt.account_id - + token_id = create_fungible_token(env) - - associate_transaction = TokenAssociateTransaction( - account_id=account_id, - token_ids=[token_id] - ) - + + associate_transaction = TokenAssociateTransaction(account_id=account_id, token_ids=[token_id]) + associate_transaction.freeze_with(env.client) associate_transaction.sign(new_account_private_key) receipt = associate_transaction.execute(env.client) - - assert receipt.status == ResponseCode.SUCCESS, f"Token association failed with status: {ResponseCode(receipt.status).name}" - - freeze_transaction = TokenFreezeTransaction( - token_id=token_id, - account_id=account_id + + assert receipt.status == ResponseCode.SUCCESS, ( + f"Token association failed with status: {ResponseCode(receipt.status).name}" ) + + freeze_transaction = TokenFreezeTransaction(token_id=token_id, account_id=account_id) freeze_transaction.freeze_with(env.client) receipt = freeze_transaction.execute(env.client) - - assert receipt.status == ResponseCode.SUCCESS, f"Token freeze failed with status: {ResponseCode(receipt.status).name}" + + assert receipt.status == ResponseCode.SUCCESS, ( + f"Token freeze failed with status: {ResponseCode(receipt.status).name}" + ) finally: env.close() diff --git a/tests/integration/token_grant_kyc_transaction_e2e_test.py b/tests/integration/token_grant_kyc_transaction_e2e_test.py index f2c3dad06..fe3dc5a78 100644 --- a/tests/integration/token_grant_kyc_transaction_e2e_test.py +++ b/tests/integration/token_grant_kyc_transaction_e2e_test.py @@ -1,21 +1,24 @@ +from __future__ import annotations + import pytest +from hiero_sdk_python.account.account_create_transaction import AccountCreateTransaction from hiero_sdk_python.crypto.private_key import PrivateKey from hiero_sdk_python.hbar import Hbar -from hiero_sdk_python.tokens.token_associate_transaction import TokenAssociateTransaction -from hiero_sdk_python.account.account_create_transaction import AccountCreateTransaction from hiero_sdk_python.response_code import ResponseCode +from hiero_sdk_python.tokens.token_associate_transaction import TokenAssociateTransaction from hiero_sdk_python.tokens.token_grant_kyc_transaction import TokenGrantKycTransaction from tests.integration.utils import IntegrationTestEnv, create_fungible_token + @pytest.mark.integration def test_token_grant_kyc_transaction_can_execute(): env = IntegrationTestEnv() - + try: new_account_private_key = PrivateKey.generate_ed25519() new_account_public_key = new_account_private_key.public_key() - + # Create a new account receipt = ( AccountCreateTransaction() @@ -23,12 +26,14 @@ def test_token_grant_kyc_transaction_can_execute(): .set_initial_balance(Hbar(2)) .execute(env.client) ) - assert receipt.status == ResponseCode.SUCCESS, f"Account creation failed with status: {ResponseCode(receipt.status).name}" + assert receipt.status == ResponseCode.SUCCESS, ( + f"Account creation failed with status: {ResponseCode(receipt.status).name}" + ) account_id = receipt.account_id # Create a new token and set the kyc key to be the operator's key token_id = create_fungible_token(env, [lambda tx: tx.set_kyc_key(env.operator_key)]) - + # Associate the token to the new account receipt = ( TokenAssociateTransaction() @@ -38,27 +43,27 @@ def test_token_grant_kyc_transaction_can_execute(): .sign(new_account_private_key) .execute(env.client) ) - assert receipt.status == ResponseCode.SUCCESS, f"Token association failed with status: {ResponseCode(receipt.status).name}" - + assert receipt.status == ResponseCode.SUCCESS, ( + f"Token association failed with status: {ResponseCode(receipt.status).name}" + ) + # Grant KYC to the new account - receipt = ( - TokenGrantKycTransaction() - .set_account_id(account_id) - .set_token_id(token_id) - .execute(env.client) + receipt = TokenGrantKycTransaction().set_account_id(account_id).set_token_id(token_id).execute(env.client) + assert receipt.status == ResponseCode.SUCCESS, ( + f"Token grant KYC failed with status: {ResponseCode(receipt.status).name}" ) - assert receipt.status == ResponseCode.SUCCESS, f"Token grant KYC failed with status: {ResponseCode(receipt.status).name}" finally: env.close() + @pytest.mark.integration def test_token_grant_kyc_transaction_fails_with_no_kyc_key(): env = IntegrationTestEnv() - + try: new_account_private_key = PrivateKey.generate_ed25519() new_account_public_key = new_account_private_key.public_key() - + # Create a new account receipt = ( AccountCreateTransaction() @@ -66,12 +71,14 @@ def test_token_grant_kyc_transaction_fails_with_no_kyc_key(): .set_initial_balance(Hbar(2)) .execute(env.client) ) - assert receipt.status == ResponseCode.SUCCESS, f"Account creation failed with status: {ResponseCode(receipt.status).name}" + assert receipt.status == ResponseCode.SUCCESS, ( + f"Account creation failed with status: {ResponseCode(receipt.status).name}" + ) account_id = receipt.account_id - + # Create a new token without KYC key token_id = create_fungible_token(env) - + # Associate the token to the new account receipt = ( TokenAssociateTransaction() @@ -81,17 +88,16 @@ def test_token_grant_kyc_transaction_fails_with_no_kyc_key(): .sign(new_account_private_key) .execute(env.client) ) - assert receipt.status == ResponseCode.SUCCESS, f"Token association failed with status: {ResponseCode(receipt.status).name}" - + assert receipt.status == ResponseCode.SUCCESS, ( + f"Token association failed with status: {ResponseCode(receipt.status).name}" + ) + # Try to grant KYC for token without KYC key - should fail with TOKEN_HAS_NO_KYC_KEY - receipt = ( - TokenGrantKycTransaction() - .set_account_id(account_id) - .set_token_id(token_id) - .execute(env.client) + receipt = TokenGrantKycTransaction().set_account_id(account_id).set_token_id(token_id).execute(env.client) + assert receipt.status == ResponseCode.TOKEN_HAS_NO_KYC_KEY, ( + f"Token grant KYC should have failed with TOKEN_HAS_NO_KYC_KEY status but got: {ResponseCode(receipt.status).name}" ) - assert receipt.status == ResponseCode.TOKEN_HAS_NO_KYC_KEY, f"Token grant KYC should have failed with TOKEN_HAS_NO_KYC_KEY status but got: {ResponseCode(receipt.status).name}" - + # Try to grant KYC with non-KYC key - should fail with TOKEN_HAS_NO_KYC_KEY receipt = ( TokenGrantKycTransaction() @@ -101,18 +107,21 @@ def test_token_grant_kyc_transaction_fails_with_no_kyc_key(): .sign(new_account_private_key) .execute(env.client) ) - assert receipt.status == ResponseCode.TOKEN_HAS_NO_KYC_KEY, f"Token grant KYC should have failed with TOKEN_HAS_NO_KYC_KEY status but got: {ResponseCode(receipt.status).name}" + assert receipt.status == ResponseCode.TOKEN_HAS_NO_KYC_KEY, ( + f"Token grant KYC should have failed with TOKEN_HAS_NO_KYC_KEY status but got: {ResponseCode(receipt.status).name}" + ) finally: env.close() - + + @pytest.mark.integration def test_token_grant_kyc_transaction_fails_when_account_not_associated(): env = IntegrationTestEnv() - + try: new_account_private_key = PrivateKey.generate_ed25519() new_account_public_key = new_account_private_key.public_key() - + # Create a new account receipt = ( AccountCreateTransaction() @@ -120,19 +129,18 @@ def test_token_grant_kyc_transaction_fails_when_account_not_associated(): .set_initial_balance(Hbar(2)) .execute(env.client) ) - assert receipt.status == ResponseCode.SUCCESS, f"Account creation failed with status: {ResponseCode(receipt.status).name}" + assert receipt.status == ResponseCode.SUCCESS, ( + f"Account creation failed with status: {ResponseCode(receipt.status).name}" + ) account_id = receipt.account_id - + # Create a new token and set the kyc key to be the operator's key token_id = create_fungible_token(env, [lambda tx: tx.set_kyc_key(env.operator_key)]) - + # Grant KYC to the new account - should fail with ACCOUNT_NOT_ASSOCIATED_TO_TOKEN - receipt = ( - TokenGrantKycTransaction() - .set_account_id(account_id) - .set_token_id(token_id) - .execute(env.client) + receipt = TokenGrantKycTransaction().set_account_id(account_id).set_token_id(token_id).execute(env.client) + assert receipt.status == ResponseCode.TOKEN_NOT_ASSOCIATED_TO_ACCOUNT, ( + f"Token grant KYC should have failed with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT status but got: {ResponseCode(receipt.status).name}" ) - assert receipt.status == ResponseCode.TOKEN_NOT_ASSOCIATED_TO_ACCOUNT, f"Token grant KYC should have failed with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT status but got: {ResponseCode(receipt.status).name}" finally: - env.close() \ No newline at end of file + env.close() diff --git a/tests/integration/token_info_query_e2e_test.py b/tests/integration/token_info_query_e2e_test.py index 394073c32..4e622dc60 100644 --- a/tests/integration/token_info_query_e2e_test.py +++ b/tests/integration/token_info_query_e2e_test.py @@ -1,70 +1,80 @@ +from __future__ import annotations + import pytest from hiero_sdk_python.exceptions import PrecheckError from hiero_sdk_python.hbar import Hbar +from hiero_sdk_python.query.token_info_query import TokenInfoQuery from hiero_sdk_python.tokens.supply_type import SupplyType -from hiero_sdk_python.tokens.token_type import TokenType from hiero_sdk_python.tokens.token_id import TokenId -from hiero_sdk_python.query.token_info_query import TokenInfoQuery +from hiero_sdk_python.tokens.token_type import TokenType from tests.integration.utils import IntegrationTestEnv, create_fungible_token + @pytest.mark.integration def test_integration_token_info_query_can_execute(): env = IntegrationTestEnv() - + try: - token_id = create_fungible_token(env, [ - lambda tx: tx.set_decimals(3), - lambda tx: tx.set_wipe_key(None) # Set wipe key to None to verify TokenInfoQuery returns correct key state - ]) - + token_id = create_fungible_token( + env, + [ + lambda tx: tx.set_decimals(3), + lambda tx: tx.set_wipe_key( + None + ), # Set wipe key to None to verify TokenInfoQuery returns correct key state + ], + ) + info = TokenInfoQuery(token_id).execute(env.client) - + assert str(info.token_id) == str(token_id), "Token ID mismatch" assert info.name == "PTokenTest34", "Name mismatch" - assert info.symbol == "PTT34", "Symbol mismatch" + assert info.symbol == "PTT34", "Symbol mismatch" assert info.decimals == 3, "Decimals mismatch" assert str(info.treasury) == str(env.operator_id), "Treasury mismatch" assert info.token_type == TokenType.FUNGIBLE_COMMON, "Token type mismatch" assert info.supply_type == SupplyType.FINITE, "Supply type mismatch" assert info.max_supply == 10000, "Max supply mismatch" - + assert info.admin_key is not None, "Admin key should not be None" assert info.freeze_key is not None, "Freeze key should not be None" assert info.wipe_key is None, "Wipe key should be None" assert info.supply_key is not None, "Supply key should not be None" assert info.kyc_key is None, "KYC key should be None" - + assert str(info.admin_key) == str(env.operator_key.public_key()), "Admin key mismatch" assert str(info.freeze_key) == str(env.operator_key.public_key()), "Freeze key mismatch" assert str(info.supply_key) == str(env.operator_key.public_key()), "Supply key mismatch" finally: env.close() + @pytest.mark.integration def test_integration_token_info_query_fails_with_insufficient_tx_fee(): """Test that token info query fails with insufficient payment.""" env = IntegrationTestEnv() - + try: token_id = create_fungible_token(env) - + query = TokenInfoQuery(token_id) - query.set_query_payment(Hbar.from_tinybars(1)) # Set very low query payment - + query.set_query_payment(Hbar.from_tinybars(1)) # Set very low query payment + with pytest.raises(PrecheckError, match="failed precheck with status: INSUFFICIENT_TX_FEE"): query.execute(env.client) finally: env.close() + @pytest.mark.integration def test_integration_token_info_query_fails_with_invalid_token_id(): env = IntegrationTestEnv() - + try: - token_id = TokenId(0,0,123456789) - + token_id = TokenId(0, 0, 123456789) + with pytest.raises(PrecheckError, match="failed precheck with status: INVALID_TOKEN_ID"): TokenInfoQuery(token_id).execute(env.client) finally: - env.close() \ No newline at end of file + env.close() diff --git a/tests/integration/token_mint_transaction_e2e_test.py b/tests/integration/token_mint_transaction_e2e_test.py index c4589e01e..fe03e7664 100644 --- a/tests/integration/token_mint_transaction_e2e_test.py +++ b/tests/integration/token_mint_transaction_e2e_test.py @@ -1,86 +1,86 @@ +from __future__ import annotations + import pytest from hiero_sdk_python.account.account_create_transaction import AccountCreateTransaction from hiero_sdk_python.crypto.private_key import PrivateKey from hiero_sdk_python.hbar import Hbar -from hiero_sdk_python.tokens.token_mint_transaction import TokenMintTransaction from hiero_sdk_python.response_code import ResponseCode +from hiero_sdk_python.tokens.token_mint_transaction import TokenMintTransaction from tests.integration.utils import IntegrationTestEnv, create_fungible_token @pytest.mark.integration def test_integration_token_mint_nft_transaction_can_execute(): env = IntegrationTestEnv() - + try: new_account_private_key = PrivateKey.generate() new_account_public_key = new_account_private_key.public_key() - + initial_balance = Hbar(2) - + transaction = AccountCreateTransaction( - key=new_account_public_key, - initial_balance=initial_balance, - memo="Recipient Account" + key=new_account_public_key, initial_balance=initial_balance, memo="Recipient Account" ) - + transaction.freeze_with(env.client) receipt = transaction.execute(env.client) - - assert receipt.status == ResponseCode.SUCCESS, f"Account creation failed with status: {ResponseCode(receipt.status).name}" - + + assert receipt.status == ResponseCode.SUCCESS, ( + f"Account creation failed with status: {ResponseCode(receipt.status).name}" + ) + account_id = receipt.account_id assert account_id is not None - + token_id = create_fungible_token(env) assert token_id is not None - + metadata = [b"NFT Token A", b"NFT Token B"] - mint_transaction = TokenMintTransaction( - token_id=token_id, - metadata=metadata - ) - + mint_transaction = TokenMintTransaction(token_id=token_id, metadata=metadata) + mint_transaction.freeze_with(env.client) receipt = mint_transaction.execute(env.client) - - assert receipt.status == ResponseCode.SUCCESS, f"NFT token minting failed with status: {ResponseCode(receipt.status).name}" + + assert receipt.status == ResponseCode.SUCCESS, ( + f"NFT token minting failed with status: {ResponseCode(receipt.status).name}" + ) finally: - env.close() + env.close() @pytest.mark.integration def test_integration_token_mint_fungible_transaction_can_execute(): env = IntegrationTestEnv() - + try: new_account_private_key = PrivateKey.generate() new_account_public_key = new_account_private_key.public_key() - + initial_balance = Hbar(2) - + transaction = AccountCreateTransaction( - key=new_account_public_key, - initial_balance=initial_balance, - memo="Recipient Account" + key=new_account_public_key, initial_balance=initial_balance, memo="Recipient Account" ) - + transaction.freeze_with(env.client) receipt = transaction.execute(env.client) - - assert receipt.status == ResponseCode.SUCCESS, f"Account creation failed with status: {ResponseCode(receipt.status).name}" - + + assert receipt.status == ResponseCode.SUCCESS, ( + f"Account creation failed with status: {ResponseCode(receipt.status).name}" + ) + token_id = create_fungible_token(env) assert token_id is not None - - mint_transaction = TokenMintTransaction( - token_id=token_id, - amount=2000 - ) - + + mint_transaction = TokenMintTransaction(token_id=token_id, amount=2000) + mint_transaction.freeze_with(env.client) receipt = mint_transaction.execute(env.client) - - assert receipt.status == ResponseCode.SUCCESS, f"Token minting failed with status: {ResponseCode(receipt.status).name}" + + assert receipt.status == ResponseCode.SUCCESS, ( + f"Token minting failed with status: {ResponseCode(receipt.status).name}" + ) finally: - env.close() + env.close() diff --git a/tests/integration/token_nft_info_query_e2e_test.py b/tests/integration/token_nft_info_query_e2e_test.py index 216653847..9cf7c3307 100644 --- a/tests/integration/token_nft_info_query_e2e_test.py +++ b/tests/integration/token_nft_info_query_e2e_test.py @@ -1,50 +1,52 @@ +from __future__ import annotations + import pytest from hiero_sdk_python.exceptions import PrecheckError -from hiero_sdk_python.response_code import ResponseCode -from hiero_sdk_python.tokens.token_nft_info import TokenNftInfo from hiero_sdk_python.query.token_nft_info_query import TokenNftInfoQuery +from hiero_sdk_python.response_code import ResponseCode +from hiero_sdk_python.tokens.nft_id import NftId from hiero_sdk_python.tokens.token_mint_transaction import TokenMintTransaction from tests.integration.utils import IntegrationTestEnv, create_nft_token -from hiero_sdk_python.tokens.nft_id import NftId + @pytest.mark.integration def test_integration_token_nft_info_query_can_execute(): env = IntegrationTestEnv() - + try: token_id = create_nft_token(env) - + metadata = b"Token A" - - mint = TokenMintTransaction( - token_id=token_id, - metadata=metadata - ) - + + mint = TokenMintTransaction(token_id=token_id, metadata=metadata) + receipt = mint.execute(env.client) - - assert receipt.status == ResponseCode.SUCCESS, f"Token minting failed with status: {ResponseCode(receipt.status).name}" + + assert receipt.status == ResponseCode.SUCCESS, ( + f"Token minting failed with status: {ResponseCode(receipt.status).name}" + ) nft_id = NftId(token_id, receipt.serial_numbers[0]) - + info = TokenNftInfoQuery(nft_id).execute(env.client) - - assert str(info.nft_id) == str(nft_id), f"NFT ID mismatch" - assert info.nft_id == nft_id, f"NFT ID mismatch" - assert info.metadata == metadata, f"Metadata mismatch" + + assert str(info.nft_id) == str(nft_id), "NFT ID mismatch" + assert info.nft_id == nft_id, "NFT ID mismatch" + assert info.metadata == metadata, "Metadata mismatch" finally: env.close() - + + @pytest.mark.integration def test_integration_token_nft_info_query_fail_nonexistent_nft(): env = IntegrationTestEnv() - + try: token_id = create_nft_token(env) - + nft_id = NftId(token_id, 1) - + with pytest.raises(PrecheckError, match="failed precheck with status: INVALID_NFT_ID"): TokenNftInfoQuery(nft_id).execute(env.client) finally: - env.close() \ No newline at end of file + env.close() diff --git a/tests/integration/token_pause_transaction_e2e_test.py b/tests/integration/token_pause_transaction_e2e_test.py index d6ea48ce5..e468c3b42 100644 --- a/tests/integration/token_pause_transaction_e2e_test.py +++ b/tests/integration/token_pause_transaction_e2e_test.py @@ -1,27 +1,28 @@ -import pytest -from pytest import mark, fixture - -from tests.integration.utils import env, create_fungible_token, create_nft_token +from __future__ import annotations -from hiero_sdk_python.crypto.private_key import PrivateKey -from hiero_sdk_python.response_code import ResponseCode - -from hiero_sdk_python.tokens.token_pause_transaction import TokenPauseTransaction -from hiero_sdk_python.tokens.token_id import TokenId +import pytest +from pytest import fixture, mark -from tests.integration.utils import create_fungible_token, Account -from hiero_sdk_python.transaction.transfer_transaction import TransferTransaction -from hiero_sdk_python.tokens.token_associate_transaction import TokenAssociateTransaction +from hiero_sdk_python.crypto.private_key import PrivateKey from hiero_sdk_python.query.account_balance_query import CryptoGetAccountBalanceQuery from hiero_sdk_python.query.token_info_query import TokenInfoQuery +from hiero_sdk_python.response_code import ResponseCode +from hiero_sdk_python.tokens.token_associate_transaction import TokenAssociateTransaction +from hiero_sdk_python.tokens.token_id import TokenId +from hiero_sdk_python.tokens.token_pause_transaction import TokenPauseTransaction +from hiero_sdk_python.transaction.transfer_transaction import TransferTransaction +from tests.integration.utils import Account, create_fungible_token + pause_key = PrivateKey.generate() + @fixture def account(env): """A fresh account funded with 1 HBAR balance.""" return env.create_account() + # Uses lambda opts to add a pause key → pausable # Create a unique pause key to enable varied tests # Signing by the treasury account handled by the executable method in env @@ -29,11 +30,13 @@ def account(env): def pausable_token(env): return create_fungible_token(env, opts=[lambda tx: tx.set_pause_key(pause_key)]) + # Fungible token in env has no pause key @fixture def unpausable_token(env): return create_fungible_token(env) + @mark.integration def test_pause_missing_token_id_raises_value_error(env): """ @@ -42,7 +45,8 @@ def test_pause_missing_token_id_raises_value_error(env): tx = TokenPauseTransaction() with pytest.raises(ValueError, match="token_id must be set"): - tx.freeze_with(env.client) # ← builds the body which fails + tx.freeze_with(env.client) # ← builds the body which fails + @mark.integration def test_pause_nonexistent_token_id_raises_precheck_error(env): @@ -56,10 +60,10 @@ def test_pause_nonexistent_token_id_raises_precheck_error(env): receipt = tx.execute(env.client) # ← auto-freeze & sign with operator key assert receipt.status == ResponseCode.INVALID_TOKEN_ID, ( - f"Expected INVALID_TOKEN_ID but got " - f"{ResponseCode(receipt.status).name}" + f"Expected INVALID_TOKEN_ID but got {ResponseCode(receipt.status).name}" ) + @mark.integration def test_pause_fails_for_unpausable_token(env, unpausable_token): """ @@ -71,10 +75,10 @@ def test_pause_fails_for_unpausable_token(env, unpausable_token): receipt = tx.execute(env.client) # ← auto-freeze & sign with operator key assert receipt.status == ResponseCode.TOKEN_HAS_NO_PAUSE_KEY, ( - f"Expected TOKEN_HAS_NO_PAUSE_KEY but got " - f"{ResponseCode(receipt.status).name}" + f"Expected TOKEN_HAS_NO_PAUSE_KEY but got {ResponseCode(receipt.status).name}" ) + @mark.integration def test_pause_requires_pause_key_signature(env, pausable_token): """ @@ -85,13 +89,13 @@ def test_pause_requires_pause_key_signature(env, pausable_token): tx = TokenPauseTransaction().set_token_id(pausable_token).freeze_with(env.client) # Execute autosigns tx with operator key, which is different to the pause key signature - receipt = tx.execute(env.client) + receipt = tx.execute(env.client) assert receipt.status == ResponseCode.INVALID_SIGNATURE, ( - f"Expected INVALID_SIGNATURE but got " - f"{ResponseCode(receipt.status).name}" + f"Expected INVALID_SIGNATURE but got {ResponseCode(receipt.status).name}" ) + @mark.integration def test_pause_with_invalid_key(env, pausable_token): """ @@ -101,14 +105,14 @@ def test_pause_with_invalid_key(env, pausable_token): wrong_key = PrivateKey.generate() tx = TokenPauseTransaction().set_token_id(pausable_token).freeze_with(env.client) - tx = tx.sign(wrong_key) # ← signed with wrong key + tx = tx.sign(wrong_key) # ← signed with wrong key receipt = tx.execute(env.client) assert receipt.status == ResponseCode.INVALID_SIGNATURE, ( - f"Expected INVALID_SIGNATURE but got " - f"{ResponseCode(receipt.status).name}" + f"Expected INVALID_SIGNATURE but got {ResponseCode(receipt.status).name}" ) + @mark.integration def test_transfer_before_pause(env, account: Account, pausable_token): """ @@ -121,6 +125,7 @@ def test_transfer_before_pause(env, account: Account, pausable_token): balance = CryptoGetAccountBalanceQuery(account.id).execute(env.client).token_balances[pausable_token] assert balance == 10 + @mark.integration def test_pause_sets_pause_status_to_paused(env, pausable_token): """ @@ -133,12 +138,7 @@ def test_pause_sets_pause_status_to_paused(env, pausable_token): assert info.pause_status.name == "UNPAUSED" # 2) build, freeze, sign & execute the pause tx - tx = ( - TokenPauseTransaction() - .set_token_id(pausable_token) - .freeze_with(env.client) - .sign(pause_key) - ) + tx = TokenPauseTransaction().set_token_id(pausable_token).freeze_with(env.client).sign(pause_key) receipt = tx.execute(env.client) assert receipt.status == ResponseCode.SUCCESS @@ -147,6 +147,7 @@ def test_pause_sets_pause_status_to_paused(env, pausable_token): assert info2.pause_status.name == "PAUSED" + @mark.integration def test_transfers_blocked_when_paused(env, account: Account, pausable_token): """ @@ -157,32 +158,27 @@ def test_transfers_blocked_when_paused(env, account: Account, pausable_token): # first associate (this must succeed) assoc_receipt = ( TokenAssociateTransaction() - .set_account_id(account.id) - .add_token_id(pausable_token) - .freeze_with(env.client) - .sign(account.key) - .execute(env.client) + .set_account_id(account.id) + .add_token_id(pausable_token) + .freeze_with(env.client) + .sign(account.key) + .execute(env.client) ) assert assoc_receipt.status == ResponseCode.SUCCESS # pause the token pause_receipt = ( - TokenPauseTransaction() - .set_token_id(pausable_token) - .freeze_with(env.client) - .sign(pause_key) - .execute(env.client) + TokenPauseTransaction().set_token_id(pausable_token).freeze_with(env.client).sign(pause_key).execute(env.client) ) assert pause_receipt.status == ResponseCode.SUCCESS # attempt to transfer 1 token transfer_receipt = ( TransferTransaction() - .add_token_transfer(pausable_token, env.operator_id, -1) - .add_token_transfer(pausable_token, account.id, 1) - .execute(env.client) + .add_token_transfer(pausable_token, env.operator_id, -1) + .add_token_transfer(pausable_token, account.id, 1) + .execute(env.client) ) assert transfer_receipt.status == ResponseCode.TOKEN_IS_PAUSED, ( - f"Expected TOKEN_IS_PAUSED but got " - f"{ResponseCode(transfer_receipt.status).name}" + f"Expected TOKEN_IS_PAUSED but got {ResponseCode(transfer_receipt.status).name}" ) diff --git a/tests/integration/token_reject_transaction_e2e_test.py b/tests/integration/token_reject_transaction_e2e_test.py index 98c913f22..92f338f1a 100644 --- a/tests/integration/token_reject_transaction_e2e_test.py +++ b/tests/integration/token_reject_transaction_e2e_test.py @@ -1,1004 +1,1204 @@ +from __future__ import annotations + import pytest +from hiero_sdk_python.account.account_create_transaction import AccountCreateTransaction from hiero_sdk_python.crypto.private_key import PrivateKey from hiero_sdk_python.exceptions import PrecheckError from hiero_sdk_python.hbar import Hbar from hiero_sdk_python.query.account_balance_query import CryptoGetAccountBalanceQuery -from hiero_sdk_python.tokens.token_associate_transaction import TokenAssociateTransaction -from hiero_sdk_python.account.account_create_transaction import AccountCreateTransaction +from hiero_sdk_python.query.token_nft_info_query import TokenNftInfoQuery from hiero_sdk_python.response_code import ResponseCode +from hiero_sdk_python.tokens.nft_id import NftId +from hiero_sdk_python.tokens.token_associate_transaction import TokenAssociateTransaction from hiero_sdk_python.tokens.token_freeze_transaction import TokenFreezeTransaction from hiero_sdk_python.tokens.token_mint_transaction import TokenMintTransaction from hiero_sdk_python.tokens.token_reject_transaction import TokenRejectTransaction from hiero_sdk_python.transaction.transfer_transaction import TransferTransaction from tests.integration.utils import IntegrationTestEnv, create_fungible_token, create_nft_token -from hiero_sdk_python.tokens.nft_id import NftId -from hiero_sdk_python.query.token_nft_info_query import TokenNftInfoQuery @pytest.mark.integration def test_integration_token_reject_transaction_can_execute(): env = IntegrationTestEnv() - + try: new_account_private_key = PrivateKey.generate() new_account_public_key = new_account_private_key.public_key() - + # Create the new account transaction = AccountCreateTransaction( - key=new_account_public_key, - initial_balance=Hbar(1), - memo="Recipient Account" + key=new_account_public_key, initial_balance=Hbar(1), memo="Recipient Account" ) - + transaction.freeze_with(env.client) receipt = transaction.execute(env.client) - - assert receipt.status == ResponseCode.SUCCESS, f"Account creation failed with status: {ResponseCode(receipt.status).name}" - + + assert receipt.status == ResponseCode.SUCCESS, ( + f"Account creation failed with status: {ResponseCode(receipt.status).name}" + ) + account_id = receipt.account_id - + token1 = create_fungible_token(env) token2 = create_fungible_token(env) - + # Associate the tokens to the new account - token_associate_transaction = TokenAssociateTransaction( - account_id=account_id, - token_ids=[token1, token2] - ) - + token_associate_transaction = TokenAssociateTransaction(account_id=account_id, token_ids=[token1, token2]) + token_associate_transaction.freeze_with(env.client) token_associate_transaction.sign(new_account_private_key) receipt = token_associate_transaction.execute(env.client) - assert receipt.status == ResponseCode.SUCCESS, f"Token association failed with status: {ResponseCode(receipt.status).name}" - + assert receipt.status == ResponseCode.SUCCESS, ( + f"Token association failed with status: {ResponseCode(receipt.status).name}" + ) + # Transfer the tokens to the new account token_transfer_transaction = TransferTransaction() token_transfer_transaction.add_token_transfer(token1, account_id, 10) token_transfer_transaction.add_token_transfer(token1, env.client.operator_account_id, -10) token_transfer_transaction.add_token_transfer(token2, account_id, 10) token_transfer_transaction.add_token_transfer(token2, env.client.operator_account_id, -10) - + receipt = token_transfer_transaction.execute(env.client) - - assert receipt.status == ResponseCode.SUCCESS, f"Token transfer failed with status: {ResponseCode(receipt.status).name}" - - # Reject the tokens - token_reject_transaction = TokenRejectTransaction( - owner_id=account_id, - token_ids=[token1, token2] + + assert receipt.status == ResponseCode.SUCCESS, ( + f"Token transfer failed with status: {ResponseCode(receipt.status).name}" ) - + + # Reject the tokens + token_reject_transaction = TokenRejectTransaction(owner_id=account_id, token_ids=[token1, token2]) + token_reject_transaction.freeze_with(env.client) token_reject_transaction.sign(new_account_private_key) receipt = token_reject_transaction.execute(env.client) - - assert receipt.status == ResponseCode.SUCCESS, f"Token rejection failed with status: {ResponseCode(receipt.status).name}" - + + assert receipt.status == ResponseCode.SUCCESS, ( + f"Token rejection failed with status: {ResponseCode(receipt.status).name}" + ) + # Verify the balance of the new account is 0 balance = CryptoGetAccountBalanceQuery(account_id).execute(env.client) - assert balance.token_balances is not None and balance.token_balances.get(token1) == 0 and balance.token_balances.get(token2) == 0 - + assert ( + balance.token_balances is not None + and balance.token_balances.get(token1) == 0 + and balance.token_balances.get(token2) == 0 + ) + # Verify the balance of the operator account is the same as the initial balance balance = CryptoGetAccountBalanceQuery(env.client.operator_account_id).execute(env.client) - assert balance.token_balances is not None and balance.token_balances.get(token1) == 1000 and balance.token_balances.get(token2) == 1000 + assert ( + balance.token_balances is not None + and balance.token_balances.get(token1) == 1000 + and balance.token_balances.get(token2) == 1000 + ) finally: env.close() - + @pytest.mark.integration def test_integration_token_reject_transaction_can_execute_for_nft(): env = IntegrationTestEnv() - + try: nft_id_1 = create_nft_token(env) nft_id_2 = create_nft_token(env) - - receipt = (TokenMintTransaction() - .set_token_id(nft_id_1) - .set_metadata([b"metadata1", b"metadata2"]) - .execute(env.client)) - assert receipt.status == ResponseCode.SUCCESS, f"NFT minting failed with status: {ResponseCode(receipt.status).name}" + + receipt = ( + TokenMintTransaction().set_token_id(nft_id_1).set_metadata([b"metadata1", b"metadata2"]).execute(env.client) + ) + assert receipt.status == ResponseCode.SUCCESS, ( + f"NFT minting failed with status: {ResponseCode(receipt.status).name}" + ) serials_1 = receipt.serial_numbers - - receipt = (TokenMintTransaction() - .set_token_id(nft_id_2) - .set_metadata([b"metadata1", b"metadata2"]) - .execute(env.client)) - assert receipt.status == ResponseCode.SUCCESS, f"NFT minting failed with status: {ResponseCode(receipt.status).name}" + + receipt = ( + TokenMintTransaction().set_token_id(nft_id_2).set_metadata([b"metadata1", b"metadata2"]).execute(env.client) + ) + assert receipt.status == ResponseCode.SUCCESS, ( + f"NFT minting failed with status: {ResponseCode(receipt.status).name}" + ) serials_2 = receipt.serial_numbers - + new_account_private_key = PrivateKey.generate() new_account_public_key = new_account_private_key.public_key() - - receipt = (AccountCreateTransaction() - .set_key_without_alias(new_account_public_key).set_initial_balance(Hbar(1)) - .set_account_memo("Receiver Account").execute(env.client)) - assert receipt.status == ResponseCode.SUCCESS, f"Account creation failed with status: {ResponseCode(receipt.status).name}" + + receipt = ( + AccountCreateTransaction() + .set_key_without_alias(new_account_public_key) + .set_initial_balance(Hbar(1)) + .set_account_memo("Receiver Account") + .execute(env.client) + ) + assert receipt.status == ResponseCode.SUCCESS, ( + f"Account creation failed with status: {ResponseCode(receipt.status).name}" + ) account_id = receipt.account_id assert account_id is not None - + # Associate the tokens to the new account - receipt = (TokenAssociateTransaction().set_account_id(account_id) - .add_token_id(nft_id_1).add_token_id(nft_id_2) - .freeze_with(env.client).sign(new_account_private_key).execute(env.client)) - assert receipt.status == ResponseCode.SUCCESS, f"Token association failed with status: {ResponseCode(receipt.status).name}" - - receipt = (TransferTransaction() - .add_nft_transfer(NftId(nft_id_1, serials_1[0]), env.client.operator_account_id, account_id).add_nft_transfer(NftId(nft_id_1, serials_1[1]), env.client.operator_account_id, account_id) - .add_nft_transfer(NftId(nft_id_2, serials_2[0]), env.client.operator_account_id, account_id).add_nft_transfer(NftId(nft_id_2, serials_2[1]), env.client.operator_account_id, account_id) - .execute(env.client)) - assert receipt.status == ResponseCode.SUCCESS, f"NFT transfer failed with status: {ResponseCode(receipt.status).name}" - + receipt = ( + TokenAssociateTransaction() + .set_account_id(account_id) + .add_token_id(nft_id_1) + .add_token_id(nft_id_2) + .freeze_with(env.client) + .sign(new_account_private_key) + .execute(env.client) + ) + assert receipt.status == ResponseCode.SUCCESS, ( + f"Token association failed with status: {ResponseCode(receipt.status).name}" + ) + + receipt = ( + TransferTransaction() + .add_nft_transfer(NftId(nft_id_1, serials_1[0]), env.client.operator_account_id, account_id) + .add_nft_transfer(NftId(nft_id_1, serials_1[1]), env.client.operator_account_id, account_id) + .add_nft_transfer(NftId(nft_id_2, serials_2[0]), env.client.operator_account_id, account_id) + .add_nft_transfer(NftId(nft_id_2, serials_2[1]), env.client.operator_account_id, account_id) + .execute(env.client) + ) + assert receipt.status == ResponseCode.SUCCESS, ( + f"NFT transfer failed with status: {ResponseCode(receipt.status).name}" + ) + # Reject the tokens - receipt = (TokenRejectTransaction() + receipt = ( + TokenRejectTransaction() .set_owner_id(account_id) .set_nft_ids([NftId(nft_id_1, serials_1[1]), NftId(nft_id_2, serials_2[1])]) - .freeze_with(env.client).sign(new_account_private_key).execute(env.client)) - assert receipt.status == ResponseCode.SUCCESS, f"Token rejection failed with status: {ResponseCode(receipt.status).name}" - + .freeze_with(env.client) + .sign(new_account_private_key) + .execute(env.client) + ) + assert receipt.status == ResponseCode.SUCCESS, ( + f"Token rejection failed with status: {ResponseCode(receipt.status).name}" + ) + # Verify the balance is decremented by 1 for each token balance = CryptoGetAccountBalanceQuery(account_id).execute(env.client) - assert balance.token_balances and balance.token_balances.get(nft_id_1) == 1, f"Expected 1 NFT for token {nft_id_1}, got {balance.token_balances.get(nft_id_1)}" - assert balance.token_balances and balance.token_balances.get(nft_id_2) == 1, f"Expected 1 NFT for token {nft_id_2}, got {balance.token_balances.get(nft_id_2)}" - + assert balance.token_balances and balance.token_balances.get(nft_id_1) == 1, ( + f"Expected 1 NFT for token {nft_id_1}, got {balance.token_balances.get(nft_id_1)}" + ) + assert balance.token_balances and balance.token_balances.get(nft_id_2) == 1, ( + f"Expected 1 NFT for token {nft_id_2}, got {balance.token_balances.get(nft_id_2)}" + ) + # Verify the NFTs are transferred back to the treasury nft_info_1 = TokenNftInfoQuery(NftId(nft_id_1, serials_1[1])).execute(env.client) - assert nft_info_1.account_id == env.operator_id, f"Expected NFT owner to be {env.operator_id}, got {nft_info_1.account_id}" - + assert nft_info_1.account_id == env.operator_id, ( + f"Expected NFT owner to be {env.operator_id}, got {nft_info_1.account_id}" + ) + nft_info_2 = TokenNftInfoQuery(NftId(nft_id_2, serials_2[1])).execute(env.client) - assert nft_info_2.account_id == env.operator_id, f"Expected NFT owner to be {env.operator_id}, got {nft_info_2.account_id}" + assert nft_info_2.account_id == env.operator_id, ( + f"Expected NFT owner to be {env.operator_id}, got {nft_info_2.account_id}" + ) finally: env.close() - + + @pytest.mark.integration def test_integration_token_reject_transaction_can_execute_for_ft_and_nft_parallel(): env = IntegrationTestEnv() - + try: nft_id_1 = create_nft_token(env) nft_id_2 = create_nft_token(env) - + token_id_1 = create_fungible_token(env) token_id_2 = create_fungible_token(env) - - receipt = TokenMintTransaction().set_token_id(nft_id_1).set_metadata([b"metadata1", b"metadata2"]).execute(env.client) - assert receipt.status == ResponseCode.SUCCESS, f"Token minting failed with status: {ResponseCode(receipt.status).name}" + + receipt = ( + TokenMintTransaction().set_token_id(nft_id_1).set_metadata([b"metadata1", b"metadata2"]).execute(env.client) + ) + assert receipt.status == ResponseCode.SUCCESS, ( + f"Token minting failed with status: {ResponseCode(receipt.status).name}" + ) serials_1 = receipt.serial_numbers - - receipt = TokenMintTransaction().set_token_id(nft_id_2).set_metadata([b"metadata1", b"metadata2"]).execute(env.client) - assert receipt.status == ResponseCode.SUCCESS, f"Token minting failed with status: {ResponseCode(receipt.status).name}" + + receipt = ( + TokenMintTransaction().set_token_id(nft_id_2).set_metadata([b"metadata1", b"metadata2"]).execute(env.client) + ) + assert receipt.status == ResponseCode.SUCCESS, ( + f"Token minting failed with status: {ResponseCode(receipt.status).name}" + ) serials_2 = receipt.serial_numbers - + new_account_private_key = PrivateKey.generate() new_account_public_key = new_account_private_key.public_key() - - receipt = AccountCreateTransaction().set_key_without_alias(new_account_public_key).set_initial_balance(Hbar(1)).set_account_memo("Receiver Account").execute(env.client) - assert receipt.status == ResponseCode.SUCCESS, f"Account creation failed with status: {ResponseCode(receipt.status).name}" + + receipt = ( + AccountCreateTransaction() + .set_key_without_alias(new_account_public_key) + .set_initial_balance(Hbar(1)) + .set_account_memo("Receiver Account") + .execute(env.client) + ) + assert receipt.status == ResponseCode.SUCCESS, ( + f"Account creation failed with status: {ResponseCode(receipt.status).name}" + ) account_id = receipt.account_id assert account_id is not None - - receipt = (TokenAssociateTransaction().set_account_id(account_id).add_token_id(token_id_1).add_token_id(token_id_2).add_token_id(nft_id_1).add_token_id(nft_id_2) - .freeze_with(env.client).sign(new_account_private_key).execute(env.client)) - assert receipt.status == ResponseCode.SUCCESS, f"Token association failed with status: {ResponseCode(receipt.status).name}" - - receipt = (TransferTransaction() - .add_token_transfer(token_id_1, account_id, 10).add_token_transfer(token_id_1, env.client.operator_account_id, -10) - .add_token_transfer(token_id_2, account_id, 10).add_token_transfer(token_id_2, env.client.operator_account_id, -10) + + receipt = ( + TokenAssociateTransaction() + .set_account_id(account_id) + .add_token_id(token_id_1) + .add_token_id(token_id_2) + .add_token_id(nft_id_1) + .add_token_id(nft_id_2) + .freeze_with(env.client) + .sign(new_account_private_key) + .execute(env.client) + ) + assert receipt.status == ResponseCode.SUCCESS, ( + f"Token association failed with status: {ResponseCode(receipt.status).name}" + ) + + receipt = ( + TransferTransaction() + .add_token_transfer(token_id_1, account_id, 10) + .add_token_transfer(token_id_1, env.client.operator_account_id, -10) + .add_token_transfer(token_id_2, account_id, 10) + .add_token_transfer(token_id_2, env.client.operator_account_id, -10) .add_nft_transfer(NftId(nft_id_1, serials_1[0]), env.client.operator_account_id, account_id) .add_nft_transfer(NftId(nft_id_1, serials_1[1]), env.client.operator_account_id, account_id) .add_nft_transfer(NftId(nft_id_2, serials_2[0]), env.client.operator_account_id, account_id) .add_nft_transfer(NftId(nft_id_2, serials_2[1]), env.client.operator_account_id, account_id) - .execute(env.client)) - assert receipt.status == ResponseCode.SUCCESS, f"Token transfer failed with status: {ResponseCode(receipt.status).name}" - - reject_transaction = (TokenRejectTransaction().set_owner_id(account_id) + .execute(env.client) + ) + assert receipt.status == ResponseCode.SUCCESS, ( + f"Token transfer failed with status: {ResponseCode(receipt.status).name}" + ) + + reject_transaction = ( + TokenRejectTransaction() + .set_owner_id(account_id) .set_token_ids([token_id_1, token_id_2]) - .set_nft_ids([NftId(nft_id_1, serials_1[1]), NftId(nft_id_2, serials_2[1])])) + .set_nft_ids([NftId(nft_id_1, serials_1[1]), NftId(nft_id_2, serials_2[1])]) + ) reject_transaction.transaction_fee = Hbar(3).to_tinybars() # Set transaction fee to be higher than 2 Hbars receipt = reject_transaction.freeze_with(env.client).sign(new_account_private_key).execute(env.client) - assert receipt.status == ResponseCode.SUCCESS, f"Token rejection failed with status: {ResponseCode(receipt.status).name}" - + assert receipt.status == ResponseCode.SUCCESS, ( + f"Token rejection failed with status: {ResponseCode(receipt.status).name}" + ) + balance = CryptoGetAccountBalanceQuery(account_id).execute(env.client) - assert balance.token_balances and balance.token_balances.get(token_id_1) == 0, f"Expected token balance to be 0 for token {token_id_1}, got {balance.token_balances.get(token_id_1)}" - assert balance.token_balances and balance.token_balances.get(token_id_2) == 0, f"Expected token balance to be 0 for token {token_id_2}, got {balance.token_balances.get(token_id_2)}" - + assert balance.token_balances and balance.token_balances.get(token_id_1) == 0, ( + f"Expected token balance to be 0 for token {token_id_1}, got {balance.token_balances.get(token_id_1)}" + ) + assert balance.token_balances and balance.token_balances.get(token_id_2) == 0, ( + f"Expected token balance to be 0 for token {token_id_2}, got {balance.token_balances.get(token_id_2)}" + ) + balance = CryptoGetAccountBalanceQuery(env.operator_id).execute(env.client) - assert balance.token_balances and balance.token_balances.get(token_id_1) == 1000, f"Expected token balance to be 1000 for token {token_id_1}, got {balance.token_balances.get(token_id_1)}" - assert balance.token_balances and balance.token_balances.get(token_id_2) == 1000, f"Expected token balance to be 1000 for token {token_id_2}, got {balance.token_balances.get(token_id_2)}" - + assert balance.token_balances and balance.token_balances.get(token_id_1) == 1000, ( + f"Expected token balance to be 1000 for token {token_id_1}, got {balance.token_balances.get(token_id_1)}" + ) + assert balance.token_balances and balance.token_balances.get(token_id_2) == 1000, ( + f"Expected token balance to be 1000 for token {token_id_2}, got {balance.token_balances.get(token_id_2)}" + ) + nft_info_1 = TokenNftInfoQuery(NftId(nft_id_1, serials_1[1])).execute(env.client) - assert nft_info_1.account_id == env.operator_id, f"Expected NFT owner to be {env.operator_id}, got {nft_info_1.account_id}" - + assert nft_info_1.account_id == env.operator_id, ( + f"Expected NFT owner to be {env.operator_id}, got {nft_info_1.account_id}" + ) + nft_info_2 = TokenNftInfoQuery(NftId(nft_id_2, serials_2[1])).execute(env.client) - assert nft_info_2.account_id == env.operator_id, f"Expected NFT owner to be {env.operator_id}, got {nft_info_2.account_id}" + assert nft_info_2.account_id == env.operator_id, ( + f"Expected NFT owner to be {env.operator_id}, got {nft_info_2.account_id}" + ) finally: env.close() + @pytest.mark.integration def test_token_reject_transaction_fails_with_invalid_signature(): env = IntegrationTestEnv() - + try: new_account_private_key = PrivateKey.generate() new_account_public_key = new_account_private_key.public_key() - + # Create the new account transaction = AccountCreateTransaction( - key=new_account_public_key, - initial_balance=Hbar(1), - memo="Recipient Account" + key=new_account_public_key, initial_balance=Hbar(1), memo="Recipient Account" ) - + receipt = transaction.execute(env.client) - - assert receipt.status == ResponseCode.SUCCESS, f"Account creation failed with status: {ResponseCode(receipt.status).name}" - + + assert receipt.status == ResponseCode.SUCCESS, ( + f"Account creation failed with status: {ResponseCode(receipt.status).name}" + ) + account_id = receipt.account_id - + token1 = create_fungible_token(env) - + # Associate the tokens to the new account - token_associate_transaction = TokenAssociateTransaction( - account_id=account_id, - token_ids=[token1] - ) - + token_associate_transaction = TokenAssociateTransaction(account_id=account_id, token_ids=[token1]) + token_associate_transaction.freeze_with(env.client) token_associate_transaction.sign(new_account_private_key) receipt = token_associate_transaction.execute(env.client) - assert receipt.status == ResponseCode.SUCCESS, f"Token association failed with status: {ResponseCode(receipt.status).name}" - + assert receipt.status == ResponseCode.SUCCESS, ( + f"Token association failed with status: {ResponseCode(receipt.status).name}" + ) + # Transfer the tokens to the new account token_transfer_transaction = TransferTransaction() token_transfer_transaction.add_token_transfer(token1, account_id, 10) token_transfer_transaction.add_token_transfer(token1, env.client.operator_account_id, -10) - + receipt = token_transfer_transaction.execute(env.client) - - assert receipt.status == ResponseCode.SUCCESS, f"Token transfer failed with status: {ResponseCode(receipt.status).name}" - + + assert receipt.status == ResponseCode.SUCCESS, ( + f"Token transfer failed with status: {ResponseCode(receipt.status).name}" + ) + wrong_key = PrivateKey.generate() - + # Reject the tokens - token_reject_transaction = TokenRejectTransaction( - owner_id=account_id, - token_ids=[token1] - ) - + token_reject_transaction = TokenRejectTransaction(owner_id=account_id, token_ids=[token1]) + token_reject_transaction.freeze_with(env.client) # Sign with wrong key token_reject_transaction.sign(wrong_key) receipt = token_reject_transaction.execute(env.client) - - assert receipt.status == ResponseCode.INVALID_SIGNATURE, f"Token rejection should have failed with INVALID_SIGNATURE status but got: {ResponseCode(receipt.status).name}" + + assert receipt.status == ResponseCode.INVALID_SIGNATURE, ( + f"Token rejection should have failed with INVALID_SIGNATURE status but got: {ResponseCode(receipt.status).name}" + ) finally: env.close() + @pytest.mark.integration def test_integration_token_reject_transaction_fails_with_reference_size_exceeded(): env = IntegrationTestEnv() - + try: nft_id = create_nft_token(env) - + token_id = create_fungible_token(env) - + # Mint 10 NFTs - receipt = (TokenMintTransaction().set_token_id(nft_id) - .set_metadata([b"1", b"2", b"3", b"4", b"5", b"6", b"7", b"8", b"9", b"10"]).execute(env.client)) - assert receipt.status == ResponseCode.SUCCESS, f"NFT minting failed with status: {ResponseCode(receipt.status).name}" + receipt = ( + TokenMintTransaction() + .set_token_id(nft_id) + .set_metadata([b"1", b"2", b"3", b"4", b"5", b"6", b"7", b"8", b"9", b"10"]) + .execute(env.client) + ) + assert receipt.status == ResponseCode.SUCCESS, ( + f"NFT minting failed with status: {ResponseCode(receipt.status).name}" + ) serials = receipt.serial_numbers - + new_account_private_key = PrivateKey.generate() new_account_public_key = new_account_private_key.public_key() - + account_transaction = AccountCreateTransaction( - key=new_account_public_key, - initial_balance=Hbar(1), - memo="Receiver Account" + key=new_account_public_key, initial_balance=Hbar(1), memo="Receiver Account" ) receipt = account_transaction.execute(env.client) - assert receipt.status == ResponseCode.SUCCESS, f"Account creation failed with status: {ResponseCode(receipt.status).name}" + assert receipt.status == ResponseCode.SUCCESS, ( + f"Account creation failed with status: {ResponseCode(receipt.status).name}" + ) account_id = receipt.account_id assert account_id is not None - + # Associate the tokens to the new account - token_associate_transaction = TokenAssociateTransaction( - account_id=account_id, - token_ids=[nft_id, token_id] - ) + token_associate_transaction = TokenAssociateTransaction(account_id=account_id, token_ids=[nft_id, token_id]) receipt = token_associate_transaction.freeze_with(env.client).sign(new_account_private_key).execute(env.client) - assert receipt.status == ResponseCode.SUCCESS, f"Token association failed with status: {ResponseCode(receipt.status).name}" - + assert receipt.status == ResponseCode.SUCCESS, ( + f"Token association failed with status: {ResponseCode(receipt.status).name}" + ) + # Transfer the tokens to the new account - receipt = (TransferTransaction().add_token_transfer(token_id, env.client.operator_account_id, -10).add_token_transfer(token_id, account_id, 10) - .add_nft_transfer(NftId(nft_id, serials[0]), env.client.operator_account_id, account_id).add_nft_transfer(NftId(nft_id, serials[1]), env.client.operator_account_id, account_id) - .add_nft_transfer(NftId(nft_id, serials[2]), env.client.operator_account_id, account_id).add_nft_transfer(NftId(nft_id, serials[3]), env.client.operator_account_id, account_id) - .add_nft_transfer(NftId(nft_id, serials[4]), env.client.operator_account_id, account_id).add_nft_transfer(NftId(nft_id, serials[5]), env.client.operator_account_id, account_id) - .add_nft_transfer(NftId(nft_id, serials[6]), env.client.operator_account_id, account_id).add_nft_transfer(NftId(nft_id, serials[7]), env.client.operator_account_id, account_id) - .add_nft_transfer(NftId(nft_id, serials[8]), env.client.operator_account_id, account_id).add_nft_transfer(NftId(nft_id, serials[9]), env.client.operator_account_id, account_id) - .execute(env.client)) - - assert receipt.status == ResponseCode.SUCCESS, f"NFT transfer failed with status: {ResponseCode(receipt.status).name}" - + receipt = ( + TransferTransaction() + .add_token_transfer(token_id, env.client.operator_account_id, -10) + .add_token_transfer(token_id, account_id, 10) + .add_nft_transfer(NftId(nft_id, serials[0]), env.client.operator_account_id, account_id) + .add_nft_transfer(NftId(nft_id, serials[1]), env.client.operator_account_id, account_id) + .add_nft_transfer(NftId(nft_id, serials[2]), env.client.operator_account_id, account_id) + .add_nft_transfer(NftId(nft_id, serials[3]), env.client.operator_account_id, account_id) + .add_nft_transfer(NftId(nft_id, serials[4]), env.client.operator_account_id, account_id) + .add_nft_transfer(NftId(nft_id, serials[5]), env.client.operator_account_id, account_id) + .add_nft_transfer(NftId(nft_id, serials[6]), env.client.operator_account_id, account_id) + .add_nft_transfer(NftId(nft_id, serials[7]), env.client.operator_account_id, account_id) + .add_nft_transfer(NftId(nft_id, serials[8]), env.client.operator_account_id, account_id) + .add_nft_transfer(NftId(nft_id, serials[9]), env.client.operator_account_id, account_id) + .execute(env.client) + ) + + assert receipt.status == ResponseCode.SUCCESS, ( + f"NFT transfer failed with status: {ResponseCode(receipt.status).name}" + ) + # Reject the tokens with 11 token references - should fail with TOKEN_REFERENCE_LIST_SIZE_LIMIT_EXCEEDED - token_reject_transaction = (TokenRejectTransaction().set_owner_id(account_id).set_token_ids([token_id]) - .set_nft_ids([NftId(nft_id, serials[0]), NftId(nft_id, serials[1]), NftId(nft_id, serials[2]), NftId(nft_id, serials[3]), NftId(nft_id, serials[4]), - NftId(nft_id, serials[5]), NftId(nft_id, serials[6]), NftId(nft_id, serials[7]), NftId(nft_id, serials[8]), NftId(nft_id, serials[9])])) - - token_reject_transaction.transaction_fee = Hbar(3).to_tinybars() # Set transaction fee to be higher than 2 Hbars + token_reject_transaction = ( + TokenRejectTransaction() + .set_owner_id(account_id) + .set_token_ids([token_id]) + .set_nft_ids( + [ + NftId(nft_id, serials[0]), + NftId(nft_id, serials[1]), + NftId(nft_id, serials[2]), + NftId(nft_id, serials[3]), + NftId(nft_id, serials[4]), + NftId(nft_id, serials[5]), + NftId(nft_id, serials[6]), + NftId(nft_id, serials[7]), + NftId(nft_id, serials[8]), + NftId(nft_id, serials[9]), + ] + ) + ) + + token_reject_transaction.transaction_fee = Hbar( + 3 + ).to_tinybars() # Set transaction fee to be higher than 2 Hbars receipt = token_reject_transaction.freeze_with(env.client).sign(new_account_private_key).execute(env.client) - assert receipt.status == ResponseCode.TOKEN_REFERENCE_LIST_SIZE_LIMIT_EXCEEDED, f"Token rejection should have failed with TOKEN_REFERENCE_LIST_SIZE_LIMIT_EXCEEDED status but got: {ResponseCode(receipt.status).name}" + assert receipt.status == ResponseCode.TOKEN_REFERENCE_LIST_SIZE_LIMIT_EXCEEDED, ( + f"Token rejection should have failed with TOKEN_REFERENCE_LIST_SIZE_LIMIT_EXCEEDED status but got: {ResponseCode(receipt.status).name}" + ) finally: env.close() + @pytest.mark.integration def test_integration_token_reject_transaction_fails_with_invalid_token_id(): env = IntegrationTestEnv() - + try: # Leave the token_ids/nft_ids empty token_reject_transaction = TokenRejectTransaction( owner_id=env.operator_id, ) - + with pytest.raises(PrecheckError, match="failed precheck with status: EMPTY_TOKEN_REFERENCE_LIST"): token_reject_transaction.execute(env.client) finally: env.close() - + + @pytest.mark.integration def test_integration_token_reject_transaction_fails_treasury_rejects(): env = IntegrationTestEnv() - + try: # Create fungible token with treasury token_id = create_fungible_token(env) - - # Skip the transfer - + + # Skip the transfer + # Reject the token with the treasury - token_reject_transaction = TokenRejectTransaction( - owner_id=env.operator_id, - token_ids=[token_id] - ) - + token_reject_transaction = TokenRejectTransaction(owner_id=env.operator_id, token_ids=[token_id]) + receipt = token_reject_transaction.execute(env.client) - assert receipt.status == ResponseCode.ACCOUNT_IS_TREASURY, f"Token rejection should have failed with ACCOUNT_IS_TREASURY status but got: {ResponseCode(receipt.status).name}" - + assert receipt.status == ResponseCode.ACCOUNT_IS_TREASURY, ( + f"Token rejection should have failed with ACCOUNT_IS_TREASURY status but got: {ResponseCode(receipt.status).name}" + ) + # Create NFT with treasury nft_token_id = create_nft_token(env) - + # Mint NFT - mint = TokenMintTransaction( - token_id=nft_token_id, - metadata=[b"test metadata"] - ) - + mint = TokenMintTransaction(token_id=nft_token_id, metadata=[b"test metadata"]) + receipt = mint.execute(env.client) - - assert receipt.status == ResponseCode.SUCCESS, f"NFT minting failed with status: {ResponseCode(receipt.status).name}" - - # Attempt to reject the NFT token with the treasury - token_reject_transaction = TokenRejectTransaction( - owner_id=env.operator_id, - token_ids=[nft_token_id] + + assert receipt.status == ResponseCode.SUCCESS, ( + f"NFT minting failed with status: {ResponseCode(receipt.status).name}" ) - + + # Attempt to reject the NFT token with the treasury + token_reject_transaction = TokenRejectTransaction(owner_id=env.operator_id, token_ids=[nft_token_id]) + receipt = token_reject_transaction.execute(env.client) - - assert receipt.status == ResponseCode.ACCOUNT_IS_TREASURY, f"Token rejection should have failed with ACCOUNT_IS_TREASURY status but got: {ResponseCode(receipt.status).name}" + + assert receipt.status == ResponseCode.ACCOUNT_IS_TREASURY, ( + f"Token rejection should have failed with ACCOUNT_IS_TREASURY status but got: {ResponseCode(receipt.status).name}" + ) finally: env.close() + @pytest.mark.integration def test_integration_token_reject_transaction_fails_when_fungible_token_owner_has_no_balance(): env = IntegrationTestEnv() - + try: # Create fungible token with treasury token_id = create_fungible_token(env) - + # Create receiver account new_account_private_key = PrivateKey.generate() new_account_public_key = new_account_private_key.public_key() - + account_transaction = AccountCreateTransaction( - key=new_account_public_key, - initial_balance=Hbar(1), - memo="Receiver Account" + key=new_account_public_key, initial_balance=Hbar(1), memo="Receiver Account" ) - + receipt = account_transaction.execute(env.client) - - assert receipt.status == ResponseCode.SUCCESS, f"Account creation failed with status: {ResponseCode(receipt.status).name}" - + + assert receipt.status == ResponseCode.SUCCESS, ( + f"Account creation failed with status: {ResponseCode(receipt.status).name}" + ) + account_id = receipt.account_id - + # Associate the token to the receiver account - token_associate_transaction = TokenAssociateTransaction( - account_id=account_id, - token_ids=[token_id] - ) - + token_associate_transaction = TokenAssociateTransaction(account_id=account_id, token_ids=[token_id]) + token_associate_transaction.freeze_with(env.client) token_associate_transaction.sign(new_account_private_key) receipt = token_associate_transaction.execute(env.client) - - assert receipt.status == ResponseCode.SUCCESS, f"Token association failed with status: {ResponseCode(receipt.status).name}" - + + assert receipt.status == ResponseCode.SUCCESS, ( + f"Token association failed with status: {ResponseCode(receipt.status).name}" + ) + # Skip the transfer - + # Reject the token - should fail with INSUFFICIENT_TOKEN_BALANCE - token_reject_transaction = TokenRejectTransaction( - owner_id=account_id, - token_ids=[token_id] - ) - + token_reject_transaction = TokenRejectTransaction(owner_id=account_id, token_ids=[token_id]) + token_reject_transaction.freeze_with(env.client) token_reject_transaction.sign(new_account_private_key) receipt = token_reject_transaction.execute(env.client) - - assert receipt.status == ResponseCode.INSUFFICIENT_TOKEN_BALANCE, f"Token rejection should have failed with INSUFFICIENT_TOKEN_BALANCE status but got: {ResponseCode(receipt.status).name}" + + assert receipt.status == ResponseCode.INSUFFICIENT_TOKEN_BALANCE, ( + f"Token rejection should have failed with INSUFFICIENT_TOKEN_BALANCE status but got: {ResponseCode(receipt.status).name}" + ) finally: env.close() + @pytest.mark.integration def test_integration_token_reject_transaction_fails_when_nft_owner_has_no_balance(): env = IntegrationTestEnv() - + try: # Create receiver account new_account_private_key = PrivateKey.generate() new_account_public_key = new_account_private_key.public_key() - + account_transaction = AccountCreateTransaction( - key=new_account_public_key, - initial_balance=Hbar(1), - memo="Receiver Account" + key=new_account_public_key, initial_balance=Hbar(1), memo="Receiver Account" ) - + receipt = account_transaction.execute(env.client) - - assert receipt.status == ResponseCode.SUCCESS, f"Account creation failed with status: {ResponseCode(receipt.status).name}" - + + assert receipt.status == ResponseCode.SUCCESS, ( + f"Account creation failed with status: {ResponseCode(receipt.status).name}" + ) + account_id = receipt.account_id - + # Create NFT with treasury nft_token_id = create_nft_token(env) - + # Mint NFTs - mint_transaction = TokenMintTransaction( - token_id=nft_token_id, - metadata=[b"test metadata"] - ) - + mint_transaction = TokenMintTransaction(token_id=nft_token_id, metadata=[b"test metadata"]) + receipt = mint_transaction.execute(env.client) - - assert receipt.status == ResponseCode.SUCCESS, f"NFT minting failed with status: {ResponseCode(receipt.status).name}" - + + assert receipt.status == ResponseCode.SUCCESS, ( + f"NFT minting failed with status: {ResponseCode(receipt.status).name}" + ) + serials = receipt.serial_numbers - + # Associate the NFT to the receiver account - nft_associate_transaction = TokenAssociateTransaction( - account_id=account_id, - token_ids=[nft_token_id] - ) - + nft_associate_transaction = TokenAssociateTransaction(account_id=account_id, token_ids=[nft_token_id]) + nft_associate_transaction.freeze_with(env.client) nft_associate_transaction.sign(new_account_private_key) receipt = nft_associate_transaction.execute(env.client) - - assert receipt.status == ResponseCode.SUCCESS, f"NFT association failed with status: {ResponseCode(receipt.status).name}" - + + assert receipt.status == ResponseCode.SUCCESS, ( + f"NFT association failed with status: {ResponseCode(receipt.status).name}" + ) + # Skip the transfer - + # Reject the NFT - should fail with INVALID_OWNER_ID nft_id = NftId(nft_token_id, serials[0]) - nft_reject_transaction = TokenRejectTransaction( - owner_id=account_id, - nft_ids=[nft_id] - ) - + nft_reject_transaction = TokenRejectTransaction(owner_id=account_id, nft_ids=[nft_id]) + nft_reject_transaction.freeze_with(env.client) nft_reject_transaction.sign(new_account_private_key) receipt = nft_reject_transaction.execute(env.client) - - assert receipt.status == ResponseCode.INVALID_OWNER_ID, f"Token rejection should have failed with INVALID_OWNER_ID status but got: {ResponseCode(receipt.status).name}" + + assert receipt.status == ResponseCode.INVALID_OWNER_ID, ( + f"Token rejection should have failed with INVALID_OWNER_ID status but got: {ResponseCode(receipt.status).name}" + ) finally: env.close() + @pytest.mark.integration def test_token_reject_transaction_fails_with_token_reference_repeated_fungible(): env = IntegrationTestEnv() - + try: # Create a new account with private key new_account_private_key = PrivateKey.generate() new_account_public_key = new_account_private_key.public_key() - + # Create the new account transaction = AccountCreateTransaction( - key=new_account_public_key, - initial_balance=Hbar(1), - memo="Recipient Account" + key=new_account_public_key, initial_balance=Hbar(1), memo="Recipient Account" ) - + receipt = transaction.execute(env.client) - - assert receipt.status == ResponseCode.SUCCESS, f"Account creation failed with status: {ResponseCode(receipt.status).name}" - + + assert receipt.status == ResponseCode.SUCCESS, ( + f"Account creation failed with status: {ResponseCode(receipt.status).name}" + ) + account_id = receipt.account_id - + # Create fungible token with treasury token_id = create_fungible_token(env) - + # Associate the token to the new account - token_associate_transaction = TokenAssociateTransaction( - account_id=account_id, - token_ids=[token_id] - ) - + token_associate_transaction = TokenAssociateTransaction(account_id=account_id, token_ids=[token_id]) + token_associate_transaction.freeze_with(env.client) token_associate_transaction.sign(new_account_private_key) receipt = token_associate_transaction.execute(env.client) - - assert receipt.status == ResponseCode.SUCCESS, f"Token association failed with status: {ResponseCode(receipt.status).name}" - + + assert receipt.status == ResponseCode.SUCCESS, ( + f"Token association failed with status: {ResponseCode(receipt.status).name}" + ) + # Transfer tokens to the new account token_transfer_transaction = TransferTransaction() token_transfer_transaction.add_token_transfer(token_id, account_id, 10) token_transfer_transaction.add_token_transfer(token_id, env.client.operator_account_id, -10) - + receipt = token_transfer_transaction.execute(env.client) - - assert receipt.status == ResponseCode.SUCCESS, f"Token transfer failed with status: {ResponseCode(receipt.status).name}" - + + assert receipt.status == ResponseCode.SUCCESS, ( + f"Token transfer failed with status: {ResponseCode(receipt.status).name}" + ) + # Reject the token with duplicate token id - should fail with TOKEN_REFERENCE_REPEATED token_reject_transaction = TokenRejectTransaction( owner_id=account_id, - token_ids=[token_id, token_id] # Duplicate token ID + token_ids=[token_id, token_id], # Duplicate token ID ) - + token_reject_transaction.freeze_with(env.client) token_reject_transaction.sign(new_account_private_key) - + with pytest.raises(PrecheckError, match="failed precheck with status: TOKEN_REFERENCE_REPEATED"): token_reject_transaction.execute(env.client) finally: env.close() + @pytest.mark.integration def test_token_reject_transaction_fails_with_token_reference_repeated_nft(): env = IntegrationTestEnv() - + try: # Create a new account with private key new_account_private_key = PrivateKey.generate() new_account_public_key = new_account_private_key.public_key() - + # Create the new account transaction = AccountCreateTransaction( - key=new_account_public_key, - initial_balance=Hbar(1), - memo="Recipient Account" + key=new_account_public_key, initial_balance=Hbar(1), memo="Recipient Account" ) - + receipt = transaction.execute(env.client) - - assert receipt.status == ResponseCode.SUCCESS, f"Account creation failed with status: {ResponseCode(receipt.status).name}" - + + assert receipt.status == ResponseCode.SUCCESS, ( + f"Account creation failed with status: {ResponseCode(receipt.status).name}" + ) + account_id = receipt.account_id - + # Create NFT with treasury nft_token_id = create_nft_token(env) - + # Mint NFTs - mint_transaction = TokenMintTransaction( - token_id=nft_token_id, - metadata=[b"test metadata"] - ) - + mint_transaction = TokenMintTransaction(token_id=nft_token_id, metadata=[b"test metadata"]) + receipt = mint_transaction.execute(env.client) - - assert receipt.status == ResponseCode.SUCCESS, f"NFT minting failed with status: {ResponseCode(receipt.status).name}" - + + assert receipt.status == ResponseCode.SUCCESS, ( + f"NFT minting failed with status: {ResponseCode(receipt.status).name}" + ) + serials = receipt.serial_numbers - + # Associate the NFT to the receiver account - nft_associate_transaction = TokenAssociateTransaction( - account_id=account_id, - token_ids=[nft_token_id] - ) - + nft_associate_transaction = TokenAssociateTransaction(account_id=account_id, token_ids=[nft_token_id]) + nft_associate_transaction.freeze_with(env.client) nft_associate_transaction.sign(new_account_private_key) receipt = nft_associate_transaction.execute(env.client) - - assert receipt.status == ResponseCode.SUCCESS, f"NFT association failed with status: {ResponseCode(receipt.status).name}" - + + assert receipt.status == ResponseCode.SUCCESS, ( + f"NFT association failed with status: {ResponseCode(receipt.status).name}" + ) + # Transfer NFT to the receiver nft_transfer_transaction = TransferTransaction() nft_transfer_transaction.add_nft_transfer( - NftId(nft_token_id, serials[0]), - env.client.operator_account_id, - account_id + NftId(nft_token_id, serials[0]), env.client.operator_account_id, account_id ) - + receipt = nft_transfer_transaction.execute(env.client) - - assert receipt.status == ResponseCode.SUCCESS, f"NFT transfer failed with status: {ResponseCode(receipt.status).name}" - + + assert receipt.status == ResponseCode.SUCCESS, ( + f"NFT transfer failed with status: {ResponseCode(receipt.status).name}" + ) + # Reject the NFT with duplicate NFT id - should fail with TOKEN_REFERENCE_REPEATED nft_id = NftId(nft_token_id, serials[0]) nft_reject_transaction = TokenRejectTransaction( owner_id=account_id, - nft_ids=[nft_id, nft_id] # Duplicate NFT ID + nft_ids=[nft_id, nft_id], # Duplicate NFT ID ) - + nft_reject_transaction.freeze_with(env.client) nft_reject_transaction.sign(new_account_private_key) - + with pytest.raises(PrecheckError, match="failed precheck with status: TOKEN_REFERENCE_REPEATED"): nft_reject_transaction.execute(env.client) finally: env.close() + @pytest.mark.integration def test_integration_token_reject_transaction_fails_when_rejecting_nft_with_token_id(): env = IntegrationTestEnv() - + try: # Create NFT with treasury nft_token_id = create_nft_token(env) - + # Mint NFTs mint_transaction = TokenMintTransaction( - token_id=nft_token_id, - metadata=[b"metadata1", b"metadata2", b"metadata3"] + token_id=nft_token_id, metadata=[b"metadata1", b"metadata2", b"metadata3"] ) - + receipt = mint_transaction.execute(env.client) - - assert receipt.status == ResponseCode.SUCCESS, f"NFT minting failed with status: {ResponseCode(receipt.status).name}" - + + assert receipt.status == ResponseCode.SUCCESS, ( + f"NFT minting failed with status: {ResponseCode(receipt.status).name}" + ) + serials = receipt.serial_numbers - + # Create receiver account with auto associations new_account_private_key = PrivateKey.generate() new_account_public_key = new_account_private_key.public_key() - + account_transaction = AccountCreateTransaction( - key=new_account_public_key, - initial_balance=Hbar(1), - memo="Receiver Account" + key=new_account_public_key, initial_balance=Hbar(1), memo="Receiver Account" ) - + receipt = account_transaction.execute(env.client) - - assert receipt.status == ResponseCode.SUCCESS, f"Account creation failed with status: {ResponseCode(receipt.status).name}" - + + assert receipt.status == ResponseCode.SUCCESS, ( + f"Account creation failed with status: {ResponseCode(receipt.status).name}" + ) + account_id = receipt.account_id - + # Associate the NFT to the receiver - nft_associate_transaction = TokenAssociateTransaction( - account_id=account_id, - token_ids=[nft_token_id] - ) - + nft_associate_transaction = TokenAssociateTransaction(account_id=account_id, token_ids=[nft_token_id]) + nft_associate_transaction.freeze_with(env.client) nft_associate_transaction.sign(new_account_private_key) receipt = nft_associate_transaction.execute(env.client) - - assert receipt.status == ResponseCode.SUCCESS, f"NFT association failed with status: {ResponseCode(receipt.status).name}" - + + assert receipt.status == ResponseCode.SUCCESS, ( + f"NFT association failed with status: {ResponseCode(receipt.status).name}" + ) + # Transfer NFTs to the receiver nft_transfer_transaction = TransferTransaction() - nft_transfer_transaction.add_nft_transfer(NftId(nft_token_id, serials[0]), env.client.operator_account_id, account_id) - nft_transfer_transaction.add_nft_transfer(NftId(nft_token_id, serials[1]), env.client.operator_account_id, account_id) - nft_transfer_transaction.add_nft_transfer(NftId(nft_token_id, serials[2]), env.client.operator_account_id, account_id) - + nft_transfer_transaction.add_nft_transfer( + NftId(nft_token_id, serials[0]), env.client.operator_account_id, account_id + ) + nft_transfer_transaction.add_nft_transfer( + NftId(nft_token_id, serials[1]), env.client.operator_account_id, account_id + ) + nft_transfer_transaction.add_nft_transfer( + NftId(nft_token_id, serials[2]), env.client.operator_account_id, account_id + ) + receipt = nft_transfer_transaction.execute(env.client) - - assert receipt.status == ResponseCode.SUCCESS, f"NFT transfer failed with status: {ResponseCode(receipt.status).name}" - - # Reject the whole collection - should fail - token_reject_transaction = TokenRejectTransaction( - owner_id=account_id, - token_ids=[nft_token_id] + + assert receipt.status == ResponseCode.SUCCESS, ( + f"NFT transfer failed with status: {ResponseCode(receipt.status).name}" ) - + + # Reject the whole collection - should fail + token_reject_transaction = TokenRejectTransaction(owner_id=account_id, token_ids=[nft_token_id]) + token_reject_transaction.freeze_with(env.client) token_reject_transaction.sign(new_account_private_key) - + receipt = token_reject_transaction.execute(env.client) - - assert receipt.status == ResponseCode.ACCOUNT_AMOUNT_TRANSFERS_ONLY_ALLOWED_FOR_FUNGIBLE_COMMON, f"Token rejection should have failed with ACCOUNT_AMOUNT_TRANSFERS_ONLY_ALLOWED_FOR_FUNGIBLE_COMMON status but got: {ResponseCode(receipt.status).name}" + + assert receipt.status == ResponseCode.ACCOUNT_AMOUNT_TRANSFERS_ONLY_ALLOWED_FOR_FUNGIBLE_COMMON, ( + f"Token rejection should have failed with ACCOUNT_AMOUNT_TRANSFERS_ONLY_ALLOWED_FOR_FUNGIBLE_COMMON status but got: {ResponseCode(receipt.status).name}" + ) finally: env.close() + @pytest.mark.integration def test_token_reject_transaction_fails_with_nft_token_frozen(): env = IntegrationTestEnv() try: # Create NFT with treasury nft_token_id = create_nft_token(env) - + # Mint NFTs mint_transaction = TokenMintTransaction( - token_id=nft_token_id, - metadata=[b"test metadata", b"test metadata", b"test metadata"] + token_id=nft_token_id, metadata=[b"test metadata", b"test metadata", b"test metadata"] ) - + receipt = mint_transaction.execute(env.client) - - assert receipt.status == ResponseCode.SUCCESS, f"NFT minting failed with status: {ResponseCode(receipt.status).name}" - + + assert receipt.status == ResponseCode.SUCCESS, ( + f"NFT minting failed with status: {ResponseCode(receipt.status).name}" + ) + serials = receipt.serial_numbers - + # Create a new account with private key new_account_private_key = PrivateKey.generate() new_account_public_key = new_account_private_key.public_key() - + # Create the new account transaction = AccountCreateTransaction( - key=new_account_public_key, - initial_balance=Hbar(1), - memo="Recipient Account" + key=new_account_public_key, initial_balance=Hbar(1), memo="Recipient Account" ) - + receipt = transaction.execute(env.client) - - assert receipt.status == ResponseCode.SUCCESS, f"Account creation failed with status: {ResponseCode(receipt.status).name}" - + + assert receipt.status == ResponseCode.SUCCESS, ( + f"Account creation failed with status: {ResponseCode(receipt.status).name}" + ) + account_id = receipt.account_id - + # Associate the NFT to the receiver - associate_transaction = TokenAssociateTransaction( - account_id=account_id, - token_ids=[nft_token_id] - ) - + associate_transaction = TokenAssociateTransaction(account_id=account_id, token_ids=[nft_token_id]) + associate_transaction.freeze_with(env.client) associate_transaction.sign(new_account_private_key) receipt = associate_transaction.execute(env.client) - - assert receipt.status == ResponseCode.SUCCESS, f"NFT association failed with status: {ResponseCode(receipt.status).name}" - + + assert receipt.status == ResponseCode.SUCCESS, ( + f"NFT association failed with status: {ResponseCode(receipt.status).name}" + ) + # Transfer NFTs to the receiver nft_transfer_transaction = TransferTransaction() - nft_transfer_transaction.add_nft_transfer(NftId(nft_token_id, serials[0]), env.client.operator_account_id, account_id) - nft_transfer_transaction.add_nft_transfer(NftId(nft_token_id, serials[1]), env.client.operator_account_id, account_id) - + nft_transfer_transaction.add_nft_transfer( + NftId(nft_token_id, serials[0]), env.client.operator_account_id, account_id + ) + nft_transfer_transaction.add_nft_transfer( + NftId(nft_token_id, serials[1]), env.client.operator_account_id, account_id + ) + receipt = nft_transfer_transaction.execute(env.client) - - assert receipt.status == ResponseCode.SUCCESS, f"NFT transfer failed with status: {ResponseCode(receipt.status).name}" - - # Freeze the token - token_freeze_transaction = TokenFreezeTransaction( - token_id=nft_token_id, - account_id=account_id + + assert receipt.status == ResponseCode.SUCCESS, ( + f"NFT transfer failed with status: {ResponseCode(receipt.status).name}" ) - + + # Freeze the token + token_freeze_transaction = TokenFreezeTransaction(token_id=nft_token_id, account_id=account_id) + receipt = token_freeze_transaction.execute(env.client) - - assert receipt.status == ResponseCode.SUCCESS, f"Token freeze failed with status: {ResponseCode(receipt.status).name}" - - # Reject the NFT - should fail with ACCOUNT_FROZEN_FOR_TOKEN - nft_reject_transaction = TokenRejectTransaction( - owner_id=account_id, - nft_ids=[NftId(nft_token_id, serials[1])] + + assert receipt.status == ResponseCode.SUCCESS, ( + f"Token freeze failed with status: {ResponseCode(receipt.status).name}" ) - + + # Reject the NFT - should fail with ACCOUNT_FROZEN_FOR_TOKEN + nft_reject_transaction = TokenRejectTransaction(owner_id=account_id, nft_ids=[NftId(nft_token_id, serials[1])]) + nft_reject_transaction.freeze_with(env.client) nft_reject_transaction.sign(new_account_private_key) receipt = nft_reject_transaction.execute(env.client) - - assert receipt.status == ResponseCode.ACCOUNT_FROZEN_FOR_TOKEN, f"Token rejection should have failed with ACCOUNT_FROZEN_FOR_TOKEN status but got: {ResponseCode(receipt.status).name}" + + assert receipt.status == ResponseCode.ACCOUNT_FROZEN_FOR_TOKEN, ( + f"Token rejection should have failed with ACCOUNT_FROZEN_FOR_TOKEN status but got: {ResponseCode(receipt.status).name}" + ) finally: env.close() + @pytest.mark.integration def test_token_reject_transaction_fails_with_fungible_token_frozen(): env = IntegrationTestEnv() - + try: # Create a new account with private key new_account_private_key = PrivateKey.generate() new_account_public_key = new_account_private_key.public_key() - + # Create the new account transaction = AccountCreateTransaction( - key=new_account_public_key, - initial_balance=Hbar(1), - memo="Recipient Account" + key=new_account_public_key, initial_balance=Hbar(1), memo="Recipient Account" ) - + receipt = transaction.execute(env.client) - - assert receipt.status == ResponseCode.SUCCESS, f"Account creation failed with status: {ResponseCode(receipt.status).name}" - + + assert receipt.status == ResponseCode.SUCCESS, ( + f"Account creation failed with status: {ResponseCode(receipt.status).name}" + ) + account_id = receipt.account_id - + # Create fungible token with treasury token_id = create_fungible_token(env) - + # Associate the token to the receiver - associate_transaction = TokenAssociateTransaction( - account_id=account_id, - token_ids=[token_id] - ) - + associate_transaction = TokenAssociateTransaction(account_id=account_id, token_ids=[token_id]) + associate_transaction.freeze_with(env.client) associate_transaction.sign(new_account_private_key) receipt = associate_transaction.execute(env.client) - - assert receipt.status == ResponseCode.SUCCESS, f"Token association failed with status: {ResponseCode(receipt.status).name}" - + + assert receipt.status == ResponseCode.SUCCESS, ( + f"Token association failed with status: {ResponseCode(receipt.status).name}" + ) + # Transfer tokens to the receiver token_transfer_transaction = TransferTransaction() token_transfer_transaction.add_token_transfer(token_id, account_id, 10) token_transfer_transaction.add_token_transfer(token_id, env.client.operator_account_id, -10) receipt = token_transfer_transaction.execute(env.client) - - assert receipt.status == ResponseCode.SUCCESS, f"Token transfer failed with status: {ResponseCode(receipt.status).name}" - - # Freeze the token - token_freeze_transaction = TokenFreezeTransaction( - token_id=token_id, - account_id=account_id + + assert receipt.status == ResponseCode.SUCCESS, ( + f"Token transfer failed with status: {ResponseCode(receipt.status).name}" ) - + + # Freeze the token + token_freeze_transaction = TokenFreezeTransaction(token_id=token_id, account_id=account_id) + receipt = token_freeze_transaction.execute(env.client) - - assert receipt.status == ResponseCode.SUCCESS, f"Token freeze failed with status: {ResponseCode(receipt.status).name}" - - # Reject the token - should fail with ACCOUNT_FROZEN_FOR_TOKEN - token_reject_transaction = TokenRejectTransaction( - owner_id=account_id, - token_ids=[token_id] + + assert receipt.status == ResponseCode.SUCCESS, ( + f"Token freeze failed with status: {ResponseCode(receipt.status).name}" ) - + + # Reject the token - should fail with ACCOUNT_FROZEN_FOR_TOKEN + token_reject_transaction = TokenRejectTransaction(owner_id=account_id, token_ids=[token_id]) + token_reject_transaction.freeze_with(env.client) token_reject_transaction.sign(new_account_private_key) receipt = token_reject_transaction.execute(env.client) - - assert receipt.status == ResponseCode.ACCOUNT_FROZEN_FOR_TOKEN, f"Token rejection should have failed with ACCOUNT_FROZEN_FOR_TOKEN status but got: {ResponseCode(receipt.status).name}" + + assert receipt.status == ResponseCode.ACCOUNT_FROZEN_FOR_TOKEN, ( + f"Token rejection should have failed with ACCOUNT_FROZEN_FOR_TOKEN status but got: {ResponseCode(receipt.status).name}" + ) finally: env.close() + @pytest.mark.integration def test_token_reject_transaction_receiver_sig_required_nft(): env = IntegrationTestEnv() - + try: treasury_private_key = PrivateKey.generate() treasury_public_key = treasury_private_key.public_key() - - receipt = (AccountCreateTransaction().set_key_without_alias(treasury_public_key).set_initial_balance(Hbar(0)) - .set_receiver_signature_required(True).set_account_memo("Treasury Account") - .freeze_with(env.client).sign(treasury_private_key).execute(env.client)) - assert receipt.status == ResponseCode.SUCCESS, f"Treasury account creation failed with status: {ResponseCode(receipt.status).name}" + + receipt = ( + AccountCreateTransaction() + .set_key_without_alias(treasury_public_key) + .set_initial_balance(Hbar(0)) + .set_receiver_signature_required(True) + .set_account_memo("Treasury Account") + .freeze_with(env.client) + .sign(treasury_private_key) + .execute(env.client) + ) + assert receipt.status == ResponseCode.SUCCESS, ( + f"Treasury account creation failed with status: {ResponseCode(receipt.status).name}" + ) treasury_id = receipt.account_id - + # Create a new NFT token with a custom treasury account that requires receiver signatures # Pass lambda functions to create_nft_token to configure the treasury account and sign with its key - nft_token_id = create_nft_token(env, [ - lambda tx: tx.set_treasury_account_id(treasury_id).freeze_with(env.client), - lambda tx: tx.sign(treasury_private_key) - ]) - - receipt = (TokenMintTransaction().set_token_id(nft_token_id).set_metadata([b"test metadata 1", b"test metadata 2"]).execute(env.client)) - assert receipt.status == ResponseCode.SUCCESS, f"NFT minting failed with status: {ResponseCode(receipt.status).name}" + nft_token_id = create_nft_token( + env, + [ + lambda tx: tx.set_treasury_account_id(treasury_id).freeze_with(env.client), + lambda tx: tx.sign(treasury_private_key), + ], + ) + + receipt = ( + TokenMintTransaction() + .set_token_id(nft_token_id) + .set_metadata([b"test metadata 1", b"test metadata 2"]) + .execute(env.client) + ) + assert receipt.status == ResponseCode.SUCCESS, ( + f"NFT minting failed with status: {ResponseCode(receipt.status).name}" + ) serials = receipt.serial_numbers - + receiver_private_key = PrivateKey.generate() receiver_public_key = receiver_private_key.public_key() - - receipt = (AccountCreateTransaction().set_key_without_alias(receiver_public_key).set_initial_balance(Hbar(1)).set_account_memo("Receiver Account").execute(env.client)) - assert receipt.status == ResponseCode.SUCCESS, f"Receiver account creation failed with status: {ResponseCode(receipt.status).name}" - receiver_id = receipt.account_id - - associate_transaction = TokenAssociateTransaction( - account_id=receiver_id, - token_ids=[nft_token_id] + + receipt = ( + AccountCreateTransaction() + .set_key_without_alias(receiver_public_key) + .set_initial_balance(Hbar(1)) + .set_account_memo("Receiver Account") + .execute(env.client) ) + assert receipt.status == ResponseCode.SUCCESS, ( + f"Receiver account creation failed with status: {ResponseCode(receipt.status).name}" + ) + receiver_id = receipt.account_id + + associate_transaction = TokenAssociateTransaction(account_id=receiver_id, token_ids=[nft_token_id]) receipt = associate_transaction.freeze_with(env.client).sign(receiver_private_key).execute(env.client) - assert receipt.status == ResponseCode.SUCCESS, f"NFT association failed with status: {ResponseCode(receipt.status).name}" - + assert receipt.status == ResponseCode.SUCCESS, ( + f"NFT association failed with status: {ResponseCode(receipt.status).name}" + ) + nft_transfer_transaction = TransferTransaction() nft_transfer_transaction.add_nft_transfer(NftId(nft_token_id, serials[0]), treasury_id, receiver_id) nft_transfer_transaction.add_nft_transfer(NftId(nft_token_id, serials[1]), treasury_id, receiver_id) - + receipt = nft_transfer_transaction.freeze_with(env.client).sign(treasury_private_key).execute(env.client) - assert receipt.status == ResponseCode.SUCCESS, f"NFT transfer failed with status: {ResponseCode(receipt.status).name}" - + assert receipt.status == ResponseCode.SUCCESS, ( + f"NFT transfer failed with status: {ResponseCode(receipt.status).name}" + ) + # Reject one of the NFTs nft_id = NftId(nft_token_id, serials[1]) - receipt = (TokenRejectTransaction().set_owner_id(receiver_id).set_nft_ids([nft_id]) - .freeze_with(env.client).sign(receiver_private_key).execute(env.client)) - assert receipt.status == ResponseCode.SUCCESS, f"NFT rejection failed with status: {ResponseCode(receipt.status).name}" - + receipt = ( + TokenRejectTransaction() + .set_owner_id(receiver_id) + .set_nft_ids([nft_id]) + .freeze_with(env.client) + .sign(receiver_private_key) + .execute(env.client) + ) + assert receipt.status == ResponseCode.SUCCESS, ( + f"NFT rejection failed with status: {ResponseCode(receipt.status).name}" + ) + # Verify the balance is decremented by 1 token_balance = CryptoGetAccountBalanceQuery(account_id=receiver_id).execute(env.client) - assert token_balance.token_balances.get(nft_token_id) == 1, f"Expected NFT balance to be 1, got {token_balance.token_balances.get(nft_token_id)}" - + assert token_balance.token_balances.get(nft_token_id) == 1, ( + f"Expected NFT balance to be 1, got {token_balance.token_balances.get(nft_token_id)}" + ) + # Verify the NFT is transferred back to the treasury nft_info = TokenNftInfoQuery(nft_id=nft_id).execute(env.client) assert nft_info.account_id == treasury_id, f"Expected NFT owner to be {treasury_id}, got {nft_info.account_id}" finally: env.close() + @pytest.mark.integration def test_token_reject_transaction_receiver_sig_required_fungible(): env = IntegrationTestEnv() - + try: treasury_private_key = PrivateKey.generate() treasury_public_key = treasury_private_key.public_key() - + transaction = AccountCreateTransaction( - key=treasury_public_key, - initial_balance=Hbar(0), - receiver_signature_required=True, - memo="Treasury Account" + key=treasury_public_key, initial_balance=Hbar(0), receiver_signature_required=True, memo="Treasury Account" ) receipt = transaction.freeze_with(env.client).sign(treasury_private_key).execute(env.client) - assert receipt.status == ResponseCode.SUCCESS, f"Treasury account creation failed with status: {ResponseCode(receipt.status).name}" + assert receipt.status == ResponseCode.SUCCESS, ( + f"Treasury account creation failed with status: {ResponseCode(receipt.status).name}" + ) treasury_id = receipt.account_id - + # Create a fungible token with a custom treasury account that requires receiver signatures # Pass lambda functions to create_fungible_token to configure the treasury account and sign with its key - fungible_token_id = create_fungible_token(env, [ - lambda tx: tx.set_treasury_account_id(treasury_id).freeze_with(env.client), - lambda tx: tx.sign(treasury_private_key) - ]) - + fungible_token_id = create_fungible_token( + env, + [ + lambda tx: tx.set_treasury_account_id(treasury_id).freeze_with(env.client), + lambda tx: tx.sign(treasury_private_key), + ], + ) + # Create receiver account receiver_private_key = PrivateKey.generate() receiver_public_key = receiver_private_key.public_key() - + receiver_transaction = AccountCreateTransaction( - key=receiver_public_key, - initial_balance=Hbar(1), - memo="Receiver Account" + key=receiver_public_key, initial_balance=Hbar(1), memo="Receiver Account" ) receipt = receiver_transaction.execute(env.client) - assert receipt.status == ResponseCode.SUCCESS, f"Receiver account creation failed with status: {ResponseCode(receipt.status).name}" + assert receipt.status == ResponseCode.SUCCESS, ( + f"Receiver account creation failed with status: {ResponseCode(receipt.status).name}" + ) receiver_id = receipt.account_id - + # Associate the token to the receiver - associate_transaction = TokenAssociateTransaction( - account_id=receiver_id, - token_ids=[fungible_token_id] - ) + associate_transaction = TokenAssociateTransaction(account_id=receiver_id, token_ids=[fungible_token_id]) receipt = associate_transaction.freeze_with(env.client).sign(receiver_private_key).execute(env.client) - assert receipt.status == ResponseCode.SUCCESS, f"Token association failed with status: {ResponseCode(receipt.status).name}" - + assert receipt.status == ResponseCode.SUCCESS, ( + f"Token association failed with status: {ResponseCode(receipt.status).name}" + ) + # Transfer tokens to the receiver ft_transfer_transaction = TransferTransaction() ft_transfer_transaction.add_token_transfer(fungible_token_id, treasury_id, -10) ft_transfer_transaction.add_token_transfer(fungible_token_id, receiver_id, 10) - + receipt = ft_transfer_transaction.freeze_with(env.client).sign(treasury_private_key).execute(env.client) - assert receipt.status == ResponseCode.SUCCESS, f"Token transfer failed with status: {ResponseCode(receipt.status).name}" - + assert receipt.status == ResponseCode.SUCCESS, ( + f"Token transfer failed with status: {ResponseCode(receipt.status).name}" + ) + # Reject the token - receipt = (TokenRejectTransaction().set_owner_id(receiver_id).set_token_ids([fungible_token_id]) - .freeze_with(env.client).sign(receiver_private_key).execute(env.client)) - assert receipt.status == ResponseCode.SUCCESS, f"Token rejection failed with status: {ResponseCode(receipt.status).name}" - + receipt = ( + TokenRejectTransaction() + .set_owner_id(receiver_id) + .set_token_ids([fungible_token_id]) + .freeze_with(env.client) + .sign(receiver_private_key) + .execute(env.client) + ) + assert receipt.status == ResponseCode.SUCCESS, ( + f"Token rejection failed with status: {ResponseCode(receipt.status).name}" + ) + # Verify the balance of the receiver is 0 receiver_balance = CryptoGetAccountBalanceQuery(account_id=receiver_id).execute(env.client) - assert receiver_balance.token_balances.get(fungible_token_id) == 0, f"Expected token balance to be 0, got {receiver_balance.token_balances.get(fungible_token_id)}" - + assert receiver_balance.token_balances.get(fungible_token_id) == 0, ( + f"Expected token balance to be 0, got {receiver_balance.token_balances.get(fungible_token_id)}" + ) + # Verify the tokens are transferred back to the treasury treasury_balance = CryptoGetAccountBalanceQuery(account_id=treasury_id).execute(env.client) - assert treasury_balance.token_balances.get(fungible_token_id) == 1000, f"Expected treasury token balance to be 1000, got {treasury_balance.token_balances.get(fungible_token_id)}" + assert treasury_balance.token_balances.get(fungible_token_id) == 1000, ( + f"Expected treasury token balance to be 1000, got {treasury_balance.token_balances.get(fungible_token_id)}" + ) finally: env.close() diff --git a/tests/integration/token_revoke_kyc_transaction_e2e_test.py b/tests/integration/token_revoke_kyc_transaction_e2e_test.py index 6f5c27509..46dafc628 100644 --- a/tests/integration/token_revoke_kyc_transaction_e2e_test.py +++ b/tests/integration/token_revoke_kyc_transaction_e2e_test.py @@ -1,22 +1,25 @@ +from __future__ import annotations + import pytest +from hiero_sdk_python.account.account_create_transaction import AccountCreateTransaction from hiero_sdk_python.crypto.private_key import PrivateKey from hiero_sdk_python.hbar import Hbar -from hiero_sdk_python.tokens.token_associate_transaction import TokenAssociateTransaction -from hiero_sdk_python.account.account_create_transaction import AccountCreateTransaction from hiero_sdk_python.response_code import ResponseCode +from hiero_sdk_python.tokens.token_associate_transaction import TokenAssociateTransaction from hiero_sdk_python.tokens.token_grant_kyc_transaction import TokenGrantKycTransaction from hiero_sdk_python.tokens.token_revoke_kyc_transaction import TokenRevokeKycTransaction from tests.integration.utils import IntegrationTestEnv, create_fungible_token + @pytest.mark.integration def test_token_revoke_kyc_transaction_can_execute(): env = IntegrationTestEnv() - + try: new_account_private_key = PrivateKey.generate_ed25519() new_account_public_key = new_account_private_key.public_key() - + # Create a new account receipt = ( AccountCreateTransaction() @@ -24,12 +27,14 @@ def test_token_revoke_kyc_transaction_can_execute(): .set_initial_balance(Hbar(2)) .execute(env.client) ) - assert receipt.status == ResponseCode.SUCCESS, f"Account creation failed with status: {ResponseCode(receipt.status).name}" + assert receipt.status == ResponseCode.SUCCESS, ( + f"Account creation failed with status: {ResponseCode(receipt.status).name}" + ) account_id = receipt.account_id # Create a new token and set the kyc key to be the operator's key token_id = create_fungible_token(env, [lambda tx: tx.set_kyc_key(env.operator_key)]) - + # Associate the token to the new account receipt = ( TokenAssociateTransaction() @@ -39,36 +44,33 @@ def test_token_revoke_kyc_transaction_can_execute(): .sign(new_account_private_key) .execute(env.client) ) - assert receipt.status == ResponseCode.SUCCESS, f"Token association failed with status: {ResponseCode(receipt.status).name}" - + assert receipt.status == ResponseCode.SUCCESS, ( + f"Token association failed with status: {ResponseCode(receipt.status).name}" + ) + # Grant KYC to the new account first - receipt = ( - TokenGrantKycTransaction() - .set_account_id(account_id) - .set_token_id(token_id) - .execute(env.client) + receipt = TokenGrantKycTransaction().set_account_id(account_id).set_token_id(token_id).execute(env.client) + assert receipt.status == ResponseCode.SUCCESS, ( + f"Token grant KYC failed with status: {ResponseCode(receipt.status).name}" ) - assert receipt.status == ResponseCode.SUCCESS, f"Token grant KYC failed with status: {ResponseCode(receipt.status).name}" - + # Revoke KYC from the new account - receipt = ( - TokenRevokeKycTransaction() - .set_account_id(account_id) - .set_token_id(token_id) - .execute(env.client) + receipt = TokenRevokeKycTransaction().set_account_id(account_id).set_token_id(token_id).execute(env.client) + assert receipt.status == ResponseCode.SUCCESS, ( + f"Token revoke KYC failed with status: {ResponseCode(receipt.status).name}" ) - assert receipt.status == ResponseCode.SUCCESS, f"Token revoke KYC failed with status: {ResponseCode(receipt.status).name}" finally: env.close() + @pytest.mark.integration def test_token_revoke_kyc_transaction_fails_with_no_kyc_key(): env = IntegrationTestEnv() - + try: new_account_private_key = PrivateKey.generate_ed25519() new_account_public_key = new_account_private_key.public_key() - + # Create a new account receipt = ( AccountCreateTransaction() @@ -76,12 +78,14 @@ def test_token_revoke_kyc_transaction_fails_with_no_kyc_key(): .set_initial_balance(Hbar(2)) .execute(env.client) ) - assert receipt.status == ResponseCode.SUCCESS, f"Account creation failed with status: {ResponseCode(receipt.status).name}" + assert receipt.status == ResponseCode.SUCCESS, ( + f"Account creation failed with status: {ResponseCode(receipt.status).name}" + ) account_id = receipt.account_id - + # Create a new token without KYC key token_id = create_fungible_token(env) - + # Associate the token to the new account receipt = ( TokenAssociateTransaction() @@ -91,36 +95,33 @@ def test_token_revoke_kyc_transaction_fails_with_no_kyc_key(): .sign(new_account_private_key) .execute(env.client) ) - assert receipt.status == ResponseCode.SUCCESS, f"Token association failed with status: {ResponseCode(receipt.status).name}" - + assert receipt.status == ResponseCode.SUCCESS, ( + f"Token association failed with status: {ResponseCode(receipt.status).name}" + ) + # Try to revoke KYC for token without KYC key - should fail with TOKEN_HAS_NO_KYC_KEY - receipt = ( - TokenRevokeKycTransaction() - .set_account_id(account_id) - .set_token_id(token_id) - .execute(env.client) + receipt = TokenRevokeKycTransaction().set_account_id(account_id).set_token_id(token_id).execute(env.client) + assert receipt.status == ResponseCode.TOKEN_HAS_NO_KYC_KEY, ( + f"Token revoke KYC should have failed with TOKEN_HAS_NO_KYC_KEY status but got: {ResponseCode(receipt.status).name}" ) - assert receipt.status == ResponseCode.TOKEN_HAS_NO_KYC_KEY, f"Token revoke KYC should have failed with TOKEN_HAS_NO_KYC_KEY status but got: {ResponseCode(receipt.status).name}" - + # Try to revoke KYC with non-KYC key - should fail with TOKEN_HAS_NO_KYC_KEY - receipt = ( - TokenRevokeKycTransaction() - .set_account_id(account_id) - .set_token_id(token_id) - .execute(env.client) + receipt = TokenRevokeKycTransaction().set_account_id(account_id).set_token_id(token_id).execute(env.client) + assert receipt.status == ResponseCode.TOKEN_HAS_NO_KYC_KEY, ( + f"Token revoke KYC should have failed with TOKEN_HAS_NO_KYC_KEY status but got: {ResponseCode(receipt.status).name}" ) - assert receipt.status == ResponseCode.TOKEN_HAS_NO_KYC_KEY, f"Token revoke KYC should have failed with TOKEN_HAS_NO_KYC_KEY status but got: {ResponseCode(receipt.status).name}" finally: env.close() - + + @pytest.mark.integration def test_token_revoke_kyc_transaction_fails_when_account_not_associated(): env = IntegrationTestEnv() - + try: new_account_private_key = PrivateKey.generate_ed25519() new_account_public_key = new_account_private_key.public_key() - + # Create a new account receipt = ( AccountCreateTransaction() @@ -128,19 +129,18 @@ def test_token_revoke_kyc_transaction_fails_when_account_not_associated(): .set_initial_balance(Hbar(2)) .execute(env.client) ) - assert receipt.status == ResponseCode.SUCCESS, f"Account creation failed with status: {ResponseCode(receipt.status).name}" + assert receipt.status == ResponseCode.SUCCESS, ( + f"Account creation failed with status: {ResponseCode(receipt.status).name}" + ) account_id = receipt.account_id - + # Create a new token and set the kyc key to be the operator's key token_id = create_fungible_token(env, [lambda tx: tx.set_kyc_key(env.operator_key)]) - + # Revoke KYC from the new account - should fail with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT - receipt = ( - TokenRevokeKycTransaction() - .set_account_id(account_id) - .set_token_id(token_id) - .execute(env.client) + receipt = TokenRevokeKycTransaction().set_account_id(account_id).set_token_id(token_id).execute(env.client) + assert receipt.status == ResponseCode.TOKEN_NOT_ASSOCIATED_TO_ACCOUNT, ( + f"Token revoke KYC should have failed with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT status but got: {ResponseCode(receipt.status).name}" ) - assert receipt.status == ResponseCode.TOKEN_NOT_ASSOCIATED_TO_ACCOUNT, f"Token revoke KYC should have failed with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT status but got: {ResponseCode(receipt.status).name}" finally: - env.close() \ No newline at end of file + env.close() diff --git a/tests/integration/token_unfreeze_transaction_e2e_test.py b/tests/integration/token_unfreeze_transaction_e2e_test.py index f6dbd1063..b22f5d773 100644 --- a/tests/integration/token_unfreeze_transaction_e2e_test.py +++ b/tests/integration/token_unfreeze_transaction_e2e_test.py @@ -1,72 +1,71 @@ +from __future__ import annotations + import pytest +from hiero_sdk_python.account.account_create_transaction import AccountCreateTransaction from hiero_sdk_python.crypto.private_key import PrivateKey from hiero_sdk_python.hbar import Hbar +from hiero_sdk_python.response_code import ResponseCode from hiero_sdk_python.tokens.token_associate_transaction import TokenAssociateTransaction from hiero_sdk_python.tokens.token_freeze_transaction import TokenFreezeTransaction from hiero_sdk_python.tokens.token_unfreeze_transaction import TokenUnfreezeTransaction -from hiero_sdk_python.account.account_create_transaction import AccountCreateTransaction -from hiero_sdk_python.response_code import ResponseCode from tests.integration.utils import IntegrationTestEnv, create_fungible_token @pytest.mark.integration def test_integration_token_unfreeze_transaction_can_execute(): env = IntegrationTestEnv() - + try: new_account_private_key = PrivateKey.generate() new_account_public_key = new_account_private_key.public_key() - + initial_balance = Hbar(2) - + assert initial_balance.to_tinybars() == 200000000 account_transaction = AccountCreateTransaction( - key=new_account_public_key, - initial_balance=initial_balance, - memo="Recipient Account" + key=new_account_public_key, initial_balance=initial_balance, memo="Recipient Account" ) account_transaction.freeze_with(env.client) receipt = account_transaction.execute(env.client) - - assert receipt.status == ResponseCode.SUCCESS, f"Account creation failed with status: {ResponseCode(receipt.status).name}" - + + assert receipt.status == ResponseCode.SUCCESS, ( + f"Account creation failed with status: {ResponseCode(receipt.status).name}" + ) + account_id = receipt.account_id - + assert account_id is not None - + token_id = create_fungible_token(env) - + assert token_id is not None - - associate_transaction = TokenAssociateTransaction( - account_id=account_id, - token_ids=[token_id] - ) - + + associate_transaction = TokenAssociateTransaction(account_id=account_id, token_ids=[token_id]) + associate_transaction.freeze_with(env.client) associate_transaction.sign(new_account_private_key) receipt = associate_transaction.execute(env.client) - - assert receipt.status == ResponseCode.SUCCESS, f"Token association failed with status: {ResponseCode(receipt.status).name}" - - freeze_transaction = TokenFreezeTransaction( - token_id=token_id, - account_id=account_id + + assert receipt.status == ResponseCode.SUCCESS, ( + f"Token association failed with status: {ResponseCode(receipt.status).name}" ) + + freeze_transaction = TokenFreezeTransaction(token_id=token_id, account_id=account_id) freeze_transaction.freeze_with(env.client) receipt = freeze_transaction.execute(env.client) - - assert receipt.status == ResponseCode.SUCCESS, f"Token freeze failed with status: {ResponseCode(receipt.status).name}" - - unfreeze_transaction = TokenUnfreezeTransaction( - token_id=token_id, - account_id=account_id + + assert receipt.status == ResponseCode.SUCCESS, ( + f"Token freeze failed with status: {ResponseCode(receipt.status).name}" ) - + + unfreeze_transaction = TokenUnfreezeTransaction(token_id=token_id, account_id=account_id) + unfreeze_transaction.freeze_with(env.client) receipt = unfreeze_transaction.execute(env.client) - - assert receipt.status == ResponseCode.SUCCESS, f"Token unfreeze failed with status: {ResponseCode(receipt.status).name}" + + assert receipt.status == ResponseCode.SUCCESS, ( + f"Token unfreeze failed with status: {ResponseCode(receipt.status).name}" + ) finally: - env.close() \ No newline at end of file + env.close() diff --git a/tests/integration/token_unpause_transaction_e2e_test.py b/tests/integration/token_unpause_transaction_e2e_test.py index 7ed0512c4..8099c1c11 100644 --- a/tests/integration/token_unpause_transaction_e2e_test.py +++ b/tests/integration/token_unpause_transaction_e2e_test.py @@ -1,52 +1,47 @@ -from pytest import fixture +from __future__ import annotations + import pytest +from pytest import fixture + from hiero_sdk_python.crypto.private_key import PrivateKey from hiero_sdk_python.query.token_info_query import TokenInfoQuery from hiero_sdk_python.response_code import ResponseCode from hiero_sdk_python.tokens.token_id import TokenId from hiero_sdk_python.tokens.token_pause_transaction import TokenPauseTransaction from hiero_sdk_python.tokens.token_unpause_transaction import TokenUnpauseTransaction -from tests.integration.utils import env, create_fungible_token +from tests.integration.utils import create_fungible_token + pause_key = PrivateKey.generate() + @fixture def pausable_token(env): - """"Fixture to create a token that supports pausing.""" - return create_fungible_token( - env, - opts=[lambda tx: tx.set_pause_key(pause_key)] - ) + """ "Fixture to create a token that supports pausing.""" + return create_fungible_token(env, opts=[lambda tx: tx.set_pause_key(pause_key)]) + def pause_token(env, token_id: TokenId): """Helper function to pause the token with the given token_id.""" - token_pasue_tx = ( - TokenPauseTransaction() - .set_token_id(token_id) - .freeze_with(env.client) - .sign(pause_key) - ) + token_pasue_tx = TokenPauseTransaction().set_token_id(token_id).freeze_with(env.client).sign(pause_key) return token_pasue_tx.execute(env.client) + def get_pause_status(env, token_id: TokenId): """Helper to query and return the pause status of a token.""" token_info = TokenInfoQuery().set_token_id(token_id).execute(env.client) return token_info.pause_status.name + def test_unpause_token(env, pausable_token): """Test successful unpause of a paused token.""" pause_receipt = pause_token(env, pausable_token) - assert pause_receipt.status == ResponseCode.SUCCESS + assert pause_receipt.status == ResponseCode.SUCCESS assert get_pause_status(env, pausable_token) == "PAUSED" - unpause_tx = ( - TokenUnpauseTransaction() - .set_token_id(pausable_token) - .freeze_with(env.client) - .sign(pause_key) - ) + unpause_tx = TokenUnpauseTransaction().set_token_id(pausable_token).freeze_with(env.client).sign(pause_key) unpause_receipt = unpause_tx.execute(env.client) assert unpause_receipt.status == ResponseCode.SUCCESS @@ -60,15 +55,12 @@ def test_unpause_token_without_pause_key_signature(env, pausable_token): assert pause_receipt.status == ResponseCode.SUCCESS assert get_pause_status(env, pausable_token) == "PAUSED" - unpause_tx = ( - TokenUnpauseTransaction() - .set_token_id(pausable_token) - .freeze_with(env.client) - ) + unpause_tx = TokenUnpauseTransaction().set_token_id(pausable_token).freeze_with(env.client) unpause_receipt = unpause_tx.execute(env.client) assert unpause_receipt.status == ResponseCode.INVALID_SIGNATURE + def test_unpause_token_with_invalid_pasue_key(env, pausable_token): """Test unpause transaction with invalid pause key.""" pause_receipt = pause_token(env, pausable_token) @@ -77,32 +69,25 @@ def test_unpause_token_with_invalid_pasue_key(env, pausable_token): assert get_pause_status(env, pausable_token) == "PAUSED" unpause_tx = ( - TokenUnpauseTransaction() - .set_token_id(pausable_token) - .freeze_with(env.client) - .sign(PrivateKey.generate()) + TokenUnpauseTransaction().set_token_id(pausable_token).freeze_with(env.client).sign(PrivateKey.generate()) ) unpause_receipt = unpause_tx.execute(env.client) assert unpause_receipt.status == ResponseCode.INVALID_SIGNATURE + def test_unpause_token_when_token_id_not_set(env): """Test unpause transaction when token_id is not provided.""" - unpause_tx = TokenUnpauseTransaction() with pytest.raises(ValueError, match="Missing token ID"): unpause_tx.freeze_with(env.client) + def test_unpause_token_with_invalid_token_id(env): """Test unpause transaction using an invalid token ID.""" token_id = TokenId(0, 0, 999) - unpause_tx = ( - TokenUnpauseTransaction() - .set_token_id(token_id) - .freeze_with(env.client) - .sign(pause_key) - ) + unpause_tx = TokenUnpauseTransaction().set_token_id(token_id).freeze_with(env.client).sign(pause_key) unpause_receipt = unpause_tx.execute(env.client) assert unpause_receipt.status == ResponseCode.INVALID_TOKEN_ID diff --git a/tests/integration/token_update_nfts_transaction_e2e_test.py b/tests/integration/token_update_nfts_transaction_e2e_test.py index 30448d64c..b40b59465 100644 --- a/tests/integration/token_update_nfts_transaction_e2e_test.py +++ b/tests/integration/token_update_nfts_transaction_e2e_test.py @@ -1,128 +1,132 @@ +from __future__ import annotations + import pytest from hiero_sdk_python.crypto.private_key import PrivateKey from hiero_sdk_python.hbar import Hbar +from hiero_sdk_python.query.token_nft_info_query import TokenNftInfoQuery from hiero_sdk_python.response_code import ResponseCode +from hiero_sdk_python.tokens.nft_id import NftId from hiero_sdk_python.tokens.token_mint_transaction import TokenMintTransaction from hiero_sdk_python.tokens.token_update_nfts_transaction import TokenUpdateNftsTransaction from tests.integration.utils import IntegrationTestEnv, create_nft_token -from hiero_sdk_python.tokens.nft_id import NftId -from hiero_sdk_python.query.token_nft_info_query import TokenNftInfoQuery + @pytest.mark.integration def test_token_update_nfts_updates_metadata(): env = IntegrationTestEnv() - + try: # Create supply key supply_private_key = PrivateKey.generate_ed25519() # Create metadata key - metadata_private_key = PrivateKey.generate_ed25519() - + metadata_private_key = PrivateKey.generate_ed25519() + # Create initial metadata for NFTs mint_metadata = [b"Metadata1", b"Metadata2", b"Metadata3", b"Metadata4"] nft_count = len(mint_metadata) - + # Create updated metadata updated_metadata = b"UpdatedMetadata" # Create NFT token with metadata key - nft_id = create_nft_token(env, [ - lambda tx: tx.set_supply_key(supply_private_key), - lambda tx: tx.set_metadata_key(metadata_private_key) - ]) + nft_id = create_nft_token( + env, + [lambda tx: tx.set_supply_key(supply_private_key), lambda tx: tx.set_metadata_key(metadata_private_key)], + ) # Mint NFTs with initial metadata - mint_transaction = TokenMintTransaction( - token_id=nft_id, - metadata=mint_metadata - ) - + mint_transaction = TokenMintTransaction(token_id=nft_id, metadata=mint_metadata) + receipt = mint_transaction.freeze_with(env.client).sign(supply_private_key).execute(env.client) - - assert receipt.status == ResponseCode.SUCCESS, f"NFT minting failed with status: {ResponseCode(receipt.status).name}" + + assert receipt.status == ResponseCode.SUCCESS, ( + f"NFT minting failed with status: {ResponseCode(receipt.status).name}" + ) serials = receipt.serial_numbers # Verify initial metadata for i, serial in enumerate(serials): nft_info = TokenNftInfoQuery(NftId(nft_id, serial)).execute(env.client) assert nft_info.metadata == mint_metadata[i], f"Initial metadata mismatch for serial {serial}" - + # Update metadata for first half of NFTs update_transaction = TokenUpdateNftsTransaction( - token_id=nft_id, - serial_numbers=serials[:nft_count//2], - metadata=updated_metadata + token_id=nft_id, serial_numbers=serials[: nft_count // 2], metadata=updated_metadata ) - + receipt = update_transaction.freeze_with(env.client).sign(metadata_private_key).execute(env.client) - assert receipt.status == ResponseCode.SUCCESS, f"NFT update failed with status: {ResponseCode(receipt.status).name}" + assert receipt.status == ResponseCode.SUCCESS, ( + f"NFT update failed with status: {ResponseCode(receipt.status).name}" + ) # Verify updated metadata for first half - for serial in serials[:nft_count//2]: + for serial in serials[: nft_count // 2]: nft_info = TokenNftInfoQuery(NftId(nft_id, serial)).execute(env.client) assert nft_info.metadata == updated_metadata, f"Updated metadata mismatch for serial {serial}" # Verify unchanged metadata for second half nfts_metadata = [] - for serial in serials[nft_count//2:]: + for serial in serials[nft_count // 2 :]: nft_info = TokenNftInfoQuery(NftId(nft_id, serial)).execute(env.client) nfts_metadata.append(nft_info.metadata) - - assert nfts_metadata == mint_metadata[nft_count//2:], f"Original metadata should be unchanged for serial {serial}" - + + assert nfts_metadata == mint_metadata[nft_count // 2 :], ( + f"Original metadata should be unchanged for serial {serial}" + ) + finally: env.close() + @pytest.mark.integration def test_can_update_empty_nft_metadata(): env = IntegrationTestEnv() - + try: supply_private_key = PrivateKey.generate_ed25519() metadata_private_key = PrivateKey.generate_ed25519() # Create initial metadata for NFTs mint_metadata = [b"Metadata1", b"Metadata2", b"Metadata3", b"Metadata4"] - + # Create empty metadata for update updated_metadata = b"" - + # Create NFT token with metadata key - nft_id = create_nft_token(env, [ - lambda tx: tx.set_supply_key(supply_private_key), - lambda tx: tx.set_metadata_key(metadata_private_key) - ]) + nft_id = create_nft_token( + env, + [lambda tx: tx.set_supply_key(supply_private_key), lambda tx: tx.set_metadata_key(metadata_private_key)], + ) # Mint NFTs with initial metadata - mint_transaction = TokenMintTransaction( - token_id=nft_id, - metadata=mint_metadata - ) - + mint_transaction = TokenMintTransaction(token_id=nft_id, metadata=mint_metadata) + receipt = mint_transaction.freeze_with(env.client).sign(supply_private_key).execute(env.client) - - assert receipt.status == ResponseCode.SUCCESS, f"NFT minting failed with status: {ResponseCode(receipt.status).name}" + + assert receipt.status == ResponseCode.SUCCESS, ( + f"NFT minting failed with status: {ResponseCode(receipt.status).name}" + ) serials = receipt.serial_numbers # Get and verify initial metadata list for i, serial in enumerate(serials): nft_info = TokenNftInfoQuery(NftId(nft_id, serial)).execute(env.client) assert nft_info.metadata == mint_metadata[i], f"Initial metadata mismatch for serial {serial}" - + # Update metadata for all NFTs to empty update_transaction = TokenUpdateNftsTransaction( - token_id=nft_id, - serial_numbers=serials, - metadata=updated_metadata + token_id=nft_id, serial_numbers=serials, metadata=updated_metadata ) - update_transaction.transaction_fee = Hbar(3).to_tinybars() # Set transaction fee to be higher than 2 Hbars - + update_transaction.transaction_fee = Hbar(3).to_tinybars() # Set transaction fee to be higher than 2 Hbars + receipt = update_transaction.freeze_with(env.client).sign(metadata_private_key).execute(env.client) - - assert receipt.status == ResponseCode.SUCCESS, f"NFT update failed with status: {ResponseCode(receipt.status).name}" - + + assert receipt.status == ResponseCode.SUCCESS, ( + f"NFT update failed with status: {ResponseCode(receipt.status).name}" + ) + # Get and verify updated metadata list for serial in serials: nft_info = TokenNftInfoQuery(NftId(nft_id, serial)).execute(env.client) @@ -130,6 +134,7 @@ def test_can_update_empty_nft_metadata(): finally: env.close() + @pytest.mark.integration def test_cannot_update_nft_metadata_when_key_is_not_set(): env = IntegrationTestEnv() @@ -143,122 +148,116 @@ def test_cannot_update_nft_metadata_when_key_is_not_set(): # Create initial metadata for NFTs mint_metadata = [b"Metadata1", b"Metadata2", b"Metadata3", b"Metadata4"] - + # Create updated metadata updated_metadata = b"UpdatedMetadata" - + # Create NFT token without metadata key - nft_id = create_nft_token(env, [ - lambda x: x.set_supply_key(supply_private_key) - ]) + nft_id = create_nft_token(env, [lambda x: x.set_supply_key(supply_private_key)]) # Mint NFTs with initial metadata - mint_transaction = TokenMintTransaction( - token_id=nft_id, - metadata=mint_metadata - ) - + mint_transaction = TokenMintTransaction(token_id=nft_id, metadata=mint_metadata) + receipt = mint_transaction.freeze_with(env.client).sign(supply_private_key).execute(env.client) - - assert receipt.status == ResponseCode.SUCCESS, f"NFT minting failed with status: {ResponseCode(receipt.status).name}" + + assert receipt.status == ResponseCode.SUCCESS, ( + f"NFT minting failed with status: {ResponseCode(receipt.status).name}" + ) serials = receipt.serial_numbers # Get and verify initial metadata list for i, serial in enumerate(serials): nft_info = TokenNftInfoQuery(NftId(nft_id, serial)).execute(env.client) assert nft_info.metadata == mint_metadata[i], f"Initial metadata mismatch for serial {serial}" - + # Try to update metadata for NFTs - should fail since no metadata key is set update_transaction = TokenUpdateNftsTransaction( - token_id=nft_id, - serial_numbers=serials, - metadata=updated_metadata + token_id=nft_id, serial_numbers=serials, metadata=updated_metadata ) - update_transaction.transaction_fee = Hbar(3).to_tinybars() # Set transaction fee to be higher than 2 Hbars - + update_transaction.transaction_fee = Hbar(3).to_tinybars() # Set transaction fee to be higher than 2 Hbars + receipt = update_transaction.freeze_with(env.client).sign(metadata_private_key).execute(env.client) - - assert receipt.status == ResponseCode.INVALID_SIGNATURE, f"NFT update should fail with INVALID_SIGNATURE, got status: {ResponseCode(receipt.status).name}" + + assert receipt.status == ResponseCode.INVALID_SIGNATURE, ( + f"NFT update should fail with INVALID_SIGNATURE, got status: {ResponseCode(receipt.status).name}" + ) finally: env.close() + @pytest.mark.integration def test_cannot_update_nft_metadata_when_transaction_not_signed_with_metadata_key(): env = IntegrationTestEnv() - + try: # Create supply key supply_private_key = PrivateKey.generate_ed25519() # Create metadata key - metadata_private_key = PrivateKey.generate_ed25519() - + metadata_private_key = PrivateKey.generate_ed25519() + # Create initial metadata for NFTs mint_metadata = [b"Metadata1", b"Metadata2", b"Metadata3", b"Metadata4"] - + # Create updated metadata updated_metadata = b"UpdatedMetadata" # Create a new NFT token with supply and metadata keys # Pass lambda functions to create_nft_token to configure the token with the generated keys - nft_id = create_nft_token(env, [ - lambda tx: tx.set_supply_key(supply_private_key), - lambda tx: tx.set_metadata_key(metadata_private_key) - ]) + nft_id = create_nft_token( + env, + [lambda tx: tx.set_supply_key(supply_private_key), lambda tx: tx.set_metadata_key(metadata_private_key)], + ) # Mint NFTs with initial metadata - mint_transaction = TokenMintTransaction( - token_id=nft_id, - metadata=mint_metadata - ) - + mint_transaction = TokenMintTransaction(token_id=nft_id, metadata=mint_metadata) + receipt = mint_transaction.freeze_with(env.client).sign(supply_private_key).execute(env.client) - - assert receipt.status == ResponseCode.SUCCESS, f"NFT minting failed with status: {ResponseCode(receipt.status).name}" + + assert receipt.status == ResponseCode.SUCCESS, ( + f"NFT minting failed with status: {ResponseCode(receipt.status).name}" + ) serials = receipt.serial_numbers # Verify initial metadata for i, serial in enumerate(serials): nft_info = TokenNftInfoQuery(NftId(nft_id, serial)).execute(env.client) assert nft_info.metadata == mint_metadata[i], f"Initial metadata mismatch for serial {serial}" - + # Try to update metadata without signing it with metadata key - tx = TokenUpdateNftsTransaction( - token_id=nft_id, - serial_numbers=serials, - metadata=updated_metadata - ) + tx = TokenUpdateNftsTransaction(token_id=nft_id, serial_numbers=serials, metadata=updated_metadata) tx.transaction_fee = Hbar(3).to_tinybars() # Set transaction fee to be higher than 2 Hbars - + receipt = tx.execute(env.client) - - assert receipt.status == ResponseCode.INVALID_SIGNATURE, f"NFT update should fail with INVALID_SIGNATURE, got status: {ResponseCode(receipt.status).name}" - - # Try to update metadata by signing the transaction with some key - tx = TokenUpdateNftsTransaction( - token_id=nft_id, - serial_numbers=serials, - metadata=updated_metadata + + assert receipt.status == ResponseCode.INVALID_SIGNATURE, ( + f"NFT update should fail with INVALID_SIGNATURE, got status: {ResponseCode(receipt.status).name}" ) + + # Try to update metadata by signing the transaction with some key + tx = TokenUpdateNftsTransaction(token_id=nft_id, serial_numbers=serials, metadata=updated_metadata) tx.transaction_fee = Hbar(3).to_tinybars() # Set transaction fee to be higher than 2 Hbars - + receipt = tx.freeze_with(env.client).sign(env.operator_key).execute(env.client) - - assert receipt.status == ResponseCode.INVALID_SIGNATURE, f"NFT update should fail with INVALID_SIGNATURE, got status: {ResponseCode(receipt.status).name}" + + assert receipt.status == ResponseCode.INVALID_SIGNATURE, ( + f"NFT update should fail with INVALID_SIGNATURE, got status: {ResponseCode(receipt.status).name}" + ) finally: env.close() + @pytest.mark.integration def test_token_update_nfts_transaction_fails_for_nonexistent_serial(): env = IntegrationTestEnv() - + try: # Create supply key supply_private_key = PrivateKey.generate_ed25519() # Create metadata key - metadata_private_key = PrivateKey.generate_ed25519() - + metadata_private_key = PrivateKey.generate_ed25519() + # Create initial metadata for NFTs mint_metadata = b"Initial Metadata" @@ -266,34 +265,35 @@ def test_token_update_nfts_transaction_fails_for_nonexistent_serial(): updated_metadata = b"Updated Metadata" # Create NFT token with metadata key - nft_id = create_nft_token(env, [ - lambda tx: tx.set_supply_key(supply_private_key), - lambda tx: tx.set_metadata_key(metadata_private_key) - ]) + nft_id = create_nft_token( + env, + [lambda tx: tx.set_supply_key(supply_private_key), lambda tx: tx.set_metadata_key(metadata_private_key)], + ) # Mint NFTs with initial metadata - mint_transaction = TokenMintTransaction( - token_id=nft_id, - metadata=mint_metadata - ) - + mint_transaction = TokenMintTransaction(token_id=nft_id, metadata=mint_metadata) + receipt = mint_transaction.freeze_with(env.client).sign(supply_private_key).execute(env.client) - - assert receipt.status == ResponseCode.SUCCESS, f"NFT minting failed with status: {ResponseCode(receipt.status).name}" + + assert receipt.status == ResponseCode.SUCCESS, ( + f"NFT minting failed with status: {ResponseCode(receipt.status).name}" + ) serials = receipt.serial_numbers - + # Verify that only one NFT was minted and the serial number is 1 assert len(serials) == 1 and serials[0] == 1 - + # Update metadata for non-existent NFT serial number 2 (should fail) update_transaction = TokenUpdateNftsTransaction( token_id=nft_id, serial_numbers=[2], # Serial number 2 was never minted - metadata=updated_metadata + metadata=updated_metadata, ) - + receipt = update_transaction.freeze_with(env.client).sign(metadata_private_key).execute(env.client) - - assert receipt.status == ResponseCode.INVALID_NFT_ID, f"NFT update should fail with INVALID_NFT_ID, got status: {ResponseCode(receipt.status).name}" + + assert receipt.status == ResponseCode.INVALID_NFT_ID, ( + f"NFT update should fail with INVALID_NFT_ID, got status: {ResponseCode(receipt.status).name}" + ) finally: - env.close() \ No newline at end of file + env.close() diff --git a/tests/integration/token_update_transaction_e2e_test.py b/tests/integration/token_update_transaction_e2e_test.py index 10889c032..e5242f5d2 100644 --- a/tests/integration/token_update_transaction_e2e_test.py +++ b/tests/integration/token_update_transaction_e2e_test.py @@ -1,10 +1,14 @@ +from __future__ import annotations + import datetime + import pytest -from hiero_sdk_python.Duration import Duration +from hiero_sdk_python.account.account_create_transaction import AccountCreateTransaction from hiero_sdk_python.crypto.private_key import PrivateKey -from hiero_sdk_python.crypto.public_key import PublicKey +from hiero_sdk_python.Duration import Duration from hiero_sdk_python.hbar import Hbar +from hiero_sdk_python.query.token_info_query import TokenInfoQuery from hiero_sdk_python.response_code import ResponseCode from hiero_sdk_python.timestamp import Timestamp from hiero_sdk_python.tokens.custom_fixed_fee import CustomFixedFee @@ -12,118 +16,122 @@ from hiero_sdk_python.tokens.token_fee_schedule_update_transaction import TokenFeeScheduleUpdateTransaction from hiero_sdk_python.tokens.token_grant_kyc_transaction import TokenGrantKycTransaction from hiero_sdk_python.tokens.token_id import TokenId -from hiero_sdk_python.query.token_info_query import TokenInfoQuery from hiero_sdk_python.tokens.token_update_transaction import TokenUpdateTransaction -from hiero_sdk_python.account.account_create_transaction import AccountCreateTransaction -from hiero_sdk_python.tokens.token_mint_transaction import TokenMintTransaction from hiero_sdk_python.transaction.transaction import Transaction from tests.integration.utils import IntegrationTestEnv, create_fungible_token, create_nft_token + private_key = PrivateKey.generate() + @pytest.mark.integration def test_integration_token_update_transaction_can_execute(): env = IntegrationTestEnv() - + try: token_id = create_fungible_token(env) auto_renew_period = Duration(2592000) - + receipt = ( - TokenUpdateTransaction() - .set_token_id(token_id) - .set_token_name("UpdatedName") - .set_token_symbol("UPD") - .set_auto_renew_period(auto_renew_period) - .set_freeze_key(private_key) - .freeze_with(env.client) - .execute(env.client) - ) - assert receipt.status == ResponseCode.SUCCESS, f"Token update transaction failed with status: {ResponseCode(receipt.status).name}" - - info = ( - TokenInfoQuery() + TokenUpdateTransaction() .set_token_id(token_id) + .set_token_name("UpdatedName") + .set_token_symbol("UPD") + .set_auto_renew_period(auto_renew_period) + .set_freeze_key(private_key) + .freeze_with(env.client) .execute(env.client) ) - + assert receipt.status == ResponseCode.SUCCESS, ( + f"Token update transaction failed with status: {ResponseCode(receipt.status).name}" + ) + + info = TokenInfoQuery().set_token_id(token_id).execute(env.client) + assert info.name == "UpdatedName", "Token failed to update" assert info.symbol == "UPD", "Token symbol failed to update" assert info.auto_renew_period == auto_renew_period, "Token auto_renew_period failed to update" - assert info.freeze_key.to_bytes_raw() == private_key.public_key().to_bytes_raw(), "Freeze key did not update correctly" - assert info.admin_key.to_bytes_raw() == env.public_operator_key.to_bytes_raw(), "Admin key mismatch after update" + assert info.freeze_key.to_bytes_raw() == private_key.public_key().to_bytes_raw(), ( + "Freeze key did not update correctly" + ) + assert info.admin_key.to_bytes_raw() == env.public_operator_key.to_bytes_raw(), ( + "Admin key mismatch after update" + ) finally: env.close() + @pytest.mark.integration def test_integration_token_update_preserves_fields_without_updating_parameters(): env = IntegrationTestEnv() - + try: token_id = create_fungible_token(env) - - original_info = ( - TokenInfoQuery() - .set_token_id(token_id) - .execute(env.client) - ) - - receipt = ( - TokenUpdateTransaction() - .set_token_id(token_id) - .execute(env.client) - ) - assert receipt.status == ResponseCode.SUCCESS, f"Token update transaction failed with status: {ResponseCode(receipt.status).name}" - - info = ( - TokenInfoQuery() - .set_token_id(token_id) - .execute(env.client) + + original_info = TokenInfoQuery().set_token_id(token_id).execute(env.client) + + receipt = TokenUpdateTransaction().set_token_id(token_id).execute(env.client) + assert receipt.status == ResponseCode.SUCCESS, ( + f"Token update transaction failed with status: {ResponseCode(receipt.status).name}" ) - + + info = TokenInfoQuery().set_token_id(token_id).execute(env.client) + assert info.name == original_info.name, "Token name should not have changed" assert info.symbol == original_info.symbol, "Token symbol should not have changed" assert info.memo == original_info.memo, "Token memo should not have changed" assert info.metadata == original_info.metadata, "Token metadata should not have changed" assert info.treasury == original_info.treasury, "Token treasury should not have changed" - assert info.auto_renew_period == original_info.auto_renew_period, "Token auto renew period should not have been changed" - assert info.auto_renew_account == original_info.auto_renew_account, "Token auto renew account should not have been changed" + assert info.auto_renew_period == original_info.auto_renew_period, ( + "Token auto renew period should not have been changed" + ) + assert info.auto_renew_account == original_info.auto_renew_account, ( + "Token auto renew account should not have been changed" + ) assert info.expiry == original_info.expiry, "Token expiry should not have been changed" - assert info.admin_key.to_bytes_raw() == original_info.admin_key.to_bytes_raw(), "Admin key should not have changed" - assert info.freeze_key.to_bytes_raw() == original_info.freeze_key.to_bytes_raw(), "Freeze key should not have changed" + assert info.admin_key.to_bytes_raw() == original_info.admin_key.to_bytes_raw(), ( + "Admin key should not have changed" + ) + assert info.freeze_key.to_bytes_raw() == original_info.freeze_key.to_bytes_raw(), ( + "Freeze key should not have changed" + ) assert info.wipe_key.to_bytes_raw() == original_info.wipe_key.to_bytes_raw(), "Wipe key should not have changed" - assert info.supply_key.to_bytes_raw() == original_info.supply_key.to_bytes_raw(), "Supply key should not have changed" + assert info.supply_key.to_bytes_raw() == original_info.supply_key.to_bytes_raw(), ( + "Supply key should not have changed" + ) assert info.kyc_key is None, "Kyc key should not have changed" assert info.fee_schedule_key is None, "Fee Schedule key should not have changed" assert info.pause_key is None, "Pause key should not have changed" finally: env.close() + @pytest.mark.integration def test_integration_token_update_transaction_different_keys(): env = IntegrationTestEnv() - + try: # Generate 8 key pairs keys = [PrivateKey.generate() for _ in range(8)] - + # Create new account with first key - tx = ( - AccountCreateTransaction() - .set_key_without_alias(keys[0].public_key()) - .set_initial_balance(Hbar(2)) - ) + tx = AccountCreateTransaction().set_key_without_alias(keys[0].public_key()).set_initial_balance(Hbar(2)) receipt = tx.execute(env.client) - assert receipt.status == ResponseCode.SUCCESS, f"Account creation failed with status: {ResponseCode(receipt.status).name}" - + assert receipt.status == ResponseCode.SUCCESS, ( + f"Account creation failed with status: {ResponseCode(receipt.status).name}" + ) + # Create fungible token with initial metadata and pause keys both set to the first key - token_id = create_fungible_token(env, opts=[ - lambda tx: tx.set_metadata_key(keys[0]), - lambda tx: tx.set_pause_key(keys[0]), - lambda tx: tx.set_kyc_key(keys[0]), - lambda tx: tx.set_fee_schedule_key(keys[0]) - ]) - + token_id = create_fungible_token( + env, + opts=[ + lambda tx: tx.set_metadata_key(keys[0]), + lambda tx: tx.set_pause_key(keys[0]), + lambda tx: tx.set_kyc_key(keys[0]), + lambda tx: tx.set_fee_schedule_key(keys[0]), + ], + ) + # Update token with different keys receipt = ( TokenUpdateTransaction() @@ -141,15 +149,13 @@ def test_integration_token_update_transaction_different_keys(): .set_fee_schedule_key(keys[7]) .execute(env.client) ) - assert receipt.status == ResponseCode.SUCCESS, f"Token update transaction failed with status: {ResponseCode(receipt.status).name}" - - # Query token info and verify updates - info = ( - TokenInfoQuery() - .set_token_id(token_id) - .execute(env.client) + assert receipt.status == ResponseCode.SUCCESS, ( + f"Token update transaction failed with status: {ResponseCode(receipt.status).name}" ) - + + # Query token info and verify updates + info = TokenInfoQuery().set_token_id(token_id).execute(env.client) + assert info.name == "UpdatedName", "Token name mismatch" assert info.symbol == "UPD", "Token symbol mismatch" assert info.freeze_key.to_bytes_raw() == keys[1].public_key().to_bytes_raw(), "Freeze key mismatch" @@ -163,36 +169,36 @@ def test_integration_token_update_transaction_different_keys(): finally: env.close() + @pytest.mark.integration def test_integration_token_update_transaction_treasury(): env = IntegrationTestEnv() - + try: # Generate new key and create new account new_private_key = PrivateKey.generate() new_public_key = new_private_key.public_key() - + receipt = ( AccountCreateTransaction() .set_key_without_alias(new_public_key) .set_initial_balance(Hbar(2)) .execute(env.client) ) - assert receipt.status == ResponseCode.SUCCESS, f"Account creation failed with status: {ResponseCode(receipt.status).name}" + assert receipt.status == ResponseCode.SUCCESS, ( + f"Account creation failed with status: {ResponseCode(receipt.status).name}" + ) account_id = receipt.account_id - + # Create fungible token token_id = create_fungible_token(env) - - tx = ( - TokenAssociateTransaction() - .set_account_id(account_id) - .add_token_id(token_id) - .freeze_with(env.client) - ) + + tx = TokenAssociateTransaction().set_account_id(account_id).add_token_id(token_id).freeze_with(env.client) receipt = tx.sign(new_private_key).execute(env.client) - assert receipt.status == ResponseCode.SUCCESS, f"Token association failed with status: {ResponseCode(receipt.status).name}" - + assert receipt.status == ResponseCode.SUCCESS, ( + f"Token association failed with status: {ResponseCode(receipt.status).name}" + ) + # Update token with new treasury account receipt = ( TokenUpdateTransaction() @@ -203,136 +209,113 @@ def test_integration_token_update_transaction_treasury(): .sign(new_private_key) .execute(env.client) ) - assert receipt.status == ResponseCode.SUCCESS, f"Token update failed with status: {ResponseCode(receipt.status).name}" - - # Query token info and verify updates - info = ( - TokenInfoQuery() - .set_token_id(token_id) - .execute(env.client) + assert receipt.status == ResponseCode.SUCCESS, ( + f"Token update failed with status: {ResponseCode(receipt.status).name}" ) - + + # Query token info and verify updates + info = TokenInfoQuery().set_token_id(token_id).execute(env.client) + assert info.symbol == "UPD", "Token symbol mismatch" assert info.treasury == account_id, "Treasury account mismatch" finally: env.close() + @pytest.mark.integration def test_integration_token_update_transaction_fail_invalid_token_id(): env = IntegrationTestEnv() - + try: token_id = TokenId(1, 1, 999999999) tx = TokenUpdateTransaction(token_id=token_id) receipt = tx.execute(env.client) - - assert receipt.status == ResponseCode.INVALID_TOKEN_ID, f"Token update should have failed with INVALID_TOKEN_ID status but got: {ResponseCode(receipt.status).name}" + + assert receipt.status == ResponseCode.INVALID_TOKEN_ID, ( + f"Token update should have failed with INVALID_TOKEN_ID status but got: {ResponseCode(receipt.status).name}" + ) finally: env.close() + @pytest.mark.integration def test_integration_token_update_transaction_fungible_metadata(): env = IntegrationTestEnv() - + try: new_metadata = b"Updated metadata" - + # Create token with initial metadata (empty) token_id = create_fungible_token(env) - + # Query initial token info and verify metadata - info = ( - TokenInfoQuery() - .set_token_id(token_id) - .execute(env.client) - ) + info = TokenInfoQuery().set_token_id(token_id).execute(env.client) assert info.metadata == b"", "Initial metadata mismatch" - + # Update token with new metadata - receipt = ( - TokenUpdateTransaction() - .set_token_id(token_id) - .set_metadata(new_metadata) - .execute(env.client) + receipt = TokenUpdateTransaction().set_token_id(token_id).set_metadata(new_metadata).execute(env.client) + assert receipt.status == ResponseCode.SUCCESS, ( + f"Token update failed with status: {ResponseCode(receipt.status).name}" ) - assert receipt.status == ResponseCode.SUCCESS, f"Token update failed with status: {ResponseCode(receipt.status).name}" - + # Query token info and verify updated metadata - info = ( - TokenInfoQuery() - .set_token_id(token_id) - .execute(env.client) - ) + info = TokenInfoQuery().set_token_id(token_id).execute(env.client) assert info.metadata == new_metadata, "Updated metadata mismatch" - + finally: env.close() + @pytest.mark.integration def test_integration_token_update_transaction_nft_metadata(): env = IntegrationTestEnv() - + try: new_metadata = b"Updated metadata" - + # Create NFT with initial metadata token_id = create_nft_token(env) - + # Query initial token info and verify metadata - info = ( - TokenInfoQuery() - .set_token_id(token_id) - .execute(env.client) - ) + info = TokenInfoQuery().set_token_id(token_id).execute(env.client) assert info.metadata == b"", "Initial metadata mismatch" - + # Update token with new metadata - receipt = ( - TokenUpdateTransaction() - .set_token_id(token_id) - .set_metadata(new_metadata) - .execute(env.client) + receipt = TokenUpdateTransaction().set_token_id(token_id).set_metadata(new_metadata).execute(env.client) + assert receipt.status == ResponseCode.SUCCESS, ( + f"Token update failed with status: {ResponseCode(receipt.status).name}" ) - assert receipt.status == ResponseCode.SUCCESS, f"Token update failed with status: {ResponseCode(receipt.status).name}" - + # Query token info and verify updated metadata - info = ( - TokenInfoQuery() - .set_token_id(token_id) - .execute(env.client) - ) + info = TokenInfoQuery().set_token_id(token_id).execute(env.client) assert info.metadata == new_metadata, "Updated metadata mismatch" - + finally: env.close() + @pytest.mark.integration def test_integration_token_update_transaction_metadata_immutable_fungible_token(): env = IntegrationTestEnv() - + try: new_metadata = b"Updated metadata" - + # Generate metadata key metadata_key = PrivateKey.generate() - + # Create fungible token with metadata key but no admin key - token_id = create_fungible_token(env, opts=[ - lambda tx: tx.set_metadata_key(metadata_key), - lambda tx: tx.set_admin_key(None) - ]) - - # Query initial token info and verify metadata and keys - info = ( - TokenInfoQuery() - .set_token_id(token_id) - .execute(env.client) + token_id = create_fungible_token( + env, opts=[lambda tx: tx.set_metadata_key(metadata_key), lambda tx: tx.set_admin_key(None)] ) + + # Query initial token info and verify metadata and keys + info = TokenInfoQuery().set_token_id(token_id).execute(env.client) assert info.metadata == b"", "Initial metadata mismatch" assert info.metadata_key.to_bytes_raw() == metadata_key.public_key().to_bytes_raw(), "Metadata key mismatch" assert info.admin_key is None, "Admin key should be None" - + # Update token with new metadata, signed by metadata key receipt = ( TokenUpdateTransaction() @@ -342,45 +325,39 @@ def test_integration_token_update_transaction_metadata_immutable_fungible_token( .sign(metadata_key) .execute(env.client) ) - assert receipt.status == ResponseCode.SUCCESS, f"Token update failed with status: {ResponseCode(receipt.status).name}" - - # Query token info and verify updated metadata - info = ( - TokenInfoQuery() - .set_token_id(token_id) - .execute(env.client) + assert receipt.status == ResponseCode.SUCCESS, ( + f"Token update failed with status: {ResponseCode(receipt.status).name}" ) + + # Query token info and verify updated metadata + info = TokenInfoQuery().set_token_id(token_id).execute(env.client) assert info.metadata == new_metadata, "Updated metadata mismatch" - + finally: env.close() + @pytest.mark.integration def test_integration_token_update_transaction_metadata_immutable_nft(): env = IntegrationTestEnv() - + try: new_metadata = b"Updated metadata" - + # Generate metadata key metadata_key = PrivateKey.generate() - + # Create NFT token with metadata key but no admin key - token_id = create_nft_token(env, [ - lambda tx: tx.set_metadata_key(metadata_key), - lambda tx: tx.set_admin_key(None) - ]) - - # Query initial token info and verify metadata and keys - info = ( - TokenInfoQuery() - .set_token_id(token_id) - .execute(env.client) + token_id = create_nft_token( + env, [lambda tx: tx.set_metadata_key(metadata_key), lambda tx: tx.set_admin_key(None)] ) + + # Query initial token info and verify metadata and keys + info = TokenInfoQuery().set_token_id(token_id).execute(env.client) assert info.metadata == b"", "Initial metadata mismatch" assert info.metadata_key.to_bytes_raw() == metadata_key.public_key().to_bytes_raw(), "Metadata key mismatch" assert info.admin_key is None, "Admin key should be None" - + # Update token with new metadata, signed by metadata key receipt = ( TokenUpdateTransaction() @@ -390,91 +367,70 @@ def test_integration_token_update_transaction_metadata_immutable_nft(): .sign(metadata_key) .execute(env.client) ) - assert receipt.status == ResponseCode.SUCCESS, f"Token update failed with status: {ResponseCode(receipt.status).name}" - - # Query token info and verify updated metadata - info = ( - TokenInfoQuery() - .set_token_id(token_id) - .execute(env.client) + assert receipt.status == ResponseCode.SUCCESS, ( + f"Token update failed with status: {ResponseCode(receipt.status).name}" ) + + # Query token info and verify updated metadata + info = TokenInfoQuery().set_token_id(token_id).execute(env.client) assert info.metadata == new_metadata, "Updated metadata mismatch" - + finally: env.close() + @pytest.mark.integration def test_token_update_transaction_cannot_update_metadata_fungible(): env = IntegrationTestEnv() - + try: # Create fungible token with initial metadata token_id = create_fungible_token(env) - + # Query initial token info and verify metadata - info = ( - TokenInfoQuery() - .set_token_id(token_id) - .execute(env.client) - ) + info = TokenInfoQuery().set_token_id(token_id).execute(env.client) assert info.metadata == b"", "Initial metadata mismatch" - + # Try to update token with new memo, metadata should remain unchanged - receipt = ( - TokenUpdateTransaction() - .set_token_id(token_id) - .set_token_memo("updated memo") - .execute(env.client) + receipt = TokenUpdateTransaction().set_token_id(token_id).set_token_memo("updated memo").execute(env.client) + assert receipt.status == ResponseCode.SUCCESS, ( + f"Token update failed with status: {ResponseCode(receipt.status).name}" ) - assert receipt.status == ResponseCode.SUCCESS, f"Token update failed with status: {ResponseCode(receipt.status).name}" - + # Query token info and verify metadata remains unchanged - info = ( - TokenInfoQuery() - .set_token_id(token_id) - .execute(env.client) - ) + info = TokenInfoQuery().set_token_id(token_id).execute(env.client) assert info.metadata == b"", "Metadata should not have changed" - + finally: env.close() + @pytest.mark.integration def test_integration_token_update_transaction_cannot_update_metadata_nft(): env = IntegrationTestEnv() - + try: # Create NFT token with initial metadata token_id = create_nft_token(env) - + # Query initial token info and verify metadata - info = ( - TokenInfoQuery() - .set_token_id(token_id) - .execute(env.client) - ) + info = TokenInfoQuery().set_token_id(token_id).execute(env.client) assert info.metadata == b"", "Initial metadata mismatch" - + # Try to update token with new memo, metadata should remain unchanged - receipt = ( - TokenUpdateTransaction() - .set_token_id(token_id) - .set_token_memo("asdf") - .execute(env.client) + receipt = TokenUpdateTransaction().set_token_id(token_id).set_token_memo("asdf").execute(env.client) + assert receipt.status == ResponseCode.SUCCESS, ( + f"Token update failed with status: {ResponseCode(receipt.status).name}" ) - assert receipt.status == ResponseCode.SUCCESS, f"Token update failed with status: {ResponseCode(receipt.status).name}" - + # Query token info and verify metadata remains unchanged - info = ( - TokenInfoQuery() - .set_token_id(token_id) - .execute(env.client) - ) + info = TokenInfoQuery().set_token_id(token_id).execute(env.client) assert info.metadata == b"", "Metadata should not have changed" - + finally: env.close() + @pytest.mark.integration def test_integration_token_update_transaction_erase_metadata_fungible_token(): env = IntegrationTestEnv() @@ -484,33 +440,23 @@ def test_integration_token_update_transaction_erase_metadata_fungible_token(): token_id = create_fungible_token(env) # Query initial token info and verify metadata - info = ( - TokenInfoQuery() - .set_token_id(token_id) - .execute(env.client) - ) + info = TokenInfoQuery().set_token_id(token_id).execute(env.client) assert info.metadata == b"", "Initial metadata mismatch" # Update token with empty metadata - receipt = ( - TokenUpdateTransaction() - .set_token_id(token_id) - .set_metadata(b"") - .execute(env.client) + receipt = TokenUpdateTransaction().set_token_id(token_id).set_metadata(b"").execute(env.client) + assert receipt.status == ResponseCode.SUCCESS, ( + f"Token update failed with status: {ResponseCode(receipt.status).name}" ) - assert receipt.status == ResponseCode.SUCCESS, f"Token update failed with status: {ResponseCode(receipt.status).name}" # Query token info and verify metadata was erased - info = ( - TokenInfoQuery() - .set_token_id(token_id) - .execute(env.client) - ) + info = TokenInfoQuery().set_token_id(token_id).execute(env.client) assert info.metadata == b"", "Metadata should have been erased" finally: env.close() + @pytest.mark.integration def test_integration_token_update_transaction_erase_metadata_nft(): env = IntegrationTestEnv() @@ -520,61 +466,52 @@ def test_integration_token_update_transaction_erase_metadata_nft(): token_id = create_nft_token(env) # Query initial token info and verify metadata - info = ( - TokenInfoQuery() - .set_token_id(token_id) - .execute(env.client) - ) + info = TokenInfoQuery().set_token_id(token_id).execute(env.client) assert info.metadata == b"", "Initial metadata mismatch" # Update token with empty metadata - receipt = ( - TokenUpdateTransaction() - .set_token_id(token_id) - .set_metadata(b"") - .execute(env.client) + receipt = TokenUpdateTransaction().set_token_id(token_id).set_metadata(b"").execute(env.client) + assert receipt.status == ResponseCode.SUCCESS, ( + f"Token update failed with status: {ResponseCode(receipt.status).name}" ) - assert receipt.status == ResponseCode.SUCCESS, f"Token update failed with status: {ResponseCode(receipt.status).name}" # Query token info and verify metadata was erased - info = ( - TokenInfoQuery() - .set_token_id(token_id) - .execute(env.client) - ) + info = TokenInfoQuery().set_token_id(token_id).execute(env.client) assert info.metadata == b"", "Metadata should have been erased" finally: env.close() + @pytest.mark.integration def test_integration_token_update_transaction_cannot_update_metadata_without_key_fungible(): env = IntegrationTestEnv() - + try: # Generate metadata and admin keys metadata_key = PrivateKey.generate() admin_key = PrivateKey.generate() - + # Create fungible token with metadata key - token_id = create_fungible_token(env, [ - lambda tx: tx.set_admin_key(admin_key), - lambda tx: tx.set_metadata_key(metadata_key), - lambda tx: tx.freeze_with(env.client).sign(admin_key) - ]) - + token_id = create_fungible_token( + env, + [ + lambda tx: tx.set_admin_key(admin_key), + lambda tx: tx.set_metadata_key(metadata_key), + lambda tx: tx.freeze_with(env.client).sign(admin_key), + ], + ) + # Try to update metadata without signing with metadata key - receipt = ( - TokenUpdateTransaction() - .set_token_id(token_id) - .set_metadata(b"New metadata") - .execute(env.client) + receipt = TokenUpdateTransaction().set_token_id(token_id).set_metadata(b"New metadata").execute(env.client) + + assert receipt.status == ResponseCode.INVALID_SIGNATURE, ( + f"Token update should have failed with INVALID_SIGNATURE status but got: {ResponseCode(receipt.status).name}" ) - - assert receipt.status == ResponseCode.INVALID_SIGNATURE, f"Token update should have failed with INVALID_SIGNATURE status but got: {ResponseCode(receipt.status).name}" finally: env.close() - + + @pytest.mark.integration def test_integration_token_update_transaction_cannot_update_metadata_without_key_nft(): env = IntegrationTestEnv() @@ -585,22 +522,22 @@ def test_integration_token_update_transaction_cannot_update_metadata_without_key admin_key = PrivateKey.generate() # Create NFT token with metadata key - token_id = create_nft_token(env, [ - lambda tx: tx.set_admin_key(admin_key), - lambda tx: tx.set_supply_key(admin_key), - lambda tx: tx.set_metadata_key(metadata_key), - lambda tx: tx.freeze_with(env.client).sign(admin_key) - ]) + token_id = create_nft_token( + env, + [ + lambda tx: tx.set_admin_key(admin_key), + lambda tx: tx.set_supply_key(admin_key), + lambda tx: tx.set_metadata_key(metadata_key), + lambda tx: tx.freeze_with(env.client).sign(admin_key), + ], + ) # Try to update metadata without signing with metadata key - receipt = ( - TokenUpdateTransaction() - .set_token_id(token_id) - .set_metadata(b"New metadata") - .execute(env.client) - ) + receipt = TokenUpdateTransaction().set_token_id(token_id).set_metadata(b"New metadata").execute(env.client) - assert receipt.status == ResponseCode.INVALID_SIGNATURE, f"Token update should have failed with INVALID_SIGNATURE status but got: {ResponseCode(receipt.status).name}" + assert receipt.status == ResponseCode.INVALID_SIGNATURE, ( + f"Token update should have failed with INVALID_SIGNATURE status but got: {ResponseCode(receipt.status).name}" + ) finally: env.close() @@ -608,134 +545,115 @@ def test_integration_token_update_transaction_cannot_update_metadata_without_key @pytest.mark.integration def test_integration_token_update_transaction_cannot_update_immutable_fungible_token(): env = IntegrationTestEnv() - + try: # Create fungible token with no admin or metadata keys (immutable) - token_id = create_fungible_token(env, [ - lambda tx: tx.set_admin_key(None), - lambda tx: tx.set_metadata_key(None) - ]) - + token_id = create_fungible_token(env, [lambda tx: tx.set_admin_key(None), lambda tx: tx.set_metadata_key(None)]) + # Try to update metadata on immutable token - receipt = ( - TokenUpdateTransaction() - .set_token_id(token_id) - .set_metadata(b"New metadata") - .execute(env.client) + receipt = TokenUpdateTransaction().set_token_id(token_id).set_metadata(b"New metadata").execute(env.client) + + assert receipt.status == ResponseCode.TOKEN_IS_IMMUTABLE, ( + f"Token update should have failed with TOKEN_IS_IMMUTABLE status but got: {ResponseCode(receipt.status).name}" ) - - assert receipt.status == ResponseCode.TOKEN_IS_IMMUTABLE, f"Token update should have failed with TOKEN_IS_IMMUTABLE status but got: {ResponseCode(receipt.status).name}" finally: env.close() + @pytest.mark.integration def test_integration_token_update_transaction_cannot_update_immutable_nft(): env = IntegrationTestEnv() - + try: # Create NFT token with no admin or metadata keys (immutable) - nft_token_id = create_nft_token(env, [ - lambda tx: tx.set_admin_key(None), - lambda tx: tx.set_metadata_key(None) - ]) - + nft_token_id = create_nft_token(env, [lambda tx: tx.set_admin_key(None), lambda tx: tx.set_metadata_key(None)]) + # Try to update metadata on immutable token - receipt = ( - TokenUpdateTransaction() - .set_token_id(nft_token_id) - .set_metadata(b"New metadata") - .execute(env.client) + receipt = TokenUpdateTransaction().set_token_id(nft_token_id).set_metadata(b"New metadata").execute(env.client) + + assert receipt.status == ResponseCode.TOKEN_IS_IMMUTABLE, ( + f"Token update should have failed with TOKEN_IS_IMMUTABLE status but got: {ResponseCode(receipt.status).name}" ) - - assert receipt.status == ResponseCode.TOKEN_IS_IMMUTABLE, f"Token update should have failed with TOKEN_IS_IMMUTABLE status but got: {ResponseCode(receipt.status).name}" finally: env.close() + @pytest.mark.integration def test_integration_token_update_auto_renew_account(): env = IntegrationTestEnv() - + try: token_id = create_fungible_token(env) - old_info = ( - TokenInfoQuery() - .set_token_id(token_id) - .execute(env.client) - ) - # Update auto renew account recipient = env.create_account(1) receipt = ( - TokenUpdateTransaction() - .set_token_id(token_id) - .set_auto_renew_account_id(recipient.id) - .freeze_with(env.client) - .sign(recipient.key) - .execute(env.client) - ) - assert receipt.status == ResponseCode.SUCCESS, f"Token update transaction failed with status: {ResponseCode(receipt.status).name}" - - new_info = ( - TokenInfoQuery() + TokenUpdateTransaction() .set_token_id(token_id) + .set_auto_renew_account_id(recipient.id) + .freeze_with(env.client) + .sign(recipient.key) .execute(env.client) ) - + assert receipt.status == ResponseCode.SUCCESS, ( + f"Token update transaction failed with status: {ResponseCode(receipt.status).name}" + ) + + new_info = TokenInfoQuery().set_token_id(token_id).execute(env.client) + assert new_info.auto_renew_account == recipient.id, "Updated auto_renew_account mismatch" finally: env.close() + @pytest.mark.integration def test_integration_token_update_expiration_time(): env = IntegrationTestEnv() - auto_renew_period = Duration(2592000) # 30 days + auto_renew_period = Duration(2592000) # 30 days try: token_id = create_fungible_token(env, [lambda tx: tx.set_auto_renew_period(auto_renew_period)]) - old_info = ( - TokenInfoQuery() - .set_token_id(token_id) - .execute(env.client) - ) + old_info = TokenInfoQuery().set_token_id(token_id).execute(env.client) # Update expiration time, makesure new expiration time must be greater than old one # Old expiration will be around ~30 days based on autoRenewPeriod expiration_time = Timestamp.from_date(datetime.datetime.now() + datetime.timedelta(days=50)) receipt = ( - TokenUpdateTransaction() - .set_token_id(token_id) - .set_expiration_time(expiration_time) - .freeze_with(env.client) - .execute(env.client) - ) - assert receipt.status == ResponseCode.SUCCESS, f"Token update transaction failed with status: {ResponseCode(receipt.status).name}" - - new_info = ( - TokenInfoQuery() + TokenUpdateTransaction() .set_token_id(token_id) + .set_expiration_time(expiration_time) + .freeze_with(env.client) .execute(env.client) ) + assert receipt.status == ResponseCode.SUCCESS, ( + f"Token update transaction failed with status: {ResponseCode(receipt.status).name}" + ) + + new_info = TokenInfoQuery().set_token_id(token_id).execute(env.client) assert new_info.expiry.seconds > old_info.expiry.seconds, "Updated expiry must be greater" assert new_info.expiry.seconds == expiration_time.seconds, "Updated expiry mismatch" finally: env.close() + def test_integration_token_update_kyc_key_fungible_token(): env = IntegrationTestEnv() admin_key = PrivateKey.generate() kyc_key = PrivateKey.generate() try: - token_id = create_fungible_token(env, [ - lambda tx: tx.set_admin_key(admin_key), - lambda tx: tx.set_kyc_key(kyc_key), - lambda tx: tx.freeze_with(env.client).sign(admin_key).sign(kyc_key) - ]) - - recipient = env.create_account(1); + token_id = create_fungible_token( + env, + [ + lambda tx: tx.set_admin_key(admin_key), + lambda tx: tx.set_kyc_key(kyc_key), + lambda tx: tx.freeze_with(env.client).sign(admin_key).sign(kyc_key), + ], + ) + + recipient = env.create_account(1) association_receipt = ( TokenAssociateTransaction(account_id=recipient.id, token_ids=[token_id]) .freeze_with(env.client) @@ -743,10 +661,12 @@ def test_integration_token_update_kyc_key_fungible_token(): .sign(recipient.key) .execute(env.client) ) - assert association_receipt.status == ResponseCode.SUCCESS, f"Token association failed with status: {ResponseCode.get_name(association_receipt.status)}" + assert association_receipt.status == ResponseCode.SUCCESS, ( + f"Token association failed with status: {ResponseCode.get_name(association_receipt.status)}" + ) # Update Kyc Key new_kyc_key = PrivateKey.generate() - + receipt = ( TokenUpdateTransaction() .set_token_id(token_id) @@ -756,7 +676,9 @@ def test_integration_token_update_kyc_key_fungible_token(): .sign(new_kyc_key) .execute(env.client) ) - assert receipt.status == ResponseCode.SUCCESS, f"Token update failed with status: {ResponseCode(receipt.status).name}" + assert receipt.status == ResponseCode.SUCCESS, ( + f"Token update failed with status: {ResponseCode(receipt.status).name}" + ) token_info = TokenInfoQuery(token_id=token_id).execute(env.client) assert token_info.kyc_key.to_string() == new_kyc_key.public_key().to_string(), "Updated kyc_key mismatch" @@ -770,24 +692,30 @@ def test_integration_token_update_kyc_key_fungible_token(): .sign(env.client.operator_private_key) .execute(env.client) ) - assert kyc_receipt.status == ResponseCode.SUCCESS, f"Token grant kyc failed with status: {ResponseCode.get_name(kyc_receipt.status)}" - + assert kyc_receipt.status == ResponseCode.SUCCESS, ( + f"Token grant kyc failed with status: {ResponseCode.get_name(kyc_receipt.status)}" + ) + finally: env.close() + def test_integration_token_update_kyc_key_nft(): env = IntegrationTestEnv() admin_key = PrivateKey.generate() kyc_key = PrivateKey.generate() try: - token_id = create_nft_token(env, [ - lambda tx: tx.set_admin_key(admin_key), - lambda tx: tx.set_kyc_key(kyc_key), - lambda tx: tx.freeze_with(env.client).sign(admin_key).sign(kyc_key) - ]) - - recipient = env.create_account(1); + token_id = create_nft_token( + env, + [ + lambda tx: tx.set_admin_key(admin_key), + lambda tx: tx.set_kyc_key(kyc_key), + lambda tx: tx.freeze_with(env.client).sign(admin_key).sign(kyc_key), + ], + ) + + recipient = env.create_account(1) association_receipt = ( TokenAssociateTransaction(account_id=recipient.id, token_ids=[token_id]) .freeze_with(env.client) @@ -795,8 +723,10 @@ def test_integration_token_update_kyc_key_nft(): .sign(recipient.key) .execute(env.client) ) - assert association_receipt.status == ResponseCode.SUCCESS, f"Token association failed with status: {ResponseCode.get_name(association_receipt.status)}" - + assert association_receipt.status == ResponseCode.SUCCESS, ( + f"Token association failed with status: {ResponseCode.get_name(association_receipt.status)}" + ) + # Update Kyc Key new_kyc_key = PrivateKey.generate() receipt = ( @@ -808,7 +738,9 @@ def test_integration_token_update_kyc_key_nft(): .sign(new_kyc_key) .execute(env.client) ) - assert receipt.status == ResponseCode.SUCCESS, f"Token update failed with status: {ResponseCode(receipt.status).name}" + assert receipt.status == ResponseCode.SUCCESS, ( + f"Token update failed with status: {ResponseCode(receipt.status).name}" + ) token_info = TokenInfoQuery(token_id=token_id).execute(env.client) assert token_info.kyc_key.to_string() == new_kyc_key.public_key().to_string(), "Updated kyc_key mismatch" @@ -822,23 +754,29 @@ def test_integration_token_update_kyc_key_nft(): .sign(env.client.operator_private_key) .execute(env.client) ) - assert kyc_receipt.status == ResponseCode.SUCCESS, f"Token grant kyc failed with status: {ResponseCode.get_name(kyc_receipt.status)}" - + assert kyc_receipt.status == ResponseCode.SUCCESS, ( + f"Token grant kyc failed with status: {ResponseCode.get_name(kyc_receipt.status)}" + ) + finally: env.close() + def test_integation_token_update_fee_schedule_key_fungible_token(): env = IntegrationTestEnv() admin_key = PrivateKey.generate() fee_schedule_key = PrivateKey.generate() try: - token_id = create_fungible_token(env, [ - lambda tx: tx.set_admin_key(admin_key), - lambda tx: tx.set_fee_schedule_key(fee_schedule_key), - lambda tx: tx.freeze_with(env.client).sign(admin_key) - ]) - + token_id = create_fungible_token( + env, + [ + lambda tx: tx.set_admin_key(admin_key), + lambda tx: tx.set_fee_schedule_key(fee_schedule_key), + lambda tx: tx.freeze_with(env.client).sign(admin_key), + ], + ) + # Update Fee Schedule Key new_fee_schedule_key = PrivateKey.generate() receipt = ( @@ -849,11 +787,15 @@ def test_integation_token_update_fee_schedule_key_fungible_token(): .sign(admin_key) .execute(env.client) ) - assert receipt.status == ResponseCode.SUCCESS, f"Token update failed with status: {ResponseCode(receipt.status).name}" + assert receipt.status == ResponseCode.SUCCESS, ( + f"Token update failed with status: {ResponseCode(receipt.status).name}" + ) token_info = TokenInfoQuery(token_id=token_id).execute(env.client) - assert token_info.fee_schedule_key.to_string() == new_fee_schedule_key.public_key().to_string(), "Updated fee_schedule_key mismatch" - + assert token_info.fee_schedule_key.to_string() == new_fee_schedule_key.public_key().to_string(), ( + "Updated fee_schedule_key mismatch" + ) + # Verify fee_schedule_key update_receipt = ( TokenFeeScheduleUpdateTransaction() @@ -866,25 +808,29 @@ def test_integation_token_update_fee_schedule_key_fungible_token(): assert update_receipt.status == ResponseCode.SUCCESS token_info = TokenInfoQuery(token_id=token_id).execute(env.client) - + assert len(token_info.custom_fees) == 1 assert token_info.custom_fees[0].amount == 1 assert token_info.custom_fees[0].fee_collector_account_id == env.client.operator_account_id finally: env.close() + def test_integation_token_update_fee_schedule_key_nft(): env = IntegrationTestEnv() admin_key = PrivateKey.generate() fee_schedule_key = PrivateKey.generate() try: - token_id = create_nft_token(env, [ - lambda tx: tx.set_admin_key(admin_key), - lambda tx: tx.set_fee_schedule_key(fee_schedule_key), - lambda tx: tx.freeze_with(env.client).sign(admin_key) - ]) - + token_id = create_nft_token( + env, + [ + lambda tx: tx.set_admin_key(admin_key), + lambda tx: tx.set_fee_schedule_key(fee_schedule_key), + lambda tx: tx.freeze_with(env.client).sign(admin_key), + ], + ) + # Update Fee Schedule Key new_fee_schedule_key = PrivateKey.generate() receipt = ( @@ -895,11 +841,15 @@ def test_integation_token_update_fee_schedule_key_nft(): .sign(admin_key) .execute(env.client) ) - assert receipt.status == ResponseCode.SUCCESS, f"Token update failed with status: {ResponseCode(receipt.status).name}" + assert receipt.status == ResponseCode.SUCCESS, ( + f"Token update failed with status: {ResponseCode(receipt.status).name}" + ) token_info = TokenInfoQuery(token_id=token_id).execute(env.client) - assert token_info.fee_schedule_key.to_string() == new_fee_schedule_key.public_key().to_string(), "Updated fee_schedule_key mismatch" - + assert token_info.fee_schedule_key.to_string() == new_fee_schedule_key.public_key().to_string(), ( + "Updated fee_schedule_key mismatch" + ) + # Verify fee_schedule_key update_receipt = ( TokenFeeScheduleUpdateTransaction() @@ -912,7 +862,7 @@ def test_integation_token_update_fee_schedule_key_nft(): assert update_receipt.status == ResponseCode.SUCCESS token_info = TokenInfoQuery(token_id=token_id).execute(env.client) - + assert len(token_info.custom_fees) == 1 assert token_info.custom_fees[0].amount == 1 assert token_info.custom_fees[0].fee_collector_account_id == env.client.operator_account_id @@ -924,14 +874,14 @@ def test_integation_token_update_fee_schedule_key_nft(): def test_integration_token_update_with_public_key(): """Test updating token keys with PublicKey directly (not PrivateKey).""" env = IntegrationTestEnv() - + try: token_id = create_fungible_token(env) - + # Generate a new key pair for the freeze key freeze_private_key = PrivateKey.generate_ed25519() freeze_public_key = freeze_private_key.public_key() - + # Update token with PublicKey receipt = ( TokenUpdateTransaction() @@ -941,17 +891,21 @@ def test_integration_token_update_with_public_key(): .freeze_with(env.client) .execute(env.client) ) - - assert receipt.status == ResponseCode.SUCCESS, f"Token update transaction failed with status: {ResponseCode(receipt.status).name}" - + + assert receipt.status == ResponseCode.SUCCESS, ( + f"Token update transaction failed with status: {ResponseCode(receipt.status).name}" + ) + # Query the token and verify the freeze key matches the public key info = TokenInfoQuery().set_token_id(token_id).execute(env.client) - + assert info.name == "UpdatedWithPublicKey", "Token name failed to update" assert info.freeze_key is not None - + # info.freeze_key is already a PublicKey object, so we can compare directly - assert info.freeze_key.to_bytes_raw() == freeze_public_key.to_bytes_raw(), "Freeze key on network should match the PublicKey used in transaction" + assert info.freeze_key.to_bytes_raw() == freeze_public_key.to_bytes_raw(), ( + "Freeze key on network should match the PublicKey used in transaction" + ) finally: env.close() @@ -966,18 +920,18 @@ def test_integration_token_update_non_custodial_workflow(): 4. Operator executes the signed transaction """ env = IntegrationTestEnv() - + try: token_id = create_fungible_token(env) - + # 1. SETUP: Create a new key pair for the "user" user_private_key = PrivateKey.generate_ed25519() user_public_key = user_private_key.public_key() - + # ================================================================= # STEP 1 & 2: OPERATOR (CLIENT) BUILDS THE TRANSACTION # ================================================================= - + tx = ( TokenUpdateTransaction() .set_token_id(token_id) @@ -985,34 +939,38 @@ def test_integration_token_update_non_custodial_workflow(): .set_freeze_key(user_public_key) # <-- Using PublicKey! .freeze_with(env.client) ) - + tx_bytes = tx.to_bytes() - + # ================================================================= # STEP 3: USER (SIGNER) SIGNS THE TRANSACTION # ================================================================= - + tx_from_bytes = Transaction.from_bytes(tx_bytes) tx_from_bytes.sign(user_private_key) - + # ================================================================= # STEP 4: OPERATOR (CLIENT) EXECUTES THE SIGNED TX # ================================================================= - + receipt = tx_from_bytes.execute(env.client) - - assert receipt.status == ResponseCode.SUCCESS, f"Token update failed with status: {ResponseCode(receipt.status).name}" - + + assert receipt.status == ResponseCode.SUCCESS, ( + f"Token update failed with status: {ResponseCode(receipt.status).name}" + ) + # PROOF: Query the token and check if the freeze key matches token_info = TokenInfoQuery(token_id=token_id).execute(env.client) - + assert token_info.freeze_key is not None - + # This is the STRONG assertion: # Compare the bytes of the key from the network # with the bytes of the key we originally used. # token_info.freeze_key is already a PublicKey object, so we can compare directly - assert token_info.freeze_key.to_bytes_raw() == user_public_key.to_bytes_raw(), "Freeze key on network should match the PublicKey used in transaction" + assert token_info.freeze_key.to_bytes_raw() == user_public_key.to_bytes_raw(), ( + "Freeze key on network should match the PublicKey used in transaction" + ) assert token_info.name == "NonCustodialToken", "Token name should be updated" finally: env.close() @@ -1022,14 +980,14 @@ def test_integration_token_update_non_custodial_workflow(): def test_integration_token_update_with_ecdsa_public_key(): """Test updating token keys with ECDSA PublicKey.""" env = IntegrationTestEnv() - + try: token_id = create_fungible_token(env) - + # Generate a new ECDSA key pair for the admin key admin_private_key = PrivateKey.generate_ecdsa() admin_public_key = admin_private_key.public_key() - + # Update token with ECDSA PublicKey # Note: When updating admin_key, we need to sign with the new admin key receipt = ( @@ -1041,16 +999,20 @@ def test_integration_token_update_with_ecdsa_public_key(): .sign(admin_private_key) # Sign with the new admin key .execute(env.client) ) - - assert receipt.status == ResponseCode.SUCCESS, f"Token update transaction failed with status: {ResponseCode(receipt.status).name}" - + + assert receipt.status == ResponseCode.SUCCESS, ( + f"Token update transaction failed with status: {ResponseCode(receipt.status).name}" + ) + # Query the token and verify the admin key matches the public key token_info = TokenInfoQuery(token_id=token_id).execute(env.client) assert token_info is not None assert token_info.admin_key is not None - + # token_info.admin_key is already a PublicKey object, so we can compare directly - assert token_info.admin_key.to_bytes_raw() == admin_public_key.to_bytes_raw(), "Admin key on network should match the ECDSA PublicKey used in transaction" + assert token_info.admin_key.to_bytes_raw() == admin_public_key.to_bytes_raw(), ( + "Admin key on network should match the ECDSA PublicKey used in transaction" + ) assert token_info.name == "UpdatedWithECDSA", "Token name should be updated" finally: env.close() @@ -1060,52 +1022,54 @@ def test_integration_token_update_with_ecdsa_public_key(): def test_integration_token_update_with_mixed_key_types(): """Test updating token with mixed PrivateKey and PublicKey types.""" env = IntegrationTestEnv() - + try: token_id = create_fungible_token(env) - + # Generate keys - mix of PrivateKey and PublicKey admin_private_key = PrivateKey.generate_ed25519() freeze_public_key = PrivateKey.generate_ed25519().public_key() wipe_private_key = PrivateKey.generate_ecdsa() supply_public_key = PrivateKey.generate_ecdsa().public_key() - + # Update token with mixed key types # Note: When updating admin_key, we need to sign with the new admin key receipt = ( TokenUpdateTransaction() .set_token_id(token_id) .set_token_name("MixedKeysToken") - .set_admin_key(admin_private_key) # PrivateKey - .set_freeze_key(freeze_public_key) # PublicKey - .set_wipe_key(wipe_private_key) # PrivateKey - .set_supply_key(supply_public_key) # PublicKey + .set_admin_key(admin_private_key) # PrivateKey + .set_freeze_key(freeze_public_key) # PublicKey + .set_wipe_key(wipe_private_key) # PrivateKey + .set_supply_key(supply_public_key) # PublicKey .freeze_with(env.client) .sign(admin_private_key) # Sign with the new admin key .execute(env.client) ) - - assert receipt.status == ResponseCode.SUCCESS, f"Token update transaction failed with status: {ResponseCode(receipt.status).name}" - + + assert receipt.status == ResponseCode.SUCCESS, ( + f"Token update transaction failed with status: {ResponseCode(receipt.status).name}" + ) + # Query the token and verify all keys are correctly set token_info = TokenInfoQuery(token_id=token_id).execute(env.client) - + assert token_info.name == "MixedKeysToken", "Token name should be updated" assert token_info.admin_key is not None assert token_info.freeze_key is not None assert token_info.wipe_key is not None assert token_info.supply_key is not None - + # token_info keys are already PublicKey objects, so we can compare directly # Verify admin key (from PrivateKey) assert token_info.admin_key.to_bytes_raw() == admin_private_key.public_key().to_bytes_raw() - + # Verify freeze key (from PublicKey) assert token_info.freeze_key.to_bytes_raw() == freeze_public_key.to_bytes_raw() - + # Verify wipe key (from PrivateKey) assert token_info.wipe_key.to_bytes_raw() == wipe_private_key.public_key().to_bytes_raw() - + # Verify supply key (from PublicKey) assert token_info.supply_key.to_bytes_raw() == supply_public_key.to_bytes_raw() finally: diff --git a/tests/integration/token_wipe_account_transaction_e2e_test.py b/tests/integration/token_wipe_account_transaction_e2e_test.py index 3e1911c4b..09e00879e 100644 --- a/tests/integration/token_wipe_account_transaction_e2e_test.py +++ b/tests/integration/token_wipe_account_transaction_e2e_test.py @@ -1,12 +1,14 @@ +from __future__ import annotations + import pytest +from hiero_sdk_python.account.account_create_transaction import AccountCreateTransaction from hiero_sdk_python.crypto.private_key import PrivateKey from hiero_sdk_python.exceptions import PrecheckError from hiero_sdk_python.hbar import Hbar from hiero_sdk_python.query.account_balance_query import CryptoGetAccountBalanceQuery -from hiero_sdk_python.tokens.token_associate_transaction import TokenAssociateTransaction -from hiero_sdk_python.account.account_create_transaction import AccountCreateTransaction from hiero_sdk_python.response_code import ResponseCode +from hiero_sdk_python.tokens.token_associate_transaction import TokenAssociateTransaction from hiero_sdk_python.tokens.token_wipe_transaction import TokenWipeTransaction from hiero_sdk_python.transaction.transfer_transaction import TransferTransaction from tests.integration.utils import IntegrationTestEnv, create_fungible_token @@ -15,365 +17,359 @@ @pytest.mark.integration def test_integration_token_wipe_account_transaction_can_execute(): env = IntegrationTestEnv() - + try: new_account_private_key = PrivateKey.generate() new_account_public_key = new_account_private_key.public_key() - + initial_balance = Hbar(2) - + assert initial_balance.to_tinybars() == 200000000 - + account_transaction = AccountCreateTransaction( - key=new_account_public_key, - initial_balance=initial_balance, - memo="Recipient Account" + key=new_account_public_key, initial_balance=initial_balance, memo="Recipient Account" ) account_transaction.freeze_with(env.client) receipt = account_transaction.execute(env.client) - - assert receipt.status == ResponseCode.SUCCESS, f"Account creation failed with status: {ResponseCode(receipt.status).name}" - + + assert receipt.status == ResponseCode.SUCCESS, ( + f"Account creation failed with status: {ResponseCode(receipt.status).name}" + ) + account_id = receipt.account_id - + assert account_id is not None - + token_id = create_fungible_token(env) - + assert token_id is not None - - associate_transaction = TokenAssociateTransaction( - account_id=account_id, - token_ids=[token_id] - ) - + + associate_transaction = TokenAssociateTransaction(account_id=account_id, token_ids=[token_id]) + associate_transaction.freeze_with(env.client) associate_transaction.sign(new_account_private_key) receipt = associate_transaction.execute(env.client) - - assert receipt.status == ResponseCode.SUCCESS, f"Token association failed with status: {ResponseCode(receipt.status).name}" - + + assert receipt.status == ResponseCode.SUCCESS, ( + f"Token association failed with status: {ResponseCode(receipt.status).name}" + ) + transfer_transaction = TransferTransaction() transfer_transaction.add_token_transfer(token_id, env.client.operator_account_id, -10) transfer_transaction.add_token_transfer(token_id, account_id, 10) - + transfer_transaction.freeze_with(env.client) receipt = transfer_transaction.execute(env.client) - - assert receipt.status == ResponseCode.SUCCESS, f"Token transfer failed with status: {ResponseCode(receipt.status).name}" - + + assert receipt.status == ResponseCode.SUCCESS, ( + f"Token transfer failed with status: {ResponseCode(receipt.status).name}" + ) + query_transaction = CryptoGetAccountBalanceQuery(account_id) balance = query_transaction.execute(env.client) - + assert balance.token_balances is not None and balance.token_balances[token_id] == 10 - - wipe_transaction = TokenWipeTransaction( - token_id=token_id, - account_id=account_id, - amount=10 - ) - + + wipe_transaction = TokenWipeTransaction(token_id=token_id, account_id=account_id, amount=10) + wipe_transaction.freeze_with(env.client) receipt = wipe_transaction.execute(env.client) - - assert receipt.status == ResponseCode.SUCCESS, f"Token wipe failed with status: {ResponseCode(receipt.status).name}" - + + assert receipt.status == ResponseCode.SUCCESS, ( + f"Token wipe failed with status: {ResponseCode(receipt.status).name}" + ) + query_transaction = CryptoGetAccountBalanceQuery(account_id) balance = query_transaction.execute(env.client) - + assert balance.token_balances is not None and balance.token_balances[token_id] == 0 finally: env.close() - + + @pytest.mark.integration def test_integration_token_wipe_transaction_no_token_id(): env = IntegrationTestEnv() - + try: new_account_private_key = PrivateKey.generate() new_account_public_key = new_account_private_key.public_key() - + initial_balance = Hbar(2) - + assert initial_balance.to_tinybars() == 200000000 - + account_transaction = AccountCreateTransaction( - key=new_account_public_key, - initial_balance=initial_balance, - memo="Recipient Account" + key=new_account_public_key, initial_balance=initial_balance, memo="Recipient Account" ) account_transaction.freeze_with(env.client) receipt = account_transaction.execute(env.client) - - assert receipt.status == ResponseCode.SUCCESS, f"Account creation failed with status: {ResponseCode(receipt.status).name}" - + + assert receipt.status == ResponseCode.SUCCESS, ( + f"Account creation failed with status: {ResponseCode(receipt.status).name}" + ) + account_id = receipt.account_id - + assert account_id is not None - + token_id = create_fungible_token(env) - + assert token_id is not None - - associate_transaction = TokenAssociateTransaction( - account_id=account_id, - token_ids=[token_id] - ) - + + associate_transaction = TokenAssociateTransaction(account_id=account_id, token_ids=[token_id]) + associate_transaction.freeze_with(env.client) associate_transaction.sign(new_account_private_key) receipt = associate_transaction.execute(env.client) - - assert receipt.status == ResponseCode.SUCCESS, f"Token association failed with status: {ResponseCode(receipt.status).name}" - + + assert receipt.status == ResponseCode.SUCCESS, ( + f"Token association failed with status: {ResponseCode(receipt.status).name}" + ) + transfer_transaction = TransferTransaction() transfer_transaction.add_token_transfer(token_id, env.client.operator_account_id, -10) transfer_transaction.add_token_transfer(token_id, account_id, 10) - + transfer_transaction.freeze_with(env.client) receipt = transfer_transaction.execute(env.client) - - assert receipt.status == ResponseCode.SUCCESS, f"Token transfer failed with status: {ResponseCode(receipt.status).name}" - + + assert receipt.status == ResponseCode.SUCCESS, ( + f"Token transfer failed with status: {ResponseCode(receipt.status).name}" + ) + query_transaction = CryptoGetAccountBalanceQuery(account_id) balance = query_transaction.execute(env.client) - + assert balance.token_balances is not None and balance.token_balances[token_id] == 10 - - wipe_transaction = TokenWipeTransaction( - token_id=None, - account_id=account_id, - amount=10 - ) - + + wipe_transaction = TokenWipeTransaction(token_id=None, account_id=account_id, amount=10) + wipe_transaction.freeze_with(env.client) - + with pytest.raises(PrecheckError, match="failed precheck with status: INVALID_TOKEN_ID"): wipe_transaction.execute(env.client) - + query_transaction = CryptoGetAccountBalanceQuery(account_id) balance = query_transaction.execute(env.client) - + assert balance.token_balances is not None and balance.token_balances[token_id] == 10 finally: env.close() - + + @pytest.mark.integration def test_integration_token_wipe_transaction_no_account_id(): env = IntegrationTestEnv() - + try: new_account_private_key = PrivateKey.generate() new_account_public_key = new_account_private_key.public_key() - + initial_balance = Hbar(2) - + assert initial_balance.to_tinybars() == 200000000 - + account_transaction = AccountCreateTransaction( - key=new_account_public_key, - initial_balance=initial_balance, - memo="Recipient Account" + key=new_account_public_key, initial_balance=initial_balance, memo="Recipient Account" ) account_transaction.freeze_with(env.client) receipt = account_transaction.execute(env.client) - - assert receipt.status == ResponseCode.SUCCESS, f"Account creation failed with status: {ResponseCode(receipt.status).name}" - + + assert receipt.status == ResponseCode.SUCCESS, ( + f"Account creation failed with status: {ResponseCode(receipt.status).name}" + ) + account_id = receipt.account_id - + assert account_id is not None - + token_id = create_fungible_token(env) - + assert token_id is not None - - associate_transaction = TokenAssociateTransaction( - account_id=account_id, - token_ids=[token_id] - ) - + + associate_transaction = TokenAssociateTransaction(account_id=account_id, token_ids=[token_id]) + associate_transaction.freeze_with(env.client) associate_transaction.sign(new_account_private_key) receipt = associate_transaction.execute(env.client) - - assert receipt.status == ResponseCode.SUCCESS, f"Token association failed with status: {ResponseCode(receipt.status).name}" - + + assert receipt.status == ResponseCode.SUCCESS, ( + f"Token association failed with status: {ResponseCode(receipt.status).name}" + ) + transfer_transaction = TransferTransaction() transfer_transaction.add_token_transfer(token_id, env.client.operator_account_id, -10) transfer_transaction.add_token_transfer(token_id, account_id, 10) - + transfer_transaction.freeze_with(env.client) receipt = transfer_transaction.execute(env.client) - - assert receipt.status == ResponseCode.SUCCESS, f"Token transfer failed with status: {ResponseCode(receipt.status).name}" - + + assert receipt.status == ResponseCode.SUCCESS, ( + f"Token transfer failed with status: {ResponseCode(receipt.status).name}" + ) + query_transaction = CryptoGetAccountBalanceQuery(account_id) balance = query_transaction.execute(env.client) - + assert balance.token_balances is not None and balance.token_balances[token_id] == 10 - - wipe_transaction = TokenWipeTransaction( - token_id=token_id, - account_id=None, - amount=10 - ) - + + wipe_transaction = TokenWipeTransaction(token_id=token_id, account_id=None, amount=10) + wipe_transaction.freeze_with(env.client) - + with pytest.raises(PrecheckError, match="failed precheck with status: INVALID_ACCOUNT_ID"): wipe_transaction.execute(env.client) - + query_transaction = CryptoGetAccountBalanceQuery(account_id) balance = query_transaction.execute(env.client) - + assert balance.token_balances is not None and balance.token_balances[token_id] == 10 finally: env.close() - + + @pytest.mark.integration def test_integration_token_wipe_transaction_no_amount(): env = IntegrationTestEnv() - + try: new_account_private_key = PrivateKey.generate() new_account_public_key = new_account_private_key.public_key() - + initial_balance = Hbar(2) - + assert initial_balance.to_tinybars() == 200000000 - + account_transaction = AccountCreateTransaction( - key=new_account_public_key, - initial_balance=initial_balance, - memo="Recipient Account" + key=new_account_public_key, initial_balance=initial_balance, memo="Recipient Account" ) account_transaction.freeze_with(env.client) receipt = account_transaction.execute(env.client) - - assert receipt.status == ResponseCode.SUCCESS, f"Account creation failed with status: {ResponseCode(receipt.status).name}" - + + assert receipt.status == ResponseCode.SUCCESS, ( + f"Account creation failed with status: {ResponseCode(receipt.status).name}" + ) + account_id = receipt.account_id - + assert account_id is not None - + token_id = create_fungible_token(env) - + assert token_id is not None - - associate_transaction = TokenAssociateTransaction( - account_id=account_id, - token_ids=[token_id] - ) - + + associate_transaction = TokenAssociateTransaction(account_id=account_id, token_ids=[token_id]) + associate_transaction.freeze_with(env.client) associate_transaction.sign(new_account_private_key) receipt = associate_transaction.execute(env.client) - - assert receipt.status == ResponseCode.SUCCESS, f"Token association failed with status: {ResponseCode(receipt.status).name}" - + + assert receipt.status == ResponseCode.SUCCESS, ( + f"Token association failed with status: {ResponseCode(receipt.status).name}" + ) + transfer_transaction = TransferTransaction() transfer_transaction.add_token_transfer(token_id, env.client.operator_account_id, -10) transfer_transaction.add_token_transfer(token_id, account_id, 10) - + transfer_transaction.freeze_with(env.client) receipt = transfer_transaction.execute(env.client) - - assert receipt.status == ResponseCode.SUCCESS, f"Token transfer failed with status: {ResponseCode(receipt.status).name}" - + + assert receipt.status == ResponseCode.SUCCESS, ( + f"Token transfer failed with status: {ResponseCode(receipt.status).name}" + ) + query_transaction = CryptoGetAccountBalanceQuery(account_id) balance = query_transaction.execute(env.client) - + assert balance.token_balances is not None and balance.token_balances[token_id] == 10 - - wipe_transaction = TokenWipeTransaction( - token_id=token_id, - account_id=account_id, - amount=0 - ) - + + wipe_transaction = TokenWipeTransaction(token_id=token_id, account_id=account_id, amount=0) + wipe_transaction.freeze_with(env.client) receipt = wipe_transaction.execute(env.client) - - assert receipt.status == ResponseCode.SUCCESS, f"Token wipe failed with status: {ResponseCode(receipt.status).name}" + + assert receipt.status == ResponseCode.SUCCESS, ( + f"Token wipe failed with status: {ResponseCode(receipt.status).name}" + ) query_transaction = CryptoGetAccountBalanceQuery(account_id) balance = query_transaction.execute(env.client) - + assert balance.token_balances is not None and balance.token_balances[token_id] == 10 finally: env.close() - + @pytest.mark.integration def test_integration_token_wipe_account_transaction_not_zero_tokens_at_delete(): env = IntegrationTestEnv() - + try: new_account_private_key = PrivateKey.generate() new_account_public_key = new_account_private_key.public_key() - + initial_balance = Hbar(2) - + assert initial_balance.to_tinybars() == 200000000 - + account_transaction = AccountCreateTransaction( - key=new_account_public_key, - initial_balance=initial_balance, - memo="Recipient Account" + key=new_account_public_key, initial_balance=initial_balance, memo="Recipient Account" ) account_transaction.freeze_with(env.client) receipt = account_transaction.execute(env.client) - - assert receipt.status == ResponseCode.SUCCESS, f"Account creation failed with status: {ResponseCode(receipt.status).name}" - + + assert receipt.status == ResponseCode.SUCCESS, ( + f"Account creation failed with status: {ResponseCode(receipt.status).name}" + ) + account_id = receipt.account_id - + assert account_id is not None - + token_id = create_fungible_token(env) - + assert token_id is not None - - associate_transaction = TokenAssociateTransaction( - account_id=account_id, - token_ids=[token_id] - ) - + + associate_transaction = TokenAssociateTransaction(account_id=account_id, token_ids=[token_id]) + associate_transaction.freeze_with(env.client) associate_transaction.sign(new_account_private_key) receipt = associate_transaction.execute(env.client) - - assert receipt.status == ResponseCode.SUCCESS, f"Token association failed with status: {ResponseCode(receipt.status).name}" - + + assert receipt.status == ResponseCode.SUCCESS, ( + f"Token association failed with status: {ResponseCode(receipt.status).name}" + ) + transfer_transaction = TransferTransaction() transfer_transaction.add_token_transfer(token_id, env.client.operator_account_id, -20) transfer_transaction.add_token_transfer(token_id, account_id, 20) - + transfer_transaction.freeze_with(env.client) receipt = transfer_transaction.execute(env.client) - - assert receipt.status == ResponseCode.SUCCESS, f"Token transfer failed with status: {ResponseCode(receipt.status).name}" - + + assert receipt.status == ResponseCode.SUCCESS, ( + f"Token transfer failed with status: {ResponseCode(receipt.status).name}" + ) + query_transaction = CryptoGetAccountBalanceQuery(account_id) balance = query_transaction.execute(env.client) - + assert balance.token_balances is not None and balance.token_balances[token_id] == 20 - - wipe_transaction = TokenWipeTransaction( - token_id=token_id, - account_id=account_id, - amount=10 - ) - + + wipe_transaction = TokenWipeTransaction(token_id=token_id, account_id=account_id, amount=10) + wipe_transaction.freeze_with(env.client) receipt = wipe_transaction.execute(env.client) - - assert receipt.status == ResponseCode.SUCCESS, f"Token wipe failed with status: {ResponseCode(receipt.status).name}" - + + assert receipt.status == ResponseCode.SUCCESS, ( + f"Token wipe failed with status: {ResponseCode(receipt.status).name}" + ) + query_transaction = CryptoGetAccountBalanceQuery(account_id) balance = query_transaction.execute(env.client) - + assert balance.token_balances is not None and balance.token_balances[token_id] == 10 finally: - env.close() \ No newline at end of file + env.close() diff --git a/tests/integration/topic_create_transaction_e2e_test.py b/tests/integration/topic_create_transaction_e2e_test.py index ba1f53871..6092b4cf6 100644 --- a/tests/integration/topic_create_transaction_e2e_test.py +++ b/tests/integration/topic_create_transaction_e2e_test.py @@ -1,54 +1,61 @@ +from __future__ import annotations + import pytest from hiero_sdk_python.consensus.topic_create_transaction import TopicCreateTransaction from hiero_sdk_python.consensus.topic_delete_transaction import TopicDeleteTransaction -from hiero_sdk_python.query.topic_info_query import TopicInfoQuery -from hiero_sdk_python.response_code import ResponseCode from hiero_sdk_python.crypto.private_key import PrivateKey from hiero_sdk_python.crypto.public_key import PublicKey +from hiero_sdk_python.query.topic_info_query import TopicInfoQuery +from hiero_sdk_python.response_code import ResponseCode from hiero_sdk_python.transaction.transaction import Transaction from tests.integration.utils import IntegrationTestEnv + topic_memo = "Python SDK created topic" + @pytest.mark.integration def test_integration_topic_create_transaction_can_execute(): env = IntegrationTestEnv() - + try: transaction = TopicCreateTransaction() - + transaction.freeze_with(env.client) receipt = transaction.execute(env.client) - - assert receipt.status == ResponseCode.SUCCESS, f"Topic creation failed with status: {ResponseCode(receipt.status).name}" - - transaction = TopicCreateTransaction( - memo=topic_memo, - admin_key=env.client.operator_private_key.public_key() + + assert receipt.status == ResponseCode.SUCCESS, ( + f"Topic creation failed with status: {ResponseCode(receipt.status).name}" ) - + + transaction = TopicCreateTransaction(memo=topic_memo, admin_key=env.client.operator_private_key.public_key()) + transaction.freeze_with(env.client) receipt = transaction.execute(env.client) - - assert receipt.status == ResponseCode.SUCCESS, f"Topic creation failed with status: {ResponseCode(receipt.status).name}" - + + assert receipt.status == ResponseCode.SUCCESS, ( + f"Topic creation failed with status: {ResponseCode(receipt.status).name}" + ) + topic_id = receipt.topic_id assert topic_id is not None - + topic_info = TopicInfoQuery(topic_id=topic_id).execute(env.client) assert topic_info is not None - + assert topic_info.memo == topic_memo assert topic_info.sequence_number == 0 assert env.client.operator_private_key.public_key()._to_proto() == topic_info.admin_key delete_transaction = TopicDeleteTransaction(topic_id=topic_id) - + delete_transaction.freeze_with(env.client) receipt = delete_transaction.execute(env.client) - - assert receipt.status == ResponseCode.SUCCESS, f"Topic deletion failed with status: {ResponseCode(receipt.status).name}" + + assert receipt.status == ResponseCode.SUCCESS, ( + f"Topic deletion failed with status: {ResponseCode(receipt.status).name}" + ) finally: env.close() @@ -57,40 +64,41 @@ def test_integration_topic_create_transaction_can_execute(): def test_integration_topic_create_with_private_key(): """Test creating a topic with PrivateKey directly (not PublicKey).""" env = IntegrationTestEnv() - + try: # Generate a new private key for the admin key admin_private_key = PrivateKey.generate_ed25519() admin_public_key = admin_private_key.public_key() - + # Create topic with PrivateKey - transaction = TopicCreateTransaction( - memo="Topic with PrivateKey", - admin_key=admin_private_key - ) - + transaction = TopicCreateTransaction(memo="Topic with PrivateKey", admin_key=admin_private_key) + # Sign with the admin key (required when admin_key is set) transaction.freeze_with(env.client) transaction.sign(admin_private_key) receipt = transaction.execute(env.client) - - assert receipt.status == ResponseCode.SUCCESS, f"Topic creation failed with status: {ResponseCode(receipt.status).name}" - + + assert receipt.status == ResponseCode.SUCCESS, ( + f"Topic creation failed with status: {ResponseCode(receipt.status).name}" + ) + topic_id = receipt.topic_id assert topic_id is not None - + # Query the topic and verify the admin key matches the public key topic_info = TopicInfoQuery(topic_id=topic_id).execute(env.client) assert topic_info is not None assert topic_info.admin_key is not None - + # Convert proto Key to PublicKey for comparison admin_key_from_network = PublicKey._from_proto(topic_info.admin_key) admin_key_bytes = admin_key_from_network.to_bytes_raw() public_key_bytes = admin_public_key.to_bytes_raw() - - assert admin_key_bytes == public_key_bytes, "Admin key on network should match the public key derived from PrivateKey" - + + assert admin_key_bytes == public_key_bytes, ( + "Admin key on network should match the public key derived from PrivateKey" + ) + # Clean up delete_transaction = TopicDeleteTransaction(topic_id=topic_id) delete_transaction.freeze_with(env.client) @@ -111,56 +119,58 @@ def test_integration_topic_create_non_custodial_workflow(): 4. Operator executes the signed transaction """ env = IntegrationTestEnv() - + try: # 1. SETUP: Create a new key pair for the "user" user_private_key = PrivateKey.generate_ed25519() user_public_key = user_private_key.public_key() - + # ================================================================= # STEP 1 & 2: OPERATOR (CLIENT) BUILDS THE TRANSACTION # ================================================================= - + tx = ( TopicCreateTransaction() .set_memo("NonCustodialTopic") .set_admin_key(user_public_key) # <-- The new feature! .freeze_with(env.client) ) - + tx_bytes = tx.to_bytes() - + # ================================================================= # STEP 3: USER (SIGNER) SIGNS THE TRANSACTION # ================================================================= - + tx_from_bytes = Transaction.from_bytes(tx_bytes) tx_from_bytes.sign(user_private_key) - + # ================================================================= # STEP 4: OPERATOR (CLIENT) EXECUTES THE SIGNED TX # ================================================================= - + receipt = tx_from_bytes.execute(env.client) - + assert receipt is not None topic_id = receipt.topic_id assert topic_id is not None - + # PROOF: Query the new topic and check if the admin key matches topic_info = TopicInfoQuery(topic_id=topic_id).execute(env.client) - + assert topic_info.admin_key is not None - + # This is the STRONG assertion: # Compare the bytes of the key from the network # with the bytes of the key we originally used. admin_key_from_network = PublicKey._from_proto(topic_info.admin_key) admin_key_bytes = admin_key_from_network.to_bytes_raw() public_key_bytes = user_public_key.to_bytes_raw() - - assert admin_key_bytes == public_key_bytes, "Admin key on network should match the PublicKey used in transaction" - + + assert admin_key_bytes == public_key_bytes, ( + "Admin key on network should match the PublicKey used in transaction" + ) + # Clean up delete_transaction = TopicDeleteTransaction(topic_id=topic_id) delete_transaction.freeze_with(env.client) @@ -175,40 +185,41 @@ def test_integration_topic_create_non_custodial_workflow(): def test_integration_topic_create_with_ecdsa_private_key(): """Test creating a topic with ECDSA PrivateKey.""" env = IntegrationTestEnv() - + try: # Generate a new ECDSA private key for the admin key admin_private_key = PrivateKey.generate("ecdsa") admin_public_key = admin_private_key.public_key() - + # Create topic with ECDSA PrivateKey - transaction = TopicCreateTransaction( - memo="Topic with ECDSA PrivateKey", - admin_key=admin_private_key - ) - + transaction = TopicCreateTransaction(memo="Topic with ECDSA PrivateKey", admin_key=admin_private_key) + # Sign with the admin key (required when admin_key is set) transaction.freeze_with(env.client) transaction.sign(admin_private_key) receipt = transaction.execute(env.client) - - assert receipt.status == ResponseCode.SUCCESS, f"Topic creation failed with status: {ResponseCode(receipt.status).name}" - + + assert receipt.status == ResponseCode.SUCCESS, ( + f"Topic creation failed with status: {ResponseCode(receipt.status).name}" + ) + topic_id = receipt.topic_id assert topic_id is not None - + # Query the topic and verify the admin key matches the public key topic_info = TopicInfoQuery(topic_id=topic_id).execute(env.client) assert topic_info is not None assert topic_info.admin_key is not None - + # Convert proto Key to PublicKey for comparison admin_key_from_network = PublicKey._from_proto(topic_info.admin_key) admin_key_bytes = admin_key_from_network.to_bytes_raw() public_key_bytes = admin_public_key.to_bytes_raw() - - assert admin_key_bytes == public_key_bytes, "Admin key on network should match the public key derived from ECDSA PrivateKey" - + + assert admin_key_bytes == public_key_bytes, ( + "Admin key on network should match the public key derived from ECDSA PrivateKey" + ) + # Clean up delete_transaction = TopicDeleteTransaction(topic_id=topic_id) delete_transaction.freeze_with(env.client) @@ -216,4 +227,4 @@ def test_integration_topic_create_with_ecdsa_private_key(): delete_receipt = delete_transaction.execute(env.client) assert delete_receipt.status == ResponseCode.SUCCESS finally: - env.close() \ No newline at end of file + env.close() diff --git a/tests/integration/topic_delete_transaction_e2e_test.py b/tests/integration/topic_delete_transaction_e2e_test.py index baa08f701..72742a6a6 100644 --- a/tests/integration/topic_delete_transaction_e2e_test.py +++ b/tests/integration/topic_delete_transaction_e2e_test.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import pytest from hiero_sdk_python.consensus.topic_create_transaction import TopicCreateTransaction @@ -11,31 +13,32 @@ @pytest.mark.integration def test_integration_topic_delete_transaction_can_execute(): env = IntegrationTestEnv() - + try: - create_transaction = TopicCreateTransaction( - memo="Topic to delete", - admin_key=env.public_operator_key - ) + create_transaction = TopicCreateTransaction(memo="Topic to delete", admin_key=env.public_operator_key) create_transaction.freeze_with(env.client) create_receipt = create_transaction.execute(env.client) - - assert create_receipt.status == ResponseCode.SUCCESS, f"Topic creation failed with status: {ResponseCode(create_receipt.status).name}" - + + assert create_receipt.status == ResponseCode.SUCCESS, ( + f"Topic creation failed with status: {ResponseCode(create_receipt.status).name}" + ) + topic_id = create_receipt.topic_id assert topic_id is not None - + topic_info = TopicInfoQuery(topic_id=topic_id).execute(env.client) - + assert topic_info is not None - + delete_transaction = TopicDeleteTransaction(topic_id=topic_id) delete_transaction.freeze_with(env.client) delete_receipt = delete_transaction.execute(env.client) - - assert delete_receipt.status == ResponseCode.SUCCESS, f"Topic deletion failed with status: {ResponseCode(delete_receipt.status).name}" - + + assert delete_receipt.status == ResponseCode.SUCCESS, ( + f"Topic deletion failed with status: {ResponseCode(delete_receipt.status).name}" + ) + with pytest.raises(PrecheckError, match="failed precheck with status: INVALID_TOPIC_ID"): TopicInfoQuery(topic_id=topic_id).execute(env.client) finally: - env.close() \ No newline at end of file + env.close() diff --git a/tests/integration/topic_info_query_e2e_test.py b/tests/integration/topic_info_query_e2e_test.py index e79dfb4bc..dd60aa471 100644 --- a/tests/integration/topic_info_query_e2e_test.py +++ b/tests/integration/topic_info_query_e2e_test.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import pytest from hiero_sdk_python.consensus.topic_create_transaction import TopicCreateTransaction @@ -10,23 +12,22 @@ @pytest.mark.integration def test_integration_topic_info_query_can_execute(): env = IntegrationTestEnv() - + try: - create_transaction = TopicCreateTransaction( - memo="Topic for info query", - admin_key=env.public_operator_key - ) + create_transaction = TopicCreateTransaction(memo="Topic for info query", admin_key=env.public_operator_key) create_transaction.freeze_with(env.client) create_receipt = create_transaction.execute(env.client) - - assert create_receipt.status == ResponseCode.SUCCESS, f"Topic creation failed with status: {ResponseCode(create_receipt.status).name}" - + + assert create_receipt.status == ResponseCode.SUCCESS, ( + f"Topic creation failed with status: {ResponseCode(create_receipt.status).name}" + ) + topic_id = create_receipt.topic_id - + topic_info = TopicInfoQuery(topic_id=topic_id).execute(env.client) - + assert topic_info is not None - + assert topic_info.memo == "Topic for info query" assert topic_info.sequence_number == 0 assert env.client.operator_private_key.public_key()._to_proto() == topic_info.admin_key @@ -34,7 +35,9 @@ def test_integration_topic_info_query_can_execute(): delete_transaction = TopicDeleteTransaction(topic_id=topic_id) delete_transaction.freeze_with(env.client) delete_receipt = delete_transaction.execute(env.client) - - assert delete_receipt.status == ResponseCode.SUCCESS, f"Topic deletion failed with status: {ResponseCode(delete_receipt.status).name}" + + assert delete_receipt.status == ResponseCode.SUCCESS, ( + f"Topic deletion failed with status: {ResponseCode(delete_receipt.status).name}" + ) finally: - env.close() \ No newline at end of file + env.close() diff --git a/tests/integration/topic_message_query_e2e_test.py b/tests/integration/topic_message_query_e2e_test.py index 5749688b8..f7c1a10ee 100644 --- a/tests/integration/topic_message_query_e2e_test.py +++ b/tests/integration/topic_message_query_e2e_test.py @@ -1,9 +1,12 @@ """ Integration tests for the TopicMessageSubmitTransaction class. """ -from datetime import datetime, timedelta, timezone + +from __future__ import annotations + import time -from typing import List +from datetime import datetime, timedelta, timezone + import pytest from hiero_sdk_python.consensus.topic_create_transaction import TopicCreateTransaction @@ -13,7 +16,7 @@ ) from hiero_sdk_python.query.topic_message_query import TopicMessageQuery from hiero_sdk_python.response_code import ResponseCode -from tests.integration.utils import env + BIG_CONTENT = """ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur aliquam augue sem, ut mattis dui laoreet a. Curabitur consequat est euismod, scelerisque metus et, tristique dui. Nulla commodo mauris ut faucibus ultricies. Quisque venenatis nisl nec augue tempus, at efficitur elit eleifend. Duis pharetra felis metus, sed dapibus urna vehicula id. Duis non venenatis turpis, sit amet ornare orci. Donec non interdum quam. Sed finibus nunc et risus finibus, non sagittis lorem cursus. Proin pellentesque tempor aliquam. Sed congue nisl in enim bibendum, condimentum vehicula nisi feugiat. @@ -41,16 +44,12 @@ Etiam ut sodales ex. Nulla luctus, magna eu scelerisque sagittis, nibh quam consectetur neque, non rutrum dolor metus nec ex. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Sed egestas augue elit, sollicitudin accumsan massa lobortis ac. Curabitur placerat, dolor a aliquam maximus, velit ipsum laoreet ligula, id ullamcorper lacus nibh eget nisl. Donec eget lacus venenatis enim consequat auctor vel in. """ + def create_topic(client): """Helper transaction for creating a topic.""" - receipt = ( - TopicCreateTransaction() - .execute(client) - ) + receipt = TopicCreateTransaction().execute(client) - assert receipt.status == ResponseCode.SUCCESS, ( - f"Topic creation failed: {ResponseCode(receipt.status).name}" - ) + assert receipt.status == ResponseCode.SUCCESS, f"Topic creation failed: {ResponseCode(receipt.status).name}" return receipt.topic_id @@ -61,41 +60,29 @@ def test_topic_message_query_receives_messages(env): time.sleep(3) - messages: List[str] = [] + messages: list[str] = [] def get_message(m: TopicMessage): - messages.append(m.contents.decode('utf-8')) - + messages.append(m.contents.decode("utf-8")) + def on_error_handler(e): raise RuntimeError(f"Subscription error: {e}") - query = TopicMessageQuery( - topic_id=topic_id, - start_time=datetime.now(timezone.utc), - limit=0 - ) + query = TopicMessageQuery(topic_id=topic_id, start_time=datetime.now(timezone.utc), limit=0) + + handle = query.subscribe(env.client, on_message=get_message, on_error=on_error_handler) - handle = query.subscribe( - env.client, - on_message=get_message, - on_error=on_error_handler - ) - time.sleep(3) message_receipt = ( - TopicMessageSubmitTransaction( - topic_id=topic_id, - message="Hello, Python SDK!" - ) + TopicMessageSubmitTransaction(topic_id=topic_id, message="Hello, Python SDK!") .freeze_with(env.client) .execute(env.client) ) - assert ( - message_receipt.status == ResponseCode.SUCCESS - ), f"Message submission failed with status: {ResponseCode(message_receipt.status).name}" - + assert message_receipt.status == ResponseCode.SUCCESS, ( + f"Message submission failed with status: {ResponseCode(message_receipt.status).name}" + ) start = datetime.now() @@ -112,18 +99,14 @@ def on_error_handler(e): def test_topic_message_query_limit(env): """Test topic message query stops after receiving limit messages.""" topic_id = create_topic(env.client) - messages: List[str] = [] + messages: list[str] = [] def on_message(m: TopicMessage): messages.append(m.contents.decode("utf-8")) time.sleep(3) - query = TopicMessageQuery( - topic_id=topic_id, - start_time=datetime.now(timezone.utc), - limit=2 - ) + query = TopicMessageQuery(topic_id=topic_id, start_time=datetime.now(timezone.utc), limit=2) handle = query.subscribe(env.client, on_message=on_message) @@ -136,7 +119,7 @@ def on_message(m: TopicMessage): .freeze_with(env.client) .execute(env.client) ) - + start = datetime.now() while len(messages) != 2: @@ -155,51 +138,37 @@ def on_message(m: TopicMessage): def test_topic_message_query_large_message_chunking(env): """Test that topic message query receives chunked message.""" topic_id = create_topic(env.client) - messages: List[str] = [] + messages: list[str] = [] time.sleep(3) def get_message(m: TopicMessage): - messages.append(m.contents.decode('utf-8')) - + messages.append(m.contents.decode("utf-8")) + def on_error_handler(e): raise RuntimeError(f"Subscription error: {e}") - query = TopicMessageQuery( - topic_id=topic_id, - start_time=datetime.now(timezone.utc), - limit=0, - chunking_enabled=True - ) + query = TopicMessageQuery(topic_id=topic_id, start_time=datetime.now(timezone.utc), limit=0, chunking_enabled=True) - handle = query.subscribe( - env.client, - on_message=get_message, - on_error=on_error_handler - ) + handle = query.subscribe(env.client, on_message=get_message, on_error=on_error_handler) time.sleep(3) message_receipt = ( - TopicMessageSubmitTransaction( - topic_id=topic_id, - message=BIG_CONTENT - ) + TopicMessageSubmitTransaction(topic_id=topic_id, message=BIG_CONTENT) .freeze_with(env.client) .execute(env.client) ) - assert ( - message_receipt.status == ResponseCode.SUCCESS - ), f"Message submission failed with status: {ResponseCode(message_receipt.status).name}" - + assert message_receipt.status == ResponseCode.SUCCESS, ( + f"Message submission failed with status: {ResponseCode(message_receipt.status).name}" + ) - start = datetime.now() while len(messages) == 0: if datetime.now() - start > timedelta(seconds=180): raise TimeoutError("TopicMessage was not received in time") time.sleep(5) - + assert messages[0] == BIG_CONTENT handle.cancel() diff --git a/tests/integration/topic_message_submit_transaction_e2e_test.py b/tests/integration/topic_message_submit_transaction_e2e_test.py index b5d2c681c..cdf40e808 100644 --- a/tests/integration/topic_message_submit_transaction_e2e_test.py +++ b/tests/integration/topic_message_submit_transaction_e2e_test.py @@ -2,6 +2,8 @@ Integration tests for the TopicMessageSubmitTransaction class. """ +from __future__ import annotations + import pytest from hiero_sdk_python.consensus.topic_create_transaction import TopicCreateTransaction @@ -16,7 +18,7 @@ from hiero_sdk_python.response_code import ResponseCode from hiero_sdk_python.tokens.custom_fixed_fee import CustomFixedFee from hiero_sdk_python.transaction.custom_fee_limit import CustomFeeLimit -from tests.integration.utils import env + def create_topic(client, admin_key=None, submit_key=None, custom_fees=None): """Helper transaction for creating a topic.""" @@ -30,18 +32,13 @@ def create_topic(client, admin_key=None, submit_key=None, custom_fees=None): tx.set_custom_fees(custom_fees) receipt = tx.execute(client) - assert receipt.status == ResponseCode.SUCCESS, ( - f"Topic creation failed: {ResponseCode(receipt.status).name}" - ) + assert receipt.status == ResponseCode.SUCCESS, f"Topic creation failed: {ResponseCode(receipt.status).name}" return receipt.topic_id def delete_topic(client, topic_id): """Helper transaction to delete a topic.""" - receipt = ( - TopicDeleteTransaction(topic_id=topic_id) - .execute(client) - ) + receipt = TopicDeleteTransaction(topic_id=topic_id).execute(client) assert receipt.status == ResponseCode.SUCCESS, ( f"Topic deletion failed with status: {ResponseCode(receipt.status).name}" @@ -51,26 +48,20 @@ def delete_topic(client, topic_id): @pytest.mark.integration def test_integration_topic_message_submit_transaction_can_execute(env): """Test that a topic message submit transaction executes.""" - topic_id = create_topic( - client=env.client, - admin_key=env.operator_key - ) + topic_id = create_topic(client=env.client, admin_key=env.operator_key) info = TopicInfoQuery(topic_id=topic_id).execute(env.client) # Check that no message is submitted assert info.sequence_number == 0 - message_transaction = TopicMessageSubmitTransaction( - topic_id=topic_id, - message="Hello, Python SDK!" - ) + message_transaction = TopicMessageSubmitTransaction(topic_id=topic_id, message="Hello, Python SDK!") message_transaction.freeze_with(env.client) message_receipt = message_transaction.execute(env.client) - assert ( - message_receipt.status == ResponseCode.SUCCESS - ), f"Message submission failed with status: {ResponseCode(message_receipt.status).name}" + assert message_receipt.status == ResponseCode.SUCCESS, ( + f"Message submission failed with status: {ResponseCode(message_receipt.status).name}" + ) info = TopicInfoQuery(topic_id=topic_id).execute(env.client) # Check that one message is submitted @@ -82,25 +73,17 @@ def test_integration_topic_message_submit_transaction_can_execute(env): @pytest.mark.integration def test_topic_message_submit_transaction_can_submit_a_large_message(env): """Test topic message submit transaction can submit large message.""" - topic_id = create_topic( - client=env.client, - admin_key=env.operator_key - ) + topic_id = create_topic(client=env.client, admin_key=env.operator_key) info = TopicInfoQuery().set_topic_id(topic_id).execute(env.client) assert info.sequence_number == 0 - message = "A" * (1024 * 14) # message with (1024 * 14) bytes ie 14 chunks + message = "A" * (1024 * 14) # message with (1024 * 14) bytes ie 14 chunks - message_tx = ( - TopicMessageSubmitTransaction() - .set_topic_id(topic_id) - .set_message(message) - .freeze_with(env.client) - ) + message_tx = TopicMessageSubmitTransaction().set_topic_id(topic_id).set_message(message).freeze_with(env.client) message_receipt = message_tx.execute(env.client) - + assert message_receipt.status == ResponseCode.SUCCESS info = TopicInfoQuery().set_topic_id(topic_id).execute(env.client) @@ -112,15 +95,12 @@ def test_topic_message_submit_transaction_can_submit_a_large_message(env): @pytest.mark.integration def test_topic_message_submit_transaction_fails_if_max_chunks_less_than_requied(env): """Test topic message submit transaction fails if max_chunks less than requied.""" - topic_id = create_topic( - client=env.client, - admin_key=env.operator_key - ) + topic_id = create_topic(client=env.client, admin_key=env.operator_key) info = TopicInfoQuery().set_topic_id(topic_id).execute(env.client) assert info.sequence_number == 0 - message = "A" * (1024 * 14) # message with (1024 * 14) bytes ie 14 chunks + message = "A" * (1024 * 14) # message with (1024 * 14) bytes ie 14 chunks message_tx = ( TopicMessageSubmitTransaction() @@ -131,8 +111,8 @@ def test_topic_message_submit_transaction_fails_if_max_chunks_less_than_requied( ) with pytest.raises(ValueError): - message_receipt = message_tx.execute(env.client) - + message_tx.execute(env.client) + delete_topic(env.client, topic_id) @@ -141,29 +121,22 @@ def test_integration_topic_message_submit_transaction_with_submit_key(env): """Test that a topic message submit transaction executes with submit key.""" submit_key = PrivateKey.generate() - topic_id = create_topic( - client=env.client, - admin_key=env.operator_key, - submit_key=submit_key - ) + topic_id = create_topic(client=env.client, admin_key=env.operator_key, submit_key=submit_key) info = TopicInfoQuery(topic_id=topic_id).execute(env.client) # Check that no message is submited assert info.sequence_number == 0 - message_transaction = TopicMessageSubmitTransaction( - topic_id=topic_id, - message="Hello, Python SDK!" - ) + message_transaction = TopicMessageSubmitTransaction(topic_id=topic_id, message="Hello, Python SDK!") message_transaction.freeze_with(env.client) # Sign with submit key - message_transaction.sign(submit_key) + message_transaction.sign(submit_key) message_receipt = message_transaction.execute(env.client) - assert ( - message_receipt.status == ResponseCode.SUCCESS - ), f"Message submission failed with status: {ResponseCode(message_receipt.status).name}" + assert message_receipt.status == ResponseCode.SUCCESS, ( + f"Message submission failed with status: {ResponseCode(message_receipt.status).name}" + ) info = TopicInfoQuery(topic_id=topic_id).execute(env.client) # Check that one message is submited @@ -177,27 +150,20 @@ def test_integration_topic_message_submit_transaction_without_submit_key_fails(e """Test that a topic message fails submitting transaction without submit key.""" submit_key = PrivateKey.generate() - topic_id = create_topic( - client=env.client, - admin_key=env.operator_key, - submit_key=submit_key - ) + topic_id = create_topic(client=env.client, admin_key=env.operator_key, submit_key=submit_key) info = TopicInfoQuery(topic_id=topic_id).execute(env.client) # Check that no message is submited assert info.sequence_number == 0 - message_transaction = TopicMessageSubmitTransaction( - topic_id=topic_id, - message="Hello, Python SDK!" - ) + message_transaction = TopicMessageSubmitTransaction(topic_id=topic_id, message="Hello, Python SDK!") message_transaction.freeze_with(env.client) message_receipt = message_transaction.execute(env.client) - assert ( - message_receipt.status == ResponseCode.INVALID_SIGNATURE - ), f"Message submission must fail with status: {ResponseCode.INVALID_SIGNATURE}" + assert message_receipt.status == ResponseCode.INVALID_SIGNATURE, ( + f"Message submission must fail with status: {ResponseCode.INVALID_SIGNATURE}" + ) delete_topic(env.client, topic_id) @@ -209,23 +175,17 @@ def test_integration_topic_message_submit_transaction_can_execute_with_custom_fe account = env.create_account(3) # Create an account with 3 Hbar balance - topic_fee = ( - CustomFixedFee().set_hbar_amount(Hbar(1)).set_fee_collector_account_id(env.operator_id) - ) + topic_fee = CustomFixedFee().set_hbar_amount(Hbar(1)).set_fee_collector_account_id(env.operator_id) - topic_id = create_topic( - client=env.client, - admin_key=env.operator_key, - custom_fees=[topic_fee] - ) + topic_id = create_topic(client=env.client, admin_key=env.operator_key, custom_fees=[topic_fee]) info = TopicInfoQuery(topic_id=topic_id).execute(env.client) assert info.sequence_number == 0 balance = CryptoGetAccountBalanceQuery().set_account_id(account.id).execute(env.client) - assert ( - balance.hbars.to_tinybars() == Hbar(3).to_tinybars() - ), f"Expected balance of 3 Hbar, but got {balance.hbars.to_tinybars()}" + assert balance.hbars.to_tinybars() == Hbar(3).to_tinybars(), ( + f"Expected balance of 3 Hbar, but got {balance.hbars.to_tinybars()}" + ) env.client.set_operator(account.id, account.key) # Set the operator to the account @@ -243,17 +203,17 @@ def test_integration_topic_message_submit_transaction_can_execute_with_custom_fe tx.transaction_fee = Hbar(2).to_tinybars() receipt = tx.execute(env.client) - assert ( - receipt.status == ResponseCode.SUCCESS - ), f"Message submission failed with status: {ResponseCode(receipt.status).name}" + assert receipt.status == ResponseCode.SUCCESS, ( + f"Message submission failed with status: {ResponseCode(receipt.status).name}" + ) info = TopicInfoQuery(topic_id=topic_id).execute(env.client) assert info.sequence_number == 1 balance = CryptoGetAccountBalanceQuery().set_account_id(account.id).execute(env.client) - assert ( - balance.hbars.to_tinybars() < Hbar(2).to_tinybars() - ), f"Expected balance of less than 2 Hbar, but got {balance.hbars.to_tinybars()}" + assert balance.hbars.to_tinybars() < Hbar(2).to_tinybars(), ( + f"Expected balance of less than 2 Hbar, but got {balance.hbars.to_tinybars()}" + ) env.client.set_operator(operator_id, operator_key) delete_topic(env.client, topic_id) @@ -266,23 +226,17 @@ def test_integration_scheduled_topic_message_submit_transaction_can_execute_with account = env.create_account(3) # Create an account with 3 Hbar balance - topic_fee = ( - CustomFixedFee().set_hbar_amount(Hbar(1)).set_fee_collector_account_id(env.operator_id) - ) + topic_fee = CustomFixedFee().set_hbar_amount(Hbar(1)).set_fee_collector_account_id(env.operator_id) - topic_id = create_topic( - client=env.client, - admin_key=env.operator_key, - custom_fees=[topic_fee] - ) + topic_id = create_topic(client=env.client, admin_key=env.operator_key, custom_fees=[topic_fee]) info = TopicInfoQuery(topic_id=topic_id).execute(env.client) assert info.sequence_number == 0 balance = CryptoGetAccountBalanceQuery().set_account_id(account.id).execute(env.client) - assert ( - balance.hbars.to_tinybars() == Hbar(3).to_tinybars() - ), f"Expected balance of 3 Hbar, but got {balance.hbars.to_tinybars()}" + assert balance.hbars.to_tinybars() == Hbar(3).to_tinybars(), ( + f"Expected balance of 3 Hbar, but got {balance.hbars.to_tinybars()}" + ) # Restore the operator to the original account env.client.set_operator(account.id, account.key) @@ -301,17 +255,17 @@ def test_integration_scheduled_topic_message_submit_transaction_can_execute_with tx.transaction_fee = Hbar(2).to_tinybars() receipt = tx.execute(env.client) - assert ( - receipt.status == ResponseCode.SUCCESS - ), f"Message submission failed with status: {ResponseCode(receipt.status).name}" + assert receipt.status == ResponseCode.SUCCESS, ( + f"Message submission failed with status: {ResponseCode(receipt.status).name}" + ) info = TopicInfoQuery(topic_id=topic_id).execute(env.client) assert info.sequence_number == 1 balance = CryptoGetAccountBalanceQuery().set_account_id(account.id).execute(env.client) - assert ( - balance.hbars.to_tinybars() < Hbar(2).to_tinybars() - ), f"Expected balance of less than 2 Hbar, but got {balance.hbars.to_tinybars()}" + assert balance.hbars.to_tinybars() < Hbar(2).to_tinybars(), ( + f"Expected balance of less than 2 Hbar, but got {balance.hbars.to_tinybars()}" + ) # Restore the operator to the original account env.client.set_operator(operator_id, operator_key) @@ -323,11 +277,7 @@ def test_integration_topic_message_submit_transaction_fails_if_required_chunk_gr """Test that a topic message fails submitting transaction when required chunk greater than max_chunks.""" submit_key = PrivateKey.generate() - topic_id = create_topic( - client=env.client, - admin_key=env.operator_key, - submit_key=submit_key - ) + topic_id = create_topic(client=env.client, admin_key=env.operator_key, submit_key=submit_key) info = TopicInfoQuery(topic_id=topic_id).execute(env.client) # Check that no message is submited @@ -335,11 +285,13 @@ def test_integration_topic_message_submit_transaction_fails_if_required_chunk_gr message_transaction = TopicMessageSubmitTransaction( topic_id=topic_id, - message="A"*(1024*4) # requires 4 chunks + message="A" * (1024 * 4), # requires 4 chunks ) message_transaction.set_max_chunks(2) message_transaction.freeze_with(env.client) - with pytest.raises(ValueError, match="Message requires 4 chunks but max_chunks=2. Increase limit with set_max_chunks()."): + with pytest.raises( + ValueError, match="Message requires 4 chunks but max_chunks=2. Increase limit with set_max_chunks()." + ): message_transaction.execute(env.client) - - delete_topic(env.client, topic_id) \ No newline at end of file + + delete_topic(env.client, topic_id) diff --git a/tests/integration/topic_update_transaction_e2e_test.py b/tests/integration/topic_update_transaction_e2e_test.py index 1aa683a82..e481f3d5f 100644 --- a/tests/integration/topic_update_transaction_e2e_test.py +++ b/tests/integration/topic_update_transaction_e2e_test.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import pytest from hiero_sdk_python.consensus.topic_create_transaction import TopicCreateTransaction @@ -13,37 +15,35 @@ @pytest.mark.integration def test_integration_topic_update_transaction_can_execute(): env = IntegrationTestEnv() - + try: - create_transaction = TopicCreateTransaction( - memo="Original memo", - admin_key=env.public_operator_key - ) - + create_transaction = TopicCreateTransaction(memo="Original memo", admin_key=env.public_operator_key) + create_transaction.freeze_with(env.client) create_receipt = create_transaction.execute(env.client) - assert create_receipt.status == ResponseCode.SUCCESS, f"Topic creation failed with status: {ResponseCode(create_receipt.status).name}" - + assert create_receipt.status == ResponseCode.SUCCESS, ( + f"Topic creation failed with status: {ResponseCode(create_receipt.status).name}" + ) + topic_id = create_receipt.topic_id - + info = TopicInfoQuery(topic_id=topic_id).execute(env.client) assert info.memo == "Original memo" assert info.sequence_number == 0 assert env.client.operator_private_key.public_key()._to_proto() == info.admin_key - update_transaction = TopicUpdateTransaction( - topic_id=topic_id, - memo="Updated memo" - ) - + update_transaction = TopicUpdateTransaction(topic_id=topic_id, memo="Updated memo") + update_transaction.freeze_with(env.client) update_receipt = update_transaction.execute(env.client) - - assert update_receipt.status == ResponseCode.SUCCESS, f"Topic update failed with status: {ResponseCode(update_receipt.status).name}" - + + assert update_receipt.status == ResponseCode.SUCCESS, ( + f"Topic update failed with status: {ResponseCode(update_receipt.status).name}" + ) + info = TopicInfoQuery(topic_id=topic_id).execute(env.client) - + assert info.memo == "Updated memo" assert info.sequence_number == 0 assert env.client.operator_private_key.public_key()._to_proto() == info.admin_key @@ -51,9 +51,11 @@ def test_integration_topic_update_transaction_can_execute(): transaction = TopicDeleteTransaction(topic_id=topic_id) transaction.freeze_with(env.client) receipt = transaction.execute(env.client) - assert receipt.status == ResponseCode.SUCCESS, f"Topic deletion failed with status: {ResponseCode(receipt.status).name}" + assert receipt.status == ResponseCode.SUCCESS, ( + f"Topic deletion failed with status: {ResponseCode(receipt.status).name}" + ) finally: - env.close() + env.close() @pytest.mark.integration @@ -61,7 +63,9 @@ def test_integration_topic_update_transaction_clear_custom_fees(): env = IntegrationTestEnv() try: - custom_fee = CustomFixedFee().set_amount_in_tinybars(1).set_fee_collector_account_id(env.client.operator_account_id) + custom_fee = ( + CustomFixedFee().set_amount_in_tinybars(1).set_fee_collector_account_id(env.client.operator_account_id) + ) create_transaction = ( TopicCreateTransaction() @@ -71,7 +75,9 @@ def test_integration_topic_update_transaction_clear_custom_fees(): ) create_receipt = create_transaction.execute(env.client) - assert create_receipt.status == ResponseCode.SUCCESS, f"Topic creation failed with status: {ResponseCode(create_receipt.status).name}" + assert create_receipt.status == ResponseCode.SUCCESS, ( + f"Topic creation failed with status: {ResponseCode(create_receipt.status).name}" + ) topic_id = create_receipt.topic_id @@ -79,18 +85,17 @@ def test_integration_topic_update_transaction_clear_custom_fees(): assert info is not None assert info.custom_fees[0] == custom_fee - update_transaction = ( - TopicUpdateTransaction(topic_id=topic_id) - .clear_custom_fees() - ) + update_transaction = TopicUpdateTransaction(topic_id=topic_id).clear_custom_fees() update_receipt = update_transaction.execute(env.client) - assert update_receipt.status == ResponseCode.SUCCESS, f"Topic update failed with status: {ResponseCode(update_receipt.status).name}" + assert update_receipt.status == ResponseCode.SUCCESS, ( + f"Topic update failed with status: {ResponseCode(update_receipt.status).name}" + ) info = TopicInfoQuery(topic_id=topic_id).execute(env.client) assert info is not None assert len(info.custom_fees) == 0 - + finally: env.close() @@ -109,21 +114,22 @@ def test_integration_topic_update_transaction_clear_fee_exempt_keys(): ) create_receipt = create_transaction.execute(env.client) - assert create_receipt.status == ResponseCode.SUCCESS, f"Topic creation failed with status: {ResponseCode(create_receipt.status).name}" + assert create_receipt.status == ResponseCode.SUCCESS, ( + f"Topic creation failed with status: {ResponseCode(create_receipt.status).name}" + ) topic_id = create_receipt.topic_id info = TopicInfoQuery(topic_id=topic_id).execute(env.client) assert info is not None assert info.fee_exempt_keys[0].to_bytes_raw() == fee_exempt_key.public_key().to_bytes_raw() - - update_transaction = ( - TopicUpdateTransaction(topic_id=topic_id) - .clear_fee_exempt_keys() - ) + + update_transaction = TopicUpdateTransaction(topic_id=topic_id).clear_fee_exempt_keys() update_receipt = update_transaction.execute(env.client) - assert update_receipt.status == ResponseCode.SUCCESS, f"Topic update failed with status: {ResponseCode(update_receipt.status).name}" + assert update_receipt.status == ResponseCode.SUCCESS, ( + f"Topic update failed with status: {ResponseCode(update_receipt.status).name}" + ) info = TopicInfoQuery(topic_id=topic_id).execute(env.client) assert info is not None diff --git a/tests/integration/transaction_e2e_test.py b/tests/integration/transaction_e2e_test.py index 65fe9b577..59fa51118 100644 --- a/tests/integration/transaction_e2e_test.py +++ b/tests/integration/transaction_e2e_test.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import pytest from hiero_sdk_python.account.account_create_transaction import AccountCreateTransaction @@ -17,16 +19,11 @@ from hiero_sdk_python.transaction.transaction_receipt import TransactionReceipt from hiero_sdk_python.transaction.transaction_record import TransactionRecord from hiero_sdk_python.transaction.transaction_response import TransactionResponse -from tests.integration.utils import env def create_transaction(): """Create a minimal valid AccountCreateTransaction for integration tests.""" - return ( - AccountCreateTransaction() - .set_key_without_alias(PrivateKey.generate()) - .set_initial_balance(1) - ) + return AccountCreateTransaction().set_key_without_alias(PrivateKey.generate()).set_initial_balance(1) @pytest.mark.integration @@ -169,6 +166,7 @@ def test_get_record_vs_query_returns_same_record(env): assert record_via_response.transaction_id == record_via_query.transaction_id assert record_via_response.transaction_hash == record_via_query.transaction_hash + @pytest.mark.integration def test_chunk_tx_returns_responses_without_wait_for_receipt(env): """Test chunk transaction return only response when execute without wait for receipt.""" @@ -178,18 +176,13 @@ def test_chunk_tx_returns_responses_without_wait_for_receipt(env): ) topic_id = topic_receipt.topic_id - message = "A" * (1024 * 14) # message with (1024 * 14) bytes ie 14 chunks + message = "A" * (1024 * 14) # message with (1024 * 14) bytes ie 14 chunks # Create a chunk transaction - message_tx = ( - TopicMessageSubmitTransaction() - .set_topic_id(topic_id) - .set_message(message) - .freeze_with(env.client) - ) + message_tx = TopicMessageSubmitTransaction().set_topic_id(topic_id).set_message(message).freeze_with(env.client) message_responses = message_tx.execute_all(env.client, wait_for_receipt=False) - + assert len(message_responses) == 14 assert isinstance(message_responses[0], TransactionResponse) assert message_responses[0].transaction is message_tx @@ -216,18 +209,13 @@ def test_chunk_tx_returns_receipts_with_wait_for_receipt(env): ) topic_id = topic_receipt.topic_id - message = "A" * (1024 * 14) # message with (1024 * 14) bytes ie 14 chunks + message = "A" * (1024 * 14) # message with (1024 * 14) bytes ie 14 chunks # Create a chunk transaction - message_tx = ( - TopicMessageSubmitTransaction() - .set_topic_id(topic_id) - .set_message(message) - .freeze_with(env.client) - ) + message_tx = TopicMessageSubmitTransaction().set_topic_id(topic_id).set_message(message).freeze_with(env.client) message_receipt = message_tx.execute_all(env.client, wait_for_receipt=True) - + assert len(message_receipt) == 14 assert isinstance(message_receipt[0], TransactionReceipt) @@ -241,14 +229,11 @@ def test_chunk_tx_returns_receipts_with_wait_for_receipt(env): TopicDeleteTransaction().set_topic_id(topic_id).execute(env.client) + @pytest.mark.integration def test_get_receipt_returns_failed_status_by_default(env): """Test receipt is returned normally on failure when validation is disabled.""" - tx = ( - AccountDeleteTransaction() - .set_transfer_account_id(env.operator_id) - .set_account_id(AccountId(0, 0, 0)) - ) + tx = AccountDeleteTransaction().set_transfer_account_id(env.operator_id).set_account_id(AccountId(0, 0, 0)) response = tx.execute(env.client, wait_for_receipt=False) assert isinstance(response, TransactionResponse) @@ -263,11 +248,7 @@ def test_get_receipt_returns_failed_status_by_default(env): @pytest.mark.integration def test_get_receipt_raises_exception_on_failure_with_validation(env): """Test error is raised for failing transactions when validation is enabled.""" - tx = ( - AccountDeleteTransaction() - .set_transfer_account_id(env.operator_id) - .set_account_id(AccountId(0, 0, 0)) - ) + tx = AccountDeleteTransaction().set_transfer_account_id(env.operator_id).set_account_id(AccountId(0, 0, 0)) response = tx.execute(env.client, wait_for_receipt=False) assert isinstance(response, TransactionResponse) @@ -275,5 +256,5 @@ def test_get_receipt_raises_exception_on_failure_with_validation(env): with pytest.raises(ReceiptStatusError) as e: response.get_receipt(env.client, validate_status=True) - + assert e.value.status == ResponseCode.INVALID_ACCOUNT_ID diff --git a/tests/integration/transaction_freeze_e2e_test.py b/tests/integration/transaction_freeze_e2e_test.py index 0522e3d6a..7c9777e82 100644 --- a/tests/integration/transaction_freeze_e2e_test.py +++ b/tests/integration/transaction_freeze_e2e_test.py @@ -1,13 +1,14 @@ +from __future__ import annotations + import pytest from hiero_sdk_python.account.account_id import AccountId from hiero_sdk_python.client.client import Client from hiero_sdk_python.consensus.topic_create_transaction import TopicCreateTransaction -from hiero_sdk_python.crypto.private_key import PrivateKey from hiero_sdk_python.response_code import ResponseCode from hiero_sdk_python.transaction.transaction import Transaction from hiero_sdk_python.transaction.transaction_id import TransactionId -from tests.integration.utils import env + @pytest.mark.integration def test_transaction_executes_successfully(env): @@ -16,7 +17,7 @@ def test_transaction_executes_successfully(env): executor_key = env.operator_key tx = TopicCreateTransaction().set_memo("Test Topic Creation") - tx.freeze_with(executor_client) + tx.freeze_with(executor_client) tx.sign(executor_key) receipt = tx.execute(executor_client) @@ -26,16 +27,17 @@ def test_transaction_executes_successfully(env): assert receipt.status == ResponseCode.SUCCESS, "Transaction must execute successfully" + @pytest.mark.integration def test_transaction_executes_successfully_with_node_account_ids(env): """Test transaction can be executed successfully when node_account_ids are provided.""" - node_account_ids = [AccountId(0,0,3), AccountId(0,0,4)] + node_account_ids = [AccountId(0, 0, 3), AccountId(0, 0, 4)] executor_client = env.client executor_key = env.operator_key tx = TopicCreateTransaction().set_memo("Test Topic Creation") tx.set_node_account_ids(node_account_ids) - tx.freeze_with(executor_client) + tx.freeze_with(executor_client) tx.sign(executor_key) # Verify that the transaction_bodys are generated for the provided node_account_ids only @@ -45,16 +47,17 @@ def test_transaction_executes_successfully_with_node_account_ids(env): receipt = tx.execute(executor_client) assert receipt.status == ResponseCode.SUCCESS, "Transaction must execute successfully" + @pytest.mark.integration def test_transaction_executes_successfully_with_single_node_account_id(env): """Test transaction can be executed successfully when single node_account_id are provided.""" - node_account_id = AccountId(0,0,3) + node_account_id = AccountId(0, 0, 3) executor_client = env.client executor_key = env.operator_key tx = TopicCreateTransaction().set_memo("Test Topic Creation") tx.set_node_account_id(node_account_id) - tx.freeze_with(executor_client) + tx.freeze_with(executor_client) tx.sign(executor_key) # Verify that the transaction_bodys are generated for the provided node_account_id only @@ -64,6 +67,7 @@ def test_transaction_executes_successfully_with_single_node_account_id(env): receipt = tx.execute(executor_client) assert receipt.status == ResponseCode.SUCCESS, "Transaction must execute successfully" + @pytest.mark.integration def test_transaction_executes_successfully_after_manual_freeze(env): """Test transaction can be manually frozen and then executed successfully.""" @@ -72,18 +76,18 @@ def test_transaction_executes_successfully_after_manual_freeze(env): tx = TopicCreateTransaction().set_memo("Test Topic Creation") tx_id = TransactionId.generate(executor_client.operator_account_id) - + # Manually set Node and ID tx.set_transaction_id(tx_id) - tx.node_account_id = AccountId.from_string("0.0.3") # Explicitly set to 0.0.3 - + tx.node_account_id = AccountId.from_string("0.0.3") # Explicitly set to 0.0.3 + # Manual Freeze (Generates body ONLY for 0.0.3) - tx.freeze() + tx.freeze() unsigned_bytes = tx.to_bytes() - + assert unsigned_bytes is not None - tx2 = Transaction.from_bytes(unsigned_bytes) + tx2 = Transaction.from_bytes(unsigned_bytes) assert tx2 is not None tx2.sign(executor_key) @@ -91,6 +95,7 @@ def test_transaction_executes_successfully_after_manual_freeze(env): assert receipt.status == ResponseCode.SUCCESS, "Transaction must execute successfully" + @pytest.mark.integration def test_transaction_with_secondary_client_can_execute_successfully(env): """Test transaction created by the secondary client and then executed successfully.""" @@ -98,51 +103,51 @@ def test_transaction_with_secondary_client_can_execute_successfully(env): executor_key = env.operator_key tx_freezer_account = env.create_account(1) - + # Secondary Client tx_freezer_client = Client(network=env.client.network) tx_freezer_client.set_operator(tx_freezer_account.id, tx_freezer_account.key) tx = TopicCreateTransaction().set_memo("Test Topic Creation") tx_id = TransactionId.generate(executor_client.operator_account_id) - + tx.set_transaction_id(tx_id) tx.freeze_with(tx_freezer_client) - + unsigned_bytes = tx.to_bytes() assert unsigned_bytes is not None - + tx2 = Transaction.from_bytes(unsigned_bytes) assert tx2 is not None - + tx2.sign(executor_key) receipt = tx2.execute(executor_client) assert receipt.status == ResponseCode.SUCCESS, "Transaction must execute successfully" + @pytest.mark.integration def test_transaction_with_secondary_client_without_operator_can_execute_successfully(env): """Test transaction created by the secondary client without operator and then executed successfully.""" executor_client = env.client executor_key = env.operator_key - + # Secondary Client with no operator account set tx_freezer_client = Client(network=env.client.network) tx = TopicCreateTransaction().set_memo("Test Topic Creation") tx_id = TransactionId.generate(executor_client.operator_account_id) - + tx.set_transaction_id(tx_id) tx.freeze_with(tx_freezer_client) - + unsigned_bytes = tx.to_bytes() assert unsigned_bytes is not None - + tx2 = Transaction.from_bytes(unsigned_bytes) assert tx2 is not None - + tx2.sign(executor_key) receipt = tx2.execute(executor_client) assert receipt.status == ResponseCode.SUCCESS, "Transaction must execute successfully" - diff --git a/tests/integration/transaction_get_receipt_query_e2e_test.py b/tests/integration/transaction_get_receipt_query_e2e_test.py index 6874b6bde..abcc56465 100644 --- a/tests/integration/transaction_get_receipt_query_e2e_test.py +++ b/tests/integration/transaction_get_receipt_query_e2e_test.py @@ -7,27 +7,28 @@ - verify children behavior with and without include_children flag - verify duplicate transactions returned with include_duplicates flag -NOTE: +Note: The contract used in these tests (StatefulContract) does NOT deterministically produce child receipts, so we only assert API correctness and stability, not children count > 0. """ +from __future__ import annotations + +import threading + +import pytest + from hiero_sdk_python.account.account_delete_transaction import AccountDeleteTransaction from hiero_sdk_python.account.account_id import AccountId from hiero_sdk_python.exceptions import ReceiptStatusError -from hiero_sdk_python.transaction.transaction_id import TransactionId -import pytest -import threading - from hiero_sdk_python.hbar import Hbar from hiero_sdk_python.query.transaction_get_receipt_query import TransactionGetReceiptQuery from hiero_sdk_python.response_code import ResponseCode +from hiero_sdk_python.transaction.transaction_id import TransactionId from hiero_sdk_python.transaction.transaction_receipt import TransactionReceipt from hiero_sdk_python.transaction.transfer_transaction import TransferTransaction -from tests.integration.utils import env - def _extract_tx_id(tx, receipt): """ @@ -49,9 +50,7 @@ def _extract_tx_id(tx, receipt): if tx_ids: return tx_ids[0] - raise AssertionError( - "Unable to extract TransactionId from transaction or receipt." - ) + raise AssertionError("Unable to extract TransactionId from transaction or receipt.") def _submit_simple_transfer(env, node_ids=None, tx_id=None): @@ -83,11 +82,7 @@ def test_get_receipt_query_children_empty_when_not_requested_e2e(env): """ tx_id = _submit_simple_transfer(env) - receipt = ( - TransactionGetReceiptQuery() - .set_transaction_id(tx_id) - .execute(env.client) - ) + receipt = TransactionGetReceiptQuery().set_transaction_id(tx_id).execute(env.client) assert receipt.status == ResponseCode.SUCCESS assert receipt.children == [] @@ -102,12 +97,7 @@ def test_get_receipt_query_children_list_when_requested_e2e(env): """ tx_id = _submit_simple_transfer(env) - receipt = ( - TransactionGetReceiptQuery() - .set_transaction_id(tx_id) - .set_include_children(True) - .execute(env.client) - ) + receipt = TransactionGetReceiptQuery().set_transaction_id(tx_id).set_include_children(True).execute(env.client) assert receipt.status == ResponseCode.SUCCESS assert isinstance(receipt.children, list) @@ -127,10 +117,10 @@ def test_get_receipt_query_children_with_contract_execute_e2e(env): CONTRACT_DEPLOY_GAS, STATEFUL_CONTRACT_BYTECODE, ) - from hiero_sdk_python.file.file_create_transaction import FileCreateTransaction from hiero_sdk_python.contract.contract_create_transaction import ContractCreateTransaction from hiero_sdk_python.contract.contract_execute_transaction import ContractExecuteTransaction from hiero_sdk_python.contract.contract_function_parameters import ContractFunctionParameters + from hiero_sdk_python.file.file_create_transaction import FileCreateTransaction # Upload contract bytecode file_receipt = ( @@ -145,9 +135,7 @@ def test_get_receipt_query_children_with_contract_execute_e2e(env): assert file_id is not None # Deploy contract - constructor_params = ContractFunctionParameters().add_bytes32( - b"Initial message from constructor" - ) + constructor_params = ContractFunctionParameters().add_bytes32(b"Initial message from constructor") contract_receipt = ( ContractCreateTransaction() .set_admin_key(env.operator_key.public_key()) @@ -178,12 +166,7 @@ def test_get_receipt_query_children_with_contract_execute_e2e(env): except AssertionError as e: pytest.skip(str(e)) - queried = ( - TransactionGetReceiptQuery() - .set_transaction_id(tx_id) - .set_include_children(True) - .execute(env.client) - ) + queried = TransactionGetReceiptQuery().set_transaction_id(tx_id).set_include_children(True).execute(env.client) assert queried.status == ResponseCode.SUCCESS @@ -193,8 +176,11 @@ def test_get_receipt_query_children_with_contract_execute_e2e(env): assert isinstance(queried.children, list) + @pytest.mark.integration -@pytest.mark.xfail(reason="Flaky test due to network conditions causing no duplicates to be created. Python Virtual Threads compete too quickly.") +@pytest.mark.xfail( + reason="Flaky test due to network conditions causing no duplicates to be created. Python Virtual Threads compete too quickly." +) def test_get_receipt_query_include_duplicates_execute_e2e(env): """ E2E: @@ -213,31 +199,23 @@ def test_get_receipt_query_include_duplicates_execute_e2e(env): tx2.start() tx1.join() tx2.join() - queried = ( - TransactionGetReceiptQuery() - .set_transaction_id(tx_id) - .set_include_duplicates(True) - .execute(env.client) - ) + queried = TransactionGetReceiptQuery().set_transaction_id(tx_id).set_include_duplicates(True).execute(env.client) assert queried.status == ResponseCode.SUCCESS assert isinstance(queried.duplicates, list) assert len(queried.duplicates) == 1 + @pytest.mark.integration def test_get_receipt_returns_failed_status_receipt_if_validate_status_false(env): """Test receipt is returned despite failure when validate_status is False.""" - tx = ( - AccountDeleteTransaction() - .set_transfer_account_id(env.operator_id) - .set_account_id(AccountId(0, 0, 0)) - ) + tx = AccountDeleteTransaction().set_transfer_account_id(env.operator_id).set_account_id(AccountId(0, 0, 0)) response = tx.execute(env.client, wait_for_receipt=False) query = ( TransactionGetReceiptQuery() .set_transaction_id(response.transaction_id) - .set_validate_status(False) # Default value + .set_validate_status(False) # Default value ) receipt = query.execute(env.client) @@ -249,20 +227,12 @@ def test_get_receipt_returns_failed_status_receipt_if_validate_status_false(env) @pytest.mark.integration def test_get_receipt_throws_status_error_when_validation_enabled(env): """Test error is raised for failures when validate_status is True.""" - tx = ( - AccountDeleteTransaction() - .set_transfer_account_id(env.operator_id) - .set_account_id(AccountId(1, 0, 0)) - ) + tx = AccountDeleteTransaction().set_transfer_account_id(env.operator_id).set_account_id(AccountId(1, 0, 0)) response = tx.execute(env.client, wait_for_receipt=False) - query = ( - TransactionGetReceiptQuery() - .set_transaction_id(response.transaction_id) - .set_validate_status(True) - ) + query = TransactionGetReceiptQuery().set_transaction_id(response.transaction_id).set_validate_status(True) with pytest.raises(ReceiptStatusError) as e: query.execute(env.client) - + assert e.value.status == ResponseCode.INVALID_ACCOUNT_ID diff --git a/tests/integration/transaction_record_query_e2e_test.py b/tests/integration/transaction_record_query_e2e_test.py index a0b24f291..f5772487c 100644 --- a/tests/integration/transaction_record_query_e2e_test.py +++ b/tests/integration/transaction_record_query_e2e_test.py @@ -1,35 +1,32 @@ """Integration tests for TransactionRecordQuery end-to-end functionality.""" +from __future__ import annotations + import os import pytest -from hiero_sdk_python import AccountId, Timestamp, TransactionRecord +from hiero_sdk_python import AccountId, TransactionRecord from hiero_sdk_python.account.account_create_transaction import AccountCreateTransaction from hiero_sdk_python.crypto.private_key import PrivateKey from hiero_sdk_python.hbar import Hbar from hiero_sdk_python.query.transaction_record_query import TransactionRecordQuery from hiero_sdk_python.response_code import ResponseCode from hiero_sdk_python.schedule.schedule_id import ScheduleId +from hiero_sdk_python.timestamp import Timestamp from hiero_sdk_python.tokens.nft_id import NftId from hiero_sdk_python.tokens.token_associate_transaction import ( TokenAssociateTransaction, ) from hiero_sdk_python.tokens.token_mint_transaction import TokenMintTransaction from hiero_sdk_python.transaction.transfer_transaction import TransferTransaction -from tests.integration.utils import ( - IntegrationTestEnv, - create_fungible_token, - create_nft_token, -) +from tests.integration.utils import IntegrationTestEnv, create_fungible_token, create_nft_token def _submit_alias_auto_create_transfer(env: IntegrationTestEnv): """Submit a transfer to an EVM alias to trigger child auto-account creation.""" alias_key = PrivateKey.generate_ecdsa() - alias_account_id = AccountId.from_evm_address( - alias_key.public_key().to_evm_address(), 0, 0 - ) + alias_account_id = AccountId.from_evm_address(alias_key.public_key().to_evm_address(), 0, 0) transaction = ( TransferTransaction() @@ -65,16 +62,10 @@ def test_transaction_record_query_can_execute(): record = TransactionRecordQuery(receipt.transaction_id).execute(env.client) # Verify transaction details - assert ( - record.transaction_id == receipt.transaction_id - ), "Transaction ID should match the queried record" + assert record.transaction_id == receipt.transaction_id, "Transaction ID should match the queried record" assert record.transaction_fee > 0, "Transaction fee should be greater than zero" - assert ( - record.transaction_memo == "" - ), "Transaction memo should be empty by default" - assert ( - record.transaction_hash is not None - ), "Transaction hash should not be None" + assert record.transaction_memo == "", "Transaction memo should be empty by default" + assert record.transaction_hash is not None, "Transaction hash should not be None" finally: env.close() @@ -128,15 +119,10 @@ def test_transaction_record_query_can_execute_nft_transfer(): token_id = create_nft_token(env) # Mint NFTs - receipt = ( - TokenMintTransaction() - .set_token_id(token_id) - .set_metadata([b"NFT 1", b"NFT 2"]) - .execute(env.client) + receipt = TokenMintTransaction().set_token_id(token_id).set_metadata([b"NFT 1", b"NFT 2"]).execute(env.client) + assert receipt.status == ResponseCode.SUCCESS, ( + f"NFT mint failed with status: {ResponseCode(receipt.status).name}" ) - assert ( - receipt.status == ResponseCode.SUCCESS - ), f"NFT mint failed with status: {ResponseCode(receipt.status).name}" serial_numbers = receipt.serial_numbers assert len(serial_numbers) == 2, "Expected two NFTs to be minted" @@ -151,55 +137,37 @@ def test_transaction_record_query_can_execute_nft_transfer(): .execute(env.client) ) - assert ( - receipt.status == ResponseCode.SUCCESS - ), f"Token association failed with status: {ResponseCode(receipt.status).name}" + assert receipt.status == ResponseCode.SUCCESS, ( + f"Token association failed with status: {ResponseCode(receipt.status).name}" + ) # Transfer NFTs receipt = ( TransferTransaction() - .add_nft_transfer( - NftId(token_id, serial_numbers[0]), env.operator_id, new_account.id - ) - .add_nft_transfer( - NftId(token_id, serial_numbers[1]), env.operator_id, new_account.id - ) + .add_nft_transfer(NftId(token_id, serial_numbers[0]), env.operator_id, new_account.id) + .add_nft_transfer(NftId(token_id, serial_numbers[1]), env.operator_id, new_account.id) .execute(env.client) ) - assert ( - receipt.status == ResponseCode.SUCCESS - ), f"NFT transfer failed with status: {ResponseCode(receipt.status).name}" + assert receipt.status == ResponseCode.SUCCESS, ( + f"NFT transfer failed with status: {ResponseCode(receipt.status).name}" + ) # Query the record record = TransactionRecordQuery(receipt.transaction_id).execute(env.client) # Verify NFT transfers assert len(record.nft_transfers) == 1 - assert ( - len(record.nft_transfers[token_id]) == 2 - ), "Expected two NFT transfers in the record" + assert len(record.nft_transfers[token_id]) == 2, "Expected two NFT transfers in the record" for i, transfer in enumerate(record.nft_transfers[token_id]): - assert ( - transfer.sender_id == env.operator_id - ), "Sender should be the operator account" - assert ( - transfer.receiver_id == new_account.id - ), "Receiver should be the new account" - assert ( - transfer.serial_number == serial_numbers[i] - ), "Serial number should match the minted NFT" + assert transfer.sender_id == env.operator_id, "Sender should be the operator account" + assert transfer.receiver_id == new_account.id, "Receiver should be the new account" + assert transfer.serial_number == serial_numbers[i], "Serial number should match the minted NFT" # Verify transaction details - assert ( - record.transaction_id == receipt.transaction_id - ), "Transaction ID should match the queried record" + assert record.transaction_id == receipt.transaction_id, "Transaction ID should match the queried record" assert record.transaction_fee > 0, "Transaction fee should be greater than zero" - assert ( - record.transaction_memo == "" - ), "Transaction memo should be empty by default" - assert ( - record.transaction_hash is not None - ), "Transaction hash should not be None" + assert record.transaction_memo == "", "Transaction memo should be empty by default" + assert record.transaction_hash is not None, "Transaction hash should not be None" finally: env.close() @@ -223,9 +191,9 @@ def test_transaction_record_query_can_execute_fungible_transfer(): .sign(new_account.key) .execute(env.client) ) - assert ( - receipt.status == ResponseCode.SUCCESS - ), f"Token association failed with status: {ResponseCode(receipt.status).name}" + assert receipt.status == ResponseCode.SUCCESS, ( + f"Token association failed with status: {ResponseCode(receipt.status).name}" + ) # Transfer tokens transfer_amount = 1000 @@ -235,33 +203,25 @@ def test_transaction_record_query_can_execute_fungible_transfer(): .add_token_transfer(token_id, new_account.id, transfer_amount) .execute(env.client) ) - assert ( - receipt.status == ResponseCode.SUCCESS - ), f"Token transfer failed with status: {ResponseCode(receipt.status).name}" + assert receipt.status == ResponseCode.SUCCESS, ( + f"Token transfer failed with status: {ResponseCode(receipt.status).name}" + ) # Query the record record = TransactionRecordQuery(receipt.transaction_id).execute(env.client) # Verify token transfers assert len(record.token_transfers) == 1 - assert ( - record.token_transfers[token_id][env.operator_id] == -transfer_amount - ), "Operator should have sent tokens" - assert ( - record.token_transfers[token_id][new_account.id] == transfer_amount - ), "New account should have received tokens" + assert record.token_transfers[token_id][env.operator_id] == -transfer_amount, "Operator should have sent tokens" + assert record.token_transfers[token_id][new_account.id] == transfer_amount, ( + "New account should have received tokens" + ) # Verify transaction details - assert ( - record.transaction_id == receipt.transaction_id - ), "Transaction ID should match the queried record" + assert record.transaction_id == receipt.transaction_id, "Transaction ID should match the queried record" assert record.transaction_fee > 0, "Transaction fee should be greater than zero" - assert ( - record.transaction_memo == "" - ), "Transaction memo should be empty by default" - assert ( - record.transaction_hash is not None - ), "Transaction hash should not be None" + assert record.transaction_memo == "", "Transaction memo should be empty by default" + assert record.transaction_hash is not None, "Transaction hash should not be None" finally: env.close() @@ -316,18 +276,13 @@ def test_query_with_include_duplicates(): # import time; time.sleep(1) # if needed on slow networks # Step 3: Query with include_duplicates=True - record = ( - TransactionRecordQuery() - .set_transaction_id(tx_id) - .set_include_duplicates(True) - .execute(env.client) - ) + record = TransactionRecordQuery().set_transaction_id(tx_id).set_include_duplicates(True).execute(env.client) # Core assertions for the feature assert record.transaction_id == tx_id - assert ( - len(record.duplicates) >= 2 - ), f"Expected at least 2 duplicates after 3 submissions, got {len(record.duplicates)}" + assert len(record.duplicates) >= 2, ( + f"Expected at least 2 duplicates after 3 submissions, got {len(record.duplicates)}" + ) # Verify duplicates are TransactionRecord instances if record.duplicates: @@ -340,11 +295,12 @@ def test_query_with_include_duplicates(): # print(f"Found {len(record.duplicates)} duplicates") # for debug finally: env.close() - + + @pytest.mark.integration def test_transaction_record_new_fields(): """Simple test to verify some fields in TransactionRecord. - + consensus_timestamp, automatic_token_associations, paid_staking_rewards, evm_address, alias, ethereum_hash, parent_consensus_timestamp, assessed_custom_fees, schedule_ref, and PRNG oneof handling. @@ -362,15 +318,14 @@ def test_transaction_record_new_fields(): ) assert receipt.status == ResponseCode.SUCCESS - record: TransactionRecord = TransactionRecordQuery( - receipt.transaction_id - ).execute(env.client) + record: TransactionRecord = TransactionRecordQuery(receipt.transaction_id).execute(env.client) _assert_basic_record_fields(record, receipt) _assert_fields(record) finally: env.close() + def _assert_basic_record_fields(record: TransactionRecord, receipt) -> None: """Assert basic fields that existed before this PR.""" assert record.transaction_id == receipt.transaction_id @@ -378,6 +333,7 @@ def _assert_basic_record_fields(record: TransactionRecord, receipt) -> None: assert record.consensus_timestamp is not None assert record.transaction_hash is not None and len(record.transaction_hash) > 0 + def _assert_fields(record: TransactionRecord) -> None: """Assert all newly exposed fields from this PR.""" _assert_list_fields(record) @@ -386,38 +342,41 @@ def _assert_fields(record: TransactionRecord) -> None: _assert_prng_fields(record) _assert_token_associations(record) + def _assert_list_fields(record: TransactionRecord) -> None: """Assert list fields are properly initialized.""" assert isinstance(record.automatic_token_associations, list) assert isinstance(record.paid_staking_rewards, list) assert isinstance(record.assessed_custom_fees, list) + def _assert_optional_bytes_fields(record: TransactionRecord) -> None: """Assert optional bytes fields.""" assert record.evm_address is None or isinstance(record.evm_address, bytes) assert record.alias is None or isinstance(record.alias, bytes) assert record.ethereum_hash is None or isinstance(record.ethereum_hash, bytes) + def _assert_timestamp_and_schedule_fields(record: TransactionRecord) -> None: """Assert timestamp and schedule related fields.""" - assert record.parent_consensus_timestamp is None or isinstance( - record.parent_consensus_timestamp, Timestamp - ) + assert record.parent_consensus_timestamp is None or isinstance(record.parent_consensus_timestamp, Timestamp) assert record.schedule_ref is None or isinstance(record.schedule_ref, ScheduleId) + def _assert_prng_fields(record: TransactionRecord) -> None: """Assert PRNG oneof handling.""" - assert not (record.prng_number is not None and record.prng_bytes is not None), \ + assert not (record.prng_number is not None and record.prng_bytes is not None), ( "prng_number and prng_bytes are mutually exclusive" + ) if record.prng_number is not None: assert isinstance(record.prng_number, int) if record.prng_bytes is not None: assert isinstance(record.prng_bytes, bytes) + def _assert_token_associations(record: TransactionRecord) -> None: """Assert TokenAssociation model fields.""" for assoc in record.automatic_token_associations: assert hasattr(assoc, "token_id") assert hasattr(assoc, "account_id") - \ No newline at end of file diff --git a/tests/integration/transfer_transaction_e2e_test.py b/tests/integration/transfer_transaction_e2e_test.py index 41272e3c2..02f9e9ead 100644 --- a/tests/integration/transfer_transaction_e2e_test.py +++ b/tests/integration/transfer_transaction_e2e_test.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import pytest from hiero_sdk_python.account.account_allowance_approve_transaction import ( @@ -37,9 +39,9 @@ def test_integration_transfer_transaction_can_transfer_hbar(): receipt = account_transaction.execute(env.client) - assert ( - receipt.status == ResponseCode.SUCCESS - ), f"Account creation failed with status: {ResponseCode(receipt.status).name}" + assert receipt.status == ResponseCode.SUCCESS, ( + f"Account creation failed with status: {ResponseCode(receipt.status).name}" + ) account_id = receipt.account_id assert account_id is not None @@ -50,17 +52,17 @@ def test_integration_transfer_transaction_can_transfer_hbar(): receipt = transfer_transaction.execute(env.client) - assert ( - receipt.status == ResponseCode.SUCCESS - ), f"Transfer failed with status: {ResponseCode(receipt.status).name}" + assert receipt.status == ResponseCode.SUCCESS, ( + f"Transfer failed with status: {ResponseCode(receipt.status).name}" + ) query_transaction = CryptoGetAccountBalanceQuery(account_id) balance = query_transaction.execute(env.client) expected_balance_tinybars = Hbar(1).to_tinybars() + 1 - assert ( - balance and balance.hbars.to_tinybars() == expected_balance_tinybars - ), f"Expected balance: {expected_balance_tinybars}, actual balance: {balance.hbars.to_tinybars()}" + assert balance and balance.hbars.to_tinybars() == expected_balance_tinybars, ( + f"Expected balance: {expected_balance_tinybars}, actual balance: {balance.hbars.to_tinybars()}" + ) finally: env.close() @@ -81,9 +83,9 @@ def test_integration_token_transfer_transaction_can_transfer_token(): receipt = account_transaction.execute(env.client) - assert ( - receipt.status == ResponseCode.SUCCESS - ), f"Account creation failed with status: {ResponseCode(receipt.status).name}" + assert receipt.status == ResponseCode.SUCCESS, ( + f"Account creation failed with status: {ResponseCode(receipt.status).name}" + ) account_id = receipt.account_id assert account_id is not None @@ -91,17 +93,15 @@ def test_integration_token_transfer_transaction_can_transfer_token(): token_id = create_fungible_token(env) assert token_id is not None - associate_transaction = TokenAssociateTransaction( - account_id=account_id, token_ids=[token_id] - ) + associate_transaction = TokenAssociateTransaction(account_id=account_id, token_ids=[token_id]) associate_transaction.freeze_with(env.client) associate_transaction.sign(new_account_private_key) receipt = associate_transaction.execute(env.client) - assert ( - receipt.status == ResponseCode.SUCCESS - ), f"Token association failed with status: {ResponseCode(receipt.status).name}" + assert receipt.status == ResponseCode.SUCCESS, ( + f"Token association failed with status: {ResponseCode(receipt.status).name}" + ) transfer_transaction = TransferTransaction() transfer_transaction.add_token_transfer(token_id, env.operator_id, -1) @@ -109,9 +109,9 @@ def test_integration_token_transfer_transaction_can_transfer_token(): receipt = transfer_transaction.execute(env.client) - assert ( - receipt.status == ResponseCode.SUCCESS - ), f"Token transfer failed with status: {ResponseCode(receipt.status).name}" + assert receipt.status == ResponseCode.SUCCESS, ( + f"Token transfer failed with status: {ResponseCode(receipt.status).name}" + ) query_transaction = CryptoGetAccountBalanceQuery(account_id) balance = query_transaction.execute(env.client) @@ -137,9 +137,9 @@ def test_integration_token_transfer_transaction_can_transfer_nft(): receipt = account_transaction.execute(env.client) - assert ( - receipt.status == ResponseCode.SUCCESS - ), f"Account creation failed with status: {ResponseCode(receipt.status).name}" + assert receipt.status == ResponseCode.SUCCESS, ( + f"Account creation failed with status: {ResponseCode(receipt.status).name}" + ) account_id = receipt.account_id assert account_id is not None @@ -151,42 +151,39 @@ def test_integration_token_transfer_transaction_can_transfer_nft(): receipt = mint_transaction.execute(env.client) - assert ( - receipt.status == ResponseCode.SUCCESS - ), f"NFT mint failed with status: {ResponseCode(receipt.status).name}" + assert receipt.status == ResponseCode.SUCCESS, ( + f"NFT mint failed with status: {ResponseCode(receipt.status).name}" + ) serial_number = receipt.serial_numbers[0] nft_id = NftId(token_id, serial_number) - associate_transaction = TokenAssociateTransaction( - account_id=account_id, token_ids=[token_id] - ) + associate_transaction = TokenAssociateTransaction(account_id=account_id, token_ids=[token_id]) associate_transaction.freeze_with(env.client) associate_transaction.sign(new_account_private_key) receipt = associate_transaction.execute(env.client) - assert ( - receipt.status == ResponseCode.SUCCESS - ), f"NFT association failed with status: {ResponseCode(receipt.status).name}" + assert receipt.status == ResponseCode.SUCCESS, ( + f"NFT association failed with status: {ResponseCode(receipt.status).name}" + ) transfer_transaction = TransferTransaction() transfer_transaction.add_nft_transfer(nft_id, env.operator_id, account_id) receipt = transfer_transaction.execute(env.client) - assert ( - receipt.status == ResponseCode.SUCCESS - ), f"NFT transfer failed with status: {ResponseCode(receipt.status).name}" + assert receipt.status == ResponseCode.SUCCESS, ( + f"NFT transfer failed with status: {ResponseCode(receipt.status).name}" + ) query_transaction = CryptoGetAccountBalanceQuery(account_id) balance = query_transaction.execute(env.client) assert balance is not None, "Balance query returned None" assert balance.token_balances == {token_id: serial_number}, ( - f"Expected token_balances {{{token_id}: {serial_number}}}, " - f"got {balance.token_balances}" + f"Expected token_balances {{{token_id}: {serial_number}}}, got {balance.token_balances}" ) finally: env.close() @@ -201,9 +198,9 @@ def test_integration_transfer_transaction_transfer_hbar_nothing_set(): receipt = transfer_transaction.execute(env.client) - assert ( - receipt.status == ResponseCode.SUCCESS - ), f"Transfer failed with status: {ResponseCode(receipt.status).name}" + assert receipt.status == ResponseCode.SUCCESS, ( + f"Transfer failed with status: {ResponseCode(receipt.status).name}" + ) finally: env.close() @@ -224,9 +221,9 @@ def test_integration_transfer_transaction_transfer_wrong_hbar_amount(): receipt = account_transaction.execute(env.client) - assert ( - receipt.status == ResponseCode.SUCCESS - ), f"Account creation failed with status: {ResponseCode(receipt.status).name}" + assert receipt.status == ResponseCode.SUCCESS, ( + f"Account creation failed with status: {ResponseCode(receipt.status).name}" + ) account_id = receipt.account_id assert account_id is not None @@ -235,9 +232,7 @@ def test_integration_transfer_transaction_transfer_wrong_hbar_amount(): transfer_transaction.add_hbar_transfer(env.operator_id, -1) transfer_transaction.add_hbar_transfer(account_id, 2) - with pytest.raises( - PrecheckError, match=f"Transaction failed precheck with status: INVALID_ACCOUNT_AMOUNTS" - ): + with pytest.raises(PrecheckError, match="Transaction failed precheck with status: INVALID_ACCOUNT_AMOUNTS"): transfer_transaction.execute(env.client) finally: env.close() @@ -259,9 +254,9 @@ def test_integration_transfer_transaction_transfer_hbar_fail_not_enough_balance( receipt = account_transaction.execute(env.client) - assert ( - receipt.status == ResponseCode.SUCCESS - ), f"Account creation failed with status: {ResponseCode(receipt.status).name}" + assert receipt.status == ResponseCode.SUCCESS, ( + f"Account creation failed with status: {ResponseCode(receipt.status).name}" + ) account_1_id = receipt.account_id assert account_1_id is not None @@ -274,9 +269,9 @@ def test_integration_transfer_transaction_transfer_hbar_fail_not_enough_balance( transfer_transaction.sign(new_account_private_key) receipt = transfer_transaction.execute(env.client) - assert ( - receipt.status == ResponseCode.INSUFFICIENT_ACCOUNT_BALANCE - ), f"Transfer should have failed with INSUFFICIENT_ACCOUNT_BALANCE status but got: {ResponseCode(receipt.status).name}" + assert receipt.status == ResponseCode.INSUFFICIENT_ACCOUNT_BALANCE, ( + f"Transfer should have failed with INSUFFICIENT_ACCOUNT_BALANCE status but got: {ResponseCode(receipt.status).name}" + ) finally: env.close() @@ -297,9 +292,9 @@ def test_integration_token_transfer_transaction_fail_not_enough_balance(): receipt = account_transaction.execute(env.client) - assert ( - receipt.status == ResponseCode.SUCCESS - ), f"Account creation failed with status: {ResponseCode(receipt.status).name}" + assert receipt.status == ResponseCode.SUCCESS, ( + f"Account creation failed with status: {ResponseCode(receipt.status).name}" + ) account_id = receipt.account_id assert account_id is not None @@ -307,17 +302,15 @@ def test_integration_token_transfer_transaction_fail_not_enough_balance(): token_id = create_fungible_token(env) assert token_id is not None - associate_transaction = TokenAssociateTransaction( - account_id=account_id, token_ids=[token_id] - ) + associate_transaction = TokenAssociateTransaction(account_id=account_id, token_ids=[token_id]) associate_transaction.freeze_with(env.client) associate_transaction.sign(new_account_private_key) receipt = associate_transaction.execute(env.client) - assert ( - receipt.status == ResponseCode.SUCCESS - ), f"Token association failed with status: {ResponseCode(receipt.status).name}" + assert receipt.status == ResponseCode.SUCCESS, ( + f"Token association failed with status: {ResponseCode(receipt.status).name}" + ) transfer_transaction = TransferTransaction() transfer_transaction.add_token_transfer(token_id, env.operator_id, -100000) @@ -325,9 +318,9 @@ def test_integration_token_transfer_transaction_fail_not_enough_balance(): receipt = transfer_transaction.execute(env.client) - assert ( - receipt.status == ResponseCode.INSUFFICIENT_TOKEN_BALANCE - ), f"Token transfer should have failed with INSUFFICIENT_TOKEN_BALANCE status but got: {ResponseCode(receipt.status).name}" + assert receipt.status == ResponseCode.INSUFFICIENT_TOKEN_BALANCE, ( + f"Token transfer should have failed with INSUFFICIENT_TOKEN_BALANCE status but got: {ResponseCode(receipt.status).name}" + ) finally: env.close() @@ -348,9 +341,9 @@ def test_integration_token_transfer_transaction_fail_not_your_nft(): receipt = account_transaction.execute(env.client) - assert ( - receipt.status == ResponseCode.SUCCESS - ), f"Account creation failed with status: {ResponseCode(receipt.status).name}" + assert receipt.status == ResponseCode.SUCCESS, ( + f"Account creation failed with status: {ResponseCode(receipt.status).name}" + ) account_id = receipt.account_id assert account_id is not None @@ -362,25 +355,23 @@ def test_integration_token_transfer_transaction_fail_not_your_nft(): receipt = mint_transaction.execute(env.client) - assert ( - receipt.status == ResponseCode.SUCCESS - ), f"NFT mint failed with status: {ResponseCode(receipt.status).name}" + assert receipt.status == ResponseCode.SUCCESS, ( + f"NFT mint failed with status: {ResponseCode(receipt.status).name}" + ) serial_number = receipt.serial_numbers[0] nft_id = NftId(token_id, serial_number) - associate_transaction = TokenAssociateTransaction( - account_id=account_id, token_ids=[token_id] - ) + associate_transaction = TokenAssociateTransaction(account_id=account_id, token_ids=[token_id]) associate_transaction.freeze_with(env.client) associate_transaction.sign(new_account_private_key) receipt = associate_transaction.execute(env.client) - assert ( - receipt.status == ResponseCode.SUCCESS - ), f"NFT association failed with status: {ResponseCode(receipt.status).name}" + assert receipt.status == ResponseCode.SUCCESS, ( + f"NFT association failed with status: {ResponseCode(receipt.status).name}" + ) transfer_transaction = TransferTransaction() transfer_transaction.add_nft_transfer(nft_id, account_id, env.operator_id) @@ -389,9 +380,9 @@ def test_integration_token_transfer_transaction_fail_not_your_nft(): transfer_transaction.sign(new_account_private_key) receipt = transfer_transaction.execute(env.client) - assert ( - receipt.status == ResponseCode.SENDER_DOES_NOT_OWN_NFT_SERIAL_NO - ), f"NFT transfer should have failed with SENDER_DOES_NOT_OWN_NFT_SERIAL_NO status but got: {ResponseCode(receipt.status).name}" + assert receipt.status == ResponseCode.SENDER_DOES_NOT_OWN_NFT_SERIAL_NO, ( + f"NFT transfer should have failed with SENDER_DOES_NOT_OWN_NFT_SERIAL_NO status but got: {ResponseCode(receipt.status).name}" + ) finally: env.close() @@ -418,9 +409,9 @@ def test_integration_transfer_transaction_approved_token_transfer(): .execute(env.client) ) - assert ( - receipt.status == ResponseCode.SUCCESS - ), f"Token association failed with status: {ResponseCode(receipt.status).name}" + assert receipt.status == ResponseCode.SUCCESS, ( + f"Token association failed with status: {ResponseCode(receipt.status).name}" + ) # Test approved token transfer with decimals receipt = ( @@ -430,9 +421,9 @@ def test_integration_transfer_transaction_approved_token_transfer(): ) env.client.set_operator(account.id, account.key) - assert ( - receipt.status == ResponseCode.SUCCESS - ), f"Token allowance approval failed with status: {ResponseCode(receipt.status).name}" + assert receipt.status == ResponseCode.SUCCESS, ( + f"Token allowance approval failed with status: {ResponseCode(receipt.status).name}" + ) transfer_receipt = ( TransferTransaction() @@ -444,9 +435,9 @@ def test_integration_transfer_transaction_approved_token_transfer(): .sign(account.key) .execute(env.client) ) - assert ( - transfer_receipt.status == ResponseCode.SUCCESS - ), f"Transfer failed with status: {ResponseCode(transfer_receipt.status).name}" + assert transfer_receipt.status == ResponseCode.SUCCESS, ( + f"Transfer failed with status: {ResponseCode(transfer_receipt.status).name}" + ) finally: env.close() @@ -469,9 +460,9 @@ def test_integration_transfer_transaction_approved_nft_transfer(): receipt = account_transaction.execute(env.client) - assert ( - receipt.status == ResponseCode.SUCCESS - ), f"Account creation failed with status: {ResponseCode(receipt.status).name}" + assert receipt.status == ResponseCode.SUCCESS, ( + f"Account creation failed with status: {ResponseCode(receipt.status).name}" + ) account_id = receipt.account_id assert account_id is not None @@ -483,25 +474,23 @@ def test_integration_transfer_transaction_approved_nft_transfer(): receipt = mint_transaction.execute(env.client) - assert ( - receipt.status == ResponseCode.SUCCESS - ), f"NFT mint failed with status: {ResponseCode(receipt.status).name}" + assert receipt.status == ResponseCode.SUCCESS, ( + f"NFT mint failed with status: {ResponseCode(receipt.status).name}" + ) serial_number = receipt.serial_numbers[0] nft_id = NftId(token_id, serial_number) - associate_transaction = TokenAssociateTransaction( - account_id=account_id, token_ids=[token_id] - ) + associate_transaction = TokenAssociateTransaction(account_id=account_id, token_ids=[token_id]) associate_transaction.freeze_with(env.client) associate_transaction.sign(new_account_private_key) receipt = associate_transaction.execute(env.client) - assert ( - receipt.status == ResponseCode.SUCCESS - ), f"NFT association failed with status: {ResponseCode(receipt.status).name}" + assert receipt.status == ResponseCode.SUCCESS, ( + f"NFT association failed with status: {ResponseCode(receipt.status).name}" + ) allowance_receipt = ( AccountAllowanceApproveTransaction() @@ -509,9 +498,9 @@ def test_integration_transfer_transaction_approved_nft_transfer(): .execute(env.client) ) - assert ( - allowance_receipt.status == ResponseCode.SUCCESS - ), f"Allowance approval failed with status: {ResponseCode(allowance_receipt.status).name}" + assert allowance_receipt.status == ResponseCode.SUCCESS, ( + f"Allowance approval failed with status: {ResponseCode(allowance_receipt.status).name}" + ) env.client.set_operator(account_id, new_account_private_key) @@ -523,17 +512,16 @@ def test_integration_transfer_transaction_approved_nft_transfer(): receipt = transfer_transaction.execute(env.client) - assert ( - receipt.status == ResponseCode.SUCCESS - ), f"NFT transfer failed with status: {ResponseCode(receipt.status).name}" + assert receipt.status == ResponseCode.SUCCESS, ( + f"NFT transfer failed with status: {ResponseCode(receipt.status).name}" + ) query_transaction = CryptoGetAccountBalanceQuery(account_id) balance = query_transaction.execute(env.client) assert balance is not None, "Balance query returned None" assert balance.token_balances == {token_id: serial_number}, ( - f"Expected token_balances {{{token_id}: {serial_number}}}, " - f"got {balance.token_balances}" + f"Expected token_balances {{{token_id}: {serial_number}}}, got {balance.token_balances}" ) finally: env.close() diff --git a/tests/integration/utils.py b/tests/integration/utils.py index 87c1ff573..540bc287e 100644 --- a/tests/integration/utils.py +++ b/tests/integration/utils.py @@ -1,42 +1,39 @@ +from __future__ import annotations + import os import time -from pytest import fixture -from dotenv import load_dotenv +from collections.abc import Callable from dataclasses import dataclass -from typing import Callable, Optional, TypeVar +from typing import TypeVar + +from dotenv import load_dotenv + +from hiero_sdk_python.account.account_create_transaction import AccountCreateTransaction from hiero_sdk_python.account.account_id import AccountId from hiero_sdk_python.client.client import Client from hiero_sdk_python.client.network import Network from hiero_sdk_python.crypto.private_key import PrivateKey -from hiero_sdk_python.tokens.token_type import TokenType +from hiero_sdk_python.hbar import Hbar from hiero_sdk_python.logger.log_level import LogLevel from hiero_sdk_python.response_code import ResponseCode from hiero_sdk_python.tokens.supply_type import SupplyType +from hiero_sdk_python.tokens.token_associate_transaction import ( + TokenAssociateTransaction, +) from hiero_sdk_python.tokens.token_create_transaction import ( TokenCreateTransaction, TokenKeys, TokenParams, ) -from hiero_sdk_python.tokens.token_associate_transaction import ( - TokenAssociateTransaction, -) -from hiero_sdk_python.account.account_create_transaction import AccountCreateTransaction +from hiero_sdk_python.tokens.token_type import TokenType from hiero_sdk_python.transaction.transfer_transaction import TransferTransaction -from hiero_sdk_python.hbar import Hbar + T = TypeVar("T") load_dotenv(override=True) -@fixture -def env(): - """Integration test environment with client/operator set up.""" - e = IntegrationTestEnv() - yield e - e.close() - - @dataclass class Account: id: AccountId @@ -44,7 +41,6 @@ class Account: class IntegrationTestEnv: - def __init__(self) -> None: network_name = os.getenv("NETWORK", "solo").lower() @@ -53,8 +49,8 @@ def __init__(self) -> None: network = Network(network=network_name) self.client = Client(network) - self.operator_id: Optional[AccountId] = None - self.operator_key: Optional[PrivateKey] = None + self.operator_id: AccountId | None = None + self.operator_key: PrivateKey | None = None operator_id = os.getenv("OPERATOR_ID") operator_key = os.getenv("OPERATOR_KEY") if operator_id and operator_key: @@ -73,21 +69,13 @@ def close(self): def create_account(self, initial_hbar: float = 1.0) -> Account: """Create a new account funded with `initial_hbar` HBAR, defaulting to 1.""" key = PrivateKey.generate() - tx = ( - AccountCreateTransaction() - .set_key_without_alias(key.public_key()) - .set_initial_balance(Hbar(initial_hbar)) - ) + tx = AccountCreateTransaction().set_key_without_alias(key.public_key()).set_initial_balance(Hbar(initial_hbar)) receipt = tx.execute(self.client) if receipt.status != ResponseCode.SUCCESS: - raise AssertionError( - f"Account creation failed: {ResponseCode(receipt.status).name}" - ) + raise AssertionError(f"Account creation failed: {ResponseCode(receipt.status).name}") return Account(id=receipt.account_id, key=key) - def associate_and_transfer( - self, receiver: AccountId, receiver_key: PrivateKey, token_id, amount: int - ): + def associate_and_transfer(self, receiver: AccountId, receiver_key: PrivateKey, token_id, amount: int): """ Associate the token with `receiver`, then transfer `amount` of the token from the operator to that receiver. @@ -101,9 +89,7 @@ def associate_and_transfer( .execute(self.client) ) if assoc_receipt.status != ResponseCode.SUCCESS: - raise AssertionError( - f"Association failed: {ResponseCode(assoc_receipt.status).name}" - ) + raise AssertionError(f"Association failed: {ResponseCode(assoc_receipt.status).name}") transfer_receipt = ( TransferTransaction() @@ -112,12 +98,10 @@ def associate_and_transfer( .execute(self.client) # auto-signs with operator’s key ) if transfer_receipt.status != ResponseCode.SUCCESS: - raise AssertionError( - f"Transfer failed: {ResponseCode(transfer_receipt.status).name}" - ) + raise AssertionError(f"Transfer failed: {ResponseCode(transfer_receipt.status).name}") -def create_fungible_token(env, opts=[]): +def create_fungible_token(env, opts=None): """ Create a fungible token with the given options. @@ -127,6 +111,9 @@ def create_fungible_token(env, opts=[]): Example opt function: lambda tx: tx.set_treasury_account_id(custom_treasury_id).freeze_with(client) """ + if opts is None: + opts = [] + token_params = TokenParams( token_name="PTokenTest34", token_symbol="PTT34", @@ -154,14 +141,14 @@ def create_fungible_token(env, opts=[]): token_receipt = token_transaction.execute(env.client) - assert ( - token_receipt.status == ResponseCode.SUCCESS - ), f"Token creation failed with status: {ResponseCode(token_receipt.status).name}" + assert token_receipt.status == ResponseCode.SUCCESS, ( + f"Token creation failed with status: {ResponseCode(token_receipt.status).name}" + ) return token_receipt.token_id -def create_nft_token(env, opts=[]): +def create_nft_token(env, opts=None): """ Create a non-fungible token (NFT) with the given options. @@ -171,6 +158,9 @@ def create_nft_token(env, opts=[]): Example opt function: lambda tx: tx.set_treasury_account_id(custom_treasury_id).freeze_with(client) """ + if opts is None: + opts = [] + token_params = TokenParams( token_name="PythonNFTToken", token_symbol="PNFT", @@ -197,9 +187,9 @@ def create_nft_token(env, opts=[]): token_receipt = transaction.execute(env.client) - assert ( - token_receipt.status == ResponseCode.SUCCESS - ), f"Token creation failed with status: {ResponseCode(token_receipt.status).name}" + assert token_receipt.status == ResponseCode.SUCCESS, ( + f"Token creation failed with status: {ResponseCode(token_receipt.status).name}" + ) return token_receipt.token_id @@ -237,10 +227,6 @@ def wait_for_mirror_node( time.sleep(interval) if last_exception is not None: - raise TimeoutError( - "Timed out waiting for mirror node, Last call raised an exception" - ) from last_exception + raise TimeoutError("Timed out waiting for mirror node, Last call raised an exception") from last_exception - raise TimeoutError( - f"Timed out waiting for mirror node. Last response: {last_response}" - ) + raise TimeoutError(f"Timed out waiting for mirror node. Last response: {last_response}") diff --git a/tests/tck/client_utils_test.py b/tests/tck/client_utils_test.py index 9ec7df376..40c3f16e1 100644 --- a/tests/tck/client_utils_test.py +++ b/tests/tck/client_utils_test.py @@ -1,10 +1,17 @@ """Unit tests for the client manager module.""" + +from __future__ import annotations + from unittest.mock import MagicMock + import pytest -from tck.util.client_utils import store_client, get_client, remove_client, _CLIENTS + +from tck.util.client_utils import _CLIENTS, get_client, remove_client, store_client + pytestmark = pytest.mark.unit + @pytest.fixture(autouse=True) def clear_clients(): """Clear the clients registry before each test.""" @@ -15,7 +22,7 @@ def clear_clients(): class TestClientManager: """Test client storage, retrieval, and cleanup.""" - + def test_store_and_retrieve_client(self): """Test that a client can be stored and retrieved by session ID.""" mock_client = MagicMock() @@ -38,18 +45,18 @@ def test_store_multiple_clients(self): client1 = MagicMock() client2 = MagicMock() client3 = MagicMock() - + store_client("session1", client1) store_client("session2", client2) store_client("session3", client3) - + if get_client("session1") is not client1: raise AssertionError("Expected client1 to be returned for session1") if get_client("session2") is not client2: raise AssertionError("Expected client2 to be returned for session2") if get_client("session3") is not client3: raise AssertionError("Expected client3 to be returned for session3") - + def test_overwrite_existing_client(self): """Test that storing a client with an existing session ID overwrites it.""" old_client = MagicMock() @@ -68,7 +75,6 @@ def test_overwrite_existing_client(self): # Verify that close() was called on the old client old_client.close.assert_called_once() - def test_remove_client_calls_close(self): """Test that remove_client calls close() on the client.""" mock_client = MagicMock() diff --git a/tests/tck/common_params_test.py b/tests/tck/common_params_test.py index 83ac633b5..1c884c603 100644 --- a/tests/tck/common_params_test.py +++ b/tests/tck/common_params_test.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from unittest.mock import MagicMock import pytest @@ -13,58 +15,60 @@ @pytest.fixture def json_params_dict(): - return { - 'transactionId': "0.0.90@1773846665.518000566", - "maxTransactionFee": "100000000", - "validTransactionDuration": "120", - "memo": "Test Memo", - "regenerateTransactionId": "False", - "signers": ["30540201010420d0b3d3c266ad9aa414f41e3050d64f4012765abc94a745cbd0607bf41da51a96a00706052b8104000aa124032200037aa11171d538daf5c624f313bc106fff289e4a24768880d0fa71dd302a1fa9e7"] - } + return { + "transactionId": "0.0.90@1773846665.518000566", + "maxTransactionFee": "100000000", + "validTransactionDuration": "120", + "memo": "Test Memo", + "regenerateTransactionId": "False", + "signers": [ + "30540201010420d0b3d3c266ad9aa414f41e3050d64f4012765abc94a745cbd0607bf41da51a96a00706052b8104000aa124032200037aa11171d538daf5c624f313bc106fff289e4a24768880d0fa71dd302a1fa9e7" + ], + } def test_parse_common_params(json_params_dict): - """Test the commonTransaction params can be parse form dict""" - params = CommonTransactionParams.parse_json_params(json_params_dict) + """Test the commonTransaction params can be parse form dict""" + params = CommonTransactionParams.parse_json_params(json_params_dict) + + assert isinstance(params.transactionId, str) + assert params.transactionId == "0.0.90@1773846665.518000566" - assert isinstance(params.transactionId, str) - assert params.transactionId == "0.0.90@1773846665.518000566" + assert isinstance(params.maxTransactionFee, int) + assert params.maxTransactionFee == 100000000 - assert isinstance(params.maxTransactionFee, int) - assert params.maxTransactionFee == 100000000 - - assert isinstance(params.memo, str) - assert params.memo == "Test Memo" + assert isinstance(params.memo, str) + assert params.memo == "Test Memo" - assert isinstance(params.validTransactionDuration, int) - assert params.validTransactionDuration == 120 + assert isinstance(params.validTransactionDuration, int) + assert params.validTransactionDuration == 120 - assert isinstance(params.regenerateTransactionId, bool) - assert params.regenerateTransactionId == False + assert isinstance(params.regenerateTransactionId, bool) + assert params.regenerateTransactionId == False - assert isinstance(params.signers, list) - assert len(params.signers) == 1 - assert params.signers[0] == "30540201010420d0b3d3c266ad9aa414f41e3050d64f4012765abc94a745cbd0607bf41da51a96a00706052b8104000aa124032200037aa11171d538daf5c624f313bc106fff289e4a24768880d0fa71dd302a1fa9e7" + assert isinstance(params.signers, list) + assert len(params.signers) == 1 + assert ( + params.signers[0] + == "30540201010420d0b3d3c266ad9aa414f41e3050d64f4012765abc94a745cbd0607bf41da51a96a00706052b8104000aa124032200037aa11171d538daf5c624f313bc106fff289e4a24768880d0fa71dd302a1fa9e7" + ) def test_apply_common_params(json_params_dict): - """Test commonTransactionParams can be apply to transaction""" - params = CommonTransactionParams.parse_json_params(json_params_dict) - tx = ( - AccountCreateTransaction() - .set_key_without_alias(PrivateKey.generate()) - ) - - client = MagicMock(spec=Client) - - tx.freeze_with = MagicMock() - tx.sign = MagicMock() - - params.apply_common_params(tx, client) - - assert tx.memo == "Test Memo" - assert tx.transaction_valid_duration == 120 - assert str(tx.transaction_id) == "0.0.90@1773846665.518000566" - - tx.freeze_with.assert_called_once_with(client) - assert tx.sign.call_count == 1 \ No newline at end of file + """Test commonTransactionParams can be apply to transaction""" + params = CommonTransactionParams.parse_json_params(json_params_dict) + tx = AccountCreateTransaction().set_key_without_alias(PrivateKey.generate()) + + client = MagicMock(spec=Client) + + tx.freeze_with = MagicMock() + tx.sign = MagicMock() + + params.apply_common_params(tx, client) + + assert tx.memo == "Test Memo" + assert tx.transaction_valid_duration == 120 + assert str(tx.transaction_id) == "0.0.90@1773846665.518000566" + + tx.freeze_with.assert_called_once_with(client) + assert tx.sign.call_count == 1 diff --git a/tests/tck/conftest.py b/tests/tck/conftest.py index 40c82886c..d3c00eff0 100644 --- a/tests/tck/conftest.py +++ b/tests/tck/conftest.py @@ -1,6 +1,10 @@ """Fixtures for JSON-RPC request tests.""" + +from __future__ import annotations + import pytest + @pytest.fixture def valid_jsonrpc_request(): """Returns a valid JSON-RPC request.""" @@ -11,25 +15,23 @@ def valid_jsonrpc_request(): "id": 1, } + @pytest.fixture def invalid_json_request(): """Returns a malformed JSON-RPC request.""" return '{"id": malformed}' + @pytest.fixture def request_missing_fields(): """Returns a JSON-RPC request missing the 'method' field.""" - return { - "jsonrpc": "2.0", - "params": {}, - "id": 1 - } + return {"jsonrpc": "2.0", "params": {}, "id": 1} @pytest.fixture def request_with_string_id(): """Returns a JSON-RPC request with a string id (valid per JSON-RPC 2.0). - + The id can be a string, number, or NULL; this tests string id support. """ return { @@ -39,10 +41,11 @@ def request_with_string_id(): "id": "string-id-123", } + @pytest.fixture def request_invalid_jsonrpc_version(): """Returns a JSON-RPC request with an invalid version (1.0 instead of 2.0). - + Tests boundary condition: server should reject non-2.0 versions. """ return { @@ -52,10 +55,11 @@ def request_invalid_jsonrpc_version(): "id": 1, } + @pytest.fixture def request_with_session_id(): """Returns a JSON-RPC request with sessionId in params. - + Tests that sessionId parameter is properly extracted and passed through. """ return { diff --git a/tests/tck/handlers_test.py b/tests/tck/handlers_test.py index 06e2b7882..e09fd3e8f 100644 --- a/tests/tck/handlers_test.py +++ b/tests/tck/handlers_test.py @@ -1,33 +1,34 @@ """Test cases for the Hiero SDK TCK handlers registry and dispatch functionality.""" -import pytest -from unittest.mock import MagicMock +from __future__ import annotations + from dataclasses import dataclass +from unittest.mock import MagicMock + +import pytest from hiero_sdk_python.exceptions import ( + MaxAttemptsError, PrecheckError, ReceiptStatusError, - MaxAttemptsError, -) - -from tck.handlers.registry import ( - rpc_method, - get_handler, - get_all_handlers, - dispatch, - safe_dispatch, - parse_result, ) - from tck.errors import ( - JsonRpcError, - METHOD_NOT_FOUND, - INTERNAL_ERROR, HIERO_ERROR, + INTERNAL_ERROR, INVALID_PARAMS, + METHOD_NOT_FOUND, + JsonRpcError, ) - from tck.handlers import registry +from tck.handlers.registry import ( + dispatch, + get_all_handlers, + get_handler, + parse_result, + rpc_method, + safe_dispatch, +) + pytestmark = pytest.mark.unit @@ -51,17 +52,17 @@ def parse_json_params(cls, params): @dataclass class DummyResult: """Dataclass used to test parse_result.""" + value: int | None = None other: str | None = None class TestHandlerRegistration: - def test_handler_registration_via_decorator(self): """Test that @rpc_method decorator registers handler.""" @rpc_method("test_method") - def handler(params: DummyParams): + def handler(_params: DummyParams): return {"status": "ok"} handler_fn = get_handler("test_method") @@ -71,16 +72,15 @@ def handler(params: DummyParams): result = handler_fn({}) assert result == {"status": "ok"}, "Expected handler result" - def test_get_all_handlers(self): """Test retrieving all handlers.""" @rpc_method("method1") - def handler1(params: DummyParams): + def handler1(_params: DummyParams): return "result1" @rpc_method("method2") - def handler2(params: DummyParams): + def handler2(_params: DummyParams): return "result2" handlers = get_all_handlers() @@ -89,33 +89,30 @@ def handler2(params: DummyParams): assert "method1" in handlers, "method1 missing" assert "method2" in handlers, "method2 missing" - def test_get_nonexistent_handler_returns_none(self): """Nonexistent handler should return None.""" assert get_handler("missing") is None, "Expected None" - def test_handler_override(self): """Second registration should overwrite first.""" @rpc_method("override") - def first(params: DummyParams): + def first(_params: DummyParams): return "first" @rpc_method("override") - def second(params: DummyParams): + def second(_params: DummyParams): return "second" handler = get_handler("override") assert handler({}) == "second", "Override failed" - def test_get_all_handlers_returns_copy(self): """Returned dict should not modify registry.""" @rpc_method("protected") - def handler(params: DummyParams): + def handler(_params: DummyParams): return "ok" handlers = get_all_handlers() @@ -128,35 +125,30 @@ def handler(params: DummyParams): class TestDispatch: - def test_dispatch_success(self): """Dispatch should call registered handler.""" @rpc_method("setup") - def handler(params: DummyParams): + def handler(_params: DummyParams): return DummyResult(value=1, other="value") - + params = DummyParams() result = dispatch("setup", params) - assert result == {"value": 1, "other": "value"}, "Dispatch failed" - def test_dispatch_unknown_method(self): """Unknown method should raise METHOD_NOT_FOUND.""" - with pytest.raises(JsonRpcError) as excinfo: dispatch("missing", DummyParams()) assert excinfo.value.code == METHOD_NOT_FOUND, "Expected METHOD_NOT_FOUND" - def test_dispatch_reraises_json_rpc_error(self): """JsonRpcError should pass through.""" @rpc_method("error") - def handler(params: DummyParams): + def handler(_params: DummyParams): raise JsonRpcError.invalid_params_error() with pytest.raises(JsonRpcError) as excinfo: @@ -164,12 +156,11 @@ def handler(params: DummyParams): assert excinfo.value.code == INVALID_PARAMS, "Expected INVALID_PARAMS" - def test_dispatch_converts_generic_exception(self): """Generic exception → INTERNAL_ERROR.""" @rpc_method("crash") - def handler(params: DummyParams): + def handler(_params: DummyParams): raise ValueError("Boom") with pytest.raises(JsonRpcError) as excinfo: @@ -177,12 +168,11 @@ def handler(params: DummyParams): assert excinfo.value.code == INTERNAL_ERROR, "Expected INTERNAL_ERROR" - def test_dispatch_converts_precheck_error(self): """PrecheckError → HIERO_ERROR.""" @rpc_method("precheck") - def handler(params: DummyParams): + def handler(_params: DummyParams): raise PrecheckError( status=1, transaction_id="0.0.1", @@ -194,12 +184,11 @@ def handler(params: DummyParams): assert excinfo.value.code == HIERO_ERROR, "Expected HIERO_ERROR" - def test_dispatch_converts_receipt_status_error(self): """ReceiptStatusError → HIERO_ERROR.""" @rpc_method("receipt") - def handler(params: DummyParams): + def handler(_params: DummyParams): raise ReceiptStatusError( status=1, transaction_id=None, @@ -212,12 +201,11 @@ def handler(params: DummyParams): assert excinfo.value.code == HIERO_ERROR, "Expected HIERO_ERROR" - def test_dispatch_converts_max_attempts_error(self): """MaxAttemptsError → HIERO_ERROR.""" @rpc_method("max_attempts") - def handler(params: DummyParams): + def handler(_params: DummyParams): raise MaxAttemptsError("fail", node_id="0.0.1") with pytest.raises(JsonRpcError) as excinfo: @@ -227,36 +215,33 @@ def handler(params: DummyParams): class TestSafeDispatch: - def test_safe_dispatch_success(self): """safe_dispatch should return raw result.""" @rpc_method("success") - def handler(params: DummyParams): + def handler(_params: DummyParams): return DummyResult(value=1, other="value") result = safe_dispatch("success", {}, 1) - assert result == {"value":1, "other":"value"}, "Unexpected result" - + assert result == {"value": 1, "other": "value"}, "Unexpected result" def test_safe_dispatch_json_error(self): """JsonRpcError should produce JSON-RPC error response.""" @rpc_method("json_error") - def handler(params: DummyParams): + def handler(_params: DummyParams): raise JsonRpcError.invalid_params_error(data="field") response = safe_dispatch("json_error", {}, 10) assert response["error"]["code"] == INVALID_PARAMS, "Expected INVALID_PARAMS" - def test_safe_dispatch_generic_exception(self): """Generic exception → INTERNAL_ERROR.""" @rpc_method("generic_error") - def handler(params: DummyParams): + def handler(_params: DummyParams): raise RuntimeError("unexpected") response = safe_dispatch("generic_error", {}, 2) @@ -264,12 +249,9 @@ def handler(params: DummyParams): assert response["error"]["code"] == INTERNAL_ERROR, "Expected INTERNAL_ERROR" - class TestParseResult: - def test_parse_result_dataclass(self): """Dataclass should convert to dict.""" - result = DummyResult(value=10, other="other") parsed = parse_result(result) @@ -278,9 +260,8 @@ def test_parse_result_dataclass(self): def test_parse_result_dataclass_ignore_none(self): """Dataclass should convert to dict without None values.""" - result = DummyResult(value=10, other=None) parsed = parse_result(result) - assert parsed == {"value": 10}, "Expected filtered dataclass result" \ No newline at end of file + assert parsed == {"value": 10}, "Expected filtered dataclass result" diff --git a/tests/tck/protocol_test.py b/tests/tck/protocol_test.py index b87f20d40..d988e152e 100644 --- a/tests/tck/protocol_test.py +++ b/tests/tck/protocol_test.py @@ -1,31 +1,37 @@ """Unit tests for the JSON-RPC protocol handling in the TCK.""" + +from __future__ import annotations + import json + import pytest +from tck.errors import INVALID_REQUEST, PARSE_ERROR, JsonRpcError from tck.protocol import ( - parse_json_rpc_request, _extract_session_id, - build_json_rpc_success_response, build_json_rpc_error_response, + build_json_rpc_success_response, + parse_json_rpc_request, ) -from tck.errors import INVALID_REQUEST,PARSE_ERROR, JsonRpcError + pytestmark = pytest.mark.unit + def test_parsing_valid_request(valid_jsonrpc_request): """Test parsing of a valid JSON-RPC request.""" raw = json.dumps(valid_jsonrpc_request) parsed = parse_json_rpc_request(raw) - + if not isinstance(parsed, dict): raise AssertionError("Expected parsed result to be a dict") - if parsed['jsonrpc'] != '2.0': + if parsed["jsonrpc"] != "2.0": raise AssertionError("Expected jsonrpc version 2.0") - if parsed['method'] != 'setup': + if parsed["method"] != "setup": raise AssertionError("Expected method to be 'setup'") - if parsed['id'] != 1: + if parsed["id"] != 1: raise AssertionError("Expected id to be 1") - if parsed['sessionId'] is not None: + if parsed["sessionId"] is not None: raise AssertionError("Expected sessionId to be None when not in params") @@ -55,6 +61,7 @@ def test_response_formatting_error(): if resp["error"]["message"] != "Invalid Request": raise AssertionError("Expected error message 'Invalid Request'") + def test_invalid_json_returns_parse_error(invalid_json_request): """Test that invalid JSON input returns a parse error.""" req = invalid_json_request @@ -65,6 +72,7 @@ def test_invalid_json_returns_parse_error(invalid_json_request): if parsed.code != PARSE_ERROR: raise AssertionError("Expected PARSE_ERROR code") + def test_missing_required_fields_returns_invalid_request(request_missing_fields): """Test that missing required fields returns an invalid request error.""" req = request_missing_fields @@ -77,6 +85,7 @@ def test_missing_required_fields_returns_invalid_request(request_missing_fields) if parsed.code != INVALID_REQUEST: raise AssertionError("Expected INVALID_REQUEST code") + def test_session_id_extraction_no_session(): """Test extraction of session ID when no session is present.""" params = {} @@ -84,12 +93,14 @@ def test_session_id_extraction_no_session(): if sid is not None: raise AssertionError("sessionId should be None when not in params") + def test_session_id_extraction_with_session(request_with_session_id): """Test extraction of session ID when sessionId is present in params.""" sid = _extract_session_id(request_with_session_id["params"]) if sid != "session-abc-123": raise AssertionError("Expected sessionId to be extracted from params") + def test_parsing_request_with_string_id(request_with_string_id): """Test parsing of a valid JSON-RPC request with string ID.""" raw = json.dumps(request_with_string_id) @@ -97,13 +108,14 @@ def test_parsing_request_with_string_id(request_with_string_id): if not isinstance(parsed, dict): raise AssertionError("Expected parsed result to be a dict") - if parsed['jsonrpc'] != '2.0': + if parsed["jsonrpc"] != "2.0": raise AssertionError("Expected jsonrpc version 2.0") - if parsed['method'] != 'setup': + if parsed["method"] != "setup": raise AssertionError("Expected method to be 'setup'") - if parsed['id'] != "string-id-123": + if parsed["id"] != "string-id-123": raise AssertionError("Expected string id to be preserved") + def test_invalid_jsonrpc_version_returns_error(request_invalid_jsonrpc_version): """Test that invalid jsonrpc version returns an invalid request error.""" raw = json.dumps(request_invalid_jsonrpc_version) diff --git a/tests/unit/account_allowance_approve_transaction_test.py b/tests/unit/account_allowance_approve_transaction_test.py index 67a555288..1655f2fdb 100644 --- a/tests/unit/account_allowance_approve_transaction_test.py +++ b/tests/unit/account_allowance_approve_transaction_test.py @@ -2,6 +2,8 @@ Unit tests for the AccountAllowanceApproveTransaction class. """ +from __future__ import annotations + import pytest from hiero_sdk_python.account.account_allowance_approve_transaction import ( @@ -15,6 +17,7 @@ from hiero_sdk_python.tokens.nft_id import NftId from hiero_sdk_python.tokens.token_id import TokenId + pytestmark = pytest.mark.unit @@ -48,9 +51,7 @@ def test_account_allowance_transaction_initialization(account_allowance_transact assert account_allowance_transaction.hbar_allowances == [] assert account_allowance_transaction.token_allowances == [] assert account_allowance_transaction.nft_allowances == [] - assert ( - account_allowance_transaction._default_transaction_fee == Hbar(1).to_tinybars() - ) + assert account_allowance_transaction._default_transaction_fee == Hbar(1).to_tinybars() def test_approve_hbar_allowance(account_allowance_transaction, sample_accounts): @@ -59,9 +60,7 @@ def test_approve_hbar_allowance(account_allowance_transaction, sample_accounts): spender = sample_accounts["spender"] amount = Hbar(100) - result = account_allowance_transaction.approve_hbar_allowance( - owner, spender, amount - ) + result = account_allowance_transaction.approve_hbar_allowance(owner, spender, amount) assert result is account_allowance_transaction assert len(account_allowance_transaction.hbar_allowances) == 1 @@ -72,9 +71,7 @@ def test_approve_hbar_allowance(account_allowance_transaction, sample_accounts): assert allowance.amount == amount.to_tinybars() -def test_approve_hbar_allowance_multiple( - account_allowance_transaction, sample_accounts -): +def test_approve_hbar_allowance_multiple(account_allowance_transaction, sample_accounts): """Test approving multiple HBAR allowances""" owner = sample_accounts["owner"] spender1 = sample_accounts["spender"] @@ -84,28 +81,18 @@ def test_approve_hbar_allowance_multiple( account_allowance_transaction.approve_hbar_allowance(owner, spender2, Hbar(200)) assert len(account_allowance_transaction.hbar_allowances) == 2 - assert ( - account_allowance_transaction.hbar_allowances[0].amount - == Hbar(100).to_tinybars() - ) - assert ( - account_allowance_transaction.hbar_allowances[1].amount - == Hbar(200).to_tinybars() - ) + assert account_allowance_transaction.hbar_allowances[0].amount == Hbar(100).to_tinybars() + assert account_allowance_transaction.hbar_allowances[1].amount == Hbar(200).to_tinybars() -def test_approve_token_allowance( - account_allowance_transaction, sample_accounts, sample_tokens -): +def test_approve_token_allowance(account_allowance_transaction, sample_accounts, sample_tokens): """Test approving token allowance""" token_id = sample_tokens["token1"] owner = sample_accounts["owner"] spender = sample_accounts["spender"] amount = 1000 - result = account_allowance_transaction.approve_token_allowance( - token_id, owner, spender, amount - ) + result = account_allowance_transaction.approve_token_allowance(token_id, owner, spender, amount) assert result is account_allowance_transaction assert len(account_allowance_transaction.token_allowances) == 1 @@ -117,9 +104,7 @@ def test_approve_token_allowance( assert allowance.amount == amount -def test_approve_token_allowance_multiple( - account_allowance_transaction, sample_accounts, sample_tokens -): +def test_approve_token_allowance_multiple(account_allowance_transaction, sample_accounts, sample_tokens): """Test approving multiple token allowances""" token1 = sample_tokens["token1"] token2 = sample_tokens["token2"] @@ -134,18 +119,14 @@ def test_approve_token_allowance_multiple( assert account_allowance_transaction.token_allowances[1].amount == 2000 -def test_approve_token_nft_allowance( - account_allowance_transaction, sample_accounts, sample_tokens -): +def test_approve_token_nft_allowance(account_allowance_transaction, sample_accounts, sample_tokens): """Test approving NFT allowance""" token_id = sample_tokens["token1"] nft_id = NftId(token_id, 1) owner = sample_accounts["owner"] spender = sample_accounts["spender"] - result = account_allowance_transaction.approve_token_nft_allowance( - nft_id, owner, spender - ) + result = account_allowance_transaction.approve_token_nft_allowance(nft_id, owner, spender) assert result is account_allowance_transaction assert len(account_allowance_transaction.nft_allowances) == 1 @@ -158,9 +139,7 @@ def test_approve_token_nft_allowance( assert allowance.approved_for_all is False -def test_approve_token_nft_allowance_multiple_serials( - account_allowance_transaction, sample_accounts, sample_tokens -): +def test_approve_token_nft_allowance_multiple_serials(account_allowance_transaction, sample_accounts, sample_tokens): """Test approving NFT allowance with multiple serials""" token_id = sample_tokens["token1"] owner = sample_accounts["owner"] @@ -179,17 +158,13 @@ def test_approve_token_nft_allowance_multiple_serials( assert allowance.serial_numbers == [1, 2] -def test_approve_token_nft_allowance_all_serials( - account_allowance_transaction, sample_accounts, sample_tokens -): +def test_approve_token_nft_allowance_all_serials(account_allowance_transaction, sample_accounts, sample_tokens): """Test approving NFT allowance for all serials""" token_id = sample_tokens["token1"] owner = sample_accounts["owner"] spender = sample_accounts["spender"] - result = account_allowance_transaction.approve_token_nft_allowance_all_serials( - token_id, owner, spender - ) + result = account_allowance_transaction.approve_token_nft_allowance_all_serials(token_id, owner, spender) assert result is account_allowance_transaction assert len(account_allowance_transaction.nft_allowances) == 1 @@ -215,9 +190,7 @@ def test_approve_token_nft_allowance_all_serials_existing( account_allowance_transaction.approve_token_nft_allowance(nft_id, owner, spender) # Then approve all serials - account_allowance_transaction.approve_token_nft_allowance_all_serials( - token_id, owner, spender - ) + account_allowance_transaction.approve_token_nft_allowance_all_serials(token_id, owner, spender) assert len(account_allowance_transaction.nft_allowances) == 1 allowance = account_allowance_transaction.nft_allowances[0] @@ -225,17 +198,13 @@ def test_approve_token_nft_allowance_all_serials_existing( assert allowance.approved_for_all is True -def test_delete_token_nft_allowance_all_serials( - account_allowance_transaction, sample_accounts, sample_tokens -): +def test_delete_token_nft_allowance_all_serials(account_allowance_transaction, sample_accounts, sample_tokens): """Test deleting NFT allowance for all serials""" token_id = sample_tokens["token1"] owner = sample_accounts["owner"] spender = sample_accounts["spender"] - result = account_allowance_transaction.delete_token_nft_allowance_all_serials( - token_id, owner, spender - ) + result = account_allowance_transaction.delete_token_nft_allowance_all_serials(token_id, owner, spender) assert result is account_allowance_transaction assert len(account_allowance_transaction.nft_allowances) == 1 @@ -248,9 +217,7 @@ def test_delete_token_nft_allowance_all_serials( assert allowance.approved_for_all is False -def test_add_all_token_nft_approval( - account_allowance_transaction, sample_accounts, sample_tokens -): +def test_add_all_token_nft_approval(account_allowance_transaction, sample_accounts, sample_tokens): """Test adding all token NFT approval""" token_id = sample_tokens["token1"] spender = sample_accounts["spender"] @@ -278,9 +245,7 @@ def test_build_proto_body_empty(account_allowance_transaction): assert len(proto_body.nftAllowances) == 0 -def test_build_proto_body_with_allowances( - account_allowance_transaction, sample_accounts, sample_tokens -): +def test_build_proto_body_with_allowances(account_allowance_transaction, sample_accounts, sample_tokens): """Test building protobuf body with allowances""" owner = sample_accounts["owner"] spender = sample_accounts["spender"] @@ -289,9 +254,7 @@ def test_build_proto_body_with_allowances( # Add all types of allowances account_allowance_transaction.approve_hbar_allowance(owner, spender, Hbar(100)) - account_allowance_transaction.approve_token_allowance( - token_id, owner, spender, 1000 - ) + account_allowance_transaction.approve_token_allowance(token_id, owner, spender, 1000) account_allowance_transaction.approve_token_nft_allowance(nft_id, owner, spender) proto_body = account_allowance_transaction._build_proto_body() @@ -341,19 +304,13 @@ def test_require_not_frozen(account_allowance_transaction, sample_accounts): account_allowance_transaction.approve_hbar_allowance(owner, spender, Hbar(100)) with pytest.raises(Exception, match="Transaction is immutable"): - account_allowance_transaction.approve_token_allowance( - TokenId(0, 0, 100), owner, spender, 1000 - ) + account_allowance_transaction.approve_token_allowance(TokenId(0, 0, 100), owner, spender, 1000) with pytest.raises(Exception, match="Transaction is immutable"): - account_allowance_transaction.approve_token_nft_allowance( - NftId(TokenId(0, 0, 100), 1), owner, spender - ) + account_allowance_transaction.approve_token_nft_allowance(NftId(TokenId(0, 0, 100), 1), owner, spender) -def test_mixed_allowance_types( - account_allowance_transaction, sample_accounts, sample_tokens -): +def test_mixed_allowance_types(account_allowance_transaction, sample_accounts, sample_tokens): """Test transaction with mixed allowance types""" owner = sample_accounts["owner"] spender = sample_accounts["spender"] @@ -362,13 +319,9 @@ def test_mixed_allowance_types( # Add all types of allowances account_allowance_transaction.approve_hbar_allowance(owner, spender, Hbar(100)) - account_allowance_transaction.approve_token_allowance( - token_id, owner, spender, 1000 - ) + account_allowance_transaction.approve_token_allowance(token_id, owner, spender, 1000) account_allowance_transaction.approve_token_nft_allowance(nft_id, owner, spender) - account_allowance_transaction.approve_token_nft_allowance_all_serials( - sample_tokens["token2"], owner, spender - ) + account_allowance_transaction.approve_token_nft_allowance_all_serials(sample_tokens["token2"], owner, spender) # Verify all allowances are present assert len(account_allowance_transaction.hbar_allowances) == 1 @@ -382,9 +335,7 @@ def test_mixed_allowance_types( assert len(proto_body.nftAllowances) == 2 -def test_zero_amount_allowances( - account_allowance_transaction, sample_accounts, sample_tokens -): +def test_zero_amount_allowances(account_allowance_transaction, sample_accounts, sample_tokens): """Test allowances with zero amounts (for removal)""" owner = sample_accounts["owner"] spender = sample_accounts["spender"] diff --git a/tests/unit/account_allowance_delete_transaction_test.py b/tests/unit/account_allowance_delete_transaction_test.py index ce9e03dc2..fb619e0bc 100644 --- a/tests/unit/account_allowance_delete_transaction_test.py +++ b/tests/unit/account_allowance_delete_transaction_test.py @@ -2,6 +2,8 @@ Unit tests for the AccountAllowanceDeleteTransaction class. """ +from __future__ import annotations + import pytest from hiero_sdk_python.account.account_allowance_delete_transaction import ( @@ -16,6 +18,7 @@ from hiero_sdk_python.tokens.token_id import TokenId from hiero_sdk_python.tokens.token_nft_allowance import TokenNftAllowance + pytestmark = pytest.mark.unit @@ -49,23 +52,15 @@ def test_account_allowance_delete_transaction_initialization( ): """Test the initialization of the AccountAllowanceDeleteTransaction class""" assert account_allowance_delete_transaction.nft_wipe == [] - assert ( - account_allowance_delete_transaction._default_transaction_fee - == Hbar(1).to_tinybars() - ) + assert account_allowance_delete_transaction._default_transaction_fee == Hbar(1).to_tinybars() -def test_account_allowance_delete_transaction_initialization_with_allowances( - sample_accounts, sample_tokens -): +def test_account_allowance_delete_transaction_initialization_with_allowances(sample_accounts, sample_tokens): """Test initialization with initial allowances""" owner = sample_accounts["owner"] token_id = sample_tokens["token1"] - nft_id = NftId(token_id, 1) - nft_wipe = [ - TokenNftAllowance(token_id=token_id, owner_account_id=owner, serial_numbers=[1]) - ] + nft_wipe = [TokenNftAllowance(token_id=token_id, owner_account_id=owner, serial_numbers=[1])] tx = AccountAllowanceDeleteTransaction(nft_wipe=nft_wipe) @@ -73,17 +68,13 @@ def test_account_allowance_delete_transaction_initialization_with_allowances( assert tx.nft_wipe[0].serial_numbers == [1] -def test_delete_all_token_nft_allowances( - account_allowance_delete_transaction, sample_accounts, sample_tokens -): +def test_delete_all_token_nft_allowances(account_allowance_delete_transaction, sample_accounts, sample_tokens): """Test deleting NFT allowance""" token_id = sample_tokens["token1"] nft_id = NftId(token_id, 1) owner = sample_accounts["owner"] - result = account_allowance_delete_transaction.delete_all_token_nft_allowances( - nft_id, owner - ) + result = account_allowance_delete_transaction.delete_all_token_nft_allowances(nft_id, owner) assert result is account_allowance_delete_transaction assert len(account_allowance_delete_transaction.nft_wipe) == 1 @@ -125,15 +116,11 @@ def test_delete_all_token_nft_allowances_different_owners( # Add NFT for first owner nft_id1 = NftId(token_id, 1) - account_allowance_delete_transaction.delete_all_token_nft_allowances( - nft_id1, owner1 - ) + account_allowance_delete_transaction.delete_all_token_nft_allowances(nft_id1, owner1) # Add NFT for second owner nft_id2 = NftId(token_id, 2) - account_allowance_delete_transaction.delete_all_token_nft_allowances( - nft_id2, owner2 - ) + account_allowance_delete_transaction.delete_all_token_nft_allowances(nft_id2, owner2) assert len(account_allowance_delete_transaction.nft_wipe) == 2 assert account_allowance_delete_transaction.nft_wipe[0].owner_account_id == owner1 @@ -169,9 +156,7 @@ def test_build_proto_body_empty(account_allowance_delete_transaction): assert len(proto_body.nftAllowances) == 0 -def test_build_proto_body_with_allowances( - account_allowance_delete_transaction, sample_accounts, sample_tokens -): +def test_build_proto_body_with_allowances(account_allowance_delete_transaction, sample_accounts, sample_tokens): """Test building protobuf body with allowances""" owner = sample_accounts["owner"] token_id = sample_tokens["token1"] @@ -203,9 +188,7 @@ def test_build_transaction_body(account_allowance_delete_transaction, sample_acc assert len(proto_body.nftAllowances) == 1 -def test_build_scheduled_body( - account_allowance_delete_transaction, sample_accounts, sample_tokens -): +def test_build_scheduled_body(account_allowance_delete_transaction, sample_accounts, sample_tokens): """Test building scheduled transaction body""" owner = sample_accounts["owner"] token_id = sample_tokens["token1"] @@ -218,9 +201,7 @@ def test_build_scheduled_body( assert scheduled_body.cryptoDeleteAllowance is not None -def test_require_not_frozen( - account_allowance_delete_transaction, sample_accounts, sample_tokens -): +def test_require_not_frozen(account_allowance_delete_transaction, sample_accounts, sample_tokens): """Test that methods require transaction not to be frozen""" owner = sample_accounts["owner"] token_id = sample_tokens["token1"] @@ -231,14 +212,10 @@ def test_require_not_frozen( # This should raise an error with pytest.raises(Exception, match="Transaction is immutable"): - account_allowance_delete_transaction.delete_all_token_nft_allowances( - nft_id, owner - ) + account_allowance_delete_transaction.delete_all_token_nft_allowances(nft_id, owner) -def test_duplicate_serial_number_handling( - account_allowance_delete_transaction, sample_accounts, sample_tokens -): +def test_duplicate_serial_number_handling(account_allowance_delete_transaction, sample_accounts, sample_tokens): """Test that duplicate serial numbers are not added""" token_id = sample_tokens["token1"] owner = sample_accounts["owner"] @@ -253,9 +230,7 @@ def test_duplicate_serial_number_handling( assert wipe_entry.serial_numbers == [1] # Should not have duplicates -def test_mixed_nft_deletions( - account_allowance_delete_transaction, sample_accounts, sample_tokens -): +def test_mixed_nft_deletions(account_allowance_delete_transaction, sample_accounts, sample_tokens): """Test transaction with mixed NFT deletions""" owner1 = sample_accounts["owner"] owner2 = sample_accounts["owner2"] @@ -263,20 +238,12 @@ def test_mixed_nft_deletions( token2 = sample_tokens["token2"] # Add various NFT deletions - account_allowance_delete_transaction.delete_all_token_nft_allowances( - NftId(token1, 1), owner1 - ) - account_allowance_delete_transaction.delete_all_token_nft_allowances( - NftId(token1, 2), owner1 - ) - account_allowance_delete_transaction.delete_all_token_nft_allowances( - NftId(token2, 1), owner2 - ) + account_allowance_delete_transaction.delete_all_token_nft_allowances(NftId(token1, 1), owner1) + account_allowance_delete_transaction.delete_all_token_nft_allowances(NftId(token1, 2), owner1) + account_allowance_delete_transaction.delete_all_token_nft_allowances(NftId(token2, 1), owner2) # Verify all deletions are present - assert ( - len(account_allowance_delete_transaction.nft_wipe) == 2 - ) # Grouped by token+owner + assert len(account_allowance_delete_transaction.nft_wipe) == 2 # Grouped by token+owner # Verify protobuf body includes all deletions proto_body = account_allowance_delete_transaction._build_proto_body() diff --git a/tests/unit/account_balance_query_test.py b/tests/unit/account_balance_query_test.py index f989cbea4..799e29569 100644 --- a/tests/unit/account_balance_query_test.py +++ b/tests/unit/account_balance_query_test.py @@ -1,8 +1,11 @@ """Tests for the AccountBalanceQuery functionality.""" +from __future__ import annotations + import pytest from hiero_sdk_python.account.account_id import AccountId +from hiero_sdk_python.contract.contract_id import ContractId from hiero_sdk_python.hapi.services import ( basic_types_pb2, response_header_pb2, @@ -14,10 +17,9 @@ from hiero_sdk_python.hapi.services.query_header_pb2 import ResponseType from hiero_sdk_python.query.account_balance_query import CryptoGetAccountBalanceQuery from hiero_sdk_python.response_code import ResponseCode -from hiero_sdk_python.contract.contract_id import ContractId - from tests.unit.mock_server import mock_hedera_servers + pytestmark = pytest.mark.unit @@ -38,9 +40,7 @@ def test_execute_account_balance_query(): responseType=ResponseType.ANSWER_ONLY, cost=0, ), - accountID=basic_types_pb2.AccountID( - shardNum=0, realmNum=0, accountNum=1800 - ), + accountID=basic_types_pb2.AccountID(shardNum=0, realmNum=0, accountNum=1800), balance=2000, ) ) @@ -51,9 +51,7 @@ def test_execute_account_balance_query(): with mock_hedera_servers(response_sequences) as client: # Create the query and verify no exceptions are raised try: - CryptoGetAccountBalanceQuery().set_account_id( - AccountId(0, 0, 1800) - ).execute(client) + CryptoGetAccountBalanceQuery().set_account_id(AccountId(0, 0, 1800)).execute(client) except Exception as e: pytest.fail(f"Unexpected exception raised: {e}") @@ -115,11 +113,7 @@ def test_set_contract_id_method_chaining_resets_account_id(mock_account_ids): account_id_sender, *_ = mock_account_ids contract_id = ContractId(0, 0, 1234) - query = ( - CryptoGetAccountBalanceQuery() - .set_account_id(account_id_sender) - .set_contract_id(contract_id) - ) + query = CryptoGetAccountBalanceQuery().set_account_id(account_id_sender).set_contract_id(contract_id) assert query.contract_id == contract_id assert query.account_id is None @@ -132,9 +126,7 @@ def test_last_wins_when_both_account_id_and_contract_id_are_set( account_id_sender, *_ = mock_account_ids contract_id = ContractId(0, 0, 1234) - query = CryptoGetAccountBalanceQuery( - account_id=account_id_sender, contract_id=contract_id - ) + query = CryptoGetAccountBalanceQuery(account_id=account_id_sender, contract_id=contract_id) assert query.contract_id == contract_id assert query.account_id is None diff --git a/tests/unit/account_create_transaction_test.py b/tests/unit/account_create_transaction_test.py index ac8d2365d..d5c2ac0d9 100644 --- a/tests/unit/account_create_transaction_test.py +++ b/tests/unit/account_create_transaction_test.py @@ -1,28 +1,34 @@ +from __future__ import annotations + import time -import pytest from unittest.mock import patch + +import pytest + from hiero_sdk_python.account.account_create_transaction import AccountCreateTransaction from hiero_sdk_python.account.account_id import AccountId from hiero_sdk_python.crypto.private_key import PrivateKey -from hiero_sdk_python.transaction.transaction_id import TransactionId -from hiero_sdk_python.response_code import ResponseCode -from hiero_sdk_python.hapi.services import timestamp_pb2 -from hiero_sdk_python.hapi.services import basic_types_pb2, response_pb2 -from hiero_sdk_python.hapi.services.transaction_response_pb2 import ( - TransactionResponse as TransactionResponseProto, -) -from hiero_sdk_python.hapi.services.transaction_receipt_pb2 import ( - TransactionReceipt as TransactionReceiptProto, -) from hiero_sdk_python.hapi.services import ( - transaction_get_receipt_pb2, + basic_types_pb2, response_header_pb2, + response_pb2, + timestamp_pb2, + transaction_get_receipt_pb2, ) from hiero_sdk_python.hapi.services.schedulable_transaction_body_pb2 import ( SchedulableTransactionBody, ) +from hiero_sdk_python.hapi.services.transaction_receipt_pb2 import ( + TransactionReceipt as TransactionReceiptProto, +) +from hiero_sdk_python.hapi.services.transaction_response_pb2 import ( + TransactionResponse as TransactionResponseProto, +) +from hiero_sdk_python.response_code import ResponseCode +from hiero_sdk_python.transaction.transaction_id import TransactionId from tests.unit.mock_server import mock_hedera_servers + pytestmark = pytest.mark.unit @@ -32,12 +38,9 @@ def generate_transaction_id(account_id_proto): timestamp_seconds = int(current_time) timestamp_nanos = int((current_time - timestamp_seconds) * 1e9) - tx_timestamp = timestamp_pb2.Timestamp( - seconds=timestamp_seconds, nanos=timestamp_nanos - ) + tx_timestamp = timestamp_pb2.Timestamp(seconds=timestamp_seconds, nanos=timestamp_nanos) - tx_id = TransactionId(valid_start=tx_timestamp, account_id=account_id_proto) - return tx_id + return TransactionId(valid_start=tx_timestamp, account_id=account_id_proto) # This test uses fixture mock_account_ids as parameter @@ -124,23 +127,15 @@ def test_account_create_transaction_sign(mock_account_ids, mock_client): account_tx.sign(mock_client.operator_private_key) body_bytes = account_tx._transaction_body_bytes[node_account_id] - assert ( - body_bytes in account_tx._signature_map - ), "Body bytes should be a key in the signature map dictionary" - assert ( - len(account_tx._signature_map[body_bytes].sigPair) == 1 - ), "Transaction should have exactly one signature" + assert body_bytes in account_tx._signature_map, "Body bytes should be a key in the signature map dictionary" + assert len(account_tx._signature_map[body_bytes].sigPair) == 1, "Transaction should have exactly one signature" # Add second signiture account_tx.sign(operator_private_key) body_bytes = account_tx._transaction_body_bytes[node_account_id] - assert ( - body_bytes in account_tx._signature_map - ), "Body bytes should be a key in the signature map dictionary" - assert ( - len(account_tx._signature_map[body_bytes].sigPair) == 2 - ), "Transaction should have exactly two signatures" + assert body_bytes in account_tx._signature_map, "Body bytes should be a key in the signature map dictionary" + assert len(account_tx._signature_map[body_bytes].sigPair) == 2, "Transaction should have exactly two signatures" def test_account_create_transaction(): @@ -161,9 +156,7 @@ def test_account_create_transaction(): # Create a response for the receipt query receipt_query_response = response_pb2.Response( transactionGetReceipt=transaction_get_receipt_pb2.TransactionGetReceiptResponse( - header=response_header_pb2.ResponseHeader( - nodeTransactionPrecheckCode=ResponseCode.OK - ), + header=response_header_pb2.ResponseHeader(nodeTransactionPrecheckCode=ResponseCode.OK), receipt=mock_receipt_proto, ) ) @@ -174,7 +167,6 @@ def test_account_create_transaction(): # Use the context manager to set up and tear down the mock environment with mock_hedera_servers(response_sequences) as client, patch("time.sleep"): - # Create the transaction new_key = PrivateKey.generate() transaction = ( @@ -187,12 +179,8 @@ def test_account_create_transaction(): receipt = transaction.execute(client) # Verify the results - assert ( - receipt.status == ResponseCode.SUCCESS - ), "Transaction should have succeeded" - assert ( - receipt.account_id.num == 1234 - ), "Should have created account with ID 1234" + assert receipt.status == ResponseCode.SUCCESS, "Transaction should have succeeded" + assert receipt.account_id.num == 1234, "Should have created account with ID 1234" def test_sign_account_create_without_freezing_raises_error(mock_account_ids): @@ -298,9 +286,7 @@ def test_create_account_transaction_set_key_with_alias(mock_account_ids): tx_body = tx.build_transaction_body() assert tx_body.cryptoCreateAccount.key == public_key._to_proto() - assert ( - tx_body.cryptoCreateAccount.alias == public_key.to_evm_address().address_bytes - ) + assert tx_body.cryptoCreateAccount.alias == public_key.to_evm_address().address_bytes def test_create_account_transaction_set_key_with_seperate_key_for_alias( @@ -343,11 +329,7 @@ def test_create_account_transaction_with_set_alias(mock_account_ids): public_key = PrivateKey.generate().public_key() evm_address = PrivateKey.generate_ecdsa().public_key().to_evm_address() - tx = ( - AccountCreateTransaction() - .set_key_without_alias(public_key) - .set_alias(evm_address) - ) + tx = AccountCreateTransaction().set_key_without_alias(public_key).set_alias(evm_address) assert tx.key == public_key assert tx.alias == evm_address @@ -361,9 +343,7 @@ def test_create_account_transaction_with_set_alias(mock_account_ids): @pytest.mark.parametrize("with_prefix", [False, True]) -def test_create_account_transaction_with_set_alias_from_string( - mock_account_ids, with_prefix -): +def test_create_account_transaction_with_set_alias_from_string(mock_account_ids, with_prefix): """Test account creation transaction using alias from string (with and without '0x' prefix).""" operator_id, node_id = mock_account_ids public_key = PrivateKey.generate().public_key() @@ -372,11 +352,7 @@ def test_create_account_transaction_with_set_alias_from_string( evm_string = evm_address.to_string() alias_str = "0x" + evm_string if with_prefix else evm_string - tx = ( - AccountCreateTransaction() - .set_key_without_alias(public_key) - .set_alias(alias_str) - ) + tx = AccountCreateTransaction().set_key_without_alias(public_key).set_alias(alias_str) assert tx.key == public_key assert tx.alias == evm_address @@ -459,9 +435,7 @@ def test_create_account_transaction_set_key_with_alias_private_keys(mock_account alias_public_key = alias_private_key.public_key() expected_evm_address = alias_public_key.to_evm_address() - tx = AccountCreateTransaction().set_key_with_alias( - account_private_key, alias_private_key - ) + tx = AccountCreateTransaction().set_key_with_alias(account_private_key, alias_private_key) assert tx.key == account_private_key assert tx.alias == expected_evm_address @@ -508,7 +482,6 @@ def test_set_key_without_alias_then_with_alias_overrides_alias(): """set_key_with_alias should ovveride set_key_without_alias""" # Account key(ECDSA) account_private_key = PrivateKey.generate_ecdsa() - account_public_key = account_private_key.public_key() # Alias key (ECDSA) alias_private_key = PrivateKey.generate_ecdsa() @@ -528,16 +501,13 @@ def test_set_key_with_alias_then_without_alias_overrides_alias(): """set_key_without_alias should ovveride set_key_with_alias""" # Account key(ECDSA) account_private_key = PrivateKey.generate_ecdsa() - account_public_key = account_private_key.public_key() # Alias key (ECDSA) alias_private_key = PrivateKey.generate_ecdsa() alias_public_key = alias_private_key.public_key() expected_evm_address = alias_public_key.to_evm_address() - tx = AccountCreateTransaction().set_key_with_alias( - account_private_key, alias_private_key - ) + tx = AccountCreateTransaction().set_key_with_alias(account_private_key, alias_private_key) assert tx.alias == expected_evm_address @@ -545,31 +515,27 @@ def test_set_key_with_alias_then_without_alias_overrides_alias(): assert tx.alias is None + def test_set_stack_node_id_reset_stake_account_id(): """Test set_node_id reset stake_account_id if stake account id is set.""" - tx = ( - AccountCreateTransaction() - .set_staked_account_id(AccountId(0,0,1)) - ) + tx = AccountCreateTransaction().set_staked_account_id(AccountId(0, 0, 1)) - assert tx.staked_account_id == AccountId(0,0,1) - assert tx.staked_node_id == None + assert tx.staked_account_id == AccountId(0, 0, 1) + assert tx.staked_node_id is None tx.set_staked_node_id(1) assert tx.staked_node_id == 1 assert tx.staked_account_id is None + def test_set_stake_account_id_reset_stake_node_id(): """Test set_account_id reset stake_node_id if node id is set.""" - tx = ( - AccountCreateTransaction() - .set_staked_node_id(1) - ) - + tx = AccountCreateTransaction().set_staked_node_id(1) + assert tx.staked_node_id == 1 assert tx.staked_account_id is None - - tx.set_staked_account_id(AccountId(0,0,1)) - assert tx.staked_account_id == AccountId(0,0,1) + + tx.set_staked_account_id(AccountId(0, 0, 1)) + assert tx.staked_account_id == AccountId(0, 0, 1) assert tx.staked_node_id is None diff --git a/tests/unit/account_delete_transaction_test.py b/tests/unit/account_delete_transaction_test.py index 3b43e10b8..f0cc087df 100644 --- a/tests/unit/account_delete_transaction_test.py +++ b/tests/unit/account_delete_transaction_test.py @@ -2,6 +2,8 @@ Test cases for the AccountDeleteTransaction class. """ +from __future__ import annotations + from unittest.mock import MagicMock import pytest @@ -12,6 +14,7 @@ SchedulableTransactionBody, ) + pytestmark = pytest.mark.unit @@ -45,9 +48,7 @@ def test_constructor_with_account_id_only(delete_params): def test_constructor_with_transfer_account_id_only(delete_params): """Test creating an account delete transaction with only transfer_account_id.""" - delete_tx = AccountDeleteTransaction( - transfer_account_id=delete_params["transfer_account_id"] - ) + delete_tx = AccountDeleteTransaction(transfer_account_id=delete_params["transfer_account_id"]) assert delete_tx.account_id is None assert delete_tx.transfer_account_id == delete_params["transfer_account_id"] @@ -76,14 +77,8 @@ def test_build_transaction_body_with_valid_parameters(mock_account_ids, delete_p transaction_body = delete_tx.build_transaction_body() - assert ( - transaction_body.cryptoDelete.deleteAccountID - == delete_params["account_id"]._to_proto() - ) - assert ( - transaction_body.cryptoDelete.transferAccountID - == delete_params["transfer_account_id"]._to_proto() - ) + assert transaction_body.cryptoDelete.deleteAccountID == delete_params["account_id"]._to_proto() + assert transaction_body.cryptoDelete.transferAccountID == delete_params["transfer_account_id"]._to_proto() def test_build_transaction_body_missing_account_id(): @@ -126,9 +121,9 @@ def test_method_chaining_with_all_setters(delete_params): """Test that all setter methods support method chaining.""" delete_tx = AccountDeleteTransaction() - result = delete_tx.set_account_id( - delete_params["account_id"] - ).set_transfer_account_id(delete_params["transfer_account_id"]) + result = delete_tx.set_account_id(delete_params["account_id"]).set_transfer_account_id( + delete_params["transfer_account_id"] + ) assert result is delete_tx assert delete_tx.account_id == delete_params["account_id"] @@ -160,9 +155,7 @@ def test_set_methods_require_not_frozen(mock_client, delete_params): ] for method_name, value in test_cases: - with pytest.raises( - Exception, match="Transaction is immutable; it has been frozen" - ): + with pytest.raises(Exception, match="Transaction is immutable; it has been frozen"): getattr(delete_tx, method_name)(value) @@ -250,14 +243,8 @@ def test_build_scheduled_body(delete_params): assert schedulable_body.HasField("cryptoDelete") # Verify fields in the schedulable body - assert ( - schedulable_body.cryptoDelete.deleteAccountID - == delete_params["account_id"]._to_proto() - ) - assert ( - schedulable_body.cryptoDelete.transferAccountID - == delete_params["transfer_account_id"]._to_proto() - ) + assert schedulable_body.cryptoDelete.deleteAccountID == delete_params["account_id"]._to_proto() + assert schedulable_body.cryptoDelete.transferAccountID == delete_params["transfer_account_id"]._to_proto() def test_parameter_validation_none_values(): diff --git a/tests/unit/account_id_test.py b/tests/unit/account_id_test.py index d29da2ea9..ea4373a56 100644 --- a/tests/unit/account_id_test.py +++ b/tests/unit/account_id_test.py @@ -2,7 +2,10 @@ Unit tests for the AccountId class. """ +from __future__ import annotations + from unittest.mock import MagicMock, patch + import pytest from hiero_sdk_python.account.account_id import AccountId @@ -10,6 +13,7 @@ from hiero_sdk_python.crypto.private_key import PrivateKey from hiero_sdk_python.hapi.services import basic_types_pb2 + pytestmark = pytest.mark.unit @@ -116,9 +120,7 @@ def test_str_representation_with_checksum(client, account_id_100): assert account_id_100.to_string_with_checksum(client) == "0.0.100-hhghj" -def test_str_representation_with_checksum_if_alias_key_present( - client, account_id_100, alias_key -): +def test_str_representation_with_checksum_if_alias_key_present(client, account_id_100, alias_key): """AccountId with aliasKey should raise ValueError on to_string_with_checksum""" account_id = account_id_100 account_id.alias_key = alias_key @@ -235,9 +237,7 @@ def test_from_string_with_checksum(): assert account_id.checksum == "abcde" -@pytest.mark.parametrize( - "alias_fixture", ["alias_key", "alias_key_ecdsa", "evm_address"] -) +@pytest.mark.parametrize("alias_fixture", ["alias_key", "alias_key_ecdsa", "evm_address"]) def test_from_string_with_alias(request, alias_fixture): """Test create AccountId from string with different alias.""" alias = request.getfixturevalue(alias_fixture) @@ -560,7 +560,7 @@ def test_equality_different_types(account_id_100): """Test AccountId equality with different types.""" assert account_id_100 != "1.2.3" assert account_id_100 != 123 - assert account_id_100 != None + assert account_id_100 is not None def test_hash(account_id_100, account_id_101): @@ -703,9 +703,7 @@ def test_populate_account_num(evm_address): response = {"account": "0.0.100"} - with patch( - "hiero_sdk_python.account.account_id.perform_query_to_mirror_node" - ) as mock_query: + with patch("hiero_sdk_python.account.account_id.perform_query_to_mirror_node") as mock_query: mock_query.return_value = response new_account_id = account_id.populate_account_num(mock_client) @@ -722,9 +720,7 @@ def test_populate_account_num_missing_account(evm_address): mock_client = MagicMock() mock_client.network.get_mirror_rest_url.return_value = "http://mirror_node_rest_url" - with patch( - "hiero_sdk_python.account.account_id.perform_query_to_mirror_node" - ) as mock_query: + with patch("hiero_sdk_python.account.account_id.perform_query_to_mirror_node") as mock_query: mock_query.return_value = {} with pytest.raises(ValueError, match="Mirror node response missing 'account'"): account_id.populate_account_num(mock_client) @@ -739,13 +735,9 @@ def test_populate_account_num_invalid_account_format(evm_address): # account value cannot be split into a valid int response = {"account": "invalid.account.format"} - with patch( - "hiero_sdk_python.account.account_id.perform_query_to_mirror_node" - ) as mock_query: + with patch("hiero_sdk_python.account.account_id.perform_query_to_mirror_node") as mock_query: mock_query.return_value = response - with pytest.raises( - ValueError, match="Invalid account format received: invalid.account.format" - ): + with pytest.raises(ValueError, match="Invalid account format received: invalid.account.format"): account_id.populate_account_num(mock_client) @@ -754,9 +746,7 @@ def test_populate_account_num_missing_evm_address(): account_id = AccountId.from_string("0.0.100") mock_client = MagicMock() - with pytest.raises( - ValueError, match="Account evm_address is required before populating num" - ): + with pytest.raises(ValueError, match="Account evm_address is required before populating num"): account_id.populate_account_num(mock_client) @@ -768,15 +758,17 @@ def test_populate_account_num_mirror_node_failure(): mock_client = MagicMock() mock_client.network.get_mirror_rest_url.return_value = "http://mirror-node" - with patch( - "hiero_sdk_python.account.account_id.perform_query_to_mirror_node", - side_effect=RuntimeError("mirror node query error"), - ): - with pytest.raises( + with ( + patch( + "hiero_sdk_python.account.account_id.perform_query_to_mirror_node", + side_effect=RuntimeError("mirror node query error"), + ), + pytest.raises( RuntimeError, match="Failed to populate account number from mirror node for evm_address", - ): - account_id.populate_account_num(mock_client) + ), + ): + account_id.populate_account_num(mock_client) def test_populate_account_evm_address(evm_address): @@ -788,13 +780,11 @@ def test_populate_account_evm_address(evm_address): response = {"evm_address": evm_address.to_string()} - with patch( - "hiero_sdk_python.account.account_id.perform_query_to_mirror_node" - ) as mock_query: + with patch("hiero_sdk_python.account.account_id.perform_query_to_mirror_node") as mock_query: mock_query.return_value = response new_account_id = account_id.populate_evm_address(mock_client) - assert account_id.evm_address == None + assert account_id.evm_address is None assert new_account_id.evm_address == evm_address @@ -807,13 +797,9 @@ def test_populate_evm_address_response_missing_evm_address(): mock_client = MagicMock() mock_client.network.get_mirror_rest_url.return_value = "http://mirror_node_rest_url" - with patch( - "hiero_sdk_python.account.account_id.perform_query_to_mirror_node" - ) as mock_query: + with patch("hiero_sdk_python.account.account_id.perform_query_to_mirror_node") as mock_query: mock_query.return_value = {} - with pytest.raises( - ValueError, match="Mirror node response missing 'evm_address'" - ): + with pytest.raises(ValueError, match="Mirror node response missing 'evm_address'"): account_id.populate_evm_address(mock_client) @@ -822,9 +808,7 @@ def test_populate_evm_address_missing_num(evm_address): account_id = AccountId.from_evm_address(evm_address, 0, 0) # num == 0 mock_client = MagicMock() - with pytest.raises( - ValueError, match="Account number is required before populating evm_address" - ): + with pytest.raises(ValueError, match="Account number is required before populating evm_address"): account_id.populate_evm_address(mock_client) @@ -835,15 +819,17 @@ def test_populate_evm_address_mirror_node_failure(): mock_client = MagicMock() mock_client.network.get_mirror_rest_url.return_value = "http://mirror-node" - with patch( - "hiero_sdk_python.account.account_id.perform_query_to_mirror_node", - side_effect=RuntimeError("mirror node query error"), - ): - with pytest.raises( + with ( + patch( + "hiero_sdk_python.account.account_id.perform_query_to_mirror_node", + side_effect=RuntimeError("mirror node query error"), + ), + pytest.raises( RuntimeError, match="Failed to populate evm_address from mirror node for account 123", - ): - account_id.populate_evm_address(mock_client) + ), + ): + account_id.populate_evm_address(mock_client) def test_populate_evm_address_requires_account_num(): @@ -852,9 +838,7 @@ def test_populate_evm_address_requires_account_num(): mock_client = MagicMock() - with pytest.raises( - ValueError, match="Account number is required before populating evm_address" - ): + with pytest.raises(ValueError, match="Account number is required before populating evm_address"): account_id.populate_evm_address(mock_client) diff --git a/tests/unit/account_info_query_test.py b/tests/unit/account_info_query_test.py index c8fa8ed91..204733083 100644 --- a/tests/unit/account_info_query_test.py +++ b/tests/unit/account_info_query_test.py @@ -1,22 +1,25 @@ -import pytest +from __future__ import annotations + from unittest.mock import Mock +import pytest + from hiero_sdk_python.account.account_id import AccountId -from hiero_sdk_python.hapi.services.query_header_pb2 import ResponseType -from hiero_sdk_python.query.account_info_query import AccountInfoQuery -from hiero_sdk_python.response_code import ResponseCode +from hiero_sdk_python.Duration import Duration from hiero_sdk_python.hapi.services import ( - response_pb2, - response_header_pb2, crypto_get_info_pb2, + response_header_pb2, + response_pb2, ) -from hiero_sdk_python.Duration import Duration -from hiero_sdk_python.hapi.services.timestamp_pb2 import Timestamp as TimestampProto from hiero_sdk_python.hapi.services.duration_pb2 import Duration as DurationProto +from hiero_sdk_python.hapi.services.query_header_pb2 import ResponseType +from hiero_sdk_python.hapi.services.timestamp_pb2 import Timestamp as TimestampProto +from hiero_sdk_python.query.account_info_query import AccountInfoQuery +from hiero_sdk_python.response_code import ResponseCode from hiero_sdk_python.timestamp import Timestamp - from tests.unit.mock_server import mock_hedera_servers + pytestmark = pytest.mark.unit @@ -35,9 +38,7 @@ def test_execute_fails_with_missing_account_id(mock_client): """Test request creation with missing Account ID.""" query = AccountInfoQuery() - with pytest.raises( - ValueError, match=r"Account ID must be set before making the request\." - ): + with pytest.raises(ValueError, match=r"Account ID must be set before making the request\."): query.execute(mock_client) diff --git a/tests/unit/account_info_test.py b/tests/unit/account_info_test.py index 5901b529c..df7eeeab4 100644 --- a/tests/unit/account_info_test.py +++ b/tests/unit/account_info_test.py @@ -1,17 +1,18 @@ +from __future__ import annotations + import pytest -from hiero_sdk_python.account.account_info import AccountInfo from hiero_sdk_python.account.account_id import AccountId -from hiero_sdk_python.staking_info import StakingInfo +from hiero_sdk_python.account.account_info import AccountInfo from hiero_sdk_python.crypto.private_key import PrivateKey -from hiero_sdk_python.hbar import Hbar from hiero_sdk_python.Duration import Duration -from hiero_sdk_python.timestamp import Timestamp -from hiero_sdk_python.tokens.token_relationship import TokenRelationship -from hiero_sdk_python.tokens.token_id import TokenId -from hiero_sdk_python.hapi.services.crypto_get_info_pb2 import CryptoGetInfoResponse from hiero_sdk_python.hapi.services.basic_types_pb2 import StakingInfo as StakingInfoProto +from hiero_sdk_python.hapi.services.crypto_get_info_pb2 import CryptoGetInfoResponse from hiero_sdk_python.hapi.services.timestamp_pb2 import Timestamp as TimestampProto +from hiero_sdk_python.hbar import Hbar +from hiero_sdk_python.staking_info import StakingInfo +from hiero_sdk_python.timestamp import Timestamp + pytestmark = pytest.mark.unit @@ -165,11 +166,11 @@ def test_to_proto(account_info): assert proto.tokenRelationships == [] assert proto.memo == "Test account memo" assert proto.ownedNfts == 5 - assert proto.HasField('staking_info') + assert proto.HasField("staking_info") assert proto.staking_info.decline_reward is True assert proto.staking_info.pending_reward == 500 assert proto.staking_info.staked_to_me == 1000 - assert proto.staking_info.HasField('staked_account_id') + assert proto.staking_info.HasField("staked_account_id") assert proto.staking_info.staked_account_id == AccountId(0, 0, 200)._to_proto() @@ -199,27 +200,14 @@ def test_proto_conversion(account_info): converted_account_info = AccountInfo._from_proto(proto) assert converted_account_info.account_id == account_info.account_id - assert ( - converted_account_info.contract_account_id == account_info.contract_account_id - ) + assert converted_account_info.contract_account_id == account_info.contract_account_id assert converted_account_info.is_deleted == account_info.is_deleted - assert ( - converted_account_info.proxy_received.to_tinybars() - == account_info.proxy_received.to_tinybars() - ) - assert ( - converted_account_info.balance.to_tinybars() - == account_info.balance.to_tinybars() - ) - assert ( - converted_account_info.receiver_signature_required - == account_info.receiver_signature_required - ) + assert converted_account_info.proxy_received.to_tinybars() == account_info.proxy_received.to_tinybars() + assert converted_account_info.balance.to_tinybars() == account_info.balance.to_tinybars() + assert converted_account_info.receiver_signature_required == account_info.receiver_signature_required assert converted_account_info.expiration_time == account_info.expiration_time assert converted_account_info.auto_renew_period == account_info.auto_renew_period - assert ( - converted_account_info.token_relationships == account_info.token_relationships - ) + assert converted_account_info.token_relationships == account_info.token_relationships assert converted_account_info.account_memo == account_info.account_memo assert converted_account_info.owned_nfts == account_info.owned_nfts assert converted_account_info.staking_info == account_info.staking_info @@ -272,6 +260,7 @@ def test_str_and_repr(account_info): # Deprecated flat property tests on AccountInfo # --------------------------------------------------------------------------- + class TestDeprecatedProperties: """Ensure the deprecated flat staking properties still work with warnings.""" @@ -284,6 +273,7 @@ def _make_info(self): def test_staked_account_id_deprecated(self): import warnings + info = self._make_info() with warnings.catch_warnings(record=True) as w: warnings.simplefilter("always") @@ -295,6 +285,7 @@ def test_staked_account_id_deprecated(self): def test_staked_node_id_deprecated(self): import warnings + info = self._make_info() with warnings.catch_warnings(record=True) as w: warnings.simplefilter("always") @@ -304,6 +295,7 @@ def test_staked_node_id_deprecated(self): def test_decline_staking_reward_deprecated(self): import warnings + info = self._make_info() with warnings.catch_warnings(record=True) as w: warnings.simplefilter("always") @@ -313,6 +305,7 @@ def test_decline_staking_reward_deprecated(self): def test_deprecated_properties_return_none_without_staking_info(self): import warnings + info = AccountInfo() with warnings.catch_warnings(record=True): warnings.simplefilter("always") @@ -322,6 +315,7 @@ def test_deprecated_properties_return_none_without_staking_info(self): def test_staked_account_id_setter(self): import warnings + info = AccountInfo() new_id = AccountId(0, 0, 50) with warnings.catch_warnings(record=True) as w: @@ -333,6 +327,7 @@ def test_staked_account_id_setter(self): def test_staked_node_id_setter(self): import warnings + info = AccountInfo() with warnings.catch_warnings(record=True) as w: warnings.simplefilter("always") @@ -343,6 +338,7 @@ def test_staked_node_id_setter(self): def test_decline_staking_reward_setter(self): import warnings + info = AccountInfo() with warnings.catch_warnings(record=True) as w: warnings.simplefilter("always") @@ -353,6 +349,7 @@ def test_decline_staking_reward_setter(self): def test_setters_preserve_other_staking_fields(self): import warnings + si = StakingInfo( pending_reward=Hbar.from_tinybars(100), staked_to_me=Hbar.from_tinybars(200), @@ -394,4 +391,3 @@ def test_constructor_legacy_kwargs_deprecated(self): info = AccountInfo(decline_staking_reward=True) assert info.staking_info.decline_reward is True assert any(issubclass(x.category, DeprecationWarning) for x in w) - diff --git a/tests/unit/account_records_query_test.py b/tests/unit/account_records_query_test.py index c5de3c5bc..d5363719d 100644 --- a/tests/unit/account_records_query_test.py +++ b/tests/unit/account_records_query_test.py @@ -2,6 +2,8 @@ Unit tests for the AccountRecordsQuery class. """ +from __future__ import annotations + from unittest.mock import Mock import pytest @@ -23,6 +25,7 @@ from hiero_sdk_python.response_code import ResponseCode from tests.unit.mock_server import mock_hedera_servers + pytestmark = pytest.mark.unit @@ -41,9 +44,7 @@ def test_execute_fails_with_missing_account_id(mock_client): """Test request creation with missing Account ID.""" query = AccountRecordsQuery() - with pytest.raises( - ValueError, match="Account ID must be set before making the request." - ): + with pytest.raises(ValueError, match="Account ID must be set before making the request."): query.execute(mock_client) diff --git a/tests/unit/account_update_transaction_test.py b/tests/unit/account_update_transaction_test.py index b5e978c0e..b503c6dde 100644 --- a/tests/unit/account_update_transaction_test.py +++ b/tests/unit/account_update_transaction_test.py @@ -2,6 +2,8 @@ Test cases for the AccountUpdateTransaction class. """ +from __future__ import annotations + from unittest.mock import MagicMock import pytest @@ -35,6 +37,7 @@ from hiero_sdk_python.response_code import ResponseCode from tests.unit.mock_server import mock_hedera_servers + pytestmark = pytest.mark.unit @@ -77,9 +80,7 @@ def test_constructor_with_account_params(): assert account_tx.expiration_time == expiration_time assert account_tx.max_automatic_token_associations == max_associations assert account_tx.staked_account_id == staked_account_id - assert ( - account_tx.staked_node_id is None - ) # Should be cleared when staked_account_id is set + assert account_tx.staked_node_id is None # Should be cleared when staked_account_id is set assert account_tx.decline_staking_reward == decline_reward @@ -193,9 +194,7 @@ def test_set_methods_require_not_frozen(mock_client): ] for method_name, value in test_cases: - with pytest.raises( - Exception, match="Transaction is immutable; it has been frozen" - ): + with pytest.raises(Exception, match="Transaction is immutable; it has been frozen"): getattr(account_tx, method_name)(value) zero_arg_methods = [ @@ -204,9 +203,7 @@ def test_set_methods_require_not_frozen(mock_client): ] for method_name in zero_arg_methods: - with pytest.raises( - Exception, match="Transaction is immutable; it has been frozen" - ): + with pytest.raises(Exception, match="Transaction is immutable; it has been frozen"): getattr(account_tx, method_name)() @@ -239,22 +236,12 @@ def test_build_transaction_body(mock_account_ids): transaction_body = account_tx.build_transaction_body() - assert ( - transaction_body.cryptoUpdateAccount.accountIDToUpdate == account_id._to_proto() - ) + assert transaction_body.cryptoUpdateAccount.accountIDToUpdate == account_id._to_proto() assert transaction_body.cryptoUpdateAccount.key == public_key._to_proto() - assert ( - transaction_body.cryptoUpdateAccount.autoRenewPeriod - == auto_renew_period._to_proto() - ) + assert transaction_body.cryptoUpdateAccount.autoRenewPeriod == auto_renew_period._to_proto() assert transaction_body.cryptoUpdateAccount.memo == StringValue(value=account_memo) - assert transaction_body.cryptoUpdateAccount.receiverSigRequiredWrapper == BoolValue( - value=receiver_sig_required - ) - assert ( - transaction_body.cryptoUpdateAccount.expirationTime - == expiration_time._to_protobuf() - ) + assert transaction_body.cryptoUpdateAccount.receiverSigRequiredWrapper == BoolValue(value=receiver_sig_required) + assert transaction_body.cryptoUpdateAccount.expirationTime == expiration_time._to_protobuf() def test_build_transaction_body_with_optional_fields(mock_account_ids): @@ -271,24 +258,17 @@ def test_build_transaction_body_with_optional_fields(mock_account_ids): transaction_body = account_tx.build_transaction_body() - assert ( - transaction_body.cryptoUpdateAccount.accountIDToUpdate == account_id._to_proto() - ) + assert transaction_body.cryptoUpdateAccount.accountIDToUpdate == account_id._to_proto() # When key is None, the key field should not be set in the protobuf assert not transaction_body.cryptoUpdateAccount.HasField("key") # When account_memo is None, the memo field should not be set in the protobuf assert not transaction_body.cryptoUpdateAccount.HasField("memo") # When receiver_signature_required is None, the field should not be set - assert not transaction_body.cryptoUpdateAccount.HasField( - "receiverSigRequiredWrapper" - ) + assert not transaction_body.cryptoUpdateAccount.HasField("receiverSigRequiredWrapper") # When expiration_time is None, the expirationTime field should not be set assert not transaction_body.cryptoUpdateAccount.HasField("expirationTime") # auto_renew_period should still be set to default value - assert ( - transaction_body.cryptoUpdateAccount.autoRenewPeriod - == AUTO_RENEW_PERIOD._to_proto() - ) + assert transaction_body.cryptoUpdateAccount.autoRenewPeriod == AUTO_RENEW_PERIOD._to_proto() def test_build_transaction_body_account_memo_variants(mock_account_ids): @@ -336,25 +316,19 @@ def test_build_transaction_body_receiver_sig_required_variants(mock_account_ids) transaction_body = account_tx.build_transaction_body() # When receiver_signature_required is None, the field should not be set - assert not transaction_body.cryptoUpdateAccount.HasField( - "receiverSigRequiredWrapper" - ) + assert not transaction_body.cryptoUpdateAccount.HasField("receiverSigRequiredWrapper") account_tx.set_receiver_signature_required(True) transaction_body = account_tx.build_transaction_body() # When receiver_signature_required is set to True, the field should be set in the protobuf assert transaction_body.cryptoUpdateAccount.HasField("receiverSigRequiredWrapper") - assert transaction_body.cryptoUpdateAccount.receiverSigRequiredWrapper == BoolValue( - value=True - ) + assert transaction_body.cryptoUpdateAccount.receiverSigRequiredWrapper == BoolValue(value=True) account_tx.set_receiver_signature_required(False) transaction_body = account_tx.build_transaction_body() # When receiver_signature_required is set to False, the field should be set in the protobuf assert transaction_body.cryptoUpdateAccount.HasField("receiverSigRequiredWrapper") - assert transaction_body.cryptoUpdateAccount.receiverSigRequiredWrapper == BoolValue( - value=False - ) + assert transaction_body.cryptoUpdateAccount.receiverSigRequiredWrapper == BoolValue(value=False) def test_missing_account_id(): @@ -421,9 +395,7 @@ def test_account_update_transaction_can_execute(): # Create a response for the receipt query receipt_query_response = response_pb2.Response( transactionGetReceipt=transaction_get_receipt_pb2.TransactionGetReceiptResponse( - header=response_header_pb2.ResponseHeader( - nodeTransactionPrecheckCode=ResponseCode.OK - ), + header=response_header_pb2.ResponseHeader(nodeTransactionPrecheckCode=ResponseCode.OK), receipt=mock_receipt_proto, ) ) @@ -446,9 +418,7 @@ def test_account_update_transaction_can_execute(): receipt = transaction.execute(client) - assert ( - receipt.status == ResponseCode.SUCCESS - ), "Transaction should have succeeded" + assert receipt.status == ResponseCode.SUCCESS, "Transaction should have succeeded" def test_get_method(): @@ -503,9 +473,7 @@ def test_build_transaction_body_with_none_auto_renew_period(mock_account_ids): transaction_body = account_tx.build_transaction_body() - assert ( - transaction_body.cryptoUpdateAccount.accountIDToUpdate == account_id._to_proto() - ) + assert transaction_body.cryptoUpdateAccount.accountIDToUpdate == account_id._to_proto() # When auto_renew_period is None, the field should not be set in the protobuf assert not transaction_body.cryptoUpdateAccount.HasField("autoRenewPeriod") @@ -546,22 +514,12 @@ def test_build_scheduled_body(mock_account_ids): # Verify the transaction was built with account update type assert schedulable_body.HasField("cryptoUpdateAccount") - assert ( - schedulable_body.cryptoUpdateAccount.accountIDToUpdate == account_id._to_proto() - ) + assert schedulable_body.cryptoUpdateAccount.accountIDToUpdate == account_id._to_proto() assert schedulable_body.cryptoUpdateAccount.key == public_key._to_proto() - assert ( - schedulable_body.cryptoUpdateAccount.autoRenewPeriod - == auto_renew_period._to_proto() - ) + assert schedulable_body.cryptoUpdateAccount.autoRenewPeriod == auto_renew_period._to_proto() assert schedulable_body.cryptoUpdateAccount.memo == StringValue(value=account_memo) - assert schedulable_body.cryptoUpdateAccount.receiverSigRequiredWrapper == BoolValue( - value=receiver_sig_required - ) - assert ( - schedulable_body.cryptoUpdateAccount.expirationTime - == expiration_time._to_protobuf() - ) + assert schedulable_body.cryptoUpdateAccount.receiverSigRequiredWrapper == BoolValue(value=receiver_sig_required) + assert schedulable_body.cryptoUpdateAccount.expirationTime == expiration_time._to_protobuf() def test_constructor_with_new_fields(): @@ -582,9 +540,7 @@ def test_constructor_with_new_fields(): assert account_tx.account_id == account_id assert account_tx.max_automatic_token_associations == max_associations assert account_tx.staked_account_id == staked_account_id - assert ( - account_tx.staked_node_id is None - ) # Should be cleared when staked_account_id is set + assert account_tx.staked_node_id is None # Should be cleared when staked_account_id is set assert account_tx.decline_staking_reward is True @@ -744,17 +700,9 @@ def test_build_transaction_body_with_new_fields(mock_account_ids): transaction_body = account_tx.build_transaction_body() - assert ( - transaction_body.cryptoUpdateAccount.accountIDToUpdate == account_id._to_proto() - ) - assert ( - transaction_body.cryptoUpdateAccount.max_automatic_token_associations - == Int32Value(value=max_associations) - ) - assert ( - transaction_body.cryptoUpdateAccount.staked_account_id.accountNum - == staked_account_id.num - ) + assert transaction_body.cryptoUpdateAccount.accountIDToUpdate == account_id._to_proto() + assert transaction_body.cryptoUpdateAccount.max_automatic_token_associations == Int32Value(value=max_associations) + assert transaction_body.cryptoUpdateAccount.staked_account_id.accountNum == staked_account_id.num assert transaction_body.cryptoUpdateAccount.decline_reward == BoolValue(value=True) @@ -773,9 +721,7 @@ def test_build_transaction_body_with_staked_node_id(mock_account_ids): transaction_body = account_tx.build_transaction_body() - assert ( - transaction_body.cryptoUpdateAccount.accountIDToUpdate == account_id._to_proto() - ) + assert transaction_body.cryptoUpdateAccount.accountIDToUpdate == account_id._to_proto() assert transaction_body.cryptoUpdateAccount.staked_node_id == staked_node_id # staked_account_id should not be set assert not transaction_body.cryptoUpdateAccount.HasField("staked_account_id") @@ -795,9 +741,7 @@ def test_build_transaction_body_with_optional_new_fields_none(mock_account_ids): transaction_body = account_tx.build_transaction_body() # When new fields are None, they should not be set in the protobuf - assert not transaction_body.cryptoUpdateAccount.HasField( - "max_automatic_token_associations" - ) + assert not transaction_body.cryptoUpdateAccount.HasField("max_automatic_token_associations") assert not transaction_body.cryptoUpdateAccount.HasField("staked_account_id") assert not transaction_body.cryptoUpdateAccount.HasField("staked_node_id") assert not transaction_body.cryptoUpdateAccount.HasField("decline_reward") @@ -828,6 +772,7 @@ def test_build_transaction_body_with_cleared_staking(mock_account_ids): assert txn_body.staked_node_id == -1 assert not txn_body.HasField("staked_account_id") + def test_set_keylist_multiple_keys(): """Verify that a generic KeyList is properly converted to protobuf in AccountUpdateTransaction.""" tx = AccountUpdateTransaction() diff --git a/tests/unit/assessed_custom_fee_test.py b/tests/unit/assessed_custom_fee_test.py index dcc477f21..657432744 100644 --- a/tests/unit/assessed_custom_fee_test.py +++ b/tests/unit/assessed_custom_fee_test.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import pytest from hiero_sdk_python.account.account_id import AccountId @@ -43,10 +45,10 @@ def test_constructor_all_fields( assert fee.fee_collector_account_id == sample_account_id assert fee.effective_payer_account_ids == payers # Protect against breaking changes - assert hasattr(fee, 'amount') - assert hasattr(fee, 'token_id') - assert hasattr(fee, 'fee_collector_account_id') - assert hasattr(fee, 'effective_payer_account_ids') + assert hasattr(fee, "amount") + assert hasattr(fee, "token_id") + assert hasattr(fee, "fee_collector_account_id") + assert hasattr(fee, "effective_payer_account_ids") def test_constructor_hbar_case(sample_account_id: AccountId): @@ -71,6 +73,7 @@ def test_constructor_empty_payers(sample_account_id: AccountId, sample_token_id: assert fee.effective_payer_account_ids == [] assert fee.token_id == sample_token_id + def test_constructor_missing_fee_collector_raises(): """Verify that omitting fee_collector_account_id raises ValueError.""" with pytest.raises(ValueError, match="fee_collector_account_id is required"): @@ -80,6 +83,7 @@ def test_constructor_missing_fee_collector_raises(): fee_collector_account_id=None, ) + def test_from_proto_missing_token_id(sample_account_id: AccountId): """Verify that absence of token_id in protobuf correctly maps to None.""" proto = AssessedCustomFeeProto( @@ -95,6 +99,7 @@ def test_from_proto_missing_token_id(sample_account_id: AccountId): assert fee.fee_collector_account_id == sample_account_id assert fee.effective_payer_account_ids == [], "effective payers should default to empty list" + def test_from_proto_with_token_id(sample_account_id: AccountId, sample_token_id: TokenId): """Verify that token_id is correctly deserialized when present in proto.""" proto = AssessedCustomFeeProto( @@ -112,12 +117,14 @@ def test_from_proto_with_token_id(sample_account_id: AccountId, sample_token_id: assert fee.fee_collector_account_id == sample_account_id assert len(fee.effective_payer_account_ids) == 1 + def test_from_proto_missing_fee_collector_raises(): """Verify that missing fee_collector_account_id in proto raises ValueError.""" proto = AssessedCustomFeeProto(amount=750_000) with pytest.raises(ValueError, match="fee_collector_account_id is required"): AssessedCustomFee._from_proto(proto) + def test_to_proto_basic_fields( sample_account_id: AccountId, sample_token_id: TokenId, diff --git a/tests/unit/batch_transaction_test.py b/tests/unit/batch_transaction_test.py index 39164f3b4..eec1d2093 100644 --- a/tests/unit/batch_transaction_test.py +++ b/tests/unit/batch_transaction_test.py @@ -1,22 +1,24 @@ +from __future__ import annotations + from unittest.mock import MagicMock import pytest +from hiero_sdk_python.account.account_id import AccountId +from hiero_sdk_python.crypto.private_key import PrivateKey +from hiero_sdk_python.crypto.public_key import PublicKey from hiero_sdk_python.hapi.services import ( response_header_pb2, response_pb2, transaction_get_receipt_pb2, ) +from hiero_sdk_python.hapi.services.transaction_pb2 import AtomicBatchTransactionBody from hiero_sdk_python.hapi.services.transaction_receipt_pb2 import ( TransactionReceipt as TransactionReceiptProto, ) from hiero_sdk_python.hapi.services.transaction_response_pb2 import ( TransactionResponse as TransactionResponseProto, ) -from hiero_sdk_python.account.account_id import AccountId -from hiero_sdk_python.crypto.private_key import PrivateKey -from hiero_sdk_python.crypto.public_key import PublicKey -from hiero_sdk_python.hapi.services.transaction_pb2 import AtomicBatchTransactionBody from hiero_sdk_python.response_code import ResponseCode from hiero_sdk_python.system.freeze_transaction import FreezeTransaction from hiero_sdk_python.transaction.batch_transaction import BatchTransaction @@ -25,6 +27,7 @@ from hiero_sdk_python.transaction.transfer_transaction import TransferTransaction from tests.unit.mock_server import mock_hedera_servers + pytestmark = pytest.mark.unit @@ -52,12 +55,8 @@ def _make_tx(batch_key=None, freeze=True): def test_constructor_without_params_creates_empty_inner_transactions(): """Test create batch transaction without constructor params.""" batch_tx = BatchTransaction() - assert ( - batch_tx.inner_transactions is not None - ), "inner_transactions should not be none" - assert ( - len(batch_tx.inner_transactions) == 0 - ), "inner_transactions should be empty by default" + assert batch_tx.inner_transactions is not None, "inner_transactions should not be none" + assert len(batch_tx.inner_transactions) == 0, "inner_transactions should be empty by default" def test_constructor_with_params_accepts_valid_inner_transactions(mock_tx): @@ -158,14 +157,10 @@ def test_set_inner_transactions_invalid_param(mock_tx, mock_client): ValueError, match="Transaction type FreezeTransaction is not allowed in a batch transaction", ): - batch_tx.set_inner_transactions( - [FreezeTransaction().set_batch_key(batch_key).freeze_with(mock_client)] - ) + batch_tx.set_inner_transactions([FreezeTransaction().set_batch_key(batch_key).freeze_with(mock_client)]) # BatchTransaction not allowed - nested_batch = BatchTransaction( - inner_transactions=[mock_tx(batch_key=batch_key, freeze=True)] - ) + nested_batch = BatchTransaction(inner_transactions=[mock_tx(batch_key=batch_key, freeze=True)]) nested_batch.set_batch_key(batch_key) nested_batch.freeze_with(mock_client) with pytest.raises( @@ -175,7 +170,7 @@ def test_set_inner_transactions_invalid_param(mock_tx, mock_client): batch_tx.set_inner_transactions([nested_batch]) -def test_add_inner_transaction_valid_param(mock_tx, mock_client): +def test_add_inner_transaction_valid_param(mock_tx): """Test add_inner_transaction method adds a inner_transactions.""" batch_key = PrivateKey.generate() batch_tx = BatchTransaction() @@ -205,14 +200,10 @@ def test_add_inner_transaction_method_invalid_param(mock_tx, mock_client): ValueError, match="Transaction type FreezeTransaction is not allowed in a batch transaction", ): - batch_tx.add_inner_transaction( - FreezeTransaction().set_batch_key(batch_key).freeze_with(mock_client) - ) + batch_tx.add_inner_transaction(FreezeTransaction().set_batch_key(batch_key).freeze_with(mock_client)) # BatchTransaction - nested_batch = BatchTransaction( - inner_transactions=[mock_tx(batch_key=batch_key, freeze=True)] - ) + nested_batch = BatchTransaction(inner_transactions=[mock_tx(batch_key=batch_key, freeze=True)]) nested_batch.set_batch_key(batch_key) nested_batch.freeze_with(mock_client) with pytest.raises( @@ -226,9 +217,7 @@ def test_get_inner_transactions_ids_returns_transaction_ids(mock_tx): """Test get_transaction_ids methods returns transaction_ids.""" batch_key = PrivateKey.generate() batch_tx = BatchTransaction() - assert ( - batch_tx.get_inner_transaction_ids() == [] - ), "No inner transactions should return an empty list" + assert batch_tx.get_inner_transaction_ids() == [], "No inner transactions should return an empty list" transaction = mock_tx(batch_key=batch_key, freeze=True) batch_tx.add_inner_transaction(transaction) @@ -278,18 +267,12 @@ def test_batchify_sets_required_fields(mock_account_ids, mock_client): .batchify(mock_client, batch_key) ) - assert ( - tx._transaction_body_bytes is not None - ), "batchify should set _transaction_body_bytes" + assert tx._transaction_body_bytes is not None, "batchify should set _transaction_body_bytes" assert tx.batch_key == batch_key - assert tx.node_account_id == AccountId( - 0, 0, 0 - ), "node_account_id for batched tx should be 0.0.0" + assert tx.node_account_id == AccountId(0, 0, 0), "node_account_id for batched tx should be 0.0.0" -def test_round_trip_to_bytes_and_back_preserves_inner_transactions( - mock_account_ids, mock_client -): +def test_round_trip_to_bytes_and_back_preserves_inner_transactions(mock_account_ids, mock_client): """Test round trip of converting transaction to_bytes and from_bytes.""" sender, receiver, _, _, _ = mock_account_ids batch_key = PrivateKey.generate() @@ -301,12 +284,7 @@ def test_round_trip_to_bytes_and_back_preserves_inner_transactions( .batchify(mock_client, batch_key) ) - batch_tx = ( - BatchTransaction() - .add_inner_transaction(transfer_tx) - .freeze_with(mock_client) - .sign(batch_key) - ) + batch_tx = BatchTransaction().add_inner_transaction(transfer_tx).freeze_with(mock_client).sign(batch_key) batch_tx_bytes = batch_tx.to_bytes() assert batch_tx_bytes and len(batch_tx_bytes) > 0 @@ -324,9 +302,7 @@ def test_round_trip_to_bytes_and_back_preserves_inner_transactions( def test_sign_transaction(mock_client, mock_tx): """Test signing the batch transaction with a private key.""" batch_tx = BatchTransaction() - batch_tx.set_inner_transactions( - [mock_tx(batch_key=PrivateKey.generate(), freeze=True)] - ) + batch_tx.set_inner_transactions([mock_tx(batch_key=PrivateKey.generate(), freeze=True)]) private_key = MagicMock() private_key.sign.return_value = b"signature" @@ -338,9 +314,7 @@ def test_sign_transaction(mock_client, mock_tx): node_id = mock_client.network.current_node._account_id body_bytes = batch_tx._transaction_body_bytes[node_id] - assert ( - body_bytes in batch_tx._signature_map - ), "signature map must contain an entry for the tx body bytes" + assert body_bytes in batch_tx._signature_map, "signature map must contain an entry for the tx body bytes" sig_pairs = batch_tx._signature_map[body_bytes].sigPair assert len(sig_pairs) == 1 sig_pair = sig_pairs[0] @@ -351,9 +325,7 @@ def test_sign_transaction(mock_client, mock_tx): def test_to_proto(mock_client, mock_tx): """Test converting the batch transaction to protobuf format after signing.""" batch_tx = BatchTransaction() - batch_tx.set_inner_transactions( - [mock_tx(batch_key=PrivateKey.generate(), freeze=True)] - ) + batch_tx.set_inner_transactions([mock_tx(batch_key=PrivateKey.generate(), freeze=True)]) private_key = MagicMock() private_key.sign.return_value = b"signature" @@ -363,9 +335,7 @@ def test_to_proto(mock_client, mock_tx): batch_tx.sign(private_key) proto = batch_tx._to_proto() - assert getattr( - proto, "signedTransactionBytes", None - ), "proto must include signedTransactionBytes" + assert getattr(proto, "signedTransactionBytes", None), "proto must include signedTransactionBytes" assert len(proto.signedTransactionBytes) > 0 @@ -383,9 +353,7 @@ def test_batch_transaction_execute_successful(mock_account_ids, mock_client): receipt_query_response = response_pb2.Response( transactionGetReceipt=transaction_get_receipt_pb2.TransactionGetReceiptResponse( - header=response_header_pb2.ResponseHeader( - nodeTransactionPrecheckCode=ResponseCode.OK - ), + header=response_header_pb2.ResponseHeader(nodeTransactionPrecheckCode=ResponseCode.OK), receipt=mock_receipt_proto, ) ) @@ -408,9 +376,7 @@ def test_batch_transaction_execute_successful(mock_account_ids, mock_client): ) receipt = transaction.execute(client) - assert ( - receipt.status == ResponseCode.SUCCESS - ), f"Transaction should have succeeded, got {receipt.status}" + assert receipt.status == ResponseCode.SUCCESS, f"Transaction should have succeeded, got {receipt.status}" def test_batch_key_accepts_public_key(mock_account_ids): @@ -455,9 +421,7 @@ def test_batchify_with_public_key(mock_client, mock_account_ids): assert tx._transaction_body_bytes # Should be frozen -def test_batch_transaction_with_public_key_inner_transactions( - mock_client, mock_account_ids -): +def test_batch_transaction_with_public_key_inner_transactions(mock_client, mock_account_ids): """Test BatchTransaction can accept inner transactions with PublicKey batch_keys.""" sender, receiver, _, _, _ = mock_account_ids diff --git a/tests/unit/client_test.py b/tests/unit/client_test.py index f2c0fc41f..6e722c7b9 100644 --- a/tests/unit/client_test.py +++ b/tests/unit/client_test.py @@ -2,18 +2,21 @@ Unit tests for Client methods (eg. from_env, for_testnet, for_mainnet, for_previewnet). """ -from decimal import Decimal +from __future__ import annotations + import os +from decimal import Decimal +from unittest.mock import patch + import pytest -from unittest.mock import MagicMock, patch +from hiero_sdk_python import AccountId, Client, PrivateKey from hiero_sdk_python.client import client as client_module - -from hiero_sdk_python import Client, AccountId, PrivateKey from hiero_sdk_python.hbar import Hbar from hiero_sdk_python.node import _Node from hiero_sdk_python.transaction.transaction_id import TransactionId + pytestmark = pytest.mark.unit @@ -73,20 +76,24 @@ def test_from_env_missing_operator_id_raises_error(): """Test that from_env raises ValueError when OPERATOR_ID is missing.""" dummy_key = PrivateKey.generate_ed25519().to_string_der() - with patch.object(client_module, "load_dotenv"): - with patch.dict(os.environ, {"OPERATOR_KEY": dummy_key}, clear=True): - with pytest.raises(ValueError) as exc_info: - Client.from_env() - assert "OPERATOR_ID" in str(exc_info.value) + with ( + patch.object(client_module, "load_dotenv"), + patch.dict(os.environ, {"OPERATOR_KEY": dummy_key}, clear=True), + pytest.raises(ValueError) as exc_info, + ): + Client.from_env() + assert "OPERATOR_ID" in str(exc_info.value) def test_from_env_missing_operator_key_raises_error(): """Test that from_env raises ValueError when OPERATOR_KEY is missing.""" - with patch.object(client_module, "load_dotenv"): - with patch.dict(os.environ, {"OPERATOR_ID": "0.0.1234"}, clear=True): - with pytest.raises(ValueError) as exc_info: - Client.from_env() - assert "OPERATOR_KEY" in str(exc_info.value) + with ( + patch.object(client_module, "load_dotenv"), + patch.dict(os.environ, {"OPERATOR_ID": "0.0.1234"}, clear=True), + pytest.raises(ValueError) as exc_info, + ): + Client.from_env() + assert "OPERATOR_KEY" in str(exc_info.value) def test_from_env_with_valid_credentials(): @@ -99,12 +106,11 @@ def test_from_env_with_valid_credentials(): "OPERATOR_KEY": test_key_str, } - with patch.object(client_module, "load_dotenv"): - with patch.dict(os.environ, env_vars, clear=True): - client = Client.from_env() - assert isinstance(client, Client) - assert client.operator_account_id == AccountId.from_string("0.0.1234") - client.close() + with patch.object(client_module, "load_dotenv"), patch.dict(os.environ, env_vars, clear=True): + client = Client.from_env() + assert isinstance(client, Client) + assert client.operator_account_id == AccountId.from_string("0.0.1234") + client.close() def test_from_env_with_explicit_network_parameter(): @@ -118,11 +124,10 @@ def test_from_env_with_explicit_network_parameter(): "NETWORK": "testnet", } - with patch.object(client_module, "load_dotenv"): - with patch.dict(os.environ, env_vars, clear=True): - client = Client.from_env(network="mainnet") - assert client.network.network == "mainnet" - client.close() + with patch.object(client_module, "load_dotenv"), patch.dict(os.environ, env_vars, clear=True): + client = Client.from_env(network="mainnet") + assert client.network.network == "mainnet" + client.close() def test_from_env_defaults_to_testnet(): @@ -135,11 +140,10 @@ def test_from_env_defaults_to_testnet(): "OPERATOR_KEY": test_key_str, } - with patch.object(client_module, "load_dotenv"): - with patch.dict(os.environ, env_vars, clear=True): - client = Client.from_env() - assert client.network.network == "testnet" - client.close() + with patch.object(client_module, "load_dotenv"), patch.dict(os.environ, env_vars, clear=True): + client = Client.from_env() + assert client.network.network == "testnet" + client.close() def test_from_env_uses_network_env_var(): @@ -153,11 +157,10 @@ def test_from_env_uses_network_env_var(): "NETWORK": "previewnet", } - with patch.object(client_module, "load_dotenv"): - with patch.dict(os.environ, env_vars, clear=True): - client = Client.from_env() - assert client.network.network == "previewnet" - client.close() + with patch.object(client_module, "load_dotenv"), patch.dict(os.environ, env_vars, clear=True): + client = Client.from_env() + assert client.network.network == "previewnet" + client.close() def test_from_env_with_invalid_network_name(): @@ -168,10 +171,12 @@ def test_from_env_with_invalid_network_name(): "OPERATOR_KEY": test_key.to_string_der(), } - with patch.object(client_module, "load_dotenv"): - with patch.dict(os.environ, env_vars, clear=True): - with pytest.raises(ValueError, match="Invalid network name"): - Client.from_env(network="mars_network") + with ( + patch.object(client_module, "load_dotenv"), + patch.dict(os.environ, env_vars, clear=True), + pytest.raises(ValueError, match="Invalid network name"), + ): + Client.from_env(network="mars_network") def test_from_env_with_malformed_operator_id(): @@ -182,10 +187,12 @@ def test_from_env_with_malformed_operator_id(): "OPERATOR_KEY": test_key.to_string_der(), } - with patch.object(client_module, "load_dotenv"): - with patch.dict(os.environ, env_vars, clear=True): - with pytest.raises(ValueError, match="Invalid account ID"): - Client.from_env() + with ( + patch.object(client_module, "load_dotenv"), + patch.dict(os.environ, env_vars, clear=True), + pytest.raises(ValueError, match="Invalid account ID"), + ): + Client.from_env() def test_from_env_with_malformed_operator_key(): @@ -195,10 +202,12 @@ def test_from_env_with_malformed_operator_key(): "OPERATOR_KEY": "not-a-valid-key", } - with patch.object(client_module, "load_dotenv"): - with patch.dict(os.environ, env_vars, clear=True): - with pytest.raises(ValueError): - Client.from_env() + with ( + patch.object(client_module, "load_dotenv"), + patch.dict(os.environ, env_vars, clear=True), + pytest.raises(ValueError), + ): + Client.from_env() @pytest.mark.parametrize( @@ -220,9 +229,7 @@ def test_set_default_max_query_payment_valid_param(valid_amount, expected): assert client.default_max_query_payment == expected -@pytest.mark.parametrize( - "negative_amount", [-1, -0.1, Decimal("-0.1"), Decimal("-1"), Hbar(-1)] -) +@pytest.mark.parametrize("negative_amount", [-1, -0.1, Decimal("-0.1"), Decimal("-1"), Hbar(-1)]) def test_set_default_max_query_payment_negative_value(negative_amount): """Test set_default_max_query_payment for negative amount values.""" client = Client.for_testnet() @@ -238,10 +245,7 @@ def test_set_default_max_query_payment_invalid_param(invalid_amount): with pytest.raises( TypeError, - match=( - "max_query_payment must be int, float, Decimal, or Hbar, " - f"got {type(invalid_amount).__name__}" - ), + match=(f"max_query_payment must be int, float, Decimal, or Hbar, got {type(invalid_amount).__name__}"), ): client.set_default_max_query_payment(invalid_amount) @@ -314,16 +318,12 @@ def test_set_grpc_deadline_with_invalid_type(invalid_grpc_deadline): client.set_grpc_deadline(invalid_grpc_deadline) -@pytest.mark.parametrize( - "invalid_grpc_deadline", [0, -10, 0.0, -2.3, float("inf"), float("nan")] -) +@pytest.mark.parametrize("invalid_grpc_deadline", [0, -10, 0.0, -2.3, float("inf"), float("nan")]) def test_set_grpc_deadline_with_invalid_value(invalid_grpc_deadline): """Test that set_grpc_deadline raises ValueError for non-positive values.""" client = Client.for_testnet() - with pytest.raises( - ValueError, match="grpc_deadline must be a finite value greater than 0" - ): + with pytest.raises(ValueError, match="grpc_deadline must be a finite value greater than 0"): client.set_grpc_deadline(invalid_grpc_deadline) @@ -352,16 +352,12 @@ def test_set_request_timeout_with_invalid_type(invalid_request_timeout): client.set_request_timeout(invalid_request_timeout) -@pytest.mark.parametrize( - "invalid_request_timeout", [0, -10, 0.0, -2.3, float("inf"), float("nan")] -) +@pytest.mark.parametrize("invalid_request_timeout", [0, -10, 0.0, -2.3, float("inf"), float("nan")]) def test_set_request_timeout_with_invalid_value(invalid_request_timeout): """Test that set_request_timeout raises ValueError for non-positive values.""" client = Client.for_testnet() - with pytest.raises( - ValueError, match="request_timeout must be a finite value greater than 0" - ): + with pytest.raises(ValueError, match="request_timeout must be a finite value greater than 0"): client.set_request_timeout(invalid_request_timeout) @@ -390,9 +386,7 @@ def test_set_min_backoff_with_invalid_type(invalid_min_backoff): client.set_min_backoff(invalid_min_backoff) -@pytest.mark.parametrize( - "invalid_min_backoff", [-1, -10, float("inf"), float("-inf"), float("nan")] -) +@pytest.mark.parametrize("invalid_min_backoff", [-1, -10, float("inf"), float("-inf"), float("nan")]) def test_set_min_backoff_with_invalid_value(invalid_min_backoff): """Test that set_min_backoff raises ValueError for invalid values.""" client = Client.for_testnet() @@ -435,9 +429,7 @@ def test_set_max_backoff_with_invalid_type(invalid_max_backoff): client.set_max_backoff(invalid_max_backoff) -@pytest.mark.parametrize( - "invalid_max_backoff", [-1, -10, float("inf"), float("-inf"), float("nan")] -) +@pytest.mark.parametrize("invalid_max_backoff", [-1, -10, float("inf"), float("-inf"), float("nan")]) def test_set_max_backoff_with_invalid_value(invalid_max_backoff): """Test that set_max_backoff raises ValueError for invalid values.""" client = Client.for_testnet() @@ -469,6 +461,7 @@ def test_update_network_refreshes_nodes_and_returns_self(): client.close() + def test_warning_when_grpc_deadline_exceeds_request_timeout(): """Warn when grpc_deadline is greater than request_timeout.""" client = Client.for_testnet() @@ -498,7 +491,7 @@ def test_generate_transaction_id_requires_operator_set(): client.close() -def test_generate_transaction_id_returns_transaction_id(monkeypatch): +def test_generate_transaction_id_returns_transaction_id(): """Test that generate_transaction_id returns a TransactionId object when operator is set.""" client = Client.for_testnet() client.operator_account_id = AccountId(0, 0, 1234) diff --git a/tests/unit/conftest.py b/tests/unit/conftest.py index edbbfc897..be260a7cc 100644 --- a/tests/unit/conftest.py +++ b/tests/unit/conftest.py @@ -1,5 +1,6 @@ +from __future__ import annotations + import hashlib -import time import pytest @@ -13,7 +14,6 @@ from hiero_sdk_python.file.file_id import FileId from hiero_sdk_python.logger.log_level import LogLevel from hiero_sdk_python.node import _Node -from hiero_sdk_python.tokens.token_id import TokenId from hiero_sdk_python.tokens.nft_id import NftId from hiero_sdk_python.tokens.token_id import TokenId from hiero_sdk_python.transaction.transaction_id import TransactionId @@ -52,7 +52,7 @@ def amount(): @pytest.fixture def metadata(): """Fixture to provide mock metadata for NFTs.""" - return [b'a'] + return [b"a"] @pytest.fixture diff --git a/tests/unit/contract_bytecode_query_test.py b/tests/unit/contract_bytecode_query_test.py index 0ca7589b1..ee7435f06 100644 --- a/tests/unit/contract_bytecode_query_test.py +++ b/tests/unit/contract_bytecode_query_test.py @@ -2,6 +2,8 @@ Unit tests for ContractBytecodeQuery. """ +from __future__ import annotations + from unittest.mock import Mock import pytest @@ -17,6 +19,7 @@ from hiero_sdk_python.response_code import ResponseCode from tests.unit.mock_server import mock_hedera_servers + pytestmark = pytest.mark.unit @@ -60,9 +63,7 @@ def test_execute_fails_with_missing_contract_id(mock_client): """Test request creation with missing Contract ID.""" query = ContractBytecodeQuery() - with pytest.raises( - ValueError, match="Contract ID must be set before making the request." - ): + with pytest.raises(ValueError, match="Contract ID must be set before making the request."): query.execute(mock_client) @@ -84,9 +85,7 @@ def test_make_request_with_missing_contract_id(): """Test _make_request raises ValueError when contract ID is missing.""" query = ContractBytecodeQuery() - with pytest.raises( - ValueError, match="Contract ID must be set before making the request." - ): + with pytest.raises(ValueError, match="Contract ID must be set before making the request."): query._make_request() diff --git a/tests/unit/contract_call_query_test.py b/tests/unit/contract_call_query_test.py index 4fd03cb60..e7c9f8ac2 100644 --- a/tests/unit/contract_call_query_test.py +++ b/tests/unit/contract_call_query_test.py @@ -2,6 +2,8 @@ Unit tests for the ContractCallQuery class. """ +from __future__ import annotations + from unittest.mock import Mock import pytest @@ -15,15 +17,16 @@ from hiero_sdk_python.contract.contract_function_result import ContractFunctionResult from hiero_sdk_python.contract.contract_id import ContractId from hiero_sdk_python.hapi.services import ( + contract_call_local_pb2, contract_types_pb2, response_header_pb2, response_pb2, - contract_call_local_pb2, ) from hiero_sdk_python.hapi.services.query_header_pb2 import ResponseType from hiero_sdk_python.response_code import ResponseCode from tests.unit.mock_server import mock_hedera_servers + pytestmark = pytest.mark.unit @@ -60,9 +63,7 @@ def test_setters_combined(): max_result_size = 1024 sender_account = AccountId(0, 0, 1) function_params_only = ContractFunctionParameters().add_string("test_param") - function_params_with_name = ContractFunctionParameters("testFunction").add_string( - "test_param" - ) + function_params_with_name = ContractFunctionParameters("testFunction").add_string("test_param") query = ( ContractCallQuery() @@ -89,19 +90,14 @@ def test_setters_combined(): # Test set_function with default parameters query.set_function("testFunction") - assert ( - query.function_parameters - == ContractFunctionParameters("testFunction").to_bytes() - ) + assert query.function_parameters == ContractFunctionParameters("testFunction").to_bytes() def test_execute_fails_with_missing_contract_id(mock_client): """Test request creation with missing Contract ID.""" query = ContractCallQuery() - with pytest.raises( - ValueError, match="Contract ID must be set before making the request." - ): + with pytest.raises(ValueError, match="Contract ID must be set before making the request."): query.execute(mock_client) diff --git a/tests/unit/contract_create_transaction_test.py b/tests/unit/contract_create_transaction_test.py index 578229dbf..33f410257 100644 --- a/tests/unit/contract_create_transaction_test.py +++ b/tests/unit/contract_create_transaction_test.py @@ -2,6 +2,8 @@ Unit tests for the ContractCreateTransaction class. """ +from __future__ import annotations + import pytest from hiero_sdk_python.account.account_id import AccountId @@ -35,6 +37,7 @@ from hiero_sdk_python.response_code import ResponseCode from tests.unit.mock_server import mock_hedera_servers + pytestmark = pytest.mark.unit @@ -72,9 +75,7 @@ def test_constructor_with_parameters(contract_params): contract_memo=contract_params["contract_memo"], bytecode=contract_params["bytecode"], auto_renew_account_id=contract_params["auto_renew_account_id"], - max_automatic_token_associations=contract_params[ - "max_automatic_token_associations" - ], + max_automatic_token_associations=contract_params["max_automatic_token_associations"], staked_account_id=contract_params["staked_account_id"], staked_node_id=contract_params["staked_node_id"], decline_reward=contract_params["decline_reward"], @@ -92,10 +93,7 @@ def test_constructor_with_parameters(contract_params): assert contract_tx.contract_memo == contract_params["contract_memo"] assert contract_tx.bytecode == contract_params["bytecode"] assert contract_tx.auto_renew_account_id == contract_params["auto_renew_account_id"] - assert ( - contract_tx.max_automatic_token_associations - == contract_params["max_automatic_token_associations"] - ) + assert contract_tx.max_automatic_token_associations == contract_params["max_automatic_token_associations"] assert contract_tx.staked_account_id == contract_params["staked_account_id"] assert contract_tx.staked_node_id == contract_params["staked_node_id"] assert contract_tx.decline_reward == contract_params["decline_reward"] @@ -122,9 +120,7 @@ def test_constructor_default_values(): assert contract_tx.decline_reward is None -def test_build_transaction_body_with_bytecode_file_id( - mock_account_ids, contract_params -): +def test_build_transaction_body_with_bytecode_file_id(mock_account_ids, contract_params): """Test building a contract create transaction body with bytecode file ID.""" operator_id, _, node_account_id, _, _ = mock_account_ids @@ -145,26 +141,12 @@ def test_build_transaction_body_with_bytecode_file_id( transaction_body = contract_tx.build_transaction_body() - assert ( - transaction_body.contractCreateInstance.fileID - == contract_params["bytecode_file_id"]._to_proto() - ) + assert transaction_body.contractCreateInstance.fileID == contract_params["bytecode_file_id"]._to_proto() assert transaction_body.contractCreateInstance.gas == contract_params["gas"] - assert ( - transaction_body.contractCreateInstance.initialBalance - == contract_params["initial_balance"] - ) - assert ( - transaction_body.contractCreateInstance.adminKey - == contract_params["admin_key"]._to_proto() - ) - assert ( - transaction_body.contractCreateInstance.memo == contract_params["contract_memo"] - ) - assert ( - transaction_body.contractCreateInstance.constructorParameters - == contract_params["parameters"] - ) + assert transaction_body.contractCreateInstance.initialBalance == contract_params["initial_balance"] + assert transaction_body.contractCreateInstance.adminKey == contract_params["admin_key"]._to_proto() + assert transaction_body.contractCreateInstance.memo == contract_params["contract_memo"] + assert transaction_body.contractCreateInstance.constructorParameters == contract_params["parameters"] assert transaction_body.contractCreateInstance.initcode == b"" @@ -186,14 +168,9 @@ def test_build_transaction_body_with_bytecode(mock_account_ids, contract_params) transaction_body = contract_tx.build_transaction_body() - assert ( - transaction_body.contractCreateInstance.initcode == contract_params["bytecode"] - ) + assert transaction_body.contractCreateInstance.initcode == contract_params["bytecode"] assert transaction_body.contractCreateInstance.gas == contract_params["gas"] - assert ( - transaction_body.contractCreateInstance.initialBalance - == contract_params["initial_balance"] - ) + assert transaction_body.contractCreateInstance.initialBalance == contract_params["initial_balance"] assert not transaction_body.contractCreateInstance.HasField("fileID") @@ -223,26 +200,12 @@ def test_build_scheduled_body(mock_account_ids, contract_params): assert isinstance(schedulable_body, SchedulableTransactionBody) # Verify fields in the schedulable body - assert ( - schedulable_body.contractCreateInstance.fileID - == contract_params["bytecode_file_id"]._to_proto() - ) + assert schedulable_body.contractCreateInstance.fileID == contract_params["bytecode_file_id"]._to_proto() assert schedulable_body.contractCreateInstance.gas == contract_params["gas"] - assert ( - schedulable_body.contractCreateInstance.initialBalance - == contract_params["initial_balance"] - ) - assert ( - schedulable_body.contractCreateInstance.adminKey - == contract_params["admin_key"]._to_proto() - ) - assert ( - schedulable_body.contractCreateInstance.memo == contract_params["contract_memo"] - ) - assert ( - schedulable_body.contractCreateInstance.constructorParameters - == contract_params["parameters"] - ) + assert schedulable_body.contractCreateInstance.initialBalance == contract_params["initial_balance"] + assert schedulable_body.contractCreateInstance.adminKey == contract_params["admin_key"]._to_proto() + assert schedulable_body.contractCreateInstance.memo == contract_params["contract_memo"] + assert schedulable_body.contractCreateInstance.constructorParameters == contract_params["parameters"] assert schedulable_body.contractCreateInstance.initcode == b"" @@ -251,15 +214,11 @@ def test_build_transaction_body_validation_errors(): # Test missing bytecode_file_id and bytecode contract_tx = ContractCreateTransaction() - with pytest.raises( - ValueError, match="Either bytecode_file_id or bytecode must be provided" - ): + with pytest.raises(ValueError, match="Either bytecode_file_id or bytecode must be provided"): contract_tx.build_transaction_body() # Test missing gas - contract_tx = ContractCreateTransaction( - contract_params=ContractCreateParams(bytecode=b"test bytecode") - ) + contract_tx = ContractCreateTransaction(contract_params=ContractCreateParams(bytecode=b"test bytecode")) with pytest.raises(ValueError, match="Gas limit must be provided"): contract_tx.build_transaction_body() @@ -319,9 +278,7 @@ def test_set_methods(contract_params): def test_set_bytecode_clears_file_id(contract_params): """Test that setting bytecode clears the bytecode_file_id.""" contract_tx = ContractCreateTransaction( - contract_params=ContractCreateParams( - bytecode_file_id=contract_params["bytecode_file_id"] - ) + contract_params=ContractCreateParams(bytecode_file_id=contract_params["bytecode_file_id"]) ) assert contract_tx.bytecode_file_id is not None @@ -386,9 +343,7 @@ def test_set_methods_require_not_frozen(mock_client, contract_params): ] for method_name, value in test_cases: - with pytest.raises( - Exception, match="Transaction is immutable; it has been frozen" - ): + with pytest.raises(Exception, match="Transaction is immutable; it has been frozen"): getattr(contract_tx, method_name)(value) @@ -407,9 +362,7 @@ def test_contract_create_transaction_can_execute(): # Create a response for the receipt query receipt_query_response = response_pb2.Response( transactionGetReceipt=transaction_get_receipt_pb2.TransactionGetReceiptResponse( - header=response_header_pb2.ResponseHeader( - nodeTransactionPrecheckCode=ResponseCode.OK - ), + header=response_header_pb2.ResponseHeader(nodeTransactionPrecheckCode=ResponseCode.OK), receipt=mock_receipt_proto, ) ) @@ -431,9 +384,7 @@ def test_contract_create_transaction_can_execute(): receipt = transaction.execute(client) - assert ( - receipt.status == ResponseCode.SUCCESS - ), "Transaction should have succeeded" + assert receipt.status == ResponseCode.SUCCESS, "Transaction should have succeeded" assert receipt.contract_id.contract == 1234 diff --git a/tests/unit/contract_delete_transaction_test.py b/tests/unit/contract_delete_transaction_test.py index 38b6abe48..efd56b635 100644 --- a/tests/unit/contract_delete_transaction_test.py +++ b/tests/unit/contract_delete_transaction_test.py @@ -2,6 +2,8 @@ Test cases for the ContractDeleteTransaction class. """ +from __future__ import annotations + from unittest.mock import MagicMock import pytest @@ -12,6 +14,7 @@ ) from hiero_sdk_python.contract.contract_id import ContractId + pytestmark = pytest.mark.unit @@ -80,23 +83,12 @@ def test_build_transaction_body_with_valid_parameters(mock_account_ids, delete_p # When both transfer_account_id and transfer_contract_id are set, # the protobuf only will only set transferAccountID - assert ( - transaction_body.contractDeleteInstance.contractID - == delete_params["contract_id"]._to_proto() - ) - assert ( - transaction_body.contractDeleteInstance.transferAccountID - == delete_params["transfer_account_id"]._to_proto() - ) - assert ( - transaction_body.contractDeleteInstance.permanent_removal - == delete_params["permanent_removal"] - ) + assert transaction_body.contractDeleteInstance.contractID == delete_params["contract_id"]._to_proto() + assert transaction_body.contractDeleteInstance.transferAccountID == delete_params["transfer_account_id"]._to_proto() + assert transaction_body.contractDeleteInstance.permanent_removal == delete_params["permanent_removal"] -def test_build_transaction_body_with_required_params_only( - mock_account_ids, contract_id -): +def test_build_transaction_body_with_required_params_only(mock_account_ids, contract_id): """Test building transaction body with only required parameters.""" operator_id, _, node_account_id, _, _ = mock_account_ids @@ -114,9 +106,7 @@ def test_build_transaction_body_with_required_params_only( assert transaction_body.contractDeleteInstance.permanent_removal is False -def test_build_transaction_body_with_transfer_contract_id_only( - mock_account_ids, delete_params -): +def test_build_transaction_body_with_transfer_contract_id_only(mock_account_ids, delete_params): """Test building transaction body with transfer_contract_id only.""" operator_id, _, node_account_id, _, _ = mock_account_ids @@ -130,21 +120,15 @@ def test_build_transaction_body_with_transfer_contract_id_only( transaction_body = delete_tx.build_transaction_body() + assert transaction_body.contractDeleteInstance.contractID == delete_params["contract_id"]._to_proto() assert ( - transaction_body.contractDeleteInstance.contractID - == delete_params["contract_id"]._to_proto() - ) - assert ( - transaction_body.contractDeleteInstance.transferContractID - == delete_params["transfer_contract_id"]._to_proto() + transaction_body.contractDeleteInstance.transferContractID == delete_params["transfer_contract_id"]._to_proto() ) assert not transaction_body.contractDeleteInstance.HasField("transferAccountID") assert transaction_body.contractDeleteInstance.permanent_removal is False -def test_build_transaction_body_with_transfer_account_id_only( - mock_account_ids, delete_params -): +def test_build_transaction_body_with_transfer_account_id_only(mock_account_ids, delete_params): """Test building transaction body with transfer_account_id only.""" operator_id, _, node_account_id, _, _ = mock_account_ids @@ -158,21 +142,13 @@ def test_build_transaction_body_with_transfer_account_id_only( transaction_body = delete_tx.build_transaction_body() - assert ( - transaction_body.contractDeleteInstance.contractID - == delete_params["contract_id"]._to_proto() - ) + assert transaction_body.contractDeleteInstance.contractID == delete_params["contract_id"]._to_proto() assert not transaction_body.contractDeleteInstance.HasField("transferContractID") - assert ( - transaction_body.contractDeleteInstance.transferAccountID - == delete_params["transfer_account_id"]._to_proto() - ) + assert transaction_body.contractDeleteInstance.transferAccountID == delete_params["transfer_account_id"]._to_proto() assert transaction_body.contractDeleteInstance.permanent_removal is False -def test_build_transaction_body_with_permanent_removal_only( - mock_account_ids, delete_params -): +def test_build_transaction_body_with_permanent_removal_only(mock_account_ids, delete_params): """Test building transaction body with permanent_removal only.""" operator_id, _, node_account_id, _, _ = mock_account_ids @@ -186,16 +162,10 @@ def test_build_transaction_body_with_permanent_removal_only( transaction_body = delete_tx.build_transaction_body() - assert ( - transaction_body.contractDeleteInstance.contractID - == delete_params["contract_id"]._to_proto() - ) + assert transaction_body.contractDeleteInstance.contractID == delete_params["contract_id"]._to_proto() assert not transaction_body.contractDeleteInstance.HasField("transferContractID") assert not transaction_body.contractDeleteInstance.HasField("transferAccountID") - assert ( - transaction_body.contractDeleteInstance.permanent_removal - == delete_params["permanent_removal"] - ) + assert transaction_body.contractDeleteInstance.permanent_removal == delete_params["permanent_removal"] def test_build_transaction_body_missing_contract_id(): @@ -288,9 +258,7 @@ def test_method_chaining_partial_setters(delete_params): """Test method chaining with only some setters.""" delete_tx = ContractDeleteTransaction() - result = delete_tx.set_contract_id( - delete_params["contract_id"] - ).set_permanent_removal(True) + result = delete_tx.set_contract_id(delete_params["contract_id"]).set_permanent_removal(True) assert result is delete_tx assert delete_tx.contract_id == delete_params["contract_id"] @@ -312,9 +280,7 @@ def test_set_methods_require_not_frozen(mock_client, delete_params): ] for method_name, value in test_cases: - with pytest.raises( - Exception, match="Transaction is immutable; it has been frozen" - ): + with pytest.raises(Exception, match="Transaction is immutable; it has been frozen"): getattr(delete_tx, method_name)(value) diff --git a/tests/unit/contract_execute_transaction_test.py b/tests/unit/contract_execute_transaction_test.py index f2abcf429..f7ce6a450 100644 --- a/tests/unit/contract_execute_transaction_test.py +++ b/tests/unit/contract_execute_transaction_test.py @@ -2,6 +2,8 @@ Unit tests for the ContractExecuteTransaction class. """ +from __future__ import annotations + from unittest.mock import MagicMock import pytest @@ -28,6 +30,7 @@ from hiero_sdk_python.response_code import ResponseCode from tests.unit.mock_server import mock_hedera_servers + pytestmark = pytest.mark.unit @@ -84,16 +87,10 @@ def test_build_transaction_body_with_valid_parameters(mock_account_ids, execute_ transaction_body = execute_tx.build_transaction_body() - assert ( - transaction_body.contractCall.contractID - == execute_params["contract_id"]._to_proto() - ) + assert transaction_body.contractCall.contractID == execute_params["contract_id"]._to_proto() assert transaction_body.contractCall.gas == execute_params["gas"] assert transaction_body.contractCall.amount == execute_params["amount"] - assert ( - transaction_body.contractCall.functionParameters - == execute_params["function_parameters"] - ) + assert transaction_body.contractCall.functionParameters == execute_params["function_parameters"] def test_build_scheduled_body_with_valid_parameters(mock_account_ids, execute_params): @@ -120,16 +117,10 @@ def test_build_scheduled_body_with_valid_parameters(mock_account_ids, execute_pa assert schedulable_body.HasField("contractCall") # Verify fields in the schedulable body - assert ( - schedulable_body.contractCall.contractID - == execute_params["contract_id"]._to_proto() - ) + assert schedulable_body.contractCall.contractID == execute_params["contract_id"]._to_proto() assert schedulable_body.contractCall.gas == execute_params["gas"] assert schedulable_body.contractCall.amount == execute_params["amount"] - assert ( - schedulable_body.contractCall.functionParameters - == execute_params["function_parameters"] - ) + assert schedulable_body.contractCall.functionParameters == execute_params["function_parameters"] def test_build_transaction_body_missing_contract_id(): @@ -207,9 +198,7 @@ def test_set_function_parameters_with_contract_function_parameters(): def test_set_function_parameters_with_none(execute_params): """Test setting function parameters to None.""" - execute_tx = ContractExecuteTransaction( - function_parameters=execute_params["function_parameters"] - ) + execute_tx = ContractExecuteTransaction(function_parameters=execute_params["function_parameters"]) result = execute_tx.set_function_parameters(None) @@ -288,9 +277,7 @@ def test_set_methods_require_not_frozen(mock_client, execute_params): ] for method_name, value in test_cases: - with pytest.raises( - Exception, match="Transaction is immutable; it has been frozen" - ): + with pytest.raises(Exception, match="Transaction is immutable; it has been frozen"): getattr(execute_tx, method_name)(value) @@ -328,9 +315,7 @@ def test_build_transaction_body_with_required_params(mock_account_ids, contract_ def test_sign_transaction(mock_client, execute_params): """Test signing the contract execute transaction with a private key.""" - execute_tx = ContractExecuteTransaction( - contract_id=execute_params["contract_id"], gas=execute_params["gas"] - ) + execute_tx = ContractExecuteTransaction(contract_id=execute_params["contract_id"], gas=execute_params["gas"]) private_key = MagicMock() private_key.sign.return_value = b"signature" @@ -350,9 +335,7 @@ def test_sign_transaction(mock_client, execute_params): def test_to_proto(mock_client, execute_params): """Test converting the contract execute transaction to protobuf format after signing.""" - execute_tx = ContractExecuteTransaction( - contract_id=execute_params["contract_id"], gas=execute_params["gas"] - ) + execute_tx = ContractExecuteTransaction(contract_id=execute_params["contract_id"], gas=execute_params["gas"]) private_key = MagicMock() private_key.sign.return_value = b"signature" @@ -371,9 +354,7 @@ def test_contract_execute_transaction_can_execute(): ok_response = transaction_response_pb2.TransactionResponse() ok_response.nodeTransactionPrecheckCode = ResponseCode.OK - contract_id_proto = basic_types_pb2.ContractID( - shardNum=0, realmNum=0, contractNum=1234 - ) + contract_id_proto = basic_types_pb2.ContractID(shardNum=0, realmNum=0, contractNum=1234) mock_receipt_proto = transaction_receipt_pb2.TransactionReceipt( status=ResponseCode.SUCCESS, contractID=contract_id_proto ) @@ -381,9 +362,7 @@ def test_contract_execute_transaction_can_execute(): # Create a response for the receipt query receipt_query_response = response_pb2.Response( transactionGetReceipt=transaction_get_receipt_pb2.TransactionGetReceiptResponse( - header=response_header_pb2.ResponseHeader( - nodeTransactionPrecheckCode=ResponseCode.OK - ), + header=response_header_pb2.ResponseHeader(nodeTransactionPrecheckCode=ResponseCode.OK), receipt=mock_receipt_proto, ) ) @@ -398,14 +377,10 @@ def test_contract_execute_transaction_can_execute(): ContractExecuteTransaction() .set_contract_id(contract_id) .set_gas(1000000) - .set_function( - "setMessage", ContractFunctionParameters().add_bytes32(b"Test message") - ) + .set_function("setMessage", ContractFunctionParameters().add_bytes32(b"Test message")) ) receipt = transaction.execute(client) - assert ( - receipt.status == ResponseCode.SUCCESS - ), "Transaction should have succeeded" + assert receipt.status == ResponseCode.SUCCESS, "Transaction should have succeeded" assert str(receipt.contract_id) == str(contract_id) diff --git a/tests/unit/contract_function_result_test.py b/tests/unit/contract_function_result_test.py index 038d7322a..cb212e5e2 100644 --- a/tests/unit/contract_function_result_test.py +++ b/tests/unit/contract_function_result_test.py @@ -2,6 +2,8 @@ Unit tests for the ContractFunctionResult class. """ +from __future__ import annotations + import pytest from google.protobuf.wrappers_pb2 import BytesValue, Int64Value @@ -11,6 +13,7 @@ from hiero_sdk_python.contract.contract_nonce_info import ContractNonceInfo from hiero_sdk_python.hapi.services import contract_types_pb2 + pytestmark = pytest.mark.unit @@ -29,47 +32,29 @@ def log_info(): def contract_call_result_bytes(): """Fixture for contract call result bytes.""" # Static types (32 bytes each) - uint256_bytes = bytes.fromhex( - "000000000000000000000000000000000000000000000000000000000000002a" - ) # 42 + uint256_bytes = bytes.fromhex("000000000000000000000000000000000000000000000000000000000000002a") # 42 - bool_bytes = bytes.fromhex( - "0000000000000000000000000000000000000000000000000000000000000001" - ) # true + bool_bytes = bytes.fromhex("0000000000000000000000000000000000000000000000000000000000000001") # true - address_bytes = bytes.fromhex( - "000000000000000000000000abcdef0123456789abcdef0123456789abcdef01" - ) # address + address_bytes = bytes.fromhex("000000000000000000000000abcdef0123456789abcdef0123456789abcdef01") # address - bytes32_value = bytes.fromhex( - "1122334455667788990011223344556677889900112233445566778899001122" - ) # bytes32 + bytes32_value = bytes.fromhex("1122334455667788990011223344556677889900112233445566778899001122") # bytes32 # Dynamic type offsets (32 bytes each) - bytes_offset = bytes.fromhex( - "00000000000000000000000000000000000000000000000000000000000000c0" - ) # offset to bytes + bytes_offset = bytes.fromhex("00000000000000000000000000000000000000000000000000000000000000c0") # offset to bytes string_offset = bytes.fromhex( "0000000000000000000000000000000000000000000000000000000000000100" ) # offset to string # Dynamic data section - bytes_length = bytes.fromhex( - "000000000000000000000000000000000000000000000000000000000000000a" - ) # length: 10 + bytes_length = bytes.fromhex("000000000000000000000000000000000000000000000000000000000000000a") # length: 10 - bytes_value = bytes.fromhex( - "1234567890123456789000000000000000000000000000000000000000000000" - ) # bytes data + bytes_value = bytes.fromhex("1234567890123456789000000000000000000000000000000000000000000000") # bytes data - string_length = bytes.fromhex( - "000000000000000000000000000000000000000000000000000000000000000d" - ) # length: 13 + string_length = bytes.fromhex("000000000000000000000000000000000000000000000000000000000000000d") # length: 13 - string_value = bytes.fromhex( - "48656c6c6f2c20776f726c642100000000000000000000000000000000000000" - ) # "Hello, world!" + string_value = bytes.fromhex("48656c6c6f2c20776f726c642100000000000000000000000000000000000000") # "Hello, world!" return ( uint256_bytes # 32 bytes: uint256 = 42 @@ -95,9 +80,7 @@ def contract_function_result(contract_id, log_info, contract_call_result_bytes): bloom=bytes.fromhex("ffff"), gas_used=100000, log_info=[log_info], - evm_address=ContractId( - evm_address=bytes.fromhex("abcdef0123456789abcdef0123456789abcdef01") - ), + evm_address=ContractId(evm_address=bytes.fromhex("abcdef0123456789abcdef0123456789abcdef01")), gas_available=1000000, amount=50, function_parameters=bytes.fromhex("aabb"), @@ -122,9 +105,7 @@ def proto_contract_function_result(contract_id, log_info): bloom=bytes.fromhex("ffff"), gasUsed=100000, logInfo=[log_info_proto], - evm_address=BytesValue( - value=bytes.fromhex("abcdef0123456789abcdef0123456789abcdef01") - ), + evm_address=BytesValue(value=bytes.fromhex("abcdef0123456789abcdef0123456789abcdef01")), gas=1000000, amount=50, functionParameters=bytes.fromhex("aabb"), @@ -160,9 +141,7 @@ def test_initialization_with_values(contract_id, log_info, contract_call_result_ bloom=bytes.fromhex("ffff"), gas_used=100000, log_info=[log_info], - evm_address=ContractId( - evm_address=bytes.fromhex("abcdef0123456789abcdef0123456789abcdef01") - ), + evm_address=ContractId(evm_address=bytes.fromhex("abcdef0123456789abcdef0123456789abcdef01")), gas_available=1000000, amount=50, function_parameters=bytes.fromhex("aabb"), @@ -176,9 +155,7 @@ def test_initialization_with_values(contract_id, log_info, contract_call_result_ assert result.bloom == bytes.fromhex("ffff") assert result.gas_used == 100000 assert result.log_info == [log_info] - assert result.evm_address.evm_address == bytes.fromhex( - "abcdef0123456789abcdef0123456789abcdef01" - ) + assert result.evm_address.evm_address == bytes.fromhex("abcdef0123456789abcdef0123456789abcdef01") assert result.gas_available == 1000000 assert result.amount == 50 assert result.function_parameters == bytes.fromhex("aabb") @@ -219,16 +196,11 @@ def test_contract_function_result_getters(contract_function_result): # Test all getters with the fixture data assert contract_function_result.get_uint256(0) == 42 assert contract_function_result.get_bool(1) is True - assert ( - contract_function_result.get_address(2) - == "abcdef0123456789abcdef0123456789abcdef01" - ) + assert contract_function_result.get_address(2) == "abcdef0123456789abcdef0123456789abcdef01" assert contract_function_result.get_bytes32(3) == bytes.fromhex( "1122334455667788990011223344556677889900112233445566778899001122" ) - assert contract_function_result.get_bytes(4) == bytes.fromhex( - "12345678901234567890" - ) + assert contract_function_result.get_bytes(4) == bytes.fromhex("12345678901234567890") assert contract_function_result.get_string(5) == "Hello, world!" # Test get_result with all supported Solidity output types and verify decoded values @@ -246,9 +218,7 @@ def test_contract_function_result_getters(contract_function_result): 42, True, "0xabcdef0123456789abcdef0123456789abcdef01", # address starting with 0x - bytes.fromhex( - "1122334455667788990011223344556677889900112233445566778899001122" - ), + bytes.fromhex("1122334455667788990011223344556677889900112233445566778899001122"), bytes.fromhex("12345678901234567890"), "Hello, world!", ] @@ -278,9 +248,7 @@ def test_get_address(contract_function_result): def test_uint256_values(): """Test uint256 getter with various values.""" test_values = [0, 1, 2**32, 2**64, 2**255, 2**256 - 1] - encoded_bytes = b"".join( - value.to_bytes(32, byteorder="big", signed=False) for value in test_values - ) + encoded_bytes = b"".join(value.to_bytes(32, byteorder="big", signed=False) for value in test_values) result = ContractFunctionResult(contract_call_result=encoded_bytes) for idx, expected in enumerate(test_values): assert result.get_uint256(idx) == expected, f"Failed at idx={idx} for uint256" @@ -289,9 +257,7 @@ def test_uint256_values(): def test_int256_values(): """Test int256 getter with various values.""" test_values = [0, 1, -1, 2**127 - 1, -(2**127), 123456789, -123456789] - encoded_bytes = b"".join( - value.to_bytes(32, byteorder="big", signed=True) for value in test_values - ) + encoded_bytes = b"".join(value.to_bytes(32, byteorder="big", signed=True) for value in test_values) result = ContractFunctionResult(contract_call_result=encoded_bytes) for idx, expected in enumerate(test_values): assert result.get_int256(idx) == expected, f"Failed at idx={idx} for int256" @@ -328,44 +294,29 @@ def test_edge_case_values(): """Test edge cases for integer getters.""" # Test maximum values for different bit sizes max_values = [255, 65535, 16777215, 4294967295, 1099511627775, 281474976710655] - encoded_bytes = b"".join( - value.to_bytes(32, byteorder="big", signed=False) for value in max_values - ) + encoded_bytes = b"".join(value.to_bytes(32, byteorder="big", signed=False) for value in max_values) result = ContractFunctionResult(contract_call_result=encoded_bytes) # Test uint getters with max values assert result.get_uint8(0) == 255 and result.get_uint16(1) == 65535 assert result.get_uint24(2) == 16777215 and result.get_uint32(3) == 4294967295 - assert ( - result.get_uint40(4) == 1099511627775 - and result.get_uint48(5) == 281474976710655 - ) + assert result.get_uint40(4) == 1099511627775 and result.get_uint48(5) == 281474976710655 # Test negative values for int getters neg_values = [-1, -32768, -8388608, -2147483648] - neg_encoded = b"".join( - value.to_bytes(32, byteorder="big", signed=True) for value in neg_values - ) + neg_encoded = b"".join(value.to_bytes(32, byteorder="big", signed=True) for value in neg_values) neg_result = ContractFunctionResult(contract_call_result=neg_encoded) assert neg_result.get_int8(0) == -1 and neg_result.get_int16(1) == -32768 - assert ( - neg_result.get_int24(2) == -8388608 and neg_result.get_int32(3) == -2147483648 - ) + assert neg_result.get_int24(2) == -8388608 and neg_result.get_int32(3) == -2147483648 def test_string_retrieval(): """Test string retrieval from contract function result.""" greet_result_bytes = ( - bytes.fromhex( - "0000000000000000000000000000000000000000000000000000000000000020" - ) # offset - + bytes.fromhex( - "000000000000000000000000000000000000000000000000000000000000000d" - ) # length - + bytes.fromhex( - "48656c6c6f2c20776f726c642100000000000000000000000000000000000000" - ) # "Hello, world!" + bytes.fromhex("0000000000000000000000000000000000000000000000000000000000000020") # offset + + bytes.fromhex("000000000000000000000000000000000000000000000000000000000000000d") # length + + bytes.fromhex("48656c6c6f2c20776f726c642100000000000000000000000000000000000000") # "Hello, world!" ) greet_result = ContractFunctionResult(contract_call_result=greet_result_bytes) assert greet_result.get_string(0) == "Hello, world!" @@ -375,9 +326,7 @@ def test_string_retrieval(): def test_bytes32_retrieval(): """Test bytes32 retrieval from contract function result.""" - message_result_bytes = bytes.fromhex( - "1122334455667788990011223344556677889900112233445566778899001122" - ) + message_result_bytes = bytes.fromhex("1122334455667788990011223344556677889900112233445566778899001122") message_result = ContractFunctionResult(contract_call_result=message_result_bytes) assert message_result.get_bytes32(0) == bytes.fromhex( "1122334455667788990011223344556677889900112233445566778899001122" @@ -388,12 +337,8 @@ def test_large_numbers(): """Test handling of large numbers (uint256 max, int256 -1).""" large_numbers_bytes = bytes.fromhex( "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" - ) + bytes.fromhex( - "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" - ) - large_numbers_result = ContractFunctionResult( - contract_call_result=large_numbers_bytes - ) + ) + bytes.fromhex("ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff") + large_numbers_result = ContractFunctionResult(contract_call_result=large_numbers_bytes) assert large_numbers_result.get_uint256(0) == 2**256 - 1 assert large_numbers_result.get_int256(1) == -1 @@ -416,9 +361,7 @@ def test_error_handling(): ) + bytes.fromhex( # valid uint256 "000000000000000000000000000000000000000000000000000000000000ffff" ) # invalid offset - invalid_offset_result = ContractFunctionResult( - contract_call_result=invalid_offset_bytes - ) + invalid_offset_result = ContractFunctionResult(contract_call_result=invalid_offset_bytes) with pytest.raises(ValueError, match="Result index out of bounds"): invalid_offset_result.get_bytes(1) @@ -433,9 +376,7 @@ def test_from_proto(proto_contract_function_result): assert result.bloom == bytes.fromhex("ffff") assert result.gas_used == 100000 assert len(result.log_info) == 1 - assert result.evm_address.evm_address == bytes.fromhex( - "abcdef0123456789abcdef0123456789abcdef01" - ) + assert result.evm_address.evm_address == bytes.fromhex("abcdef0123456789abcdef0123456789abcdef01") assert result.gas_available == 1000000 assert result.amount == 50 assert result.function_parameters == bytes.fromhex("aabb") diff --git a/tests/unit/contract_id_test.py b/tests/unit/contract_id_test.py index 4886d569c..b56fb4140 100644 --- a/tests/unit/contract_id_test.py +++ b/tests/unit/contract_id_test.py @@ -2,14 +2,17 @@ Unit tests for the ContractId class. """ +from __future__ import annotations + import struct from unittest.mock import patch + import pytest from hiero_sdk_python.contract.contract_id import ContractId -from hiero_sdk_python.crypto.key import Key from hiero_sdk_python.hapi.services import basic_types_pb2 + pytestmark = pytest.mark.unit @@ -19,7 +22,6 @@ def client(mock_client): return mock_client - def test_default_initialization(): """Test ContractId initialization with default values.""" contract_id = ContractId() @@ -295,7 +297,6 @@ def test_evm_address_hash(): assert hash(contract_id1) != hash(contract_id3) - def test_to_evm_address(): """Test ContractId.to_evm_address() for both explicit and computed EVM addresses.""" # Explicit EVM address @@ -305,27 +306,21 @@ def test_to_evm_address(): # Computed EVM address (no explicit evm_address) contract_id = ContractId(shard=1, realm=2, contract=3) - expected_bytes = struct.pack( - ">iqq", contract_id.shard, contract_id.realm, contract_id.contract - ) + expected_bytes = struct.pack(">iqq", contract_id.shard, contract_id.realm, contract_id.contract) assert contract_id.to_evm_address() == expected_bytes.hex() # Default values contract_id = ContractId() - expected_bytes = struct.pack( - ">iqq", contract_id.shard, contract_id.realm, contract_id.contract - ) + expected_bytes = struct.pack(">iqq", contract_id.shard, contract_id.realm, contract_id.contract) assert contract_id.to_evm_address() == expected_bytes.hex() - def test_str_representation_with_checksum(client): """Should return string representation with checksum""" contract_id = ContractId.from_string("0.0.1") assert contract_id.to_string_with_checksum(client) == "0.0.1-dfkxr" - def test_str_representation_checksum_with_evm_address(client): """Should raise error on to_string_with_checksum is called when evm_address is set""" contract_id = ContractId.from_string("0.0.abcdef0123456789abcdef0123456789abcdef01") @@ -337,14 +332,12 @@ def test_str_representation_checksum_with_evm_address(client): contract_id.to_string_with_checksum(client) - def test_validate_checksum_success(client): """Should pass checksum validation when checksum is correct.""" contract_id = ContractId.from_string("0.0.1-dfkxr") contract_id.validate_checksum(client) - def test_validate_checksum_failure(client): """Should raise ValueError if checksum validation fails.""" contract_id = ContractId.from_string("0.0.1-wronx") @@ -358,12 +351,14 @@ def test_str_representation_with_evm_address(): contract_id = ContractId.from_string("0.0.abcdef0123456789abcdef0123456789abcdef01") assert contract_id.__str__() == "0.0.abcdef0123456789abcdef0123456789abcdef01" + def test_contract_id_repr_numeric(): """Test __repr__ output for numeric contract ID.""" contract_id = ContractId(0, 0, 12345) expected = "ContractId(shard=0, realm=0, contract=12345)" assert repr(contract_id) == expected + def test_contract_id_repr_evm_address(): """Test __repr__ output for EVM-based contract ID.""" evm_bytes = bytes.fromhex("a" * 40) @@ -442,12 +437,8 @@ def test_from_evm_address_invalid_evm_address_type(invalid_address): ) def test_from_evm_address_invalid_shard_type(invalid_shard): """Test from_evm_address raise error for invalid shard types.""" - with pytest.raises( - TypeError, match=f"shard must be int, got {type(invalid_shard).__name__}" - ): - ContractId.from_evm_address( - invalid_shard, 0, "abcdef0123456789abcdef0123456789abcdef01" - ) + with pytest.raises(TypeError, match=f"shard must be int, got {type(invalid_shard).__name__}"): + ContractId.from_evm_address(invalid_shard, 0, "abcdef0123456789abcdef0123456789abcdef01") def test_from_evm_address_negative_shard_value(): @@ -462,12 +453,8 @@ def test_from_evm_address_negative_shard_value(): ) def test_from_evm_address_invalid_realm_type(invalid_realm): """Test from_evm_address raise error for invalid realm types.""" - with pytest.raises( - TypeError, match=f"realm must be int, got {type(invalid_realm).__name__}" - ): - ContractId.from_evm_address( - 0, invalid_realm, "abcdef0123456789abcdef0123456789abcdef01" - ) + with pytest.raises(TypeError, match=f"realm must be int, got {type(invalid_realm).__name__}"): + ContractId.from_evm_address(0, invalid_realm, "abcdef0123456789abcdef0123456789abcdef01") def test_from_evm_address_negative_realm_value(): @@ -519,15 +506,17 @@ def test_populate_contract_num_invalid_response(client): evm_address = bytes.fromhex("abcdef0123456789abcdef0123456789abcdef01") contract_id = ContractId(shard=0, realm=0, evm_address=evm_address) - with patch( - "hiero_sdk_python.contract.contract_id.perform_query_to_mirror_node", - return_value={"contract_id": "invalid.account.format"}, - ): - with pytest.raises( + with ( + patch( + "hiero_sdk_python.contract.contract_id.perform_query_to_mirror_node", + return_value={"contract_id": "invalid.account.format"}, + ), + pytest.raises( ValueError, match="Invalid contract_id format received: invalid.account.format", - ): - contract_id.populate_contract_num(client) + ), + ): + contract_id.populate_contract_num(client) def test_populate_contract_num_query_fails(client): @@ -535,24 +524,24 @@ def test_populate_contract_num_query_fails(client): evm_address = bytes.fromhex("abcdef0123456789abcdef0123456789abcdef01") contract_id = ContractId(shard=0, realm=0, evm_address=evm_address) - with patch( - "hiero_sdk_python.contract.contract_id.perform_query_to_mirror_node", - side_effect=RuntimeError("mirror node query error"), - ): - with pytest.raises( + with ( + patch( + "hiero_sdk_python.contract.contract_id.perform_query_to_mirror_node", + side_effect=RuntimeError("mirror node query error"), + ), + pytest.raises( RuntimeError, match="Failed to populate contract num from mirror node for evm_address abcdef0123456789abcdef0123456789abcdef01", - ): - contract_id.populate_contract_num(client) + ), + ): + contract_id.populate_contract_num(client) def test_populate_contract_num_without_evm_address(client): """Should raise error when populate_contract_num is called without evm_address.""" contract_id = ContractId(shard=0, realm=0, contract=1) - with pytest.raises( - ValueError, match="evm_address is required to populate the contract number" - ): + with pytest.raises(ValueError, match="evm_address is required to populate the contract number"): contract_id.populate_contract_num(client) @@ -561,14 +550,15 @@ def test_populate_contract_num_invalid_mirror_response(client): evm_address = bytes.fromhex("abcdef0123456789abcdef0123456789abcdef01") contract_id = ContractId(shard=0, realm=0, evm_address=evm_address) - with patch( - "hiero_sdk_python.contract.contract_id.perform_query_to_mirror_node", - return_value={}, + with ( + patch( + "hiero_sdk_python.contract.contract_id.perform_query_to_mirror_node", + return_value={}, + ), + pytest.raises(ValueError, match="Mirror node response missing 'contract_id'"), ): - with pytest.raises( - ValueError, match="Mirror node response missing 'contract_id'" - ): - contract_id.populate_contract_num(client) + contract_id.populate_contract_num(client) + def test_to_proto_key(): """Test to_proto_key returns the Key protobuf.""" diff --git a/tests/unit/contract_info_query_test.py b/tests/unit/contract_info_query_test.py index b909cef2c..4534ccc57 100644 --- a/tests/unit/contract_info_query_test.py +++ b/tests/unit/contract_info_query_test.py @@ -2,6 +2,8 @@ Unit tests for the ContractInfoQuery class. """ +from __future__ import annotations + from unittest.mock import Mock import pytest @@ -20,6 +22,7 @@ from hiero_sdk_python.timestamp import Timestamp from tests.unit.mock_server import mock_hedera_servers + pytestmark = pytest.mark.unit @@ -49,9 +52,7 @@ def test_execute_fails_with_missing_contract_id(mock_client): """Test request creation with missing Contract ID.""" query = ContractInfoQuery() - with pytest.raises( - ValueError, match="Contract ID must be set before making the request." - ): + with pytest.raises(ValueError, match="Contract ID must be set before making the request."): query.execute(mock_client) @@ -104,9 +105,7 @@ def test_contract_info_query_execute(private_key): assert result.contract_id == contract_id assert result.contract_account_id == "0.0.100" - assert ( - result.admin_key.to_bytes_raw() == private_key.public_key().to_bytes_raw() - ) + assert result.admin_key.to_bytes_raw() == private_key.public_key().to_bytes_raw() assert result.expiration_time == Timestamp._from_protobuf(expiration_time) assert result.storage == 2048 assert result.contract_memo == "test contract memo" diff --git a/tests/unit/contract_info_test.py b/tests/unit/contract_info_test.py index d77c022cf..2aa55be67 100644 --- a/tests/unit/contract_info_test.py +++ b/tests/unit/contract_info_test.py @@ -2,6 +2,8 @@ Unit tests for the ContractInfo class. """ +from __future__ import annotations + import pytest from hiero_sdk_python.account.account_id import AccountId @@ -18,6 +20,7 @@ from hiero_sdk_python.tokens.token_kyc_status import TokenKycStatus from hiero_sdk_python.tokens.token_relationship import TokenRelationship + pytestmark = pytest.mark.unit @@ -90,7 +93,7 @@ def proto_contract_info(token_relationship): """Fixture for a proto ContractInfo object""" public_key = PrivateKey.generate_ed25519().public_key() - proto = ContractGetInfoResponse.ContractInfo( + return ContractGetInfoResponse.ContractInfo( contractID=ContractId(0, 0, 200)._to_proto(), accountID=AccountId(0, 0, 300)._to_proto(), contractAccountID="0.0.300", @@ -110,7 +113,6 @@ def proto_contract_info(token_relationship): decline_reward=False, ), ) - return proto def test_contract_info_initialization(contract_info): @@ -285,10 +287,7 @@ def test_proto_conversion_full_object(contract_info): assert converted.balance == contract_info.balance assert converted.is_deleted == contract_info.is_deleted assert converted.ledger_id == contract_info.ledger_id - assert ( - converted.max_automatic_token_associations - == contract_info.max_automatic_token_associations - ) + assert converted.max_automatic_token_associations == contract_info.max_automatic_token_associations assert len(converted.token_relationships) == len(contract_info.token_relationships) assert converted.staking_info.staked_account_id == contract_info.staking_info.staked_account_id assert converted.staking_info.staked_node_id == contract_info.staking_info.staked_node_id @@ -341,9 +340,9 @@ def test_from_proto_with_no_staking_info(): storage=1024, balance=5000000, ) - + contract_info = ContractInfo._from_proto(proto) - + assert contract_info.contract_id == ContractId(0, 0, 200) assert contract_info.staking_info is None @@ -360,9 +359,9 @@ def test_from_proto_with_staked_node_id(): decline_reward=True, ), ) - + contract_info = ContractInfo._from_proto(proto) - + assert contract_info.staking_info is not None assert contract_info.staking_info.staked_account_id is None assert contract_info.staking_info.staked_node_id == 3 @@ -380,11 +379,11 @@ def test_to_proto_with_staked_account_id(): decline_reward=False, ), ) - + proto = contract_info._to_proto() - - assert proto.HasField('staking_info') - assert proto.staking_info.HasField('staked_account_id') + + assert proto.HasField("staking_info") + assert proto.staking_info.HasField("staked_account_id") assert proto.staking_info.staked_account_id == AccountId(0, 0, 500)._to_proto() assert proto.staking_info.decline_reward is False @@ -400,10 +399,10 @@ def test_to_proto_with_staked_node_id(): decline_reward=True, ), ) - + proto = contract_info._to_proto() - - assert proto.HasField('staking_info') + + assert proto.HasField("staking_info") assert proto.staking_info.staked_node_id == 5 assert proto.staking_info.decline_reward is True @@ -419,9 +418,9 @@ def test_proto_conversion_staking_node_round_trip(): decline_reward=False, ), ) - + converted = ContractInfo._from_proto(contract_info._to_proto()) - + assert converted.contract_id == contract_info.contract_id assert converted.account_id == contract_info.account_id assert converted.balance == contract_info.balance @@ -442,9 +441,9 @@ def test_proto_conversion_staking_account_round_trip(): decline_reward=True, ), ) - + converted = ContractInfo._from_proto(contract_info._to_proto()) - + assert converted.contract_id == contract_info.contract_id assert converted.account_id == contract_info.account_id assert converted.balance == contract_info.balance @@ -465,9 +464,9 @@ def test_proto_conversion_with_staked_node_zero(): decline_reward=True, ), ) - + converted = ContractInfo._from_proto(contract_info._to_proto()) - + assert converted.staking_info is not None assert converted.staking_info.staked_node_id == 0 assert isinstance(converted.staking_info.staked_node_id, int) @@ -482,9 +481,9 @@ def test_proto_conversion_no_staking_info_round_trip(): balance=5000000, staking_info=None, ) - + converted = ContractInfo._from_proto(contract_info._to_proto()) - + assert converted.contract_id == contract_info.contract_id assert converted.account_id == contract_info.account_id assert converted.balance == contract_info.balance diff --git a/tests/unit/contract_log_info_test.py b/tests/unit/contract_log_info_test.py index a568a8536..bf0551909 100644 --- a/tests/unit/contract_log_info_test.py +++ b/tests/unit/contract_log_info_test.py @@ -2,12 +2,15 @@ Test the ContractLogInfo class. """ +from __future__ import annotations + import pytest from hiero_sdk_python.contract.contract_id import ContractId from hiero_sdk_python.contract.contract_log_info import ContractLogInfo from hiero_sdk_python.hapi.services import basic_types_pb2, contract_types_pb2 + pytestmark = pytest.mark.unit @@ -28,9 +31,7 @@ def test_custom_initialization(): topics = [bytes.fromhex("abcd"), bytes.fromhex("ef01")] data = bytes.fromhex("5678") - log_info = ContractLogInfo( - contract_id=contract_id, bloom=bloom, topics=topics, data=data - ) + log_info = ContractLogInfo(contract_id=contract_id, bloom=bloom, topics=topics, data=data) assert log_info.contract_id == contract_id assert log_info.bloom == bloom @@ -45,9 +46,7 @@ def test_str_representation(): topics = [bytes.fromhex("abcd"), bytes.fromhex("ef01")] data = bytes.fromhex("5678") - log_info = ContractLogInfo( - contract_id=contract_id, bloom=bloom, topics=topics, data=data - ) + log_info = ContractLogInfo(contract_id=contract_id, bloom=bloom, topics=topics, data=data) string_repr = str(log_info) assert "ContractLogInfo" in string_repr @@ -64,9 +63,7 @@ def test_to_proto(): topics = [bytes.fromhex("abcd"), bytes.fromhex("ef01")] data = bytes.fromhex("5678") - log_info = ContractLogInfo( - contract_id=contract_id, bloom=bloom, topics=topics, data=data - ) + log_info = ContractLogInfo(contract_id=contract_id, bloom=bloom, topics=topics, data=data) proto = log_info._to_proto() @@ -79,9 +76,7 @@ def test_to_proto(): def test_from_proto(): """Test creating ContractLogInfo from protobuf format.""" - contract_id_proto = basic_types_pb2.ContractID( - shardNum=1, realmNum=2, contractNum=3 - ) + contract_id_proto = basic_types_pb2.ContractID(shardNum=1, realmNum=2, contractNum=3) proto = contract_types_pb2.ContractLoginfo( contractID=contract_id_proto, @@ -111,9 +106,7 @@ def test_roundtrip_proto_conversion(): topics = [bytes.fromhex("ccdd"), bytes.fromhex("eeff")] data = bytes.fromhex("1122") - original = ContractLogInfo( - contract_id=contract_id, bloom=bloom, topics=topics, data=data - ) + original = ContractLogInfo(contract_id=contract_id, bloom=bloom, topics=topics, data=data) proto = original._to_proto() reconstructed = ContractLogInfo._from_proto(proto) diff --git a/tests/unit/contract_nonce_info_test.py b/tests/unit/contract_nonce_info_test.py index 68cdae757..f39fb0eb9 100644 --- a/tests/unit/contract_nonce_info_test.py +++ b/tests/unit/contract_nonce_info_test.py @@ -2,12 +2,15 @@ Test the ContractNonceInfo class. """ +from __future__ import annotations + import pytest from hiero_sdk_python.contract.contract_id import ContractId from hiero_sdk_python.contract.contract_nonce_info import ContractNonceInfo from hiero_sdk_python.hapi.services import contract_types_pb2 + pytestmark = pytest.mark.unit diff --git a/tests/unit/contract_update_transaction_test.py b/tests/unit/contract_update_transaction_test.py index 05f128b88..9794eeceb 100644 --- a/tests/unit/contract_update_transaction_test.py +++ b/tests/unit/contract_update_transaction_test.py @@ -2,6 +2,8 @@ Unit tests for the ContractUpdateTransaction class. """ +from __future__ import annotations + import pytest from hiero_sdk_python.account.account_id import AccountId @@ -17,6 +19,7 @@ ) from hiero_sdk_python.hbar import Hbar + pytestmark = pytest.mark.unit @@ -66,9 +69,7 @@ def test_constructor_with_parameters(update_params): contract_memo=update_params["memo"], admin_key=update_params["admin_key"], auto_renew_period=update_params["auto_renew_period"], - max_automatic_token_associations=update_params[ - "max_automatic_token_associations" - ], + max_automatic_token_associations=update_params["max_automatic_token_associations"], auto_renew_account_id=update_params["auto_renew_account_id"], staked_node_id=update_params["staked_node_id"], decline_reward=update_params["decline_reward"], @@ -79,10 +80,7 @@ def test_constructor_with_parameters(update_params): assert tx.contract_memo == update_params["memo"] assert tx.admin_key == update_params["admin_key"] assert tx.auto_renew_period == update_params["auto_renew_period"] - assert ( - tx.max_automatic_token_associations - == update_params["max_automatic_token_associations"] - ) + assert tx.max_automatic_token_associations == update_params["max_automatic_token_associations"] assert tx.auto_renew_account_id == update_params["auto_renew_account_id"] assert tx.staked_node_id == update_params["staked_node_id"] assert tx.decline_reward == update_params["decline_reward"] @@ -181,9 +179,7 @@ def test_method_chaining(update_params): .set_contract_memo(update_params["memo"]) .set_admin_key(update_params["admin_key"]) .set_auto_renew_period(update_params["auto_renew_period"]) - .set_max_automatic_token_associations( - update_params["max_automatic_token_associations"] - ) + .set_max_automatic_token_associations(update_params["max_automatic_token_associations"]) .set_auto_renew_account_id(update_params["auto_renew_account_id"]) .set_staked_node_id(update_params["staked_node_id"]) .set_decline_reward(update_params["decline_reward"]) @@ -193,10 +189,7 @@ def test_method_chaining(update_params): assert tx.contract_memo == update_params["memo"] assert tx.admin_key == update_params["admin_key"] assert tx.auto_renew_period == update_params["auto_renew_period"] - assert ( - tx.max_automatic_token_associations - == update_params["max_automatic_token_associations"] - ) + assert tx.max_automatic_token_associations == update_params["max_automatic_token_associations"] assert tx.auto_renew_account_id == update_params["auto_renew_account_id"] assert tx.staked_node_id == update_params["staked_node_id"] assert tx.decline_reward == update_params["decline_reward"] @@ -217,16 +210,9 @@ def test_build_transaction_body_success(contract_id, mock_account_ids, transacti transaction_body = tx.build_transaction_body() - assert ( - transaction_body.contractUpdateInstance.contractID.contractNum - == contract_id.contract - ) - assert ( - transaction_body.contractUpdateInstance.contractID.shardNum == contract_id.shard - ) - assert ( - transaction_body.contractUpdateInstance.contractID.realmNum == contract_id.realm - ) + assert transaction_body.contractUpdateInstance.contractID.contractNum == contract_id.contract + assert transaction_body.contractUpdateInstance.contractID.shardNum == contract_id.shard + assert transaction_body.contractUpdateInstance.contractID.realmNum == contract_id.realm def test_build_transaction_body_missing_contract_id(): @@ -238,9 +224,7 @@ def test_build_transaction_body_missing_contract_id(): tx.build_transaction_body() -def test_build_transaction_body_with_all_parameters( - update_params, mock_account_ids, transaction_id -): +def test_build_transaction_body_with_all_parameters(update_params, mock_account_ids, transaction_id): """Test building transaction body with all parameters set.""" _, _, node_account_id, _, _ = mock_account_ids @@ -250,9 +234,7 @@ def test_build_transaction_body_with_all_parameters( contract_memo=update_params["memo"], admin_key=update_params["admin_key"], auto_renew_period=update_params["auto_renew_period"], - max_automatic_token_associations=update_params[ - "max_automatic_token_associations" - ], + max_automatic_token_associations=update_params["max_automatic_token_associations"], auto_renew_account_id=update_params["auto_renew_account_id"], staked_node_id=update_params["staked_node_id"], decline_reward=update_params["decline_reward"], @@ -264,26 +246,15 @@ def test_build_transaction_body_with_all_parameters( transaction_body = tx.build_transaction_body() # Verify contract ID is set - assert ( - transaction_body.contractUpdateInstance.contractID.contractNum - == update_params["contract_id"].contract - ) - assert ( - transaction_body.contractUpdateInstance.contractID.shardNum - == update_params["contract_id"].shard - ) - assert ( - transaction_body.contractUpdateInstance.contractID.realmNum - == update_params["contract_id"].realm - ) + assert transaction_body.contractUpdateInstance.contractID.contractNum == update_params["contract_id"].contract + assert transaction_body.contractUpdateInstance.contractID.shardNum == update_params["contract_id"].shard + assert transaction_body.contractUpdateInstance.contractID.realmNum == update_params["contract_id"].realm # Verify other fields are present (the actual protobuf structure may vary) assert transaction_body.contractUpdateInstance.HasField("contractID") -def test_build_scheduled_body_with_all_parameters( - update_params, mock_account_ids, transaction_id -): +def test_build_scheduled_body_with_all_parameters(update_params, mock_account_ids, transaction_id): """Test building schedulable transaction body with all parameters set.""" _, _, node_account_id, _, _ = mock_account_ids @@ -293,9 +264,7 @@ def test_build_scheduled_body_with_all_parameters( contract_memo=update_params["memo"], admin_key=update_params["admin_key"], auto_renew_period=update_params["auto_renew_period"], - max_automatic_token_associations=update_params[ - "max_automatic_token_associations" - ], + max_automatic_token_associations=update_params["max_automatic_token_associations"], auto_renew_account_id=update_params["auto_renew_account_id"], staked_node_id=update_params["staked_node_id"], decline_reward=update_params["decline_reward"], @@ -313,18 +282,9 @@ def test_build_scheduled_body_with_all_parameters( assert schedulable_body.HasField("contractUpdateInstance") # Verify contract ID is set - assert ( - schedulable_body.contractUpdateInstance.contractID.contractNum - == update_params["contract_id"].contract - ) - assert ( - schedulable_body.contractUpdateInstance.contractID.shardNum - == update_params["contract_id"].shard - ) - assert ( - schedulable_body.contractUpdateInstance.contractID.realmNum - == update_params["contract_id"].realm - ) + assert schedulable_body.contractUpdateInstance.contractID.contractNum == update_params["contract_id"].contract + assert schedulable_body.contractUpdateInstance.contractID.shardNum == update_params["contract_id"].shard + assert schedulable_body.contractUpdateInstance.contractID.realmNum == update_params["contract_id"].realm ########### Transaction Execution Tests ########### @@ -349,11 +309,7 @@ def test_transaction_immutability_concept(contract_id): def test_memo_only_update(contract_id): """Test updating only the memo field.""" - tx = ( - ContractUpdateTransaction() - .set_contract_id(contract_id) - .set_contract_memo("New memo only") - ) + tx = ContractUpdateTransaction().set_contract_id(contract_id).set_contract_memo("New memo only") assert tx.contract_id == contract_id assert tx.contract_memo == "New memo only" @@ -363,11 +319,7 @@ def test_memo_only_update(contract_id): def test_admin_key_only_update(contract_id): """Test updating only the admin key field.""" new_admin_key = PrivateKey.generate().public_key() - tx = ( - ContractUpdateTransaction() - .set_contract_id(contract_id) - .set_admin_key(new_admin_key) - ) + tx = ContractUpdateTransaction().set_contract_id(contract_id).set_admin_key(new_admin_key) assert tx.contract_id == contract_id assert tx.admin_key.to_string() == new_admin_key.to_string() @@ -407,22 +359,14 @@ def test_empty_memo(contract_id): def test_very_long_memo(contract_id): """Test setting a very long memo.""" long_memo = "x" * 1000 # 1000 character memo - tx = ( - ContractUpdateTransaction() - .set_contract_id(contract_id) - .set_contract_memo(long_memo) - ) + tx = ContractUpdateTransaction().set_contract_id(contract_id).set_contract_memo(long_memo) assert tx.contract_memo == long_memo def test_zero_max_automatic_token_associations(contract_id): """Test setting max automatic token associations to zero.""" - tx = ( - ContractUpdateTransaction() - .set_contract_id(contract_id) - .set_max_automatic_token_associations(0) - ) + tx = ContractUpdateTransaction().set_contract_id(contract_id).set_max_automatic_token_associations(0) assert tx.max_automatic_token_associations == 0 @@ -436,10 +380,6 @@ def test_negative_staked_node_id(contract_id): def test_decline_reward_false(contract_id): """Test setting decline reward to False.""" - tx = ( - ContractUpdateTransaction() - .set_contract_id(contract_id) - .set_decline_reward(False) - ) + tx = ContractUpdateTransaction().set_contract_id(contract_id).set_decline_reward(False) assert tx.decline_reward is False diff --git a/tests/unit/crypto_utils_test.py b/tests/unit/crypto_utils_test.py index 7ecc07235..936449b2f 100644 --- a/tests/unit/crypto_utils_test.py +++ b/tests/unit/crypto_utils_test.py @@ -1,7 +1,9 @@ """Unit tests for crypto_utils module.""" -from cryptography.hazmat.primitives.asymmetric import ec +from __future__ import annotations + import pytest +from cryptography.hazmat.primitives.asymmetric import ec from hiero_sdk_python.utils.crypto_utils import ( compress_point_unchecked, @@ -10,28 +12,20 @@ keccak256, ) + pytestmark = pytest.mark.unit def test_keccak256(): """Test keccak256 hashing.""" # Known vector: empty string -> c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470 - assert ( - keccak256(b"").hex() - == "c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470" - ) + assert keccak256(b"").hex() == "c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470" # "hello" -> 1c8aff950685c2ed4bc3174f3472287b56d9517b9c948127319a09a7a36deac8 - assert ( - keccak256(b"hello").hex() - == "1c8aff950685c2ed4bc3174f3472287b56d9517b9c948127319a09a7a36deac8" - ) + assert keccak256(b"hello").hex() == "1c8aff950685c2ed4bc3174f3472287b56d9517b9c948127319a09a7a36deac8" # "Transfer" -> 461a29a8a7db848c0827103038dd4776114eb182e0717208d0a793574936353d - assert ( - keccak256(b"Transfer").hex() - == "f099cd8bde557814842a3121e8ddfd433a539b8c9f14bf31ebf108d12e6196e9" - ) + assert keccak256(b"Transfer").hex() == "f099cd8bde557814842a3121e8ddfd433a539b8c9f14bf31ebf108d12e6196e9" def test_compress_point_unchecked(): diff --git a/tests/unit/custom_fee_limit_test.py b/tests/unit/custom_fee_limit_test.py index c80464a97..2c012c1d7 100644 --- a/tests/unit/custom_fee_limit_test.py +++ b/tests/unit/custom_fee_limit_test.py @@ -2,6 +2,8 @@ Unit tests for the CustomFeeLimit class. """ +from __future__ import annotations + import pytest from hiero_sdk_python.account.account_id import AccountId @@ -12,6 +14,7 @@ from hiero_sdk_python.tokens.token_id import TokenId from hiero_sdk_python.transaction.custom_fee_limit import CustomFeeLimit + pytestmark = pytest.mark.unit @@ -56,7 +59,7 @@ def proto_custom_fee_limit(custom_fixed_fee): """Fixture for a proto CustomFeeLimit object""" from hiero_sdk_python.hapi.services.custom_fees_pb2 import FixedFee - proto = CustomFeeLimitProto( + return CustomFeeLimitProto( account_id=AccountId(0, 0, 100)._to_proto(), fees=[ FixedFee( @@ -69,7 +72,6 @@ def proto_custom_fee_limit(custom_fixed_fee): ) ], ) - return proto def test_custom_fee_limit_initialization(custom_fee_limit): @@ -78,9 +80,7 @@ def test_custom_fee_limit_initialization(custom_fee_limit): assert len(custom_fee_limit.custom_fees) == 1 assert custom_fee_limit.custom_fees[0].amount == 1000 assert custom_fee_limit.custom_fees[0].denominating_token_id == TokenId(0, 0, 500) - assert custom_fee_limit.custom_fees[0].fee_collector_account_id == AccountId( - 0, 0, 600 - ) + assert custom_fee_limit.custom_fees[0].fee_collector_account_id == AccountId(0, 0, 600) def test_custom_fee_limit_default_initialization(): @@ -200,9 +200,7 @@ def test_from_proto_without_payer_id(): FixedFee( amount=custom_fee.amount, denominating_token_id=( - custom_fee.denominating_token_id._to_proto() - if custom_fee.denominating_token_id - else None + custom_fee.denominating_token_id._to_proto() if custom_fee.denominating_token_id else None ), ) ], @@ -272,10 +270,7 @@ def test_proto_conversion_full_object(custom_fee_limit): assert converted.payer_id == custom_fee_limit.payer_id assert len(converted.custom_fees) == len(custom_fee_limit.custom_fees) assert converted.custom_fees[0].amount == custom_fee_limit.custom_fees[0].amount - assert ( - converted.custom_fees[0].denominating_token_id - == custom_fee_limit.custom_fees[0].denominating_token_id - ) + assert converted.custom_fees[0].denominating_token_id == custom_fee_limit.custom_fees[0].denominating_token_id # fee_collector_account_id is not preserved assert converted.custom_fees[0].fee_collector_account_id is None @@ -320,11 +315,7 @@ def test_method_chaining(): fee_collector_account_id=AccountId(0, 0, 1000), ) - result = ( - custom_fee_limit.set_payer_id(payer_id) - .add_custom_fee(custom_fee) - .set_custom_fees([custom_fee]) - ) + result = custom_fee_limit.set_payer_id(payer_id).add_custom_fee(custom_fee).set_custom_fees([custom_fee]) assert result is custom_fee_limit assert custom_fee_limit.payer_id == payer_id diff --git a/tests/unit/custom_fee_test.py b/tests/unit/custom_fee_test.py index 9127bf6da..286b7935d 100644 --- a/tests/unit/custom_fee_test.py +++ b/tests/unit/custom_fee_test.py @@ -1,15 +1,20 @@ -from hiero_sdk_python.client.client import Client -import pytest +from __future__ import annotations + import warnings from unittest import mock + +import pytest + +from hiero_sdk_python.account.account_id import AccountId +from hiero_sdk_python.client.client import Client from hiero_sdk_python.tokens.custom_fee import CustomFee from hiero_sdk_python.tokens.custom_fixed_fee import CustomFixedFee from hiero_sdk_python.tokens.custom_fractional_fee import CustomFractionalFee from hiero_sdk_python.tokens.custom_royalty_fee import CustomRoyaltyFee from hiero_sdk_python.tokens.fee_assessment_method import FeeAssessmentMethod -from hiero_sdk_python.account.account_id import AccountId from hiero_sdk_python.tokens.token_id import TokenId + pytestmark = pytest.mark.unit @@ -122,9 +127,7 @@ def test_custom_fractional_fee(): ) proto = fee._to_proto() # Changed from _to_protobuf - new_fee = CustomFractionalFee._from_proto( - proto - ) # Changed from CustomFee._from_protobuf + new_fee = CustomFractionalFee._from_proto(proto) # Changed from CustomFee._from_protobuf assert isinstance(new_fee, CustomFractionalFee) assert new_fee.numerator == 1 @@ -150,9 +153,7 @@ def test_custom_royalty_fee(): ) proto = fee._to_proto() # Changed from _to_protobuf - new_fee = CustomRoyaltyFee._from_proto( - proto - ) # Changed from CustomFee._from_protobuf + new_fee = CustomRoyaltyFee._from_proto(proto) # Changed from CustomFee._from_protobuf assert isinstance(new_fee, CustomRoyaltyFee) assert new_fee.numerator == 5 @@ -212,9 +213,7 @@ def test_custom_royalty_fee(): ), ], ) -def test_custom_royalty_fee_str( - custom_royalty_fee: CustomRoyaltyFee, expected_str: str -): +def test_custom_royalty_fee_str(custom_royalty_fee: CustomRoyaltyFee, expected_str: str): """Test the string representation of CustomRoyaltyFee.""" fee_str = str(custom_royalty_fee) assert fee_str == expected_str @@ -271,7 +270,7 @@ def test_custom_fee_validate_checksums(): def test_custom_fee_from_proto_unrecognized(): class FakeProto: - def WhichOneof(self, name): + def WhichOneof(self, _name): return "unknown_fee" with pytest.raises(ValueError): diff --git a/tests/unit/delegate_contract_id_test.py b/tests/unit/delegate_contract_id_test.py index 4c14920ac..1377a54b7 100644 --- a/tests/unit/delegate_contract_id_test.py +++ b/tests/unit/delegate_contract_id_test.py @@ -2,14 +2,17 @@ Unit tests for the DelegateContractId class. """ +from __future__ import annotations + import struct from unittest.mock import patch + import pytest from hiero_sdk_python.contract.delegate_contract_id import DelegateContractId -from hiero_sdk_python.crypto.key import Key from hiero_sdk_python.hapi.services import basic_types_pb2 + pytestmark = pytest.mark.unit @@ -229,9 +232,7 @@ def test_equality(): def test_evm_address_initialization(): """Test DelegateContractId initialization with EVM address.""" evm_address = bytes.fromhex("abcdef0123456789abcdef0123456789abcdef01") - contract_id = DelegateContractId( - shard=1, realm=2, contract=3, evm_address=evm_address - ) + contract_id = DelegateContractId(shard=1, realm=2, contract=3, evm_address=evm_address) assert contract_id.shard == 1 assert contract_id.realm == 2 @@ -242,9 +243,7 @@ def test_evm_address_initialization(): def test_evm_address_to_proto(): """Test converting DelegateContractId with EVM address to protobuf format.""" evm_address = bytes.fromhex("abcdef0123456789abcdef0123456789abcdef01") - contract_id = DelegateContractId( - shard=1, realm=2, contract=3, evm_address=evm_address - ) + contract_id = DelegateContractId(shard=1, realm=2, contract=3, evm_address=evm_address) proto = contract_id._to_proto() assert isinstance(proto, basic_types_pb2.ContractID) @@ -271,15 +270,9 @@ def test_evm_address_equality(): evm_address1 = bytes.fromhex("abcdef0123456789abcdef0123456789abcdef01") evm_address2 = bytes.fromhex("1234567890abcdef1234567890abcdef12345678") - contract_id1 = DelegateContractId( - shard=1, realm=2, contract=3, evm_address=evm_address1 - ) - contract_id2 = DelegateContractId( - shard=1, realm=2, contract=3, evm_address=evm_address1 - ) - contract_id3 = DelegateContractId( - shard=1, realm=2, contract=3, evm_address=evm_address2 - ) + contract_id1 = DelegateContractId(shard=1, realm=2, contract=3, evm_address=evm_address1) + contract_id2 = DelegateContractId(shard=1, realm=2, contract=3, evm_address=evm_address1) + contract_id3 = DelegateContractId(shard=1, realm=2, contract=3, evm_address=evm_address2) contract_id4 = DelegateContractId(shard=1, realm=2, contract=3, evm_address=None) # Same EVM address should be equal @@ -297,15 +290,9 @@ def test_evm_address_hash(): evm_address1 = bytes.fromhex("abcdef0123456789abcdef0123456789abcdef01") evm_address2 = bytes.fromhex("1234567890abcdef1234567890abcdef12345678") - contract_id1 = DelegateContractId( - shard=1, realm=2, contract=3, evm_address=evm_address1 - ) - contract_id2 = DelegateContractId( - shard=1, realm=2, contract=3, evm_address=evm_address1 - ) - contract_id3 = DelegateContractId( - shard=1, realm=2, contract=3, evm_address=evm_address2 - ) + contract_id1 = DelegateContractId(shard=1, realm=2, contract=3, evm_address=evm_address1) + contract_id2 = DelegateContractId(shard=1, realm=2, contract=3, evm_address=evm_address1) + contract_id3 = DelegateContractId(shard=1, realm=2, contract=3, evm_address=evm_address2) # Same EVM address should have same hash assert hash(contract_id1) == hash(contract_id2) @@ -318,23 +305,17 @@ def test_to_evm_address(): """Test DelegateContractId.to_evm_address() for both explicit and computed EVM addresses.""" # Explicit EVM address evm_address = bytes.fromhex("abcdef0123456789abcdef0123456789abcdef01") - contract_id = DelegateContractId( - shard=1, realm=2, contract=3, evm_address=evm_address - ) + contract_id = DelegateContractId(shard=1, realm=2, contract=3, evm_address=evm_address) assert contract_id.to_evm_address() == evm_address.hex() # Computed EVM address (no explicit evm_address) contract_id = DelegateContractId(shard=1, realm=2, contract=3) - expected_bytes = struct.pack( - ">iqq", contract_id.shard, contract_id.realm, contract_id.contract - ) + expected_bytes = struct.pack(">iqq", contract_id.shard, contract_id.realm, contract_id.contract) assert contract_id.to_evm_address() == expected_bytes.hex() # Default values contract_id = DelegateContractId() - expected_bytes = struct.pack( - ">iqq", contract_id.shard, contract_id.realm, contract_id.contract - ) + expected_bytes = struct.pack(">iqq", contract_id.shard, contract_id.realm, contract_id.contract) assert contract_id.to_evm_address() == expected_bytes.hex() @@ -346,9 +327,7 @@ def test_str_representation_with_checksum(client): def test_str_representation_checksum_with_evm_address(client): """Should raise error on to_string_with_checksum is called when evm_address is set""" - contract_id = DelegateContractId.from_string( - "0.0.abcdef0123456789abcdef0123456789abcdef01" - ) + contract_id = DelegateContractId.from_string("0.0.abcdef0123456789abcdef0123456789abcdef01") with pytest.raises( ValueError, @@ -373,9 +352,7 @@ def test_validate_checksum_failure(client): def test_str_representation_with_evm_address(): """Should return str representing with evm_address""" - contract_id = DelegateContractId.from_string( - "0.0.abcdef0123456789abcdef0123456789abcdef01" - ) + contract_id = DelegateContractId.from_string("0.0.abcdef0123456789abcdef0123456789abcdef01") assert contract_id.__str__() == "0.0.abcdef0123456789abcdef0123456789abcdef01" @@ -464,20 +441,14 @@ def test_from_evm_address_invalid_evm_address_type(invalid_address): ) def test_from_evm_address_invalid_shard_type(invalid_shard): """Test from_evm_address raise error for invalid shard types.""" - with pytest.raises( - TypeError, match=f"shard must be int, got {type(invalid_shard).__name__}" - ): - DelegateContractId.from_evm_address( - invalid_shard, 0, "abcdef0123456789abcdef0123456789abcdef01" - ) + with pytest.raises(TypeError, match=f"shard must be int, got {type(invalid_shard).__name__}"): + DelegateContractId.from_evm_address(invalid_shard, 0, "abcdef0123456789abcdef0123456789abcdef01") def test_from_evm_address_negative_shard_value(): """Test from_evm_address raise error for negative shard values.""" with pytest.raises(ValueError, match="shard must be a non-negative integer"): - DelegateContractId.from_evm_address( - -1, 0, "abcdef0123456789abcdef0123456789abcdef01" - ) + DelegateContractId.from_evm_address(-1, 0, "abcdef0123456789abcdef0123456789abcdef01") @pytest.mark.parametrize( @@ -486,20 +457,14 @@ def test_from_evm_address_negative_shard_value(): ) def test_from_evm_address_invalid_realm_type(invalid_realm): """Test from_evm_address raise error for invalid realm types.""" - with pytest.raises( - TypeError, match=f"realm must be int, got {type(invalid_realm).__name__}" - ): - DelegateContractId.from_evm_address( - 0, invalid_realm, "abcdef0123456789abcdef0123456789abcdef01" - ) + with pytest.raises(TypeError, match=f"realm must be int, got {type(invalid_realm).__name__}"): + DelegateContractId.from_evm_address(0, invalid_realm, "abcdef0123456789abcdef0123456789abcdef01") def test_from_evm_address_negative_realm_value(): """Test from_evm_address raise error for negative realm values.""" with pytest.raises(ValueError, match="realm must be a non-negative integer"): - DelegateContractId.from_evm_address( - 0, -1, "abcdef0123456789abcdef0123456789abcdef01" - ) + DelegateContractId.from_evm_address(0, -1, "abcdef0123456789abcdef0123456789abcdef01") def test_from_bytes_success(): @@ -545,15 +510,17 @@ def test_populate_contract_num_invalid_response(client): evm_address = bytes.fromhex("abcdef0123456789abcdef0123456789abcdef01") contract_id = DelegateContractId(shard=0, realm=0, evm_address=evm_address) - with patch( - "hiero_sdk_python.contract.contract_id.perform_query_to_mirror_node", - return_value={"contract_id": "invalid.account.format"}, - ): - with pytest.raises( + with ( + patch( + "hiero_sdk_python.contract.contract_id.perform_query_to_mirror_node", + return_value={"contract_id": "invalid.account.format"}, + ), + pytest.raises( ValueError, match="Invalid contract_id format received: invalid.account.format", - ): - contract_id.populate_contract_num(client) + ), + ): + contract_id.populate_contract_num(client) def test_populate_contract_num_query_fails(client): @@ -561,24 +528,24 @@ def test_populate_contract_num_query_fails(client): evm_address = bytes.fromhex("abcdef0123456789abcdef0123456789abcdef01") contract_id = DelegateContractId(shard=0, realm=0, evm_address=evm_address) - with patch( - "hiero_sdk_python.contract.contract_id.perform_query_to_mirror_node", - side_effect=RuntimeError("mirror node query error"), - ): - with pytest.raises( + with ( + patch( + "hiero_sdk_python.contract.contract_id.perform_query_to_mirror_node", + side_effect=RuntimeError("mirror node query error"), + ), + pytest.raises( RuntimeError, match="Failed to populate contract num from mirror node for evm_address abcdef0123456789abcdef0123456789abcdef01", - ): - contract_id.populate_contract_num(client) + ), + ): + contract_id.populate_contract_num(client) def test_populate_contract_num_without_evm_address(client): """Should raise error when populate_contract_num is called without evm_address.""" contract_id = DelegateContractId(shard=0, realm=0, contract=1) - with pytest.raises( - ValueError, match="evm_address is required to populate the contract number" - ): + with pytest.raises(ValueError, match="evm_address is required to populate the contract number"): contract_id.populate_contract_num(client) @@ -587,14 +554,14 @@ def test_populate_contract_num_invalid_mirror_response(client): evm_address = bytes.fromhex("abcdef0123456789abcdef0123456789abcdef01") contract_id = DelegateContractId(shard=0, realm=0, evm_address=evm_address) - with patch( - "hiero_sdk_python.contract.contract_id.perform_query_to_mirror_node", - return_value={}, + with ( + patch( + "hiero_sdk_python.contract.contract_id.perform_query_to_mirror_node", + return_value={}, + ), + pytest.raises(ValueError, match="Mirror node response missing 'contract_id'"), ): - with pytest.raises( - ValueError, match="Mirror node response missing 'contract_id'" - ): - contract_id.populate_contract_num(client) + contract_id.populate_contract_num(client) def test_to_proto_key(): diff --git a/tests/unit/endpoint_test.py b/tests/unit/endpoint_test.py index 73cefc718..78a9417ec 100644 --- a/tests/unit/endpoint_test.py +++ b/tests/unit/endpoint_test.py @@ -1,13 +1,17 @@ -import pytest +from __future__ import annotations + from unittest.mock import MagicMock + +import pytest + from src.hiero_sdk_python.address_book.endpoint import Endpoint + pytestmark = pytest.mark.unit def test_getter_setter(): """Test for Endpoint constructor, getters, and setters with fluent interface.""" - endpoint = Endpoint(address=None, port=None, domain_name=None) # Test fluent interface (method chaining) @@ -71,7 +75,6 @@ def test_from_proto_port_mapping(input_port, expected_port): - Port 0 or 50111 maps to 50211 (legacy/default behavior) - Other ports pass through unchanged """ - mock_proto = MagicMock() mock_proto.port = input_port mock_proto.ipAddressV4 = b"127.0.1.1" @@ -84,9 +87,7 @@ def test_from_proto_port_mapping(input_port, expected_port): # Verify all fields are mapped correctly (not just port) assert endpoint.get_address() == b"127.0.1.1", "Address must be mapped from proto" - assert ( - endpoint.get_domain_name() == "redpanda.com" - ), "Domain name must be mapped from proto" + assert endpoint.get_domain_name() == "redpanda.com", "Domain name must be mapped from proto" # Protect against breaking changes - PRIORITY 1 assert isinstance(endpoint, Endpoint), "Must return Endpoint instance" @@ -121,8 +122,8 @@ def test_to_proto_with_none_values(field_to_none, attr_name, expected_default): def test_to_proto(): """Verifies that an Endpoint instance can be correctly serialized back into - a Protobuf ServiceEndpoint object with all fields intact.""" - + a Protobuf ServiceEndpoint object with all fields intact. + """ endpoint = Endpoint(address=b"127.0.1.1", port=77777, domain_name="redpanda.com") proto = endpoint._to_proto() assert proto.ipAddressV4 == b"127.0.1.1" @@ -132,7 +133,6 @@ def test_to_proto(): def test_str(): """Tests the human-readable string representation of the Endpoint.""" - endpoint = Endpoint(address=b"127.0.1.1", port=77777, domain_name="redpanda.com") result = str(endpoint) diff --git a/tests/unit/entity_id_helper_test.py b/tests/unit/entity_id_helper_test.py index 98d1a55c6..256d1eaee 100644 --- a/tests/unit/entity_id_helper_test.py +++ b/tests/unit/entity_id_helper_test.py @@ -1,18 +1,22 @@ +from __future__ import annotations + +import struct from unittest.mock import MagicMock, patch + import pytest -import struct import requests from hiero_sdk_python.utils.entity_id_helper import ( - parse_from_string, + format_to_string, + format_to_string_with_checksum, generate_checksum, + parse_from_string, perform_query_to_mirror_node, to_solidity_address, validate_checksum, - format_to_string, - format_to_string_with_checksum, ) + pytestmark = pytest.mark.unit @@ -132,6 +136,7 @@ def test_parse_and_format_without_checksum(): assert formatted == original + def test_to_solidity_address_valid(): shard, realm, num = 0, 0, 1001 result = to_solidity_address(shard, realm, num) @@ -143,17 +148,19 @@ def test_to_solidity_address_valid(): assert len(result) == 40 # exactly 20 bytes assert result.islower() + def test_to_solidity_address_zero_values(): assert to_solidity_address(0, 0, 0) == ("00" * 20) + def test_to_solidity_address_out_of_range(): shard, realm, num = 2**31, 0, 0 with pytest.raises(ValueError, match="shard out of 32-bit range"): to_solidity_address(shard, realm, num) + def test_perform_query_to_mirror_node_success(): """Test successful mirror node response without requests_mock.""" - mock_response = MagicMock() mock_response.json.return_value = {"account": "0.0.777"} mock_response.raise_for_status.return_value = None @@ -162,9 +169,9 @@ def test_perform_query_to_mirror_node_success(): result = perform_query_to_mirror_node("http://mirror-node/accounts/123") assert result == {"account": "0.0.777"} + def test_perform_query_to_mirror_node_failure(): """Test mirror node failure handling.""" - with patch("hiero_sdk_python.utils.entity_id_helper.requests.get") as mock_get: mock_get.side_effect = requests.RequestException("boom") @@ -179,39 +186,46 @@ def test_perform_query_to_mirror_node_http_error(): mock_response = MagicMock() mock_response.raise_for_status.side_effect = requests.exceptions.HTTPError("HTTP fail") - with patch("hiero_sdk_python.utils.entity_id_helper.requests.get", return_value=mock_response): - with pytest.raises(RuntimeError, match="Mirror node request failed"): - perform_query_to_mirror_node("http://mirror-node/accounts/123") + with ( + patch("hiero_sdk_python.utils.entity_id_helper.requests.get", return_value=mock_response), + pytest.raises(RuntimeError, match="Mirror node request failed"), + ): + perform_query_to_mirror_node("http://mirror-node/accounts/123") def test_perform_query_to_mirror_node_connection_error(): """ Test that perform_query_to_mirror_node raises a RuntimeError when requests.get raises a ConnectionError. """ - with patch( - "hiero_sdk_python.utils.entity_id_helper.requests.get", - side_effect=requests.exceptions.ConnectionError("Connection fail") + with ( + patch( + "hiero_sdk_python.utils.entity_id_helper.requests.get", + side_effect=requests.exceptions.ConnectionError("Connection fail"), + ), + pytest.raises(RuntimeError, match="Mirror node request failed"), ): - with pytest.raises(RuntimeError, match="Mirror node request failed"): - perform_query_to_mirror_node("http://mirror-node/accounts/123") + perform_query_to_mirror_node("http://mirror-node/accounts/123") def test_perform_query_to_mirror_node_timeout(): """ Test that perform_query_to_mirror_node raises a RuntimeError when requests.get raises a Timeout exception. """ - with patch( - "hiero_sdk_python.utils.entity_id_helper.requests.get", - side_effect=requests.exceptions.Timeout("Timeout") + with ( + patch( + "hiero_sdk_python.utils.entity_id_helper.requests.get", side_effect=requests.exceptions.Timeout("Timeout") + ), + pytest.raises(RuntimeError, match="Mirror node request timed out"), ): - with pytest.raises(RuntimeError, match="Mirror node request timed out"): - perform_query_to_mirror_node("http://mirror-node/accounts/123") + perform_query_to_mirror_node("http://mirror-node/accounts/123") + def test_perform_query_to_mirror_node_invalid_url_none(): """Test url must be a non-empty string (None case).""" with pytest.raises(ValueError, match="url must be a non-empty string"): perform_query_to_mirror_node(None) + def test_perform_query_to_mirror_node_invalid_url_empty(): """Test url must be a non-empty string (empty string case).""" with pytest.raises(ValueError, match="url must be a non-empty string"): diff --git a/tests/unit/ethereum_transaction_test.py b/tests/unit/ethereum_transaction_test.py index db90dfd9e..26f1d8cc4 100644 --- a/tests/unit/ethereum_transaction_test.py +++ b/tests/unit/ethereum_transaction_test.py @@ -2,6 +2,8 @@ Unit tests for the EthereumTransaction class. """ +from __future__ import annotations + from unittest.mock import MagicMock import pytest @@ -18,6 +20,7 @@ from hiero_sdk_python.response_code import ResponseCode from tests.unit.mock_server import mock_hedera_servers + pytestmark = pytest.mark.unit @@ -53,9 +56,7 @@ def test_constructor_default_values(): assert ethereum_tx.max_gas_allowed is None -def test_build_transaction_body_with_valid_parameters( - mock_account_ids, ethereum_params -): +def test_build_transaction_body_with_valid_parameters(mock_account_ids, ethereum_params): """Test building an ethereum transaction body with valid parameters.""" operator_id, _, node_account_id, _, _ = mock_account_ids @@ -71,18 +72,9 @@ def test_build_transaction_body_with_valid_parameters( transaction_body = ethereum_tx.build_transaction_body() - assert ( - transaction_body.ethereumTransaction.ethereum_data - == ethereum_params["ethereum_data"] - ) - assert ( - transaction_body.ethereumTransaction.call_data - == ethereum_params["call_data"]._to_proto() - ) - assert ( - transaction_body.ethereumTransaction.max_gas_allowance - == ethereum_params["max_gas_allowed"] - ) + assert transaction_body.ethereumTransaction.ethereum_data == ethereum_params["ethereum_data"] + assert transaction_body.ethereumTransaction.call_data == ethereum_params["call_data"]._to_proto() + assert transaction_body.ethereumTransaction.max_gas_allowance == ethereum_params["max_gas_allowed"] def test_build_transaction_body_with_minimal_parameters(mock_account_ids): @@ -145,9 +137,7 @@ def test_set_methods_require_not_frozen(mock_client, ethereum_params): ] for method_name, value in test_cases: - with pytest.raises( - Exception, match="Transaction is immutable; it has been frozen" - ): + with pytest.raises(Exception, match="Transaction is immutable; it has been frozen"): getattr(ethereum_tx, method_name)(value) @@ -227,9 +217,7 @@ def test_ethereum_transaction_can_execute(): # Create a response for the receipt query receipt_query_response = response_pb2.Response( transactionGetReceipt=transaction_get_receipt_pb2.TransactionGetReceiptResponse( - header=response_header_pb2.ResponseHeader( - nodeTransactionPrecheckCode=ResponseCode.OK - ), + header=response_header_pb2.ResponseHeader(nodeTransactionPrecheckCode=ResponseCode.OK), receipt=mock_receipt_proto, ) ) @@ -240,14 +228,8 @@ def test_ethereum_transaction_can_execute(): with mock_hedera_servers(response_sequences) as client: ethereum_data = b"test ethereum transaction data" - transaction = ( - EthereumTransaction() - .set_ethereum_data(ethereum_data) - .set_max_gas_allowed(1000000) - ) + transaction = EthereumTransaction().set_ethereum_data(ethereum_data).set_max_gas_allowed(1000000) receipt = transaction.execute(client) - assert ( - receipt.status == ResponseCode.SUCCESS - ), "Transaction should have succeeded" + assert receipt.status == ResponseCode.SUCCESS, "Transaction should have succeeded" diff --git a/tests/unit/evm_address_test.py b/tests/unit/evm_address_test.py index 61c565276..1bdfceaa1 100644 --- a/tests/unit/evm_address_test.py +++ b/tests/unit/evm_address_test.py @@ -1,8 +1,12 @@ +from __future__ import annotations + import re import pytest + from hiero_sdk_python.crypto.evm_address import EvmAddress + pytestmark = pytest.mark.unit @@ -64,7 +68,7 @@ def test_equality(): def test_to_proto_key(): - "Test to_proto_key raises error when call." + """Test to_proto_key raises error when call.""" raw = bytes(range(20)) address = EvmAddress.from_bytes(raw) diff --git a/tests/unit/exceptions_test.py b/tests/unit/exceptions_test.py index b90ce9516..431397d21 100644 --- a/tests/unit/exceptions_test.py +++ b/tests/unit/exceptions_test.py @@ -1,10 +1,16 @@ -import pytest +from __future__ import annotations + from unittest.mock import Mock -from hiero_sdk_python.exceptions import PrecheckError, MaxAttemptsError, ReceiptStatusError + +import pytest + +from hiero_sdk_python.exceptions import MaxAttemptsError, PrecheckError, ReceiptStatusError from hiero_sdk_python.response_code import ResponseCode + pytestmark = pytest.mark.unit + def test_precheck_error_typing_and_defaults(): """Test PrecheckError with and without optional arguments.""" # Mock TransactionId @@ -23,6 +29,7 @@ def test_precheck_error_typing_and_defaults(): expected_msg = "Transaction failed precheck with status: INVALID_TRANSACTION (1), transaction ID: 0.0.123@111.222" assert str(err_default) == expected_msg + def test_precheck_error_with_int_status(): """Test PrecheckError accepts int status for backwards compatibility.""" tx_id_mock = Mock() @@ -34,6 +41,7 @@ def test_precheck_error_with_int_status(): assert isinstance(err.status, ResponseCode) assert "INVALID_TRANSACTION" in str(err) + def test_max_attempts_error_typing(): """Test MaxAttemptsError with required and optional arguments.""" # Case 1: With last_error as BaseException @@ -47,18 +55,19 @@ def test_max_attempts_error_typing(): # Case 2: Without last_error err_simple = MaxAttemptsError("Just failed", "0.0.4") assert str(err_simple) == "Just failed" - + # Case 3: With other BaseException types runtime_error = RuntimeError("Network timeout") err_runtime = MaxAttemptsError("Request failed", "0.0.5", runtime_error) assert err_runtime.last_error is runtime_error assert "Network timeout" in str(err_runtime) + def test_receipt_status_error_typing(): """Test ReceiptStatusError initialization.""" tx_id_mock = Mock() receipt_mock = Mock() - + # Case 1: Default message err = ReceiptStatusError(ResponseCode.RECEIPT_NOT_FOUND, tx_id_mock, receipt_mock) assert err.status == ResponseCode.RECEIPT_NOT_FOUND @@ -69,21 +78,23 @@ def test_receipt_status_error_typing(): err_custom = ReceiptStatusError(ResponseCode.FAIL_INVALID, tx_id_mock, receipt_mock, "Fatal receipt error") assert str(err_custom) == "Fatal receipt error" + def test_receipt_status_error_with_int_status(): """Test ReceiptStatusError accepts int status for backwards compatibility.""" tx_id_mock = Mock() receipt_mock = Mock() - + # Pass int directly (mimicking protobuf field) err = ReceiptStatusError(22, tx_id_mock, receipt_mock) assert err.status == ResponseCode.SUCCESS assert isinstance(err.status, ResponseCode) assert "SUCCESS" in str(err) + def test_receipt_status_error_with_none_transaction_id(): """Test ReceiptStatusError when transaction_id is None.""" receipt_mock = Mock() - + # Case 1: None transaction_id with default message err = ReceiptStatusError(ResponseCode.INVALID_ACCOUNT_ID, None, receipt_mock) assert err.status == ResponseCode.INVALID_ACCOUNT_ID @@ -92,12 +103,13 @@ def test_receipt_status_error_with_none_transaction_id(): # Message should not include transaction ID when it's None assert "contained error status: INVALID_ACCOUNT_ID" in str(err) assert "transaction" not in str(err).split("contained")[0] # No tx ID before "contained" - + # Case 2: None transaction_id with custom message err_custom = ReceiptStatusError(ResponseCode.FAIL_INVALID, None, receipt_mock, "No transaction ID available") assert err_custom.transaction_id is None assert str(err_custom) == "No transaction ID available" + def test_precheck_error_with_none_transaction_id(): """Test PrecheckError when transaction_id is None.""" # Case 1: None transaction_id with default message @@ -106,8 +118,8 @@ def test_precheck_error_with_none_transaction_id(): assert err.transaction_id is None expected_msg = f"Transaction failed precheck with status: INSUFFICIENT_ACCOUNT_BALANCE ({ResponseCode.INSUFFICIENT_ACCOUNT_BALANCE})" assert str(err) == expected_msg - + # Case 2: None transaction_id with custom message err_custom = PrecheckError(ResponseCode.INVALID_SIGNATURE, None, "Missing transaction context") assert err_custom.transaction_id is None - assert str(err_custom) == "Missing transaction context" \ No newline at end of file + assert str(err_custom) == "Missing transaction context" diff --git a/tests/unit/executable_test.py b/tests/unit/executable_test.py index ad9cd936a..bbe7f76a2 100644 --- a/tests/unit/executable_test.py +++ b/tests/unit/executable_test.py @@ -1,10 +1,14 @@ -import pytest -import grpc -from unittest.mock import patch +from __future__ import annotations + from itertools import chain, repeat +from unittest.mock import patch + +import grpc +import pytest from hiero_sdk_python.account.account_create_transaction import AccountCreateTransaction from hiero_sdk_python.account.account_id import AccountId +from hiero_sdk_python.consensus.topic_create_transaction import TopicCreateTransaction from hiero_sdk_python.crypto.private_key import PrivateKey from hiero_sdk_python.exceptions import MaxAttemptsError, PrecheckError from hiero_sdk_python.executable import ( @@ -22,7 +26,6 @@ from hiero_sdk_python.hapi.services.transaction_response_pb2 import ( TransactionResponse as TransactionResponseProto, ) -from hiero_sdk_python.consensus.topic_create_transaction import TopicCreateTransaction from hiero_sdk_python.query.account_balance_query import CryptoGetAccountBalanceQuery from hiero_sdk_python.query.transaction_get_receipt_query import ( TransactionGetReceiptQuery, @@ -32,26 +35,21 @@ from hiero_sdk_python.transaction.transaction_id import TransactionId from tests.unit.mock_server import RealRpcError, mock_hedera_servers + pytestmark = pytest.mark.unit def test_retry_success_before_max_attempts(): """Test that execution succeeds on the last attempt before max_attempts.""" - busy_response = TransactionResponseProto( - nodeTransactionPrecheckCode=ResponseCode.BUSY - ) + busy_response = TransactionResponseProto(nodeTransactionPrecheckCode=ResponseCode.BUSY) ok_response = TransactionResponseProto(nodeTransactionPrecheckCode=ResponseCode.OK) receipt_response = response_pb2.Response( transactionGetReceipt=transaction_get_receipt_pb2.TransactionGetReceiptResponse( - header=response_header_pb2.ResponseHeader( - nodeTransactionPrecheckCode=ResponseCode.OK - ), + header=response_header_pb2.ResponseHeader(nodeTransactionPrecheckCode=ResponseCode.OK), receipt=transaction_receipt_pb2.TransactionReceipt( status=ResponseCode.SUCCESS, - accountID=basic_types_pb2.AccountID( - shardNum=0, realmNum=0, accountNum=1234 - ), + accountID=basic_types_pb2.AccountID(shardNum=0, realmNum=0, accountNum=1234), ), ) ) @@ -75,18 +73,14 @@ def test_retry_success_before_max_attempts(): try: receipt = transaction.execute(client) except (Exception, grpc.RpcError) as e: - pytest.fail( - f"Transaction execution should not raise an exception, but raised: {e}" - ) + pytest.fail(f"Transaction execution should not raise an exception, but raised: {e}") assert receipt.status == ResponseCode.SUCCESS def test_retry_failure_after_max_attempts(): """Test that execution fails after max_attempts with retriable errors.""" - busy_response = TransactionResponseProto( - nodeTransactionPrecheckCode=ResponseCode.BUSY - ) + busy_response = TransactionResponseProto(nodeTransactionPrecheckCode=ResponseCode.BUSY) response_sequences = [[busy_response, busy_response]] @@ -119,12 +113,8 @@ def test_node_switching_after_single_grpc_error(): receipt_response = response_pb2.Response( transactionGetReceipt=transaction_get_receipt_pb2.TransactionGetReceiptResponse( - header=response_header_pb2.ResponseHeader( - nodeTransactionPrecheckCode=ResponseCode.OK - ), - receipt=transaction_receipt_pb2.TransactionReceipt( - status=ResponseCode.SUCCESS - ), + header=response_header_pb2.ResponseHeader(nodeTransactionPrecheckCode=ResponseCode.OK), + receipt=transaction_receipt_pb2.TransactionReceipt(status=ResponseCode.SUCCESS), ) ) @@ -148,13 +138,11 @@ def test_node_switching_after_single_grpc_error(): try: transaction.execute(client) except (Exception, grpc.RpcError) as e: - pytest.fail( - f"Transaction execution should not raise an exception, but raised: {e}" - ) + pytest.fail(f"Transaction execution should not raise an exception, but raised: {e}") # Verify we're now on the second node - assert transaction.node_account_ids[ - transaction._node_account_ids_index - ] == AccountId(0, 0, 4), "Client should have switched to the second node" + assert transaction.node_account_ids[transaction._node_account_ids_index] == AccountId(0, 0, 4), ( + "Client should have switched to the second node" + ) def test_node_switching_after_multiple_grpc_errors(): @@ -164,12 +152,8 @@ def test_node_switching_after_multiple_grpc_errors(): receipt_response = response_pb2.Response( transactionGetReceipt=transaction_get_receipt_pb2.TransactionGetReceiptResponse( - header=response_header_pb2.ResponseHeader( - nodeTransactionPrecheckCode=ResponseCode.OK - ), - receipt=transaction_receipt_pb2.TransactionReceipt( - status=ResponseCode.SUCCESS - ), + header=response_header_pb2.ResponseHeader(nodeTransactionPrecheckCode=ResponseCode.OK), + receipt=transaction_receipt_pb2.TransactionReceipt(status=ResponseCode.SUCCESS), ) ) @@ -192,34 +176,19 @@ def test_node_switching_after_multiple_grpc_errors(): try: receipt = transaction.execute(client) except (Exception, grpc.RpcError) as e: - pytest.fail( - f"Transaction execution should not raise an exception, but raised: {e}" - ) + pytest.fail(f"Transaction execution should not raise an exception, but raised: {e}") # Verify we're now on the third node - assert transaction.node_account_ids[ - transaction._node_account_ids_index - ] == AccountId(0, 0, 5), "Client should have switched to the third node" + assert transaction.node_account_ids[transaction._node_account_ids_index] == AccountId(0, 0, 5), ( + "Client should have switched to the third node" + ) assert receipt.status == ResponseCode.SUCCESS def test_transaction_with_expired_error_not_retried(): """Test that an expired error is not retried.""" - error_response = TransactionResponseProto( - nodeTransactionPrecheckCode=ResponseCode.TRANSACTION_EXPIRED - ) - ok_response = TransactionResponseProto(nodeTransactionPrecheckCode=ResponseCode.OK) + error_response = TransactionResponseProto(nodeTransactionPrecheckCode=ResponseCode.TRANSACTION_EXPIRED) - receipt_response = response_pb2.Response( - transactionGetReceipt=transaction_get_receipt_pb2.TransactionGetReceiptResponse( - header=response_header_pb2.ResponseHeader( - nodeTransactionPrecheckCode=ResponseCode.OK - ), - receipt=transaction_receipt_pb2.TransactionReceipt( - status=ResponseCode.SUCCESS - ), - ) - ) response_sequences = [[error_response]] with ( @@ -240,21 +209,8 @@ def test_transaction_with_expired_error_not_retried(): def test_transaction_with_fatal_error_not_retried(): """Test that a fatal error is not retried.""" - error_response = TransactionResponseProto( - nodeTransactionPrecheckCode=ResponseCode.INVALID_TRANSACTION_BODY - ) - ok_response = TransactionResponseProto(nodeTransactionPrecheckCode=ResponseCode.OK) + error_response = TransactionResponseProto(nodeTransactionPrecheckCode=ResponseCode.INVALID_TRANSACTION_BODY) - receipt_response = response_pb2.Response( - transactionGetReceipt=transaction_get_receipt_pb2.TransactionGetReceiptResponse( - header=response_header_pb2.ResponseHeader( - nodeTransactionPrecheckCode=ResponseCode.OK - ), - receipt=transaction_receipt_pb2.TransactionReceipt( - status=ResponseCode.SUCCESS - ), - ) - ) response_sequences = [[error_response]] with ( @@ -275,26 +231,18 @@ def test_transaction_with_fatal_error_not_retried(): def test_exponential_backoff_retry(): """Test that the retry mechanism uses exponential backoff.""" - busy_response = TransactionResponseProto( - nodeTransactionPrecheckCode=ResponseCode.BUSY - ) + busy_response = TransactionResponseProto(nodeTransactionPrecheckCode=ResponseCode.BUSY) ok_response = TransactionResponseProto(nodeTransactionPrecheckCode=ResponseCode.OK) receipt_response = response_pb2.Response( transactionGetReceipt=transaction_get_receipt_pb2.TransactionGetReceiptResponse( - header=response_header_pb2.ResponseHeader( - nodeTransactionPrecheckCode=ResponseCode.OK - ), - receipt=transaction_receipt_pb2.TransactionReceipt( - status=ResponseCode.SUCCESS - ), + header=response_header_pb2.ResponseHeader(nodeTransactionPrecheckCode=ResponseCode.OK), + receipt=transaction_receipt_pb2.TransactionReceipt(status=ResponseCode.SUCCESS), ) ) # Create several BUSY responses to force multiple retries - response_sequences = [ - [busy_response, busy_response, busy_response, ok_response, receipt_response] - ] + response_sequences = [[busy_response, busy_response, busy_response, ok_response, receipt_response]] # Use a mock for time.sleep to capture the delay values with ( @@ -312,40 +260,28 @@ def test_exponential_backoff_retry(): try: transaction.execute(client) except (Exception, grpc.RpcError) as e: - pytest.fail( - f"Transaction execution should not raise an exception, but raised: {e}" - ) + pytest.fail(f"Transaction execution should not raise an exception, but raised: {e}") # Check that time.sleep was called the expected number of times (3 retries) - assert ( - mock_sleep.call_count == 3 - ), f"Expected 3 sleep calls, got {mock_sleep.call_count}" + assert mock_sleep.call_count == 3, f"Expected 3 sleep calls, got {mock_sleep.call_count}" # Verify exponential backoff by checking sleep durations are increasing sleep_args = [call_args[0][0] for call_args in mock_sleep.call_args_list] # Verify each subsequent delay is double than the previous for i in range(1, len(sleep_args)): - assert ( - abs(sleep_args[i] - sleep_args[i - 1] * 2) < 0.1 - ), f"Expected doubling delays, but got {sleep_args}" + assert abs(sleep_args[i] - sleep_args[i - 1] * 2) < 0.1, f"Expected doubling delays, but got {sleep_args}" def test_retriable_error_does_not_switch_node(): """Test that a retriable error does not switch nodes.""" - busy_response = TransactionResponseProto( - nodeTransactionPrecheckCode=ResponseCode.BUSY - ) + busy_response = TransactionResponseProto(nodeTransactionPrecheckCode=ResponseCode.BUSY) ok_response = TransactionResponseProto(nodeTransactionPrecheckCode=ResponseCode.OK) receipt_response = response_pb2.Response( transactionGetReceipt=transaction_get_receipt_pb2.TransactionGetReceiptResponse( - header=response_header_pb2.ResponseHeader( - nodeTransactionPrecheckCode=ResponseCode.OK - ), - receipt=transaction_receipt_pb2.TransactionReceipt( - status=ResponseCode.SUCCESS - ), + header=response_header_pb2.ResponseHeader(nodeTransactionPrecheckCode=ResponseCode.OK), + receipt=transaction_receipt_pb2.TransactionReceipt(status=ResponseCode.SUCCESS), ) ) response_sequences = [[busy_response, ok_response, receipt_response]] @@ -362,29 +298,23 @@ def test_retriable_error_does_not_switch_node(): try: transaction.execute(client) except (Exception, grpc.RpcError) as e: - pytest.fail( - f"Transaction execution should not raise an exception, but raised: {e}" - ) + pytest.fail(f"Transaction execution should not raise an exception, but raised: {e}") - assert client.network.current_node._account_id == AccountId( - 0, 0, 3 - ), "Client should not switch node on retriable errors" + assert client.network.current_node._account_id == AccountId(0, 0, 3), ( + "Client should not switch node on retriable errors" + ) def test_topic_create_transaction_retry_on_busy(): """Test that TopicCreateTransaction retries on BUSY response.""" # First response is BUSY, second is OK - busy_response = TransactionResponseProto( - nodeTransactionPrecheckCode=ResponseCode.BUSY - ) + busy_response = TransactionResponseProto(nodeTransactionPrecheckCode=ResponseCode.BUSY) ok_response = TransactionResponseProto(nodeTransactionPrecheckCode=ResponseCode.OK) receipt_response = response_pb2.Response( transactionGetReceipt=transaction_get_receipt_pb2.TransactionGetReceiptResponse( - header=response_header_pb2.ResponseHeader( - nodeTransactionPrecheckCode=ResponseCode.OK - ), + header=response_header_pb2.ResponseHeader(nodeTransactionPrecheckCode=ResponseCode.OK), receipt=transaction_receipt_pb2.TransactionReceipt( status=ResponseCode.SUCCESS, topicID=basic_types_pb2.TopicID(shardNum=0, realmNum=0, topicNum=456), @@ -402,11 +332,7 @@ def test_topic_create_transaction_retry_on_busy(): ): client.max_attempts = 3 - tx = ( - TopicCreateTransaction() - .set_memo("Test with retry") - .set_admin_key(PrivateKey.generate().public_key()) - ) + tx = TopicCreateTransaction().set_memo("Test with retry").set_admin_key(PrivateKey.generate().public_key()) try: receipt = tx.execute(client) @@ -420,17 +346,13 @@ def test_topic_create_transaction_retry_on_busy(): assert mock_sleep.call_count == 1, "Should have retried once" # Verify we didn't switch nodes (BUSY is retriable without node switch) - assert client.network.current_node._account_id == AccountId( - 0, 0, 3 - ), "Should not have switched nodes on BUSY" + assert client.network.current_node._account_id == AccountId(0, 0, 3), "Should not have switched nodes on BUSY" def test_topic_create_transaction_fails_on_nonretriable_error(): """Test that TopicCreateTransaction fails on non-retriable error.""" # Create a response with a non-retriable error - error_response = TransactionResponseProto( - nodeTransactionPrecheckCode=ResponseCode.INVALID_TRANSACTION_BODY - ) + error_response = TransactionResponseProto(nodeTransactionPrecheckCode=ResponseCode.INVALID_TRANSACTION_BODY) response_sequences = [ [error_response], @@ -440,15 +362,9 @@ def test_topic_create_transaction_fails_on_nonretriable_error(): mock_hedera_servers(response_sequences) as client, patch("hiero_sdk_python.executable.time.sleep"), ): - tx = ( - TopicCreateTransaction() - .set_memo("Test with error") - .set_admin_key(PrivateKey.generate().public_key()) - ) + tx = TopicCreateTransaction().set_memo("Test with error").set_admin_key(PrivateKey.generate().public_key()) - with pytest.raises( - PrecheckError, match="failed precheck with status: INVALID_TRANSACTION_BODY" - ): + with pytest.raises(PrecheckError, match="failed precheck with status: INVALID_TRANSACTION_BODY"): tx.execute(client) @@ -459,12 +375,8 @@ def test_transaction_node_switching_body_bytes(): receipt_response = response_pb2.Response( transactionGetReceipt=transaction_get_receipt_pb2.TransactionGetReceiptResponse( - header=response_header_pb2.ResponseHeader( - nodeTransactionPrecheckCode=ResponseCode.OK - ), - receipt=transaction_receipt_pb2.TransactionReceipt( - status=ResponseCode.SUCCESS - ), + header=response_header_pb2.ResponseHeader(nodeTransactionPrecheckCode=ResponseCode.OK), + receipt=transaction_receipt_pb2.TransactionReceipt(status=ResponseCode.SUCCESS), ) ) # First node gives error, second node gives OK, third node gives error @@ -490,29 +402,24 @@ def test_transaction_node_switching_body_bytes(): ) for node in client.network.nodes: - assert ( - transaction._transaction_body_bytes.get(node._account_id) is not None - ), "Transaction body bytes should be set for all nodes" - sig_map = transaction._signature_map.get( - transaction._transaction_body_bytes[node._account_id] + assert transaction._transaction_body_bytes.get(node._account_id) is not None, ( + "Transaction body bytes should be set for all nodes" ) + sig_map = transaction._signature_map.get(transaction._transaction_body_bytes[node._account_id]) assert sig_map is not None, "Signature map should be set for all nodes" assert len(sig_map.sigPair) == 1, "Signature map should have one signature" - assert ( - sig_map.sigPair[0].pubKeyPrefix - == client.operator_private_key.public_key().to_bytes_raw() - ), "Signature should be for the operator" + assert sig_map.sigPair[0].pubKeyPrefix == client.operator_private_key.public_key().to_bytes_raw(), ( + "Signature should be for the operator" + ) try: transaction.execute(client) except (Exception, grpc.RpcError) as e: - pytest.fail( - f"Transaction execution should not raise an exception, but raised: {e}" - ) + pytest.fail(f"Transaction execution should not raise an exception, but raised: {e}") # Verify we're now on the second node - assert transaction.node_account_ids[ - transaction._node_account_ids_index - ] == AccountId(0, 0, 4), "Client should have switched to the second node" + assert transaction.node_account_ids[transaction._node_account_ids_index] == AccountId(0, 0, 4), ( + "Client should have switched to the second node" + ) def test_query_retry_on_busy(): @@ -530,9 +437,7 @@ def test_query_retry_on_busy(): # This response indicates the node cannot process the request at this time busy_response = response_pb2.Response( cryptogetAccountBalance=crypto_get_account_balance_pb2.CryptoGetAccountBalanceResponse( - header=response_header_pb2.ResponseHeader( - nodeTransactionPrecheckCode=ResponseCode.BUSY - ) + header=response_header_pb2.ResponseHeader(nodeTransactionPrecheckCode=ResponseCode.BUSY) ) ) @@ -540,9 +445,7 @@ def test_query_retry_on_busy(): # This simulates a successful account balance query response ok_response = response_pb2.Response( cryptogetAccountBalance=crypto_get_account_balance_pb2.CryptoGetAccountBalanceResponse( - header=response_header_pb2.ResponseHeader( - nodeTransactionPrecheckCode=ResponseCode.OK - ), + header=response_header_pb2.ResponseHeader(nodeTransactionPrecheckCode=ResponseCode.OK), balance=100000000, # Balance in tinybars ) ) @@ -574,9 +477,9 @@ def test_query_retry_on_busy(): assert balance.hbars.to_tinybars() == 100000000 # Verify we switched to the second node assert query._node_account_ids_index == 1 - assert query.node_account_ids[query._node_account_ids_index] == AccountId( - 0, 0, 4 - ), "Client should have switched to the second node" + assert query.node_account_ids[query._node_account_ids_index] == AccountId(0, 0, 4), ( + "Client should have switched to the second node" + ) # Set max_attempts @@ -585,14 +488,14 @@ def test_set_max_attempts_with_valid_param(): # Transaction transaction = AccountCreateTransaction() - assert transaction._max_attempts == None + assert transaction._max_attempts is None transaction.set_max_attempts(10) assert transaction._max_attempts == 10 # Query query = CryptoGetAccountBalanceQuery() - assert query._max_attempts == None + assert query._max_attempts is None query.set_max_attempts(10) assert query._max_attempts == 10 @@ -632,7 +535,7 @@ def test_set_grpc_deadline_with_valid_param(): """Test that set_grpc_deadline updates default value of _grpc_deadline.""" # Transaction transaction = AccountCreateTransaction() - assert transaction._grpc_deadline == None + assert transaction._grpc_deadline is None returned = transaction.set_grpc_deadline(20) assert transaction._grpc_deadline == 20 @@ -640,7 +543,7 @@ def test_set_grpc_deadline_with_valid_param(): # Query query = CryptoGetAccountBalanceQuery() - assert query._grpc_deadline == None + assert query._grpc_deadline is None returned = query.set_grpc_deadline(20) assert query._grpc_deadline == 20 @@ -666,21 +569,15 @@ def test_set_grpc_deadline_with_invalid_type(invalid_grpc_deadline): query.set_grpc_deadline(invalid_grpc_deadline) -@pytest.mark.parametrize( - "invalid_grpc_deadline", [0, -10, 0.0, -2.3, float("inf"), float("nan")] -) +@pytest.mark.parametrize("invalid_grpc_deadline", [0, -10, 0.0, -2.3, float("inf"), float("nan")]) def test_set_grpc_deadline_with_invalid_value(invalid_grpc_deadline): """Test that set_grpc_deadline raises ValueError for non-positive values.""" - with pytest.raises( - ValueError, match="grpc_deadline must be a finite value greater than 0" - ): + with pytest.raises(ValueError, match="grpc_deadline must be a finite value greater than 0"): # Transaction transaction = AccountCreateTransaction() transaction.set_grpc_deadline(invalid_grpc_deadline) - with pytest.raises( - ValueError, match="grpc_deadline must be a finite value greater than 0" - ): + with pytest.raises(ValueError, match="grpc_deadline must be a finite value greater than 0"): # Query query = CryptoGetAccountBalanceQuery() query.set_grpc_deadline(invalid_grpc_deadline) @@ -700,7 +597,7 @@ def test_set_request_timeout_with_valid_param(): """Test that set_request_timeout updates default value of _request_timeout.""" # Transaction transaction = AccountCreateTransaction() - assert transaction._request_timeout == None + assert transaction._request_timeout is None returned = transaction.set_request_timeout(200) assert transaction._request_timeout == 200 @@ -708,7 +605,7 @@ def test_set_request_timeout_with_valid_param(): # Query query = CryptoGetAccountBalanceQuery() - assert query._request_timeout == None + assert query._request_timeout is None returned = query.set_request_timeout(200) assert query._request_timeout == 200 @@ -735,20 +632,14 @@ def test_set_request_timeout_with_invalid_type(invalid_request_timeout): query.set_request_timeout(invalid_request_timeout) -@pytest.mark.parametrize( - "invalid_request_timeout", [0, -10, 0.0, -2.3, float("inf"), float("nan")] -) +@pytest.mark.parametrize("invalid_request_timeout", [0, -10, 0.0, -2.3, float("inf"), float("nan")]) def test_set_request_timeout_with_invalid_value(invalid_request_timeout): """Test that set_request_timeout raises ValueError for non-positive values.""" - with pytest.raises( - ValueError, match="request_timeout must be a finite value greater than 0" - ): + with pytest.raises(ValueError, match="request_timeout must be a finite value greater than 0"): transaction = AccountCreateTransaction() transaction.set_request_timeout(invalid_request_timeout) - with pytest.raises( - ValueError, match="request_timeout must be a finite value greater than 0" - ): + with pytest.raises(ValueError, match="request_timeout must be a finite value greater than 0"): query = CryptoGetAccountBalanceQuery() query.set_request_timeout(invalid_request_timeout) @@ -766,9 +657,7 @@ def test_warning_when_grpc_deadline_exceeds_request_timeout(): # Test is transaction_recepit_or_record def test_is_transaction_receipt_or_record_request(): """Detect receipt and record query requests correctly.""" - receipt_query = query_pb2.Query( - transactionGetReceipt=transaction_get_receipt_pb2.TransactionGetReceiptQuery() - ) + receipt_query = query_pb2.Query(transactionGetReceipt=transaction_get_receipt_pb2.TransactionGetReceiptQuery()) assert _is_transaction_receipt_or_record_request(receipt_query) is True assert _is_transaction_receipt_or_record_request(object()) is False @@ -779,7 +668,7 @@ def test_set_min_backoff_with_valid_param(): """Test that set_min_backoff updates default value of _min_backoff.""" # Transaction transaction = AccountCreateTransaction() - assert transaction._min_backoff == None + assert transaction._min_backoff is None returned = transaction.set_min_backoff(2) assert transaction._min_backoff == 2 @@ -787,7 +676,7 @@ def test_set_min_backoff_with_valid_param(): # Query query = CryptoGetAccountBalanceQuery() - assert query._min_backoff == None + assert query._min_backoff is None returned = query.set_min_backoff(2) assert query._min_backoff == 2 @@ -813,9 +702,7 @@ def test_set_min_backoff_with_invalid_type(invalid_min_backoff): query.set_min_backoff(invalid_min_backoff) -@pytest.mark.parametrize( - "invalid_min_backoff", [-1, -10, float("inf"), float("-inf"), float("nan")] -) +@pytest.mark.parametrize("invalid_min_backoff", [-1, -10, float("inf"), float("-inf"), float("nan")]) def test_set_min_backoff_with_invalid_value(invalid_min_backoff): """Test that set_min_backoff raises ValueError for invalid values.""" with pytest.raises(ValueError, match="min_backoff must be a finite value >= 0"): @@ -847,7 +734,7 @@ def test_set_max_backoff_with_valid_param(): """Test that set_max_backoff updates default value of _max_backoff.""" # Transaction transaction = AccountCreateTransaction() - assert transaction._max_backoff == None + assert transaction._max_backoff is None returned = transaction.set_max_backoff(2) assert transaction._max_backoff == 2 @@ -855,7 +742,7 @@ def test_set_max_backoff_with_valid_param(): # Query query = CryptoGetAccountBalanceQuery() - assert query._max_backoff == None + assert query._max_backoff is None returned = query.set_max_backoff(2) assert query._max_backoff == 2 @@ -880,9 +767,7 @@ def test_set_max_backoff_with_invalid_type(invalid_max_backoff): query.set_max_backoff(invalid_max_backoff) -@pytest.mark.parametrize( - "invalid_max_backoff", [-1, -10, float("inf"), float("-inf"), float("nan")] -) +@pytest.mark.parametrize("invalid_max_backoff", [-1, -10, float("inf"), float("-inf"), float("nan")]) def test_set_max_backoff_with_invalid_value(invalid_max_backoff): """Test that set_max_backoff raises ValueError for invalid values.""" with pytest.raises(ValueError, match="max_backoff must be a finite value >= 0"): @@ -955,11 +840,7 @@ def test_no_healthy_nodes_raises(mock_client): """Test that execution fails if no healthy nodes are available.""" mock_client.network._healthy_nodes = [] - tx = ( - AccountCreateTransaction() - .set_key_without_alias(PrivateKey.generate().public_key()) - .set_initial_balance(1) - ) + tx = AccountCreateTransaction().set_key_without_alias(PrivateKey.generate().public_key()).set_initial_balance(1) with pytest.raises(RuntimeError, match="No healthy nodes available"): tx.execute(mock_client) @@ -995,9 +876,7 @@ def test_set_timeout_overrides_parameter_timeout(mock_client): # Reuest timeout def test_request_timeout_exceeded_stops_execution(): """Test that execution stops when request_timeout is exceeded.""" - busy_response = TransactionResponseProto( - nodeTransactionPrecheckCode=ResponseCode.BUSY - ) + busy_response = TransactionResponseProto(nodeTransactionPrecheckCode=ResponseCode.BUSY) response_sequences = [[busy_response]] @@ -1012,9 +891,7 @@ def fake_time(): with ( mock_hedera_servers(response_sequences) as client, patch("hiero_sdk_python.executable.time.sleep"), - patch( - "hiero_sdk_python.executable.time.monotonic", side_effect=lambda: next(time_iter) - ), + patch("hiero_sdk_python.executable.time.monotonic", side_effect=lambda: next(time_iter)), patch("hiero_sdk_python.node._Node.is_healthy", return_value=True), patch( "hiero_sdk_python.executable._execute_method", @@ -1024,11 +901,7 @@ def fake_time(): client._request_timeout = 10 client.max_attempts = 5 - tx = ( - AccountCreateTransaction() - .set_key_without_alias(PrivateKey.generate().public_key()) - .set_initial_balance(1) - ) + tx = AccountCreateTransaction().set_key_without_alias(PrivateKey.generate().public_key()).set_initial_balance(1) with pytest.raises(MaxAttemptsError): tx.execute(client) @@ -1040,9 +913,7 @@ def fake_time(): RealRpcError(grpc.StatusCode.DEADLINE_EXCEEDED, "timeout"), RealRpcError(grpc.StatusCode.UNAVAILABLE, "unavailable"), RealRpcError(grpc.StatusCode.RESOURCE_EXHAUSTED, "busy"), - RealRpcError( - grpc.StatusCode.INTERNAL, "received rst stream" - ), # internal with rst stream + RealRpcError(grpc.StatusCode.INTERNAL, "received rst stream"), # internal with rst stream Exception("non grpc exception"), # non grpc exception ], ) @@ -1056,9 +927,7 @@ def test_should_exponential_returns_true(error): "error", [ RealRpcError(grpc.StatusCode.INVALID_ARGUMENT, "invalid args"), - RealRpcError( - grpc.StatusCode.INTERNAL, "internal" - ), # internal with no rst stream + RealRpcError(grpc.StatusCode.INTERNAL, "internal"), # internal with no rst stream ], ) def test_should_exponential_returns_false(error): @@ -1081,12 +950,8 @@ def test_should_exponential_error_mark_node_unhealty_and_advance(error): receipt_response = response_pb2.Response( transactionGetReceipt=transaction_get_receipt_pb2.TransactionGetReceiptResponse( - header=response_header_pb2.ResponseHeader( - nodeTransactionPrecheckCode=ResponseCode.OK - ), - receipt=transaction_receipt_pb2.TransactionReceipt( - status=ResponseCode.SUCCESS - ), + header=response_header_pb2.ResponseHeader(nodeTransactionPrecheckCode=ResponseCode.OK), + receipt=transaction_receipt_pb2.TransactionReceipt(status=ResponseCode.SUCCESS), ) ) @@ -1099,11 +964,7 @@ def test_should_exponential_error_mark_node_unhealty_and_advance(error): mock_hedera_servers(response_sequences) as client, patch("hiero_sdk_python.executable.time.sleep") as mock_sleep, ): - tx = ( - AccountCreateTransaction() - .set_key_without_alias(PrivateKey.generate().public_key()) - .set_initial_balance(1) - ) + tx = AccountCreateTransaction().set_key_without_alias(PrivateKey.generate().public_key()).set_initial_balance(1) receipt = tx.execute(client) @@ -1122,12 +983,8 @@ def test_rst_stream_error_marks_node_unhealthy_and_advances_without_backoff(): receipt_response = response_pb2.Response( transactionGetReceipt=transaction_get_receipt_pb2.TransactionGetReceiptResponse( - header=response_header_pb2.ResponseHeader( - nodeTransactionPrecheckCode=ResponseCode.OK - ), - receipt=transaction_receipt_pb2.TransactionReceipt( - status=ResponseCode.SUCCESS - ), + header=response_header_pb2.ResponseHeader(nodeTransactionPrecheckCode=ResponseCode.OK), + receipt=transaction_receipt_pb2.TransactionReceipt(status=ResponseCode.SUCCESS), ) ) @@ -1140,11 +997,7 @@ def test_rst_stream_error_marks_node_unhealthy_and_advances_without_backoff(): mock_hedera_servers(response_sequences) as client, patch("hiero_sdk_python.executable.time.sleep") as mock_sleep, ): - tx = ( - AccountCreateTransaction() - .set_key_without_alias(PrivateKey.generate().public_key()) - .set_initial_balance(1) - ) + tx = AccountCreateTransaction().set_key_without_alias(PrivateKey.generate().public_key()).set_initial_balance(1) receipt = tx.execute(client) @@ -1172,30 +1025,20 @@ def test_non_exponential_grpc_error_raises_exception(error): mock_hedera_servers(response_sequences) as client, pytest.raises(grpc.RpcError), ): - tx = ( - AccountCreateTransaction() - .set_key_without_alias(PrivateKey.generate().public_key()) - .set_initial_balance(1) - ) + tx = AccountCreateTransaction().set_key_without_alias(PrivateKey.generate().public_key()).set_initial_balance(1) tx.execute(client) def test_execution_skips_unhealthy_nodes_and_advances(): """Execution should skip unhealthy nodes and advance to the next healthy one.""" - busy_response = TransactionResponseProto( - nodeTransactionPrecheckCode=ResponseCode.BUSY - ) + busy_response = TransactionResponseProto(nodeTransactionPrecheckCode=ResponseCode.BUSY) ok_response = TransactionResponseProto(nodeTransactionPrecheckCode=ResponseCode.OK) receipt_response = response_pb2.Response( transactionGetReceipt=transaction_get_receipt_pb2.TransactionGetReceiptResponse( - header=response_header_pb2.ResponseHeader( - nodeTransactionPrecheckCode=ResponseCode.OK - ), - receipt=transaction_receipt_pb2.TransactionReceipt( - status=ResponseCode.SUCCESS - ), + header=response_header_pb2.ResponseHeader(nodeTransactionPrecheckCode=ResponseCode.OK), + receipt=transaction_receipt_pb2.TransactionReceipt(status=ResponseCode.SUCCESS), ) ) @@ -1211,11 +1054,7 @@ def test_execution_skips_unhealthy_nodes_and_advances(): side_effect=chain([False, True], repeat(True)), ), ): - tx = ( - AccountCreateTransaction() - .set_key_without_alias(PrivateKey.generate().public_key()) - .set_initial_balance(1) - ) + tx = AccountCreateTransaction().set_key_without_alias(PrivateKey.generate().public_key()).set_initial_balance(1) receipt = tx.execute(client) @@ -1226,32 +1065,24 @@ def test_execution_skips_unhealthy_nodes_and_advances(): def test_execution_raises_if_all_nodes_unhealthy(mock_client): """Execution should raise RuntimeError if all nodes are unhealthy.""" - tx = ( - AccountCreateTransaction() - .set_key_without_alias(PrivateKey.generate().public_key()) - .set_initial_balance(1) - ) + tx = AccountCreateTransaction().set_key_without_alias(PrivateKey.generate().public_key()).set_initial_balance(1) # Patch node health to always return False - with patch("hiero_sdk_python.node._Node.is_healthy", side_effect=repeat(False)): - with pytest.raises(RuntimeError, match="All nodes are unhealthy"): - tx.execute(mock_client) + with ( + patch("hiero_sdk_python.node._Node.is_healthy", side_effect=repeat(False)), + pytest.raises(RuntimeError, match="All nodes are unhealthy"), + ): + tx.execute(mock_client) @pytest.mark.parametrize( "tx", [ - TransactionRecordQuery().set_transaction_id( - TransactionId.from_string("0.0.3@1769674705.770340600") - ), - TransactionGetReceiptQuery().set_transaction_id( - TransactionId.from_string("0.0.3@1769674705.770340600") - ), + TransactionRecordQuery().set_transaction_id(TransactionId.from_string("0.0.3@1769674705.770340600")), + TransactionGetReceiptQuery().set_transaction_id(TransactionId.from_string("0.0.3@1769674705.770340600")), ], ) -def test_unhealthy_node_receipt_request_triggers_delay_and_no_node_change( - tx, mock_client -): +def test_unhealthy_node_receipt_request_triggers_delay_and_no_node_change(tx, mock_client): """Unhealthy node with transaction receipt/record request calls _delay_for_attempt but does not advance node.""" initial_index = tx._node_account_ids_index @@ -1259,8 +1090,7 @@ def test_unhealthy_node_receipt_request_triggers_delay_and_no_node_change( patch("hiero_sdk_python.node._Node.is_healthy", return_value=False), patch("hiero_sdk_python.executable._delay_for_attempt") as mock_delay, ): - - with pytest.raises(Exception): + with pytest.raises(MaxAttemptsError): tx.execute(mock_client) # _delay_for_attempt called @@ -1274,20 +1104,14 @@ def test_retry_invalid_node_account_updates_network(): Verify that a RETRY execution state with INVALID_NODE_ACCOUNT triggers node backoff, network refresh, and retry delay before succeeding. """ - error_response = TransactionResponseProto( - nodeTransactionPrecheckCode=ResponseCode.INVALID_NODE_ACCOUNT - ) + error_response = TransactionResponseProto(nodeTransactionPrecheckCode=ResponseCode.INVALID_NODE_ACCOUNT) ok_response = TransactionResponseProto(nodeTransactionPrecheckCode=ResponseCode.OK) receipt_response = response_pb2.Response( transactionGetReceipt=transaction_get_receipt_pb2.TransactionGetReceiptResponse( - header=response_header_pb2.ResponseHeader( - nodeTransactionPrecheckCode=ResponseCode.OK - ), - receipt=transaction_receipt_pb2.TransactionReceipt( - status=ResponseCode.SUCCESS - ), + header=response_header_pb2.ResponseHeader(nodeTransactionPrecheckCode=ResponseCode.OK), + receipt=transaction_receipt_pb2.TransactionReceipt(status=ResponseCode.SUCCESS), ) ) @@ -1313,11 +1137,7 @@ def test_retry_invalid_node_account_updates_network(): return_value=receipt_response, ), ): - tx = ( - AccountCreateTransaction() - .set_key_without_alias(PrivateKey.generate().public_key()) - .set_initial_balance(1) - ) + tx = AccountCreateTransaction().set_key_without_alias(PrivateKey.generate().public_key()).set_initial_balance(1) tx.execute(client) diff --git a/tests/unit/file_append_transaction_test.py b/tests/unit/file_append_transaction_test.py index 7b6c3cf10..479acefd5 100644 --- a/tests/unit/file_append_transaction_test.py +++ b/tests/unit/file_append_transaction_test.py @@ -1,10 +1,19 @@ -from unittest.mock import patch, MagicMock +from __future__ import annotations + +from unittest.mock import MagicMock, patch + import pytest from hiero_sdk_python.exceptions import PrecheckError, ReceiptStatusError from hiero_sdk_python.file.file_append_transaction import FileAppendTransaction from hiero_sdk_python.file.file_id import FileId -from hiero_sdk_python.hapi.services import response_header_pb2, response_pb2, transaction_get_receipt_pb2, transaction_receipt_pb2, transaction_response_pb2 +from hiero_sdk_python.hapi.services import ( + response_header_pb2, + response_pb2, + transaction_get_receipt_pb2, + transaction_receipt_pb2, + transaction_response_pb2, +) from hiero_sdk_python.hapi.services.schedulable_transaction_body_pb2 import ( SchedulableTransactionBody, ) @@ -80,15 +89,11 @@ def test_get_required_chunks(): def test_freeze_with_generates_transaction_ids(): """Test that freeze_with generates transaction IDs for all chunks.""" content = b"Large content that needs multiple chunks" - file_tx = FileAppendTransaction( - file_id=FileId(0, 0, 12345), contents=content, chunk_size=10 - ) + file_tx = FileAppendTransaction(file_id=FileId(0, 0, 12345), contents=content, chunk_size=10) # Mock client and transaction_id mock_client = MagicMock() - mock_transaction_id = TransactionId( - account_id=MagicMock(), valid_start=Timestamp(0, 1) - ) + mock_transaction_id = TransactionId(account_id=MagicMock(), valid_start=Timestamp(0, 1)) file_tx.transaction_id = mock_transaction_id file_tx.freeze_with(mock_client) @@ -109,14 +114,10 @@ def test_freeze_with_generates_transaction_ids(): def test_validate_chunking(): """Test chunking validation.""" large_content = b"Large content " * 1000 # ~14000 bytes - file_tx = FileAppendTransaction( - contents=large_content, chunk_size=100, max_chunks=5 - ) + file_tx = FileAppendTransaction(contents=large_content, chunk_size=100, max_chunks=5) # Should raise error when required chunks > max_chunks - with pytest.raises( - ValueError, match="Cannot execute FileAppendTransaction with more than 5 chunks" - ): + with pytest.raises(ValueError, match="Cannot execute FileAppendTransaction with more than 5 chunks"): file_tx._validate_chunking() @@ -175,24 +176,16 @@ def test_build_scheduled_body(): assert schedulable_body.fileAppend.contents == contents[:100] # First chunk - def test_file_append_tx_execute_without_wait_for_receipt(file_id): """Test should return TransactionResponse when wait_for_receipt=False.""" content = "Hello Hiero" - tx_response = transaction_response_pb2.TransactionResponse( - nodeTransactionPrecheckCode=ResponseCode.OK - ) + tx_response = transaction_response_pb2.TransactionResponse(nodeTransactionPrecheckCode=ResponseCode.OK) response_sequence = [tx_response] # No receipt with mock_hedera_servers([response_sequence]) as client: - tx = ( - FileAppendTransaction() - .set_file_id(file_id) - .set_contents(content) - .freeze_with(client) - ) + tx = FileAppendTransaction().set_file_id(file_id).set_contents(content).freeze_with(client) response = tx.execute(client, wait_for_receipt=False) @@ -203,54 +196,35 @@ def test_file_append_tx_execute_with_wait_for_receipt(file_id): """Test should return TransactionReceipt when wait_for_receipt=True.""" content = "Hello Hiero" - tx_response = transaction_response_pb2.TransactionResponse( - nodeTransactionPrecheckCode=ResponseCode.OK - ) + tx_response = transaction_response_pb2.TransactionResponse(nodeTransactionPrecheckCode=ResponseCode.OK) receipt_response = response_pb2.Response( transactionGetReceipt=transaction_get_receipt_pb2.TransactionGetReceiptResponse( - header=response_header_pb2.ResponseHeader( - nodeTransactionPrecheckCode=ResponseCode.OK - ), - receipt=transaction_receipt_pb2.TransactionReceipt( - status=ResponseCode.SUCCESS - ) + header=response_header_pb2.ResponseHeader(nodeTransactionPrecheckCode=ResponseCode.OK), + receipt=transaction_receipt_pb2.TransactionReceipt(status=ResponseCode.SUCCESS), ) ) - response_sequence = [tx_response, receipt_response] + response_sequence = [tx_response, receipt_response] with mock_hedera_servers([response_sequence]) as client: - tx = ( - FileAppendTransaction() - .set_file_id(file_id) - .set_contents(content) - .freeze_with(client) - ) + tx = FileAppendTransaction().set_file_id(file_id).set_contents(content).freeze_with(client) response = tx.execute(client, wait_for_receipt=True) assert isinstance(response, TransactionReceipt) - def test_file_append_tx_execute_all_without_wait_for_receipt(file_id): """Test should return list of TransactionResponse when wait_for_receipt=False.""" content = "Hello Hiero" - tx_response = transaction_response_pb2.TransactionResponse( - nodeTransactionPrecheckCode=ResponseCode.OK - ) + tx_response = transaction_response_pb2.TransactionResponse(nodeTransactionPrecheckCode=ResponseCode.OK) response_sequence = [tx_response] # No receipt with mock_hedera_servers([response_sequence]) as client: - tx = ( - FileAppendTransaction() - .set_file_id(file_id) - .set_contents(content) - .freeze_with(client) - ) + tx = FileAppendTransaction().set_file_id(file_id).set_contents(content).freeze_with(client) responses = tx.execute_all(client, wait_for_receipt=False) @@ -262,36 +236,26 @@ def test_file_append_tx_execute_all_with_wait_for_receipt(file_id): """Test should return list of TransactionReceipt when wait_for_receipt=True.""" content = "Hello Hiero" - tx_response = transaction_response_pb2.TransactionResponse( - nodeTransactionPrecheckCode=ResponseCode.OK - ) + tx_response = transaction_response_pb2.TransactionResponse(nodeTransactionPrecheckCode=ResponseCode.OK) receipt_response = response_pb2.Response( transactionGetReceipt=transaction_get_receipt_pb2.TransactionGetReceiptResponse( - header=response_header_pb2.ResponseHeader( - nodeTransactionPrecheckCode=ResponseCode.OK - ), - receipt=transaction_receipt_pb2.TransactionReceipt( - status=ResponseCode.SUCCESS - ) + header=response_header_pb2.ResponseHeader(nodeTransactionPrecheckCode=ResponseCode.OK), + receipt=transaction_receipt_pb2.TransactionReceipt(status=ResponseCode.SUCCESS), ) ) - response_sequence = [tx_response, receipt_response] + response_sequence = [tx_response, receipt_response] with mock_hedera_servers([response_sequence]) as client: - tx = ( - FileAppendTransaction() - .set_file_id(file_id) - .set_contents(content) - .freeze_with(client) - ) + tx = FileAppendTransaction().set_file_id(file_id).set_contents(content).freeze_with(client) responses = tx.execute_all(client, wait_for_receipt=True) assert isinstance(responses, list) assert isinstance(responses[0], TransactionReceipt) + def test_file_append_tx_execute_throw_error_when_transaction_fails(file_id): """Test execute tx should throw error if transaction fails.""" tx_response = transaction_response_pb2.TransactionResponse( @@ -300,15 +264,10 @@ def test_file_append_tx_execute_throw_error_when_transaction_fails(file_id): response_sequence = [tx_response] with mock_hedera_servers([response_sequence]) as client: - tx = ( - FileAppendTransaction() - .set_file_id(file_id) - .set_contents("Hello Hiero") - .freeze_with(client) - ) + tx = FileAppendTransaction().set_file_id(file_id).set_contents("Hello Hiero").freeze_with(client) with pytest.raises(PrecheckError, match="Transaction failed precheck"): - tx.execute(client) + tx.execute(client) def test_file_append_tx_execute_all_throw_error_when_transaction_fails(file_id): @@ -319,141 +278,96 @@ def test_file_append_tx_execute_all_throw_error_when_transaction_fails(file_id): response_sequence = [tx_response] with mock_hedera_servers([response_sequence]) as client: - tx = ( - FileAppendTransaction() - .set_file_id(file_id) - .set_contents("Hello Hiero") - .freeze_with(client) - ) + tx = FileAppendTransaction().set_file_id(file_id).set_contents("Hello Hiero").freeze_with(client) with pytest.raises(PrecheckError, match="Transaction failed precheck"): - tx.execute_all(client) + tx.execute_all(client) + def test_execute_raises_error_when_validation_enabled(file_id): """Test execute raises error for failing transactions when validate_status is True.""" - ok_response = transaction_response_pb2.TransactionResponse( - nodeTransactionPrecheckCode=ResponseCode.OK - ) + ok_response = transaction_response_pb2.TransactionResponse(nodeTransactionPrecheckCode=ResponseCode.OK) receipt_response = response_pb2.Response( transactionGetReceipt=transaction_get_receipt_pb2.TransactionGetReceiptResponse( - header=response_header_pb2.ResponseHeader( - nodeTransactionPrecheckCode=ResponseCode.OK - ), - receipt=transaction_receipt_pb2.TransactionReceipt( - status=ResponseCode.INVALID_SIGNATURE - ), + header=response_header_pb2.ResponseHeader(nodeTransactionPrecheckCode=ResponseCode.OK), + receipt=transaction_receipt_pb2.TransactionReceipt(status=ResponseCode.INVALID_SIGNATURE), ) ) response_sequence = [[ok_response, receipt_response]] with mock_hedera_servers(response_sequence) as client: - tx = ( - FileAppendTransaction() - .set_file_id(file_id) - .set_contents("Hello Hiero") - .freeze_with(client) - ) + tx = FileAppendTransaction().set_file_id(file_id).set_contents("Hello Hiero").freeze_with(client) with pytest.raises(ReceiptStatusError) as e: tx.execute(client, validate_status=True) assert e.value.status == ResponseCode.INVALID_SIGNATURE + def test_execute_returns_failed_receipt_when_validation_disabled(file_id): """Test execute returns the failing receipt by default when validation is disabled.""" - ok_response = transaction_response_pb2.TransactionResponse( - nodeTransactionPrecheckCode=ResponseCode.OK - ) + ok_response = transaction_response_pb2.TransactionResponse(nodeTransactionPrecheckCode=ResponseCode.OK) receipt_response = response_pb2.Response( transactionGetReceipt=transaction_get_receipt_pb2.TransactionGetReceiptResponse( - header=response_header_pb2.ResponseHeader( - nodeTransactionPrecheckCode=ResponseCode.OK - ), - receipt=transaction_receipt_pb2.TransactionReceipt( - status=ResponseCode.INVALID_SIGNATURE - ), + header=response_header_pb2.ResponseHeader(nodeTransactionPrecheckCode=ResponseCode.OK), + receipt=transaction_receipt_pb2.TransactionReceipt(status=ResponseCode.INVALID_SIGNATURE), ) ) response_sequence = [[ok_response, receipt_response]] with mock_hedera_servers(response_sequence) as client: - tx = ( - FileAppendTransaction() - .set_file_id(file_id) - .set_contents("Hello Hiero") - .freeze_with(client) - ) - + tx = FileAppendTransaction().set_file_id(file_id).set_contents("Hello Hiero").freeze_with(client) + receipt = tx.execute(client) assert receipt.status == ResponseCode.INVALID_SIGNATURE + def test_file_append_execute_all_raises_error_with_validation(file_id): """Test execute_all raises error when validate_status is True and a chunk fails.""" content = "A" * 1024 - tx_response = transaction_response_pb2.TransactionResponse( - nodeTransactionPrecheckCode=ResponseCode.OK - ) + tx_response = transaction_response_pb2.TransactionResponse(nodeTransactionPrecheckCode=ResponseCode.OK) receipt_response = response_pb2.Response( transactionGetReceipt=transaction_get_receipt_pb2.TransactionGetReceiptResponse( - header=response_header_pb2.ResponseHeader( - nodeTransactionPrecheckCode=ResponseCode.OK - ), - receipt=transaction_receipt_pb2.TransactionReceipt( - status=ResponseCode.INVALID_FILE_ID - ) + header=response_header_pb2.ResponseHeader(nodeTransactionPrecheckCode=ResponseCode.OK), + receipt=transaction_receipt_pb2.TransactionReceipt(status=ResponseCode.INVALID_FILE_ID), ) ) response_sequence = [tx_response, receipt_response] * 4 with mock_hedera_servers([response_sequence]) as client: - tx = ( - FileAppendTransaction() - .set_file_id(file_id) - .set_contents(content) - .freeze_with(client) - ) + tx = FileAppendTransaction().set_file_id(file_id).set_contents(content).freeze_with(client) with pytest.raises(ReceiptStatusError) as e: tx.execute_all(client, validate_status=True) assert e.value.status == ResponseCode.INVALID_FILE_ID + def test_file_append_execute_all_returns_receipt_without_validation(file_id): """Test execute_all returns failing receipts normally when validation is disabled.""" content = "A" * 1024 - tx_response = transaction_response_pb2.TransactionResponse( - nodeTransactionPrecheckCode=ResponseCode.OK - ) + tx_response = transaction_response_pb2.TransactionResponse(nodeTransactionPrecheckCode=ResponseCode.OK) receipt_response = response_pb2.Response( transactionGetReceipt=transaction_get_receipt_pb2.TransactionGetReceiptResponse( - header=response_header_pb2.ResponseHeader( - nodeTransactionPrecheckCode=ResponseCode.OK - ), - receipt=transaction_receipt_pb2.TransactionReceipt( - status=ResponseCode.INVALID_FILE_ID - ) + header=response_header_pb2.ResponseHeader(nodeTransactionPrecheckCode=ResponseCode.OK), + receipt=transaction_receipt_pb2.TransactionReceipt(status=ResponseCode.INVALID_FILE_ID), ) ) response_sequence = [tx_response, receipt_response] * 4 # 4 chunks with mock_hedera_servers([response_sequence]) as client: - tx = ( - FileAppendTransaction() - .set_file_id(file_id) - .set_contents(content) - .freeze_with(client) - ) + tx = FileAppendTransaction().set_file_id(file_id).set_contents(content).freeze_with(client) receipts = tx.execute_all(client) diff --git a/tests/unit/file_contents_query_test.py b/tests/unit/file_contents_query_test.py index 2848a5f67..82a1a4356 100644 --- a/tests/unit/file_contents_query_test.py +++ b/tests/unit/file_contents_query_test.py @@ -2,6 +2,8 @@ Unit tests for FileContentsQuery. """ +from __future__ import annotations + from unittest.mock import Mock import pytest @@ -17,6 +19,7 @@ from hiero_sdk_python.response_code import ResponseCode from tests.unit.mock_server import mock_hedera_servers + pytestmark = pytest.mark.unit @@ -35,9 +38,7 @@ def test_execute_fails_with_missing_file_id(mock_client): """Test request creation with missing File ID.""" query = FileContentsQuery() - with pytest.raises( - ValueError, match="File ID must be set before making the request." - ): + with pytest.raises(ValueError, match="File ID must be set before making the request."): query.execute(mock_client) diff --git a/tests/unit/file_create_transaction_test.py b/tests/unit/file_create_transaction_test.py index 395d5800f..dbdc1a5e4 100644 --- a/tests/unit/file_create_transaction_test.py +++ b/tests/unit/file_create_transaction_test.py @@ -1,30 +1,34 @@ -import pytest +from __future__ import annotations + from unittest.mock import patch -from hiero_sdk_python.file.file_create_transaction import FileCreateTransaction +import pytest + from hiero_sdk_python.crypto.private_key import PrivateKey from hiero_sdk_python.crypto.public_key import PublicKey -from hiero_sdk_python.hbar import Hbar -from hiero_sdk_python.response_code import ResponseCode -from hiero_sdk_python.timestamp import Timestamp -from hiero_sdk_python.hapi.services import basic_types_pb2, response_pb2 -from hiero_sdk_python.hapi.services.transaction_response_pb2 import ( - TransactionResponse as TransactionResponseProto, -) -from hiero_sdk_python.hapi.services.transaction_receipt_pb2 import ( - TransactionReceipt as TransactionReceiptProto, -) +from hiero_sdk_python.file.file_create_transaction import FileCreateTransaction from hiero_sdk_python.hapi.services import ( - transaction_get_receipt_pb2, + basic_types_pb2, + file_create_pb2, response_header_pb2, + response_pb2, + transaction_get_receipt_pb2, ) -from hiero_sdk_python.hapi.services import file_create_pb2 from hiero_sdk_python.hapi.services.schedulable_transaction_body_pb2 import ( SchedulableTransactionBody, ) - +from hiero_sdk_python.hapi.services.transaction_receipt_pb2 import ( + TransactionReceipt as TransactionReceiptProto, +) +from hiero_sdk_python.hapi.services.transaction_response_pb2 import ( + TransactionResponse as TransactionResponseProto, +) +from hiero_sdk_python.hbar import Hbar +from hiero_sdk_python.response_code import ResponseCode +from hiero_sdk_python.timestamp import Timestamp from tests.unit.mock_server import mock_hedera_servers + pytestmark = pytest.mark.unit @@ -36,9 +40,7 @@ def test_constructor_with_parameters(): contents = b"Test file content" file_memo = "Test memo" - file_tx = FileCreateTransaction( - keys=key_list, contents=contents, file_memo=file_memo - ) + file_tx = FileCreateTransaction(keys=key_list, contents=contents, file_memo=file_memo) assert file_tx.keys == key_list assert file_tx.contents == contents @@ -54,9 +56,7 @@ def test_constructor_default_expiration_time(): with patch("time.time", return_value=fixed_time): file_tx = FileCreateTransaction() - expected_expiration = Timestamp( - fixed_time + FileCreateTransaction.DEFAULT_EXPIRY_SECONDS, 0 - ) + expected_expiration = Timestamp(fixed_time + FileCreateTransaction.DEFAULT_EXPIRY_SECONDS, 0) assert file_tx.expiration_time == expected_expiration @@ -78,9 +78,7 @@ def test_build_transaction_body(mock_account_ids): public_key = private_key.public_key() key_list = [public_key] - file_tx = FileCreateTransaction( - keys=key_list, contents=b"Test content", file_memo="Test memo" - ) + file_tx = FileCreateTransaction(keys=key_list, contents=b"Test content", file_memo="Test memo") # Set operator and node account IDs needed for building transaction body file_tx.operator_account_id = operator_id @@ -189,9 +187,7 @@ def test_set_methods_require_not_frozen(mock_client): ] for method_name, value in test_cases: - with pytest.raises( - Exception, match="Transaction is immutable; it has been frozen" - ): + with pytest.raises(Exception, match="Transaction is immutable; it has been frozen"): getattr(file_tx, method_name)(value) @@ -210,9 +206,7 @@ def test_file_create_transaction_can_execute(): # Create a response for the receipt query receipt_query_response = response_pb2.Response( transactionGetReceipt=transaction_get_receipt_pb2.TransactionGetReceiptResponse( - header=response_header_pb2.ResponseHeader( - nodeTransactionPrecheckCode=ResponseCode.OK - ), + header=response_header_pb2.ResponseHeader(nodeTransactionPrecheckCode=ResponseCode.OK), receipt=mock_receipt_proto, ) ) @@ -233,9 +227,7 @@ def test_file_create_transaction_can_execute(): receipt = transaction.execute(client) - assert ( - receipt.status == ResponseCode.SUCCESS - ), "Transaction should have succeeded" + assert receipt.status == ResponseCode.SUCCESS, "Transaction should have succeeded" assert receipt.file_id.file == 5678 @@ -262,9 +254,7 @@ def test_file_create_transaction_from_proto(): assert isinstance(from_proto.keys[0], PublicKey) # Deserialize empty protobuf - from_proto = FileCreateTransaction()._from_proto( - file_create_pb2.FileCreateTransactionBody() - ) + from_proto = FileCreateTransaction()._from_proto(file_create_pb2.FileCreateTransactionBody()) # Verify empty protobuf deserializes to empty/default values assert from_proto.contents == b"" diff --git a/tests/unit/file_delete_transaction_test.py b/tests/unit/file_delete_transaction_test.py index fb6bdb663..2aa95840f 100644 --- a/tests/unit/file_delete_transaction_test.py +++ b/tests/unit/file_delete_transaction_test.py @@ -2,6 +2,8 @@ Test cases for the FileDeleteTransaction class. """ +from __future__ import annotations + from unittest.mock import MagicMock import pytest @@ -11,6 +13,7 @@ SchedulableTransactionBody, ) + pytestmark = pytest.mark.unit @@ -81,7 +84,6 @@ def test_sign_transaction(mock_client, file_id): def test_to_proto(mock_client, file_id): """Test converting the file delete transaction to protobuf format after signing.""" - delete_tx = FileDeleteTransaction(file_id=file_id) private_key = MagicMock() diff --git a/tests/unit/file_id_test.py b/tests/unit/file_id_test.py index bc405c504..c43f7f2d6 100644 --- a/tests/unit/file_id_test.py +++ b/tests/unit/file_id_test.py @@ -1,7 +1,11 @@ +from __future__ import annotations + import pytest + from hiero_sdk_python.file.file_id import FileId from hiero_sdk_python.hapi.services import basic_types_pb2 + pytestmark = pytest.mark.unit @@ -18,7 +22,7 @@ def test_default_initialization(): assert file_id.shard == 0 assert file_id.realm == 0 assert file_id.file == 0 - assert file_id.checksum == None + assert file_id.checksum is None def test_custom_initialization(): @@ -28,7 +32,7 @@ def test_custom_initialization(): assert file_id.shard == 1 assert file_id.realm == 2 assert file_id.file == 3 - assert file_id.checksum == None + assert file_id.checksum is None def test_str_representation(): @@ -64,7 +68,7 @@ def test_from_string_valid(): assert file_id.shard == 1 assert file_id.realm == 2 assert file_id.file == 3 - assert file_id.checksum == None + assert file_id.checksum is None def test_from_string_with_spaces(): @@ -74,7 +78,7 @@ def test_from_string_with_spaces(): assert file_id.shard == 1 assert file_id.realm == 2 assert file_id.file == 3 - assert file_id.checksum == None + assert file_id.checksum is None def test_from_string_zeros(): @@ -84,7 +88,7 @@ def test_from_string_zeros(): assert file_id.shard == 0 assert file_id.realm == 0 assert file_id.file == 0 - assert file_id.checksum == None + assert file_id.checksum is None def test_from_string_large_numbers(): @@ -94,7 +98,7 @@ def test_from_string_large_numbers(): assert file_id.shard == 999 assert file_id.realm == 888 assert file_id.file == 777 - assert file_id.checksum == None + assert file_id.checksum is None def test_from_string_with_checksum(): diff --git a/tests/unit/file_info_query_test.py b/tests/unit/file_info_query_test.py index e8602c34a..1c562f308 100644 --- a/tests/unit/file_info_query_test.py +++ b/tests/unit/file_info_query_test.py @@ -1,21 +1,24 @@ -import pytest +from __future__ import annotations + from unittest.mock import Mock +import pytest + from hiero_sdk_python.file.file_id import FileId -from hiero_sdk_python.hapi.services.query_header_pb2 import ResponseType from hiero_sdk_python.file.file_info_query import FileInfoQuery -from hiero_sdk_python.response_code import ResponseCode from hiero_sdk_python.hapi.services import ( - response_pb2, - response_header_pb2, file_get_info_pb2, + response_header_pb2, + response_pb2, ) from hiero_sdk_python.hapi.services.basic_types_pb2 import KeyList as KeyListProto +from hiero_sdk_python.hapi.services.query_header_pb2 import ResponseType from hiero_sdk_python.hapi.services.timestamp_pb2 import Timestamp as TimestampProto +from hiero_sdk_python.response_code import ResponseCode from hiero_sdk_python.timestamp import Timestamp - from tests.unit.mock_server import mock_hedera_servers + pytestmark = pytest.mark.unit @@ -34,9 +37,7 @@ def test_execute_fails_with_missing_file_id(mock_client): """Test request creation with missing File ID.""" query = FileInfoQuery() - with pytest.raises( - ValueError, match="File ID must be set before making the request." - ): + with pytest.raises(ValueError, match="File ID must be set before making the request."): query.execute(mock_client) diff --git a/tests/unit/file_info_test.py b/tests/unit/file_info_test.py index e463f8fca..76e7fb35c 100644 --- a/tests/unit/file_info_test.py +++ b/tests/unit/file_info_test.py @@ -2,6 +2,8 @@ Unit tests for the FileInfo class. """ +from __future__ import annotations + import pytest from hiero_sdk_python.crypto.private_key import PrivateKey @@ -11,6 +13,7 @@ from hiero_sdk_python.hapi.services.file_get_info_pb2 import FileGetInfoResponse from hiero_sdk_python.timestamp import Timestamp + pytestmark = pytest.mark.unit @@ -32,7 +35,7 @@ def file_info(): def proto_file_info(): """Fixture for a proto FileInfo object""" public_key = PrivateKey.generate_ed25519().public_key() - proto = FileGetInfoResponse.FileInfo( + return FileGetInfoResponse.FileInfo( fileID=FileId(0, 0, 100)._to_proto(), size=1024, expirationTime=Timestamp(1625097600, 0)._to_protobuf(), @@ -41,7 +44,6 @@ def proto_file_info(): memo="Test file memo", ledger_id=b"test_ledger_id", ) - return proto def test_file_info_initialization(file_info): diff --git a/tests/unit/file_update_transaction_test.py b/tests/unit/file_update_transaction_test.py index e0cb78952..cf0a71840 100644 --- a/tests/unit/file_update_transaction_test.py +++ b/tests/unit/file_update_transaction_test.py @@ -2,6 +2,8 @@ Test cases for the FileUpdateTransaction class. """ +from __future__ import annotations + from unittest.mock import MagicMock import pytest @@ -32,6 +34,7 @@ from hiero_sdk_python.timestamp import Timestamp from tests.unit.mock_server import mock_hedera_servers + pytestmark = pytest.mark.unit @@ -141,9 +144,7 @@ def test_set_methods_require_not_frozen(mock_client, file_id): ] for method_name, value in test_cases: - with pytest.raises( - Exception, match="Transaction is immutable; it has been frozen" - ): + with pytest.raises(Exception, match="Transaction is immutable; it has been frozen"): getattr(file_tx, method_name)(value) @@ -300,9 +301,7 @@ def test_file_update_transaction_can_execute(file_id): # Create a response for the receipt query receipt_query_response = response_pb2.Response( transactionGetReceipt=transaction_get_receipt_pb2.TransactionGetReceiptResponse( - header=response_header_pb2.ResponseHeader( - nodeTransactionPrecheckCode=ResponseCode.OK - ), + header=response_header_pb2.ResponseHeader(nodeTransactionPrecheckCode=ResponseCode.OK), receipt=mock_receipt_proto, ) ) @@ -325,9 +324,7 @@ def test_file_update_transaction_can_execute(file_id): receipt = transaction.execute(client) - assert ( - receipt.status == ResponseCode.SUCCESS - ), "Transaction should have succeeded" + assert receipt.status == ResponseCode.SUCCESS, "Transaction should have succeeded" def test_get_method(): diff --git a/tests/unit/freeze_transaction_test.py b/tests/unit/freeze_transaction_test.py index f359154e2..026545b9f 100644 --- a/tests/unit/freeze_transaction_test.py +++ b/tests/unit/freeze_transaction_test.py @@ -2,6 +2,8 @@ Test cases for the FreezeTransaction class. """ +from __future__ import annotations + from unittest.mock import MagicMock import pytest @@ -17,6 +19,7 @@ from hiero_sdk_python.system.freeze_type import FreezeType from hiero_sdk_python.timestamp import Timestamp + pytestmark = pytest.mark.unit @@ -203,9 +206,7 @@ def test_set_methods_require_not_frozen(mock_client, freeze_params): ] for method_name, value in test_cases: - with pytest.raises( - Exception, match="Transaction is immutable; it has been frozen" - ): + with pytest.raises(Exception, match="Transaction is immutable; it has been frozen"): getattr(freeze_tx, method_name)(value) diff --git a/tests/unit/freeze_type_test.py b/tests/unit/freeze_type_test.py index 58110dc14..a37c0c676 100644 --- a/tests/unit/freeze_type_test.py +++ b/tests/unit/freeze_type_test.py @@ -2,6 +2,8 @@ Test cases for the FreezeType enum class. """ +from __future__ import annotations + from unittest.mock import MagicMock import pytest @@ -11,6 +13,7 @@ ) from hiero_sdk_python.system.freeze_type import FreezeType + pytestmark = pytest.mark.unit @@ -43,7 +46,7 @@ def test_from_proto_all_types(): def test_from_proto_invalid_value(): """Test _from_proto method with invalid proto value returns UNKNOWN_FREEZE_TYPE.""" mock_proto = MagicMock() - mock_proto.__eq__ = lambda self, other: False + mock_proto.__eq__ = lambda _self, _other: False result = FreezeType._from_proto(mock_proto) assert result == FreezeType.UNKNOWN_FREEZE_TYPE diff --git a/tests/unit/get_receipt_query_test.py b/tests/unit/get_receipt_query_test.py index 66cdc0225..d1c6c1301 100644 --- a/tests/unit/get_receipt_query_test.py +++ b/tests/unit/get_receipt_query_test.py @@ -1,8 +1,11 @@ """Tests for the TransactionGetReceiptQuery functionality.""" -import pytest +from __future__ import annotations + from unittest.mock import patch +import pytest + from hiero_sdk_python.account.account_id import AccountId from hiero_sdk_python.exceptions import MaxAttemptsError, ReceiptStatusError from hiero_sdk_python.hapi.services import ( @@ -16,9 +19,9 @@ TransactionGetReceiptQuery, ) from hiero_sdk_python.response_code import ResponseCode - from tests.unit.mock_server import mock_hedera_servers + pytestmark = pytest.mark.unit @@ -27,12 +30,8 @@ def test_transaction_get_receipt_query(transaction_id): """Test basic functionality of TransactionGetReceiptQuery with a mocked client.""" response = response_pb2.Response( transactionGetReceipt=transaction_get_receipt_pb2.TransactionGetReceiptResponse( - header=response_header_pb2.ResponseHeader( - nodeTransactionPrecheckCode=ResponseCode.OK - ), - receipt=transaction_receipt_pb2.TransactionReceipt( - status=ResponseCode.SUCCESS - ), + header=response_header_pb2.ResponseHeader(nodeTransactionPrecheckCode=ResponseCode.OK), + receipt=transaction_receipt_pb2.TransactionReceipt(status=ResponseCode.SUCCESS), ) ) @@ -55,25 +54,17 @@ def test_receipt_query_retry_on_receipt_not_found(transaction_id): # First response has RECEIPT_NOT_FOUND, second has SUCCESS not_found_response = response_pb2.Response( transactionGetReceipt=transaction_get_receipt_pb2.TransactionGetReceiptResponse( - header=response_header_pb2.ResponseHeader( - nodeTransactionPrecheckCode=ResponseCode.OK - ), - receipt=transaction_receipt_pb2.TransactionReceipt( - status=ResponseCode.RECEIPT_NOT_FOUND - ), + header=response_header_pb2.ResponseHeader(nodeTransactionPrecheckCode=ResponseCode.OK), + receipt=transaction_receipt_pb2.TransactionReceipt(status=ResponseCode.RECEIPT_NOT_FOUND), ) ) success_response = response_pb2.Response( transactionGetReceipt=transaction_get_receipt_pb2.TransactionGetReceiptResponse( - header=response_header_pb2.ResponseHeader( - nodeTransactionPrecheckCode=ResponseCode.OK - ), + header=response_header_pb2.ResponseHeader(nodeTransactionPrecheckCode=ResponseCode.OK), receipt=transaction_receipt_pb2.TransactionReceipt( status=ResponseCode.SUCCESS, - accountID=basic_types_pb2.AccountID( - shardNum=0, realmNum=0, accountNum=1234 - ), + accountID=basic_types_pb2.AccountID(shardNum=0, realmNum=0, accountNum=1234), ), ) ) @@ -107,12 +98,8 @@ def test_receipt_query_receipt_status_error(transaction_id): # Create a response with a receipt status error error_response = response_pb2.Response( transactionGetReceipt=transaction_get_receipt_pb2.TransactionGetReceiptResponse( - header=response_header_pb2.ResponseHeader( - nodeTransactionPrecheckCode=ResponseCode.UNKNOWN - ), - receipt=transaction_receipt_pb2.TransactionReceipt( - status=ResponseCode.UNKNOWN - ), + header=response_header_pb2.ResponseHeader(nodeTransactionPrecheckCode=ResponseCode.UNKNOWN), + receipt=transaction_receipt_pb2.TransactionReceipt(status=ResponseCode.UNKNOWN), ) ) @@ -143,11 +130,7 @@ def test_transaction_get_receipt_query_sets_include_child_receipts_in_request( """ Test that _make_request() sets include_child_receipts correctly in the protobuf query. """ - query = ( - TransactionGetReceiptQuery() - .set_transaction_id(transaction_id) - .set_include_children(True) - ) + query = TransactionGetReceiptQuery().set_transaction_id(transaction_id).set_include_children(True) request = query._make_request() @@ -164,17 +147,11 @@ def test_transaction_get_receipt_query_returns_child_receipts_when_requested( """ response = response_pb2.Response( transactionGetReceipt=transaction_get_receipt_pb2.TransactionGetReceiptResponse( - header=response_header_pb2.ResponseHeader( - nodeTransactionPrecheckCode=ResponseCode.OK - ), - receipt=transaction_receipt_pb2.TransactionReceipt( - status=ResponseCode.SUCCESS - ), + header=response_header_pb2.ResponseHeader(nodeTransactionPrecheckCode=ResponseCode.OK), + receipt=transaction_receipt_pb2.TransactionReceipt(status=ResponseCode.SUCCESS), child_transaction_receipts=[ transaction_receipt_pb2.TransactionReceipt(status=ResponseCode.SUCCESS), - transaction_receipt_pb2.TransactionReceipt( - status=ResponseCode.FAIL_INVALID - ), + transaction_receipt_pb2.TransactionReceipt(status=ResponseCode.FAIL_INVALID), ], ) ) @@ -182,11 +159,7 @@ def test_transaction_get_receipt_query_returns_child_receipts_when_requested( response_sequences = [[response]] with mock_hedera_servers(response_sequences) as client: - query = ( - TransactionGetReceiptQuery() - .set_transaction_id(transaction_id) - .set_include_children(True) - ) + query = TransactionGetReceiptQuery().set_transaction_id(transaction_id).set_include_children(True) result = query.execute(client) @@ -207,12 +180,8 @@ def test_transaction_get_receipt_query_children_empty_when_not_requested( """ response = response_pb2.Response( transactionGetReceipt=transaction_get_receipt_pb2.TransactionGetReceiptResponse( - header=response_header_pb2.ResponseHeader( - nodeTransactionPrecheckCode=ResponseCode.OK - ), - receipt=transaction_receipt_pb2.TransactionReceipt( - status=ResponseCode.SUCCESS - ), + header=response_header_pb2.ResponseHeader(nodeTransactionPrecheckCode=ResponseCode.OK), + receipt=transaction_receipt_pb2.TransactionReceipt(status=ResponseCode.SUCCESS), child_transaction_receipts=[ transaction_receipt_pb2.TransactionReceipt(status=ResponseCode.SUCCESS), ], @@ -236,12 +205,8 @@ def test_transaction_get_receipt_query_include_children_with_no_children( """Testing that nothing explode if no children ar passed""" response = response_pb2.Response( transactionGetReceipt=transaction_get_receipt_pb2.TransactionGetReceiptResponse( - header=response_header_pb2.ResponseHeader( - nodeTransactionPrecheckCode=ResponseCode.OK - ), - receipt=transaction_receipt_pb2.TransactionReceipt( - status=ResponseCode.SUCCESS - ), + header=response_header_pb2.ResponseHeader(nodeTransactionPrecheckCode=ResponseCode.OK), + receipt=transaction_receipt_pb2.TransactionReceipt(status=ResponseCode.SUCCESS), # no child_transaction_receipts ) ) @@ -249,11 +214,7 @@ def test_transaction_get_receipt_query_include_children_with_no_children( response_sequences = [[response]] with mock_hedera_servers(response_sequences) as client: - query = ( - TransactionGetReceiptQuery() - .set_transaction_id(transaction_id) - .set_include_children(True) - ) + query = TransactionGetReceiptQuery().set_transaction_id(transaction_id).set_include_children(True) result = query.execute(client) assert result.status == ResponseCode.SUCCESS @@ -269,17 +230,11 @@ def test_transaction_get_receipt_query_returns_duplicate_receipts_when_requested """ response = response_pb2.Response( transactionGetReceipt=transaction_get_receipt_pb2.TransactionGetReceiptResponse( - header=response_header_pb2.ResponseHeader( - nodeTransactionPrecheckCode=ResponseCode.OK - ), - receipt=transaction_receipt_pb2.TransactionReceipt( - status=ResponseCode.SUCCESS - ), + header=response_header_pb2.ResponseHeader(nodeTransactionPrecheckCode=ResponseCode.OK), + receipt=transaction_receipt_pb2.TransactionReceipt(status=ResponseCode.SUCCESS), duplicateTransactionReceipts=[ transaction_receipt_pb2.TransactionReceipt(status=ResponseCode.SUCCESS), - transaction_receipt_pb2.TransactionReceipt( - status=ResponseCode.FAIL_INVALID - ), + transaction_receipt_pb2.TransactionReceipt(status=ResponseCode.FAIL_INVALID), ], ) ) @@ -287,11 +242,7 @@ def test_transaction_get_receipt_query_returns_duplicate_receipts_when_requested response_sequences = [[response]] with mock_hedera_servers(response_sequences) as client: - query = ( - TransactionGetReceiptQuery() - .set_transaction_id(transaction_id) - .set_include_duplicates(True) - ) + query = TransactionGetReceiptQuery().set_transaction_id(transaction_id).set_include_duplicates(True) result = query.execute(client) @@ -300,10 +251,7 @@ def test_transaction_get_receipt_query_returns_duplicate_receipts_when_requested assert result.status == ResponseCode.SUCCESS assert len(result.duplicates) == 2 for idx, duplicate in enumerate(result.duplicates): - assert ( - duplicate._to_proto() - == response.transactionGetReceipt.duplicateTransactionReceipts[idx] - ) + assert duplicate._to_proto() == response.transactionGetReceipt.duplicateTransactionReceipts[idx] def test_transaction_get_receipt_query_returns_empty_duplicate_receipts_when_requested( @@ -315,23 +263,15 @@ def test_transaction_get_receipt_query_returns_empty_duplicate_receipts_when_req """ response = response_pb2.Response( transactionGetReceipt=transaction_get_receipt_pb2.TransactionGetReceiptResponse( - header=response_header_pb2.ResponseHeader( - nodeTransactionPrecheckCode=ResponseCode.OK - ), - receipt=transaction_receipt_pb2.TransactionReceipt( - status=ResponseCode.SUCCESS - ), + header=response_header_pb2.ResponseHeader(nodeTransactionPrecheckCode=ResponseCode.OK), + receipt=transaction_receipt_pb2.TransactionReceipt(status=ResponseCode.SUCCESS), ), ) response_sequences = [[response]] with mock_hedera_servers(response_sequences) as client: - query = ( - TransactionGetReceiptQuery() - .set_transaction_id(transaction_id) - .set_include_duplicates(True) - ) + query = TransactionGetReceiptQuery().set_transaction_id(transaction_id).set_include_duplicates(True) result = query.execute(client) @@ -350,17 +290,11 @@ def test_transaction_get_receipt_query_returns_empty_duplicate_receipts_when_not """ response = response_pb2.Response( transactionGetReceipt=transaction_get_receipt_pb2.TransactionGetReceiptResponse( - header=response_header_pb2.ResponseHeader( - nodeTransactionPrecheckCode=ResponseCode.OK - ), - receipt=transaction_receipt_pb2.TransactionReceipt( - status=ResponseCode.SUCCESS - ), + header=response_header_pb2.ResponseHeader(nodeTransactionPrecheckCode=ResponseCode.OK), + receipt=transaction_receipt_pb2.TransactionReceipt(status=ResponseCode.SUCCESS), duplicateTransactionReceipts=[ transaction_receipt_pb2.TransactionReceipt(status=ResponseCode.SUCCESS), - transaction_receipt_pb2.TransactionReceipt( - status=ResponseCode.FAIL_INVALID - ), + transaction_receipt_pb2.TransactionReceipt(status=ResponseCode.FAIL_INVALID), ], ), ) @@ -376,30 +310,23 @@ def test_transaction_get_receipt_query_returns_empty_duplicate_receipts_when_not assert result.status == ResponseCode.SUCCESS assert len(result.duplicates) == 0 + def test_transaction_receipt_query_should_not_raise_receipt_error(transaction_id): """Test receipt query should not raise error if the status is non success and non retryable when validate_status is false.""" response = response_pb2.Response( transactionGetReceipt=transaction_get_receipt_pb2.TransactionGetReceiptResponse( - header=response_header_pb2.ResponseHeader( - nodeTransactionPrecheckCode=ResponseCode.OK - ), - receipt=transaction_receipt_pb2.TransactionReceipt( - status=ResponseCode.INVALID_SIGNATURE - ), + header=response_header_pb2.ResponseHeader(nodeTransactionPrecheckCode=ResponseCode.OK), + receipt=transaction_receipt_pb2.TransactionReceipt(status=ResponseCode.INVALID_SIGNATURE), ) ) response_sequences = [[response]] with mock_hedera_servers(response_sequences) as client: - query = ( - TransactionGetReceiptQuery() - .set_transaction_id(transaction_id) - .set_validate_status(False) - ) + query = TransactionGetReceiptQuery().set_transaction_id(transaction_id).set_validate_status(False) receipt = query.execute(client) - + assert receipt.status == ResponseCode.INVALID_SIGNATURE @@ -407,25 +334,17 @@ def test_transaction_receipt_query_should_raise_receipt_error(transaction_id): """Test receipt query should raise error if the status is non success and non retryable when validate_status is true.""" response = response_pb2.Response( transactionGetReceipt=transaction_get_receipt_pb2.TransactionGetReceiptResponse( - header=response_header_pb2.ResponseHeader( - nodeTransactionPrecheckCode=ResponseCode.OK - ), - receipt=transaction_receipt_pb2.TransactionReceipt( - status=ResponseCode.INVALID_SIGNATURE - ), + header=response_header_pb2.ResponseHeader(nodeTransactionPrecheckCode=ResponseCode.OK), + receipt=transaction_receipt_pb2.TransactionReceipt(status=ResponseCode.INVALID_SIGNATURE), ) ) response_sequences = [[response]] with mock_hedera_servers(response_sequences) as client: - query = ( - TransactionGetReceiptQuery() - .set_transaction_id(transaction_id) - .set_validate_status(True) - ) + query = TransactionGetReceiptQuery().set_transaction_id(transaction_id).set_validate_status(True) with pytest.raises(ReceiptStatusError) as e: query.execute(client) - - assert e.value.status == ResponseCode.INVALID_SIGNATURE \ No newline at end of file + + assert e.value.status == ResponseCode.INVALID_SIGNATURE diff --git a/tests/unit/hbar_allowance_test.py b/tests/unit/hbar_allowance_test.py index 756d3f28a..246619a93 100644 --- a/tests/unit/hbar_allowance_test.py +++ b/tests/unit/hbar_allowance_test.py @@ -2,6 +2,8 @@ Unit tests for the HbarAllowance class. """ +from __future__ import annotations + import pytest from hiero_sdk_python.account.account_id import AccountId @@ -10,6 +12,7 @@ ) from hiero_sdk_python.tokens.hbar_allowance import HbarAllowance + pytestmark = pytest.mark.unit @@ -29,12 +32,11 @@ def proto_hbar_allowance(): owner_account_id = AccountId(0, 0, 200) spender_account_id = AccountId(0, 0, 300) - proto = CryptoAllowanceProto( + return CryptoAllowanceProto( owner=owner_account_id._to_proto(), spender=spender_account_id._to_proto(), amount=1000, ) - return proto def test_hbar_allowance_initialization(hbar_allowance): @@ -287,9 +289,7 @@ def test_from_proto_field_helper(): # Test with empty field (should not happen in this proto, but testing the method) proto_empty = CryptoAllowanceProto(amount=1000) - result = HbarAllowance._from_proto_field( - proto_empty, "owner", AccountId._from_proto - ) + result = HbarAllowance._from_proto_field(proto_empty, "owner", AccountId._from_proto) assert result is None @@ -365,7 +365,5 @@ def test_string_representation_conditional_formatting(): allowance_amount = HbarAllowance(amount=1000) str_amount = str(allowance_amount) assert ( - "amount=1000" in str_amount - and "owner_account_id" not in str_amount - and "spender_account_id" not in str_amount + "amount=1000" in str_amount and "owner_account_id" not in str_amount and "spender_account_id" not in str_amount ) diff --git a/tests/unit/hbar_test.py b/tests/unit/hbar_test.py index 99137d3f4..f0e42120e 100644 --- a/tests/unit/hbar_test.py +++ b/tests/unit/hbar_test.py @@ -1,10 +1,14 @@ -from decimal import Decimal +from __future__ import annotations + import re +from decimal import Decimal + import pytest from hiero_sdk_python.hbar import Hbar from hiero_sdk_python.hbar_unit import HbarUnit + pytestmark = pytest.mark.unit @@ -26,9 +30,7 @@ def test_constructor(): @pytest.mark.parametrize("invalid_amount", ["1", True, False, {}, object]) def test_constructor_invalid_amount_type(invalid_amount): """Test creation with invalid type raise errors.""" - with pytest.raises( - TypeError, match="Amount must be of type int, float, or Decimal" - ): + with pytest.raises(TypeError, match="Amount must be of type int, float, or Decimal"): Hbar(invalid_amount) @@ -98,9 +100,7 @@ def test_constructor_fractional_non_tinybar_amount(): def test_constructor_invalid_type(): """Test creation of Hbar with invalid type.""" - with pytest.raises( - TypeError, match="Amount must be of type int, float, or Decimal" - ): + with pytest.raises(TypeError, match="Amount must be of type int, float, or Decimal"): Hbar("10") @@ -150,9 +150,7 @@ def test_from_string_preserves_large_integer_hbar_values(): ) def test_from_string_invalid(invalid_str): """Test creation of HBAR from invalid string""" - with pytest.raises( - ValueError, match=re.escape(f"Invalid Hbar format: '{invalid_str}'") - ): + with pytest.raises(ValueError, match=re.escape(f"Invalid Hbar format: '{invalid_str}'")): Hbar.from_string(invalid_str) @@ -214,7 +212,6 @@ def test_comparison(): def test_factory_methods(): """Test the convenient from_X factory methods.""" - # from_microbars # 1 microbar = 100 tinybars result = Hbar.from_microbars(1) @@ -330,9 +327,7 @@ def test_hash_consistency_for_equal_values(): assert d[h1] == "value2" -@pytest.mark.parametrize( - "invalid_tinybars", ["1", 0.1, Decimal("0.1"), True, False, object, {}] -) +@pytest.mark.parametrize("invalid_tinybars", ["1", 0.1, Decimal("0.1"), True, False, object, {}]) def test_from_tinybars_invalid_type_param(invalid_tinybars): """Test from_tinybar method raises error if the type is not int.""" with pytest.raises(TypeError, match=re.escape("tinybars must be an int.")): diff --git a/tests/unit/hbar_transfer_test.py b/tests/unit/hbar_transfer_test.py index 33a68819d..5ff632d4d 100644 --- a/tests/unit/hbar_transfer_test.py +++ b/tests/unit/hbar_transfer_test.py @@ -2,12 +2,15 @@ Unit tests for the HbarTransfer class. """ +from __future__ import annotations + import pytest from hiero_sdk_python.account.account_id import AccountId from hiero_sdk_python.hapi.services import basic_types_pb2 from hiero_sdk_python.tokens.hbar_transfer import HbarTransfer + pytestmark = pytest.mark.unit @@ -28,9 +31,7 @@ def test_hbar_transfer_constructor(mock_account_ids): assert hbar_transfer.is_approved is False # Test with explicit is_approved=True - approved_transfer = HbarTransfer( - account_id=account_id, amount=amount, is_approved=True - ) + approved_transfer = HbarTransfer(account_id=account_id, amount=amount, is_approved=True) assert approved_transfer.account_id == account_id assert approved_transfer.amount == amount @@ -50,9 +51,7 @@ def test_to_proto(mock_account_ids): amount = 1000 is_approved = True - hbar_transfer = HbarTransfer( - account_id=account_id, amount=amount, is_approved=is_approved - ) + hbar_transfer = HbarTransfer(account_id=account_id, amount=amount, is_approved=is_approved) # Convert to protobuf proto = hbar_transfer._to_proto() @@ -72,9 +71,7 @@ def test_from_proto(mock_account_ids): amount = 1000 is_approved = True - proto = basic_types_pb2.AccountAmount( - accountID=account_id._to_proto(), amount=amount, is_approval=is_approved - ) + proto = basic_types_pb2.AccountAmount(accountID=account_id._to_proto(), amount=amount, is_approval=is_approved) hbar_transfer = HbarTransfer._from_proto(proto) diff --git a/tests/unit/hedera_trust_manager_test.py b/tests/unit/hedera_trust_manager_test.py index 33ce72d51..5af6265ae 100644 --- a/tests/unit/hedera_trust_manager_test.py +++ b/tests/unit/hedera_trust_manager_test.py @@ -1,9 +1,14 @@ """Unit tests for _HederaTrustManager certificate validation.""" +from __future__ import annotations + import hashlib + import pytest + from src.hiero_sdk_python.node import _HederaTrustManager + pytestmark = pytest.mark.unit @@ -47,9 +52,7 @@ def test_trust_manager_check_server_trusted_matching_hash(): cert_hash_bytes = hashlib.sha384(pem_cert).digest() cert_hash_hex = cert_hash_bytes.hex().lower() - trust_manager = _HederaTrustManager( - cert_hash_hex.encode("utf-8"), verify_certificate=True - ) + trust_manager = _HederaTrustManager(cert_hash_hex.encode("utf-8"), verify_certificate=True) # Should not raise assert trust_manager.check_server_trusted(pem_cert) is True diff --git a/tests/unit/key_list_test.py b/tests/unit/key_list_test.py index e3b584383..6dadb3260 100644 --- a/tests/unit/key_list_test.py +++ b/tests/unit/key_list_test.py @@ -1,10 +1,13 @@ +from __future__ import annotations + import pytest +from hiero_sdk_python.crypto.key import Key from hiero_sdk_python.crypto.key_list import KeyList from hiero_sdk_python.crypto.private_key import PrivateKey -from hiero_sdk_python.crypto.key import Key from hiero_sdk_python.hapi.services import basic_types_pb2 + pytestmark = pytest.mark.unit @@ -188,9 +191,7 @@ def test_from_proto_threshold(): proto_key = kl.to_proto_key() - loaded = KeyList.from_proto( - proto_key.thresholdKey.keys, threshold=proto_key.thresholdKey.threshold - ) + loaded = KeyList.from_proto(proto_key.thresholdKey.keys, threshold=proto_key.thresholdKey.threshold) assert loaded.threshold == 2 assert len(loaded.keys) == 3 diff --git a/tests/unit/key_test.py b/tests/unit/key_test.py index 6e35b1bf6..f71276a90 100644 --- a/tests/unit/key_test.py +++ b/tests/unit/key_test.py @@ -1,14 +1,17 @@ +from __future__ import annotations + import pytest +from hiero_sdk_python.contract.contract_id import ContractId from hiero_sdk_python.contract.delegate_contract_id import DelegateContractId +from hiero_sdk_python.crypto.evm_address import EvmAddress from hiero_sdk_python.crypto.key import Key +from hiero_sdk_python.crypto.key_list import KeyList from hiero_sdk_python.crypto.private_key import PrivateKey from hiero_sdk_python.crypto.public_key import PublicKey -from hiero_sdk_python.crypto.key_list import KeyList -from hiero_sdk_python.crypto.evm_address import EvmAddress -from hiero_sdk_python.contract.contract_id import ContractId from hiero_sdk_python.hapi.services import basic_types_pb2 + pytestmark = pytest.mark.unit diff --git a/tests/unit/key_utils_test.py b/tests/unit/key_utils_test.py index a44d1bbb8..45a272495 100644 --- a/tests/unit/key_utils_test.py +++ b/tests/unit/key_utils_test.py @@ -1,12 +1,14 @@ """Tests for the key_utils module.""" +from __future__ import annotations + import pytest from hiero_sdk_python.crypto.private_key import PrivateKey -from hiero_sdk_python.crypto.public_key import PublicKey from hiero_sdk_python.hapi.services import basic_types_pb2 from hiero_sdk_python.utils.key_utils import Key, key_to_proto + pytestmark = pytest.mark.unit diff --git a/tests/unit/keys_private_test.py b/tests/unit/keys_private_test.py index 37de97668..8039d5565 100644 --- a/tests/unit/keys_private_test.py +++ b/tests/unit/keys_private_test.py @@ -1,13 +1,16 @@ -import pytest -import warnings +from __future__ import annotations + import re +import warnings +import pytest from cryptography.exceptions import InvalidSignature -from cryptography.hazmat.primitives.asymmetric import ec, ed25519, rsa -from cryptography.hazmat.primitives import serialization +from cryptography.hazmat.primitives.asymmetric import ec, ed25519 + from hiero_sdk_python.crypto.key import Key -from hiero_sdk_python.crypto.public_key import PublicKey from hiero_sdk_python.crypto.private_key import PrivateKey +from hiero_sdk_python.crypto.public_key import PublicKey + pytestmark = pytest.mark.unit @@ -141,10 +144,7 @@ def test_from_string_der_ed25519(): This example DER was built using a known Ed25519 seed (all '01'). """ - der_hex = ( - "302e020100300506032b657004220420" - "0101010101010101010101010101010101010101010101010101010101010101" - ) + der_hex = "302e020100300506032b6570042204200101010101010101010101010101010101010101010101010101010101010101" priv = PrivateKey.from_string_der(der_hex) assert priv.is_ed25519() @@ -321,7 +321,7 @@ def test_from_bytes_ambiguity_prefers_ecdsa_when_ed25519_fails(monkeypatch): ecdsa_scalar_one = (1).to_bytes(32, "big") # 2) Force the Ed25519 loader to always return None - monkeypatch.setattr(PrivateKey, "_try_load_ed25519", staticmethod(lambda b: None)) + monkeypatch.setattr(PrivateKey, "_try_load_ed25519", staticmethod(lambda _b: None)) # 3) Now from_bytes should skip Ed25519 and succeed with ECDSA with warnings.catch_warnings(record=True) as w: @@ -361,10 +361,7 @@ def test_from_bytes_warning_message(): def test_raw_roundtrip_idempotent(key_type): priv1 = PrivateKey.generate(key_type) raw = priv1.to_bytes_raw() - if key_type == "ed25519": - priv2 = PrivateKey.from_bytes_ed25519(raw) - else: - priv2 = PrivateKey.from_bytes_ecdsa(raw) + priv2 = PrivateKey.from_bytes_ed25519(raw) if key_type == "ed25519" else PrivateKey.from_bytes_ecdsa(raw) assert priv2.to_bytes_raw() == raw @@ -488,7 +485,7 @@ def test_equality_ed25519_different_keys(): def test_equality_ecdsa_same_key(): """Two PrivateKey objects wrapping the same ECDSA key must be equal.""" - pr_key = ec.generate_private_key(ec.SECP256K1()) + pr_key = ec.generate_private_key(ec.SECP256K1()) k1 = PrivateKey(pr_key) k2 = PrivateKey(pr_key) @@ -508,6 +505,7 @@ def test_equality_ecdsa_different_keys(): assert k1 != k2 assert hash(k1) != hash(k2) + def test_equality_algorithm_mismatch(): """Private keys of different algorithms must never be equal.""" ed_pr = ed25519.Ed25519PrivateKey.generate() @@ -521,8 +519,7 @@ def test_equality_algorithm_mismatch(): @pytest.mark.parametrize( - "other", - [None, 1, 1.0, "key", object(), PublicKey(ed25519.Ed25519PrivateKey.generate().public_key())] + "other", [None, 1, 1.0, "key", object(), PublicKey(ed25519.Ed25519PrivateKey.generate().public_key())] ) def test_equality_with_non_privatekey_returns_false(other): """Equality comparison with a non-PrivateKey type should return False.""" @@ -554,6 +551,7 @@ def test_to_proto_key_ecd25519(): assert proto_key.ed25519 is not None assert proto_key.ed25519 == pr_key.public_key().to_bytes_raw() + def test_to_proto_key_ecdsa(): """Test to_proto_key properly convert ecdsa to Key protobuf.""" pr_key = PrivateKey.generate_ecdsa() @@ -563,16 +561,14 @@ def test_to_proto_key_ecdsa(): assert proto_key.ECDSA_secp256k1 is not None assert proto_key.ECDSA_secp256k1 == pr_key.public_key().to_bytes_raw() -@pytest.mark.parametrize( - "key", - [PrivateKey.generate_ed25519(), PrivateKey.generate_ecdsa()] -) + +@pytest.mark.parametrize("key", [PrivateKey.generate_ed25519(), PrivateKey.generate_ecdsa()]) def test_protobuf_roundtrip(key): """Test protobuf roundtrip properly convert to Key protobuf and vice versa.""" proto = key.to_proto_key() - + pub_key = key.public_key() - loaded = Key.from_proto_key(proto) # Returns public key + loaded = Key.from_proto_key(proto) # Returns public key assert isinstance(loaded, PublicKey) assert loaded.is_ed25519() == pub_key.is_ed25519() diff --git a/tests/unit/keys_public_test.py b/tests/unit/keys_public_test.py index bbf0f2049..4dfd8a9ce 100644 --- a/tests/unit/keys_public_test.py +++ b/tests/unit/keys_public_test.py @@ -1,16 +1,22 @@ +from __future__ import annotations + import pytest -import warnings -from cryptography.hazmat.primitives.asymmetric import ec, ed25519 -from cryptography.hazmat.primitives import serialization, hashes -from cryptography.hazmat.primitives.asymmetric import utils as asym_utils from cryptography.exceptions import InvalidSignature +from cryptography.hazmat.primitives import hashes, serialization +from cryptography.hazmat.primitives.asymmetric import ( + ec, + ed25519, + utils as asym_utils, +) + from hiero_sdk_python.crypto.evm_address import EvmAddress from hiero_sdk_python.crypto.key import Key from hiero_sdk_python.crypto.private_key import PrivateKey -from hiero_sdk_python.hapi.services import basic_types_pb2 from hiero_sdk_python.crypto.public_key import PublicKey +from hiero_sdk_python.hapi.services import basic_types_pb2 from hiero_sdk_python.utils.crypto_utils import keccak256 + pytestmark = pytest.mark.unit @@ -318,9 +324,8 @@ def test_from_bytes_invalid(): data = b"\x00" # Always warns about Ed25519 ambiguity, then fails in DER loader - with pytest.warns(UserWarning): - with pytest.raises(ValueError, match="Failed to load public key"): - PublicKey.from_bytes(data) + with pytest.warns(UserWarning), pytest.raises(ValueError, match="Failed to load public key"): + PublicKey.from_bytes(data) # ------------------------------------------------------------------------------ @@ -333,14 +338,10 @@ def test_from_string_ed25519(ed25519_keypair): and that round-trip hex output matches the input. """ _, pub = ed25519_keypair - raw = pub.public_bytes( - encoding=serialization.Encoding.Raw, format=serialization.PublicFormat.Raw - ) + raw = pub.public_bytes(encoding=serialization.Encoding.Raw, format=serialization.PublicFormat.Raw) hex_str = raw.hex() - with pytest.warns( - UserWarning, match="cannot distinguish Ed25519 private seeds from public keys" - ): + with pytest.warns(UserWarning, match="cannot distinguish Ed25519 private seeds from public keys"): pubk = PublicKey.from_string_ed25519(hex_str) assert pubk.is_ed25519() assert pubk.to_string_ed25519() == hex_str @@ -441,9 +442,7 @@ def test_from_string_catch_all_ed25519(ed25519_keypair): Ensures warning and correct detection. """ _, pub = ed25519_keypair - raw = pub.public_bytes( - encoding=serialization.Encoding.Raw, format=serialization.PublicFormat.Raw - ) + raw = pub.public_bytes(encoding=serialization.Encoding.Raw, format=serialization.PublicFormat.Raw) hex_str = raw.hex() with pytest.warns(UserWarning, match="from_string.*cannot distinguish"): @@ -701,10 +700,7 @@ def test_equality_algorithm_mismatch(ed25519_keypair, ecdsa_keypair): assert hash(ed_key) != hash(ec_key) -@pytest.mark.parametrize( - "other", - [None, 1, 1.0, "key", object(), PrivateKey(ed25519.Ed25519PrivateKey.generate())] -) +@pytest.mark.parametrize("other", [None, 1, 1.0, "key", object(), PrivateKey(ed25519.Ed25519PrivateKey.generate())]) def test_equality_with_non_publickey_returns_false(ed25519_keypair, other): """Equality comparison with a non-PublicKey type should return False.""" _, pub = ed25519_keypair @@ -712,6 +708,7 @@ def test_equality_with_non_publickey_returns_false(ed25519_keypair, other): assert (key == other) is False + def test_to_proto_key_ecd25519(): """Test to_proto_key properly convert ed25519 to Key protobuf.""" pub_key = PrivateKey.generate_ed25519().public_key() @@ -721,6 +718,7 @@ def test_to_proto_key_ecd25519(): assert proto_key.ed25519 is not None assert proto_key.ed25519 == pub_key.to_bytes_raw() + def test_to_proto_key_ecdsa(): """Test to_proto_key properly convert ecdsa to Key protobuf.""" pub_key = PrivateKey.generate_ecdsa().public_key() @@ -730,10 +728,8 @@ def test_to_proto_key_ecdsa(): assert proto_key.ECDSA_secp256k1 is not None assert proto_key.ECDSA_secp256k1 == pub_key.to_bytes_raw() -@pytest.mark.parametrize( - "key", - [PrivateKey.generate_ed25519(), PrivateKey.generate_ecdsa()] -) + +@pytest.mark.parametrize("key", [PrivateKey.generate_ed25519(), PrivateKey.generate_ecdsa()]) def test_protobuf_roundtrip(key): """Test protobuf roundtrip properly convert to Key protobuf and vice versa.""" pub_key = key.public_key() @@ -745,4 +741,3 @@ def test_protobuf_roundtrip(key): assert loaded.is_ed25519() == pub_key.is_ed25519() assert loaded.is_ecdsa() == pub_key.is_ecdsa() assert loaded.to_bytes_raw() == pub_key.to_bytes_raw() - diff --git a/tests/unit/logger_test.py b/tests/unit/logger_test.py index 247686e7f..565590699 100644 --- a/tests/unit/logger_test.py +++ b/tests/unit/logger_test.py @@ -1,7 +1,12 @@ +from __future__ import annotations + import os + import pytest -from src.hiero_sdk_python.logger.logger import Logger + from src.hiero_sdk_python.logger.log_level import LogLevel +from src.hiero_sdk_python.logger.logger import Logger + pytestmark = pytest.mark.unit diff --git a/tests/unit/managed_node_address_test.py b/tests/unit/managed_node_address_test.py index 90b62eb6c..89560c6dd 100644 --- a/tests/unit/managed_node_address_test.py +++ b/tests/unit/managed_node_address_test.py @@ -1,20 +1,27 @@ +from __future__ import annotations + import pytest + from src.hiero_sdk_python.managed_node_address import _ManagedNodeAddress + pytestmark = pytest.mark.unit + def test_init(): """Test initialization of _ManagedNodeAddress.""" address = _ManagedNodeAddress(address="127.0.0.1", port=50211) assert address._address == "127.0.0.1" assert address._port == 50211 + def test_from_string_valid(): """Test creating _ManagedNodeAddress from a valid string.""" address = _ManagedNodeAddress._from_string("127.0.0.1:50211") assert address._address == "127.0.0.1" assert address._port == 50211 + def test_from_string_ip_address(): """Test creating _ManagedNodeAddress from an IP address string.""" address = _ManagedNodeAddress._from_string("35.237.200.180:50211") @@ -22,6 +29,7 @@ def test_from_string_ip_address(): assert address._port == 50211 assert str(address) == "35.237.200.180:50211" + def test_from_string_url_address(): """Test creating _ManagedNodeAddress from a URL string.""" address = _ManagedNodeAddress._from_string("0.testnet.hedera.com:50211") @@ -29,6 +37,7 @@ def test_from_string_url_address(): assert address._port == 50211 assert str(address) == "0.testnet.hedera.com:50211" + def test_from_string_mirror_node_address(): """Test creating _ManagedNodeAddress from a mirror node address string.""" mirror_address = _ManagedNodeAddress._from_string("hcs.mainnet.mirrornode.hedera.com:50211") @@ -36,98 +45,111 @@ def test_from_string_mirror_node_address(): assert mirror_address._port == 50211 assert str(mirror_address) == "hcs.mainnet.mirrornode.hedera.com:50211" + def test_from_string_invalid_format(): """Test creating _ManagedNodeAddress from an invalid string format.""" with pytest.raises(ValueError): _ManagedNodeAddress._from_string("invalid_format") + def test_from_string_invalid_string_with_spaces(): """Test creating _ManagedNodeAddress from an invalid string with spaces.""" with pytest.raises(ValueError): _ManagedNodeAddress._from_string("this is a random string with spaces:443") + def test_from_string_invalid_port(): """Test creating _ManagedNodeAddress with invalid port.""" with pytest.raises(ValueError): _ManagedNodeAddress._from_string("127.0.0.1:invalid") + def test_from_string_invalid_url_port(): """Test creating _ManagedNodeAddress with invalid URL port.""" with pytest.raises(ValueError): _ManagedNodeAddress._from_string("hcs.mainnet.mirrornode.hedera.com:notarealport") + def test_is_transport_security(): """Test _is_transport_security method.""" secure_address = _ManagedNodeAddress(address="127.0.0.1", port=50212) insecure_address = _ManagedNodeAddress(address="127.0.0.1", port=50211) - + assert secure_address._is_transport_security() is True assert insecure_address._is_transport_security() is False + def test_string_representation(): """Test string representation.""" address = _ManagedNodeAddress(address="127.0.0.1", port=50211) assert str(address) == "127.0.0.1:50211" - + # Test with None address empty_address = _ManagedNodeAddress() assert str(empty_address) == "" + def test_to_secure_node_port(): """Test converting node address from plaintext to TLS port.""" insecure = _ManagedNodeAddress(address="127.0.0.1", port=50211) secure = insecure._to_secure() - + assert secure._port == 50212 assert secure._address == "127.0.0.1" assert secure._is_transport_security() is True + def test_to_secure_already_secure(): """Test converting already secure address (should be idempotent).""" secure = _ManagedNodeAddress(address="127.0.0.1", port=50212) result = secure._to_secure() - + assert result._port == 50212 assert result._is_transport_security() is True # Should return same instance or equivalent assert str(result) == str(secure) + def test_to_secure_custom_port(): """Test converting address with custom port (should remain unchanged).""" custom = _ManagedNodeAddress(address="127.0.0.1", port=9999) result = custom._to_secure() - + assert result._port == 9999 # Custom port unchanged assert result._address == "127.0.0.1" + def test_to_insecure_node_port(): """Test converting node address from TLS to plaintext port.""" secure = _ManagedNodeAddress(address="127.0.0.1", port=50212) insecure = secure._to_insecure() - + assert insecure._port == 50211 assert insecure._address == "127.0.0.1" assert insecure._is_transport_security() is False + def test_to_insecure_already_insecure(): """Test converting already insecure address (should be idempotent).""" insecure = _ManagedNodeAddress(address="127.0.0.1", port=50211) result = insecure._to_insecure() - + assert result._port == 50211 assert result._is_transport_security() is False assert str(result) == str(insecure) + def test_to_insecure_custom_port(): """Test converting address with custom port (should remain unchanged).""" custom = _ManagedNodeAddress(address="127.0.0.1", port=9999) result = custom._to_insecure() - + assert result._port == 9999 # Custom port unchanged assert result._address == "127.0.0.1" + def test_get_host_and_port(): """Test getting host and port components.""" address = _ManagedNodeAddress(address="example.com", port=50211) assert address._get_host() == "example.com" - assert address._get_port() == 50211 \ No newline at end of file + assert address._get_port() == 50211 diff --git a/tests/unit/mock_server.py b/tests/unit/mock_server.py index 71c8df412..3ee264b4f 100644 --- a/tests/unit/mock_server.py +++ b/tests/unit/mock_server.py @@ -1,20 +1,23 @@ -import grpc +from __future__ import annotations + import threading from concurrent import futures from contextlib import contextmanager -from hiero_sdk_python.client.network import Network -from hiero_sdk_python.client.client import Client + +import grpc + from hiero_sdk_python.account.account_id import AccountId +from hiero_sdk_python.client.client import Client +from hiero_sdk_python.client.network import Network, _Node from hiero_sdk_python.crypto.private_key import PrivateKey -from hiero_sdk_python.client.network import _Node from hiero_sdk_python.hapi.services import ( - crypto_service_pb2_grpc, - token_service_pb2_grpc, consensus_service_pb2_grpc, - schedule_service_pb2_grpc, - network_service_pb2_grpc, + crypto_service_pb2_grpc, file_service_pb2_grpc, + network_service_pb2_grpc, + schedule_service_pb2_grpc, smart_contract_service_pb2_grpc, + token_service_pb2_grpc, util_service_pb2_grpc, ) from hiero_sdk_python.logger.log_level import LogLevel @@ -33,8 +36,8 @@ def __init__(self, responses): self.responses = responses self._lock = threading.Lock() self.server = grpc.server(futures.ThreadPoolExecutor(max_workers=10)) - - self.port = self.server.add_insecure_port('[::]:0') + + self.port = self.server.add_insecure_port("[::]:0") self.address = f"localhost:{self.port}" self._register_services() @@ -95,7 +98,7 @@ def _create_mock_servicer(self, servicer_class): A mock servicer object """ responses = self.responses - lock = self._lock; + lock = self._lock class MockServicer(servicer_class): def __getattribute__(self, name): @@ -103,7 +106,7 @@ def __getattribute__(self, name): if name in ("_next_response", "__class__"): return super().__getattribute__(name) - def method_wrapper(request, context): + def method_wrapper(_, context): with lock: if not responses: return None diff --git a/tests/unit/network_test.py b/tests/unit/network_test.py index 6d7de27e7..9eea1a702 100644 --- a/tests/unit/network_test.py +++ b/tests/unit/network_test.py @@ -1,12 +1,16 @@ +from __future__ import annotations + import time -import pytest from unittest.mock import Mock, patch +import pytest + from hiero_sdk_python.account.account_id import AccountId from hiero_sdk_python.address_book.node_address import NodeAddress from hiero_sdk_python.client.network import Network from hiero_sdk_python.node import _Node + pytestmark = pytest.mark.unit @@ -19,7 +23,7 @@ def mock_network_nodes(monkeypatch): _Node(AccountId(0, 0, 5), "127.0.0.1:50212", NodeAddress()), ] - def fake_fetch_nodes(self): + def fake_fetch_nodes(_self): return fake_nodes monkeypatch.setattr(Network, "_fetch_nodes_from_mirror_node", fake_fetch_nodes) @@ -340,9 +344,7 @@ def test_get_node_by_account_id(): network._healthy_nodes = [node] - with patch( - "hiero_sdk_python.client.network.Network._readmit_nodes" - ) as mock_readmit: + with patch("hiero_sdk_python.client.network.Network._readmit_nodes") as mock_readmit: result = network._get_node(AccountId(0, 0, 3)) assert mock_readmit.call_count == 1 diff --git a/tests/unit/network_tls_test.py b/tests/unit/network_tls_test.py index 455c9d7ab..677f7160d 100644 --- a/tests/unit/network_tls_test.py +++ b/tests/unit/network_tls_test.py @@ -1,11 +1,15 @@ """Unit tests for TLS configuration in Network and Client.""" +from __future__ import annotations + import pytest + +from src.hiero_sdk_python.account.account_id import AccountId from src.hiero_sdk_python.client.client import Client from src.hiero_sdk_python.client.network import Network -from src.hiero_sdk_python.account.account_id import AccountId from src.hiero_sdk_python.node import _Node + pytestmark = pytest.mark.unit @@ -13,24 +17,19 @@ def test_network_tls_enabled_by_default_for_hosted_networks(): """Test that TLS is enabled by default for hosted networks.""" for network_name in ("mainnet", "testnet", "previewnet"): network = Network(network_name) - assert ( - network.is_transport_security() is True - ), f"TLS should be enabled for {network_name}" + assert network.is_transport_security() is True, f"TLS should be enabled for {network_name}" def test_network_tls_disabled_by_default_for_local_networks(): """Test that TLS is disabled by default for local networks.""" for network_name in ("solo", "localhost", "local"): network = Network(network_name) - assert ( - network.is_transport_security() is False - ), f"TLS should be disabled for {network_name}" + assert network.is_transport_security() is False, f"TLS should be disabled for {network_name}" def test_network_tls_disabled_by_default_for_custom_networks(): """Test that TLS is disabled by default for custom networks.""" # Provide nodes for custom network since it has no defaults - from src.hiero_sdk_python.node import _Node nodes = [_Node(AccountId(0, 0, 3), "127.0.0.1:50211", None)] network = Network("custom-network", nodes=nodes) @@ -41,9 +40,7 @@ def test_network_verification_enabled_by_default(): """Test that certificate verification is enabled by default for all networks.""" for network_name in ("mainnet", "testnet", "previewnet", "solo", "localhost"): network = Network(network_name) - assert ( - network.is_verify_certificates() is True - ), f"Verification should be enabled for {network_name}" + assert network.is_verify_certificates() is True, f"Verification should be enabled for {network_name}" def test_network_set_transport_security_enable(): @@ -191,12 +188,9 @@ def test_network_get_mirror_rest_url_localhost(): def test_network_get_mirror_rest_url_custom_port(): """Test REST URL generation with custom port for network without MIRROR_NODE_URLS entry.""" # Use a custom network that doesn't have MIRROR_NODE_URLS entry - from src.hiero_sdk_python.node import _Node nodes = [_Node(AccountId(0, 0, 3), "127.0.0.1:50211", None)] - network = Network( - "custom-network", nodes=nodes, mirror_address="custom.mirror.com:8443" - ) + network = Network("custom-network", nodes=nodes, mirror_address="custom.mirror.com:8443") url = network.get_mirror_rest_url() # Should use custom mirror_address and include port assert url.startswith("https://custom.mirror.com:8443/api/v1") diff --git a/tests/unit/nft_id_test.py b/tests/unit/nft_id_test.py index 9d8efd742..79099c514 100644 --- a/tests/unit/nft_id_test.py +++ b/tests/unit/nft_id_test.py @@ -1,8 +1,11 @@ +from __future__ import annotations + import pytest +from hiero_sdk_python.hapi.services import basic_types_pb2 from hiero_sdk_python.tokens.nft_id import NftId from hiero_sdk_python.tokens.token_id import TokenId -from hiero_sdk_python.hapi.services import basic_types_pb2 + pytestmark = pytest.mark.unit @@ -10,9 +13,7 @@ def test_nft_id(): # return true nftid_constructor_tokenid = TokenId(shard=0, realm=1, num=2) - nftid_constructor_test = NftId( - token_id=nftid_constructor_tokenid, serial_number=1234 - ) + nftid_constructor_test = NftId(token_id=nftid_constructor_tokenid, serial_number=1234) assert str(nftid_constructor_test) == "0.1.2/1234" @@ -33,23 +34,19 @@ def test_nft_id(): # return false with pytest.raises(TypeError): - nftid_failed_constructor_tokenid1 = TokenId(shard=0, realm=1, num="A") + TokenId(shard=0, realm=1, num="A") with pytest.raises(TypeError): - nftid_failed_constructor_tokenid = TokenId(shard=0, realm="b", num=1) + TokenId(shard=0, realm="b", num=1) with pytest.raises(TypeError): - nftid_failed_constructor_tokenid = TokenId(shard="c", realm=1, num=1) + TokenId(shard="c", realm=1, num=1) with pytest.raises(TypeError): - nftid_failed_constructor = NftId(token_id=None, serial_number=1234) + NftId(token_id=None, serial_number=1234) with pytest.raises(TypeError): - nftid_failed_constructor = NftId(token_id=1234, serial_number=1234) + NftId(token_id=1234, serial_number=1234) with pytest.raises(TypeError): - nftid_failed_constructor = NftId( - token_id=TokenId(shard=0, realm=1, num=0), serial_number="asdfasdfasdf" - ) + NftId(token_id=TokenId(shard=0, realm=1, num=0), serial_number="asdfasdfasdf") with pytest.raises(ValueError): - nftid_failed_constructor = NftId( - token_id=TokenId(shard=0, realm=1, num=0), serial_number=-1234 - ) + NftId(token_id=TokenId(shard=0, realm=1, num=0), serial_number=-1234) # don't need to test protobuf cause its final and type checked with pytest.raises(ValueError): @@ -94,22 +91,16 @@ def test_nft_id_repr(): nft_id = NftId(token_id, 5) result = repr(nft_id) assert isinstance(result, str), "repr should return a string" - assert ( - result == "NftId(token_id=0.0.123, serial_number=5)" - ), f"Unexpected repr: {result}" + assert result == "NftId(token_id=0.0.123, serial_number=5)", f"Unexpected repr: {result}" def test_nft_id_repr_zero_serial(): token_id = TokenId(0, 0, 1) nft_id = NftId(token_id, 0) - assert ( - repr(nft_id) == "NftId(token_id=0.0.1, serial_number=0)" - ), "repr should handle serial_number=0" + assert repr(nft_id) == "NftId(token_id=0.0.1, serial_number=0)", "repr should handle serial_number=0" def test_nft_id_repr_large_values(): token_id = TokenId(1, 2, 999999) nft_id = NftId(token_id, 2147483647) - assert ( - repr(nft_id) == "NftId(token_id=1.2.999999, serial_number=2147483647)" - ), "repr should handle large values" + assert repr(nft_id) == "NftId(token_id=1.2.999999, serial_number=2147483647)", "repr should handle large values" diff --git a/tests/unit/node_address_test.py b/tests/unit/node_address_test.py index db361893e..9504d7699 100644 --- a/tests/unit/node_address_test.py +++ b/tests/unit/node_address_test.py @@ -1,5 +1,9 @@ +from __future__ import annotations + import binascii + import pytest + from hiero_sdk_python.account.account_id import AccountId from hiero_sdk_python.address_book.endpoint import Endpoint from hiero_sdk_python.address_book.node_address import NodeAddress @@ -7,6 +11,7 @@ NodeAddress as NodeAddressProto, ) + pytestmark = pytest.mark.unit @@ -14,11 +19,7 @@ def test_init(): """Test initialization of _NodeAddress.""" # Create test data account_id = AccountId(0, 0, 123) - addresses = [ - Endpoint( - address=bytes("192.168.1.1", "utf-8"), port=8080, domain_name="example.com" - ) - ] + addresses = [Endpoint(address=bytes("192.168.1.1", "utf-8"), port=8080, domain_name="example.com")] cert_hash = b"sample-cert-hash" # Initialize _NodeAddress @@ -46,9 +47,7 @@ def test_string_representation(): account_id = AccountId(0, 0, 123) # Create - endpoint = Endpoint( - address=bytes("192.168.1.1", "utf-8"), port=8080, domain_name="example.com" - ) + endpoint = Endpoint(address=bytes("192.168.1.1", "utf-8"), port=8080, domain_name="example.com") # Create NodeAddress node_address = NodeAddress( @@ -65,9 +64,7 @@ def test_string_representation(): # Check if expected fields are in the result assert "NodeAccountId: 0.0.123" in result - assert ( - "CertHash: 73616d706c652d636572742d68617368" in result - ) # hex representation of sample-cert-hash + assert "CertHash: 73616d706c652d636572742d68617368" in result # hex representation of sample-cert-hash assert "NodeId: 1234" in result assert "PubKey: sample-public-key" in result @@ -76,15 +73,9 @@ def test_to_proto(): """Test conversion of NodeAddress to protobuf with endpoints.""" account_id = AccountId(0, 0, 123) endpoints = [ - Endpoint( - address=bytes("192.168.1.1", "utf-8"), port=8080, domain_name="example1.com" - ), - Endpoint( - address=bytes("192.168.1.2", "utf-8"), port=8081, domain_name="example2.com" - ), - Endpoint( - address=bytes("192.168.1.3", "utf-8"), port=8082, domain_name="example3.com" - ), + Endpoint(address=bytes("192.168.1.1", "utf-8"), port=8080, domain_name="example1.com"), + Endpoint(address=bytes("192.168.1.2", "utf-8"), port=8081, domain_name="example2.com"), + Endpoint(address=bytes("192.168.1.3", "utf-8"), port=8082, domain_name="example3.com"), ] node_address = NodeAddress( public_key="sample-public-key", @@ -126,9 +117,7 @@ def test_from_dict(): "node_id": 1234, "node_cert_hash": binascii.hexlify(b"sample-cert-hash").decode("utf-8"), "description": "Sample Node", - "service_endpoints": [ - {"ip_address_v4": "192.168.1.1", "port": 8080, "domain_name": "example.com"} - ], + "service_endpoints": [{"ip_address_v4": "192.168.1.1", "port": 8080, "domain_name": "example.com"}], } # Create NodeAddress from dict @@ -164,9 +153,7 @@ def test_from_dict_with_0x_prefix(): def test_from_proto(): """Test creation of NodeAddress from protobuf with endpoint.""" account_id_proto = AccountId(0, 0, 123)._to_proto() - endpoint_proto = Endpoint( - address=bytes("192.168.1.1", "utf-8"), port=8080, domain_name="example.com" - )._to_proto() + endpoint_proto = Endpoint(address=bytes("192.168.1.1", "utf-8"), port=8080, domain_name="example.com")._to_proto() # Create NodeAddressProto node_address_proto = NodeAddressProto( @@ -194,9 +181,7 @@ def test_from_proto(): def test_round_trip(): """Ensure NodeAddress → Proto → NodeAddress round trip works.""" account_id = AccountId(0, 0, 123) - endpoint = Endpoint( - address=bytes("192.168.1.1", "utf-8"), port=8080, domain_name="example.com" - ) + endpoint = Endpoint(address=bytes("192.168.1.1", "utf-8"), port=8080, domain_name="example.com") # Create NodeAddress node_address = NodeAddress( diff --git a/tests/unit/node_create_transaction_test.py b/tests/unit/node_create_transaction_test.py index 3068cb2e5..6c768d346 100644 --- a/tests/unit/node_create_transaction_test.py +++ b/tests/unit/node_create_transaction_test.py @@ -2,6 +2,8 @@ Test cases for the NodeCreateTransaction class. """ +from __future__ import annotations + from unittest.mock import MagicMock import pytest @@ -17,6 +19,7 @@ NodeCreateTransaction, ) + pytestmark = pytest.mark.unit @@ -99,22 +102,14 @@ def test_build_transaction_body_with_valid_parameters(mock_account_ids, node_par assert node_create.account_id == node_params["account_id"]._to_proto() assert node_create.description == node_params["description"] assert len(node_create.gossip_endpoint) == 1 - assert ( - node_create.gossip_endpoint[0] == node_params["gossip_endpoints"][0]._to_proto() - ) + assert node_create.gossip_endpoint[0] == node_params["gossip_endpoints"][0]._to_proto() assert len(node_create.service_endpoint) == 1 - assert ( - node_create.service_endpoint[0] - == node_params["service_endpoints"][0]._to_proto() - ) + assert node_create.service_endpoint[0] == node_params["service_endpoints"][0]._to_proto() assert node_create.gossip_ca_certificate == node_params["gossip_ca_certificate"] assert node_create.grpc_certificate_hash == node_params["grpc_certificate_hash"] assert node_create.admin_key == node_params["admin_key"]._to_proto() assert node_create.decline_reward == node_params["decline_reward"] - assert ( - node_create.grpc_proxy_endpoint - == node_params["grpc_web_proxy_endpoint"]._to_proto() - ) + assert node_create.grpc_proxy_endpoint == node_params["grpc_web_proxy_endpoint"]._to_proto() def test_build_scheduled_body(node_params): @@ -135,22 +130,14 @@ def test_build_scheduled_body(node_params): assert node_create.account_id == node_params["account_id"]._to_proto() assert node_create.description == node_params["description"] assert len(node_create.gossip_endpoint) == 1 - assert ( - node_create.gossip_endpoint[0] == node_params["gossip_endpoints"][0]._to_proto() - ) + assert node_create.gossip_endpoint[0] == node_params["gossip_endpoints"][0]._to_proto() assert len(node_create.service_endpoint) == 1 - assert ( - node_create.service_endpoint[0] - == node_params["service_endpoints"][0]._to_proto() - ) + assert node_create.service_endpoint[0] == node_params["service_endpoints"][0]._to_proto() assert node_create.gossip_ca_certificate == node_params["gossip_ca_certificate"] assert node_create.grpc_certificate_hash == node_params["grpc_certificate_hash"] assert node_create.admin_key == node_params["admin_key"]._to_proto() assert node_create.decline_reward == node_params["decline_reward"] - assert ( - node_create.grpc_proxy_endpoint - == node_params["grpc_web_proxy_endpoint"]._to_proto() - ) + assert node_create.grpc_proxy_endpoint == node_params["grpc_web_proxy_endpoint"]._to_proto() def test_set_account_id(node_params): @@ -289,9 +276,7 @@ def test_set_methods_require_not_frozen(mock_client, node_params): ] for method_name, value in test_cases: - with pytest.raises( - Exception, match="Transaction is immutable; it has been frozen" - ): + with pytest.raises(Exception, match="Transaction is immutable; it has been frozen"): getattr(node_tx, method_name)(value) diff --git a/tests/unit/node_delete_transaction_test.py b/tests/unit/node_delete_transaction_test.py index 68369cb4a..621075197 100644 --- a/tests/unit/node_delete_transaction_test.py +++ b/tests/unit/node_delete_transaction_test.py @@ -2,6 +2,8 @@ Test cases for the NodeDeleteTransaction class. """ +from __future__ import annotations + from unittest.mock import MagicMock import pytest @@ -11,6 +13,7 @@ ) from hiero_sdk_python.nodes.node_delete_transaction import NodeDeleteTransaction + pytestmark = pytest.mark.unit diff --git a/tests/unit/node_test.py b/tests/unit/node_test.py index f82335363..0e43c044b 100644 --- a/tests/unit/node_test.py +++ b/tests/unit/node_test.py @@ -1,4 +1,7 @@ +from __future__ import annotations + import time + import pytest from hiero_sdk_python.account.account_id import AccountId @@ -6,6 +9,7 @@ from hiero_sdk_python.address_book.node_address import NodeAddress from hiero_sdk_python.node import _Node + pytestmark = pytest.mark.unit @@ -13,20 +17,14 @@ def mock_address_book(): """Create a mock address book with certificate hash.""" cert_hash = b"test_cert_hash_12345" - endpoint = Endpoint( - address=b"node.example.com", port=50211, domain_name="node.example.com" - ) - address_book = NodeAddress( - account_id=AccountId(0, 0, 3), cert_hash=cert_hash, addresses=[endpoint] - ) - return address_book + endpoint = Endpoint(address=b"node.example.com", port=50211, domain_name="node.example.com") + return NodeAddress(account_id=AccountId(0, 0, 3), cert_hash=cert_hash, addresses=[endpoint]) @pytest.fixture def node(mock_address_book): """Create a node with deterministic value for unit tests.""" - node = _Node(AccountId(0, 0, 3), "127.0.0.1:50211", mock_address_book) - return node + return _Node(AccountId(0, 0, 3), "127.0.0.1:50211", mock_address_book) # Test is_healthy @@ -84,4 +82,3 @@ def test_decrease_backoff_floors_at_min(node): node._decrease_backoff() assert node._current_backoff == node._min_backoff - diff --git a/tests/unit/node_tls_test.py b/tests/unit/node_tls_test.py index 3fb979e5b..a1a267745 100644 --- a/tests/unit/node_tls_test.py +++ b/tests/unit/node_tls_test.py @@ -1,13 +1,18 @@ """Unit tests for TLS functionality in _Node.""" +from __future__ import annotations + import hashlib import ssl -from unittest.mock import Mock, patch, MagicMock +from unittest.mock import MagicMock, Mock, patch + import pytest -from src.hiero_sdk_python.node import _Node + from src.hiero_sdk_python.account.account_id import AccountId -from src.hiero_sdk_python.address_book.node_address import NodeAddress from src.hiero_sdk_python.address_book.endpoint import Endpoint +from src.hiero_sdk_python.address_book.node_address import NodeAddress +from src.hiero_sdk_python.node import _Node + pytestmark = pytest.mark.unit @@ -16,13 +21,8 @@ def mock_address_book(): """Create a mock address book with certificate hash.""" cert_hash = b"test_cert_hash_12345" - endpoint = Endpoint( - address=b"node.example.com", port=50212, domain_name="node.example.com" - ) - address_book = NodeAddress( - account_id=AccountId(0, 0, 3), cert_hash=cert_hash, addresses=[endpoint] - ) - return address_book + endpoint = Endpoint(address=b"node.example.com", port=50212, domain_name="node.example.com") + return NodeAddress(account_id=AccountId(0, 0, 3), cert_hash=cert_hash, addresses=[endpoint]) @pytest.fixture @@ -30,10 +30,7 @@ def mock_address_book_no_domain(): """Create a mock address book without domain name.""" cert_hash = b"test_cert_hash_12345" endpoint = Endpoint(address=b"127.0.0.1", port=50212, domain_name=None) - address_book = NodeAddress( - account_id=AccountId(0, 0, 3), cert_hash=cert_hash, addresses=[endpoint] - ) - return address_book + return NodeAddress(account_id=AccountId(0, 0, 3), cert_hash=cert_hash, addresses=[endpoint]) @pytest.fixture @@ -123,12 +120,8 @@ def test_node_set_verify_certificates_idempotent(mock_node_with_address_book): def test_node_build_channel_options_with_hostname_not_override(): """Test channel options include hostname override when domain differs from address.""" - endpoint = Endpoint( - address=b"127.0.0.1", port=50212, domain_name="node.example.com" - ) - address_book = NodeAddress( - account_id=AccountId(0, 0, 3), cert_hash=b"hash", addresses=[endpoint] - ) + endpoint = Endpoint(address=b"127.0.0.1", port=50212, domain_name="node.example.com") + address_book = NodeAddress(account_id=AccountId(0, 0, 3), cert_hash=b"hash", addresses=[endpoint]) node = _Node(AccountId(0, 0, 3), "127.0.0.1:50212", address_book) options = node._build_channel_options() @@ -138,12 +131,8 @@ def test_node_build_channel_options_with_hostname_not_override(): def test_node_build_channel_options_no_override_when_same(): """Test channel options don't include override when hostname matches address.""" - endpoint = Endpoint( - address=b"node.example.com", port=50212, domain_name="node.example.com" - ) - address_book = NodeAddress( - account_id=AccountId(0, 0, 3), cert_hash=b"hash", addresses=[endpoint] - ) + endpoint = Endpoint(address=b"node.example.com", port=50212, domain_name="node.example.com") + address_book = NodeAddress(account_id=AccountId(0, 0, 3), cert_hash=b"hash", addresses=[endpoint]) node = _Node(AccountId(0, 0, 3), "node.example.com:50212", address_book) options = node._build_channel_options() @@ -173,18 +162,14 @@ def test_node_build_channel_options_override_localhost_without_address_book( @patch("socket.create_connection") @patch("ssl.create_default_context") -def test_node_fetch_server_certificate_pem( - mock_ssl_context, mock_socket_conn, mock_node_with_address_book -): +def test_node_fetch_server_certificate_pem(mock_ssl_context, mock_socket_conn, mock_node_with_address_book): """Test fetching server certificate in PEM format.""" node = mock_node_with_address_book # Mock SSL context and socket mock_context = MagicMock() mock_ssl_context.return_value = mock_context - mock_context.wrap_socket.return_value.__enter__.return_value.getpeercert.return_value = ( - b"DER_CERT" - ) + mock_context.wrap_socket.return_value.__enter__.return_value.getpeercert.return_value = b"DER_CERT" mock_sock = MagicMock() mock_socket_conn.return_value.__enter__.return_value = mock_sock @@ -253,16 +238,12 @@ def test_node_validate_tls_certificate_no_address_book(): @patch("grpc.secure_channel") @patch("grpc.insecure_channel") -def test_node_get_channel_secure( - mock_insecure, mock_secure, mock_node_with_address_book -): +def test_node_get_channel_secure(mock_insecure, mock_secure, mock_node_with_address_book): """Test channel creation for secure connection.""" node = mock_node_with_address_book node._address = node._address._to_secure() # Ensure TLS is enabled - with patch.object( - node, "_fetch_server_certificate_pem", return_value=b"dummy-cert" - ): + with patch.object(node, "_fetch_server_certificate_pem", return_value=b"dummy-cert"): mock_channel = Mock() mock_secure.return_value = mock_channel @@ -278,9 +259,7 @@ def test_node_get_channel_secure( @patch("grpc.secure_channel") @patch("grpc.insecure_channel") -def test_node_get_channel_insecure( - mock_insecure, mock_secure, mock_node_without_address_book -): +def test_node_get_channel_insecure(mock_insecure, mock_secure, mock_node_without_address_book): """Test channel creation for insecure connection.""" node = mock_node_without_address_book @@ -296,16 +275,12 @@ def test_node_get_channel_insecure( @patch("grpc.secure_channel") @patch("grpc.insecure_channel") -def test_node_get_channel_reuses_existing( - mock_insecure, mock_secure, mock_node_with_address_book -): +def test_node_get_channel_reuses_existing(mock_insecure, mock_secure, mock_node_with_address_book): # noqa: ARG001 """Test that channel is reused when already created.""" node = mock_node_with_address_book node._verify_certificates = False - with patch.object( - node, "_fetch_server_certificate_pem", return_value=b"dummy-cert" - ): + with patch.object(node, "_fetch_server_certificate_pem", return_value=b"dummy-cert"): mock_channel = Mock() mock_secure.return_value = mock_channel @@ -335,7 +310,6 @@ def test_node_set_root_certificates_closes_channel(mock_node_with_address_book): patch("grpc.secure_channel") as mock_secure, patch.object(node, "_fetch_server_certificate_pem", return_value=b"dummy-cert"), ): - mock_channel = Mock() mock_secure.return_value = mock_channel node._get_channel() @@ -358,9 +332,7 @@ def test_secure_connect_raise_error_if_no_certificate_is_available( @patch("grpc.secure_channel") -def test_node_get_channel_with_root_certificates( - mock_secure, mock_node_with_address_book -): +def test_node_get_channel_with_root_certificates(mock_secure, mock_node_with_address_book): """Test secure channel uses provided root certificates.""" node = mock_node_with_address_book node._address = node._address._to_secure() diff --git a/tests/unit/node_update_transaction_test.py b/tests/unit/node_update_transaction_test.py index d6b092dd1..b5bc95a43 100644 --- a/tests/unit/node_update_transaction_test.py +++ b/tests/unit/node_update_transaction_test.py @@ -2,6 +2,8 @@ Test cases for the NodeUpdateTransaction class. """ +from __future__ import annotations + from unittest.mock import MagicMock import pytest @@ -17,6 +19,7 @@ NodeUpdateTransaction, ) + pytestmark = pytest.mark.unit @@ -104,26 +107,14 @@ def test_build_transaction_body(mock_account_ids, node_params): assert node_update.account_id == node_params["account_id"]._to_proto() assert node_update.description.value == node_params["description"] assert len(node_update.gossip_endpoint) == 1 - assert ( - node_update.gossip_endpoint[0] == node_params["gossip_endpoints"][0]._to_proto() - ) + assert node_update.gossip_endpoint[0] == node_params["gossip_endpoints"][0]._to_proto() assert len(node_update.service_endpoint) == 1 - assert ( - node_update.service_endpoint[0] - == node_params["service_endpoints"][0]._to_proto() - ) - assert ( - node_update.gossip_ca_certificate.value == node_params["gossip_ca_certificate"] - ) - assert ( - node_update.grpc_certificate_hash.value == node_params["grpc_certificate_hash"] - ) + assert node_update.service_endpoint[0] == node_params["service_endpoints"][0]._to_proto() + assert node_update.gossip_ca_certificate.value == node_params["gossip_ca_certificate"] + assert node_update.grpc_certificate_hash.value == node_params["grpc_certificate_hash"] assert node_update.admin_key == node_params["admin_key"]._to_proto() assert node_update.decline_reward.value == node_params["decline_reward"] - assert ( - node_update.grpc_proxy_endpoint - == node_params["grpc_web_proxy_endpoint"]._to_proto() - ) + assert node_update.grpc_proxy_endpoint == node_params["grpc_web_proxy_endpoint"]._to_proto() def test_build_scheduled_body(node_params): @@ -145,26 +136,14 @@ def test_build_scheduled_body(node_params): assert node_update.account_id == node_params["account_id"]._to_proto() assert node_update.description.value == node_params["description"] assert len(node_update.gossip_endpoint) == 1 - assert ( - node_update.gossip_endpoint[0] == node_params["gossip_endpoints"][0]._to_proto() - ) + assert node_update.gossip_endpoint[0] == node_params["gossip_endpoints"][0]._to_proto() assert len(node_update.service_endpoint) == 1 - assert ( - node_update.service_endpoint[0] - == node_params["service_endpoints"][0]._to_proto() - ) - assert ( - node_update.gossip_ca_certificate.value == node_params["gossip_ca_certificate"] - ) - assert ( - node_update.grpc_certificate_hash.value == node_params["grpc_certificate_hash"] - ) + assert node_update.service_endpoint[0] == node_params["service_endpoints"][0]._to_proto() + assert node_update.gossip_ca_certificate.value == node_params["gossip_ca_certificate"] + assert node_update.grpc_certificate_hash.value == node_params["grpc_certificate_hash"] assert node_update.admin_key == node_params["admin_key"]._to_proto() assert node_update.decline_reward.value == node_params["decline_reward"] - assert ( - node_update.grpc_proxy_endpoint - == node_params["grpc_web_proxy_endpoint"]._to_proto() - ) + assert node_update.grpc_proxy_endpoint == node_params["grpc_web_proxy_endpoint"]._to_proto() def test_set_node_id(node_params): @@ -316,9 +295,7 @@ def test_set_methods_require_not_frozen(mock_client, node_params): ] for method_name, value in test_cases: - with pytest.raises( - Exception, match="Transaction is immutable; it has been frozen" - ): + with pytest.raises(Exception, match="Transaction is immutable; it has been frozen"): getattr(node_tx, method_name)(value) diff --git a/tests/unit/prng_transaction_test.py b/tests/unit/prng_transaction_test.py index cd16f0691..ef9f2e45c 100644 --- a/tests/unit/prng_transaction_test.py +++ b/tests/unit/prng_transaction_test.py @@ -2,6 +2,8 @@ Test cases for the PrngTransaction class. """ +from __future__ import annotations + from unittest.mock import MagicMock import pytest @@ -17,6 +19,7 @@ from hiero_sdk_python.response_code import ResponseCode from tests.unit.mock_server import mock_hedera_servers + pytestmark = pytest.mark.unit @@ -139,16 +142,12 @@ def test_prng_transaction_can_execute(): ok_response = transaction_response_pb2.TransactionResponse() ok_response.nodeTransactionPrecheckCode = ResponseCode.OK - mock_receipt_proto = transaction_receipt_pb2.TransactionReceipt( - status=ResponseCode.SUCCESS - ) + mock_receipt_proto = transaction_receipt_pb2.TransactionReceipt(status=ResponseCode.SUCCESS) # Create a response for the receipt query receipt_query_response = response_pb2.Response( transactionGetReceipt=transaction_get_receipt_pb2.TransactionGetReceiptResponse( - header=response_header_pb2.ResponseHeader( - nodeTransactionPrecheckCode=ResponseCode.OK - ), + header=response_header_pb2.ResponseHeader(nodeTransactionPrecheckCode=ResponseCode.OK), receipt=mock_receipt_proto, ) ) @@ -162,6 +161,4 @@ def test_prng_transaction_can_execute(): receipt = transaction.execute(client) - assert ( - receipt.status == ResponseCode.SUCCESS - ), "Transaction should have succeeded" + assert receipt.status == ResponseCode.SUCCESS, "Transaction should have succeeded" diff --git a/tests/unit/query_nodes_test.py b/tests/unit/query_nodes_test.py index 8c3465ab0..4a2c41555 100644 --- a/tests/unit/query_nodes_test.py +++ b/tests/unit/query_nodes_test.py @@ -1,6 +1,7 @@ -import pytest -from hiero_sdk_python.query.query import Query +from __future__ import annotations + from hiero_sdk_python.account.account_id import AccountId +from hiero_sdk_python.query.query import Query def test_set_single_node_account_id(): diff --git a/tests/unit/query_test.py b/tests/unit/query_test.py index 3f8ab911e..91cfe5f62 100644 --- a/tests/unit/query_test.py +++ b/tests/unit/query_test.py @@ -1,23 +1,27 @@ -from decimal import Decimal +from __future__ import annotations + import re -import pytest +from decimal import Decimal from unittest.mock import MagicMock -from hiero_sdk_python.query.query import Query -from hiero_sdk_python.query.account_balance_query import CryptoGetAccountBalanceQuery -from hiero_sdk_python.hbar import Hbar -from hiero_sdk_python.query.token_info_query import TokenInfoQuery -from hiero_sdk_python.response_code import ResponseCode +import pytest + from hiero_sdk_python.executable import _ExecutionState from hiero_sdk_python.hapi.services import ( + crypto_get_account_balance_pb2, query_header_pb2, - response_pb2, response_header_pb2, - crypto_get_account_balance_pb2, + response_pb2, token_get_info_pb2, ) +from hiero_sdk_python.hbar import Hbar +from hiero_sdk_python.query.account_balance_query import CryptoGetAccountBalanceQuery +from hiero_sdk_python.query.query import Query +from hiero_sdk_python.query.token_info_query import TokenInfoQuery +from hiero_sdk_python.response_code import ResponseCode from tests.unit.mock_server import mock_hedera_servers + pytestmark = pytest.mark.unit @@ -31,8 +35,7 @@ def query(): @pytest.fixture def query_requires_payment(): """Fixture for a query that requires payment""" - query = TokenInfoQuery() - return query + return TokenInfoQuery() def test_query_initialization(query): @@ -93,9 +96,7 @@ def test_before_execute_payment_required(query_requires_payment, mock_client): def test_request_header_no_fields_set(query): """Test combinations with no fields set""" header = query._make_request_header() - assert not header.HasField( - "payment" - ), "Payment field should not be present when no fields are set" + assert not header.HasField("payment"), "Payment field should not be present when no fields are set" def test_request_header_payment_set(query, mock_client): @@ -103,16 +104,12 @@ def test_request_header_payment_set(query, mock_client): # Test with only query payment set query.payment_amount = Hbar(1) header = query._make_request_header() - assert not header.HasField( - "payment" - ), "Payment field should not be present when only query payment is set" + assert not header.HasField("payment"), "Payment field should not be present when only query payment is set" # Test with query payment and operator set query.operator = mock_client.operator header = query._make_request_header() - assert not header.HasField( - "payment" - ), "Payment field should not be present when only operator and payment are set" + assert not header.HasField("payment"), "Payment field should not be present when only operator and payment are set" def test_request_header_node_account_set(query, mock_client): @@ -121,16 +118,14 @@ def test_request_header_node_account_set(query, mock_client): query.node_account_id = mock_client.network.current_node._account_id header = query._make_request_header() - assert not header.HasField( - "payment" - ), "Payment field should not be present when only node account is set" + assert not header.HasField("payment"), "Payment field should not be present when only node account is set" # Test with node account and query payment set query.payment_amount = Hbar(1) header = query._make_request_header() - assert not header.HasField( - "payment" - ), "Payment field should not be present when only node account and payment are set" + assert not header.HasField("payment"), ( + "Payment field should not be present when only node account and payment are set" + ) def test_request_header_operator_set(query, mock_client): @@ -139,17 +134,15 @@ def test_request_header_operator_set(query, mock_client): query.operator = mock_client.operator header = query._make_request_header() - assert not header.HasField( - "payment" - ), "Payment field should not be present when only operator is set" + assert not header.HasField("payment"), "Payment field should not be present when only operator is set" # Test with operator and node account set query.node_account_id = mock_client.network.current_node._account_id header = query._make_request_header() - assert not header.HasField( - "payment" - ), "Payment field should not be present when only operator and node account are set" + assert not header.HasField("payment"), ( + "Payment field should not be present when only operator and node account are set" + ) def test_request_header_payment_zero(query, mock_client): @@ -161,26 +154,22 @@ def test_request_header_payment_zero(query, mock_client): # Test with payment amount set to 0 Hbar query.payment_amount = Hbar(0) header = query._make_request_header() - assert not header.HasField( - "payment" - ), "Payment field should not be present when payment is set to 0" + assert not header.HasField("payment"), "Payment field should not be present when payment is set to 0" def test_make_request_header_with_payment(query_requires_payment, mock_client): """Test making request header with payment transaction for queries that require payment""" query_requires_payment.operator = mock_client.operator - query_requires_payment.node_account_id = ( - mock_client.network.current_node._account_id - ) + query_requires_payment.node_account_id = mock_client.network.current_node._account_id query_requires_payment.set_query_payment(Hbar(1)) header = query_requires_payment._make_request_header() assert isinstance(header, query_header_pb2.QueryHeader) assert header.responseType == query_header_pb2.ResponseType.ANSWER_ONLY - assert header.HasField( - "payment" - ), "Payment field should be present when payment is set for queries that require payment" + assert header.HasField("payment"), ( + "Payment field should be present when payment is set for queries that require payment" + ) def test_request_header_excludes_payment_for_free_query(query, mock_client): @@ -195,9 +184,7 @@ def test_request_header_excludes_payment_for_free_query(query, mock_client): assert isinstance(header, query_header_pb2.QueryHeader) assert header.responseType == query_header_pb2.ResponseType.ANSWER_ONLY - assert not header.HasField( - "payment" - ), "Payment field should not be present for queries that don't require payment" + assert not header.HasField("payment"), "Payment field should not be present for queries that don't require payment" def test_should_retry_retryable_statuses(query): @@ -212,9 +199,7 @@ def test_should_retry_retryable_statuses(query): for status in retryable_statuses: response = response_pb2.Response( cryptogetAccountBalance=crypto_get_account_balance_pb2.CryptoGetAccountBalanceResponse( - header=response_header_pb2.ResponseHeader( - nodeTransactionPrecheckCode=status - ) + header=response_header_pb2.ResponseHeader(nodeTransactionPrecheckCode=status) ) ) @@ -226,9 +211,7 @@ def test_should_retry_ok_status(query): """Test that OK status finishes execution""" response = response_pb2.Response( cryptogetAccountBalance=crypto_get_account_balance_pb2.CryptoGetAccountBalanceResponse( - header=response_header_pb2.ResponseHeader( - nodeTransactionPrecheckCode=ResponseCode.OK - ) + header=response_header_pb2.ResponseHeader(nodeTransactionPrecheckCode=ResponseCode.OK) ) ) @@ -240,9 +223,7 @@ def test_should_retry_error_status(query): """Test that non-retryable error status triggers error state""" response = response_pb2.Response( cryptogetAccountBalance=crypto_get_account_balance_pb2.CryptoGetAccountBalanceResponse( - header=response_header_pb2.ResponseHeader( - nodeTransactionPrecheckCode=ResponseCode.INVALID_TRANSACTION - ) + header=response_header_pb2.ResponseHeader(nodeTransactionPrecheckCode=ResponseCode.INVALID_TRANSACTION) ) ) @@ -271,7 +252,6 @@ def test_get_cost_when_payment_required_and_set(query_requires_payment, mock_cli def test_get_cost_when_payment_required_and_not_set(query_requires_payment, token_id): """Test get_cost when payment is required and not set""" - # Create mock response containing cost information (2 tinybars) for token info query response = response_pb2.Response( tokenGetInfo=token_get_info_pb2.TokenGetInfoResponse( @@ -319,9 +299,7 @@ def test_set_max_query_payment_valid_param(query, valid_amount, expected): assert query.max_query_payment == expected -@pytest.mark.parametrize( - "negative_amount", [-1, -0.1, Decimal("-0.1"), Decimal("-1"), Hbar(-1), Hbar(-0.2)] -) +@pytest.mark.parametrize("negative_amount", [-1, -0.1, Decimal("-0.1"), Decimal("-1"), Hbar(-1), Hbar(-0.2)]) def test_set_max_query_payment_negative_value(query, negative_amount): """Test set_max_query_payment for negative amount values.""" with pytest.raises(ValueError, match="max_query_payment must be non-negative"): @@ -333,10 +311,7 @@ def test_set_max_query_payment_invalid_param(query, invalid_amount): """Test that set_max_query_payment raise error for invalid param.""" with pytest.raises( TypeError, - match=( - "max_query_payment must be int, float, Decimal, or Hbar, " - f"got {type(invalid_amount).__name__}" - ), + match=(f"max_query_payment must be int, float, Decimal, or Hbar, got {type(invalid_amount).__name__}"), ): query.set_max_query_payment(invalid_amount) @@ -348,9 +323,7 @@ def test_set_max_query_payment_non_finite_value(query, invalid_amount): query.set_max_query_payment(invalid_amount) -def test_set_max_payment_override_client_max_payment( - query_requires_payment, mock_client -): +def test_set_max_payment_override_client_max_payment(query_requires_payment, mock_client): """ Test that a query can override the Client's default_max_query_payment """ @@ -372,9 +345,7 @@ def test_set_max_payment_override_client_max_payment( assert query_requires_payment.payment_amount == Hbar(2) -def test_set_max_payment_override_client_max_payment_and_error( - query_requires_payment, mock_client -): +def test_set_max_payment_override_client_max_payment_and_error(query_requires_payment, mock_client): """ Test that a query can override the Client's default_max_query_payment and that execution fails if the query cost exceeds the query-specific max. @@ -422,9 +393,7 @@ def test_payment_query_use_client_max_payment(query_requires_payment, mock_clien assert query_requires_payment.payment_amount == Hbar(2) -def test_payment_query_use_client_max_payment_and_error( - query_requires_payment, mock_client -): +def test_payment_query_use_client_max_payment_and_error(query_requires_payment, mock_client): """ Test that execution fails if cost > client default max when query doesn't override. """ diff --git a/tests/unit/schedule_create_transaction_test.py b/tests/unit/schedule_create_transaction_test.py index 8ddd9298e..2354acd49 100644 --- a/tests/unit/schedule_create_transaction_test.py +++ b/tests/unit/schedule_create_transaction_test.py @@ -2,6 +2,8 @@ Test cases for the ScheduleCreateTransaction class. """ +from __future__ import annotations + from unittest.mock import MagicMock import pytest @@ -15,6 +17,7 @@ from hiero_sdk_python.timestamp import Timestamp from hiero_sdk_python.transaction.transfer_transaction import TransferTransaction + pytestmark = pytest.mark.unit @@ -83,16 +86,11 @@ def test_build_transaction_body_with_valid_parameters(mock_account_ids, schedule # Verify all fields are correctly set schedule_create = transaction_body.scheduleCreate - assert ( - schedule_create.payerAccountID == schedule_params["payer_account_id"]._to_proto() - ) + assert schedule_create.payerAccountID == schedule_params["payer_account_id"]._to_proto() assert schedule_create.adminKey == schedule_params["admin_key"]._to_proto() assert schedule_create.scheduledTransactionBody == schedule_params["schedulable_body"] assert schedule_create.memo == schedule_params["schedule_memo"] - assert ( - schedule_create.expiration_time - == schedule_params["expiration_time"]._to_protobuf() - ) + assert schedule_create.expiration_time == schedule_params["expiration_time"]._to_protobuf() assert schedule_create.wait_for_expiry == schedule_params["wait_for_expiry"] @@ -205,9 +203,7 @@ def test_set_methods_require_not_frozen(mock_client, schedule_params): ] for method_name, value in test_cases: - with pytest.raises( - Exception, match="Transaction is immutable; it has been frozen" - ): + with pytest.raises(Exception, match="Transaction is immutable; it has been frozen"): getattr(schedule_tx, method_name)(value) diff --git a/tests/unit/schedule_delete_transaction_test.py b/tests/unit/schedule_delete_transaction_test.py index 19648b96b..65c4e9383 100644 --- a/tests/unit/schedule_delete_transaction_test.py +++ b/tests/unit/schedule_delete_transaction_test.py @@ -2,6 +2,8 @@ Test cases for the ScheduleDeleteTransaction class. """ +from __future__ import annotations + from unittest.mock import MagicMock import pytest @@ -24,6 +26,7 @@ from hiero_sdk_python.schedule.schedule_id import ScheduleId from tests.unit.mock_server import mock_hedera_servers + pytestmark = pytest.mark.unit @@ -62,10 +65,7 @@ def test_build_transaction_body_with_valid_parameters(mock_account_ids, delete_p transaction_body = delete_tx.build_transaction_body() - assert ( - transaction_body.scheduleDelete.scheduleID - == delete_params["schedule_id"]._to_proto() - ) + assert transaction_body.scheduleDelete.scheduleID == delete_params["schedule_id"]._to_proto() def test_build_transaction_body_missing_schedule_id(): @@ -232,9 +232,7 @@ def test_schedule_delete_transaction_can_execute(): # Create a response for the receipt query receipt_query_response = response_pb2.Response( transactionGetReceipt=transaction_get_receipt_pb2.TransactionGetReceiptResponse( - header=response_header_pb2.ResponseHeader( - nodeTransactionPrecheckCode=ResponseCode.OK - ), + header=response_header_pb2.ResponseHeader(nodeTransactionPrecheckCode=ResponseCode.OK), receipt=mock_receipt_proto, ) ) @@ -250,6 +248,4 @@ def test_schedule_delete_transaction_can_execute(): receipt = transaction.execute(client) - assert ( - receipt.status == ResponseCode.SUCCESS - ), "Transaction should have succeeded" + assert receipt.status == ResponseCode.SUCCESS, "Transaction should have succeeded" diff --git a/tests/unit/schedule_id_test.py b/tests/unit/schedule_id_test.py index 7d025c5d9..684a6050b 100644 --- a/tests/unit/schedule_id_test.py +++ b/tests/unit/schedule_id_test.py @@ -1,14 +1,17 @@ +from __future__ import annotations + import pytest from hiero_sdk_python.hapi.services.basic_types_pb2 import ScheduleID from hiero_sdk_python.schedule.schedule_id import ScheduleId + pytestmark = pytest.mark.unit @pytest.fixture def client(mock_client): - mock_client.network.ledger_id = bytes.fromhex("00") # mainnet ledger id + mock_client.network.ledger_id = bytes.fromhex("00") # mainnet ledger id return mock_client @@ -65,29 +68,29 @@ def test_from_string_valid_with_checksum(): assert schedule_id.schedule == 3 assert schedule_id.checksum == "abcde" + @pytest.mark.parametrize( - 'invalid_id', + "invalid_id", [ - '1.2', # Too few parts - '1.2.3.4', # Too many parts - 'a.b.c', # Non-numeric parts - '', # Empty string - '1.a.3', # Partial numeric + "1.2", # Too few parts + "1.2.3.4", # Too many parts + "a.b.c", # Non-numeric parts + "", # Empty string + "1.a.3", # Partial numeric 123, None, - '0.0.-1', - 'abc.def.ghi', - '0.0.1-ad', - '0.0.1-addefgh', - '0.0.1 - abcde', - ' 0.0.100 ' - ] + "0.0.-1", + "abc.def.ghi", + "0.0.1-ad", + "0.0.1-addefgh", + "0.0.1 - abcde", + " 0.0.100 ", + ], ) def test_from_string_invalid_formats(invalid_id): """Test creating ScheduleId from various invalid string formats.""" with pytest.raises( - ValueError, - match=f"Invalid schedule ID string '{invalid_id}'. Expected format 'shard.realm.schedule'" + ValueError, match=f"Invalid schedule ID string '{invalid_id}'. Expected format 'shard.realm.schedule'" ): ScheduleId.from_string(invalid_id) diff --git a/tests/unit/schedule_info_query_test.py b/tests/unit/schedule_info_query_test.py index c65843371..857c7a007 100644 --- a/tests/unit/schedule_info_query_test.py +++ b/tests/unit/schedule_info_query_test.py @@ -2,6 +2,8 @@ Unit tests for the ScheduleInfoQuery class. """ +from __future__ import annotations + from unittest.mock import Mock import pytest @@ -22,6 +24,7 @@ from hiero_sdk_python.transaction.transaction_id import TransactionId from tests.unit.mock_server import mock_hedera_servers + pytestmark = pytest.mark.unit @@ -51,9 +54,7 @@ def test_execute_fails_with_missing_schedule_id(mock_client): """Test request creation with missing Schedule ID.""" query = ScheduleInfoQuery() - with pytest.raises( - ValueError, match="Schedule ID must be set before making the request." - ): + with pytest.raises(ValueError, match="Schedule ID must be set before making the request."): query.execute(mock_client) diff --git a/tests/unit/schedule_info_test.py b/tests/unit/schedule_info_test.py index af0a081fc..988d23cf9 100644 --- a/tests/unit/schedule_info_test.py +++ b/tests/unit/schedule_info_test.py @@ -2,6 +2,8 @@ Unit tests for the ScheduleInfo class. """ +from __future__ import annotations + import pytest from hiero_sdk_python.account.account_id import AccountId @@ -18,6 +20,7 @@ from hiero_sdk_python.timestamp import Timestamp from hiero_sdk_python.transaction.transaction_id import TransactionId + pytestmark = pytest.mark.unit @@ -50,7 +53,7 @@ def proto_schedule_info(): payer_account_id = AccountId(0, 0, 300) scheduled_transaction_id = TransactionId.generate(creator_account_id) - proto = ScheduleInfoProto( + return ScheduleInfoProto( scheduleID=schedule_id._to_proto(), creatorAccountID=creator_account_id._to_proto(), payerAccountID=payer_account_id._to_proto(), @@ -65,7 +68,6 @@ def proto_schedule_info(): ledger_id=b"test_ledger_id", wait_for_expiry=True, ) - return proto def test_schedule_info_initialization(schedule_info): @@ -227,9 +229,7 @@ def test_proto_conversion_full_object(schedule_info): assert converted.executed_at == schedule_info.executed_at assert converted.expiration_time == schedule_info.expiration_time assert converted.scheduled_transaction_id == schedule_info.scheduled_transaction_id - assert ( - converted.scheduled_transaction_body == schedule_info.scheduled_transaction_body - ) + assert converted.scheduled_transaction_body == schedule_info.scheduled_transaction_body assert converted.schedule_memo == schedule_info.schedule_memo assert converted.admin_key.to_bytes_raw() == schedule_info.admin_key.to_bytes_raw() assert len(converted.signers) == len(schedule_info.signers) @@ -284,9 +284,7 @@ def test_from_proto_field_helper(): assert result == schedule_id # Test with empty field - result = ScheduleInfo._from_proto_field( - proto, "execution_time", ScheduleId._from_proto - ) + result = ScheduleInfo._from_proto_field(proto, "execution_time", ScheduleId._from_proto) assert result is None diff --git a/tests/unit/schedule_sign_transaction_test.py b/tests/unit/schedule_sign_transaction_test.py index 3e66195e2..ce3a67fd5 100644 --- a/tests/unit/schedule_sign_transaction_test.py +++ b/tests/unit/schedule_sign_transaction_test.py @@ -2,6 +2,8 @@ Test cases for the ScheduleSignTransaction class. """ +from __future__ import annotations + from unittest.mock import MagicMock import pytest @@ -9,6 +11,7 @@ from hiero_sdk_python.schedule.schedule_id import ScheduleId from hiero_sdk_python.schedule.schedule_sign_transaction import ScheduleSignTransaction + pytestmark = pytest.mark.unit diff --git a/tests/unit/staking_info_test.py b/tests/unit/staking_info_test.py index 02506d014..f573a12ab 100644 --- a/tests/unit/staking_info_test.py +++ b/tests/unit/staking_info_test.py @@ -1,8 +1,11 @@ """Tests for the StakingInfo class.""" -import pytest +from __future__ import annotations + from dataclasses import FrozenInstanceError +import pytest + from hiero_sdk_python.account.account_id import AccountId from hiero_sdk_python.hapi.services.basic_types_pb2 import ( StakingInfo as StakingInfoProto, @@ -11,6 +14,7 @@ from hiero_sdk_python.staking_info import StakingInfo from hiero_sdk_python.timestamp import Timestamp + pytestmark = pytest.mark.unit @@ -183,9 +187,7 @@ def test_proto_round_trip_with_account(staking_info_with_account): assert restored.stake_period_start == staking_info_with_account.stake_period_start assert restored.pending_reward == staking_info_with_account.pending_reward assert restored.staked_to_me == staking_info_with_account.staked_to_me - assert str(restored.staked_account_id) == str( - staking_info_with_account.staked_account_id - ) + assert str(restored.staked_account_id) == str(staking_info_with_account.staked_account_id) assert restored.staked_node_id is None @@ -210,9 +212,7 @@ def test_from_bytes_deserializes(staking_info_with_account): assert restored.stake_period_start == staking_info_with_account.stake_period_start assert restored.pending_reward == staking_info_with_account.pending_reward assert restored.staked_to_me == staking_info_with_account.staked_to_me - assert str(restored.staked_account_id) == str( - staking_info_with_account.staked_account_id - ) + assert str(restored.staked_account_id) == str(staking_info_with_account.staked_account_id) assert restored.staked_node_id is None diff --git a/tests/unit/subscription_handle_test.py b/tests/unit/subscription_handle_test.py index 230680d6e..218da4c2d 100644 --- a/tests/unit/subscription_handle_test.py +++ b/tests/unit/subscription_handle_test.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from unittest.mock import Mock from hiero_sdk_python.utils.subscription_handle import SubscriptionHandle diff --git a/tests/unit/supply_type_test.py b/tests/unit/supply_type_test.py index bfcaf7884..77c91be8a 100644 --- a/tests/unit/supply_type_test.py +++ b/tests/unit/supply_type_test.py @@ -1,16 +1,23 @@ +from __future__ import annotations + import pytest + from hiero_sdk_python.tokens.supply_type import SupplyType + pytestmark = pytest.mark.unit + def test_members(): assert SupplyType.INFINITE.value == 0 assert SupplyType.FINITE.value == 1 + def test_name(): assert SupplyType.INFINITE.name == "INFINITE" assert SupplyType.FINITE.name == "FINITE" + def test_supply_type_enum_members(): members = list(SupplyType) assert len(members) == 2 diff --git a/tests/unit/test_account_balance.py b/tests/unit/test_account_balance.py index fb036172c..2700f8aea 100644 --- a/tests/unit/test_account_balance.py +++ b/tests/unit/test_account_balance.py @@ -1,11 +1,14 @@ """Tests for the AccountBalance class.""" +from __future__ import annotations + import pytest from hiero_sdk_python.account.account_balance import AccountBalance from hiero_sdk_python.hbar import Hbar from hiero_sdk_python.tokens.token_id import TokenId + pytestmark = pytest.mark.unit @@ -13,9 +16,9 @@ def test_account_balance_str_with_hbars_only(): """Test __str__ method with only hbars.""" hbars = Hbar(10) account_balance = AccountBalance(hbars=hbars) - + result = str(account_balance) - + assert "HBAR Balance:" in result assert "10.00000000 ℏ" in result assert "hbars" in result @@ -30,9 +33,9 @@ def test_account_balance_str_with_token_balances(): token_id_2 = TokenId(0, 0, 200) token_balances = {token_id_1: 1000, token_id_2: 500} account_balance = AccountBalance(hbars=hbars, token_balances=token_balances) - + result = str(account_balance) - + assert "HBAR Balance:" in result assert "10.00000000 ℏ" in result assert " hbars" in result @@ -45,9 +48,9 @@ def test_account_balance_str_with_empty_token_balances(): """Test __str__ method with empty token balances dict.""" hbars = Hbar(5.5) account_balance = AccountBalance(hbars=hbars, token_balances={}) - + result = str(account_balance) - + assert "HBAR Balance:" in result assert "5.50000000 ℏ" in result assert " hbars" in result @@ -59,9 +62,9 @@ def test_account_balance_repr_with_hbars_only(): """Test __repr__ method with only hbars.""" hbars = Hbar(10) account_balance = AccountBalance(hbars=hbars) - + result = repr(account_balance) - + assert "AccountBalance" in result assert "hbars=" in result assert "token_balances={}" in result @@ -75,13 +78,12 @@ def test_account_balance_repr_with_token_balances(): token_id_2 = TokenId(0, 0, 200) token_balances = {token_id_1: 1000, token_id_2: 500} account_balance = AccountBalance(hbars=hbars, token_balances=token_balances) - + result = repr(account_balance) - + assert "AccountBalance" in result assert "hbars=" in result assert "token_balances=" in result assert "0.0.100" in result or "TokenId" in result assert "1000" in result assert "500" in result - diff --git a/tests/unit/test_key_format.py b/tests/unit/test_key_format.py index b11d5c98b..ff0a0ae00 100644 --- a/tests/unit/test_key_format.py +++ b/tests/unit/test_key_format.py @@ -1,14 +1,18 @@ """Tests for the key_format module.""" +from __future__ import annotations + import pytest from hiero_sdk_python.crypto.private_key import PrivateKey from hiero_sdk_python.hapi.services import basic_types_pb2 -from hiero_sdk_python.utils.key_utils import key_to_proto from hiero_sdk_python.utils.key_format import format_key +from hiero_sdk_python.utils.key_utils import key_to_proto + pytestmark = pytest.mark.unit + def test_format_key_ed25519(): """Test formatting an Ed25519 key.""" private_key = PrivateKey.generate_ed25519() @@ -21,15 +25,16 @@ def test_format_key_ed25519(): assert formatted == expected + def test_format_key_none(): """Test formatting a None key.""" formatted = format_key(None) - assert formatted == "None" + assert formatted == "None" + def test_format_key_threshold_key(): """Test formatting a ThresholdKey.""" - key = basic_types_pb2.Key() key.thresholdKey.threshold = 2 @@ -37,24 +42,24 @@ def test_format_key_threshold_key(): assert formatted == "thresholdKey(...)" + def test_format_key_contract_id(): """Test formatting a ContractID key.""" - key = basic_types_pb2.Key() key.contractID.shardNum = 0 key.contractID.realmNum = 0 key.contractID.contractNum = 5678 - expected_inner = str(key.contractID) + expected_inner = str(key.contractID) expected = f"contractID({expected_inner})" formatted = format_key(key) assert formatted == expected + def test_format_key_keylist(): """Test formatting a KeyList.""" - key = basic_types_pb2.Key() key.keyList.keys.add() @@ -62,6 +67,7 @@ def test_format_key_keylist(): assert formatted == "keyList(...)" + def test_format_key_unknown(): """Test formatting an unknown key type.""" key = basic_types_pb2.Key() @@ -70,4 +76,4 @@ def test_format_key_unknown(): formatted = format_key(key) expected = str(key).replace("\n", " ") - assert formatted == expected \ No newline at end of file + assert formatted == expected diff --git a/tests/unit/test_transaction_receipt.py b/tests/unit/test_transaction_receipt.py index 9549ceca9..c4811506c 100644 --- a/tests/unit/test_transaction_receipt.py +++ b/tests/unit/test_transaction_receipt.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import pytest from hiero_sdk_python.hapi.services import transaction_receipt_pb2 diff --git a/tests/unit/test_transaction_response.py b/tests/unit/test_transaction_response.py index af7afbc81..29e562167 100644 --- a/tests/unit/test_transaction_response.py +++ b/tests/unit/test_transaction_response.py @@ -2,36 +2,38 @@ Tests for TransactionResponse behavior. """ +from __future__ import annotations + import pytest from hiero_sdk_python.account.account_id import AccountId -from hiero_sdk_python.response_code import ResponseCode -from hiero_sdk_python.transaction.transaction_response import TransactionResponse from hiero_sdk_python.hapi.services import ( response_header_pb2, response_pb2, transaction_get_receipt_pb2, transaction_receipt_pb2, ) - +from hiero_sdk_python.response_code import ResponseCode +from hiero_sdk_python.transaction.transaction_response import TransactionResponse from tests.unit.mock_server import mock_hedera_servers + pytestmark = pytest.mark.unit def test_transaction_response_fields(transaction_id): - """asserting response is correctly populated""" + """Asserting response is correctly populated""" resp = TransactionResponse() - + # Assert public attributes exist (PRIORITY 1: protect against breaking changes) - assert hasattr(resp, 'transaction_id'), "Missing public attribute: transaction_id" - assert hasattr(resp, 'node_id'), "Missing public attribute: node_id" - assert hasattr(resp, 'hash'), "Missing public attribute: hash" - assert hasattr(resp, 'validate_status'), "Missing public attribute: validate_status" - assert hasattr(resp, 'transaction'), "Missing public attribute: transaction" - + assert hasattr(resp, "transaction_id"), "Missing public attribute: transaction_id" + assert hasattr(resp, "node_id"), "Missing public attribute: node_id" + assert hasattr(resp, "hash"), "Missing public attribute: hash" + assert hasattr(resp, "validate_status"), "Missing public attribute: validate_status" + assert hasattr(resp, "transaction"), "Missing public attribute: transaction" + # Assert default values - assert resp.hash == bytes(), "Default hash should be empty bytes" + assert resp.hash == b"", "Default hash should be empty bytes" assert resp.validate_status is False, "Default validate_status should be False" assert resp.transaction is None, "Default transaction should be None" @@ -54,23 +56,15 @@ def test_transaction_response_get_receipt_is_pinned_to_submitting_node(transacti """ bad_node_response = response_pb2.Response( transactionGetReceipt=transaction_get_receipt_pb2.TransactionGetReceiptResponse( - header=response_header_pb2.ResponseHeader( - nodeTransactionPrecheckCode=ResponseCode.INVALID_TRANSACTION - ), - receipt=transaction_receipt_pb2.TransactionReceipt( - status=ResponseCode.UNKNOWN - ), + header=response_header_pb2.ResponseHeader(nodeTransactionPrecheckCode=ResponseCode.INVALID_TRANSACTION), + receipt=transaction_receipt_pb2.TransactionReceipt(status=ResponseCode.UNKNOWN), ) ) good_node_response = response_pb2.Response( transactionGetReceipt=transaction_get_receipt_pb2.TransactionGetReceiptResponse( - header=response_header_pb2.ResponseHeader( - nodeTransactionPrecheckCode=ResponseCode.OK - ), - receipt=transaction_receipt_pb2.TransactionReceipt( - status=ResponseCode.SUCCESS - ), + header=response_header_pb2.ResponseHeader(nodeTransactionPrecheckCode=ResponseCode.OK), + receipt=transaction_receipt_pb2.TransactionReceipt(status=ResponseCode.SUCCESS), ) ) diff --git a/tests/unit/timestamp_test.py b/tests/unit/timestamp_test.py index e5d486a2d..8e6024d1b 100644 --- a/tests/unit/timestamp_test.py +++ b/tests/unit/timestamp_test.py @@ -6,12 +6,16 @@ ensure robust coverage of timestamp functionality. """ +from __future__ import annotations + import time -import pytest from datetime import datetime, timezone -from hiero_sdk_python.timestamp import Timestamp +import pytest + from hiero_sdk_python.hapi.services.timestamp_pb2 import Timestamp as TimestampProto +from hiero_sdk_python.timestamp import Timestamp + pytestmark = pytest.mark.unit @@ -66,7 +70,7 @@ def test_from_date_unix_epoch(): ts = Timestamp.from_date(dt) assert ts.seconds == 0 assert ts.nanos == 0 - + def test_from_date_max_microseconds(): """Test from_date with maximum microseconds to ensure nanos calculation is correct.""" @@ -76,6 +80,7 @@ def test_from_date_max_microseconds(): expected = 999_999_000 assert abs(ts.nanos - expected) < 1_000 + @pytest.mark.parametrize("bad_input", [None, [], {}, 3.14]) def test_from_date_invalid_type(bad_input): """Ensure from_date raises ValueError for invalid input types.""" @@ -181,7 +186,6 @@ def test_generate_without_jitter(): assert delta < 0.1 - def test_generate_with_jitter(): """Verify that generated timestamps with jitter remain close to the system time within a safe tolerance.""" ts = Timestamp.generate(has_jitter=True) @@ -190,6 +194,7 @@ def test_generate_with_jitter(): # Jitter is explicitly 3-8 seconds backward assert 3.0 <= delta <= 9.0 + # Protobuf serialization tests diff --git a/tests/unit/token_airdrop_claim_test.py b/tests/unit/token_airdrop_claim_test.py index 3b51f7fcd..5c0e56d0e 100644 --- a/tests/unit/token_airdrop_claim_test.py +++ b/tests/unit/token_airdrop_claim_test.py @@ -1,25 +1,30 @@ +from __future__ import annotations + import pytest -from hiero_sdk_python.hapi.services import timestamp_pb2 -from hiero_sdk_python.hapi.services import transaction_pb2 -from hiero_sdk_python.hapi.services.token_claim_airdrop_pb2 import ( # pylint: disable=no-name-in-module +from hiero_sdk_python.account.account_id import AccountId +from hiero_sdk_python.hapi.services import timestamp_pb2, transaction_pb2 +from hiero_sdk_python.hapi.services.token_claim_airdrop_pb2 import ( # pylint: disable=no-name-in-module TokenClaimAirdropTransactionBody, ) -from hiero_sdk_python.transaction.transaction_id import TransactionId -from hiero_sdk_python.account.account_id import AccountId from hiero_sdk_python.tokens.nft_id import NftId -from hiero_sdk_python.tokens.token_id import TokenId from hiero_sdk_python.tokens.token_airdrop_claim import TokenClaimAirdropTransaction from hiero_sdk_python.tokens.token_airdrop_pending_id import PendingAirdropId +from hiero_sdk_python.tokens.token_id import TokenId +from hiero_sdk_python.transaction.transaction_id import TransactionId + pytestmark = pytest.mark.unit + def _make_fungible_pending(sender: AccountId, receiver: AccountId, num: int) -> PendingAirdropId: return PendingAirdropId(sender, receiver, TokenId(0, 0, num), None) + def _make_nft_pending(sender: AccountId, receiver: AccountId, num: int, serial: int) -> PendingAirdropId: return PendingAirdropId(sender, receiver, None, NftId(TokenId(0, 0, num), serial)) + def test_add_pending_airdrop_id(): """Test adding one pending fungible airdrop id using chaining method""" sender = AccountId(0, 0, 1001) @@ -36,6 +41,7 @@ def test_add_pending_airdrop_id(): assert len(ids) == 1 assert ids[0] == pending_airdrop_fungible_1 + def test_add_pending_airdrop_id_nft(): """Test adding one pending NFT airdrop id using chaining method""" sender = AccountId(0, 0, 2001) @@ -52,13 +58,14 @@ def test_add_pending_airdrop_id_nft(): assert len(ids) == 1 assert ids[0] == pending_airdrop_nft_1 + def test_add_pending_airdrop_ids_mixed_fungible_and_nft(): """Claim one fungible and one NFT pending airdrop in a single transaction.""" sender = AccountId(0, 0, 3001) receiver = AccountId(0, 0, 3002) - fungible = _make_fungible_pending(sender, receiver, 3000) # token num=3000 - nft = _make_nft_pending(sender, receiver, 4000, 1) # token num=4000, serial=1 + fungible = _make_fungible_pending(sender, receiver, 3000) # token num=3000 + nft = _make_nft_pending(sender, receiver, 4000, 1) # token num=4000, serial=1 tx_claim = TokenClaimAirdropTransaction() tx_claim.add_pending_airdrop_id(fungible).add_pending_airdrop_id(nft) @@ -71,6 +78,7 @@ def test_add_pending_airdrop_ids_mixed_fungible_and_nft(): assert ids[0] == fungible assert ids[1] == nft + def test_add_pending_airdrop_ids_multiple_mixed_dynamic(): """Test adding several fungible + NFT pending airdrop IDs built dynamically.""" sender = AccountId(0, 0, 6201) @@ -90,19 +98,20 @@ def test_add_pending_airdrop_ids_multiple_mixed_dynamic(): ids = tx_claim.get_pending_airdrop_ids() assert ids == pending_ids + def test_cannot_exceed_max_airdrops(): - """ Tests that 10 airdrops is fine but anything more not""" + """Tests that 10 airdrops is fine but anything more not""" sender = AccountId(0, 0, 8001) receiver = AccountId(0, 0, 8002) tx = TokenClaimAirdropTransaction() - items = [PendingAirdropId(sender, receiver, TokenId(0, 0, 8000 + i), None) - for i in range(tx.MAX_IDS)] + items = [PendingAirdropId(sender, receiver, TokenId(0, 0, 8000 + i), None) for i in range(tx.MAX_IDS)] tx.add_pending_airdrop_ids(items) assert len(tx.get_pending_airdrop_ids()) == tx.MAX_IDS with pytest.raises(ValueError): - tx.add_pending_airdrop_id(PendingAirdropId(sender, receiver, TokenId(0, 0, 9999), None)) #This would be 11 + tx.add_pending_airdrop_id(PendingAirdropId(sender, receiver, TokenId(0, 0, 9999), None)) # This would be 11 + def test_add_batch_overflow_is_atomic(): sender_account = AccountId(0, 0, 9001) @@ -128,8 +137,9 @@ def test_add_batch_overflow_is_atomic(): assert after_ids == before_ids + def test_min_ids_enforced_on_build_hits_validation(): - """ Tests that at least one airdrop is required to claim""" + """Tests that at least one airdrop is required to claim""" transaction_claim = TokenClaimAirdropTransaction() transaction_claim.transaction_id = TransactionId(AccountId(0, 0, 9999), timestamp_pb2.Timestamp(seconds=1)) transaction_claim.node_account_id = AccountId(0, 0, 3) @@ -137,6 +147,7 @@ def test_min_ids_enforced_on_build_hits_validation(): with pytest.raises(ValueError): transaction_claim.build_transaction_body() + def test_rejects_duplicate_fungible(): sender = AccountId(0, 0, 8101) receiver = AccountId(0, 0, 8102) @@ -153,6 +164,7 @@ def test_rejects_duplicate_fungible(): ids = tx.get_pending_airdrop_ids() assert ids == [f1] + def test_rejects_duplicate_nft(): sender = AccountId(0, 0, 8201) receiver = AccountId(0, 0, 8202) @@ -169,6 +181,7 @@ def test_rejects_duplicate_nft(): ids = tx.get_pending_airdrop_ids() assert ids == [n1] + def test_build_transaction_body_populates_proto(): sender = AccountId(0, 0, 8401) receiver = AccountId(0, 0, 8402) @@ -176,14 +189,10 @@ def test_build_transaction_body_populates_proto(): fungible_airdrop = PendingAirdropId(sender, receiver, TokenId(0, 0, 8400), None) nft_airdrop = PendingAirdropId(sender, receiver, None, NftId(TokenId(0, 0, 8405), 3)) - tx_claim = TokenClaimAirdropTransaction().add_pending_airdrop_ids( - [fungible_airdrop, nft_airdrop] - ) + tx_claim = TokenClaimAirdropTransaction().add_pending_airdrop_ids([fungible_airdrop, nft_airdrop]) # Satisfy base preconditions: set transaction_id and node_account_id - tx_claim.transaction_id = TransactionId( - sender, timestamp_pb2.Timestamp(seconds=1, nanos=0) - ) + tx_claim.transaction_id = TransactionId(sender, timestamp_pb2.Timestamp(seconds=1, nanos=0)) tx_claim.node_account_id = AccountId(0, 0, 3) # dummy node account body: transaction_pb2.TransactionBody = tx_claim.build_transaction_body() @@ -196,6 +205,7 @@ def test_build_transaction_body_populates_proto(): actual = [a.SerializeToString() for a in claim.pending_airdrops] assert actual == expected + def test_from_proto_round_trip(): sender_account = AccountId(0, 0, 9041) receiver_account = AccountId(0, 0, 9042) @@ -208,6 +218,7 @@ def test_from_proto_round_trip(): rebuilt = TokenClaimAirdropTransaction._from_proto(proto_body) # pylint: disable=protected-access assert rebuilt.get_pending_airdrop_ids() == original_ids + def test_get_pending_airdrop_ids_returns_copy(): sender_account = AccountId(0, 0, 9021) receiver_account = AccountId(0, 0, 9022) @@ -219,6 +230,7 @@ def test_get_pending_airdrop_ids_returns_copy(): assert transaction_claim.get_pending_airdrop_ids() == [airdrop_id] # unchanged + def test_order_preserved_across_batched_adds(): sender_account = AccountId(0, 0, 9031) receiver_account = AccountId(0, 0, 9032) @@ -229,10 +241,13 @@ def test_order_preserved_across_batched_adds(): id_d = PendingAirdropId(sender_account, receiver_account, None, NftId(TokenId(0, 0, 9035), 2)) transaction_claim = TokenClaimAirdropTransaction() - transaction_claim.add_pending_airdrop_ids([id_a, id_b]).add_pending_airdrop_ids([id_c]).add_pending_airdrop_ids([id_d]) + transaction_claim.add_pending_airdrop_ids([id_a, id_b]).add_pending_airdrop_ids([id_c]).add_pending_airdrop_ids( + [id_d] + ) assert transaction_claim.get_pending_airdrop_ids() == [id_a, id_b, id_c, id_d] + def test_add_empty_list_is_noop(): sender_account = AccountId(0, 0, 9071) receiver_account = AccountId(0, 0, 9072) @@ -243,16 +258,20 @@ def test_add_empty_list_is_noop(): assert transaction_claim.get_pending_airdrop_ids() == [first_id] + def test_from_proto_rejects_too_many(): sender_account = AccountId(0, 0, 9051) receiver_account = AccountId(0, 0, 9052) - too_many = [PendingAirdropId(sender_account, receiver_account, TokenId(0, 0, 9050 + i), None) - for i in range(TokenClaimAirdropTransaction.MAX_IDS + 1)] + too_many = [ + PendingAirdropId(sender_account, receiver_account, TokenId(0, 0, 9050 + i), None) + for i in range(TokenClaimAirdropTransaction.MAX_IDS + 1) + ] body = TokenClaimAirdropTransactionBody(pending_airdrops=[x._to_proto() for x in too_many]) with pytest.raises(ValueError): TokenClaimAirdropTransaction._from_proto(body) # pylint: disable=protected-access + def test_from_proto_rejects_duplicates(): sender_account = AccountId(0, 0, 9061) receiver_account = AccountId(0, 0, 9062) @@ -262,6 +281,7 @@ def test_from_proto_rejects_duplicates(): with pytest.raises(ValueError): TokenClaimAirdropTransaction._from_proto(body) # pylint: disable=protected-access + def test_reject_pending_airdrop_with_both_token_and_nft(): """A PendingAirdropId must not have both token_id and nft_id at the same time""" sender = AccountId(0, 0, 9111) @@ -274,6 +294,7 @@ def test_reject_pending_airdrop_with_both_token_and_nft(): with pytest.raises(ValueError, match="Exactly one of 'token_id' or 'nft_id' must be required."): PendingAirdropId(sender, receiver, token_id, nft_id) + def test_from_proto_with_invalid_pending_airdrop(): """_from_proto should raise if proto contains a PendingAirdropId with neither token_id nor nft_id""" sender = AccountId(0, 0, 9111) @@ -283,11 +304,12 @@ def test_from_proto_with_invalid_pending_airdrop(): with pytest.raises(ValueError): PendingAirdropId(sender, receiver, token_id=None, nft_id=None) + def test_str_and_repr(): sender = AccountId(0, 0, 1) receiver = AccountId(0, 0, 2) tx = TokenClaimAirdropTransaction() assert str(tx) == "No pending airdrops in this transaction." - tx.add_pending_airdrop_id(PendingAirdropId(sender, receiver, TokenId(0,0,10), None)) + tx.add_pending_airdrop_id(PendingAirdropId(sender, receiver, TokenId(0, 0, 10), None)) assert "Pending Airdrops to claim:" in str(tx) - assert repr(tx).startswith("TokenClaimAirdropTransaction(") \ No newline at end of file + assert repr(tx).startswith("TokenClaimAirdropTransaction(") diff --git a/tests/unit/token_airdrop_pending_id_test.py b/tests/unit/token_airdrop_pending_id_test.py index 27bdab83c..da9a6cb6d 100644 --- a/tests/unit/token_airdrop_pending_id_test.py +++ b/tests/unit/token_airdrop_pending_id_test.py @@ -1,62 +1,52 @@ +from __future__ import annotations + +import pytest + from hiero_sdk_python.account.account_id import AccountId from hiero_sdk_python.hapi.services import basic_types_pb2 from hiero_sdk_python.tokens.nft_id import NftId +from hiero_sdk_python.tokens.token_airdrop_pending_id import PendingAirdropId from hiero_sdk_python.tokens.token_id import TokenId -import pytest -from hiero_sdk_python.tokens.token_airdrop_pending_id import PendingAirdropId pytestmark = pytest.mark.unit + def test_pending_airdrop_id_constructor(mock_account_ids): """Test PendingAirdropId constructor with various params""" sender_id, receiver_id, _, token_id_1, token_id_2 = mock_account_ids nft_id = NftId(token_id=token_id_2, serial_number=10) # Test with token_id - token_pending_airdrop_id = PendingAirdropId( - sender_id=sender_id, - receiver_id=receiver_id, - token_id=token_id_1 - ) + token_pending_airdrop_id = PendingAirdropId(sender_id=sender_id, receiver_id=receiver_id, token_id=token_id_1) assert token_pending_airdrop_id.sender_id == sender_id assert token_pending_airdrop_id.receiver_id == receiver_id assert token_pending_airdrop_id.token_id == token_id_1 - assert token_pending_airdrop_id.nft_id == None + assert token_pending_airdrop_id.nft_id is None - #Test wit nft_id - nft_pending_airdrop_id = PendingAirdropId( - sender_id=sender_id, - receiver_id=receiver_id, - nft_id=nft_id - ) + # Test wit nft_id + nft_pending_airdrop_id = PendingAirdropId(sender_id=sender_id, receiver_id=receiver_id, nft_id=nft_id) assert nft_pending_airdrop_id.sender_id == sender_id assert nft_pending_airdrop_id.receiver_id == receiver_id - assert nft_pending_airdrop_id.token_id == None + assert nft_pending_airdrop_id.token_id is None assert nft_pending_airdrop_id.nft_id == nft_id + def test_pending_airdrop_id_constructor_for_invalid_param(mock_account_ids): """Test PendingAirdropId constructor for invalid params""" sender_id, receiver_id, _, token_id_1, token_id_2 = mock_account_ids nft_id = NftId(token_id=token_id_2, serial_number=10) - #Both token_id and nft_id not provide: + # Both token_id and nft_id not provide: with pytest.raises(ValueError, match="Exactly one of 'token_id' or 'nft_id' must be required."): - PendingAirdropId( - sender_id=sender_id, - receiver_id=receiver_id - ) + PendingAirdropId(sender_id=sender_id, receiver_id=receiver_id) - #Bot token_id and nft is provided: + # Bot token_id and nft is provided: with pytest.raises(ValueError, match="Exactly one of 'token_id' or 'nft_id' must be required."): - PendingAirdropId( - sender_id=sender_id, - receiver_id=receiver_id, - token_id=token_id_1, - nft_id=nft_id - ) + PendingAirdropId(sender_id=sender_id, receiver_id=receiver_id, token_id=token_id_1, nft_id=nft_id) + def test_convert_to_proto(mock_account_ids): """Test PendingAirdropId _to_proto() method""" @@ -64,17 +54,13 @@ def test_convert_to_proto(mock_account_ids): nft_id = NftId(token_id=token_id_2, serial_number=10) # Test with token_id - token_pending_airdrop_id = PendingAirdropId( - sender_id=sender_id, - receiver_id=receiver_id, - token_id=token_id_1 - ) + token_pending_airdrop_id = PendingAirdropId(sender_id=sender_id, receiver_id=receiver_id, token_id=token_id_1) token_proto = token_pending_airdrop_id._to_proto() assert token_proto.sender_id.shardNum == sender_id.shard assert token_proto.sender_id.realmNum == sender_id.realm assert token_proto.sender_id.accountNum == sender_id.num - assert token_proto.receiver_id.shardNum== receiver_id.shard + assert token_proto.receiver_id.shardNum == receiver_id.shard assert token_proto.receiver_id.realmNum == receiver_id.realm assert token_proto.receiver_id.accountNum == receiver_id.num assert token_proto.fungible_token_type.shardNum == token_id_1.shard @@ -83,17 +69,13 @@ def test_convert_to_proto(mock_account_ids): assert token_proto.HasField("non_fungible_token") == False # Test with nft_id - nft_pending_airdrop_id = PendingAirdropId( - sender_id=sender_id, - receiver_id=receiver_id, - nft_id=nft_id - ) + nft_pending_airdrop_id = PendingAirdropId(sender_id=sender_id, receiver_id=receiver_id, nft_id=nft_id) nft_proto = nft_pending_airdrop_id._to_proto() assert nft_proto.sender_id.shardNum == sender_id.shard assert nft_proto.sender_id.realmNum == sender_id.realm assert nft_proto.sender_id.accountNum == sender_id.num - assert nft_proto.receiver_id.shardNum== receiver_id.shard + assert nft_proto.receiver_id.shardNum == receiver_id.shard assert nft_proto.receiver_id.realmNum == receiver_id.realm assert nft_proto.receiver_id.accountNum == receiver_id.num assert nft_proto.non_fungible_token.serial_number == 10 @@ -102,6 +84,7 @@ def test_convert_to_proto(mock_account_ids): assert nft_proto.non_fungible_token.token_ID.tokenNum == token_id_2.num assert nft_proto.HasField("fungible_token_type") == False + def test_from_proto(mock_account_ids): """Test PendingAirdropId _from_proto() method""" sender_id, receiver_id, _, token_id_1, token_id_2 = mock_account_ids @@ -112,7 +95,7 @@ def test_from_proto(mock_account_ids): sender_id=AccountId._to_proto(sender_id), receiver_id=AccountId._to_proto(receiver_id), fungible_token_type=TokenId._to_proto(token_id_1), - non_fungible_token=None + non_fungible_token=None, ) token_pending_airdrop_id = PendingAirdropId._from_proto(token_pending_airdrop_proto) @@ -126,14 +109,14 @@ def test_from_proto(mock_account_ids): assert token_pending_airdrop_id.token_id.shard == token_id_1.shard assert token_pending_airdrop_id.token_id.realm == token_id_1.realm assert token_pending_airdrop_id.token_id.num == token_id_1.num - assert token_pending_airdrop_id.nft_id == None + assert token_pending_airdrop_id.nft_id is None # Test with nft_id nft_pending_airdrop_proto = basic_types_pb2.PendingAirdropId( sender_id=AccountId._to_proto(sender_id), receiver_id=AccountId._to_proto(receiver_id), fungible_token_type=None, - non_fungible_token=NftId._to_proto(nft_id) + non_fungible_token=NftId._to_proto(nft_id), ) nft_pending_airdrop_id = PendingAirdropId._from_proto(nft_pending_airdrop_proto) @@ -148,4 +131,4 @@ def test_from_proto(mock_account_ids): assert nft_pending_airdrop_id.nft_id.token_id.shard == token_id_2.shard assert nft_pending_airdrop_id.nft_id.token_id.realm == token_id_2.realm assert nft_pending_airdrop_id.nft_id.token_id.num == token_id_2.num - assert nft_pending_airdrop_id.token_id == None \ No newline at end of file + assert nft_pending_airdrop_id.token_id is None diff --git a/tests/unit/token_airdrop_pending_record_test.py b/tests/unit/token_airdrop_pending_record_test.py index 5e3d6ae3b..1307e3ba6 100644 --- a/tests/unit/token_airdrop_pending_record_test.py +++ b/tests/unit/token_airdrop_pending_record_test.py @@ -1,68 +1,49 @@ +from __future__ import annotations + from hiero_sdk_python.hapi.services import basic_types_pb2, transaction_record_pb2 from hiero_sdk_python.tokens.nft_id import NftId from hiero_sdk_python.tokens.token_airdrop_pending_id import PendingAirdropId -import pytest - from hiero_sdk_python.tokens.token_airdrop_pending_record import PendingAirdropRecord + def test_pending_airdrop_record_constructor(mock_account_ids): """Test PendingAirdropRecord constructor with various params""" sender_id, receiver_id, _, token_id_1, token_id_2 = mock_account_ids nft_id = NftId(token_id=token_id_2, serial_number=10) amount = 1 - token_pending_airdrop_id = PendingAirdropId( - sender_id=sender_id, - receiver_id=receiver_id, - token_id=token_id_1 - ) + token_pending_airdrop_id = PendingAirdropId(sender_id=sender_id, receiver_id=receiver_id, token_id=token_id_1) - nft_pending_airdrop_id = PendingAirdropId( - sender_id=sender_id, - receiver_id=receiver_id, - nft_id=nft_id - ) + nft_pending_airdrop_id = PendingAirdropId(sender_id=sender_id, receiver_id=receiver_id, nft_id=nft_id) - #Pending airdrop id with token - pending_airdrop_record_1 = PendingAirdropRecord( - pending_airdrop_id=token_pending_airdrop_id, - amount=amount - ) + # Pending airdrop id with token + pending_airdrop_record_1 = PendingAirdropRecord(pending_airdrop_id=token_pending_airdrop_id, amount=amount) assert pending_airdrop_record_1.pending_airdrop_id == token_pending_airdrop_id assert pending_airdrop_record_1.amount == amount - #Pending airdrop id with nft - pending_airdrop_record_2 = PendingAirdropRecord( - pending_airdrop_id=nft_pending_airdrop_id, - amount=amount - ) + # Pending airdrop id with nft + pending_airdrop_record_2 = PendingAirdropRecord(pending_airdrop_id=nft_pending_airdrop_id, amount=amount) assert pending_airdrop_record_2.pending_airdrop_id == nft_pending_airdrop_id assert pending_airdrop_record_2.amount == amount + def test_pending_airdrop_record_to_proto_for_fungible_token(mock_account_ids): """Test PendingAirdropRecord _to_proto() method for fungible token""" sender_id, receiver_id, _, token_id, _ = mock_account_ids amount = 1 - pending_airdrop_id = PendingAirdropId( - sender_id=sender_id, - receiver_id=receiver_id, - token_id=token_id - ) + pending_airdrop_id = PendingAirdropId(sender_id=sender_id, receiver_id=receiver_id, token_id=token_id) - #Pending airdrop id with token - record = PendingAirdropRecord( - pending_airdrop_id=pending_airdrop_id, - amount=amount - ) + # Pending airdrop id with token + record = PendingAirdropRecord(pending_airdrop_id=pending_airdrop_id, amount=amount) proto = record._to_proto() assert proto.pending_airdrop_id.sender_id.shardNum == sender_id.shard assert proto.pending_airdrop_id.sender_id.realmNum == sender_id.realm assert proto.pending_airdrop_id.sender_id.accountNum == sender_id.num - assert proto.pending_airdrop_id.receiver_id.shardNum== receiver_id.shard + assert proto.pending_airdrop_id.receiver_id.shardNum == receiver_id.shard assert proto.pending_airdrop_id.receiver_id.realmNum == receiver_id.realm assert proto.pending_airdrop_id.receiver_id.accountNum == receiver_id.num assert proto.pending_airdrop_id.fungible_token_type.shardNum == token_id.shard @@ -71,29 +52,23 @@ def test_pending_airdrop_record_to_proto_for_fungible_token(mock_account_ids): assert proto.pending_airdrop_id.HasField("non_fungible_token") == False assert proto.pending_airdrop_value.amount == amount + def test_pending_airdrop_record_to_proto_for_nft(mock_account_ids): """Test PendingAirdropRecord _to_proto() method for nft""" sender_id, receiver_id, _, token_id, _ = mock_account_ids nft_id = NftId(token_id=token_id, serial_number=10) amount = 1 - pending_airdrop_id = PendingAirdropId( - sender_id=sender_id, - receiver_id=receiver_id, - nft_id=nft_id - ) + pending_airdrop_id = PendingAirdropId(sender_id=sender_id, receiver_id=receiver_id, nft_id=nft_id) - #Pending airdrop id with nft - record = PendingAirdropRecord( - pending_airdrop_id=pending_airdrop_id, - amount=amount - ) + # Pending airdrop id with nft + record = PendingAirdropRecord(pending_airdrop_id=pending_airdrop_id, amount=amount) proto = record._to_proto() assert proto.pending_airdrop_id.sender_id.shardNum == sender_id.shard assert proto.pending_airdrop_id.sender_id.realmNum == sender_id.realm assert proto.pending_airdrop_id.sender_id.accountNum == sender_id.num - assert proto.pending_airdrop_id.receiver_id.shardNum== receiver_id.shard + assert proto.pending_airdrop_id.receiver_id.shardNum == receiver_id.shard assert proto.pending_airdrop_id.receiver_id.realmNum == receiver_id.realm assert proto.pending_airdrop_id.receiver_id.accountNum == receiver_id.num assert proto.pending_airdrop_id.non_fungible_token.serial_number == 10 @@ -109,16 +84,12 @@ def test_pending_airdrop_record_from_proto_for_fungible_token(mock_account_ids): sender_id, receiver_id, _, token_id, _ = mock_account_ids amount = 1 - pending_airdrop_id = PendingAirdropId( - sender_id=sender_id, - receiver_id=receiver_id, - token_id=token_id - ) + pending_airdrop_id = PendingAirdropId(sender_id=sender_id, receiver_id=receiver_id, token_id=token_id) - #Pending airdrop id with token + # Pending airdrop id with token proto = transaction_record_pb2.PendingAirdropRecord( pending_airdrop_id=pending_airdrop_id._to_proto(), - pending_airdrop_value=basic_types_pb2.PendingAirdropValue(amount=amount) + pending_airdrop_value=basic_types_pb2.PendingAirdropValue(amount=amount), ) record = PendingAirdropRecord._from_proto(proto=proto) @@ -132,25 +103,22 @@ def test_pending_airdrop_record_from_proto_for_fungible_token(mock_account_ids): assert record.pending_airdrop_id.token_id.shard == token_id.shard assert record.pending_airdrop_id.token_id.realm == token_id.realm assert record.pending_airdrop_id.token_id.num == token_id.num - assert record.pending_airdrop_id.nft_id == None + assert record.pending_airdrop_id.nft_id is None assert record.amount == amount + def test_pending_airdrop_record_from_proto_for_nft(mock_account_ids): """Test PendingAirdropRecord _from_proto() method for nft""" sender_id, receiver_id, _, token_id, _ = mock_account_ids nft_id = NftId(token_id=token_id, serial_number=10) amount = 1 - pending_airdrop_id = PendingAirdropId( - sender_id=sender_id, - receiver_id=receiver_id, - nft_id=nft_id - ) + pending_airdrop_id = PendingAirdropId(sender_id=sender_id, receiver_id=receiver_id, nft_id=nft_id) - #Pending airdrop id with nft + # Pending airdrop id with nft proto = transaction_record_pb2.PendingAirdropRecord( pending_airdrop_id=pending_airdrop_id._to_proto(), - pending_airdrop_value=basic_types_pb2.PendingAirdropValue(amount=amount) + pending_airdrop_value=basic_types_pb2.PendingAirdropValue(amount=amount), ) record = PendingAirdropRecord._from_proto(proto=proto) @@ -165,4 +133,4 @@ def test_pending_airdrop_record_from_proto_for_nft(mock_account_ids): assert record.pending_airdrop_id.nft_id.token_id.shard == token_id.shard assert record.pending_airdrop_id.nft_id.token_id.realm == token_id.realm assert record.pending_airdrop_id.nft_id.token_id.num == token_id.num - assert record.pending_airdrop_id.token_id == None + assert record.pending_airdrop_id.token_id is None diff --git a/tests/unit/token_airdrop_transaction_cancel_test.py b/tests/unit/token_airdrop_transaction_cancel_test.py index cf6f9049b..ac84eb223 100644 --- a/tests/unit/token_airdrop_transaction_cancel_test.py +++ b/tests/unit/token_airdrop_transaction_cancel_test.py @@ -1,4 +1,9 @@ +from __future__ import annotations + from unittest.mock import MagicMock + +import pytest + from hiero_sdk_python.hapi.services import timestamp_pb2 from hiero_sdk_python.hapi.services.schedulable_transaction_body_pb2 import ( SchedulableTransactionBody, @@ -7,24 +12,23 @@ from hiero_sdk_python.tokens.token_airdrop_pending_id import PendingAirdropId from hiero_sdk_python.tokens.token_airdrop_transaction_cancel import TokenCancelAirdropTransaction from hiero_sdk_python.transaction.transaction_id import TransactionId -import pytest + pytestmark = pytest.mark.unit + def generate_transaction_id(account_id_proto): """Generate a unique transaction ID based on the account ID and the current timestamp.""" import time + current_time = time.time() timestamp_seconds = int(current_time) timestamp_nanos = int((current_time - timestamp_seconds) * 1e9) tx_timestamp = timestamp_pb2.Timestamp(seconds=timestamp_seconds, nanos=timestamp_nanos) - tx_id = TransactionId( - valid_start=tx_timestamp, - account_id=account_id_proto - ) - return tx_id + return TransactionId(valid_start=tx_timestamp, account_id=account_id_proto) + def test_constructor_for_token_cancel_airdrop(mock_account_ids): """Test constructor of TokenCancelAirdropTransaction""" @@ -32,7 +36,7 @@ def test_constructor_for_token_cancel_airdrop(mock_account_ids): nft_id = NftId(token_id_2, 10) pending_airdrops = [ PendingAirdropId(sender_id=sender, receiver_id=receiver, token_id=token_id_1), - PendingAirdropId(sender_id=sender, receiver_id=receiver, nft_id=nft_id) + PendingAirdropId(sender_id=sender, receiver_id=receiver, nft_id=nft_id), ] # Without any param cancel_airdrop_tx_1 = TokenCancelAirdropTransaction() @@ -43,6 +47,7 @@ def test_constructor_for_token_cancel_airdrop(mock_account_ids): assert len(cancel_airdrop_tx_2.pending_airdrops) == 2 assert cancel_airdrop_tx_2.pending_airdrops == pending_airdrops + def test_build_transaction_body(mock_account_ids): """Test building the token cancel airdrop transaction body with valid params.""" sender_id, receiver_id, node_account_id, token_id_1, token_id_2 = mock_account_ids @@ -75,7 +80,7 @@ def test_build_transaction_body(mock_account_ids): assert pending_airdrops[1].sender_id.shardNum == sender_id.shard assert pending_airdrops[1].sender_id.realmNum == sender_id.realm assert pending_airdrops[1].sender_id.accountNum == sender_id.num - assert pending_airdrops[1].receiver_id.shardNum== receiver_id.shard + assert pending_airdrops[1].receiver_id.shardNum == receiver_id.shard assert pending_airdrops[1].receiver_id.realmNum == receiver_id.realm assert pending_airdrops[1].receiver_id.accountNum == receiver_id.num assert pending_airdrops[1].non_fungible_token.serial_number == 10 @@ -84,6 +89,7 @@ def test_build_transaction_body(mock_account_ids): assert pending_airdrops[1].non_fungible_token.token_ID.tokenNum == token_id_2.num assert pending_airdrops[1].HasField("fungible_token_type") == False + def test_transaction_for_invalid_params(mock_account_ids): """Test building the token cancel airdrop transaction body with invalid params.""" sender_id, receiver_id, _, token_id, _ = mock_account_ids @@ -101,6 +107,7 @@ def test_transaction_for_invalid_params(mock_account_ids): with pytest.raises(ValueError, match="Pending airdrops list must contain mininum 1 and maximum 10 pendingAirdrop."): cancel_airdrop_tx_2.build_transaction_body() + def test_set_pending_airdrops(mock_account_ids): """Test set_pending_airdrops() method""" sender, receiver, _, token_id_1, token_id_2 = mock_account_ids @@ -123,7 +130,7 @@ def test_set_pending_airdrops(mock_account_ids): assert cancel_airdrop_tx.pending_airdrops[0].token_id.shard == token_id_1.shard assert cancel_airdrop_tx.pending_airdrops[0].token_id.realm == token_id_1.realm assert cancel_airdrop_tx.pending_airdrops[0].token_id.num == token_id_1.num - assert cancel_airdrop_tx.pending_airdrops[0].nft_id == None + assert cancel_airdrop_tx.pending_airdrops[0].nft_id is None assert cancel_airdrop_tx.pending_airdrops[1].sender_id.shard == sender.shard assert cancel_airdrop_tx.pending_airdrops[1].sender_id.realm == sender.realm assert cancel_airdrop_tx.pending_airdrops[1].sender_id.num == sender.num @@ -134,7 +141,8 @@ def test_set_pending_airdrops(mock_account_ids): assert cancel_airdrop_tx.pending_airdrops[1].nft_id.token_id.shard == token_id_2.shard assert cancel_airdrop_tx.pending_airdrops[1].nft_id.token_id.realm == token_id_2.realm assert cancel_airdrop_tx.pending_airdrops[1].nft_id.token_id.num == token_id_2.num - assert cancel_airdrop_tx.pending_airdrops[1].token_id == None + assert cancel_airdrop_tx.pending_airdrops[1].token_id is None + def test_add_pending_airdrop(mock_account_ids): """Test add_pending_airdrop() method""" @@ -159,7 +167,7 @@ def test_add_pending_airdrop(mock_account_ids): assert cancel_airdrop_tx.pending_airdrops[0].token_id.shard == token_id_1.shard assert cancel_airdrop_tx.pending_airdrops[0].token_id.realm == token_id_1.realm assert cancel_airdrop_tx.pending_airdrops[0].token_id.num == token_id_1.num - assert cancel_airdrop_tx.pending_airdrops[0].nft_id == None + assert cancel_airdrop_tx.pending_airdrops[0].nft_id is None assert cancel_airdrop_tx.pending_airdrops[1].sender_id.shard == sender.shard assert cancel_airdrop_tx.pending_airdrops[1].sender_id.realm == sender.realm assert cancel_airdrop_tx.pending_airdrops[1].sender_id.num == sender.num @@ -170,7 +178,8 @@ def test_add_pending_airdrop(mock_account_ids): assert cancel_airdrop_tx.pending_airdrops[1].nft_id.token_id.shard == token_id_2.shard assert cancel_airdrop_tx.pending_airdrops[1].nft_id.token_id.realm == token_id_2.realm assert cancel_airdrop_tx.pending_airdrops[1].nft_id.token_id.num == token_id_2.num - assert cancel_airdrop_tx.pending_airdrops[1].token_id == None + assert cancel_airdrop_tx.pending_airdrops[1].token_id is None + def test_clear_pending_airdrops(mock_account_ids): """Test clear_pending_airdrops() method""" @@ -188,7 +197,8 @@ def test_clear_pending_airdrops(mock_account_ids): cancel_airdrop_tx.clear_pending_airdrops() assert len(cancel_airdrop_tx.pending_airdrops) == 0 - + + def test_sign_transaction(mock_account_ids, mock_client): """Test signing the token cancel airdrop transaction with a private key.""" sender, receiver, _, token_id, _ = mock_account_ids @@ -199,21 +209,22 @@ def test_sign_transaction(mock_account_ids, mock_client): cancel_airdrop_tx.transaction_id = generate_transaction_id(sender) private_key = MagicMock() - private_key.sign.return_value = b'signature' - private_key.public_key().to_bytes_raw.return_value = b'public_key' + private_key.sign.return_value = b"signature" + private_key.public_key().to_bytes_raw.return_value = b"public_key" # Freeze the transaction cancel_airdrop_tx.freeze_with(mock_client) # Sign the transaction cancel_airdrop_tx.sign(private_key) - + node_id = mock_client.network.current_node._account_id body_bytes = cancel_airdrop_tx._transaction_body_bytes[node_id] assert body_bytes in cancel_airdrop_tx._signature_map, "Body bytes should be a key in the signature map dictionary" assert len(cancel_airdrop_tx._signature_map[body_bytes].sigPair) == 1 sig_pair = cancel_airdrop_tx._signature_map[body_bytes].sigPair[0] - assert sig_pair.pubKeyPrefix == b'public_key' - assert sig_pair.ed25519 == b'signature' + assert sig_pair.pubKeyPrefix == b"public_key" + assert sig_pair.ed25519 == b"signature" + def test_to_proto(mock_account_ids, mock_client): """Test converting the token cancel airdrop transaction to protobuf format after signing.""" @@ -225,8 +236,8 @@ def test_to_proto(mock_account_ids, mock_client): cancel_airdrop_tx.transaction_id = generate_transaction_id(sender) private_key = MagicMock() - private_key.sign.return_value = b'signature' - private_key.public_key().to_bytes_raw.return_value = b'public_key' + private_key.sign.return_value = b"signature" + private_key.public_key().to_bytes_raw.return_value = b"public_key" cancel_airdrop_tx.freeze_with(mock_client) cancel_airdrop_tx.sign(private_key) @@ -234,25 +245,26 @@ def test_to_proto(mock_account_ids, mock_client): assert proto.signedTransactionBytes assert len(proto.signedTransactionBytes) > 0 - + + def test_build_scheduled_body(mock_account_ids): """Test building a scheduled transaction body for token cancel airdrop transaction.""" sender_id, receiver_id, _, token_id, token_id_2 = mock_account_ids - + token_pending_airdrop = PendingAirdropId(sender_id=sender_id, receiver_id=receiver_id, token_id=token_id) nft_pending_airdrop = PendingAirdropId(sender_id=sender_id, receiver_id=receiver_id, nft_id=NftId(token_id_2, 10)) - + cancel_airdrop_tx = TokenCancelAirdropTransaction() cancel_airdrop_tx.add_pending_airdrop(token_pending_airdrop) cancel_airdrop_tx.add_pending_airdrop(nft_pending_airdrop) - + schedulable_body = cancel_airdrop_tx.build_scheduled_body() - + # Verify the schedulable body has the correct structure and fields assert isinstance(schedulable_body, SchedulableTransactionBody) assert schedulable_body.HasField("tokenCancelAirdrop") assert len(schedulable_body.tokenCancelAirdrop.pending_airdrops) == 2 - + # Verify the pending airdrop fields proto_pending_airdrop = schedulable_body.tokenCancelAirdrop.pending_airdrops[0] assert proto_pending_airdrop.sender_id == sender_id._to_proto() @@ -265,4 +277,4 @@ def test_build_scheduled_body(mock_account_ids): assert proto_pending_airdrop.receiver_id == receiver_id._to_proto() assert proto_pending_airdrop.non_fungible_token.token_ID == token_id_2._to_proto() assert proto_pending_airdrop.non_fungible_token.serial_number == 10 - assert proto_pending_airdrop.HasField("fungible_token_type") == False \ No newline at end of file + assert proto_pending_airdrop.HasField("fungible_token_type") == False diff --git a/tests/unit/token_airdrop_transaction_test.py b/tests/unit/token_airdrop_transaction_test.py index 09c87f271..db2ce86f4 100644 --- a/tests/unit/token_airdrop_transaction_test.py +++ b/tests/unit/token_airdrop_transaction_test.py @@ -1,16 +1,21 @@ -import pytest +from __future__ import annotations from unittest.mock import MagicMock + +import pytest + from hiero_sdk_python.hapi.services.basic_types_pb2 import AccountAmount, NftTransfer, TokenTransferList -from hiero_sdk_python.hapi.services.token_airdrop_pb2 import TokenAirdropTransactionBody -from hiero_sdk_python.tokens.nft_id import NftId -from hiero_sdk_python.tokens.token_airdrop_transaction import TokenAirdropTransaction from hiero_sdk_python.hapi.services.schedulable_transaction_body_pb2 import ( SchedulableTransactionBody, ) +from hiero_sdk_python.hapi.services.token_airdrop_pb2 import TokenAirdropTransactionBody +from hiero_sdk_python.tokens.nft_id import NftId +from hiero_sdk_python.tokens.token_airdrop_transaction import TokenAirdropTransaction + pytestmark = pytest.mark.unit + def test_build_transaction_body(mock_account_ids): """Test building the token airdrop transaction body""" sender, receiver, node_account_id, token_id_1, token_id_2 = mock_account_ids @@ -29,18 +34,18 @@ def test_build_transaction_body(mock_account_ids): transaction_body = airdrop_tx.build_transaction_body() assert len(transaction_body.tokenAirdrop.token_transfers) == 2 - + token_transfer_1 = transaction_body.tokenAirdrop.token_transfers[0] assert token_transfer_1.token.tokenNum == token_id_1.num assert token_transfer_1.expected_decimals.value == 0 assert len(token_transfer_1.transfers) == 2 - assert token_transfer_1.transfers[0].accountID.accountNum== sender.num + assert token_transfer_1.transfers[0].accountID.accountNum == sender.num assert token_transfer_1.transfers[0].amount == -amount assert token_transfer_1.transfers[0].is_approval == False assert token_transfer_1.transfers[1].accountID.accountNum == receiver.num assert token_transfer_1.transfers[1].amount == amount assert token_transfer_1.transfers[1].is_approval == False - + token_transfer_2 = transaction_body.tokenAirdrop.token_transfers[1] assert token_transfer_2.token.tokenNum == token_id_2.num assert len(token_transfer_2.transfers) == 0 @@ -50,6 +55,7 @@ def test_build_transaction_body(mock_account_ids): assert token_transfer_2.nftTransfers[0].receiverAccountID.accountNum == receiver.num assert token_transfer_2.nftTransfers[0].is_approval == False + def test_build_transaction_body_with_approved_transfer(mock_account_ids): """Test building the token airdrop transaction body with approved transfers""" sender, receiver, node_account_id, token_id_1, token_id_2 = mock_account_ids @@ -68,18 +74,18 @@ def test_build_transaction_body_with_approved_transfer(mock_account_ids): transaction_body = airdrop_tx.build_transaction_body() assert len(transaction_body.tokenAirdrop.token_transfers) == 2 - + token_transfer_1 = transaction_body.tokenAirdrop.token_transfers[0] assert token_transfer_1.token.tokenNum == token_id_1.num assert token_transfer_1.expected_decimals.value == 0 assert len(token_transfer_1.transfers) == 2 - assert token_transfer_1.transfers[0].accountID.accountNum== sender.num + assert token_transfer_1.transfers[0].accountID.accountNum == sender.num assert token_transfer_1.transfers[0].amount == -amount assert token_transfer_1.transfers[0].is_approval == True assert token_transfer_1.transfers[1].accountID.accountNum == receiver.num assert token_transfer_1.transfers[1].amount == amount assert token_transfer_1.transfers[1].is_approval == True - + token_transfer_2 = transaction_body.tokenAirdrop.token_transfers[1] assert token_transfer_2.token.tokenNum == token_id_2.num assert len(token_transfer_2.transfers) == 0 @@ -89,6 +95,7 @@ def test_build_transaction_body_with_approved_transfer(mock_account_ids): assert token_transfer_2.nftTransfers[0].receiverAccountID.accountNum == receiver.num assert token_transfer_2.nftTransfers[0].is_approval == True + def test_build_transaction_body_with_expected_decimal(mock_account_ids): """Test building the token airdrop transaction body with expected decimals""" sender, receiver, node_account_id, token_id_1, token_id_2 = mock_account_ids @@ -96,21 +103,29 @@ def test_build_transaction_body_with_expected_decimal(mock_account_ids): decimal = 1 airdrop_tx = TokenAirdropTransaction() - airdrop_tx.add_token_transfer_with_decimals(token_id=token_id_1, account_id=sender, amount=-amount, decimals=decimal) - airdrop_tx.add_token_transfer_with_decimals(token_id=token_id_1, account_id=receiver, amount=amount, decimals=decimal) - airdrop_tx.add_approved_token_transfer_with_decimals(token_id=token_id_2, account_id=sender, amount=-amount, decimals=decimal) - airdrop_tx.add_approved_token_transfer_with_decimals(token_id=token_id_2, account_id=receiver, amount=amount, decimals=decimal) + airdrop_tx.add_token_transfer_with_decimals( + token_id=token_id_1, account_id=sender, amount=-amount, decimals=decimal + ) + airdrop_tx.add_token_transfer_with_decimals( + token_id=token_id_1, account_id=receiver, amount=amount, decimals=decimal + ) + airdrop_tx.add_approved_token_transfer_with_decimals( + token_id=token_id_2, account_id=sender, amount=-amount, decimals=decimal + ) + airdrop_tx.add_approved_token_transfer_with_decimals( + token_id=token_id_2, account_id=receiver, amount=amount, decimals=decimal + ) airdrop_tx.operator_account_id = sender airdrop_tx.node_account_id = node_account_id transaction_body = airdrop_tx.build_transaction_body() assert len(transaction_body.tokenAirdrop.token_transfers) == 2 - + token_transfer_1 = transaction_body.tokenAirdrop.token_transfers[0] assert token_transfer_1.token.tokenNum == token_id_1.num assert token_transfer_1.expected_decimals.value == decimal assert len(token_transfer_1.transfers) == 2 - assert token_transfer_1.transfers[0].accountID.accountNum== sender.num + assert token_transfer_1.transfers[0].accountID.accountNum == sender.num assert token_transfer_1.transfers[0].amount == -amount assert token_transfer_1.transfers[0].is_approval == False assert token_transfer_1.transfers[1].accountID.accountNum == receiver.num @@ -121,13 +136,14 @@ def test_build_transaction_body_with_expected_decimal(mock_account_ids): assert token_transfer_2.token.tokenNum == token_id_2.num assert len(token_transfer_2.transfers) == 2 assert token_transfer_2.expected_decimals.value == decimal - assert token_transfer_2.transfers[0].accountID.accountNum== sender.num + assert token_transfer_2.transfers[0].accountID.accountNum == sender.num assert token_transfer_2.transfers[0].amount == -amount assert token_transfer_2.transfers[0].is_approval == True assert token_transfer_2.transfers[1].accountID.accountNum == receiver.num assert token_transfer_2.transfers[1].amount == amount assert token_transfer_2.transfers[1].is_approval == True + def test_add_zero_transfer_amount(mock_account_ids): account_id, _, _, token_id, _ = mock_account_ids airdrop_tx = TokenAirdropTransaction() @@ -140,10 +156,11 @@ def test_add_zero_transfer_amount(mock_account_ids): with pytest.raises(ValueError): airdrop_tx.add_approved_token_transfer(token_id, account_id, 0) - + with pytest.raises(ValueError): airdrop_tx.add_approved_token_transfer_with_decimals(token_id, account_id, 0, 1) + def test_add_unbalanced_transfer_amount(mock_account_ids): sender, receiver, _, token_id, _ = mock_account_ids airdrop_tx = TokenAirdropTransaction() @@ -153,6 +170,7 @@ def test_add_unbalanced_transfer_amount(mock_account_ids): with pytest.raises(ValueError): airdrop_tx.build_transaction_body() + def test_add_invalid_transfer(mock_account_ids): _, _, _, _, _ = mock_account_ids airdrop_tx = TokenAirdropTransaction() @@ -160,6 +178,7 @@ def test_add_invalid_transfer(mock_account_ids): with pytest.raises(ValueError): airdrop_tx.build_transaction_body() + def test_sign_transaction(mock_account_ids, mock_client): """Test signing the token airdrop transaction with a private key.""" sender, receiver, _, token_id_1, token_id_2 = mock_account_ids @@ -175,15 +194,15 @@ def test_sign_transaction(mock_account_ids, mock_client): airdrop_tx.add_nft_transfer(nft_id=nft_id, sender_id=sender, receiver_id=receiver) private_key = MagicMock() - private_key.sign.return_value = b'signature' - private_key.public_key().to_bytes_raw.return_value = b'public_key' - + private_key.sign.return_value = b"signature" + private_key.public_key().to_bytes_raw.return_value = b"public_key" + # Freeze the transaction airdrop_tx.freeze_with(mock_client) - + # Sign the transaction airdrop_tx.sign(private_key) - + node_id = mock_client.network.current_node._account_id body_bytes = airdrop_tx._transaction_body_bytes[node_id] @@ -191,8 +210,9 @@ def test_sign_transaction(mock_account_ids, mock_client): assert len(airdrop_tx._signature_map[body_bytes].sigPair) == 1 sig_pair = airdrop_tx._signature_map[body_bytes].sigPair[0] - assert sig_pair.pubKeyPrefix == b'public_key' - assert sig_pair.ed25519 == b'signature' + assert sig_pair.pubKeyPrefix == b"public_key" + assert sig_pair.ed25519 == b"signature" + def test_to_proto(mock_account_ids, mock_client): """Test converting the token airdrop transaction to protobuf format after signing.""" @@ -209,8 +229,8 @@ def test_to_proto(mock_account_ids, mock_client): airdrop_tx.add_nft_transfer(nft_id=nft_id, sender_id=sender, receiver_id=receiver) private_key = MagicMock() - private_key.sign.return_value = b'signature' - private_key.public_key().to_bytes_raw.return_value = b'public_key' + private_key.sign.return_value = b"signature" + private_key.public_key().to_bytes_raw.return_value = b"public_key" airdrop_tx.freeze_with(mock_client) @@ -219,29 +239,30 @@ def test_to_proto(mock_account_ids, mock_client): assert proto.signedTransactionBytes assert len(proto.signedTransactionBytes) > 0 - + + def test_build_scheduled_body(mock_account_ids): """Test building a scheduled transaction body for token airdrop transaction.""" sender, receiver, _, token_id_1, token_id_2 = mock_account_ids amount = 1 serial_number = 1 - + nft_id = NftId(token_id_2, serial_number) - + airdrop_tx = TokenAirdropTransaction() - + # Add token and NFT transfers airdrop_tx.add_token_transfer(token_id=token_id_1, account_id=sender, amount=-amount) airdrop_tx.add_token_transfer(token_id=token_id_1, account_id=receiver, amount=amount) airdrop_tx.add_nft_transfer(nft_id=nft_id, sender_id=sender, receiver_id=receiver) - + schedulable_body = airdrop_tx.build_scheduled_body() - + # Verify the schedulable body has the correct structure and fields assert isinstance(schedulable_body, SchedulableTransactionBody) assert schedulable_body.HasField("tokenAirdrop") assert len(schedulable_body.tokenAirdrop.token_transfers) == 2 - + token_transfer_1 = schedulable_body.tokenAirdrop.token_transfers[0] assert token_transfer_1.token.tokenNum == token_id_1.num assert token_transfer_1.expected_decimals.value == 0 @@ -252,7 +273,7 @@ def test_build_scheduled_body(mock_account_ids): assert token_transfer_1.transfers[1].accountID == receiver._to_proto() assert token_transfer_1.transfers[1].amount == amount assert token_transfer_1.transfers[1].is_approval == False - + token_transfer_2 = schedulable_body.tokenAirdrop.token_transfers[1] assert token_transfer_2.token.tokenNum == token_id_2.num assert len(token_transfer_2.transfers) == 0 @@ -262,6 +283,7 @@ def test_build_scheduled_body(mock_account_ids): assert token_transfer_2.nftTransfers[0].receiverAccountID == receiver._to_proto() assert token_transfer_2.nftTransfers[0].is_approval == False + def test_from_proto(mock_account_ids): """Test _from_proto() correctly reconstructs a TokenAirdropTransaction.""" sender_id, receiver_id, _, token_id1, token_id2 = mock_account_ids @@ -271,11 +293,11 @@ def test_from_proto(mock_account_ids): proto.token_transfers.append( TokenTransferList( token=token_id1._to_proto(), - expected_decimals={'value': 1}, + expected_decimals={"value": 1}, transfers=[ AccountAmount(accountID=sender_id._to_proto(), amount=-1, is_approval=True), - AccountAmount(accountID=receiver_id._to_proto(), amount=1, is_approval=True) - ] + AccountAmount(accountID=receiver_id._to_proto(), amount=1, is_approval=True), + ], ) ) proto.token_transfers.append( @@ -286,9 +308,9 @@ def test_from_proto(mock_account_ids): senderAccountID=sender_id._to_proto(), receiverAccountID=receiver_id._to_proto(), serialNumber=1, - is_approval=True + is_approval=True, ) - ] + ], ) ) @@ -314,6 +336,7 @@ def test_from_proto(mock_account_ids): assert nft_transfer[0].serial_number == 1 assert nft_transfer[0].is_approved == True + def test_from_proto_without_nft_transfers(mock_account_ids): """Test _from_proto should handle absence of NFT transfers.""" sender_id, receiver_id, _, token_id, _ = mock_account_ids @@ -323,17 +346,17 @@ def test_from_proto_without_nft_transfers(mock_account_ids): proto.token_transfers.append( TokenTransferList( token=token_id._to_proto(), - expected_decimals={'value': 1}, + expected_decimals={"value": 1}, transfers=[ AccountAmount(accountID=sender_id._to_proto(), amount=-1, is_approval=True), - AccountAmount(accountID=receiver_id._to_proto(), amount=1, is_approval=True) - ] + AccountAmount(accountID=receiver_id._to_proto(), amount=1, is_approval=True), + ], ) ) airdrop_tx = TokenAirdropTransaction._from_proto(proto) - token_transfer = airdrop_tx.token_transfers[token_id]; + token_transfer = airdrop_tx.token_transfers[token_id] assert token_transfer[0].token_id == token_id assert token_transfer[0].account_id == sender_id assert token_transfer[0].amount == -1 @@ -348,6 +371,7 @@ def test_from_proto_without_nft_transfers(mock_account_ids): assert not airdrop_tx.nft_transfers + def test_from_proto_without_token_transfer(mock_account_ids): """_from_proto should handle absence of token transfers.""" sender_id, receiver_id, _, token_id, _ = mock_account_ids @@ -362,9 +386,9 @@ def test_from_proto_without_token_transfer(mock_account_ids): senderAccountID=sender_id._to_proto(), receiverAccountID=receiver_id._to_proto(), serialNumber=1, - is_approval=True + is_approval=True, ) - ] + ], ) ) diff --git a/tests/unit/token_allowance_test.py b/tests/unit/token_allowance_test.py index 35504aeae..fadf8875c 100644 --- a/tests/unit/token_allowance_test.py +++ b/tests/unit/token_allowance_test.py @@ -2,6 +2,8 @@ Unit tests for the TokenAllowance class. """ +from __future__ import annotations + import pytest from hiero_sdk_python.account.account_id import AccountId @@ -11,6 +13,7 @@ from hiero_sdk_python.tokens.token_allowance import TokenAllowance from hiero_sdk_python.tokens.token_id import TokenId + pytestmark = pytest.mark.unit @@ -32,13 +35,12 @@ def proto_token_allowance(): owner_account_id = AccountId(0, 0, 200) spender_account_id = AccountId(0, 0, 300) - proto = TokenAllowanceProto( + return TokenAllowanceProto( tokenId=token_id._to_proto(), owner=owner_account_id._to_proto(), spender=spender_account_id._to_proto(), amount=1000, ) - return proto def test_token_allowance_initialization(token_allowance): diff --git a/tests/unit/token_associate_transaction_test.py b/tests/unit/token_associate_transaction_test.py index 7da74d7d6..9c6bd5f3e 100644 --- a/tests/unit/token_associate_transaction_test.py +++ b/tests/unit/token_associate_transaction_test.py @@ -1,29 +1,33 @@ -import pytest +from __future__ import annotations + from unittest.mock import MagicMock -from hiero_sdk_python.tokens.token_associate_transaction import TokenAssociateTransaction + +import pytest + from hiero_sdk_python.hapi.services import timestamp_pb2, token_associate_pb2 from hiero_sdk_python.hapi.services.schedulable_transaction_body_pb2 import ( SchedulableTransactionBody, ) -from hiero_sdk_python.transaction.transaction_id import TransactionId +from hiero_sdk_python.tokens.token_associate_transaction import TokenAssociateTransaction from hiero_sdk_python.tokens.token_id import TokenId +from hiero_sdk_python.transaction.transaction_id import TransactionId + pytestmark = pytest.mark.unit + def generate_transaction_id(account_id_proto): """Generate a unique transaction ID based on the account ID and the current timestamp.""" import time + current_time = time.time() timestamp_seconds = int(current_time) timestamp_nanos = int((current_time - timestamp_seconds) * 1e9) tx_timestamp = timestamp_pb2.Timestamp(seconds=timestamp_seconds, nanos=timestamp_nanos) - tx_id = TransactionId( - valid_start=tx_timestamp, - account_id=account_id_proto - ) - return tx_id + return TransactionId(valid_start=tx_timestamp, account_id=account_id_proto) + # This test uses fixture mock_account_ids as parameter def test_build_transaction_body(mock_account_ids): @@ -54,26 +58,27 @@ def test_missing_fields(): with pytest.raises(ValueError, match="Account ID and token IDs must be set."): associate_tx.build_transaction_body() + # This test uses fixture (mock_account_ids, mock_client) as parameter def test_sign_transaction(mock_account_ids, mock_client): """Test signing the token associate transaction with a private key.""" account_id, _, _, token_id_1, _ = mock_account_ids - + associate_tx = TokenAssociateTransaction() associate_tx.set_account_id(account_id) associate_tx.add_token_id(token_id_1) associate_tx.transaction_id = generate_transaction_id(account_id) private_key = MagicMock() - private_key.sign.return_value = b'signature' - private_key.public_key().to_bytes_raw.return_value = b'public_key' - + private_key.sign.return_value = b"signature" + private_key.public_key().to_bytes_raw.return_value = b"public_key" + # Freeze the transaction associate_tx.freeze_with(mock_client) - + # Sign the transaction associate_tx.sign(private_key) - + node_id = mock_client.network.current_node._account_id body_bytes = associate_tx._transaction_body_bytes[node_id] @@ -81,22 +86,23 @@ def test_sign_transaction(mock_account_ids, mock_client): assert len(associate_tx._signature_map[body_bytes].sigPair) == 1 sig_pair = associate_tx._signature_map[body_bytes].sigPair[0] - assert sig_pair.pubKeyPrefix == b'public_key' - assert sig_pair.ed25519 == b'signature' + assert sig_pair.pubKeyPrefix == b"public_key" + assert sig_pair.ed25519 == b"signature" + # This test uses fixture (mock_account_ids, mock_client) as parameter def test_to_proto(mock_account_ids, mock_client): """Test converting the token associate transaction to protobuf format after signing.""" account_id, _, _, token_id_1, _ = mock_account_ids - + associate_tx = TokenAssociateTransaction() associate_tx.set_account_id(account_id) associate_tx.add_token_id(token_id_1) associate_tx.transaction_id = generate_transaction_id(account_id) private_key = MagicMock() - private_key.sign.return_value = b'signature' - private_key.public_key().to_bytes_raw.return_value = b'public_key' + private_key.sign.return_value = b"signature" + private_key.public_key().to_bytes_raw.return_value = b"public_key" associate_tx.freeze_with(mock_client) @@ -105,18 +111,19 @@ def test_to_proto(mock_account_ids, mock_client): assert proto.signedTransactionBytes assert len(proto.signedTransactionBytes) > 0 - + + def test_build_scheduled_body(mock_account_ids): """Test building a scheduled transaction body for token associate transaction.""" account_id, _, _, token_id_1, token_id_2 = mock_account_ids - + associate_tx = TokenAssociateTransaction() associate_tx.set_account_id(account_id) associate_tx.add_token_id(token_id_1) associate_tx.add_token_id(token_id_2) - + schedulable_body = associate_tx.build_scheduled_body() - + # Verify the schedulable body has the correct structure and fields assert isinstance(schedulable_body, SchedulableTransactionBody) assert schedulable_body.HasField("tokenAssociate") @@ -125,6 +132,7 @@ def test_build_scheduled_body(mock_account_ids): assert schedulable_body.tokenAssociate.tokens[0] == token_id_1._to_proto() assert schedulable_body.tokenAssociate.tokens[1] == token_id_2._to_proto() + # This test uses fixture mock_account_ids as parameter def test_set_token_ids_accepts_tokenid_and_string(mock_account_ids): """ @@ -149,6 +157,7 @@ def test_set_token_ids_accepts_tokenid_and_string(mock_account_ids): assert associate_tx.token_ids[1].realm == 0 assert associate_tx.token_ids[1].num == 1234 + def test_set_token_ids_invalid_type_raises(): """ set_token_ids should raise ValueError if any element is neither TokenId nor str. @@ -158,6 +167,7 @@ def test_set_token_ids_invalid_type_raises(): with pytest.raises(TypeError, match="Invalid token_id type:"): associate_tx.set_token_ids([123]) # int is not allowed + def test_set_token_ids_non_iterable_raises_typeerror(): """ set_token_ids should raise a TypeError if called with a non-iterable, @@ -169,6 +179,7 @@ def test_set_token_ids_non_iterable_raises_typeerror(): with pytest.raises(TypeError): associate_tx.set_token_ids(123) + def test_validate_checksums_calls_validate_on_ids(): associate_tx = TokenAssociateTransaction() @@ -186,6 +197,7 @@ def test_validate_checksums_calls_validate_on_ids(): token_id_1.validate_checksum.assert_called_once_with(client) token_id_2.validate_checksum.assert_called_once_with(client) + # This test uses fixture mock_account_ids as parameter def test_from_proto_builds_transaction(mock_account_ids): """ @@ -205,4 +217,3 @@ def test_from_proto_builds_transaction(mock_account_ids): assert len(tx.token_ids) == 2 assert tx.token_ids[0] == token_id_1 assert tx.token_ids[1] == token_id_2 - diff --git a/tests/unit/token_association_test.py b/tests/unit/token_association_test.py index e80b79c8f..02bcdbd74 100644 --- a/tests/unit/token_association_test.py +++ b/tests/unit/token_association_test.py @@ -1,4 +1,7 @@ """Unit tests for the TokenAssociation class.""" + +from __future__ import annotations + import pytest from hiero_sdk_python.account.account_id import AccountId @@ -6,6 +9,7 @@ from hiero_sdk_python.tokens.token_association import TokenAssociation from hiero_sdk_python.tokens.token_id import TokenId + pytestmark = pytest.mark.unit @@ -31,10 +35,7 @@ def test_default_initialization(): def test_initialization_both_fields(sample_token_id: TokenId, sample_account_id: AccountId): """Test initialization with both token_id and account_id provided.""" - assoc = TokenAssociation( - token_id=sample_token_id, - account_id=sample_account_id - ) + assoc = TokenAssociation(token_id=sample_token_id, account_id=sample_account_id) assert assoc.token_id == sample_token_id assert assoc.account_id == sample_account_id @@ -109,10 +110,7 @@ def test_from_proto_empty(): def test_to_proto_both_fields(sample_token_id: TokenId, sample_account_id: AccountId): """Test _to_proto serializes both fields correctly.""" - assoc = TokenAssociation( - token_id=sample_token_id, - account_id=sample_account_id - ) + assoc = TokenAssociation(token_id=sample_token_id, account_id=sample_account_id) proto = assoc._to_proto() @@ -156,10 +154,7 @@ def test_to_proto_empty(): def test_round_trip_both_fields(sample_token_id: TokenId, sample_account_id: AccountId): """Test full round-trip conversion preserves both fields.""" - original = TokenAssociation( - token_id=sample_token_id, - account_id=sample_account_id - ) + original = TokenAssociation(token_id=sample_token_id, account_id=sample_account_id) proto = original._to_proto() reconstructed = TokenAssociation._from_proto(proto) @@ -187,6 +182,7 @@ def test_round_trip_account_only(sample_account_id: AccountId): assert reconstructed.token_id is None assert reconstructed.account_id == original.account_id + def test_round_trip_empty(): """Test round-trip with empty/default instance.""" original = TokenAssociation() @@ -196,10 +192,11 @@ def test_round_trip_empty(): assert reconstructed.token_id is None assert reconstructed.account_id is None + def test_bytes_round_trip_both_fields( sample_token_id: TokenId, sample_account_id: AccountId, - ): +): """Test round-trip via public to_bytes() / from_bytes() preserves both fields.""" original = TokenAssociation( token_id=sample_token_id, @@ -210,6 +207,7 @@ def test_bytes_round_trip_both_fields( assert reconstructed == original + def test_bytes_round_trip_empty(): """Test round-trip via to_bytes()/from_bytes() with empty/default instance.""" original = TokenAssociation() @@ -218,12 +216,10 @@ def test_bytes_round_trip_empty(): assert reconstructed.token_id is None assert reconstructed.account_id is None + def test_repr_representation(sample_token_id: TokenId, sample_account_id: AccountId): """Test __repr__ output for TokenAssociation.""" - assoc = TokenAssociation( - token_id=sample_token_id, - account_id=sample_account_id - ) + assoc = TokenAssociation(token_id=sample_token_id, account_id=sample_account_id) repr_str = repr(assoc) assert "TokenAssociation" in repr_str diff --git a/tests/unit/token_burn_transaction_test.py b/tests/unit/token_burn_transaction_test.py index 03951d110..97b58be9e 100644 --- a/tests/unit/token_burn_transaction_test.py +++ b/tests/unit/token_burn_transaction_test.py @@ -1,40 +1,39 @@ +from __future__ import annotations + import pytest -from hiero_sdk_python.tokens.token_id import TokenId -from hiero_sdk_python.hapi.services.token_burn_pb2 import TokenBurnTransactionBody -from hiero_sdk_python.hapi.services.transaction_receipt_pb2 import TransactionReceipt as TransactionReceiptProto -from hiero_sdk_python.hapi.services.transaction_response_pb2 import TransactionResponse as TransactionResponseProto +from hiero_sdk_python.hapi.services import response_header_pb2, response_pb2, transaction_get_receipt_pb2 from hiero_sdk_python.hapi.services.schedulable_transaction_body_pb2 import ( SchedulableTransactionBody, ) +from hiero_sdk_python.hapi.services.token_burn_pb2 import TokenBurnTransactionBody +from hiero_sdk_python.hapi.services.transaction_receipt_pb2 import TransactionReceipt as TransactionReceiptProto +from hiero_sdk_python.hapi.services.transaction_response_pb2 import TransactionResponse as TransactionResponseProto from hiero_sdk_python.response_code import ResponseCode from hiero_sdk_python.tokens.token_burn_transaction import TokenBurnTransaction -from hiero_sdk_python.hapi.services import response_header_pb2, response_pb2, transaction_get_receipt_pb2 +from hiero_sdk_python.tokens.token_id import TokenId from tests.unit.mock_server import mock_hedera_servers + pytestmark = pytest.mark.unit + def test_constructor_with_parameters(mock_account_ids): """Test creating a token burn transaction with constructor parameters.""" _, _, _, token_id, _ = mock_account_ids - burn_tx = TokenBurnTransaction( - token_id=token_id, - amount=100 - ) + burn_tx = TokenBurnTransaction(token_id=token_id, amount=100) assert burn_tx.token_id == token_id assert burn_tx.amount == 100 assert burn_tx.serials == [] - - burn_tx = TokenBurnTransaction( - token_id=token_id, - serials=[1, 2, 3] - ) - + + burn_tx = TokenBurnTransaction(token_id=token_id, serials=[1, 2, 3]) + assert burn_tx.token_id == token_id assert burn_tx.serials == [1, 2, 3] + def test_build_transaction_body(mock_account_ids): """Test building a token burn transaction body with valid values.""" operator_id, _, node_account_id, token_id, _ = mock_account_ids @@ -49,16 +48,18 @@ def test_build_transaction_body(mock_account_ids): assert transaction_body.tokenBurn.token == token_id._to_proto() assert transaction_body.tokenBurn.amount == 100 + def test_build_transaction_body_validation_errors(): """Test that build_transaction_body raises appropriate validation errors.""" burn_tx = TokenBurnTransaction() with pytest.raises(ValueError, match="Missing token ID"): burn_tx.build_transaction_body() - + with pytest.raises(ValueError, match="Cannot burn both amount and serial in the same transaction"): burn_tx.set_token_id(TokenId(0, 0, 0)).set_amount(100).set_serials([1, 2, 3]).build_transaction_body() + def test_set_methods(mock_account_ids): """Test the set methods of TokenBurnTransaction.""" _, _, _, token_id, _ = mock_account_ids @@ -66,9 +67,9 @@ def test_set_methods(mock_account_ids): burn_tx = TokenBurnTransaction() test_cases = [ - ('set_token_id', token_id, 'token_id'), - ('set_amount', 100, 'amount'), - ('set_serials', [1, 2, 3], 'serials') + ("set_token_id", token_id, "token_id"), + ("set_amount", 100, "amount"), + ("set_serials", [1, 2, 3], "serials"), ] for method_name, value, attr_name in test_cases: @@ -76,6 +77,7 @@ def test_set_methods(mock_account_ids): assert tx_after_set is burn_tx assert getattr(burn_tx, attr_name) == value + def test_set_methods_require_not_frozen(mock_account_ids, mock_client): """Test that set methods raise exception when transaction is frozen.""" _, _, _, token_id, _ = mock_account_ids @@ -83,16 +85,13 @@ def test_set_methods_require_not_frozen(mock_account_ids, mock_client): burn_tx = TokenBurnTransaction(token_id=token_id, amount=100) burn_tx.freeze_with(mock_client) - test_cases = [ - ('set_token_id', token_id), - ('set_amount', 200), - ('set_serials', [1, 2, 3]) - ] + test_cases = [("set_token_id", token_id), ("set_amount", 200), ("set_serials", [1, 2, 3])] for method_name, value in test_cases: with pytest.raises(Exception, match="Transaction is immutable; it has been frozen"): getattr(burn_tx, method_name)(value) + def test_burn_transaction_can_execute(mock_account_ids): """Test that a token burn transaction can be executed successfully.""" _, _, _, token_id, _ = mock_account_ids @@ -102,17 +101,13 @@ def test_burn_transaction_can_execute(mock_account_ids): ok_response.nodeTransactionPrecheckCode = ResponseCode.OK # Create a mock receipt for a successful token burn - mock_receipt_proto = TransactionReceiptProto( - status=ResponseCode.SUCCESS - ) + mock_receipt_proto = TransactionReceiptProto(status=ResponseCode.SUCCESS) # Create a response for the receipt query receipt_query_response = response_pb2.Response( transactionGetReceipt=transaction_get_receipt_pb2.TransactionGetReceiptResponse( - header=response_header_pb2.ResponseHeader( - nodeTransactionPrecheckCode=ResponseCode.OK - ), - receipt=mock_receipt_proto + header=response_header_pb2.ResponseHeader(nodeTransactionPrecheckCode=ResponseCode.OK), + receipt=mock_receipt_proto, ) ) @@ -121,74 +116,69 @@ def test_burn_transaction_can_execute(mock_account_ids): ] with mock_hedera_servers(response_sequences) as client: - transaction = ( - TokenBurnTransaction() - .set_token_id(token_id) - .set_amount(100) - ) + transaction = TokenBurnTransaction().set_token_id(token_id).set_amount(100) receipt = transaction.execute(client) assert receipt.status == ResponseCode.SUCCESS, "Transaction should have succeeded" - + + def test_burn_transaction_from_proto(mock_account_ids): """Test that a burn transaction can be created from a protobuf object.""" _, _, _, token_id, _ = mock_account_ids # Create protobuf object with token burn details - proto = TokenBurnTransactionBody( - token=token_id._to_proto(), - amount=100, - serialNumbers=[1, 2, 3] - ) - + proto = TokenBurnTransactionBody(token=token_id._to_proto(), amount=100, serialNumbers=[1, 2, 3]) + # Deserialize the protobuf object from_proto = TokenBurnTransaction()._from_proto(proto) - + # Verify deserialized transaction matches original data assert from_proto.token_id == token_id assert from_proto.amount == 100 assert from_proto.serials == [1, 2, 3] - + # Deserialize empty protobuf from_proto = TokenBurnTransaction()._from_proto(TokenBurnTransactionBody()) - + # Verify empty protobuf deserializes to empty/default values - assert from_proto.token_id == TokenId(0,0,0) + assert from_proto.token_id == TokenId(0, 0, 0) assert from_proto.amount == 0 assert from_proto.serials == [] - + + def test_build_scheduled_body_fungible(mock_account_ids): """Test building a scheduled transaction body for fungible token burn transaction.""" _, _, _, token_id, _ = mock_account_ids - + burn_tx = TokenBurnTransaction() burn_tx.set_token_id(token_id) burn_tx.set_amount(100) - + schedulable_body = burn_tx.build_scheduled_body() - + # Verify the schedulable body has the correct structure and fields assert isinstance(schedulable_body, SchedulableTransactionBody) assert schedulable_body.HasField("tokenBurn") assert schedulable_body.tokenBurn.token == token_id._to_proto() assert schedulable_body.tokenBurn.amount == 100 assert len(schedulable_body.tokenBurn.serialNumbers) == 0 - + + def test_build_scheduled_body_nft(mock_account_ids): """Test building a scheduled transaction body for NFT burn transaction.""" _, _, _, token_id, _ = mock_account_ids serials = [1, 2, 3] - + burn_tx = TokenBurnTransaction() burn_tx.set_token_id(token_id) burn_tx.set_serials(serials) - + schedulable_body = burn_tx.build_scheduled_body() - + # Verify the schedulable body has the correct structure and fields assert isinstance(schedulable_body, SchedulableTransactionBody) assert schedulable_body.HasField("tokenBurn") assert schedulable_body.tokenBurn.token == token_id._to_proto() assert schedulable_body.tokenBurn.amount == 0 - assert schedulable_body.tokenBurn.serialNumbers == serials \ No newline at end of file + assert schedulable_body.tokenBurn.serialNumbers == serials diff --git a/tests/unit/token_create_transaction_test.py b/tests/unit/token_create_transaction_test.py index 055ea600f..fba6d30a1 100644 --- a/tests/unit/token_create_transaction_test.py +++ b/tests/unit/token_create_transaction_test.py @@ -12,59 +12,61 @@ - Transaction execution error handling """ +from __future__ import annotations + import datetime -import pytest from unittest.mock import MagicMock, patch +import pytest + +from hiero_sdk_python.account.account_id import AccountId +from hiero_sdk_python.crypto.private_key import PrivateKey from hiero_sdk_python.Duration import Duration -from hiero_sdk_python.timestamp import Timestamp -from hiero_sdk_python.tokens.token_create_transaction import ( - TokenCreateTransaction, - TokenParams, - TokenKeys, -) -from hiero_sdk_python.tokens.token_type import TokenType -from hiero_sdk_python.tokens.supply_type import SupplyType -from hiero_sdk_python.response_code import ResponseCode +from hiero_sdk_python.exceptions import PrecheckError from hiero_sdk_python.hapi.services import ( - transaction_pb2, - transaction_pb2, - transaction_contents_pb2, + basic_types_pb2, timestamp_pb2, + transaction_contents_pb2, + transaction_pb2, ) -from hiero_sdk_python.transaction.transaction_id import TransactionId -from hiero_sdk_python.account.account_id import AccountId -from hiero_sdk_python.exceptions import PrecheckError -from hiero_sdk_python.crypto.private_key import PrivateKey -from hiero_sdk_python.hapi.services import basic_types_pb2 -from hiero_sdk_python.crypto.public_key import PublicKey from hiero_sdk_python.hapi.services.schedulable_transaction_body_pb2 import ( SchedulableTransactionBody, ) -from hiero_sdk_python.tokens.token_update_transaction import TokenUpdateTransaction -from hiero_sdk_python.tokens.token_delete_transaction import TokenDeleteTransaction from hiero_sdk_python.query.token_info_query import TokenInfoQuery +from hiero_sdk_python.response_code import ResponseCode +from hiero_sdk_python.timestamp import Timestamp +from hiero_sdk_python.tokens.supply_type import SupplyType +from hiero_sdk_python.tokens.token_create_transaction import ( + TokenCreateTransaction, + TokenKeys, + TokenParams, +) +from hiero_sdk_python.tokens.token_delete_transaction import TokenDeleteTransaction from hiero_sdk_python.tokens.token_id import TokenId +from hiero_sdk_python.tokens.token_type import TokenType +from hiero_sdk_python.tokens.token_update_transaction import TokenUpdateTransaction +from hiero_sdk_python.transaction.transaction_id import TransactionId + pytestmark = pytest.mark.unit + def generate_transaction_id(account_id_proto): """Generate a unique transaction ID based on the account ID and the current timestamp.""" import time + current_time = time.time() timestamp_seconds = int(current_time) timestamp_nanos = int((current_time - timestamp_seconds) * 1e9) tx_timestamp = timestamp_pb2.Timestamp(seconds=timestamp_seconds, nanos=timestamp_nanos) - tx_id = TransactionId( - valid_start=tx_timestamp, - account_id=account_id_proto - ) - return tx_id + return TransactionId(valid_start=tx_timestamp, account_id=account_id_proto) + ########### Basic Tests for Building Transactions ########### + # This test uses fixture mock_account_ids as parameter def test_build_transaction_body_without_key(mock_account_ids): """Test building a token creation transaction body without an admin, supply or freeze key.""" @@ -87,8 +89,10 @@ def test_build_transaction_body_without_key(mock_account_ids): assert transaction_body.tokenCreation.decimals == 2 assert transaction_body.tokenCreation.initialSupply == 1000 assert transaction_body.tokenCreation.memo == "Token Memo" - assert transaction_body.tokenCreation.autoRenewPeriod == Duration(7890000)._to_proto() # Default value of 90days. - assert not transaction_body.tokenCreation.HasField('expiry') # By default, this is set to "now + AUTO_RENEW_PERIOD" (90 days). + assert transaction_body.tokenCreation.autoRenewPeriod == Duration(7890000)._to_proto() # Default value of 90days. + assert not transaction_body.tokenCreation.HasField( + "expiry" + ) # By default, this is set to "now + AUTO_RENEW_PERIOD" (90 days). # Ensure keys are not set assert not transaction_body.tokenCreation.HasField("adminKey") assert not transaction_body.tokenCreation.HasField("supplyKey") @@ -98,6 +102,7 @@ def test_build_transaction_body_without_key(mock_account_ids): assert not transaction_body.tokenCreation.HasField("pause_key") assert not transaction_body.tokenCreation.HasField("fee_schedule_key") + # This test uses fixture mock_account_ids as parameter def test_build_transaction_body(mock_account_ids): """Test building a token creation transaction body with valid values and admin, supply and freeze keys.""" @@ -145,6 +150,7 @@ def test_build_transaction_body(mock_account_ids): assert transaction_body.tokenCreation.kycKey == private_key_kyc.public_key()._to_proto() assert transaction_body.tokenCreation.fee_schedule_key == private_key_fee_schedule.public_key()._to_proto() + # This test uses fixture mock_account_ids as parameter def test_build_transaction_body_with_metadata(mock_account_ids): """Test building a token creation transaction body with metadata bytes set.""" @@ -169,6 +175,7 @@ def test_build_transaction_body_with_metadata(mock_account_ids): assert transaction_body.tokenCreation.symbol == "MTKM" assert transaction_body.tokenCreation.metadata == metadata + def test_set_metadata_raises_when_over_100_bytes(): """set_metadata must reject metadata longer than 100 bytes.""" token_tx = TokenCreateTransaction() @@ -177,54 +184,61 @@ def test_set_metadata_raises_when_over_100_bytes(): with pytest.raises(ValueError, match="Metadata must not exceed 100 bytes"): token_tx.set_metadata(too_long_metadata) + @pytest.mark.parametrize( "token_name, token_symbol, decimals, initial_supply, token_type, expected_error", [ # ------------------ Fungible Invalid Cases ------------------ # ("", "SYMB", 2, 100, TokenType.FUNGIBLE_COMMON, "Token name is required"), - ("1"*101, "SYMB", 2, 100, TokenType.FUNGIBLE_COMMON, - "Token name must be between 1 and 100 bytes"), - ("\x00", "SYMB", 2, 100, TokenType.FUNGIBLE_COMMON, - "Token name must not contain the Unicode NUL"), - + ("1" * 101, "SYMB", 2, 100, TokenType.FUNGIBLE_COMMON, "Token name must be between 1 and 100 bytes"), + ("\x00", "SYMB", 2, 100, TokenType.FUNGIBLE_COMMON, "Token name must not contain the Unicode NUL"), ("MyToken", "", 2, 100, TokenType.FUNGIBLE_COMMON, "Token symbol is required"), - ("MyToken", "1"*101, 2, 100, TokenType.FUNGIBLE_COMMON, - "Token symbol must be between 1 and 100 bytes"), - ("MyToken", "\x00", 2, 100, TokenType.FUNGIBLE_COMMON, - "Token symbol must not contain the Unicode NUL"), - - ("MyToken", "SYMB", -2, 100, TokenType.FUNGIBLE_COMMON, - "Decimals must be a non-negative integer"), - ("MyToken", "SYMB", 2, -100, TokenType.FUNGIBLE_COMMON, - "Initial supply must be a non-negative integer"), - ("MyToken", "SYMB", 2, 0, TokenType.FUNGIBLE_COMMON, - "A Fungible Token requires an initial supply greater than zero"), - ("MyToken", "SYMB", 2, 2**64, TokenType.FUNGIBLE_COMMON, - "Initial supply cannot exceed"), - + ("MyToken", "1" * 101, 2, 100, TokenType.FUNGIBLE_COMMON, "Token symbol must be between 1 and 100 bytes"), + ("MyToken", "\x00", 2, 100, TokenType.FUNGIBLE_COMMON, "Token symbol must not contain the Unicode NUL"), + ("MyToken", "SYMB", -2, 100, TokenType.FUNGIBLE_COMMON, "Decimals must be a non-negative integer"), + ("MyToken", "SYMB", 2, -100, TokenType.FUNGIBLE_COMMON, "Initial supply must be a non-negative integer"), + ( + "MyToken", + "SYMB", + 2, + 0, + TokenType.FUNGIBLE_COMMON, + "A Fungible Token requires an initial supply greater than zero", + ), + ("MyToken", "SYMB", 2, 2**64, TokenType.FUNGIBLE_COMMON, "Initial supply cannot exceed"), # Valid fungible ("MyToken", "SYMB", 2, 100, TokenType.FUNGIBLE_COMMON, None), - # ------------------ Non-Fungible Invalid Cases ------------------ # - ("", "SYMB", 0, 0, TokenType.NON_FUNGIBLE_UNIQUE, "Token name is required"), - ("1"*101, "SYMB", 0, 0, TokenType.NON_FUNGIBLE_UNIQUE, - "Token name must be between 1 and 100 bytes"), - ("\x00", "SYMB", 0, 0, TokenType.NON_FUNGIBLE_UNIQUE, - "Token name must not contain the Unicode NUL character"), - + ("", "SYMB", 0, 0, TokenType.NON_FUNGIBLE_UNIQUE, "Token name is required"), + ("1" * 101, "SYMB", 0, 0, TokenType.NON_FUNGIBLE_UNIQUE, "Token name must be between 1 and 100 bytes"), + ("\x00", "SYMB", 0, 0, TokenType.NON_FUNGIBLE_UNIQUE, "Token name must not contain the Unicode NUL character"), ("MyNFTToken", "", 0, 0, TokenType.NON_FUNGIBLE_UNIQUE, "Token symbol is required"), - ("MyNFTToken", "1"*101, 0, 0, TokenType.NON_FUNGIBLE_UNIQUE, - "Token symbol must be between 1 and 100 bytes"), - ("MyNFTToken", "\x00", 0, 0, TokenType.NON_FUNGIBLE_UNIQUE, - "Token symbol must not contain the Unicode NUL character"), - - ("MyNFTToken", "SYMB", -2, 0, TokenType.NON_FUNGIBLE_UNIQUE, - "Decimals must be a non-negative integer"), - ("MyNFTToken", "SYMB", 2, 0, TokenType.NON_FUNGIBLE_UNIQUE, - "A Non-fungible Unique Token must have zero decimals"), - ("MyNFTToken", "SYMB", 0, 100, TokenType.NON_FUNGIBLE_UNIQUE, - "A Non-fungible Unique Token requires an initial supply of zero"), - + ("MyNFTToken", "1" * 101, 0, 0, TokenType.NON_FUNGIBLE_UNIQUE, "Token symbol must be between 1 and 100 bytes"), + ( + "MyNFTToken", + "\x00", + 0, + 0, + TokenType.NON_FUNGIBLE_UNIQUE, + "Token symbol must not contain the Unicode NUL character", + ), + ("MyNFTToken", "SYMB", -2, 0, TokenType.NON_FUNGIBLE_UNIQUE, "Decimals must be a non-negative integer"), + ( + "MyNFTToken", + "SYMB", + 2, + 0, + TokenType.NON_FUNGIBLE_UNIQUE, + "A Non-fungible Unique Token must have zero decimals", + ), + ( + "MyNFTToken", + "SYMB", + 0, + 100, + TokenType.NON_FUNGIBLE_UNIQUE, + "A Non-fungible Unique Token requires an initial supply of zero", + ), # Valid non-fungible ("MyNFTToken", "SYMB", 0, 0, TokenType.NON_FUNGIBLE_UNIQUE, None), ], @@ -260,7 +274,7 @@ def test_token_creation_validation( ) # Building triggers validation tx = TokenCreateTransaction(params) - tx.build_transaction_body() + tx.build_transaction_body() else: # Valid scenario; no error expected params = TokenParams( @@ -286,6 +300,7 @@ def test_token_creation_validation( ########### Tests for Signing and Protobuf Conversion ########### + # This test uses fixture (mock_account_ids, mock_client) as parameter def test_sign_transaction(mock_account_ids, mock_client): """Test signing the token creation transaction that has multiple keys.""" @@ -327,7 +342,9 @@ def test_sign_transaction(mock_account_ids, mock_client): private_key_fee_schedule = MagicMock(spec=PrivateKey) private_key_fee_schedule.sign.return_value = b"fee_schedule_signature" - private_key_fee_schedule.public_key()._to_proto.return_value = basic_types_pb2.Key(ed25519=b"fee_schedule_public_key") + private_key_fee_schedule.public_key()._to_proto.return_value = basic_types_pb2.Key( + ed25519=b"fee_schedule_public_key" + ) token_tx = TokenCreateTransaction() token_tx.set_token_name("MyToken") @@ -343,15 +360,15 @@ def test_sign_transaction(mock_account_ids, mock_client): token_tx.set_pause_key(private_key_pause) token_tx.set_kyc_key(private_key_kyc) token_tx.set_fee_schedule_key(private_key_fee_schedule) - + token_tx.transaction_id = generate_transaction_id(treasury_account) - + token_tx.freeze_with(mock_client) # Sign with both sign keys - token_tx.sign(private_key) # Necessary - token_tx.sign(private_key_admin) # Since admin key exists - + token_tx.sign(private_key) # Necessary + token_tx.sign(private_key_admin) # Since admin key exists + node_id = mock_client.network.current_node._account_id body_bytes = token_tx._transaction_body_bytes[node_id] @@ -370,13 +387,14 @@ def test_sign_transaction(mock_account_ids, mock_client): for sig_pair in token_tx._signature_map[body_bytes].sigPair: assert sig_pair.pubKeyPrefix not in ( b"supply_public_key", - b"freeze_public_key", - b"wipe_public_key", + b"freeze_public_key", + b"wipe_public_key", b"metadata_public_key", b"pause_public_key", - b"fee_schedule_public_key" + b"fee_schedule_public_key", ) + # This test uses fixture (mock_account_ids, mock_client) as parameter def test_to_proto_without_keys(mock_account_ids, mock_client): """Test protobuf conversion when keys are not set.""" @@ -408,9 +426,7 @@ def test_to_proto_without_keys(mock_account_ids, mock_client): outer_tx = transaction_pb2.Transaction.FromString(proto_tx.SerializeToString()) assert len(outer_tx.signedTransactionBytes) > 0 - signed_tx = transaction_contents_pb2.SignedTransaction.FromString( - outer_tx.signedTransactionBytes - ) + signed_tx = transaction_contents_pb2.SignedTransaction.FromString(outer_tx.signedTransactionBytes) assert len(signed_tx.bodyBytes) > 0 transaction_body = transaction_pb2.TransactionBody.FromString(signed_tx.bodyBytes) @@ -423,10 +439,11 @@ def test_to_proto_without_keys(mock_account_ids, mock_client): assert transaction_body.tokenCreation.memo == "Token Memo" # Default Values assert transaction_body.tokenCreation.autoRenewPeriod == Duration(7890000)._to_proto() - assert not transaction_body.tokenCreation.HasField('expiry') + assert not transaction_body.tokenCreation.HasField("expiry") assert not transaction_body.tokenCreation.HasField("adminKey") + # This test uses fixture (mock_account_ids, mock_client) as parameter def test_to_proto_with_keys(mock_account_ids, mock_client): """Test converting the token creation transaction to protobuf format after signing.""" @@ -473,9 +490,7 @@ def test_to_proto_with_keys(mock_account_ids, mock_client): assert len(outer_tx.signedTransactionBytes) > 0 # 2) Parse the inner SignedTransaction - signed_tx = transaction_contents_pb2.SignedTransaction.FromString( - outer_tx.signedTransactionBytes - ) + signed_tx = transaction_contents_pb2.SignedTransaction.FromString(outer_tx.signedTransactionBytes) assert len(signed_tx.bodyBytes) > 0 # 3) Finally parse the TransactionBody @@ -491,6 +506,7 @@ def test_to_proto_with_keys(mock_account_ids, mock_client): assert tx_body.tokenCreation.kycKey == private_key_kyc.public_key()._to_proto() assert tx_body.tokenCreation.fee_schedule_key == private_key_fee_schedule.public_key()._to_proto() + # This test uses fixture mock_account_ids as parameter def test_freeze_status_without_freeze_key(mock_account_ids): """ @@ -513,6 +529,7 @@ def test_freeze_status_without_freeze_key(mock_account_ids): with pytest.raises(ValueError, match="Token is permanently frozen"): TokenCreateTransaction(params, keys=TokenKeys()).build_transaction_body() + # This test uses fixture mock_account_ids as parameter def test_transaction_execution_failure(mock_account_ids): """ @@ -533,19 +550,19 @@ def test_transaction_execution_failure(mock_account_ids): ) token_tx.node_account_id = node_account_id token_tx.transaction_id = generate_transaction_id(treasury_account) - + # Set the transaction body bytes to avoid calling build_transaction_body token_tx._transaction_body_bytes = b"mock_body_bytes" - + # Mock the client and its operator_private_key token_tx.client = MagicMock() mock_public_key = MagicMock() mock_public_key.to_bytes_raw.return_value = b"mock_public_key" - + token_tx.client.operator_private_key = MagicMock() token_tx.client.operator_private_key.sign.return_value = b"mock_signature" token_tx.client.operator_private_key.public_key.return_value = mock_public_key - + # Skip the actual sign method by mocking is_signed_by to return True token_tx.is_signed_by = MagicMock(return_value=True) @@ -554,7 +571,7 @@ def test_transaction_execution_failure(mock_account_ids): precheck_error = PrecheckError(ResponseCode.INVALID_SIGNATURE, token_tx.transaction_id) # Make _execute raise this error when called mock_execute.side_effect = precheck_error - + # The expected message pattern should match the PrecheckError message format expected_pattern = r"Transaction failed precheck with status: INVALID_SIGNATURE \(7\)" @@ -565,6 +582,7 @@ def test_transaction_execution_failure(mock_account_ids): # Verify _execute was called with client and timeout as None mock_execute.assert_called_once_with(token_tx.client, None) + # This test uses fixture (mock_account_ids, mock_client) as parameter def test_overwrite_defaults(mock_account_ids, mock_client): """ @@ -577,8 +595,8 @@ def test_overwrite_defaults(mock_account_ids, mock_client): token_tx = TokenCreateTransaction() # Assert the internal defaults. - assert token_tx._token_params.token_name == "" #Empty String - assert token_tx._token_params.token_symbol == "" #Empty String + assert token_tx._token_params.token_name == "" # Empty String + assert token_tx._token_params.token_symbol == "" # Empty String assert token_tx._token_params.treasury_account_id == AccountId(0, 0, 1) assert token_tx._token_params.decimals == 0 assert token_tx._token_params.initial_supply == 0 @@ -640,6 +658,7 @@ def test_overwrite_defaults(mock_account_ids, mock_client): # Confirm no adminKey was set assert not tx_body.tokenCreation.HasField("adminKey") + # This test uses fixture (mock_account_ids, mock_client) as parameter def test_transaction_freeze_prevents_modification(mock_account_ids, mock_client): """ @@ -659,7 +678,7 @@ def test_transaction_freeze_prevents_modification(mock_account_ids, mock_client) transaction.node_account_id = node_account_id transaction.transaction_id = generate_transaction_id(treasury_account) - + # Freeze the transaction transaction.freeze_with(mock_client) @@ -677,22 +696,20 @@ def test_transaction_freeze_prevents_modification(mock_account_ids, mock_client) transaction.set_decimals(8) with pytest.raises(Exception, match="Transaction is immutable; it has been frozen."): - transaction.set_token_type(TokenType.NON_FUNGIBLE_UNIQUE) # Should have defaulted to this + transaction.set_token_type(TokenType.NON_FUNGIBLE_UNIQUE) # Should have defaulted to this with pytest.raises(Exception, match="Transaction is immutable; it has been frozen."): transaction.set_expiration_time(Duration(2592000)) with pytest.raises(Exception, match="Transaction is immutable; it has been frozen."): transaction.set_auto_renew_period(Duration(2592000)) - + with pytest.raises(Exception, match="Transaction is immutable; it has been frozen."): transaction.set_auto_renew_account_id(AccountId(0, 0, 2)) - - # Confirm that values remain unchanged after freeze attempt assert transaction._token_params.token_name == "TestName" - assert transaction._token_params.token_symbol == "TEST" + assert transaction._token_params.token_symbol == "TEST" assert transaction._token_params.initial_supply == 1000 assert transaction._token_params.decimals == 2 assert transaction._token_params.treasury_account_id == treasury_account @@ -716,8 +733,8 @@ def test_build_transaction_body_non_fungible(mock_account_ids): token_tx.set_token_symbol("NFT") token_tx.set_treasury_account_id(treasury_account) token_tx.set_token_type(TokenType.NON_FUNGIBLE_UNIQUE) - token_tx.set_decimals(0) # NFTs must have 0 decimals - token_tx.set_initial_supply(0) # NFTs must have 0 initial supply + token_tx.set_decimals(0) # NFTs must have 0 decimals + token_tx.set_initial_supply(0) # NFTs must have 0 initial supply token_tx.set_memo("NFT Memo") token_tx.transaction_id = generate_transaction_id(treasury_account) @@ -733,8 +750,10 @@ def test_build_transaction_body_non_fungible(mock_account_ids): assert transaction_body.tokenCreation.decimals == 0 assert transaction_body.tokenCreation.initialSupply == 0 assert transaction_body.tokenCreation.memo == "NFT Memo" - assert transaction_body.tokenCreation.autoRenewPeriod == Duration(7890000)._to_proto() # Default value of 90days. - assert not transaction_body.tokenCreation.HasField('expiry') # By default, this is set to "now + AUTO_RENEW_PERIOD" (90 days). + assert transaction_body.tokenCreation.autoRenewPeriod == Duration(7890000)._to_proto() # Default value of 90days. + assert not transaction_body.tokenCreation.HasField( + "expiry" + ) # By default, this is set to "now + AUTO_RENEW_PERIOD" (90 days). # No keys are set assert not transaction_body.tokenCreation.HasField("adminKey") @@ -745,10 +764,11 @@ def test_build_transaction_body_non_fungible(mock_account_ids): assert not transaction_body.tokenCreation.HasField("kycKey") assert not transaction_body.tokenCreation.HasField("fee_schedule_key") + # This test uses fixture (mock_account_ids, mock_client) as parameter def test_build_and_sign_nft_transaction_to_proto(mock_account_ids, mock_client): """ - Test building, signing, and protobuf serialization of + Test building, signing, and protobuf serialization of a valid Non-Fungible Unique token creation transaction. """ treasury_account, _, _, _, _ = mock_account_ids @@ -789,7 +809,9 @@ def test_build_and_sign_nft_transaction_to_proto(mock_account_ids, mock_client): private_key_fee_schedule = MagicMock(spec=PrivateKey) private_key_fee_schedule.sign.return_value = b"fee_schedule_signature" - private_key_fee_schedule.public_key()._to_proto.return_value = basic_types_pb2.Key(ed25519=b"fee_schedule_public_key") + private_key_fee_schedule.public_key()._to_proto.return_value = basic_types_pb2.Key( + ed25519=b"fee_schedule_public_key" + ) # Build the transaction token_tx = TokenCreateTransaction() @@ -843,10 +865,11 @@ def test_build_and_sign_nft_transaction_to_proto(mock_account_ids, mock_client): assert tx_body.tokenCreation.freezeKey.ed25519 == b"freeze_public_key" assert tx_body.tokenCreation.wipeKey.ed25519 == b"wipe_public_key" assert tx_body.tokenCreation.metadata_key.ed25519 == b"metadata_public_key" - assert tx_body.tokenCreation.pause_key.ed25519 == b"pause_public_key" + assert tx_body.tokenCreation.pause_key.ed25519 == b"pause_public_key" assert tx_body.tokenCreation.kycKey.ed25519 == b"kyc_public_key" assert tx_body.tokenCreation.fee_schedule_key.ed25519 == b"fee_schedule_public_key" + @pytest.mark.parametrize( "token_type, supply_type, max_supply, initial_supply, expected_error", [ @@ -856,49 +879,67 @@ def test_build_and_sign_nft_transaction_to_proto(mock_account_ids, mock_client): # 1) Infinite supply requires max_supply=0 => VALID (TokenType.FUNGIBLE_COMMON, SupplyType.INFINITE, 0, 1, None), # 2) Infinite supply but max_supply != 0 => ERROR - (TokenType.FUNGIBLE_COMMON, SupplyType.INFINITE, 100, 100, - "Setting a max supply field requires setting a finite supply type"), + ( + TokenType.FUNGIBLE_COMMON, + SupplyType.INFINITE, + 100, + 100, + "Setting a max supply field requires setting a finite supply type", + ), # # FUNGIBLE + FINITE # # 3) Finite supply but max_supply=0 => ERROR - (TokenType.FUNGIBLE_COMMON, SupplyType.FINITE, 0, 100, - "A finite supply token requires max_supply greater than zero 0"), + ( + TokenType.FUNGIBLE_COMMON, + SupplyType.FINITE, + 0, + 100, + "A finite supply token requires max_supply greater than zero 0", + ), # 4) Finite supply, max_supply>0 but initial_supply > max_supply => ERROR - (TokenType.FUNGIBLE_COMMON, SupplyType.FINITE, 500, 600, - "Initial supply cannot exceed the defined max supply for a finite token"), + ( + TokenType.FUNGIBLE_COMMON, + SupplyType.FINITE, + 500, + 600, + "Initial supply cannot exceed the defined max supply for a finite token", + ), # 5) Finite supply, max_supply>0, initial_supply <= max_supply => VALID (TokenType.FUNGIBLE_COMMON, SupplyType.FINITE, 5000, 100, None), - # # NON-FUNGIBLE + INFINITE # # 6) NFT + infinite supply => must have max_supply=0 => VALID (TokenType.NON_FUNGIBLE_UNIQUE, SupplyType.INFINITE, 0, 0, None), # 7) NFT + infinite supply + nonzero max_supply => ERROR - (TokenType.NON_FUNGIBLE_UNIQUE, SupplyType.INFINITE, 200, 0, - "Setting a max supply field requires setting a finite supply type"), + ( + TokenType.NON_FUNGIBLE_UNIQUE, + SupplyType.INFINITE, + 200, + 0, + "Setting a max supply field requires setting a finite supply type", + ), # # NON-FUNGIBLE + FINITE # # 8) NFT, finite supply but max_supply=0 => ERROR - (TokenType.NON_FUNGIBLE_UNIQUE, SupplyType.FINITE, 0, 0, - "A finite supply token requires max_supply greater than zero 0"), - + ( + TokenType.NON_FUNGIBLE_UNIQUE, + SupplyType.FINITE, + 0, + 0, + "A finite supply token requires max_supply greater than zero 0", + ), # 9) NFT, finite supply, no initial supply, max_supply>0 => VALID (TokenType.NON_FUNGIBLE_UNIQUE, SupplyType.FINITE, 100, 0, None), - ] + ], ) def test_supply_type_and_max_supply_validation( - mock_account_ids, - token_type, - supply_type, - max_supply, - initial_supply, - expected_error + mock_account_ids, token_type, supply_type, max_supply, initial_supply, expected_error ): - """ - Verifies the combination of token_type, supply_type, max_supply, and initial_supply + """ + Verifies the combination of token_type, supply_type, max_supply, and initial_supply either passes validation or raises the correct ValueError """ treasury_account, _, node_account_id, _, _ = mock_account_ids @@ -913,7 +954,7 @@ def test_supply_type_and_max_supply_validation( token_type=token_type, supply_type=supply_type, max_supply=max_supply, - freeze_default=False + freeze_default=False, ) if expected_error: @@ -930,10 +971,11 @@ def test_supply_type_and_max_supply_validation( assert body.tokenCreation.maxSupply == max_supply assert body.tokenCreation.initialSupply == initial_supply + def test_build_scheduled_body_fungible_token(mock_account_ids, private_key): """Test building a scheduled transaction body for fungible token creation.""" treasury_account, _, _, _, _ = mock_account_ids - + # Prepare token parameters for a fungible token params = TokenParams( token_name="TestToken", @@ -944,18 +986,15 @@ def test_build_scheduled_body_fungible_token(mock_account_ids, private_key): token_type=TokenType.FUNGIBLE_COMMON, supply_type=SupplyType.INFINITE, ) - + # Prepare token keys - keys = TokenKeys( - admin_key=private_key, - supply_key=private_key - ) - + keys = TokenKeys(admin_key=private_key, supply_key=private_key) + # Create the transaction token_tx = TokenCreateTransaction(params, keys) schedulable_body = token_tx.build_scheduled_body() - + # Verify the schedulable body has the correct structure and fields assert isinstance(schedulable_body, SchedulableTransactionBody) assert schedulable_body.HasField("tokenCreation") @@ -968,11 +1007,12 @@ def test_build_scheduled_body_fungible_token(mock_account_ids, private_key): assert schedulable_body.tokenCreation.supplyType == SupplyType.INFINITE.value assert schedulable_body.tokenCreation.adminKey.HasField("ed25519") assert schedulable_body.tokenCreation.supplyKey.HasField("ed25519") - + + def test_build_scheduled_body_nft(mock_account_ids, private_key): """Test building a scheduled transaction body for NFT token creation.""" treasury_account, _, _, _, _ = mock_account_ids - + # Prepare token parameters for an NFT params = TokenParams( token_name="TestNFT", @@ -982,19 +1022,15 @@ def test_build_scheduled_body_nft(mock_account_ids, private_key): supply_type=SupplyType.FINITE, max_supply=1000, ) - + # Prepare token keys - keys = TokenKeys( - admin_key=private_key, - supply_key=private_key, - wipe_key=private_key - ) - + keys = TokenKeys(admin_key=private_key, supply_key=private_key, wipe_key=private_key) + # Create the transaction token_tx = TokenCreateTransaction(params, keys) - + schedulable_body = token_tx.build_scheduled_body() - + # Verify the schedulable body has the correct structure and fields assert isinstance(schedulable_body, SchedulableTransactionBody) assert schedulable_body.HasField("tokenCreation") @@ -1008,31 +1044,28 @@ def test_build_scheduled_body_nft(mock_account_ids, private_key): assert schedulable_body.tokenCreation.supplyKey.HasField("ed25519") assert schedulable_body.tokenCreation.wipeKey.HasField("ed25519") + def test_token_create_with_expiration_time_overrides_auto_renew(mock_account_ids): """Test set_expiration_time set the autoRenewPeriod to None""" treasury_account, _, node_account_id, *_ = mock_account_ids - expiration_time = Timestamp.from_date( - datetime.datetime.now(datetime.timezone.utc) + datetime.timedelta(days=30) - ) + expiration_time = Timestamp.from_date(datetime.datetime.now(datetime.timezone.utc) + datetime.timedelta(days=30)) params = TokenParams( - token_name="ExpToken", - token_symbol="EXP", - treasury_account_id=treasury_account, - initial_supply=1 + token_name="ExpToken", token_symbol="EXP", treasury_account_id=treasury_account, initial_supply=1 ) tx = TokenCreateTransaction(params) - + assert tx._token_params.auto_renew_period == Duration(seconds=7890000) assert tx._token_params.expiration_time is None - - tx.set_expiration_time(expiration_time); + + tx.set_expiration_time(expiration_time) tx.transaction_id = generate_transaction_id(treasury_account) - tx.node_account_id = node_account_id + tx.node_account_id = node_account_id body = tx.build_transaction_body() assert body.tokenCreation.expiry.seconds == expiration_time.seconds - assert not body.tokenCreation.HasField("autoRenewPeriod") + assert not body.tokenCreation.HasField("autoRenewPeriod") + def test_auto_renew_account_assignment_during_freeze_with_client(mock_account_ids, mock_client): """Test autoRenewPeriod and autoRenewAccount assignment behavior when freezing with a client.""" @@ -1045,29 +1078,26 @@ def test_auto_renew_account_assignment_during_freeze_with_client(mock_account_id token_symbol="FCT", treasury_account_id=treasury_account, initial_supply=1, - auto_renew_period=None + auto_renew_period=None, ) ) frozen_tx = tx.freeze_with(mock_client) body = frozen_tx.build_transaction_body() - assert not body.tokenCreation.HasField("autoRenewPeriod") - assert not body.tokenCreation.HasField('autoRenewAccount') + assert not body.tokenCreation.HasField("autoRenewPeriod") + assert not body.tokenCreation.HasField("autoRenewAccount") # If autoRenewAccountId is set then freezeWith(client) will not assing value to it tx1 = TokenCreateTransaction( TokenParams( - token_name="FreezeClientToken", - token_symbol="FCT", - treasury_account_id=treasury_account, - initial_supply=1 + token_name="FreezeClientToken", token_symbol="FCT", treasury_account_id=treasury_account, initial_supply=1 ) ) tx1.set_auto_renew_account_id(auto_renew_account_id) frozen_tx1 = tx1.freeze_with(mock_client) body1 = frozen_tx1.build_transaction_body() - assert body1.tokenCreation.autoRenewPeriod == Duration(7890000)._to_proto() # Default around 90 days + assert body1.tokenCreation.autoRenewPeriod == Duration(7890000)._to_proto() # Default around 90 days assert body1.tokenCreation.autoRenewAccount == auto_renew_account_id._to_proto() # If Trasnaction Id not generated. Then use client operator_account @@ -1081,9 +1111,9 @@ def test_auto_renew_account_assignment_during_freeze_with_client(mock_account_id ) frozen_tx2 = tx2.freeze_with(mock_client) - body2 = frozen_tx2.build_transaction_body(); + body2 = frozen_tx2.build_transaction_body() - assert body2.tokenCreation.autoRenewPeriod == Duration(7890000)._to_proto() # Default around 90 days + assert body2.tokenCreation.autoRenewPeriod == Duration(7890000)._to_proto() # Default around 90 days assert body2.tokenCreation.autoRenewAccount == mock_client.operator_account_id._to_proto() # If Transaction Id generated. Then use transaction_id account @@ -1094,15 +1124,16 @@ def test_auto_renew_account_assignment_during_freeze_with_client(mock_account_id treasury_account_id=treasury_account, initial_supply=1, ) - ); + ) tx3.transaction_id = generate_transaction_id(treasury_account) frozen_tx3 = tx3.freeze_with(mock_client) body3 = frozen_tx3.build_transaction_body() - - assert body3.tokenCreation.autoRenewPeriod == Duration(7890000)._to_proto() # Default around 90 days + + assert body3.tokenCreation.autoRenewPeriod == Duration(7890000)._to_proto() # Default around 90 days assert body3.tokenCreation.autoRenewAccount == treasury_account._to_proto() + def test_admin_key_token_operations_logic(mock_client): """ Test the logic of admin key token operations from the example. @@ -1110,19 +1141,21 @@ def test_admin_key_token_operations_logic(mock_client): """ client = mock_client operator_id = client.operator_account_id - operator_key = PrivateKey.generate() # Generate a key for testing # Find a node account ID from the client's network node_account_id = client.network.nodes[0]._account_id if client.network.nodes else AccountId(0, 0, 3) # Generate transaction ID helper import time + current_time = time.time() timestamp_seconds = int(current_time) timestamp_nanos = int((current_time - timestamp_seconds) * 1e9) from hiero_sdk_python.hapi.services import timestamp_pb2 + tx_timestamp = timestamp_pb2.Timestamp(seconds=timestamp_seconds, nanos=timestamp_nanos) from hiero_sdk_python.transaction.transaction_id import TransactionId + tx_id = TransactionId(valid_start=tx_timestamp, account_id=operator_id) # Step 1: Generate admin key @@ -1187,23 +1220,25 @@ def test_token_info_query_structure(): print("✅ TokenInfoQuery structure test passed") + # --- Tests for _to_proto_key (backward compatibility wrapper) --- # Note: Core functionality tests for key_to_proto are in key_utils_test.py + def test_to_proto_key_wrapper_still_works(): """Tests that _to_proto_key wrapper method still works for backward compatibility.""" tx = TokenCreateTransaction() private_key = PrivateKey.generate_ed25519() public_key = private_key.public_key() - + # Test that the wrapper method still exists and works result_proto = tx._to_proto_key(public_key) assert result_proto is not None assert isinstance(result_proto, basic_types_pb2.Key) - + # Test with None assert tx._to_proto_key(None) is None - + # Test error handling with pytest.raises(TypeError) as e: tx._to_proto_key("this is not a key") diff --git a/tests/unit/token_delete_transaction_test.py b/tests/unit/token_delete_transaction_test.py index e1bb8680b..bb961eb4b 100644 --- a/tests/unit/token_delete_transaction_test.py +++ b/tests/unit/token_delete_transaction_test.py @@ -1,33 +1,37 @@ -import pytest +from __future__ import annotations + from unittest.mock import MagicMock -from hiero_sdk_python.tokens.token_delete_transaction import TokenDeleteTransaction -from hiero_sdk_python.hapi.services import basic_types_pb2, timestamp_pb2 + +import pytest + +from hiero_sdk_python.hapi.services import timestamp_pb2 from hiero_sdk_python.hapi.services.schedulable_transaction_body_pb2 import ( SchedulableTransactionBody, ) +from hiero_sdk_python.tokens.token_delete_transaction import TokenDeleteTransaction from hiero_sdk_python.transaction.transaction_id import TransactionId + pytestmark = pytest.mark.unit + def generate_transaction_id(account_id_proto): """Generate a unique transaction ID based on the account ID and the current timestamp.""" import time + current_time = time.time() timestamp_seconds = int(current_time) timestamp_nanos = int((current_time - timestamp_seconds) * 1e9) tx_timestamp = timestamp_pb2.Timestamp(seconds=timestamp_seconds, nanos=timestamp_nanos) - tx_id = TransactionId( - valid_start=tx_timestamp, - account_id=account_id_proto - ) - return tx_id + return TransactionId(valid_start=tx_timestamp, account_id=account_id_proto) + # This test uses fixture mock_account_ids as parameter def test_build_transaction_body(mock_account_ids): """Test building a token delete transaction body with a valid value.""" - account_id, _, node_account_id, token_id, _= mock_account_ids + account_id, _, node_account_id, token_id, _ = mock_account_ids delete_tx = TokenDeleteTransaction() delete_tx.set_token_id(token_id) @@ -40,25 +44,27 @@ def test_build_transaction_body(mock_account_ids): assert transaction_body.tokenDeletion.token.realmNum == 1 assert transaction_body.tokenDeletion.token.tokenNum == 1 + def test_missing_token_id(): """Test that building a transaction without setting TokenID raises a ValueError.""" delete_tx = TokenDeleteTransaction() with pytest.raises(ValueError, match="Missing required TokenID."): delete_tx.build_transaction_body() + # This test uses fixtures (mock_account_ids, mock_client) as parameters def test_sign_transaction(mock_account_ids, mock_client): """Test signing the token delete transaction with a private key.""" - operator_id, _, _, token_id, _= mock_account_ids - + operator_id, _, _, token_id, _ = mock_account_ids + delete_tx = TokenDeleteTransaction() delete_tx.set_token_id(token_id) delete_tx.transaction_id = generate_transaction_id(operator_id) private_key = MagicMock() - private_key.sign.return_value = b'signature' - private_key.public_key().to_bytes_raw.return_value = b'public_key' - + private_key.sign.return_value = b"signature" + private_key.public_key().to_bytes_raw.return_value = b"public_key" + delete_tx.freeze_with(mock_client) delete_tx.sign(private_key) @@ -68,22 +74,23 @@ def test_sign_transaction(mock_account_ids, mock_client): assert len(delete_tx._signature_map[body_bytes].sigPair) == 1 sig_pair = delete_tx._signature_map[body_bytes].sigPair[0] - assert sig_pair.pubKeyPrefix == b'public_key' - assert sig_pair.ed25519 == b'signature' + assert sig_pair.pubKeyPrefix == b"public_key" + assert sig_pair.ed25519 == b"signature" + # This test uses fixtures (mock_account_ids, mock_client) as parameters def test_to_proto(mock_account_ids, mock_client): """Test converting the token delete transaction to protobuf format after signing.""" - operator_id, _, _, token_id, _= mock_account_ids - + operator_id, _, _, token_id, _ = mock_account_ids + delete_tx = TokenDeleteTransaction() delete_tx.set_token_id(token_id) delete_tx.transaction_id = generate_transaction_id(operator_id) private_key = MagicMock() - private_key.sign.return_value = b'signature' - private_key.public_key().to_bytes_raw.return_value = b'public_key' - + private_key.sign.return_value = b"signature" + private_key.public_key().to_bytes_raw.return_value = b"public_key" + delete_tx.freeze_with(mock_client) delete_tx.sign(private_key) @@ -91,16 +98,17 @@ def test_to_proto(mock_account_ids, mock_client): assert proto.signedTransactionBytes assert len(proto.signedTransactionBytes) > 0 - + + def test_build_scheduled_body(mock_account_ids): """Test building a scheduled transaction body for token delete transaction.""" _, _, _, token_id, _ = mock_account_ids - + delete_tx = TokenDeleteTransaction() delete_tx.set_token_id(token_id) schedulable_body = delete_tx.build_scheduled_body() - + # Verify the schedulable body has the correct structure and fields assert isinstance(schedulable_body, SchedulableTransactionBody) assert schedulable_body.HasField("tokenDeletion") diff --git a/tests/unit/token_dissociate_transaction_test.py b/tests/unit/token_dissociate_transaction_test.py index 892bd11a6..615c7d503 100644 --- a/tests/unit/token_dissociate_transaction_test.py +++ b/tests/unit/token_dissociate_transaction_test.py @@ -1,29 +1,32 @@ -from unittest.mock import call, MagicMock, Mock +from __future__ import annotations + +from unittest.mock import MagicMock, call + import pytest -from hiero_sdk_python.tokens.token_dissociate_transaction import TokenDissociateTransaction + from hiero_sdk_python.hapi.services import timestamp_pb2 from hiero_sdk_python.hapi.services.schedulable_transaction_body_pb2 import ( SchedulableTransactionBody, ) +from hiero_sdk_python.tokens.token_dissociate_transaction import TokenDissociateTransaction from hiero_sdk_python.transaction.transaction_id import TransactionId -from hiero_sdk_python.tokens.token_id import TokenId + pytestmark = pytest.mark.unit + def generate_transaction_id(account_id_proto): """Generate a unique transaction ID based on the account ID and the current timestamp.""" import time + current_time = time.time() timestamp_seconds = int(current_time) timestamp_nanos = int((current_time - timestamp_seconds) * 1e9) tx_timestamp = timestamp_pb2.Timestamp(seconds=timestamp_seconds, nanos=timestamp_nanos) - tx_id = TransactionId( - valid_start=tx_timestamp, - account_id=account_id_proto - ) - return tx_id + return TransactionId(valid_start=tx_timestamp, account_id=account_id_proto) + # This test uses fixture mock_account_ids as parameter def test_build_transaction_body(mock_account_ids): @@ -45,6 +48,7 @@ def test_build_transaction_body(mock_account_ids): assert len(transaction_body.tokenDissociate.tokens) == 1 assert transaction_body.tokenDissociate.tokens[0].tokenNum == token_id_1.num + # This test uses fixture mock_account_ids as parameter def test_transaction_body_with_multiple_tokens(mock_account_ids): """Test building the transaction body for dissociating multiple tokens.""" @@ -60,21 +64,22 @@ def test_transaction_body_with_multiple_tokens(mock_account_ids): dissociate_tx.node_account_id = node_account_id transaction_body = dissociate_tx.build_transaction_body() - + assert transaction_body.tokenDissociate.account.shardNum == account_id.shard assert transaction_body.tokenDissociate.account.realmNum == account_id.realm assert transaction_body.tokenDissociate.account.accountNum == account_id.num - + assert len(transaction_body.tokenDissociate.tokens) == len(token_ids) for i, token_id in enumerate(token_ids): assert transaction_body.tokenDissociate.tokens[i].tokenNum == token_id.num + # This test uses fixture mock_account_ids as parameter def test_set_token_ids(mock_account_ids): """Test setting multiple token IDs at once for dissociation.""" account_id, _, _, token_id_1, token_id_2 = mock_account_ids token_ids = [token_id_1, token_id_2] - + dissociate_tx = TokenDissociateTransaction() dissociate_tx.set_account_id(account_id) dissociate_tx.set_token_ids(token_ids) @@ -103,6 +108,7 @@ def test_validate_check_sum(mock_account_ids, mock_client, monkeypatch): assert token_validate.call_count == 2 token_validate.assert_has_calls([call(mock_client), call(mock_client)]) + def test_missing_fields(): """Test that building the transaction without account ID or token IDs raises a ValueError.""" dissociate_tx = TokenDissociateTransaction() @@ -110,19 +116,20 @@ def test_missing_fields(): with pytest.raises(ValueError, match="Account ID and token IDs must be set."): dissociate_tx.build_transaction_body() + def test_sign_transaction(mock_account_ids, mock_client): """Test signing the token dissociate transaction with a private key.""" account_id, _, _, token_id_1, _ = mock_account_ids - + dissociate_tx = TokenDissociateTransaction() dissociate_tx.set_account_id(account_id) dissociate_tx.add_token_id(token_id_1) dissociate_tx.transaction_id = generate_transaction_id(account_id) private_key = MagicMock() - private_key.sign.return_value = b'signature' - private_key.public_key().to_bytes_raw.return_value = b'public_key' - + private_key.sign.return_value = b"signature" + private_key.public_key().to_bytes_raw.return_value = b"public_key" + dissociate_tx.freeze_with(mock_client) dissociate_tx.sign(private_key) @@ -133,22 +140,23 @@ def test_sign_transaction(mock_account_ids, mock_client): assert len(dissociate_tx._signature_map[body_bytes].sigPair) == 1 sig_pair = dissociate_tx._signature_map[body_bytes].sigPair[0] - assert sig_pair.pubKeyPrefix == b'public_key' - assert sig_pair.ed25519 == b'signature' + assert sig_pair.pubKeyPrefix == b"public_key" + assert sig_pair.ed25519 == b"signature" + def test_to_proto(mock_account_ids, mock_client): """Test converting the token dissociate transaction to protobuf format after signing.""" account_id, _, _, token_id_1, _ = mock_account_ids - + dissociate_tx = TokenDissociateTransaction() dissociate_tx.set_account_id(account_id) dissociate_tx.add_token_id(token_id_1) - dissociate_tx.transaction_id = generate_transaction_id(account_id) - + dissociate_tx.transaction_id = generate_transaction_id(account_id) + private_key = MagicMock() - private_key.sign.return_value = b'signature' - private_key.public_key().to_bytes_raw.return_value = b'public_key' - + private_key.sign.return_value = b"signature" + private_key.public_key().to_bytes_raw.return_value = b"public_key" + dissociate_tx.freeze_with(mock_client) dissociate_tx.sign(private_key) @@ -157,6 +165,7 @@ def test_to_proto(mock_account_ids, mock_client): assert proto.signedTransactionBytes assert len(proto.signedTransactionBytes) > 0 + def test_from_proto(mock_account_ids): """Test creating a TokenDissociateTransaction from a protobuf object.""" account_id, _, _, token_id_1, token_id_2 = mock_account_ids @@ -170,18 +179,19 @@ def test_from_proto(mock_account_ids): assert reconstructed_tx.token_ids[0] == token_id_1 assert reconstructed_tx.token_ids[1] == token_id_2 + def test_build_scheduled_body(mock_account_ids): """Test building a scheduled transaction body for token dissociate transaction.""" account_id, _, _, token_id_1, token_id_2 = mock_account_ids token_ids = [token_id_1, token_id_2] - + dissociate_tx = TokenDissociateTransaction() dissociate_tx.set_account_id(account_id) for token_id in token_ids: dissociate_tx.add_token_id(token_id) - + schedulable_body = dissociate_tx.build_scheduled_body() - + # Verify the schedulable body has the correct structure and fields assert isinstance(schedulable_body, SchedulableTransactionBody) assert schedulable_body.HasField("tokenDissociate") diff --git a/tests/unit/token_fee_schedule_update_transaction_test.py b/tests/unit/token_fee_schedule_update_transaction_test.py index 6cbfdd452..8a62285c6 100644 --- a/tests/unit/token_fee_schedule_update_transaction_test.py +++ b/tests/unit/token_fee_schedule_update_transaction_test.py @@ -1,22 +1,21 @@ +from __future__ import annotations + import pytest +from hiero_sdk_python.account.account_id import AccountId +from hiero_sdk_python.tokens.custom_fixed_fee import CustomFixedFee from hiero_sdk_python.tokens.token_fee_schedule_update_transaction import ( TokenFeeScheduleUpdateTransaction, ) -from hiero_sdk_python.tokens.token_id import TokenId -from hiero_sdk_python.account.account_id import AccountId -from hiero_sdk_python.tokens.custom_fixed_fee import CustomFixedFee -from hiero_sdk_python.tokens.custom_royalty_fee import CustomRoyaltyFee pytestmark = pytest.mark.unit + def test_setters_and_constructor(mock_account_ids): _, _, _, token_id, _ = mock_account_ids collector_account = AccountId(0, 0, 987) - test_fee_list = [ - CustomFixedFee(amount=100, fee_collector_account_id=collector_account) - ] + test_fee_list = [CustomFixedFee(amount=100, fee_collector_account_id=collector_account)] txn_constructor = TokenFeeScheduleUpdateTransaction(token_id=token_id, custom_fees=test_fee_list) assert txn_constructor.token_id == token_id @@ -30,12 +29,11 @@ def test_setters_and_constructor(mock_account_ids): assert txn_setters.custom_fees == test_fee_list assert len(txn_setters.custom_fees) == 1 + def test_setters_chaining(mock_account_ids): _, _, _, token_id, _ = mock_account_ids collector_account = AccountId(0, 0, 987) - test_fee_list = [ - CustomFixedFee(amount=200, fee_collector_account_id=collector_account) - ] + test_fee_list = [CustomFixedFee(amount=200, fee_collector_account_id=collector_account)] txn = TokenFeeScheduleUpdateTransaction().set_token_id(token_id).set_custom_fees(test_fee_list) @@ -43,29 +41,27 @@ def test_setters_chaining(mock_account_ids): assert txn.custom_fees == test_fee_list assert len(txn.custom_fees) == 1 + def test_build_raises_error_if_no_token_id(): update_tx = TokenFeeScheduleUpdateTransaction() - test_fee_list = [ - CustomFixedFee(amount=100, fee_collector_account_id=AccountId(0, 0, 987)) - ] + test_fee_list = [CustomFixedFee(amount=100, fee_collector_account_id=AccountId(0, 0, 987))] update_tx.set_custom_fees(test_fee_list) with pytest.raises(ValueError, match="Missing token ID"): update_tx.build_transaction_body() + def test_fails_precheck_without_token_id(): """Test precheck failure when token ID is missing.""" - new_fee = CustomFixedFee(amount=50, fee_collector_account_id=AccountId(0,0,123)) - update_tx = ( - TokenFeeScheduleUpdateTransaction() - .set_custom_fees([new_fee]) - ) + new_fee = CustomFixedFee(amount=50, fee_collector_account_id=AccountId(0, 0, 123)) + update_tx = TokenFeeScheduleUpdateTransaction().set_custom_fees([new_fee]) with pytest.raises(ValueError, match="Missing token ID"): update_tx.build_transaction_body() + def test_build_transaction_body_sets_token_id(mock_account_ids): operator_id, _, node_account_id, token_id, _ = mock_account_ids - + update_tx = TokenFeeScheduleUpdateTransaction(token_id=token_id) update_tx.operator_account_id = operator_id update_tx.node_account_id = node_account_id @@ -75,12 +71,10 @@ def test_build_transaction_body_sets_token_id(mock_account_ids): assert transaction_body.HasField("token_fee_schedule_update") assert transaction_body.token_fee_schedule_update.token_id == token_id._to_proto() + def test_build_transaction_body_sets_custom_fees(mock_account_ids): operator_id, _, node_account_id, token_id, _ = mock_account_ids - test_fee = CustomFixedFee( - amount=150, - fee_collector_account_id=operator_id - ) + test_fee = CustomFixedFee(amount=150, fee_collector_account_id=operator_id) test_fees_list = [test_fee] update_tx = TokenFeeScheduleUpdateTransaction(token_id=token_id, custom_fees=test_fees_list) @@ -94,10 +88,11 @@ def test_build_transaction_body_sets_custom_fees(mock_account_ids): assert proto_fee.fixed_fee.amount == 150 assert proto_fee.fee_collector_account_id == operator_id._to_proto() + def test_build_transaction_body_with_empty_custom_fees(mock_account_ids): """Test for empty custom fee list.""" operator_id, _, node_account_id, token_id, _ = mock_account_ids - + update_tx = TokenFeeScheduleUpdateTransaction(token_id=token_id, custom_fees=[]) update_tx.operator_account_id = operator_id update_tx.node_account_id = node_account_id @@ -106,4 +101,4 @@ def test_build_transaction_body_with_empty_custom_fees(mock_account_ids): assert transaction_body.HasField("token_fee_schedule_update") assert transaction_body.token_fee_schedule_update.token_id == token_id._to_proto() - assert len(transaction_body.token_fee_schedule_update.custom_fees) == 0 \ No newline at end of file + assert len(transaction_body.token_fee_schedule_update.custom_fees) == 0 diff --git a/tests/unit/token_freeze_transaction_test.py b/tests/unit/token_freeze_transaction_test.py index d20030396..e19f15dd8 100644 --- a/tests/unit/token_freeze_transaction_test.py +++ b/tests/unit/token_freeze_transaction_test.py @@ -1,33 +1,37 @@ -import pytest +from __future__ import annotations + from unittest.mock import MagicMock -from hiero_sdk_python.tokens.token_freeze_transaction import TokenFreezeTransaction + +import pytest + from hiero_sdk_python.hapi.services import timestamp_pb2 from hiero_sdk_python.hapi.services.schedulable_transaction_body_pb2 import ( SchedulableTransactionBody, ) +from hiero_sdk_python.tokens.token_freeze_transaction import TokenFreezeTransaction from hiero_sdk_python.transaction.transaction_id import TransactionId + pytestmark = pytest.mark.unit + def generate_transaction_id(account_id_proto): """Generate a unique transaction ID based on the account ID and the current timestamp.""" import time + current_time = time.time() timestamp_seconds = int(current_time) timestamp_nanos = int((current_time - timestamp_seconds) * 1e9) tx_timestamp = timestamp_pb2.Timestamp(seconds=timestamp_seconds, nanos=timestamp_nanos) - tx_id = TransactionId( - valid_start=tx_timestamp, - account_id=account_id_proto - ) - return tx_id + return TransactionId(valid_start=tx_timestamp, account_id=account_id_proto) + # This test uses fixture mock_account_ids as parameter def test_build_transaction_body(mock_account_ids): """Test building a token freeze transaction body with a valid value.""" - account_id, freeze_id, node_account_id, token_id, _= mock_account_ids + account_id, freeze_id, node_account_id, token_id, _ = mock_account_ids freeze_tx = TokenFreezeTransaction() freeze_tx.set_token_id(token_id) @@ -44,40 +48,43 @@ def test_build_transaction_body(mock_account_ids): proto_account = freeze_id._to_proto() assert transaction_body.tokenFreeze.account == proto_account + # This test uses fixture mock_account_ids as parameter def test_missing_token_id(mock_account_ids): """Test that building a transaction without setting TokenID raises a ValueError.""" - account_id, freeze_id, node_account_id, token_id, _= mock_account_ids + account_id, freeze_id, node_account_id, token_id, _ = mock_account_ids freeze_tx = TokenFreezeTransaction() freeze_tx.set_account_id(freeze_id) with pytest.raises(ValueError, match="Missing required TokenID."): freeze_tx.build_transaction_body() + # This test uses fixture mock_account_ids as parameter def test_missing_account_id(mock_account_ids): """Test that building a transaction without setting AccountID raises a ValueError.""" - account_id, freeze_id, node_account_id, token_id, _= mock_account_ids + account_id, freeze_id, node_account_id, token_id, _ = mock_account_ids freeze_tx = TokenFreezeTransaction() freeze_tx.set_token_id(token_id) with pytest.raises(ValueError, match="Missing required AccountID."): freeze_tx.build_transaction_body() + # This test uses fixtures (mock_account_id, mock_client) as parameters def test_sign_transaction(mock_account_ids, mock_client): """Test signing the token freeze transaction with a freeze key.""" - account_id, freeze_id, _, token_id, _= mock_account_ids - + account_id, freeze_id, _, token_id, _ = mock_account_ids + freeze_tx = TokenFreezeTransaction() freeze_tx.set_token_id(token_id) freeze_tx.set_account_id(freeze_id) freeze_tx.transaction_id = generate_transaction_id(account_id) freeze_key = MagicMock() - freeze_key.sign.return_value = b'signature' - freeze_key.public_key().to_bytes_raw.return_value = b'public_key' - + freeze_key.sign.return_value = b"signature" + freeze_key.public_key().to_bytes_raw.return_value = b"public_key" + freeze_tx.freeze_with(mock_client) freeze_tx.sign(freeze_key) @@ -87,13 +94,14 @@ def test_sign_transaction(mock_account_ids, mock_client): assert len(freeze_tx._signature_map[body_bytes].sigPair) == 1 sig_pair = freeze_tx._signature_map[body_bytes].sigPair[0] - assert sig_pair.pubKeyPrefix == b'public_key' - assert sig_pair.ed25519 == b'signature' + assert sig_pair.pubKeyPrefix == b"public_key" + assert sig_pair.ed25519 == b"signature" + # This test uses fixtures (mock_account_ids) as parameters def test_to_proto(mock_account_ids, mock_client): """Test converting the token freeze transaction to protobuf format after signing.""" - account_id, freeze_id, _, token_id, _= mock_account_ids + account_id, freeze_id, _, token_id, _ = mock_account_ids freeze_tx = TokenFreezeTransaction() freeze_tx.set_token_id(token_id) @@ -101,9 +109,9 @@ def test_to_proto(mock_account_ids, mock_client): freeze_tx.transaction_id = generate_transaction_id(account_id) freeze_key = MagicMock() - freeze_key.sign.return_value = b'signature' - freeze_key.public_key().to_bytes_raw.return_value = b'public_key' - + freeze_key.sign.return_value = b"signature" + freeze_key.public_key().to_bytes_raw.return_value = b"public_key" + freeze_tx.freeze_with(mock_client) freeze_tx.sign(freeze_key) @@ -111,17 +119,18 @@ def test_to_proto(mock_account_ids, mock_client): assert proto.signedTransactionBytes assert len(proto.signedTransactionBytes) > 0 - + + def test_build_scheduled_body(mock_account_ids): """Test building a scheduled transaction body for token freeze transaction.""" _, freeze_id, _, token_id, _ = mock_account_ids - + freeze_tx = TokenFreezeTransaction() freeze_tx.set_token_id(token_id) freeze_tx.set_account_id(freeze_id) schedulable_body = freeze_tx.build_scheduled_body() - + # Verify the schedulable body has the correct structure and fields assert isinstance(schedulable_body, SchedulableTransactionBody) assert schedulable_body.HasField("tokenFreeze") diff --git a/tests/unit/token_grant_kyc_transaction_test.py b/tests/unit/token_grant_kyc_transaction_test.py index 804816d16..46d5cd67e 100644 --- a/tests/unit/token_grant_kyc_transaction_test.py +++ b/tests/unit/token_grant_kyc_transaction_test.py @@ -1,53 +1,53 @@ +from __future__ import annotations + import pytest +from hiero_sdk_python.account.account_id import AccountId +from hiero_sdk_python.hapi.services import response_header_pb2, response_pb2, transaction_get_receipt_pb2 +from hiero_sdk_python.hapi.services.schedulable_transaction_body_pb2 import ( + SchedulableTransactionBody, +) from hiero_sdk_python.hapi.services.token_grant_kyc_pb2 import TokenGrantKycTransactionBody from hiero_sdk_python.hapi.services.transaction_receipt_pb2 import TransactionReceipt as TransactionReceiptProto from hiero_sdk_python.hapi.services.transaction_response_pb2 import TransactionResponse as TransactionResponseProto from hiero_sdk_python.response_code import ResponseCode from hiero_sdk_python.tokens.token_grant_kyc_transaction import TokenGrantKycTransaction -from hiero_sdk_python.hapi.services import response_header_pb2, response_pb2, transaction_get_receipt_pb2 -from hiero_sdk_python.hapi.services.schedulable_transaction_body_pb2 import ( - SchedulableTransactionBody, -) -from hiero_sdk_python.account.account_id import AccountId from hiero_sdk_python.tokens.token_id import TokenId - from tests.unit.mock_server import mock_hedera_servers + pytestmark = pytest.mark.unit + def test_build_transaction_body(mock_account_ids): """Test building a token grant KYC transaction body with valid values.""" account_id, _, node_account_id, token_id, _ = mock_account_ids - grant_kyc_tx = ( - TokenGrantKycTransaction() - .set_token_id(token_id) - .set_account_id(account_id) - ) - + grant_kyc_tx = TokenGrantKycTransaction().set_token_id(token_id).set_account_id(account_id) + # Set operator and node account IDs needed for building transaction body grant_kyc_tx.operator_account_id = account_id grant_kyc_tx.node_account_id = node_account_id - + transaction_body = grant_kyc_tx.build_transaction_body() assert transaction_body.tokenGrantKyc.token == token_id._to_proto() assert transaction_body.tokenGrantKyc.account == account_id._to_proto() + def test_build_transaction_body_validation(mock_account_ids): """Test validation when building transaction body.""" account_id, _, _, token_id, _ = mock_account_ids # Test missing token ID grant_kyc_tx = TokenGrantKycTransaction(account_id=account_id) - + with pytest.raises(ValueError, match="Missing token ID"): grant_kyc_tx.build_transaction_body() # Test missing account ID grant_kyc_tx = TokenGrantKycTransaction(token_id=token_id) - + with pytest.raises(ValueError, match="Missing account ID"): grant_kyc_tx.build_transaction_body() @@ -56,42 +56,42 @@ def test_constructor_with_parameters(mock_account_ids): """Test creating a token grant KYC transaction with constructor parameters.""" account_id, _, _, token_id, _ = mock_account_ids - grant_kyc_tx = TokenGrantKycTransaction( - token_id=token_id, - account_id=account_id - ) + grant_kyc_tx = TokenGrantKycTransaction(token_id=token_id, account_id=account_id) assert grant_kyc_tx.token_id == token_id assert grant_kyc_tx.account_id == account_id + def test_set_methods(mock_account_ids): """Test the set methods of TokenGrantKycTransaction.""" account_id, _, _, token_id, _ = mock_account_ids grant_kyc_tx = TokenGrantKycTransaction() - + # Test method chaining tx_after_set = grant_kyc_tx.set_token_id(token_id) assert tx_after_set is grant_kyc_tx assert grant_kyc_tx.token_id == token_id - + tx_after_set = grant_kyc_tx.set_account_id(account_id) assert tx_after_set is grant_kyc_tx assert grant_kyc_tx.account_id == account_id + def test_set_methods_require_not_frozen(mock_account_ids, mock_client): """Test that set methods raise exception when transaction is frozen.""" account_id, _, _, token_id, _ = mock_account_ids grant_kyc_tx = TokenGrantKycTransaction(token_id=token_id, account_id=account_id) grant_kyc_tx.freeze_with(mock_client) - + with pytest.raises(Exception, match="Transaction is immutable; it has been frozen"): grant_kyc_tx.set_token_id(token_id) - + with pytest.raises(Exception, match="Transaction is immutable; it has been frozen"): grant_kyc_tx.set_account_id(account_id) + def test_grant_kyc_transaction_can_execute(mock_account_ids): """Test that a grant KYC transaction can be executed successfully.""" account_id, _, _, token_id, _ = mock_account_ids @@ -99,73 +99,64 @@ def test_grant_kyc_transaction_can_execute(mock_account_ids): # Create test transaction responses ok_response = TransactionResponseProto() ok_response.nodeTransactionPrecheckCode = ResponseCode.OK - + # Create a mock receipt for a successful token grant KYC - mock_receipt_proto = TransactionReceiptProto( - status=ResponseCode.SUCCESS - ) - + mock_receipt_proto = TransactionReceiptProto(status=ResponseCode.SUCCESS) + # Create a response for the receipt query receipt_query_response = response_pb2.Response( transactionGetReceipt=transaction_get_receipt_pb2.TransactionGetReceiptResponse( - header=response_header_pb2.ResponseHeader( - nodeTransactionPrecheckCode=ResponseCode.OK - ), - receipt=mock_receipt_proto + header=response_header_pb2.ResponseHeader(nodeTransactionPrecheckCode=ResponseCode.OK), + receipt=mock_receipt_proto, ) ) - + response_sequences = [ [ok_response, receipt_query_response], ] - + with mock_hedera_servers(response_sequences) as client: - transaction = ( - TokenGrantKycTransaction() - .set_token_id(token_id) - .set_account_id(account_id) - ) - + transaction = TokenGrantKycTransaction().set_token_id(token_id).set_account_id(account_id) + receipt = transaction.execute(client) - + assert receipt.status == ResponseCode.SUCCESS, "Transaction should have succeeded" + def test_grant_kyc_transaction_from_proto(mock_account_ids): """Test that a grant KYC transaction can be created from a protobuf object.""" account_id, _, _, token_id, _ = mock_account_ids # Create protobuf object - proto = TokenGrantKycTransactionBody( - token=token_id._to_proto(), - account=account_id._to_proto() - ) - + proto = TokenGrantKycTransactionBody(token=token_id._to_proto(), account=account_id._to_proto()) + # Deserialize the protobuf object from_proto = TokenGrantKycTransaction()._from_proto(proto) - + # Verify deserialized transaction matches original data assert from_proto.token_id == token_id assert from_proto.account_id == account_id - + # Deserialize empty protobuf from_proto = TokenGrantKycTransaction()._from_proto(TokenGrantKycTransactionBody()) - + # Verify empty protobuf deserializes to empty/default values - assert from_proto.token_id == TokenId(0,0,0) + assert from_proto.token_id == TokenId(0, 0, 0) assert from_proto.account_id == AccountId() - + + def test_build_scheduled_body(mock_account_ids): """Test building a scheduled transaction body for token grant KYC transaction.""" account_id, _, _, token_id, _ = mock_account_ids - + grant_kyc_tx = TokenGrantKycTransaction() grant_kyc_tx.set_token_id(token_id) grant_kyc_tx.set_account_id(account_id) schedulable_body = grant_kyc_tx.build_scheduled_body() - + # Verify the schedulable body has the correct structure and fields assert isinstance(schedulable_body, SchedulableTransactionBody) assert schedulable_body.HasField("tokenGrantKyc") assert schedulable_body.tokenGrantKyc.token == token_id._to_proto() - assert schedulable_body.tokenGrantKyc.account == account_id._to_proto() \ No newline at end of file + assert schedulable_body.tokenGrantKyc.account == account_id._to_proto() diff --git a/tests/unit/token_id_test.py b/tests/unit/token_id_test.py index 6b8189f88..f6debf61d 100644 --- a/tests/unit/token_id_test.py +++ b/tests/unit/token_id_test.py @@ -1,13 +1,17 @@ +from __future__ import annotations + import pytest from hiero_sdk_python.tokens.token_id import TokenId + pytestmark = pytest.mark.unit + def test_create_token_id_from_string(): """Should correctly create TokenId from string input, with and without checksum.""" # Without checksum - token_id = TokenId.from_string('0.0.1') + token_id = TokenId.from_string("0.0.1") assert token_id.shard == 0 assert token_id.realm == 0 @@ -15,36 +19,38 @@ def test_create_token_id_from_string(): assert token_id.checksum is None # With checksum - token_id = TokenId.from_string('0.0.1-dfkxr') + token_id = TokenId.from_string("0.0.1-dfkxr") assert token_id.shard == 0 assert token_id.realm == 0 assert token_id.num == 1 - assert token_id.checksum == 'dfkxr' + assert token_id.checksum == "dfkxr" + @pytest.mark.parametrize( - 'invalid_id', + "invalid_id", [ - '1.2', # Too few parts - '1.2.3.4', # Too many parts - 'a.b.c', # Non-numeric parts - '', # Empty string - '1.a.3', # Partial numeric + "1.2", # Too few parts + "1.2.3.4", # Too many parts + "a.b.c", # Non-numeric parts + "", # Empty string + "1.a.3", # Partial numeric 123, None, - '0.0.-1', - 'abc.def.ghi', - '0.0.1-ad', - '0.0.1-addefgh', - '0.0.1 - abcde', - ' 0.0.100 ' - ] + "0.0.-1", + "abc.def.ghi", + "0.0.1-ad", + "0.0.1-addefgh", + "0.0.1 - abcde", + " 0.0.100 ", + ], ) def test_from_string_for_invalid_format(invalid_id): """Should raise error when creating TokenId from invalid string input.""" with pytest.raises((TypeError, ValueError)): TokenId.from_string(invalid_id) + def test_str_representaion_with_checksum(mock_client): """Should return string with checksum when ledger id is provided.""" client = mock_client @@ -53,6 +59,7 @@ def test_str_representaion_with_checksum(mock_client): token_id = TokenId.from_string("0.0.1") assert token_id.to_string_with_checksum(client) == "0.0.1-dfkxr" + def test_validate_checksum_success(mock_client): """Should pass checksum validation when checksum is correct.""" client = mock_client @@ -61,6 +68,7 @@ def test_validate_checksum_success(mock_client): token_id.validate_checksum(client) + def test_validate_checksum_failure(mock_client): """Should raise ValueError if checksum validation fails.""" client = mock_client @@ -70,11 +78,12 @@ def test_validate_checksum_failure(mock_client): with pytest.raises(ValueError): token_id.validate_checksum(client) + def test_token_id_repr(): """Should return constructor-style representation without checksum.""" token_id = TokenId(0, 0, 123) assert repr(token_id) == "TokenId(shard=0, realm=0, num=123)" - + # Test with different values token_id2 = TokenId(1, 2, 456) - assert repr(token_id2) == "TokenId(shard=1, realm=2, num=456)" \ No newline at end of file + assert repr(token_id2) == "TokenId(shard=1, realm=2, num=456)" diff --git a/tests/unit/token_info_query_test.py b/tests/unit/token_info_query_test.py index 3387badca..69c412330 100644 --- a/tests/unit/token_info_query_test.py +++ b/tests/unit/token_info_query_test.py @@ -1,49 +1,56 @@ -import pytest +from __future__ import annotations + from unittest.mock import Mock -from hiero_sdk_python.hapi.services.query_header_pb2 import ResponseType -from hiero_sdk_python.query.token_info_query import TokenInfoQuery -from hiero_sdk_python.response_code import ResponseCode +import pytest + from hiero_sdk_python.hapi.services import ( - response_pb2, response_header_pb2, + response_pb2, token_get_info_pb2, ) - +from hiero_sdk_python.hapi.services.query_header_pb2 import ResponseType +from hiero_sdk_python.query.token_info_query import TokenInfoQuery +from hiero_sdk_python.response_code import ResponseCode from tests.unit.mock_server import mock_hedera_servers + pytestmark = pytest.mark.unit + # This test uses fixture token_id as parameter def test_constructor(token_id): """Test initialization of TokenInfoQuery.""" query = TokenInfoQuery() assert query.token_id is None - + query = TokenInfoQuery(token_id) assert query.token_id == token_id + # This test uses fixture mock_client as parameter def test_execute_fails_with_missing_token_id(mock_client): """Test request creation with missing Token ID.""" query = TokenInfoQuery() - + with pytest.raises(ValueError, match="Token ID must be set before making the request."): query.execute(mock_client) + def test_get_method(): """Test retrieving the gRPC method for the query.""" query = TokenInfoQuery() - + mock_channel = Mock() mock_token_stub = Mock() mock_channel.token = mock_token_stub - + method = query._get_method(mock_channel) - + assert method.transaction is None assert method.query == mock_token_stub.getTokenInfo + # This test uses fixture (mock_account_ids, private_key) as parameter def test_token_info_query_execute(mock_account_ids, private_key): """Test basic functionality of TokenInfoQuery with mock server.""" @@ -68,47 +75,41 @@ def test_token_info_query_execute(mock_account_ids, private_key): response_pb2.Response( tokenGetInfo=token_get_info_pb2.TokenGetInfoResponse( header=response_header_pb2.ResponseHeader( - nodeTransactionPrecheckCode=ResponseCode.OK, - responseType=ResponseType.COST_ANSWER, - cost=2 + nodeTransactionPrecheckCode=ResponseCode.OK, responseType=ResponseType.COST_ANSWER, cost=2 ) ) ), response_pb2.Response( tokenGetInfo=token_get_info_pb2.TokenGetInfoResponse( header=response_header_pb2.ResponseHeader( - nodeTransactionPrecheckCode=ResponseCode.OK, - responseType=ResponseType.COST_ANSWER, - cost=2 + nodeTransactionPrecheckCode=ResponseCode.OK, responseType=ResponseType.COST_ANSWER, cost=2 ) ) ), response_pb2.Response( tokenGetInfo=token_get_info_pb2.TokenGetInfoResponse( header=response_header_pb2.ResponseHeader( - nodeTransactionPrecheckCode=ResponseCode.OK, - responseType=ResponseType.ANSWER_ONLY, - cost=2 + nodeTransactionPrecheckCode=ResponseCode.OK, responseType=ResponseType.ANSWER_ONLY, cost=2 ), - tokenInfo=token_info_response + tokenInfo=token_info_response, ) - ) + ), ] - + response_sequences = [responses] - + with mock_hedera_servers(response_sequences) as client: query = TokenInfoQuery(token_id) - + try: # Get the cost of executing the query - should be 2 tinybars based on the mock response cost = query.get_cost(client) assert cost.to_tinybars() == 2 - + result = query.execute(client) except Exception as e: pytest.fail(f"Unexpected exception raised: {e}") - + assert result.token_id == token_id assert result.name == "Test Token" assert result.symbol == "TEST" @@ -122,5 +123,5 @@ def test_token_info_query_execute(mock_account_ids, private_key): assert result.admin_key.to_bytes_raw() == private_key.public_key().to_bytes_raw() assert result.kyc_key.to_bytes_raw() == private_key.public_key().to_bytes_raw() assert result.wipe_key.to_bytes_raw() == private_key.public_key().to_bytes_raw() - assert result.supply_key == None - assert result.freeze_key == None \ No newline at end of file + assert result.supply_key is None + assert result.freeze_key is None diff --git a/tests/unit/token_info_test.py b/tests/unit/token_info_test.py index 386b536bd..b98af0b15 100644 --- a/tests/unit/token_info_test.py +++ b/tests/unit/token_info_test.py @@ -1,20 +1,24 @@ -import pytest +from __future__ import annotations from dataclasses import FrozenInstanceError, replace +import pytest + import hiero_sdk_python.hapi.services.basic_types_pb2 -from hiero_sdk_python.tokens.token_info import TokenInfo, TokenId, AccountId, Timestamp from hiero_sdk_python.crypto.private_key import PrivateKey from hiero_sdk_python.Duration import Duration +from hiero_sdk_python.hapi.services.token_get_info_pb2 import TokenInfo as proto_TokenInfo from hiero_sdk_python.tokens.supply_type import SupplyType -from hiero_sdk_python.tokens.token_type import TokenType -from hiero_sdk_python.tokens.token_kyc_status import TokenKycStatus from hiero_sdk_python.tokens.token_freeze_status import TokenFreezeStatus +from hiero_sdk_python.tokens.token_info import AccountId, Timestamp, TokenId, TokenInfo +from hiero_sdk_python.tokens.token_kyc_status import TokenKycStatus from hiero_sdk_python.tokens.token_pause_status import TokenPauseStatus -from hiero_sdk_python.hapi.services.token_get_info_pb2 import TokenInfo as proto_TokenInfo +from hiero_sdk_python.tokens.token_type import TokenType + pytestmark = pytest.mark.unit + @pytest.fixture def token_info(): return TokenInfo( @@ -29,12 +33,13 @@ def token_info(): token_type=TokenType.FUNGIBLE_COMMON, max_supply=10000000, ledger_id=b"ledger123", - metadata=b"Test metadata" + metadata=b"Test metadata", ) + @pytest.fixture def proto_token_info(): - proto = proto_TokenInfo( + return proto_TokenInfo( tokenId=TokenId(0, 0, 100)._to_proto(), name="TestToken", symbol="TST", @@ -47,9 +52,9 @@ def proto_token_info(): maxSupply=10000000, ledger_id=b"ledger123", supplyType=SupplyType.FINITE.value, - metadata=b"Test metadata" + metadata=b"Test metadata", ) - return proto + def test_token_info_initialization(token_info): assert token_info.token_id == TokenId(0, 0, 100) @@ -79,11 +84,13 @@ def test_token_info_initialization(token_info): assert token_info.expiry is None assert token_info.pause_key is None + def test_token_info_is_immutable(token_info): """TokenInfo deve essere immutabile (dataclass frozen).""" with pytest.raises(FrozenInstanceError): token_info.name = "Changed" + def test_from_proto(proto_token_info): public_key = PrivateKey.generate_ed25519().public_key() proto_token_info.adminKey.ed25519 = public_key.to_bytes_raw() @@ -130,6 +137,7 @@ def test_from_proto(proto_token_info): assert token_info.pause_status == TokenPauseStatus.PAUSED assert token_info.supply_type == SupplyType.INFINITE + def test_to_proto(token_info): public_key = PrivateKey.generate_ed25519().public_key() @@ -180,6 +188,7 @@ def test_to_proto(token_info): assert proto.pause_key.ed25519 == public_key.to_bytes_raw() assert proto.pause_status == TokenPauseStatus.PAUSED + def test_str_representation(token_info): expected = ( f"TokenInfo(token_id={token_info.token_id}, name={token_info.name!r}, " diff --git a/tests/unit/token_mint_transaction_test.py b/tests/unit/token_mint_transaction_test.py index 0945d720b..b617dcfd7 100644 --- a/tests/unit/token_mint_transaction_test.py +++ b/tests/unit/token_mint_transaction_test.py @@ -1,21 +1,19 @@ +from __future__ import annotations + from unittest.mock import MagicMock import pytest -from cryptography.hazmat.primitives import serialization from hiero_sdk_python.hapi.services import ( - basic_types_pb2, timestamp_pb2, - token_mint_pb2, - transaction_pb2, ) from hiero_sdk_python.hapi.services.schedulable_transaction_body_pb2 import ( SchedulableTransactionBody, ) -from hiero_sdk_python.response_code import ResponseCode from hiero_sdk_python.tokens.token_mint_transaction import TokenMintTransaction from hiero_sdk_python.transaction.transaction_id import TransactionId + pytestmark = pytest.mark.unit @@ -27,12 +25,9 @@ def generate_transaction_id(account_id_proto): timestamp_seconds = int(current_time) timestamp_nanos = int((current_time - timestamp_seconds) * 1e9) - tx_timestamp = timestamp_pb2.Timestamp( - seconds=timestamp_seconds, nanos=timestamp_nanos - ) + tx_timestamp = timestamp_pb2.Timestamp(seconds=timestamp_seconds, nanos=timestamp_nanos) - tx_id = TransactionId(valid_start=tx_timestamp, account_id=account_id_proto) - return tx_id + return TransactionId(valid_start=tx_timestamp, account_id=account_id_proto) # This test uses fixture mock_account_ids as parameter @@ -72,9 +67,7 @@ def test_build_transaction_body_fungible(mock_account_ids, amount): assert transaction_body.tokenMint.token.realmNum == 1 assert transaction_body.tokenMint.token.tokenNum == 1 assert transaction_body.tokenMint.amount == amount - assert ( - len(transaction_body.tokenMint.metadata) == 0 - ) # No metadata for fungible tokens + assert len(transaction_body.tokenMint.metadata) == 0 # No metadata for fungible tokens # This test uses fixtures (mock_account_ids, metadata) as parameters @@ -136,9 +129,7 @@ def test_build_nft_transaction_body_invalid_metadata_type(mock_account_ids): mint_tx = TokenMintTransaction() mint_tx.set_token_id(token_id) mint_tx.set_metadata(metadata) - with pytest.raises( - ValueError, match="Metadata must be a list of byte arrays for NFTs." - ): + with pytest.raises(ValueError, match="Metadata must be a list of byte arrays for NFTs."): mint_tx.build_transaction_body() @@ -156,9 +147,7 @@ def test_build_nft_transaction_body_empty_metadata(mock_account_ids): # This test uses fixtures (mock_account_ids, amount, metadata) as parameters -def test_build_transaction_body_both_amount_and_metadata( - mock_account_ids, amount, metadata -): +def test_build_transaction_body_both_amount_and_metadata(mock_account_ids, amount, metadata): """Test that setting both amount and metadata raises a ValueError.""" payer_account, _, node_account_id, token_id, _ = mock_account_ids diff --git a/tests/unit/token_nft_allowance_test.py b/tests/unit/token_nft_allowance_test.py index 8b4004b67..df2ce71b2 100644 --- a/tests/unit/token_nft_allowance_test.py +++ b/tests/unit/token_nft_allowance_test.py @@ -2,6 +2,8 @@ Unit tests for the TokenNftAllowance class. """ +from __future__ import annotations + import pytest from google.protobuf.wrappers_pb2 import BoolValue @@ -15,6 +17,7 @@ from hiero_sdk_python.tokens.token_id import TokenId from hiero_sdk_python.tokens.token_nft_allowance import TokenNftAllowance + pytestmark = pytest.mark.unit @@ -39,7 +42,7 @@ def proto_nft_allowance(): spender_account_id = AccountId(0, 0, 300) delegating_spender = AccountId(0, 0, 400) - proto = NftAllowanceProto( + return NftAllowanceProto( tokenId=token_id._to_proto(), owner=owner_account_id._to_proto(), spender=spender_account_id._to_proto(), @@ -47,7 +50,6 @@ def proto_nft_allowance(): approved_for_all=BoolValue(value=True), delegating_spender=delegating_spender._to_proto(), ) - return proto @pytest.fixture @@ -56,12 +58,11 @@ def proto_nft_remove_allowance(): token_id = TokenId(0, 0, 100) owner_account_id = AccountId(0, 0, 200) - proto = NftRemoveAllowanceProto( + return NftRemoveAllowanceProto( token_id=token_id._to_proto(), owner=owner_account_id._to_proto(), serial_numbers=[1, 2, 3], ) - return proto def test_token_nft_allowance_initialization(token_nft_allowance): diff --git a/tests/unit/token_nft_info_query_test.py b/tests/unit/token_nft_info_query_test.py index 6d3374f4b..5005d29c5 100644 --- a/tests/unit/token_nft_info_query_test.py +++ b/tests/unit/token_nft_info_query_test.py @@ -1,116 +1,108 @@ -import pytest +from __future__ import annotations + from unittest.mock import Mock -from hiero_sdk_python.hapi.services.query_header_pb2 import ResponseType -from hiero_sdk_python.query.token_nft_info_query import TokenNftInfoQuery -from hiero_sdk_python.response_code import ResponseCode +import pytest + from hiero_sdk_python.hapi.services import ( - response_pb2, + basic_types_pb2, response_header_pb2, - timestamp_pb2, + response_pb2, + timestamp_pb2, token_get_nft_info_pb2, - basic_types_pb2 ) - +from hiero_sdk_python.hapi.services.query_header_pb2 import ResponseType +from hiero_sdk_python.query.token_nft_info_query import TokenNftInfoQuery +from hiero_sdk_python.response_code import ResponseCode from tests.unit.mock_server import mock_hedera_servers + pytestmark = pytest.mark.unit + # This test uses fixture nft_id as parameter def test_constructor(nft_id): """Test initialization of TokenNftInfoQuery.""" query = TokenNftInfoQuery() assert query.nft_id is None - + query = TokenNftInfoQuery(nft_id) assert query.nft_id == nft_id + # This test uses fixture mock_client as parameter def test_execute_without_nft_id(mock_client): """Test request creation with missing NFT ID.""" query = TokenNftInfoQuery() - + with pytest.raises(ValueError, match="NFT ID must be set before making the request."): query.execute(mock_client) + def test_get_method(): """Test retrieving the gRPC method for the query.""" query = TokenNftInfoQuery() - + mock_channel = Mock() mock_token_stub = Mock() mock_channel.token = mock_token_stub - + method = query._get_method(mock_channel) - + assert method.transaction is None assert method.query == mock_token_stub.getTokenNftInfo + # This test uses fixture nft_id as parameter def test_execute_token_nft_info_query(nft_id): """Test basic functionality of TokenNftInfoQuery with mock server.""" nft_info_response = token_get_nft_info_pb2.TokenNftInfo( nftID=basic_types_pb2.NftID( - token_ID=basic_types_pb2.TokenID( - shardNum=0, - realmNum=0, - tokenNum=1 - ), - serial_number=2 - ), - accountID=basic_types_pb2.AccountID( - shardNum=0, - realmNum=0, - accountNum=3 + token_ID=basic_types_pb2.TokenID(shardNum=0, realmNum=0, tokenNum=1), serial_number=2 ), + accountID=basic_types_pb2.AccountID(shardNum=0, realmNum=0, accountNum=3), creationTime=timestamp_pb2.Timestamp(seconds=1623456789), - metadata=b'metadata' + metadata=b"metadata", ) responses = [ response_pb2.Response( tokenGetNftInfo=token_get_nft_info_pb2.TokenGetNftInfoResponse( header=response_header_pb2.ResponseHeader( - nodeTransactionPrecheckCode=ResponseCode.OK, - responseType=ResponseType.COST_ANSWER, - cost=2 + nodeTransactionPrecheckCode=ResponseCode.OK, responseType=ResponseType.COST_ANSWER, cost=2 ) ) ), response_pb2.Response( tokenGetNftInfo=token_get_nft_info_pb2.TokenGetNftInfoResponse( header=response_header_pb2.ResponseHeader( - nodeTransactionPrecheckCode=ResponseCode.OK, - responseType=ResponseType.COST_ANSWER, - cost=2 + nodeTransactionPrecheckCode=ResponseCode.OK, responseType=ResponseType.COST_ANSWER, cost=2 ) ) ), response_pb2.Response( tokenGetNftInfo=token_get_nft_info_pb2.TokenGetNftInfoResponse( header=response_header_pb2.ResponseHeader( - nodeTransactionPrecheckCode=ResponseCode.OK, - responseType=ResponseType.ANSWER_ONLY, - cost=2 + nodeTransactionPrecheckCode=ResponseCode.OK, responseType=ResponseType.ANSWER_ONLY, cost=2 ), - nft=nft_info_response + nft=nft_info_response, ) - ) + ), ] - + response_sequences = [responses] - + with mock_hedera_servers(response_sequences) as client: query = TokenNftInfoQuery().set_nft_id(nft_id) - + try: # Get the cost of executing the query - should be 2 tinybars based on the mock response cost = query.get_cost(client) assert cost.to_tinybars() == 2 - + result = query.execute(client) except Exception as e: pytest.fail(f"Unexpected exception raised: {e}") - + # Verify the result contains the expected values assert result.nft_id.token_id.shard == 0 assert result.nft_id.token_id.realm == 0 @@ -119,4 +111,4 @@ def test_execute_token_nft_info_query(nft_id): assert result.account_id.shard == 0 assert result.account_id.realm == 0 assert result.account_id.num == 3 - assert result.metadata == b'metadata' \ No newline at end of file + assert result.metadata == b"metadata" diff --git a/tests/unit/token_nft_info_test.py b/tests/unit/token_nft_info_test.py index 6a648d545..5816c3b6f 100644 --- a/tests/unit/token_nft_info_test.py +++ b/tests/unit/token_nft_info_test.py @@ -1,13 +1,14 @@ +from __future__ import annotations + import pytest +from hiero_sdk_python.hapi.services import basic_types_pb2, timestamp_pb2, token_get_nft_info_pb2 from hiero_sdk_python.tokens.token_nft_info import TokenNftInfo -from hiero_sdk_python.tokens.nft_id import NftId -from hiero_sdk_python.tokens.token_id import TokenId -from hiero_sdk_python.account.account_id import AccountId -from hiero_sdk_python.hapi.services import token_get_nft_info_pb2, timestamp_pb2, basic_types_pb2 + pytestmark = pytest.mark.unit + # This test uses fixture (mock_account_ids,nft_id) as parameter def test_token_nft_info_constructor(mock_account_ids, nft_id): """Test the TokenNftInfo constructor with various parameters""" @@ -15,23 +16,19 @@ def test_token_nft_info_constructor(mock_account_ids, nft_id): account_id, spender_id, _, _, _ = mock_account_ids creation_time = 1623456789 metadata = b"test metadata" - + # Test with all parameters token_nft_info = TokenNftInfo( - nft_id=nft_id, - account_id=account_id, - creation_time=creation_time, - metadata=metadata, - spender_id=spender_id + nft_id=nft_id, account_id=account_id, creation_time=creation_time, metadata=metadata, spender_id=spender_id ) - + # Verify all fields were set correctly assert token_nft_info.nft_id == nft_id assert token_nft_info.account_id == account_id assert token_nft_info.creation_time == creation_time assert token_nft_info.metadata == metadata assert token_nft_info.spender_id == spender_id - + # Test with default parameters empty_token_nft_info = TokenNftInfo() assert empty_token_nft_info.nft_id is None @@ -40,110 +37,92 @@ def test_token_nft_info_constructor(mock_account_ids, nft_id): assert empty_token_nft_info.metadata is None assert empty_token_nft_info.spender_id is None + def test_from_proto(): """Test creating a TokenNftInfo from a protobuf object""" # Create a mock protobuf proto = token_get_nft_info_pb2.TokenNftInfo( nftID=basic_types_pb2.NftID( - token_ID=basic_types_pb2.TokenID( - shardNum=0, - realmNum=0, - tokenNum=123 - ), - serial_number=456 - ), - accountID=basic_types_pb2.AccountID( - shardNum=0, - realmNum=0, - accountNum=789 + token_ID=basic_types_pb2.TokenID(shardNum=0, realmNum=0, tokenNum=123), serial_number=456 ), + accountID=basic_types_pb2.AccountID(shardNum=0, realmNum=0, accountNum=789), creationTime=timestamp_pb2.Timestamp(seconds=1623456789), metadata=b"test metadata", - spender_id=basic_types_pb2.AccountID( - shardNum=0, - realmNum=0, - accountNum=101112 - ) + spender_id=basic_types_pb2.AccountID(shardNum=0, realmNum=0, accountNum=101112), ) - + # Create TokenNftInfo from proto token_nft_info = TokenNftInfo._from_proto(proto) - + # Verify fields assert token_nft_info.nft_id.token_id.shard == 0 assert token_nft_info.nft_id.token_id.realm == 0 assert token_nft_info.nft_id.token_id.num == 123 assert token_nft_info.nft_id.serial_number == 456 - + assert token_nft_info.account_id.shard == 0 assert token_nft_info.account_id.realm == 0 assert token_nft_info.account_id.num == 789 - + assert token_nft_info.creation_time == 1623456789 assert token_nft_info.metadata == b"test metadata" - + assert token_nft_info.spender_id.shard == 0 assert token_nft_info.spender_id.realm == 0 assert token_nft_info.spender_id.num == 101112 + # This test uses fixture (mock_account_ids, nft_id) as parameter def test_to_proto(mock_account_ids, nft_id): """Test converting TokenNftInfo to a protobuf object""" account_id, spender_id, _, _, _ = mock_account_ids creation_time = 1623456789 metadata = b"test metadata" - + # Create TokenNftInfo instance token_nft_info = TokenNftInfo( - nft_id=nft_id, - account_id=account_id, - creation_time=creation_time, - metadata=metadata, - spender_id=spender_id + nft_id=nft_id, account_id=account_id, creation_time=creation_time, metadata=metadata, spender_id=spender_id ) - + # Convert to protobuf proto = token_nft_info._to_proto() - + # Verify protobuf fields assert proto.nftID.token_ID.shardNum == nft_id.token_id.shard assert proto.nftID.token_ID.realmNum == nft_id.token_id.realm assert proto.nftID.token_ID.tokenNum == nft_id.token_id.num assert proto.nftID.serial_number == nft_id.serial_number - + assert proto.accountID.shardNum == account_id.shard assert proto.accountID.realmNum == account_id.realm assert proto.accountID.accountNum == account_id.num - + assert proto.creationTime.seconds == creation_time assert proto.metadata == metadata - + assert proto.spender_id.shardNum == spender_id.shard assert proto.spender_id.realmNum == spender_id.realm assert proto.spender_id.accountNum == spender_id.num + # This test uses fixture (mock_account_ids, nft_id) as parameter def test_str_representation(mock_account_ids, nft_id): """Test the string representation of TokenNftInfo""" account_id, spender_id, _, _, _ = mock_account_ids creation_time = 1623456789 metadata = b"test metadata" - + # Create TokenNftInfo instance token_nft_info = TokenNftInfo( - nft_id=nft_id, - account_id=account_id, - creation_time=creation_time, - metadata=metadata, - spender_id=spender_id + nft_id=nft_id, account_id=account_id, creation_time=creation_time, metadata=metadata, spender_id=spender_id ) - + # Get string representation string_repr = str(token_nft_info) - + # Verify string contains all important fields assert str(nft_id) in string_repr assert str(account_id) in string_repr assert str(creation_time) in string_repr assert str(metadata) in string_repr - assert str(spender_id) in string_repr \ No newline at end of file + assert str(spender_id) in string_repr diff --git a/tests/unit/token_nft_transfer_test.py b/tests/unit/token_nft_transfer_test.py index eb9147970..e3dcb1773 100644 --- a/tests/unit/token_nft_transfer_test.py +++ b/tests/unit/token_nft_transfer_test.py @@ -1,93 +1,94 @@ -from hiero_sdk_python.tokens.token_id import TokenId +from __future__ import annotations + import pytest from hiero_sdk_python.hapi.services import basic_types_pb2 +from hiero_sdk_python.tokens.token_id import TokenId from hiero_sdk_python.tokens.token_nft_transfer import TokenNftTransfer + pytestmark = pytest.mark.unit + def test_token_nft_transfer_constructor(mock_account_ids): """Test the TokenNftTransfer constructor with various parameters""" sender_id, receiver_id, _, token_id, _ = mock_account_ids serial_number = 789 - + nft_transfer = TokenNftTransfer( - token_id=token_id, - sender_id=sender_id, - receiver_id=receiver_id, - serial_number=serial_number + token_id=token_id, sender_id=sender_id, receiver_id=receiver_id, serial_number=serial_number ) - + # Verify all fields were set correctly - + assert nft_transfer.token_id == token_id assert nft_transfer.sender_id == sender_id assert nft_transfer.receiver_id == receiver_id assert nft_transfer.serial_number == serial_number assert nft_transfer.is_approved is False - + # Test with explicit is_approved=True approved_transfer = TokenNftTransfer( - token_id=token_id, - sender_id=sender_id, - receiver_id=receiver_id, - serial_number=serial_number, - is_approved=True + token_id=token_id, sender_id=sender_id, receiver_id=receiver_id, serial_number=serial_number, is_approved=True ) - + assert nft_transfer.token_id == token_id assert approved_transfer.sender_id == sender_id assert approved_transfer.receiver_id == receiver_id assert approved_transfer.serial_number == serial_number assert approved_transfer.is_approved is True + def test_to_proto(mock_account_ids): """Test converting TokenNftTransfer to a protobuf object""" sender_id, receiver_id, _, token_id, _ = mock_account_ids serial_number = 789 is_approved = True - + nft_transfer = TokenNftTransfer( token_id=token_id, sender_id=sender_id, receiver_id=receiver_id, serial_number=serial_number, - is_approved=is_approved + is_approved=is_approved, ) - + # Convert to protobuf proto = nft_transfer._to_proto() - + # Verify protobuf fields assert proto.senderAccountID.shardNum == sender_id.shard assert proto.senderAccountID.realmNum == sender_id.realm assert proto.senderAccountID.accountNum == sender_id.num - + assert proto.receiverAccountID.shardNum == receiver_id.shard assert proto.receiverAccountID.realmNum == receiver_id.realm assert proto.receiverAccountID.accountNum == receiver_id.num - + assert proto.serialNumber == serial_number assert proto.is_approval is is_approved - + + def test_from_proto(mock_account_ids): """Test converting a protobuf object to a TokenNftTransfer""" sender_id, receiver_id, _, token_id, _ = mock_account_ids serial_number = 789 is_approved = True - + proto = basic_types_pb2.TokenTransferList( - token = TokenId._to_proto(token_id), - nftTransfers= [basic_types_pb2.NftTransfer( - senderAccountID=sender_id._to_proto(), - receiverAccountID=receiver_id._to_proto(), - serialNumber=serial_number, - is_approval=is_approved - )] + token=TokenId._to_proto(token_id), + nftTransfers=[ + basic_types_pb2.NftTransfer( + senderAccountID=sender_id._to_proto(), + receiverAccountID=receiver_id._to_proto(), + serialNumber=serial_number, + is_approval=is_approved, + ) + ], ) - + nft_transfer = TokenNftTransfer._from_proto(proto) - + assert nft_transfer[0].token_id == token_id assert nft_transfer[0].is_approved == is_approved assert nft_transfer[0].sender_id == sender_id diff --git a/tests/unit/token_pause_transaction_test.py b/tests/unit/token_pause_transaction_test.py index 114f42b75..2b804c9d8 100644 --- a/tests/unit/token_pause_transaction_test.py +++ b/tests/unit/token_pause_transaction_test.py @@ -1,27 +1,29 @@ -import pytest +from __future__ import annotations + from unittest.mock import MagicMock, Mock +import pytest from hiero_sdk_python.hapi.services import ( response_header_pb2, response_pb2, transaction_get_receipt_pb2, ) -from hiero_sdk_python.hapi.services.transaction_receipt_pb2 import TransactionReceipt as TransactionReceiptProto -from hiero_sdk_python.hapi.services.transaction_response_pb2 import TransactionResponse as TransactionResponseProto -from hiero_sdk_python.hapi.services.token_pause_pb2 import TokenPauseTransactionBody from hiero_sdk_python.hapi.services.schedulable_transaction_body_pb2 import ( SchedulableTransactionBody, ) -from hiero_sdk_python.tokens.token_pause_transaction import TokenPauseTransaction -from hiero_sdk_python.tokens.token_id import TokenId -from hiero_sdk_python.transaction.transaction_id import TransactionId - +from hiero_sdk_python.hapi.services.token_pause_pb2 import TokenPauseTransactionBody +from hiero_sdk_python.hapi.services.transaction_receipt_pb2 import TransactionReceipt as TransactionReceiptProto +from hiero_sdk_python.hapi.services.transaction_response_pb2 import TransactionResponse as TransactionResponseProto from hiero_sdk_python.response_code import ResponseCode +from hiero_sdk_python.tokens.token_id import TokenId +from hiero_sdk_python.tokens.token_pause_transaction import TokenPauseTransaction from tests.unit.mock_server import mock_hedera_servers + pytestmark = pytest.mark.unit + # This test uses fixture mock_account_ids and token_id as parameter def test_build_transaction_body(mock_account_ids, token_id): """Test building a fungible token pause transaction body with valid values.""" @@ -31,11 +33,12 @@ def test_build_transaction_body(mock_account_ids, token_id): pause_tx.operator_account_id = account_id pause_tx.node_account_id = node_account_id - transaction_body = pause_tx.build_transaction_body() # Will generate a transaction_id + transaction_body = pause_tx.build_transaction_body() # Will generate a transaction_id + + assert transaction_body.token_pause.token == token_id._to_proto() + assert transaction_body.transactionID == pause_tx.transaction_id._to_proto() + assert transaction_body.nodeAccountID == pause_tx.node_account_id._to_proto() - assert transaction_body.token_pause.token == token_id._to_proto() - assert transaction_body.transactionID == pause_tx.transaction_id._to_proto() - assert transaction_body.nodeAccountID == pause_tx.node_account_id._to_proto() def test_build_transaction_body_nft(mock_account_ids, nft_id): """Test building an NFT‐pause transaction body with valid values.""" @@ -51,24 +54,21 @@ def test_build_transaction_body_nft(mock_account_ids, nft_id): transaction_body = pause_tx.build_transaction_body() assert transaction_body.token_pause.token == base_token_id._to_proto() - assert transaction_body.transactionID == pause_tx.transaction_id._to_proto() - assert transaction_body.nodeAccountID == pause_tx.node_account_id._to_proto() + assert transaction_body.transactionID == pause_tx.transaction_id._to_proto() + assert transaction_body.nodeAccountID == pause_tx.node_account_id._to_proto() + # This test uses fixture (token_id, mock_client) as parameter def test__to_proto(token_id, mock_client): """Test converting the token pause transaction to protobuf format after signing.""" - - # Build the TokenPauseTransaction - pause_tx = ( - TokenPauseTransaction() - .set_token_id(token_id) - ) - + # Build the TokenPauseTransaction + pause_tx = TokenPauseTransaction().set_token_id(token_id) + # Create a fake pause key that returns certain bytes when sign() is called: pause_key = MagicMock() - pause_key.sign.return_value = b'signature' - pause_key.public_key().to_bytes_raw.return_value = b'public_key' - + pause_key.sign.return_value = b"signature" + pause_key.public_key().to_bytes_raw.return_value = b"public_key" + # Freeze and sign using the pause key, which also generates transaction_id: pause_tx.freeze_with(mock_client) pause_tx.sign(pause_key) @@ -79,6 +79,7 @@ def test__to_proto(token_id, mock_client): assert proto.signedTransactionBytes assert len(proto.signedTransactionBytes) > 0 + def test__from_proto_restores_token_id(): """ _from_proto() must deserialize TokenPauseTransactionBody → .token_id correctly. @@ -92,6 +93,7 @@ def test__from_proto_restores_token_id(): # Verify that tx.token_id matches TokenId(7, 8, 9) assert tx.token_id == TokenId(7, 8, 9) + @pytest.mark.parametrize("bad_token", [None, TokenId(0, 0, 0)]) def test_build_transaction_body_without_valid_token_id_raises(bad_token): """ @@ -104,63 +106,58 @@ def test_build_transaction_body_without_valid_token_id_raises(bad_token): with pytest.raises(ValueError, match="token_id must be set"): tx.build_transaction_body() + def test__get_method_points_to_pause_token(): """_get_method() should return pauseToken as the transaction RPC, and no query RPC.""" query = TokenPauseTransaction().set_token_id(TokenId(1, 2, 3)) - - mock_channel = Mock() + + mock_channel = Mock() mock_token_stub = Mock() mock_channel.token = mock_token_stub method = query._get_method(mock_channel) assert method.transaction is mock_token_stub.pauseToken - assert method.query is None + assert method.query is None + # This test uses fixture token_id as parameter def test_pause_transaction_can_execute(token_id): """Test that a pause transaction can be executed successfully.""" - # Create test transaction responses ok_response = TransactionResponseProto() ok_response.nodeTransactionPrecheckCode = ResponseCode.OK - + # Create a mock receipt for a successful token wipe - mock_receipt_proto = TransactionReceiptProto( - status=ResponseCode.SUCCESS - ) - + mock_receipt_proto = TransactionReceiptProto(status=ResponseCode.SUCCESS) + # Create a response for the receipt query receipt_query_response = response_pb2.Response( transactionGetReceipt=transaction_get_receipt_pb2.TransactionGetReceiptResponse( - header=response_header_pb2.ResponseHeader( - nodeTransactionPrecheckCode=ResponseCode.OK - ), - receipt=mock_receipt_proto + header=response_header_pb2.ResponseHeader(nodeTransactionPrecheckCode=ResponseCode.OK), + receipt=mock_receipt_proto, ) ) - + response_sequences = [ [ok_response, receipt_query_response], ] - + with mock_hedera_servers(response_sequences) as client: - transaction = ( - TokenPauseTransaction() - .set_token_id(token_id) - ) - + transaction = TokenPauseTransaction().set_token_id(token_id) + receipt = transaction.execute(client) - + assert receipt.status == ResponseCode.SUCCESS, "Transaction should have succeeded" + def test_build_scheduled_body(token_id): """Test building a scheduled transaction body for token pause transaction.""" pause_tx = TokenPauseTransaction().set_token_id(token_id) schedulable_body = pause_tx.build_scheduled_body() - + # Verify the schedulable body has the correct structure and fields assert isinstance(schedulable_body, SchedulableTransactionBody) assert schedulable_body.HasField("token_pause") - assert schedulable_body.token_pause.token == token_id._to_proto() \ No newline at end of file + assert schedulable_body.token_pause.token == token_id._to_proto() diff --git a/tests/unit/token_reject_transaction_test.py b/tests/unit/token_reject_transaction_test.py index 55c38f96e..b4306b403 100644 --- a/tests/unit/token_reject_transaction_test.py +++ b/tests/unit/token_reject_transaction_test.py @@ -1,47 +1,44 @@ +from __future__ import annotations + import pytest +from hiero_sdk_python.account.account_id import AccountId +from hiero_sdk_python.hapi.services import response_header_pb2, response_pb2, timestamp_pb2, transaction_get_receipt_pb2 +from hiero_sdk_python.hapi.services.schedulable_transaction_body_pb2 import ( + SchedulableTransactionBody, +) from hiero_sdk_python.hapi.services.token_reject_pb2 import TokenReference, TokenRejectTransactionBody from hiero_sdk_python.hapi.services.transaction_receipt_pb2 import TransactionReceipt as TransactionReceiptProto from hiero_sdk_python.hapi.services.transaction_response_pb2 import TransactionResponse as TransactionResponseProto from hiero_sdk_python.response_code import ResponseCode -from hiero_sdk_python.tokens.token_reject_transaction import TokenRejectTransaction from hiero_sdk_python.tokens.nft_id import NftId -from hiero_sdk_python.hapi.services import response_header_pb2, response_pb2, timestamp_pb2, transaction_get_receipt_pb2 -from hiero_sdk_python.hapi.services.schedulable_transaction_body_pb2 import ( - SchedulableTransactionBody, -) +from hiero_sdk_python.tokens.token_reject_transaction import TokenRejectTransaction from hiero_sdk_python.transaction.transaction_id import TransactionId -from hiero_sdk_python.account.account_id import AccountId - from tests.unit.mock_server import mock_hedera_servers + pytestmark = pytest.mark.unit + def generate_transaction_id(account_id_proto): """Generate a unique transaction ID based on the account ID and the current timestamp.""" import time + current_time = time.time() timestamp_seconds = int(current_time) timestamp_nanos = int((current_time - timestamp_seconds) * 1e9) tx_timestamp = timestamp_pb2.Timestamp(seconds=timestamp_seconds, nanos=timestamp_nanos) - tx_id = TransactionId( - valid_start=tx_timestamp, - account_id=account_id_proto - ) - return tx_id + return TransactionId(valid_start=tx_timestamp, account_id=account_id_proto) + def test_build_transaction_body_with_token_ids(mock_account_ids): """Test building a token reject transaction body with token IDs.""" account_id, owner_account_id, node_account_id, token_id, _ = mock_account_ids token_ids = [token_id] - reject_tx = ( - TokenRejectTransaction() - .set_owner_id(owner_account_id) - .set_token_ids(token_ids) - ) + reject_tx = TokenRejectTransaction().set_owner_id(owner_account_id).set_token_ids(token_ids) reject_tx.transaction_id = generate_transaction_id(account_id) reject_tx.node_account_id = node_account_id @@ -57,6 +54,7 @@ def test_build_transaction_body_with_token_ids(mock_account_ids): assert transaction_body.tokenReject.rejections[0].fungible_token.realmNum == token_id.realm assert transaction_body.tokenReject.rejections[0].fungible_token.tokenNum == token_id.num + def test_build_transaction_body_with_nft_ids(mock_account_ids): """Test building a token reject transaction body with NFT IDs.""" account_id, owner_account_id, node_account_id, token_id, _ = mock_account_ids @@ -64,11 +62,7 @@ def test_build_transaction_body_with_nft_ids(mock_account_ids): # Create NftId instances nft_ids = [NftId(token_id=token_id, serial_number=1), NftId(token_id=token_id, serial_number=2)] - reject_tx = ( - TokenRejectTransaction() - .set_owner_id(owner_account_id) - .set_nft_ids(nft_ids) - ) + reject_tx = TokenRejectTransaction().set_owner_id(owner_account_id).set_nft_ids(nft_ids) reject_tx.transaction_id = generate_transaction_id(account_id) reject_tx.node_account_id = node_account_id @@ -93,22 +87,20 @@ def test_build_transaction_body_with_nft_ids(mock_account_ids): assert transaction_body.tokenReject.rejections[1].nft.token_ID.tokenNum == token_id.num assert transaction_body.tokenReject.rejections[1].nft.serial_number == 2 + def test_constructor_with_parameters(mock_account_ids): """Test creating a token reject transaction with constructor parameters.""" _, owner_account_id, _, token_id, _ = mock_account_ids token_ids = [token_id] nft_ids = [NftId(token_id=token_id, serial_number=1)] - reject_tx = TokenRejectTransaction( - owner_id=owner_account_id, - token_ids=token_ids, - nft_ids=nft_ids - ) + reject_tx = TokenRejectTransaction(owner_id=owner_account_id, token_ids=token_ids, nft_ids=nft_ids) assert reject_tx.owner_id == owner_account_id assert reject_tx.token_ids == token_ids assert reject_tx.nft_ids == nft_ids + def test_set_methods(mock_account_ids): """Test the set methods of TokenRejectTransaction.""" _, owner_account_id, _, token_id, _ = mock_account_ids @@ -130,6 +122,7 @@ def test_set_methods(mock_account_ids): assert tx_after_set is reject_tx assert reject_tx.nft_ids == nft_ids + def test_set_methods_require_not_frozen(mock_account_ids, nft_id, mock_client): """Test the set methods of TokenRejectTransaction.""" _, owner_account_id, _, token_id, _ = mock_account_ids @@ -148,6 +141,7 @@ def test_set_methods_require_not_frozen(mock_account_ids, nft_id, mock_client): with pytest.raises(Exception, match="Transaction is immutable; it has been frozen"): reject_tx.set_nft_ids(nft_ids) + def test_reject_transaction_can_execute(mock_account_ids): """Test that a reject transaction can be executed successfully.""" account_id, owner_account_id, _, token_id, _ = mock_account_ids @@ -158,17 +152,13 @@ def test_reject_transaction_can_execute(mock_account_ids): ok_response.nodeTransactionPrecheckCode = ResponseCode.OK # Create a mock receipt for a successful token reject - mock_receipt_proto = TransactionReceiptProto( - status=ResponseCode.SUCCESS - ) + mock_receipt_proto = TransactionReceiptProto(status=ResponseCode.SUCCESS) # Create a response for the receipt query receipt_query_response = response_pb2.Response( transactionGetReceipt=transaction_get_receipt_pb2.TransactionGetReceiptResponse( - header=response_header_pb2.ResponseHeader( - nodeTransactionPrecheckCode=ResponseCode.OK - ), - receipt=mock_receipt_proto + header=response_header_pb2.ResponseHeader(nodeTransactionPrecheckCode=ResponseCode.OK), + receipt=mock_receipt_proto, ) ) @@ -177,16 +167,13 @@ def test_reject_transaction_can_execute(mock_account_ids): ] with mock_hedera_servers(response_sequences) as client: - transaction = ( - TokenRejectTransaction() - .set_owner_id(owner_account_id) - .set_token_ids(token_ids) - ) + transaction = TokenRejectTransaction().set_owner_id(owner_account_id).set_token_ids(token_ids) receipt = transaction.execute(client) assert receipt.status == ResponseCode.SUCCESS, "Transaction should have succeeded" + def test_reject_transaction_from_proto(mock_account_ids): """Test that a reject transaction can be created from a protobuf object.""" _, owner_account_id, _, token_id, _ = mock_account_ids @@ -198,8 +185,8 @@ def test_reject_transaction_from_proto(mock_account_ids): owner=owner_account_id._to_proto(), rejections=[ TokenReference(fungible_token=token_id._to_proto(), nft=None), - TokenReference(fungible_token=None, nft=nft_ids[0]._to_proto()) - ] + TokenReference(fungible_token=None, nft=nft_ids[0]._to_proto()), + ], ) # Deserialize the protobuf object @@ -217,18 +204,19 @@ def test_reject_transaction_from_proto(mock_account_ids): assert _from_proto.owner_id == AccountId() assert _from_proto.token_ids == [] assert _from_proto.nft_ids == [] - + + def test_build_scheduled_body_with_token_ids(mock_account_ids): """Test building a scheduled transaction body for token reject transaction with token IDs.""" _, owner_account_id, _, token_id, _ = mock_account_ids token_ids = [token_id] - + reject_tx = TokenRejectTransaction() reject_tx.set_owner_id(owner_account_id) reject_tx.set_token_ids(token_ids) - + schedulable_body = reject_tx.build_scheduled_body() - + # Verify the schedulable body has the correct structure and fields assert isinstance(schedulable_body, SchedulableTransactionBody) assert schedulable_body.HasField("tokenReject") @@ -237,17 +225,18 @@ def test_build_scheduled_body_with_token_ids(mock_account_ids): assert schedulable_body.tokenReject.rejections[0].HasField("fungible_token") assert schedulable_body.tokenReject.rejections[0].fungible_token == token_id._to_proto() + def test_build_scheduled_body_with_nft_ids(mock_account_ids): """Test building a scheduled transaction body for token reject transaction with NFT IDs.""" _, owner_account_id, _, token_id, _ = mock_account_ids nft_ids = [NftId(token_id=token_id, serial_number=1)] - + reject_tx = TokenRejectTransaction() reject_tx.set_owner_id(owner_account_id) reject_tx.set_nft_ids(nft_ids) schedulable_body = reject_tx.build_scheduled_body() - + # Verify the schedulable body has the correct structure and fields assert isinstance(schedulable_body, SchedulableTransactionBody) assert schedulable_body.HasField("tokenReject") diff --git a/tests/unit/token_relationship_test.py b/tests/unit/token_relationship_test.py index 17dfc1631..2f73e2f8c 100644 --- a/tests/unit/token_relationship_test.py +++ b/tests/unit/token_relationship_test.py @@ -1,15 +1,21 @@ +from __future__ import annotations + import pytest -from hiero_sdk_python.tokens.token_relationship import TokenRelationship +from hiero_sdk_python.hapi.services.basic_types_pb2 import ( + TokenFreezeStatus as TokenFreezeStatusProto, + TokenKycStatus as TokenKycStatusProto, + TokenRelationship as TokenRelationshipProto, +) +from hiero_sdk_python.tokens.token_freeze_status import TokenFreezeStatus from hiero_sdk_python.tokens.token_id import TokenId from hiero_sdk_python.tokens.token_kyc_status import TokenKycStatus -from hiero_sdk_python.tokens.token_freeze_status import TokenFreezeStatus -from hiero_sdk_python.hapi.services.basic_types_pb2 import TokenRelationship as TokenRelationshipProto -from hiero_sdk_python.hapi.services.basic_types_pb2 import TokenFreezeStatus as TokenFreezeStatusProto -from hiero_sdk_python.hapi.services.basic_types_pb2 import TokenKycStatus as TokenKycStatusProto +from hiero_sdk_python.tokens.token_relationship import TokenRelationship + pytestmark = pytest.mark.unit + @pytest.fixture def token_relationship(): return TokenRelationship( @@ -19,21 +25,22 @@ def token_relationship(): kyc_status=TokenKycStatus.GRANTED, freeze_status=TokenFreezeStatus.UNFROZEN, decimals=8, - automatic_association=True + automatic_association=True, ) + @pytest.fixture def proto_token_relationship(): - proto = TokenRelationshipProto( + return TokenRelationshipProto( tokenId=TokenId(0, 0, 100)._to_proto(), symbol="TEST", balance=1000000, kycStatus=TokenKycStatusProto.Granted, freezeStatus=TokenFreezeStatusProto.Unfrozen, decimals=8, - automatic_association=True + automatic_association=True, ) - return proto + def test_token_relationship_initialization(token_relationship): """Test the initialization of the TokenRelationship class""" @@ -45,6 +52,7 @@ def test_token_relationship_initialization(token_relationship): assert token_relationship.decimals == 8 assert token_relationship.automatic_association is True + def test_token_relationship_default_initialization(): """Test the default initialization of the TokenRelationship class""" token_relationship = TokenRelationship() @@ -56,10 +64,11 @@ def test_token_relationship_default_initialization(): assert token_relationship.decimals is None assert token_relationship.automatic_association is None + def test_from_proto(proto_token_relationship): """Test the from_proto method of the TokenRelationship class""" token_relationship = TokenRelationship._from_proto(proto_token_relationship) - + assert token_relationship.token_id == TokenId(0, 0, 100) assert token_relationship.symbol == "TEST" assert token_relationship.balance == 1000000 @@ -68,6 +77,7 @@ def test_from_proto(proto_token_relationship): assert token_relationship.decimals == 8 assert token_relationship.automatic_association is True + def test_from_proto_with_different_statuses(): """Test the from_proto method of the TokenRelationship class with different statuses""" proto = TokenRelationshipProto( @@ -77,9 +87,9 @@ def test_from_proto_with_different_statuses(): kycStatus=TokenKycStatusProto.Revoked, freezeStatus=TokenFreezeStatusProto.Frozen, decimals=2, - automatic_association=False + automatic_association=False, ) - + token_relationship = TokenRelationship._from_proto(proto) assert token_relationship.token_id == TokenId(0, 0, 200) assert token_relationship.symbol == "OTHER" @@ -89,6 +99,7 @@ def test_from_proto_with_different_statuses(): assert token_relationship.decimals == 2 assert token_relationship.automatic_association is False + def test_from_proto_with_not_applicable_statuses(): """Test the from_proto method of the TokenRelationship class with not applicable statuses""" proto = TokenRelationshipProto( @@ -98,22 +109,24 @@ def test_from_proto_with_not_applicable_statuses(): kycStatus=TokenKycStatusProto.KycNotApplicable, freezeStatus=TokenFreezeStatusProto.FreezeNotApplicable, decimals=0, - automatic_association=False + automatic_association=False, ) - + token_relationship = TokenRelationship._from_proto(proto) assert token_relationship.kyc_status == TokenKycStatus.KYC_NOT_APPLICABLE assert token_relationship.freeze_status == TokenFreezeStatus.FREEZE_NOT_APPLICABLE + def test_from_proto_none_raises_error(): """Test the from_proto method of the TokenRelationship class with a None proto""" with pytest.raises(ValueError, match="Token relationship proto is None"): TokenRelationship._from_proto(None) + def test_to_proto(token_relationship): """Test the to_proto method of the TokenRelationship class""" proto = token_relationship._to_proto() - + assert proto.tokenId == TokenId(0, 0, 100)._to_proto() assert proto.symbol == "TEST" assert proto.balance == 1000000 @@ -122,6 +135,7 @@ def test_to_proto(token_relationship): assert proto.decimals == 8 assert proto.automatic_association is True + def test_to_proto_with_different_statuses(): """Test the to_proto method of the TokenRelationship class with different statuses""" token_relationship = TokenRelationship( @@ -131,13 +145,14 @@ def test_to_proto_with_different_statuses(): kyc_status=TokenKycStatus.REVOKED, freeze_status=TokenFreezeStatus.FROZEN, decimals=2, - automatic_association=False + automatic_association=False, ) - + proto = token_relationship._to_proto() assert proto.kycStatus == TokenKycStatusProto.Revoked assert proto.freezeStatus == TokenFreezeStatusProto.Frozen + def test_to_proto_with_not_applicable_statuses(): """Test the to_proto method of the TokenRelationship class with not applicable statuses""" token_relationship = TokenRelationship( @@ -147,18 +162,19 @@ def test_to_proto_with_not_applicable_statuses(): kyc_status=TokenKycStatus.KYC_NOT_APPLICABLE, freeze_status=TokenFreezeStatus.FREEZE_NOT_APPLICABLE, decimals=0, - automatic_association=False + automatic_association=False, ) - + proto = token_relationship._to_proto() assert proto.kycStatus == TokenKycStatusProto.KycNotApplicable assert proto.freezeStatus == TokenFreezeStatusProto.FreezeNotApplicable + def test_proto_conversion(token_relationship): """Test converting TokenRelationship to proto and back preserves data""" proto = token_relationship._to_proto() converted_token_relationship = TokenRelationship._from_proto(proto) - + assert converted_token_relationship.token_id == token_relationship.token_id assert converted_token_relationship.symbol == token_relationship.symbol assert converted_token_relationship.balance == token_relationship.balance @@ -167,6 +183,7 @@ def test_proto_conversion(token_relationship): assert converted_token_relationship.decimals == token_relationship.decimals assert converted_token_relationship.automatic_association == token_relationship.automatic_association + def test_proto_conversion_with_all_statuses(): """Test converting TokenRelationship to proto and back preserves all status combinations""" test_cases = [ @@ -174,7 +191,7 @@ def test_proto_conversion_with_all_statuses(): (TokenKycStatus.REVOKED, TokenFreezeStatus.FROZEN), (TokenKycStatus.KYC_NOT_APPLICABLE, TokenFreezeStatus.FREEZE_NOT_APPLICABLE), ] - + for kyc_status, freeze_status in test_cases: token_relationship = TokenRelationship( token_id=TokenId(0, 0, 400), @@ -183,11 +200,11 @@ def test_proto_conversion_with_all_statuses(): kyc_status=kyc_status, freeze_status=freeze_status, decimals=6, - automatic_association=True + automatic_association=True, ) - + proto = token_relationship._to_proto() converted = TokenRelationship._from_proto(proto) - + assert converted.kyc_status == kyc_status - assert converted.freeze_status == freeze_status \ No newline at end of file + assert converted.freeze_status == freeze_status diff --git a/tests/unit/token_revoke_kyc_transaction_test.py b/tests/unit/token_revoke_kyc_transaction_test.py index 6afd0c84a..b82b768a2 100644 --- a/tests/unit/token_revoke_kyc_transaction_test.py +++ b/tests/unit/token_revoke_kyc_transaction_test.py @@ -1,172 +1,160 @@ +from __future__ import annotations + import pytest -from hiero_sdk_python.hapi.services.token_revoke_kyc_pb2 import TokenRevokeKycTransactionBody -from hiero_sdk_python.hapi.services.transaction_receipt_pb2 import TransactionReceipt as TransactionReceiptProto -from hiero_sdk_python.hapi.services.transaction_response_pb2 import TransactionResponse as TransactionResponseProto +from hiero_sdk_python.account.account_id import AccountId +from hiero_sdk_python.hapi.services import response_header_pb2, response_pb2, transaction_get_receipt_pb2 from hiero_sdk_python.hapi.services.schedulable_transaction_body_pb2 import ( SchedulableTransactionBody, ) +from hiero_sdk_python.hapi.services.token_revoke_kyc_pb2 import TokenRevokeKycTransactionBody +from hiero_sdk_python.hapi.services.transaction_receipt_pb2 import TransactionReceipt as TransactionReceiptProto +from hiero_sdk_python.hapi.services.transaction_response_pb2 import TransactionResponse as TransactionResponseProto from hiero_sdk_python.response_code import ResponseCode -from hiero_sdk_python.tokens.token_revoke_kyc_transaction import TokenRevokeKycTransaction -from hiero_sdk_python.hapi.services import response_header_pb2, response_pb2, transaction_get_receipt_pb2 -from hiero_sdk_python.account.account_id import AccountId from hiero_sdk_python.tokens.token_id import TokenId - +from hiero_sdk_python.tokens.token_revoke_kyc_transaction import TokenRevokeKycTransaction from tests.unit.mock_server import mock_hedera_servers + pytestmark = pytest.mark.unit + def test_build_transaction_body(mock_account_ids): """Test building a token revoke KYC transaction body with valid values.""" account_id, _, node_account_id, token_id, _ = mock_account_ids - - revoke_kyc_tx = ( - TokenRevokeKycTransaction() - .set_token_id(token_id) - .set_account_id(account_id) - ) - + + revoke_kyc_tx = TokenRevokeKycTransaction().set_token_id(token_id).set_account_id(account_id) + # Set operator and node account IDs needed for building transaction body revoke_kyc_tx.operator_account_id = account_id revoke_kyc_tx.node_account_id = node_account_id - + transaction_body = revoke_kyc_tx.build_transaction_body() - + assert transaction_body.tokenRevokeKyc.token == token_id._to_proto() assert transaction_body.tokenRevokeKyc.account == account_id._to_proto() + def test_build_transaction_body_validation(mock_account_ids): """Test validation when building transaction body.""" account_id, _, _, token_id, _ = mock_account_ids - + # Test missing token ID revoke_kyc_tx = TokenRevokeKycTransaction(account_id=account_id) - + with pytest.raises(ValueError, match="Missing token ID"): revoke_kyc_tx.build_transaction_body() - + # Test missing account ID revoke_kyc_tx = TokenRevokeKycTransaction(token_id=token_id) - + with pytest.raises(ValueError, match="Missing account ID"): revoke_kyc_tx.build_transaction_body() + def test_constructor_with_parameters(mock_account_ids): """Test creating a token revoke KYC transaction with constructor parameters.""" account_id, _, _, token_id, _ = mock_account_ids - revoke_kyc_tx = TokenRevokeKycTransaction( - token_id=token_id, - account_id=account_id - ) + revoke_kyc_tx = TokenRevokeKycTransaction(token_id=token_id, account_id=account_id) assert revoke_kyc_tx.token_id == token_id assert revoke_kyc_tx.account_id == account_id + def test_set_methods(mock_account_ids): """Test the set methods of TokenRevokeKycTransaction.""" account_id, _, _, token_id, _ = mock_account_ids - + revoke_kyc_tx = TokenRevokeKycTransaction() - + # Test method chaining tx_after_set = revoke_kyc_tx.set_token_id(token_id) assert tx_after_set is revoke_kyc_tx assert revoke_kyc_tx.token_id == token_id - + tx_after_set = revoke_kyc_tx.set_account_id(account_id) assert tx_after_set is revoke_kyc_tx assert revoke_kyc_tx.account_id == account_id + def test_set_methods_require_not_frozen(mock_account_ids, mock_client): """Test that set methods raise exception when transaction is frozen.""" account_id, _, _, token_id, _ = mock_account_ids - + revoke_kyc_tx = TokenRevokeKycTransaction(token_id=token_id, account_id=account_id) revoke_kyc_tx.freeze_with(mock_client) - + with pytest.raises(Exception, match="Transaction is immutable; it has been frozen"): revoke_kyc_tx.set_token_id(token_id) - + with pytest.raises(Exception, match="Transaction is immutable; it has been frozen"): revoke_kyc_tx.set_account_id(account_id) + def test_revoke_kyc_transaction_can_execute(mock_account_ids): """Test that a revoke KYC transaction can be executed successfully.""" account_id, _, _, token_id, _ = mock_account_ids - + # Create test transaction responses ok_response = TransactionResponseProto() ok_response.nodeTransactionPrecheckCode = ResponseCode.OK - + # Create a mock receipt for a successful token revoke KYC - mock_receipt_proto = TransactionReceiptProto( - status=ResponseCode.SUCCESS - ) - + mock_receipt_proto = TransactionReceiptProto(status=ResponseCode.SUCCESS) + # Create a response for the receipt query receipt_query_response = response_pb2.Response( transactionGetReceipt=transaction_get_receipt_pb2.TransactionGetReceiptResponse( - header=response_header_pb2.ResponseHeader( - nodeTransactionPrecheckCode=ResponseCode.OK - ), - receipt=mock_receipt_proto + header=response_header_pb2.ResponseHeader(nodeTransactionPrecheckCode=ResponseCode.OK), + receipt=mock_receipt_proto, ) ) - + response_sequences = [ [ok_response, receipt_query_response], ] - + with mock_hedera_servers(response_sequences) as client: - transaction = ( - TokenRevokeKycTransaction() - .set_token_id(token_id) - .set_account_id(account_id) - ) - + transaction = TokenRevokeKycTransaction().set_token_id(token_id).set_account_id(account_id) + receipt = transaction.execute(client) - + assert receipt.status == ResponseCode.SUCCESS, "Transaction should have succeeded" + def test_build_scheduled_body(mock_account_ids): """Test building a scheduled transaction body for token revoke KYC transaction.""" account_id, _, _, token_id, _ = mock_account_ids - - revoke_kyc_tx = ( - TokenRevokeKycTransaction() - .set_token_id(token_id) - .set_account_id(account_id) - ) - + + revoke_kyc_tx = TokenRevokeKycTransaction().set_token_id(token_id).set_account_id(account_id) + schedulable_body = revoke_kyc_tx.build_scheduled_body() - + # Verify the schedulable body has the correct structure and fields assert isinstance(schedulable_body, SchedulableTransactionBody) assert schedulable_body.HasField("tokenRevokeKyc") assert schedulable_body.tokenRevokeKyc.token == token_id._to_proto() assert schedulable_body.tokenRevokeKyc.account == account_id._to_proto() + def test_revoke_kyc_transaction_from_proto(mock_account_ids): """Test that a revoke KYC transaction can be created from a protobuf object.""" account_id, _, _, token_id, _ = mock_account_ids - + # Create protobuf object - proto = TokenRevokeKycTransactionBody( - token=token_id._to_proto(), - account=account_id._to_proto() - ) - + proto = TokenRevokeKycTransactionBody(token=token_id._to_proto(), account=account_id._to_proto()) + # Deserialize the protobuf object from_proto = TokenRevokeKycTransaction()._from_proto(proto) - + # Verify deserialized transaction matches original data assert from_proto.token_id == token_id assert from_proto.account_id == account_id - + # Deserialize empty protobuf from_proto = TokenRevokeKycTransaction()._from_proto(TokenRevokeKycTransactionBody()) - + # Verify empty protobuf deserializes to empty/default values - assert from_proto.token_id == TokenId(0,0,0) - assert from_proto.account_id == AccountId() \ No newline at end of file + assert from_proto.token_id == TokenId(0, 0, 0) + assert from_proto.account_id == AccountId() diff --git a/tests/unit/token_transfer_list_test.py b/tests/unit/token_transfer_list_test.py index 51c45886c..6010310d5 100644 --- a/tests/unit/token_transfer_list_test.py +++ b/tests/unit/token_transfer_list_test.py @@ -1,56 +1,52 @@ +from __future__ import annotations + import pytest from hiero_sdk_python.tokens.token_nft_transfer import TokenNftTransfer from hiero_sdk_python.tokens.token_transfer import TokenTransfer from hiero_sdk_python.tokens.token_transfer_list import TokenTransferList + pytestmark = pytest.mark.unit + def test_token_transfer_list_constructor(mock_account_ids): """Test TokenTransferList constructor with various parameters""" account_id1, account_id2, _, token_id, _ = mock_account_ids expected_decimals = 1 transfers = [TokenTransfer(token_id, account_id1, 10)] nft_transfers = [TokenNftTransfer(token_id, account_id1, account_id2, 10)] - + token_transfer_list = TokenTransferList( token=token_id, ) assert token_transfer_list.token == token_id - assert token_transfer_list.expected_decimals == None + assert token_transfer_list.expected_decimals is None assert token_transfer_list.transfers == [] assert token_transfer_list.nft_transfers == [] - #Test with explicit expected_decimals - decimal_token_transfer_list = TokenTransferList( - token=token_id, - expected_decimals=expected_decimals - ) + # Test with explicit expected_decimals + decimal_token_transfer_list = TokenTransferList(token=token_id, expected_decimals=expected_decimals) assert decimal_token_transfer_list.token == token_id assert decimal_token_transfer_list.expected_decimals == expected_decimals assert decimal_token_transfer_list.transfers == [] assert decimal_token_transfer_list.nft_transfers == [] - #Test with explicit transfers - transfers_token_transfer_list = TokenTransferList( - token=token_id, - transfers=transfers - ) + # Test with explicit transfers + transfers_token_transfer_list = TokenTransferList(token=token_id, transfers=transfers) assert transfers_token_transfer_list.token == token_id - assert transfers_token_transfer_list.expected_decimals == None + assert transfers_token_transfer_list.expected_decimals is None assert transfers_token_transfer_list.transfers == transfers assert transfers_token_transfer_list.nft_transfers == [] - #Test with explicit nft_transfers - nft_transfers_token_transfer_list = TokenTransferList( - token=token_id, - nft_transfers=nft_transfers - ) + # Test with explicit nft_transfers + nft_transfers_token_transfer_list = TokenTransferList(token=token_id, nft_transfers=nft_transfers) assert nft_transfers_token_transfer_list.token == token_id - assert nft_transfers_token_transfer_list.expected_decimals == None + assert nft_transfers_token_transfer_list.expected_decimals is None assert nft_transfers_token_transfer_list.transfers == [] assert nft_transfers_token_transfer_list.nft_transfers == nft_transfers + def test_token_transfer_list_add_token_transfer(mock_account_ids): """Test TokenTransferList add_token_transfer function""" account_id, _, _, token_id, _ = mock_account_ids @@ -65,6 +61,7 @@ def test_token_transfer_list_add_token_transfer(mock_account_ids): assert token_transfer_list.transfers is not None assert token_transfer_list.transfers == [transfer] + def test_token_transfer_list_add_nft_transfer(mock_account_ids): """Test TokenTransferList add_nft_transfer function""" account_id1, account_id2, _, token_id, _ = mock_account_ids @@ -79,26 +76,22 @@ def test_token_transfer_list_add_nft_transfer(mock_account_ids): assert token_transfer_list.nft_transfers is not None assert token_transfer_list.nft_transfers == [transfer] + def test_to_proto(mock_account_ids): """Test converting TokenTransferList to protobuf object""" sender_id, receiver_id, _, token_id_1, token_id_2 = mock_account_ids expected_decimals = 1 transfers = [TokenTransfer(token_id_1, sender_id, -10), TokenTransfer(token_id_1, receiver_id, 10)] nft_transfers = [TokenNftTransfer(token_id_2, sender_id, receiver_id, 1)] - - - token_transfer_list = TokenTransferList( - token=token_id_1, - transfers=transfers, - expected_decimals=expected_decimals - ) - # Convert to protobuf + token_transfer_list = TokenTransferList(token=token_id_1, transfers=transfers, expected_decimals=expected_decimals) + + # Convert to protobuf proto = token_transfer_list._to_proto() assert proto.token.shardNum == token_id_1.shard assert proto.token.realmNum == token_id_1.realm - assert proto.token.tokenNum == token_id_1.num + assert proto.token.tokenNum == token_id_1.num assert proto.expected_decimals.value == expected_decimals assert len(proto.transfers) == 2 assert len(proto.nftTransfers) == 0 @@ -113,15 +106,15 @@ def test_to_proto(mock_account_ids): nft_transfers=nft_transfers, ) - # Convert to protobuf + # Convert to protobuf proto = nft_transfer_list._to_proto() assert proto.token.shardNum == token_id_2.shard assert proto.token.realmNum == token_id_2.realm - assert proto.token.tokenNum == token_id_2.num + assert proto.token.tokenNum == token_id_2.num assert proto.expected_decimals.value == 0 assert len(proto.transfers) == 0 assert len(proto.nftTransfers) == 1 assert proto.nftTransfers[0].senderAccountID.accountNum == sender_id.num assert proto.nftTransfers[0].receiverAccountID.accountNum == receiver_id.num - assert proto.nftTransfers[0].serialNumber == 1 \ No newline at end of file + assert proto.nftTransfers[0].serialNumber == 1 diff --git a/tests/unit/token_transfer_test.py b/tests/unit/token_transfer_test.py index 2de881ba6..a50031141 100644 --- a/tests/unit/token_transfer_test.py +++ b/tests/unit/token_transfer_test.py @@ -1,78 +1,70 @@ +from __future__ import annotations + import pytest -from hiero_sdk_python.account.account_id import AccountId from hiero_sdk_python.hapi.services import basic_types_pb2 from hiero_sdk_python.tokens.token_transfer import TokenTransfer from hiero_sdk_python.tokens.token_transfer_list import TokenTransferList + pytestmark = pytest.mark.unit + def test_token_transfer_constructor(mock_account_ids): """Test the TokenTransfer constructor with various parameters""" account_id, _, _, token_id, _ = mock_account_ids amount = 10 expected_decimals = 1 - token_transfer = TokenTransfer( - token_id=token_id, - account_id=account_id, - amount=amount - ) + token_transfer = TokenTransfer(token_id=token_id, account_id=account_id, amount=amount) assert token_transfer.token_id == token_id assert token_transfer.account_id == account_id assert token_transfer.amount == amount - assert token_transfer.expected_decimals == None + assert token_transfer.expected_decimals is None assert token_transfer.is_approved == False - + # Test with explicit excepted_decimals decimal_token_transfer = TokenTransfer( - token_id=token_id, - account_id=account_id, - amount=amount, - expected_decimals=expected_decimals + token_id=token_id, account_id=account_id, amount=amount, expected_decimals=expected_decimals ) assert decimal_token_transfer.token_id == token_id assert decimal_token_transfer.account_id == account_id assert decimal_token_transfer.amount == amount assert decimal_token_transfer.expected_decimals == expected_decimals assert decimal_token_transfer.is_approved == False - + # Test with explicit is_approved=True - approved_token_transfer = TokenTransfer( - token_id=token_id, - account_id=account_id, - amount=amount, - is_approved=True - ) + approved_token_transfer = TokenTransfer(token_id=token_id, account_id=account_id, amount=amount, is_approved=True) assert approved_token_transfer.token_id == token_id assert approved_token_transfer.account_id == account_id assert approved_token_transfer.amount == amount - assert approved_token_transfer.expected_decimals == None + assert approved_token_transfer.expected_decimals is None assert approved_token_transfer.is_approved == True + def test_to_proto(mock_account_ids): """Test converting TokenTransfer to protobuf object""" account_id, _, _, token_id, _ = mock_account_ids amount = 10 expected_decimals = 1 is_approved = True - + token_transfer = TokenTransfer( token_id=token_id, account_id=account_id, amount=amount, expected_decimals=expected_decimals, - is_approved=is_approved + is_approved=is_approved, ) assert token_transfer.token_id == token_id - # Convert to protobuf + # Convert to protobuf proto = token_transfer._to_proto() assert proto.accountID.shardNum == account_id.shard assert proto.accountID.realmNum == account_id.realm - assert proto.accountID.accountNum == account_id.num + assert proto.accountID.accountNum == account_id.num assert proto.amount == amount assert proto.is_approval is is_approved @@ -82,31 +74,32 @@ def test_to_proto(mock_account_ids): account_id=account_id, amount=-amount, expected_decimals=expected_decimals, - is_approved=is_approved + is_approved=is_approved, ) assert debiting_token_transfer.token_id == token_id - # Convert to protobuf + # Convert to protobuf proto = debiting_token_transfer._to_proto() assert proto.accountID.shardNum == account_id.shard assert proto.accountID.realmNum == account_id.realm - assert proto.accountID.accountNum == account_id.num + assert proto.accountID.accountNum == account_id.num assert proto.amount == -amount assert proto.is_approval is is_approved + def test_from_proto(mock_account_ids): """Test converting proto to List[TokenTransfer]""" sender_id, receiver_id, _, token_id, _ = mock_account_ids proto = basic_types_pb2.TokenTransferList( token=token_id._to_proto(), - expected_decimals={'value': 1}, + expected_decimals={"value": 1}, transfers=[ basic_types_pb2.AccountAmount(accountID=sender_id._to_proto(), amount=-1, is_approval=True), - basic_types_pb2.AccountAmount(accountID=receiver_id._to_proto(), amount=1, is_approval=True) - ] + basic_types_pb2.AccountAmount(accountID=receiver_id._to_proto(), amount=1, is_approval=True), + ], ) token_transfer = TokenTransfer._from_proto(proto) @@ -123,6 +116,7 @@ def test_from_proto(mock_account_ids): assert token_transfer[1].account_id == receiver_id assert token_transfer[1].is_approved == True + def test_from_proto_with_no_token_transfer(mock_account_ids): """Test converting proto return empty array if token_transfer not present in proto""" sender_id, receiver_id, _, token_id, _ = mock_account_ids @@ -135,9 +129,9 @@ def test_from_proto_with_no_token_transfer(mock_account_ids): senderAccountID=sender_id._to_proto(), receiverAccountID=receiver_id._to_proto(), serialNumber=1, - is_approval=True + is_approval=True, ) - ] + ], ) token_transfer1 = TokenTransfer._from_proto(proto1) @@ -150,6 +144,7 @@ def test_from_proto_with_no_token_transfer(mock_account_ids): assert len(token_transfer2) == 0 + def test_from_proto_round_trip(mock_account_ids): """Test round trip converting proto to List[TokenTransfer]""" sender_id, receiver_id, _, token_id, _ = mock_account_ids diff --git a/tests/unit/token_type_test.py b/tests/unit/token_type_test.py index 81e1cb00e..ed013e681 100644 --- a/tests/unit/token_type_test.py +++ b/tests/unit/token_type_test.py @@ -1,13 +1,18 @@ +from __future__ import annotations + import pytest + from hiero_sdk_python.tokens.token_type import TokenType + pytestmark = pytest.mark.unit + def test_members(): assert TokenType.FUNGIBLE_COMMON.value == 0 assert TokenType.NON_FUNGIBLE_UNIQUE.value == 1 + def test_name(): assert TokenType.FUNGIBLE_COMMON.name == "FUNGIBLE_COMMON" assert TokenType.NON_FUNGIBLE_UNIQUE.name == "NON_FUNGIBLE_UNIQUE" - diff --git a/tests/unit/token_unfreeze_transaction_test.py b/tests/unit/token_unfreeze_transaction_test.py index 018184fe2..2d0232acf 100644 --- a/tests/unit/token_unfreeze_transaction_test.py +++ b/tests/unit/token_unfreeze_transaction_test.py @@ -1,34 +1,35 @@ -import pytest +from __future__ import annotations + from unittest.mock import MagicMock -from hiero_sdk_python.tokens.token_unfreeze_transaction import TokenUnfreezeTransaction + +import pytest + from hiero_sdk_python.hapi.services import timestamp_pb2 from hiero_sdk_python.hapi.services.schedulable_transaction_body_pb2 import ( SchedulableTransactionBody, ) +from hiero_sdk_python.tokens.token_unfreeze_transaction import TokenUnfreezeTransaction from hiero_sdk_python.transaction.transaction_id import TransactionId + pytestmark = pytest.mark.unit + def generate_transaction_id(account_id_proto): """Generate a unique transaction ID based on the account ID and the current timestamp.""" - import time + current_time = time.time() timestamp_seconds = int(current_time) timestamp_nanos = int((current_time - timestamp_seconds) * 1e9) tx_timestamp = timestamp_pb2.Timestamp(seconds=timestamp_seconds, nanos=timestamp_nanos) - tx_id = TransactionId( - valid_start=tx_timestamp, - account_id=account_id_proto - ) + return TransactionId(valid_start=tx_timestamp, account_id=account_id_proto) - return tx_id def test_build_transaction_body(mock_account_ids): """Test building the token unfreeze transaction body with valid account ID and token ID.""" - - account_id, freeze_id, node_account_id, token_id, _= mock_account_ids + account_id, freeze_id, node_account_id, token_id, _ = mock_account_ids unfreeze_tx = TokenUnfreezeTransaction() unfreeze_tx.set_token_id(token_id) @@ -44,30 +45,32 @@ def test_build_transaction_body(mock_account_ids): proto_account = freeze_id._to_proto() assert transaction_body.tokenUnfreeze.account == proto_account -def test_missing_token_id(mock_account_ids): - """Test that building a transaction without setting - TokenID raises a ValueError.""" - - account_id, freeze_id, node_account_id, token_id, _= mock_account_ids +def test_missing_token_id(mock_account_ids): + """Test that building a transaction without setting + TokenID raises a ValueError. + """ + account_id, freeze_id, node_account_id, token_id, _ = mock_account_ids unfreeze_tx = TokenUnfreezeTransaction() - unfreeze_tx.set_account_id(freeze_id) - with pytest.raises(ValueError,match = "Missing required TokenID."): + unfreeze_tx.set_account_id(freeze_id) + with pytest.raises(ValueError, match="Missing required TokenID."): unfreeze_tx.build_transaction_body() + def test_missing_account_id(mock_account_ids): """Test that building a transaction without setting AccountID raises a ValueError.""" - account_id, freeze_id, node_account_id, token_id, _= mock_account_ids + account_id, freeze_id, node_account_id, token_id, _ = mock_account_ids unfreeze_tx = TokenUnfreezeTransaction() unfreeze_tx.set_token_id(token_id) with pytest.raises(ValueError, match="Missing required AccountID."): unfreeze_tx.build_transaction_body() + def test_sign_transaction(mock_account_ids, mock_client): """Test signing the token unfreeze transaction with a freeze key.""" - account_id, freeze_id, _, token_id, _= mock_account_ids + account_id, freeze_id, _, token_id, _ = mock_account_ids unfreeze_tx = TokenUnfreezeTransaction() unfreeze_tx.set_token_id(token_id) @@ -75,9 +78,9 @@ def test_sign_transaction(mock_account_ids, mock_client): unfreeze_tx.transaction_id = generate_transaction_id(account_id) freeze_key = MagicMock() - freeze_key.sign.return_value = b'signature' - freeze_key.public_key().to_bytes_raw.return_value = b'public_key' - + freeze_key.sign.return_value = b"signature" + freeze_key.public_key().to_bytes_raw.return_value = b"public_key" + unfreeze_tx.freeze_with(mock_client) unfreeze_tx.sign(freeze_key) @@ -88,28 +91,30 @@ def test_sign_transaction(mock_account_ids, mock_client): assert len(unfreeze_tx._signature_map[body_bytes].sigPair) == 1 sig_pair = unfreeze_tx._signature_map[body_bytes].sigPair[0] - assert sig_pair.pubKeyPrefix == b'public_key' - assert sig_pair.ed25519 == b'signature' + assert sig_pair.pubKeyPrefix == b"public_key" + assert sig_pair.ed25519 == b"signature" + def test_build_scheduled_body(mock_account_ids): """Test building a scheduled transaction body for token unfreeze transaction.""" _, freeze_id, _, token_id, _ = mock_account_ids - + unfreeze_tx = TokenUnfreezeTransaction() unfreeze_tx.set_token_id(token_id) unfreeze_tx.set_account_id(freeze_id) - + schedulable_body = unfreeze_tx.build_scheduled_body() - + # Verify the schedulable body has the correct structure and fields assert isinstance(schedulable_body, SchedulableTransactionBody) assert schedulable_body.HasField("tokenUnfreeze") assert schedulable_body.tokenUnfreeze.token == token_id._to_proto() assert schedulable_body.tokenUnfreeze.account == freeze_id._to_proto() + def test_to_proto(mock_account_ids, mock_client): """Test converting the token unfreeze transaction to protobuf format after signing.""" - account_id, freeze_id, _, token_id, _= mock_account_ids + account_id, freeze_id, _, token_id, _ = mock_account_ids unfreeze_tx = TokenUnfreezeTransaction() unfreeze_tx.set_token_id(token_id) @@ -117,9 +122,9 @@ def test_to_proto(mock_account_ids, mock_client): unfreeze_tx.transaction_id = generate_transaction_id(account_id) freeze_key = MagicMock() - freeze_key.sign.return_value = b'signature' - freeze_key.public_key().to_bytes_raw.return_value = b'mock_pubkey' - + freeze_key.sign.return_value = b"signature" + freeze_key.public_key().to_bytes_raw.return_value = b"mock_pubkey" + unfreeze_tx.freeze_with(mock_client) unfreeze_tx.sign(freeze_key) diff --git a/tests/unit/token_unpause_transaction_test.py b/tests/unit/token_unpause_transaction_test.py index c4c260151..d6c57af7d 100644 --- a/tests/unit/token_unpause_transaction_test.py +++ b/tests/unit/token_unpause_transaction_test.py @@ -1,23 +1,29 @@ +from __future__ import annotations + from unittest.mock import MagicMock + import pytest -from hiero_sdk_python.hapi.services.transaction_receipt_pb2 import TransactionReceipt as TransactionReceiptProto -from hiero_sdk_python.hapi.services.transaction_response_pb2 import TransactionResponse as TransactionResponseProto from hiero_sdk_python.hapi.services import response_header_pb2, response_pb2, transaction_get_receipt_pb2 from hiero_sdk_python.hapi.services.schedulable_transaction_body_pb2 import SchedulableTransactionBody from hiero_sdk_python.hapi.services.token_unpause_pb2 import TokenUnpauseTransactionBody +from hiero_sdk_python.hapi.services.transaction_receipt_pb2 import TransactionReceipt as TransactionReceiptProto +from hiero_sdk_python.hapi.services.transaction_response_pb2 import TransactionResponse as TransactionResponseProto from hiero_sdk_python.response_code import ResponseCode from hiero_sdk_python.tokens.token_id import TokenId from hiero_sdk_python.tokens.token_unpause_transaction import TokenUnpauseTransaction from tests.unit.mock_server import mock_hedera_servers + pytestmark = pytest.mark.unit + def test_constructor_without_parameters(): """Test creating token transaction without constructor parameters.""" unpause_tx = TokenUnpauseTransaction() assert unpause_tx.token_id is None + def test_constructor_with_parameters(mock_account_ids): """Test creating token transaction with constructor parameters.""" _, _, _, token_id, _ = mock_account_ids @@ -27,20 +33,19 @@ def test_constructor_with_parameters(mock_account_ids): assert unpause_tx.token_id.realm == token_id.realm assert unpause_tx.token_id.num == token_id.num + def test_constructor_with_invalid_parameters(): """Test creating token transaction with invalid constructor parameters.""" token_id = 100 with pytest.raises(TypeError, match="token_id must be an instance of TokenId"): TokenUnpauseTransaction(token_id=token_id) + def test_build_transaction_body(mock_account_ids): """Test building a token unpause transaction body""" account_id, _, node_account_id, token_id, _ = mock_account_ids - unpause_tx = ( - TokenUnpauseTransaction() - .set_token_id(token_id) - ) + unpause_tx = TokenUnpauseTransaction().set_token_id(token_id) unpause_tx.node_account_id = node_account_id unpause_tx.operator_account_id = account_id @@ -50,6 +55,7 @@ def test_build_transaction_body(mock_account_ids): assert transaction_body.token_unpause.token.realmNum == token_id.realm assert transaction_body.token_unpause.token.tokenNum == token_id.num + def test_build_transaction_body_when_token_id_not_set(mock_account_ids): """Test building a token unpause transaction body without token_id""" account_id, _, node_account_id, _, _ = mock_account_ids @@ -60,6 +66,7 @@ def test_build_transaction_body_when_token_id_not_set(mock_account_ids): with pytest.raises(ValueError, match="Missing token ID"): unpause_tx.build_transaction_body() + def test_set_method(mock_account_ids): """Test the set method of TokenUnpauseTransaction.""" _, _, _, token_id, _ = mock_account_ids @@ -71,6 +78,7 @@ def test_set_method(mock_account_ids): assert unpause_tx.token_id.realm == token_id.realm assert unpause_tx.token_id.num == token_id.num + def test_set_method_with_invalid_parameters(): """Test the set method of TokenUnpauseTransaction with invalid parameters.""" token_id = 100 @@ -79,6 +87,7 @@ def test_set_method_with_invalid_parameters(): with pytest.raises(TypeError, match="token_id must be an instance of TokenId"): unpause_tx.set_token_id(token_id) + def test_set_method_require_not_frozen(mock_account_ids, mock_client): """Test the set methods of TokenUnpauseTransaction when transaction is freeze.""" _, _, _, token_id1, token_id2 = mock_account_ids @@ -89,17 +98,18 @@ def test_set_method_require_not_frozen(mock_account_ids, mock_client): with pytest.raises(Exception, match="Transaction is immutable; it has been frozen"): unpause_tx.set_token_id(token_id2) + def test_sign_transaction(mock_account_ids, mock_client): """Test signing the token unpause transaction with a private key.""" - _, _, _, token_id, _= mock_account_ids - + _, _, _, token_id, _ = mock_account_ids + unpause_tx = TokenUnpauseTransaction() unpause_tx.set_token_id(token_id) private_key = MagicMock() - private_key.sign.return_value = b'signature' - private_key.public_key().to_bytes_raw.return_value = b'public_key' - + private_key.sign.return_value = b"signature" + private_key.public_key().to_bytes_raw.return_value = b"public_key" + unpause_tx.freeze_with(mock_client) unpause_tx.sign(private_key) @@ -109,20 +119,21 @@ def test_sign_transaction(mock_account_ids, mock_client): assert len(unpause_tx._signature_map[body_bytes].sigPair) == 1 sig_pair = unpause_tx._signature_map[body_bytes].sigPair[0] - assert sig_pair.pubKeyPrefix == b'public_key' - assert sig_pair.ed25519 == b'signature' + assert sig_pair.pubKeyPrefix == b"public_key" + assert sig_pair.ed25519 == b"signature" + def test_to_proto(mock_account_ids, mock_client): """Test converting the token unpause transaction to protobuf format after signing.""" - _, _, _, token_id, _= mock_account_ids - + _, _, _, token_id, _ = mock_account_ids + unpause_tx = TokenUnpauseTransaction() unpause_tx.set_token_id(token_id) private_key = MagicMock() - private_key.sign.return_value = b'signature' - private_key.public_key().to_bytes_raw.return_value = b'public_key' - + private_key.sign.return_value = b"signature" + private_key.public_key().to_bytes_raw.return_value = b"public_key" + unpause_tx.freeze_with(mock_client) unpause_tx.sign(private_key) @@ -130,7 +141,8 @@ def test_to_proto(mock_account_ids, mock_client): assert proto.signedTransactionBytes assert len(proto.signedTransactionBytes) > 0 - + + def test_build_scheduled_body(mock_account_ids): """Test building a scheduled transaction body for token unpause transaction.""" _, _, _, token_id, _ = mock_account_ids @@ -144,19 +156,19 @@ def test_build_scheduled_body(mock_account_ids): assert schedulable_body.HasField("token_unpause") assert schedulable_body.token_unpause.token == token_id._to_proto() + def test_from_proto(mock_account_ids): """Test creating a TokenUnpauseTransaction from protobuf representaion.""" _, _, _, token_id, _ = mock_account_ids - proto = TokenUnpauseTransactionBody( - token=TokenId._to_proto(token_id) - ) + proto = TokenUnpauseTransactionBody(token=TokenId._to_proto(token_id)) unpause_tx = TokenUnpauseTransaction._from_proto(proto) assert unpause_tx.token_id.shard == token_id.shard assert unpause_tx.token_id.realm == token_id.realm assert unpause_tx.token_id.num == token_id.num + def test_upause_transaction_can_execute(mock_account_ids): """Test that a token upause transaction can be executed successfully.""" _, _, _, token_id, _ = mock_account_ids @@ -164,34 +176,25 @@ def test_upause_transaction_can_execute(mock_account_ids): # Create test transaction responses ok_response = TransactionResponseProto() ok_response.nodeTransactionPrecheckCode = ResponseCode.OK - + # Create a mock receipt for a successful token upause - mock_receipt_proto = TransactionReceiptProto( - status=ResponseCode.SUCCESS - ) - + mock_receipt_proto = TransactionReceiptProto(status=ResponseCode.SUCCESS) + # Create a response for the receipt query receipt_query_response = response_pb2.Response( transactionGetReceipt=transaction_get_receipt_pb2.TransactionGetReceiptResponse( - header=response_header_pb2.ResponseHeader( - nodeTransactionPrecheckCode=ResponseCode.OK - ), - receipt=mock_receipt_proto + header=response_header_pb2.ResponseHeader(nodeTransactionPrecheckCode=ResponseCode.OK), + receipt=mock_receipt_proto, ) ) - + response_sequences = [ [ok_response, receipt_query_response], ] - + with mock_hedera_servers(response_sequences) as client: - transaction = ( - TokenUnpauseTransaction() - .set_token_id(token_id) - .freeze_with(client) - ) - + transaction = TokenUnpauseTransaction().set_token_id(token_id).freeze_with(client) + receipt = transaction.execute(client) - - assert receipt.status == ResponseCode.SUCCESS, "Transaction should have succeeded" + assert receipt.status == ResponseCode.SUCCESS, "Transaction should have succeeded" diff --git a/tests/unit/token_update_nfts_transaction_test.py b/tests/unit/token_update_nfts_transaction_test.py index 8aa3c8ece..9bded64df 100644 --- a/tests/unit/token_update_nfts_transaction_test.py +++ b/tests/unit/token_update_nfts_transaction_test.py @@ -1,5 +1,9 @@ +from __future__ import annotations + import pytest +from google.protobuf.wrappers_pb2 import BytesValue +from hiero_sdk_python.hapi.services import response_header_pb2, response_pb2, transaction_get_receipt_pb2 from hiero_sdk_python.hapi.services.schedulable_transaction_body_pb2 import ( SchedulableTransactionBody, ) @@ -7,153 +11,147 @@ from hiero_sdk_python.hapi.services.transaction_receipt_pb2 import TransactionReceipt as TransactionReceiptProto from hiero_sdk_python.hapi.services.transaction_response_pb2 import TransactionResponse as TransactionResponseProto from hiero_sdk_python.response_code import ResponseCode -from hiero_sdk_python.tokens.token_update_nfts_transaction import TokenUpdateNftsTransaction from hiero_sdk_python.tokens.token_id import TokenId -from hiero_sdk_python.hapi.services import response_header_pb2, response_pb2, transaction_get_receipt_pb2 -from google.protobuf.wrappers_pb2 import BytesValue +from hiero_sdk_python.tokens.token_update_nfts_transaction import TokenUpdateNftsTransaction from tests.unit.mock_server import mock_hedera_servers + pytestmark = pytest.mark.unit + def test_build_transaction_body(mock_account_ids): """Test building a token update NFTs transaction body with valid values.""" operator_id, _, node_account_id, token_id, _ = mock_account_ids serial_numbers = [1, 2, 3] - metadata = b'updated metadata' - + metadata = b"updated metadata" + update_tx = ( - TokenUpdateNftsTransaction() - .set_token_id(token_id) - .set_serial_numbers(serial_numbers) - .set_metadata(metadata) + TokenUpdateNftsTransaction().set_token_id(token_id).set_serial_numbers(serial_numbers).set_metadata(metadata) ) - + # Set operator and node account IDs needed for building transaction body update_tx.operator_account_id = operator_id update_tx.node_account_id = node_account_id transaction_body = update_tx.build_transaction_body() - + assert transaction_body.token_update_nfts.token.shardNum == token_id.shard assert transaction_body.token_update_nfts.token.realmNum == token_id.realm assert transaction_body.token_update_nfts.token.tokenNum == token_id.num assert transaction_body.token_update_nfts.serial_numbers == serial_numbers assert transaction_body.token_update_nfts.metadata.value == metadata + def test_build_transaction_body_validation_errors(mock_account_ids): """Test that build_transaction_body raises appropriate validation errors.""" _, _, _, token_id, _ = mock_account_ids - + # Test missing token_id update_tx = TokenUpdateNftsTransaction() - + with pytest.raises(ValueError, match="Missing token ID"): update_tx.build_transaction_body() - + # Test missing serial numbers update_tx = TokenUpdateNftsTransaction( token_id=token_id, ) - + with pytest.raises(ValueError, match="Missing serial numbers"): update_tx.build_transaction_body() - + # Test metadata too large update_tx = TokenUpdateNftsTransaction( token_id=token_id, serial_numbers=[1], - metadata=b'x' * 101 # 101 bytes + metadata=b"x" * 101, # 101 bytes ) - + with pytest.raises(ValueError, match="Metadata must be less than 100 bytes"): update_tx.build_transaction_body() + def test_constructor_with_parameters(mock_account_ids): """Test creating a token update NFTs transaction with constructor parameters.""" _, _, _, token_id, _ = mock_account_ids serial_numbers = [1, 2, 3] - metadata = b'new metadata' + metadata = b"new metadata" - update_tx = TokenUpdateNftsTransaction( - token_id=token_id, - serial_numbers=serial_numbers, - metadata=metadata - ) + update_tx = TokenUpdateNftsTransaction(token_id=token_id, serial_numbers=serial_numbers, metadata=metadata) assert update_tx.token_id == token_id assert update_tx.serial_numbers == serial_numbers assert update_tx.metadata == metadata + def test_set_methods(mock_account_ids): """Test the set methods of TokenUpdateNftsTransaction.""" _, _, _, token_id, _ = mock_account_ids serial_numbers = [1, 2, 3] - metadata = b'new metadata' + metadata = b"new metadata" update_tx = TokenUpdateNftsTransaction() - + # Test method chaining tx_after_set = update_tx.set_token_id(token_id) assert tx_after_set is update_tx assert update_tx.token_id == token_id - + tx_after_set = update_tx.set_serial_numbers(serial_numbers) assert tx_after_set is update_tx assert update_tx.serial_numbers == serial_numbers - + tx_after_set = update_tx.set_metadata(metadata) assert tx_after_set is update_tx assert update_tx.metadata == metadata + def test_set_methods_require_not_frozen(mock_account_ids, mock_client): """Test that set methods raise exception when transaction is frozen.""" _, _, _, token_id, _ = mock_account_ids serial_numbers = [1, 2, 3] - metadata = b'new metadata' + metadata = b"new metadata" update_tx = TokenUpdateNftsTransaction( token_id=token_id, serial_numbers=serial_numbers, ) update_tx.freeze_with(mock_client) - + with pytest.raises(Exception, match="Transaction is immutable; it has been frozen"): update_tx.set_token_id(token_id) - + with pytest.raises(Exception, match="Transaction is immutable; it has been frozen"): update_tx.set_serial_numbers(serial_numbers) - + with pytest.raises(Exception, match="Transaction is immutable; it has been frozen"): update_tx.set_metadata(metadata) + def test_update_nfts_transaction_can_execute(mock_account_ids): """Test that a token update NFTs transaction can be executed successfully.""" _, _, _, token_id, _ = mock_account_ids serial_numbers = [1, 2, 3] - metadata = b'updated metadata' + metadata = b"updated metadata" # Create test transaction responses ok_response = TransactionResponseProto() ok_response.nodeTransactionPrecheckCode = ResponseCode.OK - + # Create a mock receipt for a successful token update NFTs - mock_receipt_proto = TransactionReceiptProto( - status=ResponseCode.SUCCESS - ) - + mock_receipt_proto = TransactionReceiptProto(status=ResponseCode.SUCCESS) + # Create a response for the receipt query receipt_query_response = response_pb2.Response( transactionGetReceipt=transaction_get_receipt_pb2.TransactionGetReceiptResponse( - header=response_header_pb2.ResponseHeader( - nodeTransactionPrecheckCode=ResponseCode.OK - ), - receipt=mock_receipt_proto + header=response_header_pb2.ResponseHeader(nodeTransactionPrecheckCode=ResponseCode.OK), + receipt=mock_receipt_proto, ) ) - + response_sequences = [ [ok_response, receipt_query_response], ] - + with mock_hedera_servers(response_sequences) as client: transaction = ( TokenUpdateNftsTransaction() @@ -161,26 +159,24 @@ def test_update_nfts_transaction_can_execute(mock_account_ids): .set_serial_numbers(serial_numbers) .set_metadata(metadata) ) - + receipt = transaction.execute(client) - + assert receipt.status == ResponseCode.SUCCESS, "Transaction should have succeeded" + def test_build_scheduled_body(mock_account_ids): """Test building a scheduled transaction body for token update NFTs transaction.""" _, _, _, token_id, _ = mock_account_ids serial_numbers = [1, 2, 3] - metadata = b'updated metadata' - + metadata = b"updated metadata" + update_tx = ( - TokenUpdateNftsTransaction() - .set_token_id(token_id) - .set_serial_numbers(serial_numbers) - .set_metadata(metadata) + TokenUpdateNftsTransaction().set_token_id(token_id).set_serial_numbers(serial_numbers).set_metadata(metadata) ) - + schedulable_body = update_tx.build_scheduled_body() - + # Verify the schedulable body has the correct structure and fields assert isinstance(schedulable_body, SchedulableTransactionBody) assert schedulable_body.HasField("token_update_nfts") @@ -188,32 +184,31 @@ def test_build_scheduled_body(mock_account_ids): assert schedulable_body.token_update_nfts.serial_numbers == serial_numbers assert schedulable_body.token_update_nfts.metadata.value == metadata + def test_update_nfts_transaction_from_proto(mock_account_ids): """Test that a token update NFTs transaction can be created from a protobuf object.""" _, _, _, token_id, _ = mock_account_ids serial_numbers = [1, 2, 3] - metadata_bytes = b'updated metadata' - + metadata_bytes = b"updated metadata" + # Create protobuf object for token update NFTs transaction proto = TokenUpdateNftsTransactionBody( - token=token_id._to_proto(), - serial_numbers=serial_numbers, - metadata=BytesValue(value=metadata_bytes) + token=token_id._to_proto(), serial_numbers=serial_numbers, metadata=BytesValue(value=metadata_bytes) ) - + # Deserialize the protobuf object _from_proto = TokenUpdateNftsTransaction()._from_proto(proto) - + # Verify deserialized transaction matches original data assert _from_proto.token_id == token_id assert _from_proto.serial_numbers == serial_numbers assert _from_proto.metadata == metadata_bytes - + # Test with empty protobuf empty_proto = TokenUpdateNftsTransactionBody() empty_tx = TokenUpdateNftsTransaction()._from_proto(empty_proto) - + # TokenId._from_proto with empty proto should return a new TokenId assert isinstance(empty_tx.token_id, TokenId) assert empty_tx.serial_numbers == [] - assert empty_tx.metadata is empty_proto.metadata.value # Should be None or empty \ No newline at end of file + assert empty_tx.metadata is empty_proto.metadata.value # Should be None or empty diff --git a/tests/unit/token_update_transaction_test.py b/tests/unit/token_update_transaction_test.py index 9e2ff7781..efb395bcd 100644 --- a/tests/unit/token_update_transaction_test.py +++ b/tests/unit/token_update_transaction_test.py @@ -1,23 +1,28 @@ +from __future__ import annotations + import datetime + import pytest +from hiero_sdk_python.crypto.private_key import PrivateKey +from hiero_sdk_python.crypto.public_key import PublicKey from hiero_sdk_python.Duration import Duration +from hiero_sdk_python.hapi.services import response_header_pb2, response_pb2, transaction_get_receipt_pb2 from hiero_sdk_python.hapi.services.basic_types_pb2 import TokenKeyValidation -from hiero_sdk_python.hapi.services.transaction_receipt_pb2 import TransactionReceipt as TransactionReceiptProto -from hiero_sdk_python.hapi.services.transaction_response_pb2 import TransactionResponse as TransactionResponseProto from hiero_sdk_python.hapi.services.schedulable_transaction_body_pb2 import ( SchedulableTransactionBody, ) +from hiero_sdk_python.hapi.services.transaction_receipt_pb2 import TransactionReceipt as TransactionReceiptProto +from hiero_sdk_python.hapi.services.transaction_response_pb2 import TransactionResponse as TransactionResponseProto from hiero_sdk_python.response_code import ResponseCode from hiero_sdk_python.timestamp import Timestamp from hiero_sdk_python.tokens.token_update_transaction import TokenUpdateKeys, TokenUpdateParams, TokenUpdateTransaction -from hiero_sdk_python.hapi.services import response_header_pb2, response_pb2, transaction_get_receipt_pb2 -from hiero_sdk_python.crypto.private_key import PrivateKey -from hiero_sdk_python.crypto.public_key import PublicKey from tests.unit.mock_server import mock_hedera_servers + pytestmark = pytest.mark.unit + @pytest.fixture def new_token_data(): return { @@ -28,9 +33,10 @@ def new_token_data(): "auto_renew_period": Duration(7776000), "expiration_time": Timestamp.from_date( datetime.datetime.now(datetime.timezone.utc) + datetime.timedelta(days=30) - ) + ), } + def test_constructor_with_parameters(mock_account_ids, private_key, new_token_data): """Test creating a token update transaction with constructor parameters.""" operator_id, _, _, token_id, _ = mock_account_ids @@ -43,9 +49,9 @@ def test_constructor_with_parameters(mock_account_ids, private_key, new_token_da treasury_account_id=operator_id, auto_renew_account_id=operator_id, auto_renew_period=new_token_data["auto_renew_period"], - expiration_time=new_token_data["expiration_time"] + expiration_time=new_token_data["expiration_time"], ) - + token_keys = TokenUpdateKeys( admin_key=private_key, metadata_key=private_key, @@ -55,13 +61,13 @@ def test_constructor_with_parameters(mock_account_ids, private_key, new_token_da token_id=token_id, token_params=token_params, token_keys=token_keys, - token_key_verification_mode=TokenKeyValidation.FULL_VALIDATION + token_key_verification_mode=TokenKeyValidation.FULL_VALIDATION, ) assert update_tx.token_id == token_id assert update_tx.token_name == new_token_data["name"] assert update_tx.token_symbol == new_token_data["symbol"] - assert update_tx.token_memo == new_token_data["memo"] + assert update_tx.token_memo == new_token_data["memo"] assert update_tx.metadata == new_token_data["metadata"] assert update_tx.admin_key == private_key assert update_tx.metadata_key == private_key @@ -77,28 +83,29 @@ def test_constructor_with_parameters(mock_account_ids, private_key, new_token_da assert update_tx.kyc_key is None assert update_tx.fee_schedule_key is None + def test_build_transaction_body(mock_account_ids, new_token_data): """Test building a token update transaction body with valid values.""" operator_id, _, node_account_id, token_id, _ = mock_account_ids token_update_params = TokenUpdateParams( - treasury_account_id=operator_id, - token_name=new_token_data["name"], - token_symbol=new_token_data["symbol"], + treasury_account_id=operator_id, + token_name=new_token_data["name"], + token_symbol=new_token_data["symbol"], token_memo=new_token_data["memo"], metadata=new_token_data["metadata"], auto_renew_account_id=operator_id, auto_renew_period=new_token_data["auto_renew_period"], - expiration_time=new_token_data["expiration_time"] + expiration_time=new_token_data["expiration_time"], ) - + update_tx = TokenUpdateTransaction(token_id=token_id, token_params=token_update_params) - + # Set operator and node account IDs needed for building transaction body update_tx.operator_account_id = operator_id update_tx.node_account_id = node_account_id transaction_body = update_tx.build_transaction_body() - + assert transaction_body.tokenUpdate.token == token_id._to_proto() assert transaction_body.tokenUpdate.treasury == operator_id._to_proto() assert transaction_body.tokenUpdate.name == new_token_data["name"] @@ -110,37 +117,39 @@ def test_build_transaction_body(mock_account_ids, new_token_data): assert transaction_body.tokenUpdate.expiry.seconds == new_token_data["expiration_time"].seconds assert transaction_body.tokenUpdate.key_verification_mode == TokenKeyValidation.FULL_VALIDATION + def test_build_transaction_body_validation_errors(): """Test that build_transaction_body raises appropriate validation errors.""" # Test missing token_id update_tx = TokenUpdateTransaction() - + with pytest.raises(ValueError, match="Missing token ID"): update_tx.build_transaction_body() + def test_set_methods(mock_account_ids, private_key, new_token_data): """Test the set methods of TokenUpdateTransaction.""" operator_id, _, _, token_id, _ = mock_account_ids - + update_tx = TokenUpdateTransaction(token_id=token_id) - + test_cases = [ - ('set_token_id', token_id, 'token_id'), - ('set_token_name', new_token_data["name"], 'token_name'), - ('set_token_symbol', new_token_data["symbol"], 'token_symbol'), - ('set_token_memo', new_token_data["memo"], 'token_memo'), - ('set_metadata', new_token_data["metadata"], 'metadata'), - ('set_expiration_time', new_token_data["expiration_time"], 'expiration_time'), - ('set_auto_renew_period', new_token_data["auto_renew_period"], 'auto_renew_period'), - ('set_auto_renew_account_id', operator_id, 'auto_renew_account_id'), - ('set_admin_key', private_key, 'admin_key'), - ('set_freeze_key', private_key, 'freeze_key'), - ('set_wipe_key', private_key, 'wipe_key'), - ('set_supply_key', private_key, 'supply_key'), - ('set_metadata_key', private_key, 'metadata_key'), - ('set_pause_key', private_key, 'pause_key'), - ('set_kyc_key', private_key, 'kyc_key'), - ('set_fee_schedule_key', private_key, 'fee_schedule_key') + ("set_token_id", token_id, "token_id"), + ("set_token_name", new_token_data["name"], "token_name"), + ("set_token_symbol", new_token_data["symbol"], "token_symbol"), + ("set_token_memo", new_token_data["memo"], "token_memo"), + ("set_metadata", new_token_data["metadata"], "metadata"), + ("set_expiration_time", new_token_data["expiration_time"], "expiration_time"), + ("set_auto_renew_period", new_token_data["auto_renew_period"], "auto_renew_period"), + ("set_auto_renew_account_id", operator_id, "auto_renew_account_id"), + ("set_admin_key", private_key, "admin_key"), + ("set_freeze_key", private_key, "freeze_key"), + ("set_wipe_key", private_key, "wipe_key"), + ("set_supply_key", private_key, "supply_key"), + ("set_metadata_key", private_key, "metadata_key"), + ("set_pause_key", private_key, "pause_key"), + ("set_kyc_key", private_key, "kyc_key"), + ("set_fee_schedule_key", private_key, "fee_schedule_key"), ] for method_name, value, attr_name in test_cases: @@ -148,42 +157,42 @@ def test_set_methods(mock_account_ids, private_key, new_token_data): assert tx_after_set is update_tx assert getattr(update_tx, attr_name) == value + def test_set_methods_require_not_frozen(mock_account_ids, mock_client, new_token_data): """Test that set methods raise exception when transaction is frozen.""" operator_id, _, _, token_id, _ = mock_account_ids update_tx = TokenUpdateTransaction( - token_id=token_id, - token_params=TokenUpdateParams(token_name=new_token_data["name"]) + token_id=token_id, token_params=TokenUpdateParams(token_name=new_token_data["name"]) ) update_tx.freeze_with(mock_client) - + private_key = mock_client.operator_private_key - + test_cases = [ - ('set_token_id', token_id), - ('set_token_name', new_token_data["name"]), - ('set_token_symbol', new_token_data["symbol"]), - ('set_token_memo', new_token_data["memo"]), - ('set_metadata', new_token_data["metadata"]), - ('set_expiration_time', new_token_data["expiration_time"]), - ('set_auto_renew_period', new_token_data["auto_renew_period"]), - ('set_auto_renew_account_id', operator_id), - ('set_admin_key', private_key), - ('set_freeze_key', private_key), - ('set_wipe_key', private_key), - ('set_supply_key', private_key), - ('set_metadata_key', private_key), - ('set_pause_key', private_key), - ('set_kyc_key', private_key), - ('set_fee_schedule_key', private_key) + ("set_token_id", token_id), + ("set_token_name", new_token_data["name"]), + ("set_token_symbol", new_token_data["symbol"]), + ("set_token_memo", new_token_data["memo"]), + ("set_metadata", new_token_data["metadata"]), + ("set_expiration_time", new_token_data["expiration_time"]), + ("set_auto_renew_period", new_token_data["auto_renew_period"]), + ("set_auto_renew_account_id", operator_id), + ("set_admin_key", private_key), + ("set_freeze_key", private_key), + ("set_wipe_key", private_key), + ("set_supply_key", private_key), + ("set_metadata_key", private_key), + ("set_pause_key", private_key), + ("set_kyc_key", private_key), + ("set_fee_schedule_key", private_key), ] - + # Test all set methods raise exception when frozen for method_name, value in test_cases: with pytest.raises(Exception, match="Transaction is immutable; it has been frozen"): getattr(update_tx, method_name)(value) - + def test_update_transaction_can_execute(mock_account_ids, new_token_data): """Test that a token update transaction can be executed successfully.""" @@ -192,26 +201,22 @@ def test_update_transaction_can_execute(mock_account_ids, new_token_data): # Create test transaction responses ok_response = TransactionResponseProto() ok_response.nodeTransactionPrecheckCode = ResponseCode.OK - + # Create a mock receipt for a successful token update - mock_receipt_proto = TransactionReceiptProto( - status=ResponseCode.SUCCESS - ) - + mock_receipt_proto = TransactionReceiptProto(status=ResponseCode.SUCCESS) + # Create a response for the receipt query receipt_query_response = response_pb2.Response( transactionGetReceipt=transaction_get_receipt_pb2.TransactionGetReceiptResponse( - header=response_header_pb2.ResponseHeader( - nodeTransactionPrecheckCode=ResponseCode.OK - ), - receipt=mock_receipt_proto + header=response_header_pb2.ResponseHeader(nodeTransactionPrecheckCode=ResponseCode.OK), + receipt=mock_receipt_proto, ) ) - + response_sequences = [ [ok_response, receipt_query_response], ] - + with mock_hedera_servers(response_sequences) as client: transaction = ( TokenUpdateTransaction() @@ -221,15 +226,16 @@ def test_update_transaction_can_execute(mock_account_ids, new_token_data): .set_token_memo(new_token_data["memo"]) .set_metadata(new_token_data["metadata"]) ) - + receipt = transaction.execute(client) - + assert receipt.status == ResponseCode.SUCCESS, "Transaction should have succeeded" + def test_build_scheduled_body(mock_account_ids, private_key, new_token_data): """Test building a scheduled transaction body for token update transaction.""" operator_id, _, _, token_id, _ = mock_account_ids - + update_tx = ( TokenUpdateTransaction() .set_token_id(token_id) @@ -244,7 +250,7 @@ def test_build_scheduled_body(mock_account_ids, private_key, new_token_data): .set_admin_key(private_key) ) schedulable_body = update_tx.build_scheduled_body() - + # Verify the schedulable body has the correct structure and fields assert isinstance(schedulable_body, SchedulableTransactionBody) assert schedulable_body.HasField("tokenUpdate") @@ -264,29 +270,26 @@ def test_build_scheduled_body(mock_account_ids, private_key, new_token_data): def create_key(key_type, use_private): """ Create a key based on type and whether to use private or public. - + Args: key_type: "ed25519" or "ecdsa" use_private: True for PrivateKey, False for PublicKey - + Returns: The created key (PrivateKey or PublicKey) """ - if key_type == "ed25519": - private_key = PrivateKey.generate_ed25519() - else: # ecdsa - private_key = PrivateKey.generate_ecdsa() - + private_key = PrivateKey.generate_ed25519() if key_type == "ed25519" else PrivateKey.generate_ecdsa() + return private_key if use_private else private_key.public_key() def get_expected_public_key(key): """ Get the public key from either PrivateKey or PublicKey. - + Args: key: PrivateKey or PublicKey - + Returns: PublicKey """ @@ -296,7 +299,7 @@ def get_expected_public_key(key): def verify_key_in_proto(proto_key, expected_public_key, key_type): """ Verify the proto key matches expected public key. - + Args: proto_key: The proto key from the transaction body expected_public_key: The expected PublicKey @@ -310,80 +313,93 @@ def verify_key_in_proto(proto_key, expected_public_key, key_type): # Tests for PrivateKey and PublicKey support (ED25519 and ECDSA) -@pytest.mark.parametrize("key_type,use_private", [ - ("ed25519", True), - ("ed25519", False), - ("ecdsa", True), - ("ecdsa", False), -]) -@pytest.mark.parametrize("field_name,setter_name,proto_path", [ - ("admin_key", "set_admin_key", "adminKey"), - ("freeze_key", "set_freeze_key", "freezeKey"), - ("wipe_key", "set_wipe_key", "wipeKey"), - ("supply_key", "set_supply_key", "supplyKey"), - ("metadata_key", "set_metadata_key", "metadata_key"), - ("pause_key", "set_pause_key", "pause_key"), - ("kyc_key", "set_kyc_key", "kycKey"), - ("fee_schedule_key", "set_fee_schedule_key", "fee_schedule_key"), -]) +@pytest.mark.parametrize( + "key_type,use_private", + [ + ("ed25519", True), + ("ed25519", False), + ("ecdsa", True), + ("ecdsa", False), + ], +) +@pytest.mark.parametrize( + "field_name,setter_name,proto_path", + [ + ("admin_key", "set_admin_key", "adminKey"), + ("freeze_key", "set_freeze_key", "freezeKey"), + ("wipe_key", "set_wipe_key", "wipeKey"), + ("supply_key", "set_supply_key", "supplyKey"), + ("metadata_key", "set_metadata_key", "metadata_key"), + ("pause_key", "set_pause_key", "pause_key"), + ("kyc_key", "set_kyc_key", "kycKey"), + ("fee_schedule_key", "set_fee_schedule_key", "fee_schedule_key"), + ], +) def test_single_key_fields(mock_account_ids, key_type, use_private, field_name, setter_name, proto_path): """Test single key fields with different key types (PrivateKey and PublicKey).""" operator_id, _, node_account_id, token_id, _ = mock_account_ids - + # Create the key key = create_key(key_type, use_private) expected_public_key = get_expected_public_key(key) - + # Create transaction and set the key tx = TokenUpdateTransaction() tx.set_token_id(token_id) getattr(tx, setter_name)(key) + + assert getattr(tx, field_name) is not None + assert getattr(tx, field_name).to_bytes() == key.to_bytes() + tx.operator_account_id = operator_id tx.node_account_id = node_account_id - + # Build transaction body transaction_body = tx.build_transaction_body() - + # Get the proto key from the transaction body proto_key = getattr(transaction_body.tokenUpdate, proto_path) - + # Verify the proto key matches the expected public key verify_key_in_proto(proto_key, expected_public_key, key_type) -@pytest.mark.parametrize("key_type,use_private", [ - ("ed25519", True), - ("ed25519", False), - ("ecdsa", True), - ("ecdsa", False), -]) -def test_constructor_with_public_key(mock_account_ids, key_type, use_private, new_token_data): +@pytest.mark.parametrize( + "key_type,use_private", + [ + ("ed25519", True), + ("ed25519", False), + ("ecdsa", True), + ("ecdsa", False), + ], +) +def test_constructor_with_public_key(mock_account_ids, key_type, use_private): """Test constructor with PublicKey in TokenUpdateKeys.""" operator_id, _, _, token_id, _ = mock_account_ids - + admin_key = create_key(key_type, use_private) freeze_key = create_key(key_type, use_private) expected_admin_public = get_expected_public_key(admin_key) expected_freeze_public = get_expected_public_key(freeze_key) - + token_keys = TokenUpdateKeys( admin_key=admin_key, freeze_key=freeze_key, ) - + update_tx = TokenUpdateTransaction( token_id=token_id, token_keys=token_keys, ) - + assert update_tx.admin_key == admin_key assert update_tx.freeze_key == freeze_key - + # Verify keys are correctly stored update_tx.operator_account_id = operator_id update_tx.node_account_id = operator_id # Using operator_id as node_account_id for test transaction_body = update_tx.build_transaction_body() - + verify_key_in_proto(transaction_body.tokenUpdate.adminKey, expected_admin_public, key_type) verify_key_in_proto(transaction_body.tokenUpdate.freezeKey, expected_freeze_public, key_type) @@ -391,28 +407,28 @@ def test_constructor_with_public_key(mock_account_ids, key_type, use_private, ne def test_mixed_key_types_in_constructor(mock_account_ids): """Test constructor with mixed PrivateKey and PublicKey types.""" operator_id, _, _, token_id, _ = mock_account_ids - + ed25519_private = PrivateKey.generate_ed25519() ed25519_public = PrivateKey.generate_ed25519().public_key() ecdsa_private = PrivateKey.generate_ecdsa() ecdsa_public = PrivateKey.generate_ecdsa().public_key() - + token_keys = TokenUpdateKeys( admin_key=ed25519_private, freeze_key=ed25519_public, wipe_key=ecdsa_private, supply_key=ecdsa_public, ) - + tx = TokenUpdateTransaction( token_id=token_id, token_keys=token_keys, ) tx.operator_account_id = operator_id tx.node_account_id = operator_id - + transaction_body = tx.build_transaction_body() - + # Verify all keys are correctly converted assert transaction_body.tokenUpdate.adminKey.ed25519 == ed25519_private.public_key().to_bytes_raw() assert transaction_body.tokenUpdate.freezeKey.ed25519 == ed25519_public.to_bytes_raw() @@ -420,30 +436,33 @@ def test_mixed_key_types_in_constructor(mock_account_ids): assert transaction_body.tokenUpdate.supplyKey.HasField("ECDSA_secp256k1") -@pytest.mark.parametrize("key_type,use_private", [ - ("ed25519", True), - ("ed25519", False), - ("ecdsa", True), - ("ecdsa", False), -]) +@pytest.mark.parametrize( + "key_type,use_private", + [ + ("ed25519", True), + ("ed25519", False), + ("ecdsa", True), + ("ecdsa", False), + ], +) def test_build_transaction_body_with_keys(mock_account_ids, key_type, use_private, new_token_data): """Test building transaction body with keys (both PrivateKey and PublicKey).""" operator_id, _, node_account_id, token_id, _ = mock_account_ids - + admin_key = create_key(key_type, use_private) freeze_key = create_key(key_type, use_private) expected_admin_public = get_expected_public_key(admin_key) expected_freeze_public = get_expected_public_key(freeze_key) - + update_tx = TokenUpdateTransaction(token_id=token_id) update_tx.set_admin_key(admin_key) update_tx.set_freeze_key(freeze_key) update_tx.set_token_name(new_token_data["name"]) update_tx.operator_account_id = operator_id update_tx.node_account_id = node_account_id - + transaction_body = update_tx.build_transaction_body() - + assert transaction_body.tokenUpdate.name == new_token_data["name"] verify_key_in_proto(transaction_body.tokenUpdate.adminKey, expected_admin_public, key_type) verify_key_in_proto(transaction_body.tokenUpdate.freezeKey, expected_freeze_public, key_type) diff --git a/tests/unit/token_wipe_transaction_test.py b/tests/unit/token_wipe_transaction_test.py index 63e8099dc..d5cbb85ee 100644 --- a/tests/unit/token_wipe_transaction_test.py +++ b/tests/unit/token_wipe_transaction_test.py @@ -1,48 +1,44 @@ -import pytest +from __future__ import annotations + from unittest.mock import MagicMock -from requests import patch -from hiero_sdk_python.hapi.services.transaction_receipt_pb2 import TransactionReceipt as TransactionReceiptProto -from hiero_sdk_python.hapi.services.transaction_response_pb2 import TransactionResponse as TransactionResponseProto -from hiero_sdk_python.response_code import ResponseCode -from hiero_sdk_python.tokens.token_wipe_transaction import TokenWipeTransaction +import pytest + from hiero_sdk_python.hapi.services import response_header_pb2, response_pb2, timestamp_pb2, transaction_get_receipt_pb2 from hiero_sdk_python.hapi.services.schedulable_transaction_body_pb2 import ( SchedulableTransactionBody, ) +from hiero_sdk_python.hapi.services.transaction_receipt_pb2 import TransactionReceipt as TransactionReceiptProto +from hiero_sdk_python.hapi.services.transaction_response_pb2 import TransactionResponse as TransactionResponseProto +from hiero_sdk_python.response_code import ResponseCode +from hiero_sdk_python.tokens.token_wipe_transaction import TokenWipeTransaction from hiero_sdk_python.transaction.transaction_id import TransactionId - from tests.unit.mock_server import mock_hedera_servers + pytestmark = pytest.mark.unit + def generate_transaction_id(account_id_proto): """Generate a unique transaction ID based on the account ID and the current timestamp.""" import time + current_time = time.time() timestamp_seconds = int(current_time) timestamp_nanos = int((current_time - timestamp_seconds) * 1e9) tx_timestamp = timestamp_pb2.Timestamp(seconds=timestamp_seconds, nanos=timestamp_nanos) - tx_id = TransactionId( - valid_start=tx_timestamp, - account_id=account_id_proto - ) - return tx_id + return TransactionId(valid_start=tx_timestamp, account_id=account_id_proto) + def test_build_transaction_body(mock_account_ids): """Test building a token wipe transaction body with valid values.""" account_id, wipe_account_id, node_account_id, token_id, _ = mock_account_ids amount = 1000 - wipe_tx = ( - TokenWipeTransaction() - .set_token_id(token_id) - .set_account_id(wipe_account_id) - .set_amount(amount) - ) - + wipe_tx = TokenWipeTransaction().set_token_id(token_id).set_account_id(wipe_account_id).set_amount(amount) + wipe_tx.transaction_id = generate_transaction_id(account_id) wipe_tx.node_account_id = node_account_id @@ -58,19 +54,15 @@ def test_build_transaction_body(mock_account_ids): assert transaction_body.tokenWipe.amount == amount + # This test uses fixture mock_account_ids as parameter def test_build_transaction_body_with_serial_numbers(mock_account_ids): """Test building a token wipe transaction body with serial numbers for NFTs.""" account_id, wipe_account_id, node_account_id, token_id, _ = mock_account_ids serial_numbers = [1, 2, 3] - wipe_tx = ( - TokenWipeTransaction() - .set_token_id(token_id) - .set_account_id(wipe_account_id) - .set_serial(serial_numbers) - ) - + wipe_tx = TokenWipeTransaction().set_token_id(token_id).set_account_id(wipe_account_id).set_serial(serial_numbers) + wipe_tx.transaction_id = generate_transaction_id(account_id) wipe_tx.node_account_id = node_account_id @@ -86,26 +78,22 @@ def test_build_transaction_body_with_serial_numbers(mock_account_ids): assert transaction_body.tokenWipe.serialNumbers == serial_numbers + # This test uses fixture (mock_account_ids, mock_client) as parameter def test_to_proto(mock_account_ids, mock_client): """Test converting the token wipe transaction to protobuf format after signing.""" account_id, wipe_account_id, _, token_id, _ = mock_account_ids - + amount = 1000 - wipe_tx = ( - TokenWipeTransaction() - .set_token_id(token_id) - .set_account_id(wipe_account_id) - .set_amount(amount) - ) - + wipe_tx = TokenWipeTransaction().set_token_id(token_id).set_account_id(wipe_account_id).set_amount(amount) + wipe_tx.transaction_id = generate_transaction_id(account_id) wipe_key = MagicMock() - wipe_key.sign.return_value = b'signature' - wipe_key.public_key().to_bytes_raw.return_value = b'public_key' - + wipe_key.sign.return_value = b"signature" + wipe_key.public_key().to_bytes_raw.return_value = b"public_key" + wipe_tx.freeze_with(mock_client) wipe_tx.sign(wipe_key) @@ -114,40 +102,33 @@ def test_to_proto(mock_account_ids, mock_client): assert proto.signedTransactionBytes assert len(proto.signedTransactionBytes) > 0 + # This test uses fixture mock_account_ids as parameter def test_constructor_with_parameters(mock_account_ids): """Test creating a token wipe transaction with constructor parameters.""" _, wipe_account_id, _, token_id, _ = mock_account_ids amount = 1000 - wipe_tx = ( - TokenWipeTransaction() - .set_token_id(token_id) - .set_account_id(wipe_account_id) - .set_amount(amount) - ) + wipe_tx = TokenWipeTransaction().set_token_id(token_id).set_account_id(wipe_account_id).set_amount(amount) assert wipe_tx.token_id == token_id assert wipe_tx.account_id == wipe_account_id assert wipe_tx.amount == amount + # This test uses fixture mock_account_ids as parameter def test_constructor_with_serial_numbers(mock_account_ids): """Test creating a token wipe transaction with serial numbers in the constructor.""" _, account_id, _, token_id, _ = mock_account_ids serial_numbers = [1, 2, 3] - wipe_tx = ( - TokenWipeTransaction() - .set_token_id(token_id) - .set_account_id(account_id) - .set_serial(serial_numbers) - ) + wipe_tx = TokenWipeTransaction().set_token_id(token_id).set_account_id(account_id).set_serial(serial_numbers) assert wipe_tx.token_id == token_id assert wipe_tx.account_id == account_id assert wipe_tx.serial == serial_numbers - + + # This test uses fixture mock_account_ids as parameter def test_wipe_transaction_can_execute(mock_account_ids): """Test that a wipe transaction can be executed successfully.""" @@ -157,76 +138,59 @@ def test_wipe_transaction_can_execute(mock_account_ids): # Create test transaction responses ok_response = TransactionResponseProto() ok_response.nodeTransactionPrecheckCode = ResponseCode.OK - + # Create a mock receipt for a successful token wipe - mock_receipt_proto = TransactionReceiptProto( - status=ResponseCode.SUCCESS - ) - + mock_receipt_proto = TransactionReceiptProto(status=ResponseCode.SUCCESS) + # Create a response for the receipt query receipt_query_response = response_pb2.Response( transactionGetReceipt=transaction_get_receipt_pb2.TransactionGetReceiptResponse( - header=response_header_pb2.ResponseHeader( - nodeTransactionPrecheckCode=ResponseCode.OK - ), - receipt=mock_receipt_proto + header=response_header_pb2.ResponseHeader(nodeTransactionPrecheckCode=ResponseCode.OK), + receipt=mock_receipt_proto, ) ) - + response_sequences = [ [ok_response, receipt_query_response], ] - + with mock_hedera_servers(response_sequences) as client: - transaction = ( - TokenWipeTransaction() - .set_token_id(token_id) - .set_account_id(account_id) - .set_amount(amount) - ) - + transaction = TokenWipeTransaction().set_token_id(token_id).set_account_id(account_id).set_amount(amount) + receipt = transaction.execute(client) - + assert receipt.status == ResponseCode.SUCCESS, "Transaction should have succeeded" + def test_build_scheduled_body(mock_account_ids): """Test building a scheduled transaction body for token wipe transaction.""" _, wipe_account_id, _, token_id, _ = mock_account_ids amount = 1000 - - wipe_tx = ( - TokenWipeTransaction() - .set_token_id(token_id) - .set_account_id(wipe_account_id) - .set_amount(amount) - ) - + + wipe_tx = TokenWipeTransaction().set_token_id(token_id).set_account_id(wipe_account_id).set_amount(amount) + schedulable_body = wipe_tx.build_scheduled_body() - + # Verify the schedulable body has the correct structure and fields assert isinstance(schedulable_body, SchedulableTransactionBody) assert schedulable_body.HasField("tokenWipe") assert schedulable_body.tokenWipe.token == token_id._to_proto() assert schedulable_body.tokenWipe.account == wipe_account_id._to_proto() assert schedulable_body.tokenWipe.amount == amount - + + def test_build_scheduled_body_with_serial_numbers(mock_account_ids): """Test building a scheduled transaction body for token wipe transaction with serial numbers.""" _, wipe_account_id, _, token_id, _ = mock_account_ids serial_numbers = [1, 2, 3] - - wipe_tx = ( - TokenWipeTransaction() - .set_token_id(token_id) - .set_account_id(wipe_account_id) - .set_serial(serial_numbers) - ) - + + wipe_tx = TokenWipeTransaction().set_token_id(token_id).set_account_id(wipe_account_id).set_serial(serial_numbers) + schedulable_body = wipe_tx.build_scheduled_body() - + # Verify the schedulable body has the correct structure and fields assert isinstance(schedulable_body, SchedulableTransactionBody) assert schedulable_body.HasField("tokenWipe") assert schedulable_body.tokenWipe.token == token_id._to_proto() assert schedulable_body.tokenWipe.account == wipe_account_id._to_proto() - assert schedulable_body.tokenWipe.serialNumbers == serial_numbers \ No newline at end of file + assert schedulable_body.tokenWipe.serialNumbers == serial_numbers diff --git a/tests/unit/topic_create_transaction_test.py b/tests/unit/topic_create_transaction_test.py index 31ea57b2e..4c92ce28e 100644 --- a/tests/unit/topic_create_transaction_test.py +++ b/tests/unit/topic_create_transaction_test.py @@ -1,29 +1,31 @@ """Tests for the TopicCreateTransaction functionality.""" +from __future__ import annotations + import pytest -from hiero_sdk_python.consensus.topic_create_transaction import TopicCreateTransaction from hiero_sdk_python.account.account_id import AccountId +from hiero_sdk_python.consensus.topic_create_transaction import TopicCreateTransaction +from hiero_sdk_python.consensus.topic_id import TopicId from hiero_sdk_python.crypto.private_key import PrivateKey from hiero_sdk_python.crypto.public_key import PublicKey -from hiero_sdk_python.response_code import ResponseCode -from hiero_sdk_python.consensus.topic_id import TopicId from hiero_sdk_python.hapi.services import ( basic_types_pb2, response_header_pb2, - response_pb2, + response_pb2, transaction_get_receipt_pb2, + transaction_receipt_pb2, transaction_response_pb2, - transaction_receipt_pb2 ) from hiero_sdk_python.hapi.services.schedulable_transaction_body_pb2 import ( SchedulableTransactionBody, ) - +from hiero_sdk_python.response_code import ResponseCode from hiero_sdk_python.tokens.custom_fixed_fee import CustomFixedFee from hiero_sdk_python.tokens.token_id import TokenId from tests.unit.mock_server import mock_hedera_servers + pytestmark = pytest.mark.unit @@ -58,29 +60,25 @@ def multiple_custom_fees(): def create_key(key_type, use_private): """ Create a key based on type and whether to use private or public. - + Args: key_type: "ed25519" or "ecdsa" use_private: True for PrivateKey, False for PublicKey - + Returns: The created key (PrivateKey or PublicKey) """ - if key_type == "ed25519": - private_key = PrivateKey.generate_ed25519() - else: # ecdsa - private_key = PrivateKey.generate("ecdsa") - + private_key = PrivateKey.generate_ed25519() if key_type == "ed25519" else PrivateKey.generate("ecdsa") return private_key if use_private else private_key.public_key() def get_expected_public_key(key): """ Get the public key from either PrivateKey or PublicKey. - + Args: key: PrivateKey or PublicKey - + Returns: PublicKey """ @@ -90,7 +88,7 @@ def get_expected_public_key(key): def verify_key_in_proto(proto_key, expected_public_key, key_type): """ Verify the proto key matches expected public key. - + Args: proto_key: The proto key from the transaction body expected_public_key: The expected PublicKey @@ -104,12 +102,15 @@ def verify_key_in_proto(proto_key, expected_public_key, key_type): # This test uses fixture mock_account_ids as parameter -@pytest.mark.parametrize("key_type,use_private", [ - ("ed25519", True), - ("ed25519", False), - ("ecdsa", True), - ("ecdsa", False), -]) +@pytest.mark.parametrize( + "key_type,use_private", + [ + ("ed25519", True), + ("ed25519", False), + ("ecdsa", True), + ("ecdsa", False), + ], +) def test_build_topic_create_transaction_body(mock_account_ids, custom_fixed_fee, key_type, use_private): """Test building a TopicCreateTransaction body with different key types.""" _, _, node_account_id, _, _ = mock_account_ids @@ -147,22 +148,29 @@ def test_build_topic_create_transaction_body(mock_account_ids, custom_fixed_fee, assert len(transaction_body.consensusCreateTopic.custom_fees) == 1 verify_key_in_proto(transaction_body.consensusCreateTopic.fee_schedule_key, expected_fee_schedule_public, key_type) assert len(transaction_body.consensusCreateTopic.fee_exempt_key_list) == 2 - verify_key_in_proto(transaction_body.consensusCreateTopic.fee_exempt_key_list[0], expected_fee_exempt_publics[0], key_type) - verify_key_in_proto(transaction_body.consensusCreateTopic.fee_exempt_key_list[1], expected_fee_exempt_publics[1], key_type) + verify_key_in_proto( + transaction_body.consensusCreateTopic.fee_exempt_key_list[0], expected_fee_exempt_publics[0], key_type + ) + verify_key_in_proto( + transaction_body.consensusCreateTopic.fee_exempt_key_list[1], expected_fee_exempt_publics[1], key_type + ) -@pytest.mark.parametrize("key_type,use_private", [ - ("ed25519", True), - ("ed25519", False), - ("ecdsa", True), - ("ecdsa", False), -]) +@pytest.mark.parametrize( + "key_type,use_private", + [ + ("ed25519", True), + ("ed25519", False), + ("ecdsa", True), + ("ecdsa", False), + ], +) def test_build_scheduled_body(mock_account_ids, custom_fixed_fee, key_type, use_private): """ Test building a scheduled body for TopicCreateTransaction with valid properties. """ _, _, node_account_id, _, _ = mock_account_ids - + admin_key = create_key(key_type, use_private) submit_key = create_key(key_type, use_private) fee_schedule_key = create_key(key_type, use_private) @@ -188,13 +196,13 @@ def test_build_scheduled_body(mock_account_ids, custom_fixed_fee, key_type, use_ # Build the scheduled transaction body schedulable_body = tx.build_scheduled_body() - + # Verify it's the right type assert isinstance(schedulable_body, SchedulableTransactionBody) - + # Verify the transaction was built with the topic create type assert schedulable_body.HasField("consensusCreateTopic") - + # Verify fields in the scheduled body assert schedulable_body.consensusCreateTopic.memo == "Scheduled Topic" verify_key_in_proto(schedulable_body.consensusCreateTopic.adminKey, expected_admin_public, key_type) @@ -203,8 +211,12 @@ def test_build_scheduled_body(mock_account_ids, custom_fixed_fee, key_type, use_ assert len(schedulable_body.consensusCreateTopic.custom_fees) == 1 verify_key_in_proto(schedulable_body.consensusCreateTopic.fee_schedule_key, expected_fee_schedule_public, key_type) assert len(schedulable_body.consensusCreateTopic.fee_exempt_key_list) == 2 - verify_key_in_proto(schedulable_body.consensusCreateTopic.fee_exempt_key_list[0], expected_fee_exempt_publics[0], key_type) - verify_key_in_proto(schedulable_body.consensusCreateTopic.fee_exempt_key_list[1], expected_fee_exempt_publics[1], key_type) + verify_key_in_proto( + schedulable_body.consensusCreateTopic.fee_exempt_key_list[0], expected_fee_exempt_publics[0], key_type + ) + verify_key_in_proto( + schedulable_body.consensusCreateTopic.fee_exempt_key_list[1], expected_fee_exempt_publics[1], key_type + ) # This test uses fixture mock_account_ids as parameter @@ -220,6 +232,7 @@ def test_missing_operator_in_topic_create(mock_account_ids): with pytest.raises(ValueError, match="Operator account ID is not set."): tx.build_transaction_body() + def test_missing_node_in_topic_create(): """ Test that building the body fails if no node account ID is set. @@ -230,6 +243,7 @@ def test_missing_node_in_topic_create(): with pytest.raises(ValueError, match="Node account ID is not set."): tx.build_transaction_body() + # This test uses fixtures (mock_account_ids, private_key) as parameters def test_sign_topic_create_transaction(mock_account_ids, private_key): """ @@ -246,45 +260,36 @@ def test_sign_topic_create_transaction(mock_account_ids, private_key): tx.sign(private_key) assert len(tx._signature_map[body_bytes].sigPair) == 1 + def test_execute_topic_create_transaction(): """Test executing the TopicCreateTransaction successfully with mock server.""" # Create success response for the transaction submission - tx_response = transaction_response_pb2.TransactionResponse( - nodeTransactionPrecheckCode=ResponseCode.OK - ) - + tx_response = transaction_response_pb2.TransactionResponse(nodeTransactionPrecheckCode=ResponseCode.OK) + # Create receipt response with SUCCESS status and a topic ID topic_id = basic_types_pb2.TopicID(shardNum=0, realmNum=0, topicNum=123) receipt_response = response_pb2.Response( transactionGetReceipt=transaction_get_receipt_pb2.TransactionGetReceiptResponse( - header=response_header_pb2.ResponseHeader( - nodeTransactionPrecheckCode=ResponseCode.OK - ), - receipt=transaction_receipt_pb2.TransactionReceipt( - status=ResponseCode.SUCCESS, topicID=topic_id - ), + header=response_header_pb2.ResponseHeader(nodeTransactionPrecheckCode=ResponseCode.OK), + receipt=transaction_receipt_pb2.TransactionReceipt(status=ResponseCode.SUCCESS, topicID=topic_id), ) ) - + response_sequences = [ [tx_response, receipt_response], ] - + with mock_hedera_servers(response_sequences) as client: # Use PublicKey for this test to match original behavior admin_key = PrivateKey.generate_ed25519().public_key() - tx = ( - TopicCreateTransaction() - .set_memo("Execute test with mock server") - .set_admin_key(admin_key) - ) - + tx = TopicCreateTransaction().set_memo("Execute test with mock server").set_admin_key(admin_key) + try: receipt = tx.execute(client) except Exception as e: pytest.fail(f"Should not raise exception, but raised: {e}") - + # Verify the receipt contains the expected values assert receipt.status == ResponseCode.SUCCESS assert isinstance(receipt.topic_id, TopicId) @@ -293,12 +298,15 @@ def test_execute_topic_create_transaction(): assert receipt.topic_id.num == 123 -@pytest.mark.parametrize("key_type,use_private", [ - ("ed25519", True), - ("ed25519", False), - ("ecdsa", True), - ("ecdsa", False), -]) +@pytest.mark.parametrize( + "key_type,use_private", + [ + ("ed25519", True), + ("ed25519", False), + ("ecdsa", True), + ("ecdsa", False), + ], +) def test_constructor(multiple_custom_fees, key_type, use_private): """Test constructor with all fields using different key types.""" admin_key = create_key(key_type, use_private) @@ -353,12 +361,15 @@ def test_set_custom_fees(multiple_custom_fees): assert result is tx -@pytest.mark.parametrize("key_type,use_private", [ - ("ed25519", True), - ("ed25519", False), - ("ecdsa", True), - ("ecdsa", False), -]) +@pytest.mark.parametrize( + "key_type,use_private", + [ + ("ed25519", True), + ("ed25519", False), + ("ecdsa", True), + ("ecdsa", False), + ], +) def test_set_fee_schedule_key(key_type, use_private): """Test setting fee schedule key for the topic creation transaction.""" tx = TopicCreateTransaction() @@ -369,12 +380,15 @@ def test_set_fee_schedule_key(key_type, use_private): assert result is tx # Method chaining -@pytest.mark.parametrize("key_type,use_private", [ - ("ed25519", True), - ("ed25519", False), - ("ecdsa", True), - ("ecdsa", False), -]) +@pytest.mark.parametrize( + "key_type,use_private", + [ + ("ed25519", True), + ("ed25519", False), + ("ecdsa", True), + ("ecdsa", False), + ], +) def test_set_fee_exempt_keys(key_type, use_private): """Test setting fee exempt keys for the topic creation transaction.""" tx = TopicCreateTransaction() @@ -395,12 +409,15 @@ def test_set_fee_exempt_keys(key_type, use_private): assert result is tx2 -@pytest.mark.parametrize("key_type,use_private", [ - ("ed25519", True), - ("ed25519", False), - ("ecdsa", True), - ("ecdsa", False), -]) +@pytest.mark.parametrize( + "key_type,use_private", + [ + ("ed25519", True), + ("ed25519", False), + ("ecdsa", True), + ("ecdsa", False), + ], +) def test_method_chaining(custom_fixed_fee, key_type, use_private): """Test method chaining functionality.""" tx = TopicCreateTransaction() @@ -419,15 +436,16 @@ def test_method_chaining(custom_fixed_fee, key_type, use_private): assert tx.fee_exempt_keys == fee_exempt_keys -@pytest.mark.parametrize("key_type,use_private", [ - ("ed25519", True), - ("ed25519", False), - ("ecdsa", True), - ("ecdsa", False), -]) -def test_set_methods_require_not_frozen( - mock_account_ids, custom_fixed_fee, mock_client, key_type, use_private -): +@pytest.mark.parametrize( + "key_type,use_private", + [ + ("ed25519", True), + ("ed25519", False), + ("ecdsa", True), + ("ecdsa", False), + ], +) +def test_set_methods_require_not_frozen(mock_account_ids, custom_fixed_fee, mock_client, key_type, use_private): """Test that setter methods raise exception when transaction is frozen.""" _, _, node_account_id, _, _ = mock_account_ids @@ -446,73 +464,84 @@ def test_set_methods_require_not_frozen( ] for method_name, value in test_cases: - with pytest.raises( - Exception, match="Transaction is immutable; it has been frozen" - ): + with pytest.raises(Exception, match="Transaction is immutable; it has been frozen"): getattr(tx, method_name)(value) # Tests for PrivateKey and PublicKey support (ED25519 and ECDSA) -@pytest.mark.parametrize("key_type,use_private", [ - ("ed25519", True), - ("ed25519", False), - ("ecdsa", True), - ("ecdsa", False), -]) -@pytest.mark.parametrize("field_name,setter_name,proto_path", [ - ("admin_key", "set_admin_key", "adminKey"), - ("submit_key", "set_submit_key", "submitKey"), - ("fee_schedule_key", "set_fee_schedule_key", "fee_schedule_key"), -]) +@pytest.mark.parametrize( + "key_type,use_private", + [ + ("ed25519", True), + ("ed25519", False), + ("ecdsa", True), + ("ecdsa", False), + ], +) +@pytest.mark.parametrize( + "field_name,setter_name,proto_path", + [ + ("admin_key", "set_admin_key", "adminKey"), + ("submit_key", "set_submit_key", "submitKey"), + ("fee_schedule_key", "set_fee_schedule_key", "fee_schedule_key"), + ], +) def test_single_key_fields(mock_account_ids, key_type, use_private, field_name, setter_name, proto_path): """Test single key fields (admin_key, submit_key, fee_schedule_key) with different key types.""" _, _, node_account_id, _, _ = mock_account_ids - + # Create the key key = create_key(key_type, use_private) expected_public_key = get_expected_public_key(key) - + # Create transaction and set the key tx = TopicCreateTransaction() getattr(tx, setter_name)(key) + + assert getattr(tx, field_name) is not None + assert getattr(tx, field_name).to_bytes() == key.to_bytes() + tx.operator_account_id = AccountId(0, 0, 2) tx.node_account_id = node_account_id - + # Build transaction body transaction_body = tx.build_transaction_body() - + # Get the proto key from the transaction body proto_key = getattr(transaction_body.consensusCreateTopic, proto_path) - + # Verify the proto key matches the expected public key verify_key_in_proto(proto_key, expected_public_key, key_type) -@pytest.mark.parametrize("key_type,use_private", [ - ("ed25519", True), - ("ed25519", False), - ("ecdsa", True), - ("ecdsa", False), -]) +@pytest.mark.parametrize( + "key_type,use_private", + [ + ("ed25519", True), + ("ed25519", False), + ("ecdsa", True), + ("ecdsa", False), + ], +) def test_fee_exempt_keys(mock_account_ids, key_type, use_private): """Test fee_exempt_keys list with different key types.""" _, _, node_account_id, _, _ = mock_account_ids - + # Create two keys key1 = create_key(key_type, use_private) key2 = create_key(key_type, use_private) expected_public_key1 = get_expected_public_key(key1) expected_public_key2 = get_expected_public_key(key2) - + # Create transaction and set the keys tx = TopicCreateTransaction() tx.set_fee_exempt_keys([key1, key2]) tx.operator_account_id = AccountId(0, 0, 2) tx.node_account_id = node_account_id - + # Build transaction body transaction_body = tx.build_transaction_body() - + # Verify the proto keys match the expected public keys fee_exempt_key_list = transaction_body.consensusCreateTopic.fee_exempt_key_list assert len(fee_exempt_key_list) == 2 @@ -523,26 +552,25 @@ def test_fee_exempt_keys(mock_account_ids, key_type, use_private): def test_mixed_key_types_in_constructor(mock_account_ids): """Test constructor with mixed PrivateKey and PublicKey types.""" _, _, node_account_id, _, _ = mock_account_ids - + ed25519_private = PrivateKey.generate_ed25519() ed25519_public = PrivateKey.generate_ed25519().public_key() ecdsa_private = PrivateKey.generate("ecdsa") ecdsa_public = PrivateKey.generate("ecdsa").public_key() - + tx = TopicCreateTransaction( admin_key=ed25519_private, submit_key=ed25519_public, fee_schedule_key=ecdsa_private, - fee_exempt_keys=[ecdsa_public, ed25519_private] + fee_exempt_keys=[ecdsa_public, ed25519_private], ) tx.operator_account_id = AccountId(0, 0, 2) tx.node_account_id = node_account_id - + transaction_body = tx.build_transaction_body() - + # Verify all keys are correctly converted assert transaction_body.consensusCreateTopic.adminKey.ed25519 == ed25519_private.public_key().to_bytes_raw() assert transaction_body.consensusCreateTopic.submitKey.ed25519 == ed25519_public.to_bytes_raw() assert transaction_body.consensusCreateTopic.fee_schedule_key.HasField("ECDSA_secp256k1") assert len(transaction_body.consensusCreateTopic.fee_exempt_key_list) == 2 - diff --git a/tests/unit/topic_delete_transaction_test.py b/tests/unit/topic_delete_transaction_test.py index ebb3f1f40..925c212fe 100644 --- a/tests/unit/topic_delete_transaction_test.py +++ b/tests/unit/topic_delete_transaction_test.py @@ -1,25 +1,28 @@ """Tests for the TopicDeleteTransaction functionality.""" +from __future__ import annotations + import pytest from hiero_sdk_python.account.account_id import AccountId from hiero_sdk_python.consensus.topic_delete_transaction import TopicDeleteTransaction from hiero_sdk_python.hapi.services import ( - response_header_pb2, + response_header_pb2, response_pb2, transaction_get_receipt_pb2, transaction_receipt_pb2, - transaction_response_pb2 + transaction_response_pb2, ) from hiero_sdk_python.hapi.services.schedulable_transaction_body_pb2 import ( SchedulableTransactionBody, ) from hiero_sdk_python.response_code import ResponseCode - from tests.unit.mock_server import mock_hedera_servers + pytestmark = pytest.mark.unit + # This test uses fixtures (mock_account_ids, topic_id) as parameters def test_build_topic_delete_transaction_body(mock_account_ids, topic_id): """Test building a TopicDeleteTransaction body with a valid topic ID.""" @@ -31,28 +34,30 @@ def test_build_topic_delete_transaction_body(mock_account_ids, topic_id): transaction_body = tx.build_transaction_body() assert transaction_body.consensusDeleteTopic.topicID.topicNum == 1234 - + + # This test uses fixtures (mock_account_ids, topic_id) as parameters def test_build_scheduled_body(mock_account_ids, topic_id): """Test building a schedulable TopicDeleteTransaction body with a valid topic ID.""" _, _, node_account_id, _, _ = mock_account_ids - + # Create transaction and set required fields tx = TopicDeleteTransaction() tx.set_topic_id(topic_id) - + # Build the scheduled body schedulable_body = tx.build_scheduled_body() - + # Verify the correct type is returned assert isinstance(schedulable_body, SchedulableTransactionBody) - + # Verify the transaction was built with topic delete type assert schedulable_body.HasField("consensusDeleteTopic") - + # Verify the topic ID was correctly set assert schedulable_body.consensusDeleteTopic.topicID.topicNum == 1234 + # This test uses fixture mock_account_ids as parameter def test_missing_topic_id_in_delete(mock_account_ids): """Test that building fails if no topic ID is provided.""" @@ -64,6 +69,7 @@ def test_missing_topic_id_in_delete(mock_account_ids): with pytest.raises(ValueError, match="Missing required fields"): tx.build_transaction_body() + # This test uses fixtures (mock_account_ids, topic_id, private_key) as parameters def test_sign_topic_delete_transaction(mock_account_ids, topic_id, private_key): """Test signing the TopicDeleteTransaction with a private key.""" @@ -78,40 +84,32 @@ def test_sign_topic_delete_transaction(mock_account_ids, topic_id, private_key): tx.sign(private_key) assert len(tx._signature_map[body_bytes].sigPair) == 1 + # This test uses fixture topic_id as parameter def test_execute_topic_delete_transaction(topic_id): """Test executing the TopicDeleteTransaction successfully with mock server.""" # Create success response for the transaction submission - tx_response = transaction_response_pb2.TransactionResponse( - nodeTransactionPrecheckCode=ResponseCode.OK - ) - + tx_response = transaction_response_pb2.TransactionResponse(nodeTransactionPrecheckCode=ResponseCode.OK) + # Create receipt response with SUCCESS status receipt_response = response_pb2.Response( transactionGetReceipt=transaction_get_receipt_pb2.TransactionGetReceiptResponse( - header=response_header_pb2.ResponseHeader( - nodeTransactionPrecheckCode=ResponseCode.OK - ), - receipt=transaction_receipt_pb2.TransactionReceipt( - status=ResponseCode.SUCCESS - ) + header=response_header_pb2.ResponseHeader(nodeTransactionPrecheckCode=ResponseCode.OK), + receipt=transaction_receipt_pb2.TransactionReceipt(status=ResponseCode.SUCCESS), ) ) - + response_sequences = [ [tx_response, receipt_response], ] - + with mock_hedera_servers(response_sequences) as client: - tx = ( - TopicDeleteTransaction() - .set_topic_id(topic_id) - ) - + tx = TopicDeleteTransaction().set_topic_id(topic_id) + try: receipt = tx.execute(client) except Exception as e: pytest.fail(f"Should not raise exception, but raised: {e}") - + # Verify the receipt contains the expected values - assert receipt.status == ResponseCode.SUCCESS \ No newline at end of file + assert receipt.status == ResponseCode.SUCCESS diff --git a/tests/unit/topic_id_test.py b/tests/unit/topic_id_test.py index 7f453a364..56c75559b 100644 --- a/tests/unit/topic_id_test.py +++ b/tests/unit/topic_id_test.py @@ -1,122 +1,139 @@ +from __future__ import annotations + import pytest from hiero_sdk_python.consensus.topic_id import TopicId + pytestmark = pytest.mark.unit + @pytest.fixture def client(mock_client): - mock_client.network.ledger_id = bytes.fromhex("00") # mainnet ledger id + mock_client.network.ledger_id = bytes.fromhex("00") # mainnet ledger id return mock_client + def test_default_initialization(): """Test TopicId initialization with default values.""" topic_id = TopicId() - + assert topic_id.shard == 0 assert topic_id.realm == 0 assert topic_id.num == 0 - assert topic_id.checksum == None + assert topic_id.checksum is None + def test_custom_initialization(): """Test TopicId initialization with custom values.""" topic_id = TopicId(shard=1, realm=2, num=3) - + assert topic_id.shard == 1 assert topic_id.realm == 2 assert topic_id.num == 3 - assert topic_id.checksum == None + assert topic_id.checksum is None + def test_str_representation(): """Test string representation of TopicId.""" topic_id = TopicId(shard=1, realm=2, num=3) - + assert str(topic_id) == "1.2.3" + def test_str_representation_default(): """Test string representation of TopicId with default values.""" topic_id = TopicId() - + assert str(topic_id) == "0.0.0" + def test_from_string_valid(): """Test creating TopicId from valid string format.""" topic_id = TopicId.from_string("1.2.3") - + assert topic_id.shard == 1 assert topic_id.realm == 2 assert topic_id.num == 3 - assert topic_id.checksum == None + assert topic_id.checksum is None + def test_from_string_with_spaces(): """Test creating TopicId from string with leading/trailing spaces.""" topic_id = TopicId.from_string("1.2.3") - + assert topic_id.shard == 1 assert topic_id.realm == 2 assert topic_id.num == 3 - assert topic_id.checksum == None + assert topic_id.checksum is None + def test_from_string_zeros(): """Test creating TopicId from string with zero values.""" topic_id = TopicId.from_string("0.0.0") - + assert topic_id.shard == 0 assert topic_id.realm == 0 assert topic_id.num == 0 - assert topic_id.checksum == None + assert topic_id.checksum is None + def test_from_string_large_numbers(): """Test creating TopicId from string with large numbers.""" topic_id = TopicId.from_string("999.888.777") - + assert topic_id.shard == 999 assert topic_id.realm == 888 assert topic_id.num == 777 - assert topic_id.checksum == None + assert topic_id.checksum is None + def test_from_string_with_checksum(): """Test creating TopicId from string with leading/trailing spaces.""" topic_id = TopicId.from_string("1.2.3-abcde") - + assert topic_id.shard == 1 assert topic_id.realm == 2 assert topic_id.num == 3 assert topic_id.checksum == "abcde" + @pytest.mark.parametrize( - 'invalid_id', + "invalid_id", [ - '1.2', # Too few parts - '1.2.3.4', # Too many parts - 'a.b.c', # Non-numeric parts - '', # Empty string - '1.a.3', # Partial numeric + "1.2", # Too few parts + "1.2.3.4", # Too many parts + "a.b.c", # Non-numeric parts + "", # Empty string + "1.a.3", # Partial numeric 123, None, - '0.0.-1', - 'abc.def.ghi', - '0.0.1-ad', - '0.0.1-addefgh', - '0.0.1 - abcde', - ' 0.0.100 ' - ] + "0.0.-1", + "abc.def.ghi", + "0.0.1-ad", + "0.0.1-addefgh", + "0.0.1 - abcde", + " 0.0.100 ", + ], ) def test_from_string_for_invalid_format(invalid_id): """Should raise error when creating TopicId from invalid string input.""" with pytest.raises((TypeError, ValueError)): TopicId.from_string(invalid_id) + def test_str_representaion_with_checksum(client): """Should return string with checksum when ledger id is provided.""" topic_id = TopicId.from_string("0.0.1") assert topic_id.to_string_with_checksum(client) == "0.0.1-dfkxr" + def test_validate_checksum_success(client): """Should pass checksum validation when checksum is correct.""" topic_id = TopicId.from_string("0.0.1-dfkxr") topic_id.validate_checksum(client) + def test_validate_checksum_failure(client): """Should raise ValueError if checksum validation fails.""" topic_id = TopicId.from_string("0.0.1-wronx") @@ -124,6 +141,7 @@ def test_validate_checksum_failure(client): with pytest.raises(ValueError): topic_id.validate_checksum(client) + def test_topic_id_repr(): """Test that __repr__ returns the expected format.""" topic_id = TopicId(0, 0, 42) diff --git a/tests/unit/topic_info_query_test.py b/tests/unit/topic_info_query_test.py index 6bae027b2..335d0a4fc 100644 --- a/tests/unit/topic_info_query_test.py +++ b/tests/unit/topic_info_query_test.py @@ -1,5 +1,7 @@ """Tests for the TopicInfoQuery functionality.""" +from __future__ import annotations + import pytest from hiero_sdk_python.consensus.topic_info import TopicInfo @@ -8,57 +10,53 @@ basic_types_pb2, consensus_get_topic_info_pb2, consensus_topic_info_pb2, + query_pb2, response_header_pb2, response_pb2, - query_pb2, ) from hiero_sdk_python.hapi.services.query_header_pb2 import ResponseType from hiero_sdk_python.query.topic_info_query import TopicInfoQuery from hiero_sdk_python.response_code import ResponseCode - from tests.unit.mock_server import mock_hedera_servers + pytestmark = pytest.mark.unit + def create_topic_info_response(status_code=ResponseCode.OK, with_info=True): """Helper function to create a topic info response with the given status.""" - topic_info = consensus_topic_info_pb2.ConsensusTopicInfo( - memo="Test topic", - runningHash=b"\x00" * 48, - sequenceNumber=10, - adminKey=basic_types_pb2.Key(ed25519=b"\x01" * 32) - ) if with_info else consensus_topic_info_pb2.ConsensusTopicInfo() - - responses = [ + topic_info = ( + consensus_topic_info_pb2.ConsensusTopicInfo( + memo="Test topic", + runningHash=b"\x00" * 48, + sequenceNumber=10, + adminKey=basic_types_pb2.Key(ed25519=b"\x01" * 32), + ) + if with_info + else consensus_topic_info_pb2.ConsensusTopicInfo() + ) + + return [ response_pb2.Response( consensusGetTopicInfo=consensus_get_topic_info_pb2.ConsensusGetTopicInfoResponse( header=response_header_pb2.ResponseHeader( - nodeTransactionPrecheckCode=ResponseCode.OK, - responseType=ResponseType.COST_ANSWER, - cost=2 + nodeTransactionPrecheckCode=ResponseCode.OK, responseType=ResponseType.COST_ANSWER, cost=2 ) ) ), response_pb2.Response( consensusGetTopicInfo=consensus_get_topic_info_pb2.ConsensusGetTopicInfoResponse( header=response_header_pb2.ResponseHeader( - nodeTransactionPrecheckCode=ResponseCode.OK, - responseType=ResponseType.COST_ANSWER, - cost=2 + nodeTransactionPrecheckCode=ResponseCode.OK, responseType=ResponseType.COST_ANSWER, cost=2 ) ) ), response_pb2.Response( consensusGetTopicInfo=consensus_get_topic_info_pb2.ConsensusGetTopicInfoResponse( - header=response_header_pb2.ResponseHeader( - nodeTransactionPrecheckCode=status_code - ), - topicInfo=topic_info + header=response_header_pb2.ResponseHeader(nodeTransactionPrecheckCode=status_code), topicInfo=topic_info ) - ) + ), ] - - return responses def _response_with_status(status: ResponseCode) -> response_pb2.Response: @@ -74,22 +72,19 @@ def test_topic_info_query(topic_id): """Test basic functionality of TopicInfoQuery with mock server.""" responses = create_topic_info_response() response_sequences = [responses] - + with mock_hedera_servers(response_sequences) as client: - query = ( - TopicInfoQuery() - .set_topic_id(topic_id) - ) - + query = TopicInfoQuery().set_topic_id(topic_id) + try: # Get the cost of executing the query - should be 2 tinybars based on the mock response cost = query.get_cost(client) assert cost.to_tinybars() == 2 - + result = query.execute(client) except Exception as e: pytest.fail(f"Unexpected exception raised: {e}") - + # Verify the result contains the expected values assert isinstance(result, TopicInfo) assert result.memo == "Test topic" @@ -102,10 +97,10 @@ def test_topic_info_query_with_empty_topic_id(): """Test that TopicInfoQuery validates topic_id before execution.""" with mock_hedera_servers([[None]]) as client: query = TopicInfoQuery() # No topic ID set - + with pytest.raises(ValueError) as exc_info: query.execute(client) - + assert "Topic ID must be set" in str(exc_info.value) @@ -125,10 +120,7 @@ def test_make_request_builds_expected_protobuf(topic_id): # Compare serialized protobuf to avoid equality quirks expected_topic_id_proto = topic_id._to_proto() - assert ( - req.consensusGetTopicInfo.topicID.SerializeToString() - == expected_topic_id_proto.SerializeToString() - ) + assert req.consensusGetTopicInfo.topicID.SerializeToString() == expected_topic_id_proto.SerializeToString() @pytest.mark.parametrize( diff --git a/tests/unit/topic_info_test.py b/tests/unit/topic_info_test.py index ca6b69abd..e278de3ee 100644 --- a/tests/unit/topic_info_test.py +++ b/tests/unit/topic_info_test.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import pytest from hiero_sdk_python.account.account_id import AccountId @@ -9,6 +11,7 @@ from hiero_sdk_python.hapi.services.timestamp_pb2 import Timestamp from hiero_sdk_python.tokens.custom_fixed_fee import CustomFixedFee + pytestmark = pytest.mark.unit @@ -256,6 +259,7 @@ def test_repr_and_str(topic_info): assert "expiration_time=2021-07-01 00:00:00" in repr_output assert "auto_renew_period=7776000" in repr_output + def test_str_formatting(topic_info): """Test the string formatting of the TopicInfo class.""" str_output = str(topic_info) @@ -266,7 +270,7 @@ def test_str_formatting(topic_info): assert "sequence_number=42" in str_output assert "expiration_time=2021-07-01 00:00:00" in str_output assert "admin_key=ed25519(" in str_output - assert "submit_key=ed25519(" in str_output + assert "submit_key=ed25519(" in str_output assert "auto_renew_period=7776000" in str_output assert "auto_renew_account=AccountId(shard=0, realm=0, account=100)" in str_output assert "ledger_id=0x090a0b0c" in str_output @@ -274,6 +278,7 @@ def test_str_formatting(topic_info): assert "fee_exempt_keys=['ed25519(" in str_output assert "custom_fees=[" in str_output + def test_str_with_none_values(): """Test the string formatting of the TopicInfo class with None values.""" topic_info = TopicInfo( @@ -306,4 +311,3 @@ def test_str_with_none_values(): assert "fee_schedule_key=None" in str_output assert "fee_exempt_keys=[]" in str_output assert "custom_fees=[]" in str_output - diff --git a/tests/unit/topic_message_query_test.py b/tests/unit/topic_message_query_test.py index 0546eb939..270aba881 100644 --- a/tests/unit/topic_message_query_test.py +++ b/tests/unit/topic_message_query_test.py @@ -1,15 +1,20 @@ -import pytest -from unittest.mock import MagicMock, patch +from __future__ import annotations + from datetime import datetime, timezone -from hiero_sdk_python.query.topic_message_query import TopicMessageQuery +from unittest.mock import MagicMock, patch + +import pytest + from hiero_sdk_python.client.client import Client from hiero_sdk_python.consensus.topic_id import TopicId -from google.protobuf.timestamp_pb2 import Timestamp from hiero_sdk_python.hapi.mirror import consensus_service_pb2 as mirror_proto from hiero_sdk_python.hapi.services import timestamp_pb2 as hapi_timestamp_pb2 +from hiero_sdk_python.query.topic_message_query import TopicMessageQuery + pytestmark = pytest.mark.unit + @pytest.fixture def mock_client(): """Fixture to provide a mock Client instance.""" @@ -23,26 +28,28 @@ def mock_topic_id(): """Fixture to provide a mock TopicId instance.""" return TopicId(0, 0, 1234) + @pytest.fixture def mock_subscription_response(): """Fixture to provide a mock response from a topic subscription.""" - response = mirror_proto.ConsensusTopicResponse( + return mirror_proto.ConsensusTopicResponse( consensusTimestamp=hapi_timestamp_pb2.Timestamp(seconds=12345, nanos=67890), message=b"Hello, world!", runningHash=b"\x00" * 48, sequenceNumber=1, ) - return response + # This test uses fixtures (mock_client, mock_topic_id, mock_subscription_response) as parameters def test_topic_message_query_subscription(mock_client, mock_topic_id, mock_subscription_response): """ Test subscribing to topic messages using TopicMessageQuery. """ - query = TopicMessageQuery().set_topic_id(mock_topic_id).set_start_time(datetime.now(timezone.utc)) + query = TopicMessageQuery().set_topic_id(mock_topic_id).set_start_time(datetime.now(tz=timezone.utc)) with patch("hiero_sdk_python.query.topic_message_query.TopicMessageQuery.subscribe") as mock_subscribe: - def side_effect(client, on_message, on_error): + + def side_effect(client, on_message, on_error): # noqa: ARG001 on_message(mock_subscription_response) mock_subscribe.side_effect = side_effect diff --git a/tests/unit/topic_message_submit_transaction_test.py b/tests/unit/topic_message_submit_transaction_test.py index bb2f75de7..b0adf7a69 100644 --- a/tests/unit/topic_message_submit_transaction_test.py +++ b/tests/unit/topic_message_submit_transaction_test.py @@ -1,5 +1,7 @@ """Tests for the TopicMessageSubmitTransaction functionality.""" +from __future__ import annotations + from unittest.mock import MagicMock import pytest @@ -7,11 +9,11 @@ from hiero_sdk_python.consensus.topic_message_submit_transaction import TopicMessageSubmitTransaction from hiero_sdk_python.exceptions import PrecheckError, ReceiptStatusError from hiero_sdk_python.hapi.services import ( - response_header_pb2, + response_header_pb2, response_pb2, transaction_get_receipt_pb2, transaction_receipt_pb2, - transaction_response_pb2 + transaction_response_pb2, ) from hiero_sdk_python.hapi.services.schedulable_transaction_body_pb2 import ( SchedulableTransactionBody, @@ -22,8 +24,10 @@ from hiero_sdk_python.transaction.transaction_response import TransactionResponse from tests.unit.mock_server import mock_hedera_servers + pytestmark = pytest.mark.unit + @pytest.fixture def message(): """Fixture to provide a test message.""" @@ -42,12 +46,7 @@ def test_constructor_and_setters(topic_id, message, custom_fee_limit): chunk_size = 128 # Test constructor with parameters - tx = TopicMessageSubmitTransaction( - topic_id=topic_id, - message=message, - chunk_size=chunk_size, - max_chunks=max_chunks - ) + tx = TopicMessageSubmitTransaction(topic_id=topic_id, message=message, chunk_size=chunk_size, max_chunks=max_chunks) assert tx.topic_id == topic_id assert tx.message == message assert tx.chunk_size == chunk_size @@ -98,9 +97,7 @@ def test_constructor_and_setters(topic_id, message, custom_fee_limit): assert result is tx_default -def test_set_methods_require_not_frozen( - mock_client, topic_id, message, custom_fee_limit -): +def test_set_methods_require_not_frozen(mock_client, topic_id, message, custom_fee_limit): """Test that setter methods raise exception when transaction is frozen.""" max_chunks = 2 chunk_size = 128 @@ -114,13 +111,11 @@ def test_set_methods_require_not_frozen( ("set_custom_fee_limits", [custom_fee_limit]), ("add_custom_fee_limit", custom_fee_limit), ("set_chunk_size", chunk_size), - ("set_max_chunks", max_chunks) + ("set_max_chunks", max_chunks), ] for method_name, value in test_cases: - with pytest.raises( - Exception, match="Transaction is immutable; it has been frozen" - ): + with pytest.raises(Exception, match="Transaction is immutable; it has been frozen"): getattr(tx, method_name)(value) @@ -168,56 +163,47 @@ def test_build_scheduled_body(topic_id, message): tx = TopicMessageSubmitTransaction() tx.set_topic_id(topic_id) tx.set_message(message) - + # Build the scheduled body schedulable_body = tx.build_scheduled_body() - + # Verify the correct type is returned assert isinstance(schedulable_body, SchedulableTransactionBody) - + # Verify the transaction was built with topic message submit type assert schedulable_body.HasField("consensusSubmitMessage") - + # Verify fields in the schedulable body assert schedulable_body.consensusSubmitMessage.topicID.topicNum == 1234 - assert schedulable_body.consensusSubmitMessage.message == bytes(message, 'utf-8') + assert schedulable_body.consensusSubmitMessage.message == bytes(message, "utf-8") + # This test uses fixtures (topic_id, message) as parameters def test_execute_topic_message_submit_transaction(topic_id, message): """Test executing the TopicMessageSubmitTransaction successfully with mock server.""" # Create success response for the transaction submission - tx_response = transaction_response_pb2.TransactionResponse( - nodeTransactionPrecheckCode=ResponseCode.OK - ) - + tx_response = transaction_response_pb2.TransactionResponse(nodeTransactionPrecheckCode=ResponseCode.OK) + # Create receipt response with SUCCESS status receipt_response = response_pb2.Response( transactionGetReceipt=transaction_get_receipt_pb2.TransactionGetReceiptResponse( - header=response_header_pb2.ResponseHeader( - nodeTransactionPrecheckCode=ResponseCode.OK - ), - receipt=transaction_receipt_pb2.TransactionReceipt( - status=ResponseCode.SUCCESS - ) + header=response_header_pb2.ResponseHeader(nodeTransactionPrecheckCode=ResponseCode.OK), + receipt=transaction_receipt_pb2.TransactionReceipt(status=ResponseCode.SUCCESS), ) ) - + response_sequences = [ [tx_response, receipt_response], ] - + with mock_hedera_servers(response_sequences) as client: - tx = ( - TopicMessageSubmitTransaction() - .set_topic_id(topic_id) - .set_message(message) - ) - + tx = TopicMessageSubmitTransaction().set_topic_id(topic_id).set_message(message) + try: receipt = tx.execute(client) except Exception as e: pytest.fail(f"Should not raise exception, but raised: {e}") - + # Verify the receipt contains the expected values assert receipt.status == ResponseCode.SUCCESS @@ -229,18 +215,12 @@ def test_topic_message_submit_transaction_with_large_message(topic_id): large_message = "A" * 4000 # Create a single node response sequence for all chunks - tx_response = transaction_response_pb2.TransactionResponse( - nodeTransactionPrecheckCode=ResponseCode.OK - ) - + tx_response = transaction_response_pb2.TransactionResponse(nodeTransactionPrecheckCode=ResponseCode.OK) + receipt_response = response_pb2.Response( transactionGetReceipt=transaction_get_receipt_pb2.TransactionGetReceiptResponse( - header=response_header_pb2.ResponseHeader( - nodeTransactionPrecheckCode=ResponseCode.OK - ), - receipt=transaction_receipt_pb2.TransactionReceipt( - status=ResponseCode.SUCCESS - ) + header=response_header_pb2.ResponseHeader(nodeTransactionPrecheckCode=ResponseCode.OK), + receipt=transaction_receipt_pb2.TransactionReceipt(status=ResponseCode.SUCCESS), ) ) @@ -249,12 +229,7 @@ def test_topic_message_submit_transaction_with_large_message(topic_id): response_sequence = [tx_response, receipt_response] * 4 # 4 chunks with mock_hedera_servers([response_sequence]) as client: - tx = ( - TopicMessageSubmitTransaction() - .set_topic_id(topic_id) - .set_message(large_message) - .freeze_with(client) - ) + tx = TopicMessageSubmitTransaction().set_topic_id(topic_id).set_message(large_message).freeze_with(client) try: receipt = tx.execute(client) @@ -265,25 +240,18 @@ def test_topic_message_submit_transaction_with_large_message(topic_id): assert receipt.status == ResponseCode.SUCCESS - def test_topic_message_submit_execute_all_multi_chunk_success(topic_id): """Test multi-chunk transaction should return list of receipts for each chunk.""" # Create a large message (just under the typical 4KB limit) large_message = "A" * 4000 # Create a single node response sequence for all chunks - tx_response = transaction_response_pb2.TransactionResponse( - nodeTransactionPrecheckCode=ResponseCode.OK - ) - + tx_response = transaction_response_pb2.TransactionResponse(nodeTransactionPrecheckCode=ResponseCode.OK) + receipt_response = response_pb2.Response( transactionGetReceipt=transaction_get_receipt_pb2.TransactionGetReceiptResponse( - header=response_header_pb2.ResponseHeader( - nodeTransactionPrecheckCode=ResponseCode.OK - ), - receipt=transaction_receipt_pb2.TransactionReceipt( - status=ResponseCode.SUCCESS - ) + header=response_header_pb2.ResponseHeader(nodeTransactionPrecheckCode=ResponseCode.OK), + receipt=transaction_receipt_pb2.TransactionReceipt(status=ResponseCode.SUCCESS), ) ) @@ -292,12 +260,7 @@ def test_topic_message_submit_execute_all_multi_chunk_success(topic_id): response_sequence = [tx_response, receipt_response] * 4 # 4 chunks with mock_hedera_servers([response_sequence]) as client: - tx = ( - TopicMessageSubmitTransaction() - .set_topic_id(topic_id) - .set_message(large_message) - .freeze_with(client) - ) + tx = TopicMessageSubmitTransaction().set_topic_id(topic_id).set_message(large_message).freeze_with(client) try: receipts = tx.execute_all(client) @@ -310,35 +273,23 @@ def test_topic_message_submit_execute_all_multi_chunk_success(topic_id): assert receipt.status == ResponseCode.SUCCESS - def test_topic_message_submit_execute_all_single_chunk(topic_id): """Test single chunk transaction should return list with one receipt.""" message = "Hello Hiero" - tx_response = transaction_response_pb2.TransactionResponse( - nodeTransactionPrecheckCode=ResponseCode.OK - ) - + tx_response = transaction_response_pb2.TransactionResponse(nodeTransactionPrecheckCode=ResponseCode.OK) + receipt_response = response_pb2.Response( transactionGetReceipt=transaction_get_receipt_pb2.TransactionGetReceiptResponse( - header=response_header_pb2.ResponseHeader( - nodeTransactionPrecheckCode=ResponseCode.OK - ), - receipt=transaction_receipt_pb2.TransactionReceipt( - status=ResponseCode.SUCCESS - ) + header=response_header_pb2.ResponseHeader(nodeTransactionPrecheckCode=ResponseCode.OK), + receipt=transaction_receipt_pb2.TransactionReceipt(status=ResponseCode.SUCCESS), ) ) response_sequence = [tx_response, receipt_response] with mock_hedera_servers([response_sequence]) as client: - tx = ( - TopicMessageSubmitTransaction() - .set_topic_id(topic_id) - .set_message(message) - .freeze_with(client) - ) + tx = TopicMessageSubmitTransaction().set_topic_id(topic_id).set_message(message).freeze_with(client) try: receipts = tx.execute_all(client) @@ -356,19 +307,12 @@ def test_topic_message_submit_execute_without_wait_for_receipt(topic_id): """Test should return TransactionResponse when wait_for_receipt=False.""" message = "Hello Hiero" - tx_response = transaction_response_pb2.TransactionResponse( - nodeTransactionPrecheckCode=ResponseCode.OK - ) + tx_response = transaction_response_pb2.TransactionResponse(nodeTransactionPrecheckCode=ResponseCode.OK) response_sequence = [tx_response] # No receipt with mock_hedera_servers([response_sequence]) as client: - tx = ( - TopicMessageSubmitTransaction() - .set_topic_id(topic_id) - .set_message(message) - .freeze_with(client) - ) + tx = TopicMessageSubmitTransaction().set_topic_id(topic_id).set_message(message).freeze_with(client) response = tx.execute(client, wait_for_receipt=False) @@ -379,30 +323,19 @@ def test_topic_message_submit_execute_with_wait_for_receipt(topic_id): """Test should return TransactionReceipt when wait_for_receipt=True.""" message = "Hello Hiero" - tx_response = transaction_response_pb2.TransactionResponse( - nodeTransactionPrecheckCode=ResponseCode.OK - ) + tx_response = transaction_response_pb2.TransactionResponse(nodeTransactionPrecheckCode=ResponseCode.OK) receipt_response = response_pb2.Response( transactionGetReceipt=transaction_get_receipt_pb2.TransactionGetReceiptResponse( - header=response_header_pb2.ResponseHeader( - nodeTransactionPrecheckCode=ResponseCode.OK - ), - receipt=transaction_receipt_pb2.TransactionReceipt( - status=ResponseCode.SUCCESS - ) + header=response_header_pb2.ResponseHeader(nodeTransactionPrecheckCode=ResponseCode.OK), + receipt=transaction_receipt_pb2.TransactionReceipt(status=ResponseCode.SUCCESS), ) ) - response_sequence = [tx_response, receipt_response] + response_sequence = [tx_response, receipt_response] with mock_hedera_servers([response_sequence]) as client: - tx = ( - TopicMessageSubmitTransaction() - .set_topic_id(topic_id) - .set_message(message) - .freeze_with(client) - ) + tx = TopicMessageSubmitTransaction().set_topic_id(topic_id).set_message(message).freeze_with(client) response = tx.execute(client, wait_for_receipt=True) @@ -413,19 +346,12 @@ def test_topic_message_submit_execute_all_without_wait_for_receipt(topic_id): """Test should return list of TransactionResponse when wait_for_receipt=False.""" message = "Hello Hiero" - tx_response = transaction_response_pb2.TransactionResponse( - nodeTransactionPrecheckCode=ResponseCode.OK - ) + tx_response = transaction_response_pb2.TransactionResponse(nodeTransactionPrecheckCode=ResponseCode.OK) response_sequence = [tx_response] # No receipt with mock_hedera_servers([response_sequence]) as client: - tx = ( - TopicMessageSubmitTransaction() - .set_topic_id(topic_id) - .set_message(message) - .freeze_with(client) - ) + tx = TopicMessageSubmitTransaction().set_topic_id(topic_id).set_message(message).freeze_with(client) responses = tx.execute_all(client, wait_for_receipt=False) @@ -437,30 +363,19 @@ def test_topic_message_submit_execute_all_with_wait_for_receipt(topic_id): """Test should return list of TransactionReceipt when wait_for_receipt=True.""" message = "Hello Hiero" - tx_response = transaction_response_pb2.TransactionResponse( - nodeTransactionPrecheckCode=ResponseCode.OK - ) + tx_response = transaction_response_pb2.TransactionResponse(nodeTransactionPrecheckCode=ResponseCode.OK) receipt_response = response_pb2.Response( transactionGetReceipt=transaction_get_receipt_pb2.TransactionGetReceiptResponse( - header=response_header_pb2.ResponseHeader( - nodeTransactionPrecheckCode=ResponseCode.OK - ), - receipt=transaction_receipt_pb2.TransactionReceipt( - status=ResponseCode.SUCCESS - ) + header=response_header_pb2.ResponseHeader(nodeTransactionPrecheckCode=ResponseCode.OK), + receipt=transaction_receipt_pb2.TransactionReceipt(status=ResponseCode.SUCCESS), ) ) - response_sequence = [tx_response, receipt_response] + response_sequence = [tx_response, receipt_response] with mock_hedera_servers([response_sequence]) as client: - tx = ( - TopicMessageSubmitTransaction() - .set_topic_id(topic_id) - .set_message(message) - .freeze_with(client) - ) + tx = TopicMessageSubmitTransaction().set_topic_id(topic_id).set_message(message).freeze_with(client) responses = tx.execute_all(client, wait_for_receipt=True) @@ -476,15 +391,10 @@ def test_topic_message_submit_execute_throw_error_when_transaction_fails(topic_i response_sequence = [tx_response] with mock_hedera_servers([response_sequence]) as client: - tx = ( - TopicMessageSubmitTransaction() - .set_topic_id(topic_id) - .set_message("Hello Hiero") - .freeze_with(client) - ) + tx = TopicMessageSubmitTransaction().set_topic_id(topic_id).set_message("Hello Hiero").freeze_with(client) with pytest.raises(PrecheckError, match="Transaction failed precheck"): - tx.execute(client) + tx.execute(client) def test_topic_message_submit_execute_all_throw_error_when_transaction_fails(topic_id): @@ -495,146 +405,101 @@ def test_topic_message_submit_execute_all_throw_error_when_transaction_fails(top response_sequence = [tx_response] with mock_hedera_servers([response_sequence]) as client: - tx = ( - TopicMessageSubmitTransaction() - .set_topic_id(topic_id) - .set_message("Hello Hiero") - .freeze_with(client) - ) + tx = TopicMessageSubmitTransaction().set_topic_id(topic_id).set_message("Hello Hiero").freeze_with(client) with pytest.raises(PrecheckError, match="Transaction failed precheck"): - tx.execute_all(client) + tx.execute_all(client) + def test_topic_submit_execute_all_raises_error_with_validation(topic_id): """Test execute_all raises error when validate_status is True and a chunk fails.""" message = "Hello Hiero" - tx_response = transaction_response_pb2.TransactionResponse( - nodeTransactionPrecheckCode=ResponseCode.OK - ) + tx_response = transaction_response_pb2.TransactionResponse(nodeTransactionPrecheckCode=ResponseCode.OK) receipt_response = response_pb2.Response( transactionGetReceipt=transaction_get_receipt_pb2.TransactionGetReceiptResponse( - header=response_header_pb2.ResponseHeader( - nodeTransactionPrecheckCode=ResponseCode.OK - ), - receipt=transaction_receipt_pb2.TransactionReceipt( - status=ResponseCode.INVALID_SIGNATURE - ) + header=response_header_pb2.ResponseHeader(nodeTransactionPrecheckCode=ResponseCode.OK), + receipt=transaction_receipt_pb2.TransactionReceipt(status=ResponseCode.INVALID_SIGNATURE), ) ) - response_sequence = [tx_response, receipt_response] + response_sequence = [tx_response, receipt_response] with mock_hedera_servers([response_sequence]) as client: - tx = ( - TopicMessageSubmitTransaction() - .set_topic_id(topic_id) - .set_message(message) - .freeze_with(client) - ) + tx = TopicMessageSubmitTransaction().set_topic_id(topic_id).set_message(message).freeze_with(client) with pytest.raises(ReceiptStatusError) as e: tx.execute_all(client, validate_status=True) - + assert e.value.status == ResponseCode.INVALID_SIGNATURE + def test_topic_submit_execute_all_returns_failed_receipt_by_default(topic_id): """Test execute_all returns failing receipts normally when validation is disabled.""" message = "A" * 1024 - tx_response = transaction_response_pb2.TransactionResponse( - nodeTransactionPrecheckCode=ResponseCode.OK - ) + tx_response = transaction_response_pb2.TransactionResponse(nodeTransactionPrecheckCode=ResponseCode.OK) receipt_response = response_pb2.Response( transactionGetReceipt=transaction_get_receipt_pb2.TransactionGetReceiptResponse( - header=response_header_pb2.ResponseHeader( - nodeTransactionPrecheckCode=ResponseCode.OK - ), - receipt=transaction_receipt_pb2.TransactionReceipt( - status=ResponseCode.INVALID_SIGNATURE - ) + header=response_header_pb2.ResponseHeader(nodeTransactionPrecheckCode=ResponseCode.OK), + receipt=transaction_receipt_pb2.TransactionReceipt(status=ResponseCode.INVALID_SIGNATURE), ) ) - + response_sequence = [tx_response, receipt_response] * 4 # 4 chunks with mock_hedera_servers([response_sequence]) as client: - tx = ( - TopicMessageSubmitTransaction() - .set_topic_id(topic_id) - .set_message(message) - .freeze_with(client) - ) - + tx = TopicMessageSubmitTransaction().set_topic_id(topic_id).set_message(message).freeze_with(client) + receipt = tx.execute_all(client) - + assert receipt[0].status == ResponseCode.INVALID_SIGNATURE + def test_topic_submit_execute_raises_error_with_validation(topic_id): """Test execute raises error for failing messages when validate_status is True.""" message = "A" * 1024 - tx_response = transaction_response_pb2.TransactionResponse( - nodeTransactionPrecheckCode=ResponseCode.OK - ) + tx_response = transaction_response_pb2.TransactionResponse(nodeTransactionPrecheckCode=ResponseCode.OK) receipt_response = response_pb2.Response( transactionGetReceipt=transaction_get_receipt_pb2.TransactionGetReceiptResponse( - header=response_header_pb2.ResponseHeader( - nodeTransactionPrecheckCode=ResponseCode.OK - ), - receipt=transaction_receipt_pb2.TransactionReceipt( - status=ResponseCode.INVALID_SIGNATURE - ) + header=response_header_pb2.ResponseHeader(nodeTransactionPrecheckCode=ResponseCode.OK), + receipt=transaction_receipt_pb2.TransactionReceipt(status=ResponseCode.INVALID_SIGNATURE), ) ) response_sequence = [tx_response, receipt_response] * 4 with mock_hedera_servers([response_sequence]) as client: - tx = ( - TopicMessageSubmitTransaction() - .set_topic_id(topic_id) - .set_message(message) - .freeze_with(client) - ) + tx = TopicMessageSubmitTransaction().set_topic_id(topic_id).set_message(message).freeze_with(client) with pytest.raises(ReceiptStatusError) as e: tx.execute(client, validate_status=True) - + assert e.value.status == ResponseCode.INVALID_SIGNATURE + def test_topic_submit_execute_returns_failed_receipt_by_default(topic_id): """Test execute returns the failing receipt by default when validation is disabled.""" message = "Hello Hiero" - tx_response = transaction_response_pb2.TransactionResponse( - nodeTransactionPrecheckCode=ResponseCode.OK - ) + tx_response = transaction_response_pb2.TransactionResponse(nodeTransactionPrecheckCode=ResponseCode.OK) receipt_response = response_pb2.Response( transactionGetReceipt=transaction_get_receipt_pb2.TransactionGetReceiptResponse( - header=response_header_pb2.ResponseHeader( - nodeTransactionPrecheckCode=ResponseCode.OK - ), - receipt=transaction_receipt_pb2.TransactionReceipt( - status=ResponseCode.INVALID_SIGNATURE - ) + header=response_header_pb2.ResponseHeader(nodeTransactionPrecheckCode=ResponseCode.OK), + receipt=transaction_receipt_pb2.TransactionReceipt(status=ResponseCode.INVALID_SIGNATURE), ) ) - response_sequence = [tx_response, receipt_response] + response_sequence = [tx_response, receipt_response] with mock_hedera_servers([response_sequence]) as client: - tx = ( - TopicMessageSubmitTransaction() - .set_topic_id(topic_id) - .set_message(message) - .freeze_with(client) - ) + tx = TopicMessageSubmitTransaction().set_topic_id(topic_id).set_message(message).freeze_with(client) receipt = tx.execute(client) - + assert receipt.status == ResponseCode.INVALID_SIGNATURE diff --git a/tests/unit/topic_update_transaction_test.py b/tests/unit/topic_update_transaction_test.py index 43e9109bf..12124de98 100644 --- a/tests/unit/topic_update_transaction_test.py +++ b/tests/unit/topic_update_transaction_test.py @@ -1,5 +1,7 @@ """Tests for the TopicUpdateTransaction functionality.""" +from __future__ import annotations + import pytest from hiero_sdk_python.account.account_id import AccountId @@ -7,22 +9,23 @@ from hiero_sdk_python.crypto.private_key import PrivateKey from hiero_sdk_python.Duration import Duration from hiero_sdk_python.hapi.services import ( - response_header_pb2, + response_header_pb2, response_pb2, transaction_get_receipt_pb2, transaction_receipt_pb2, - transaction_response_pb2 + transaction_response_pb2, ) from hiero_sdk_python.hapi.services.schedulable_transaction_body_pb2 import ( SchedulableTransactionBody, ) from hiero_sdk_python.response_code import ResponseCode - from hiero_sdk_python.tokens.custom_fixed_fee import CustomFixedFee from tests.unit.mock_server import mock_hedera_servers + pytestmark = pytest.mark.unit + # This test uses fixtures (mock_account_ids, topic_id) as parameters def test_build_topic_update_transaction_body(mock_account_ids, topic_id): """Test building a TopicUpdateTransaction body with valid topic ID and memo.""" @@ -35,7 +38,8 @@ def test_build_topic_update_transaction_body(mock_account_ids, topic_id): transaction_body = tx.build_transaction_body() assert transaction_body.consensusUpdateTopic.topicID.topicNum == 1234 assert transaction_body.consensusUpdateTopic.memo.value == "Updated Memo" - + + # This test uses fixtures (topic_id) as parameters def test_build_scheduled_body(topic_id): """Test building a schedulable TopicUpdateTransaction body with all fields.""" @@ -43,7 +47,7 @@ def test_build_scheduled_body(topic_id): admin_key = PrivateKey.generate().public_key() submit_key = PrivateKey.generate().public_key() auto_renew_account = AccountId(0, 0, 9876) - + # Create transaction with all available fields tx = TopicUpdateTransaction() tx.set_topic_id(topic_id) @@ -55,16 +59,16 @@ def test_build_scheduled_body(topic_id): tx.set_fee_exempt_keys([admin_key]) tx.set_fee_schedule_key(admin_key) tx.set_custom_fees([CustomFixedFee(1000, fee_collector_account_id=AccountId(0, 0, 9876))]) - + # Build the scheduled body schedulable_body = tx.build_scheduled_body() - + # Verify the correct type is returned assert isinstance(schedulable_body, SchedulableTransactionBody) - + # Verify the transaction was built with topic update type assert schedulable_body.HasField("consensusUpdateTopic") - + # Verify all fields in the scheduled body assert schedulable_body.consensusUpdateTopic.topicID.topicNum == 1234 assert schedulable_body.consensusUpdateTopic.memo.value == "Scheduled Topic Update" @@ -72,24 +76,10 @@ def test_build_scheduled_body(topic_id): assert schedulable_body.consensusUpdateTopic.submitKey.ed25519 == submit_key.to_bytes_raw() assert schedulable_body.consensusUpdateTopic.autoRenewPeriod.seconds == 8000000 assert schedulable_body.consensusUpdateTopic.autoRenewAccount.accountNum == 9876 - assert ( - schedulable_body.consensusUpdateTopic.fee_exempt_key_list.keys[0].ed25519 - == admin_key.to_bytes_raw() - ) - assert ( - schedulable_body.consensusUpdateTopic.fee_schedule_key.ed25519 - == admin_key.to_bytes_raw() - ) - assert ( - schedulable_body.consensusUpdateTopic.custom_fees.fees[0].fixed_fee.amount - == 1000 - ) - assert ( - schedulable_body.consensusUpdateTopic.custom_fees.fees[ - 0 - ].fee_collector_account_id.accountNum - == 9876 - ) + assert schedulable_body.consensusUpdateTopic.fee_exempt_key_list.keys[0].ed25519 == admin_key.to_bytes_raw() + assert schedulable_body.consensusUpdateTopic.fee_schedule_key.ed25519 == admin_key.to_bytes_raw() + assert schedulable_body.consensusUpdateTopic.custom_fees.fees[0].fixed_fee.amount == 1000 + assert schedulable_body.consensusUpdateTopic.custom_fees.fees[0].fee_collector_account_id.accountNum == 9876 # This test uses fixture mock_account_ids as parameter @@ -124,38 +114,28 @@ def test_sign_topic_update_transaction(mock_account_ids, topic_id, private_key): def test_execute_topic_update_transaction(topic_id): """Test executing the TopicUpdateTransaction successfully with mock server.""" # Create success response for the transaction submission - tx_response = transaction_response_pb2.TransactionResponse( - nodeTransactionPrecheckCode=ResponseCode.OK - ) - + tx_response = transaction_response_pb2.TransactionResponse(nodeTransactionPrecheckCode=ResponseCode.OK) + # Create receipt response with SUCCESS status receipt_response = response_pb2.Response( transactionGetReceipt=transaction_get_receipt_pb2.TransactionGetReceiptResponse( - header=response_header_pb2.ResponseHeader( - nodeTransactionPrecheckCode=ResponseCode.OK - ), - receipt=transaction_receipt_pb2.TransactionReceipt( - status=ResponseCode.SUCCESS - ) + header=response_header_pb2.ResponseHeader(nodeTransactionPrecheckCode=ResponseCode.OK), + receipt=transaction_receipt_pb2.TransactionReceipt(status=ResponseCode.SUCCESS), ) ) - + response_sequences = [ [tx_response, receipt_response], ] - + with mock_hedera_servers(response_sequences) as client: - tx = ( - TopicUpdateTransaction() - .set_topic_id(topic_id) - .set_memo("Updated with mock server") - ) - + tx = TopicUpdateTransaction().set_topic_id(topic_id).set_memo("Updated with mock server") + try: receipt = tx.execute(client) except Exception as e: pytest.fail(f"Should not raise exception, but raised: {e}") - + # Verify the receipt contains the expected values assert receipt.status == ResponseCode.SUCCESS @@ -163,32 +143,26 @@ def test_execute_topic_update_transaction(topic_id): # This test uses fixture topic_id as parameter def test_topic_update_transaction_with_all_fields(topic_id): """Test updating a topic with all available fields.""" - tx_response = transaction_response_pb2.TransactionResponse( - nodeTransactionPrecheckCode=ResponseCode.OK - ) - + tx_response = transaction_response_pb2.TransactionResponse(nodeTransactionPrecheckCode=ResponseCode.OK) + receipt_response = response_pb2.Response( transactionGetReceipt=transaction_get_receipt_pb2.TransactionGetReceiptResponse( - header=response_header_pb2.ResponseHeader( - nodeTransactionPrecheckCode=ResponseCode.OK - ), - receipt=transaction_receipt_pb2.TransactionReceipt( - status=ResponseCode.SUCCESS - ) + header=response_header_pb2.ResponseHeader(nodeTransactionPrecheckCode=ResponseCode.OK), + receipt=transaction_receipt_pb2.TransactionReceipt(status=ResponseCode.SUCCESS), ) ) - + response_sequences = [ [tx_response, receipt_response], ] - + with mock_hedera_servers(response_sequences) as client: admin_key = PrivateKey.generate().public_key() submit_key = PrivateKey.generate().public_key() auto_renew_account = AccountId(0, 0, 5678) fee_collector_account_id = AccountId(0, 0, 9876) custom_fee = CustomFixedFee(1000, fee_collector_account_id=fee_collector_account_id) - + tx = ( TopicUpdateTransaction() .set_topic_id(topic_id) @@ -201,11 +175,11 @@ def test_topic_update_transaction_with_all_fields(topic_id): .set_fee_schedule_key(admin_key) .set_fee_exempt_keys([admin_key]) ) - + try: receipt = tx.execute(client) except Exception as e: pytest.fail(f"Should not raise exception, but raised: {e}") - + # Verify the receipt contains the expected values assert receipt.status == ResponseCode.SUCCESS diff --git a/tests/unit/transaction_freeze_and_bytes_test.py b/tests/unit/transaction_freeze_and_bytes_test.py index 2ccf188b8..c61302b39 100644 --- a/tests/unit/transaction_freeze_and_bytes_test.py +++ b/tests/unit/transaction_freeze_and_bytes_test.py @@ -6,15 +6,17 @@ and expected behavior. """ +from __future__ import annotations + import pytest + from hiero_sdk_python.account.account_id import AccountId from hiero_sdk_python.crypto.private_key import PrivateKey -from hiero_sdk_python.transaction.transfer_transaction import TransferTransaction -from hiero_sdk_python.transaction.transaction_id import TransactionId - from hiero_sdk_python.hapi.services.transaction_response_pb2 import ( TransactionResponse as TransactionResponseProto, ) +from hiero_sdk_python.transaction.transaction_id import TransactionId +from hiero_sdk_python.transaction.transfer_transaction import TransferTransaction pytestmark = pytest.mark.unit @@ -24,7 +26,7 @@ def test_freeze_without_transaction_id_raises_error(): """Test that freeze() raises ValueError when transaction_id is not set.""" transaction = TransferTransaction() transaction.node_account_id = AccountId.from_string("0.0.3") - + with pytest.raises(ValueError, match="Transaction ID must be set before freezing"): transaction.freeze() @@ -33,7 +35,7 @@ def test_freeze_without_node_account_id_raises_error(): """Test that freeze() raises ValueError when node_account_id is not set.""" transaction = TransferTransaction() transaction.transaction_id = TransactionId.generate(AccountId.from_string("0.0.1234")) - + with pytest.raises(ValueError, match="Node account ID must be set before freezing"): transaction.freeze() @@ -43,22 +45,20 @@ def test_freeze_with_valid_parameters(): operator_id = AccountId.from_string("0.0.1234") node_id = AccountId.from_string("0.0.3") receiver_id = AccountId.from_string("0.0.5678") - + transaction = ( - TransferTransaction() - .add_hbar_transfer(operator_id, -100_000_000) - .add_hbar_transfer(receiver_id, 100_000_000) + TransferTransaction().add_hbar_transfer(operator_id, -100_000_000).add_hbar_transfer(receiver_id, 100_000_000) ) - + transaction.transaction_id = TransactionId.generate(operator_id) transaction.node_account_id = node_id - + # Should not raise any errors result = transaction.freeze() - + # Should return self for method chaining assert result is transaction - + # Should have transaction body bytes set assert len(transaction._transaction_body_bytes) > 0 assert node_id in transaction._transaction_body_bytes @@ -69,21 +69,19 @@ def test_freeze_is_idempotent(): operator_id = AccountId.from_string("0.0.1234") node_id = AccountId.from_string("0.0.3") receiver_id = AccountId.from_string("0.0.5678") - + transaction = ( - TransferTransaction() - .add_hbar_transfer(operator_id, -100_000_000) - .add_hbar_transfer(receiver_id, 100_000_000) + TransferTransaction().add_hbar_transfer(operator_id, -100_000_000).add_hbar_transfer(receiver_id, 100_000_000) ) - + transaction.transaction_id = TransactionId.generate(operator_id) transaction.node_account_id = node_id - + # Freeze multiple times transaction.freeze() transaction.freeze() transaction.freeze() - + # Should still work fine assert len(transaction._transaction_body_bytes) > 0 @@ -91,7 +89,7 @@ def test_freeze_is_idempotent(): def test_to_bytes_requires_frozen_transaction(): """Test that to_bytes() raises error if transaction is not frozen.""" transaction = TransferTransaction() - + with pytest.raises(Exception, match="Transaction is not frozen"): transaction.to_bytes() @@ -101,26 +99,24 @@ def test_to_bytes_returns_bytes(): operator_id = AccountId.from_string("0.0.1234") node_id = AccountId.from_string("0.0.3") receiver_id = AccountId.from_string("0.0.5678") - + # Generate a private key for signing private_key = PrivateKey.generate() - + transaction = ( - TransferTransaction() - .add_hbar_transfer(operator_id, -100_000_000) - .add_hbar_transfer(receiver_id, 100_000_000) + TransferTransaction().add_hbar_transfer(operator_id, -100_000_000).add_hbar_transfer(receiver_id, 100_000_000) ) - + transaction.transaction_id = TransactionId.generate(operator_id) transaction.node_account_id = node_id - + # Freeze and sign the transaction transaction.freeze() transaction.sign(private_key) - + # Get bytes transaction_bytes = transaction.to_bytes() - + # Verify it's bytes assert isinstance(transaction_bytes, bytes) assert len(transaction_bytes) > 0 @@ -131,26 +127,24 @@ def test_to_bytes_produces_consistent_output(): operator_id = AccountId.from_string("0.0.1234") node_id = AccountId.from_string("0.0.3") receiver_id = AccountId.from_string("0.0.5678") - + private_key = PrivateKey.generate() - + transaction = ( - TransferTransaction() - .add_hbar_transfer(operator_id, -100_000_000) - .add_hbar_transfer(receiver_id, 100_000_000) + TransferTransaction().add_hbar_transfer(operator_id, -100_000_000).add_hbar_transfer(receiver_id, 100_000_000) ) - + transaction.transaction_id = TransactionId.generate(operator_id) transaction.node_account_id = node_id - + transaction.freeze() transaction.sign(private_key) - + # Get bytes multiple times bytes1 = transaction.to_bytes() bytes2 = transaction.to_bytes() bytes3 = transaction.to_bytes() - + # All should be identical assert bytes1 == bytes2 assert bytes2 == bytes3 @@ -161,17 +155,15 @@ def test_cannot_modify_transaction_after_freeze(): operator_id = AccountId.from_string("0.0.1234") node_id = AccountId.from_string("0.0.3") receiver_id = AccountId.from_string("0.0.5678") - + transaction = ( - TransferTransaction() - .add_hbar_transfer(operator_id, -100_000_000) - .add_hbar_transfer(receiver_id, 100_000_000) + TransferTransaction().add_hbar_transfer(operator_id, -100_000_000).add_hbar_transfer(receiver_id, 100_000_000) ) - + transaction.transaction_id = TransactionId.generate(operator_id) transaction.node_account_id = node_id transaction.freeze() - + # Attempting to add more transfers should raise an error with pytest.raises(Exception, match="Transaction is immutable"): transaction.add_hbar_transfer(AccountId.from_string("0.0.9999"), 50_000_000) @@ -197,9 +189,9 @@ def test_freeze_and_sign_workflow(): operator_id = AccountId.from_string("0.0.1234") node_id = AccountId.from_string("0.0.3") receiver_id = AccountId.from_string("0.0.5678") - + private_key = PrivateKey.generate() - + # Create transaction transaction = ( TransferTransaction() @@ -207,24 +199,24 @@ def test_freeze_and_sign_workflow(): .add_hbar_transfer(receiver_id, 100_000_000) .set_transaction_memo("Test transaction") ) - + # Set required IDs transaction.transaction_id = TransactionId.generate(operator_id) transaction.node_account_id = node_id - + # Freeze transaction.freeze() - + # Sign transaction.sign(private_key) - + # Convert to bytes transaction_bytes = transaction.to_bytes() - + # Verify assert isinstance(transaction_bytes, bytes) assert len(transaction_bytes) > 0 - + # Verify the transaction is signed assert transaction.is_signed_by(private_key.public_key()) @@ -331,9 +323,7 @@ def test_from_bytes_round_trip_multiple_signatures(): # Create transaction transaction = ( - TransferTransaction() - .add_hbar_transfer(operator_id, -300_000_000) - .add_hbar_transfer(receiver_id, 300_000_000) + TransferTransaction().add_hbar_transfer(operator_id, -300_000_000).add_hbar_transfer(receiver_id, 300_000_000) ) transaction.transaction_id = TransactionId.generate(operator_id) @@ -407,9 +397,7 @@ def test_from_bytes_external_signing_workflow(): # Step 1: Create unsigned transaction (e.g., on online system) transaction = ( - TransferTransaction() - .add_hbar_transfer(operator_id, -100_000_000) - .add_hbar_transfer(receiver_id, 100_000_000) + TransferTransaction().add_hbar_transfer(operator_id, -100_000_000).add_hbar_transfer(receiver_id, 100_000_000) ) transaction.transaction_id = TransactionId.generate(operator_id) @@ -450,9 +438,7 @@ def test_to_bytes_works_without_signatures(): receiver_id = AccountId.from_string("0.0.5678") transaction = ( - TransferTransaction() - .add_hbar_transfer(operator_id, -100_000_000) - .add_hbar_transfer(receiver_id, 100_000_000) + TransferTransaction().add_hbar_transfer(operator_id, -100_000_000).add_hbar_transfer(receiver_id, 100_000_000) ) transaction.transaction_id = TransactionId.generate(operator_id) @@ -475,9 +461,7 @@ def test_freeze_only_builds_for_single_node(): receiver_id = AccountId.from_string("0.0.5678") transaction = ( - TransferTransaction() - .add_hbar_transfer(operator_id, -100_000_000) - .add_hbar_transfer(receiver_id, 100_000_000) + TransferTransaction().add_hbar_transfer(operator_id, -100_000_000).add_hbar_transfer(receiver_id, 100_000_000) ) transaction.transaction_id = TransactionId.generate(operator_id) @@ -498,9 +482,7 @@ def test_signed_and_unsigned_bytes_are_different(): private_key = PrivateKey.generate() transaction = ( - TransferTransaction() - .add_hbar_transfer(operator_id, -100_000_000) - .add_hbar_transfer(receiver_id, 100_000_000) + TransferTransaction().add_hbar_transfer(operator_id, -100_000_000).add_hbar_transfer(receiver_id, 100_000_000) ) transaction.transaction_id = TransactionId.generate(operator_id) @@ -532,9 +514,7 @@ def test_multiple_signatures_increase_size(): key3 = PrivateKey.generate() transaction = ( - TransferTransaction() - .add_hbar_transfer(operator_id, -100_000_000) - .add_hbar_transfer(receiver_id, 100_000_000) + TransferTransaction().add_hbar_transfer(operator_id, -100_000_000).add_hbar_transfer(receiver_id, 100_000_000) ) transaction.transaction_id = TransactionId.generate(operator_id) @@ -566,9 +546,7 @@ def test_changing_node_after_freeze_fails_for_to_bytes(): receiver_id = AccountId.from_string("0.0.5678") transaction = ( - TransferTransaction() - .add_hbar_transfer(operator_id, -100_000_000) - .add_hbar_transfer(receiver_id, 100_000_000) + TransferTransaction().add_hbar_transfer(operator_id, -100_000_000).add_hbar_transfer(receiver_id, 100_000_000) ) transaction.transaction_id = TransactionId.generate(operator_id) @@ -596,9 +574,7 @@ def test_unsigned_transaction_can_be_signed_after_to_bytes(): private_key = PrivateKey.generate() transaction = ( - TransferTransaction() - .add_hbar_transfer(operator_id, -100_000_000) - .add_hbar_transfer(receiver_id, 100_000_000) + TransferTransaction().add_hbar_transfer(operator_id, -100_000_000).add_hbar_transfer(receiver_id, 100_000_000) ) transaction.transaction_id = TransactionId.generate(operator_id) @@ -617,12 +593,13 @@ def test_unsigned_transaction_can_be_signed_after_to_bytes(): assert unsigned_bytes != signed_bytes assert isinstance(signed_bytes, bytes) + def test_transaction_freeze_with_node_ids(mock_client): """ Test freeze_with() correctly initializes transaction bytes using provided node_account_id(s). """ # Case 1 Single node_account_id - single_node_id = AccountId(0,0,3) + single_node_id = AccountId(0, 0, 3) tx = TransferTransaction() tx.node_account_id = single_node_id @@ -634,10 +611,10 @@ def test_transaction_freeze_with_node_ids(mock_client): assert set(tx._transaction_body_bytes.keys()) == {single_node_id} # Case 2 node_account_id list - node_account_ids = [AccountId(0,0,3), AccountId(0,0,4)] + node_account_ids = [AccountId(0, 0, 3), AccountId(0, 0, 4)] tx = TransferTransaction() tx.node_account_ids = node_account_ids - + tx.freeze_with(mock_client) assert tx.node_account_ids == node_account_ids @@ -645,6 +622,7 @@ def test_transaction_freeze_with_node_ids(mock_client): assert len(tx._transaction_body_bytes) == 2 assert set(tx._transaction_body_bytes.keys()) == set(node_account_ids) + def test_transaction_freeze_with_node_ids_without_client(): """ Test freeze() correctly initializes transaction bytes using provided node_account_id(s). @@ -652,7 +630,7 @@ def test_transaction_freeze_with_node_ids_without_client(): operator_id = AccountId.from_string("0.0.1234") # Case 1 Single node_account_id - single_node_id = AccountId(0,0,3) + single_node_id = AccountId(0, 0, 3) tx = TransferTransaction() tx.set_transaction_id(TransactionId.generate(operator_id)) tx.node_account_id = single_node_id @@ -665,11 +643,11 @@ def test_transaction_freeze_with_node_ids_without_client(): assert set(tx._transaction_body_bytes.keys()) == {single_node_id} # Case 2 node_account_id list - node_account_ids = [AccountId(0,0,3), AccountId(0,0,4)] + node_account_ids = [AccountId(0, 0, 3), AccountId(0, 0, 4)] tx = TransferTransaction() tx.set_transaction_id(TransactionId.generate(operator_id)) tx.node_account_ids = node_account_ids - + tx.freeze() assert tx.node_account_ids == node_account_ids @@ -677,6 +655,7 @@ def test_transaction_freeze_with_node_ids_without_client(): assert len(tx._transaction_body_bytes) == 2 assert set(tx._transaction_body_bytes.keys()) == set(node_account_ids) + def test_transaction_freeze_without_node_ids(mock_client): """ Test freeze_with() initializes transaction bytes using clients network node id. @@ -689,6 +668,7 @@ def test_transaction_freeze_without_node_ids(mock_client): assert len(tx._transaction_body_bytes) == len(mock_client.network.nodes) assert set(tx._transaction_body_bytes.keys()) == set(node._account_id for node in mock_client.network.nodes) + def test_map_response_raises_if_proto_request_is_not_transaction(): """ Test _map_response raises ValueError when provided proto is not a transaction_pb2.Transaction. @@ -696,8 +676,8 @@ def test_map_response_raises_if_proto_request_is_not_transaction(): tx = TransferTransaction() mock_response = TransactionResponseProto() - mock_node_id = None - invalid_proto_request = object() + mock_node_id = None + invalid_proto_request = object() with pytest.raises(TypeError, match="Expected Transaction but got"): tx._map_response( diff --git a/tests/unit/transaction_id_test.py b/tests/unit/transaction_id_test.py index 19f249da7..132e29904 100644 --- a/tests/unit/transaction_id_test.py +++ b/tests/unit/transaction_id_test.py @@ -1,12 +1,15 @@ -import pytest +from __future__ import annotations + import re -from hiero_sdk_python import ( - TransactionId, - AccountId -) + +import pytest + +from hiero_sdk_python import AccountId, TransactionId + pytestmark = pytest.mark.unit + def test_from_string_valid(): """Test parsing a valid transaction ID string.""" tx_id_str = "0.0.123@1234567890.123456789" @@ -14,15 +17,16 @@ def test_from_string_valid(): # Protect against breaking changes assert isinstance(tx_id, TransactionId) - assert hasattr(tx_id, 'account_id') - assert hasattr(tx_id, 'valid_start') - assert hasattr(tx_id, 'scheduled') + assert hasattr(tx_id, "account_id") + assert hasattr(tx_id, "valid_start") + assert hasattr(tx_id, "scheduled") assert tx_id.account_id == AccountId(0, 0, 123) assert tx_id.valid_start.seconds == 1234567890 assert tx_id.valid_start.nanos == 123456789 assert tx_id.scheduled is False - + + def test_from_string_invalid_suffix_raises_error(): """Test that invalid suffixes raise ValueError.""" invalid_suffixes = [ @@ -35,30 +39,32 @@ def test_from_string_invalid_suffix_raises_error(): TransactionId.from_string(s) assert "Invalid transaction ID suffix" in str(exc_info.value) + def test_from_string_invalid_format_raises_error(): """Test that invalid formats raise ValueError (covers the try-except block).""" invalid_strings = [ "invalid_string", "0.0.123.123456789", # Missing @ separator - "0.0.123@12345", # Missing . in timestamp - "0.0.123@abc.def", # Non-numeric timestamp + "0.0.123@12345", # Missing . in timestamp + "0.0.123@abc.def", # Non-numeric timestamp ] - + for s in invalid_strings: with pytest.raises(ValueError, match=f"Invalid TransactionId string format: {re.escape(s)}"): TransactionId.from_string(s) + def test_hash_implementation(): """Test __hash__ implementation coverage.""" tx_id1 = TransactionId.from_string("0.0.1@100.1") tx_id2 = TransactionId.from_string("0.0.1@100.1") tx_id3 = TransactionId.from_string("0.0.2@100.1") - + # Hashes should be equal for equal objects assert hash(tx_id1) == hash(tx_id2) # Hashes should typically differ for different objects assert hash(tx_id1) != hash(tx_id3) - + # Verify usage in sets unique_ids = {tx_id1, tx_id2, tx_id3} assert len(unique_ids) == 2 @@ -68,59 +74,63 @@ def test_hash_implementation(): assert hash(tx_id4) != hash(tx_id5), "Hash should differ when scheduled flag differs" + def test_equality(): """Test __eq__ implementation.""" tx_id1 = TransactionId.from_string("0.0.1@100.1") tx_id2 = TransactionId.from_string("0.0.1@100.1") - + assert tx_id1 == tx_id2 assert tx_id1 != "some_string" assert tx_id1 != TransactionId.from_string("0.0.1@100.2") - + tx_id4 = TransactionId.from_string("0.0.1@100.1") tx_id5 = TransactionId.from_string("0.0.1@100.1") tx_id5.scheduled = True assert tx_id4 != tx_id5, "TransactionIds with different scheduled flags should not be equal" + def test_to_proto_sets_scheduled(): """Test that _to_proto sets the scheduled flag correctly.""" tx_id = TransactionId.from_string("0.0.123@100.1") - + # Default is False proto_default = tx_id._to_proto() assert proto_default.scheduled is False - + # Set to True tx_id.scheduled = True proto_scheduled = tx_id._to_proto() assert proto_scheduled.scheduled is True + def test_generate(): """Test generating a new TransactionId.""" account_id = AccountId(0, 0, 123) tx_id = TransactionId.generate(account_id) - + # Protect against breaking changes assert isinstance(tx_id, TransactionId) - assert hasattr(tx_id, 'account_id') - assert hasattr(tx_id, 'valid_start') - assert hasattr(tx_id, 'scheduled') - + assert hasattr(tx_id, "account_id") + assert hasattr(tx_id, "valid_start") + assert hasattr(tx_id, "scheduled") + assert tx_id.account_id == account_id assert tx_id.valid_start is not None assert tx_id.valid_start.seconds > 0 assert tx_id.valid_start.nanos >= 0 assert tx_id.scheduled is False, "Generated TransactionId should have scheduled=False by default" - + + def test_to_string(): """Test converting TransactionId to string.""" tx_id = TransactionId.from_string("0.0.123@1234567890.123456789") result = tx_id.to_string() - + assert isinstance(result, str) assert result == "0.0.123@1234567890.123456789" - + # Test with scheduled flag tx_id.scheduled = True result_scheduled = tx_id.to_string() @@ -130,7 +140,7 @@ def test_to_string(): def test_str_method(): """Test __str__ returns the same as to_string().""" tx_id = TransactionId.from_string("0.0.123@1234567890.123456789") - + assert str(tx_id) == tx_id.to_string() @@ -140,10 +150,10 @@ def test_from_proto(): original = TransactionId.from_string("0.0.123@1234567890.123456789") original.scheduled = True proto = original._to_proto() - + # Create from proto and verify all fields from_proto = TransactionId._from_proto(proto) - + assert isinstance(from_proto, TransactionId) assert from_proto.account_id == original.account_id assert from_proto.valid_start.seconds == original.valid_start.seconds @@ -155,19 +165,20 @@ def test_round_trip_conversion(): """Test that to_proto -> from_proto preserves all data.""" original = TransactionId.from_string("0.0.999@9876543210.987654321") original.scheduled = True - + proto = original._to_proto() recovered = TransactionId._from_proto(proto) - + assert recovered == original assert recovered.to_string() == original.to_string() - + + def test_from_string_with_scheduled_flag(): """Test parsing a transaction ID string with scheduled flag.""" tx_id = TransactionId.from_string("0.0.123@1234567890.123456789?scheduled") - + assert isinstance(tx_id, TransactionId) assert tx_id.account_id == AccountId(0, 0, 123) assert tx_id.valid_start.seconds == 1234567890 assert tx_id.valid_start.nanos == 123456789 - assert tx_id.scheduled is True \ No newline at end of file + assert tx_id.scheduled is True diff --git a/tests/unit/transaction_nodes_test.py b/tests/unit/transaction_nodes_test.py index 7f0d8e718..a703af455 100644 --- a/tests/unit/transaction_nodes_test.py +++ b/tests/unit/transaction_nodes_test.py @@ -1,6 +1,7 @@ -import pytest -from hiero_sdk_python.transaction.transaction import Transaction +from __future__ import annotations + from hiero_sdk_python.account.account_id import AccountId +from hiero_sdk_python.transaction.transaction import Transaction class DummyTransaction(Transaction): @@ -8,6 +9,7 @@ class DummyTransaction(Transaction): Minimal subclass of Transaction for testing. Transaction is abstract (requires build methods), so we stub them out. """ + def __init__(self): super().__init__() diff --git a/tests/unit/transaction_record_query_test.py b/tests/unit/transaction_record_query_test.py index b34e088af..9d4a6d2b1 100644 --- a/tests/unit/transaction_record_query_test.py +++ b/tests/unit/transaction_record_query_test.py @@ -1,40 +1,46 @@ -import pytest +from __future__ import annotations + from unittest.mock import Mock, patch -from hiero_sdk_python.hapi.services.query_header_pb2 import ResponseType -from hiero_sdk_python.query.transaction_record_query import TransactionRecordQuery -from hiero_sdk_python.response_code import ResponseCode -from hiero_sdk_python.transaction.transaction_record import TransactionRecord +import pytest + from hiero_sdk_python.hapi.services import ( - response_pb2, + query_header_pb2, + query_pb2, response_header_pb2, + response_pb2, transaction_get_record_pb2, - transaction_record_pb2, transaction_receipt_pb2, - query_pb2, - query_header_pb2, + transaction_record_pb2, ) - +from hiero_sdk_python.hapi.services.query_header_pb2 import ResponseType +from hiero_sdk_python.query.transaction_record_query import TransactionRecordQuery +from hiero_sdk_python.response_code import ResponseCode +from hiero_sdk_python.transaction.transaction_record import TransactionRecord from tests.unit.mock_server import mock_hedera_servers + pytestmark = pytest.mark.unit + def test_constructor(transaction_id): """Test initialization of TransactionRecordQuery.""" query = TransactionRecordQuery() assert query.transaction_id is None - + query = TransactionRecordQuery(transaction_id) assert query.transaction_id == transaction_id + def test_set_transaction_id(transaction_id): """Test setting transaction ID.""" query = TransactionRecordQuery() result = query.set_transaction_id(transaction_id) - + assert query.transaction_id == transaction_id assert result is query # Should return self for chaining + def test_set_include_duplicates_chaining_and_validation(): """Test set_include_duplicates: correct assignment, chaining, and type validation.""" query = TransactionRecordQuery() @@ -45,7 +51,7 @@ def test_set_include_duplicates_chaining_and_validation(): # Positive cases: valid boolean values + chaining result = query.set_include_duplicates(True) - assert result is query # chaining works + assert result is query # chaining works assert query.include_duplicates is True result = query.set_include_duplicates(False) @@ -62,156 +68,158 @@ def test_set_include_duplicates_chaining_and_validation(): with pytest.raises(TypeError, match="include_duplicates must be a boolean"): query.set_include_duplicates(None) + def test_execute_fails_with_missing_transaction_id(mock_client): """Test request creation with missing Transaction ID.""" query = TransactionRecordQuery() - + with pytest.raises(ValueError, match="Transaction ID must be set before making the request."): query.execute(mock_client) + def test_get_method(): """Test retrieving the gRPC method for the query.""" query = TransactionRecordQuery() - + mock_channel = Mock() mock_crypto_stub = Mock() mock_channel.crypto = mock_crypto_stub - + method = query._get_method(mock_channel) - + assert method.transaction is None assert method.query == mock_crypto_stub.getTxRecordByTxID + def test_is_payment_required(): """Test that transaction record query doesn't require payment.""" query = TransactionRecordQuery() assert query._is_payment_required() is True + def test_transaction_record_query_execute(transaction_id): """Test basic functionality of TransactionRecordQuery with mock server.""" # Create a mock transaction receipt - receipt = transaction_receipt_pb2.TransactionReceipt( - status=ResponseCode.SUCCESS - ) - + receipt = transaction_receipt_pb2.TransactionReceipt(status=ResponseCode.SUCCESS) + # Create a mock transaction record transaction_record = transaction_record_pb2.TransactionRecord( receipt=receipt, - transactionHash=b'\x01' * 48, + transactionHash=b"\x01" * 48, transactionID=transaction_id._to_proto(), memo="Test transaction", - transactionFee=100000 + transactionFee=100000, ) response_sequences = get_transaction_record_responses(transaction_record) - + with mock_hedera_servers(response_sequences) as client: query = TransactionRecordQuery(transaction_id) - + try: # Get the cost of executing the query - should be 2 tinybars based on the mock response cost = query.get_cost(client) assert cost.to_tinybars() == 2 - + result = query.execute(client) except Exception as e: pytest.fail(f"Unexpected exception raised: {e}") - + assert result.transaction_id == transaction_id assert result.receipt.status == ResponseCode.SUCCESS assert result.transaction_fee == 100000 - assert result.transaction_hash == b'\x01' * 48 + assert result.transaction_hash == b"\x01" * 48 assert result.transaction_memo == "Test transaction" assert result.children == [] - + + def test_transaction_record_query_execute_with_duplicates(transaction_id): """Test TransactionRecordQuery returns duplicates when include_duplicates=True.""" receipt = transaction_receipt_pb2.TransactionReceipt(status=ResponseCode.SUCCESS) - + primary_record = transaction_record_pb2.TransactionRecord( receipt=receipt, memo="primary", transactionFee=100000, ) - + duplicate_record = transaction_record_pb2.TransactionRecord( receipt=receipt, memo="duplicate", - transactionFee=100000, + transactionFee=100000, ) - response_sequences = [[ - # Cost query responses... - response_pb2.Response( - transactionGetRecord=transaction_get_record_pb2.TransactionGetRecordResponse( - header=response_header_pb2.ResponseHeader( - nodeTransactionPrecheckCode=ResponseCode.OK, - responseType=ResponseType.COST_ANSWER, - cost=2 + response_sequences = [ + [ + # Cost query responses... + response_pb2.Response( + transactionGetRecord=transaction_get_record_pb2.TransactionGetRecordResponse( + header=response_header_pb2.ResponseHeader( + nodeTransactionPrecheckCode=ResponseCode.OK, responseType=ResponseType.COST_ANSWER, cost=2 + ) ) - ) - ), - - response_pb2.Response( - transactionGetRecord=transaction_get_record_pb2.TransactionGetRecordResponse( - header=response_header_pb2.ResponseHeader( - nodeTransactionPrecheckCode=ResponseCode.OK, - responseType=ResponseType.ANSWER_ONLY, - ), - transactionRecord=primary_record, - duplicateTransactionRecords=[duplicate_record] - ) - ) - ]] - + ), + response_pb2.Response( + transactionGetRecord=transaction_get_record_pb2.TransactionGetRecordResponse( + header=response_header_pb2.ResponseHeader( + nodeTransactionPrecheckCode=ResponseCode.OK, + responseType=ResponseType.ANSWER_ONLY, + ), + transactionRecord=primary_record, + duplicateTransactionRecords=[duplicate_record], + ) + ), + ] + ] + with mock_hedera_servers(response_sequences) as client: query = TransactionRecordQuery(transaction_id, include_duplicates=True) result = query.execute(client) - + assert isinstance(result, TransactionRecord), "Primary result must be TransactionRecord" assert result.transaction_memo == "primary" - assert hasattr(result, 'duplicates'), "duplicates attribute must exist" + assert hasattr(result, "duplicates"), "duplicates attribute must exist" assert len(result.duplicates) == 1 assert result.duplicates[0].transaction_memo == "duplicate" assert isinstance(result.duplicates[0], TransactionRecord) assert result.duplicates[0].transaction_id == transaction_id - + + def get_transaction_record_responses(transaction_record): - return [[ + return [ + [ response_pb2.Response( transactionGetRecord=transaction_get_record_pb2.TransactionGetRecordResponse( header=response_header_pb2.ResponseHeader( - nodeTransactionPrecheckCode=ResponseCode.OK, - responseType=ResponseType.COST_ANSWER, - cost=2 + nodeTransactionPrecheckCode=ResponseCode.OK, responseType=ResponseType.COST_ANSWER, cost=2 ) ) ), response_pb2.Response( transactionGetRecord=transaction_get_record_pb2.TransactionGetRecordResponse( header=response_header_pb2.ResponseHeader( - nodeTransactionPrecheckCode=ResponseCode.OK, - responseType=ResponseType.COST_ANSWER, - cost=2 + nodeTransactionPrecheckCode=ResponseCode.OK, responseType=ResponseType.COST_ANSWER, cost=2 ) ) ), response_pb2.Response( transactionGetRecord=transaction_get_record_pb2.TransactionGetRecordResponse( header=response_header_pb2.ResponseHeader( - nodeTransactionPrecheckCode=ResponseCode.OK, - responseType=ResponseType.ANSWER_ONLY, - cost=2 + nodeTransactionPrecheckCode=ResponseCode.OK, responseType=ResponseType.ANSWER_ONLY, cost=2 ), - transactionRecord=transaction_record + transactionRecord=transaction_record, ) - ) - ]] + ), + ] + ] + + # ──────────────────────────────────────────────────────────────── # Unit tests for _make_request (protobuf construction) # ──────────────────────────────────────────────────────────────── -@patch.object(TransactionRecordQuery, '_make_request_header') + +@patch.object(TransactionRecordQuery, "_make_request_header") def test_make_request_constructs_correct_protobuf(mock_make_header, transaction_id): """Test that _make_request builds valid TransactionGetRecordQuery protobuf.""" # Mock the header that normally comes from Query base class @@ -235,7 +243,7 @@ def test_make_request_constructs_correct_protobuf(mock_make_header, transaction_ assert tgr.include_child_records is False -@patch.object(TransactionRecordQuery, '_make_request_header') +@patch.object(TransactionRecordQuery, "_make_request_header") def test_make_request_sets_include_duplicates_true(mock_make_header, transaction_id): """Verify includeDuplicates flag is correctly passed to protobuf.""" fake_header = query_header_pb2.QueryHeader() @@ -250,7 +258,7 @@ def test_make_request_sets_include_duplicates_true(mock_make_header, transaction assert tgr.includeDuplicates is True -@patch.object(TransactionRecordQuery, '_make_request_header') +@patch.object(TransactionRecordQuery, "_make_request_header") def test_make_request_sets_include_children_true(mock_make_header, transaction_id): """Verify include_child_records flag is correctly passed to protobuf.""" fake_header = query_header_pb2.QueryHeader() @@ -272,7 +280,7 @@ def test_make_request_raises_when_no_transaction_id(): query._make_request() -@patch.object(TransactionRecordQuery, '_make_request_header') +@patch.object(TransactionRecordQuery, "_make_request_header") def test_make_request_checks_for_transactionGetRecord_field(mock_make_header, transaction_id): """Regression check: fails if protobuf structure changes and field is missing.""" fake_header = query_header_pb2.QueryHeader() @@ -283,7 +291,7 @@ def test_make_request_checks_for_transactionGetRecord_field(mock_make_header, tr # Simulate broken generated protobuf (rare but good safety net) with patch("hiero_sdk_python.hapi.services.query_pb2.Query") as mock_query_cls: mock_query_cls.return_value = object() # no attributes: deterministic AttributeError - + with pytest.raises(AttributeError): query._make_request() @@ -292,24 +300,17 @@ def test_make_request_checks_for_transactionGetRecord_field(mock_make_header, tr # Unit tests for _map_record_list # ──────────────────────────────────────────────────────────────── + def test_map_record_list_converts_protobuf_list(transaction_id): """_map_record_list should convert each proto record using TransactionRecord._from_proto.""" # Create actual proto records with distinct data receipt = transaction_receipt_pb2.TransactionReceipt(status=ResponseCode.SUCCESS) - proto_record_1 = transaction_record_pb2.TransactionRecord( - receipt=receipt, - memo="record1", - transactionFee=100 - ) - proto_record_2 = transaction_record_pb2.TransactionRecord( - receipt=receipt, - memo="record2", - transactionFee=200 - ) + proto_record_1 = transaction_record_pb2.TransactionRecord(receipt=receipt, memo="record1", transactionFee=100) + proto_record_2 = transaction_record_pb2.TransactionRecord(receipt=receipt, memo="record2", transactionFee=200) query = TransactionRecordQuery(transaction_id=transaction_id) result = query._map_record_list([proto_record_1, proto_record_2]) - + assert len(result) == 2 # Verify actual TransactionRecord instances are returned assert isinstance(result[0], TransactionRecord) @@ -321,21 +322,24 @@ def test_map_record_list_converts_protobuf_list(transaction_id): assert result[0].transaction_memo == "record1" assert result[1].transaction_memo == "record2" + def test_map_record_list_handles_empty_list(transaction_id): """_map_record_list returns empty list for empty input.""" query = TransactionRecordQuery(transaction_id=transaction_id) - + result = query._map_record_list([]) - + assert result == [] assert isinstance(result, list) + # === Type validation / error handling tests === + def test_set_include_duplicates_raises_on_invalid_type(): """set_include_duplicates rejects non-boolean values.""" query = TransactionRecordQuery() - + with pytest.raises(TypeError) as exc: query.set_include_duplicates(42) # wrong type: int msg = str(exc.value) @@ -348,18 +352,18 @@ def test_init_raises_on_invalid_include_duplicates_type(): with pytest.raises(TypeError) as exc: TransactionRecordQuery( transaction_id=None, - include_duplicates="yes" # wrong type: str + include_duplicates="yes", # wrong type: str ) msg = str(exc.value) - assert "include_duplicates must be a bool" in msg - assert "(True or False)" in msg # optional: extra safety - assert "got str" in msg # optional: checks type name + assert "include_duplicates must be a bool" in msg + assert "(True or False)" in msg # optional: extra safety + assert "got str" in msg # optional: checks type name def test_set_transaction_id_raises_on_invalid_type(): """set_transaction_id rejects invalid types for transaction_id.""" query = TransactionRecordQuery() - + with pytest.raises(TypeError) as exc: query.set_transaction_id(["not", "a", "txid"]) # wrong type: list msg = str(exc.value) @@ -383,7 +387,7 @@ def test_setting_include_children_without_setter(): @pytest.mark.parametrize("value", ["hello from Anto :D", 123, [True], {"include_children": True}]) def test_setting_include_children_without_setter_invalid_types(value): """__init__ rejects non-boolean include_children.""" - with pytest.raises(TypeError) as exc: + with pytest.raises(TypeError): TransactionRecordQuery(include_children=value) @@ -399,7 +403,7 @@ def test_set_include_children(): def test_set_include_children_invalid_type(value): """set_include_children() rejects non-boolean include_children.""" query = TransactionRecordQuery() - with pytest.raises(TypeError) as exc: + with pytest.raises(TypeError): query.set_include_children(value) @@ -419,27 +423,29 @@ def test_transaction_record_query_execute_with_children(transaction_id): transactionFee=50000, ) - response_sequences = [[ - response_pb2.Response( - transactionGetRecord=transaction_get_record_pb2.TransactionGetRecordResponse( - header=response_header_pb2.ResponseHeader( - nodeTransactionPrecheckCode=ResponseCode.OK, - responseType=ResponseType.COST_ANSWER, - cost=2, + response_sequences = [ + [ + response_pb2.Response( + transactionGetRecord=transaction_get_record_pb2.TransactionGetRecordResponse( + header=response_header_pb2.ResponseHeader( + nodeTransactionPrecheckCode=ResponseCode.OK, + responseType=ResponseType.COST_ANSWER, + cost=2, + ) ) - ) - ), - response_pb2.Response( - transactionGetRecord=transaction_get_record_pb2.TransactionGetRecordResponse( - header=response_header_pb2.ResponseHeader( - nodeTransactionPrecheckCode=ResponseCode.OK, - responseType=ResponseType.ANSWER_ONLY, - ), - transactionRecord=primary_record, - child_transaction_records=[child_record], - ) - ), - ]] + ), + response_pb2.Response( + transactionGetRecord=transaction_get_record_pb2.TransactionGetRecordResponse( + header=response_header_pb2.ResponseHeader( + nodeTransactionPrecheckCode=ResponseCode.OK, + responseType=ResponseType.ANSWER_ONLY, + ), + transactionRecord=primary_record, + child_transaction_records=[child_record], + ) + ), + ] + ] with mock_hedera_servers(response_sequences) as client: query = TransactionRecordQuery(transaction_id, include_children=True) @@ -469,27 +475,29 @@ def test_transaction_record_query_execute_without_children(transaction_id): transactionFee=50000, ) - response_sequences = [[ - response_pb2.Response( - transactionGetRecord=transaction_get_record_pb2.TransactionGetRecordResponse( - header=response_header_pb2.ResponseHeader( - nodeTransactionPrecheckCode=ResponseCode.OK, - responseType=ResponseType.COST_ANSWER, - cost=2, + response_sequences = [ + [ + response_pb2.Response( + transactionGetRecord=transaction_get_record_pb2.TransactionGetRecordResponse( + header=response_header_pb2.ResponseHeader( + nodeTransactionPrecheckCode=ResponseCode.OK, + responseType=ResponseType.COST_ANSWER, + cost=2, + ) ) - ) - ), - response_pb2.Response( - transactionGetRecord=transaction_get_record_pb2.TransactionGetRecordResponse( - header=response_header_pb2.ResponseHeader( - nodeTransactionPrecheckCode=ResponseCode.OK, - responseType=ResponseType.ANSWER_ONLY, - ), - transactionRecord=primary_record, - child_transaction_records=[child_record], - ) - ), - ]] + ), + response_pb2.Response( + transactionGetRecord=transaction_get_record_pb2.TransactionGetRecordResponse( + header=response_header_pb2.ResponseHeader( + nodeTransactionPrecheckCode=ResponseCode.OK, + responseType=ResponseType.ANSWER_ONLY, + ), + transactionRecord=primary_record, + child_transaction_records=[child_record], + ) + ), + ] + ] with mock_hedera_servers(response_sequences) as client: query = TransactionRecordQuery(transaction_id, include_children=False) diff --git a/tests/unit/transaction_record_test.py b/tests/unit/transaction_record_test.py index 4e9078075..2aaddda8d 100644 --- a/tests/unit/transaction_record_test.py +++ b/tests/unit/transaction_record_test.py @@ -1,5 +1,7 @@ """Unit tests for the TransactionRecord class functionality.""" +from __future__ import annotations + from collections import defaultdict import pytest @@ -24,6 +26,7 @@ from hiero_sdk_python.transaction.transaction_receipt import TransactionReceipt from hiero_sdk_python.transaction.transaction_record import TransactionRecord + pytestmark = pytest.mark.unit @@ -31,10 +34,8 @@ def transaction_record(transaction_id): """Create a mock transaction record.""" receipt = TransactionReceipt( - receipt_proto=transaction_receipt_pb2.TransactionReceipt( - status=ResponseCode.SUCCESS - ), - transaction_id=transaction_id + receipt_proto=transaction_receipt_pb2.TransactionReceipt(status=ResponseCode.SUCCESS), + transaction_id=transaction_id, ) ts = Timestamp(seconds=1234567890, nanos=500000000) @@ -42,7 +43,7 @@ def transaction_record(transaction_id): return TransactionRecord( transaction_id=transaction_id, - transaction_hash=b'\x01\x02\x03\x04' * 12, + transaction_hash=b"\x01\x02\x03\x04" * 12, transaction_memo="Test transaction memo", transaction_fee=100000, receipt=receipt, @@ -56,35 +57,29 @@ def transaction_record(transaction_id): consensus_timestamp=ts, schedule_ref=sched, assessed_custom_fees=[ - AssessedCustomFee( - amount=500000000, - fee_collector_account_id=AccountId(shard=0, realm=0, num=98) - ) + AssessedCustomFee(amount=500000000, fee_collector_account_id=AccountId(shard=0, realm=0, num=98)) ], automatic_token_associations=[ TokenAssociation( - token_id=TokenId(shard=0, realm=0, num=5678), - account_id=AccountId(shard=0, realm=0, num=1234) + token_id=TokenId(shard=0, realm=0, num=5678), account_id=AccountId(shard=0, realm=0, num=1234) ) ], parent_consensus_timestamp=ts, - alias=b'\x12\x34\x56\x78', - ethereum_hash=b'\xab' * 32, - paid_staking_rewards=[ - (AccountId(shard=0, realm=0, num=456), 1000000) - ], - evm_address=b'\x12' * 20, + alias=b"\x12\x34\x56\x78", + ethereum_hash=b"\xab" * 32, + paid_staking_rewards=[(AccountId(shard=0, realm=0, num=456), 1000000)], + evm_address=b"\x12" * 20, contract_create_result=ContractFunctionResult( - contract_id=ContractId(shard=0, realm=0, contract=789), - contract_call_result=b"Mock result" + contract_id=ContractId(shard=0, realm=0, contract=789), contract_call_result=b"Mock result" ), ) + @pytest.fixture def proto_transaction_record(transaction_id): """Create a mock transaction record protobuf.""" return transaction_record_pb2.TransactionRecord( - transactionHash=b'\x01\x02\x03\x04' * 12, + transactionHash=b"\x01\x02\x03\x04" * 12, memo="Test transaction memo", transactionFee=100000, receipt=transaction_receipt_pb2.TransactionReceipt(status=ResponseCode.SUCCESS), @@ -92,10 +87,11 @@ def proto_transaction_record(transaction_id): prng_number=100, ) + def test_transaction_record_initialization(transaction_record, transaction_id): """Test the initialization of the TransactionRecord class.""" assert transaction_record.transaction_id == transaction_id - assert transaction_record.transaction_hash == b'\x01\x02\x03\x04' * 12 + assert transaction_record.transaction_hash == b"\x01\x02\x03\x04" * 12 assert transaction_record.transaction_memo == "Test transaction memo" assert transaction_record.transaction_fee == 100000 assert isinstance(transaction_record.receipt, TransactionReceipt) @@ -119,7 +115,7 @@ def test_transaction_record_default_initialization(): assert record.new_pending_airdrops == [] assert record.prng_number is None assert record.prng_bytes is None - assert hasattr(record, 'duplicates'), "TransactionRecord should have duplicates attribute" + assert hasattr(record, "duplicates"), "TransactionRecord should have duplicates attribute" assert isinstance(record.duplicates, list) assert len(record.duplicates) == 0 assert record.duplicates == [] @@ -136,12 +132,13 @@ def test_transaction_record_default_initialization(): assert record.evm_address is None assert record.contract_create_result is None + def test_from_proto(proto_transaction_record, transaction_id): """Test the from_proto method of the TransactionRecord class.""" record = TransactionRecord._from_proto(proto_transaction_record, transaction_id) assert record.transaction_id == transaction_id - assert record.transaction_hash == b'\x01\x02\x03\x04' * 12 + assert record.transaction_hash == b"\x01\x02\x03\x04" * 12 assert record.transaction_memo == "Test transaction memo" assert record.transaction_fee == 100000 assert isinstance(record.receipt, TransactionReceipt) @@ -160,6 +157,7 @@ def test_from_proto(proto_transaction_record, transaction_id): assert record.evm_address is None assert record.contract_create_result is None + def test_from_proto_with_transfers(transaction_id): """Test from_proto with HBAR transfers.""" proto = transaction_record_pb2.TransactionRecord() @@ -170,12 +168,13 @@ def test_from_proto_with_transfers(transaction_id): record = TransactionRecord._from_proto(proto, transaction_id) assert record.transfers[AccountId(0, 0, 200)] == 1000 + def test_from_proto_with_token_transfers(transaction_id): """Test from_proto with token transfers.""" proto = transaction_record_pb2.TransactionRecord() token_list = proto.tokenTransferLists.add() token_list.token.CopyFrom(TokenId(0, 0, 300)._to_proto()) - + transfer = token_list.transfers.add() transfer.accountID.CopyFrom(AccountId(0, 0, 200)._to_proto()) transfer.amount = 500 @@ -183,12 +182,13 @@ def test_from_proto_with_token_transfers(transaction_id): record = TransactionRecord._from_proto(proto, transaction_id) assert record.token_transfers[TokenId(0, 0, 300)][AccountId(0, 0, 200)] == 500 + def test_from_proto_with_nft_transfers(transaction_id): """Test from_proto with NFT transfers.""" proto = transaction_record_pb2.TransactionRecord() token_list = proto.tokenTransferLists.add() token_list.token.CopyFrom(TokenId(0, 0, 300)._to_proto()) - + nft = token_list.nftTransfers.add() nft.senderAccountID.CopyFrom(AccountId(0, 0, 100)._to_proto()) nft.receiverAccountID.CopyFrom(AccountId(0, 0, 200)._to_proto()) @@ -203,17 +203,18 @@ def test_from_proto_with_nft_transfers(transaction_id): assert transfer.serial_number == 1 assert transfer.is_approved is False + def test_from_proto_with_new_pending_airdrops(transaction_id): """Test from_proto with Pending Airdrops.""" - sender = AccountId(0,0,100) - receiver = AccountId(0,0,200) - token_id = TokenId(0,0,1) + sender = AccountId(0, 0, 100) + receiver = AccountId(0, 0, 200) + token_id = TokenId(0, 0, 1) amount = 10 proto = transaction_record_pb2.TransactionRecord() pending_airdrop_id = PendingAirdropId(sender, receiver, token_id) proto.new_pending_airdrops.add().CopyFrom(PendingAirdropRecord(pending_airdrop_id, amount)._to_proto()) - + record = TransactionRecord._from_proto(proto, transaction_id) assert len(record.new_pending_airdrops) == 1 new_pending_airdrops = record.new_pending_airdrops[0] @@ -242,6 +243,7 @@ def test_from_proto_with_prng_bytes(transaction_id): assert record.prng_bytes == b"123" assert record.prng_number is None + def test_from_proto_with_consensus_timestamp(transaction_id): """Test parsing consensus_timestamp from proto.""" proto = transaction_record_pb2.TransactionRecord() @@ -318,19 +320,19 @@ def test_from_proto_with_parent_consensus_timestamp(transaction_id): def test_from_proto_with_alias(transaction_id): """Test parsing alias bytes from proto.""" proto = transaction_record_pb2.TransactionRecord() - proto.alias = b'\x12\x34\x56\x78' + proto.alias = b"\x12\x34\x56\x78" record = TransactionRecord._from_proto(proto, transaction_id) - assert record.alias == b'\x12\x34\x56\x78' + assert record.alias == b"\x12\x34\x56\x78" def test_from_proto_with_ethereum_hash(transaction_id): """Test parsing ethereum_hash from proto.""" proto = transaction_record_pb2.TransactionRecord() - proto.ethereum_hash = b'\xab' * 32 + proto.ethereum_hash = b"\xab" * 32 record = TransactionRecord._from_proto(proto, transaction_id) - assert record.ethereum_hash == b'\xab' * 32 + assert record.ethereum_hash == b"\xab" * 32 def test_from_proto_with_paid_staking_rewards(transaction_id): @@ -352,10 +354,10 @@ def test_from_proto_with_paid_staking_rewards(transaction_id): def test_from_proto_with_evm_address(transaction_id): """Test parsing evm_address from proto.""" proto = transaction_record_pb2.TransactionRecord() - proto.evm_address = b'\x12' * 20 + proto.evm_address = b"\x12" * 20 record = TransactionRecord._from_proto(proto, transaction_id) - assert record.evm_address == b'\x12' * 20 + assert record.evm_address == b"\x12" * 20 def test_from_proto_with_contract_create_result(transaction_id): @@ -371,11 +373,12 @@ def test_from_proto_with_contract_create_result(transaction_id): assert record.contract_create_result.contract_id.contract == 789 assert record.contract_create_result.contract_call_result == b"Created!" + def test_to_proto(transaction_record, transaction_id): """Test the to_proto method of the TransactionRecord class.""" proto = transaction_record._to_proto() - assert proto.transactionHash == b'\x01\x02\x03\x04' * 12 + assert proto.transactionHash == b"\x01\x02\x03\x04" * 12 assert proto.memo == "Test transaction memo" assert proto.transactionFee == 100000 assert proto.receipt.status == ResponseCode.SUCCESS @@ -383,6 +386,7 @@ def test_to_proto(transaction_record, transaction_id): assert proto.prng_number == 100 assert proto.prng_bytes == b"" + def test_proto_conversion(transaction_record): """Test converting TransactionRecord to proto and back preserves data.""" proto = transaction_record._to_proto() @@ -396,6 +400,7 @@ def test_proto_conversion(transaction_record): assert converted.prng_number == transaction_record.prng_number assert converted.prng_bytes is None + def test_proto_conversion_with_transfers(transaction_id): """Test proto conversion preserves transfer data.""" record = TransactionRecord() @@ -409,6 +414,7 @@ def test_proto_conversion_with_transfers(transaction_id): assert converted.transfers[AccountId(0, 0, 100)] == -1000 assert converted.transfers[AccountId(0, 0, 200)] == 1000 + def test_proto_conversion_with_token_transfers(transaction_id): """Test proto conversion preserves token transfer data.""" record = TransactionRecord() @@ -423,6 +429,7 @@ def test_proto_conversion_with_token_transfers(transaction_id): assert converted.token_transfers[token_id][AccountId(0, 0, 100)] == -500 assert converted.token_transfers[token_id][AccountId(0, 0, 200)] == 500 + def test_proto_conversion_with_nft_transfers(transaction_id): """Test proto conversion preserves NFT transfer data.""" record = TransactionRecord() @@ -432,7 +439,7 @@ def test_proto_conversion_with_nft_transfers(transaction_id): sender_id=AccountId(0, 0, 100), receiver_id=AccountId(0, 0, 200), serial_number=1, - is_approved=False + is_approved=False, ) record.nft_transfers = defaultdict(list[TokenNftTransfer]) record.nft_transfers[token_id].append(nft_transfer) @@ -447,16 +454,17 @@ def test_proto_conversion_with_nft_transfers(transaction_id): assert transfer.serial_number == 1 assert not transfer.is_approved + def test_proto_conversion_with_new_pending_airdrops(transaction_id): """Test proto conversion preserves PendingAirdropsRecord.""" - sender = AccountId(0,0,100) - receiver = AccountId(0,0,200) - token_id = TokenId(0,0,1) + sender = AccountId(0, 0, 100) + receiver = AccountId(0, 0, 200) + token_id = TokenId(0, 0, 1) amount = 10 record = TransactionRecord() record.new_pending_airdrops = [] - record.new_pending_airdrops.append(PendingAirdropRecord(PendingAirdropId(sender, receiver, token_id),amount)) + record.new_pending_airdrops.append(PendingAirdropRecord(PendingAirdropId(sender, receiver, token_id), amount)) proto = record._to_proto() converted = TransactionRecord._from_proto(proto, transaction_id) @@ -468,6 +476,7 @@ def test_proto_conversion_with_new_pending_airdrops(transaction_id): assert new_pending_airdrops.pending_airdrop_id.token_id == token_id assert new_pending_airdrops.amount == amount + def test_repr_method(transaction_id): """Test the __repr__ method of TransactionRecord.""" # Test with default values @@ -503,17 +512,13 @@ def test_repr_method(transaction_id): "contract_create_result=None)" ) assert repr(record_default) == expected_repr_default - + # Test with receipt only receipt = TransactionReceipt( - receipt_proto=transaction_receipt_pb2.TransactionReceipt( - status=ResponseCode.SUCCESS - ), + receipt_proto=transaction_receipt_pb2.TransactionReceipt(status=ResponseCode.SUCCESS), transaction_id=transaction_id, ) - record_with_receipt = TransactionRecord( - transaction_id=transaction_id, receipt=receipt - ) + record_with_receipt = TransactionRecord(transaction_id=transaction_id, receipt=receipt) repr_receipt = repr(record_with_receipt) assert "duplicates_count=0" in repr_receipt assert f"transaction_id='{transaction_id}'" in repr_receipt @@ -551,25 +556,22 @@ def test_repr_method(transaction_id): sched = ScheduleId(0, 0, 9999) record_full = TransactionRecord( transaction_id=transaction_id, - transaction_hash=b'\x01\x02\x03\x04', + transaction_hash=b"\x01\x02\x03\x04", transaction_memo="Test memo", transaction_fee=100000, receipt=receipt, consensus_timestamp=ts, schedule_ref=sched, - assessed_custom_fees=[AssessedCustomFee( - amount=500000000, - fee_collector_account_id=AccountId(0, 0, 98) - )], + assessed_custom_fees=[AssessedCustomFee(amount=500000000, fee_collector_account_id=AccountId(0, 0, 98))], automatic_token_associations=[TokenAssociation(token_id=TokenId(0, 0, 5678), account_id=AccountId(0, 0, 1234))], parent_consensus_timestamp=ts, - alias=b'\x12\x34', - ethereum_hash=b'\xab' * 32, + alias=b"\x12\x34", + ethereum_hash=b"\xab" * 32, paid_staking_rewards=[(AccountId(0, 0, 456), 1000000)], - evm_address=b'\x12' * 20, + evm_address=b"\x12" * 20, contract_create_result=ContractFunctionResult(contract_id=ContractId(0, 0, 789)), ) - + repr_full = repr(record_full) assert f"transaction_id='{transaction_id}'" in repr_full assert "transaction_hash=b'\\x01\\x02\\x03\\x04'" in repr_full @@ -590,41 +592,66 @@ def test_repr_method(transaction_id): assert "children_count=0" in repr_full # Test with transfers - record_with_transfers = TransactionRecord( - transaction_id=transaction_id, receipt=receipt - ) + record_with_transfers = TransactionRecord(transaction_id=transaction_id, receipt=receipt) record_with_transfers.transfers[AccountId(0, 0, 100)] = -1000 record_with_transfers.transfers[AccountId(0, 0, 200)] = 1000 repr_transfers = repr(record_with_transfers) assert "duplicates_count=0" in repr_transfers - assert "transfers={AccountId(shard=0, realm=0, num=100): -1000, AccountId(shard=0, realm=0, num=200): 1000}" in repr_transfers - - expected_repr_with_transfers = (f"TransactionRecord(transaction_id='{transaction_id}', " - f"transaction_hash=None, " - f"transaction_memo='None', " - f"transaction_fee=None, " - f"receipt_status='SUCCESS', " - f"token_transfers={{}}, " - f"nft_transfers={{}}, " - f"transfers={{AccountId(shard=0, realm=0, num=100): -1000, AccountId(shard=0, realm=0, num=200): 1000}}, " - f"new_pending_airdrops={[]}, " - f"call_result=None, " - f"prng_number=None, " - f"prng_bytes=None, " - f"duplicates_count=0, " - f"children_count=0, " - f"consensus_timestamp=None, " - f"schedule_ref=None, " - f"assessed_custom_fees=[], " - f"automatic_token_associations=[], " - f"parent_consensus_timestamp=None, " - f"alias=None, " - f"ethereum_hash=None, " - f"paid_staking_rewards=[], " - f"evm_address=None, " - f"contract_create_result=None)") + assert ( + "transfers={AccountId(shard=0, realm=0, num=100): -1000, AccountId(shard=0, realm=0, num=200): 1000}" + in repr_transfers + ) + + expected_repr_with_transfers = ( + f"TransactionRecord(transaction_id='{transaction_id}', " + f"transaction_hash=None, " + f"transaction_memo='None', " + f"transaction_fee=None, " + f"receipt_status='SUCCESS', " + f"token_transfers={{}}, " + f"nft_transfers={{}}, " + f"transfers={{AccountId(shard=0, realm=0, num=100): -1000, AccountId(shard=0, realm=0, num=200): 1000}}, " + f"new_pending_airdrops={[]}, " + f"call_result=None, " + f"prng_number=None, " + f"prng_bytes=None, " + f"duplicates_count=0, " + f"children_count=0)" + ) + assert ( + "transfers={AccountId(shard=0, realm=0, num=100): -1000, AccountId(shard=0, realm=0, num=200): 1000}" + in repr_transfers + ) + + expected_repr_with_transfers = ( + f"TransactionRecord(transaction_id='{transaction_id}', " + f"transaction_hash=None, " + f"transaction_memo='None', " + f"transaction_fee=None, " + f"receipt_status='SUCCESS', " + f"token_transfers={{}}, " + f"nft_transfers={{}}, " + f"transfers={{AccountId(shard=0, realm=0, num=100): -1000, AccountId(shard=0, realm=0, num=200): 1000}}, " + f"new_pending_airdrops={[]}, " + f"call_result=None, " + f"prng_number=None, " + f"prng_bytes=None, " + f"duplicates_count=0, " + f"children_count=0, " + f"consensus_timestamp=None, " + f"schedule_ref=None, " + f"assessed_custom_fees=[], " + f"automatic_token_associations=[], " + f"parent_consensus_timestamp=None, " + f"alias=None, " + f"ethereum_hash=None, " + f"paid_staking_rewards=[], " + f"evm_address=None, " + f"contract_create_result=None)" + ) assert repr(record_with_transfers) == expected_repr_with_transfers + def test_proto_conversion_with_call_result(transaction_id): """Test the call_result property of TransactionRecord.""" record = TransactionRecord() @@ -650,6 +677,7 @@ def test_proto_conversion_with_call_result(transaction_id): assert converted.call_result.gas_available == record.call_result.gas_available assert converted.call_result.amount == record.call_result.amount + def test_from_proto_accepts_and_stores_duplicates(transaction_id): """Test that _from_proto correctly stores provided duplicate records.""" proto = transaction_record_pb2.TransactionRecord() @@ -706,6 +734,7 @@ def test_from_proto_with_duplicates_instances(transaction_id): assert record.duplicates[0] is dup, "Should store the exact duplicate instance by reference" + def test_to_proto_does_not_serialize_duplicates(transaction_id): """Test that _to_proto excludes duplicates, preserving the query-only invariant.""" dup = TransactionRecord(transaction_id=transaction_id, transaction_memo="dup") @@ -722,6 +751,7 @@ def test_to_proto_does_not_serialize_duplicates(transaction_id): assert round_tripped.duplicates == [], "Duplicates must not survive round-trip through proto" assert round_tripped.transaction_memo == "primary" + def test_repr_includes_duplicates_count(transaction_id): """Test that __repr__ shows correct duplicates_count.""" record = TransactionRecord(transaction_id=transaction_id) @@ -732,12 +762,15 @@ def test_repr_includes_duplicates_count(transaction_id): assert "duplicates_count=2" in repr(record), "duplicates_count should reflect list length" + def test_from_proto_raises_when_no_transaction_id_available(): """Verify error is raised when neither transaction_id param nor proto.transactionID is present.""" proto = transaction_record_pb2.TransactionRecord() - + + # Force-clear the field (works in protobuf 3 & 4) + proto.ClearField("transactionID") - + assert not proto.HasField("transactionID"), "Field should be absent after ClearField" with pytest.raises(ValueError, match=r"transaction_id is required when proto\.transactionID is not present"): @@ -754,6 +787,7 @@ def test_transaction_record_children_not_shared_between_instances(): assert len(r1.children) == 1 assert len(r2.children) == 0 + def test_round_trip_consensus_timestamp(transaction_id): """Test consensus timestamp survives protobuf round-trip.""" ts = Timestamp(1234567890, 500000000) @@ -774,10 +808,7 @@ def test_round_trip_schedule_ref(transaction_id): def test_round_trip_assessed_custom_fees(transaction_id): """Test custom fees survive protobuf round-trip.""" - fee = AssessedCustomFee( - amount=500000000, - fee_collector_account_id=AccountId(0, 0, 98) - ) + fee = AssessedCustomFee(amount=500000000, fee_collector_account_id=AccountId(0, 0, 98)) original = TransactionRecord(assessed_custom_fees=[fee]) proto = original._to_proto() round_tripped = TransactionRecord._from_proto(proto, transaction_id) @@ -787,10 +818,7 @@ def test_round_trip_assessed_custom_fees(transaction_id): def test_round_trip_automatic_token_associations(transaction_id): """Test token associations survive protobuf round-trip.""" - assoc = TokenAssociation( - token_id=TokenId(0, 0, 5678), - account_id=AccountId(0, 0, 1234) - ) + assoc = TokenAssociation(token_id=TokenId(0, 0, 5678), account_id=AccountId(0, 0, 1234)) original = TransactionRecord(automatic_token_associations=[assoc]) proto = original._to_proto() round_tripped = TransactionRecord._from_proto(proto, transaction_id) @@ -809,18 +837,18 @@ def test_round_trip_parent_consensus_timestamp(transaction_id): def test_round_trip_alias(transaction_id): """Test alias bytes survive protobuf round-trip.""" - original = TransactionRecord(alias=b'\x12\x34') + original = TransactionRecord(alias=b"\x12\x34") proto = original._to_proto() round_tripped = TransactionRecord._from_proto(proto, transaction_id) - assert round_tripped.alias == b'\x12\x34' + assert round_tripped.alias == b"\x12\x34" def test_round_trip_ethereum_hash(transaction_id): """Test ethereum hash survives protobuf round-trip.""" - original = TransactionRecord(ethereum_hash=b'\xab' * 32) + original = TransactionRecord(ethereum_hash=b"\xab" * 32) proto = original._to_proto() round_tripped = TransactionRecord._from_proto(proto, transaction_id) - assert round_tripped.ethereum_hash == b'\xab' * 32 + assert round_tripped.ethereum_hash == b"\xab" * 32 def test_round_trip_paid_staking_rewards(transaction_id): @@ -834,24 +862,22 @@ def test_round_trip_paid_staking_rewards(transaction_id): def test_round_trip_evm_address(transaction_id): """Test EVM address survives protobuf round-trip.""" - original = TransactionRecord(evm_address=b'\x12' * 20) + original = TransactionRecord(evm_address=b"\x12" * 20) proto = original._to_proto() round_tripped = TransactionRecord._from_proto(proto, transaction_id) - assert round_tripped.evm_address == b'\x12' * 20 + assert round_tripped.evm_address == b"\x12" * 20 def test_round_trip_contract_create_result(transaction_id): """Test contract creation result survives round-trip.""" - result = ContractFunctionResult( - contract_id=ContractId(0, 0, 789), - contract_call_result=b"Created!" - ) + result = ContractFunctionResult(contract_id=ContractId(0, 0, 789), contract_call_result=b"Created!") original = TransactionRecord(contract_create_result=result) proto = original._to_proto() round_tripped = TransactionRecord._from_proto(proto, transaction_id) assert round_tripped.contract_create_result.contract_id == result.contract_id assert round_tripped.contract_create_result.contract_call_result == b"Created!" + def test_to_proto_raises_when_both_call_and_create_result_set(transaction_id): """Setting both call_result and contract_create_result must raise (protobuf oneof).""" record = TransactionRecord( @@ -867,6 +893,7 @@ def test_to_proto_raises_when_both_call_and_create_result_set(transaction_id): with pytest.raises(ValueError, match="mutually exclusive"): record._to_proto() + def test_to_proto_raises_when_both_prng_fields_set(transaction_id): """Setting both prng_number and prng_bytes must raise (entropy oneof).""" record = TransactionRecord( @@ -877,4 +904,3 @@ def test_to_proto_raises_when_both_prng_fields_set(transaction_id): with pytest.raises(ValueError, match="mutually exclusive"): record._to_proto() - \ No newline at end of file diff --git a/tests/unit/transaction_response_test.py b/tests/unit/transaction_response_test.py index 5a594b1d1..23f8913ac 100644 --- a/tests/unit/transaction_response_test.py +++ b/tests/unit/transaction_response_test.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import pytest from hiero_sdk_python.account.account_id import AccountId @@ -23,6 +25,7 @@ from hiero_sdk_python.transaction.transaction_response import TransactionResponse from tests.unit.mock_server import mock_hedera_servers + pytestmark = pytest.mark.unit @@ -50,9 +53,7 @@ def test_get_receipt_executes_and_returns_receipt(transaction_response): """Test get_receipt execute receipt query and return transaction receipt.""" receipt_response = response_pb2.Response( transactionGetReceipt=transaction_get_receipt_pb2.TransactionGetReceiptResponse( - header=response_header_pb2.ResponseHeader( - nodeTransactionPrecheckCode=ResponseCode.OK - ), + header=response_header_pb2.ResponseHeader(nodeTransactionPrecheckCode=ResponseCode.OK), receipt=transaction_receipt_pb2.TransactionReceipt( status=ResponseCode.SUCCESS, accountID=basic_types_pb2.AccountID( @@ -87,12 +88,8 @@ def test_get_receipt_returns_failure_status_without_validate_status(transaction_ """Test failing status returns a receipt instead of raising an error when validation is disabled.""" receipt_response = response_pb2.Response( transactionGetReceipt=transaction_get_receipt_pb2.TransactionGetReceiptResponse( - header=response_header_pb2.ResponseHeader( - nodeTransactionPrecheckCode=ResponseCode.OK - ), - receipt=transaction_receipt_pb2.TransactionReceipt( - status=ResponseCode.INVALID_SIGNATURE - ), + header=response_header_pb2.ResponseHeader(nodeTransactionPrecheckCode=ResponseCode.OK), + receipt=transaction_receipt_pb2.TransactionReceipt(status=ResponseCode.INVALID_SIGNATURE), ) ) @@ -102,16 +99,13 @@ def test_get_receipt_returns_failure_status_without_validate_status(transaction_ assert isinstance(receipt, TransactionReceipt) assert receipt.status == ResponseCode.INVALID_SIGNATURE + def test_get_receipt_raises_exception_with_validate_status(transaction_response): """Test get_receipt error is raised for non-success statuses when validation is enabled.""" receipt_response = response_pb2.Response( transactionGetReceipt=transaction_get_receipt_pb2.TransactionGetReceiptResponse( - header=response_header_pb2.ResponseHeader( - nodeTransactionPrecheckCode=ResponseCode.OK - ), - receipt=transaction_receipt_pb2.TransactionReceipt( - status=ResponseCode.INVALID_SIGNATURE - ), + header=response_header_pb2.ResponseHeader(nodeTransactionPrecheckCode=ResponseCode.OK), + receipt=transaction_receipt_pb2.TransactionReceipt(status=ResponseCode.INVALID_SIGNATURE), ) ) @@ -135,9 +129,7 @@ def test_get_record_query_builds_query(transaction_response): def test_get_record_executes_and_returns_record(transaction_response): """Test get_record execute record query and return transaction record.""" receipt = transaction_receipt_pb2.TransactionReceipt(status=ResponseCode.SUCCESS) - record = transaction_record_pb2.TransactionRecord( - receipt=receipt, memo="record", transactionFee=100 - ) + record = transaction_record_pb2.TransactionRecord(receipt=receipt, memo="record", transactionFee=100) record_response = [ [ diff --git a/tests/unit/transaction_test.py b/tests/unit/transaction_test.py index d3504e144..efe8cbe84 100644 --- a/tests/unit/transaction_test.py +++ b/tests/unit/transaction_test.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import pytest from hiero_sdk_python.account.account_create_transaction import AccountCreateTransaction @@ -16,25 +18,20 @@ from hiero_sdk_python.transaction.transaction_response import TransactionResponse from tests.unit.mock_server import mock_hedera_servers + pytestmark = pytest.mark.unit def test_execute_waits_for_receipt_receipt(): """Test execute return TransactionReceipt when wait_for_receipt is True (default).""" - ok_response = transaction_response_pb2.TransactionResponse( - nodeTransactionPrecheckCode=ResponseCode.OK - ) + ok_response = transaction_response_pb2.TransactionResponse(nodeTransactionPrecheckCode=ResponseCode.OK) receipt_response = response_pb2.Response( transactionGetReceipt=transaction_get_receipt_pb2.TransactionGetReceiptResponse( - header=response_header_pb2.ResponseHeader( - nodeTransactionPrecheckCode=ResponseCode.OK - ), + header=response_header_pb2.ResponseHeader(nodeTransactionPrecheckCode=ResponseCode.OK), receipt=transaction_receipt_pb2.TransactionReceipt( status=ResponseCode.SUCCESS, - accountID=basic_types_pb2.AccountID( - shardNum=0, realmNum=0, accountNum=1234 - ), + accountID=basic_types_pb2.AccountID(shardNum=0, realmNum=0, accountNum=1234), ), ) ) @@ -42,11 +39,7 @@ def test_execute_waits_for_receipt_receipt(): response_sequence = [[ok_response, receipt_response]] with mock_hedera_servers(response_sequence) as client: - tx = ( - AccountCreateTransaction() - .set_initial_balance(1) - .set_key_without_alias(PrivateKey.generate()) - ) + tx = AccountCreateTransaction().set_initial_balance(1).set_key_without_alias(PrivateKey.generate()) # Default value of wait_for_receipt = True receipt = tx.execute(client, wait_for_receipt=True) @@ -57,20 +50,14 @@ def test_execute_waits_for_receipt_receipt(): def test_execute_without_wait_returns_transaction_response(): """Test execute return TransactionResponse when wait_for_receipt is False.""" - ok_response = transaction_response_pb2.TransactionResponse( - nodeTransactionPrecheckCode=ResponseCode.OK - ) + ok_response = transaction_response_pb2.TransactionResponse(nodeTransactionPrecheckCode=ResponseCode.OK) receipt_response = response_pb2.Response( transactionGetReceipt=transaction_get_receipt_pb2.TransactionGetReceiptResponse( - header=response_header_pb2.ResponseHeader( - nodeTransactionPrecheckCode=ResponseCode.OK - ), + header=response_header_pb2.ResponseHeader(nodeTransactionPrecheckCode=ResponseCode.OK), receipt=transaction_receipt_pb2.TransactionReceipt( status=ResponseCode.SUCCESS, - accountID=basic_types_pb2.AccountID( - shardNum=0, realmNum=0, accountNum=1234 - ), + accountID=basic_types_pb2.AccountID(shardNum=0, realmNum=0, accountNum=1234), ), ) ) @@ -78,11 +65,7 @@ def test_execute_without_wait_returns_transaction_response(): response_sequence = [[ok_response, receipt_response]] with mock_hedera_servers(response_sequence) as client: - tx = ( - AccountCreateTransaction() - .set_initial_balance(1) - .set_key_without_alias(PrivateKey.generate()) - ) + tx = AccountCreateTransaction().set_initial_balance(1).set_key_without_alias(PrivateKey.generate()) # Explicitly pass wait_for_receipt=False to get TransactionResponse response = tx.execute(client, wait_for_receipt=False) @@ -92,62 +75,44 @@ def test_execute_without_wait_returns_transaction_response(): assert response.node_id == tx.node_account_id assert response.validate_status is True + def test_execute_raises_error_when_validation_enabled_and_transaction_fails(): """Test execute raises error for failing transactions when validate_status is True.""" - ok_response = transaction_response_pb2.TransactionResponse( - nodeTransactionPrecheckCode=ResponseCode.OK - ) + ok_response = transaction_response_pb2.TransactionResponse(nodeTransactionPrecheckCode=ResponseCode.OK) receipt_response = response_pb2.Response( transactionGetReceipt=transaction_get_receipt_pb2.TransactionGetReceiptResponse( - header=response_header_pb2.ResponseHeader( - nodeTransactionPrecheckCode=ResponseCode.OK - ), - receipt=transaction_receipt_pb2.TransactionReceipt( - status=ResponseCode.INVALID_SIGNATURE - ), + header=response_header_pb2.ResponseHeader(nodeTransactionPrecheckCode=ResponseCode.OK), + receipt=transaction_receipt_pb2.TransactionReceipt(status=ResponseCode.INVALID_SIGNATURE), ) ) response_sequence = [[ok_response, receipt_response]] with mock_hedera_servers(response_sequence) as client: - tx = ( - AccountCreateTransaction() - .set_initial_balance(1) - .set_key_without_alias(PrivateKey.generate()) - ) + tx = AccountCreateTransaction().set_initial_balance(1).set_key_without_alias(PrivateKey.generate()) with pytest.raises(ReceiptStatusError) as e: tx.execute(client, validate_status=True) assert e.value.status == ResponseCode.INVALID_SIGNATURE + def test_execute_returns_receipt_without_error_when_validation_disabled(): """Test execute returns a receipt normally on failure when validate_status is False.""" - ok_response = transaction_response_pb2.TransactionResponse( - nodeTransactionPrecheckCode=ResponseCode.OK - ) + ok_response = transaction_response_pb2.TransactionResponse(nodeTransactionPrecheckCode=ResponseCode.OK) receipt_response = response_pb2.Response( transactionGetReceipt=transaction_get_receipt_pb2.TransactionGetReceiptResponse( - header=response_header_pb2.ResponseHeader( - nodeTransactionPrecheckCode=ResponseCode.OK - ), - receipt=transaction_receipt_pb2.TransactionReceipt( - status=ResponseCode.INVALID_SIGNATURE - ), + header=response_header_pb2.ResponseHeader(nodeTransactionPrecheckCode=ResponseCode.OK), + receipt=transaction_receipt_pb2.TransactionReceipt(status=ResponseCode.INVALID_SIGNATURE), ) ) response_sequence = [[ok_response, receipt_response]] with mock_hedera_servers(response_sequence) as client: - tx = ( - AccountCreateTransaction() - .set_initial_balance(1) - .set_key_without_alias(PrivateKey.generate()) - ) + tx = AccountCreateTransaction().set_initial_balance(1).set_key_without_alias(PrivateKey.generate()) receipt = tx.execute(client) diff --git a/tests/unit/transfer_transaction_test.py b/tests/unit/transfer_transaction_test.py index 4e8c265f4..5e9a7cc31 100644 --- a/tests/unit/transfer_transaction_test.py +++ b/tests/unit/transfer_transaction_test.py @@ -2,6 +2,8 @@ Unit tests for the TransferTransaction class """ +from __future__ import annotations + import pytest from hiero_sdk_python.hapi.services.schedulable_transaction_body_pb2 import ( @@ -12,6 +14,7 @@ from hiero_sdk_python.tokens.nft_id import NftId from hiero_sdk_python.transaction.transfer_transaction import TransferTransaction + pytestmark = pytest.mark.unit @@ -26,8 +29,7 @@ def test_constructor_with_parameters(mock_account_ids): token_id_2: {account_id_sender: -25, account_id_recipient: 25}, } - nft_transfers = {token_id_1: [ - (account_id_sender, account_id_recipient, 1, True)]} + nft_transfers = {token_id_1: [(account_id_sender, account_id_recipient, 1, True)]} # Initialize with parameters transfer_tx = TransferTransaction( @@ -36,21 +38,16 @@ def test_constructor_with_parameters(mock_account_ids): # Verify all transfers were added correctly # Check HBAR transfers - hbar_amounts = { - transfer.account_id: transfer.amount for transfer in transfer_tx.hbar_transfers} + hbar_amounts = {transfer.account_id: transfer.amount for transfer in transfer_tx.hbar_transfers} assert hbar_amounts[account_id_sender] == -1000 assert hbar_amounts[account_id_recipient] == 1000 # Check token transfers - token_amounts_1 = { - transfer.account_id: transfer.amount for transfer in transfer_tx.token_transfers[token_id_1] - } + token_amounts_1 = {transfer.account_id: transfer.amount for transfer in transfer_tx.token_transfers[token_id_1]} assert token_amounts_1[account_id_sender] == -50 assert token_amounts_1[account_id_recipient] == 50 - token_amounts_2 = { - transfer.account_id: transfer.amount for transfer in transfer_tx.token_transfers[token_id_2] - } + token_amounts_2 = {transfer.account_id: transfer.amount for transfer in transfer_tx.token_transfers[token_id_2]} assert token_amounts_2[account_id_sender] == -25 assert token_amounts_2[account_id_recipient] == 25 @@ -78,9 +75,7 @@ def test_add_token_transfer(mock_account_ids): transfer_tx.add_token_transfer(token_id_1, account_id_recipient, 100) # Find the transfers for each account - sender_transfer = next( - t for t in transfer_tx.token_transfers[token_id_1] if t.account_id == account_id_sender - ) + sender_transfer = next(t for t in transfer_tx.token_transfers[token_id_1] if t.account_id == account_id_sender) recipient_transfer = next( t for t in transfer_tx.token_transfers[token_id_1] if t.account_id == account_id_recipient ) @@ -98,12 +93,8 @@ def test_add_hbar_transfer(mock_account_ids): transfer_tx.add_hbar_transfer(account_id_recipient, 500) # Find the transfers for each account - sender_transfer = next( - t for t in transfer_tx.hbar_transfers if t.account_id == account_id_sender - ) - recipient_transfer = next( - t for t in transfer_tx.hbar_transfers if t.account_id == account_id_recipient - ) + sender_transfer = next(t for t in transfer_tx.hbar_transfers if t.account_id == account_id_sender) + recipient_transfer = next(t for t in transfer_tx.hbar_transfers if t.account_id == account_id_recipient) assert sender_transfer.amount == -500 assert recipient_transfer.amount == 500 @@ -114,9 +105,7 @@ def test_add_nft_transfer(mock_account_ids): account_id_sender, account_id_recipient, _, token_id_1, _ = mock_account_ids transfer_tx = TransferTransaction() - transfer_tx.add_nft_transfer( - NftId(token_id_1, 0), account_id_sender, account_id_recipient, True - ) + transfer_tx.add_nft_transfer(NftId(token_id_1, 0), account_id_sender, account_id_recipient, True) assert transfer_tx.nft_transfers[token_id_1][0].sender_id == account_id_sender assert transfer_tx.nft_transfers[token_id_1][0].receiver_id == account_id_recipient @@ -137,8 +126,7 @@ def test_add_invalid_transfer(mock_account_ids): transfer_tx.add_token_transfer(12345, mock_account_ids[0], -100) with pytest.raises(TypeError): - transfer_tx.add_nft_transfer( - 12345, mock_account_ids[0], mock_account_ids[1], True) + transfer_tx.add_nft_transfer(12345, mock_account_ids[0], mock_account_ids[1], True) def test_hbar_accumulation(mock_account_ids): @@ -169,8 +157,7 @@ def test_token_accumulation(mock_account_ids): transfer_tx.add_token_transfer(token_id_1, account_id_2, 50) # Verify accumulation - amounts = { - t.account_id: t.amount for t in transfer_tx.token_transfers[token_id_1]} + amounts = {t.account_id: t.amount for t in transfer_tx.token_transfers[token_id_1]} assert amounts[account_id_1] == 300 # 100 + 200 assert amounts[account_id_2] == 50 assert len(transfer_tx.token_transfers[token_id_1]) == 2 @@ -209,8 +196,7 @@ def test_token_negative_amounts(mock_account_ids): transfer_tx.add_token_transfer(token_id_1, account_id_2, -100) # Verify subtraction - amounts = { - t.account_id: t.amount for t in transfer_tx.token_transfers[token_id_1]} + amounts = {t.account_id: t.amount for t in transfer_tx.token_transfers[token_id_1]} assert amounts[account_id_1] == 800 # 1000 - 200 assert amounts[account_id_2] == 400 # 500 - 100 @@ -231,8 +217,7 @@ def test_zero_to_positive_transfers(mock_account_ids): transfer_tx.add_token_transfer(token_id_1, account_id_1, -200) transfer_tx.add_token_transfer(token_id_1, account_id_1, 500) - token_amounts = { - t.account_id: t.amount for t in transfer_tx.token_transfers[token_id_1]} + token_amounts = {t.account_id: t.amount for t in transfer_tx.token_transfers[token_id_1]} assert token_amounts[account_id_1] == 300 @@ -244,16 +229,12 @@ def test_multiple_tokens_same_account(mock_account_ids): # Add different amounts for different tokens to the same account transfer_tx.add_token_transfer(token_id_1, account_id_1, 100) transfer_tx.add_token_transfer(token_id_2, account_id_1, 200) - transfer_tx.add_token_transfer( - token_id_1, account_id_1, 50) # Accumulate token1 - transfer_tx.add_token_transfer( - token_id_2, account_id_1, -50) # Subtract from token2 + transfer_tx.add_token_transfer(token_id_1, account_id_1, 50) # Accumulate token1 + transfer_tx.add_token_transfer(token_id_2, account_id_1, -50) # Subtract from token2 # Verify each token maintains separate balance - token1_amounts = { - t.account_id: t.amount for t in transfer_tx.token_transfers[token_id_1]} - token2_amounts = { - t.account_id: t.amount for t in transfer_tx.token_transfers[token_id_2]} + token1_amounts = {t.account_id: t.amount for t in transfer_tx.token_transfers[token_id_1]} + token2_amounts = {t.account_id: t.amount for t in transfer_tx.token_transfers[token_id_2]} assert token1_amounts[account_id_1] == 150 # 100 + 50 assert token2_amounts[account_id_1] == 150 # 200 - 50 @@ -286,8 +267,7 @@ def test_edge_case_amounts(mock_account_ids): transfer_tx.add_token_transfer(token_id_1, account_id_1, 1) transfer_tx.add_token_transfer(token_id_1, account_id_1, 1) - token1_amounts = { - t.account_id: t.amount for t in transfer_tx.token_transfers[token_id_1]} + token1_amounts = {t.account_id: t.amount for t in transfer_tx.token_transfers[token_id_1]} assert token1_amounts[account_id_1] == 2 @@ -311,12 +291,8 @@ def test_multiple_nft_transfers(mock_account_ids): transfer_tx = TransferTransaction() # Add multiple NFT transfers for the same token - transfer_tx.add_nft_transfer( - NftId(token_id_1, 1), account_id_sender, account_id_recipient, False - ) - transfer_tx.add_nft_transfer( - NftId(token_id_1, 2), account_id_sender, account_id_recipient, True - ) + transfer_tx.add_nft_transfer(NftId(token_id_1, 1), account_id_sender, account_id_recipient, False) + transfer_tx.add_nft_transfer(NftId(token_id_1, 2), account_id_sender, account_id_recipient, True) # Verify all transfers were added correctly assert len(transfer_tx.nft_transfers[token_id_1]) == 2 @@ -342,8 +318,7 @@ def test_frozen_transaction(mock_account_ids, mock_client): transfer_tx.add_token_transfer(token_id_1, account_id_sender, -100) with pytest.raises(Exception, match="Transaction is immutable; it has been frozen."): - transfer_tx.add_nft_transfer( - NftId(token_id_1, 1), account_id_sender, account_id_recipient) + transfer_tx.add_nft_transfer(NftId(token_id_1, 1), account_id_sender, account_id_recipient) def test_build_transaction_body(mock_account_ids): @@ -356,8 +331,7 @@ def test_build_transaction_body(mock_account_ids): transfer_tx.add_hbar_transfer(account_id_recipient, 500) transfer_tx.add_token_transfer(token_id_1, account_id_sender, -100) transfer_tx.add_token_transfer(token_id_1, account_id_recipient, 100) - transfer_tx.add_nft_transfer( - NftId(token_id_1, 1), account_id_sender, account_id_recipient) + transfer_tx.add_nft_transfer(NftId(token_id_1, 1), account_id_sender, account_id_recipient) # Set required fields for building transaction transfer_tx.node_account_id = node_account_id @@ -416,8 +390,7 @@ def test_build_scheduled_body(mock_account_ids): transfer_tx.add_hbar_transfer(account_id_recipient, 500) transfer_tx.add_token_transfer(token_id_1, account_id_sender, -100) transfer_tx.add_token_transfer(token_id_1, account_id_recipient, 100) - transfer_tx.add_nft_transfer( - NftId(token_id_1, 1), account_id_sender, account_id_recipient) + transfer_tx.add_nft_transfer(NftId(token_id_1, 1), account_id_sender, account_id_recipient) # Build the scheduled body result = transfer_tx.build_scheduled_body() @@ -469,8 +442,7 @@ def test_approved_token_transfer_with_decimals(mock_account_ids): transfer_tx = TransferTransaction() # Add approved token transfer with decimals - transfer_tx.add_approved_token_transfer_with_decimals( - token_id_1, account_id_1, 1000, 6) + transfer_tx.add_approved_token_transfer_with_decimals(token_id_1, account_id_1, 1000, 6) # Verify the transfer was added correctly transfer = transfer_tx.token_transfers[token_id_1][0] @@ -500,8 +472,7 @@ def test_approved_token_transfer_accumulation(mock_account_ids): assert transfer_2.expected_decimals is None # Add approved transfer with decimals for account_1 (accumulates) - transfer_tx.add_approved_token_transfer_with_decimals( - token_id_1, account_id_1, 200, 8) + transfer_tx.add_approved_token_transfer_with_decimals(token_id_1, account_id_1, 200, 8) # Verify accumulation transfer_1 = transfer_tx.token_transfers[token_id_1][0] @@ -521,14 +492,11 @@ def test_approved_token_transfer_validation(mock_account_ids): # Test invalid expected_decimals type with pytest.raises(TypeError, match="expected_decimals must be an integer"): - transfer_tx.add_approved_token_transfer_with_decimals( - token_id_1, account_id_1, 1000, "invalid" - ) + transfer_tx.add_approved_token_transfer_with_decimals(token_id_1, account_id_1, 1000, "invalid") # Test zero amount with pytest.raises(ValueError, match="Amount must be a non-zero integer"): - transfer_tx.add_approved_token_transfer_with_decimals( - token_id_1, account_id_1, 0, 6) + transfer_tx.add_approved_token_transfer_with_decimals(token_id_1, account_id_1, 0, 6) def test_add_hbar_transfer_with_hbar_object(mock_account_ids): @@ -539,12 +507,8 @@ def test_add_hbar_transfer_with_hbar_object(mock_account_ids): transfer_tx.add_hbar_transfer(account_id_sender, Hbar(-500)) transfer_tx.add_hbar_transfer(account_id_recipient, Hbar(500)) - sender_transfer = next( - t for t in transfer_tx.hbar_transfers if t.account_id == account_id_sender - ) - recipient_transfer = next( - t for t in transfer_tx.hbar_transfers if t.account_id == account_id_recipient - ) + sender_transfer = next(t for t in transfer_tx.hbar_transfers if t.account_id == account_id_sender) + recipient_transfer = next(t for t in transfer_tx.hbar_transfers if t.account_id == account_id_recipient) assert sender_transfer.amount == -50_000_000_000 assert recipient_transfer.amount == 50_000_000_000 @@ -555,17 +519,11 @@ def test_add_hbar_transfer_with_hbar_tinybars(mock_account_ids): account_id_sender, account_id_recipient, _, _, _ = mock_account_ids transfer_tx = TransferTransaction() - transfer_tx.add_hbar_transfer( - account_id_sender, Hbar(-500, HbarUnit.TINYBAR)) - transfer_tx.add_hbar_transfer( - account_id_recipient, Hbar(500, HbarUnit.TINYBAR)) + transfer_tx.add_hbar_transfer(account_id_sender, Hbar(-500, HbarUnit.TINYBAR)) + transfer_tx.add_hbar_transfer(account_id_recipient, Hbar(500, HbarUnit.TINYBAR)) - sender_transfer = next( - t for t in transfer_tx.hbar_transfers if t.account_id == account_id_sender - ) - recipient_transfer = next( - t for t in transfer_tx.hbar_transfers if t.account_id == account_id_recipient - ) + sender_transfer = next(t for t in transfer_tx.hbar_transfers if t.account_id == account_id_sender) + recipient_transfer = next(t for t in transfer_tx.hbar_transfers if t.account_id == account_id_recipient) assert sender_transfer.amount == -500 assert recipient_transfer.amount == 500 @@ -639,10 +597,8 @@ def test_token_transfer_with_expected_decimals_building(mock_account_ids): account_id_1, account_id_2, node_account_id, token_id_1, _ = mock_account_ids transfer_tx = TransferTransaction() - transfer_tx.add_token_transfer_with_decimals( - token_id_1, account_id_1, -100, 8) - transfer_tx.add_token_transfer_with_decimals( - token_id_1, account_id_2, 100, 8) + transfer_tx.add_token_transfer_with_decimals(token_id_1, account_id_1, -100, 8) + transfer_tx.add_token_transfer_with_decimals(token_id_1, account_id_2, 100, 8) transfer_tx.node_account_id = node_account_id transfer_tx.operator_account_id = account_id_1 @@ -667,12 +623,8 @@ def test_nft_transfer_with_approval_building(mock_account_ids): account_id_sender, account_id_recipient, node_account_id, token_id_1, _ = mock_account_ids transfer_tx = TransferTransaction() - transfer_tx.add_nft_transfer( - NftId(token_id_1, 1), account_id_sender, account_id_recipient, False - ) - transfer_tx.add_nft_transfer( - NftId(token_id_1, 2), account_id_sender, account_id_recipient, True - ) + transfer_tx.add_nft_transfer(NftId(token_id_1, 1), account_id_sender, account_id_recipient, False) + transfer_tx.add_nft_transfer(NftId(token_id_1, 2), account_id_sender, account_id_recipient, True) transfer_tx.node_account_id = node_account_id transfer_tx.operator_account_id = account_id_sender @@ -696,9 +648,7 @@ def test_nft_transfer_reconstruction_from_protobuf(mock_account_ids): account_id_sender, account_id_recipient, node_account_id, token_id_1, _ = mock_account_ids transfer_tx = TransferTransaction() - transfer_tx.add_nft_transfer( - NftId(token_id_1, 5), account_id_sender, account_id_recipient, True - ) + transfer_tx.add_nft_transfer(NftId(token_id_1, 5), account_id_sender, account_id_recipient, True) transfer_tx.node_account_id = node_account_id transfer_tx.operator_account_id = account_id_sender @@ -721,9 +671,7 @@ def test_nft_transfers_unapproved_reconstruction(mock_account_ids): account_id_sender, account_id_recipient, node_account_id, token_id_1, _ = mock_account_ids transfer_tx = TransferTransaction() - transfer_tx.add_nft_transfer( - NftId(token_id_1, 10), account_id_sender, account_id_recipient, False - ) + transfer_tx.add_nft_transfer(NftId(token_id_1, 10), account_id_sender, account_id_recipient, False) transfer_tx.node_account_id = node_account_id transfer_tx.operator_account_id = account_id_sender @@ -742,12 +690,8 @@ def test_token_transfer_with_expected_decimals_reconstruction(mock_account_ids): account_id_sender, account_id_recipient, node_account_id, token_id_1, _ = mock_account_ids transfer_tx = TransferTransaction() - transfer_tx.add_token_transfer_with_decimals( - token_id_1, account_id_sender, -100, 6 - ) - transfer_tx.add_token_transfer_with_decimals( - token_id_1, account_id_recipient, 100, 6 - ) + transfer_tx.add_token_transfer_with_decimals(token_id_1, account_id_sender, -100, 6) + transfer_tx.add_token_transfer_with_decimals(token_id_1, account_id_recipient, 100, 6) transfer_tx.node_account_id = node_account_id transfer_tx.operator_account_id = account_id_sender @@ -772,15 +716,9 @@ def test_combined_transfers_reconstruction(mock_account_ids): transfer_tx.add_hbar_transfer(account_id_sender, -1000) transfer_tx.add_hbar_transfer(account_id_recipient, 1000) - transfer_tx.add_token_transfer_with_decimals( - token_id_1, account_id_sender, -50, 8 - ) - transfer_tx.add_token_transfer_with_decimals( - token_id_1, account_id_recipient, 50, 8 - ) - transfer_tx.add_nft_transfer( - NftId(token_id_1, 1), account_id_sender, account_id_recipient, True - ) + transfer_tx.add_token_transfer_with_decimals(token_id_1, account_id_sender, -50, 8) + transfer_tx.add_token_transfer_with_decimals(token_id_1, account_id_recipient, 50, 8) + transfer_tx.add_nft_transfer(NftId(token_id_1, 1), account_id_sender, account_id_recipient, True) transfer_tx.node_account_id = node_account_id transfer_tx.operator_account_id = account_id_sender @@ -805,12 +743,8 @@ def test_expected_decimals_field_preservation(mock_account_ids): account_id_sender, account_id_recipient, node_account_id, token_id_1, _ = mock_account_ids transfer_tx = TransferTransaction() - transfer_tx.add_token_transfer_with_decimals( - token_id_1, account_id_sender, -200, 8 - ) - transfer_tx.add_token_transfer_with_decimals( - token_id_1, account_id_recipient, 200, 8 - ) + transfer_tx.add_token_transfer_with_decimals(token_id_1, account_id_sender, -200, 8) + transfer_tx.add_token_transfer_with_decimals(token_id_1, account_id_recipient, 200, 8) transfer_tx.node_account_id = node_account_id transfer_tx.operator_account_id = account_id_sender @@ -830,9 +764,7 @@ def test_nft_transfer_fields_preservation(mock_account_ids): account_id_sender, account_id_recipient, node_account_id, token_id_1, _ = mock_account_ids transfer_tx = TransferTransaction() - transfer_tx.add_nft_transfer( - NftId(token_id_1, 42), account_id_sender, account_id_recipient, True - ) + transfer_tx.add_nft_transfer(NftId(token_id_1, 42), account_id_sender, account_id_recipient, True) transfer_tx.node_account_id = node_account_id transfer_tx.operator_account_id = account_id_sender @@ -857,15 +789,9 @@ def test_multiple_nft_transfers_all_fields(mock_account_ids): account_id_sender, account_id_recipient, node_account_id, token_id_1, _ = mock_account_ids transfer_tx = TransferTransaction() - transfer_tx.add_nft_transfer( - NftId(token_id_1, 100), account_id_sender, account_id_recipient, True - ) - transfer_tx.add_nft_transfer( - NftId(token_id_1, 101), account_id_sender, account_id_recipient, False - ) - transfer_tx.add_nft_transfer( - NftId(token_id_1, 102), account_id_sender, account_id_recipient, True - ) + transfer_tx.add_nft_transfer(NftId(token_id_1, 100), account_id_sender, account_id_recipient, True) + transfer_tx.add_nft_transfer(NftId(token_id_1, 101), account_id_sender, account_id_recipient, False) + transfer_tx.add_nft_transfer(NftId(token_id_1, 102), account_id_sender, account_id_recipient, True) transfer_tx.node_account_id = node_account_id transfer_tx.operator_account_id = account_id_sender @@ -877,8 +803,7 @@ def test_multiple_nft_transfers_all_fields(mock_account_ids): nft_transfers = reconstructed.nft_transfers[token_id_1] assert len(nft_transfers) == 3 - serial_to_approval = { - nft.serial_number: nft.is_approved for nft in nft_transfers} + serial_to_approval = {nft.serial_number: nft.is_approved for nft in nft_transfers} assert serial_to_approval[100] is True assert serial_to_approval[101] is False assert serial_to_approval[102] is True