|
| 1 | +from __future__ import annotations |
| 2 | + |
| 3 | +from collections.abc import Callable |
| 4 | +from typing import Any, cast |
| 5 | + |
| 6 | +from hiero_sdk_python.account.account_id import AccountId |
| 7 | +from hiero_sdk_python.response_code import ResponseCode |
| 8 | +from hiero_sdk_python.schedule.schedule_create_transaction import ScheduleCreateTransaction |
| 9 | +from hiero_sdk_python.timestamp import Timestamp |
| 10 | +from hiero_sdk_python.transaction.transaction import Transaction |
| 11 | +from hiero_sdk_python.transaction.transaction_receipt import TransactionReceipt |
| 12 | +from tck.errors import JsonRpcError |
| 13 | +from tck.handlers.account import _build_create_account_transaction |
| 14 | +from tck.handlers.allowance import _build_approve_allowance_transaction |
| 15 | +from tck.handlers.registry import rpc_method |
| 16 | +from tck.handlers.token import _build_burn_token_transaction, _build_mint_token_transaction |
| 17 | +from tck.handlers.topic import _build_create_topic_transaction, _build_topic_message_submit_transaction |
| 18 | +from tck.handlers.transfer import _build_transfer_transaction |
| 19 | +from tck.param.account import CreateAccountParams |
| 20 | +from tck.param.allowance import ApproveAllowanceParams |
| 21 | +from tck.param.base import BaseTransactionParams |
| 22 | +from tck.param.common import CommonTransactionParams |
| 23 | +from tck.param.schedule import CreateScheduleParams, ScheduledTransactionParams |
| 24 | +from tck.param.token import BurnTokenParams, MintTokenParams |
| 25 | +from tck.param.topic import CreateTopicParams, TopicMessageSubmitParams |
| 26 | +from tck.param.transfer import TransferCryptoParams |
| 27 | +from tck.response.schedule import CreateScheduleResponse |
| 28 | +from tck.util.client_utils import get_client |
| 29 | +from tck.util.constants import DEFAULT_GRPC_TIMEOUT |
| 30 | +from tck.util.key_utils import get_key_from_string |
| 31 | +from tck.util.param_utils import to_int |
| 32 | + |
| 33 | + |
| 34 | +# Maps a scheduled transaction method name to its params class and builder. |
| 35 | +# "submitMessage" is the name used by the TCK inside scheduledTransaction, while |
| 36 | +# "submitTopicMessage" is the top-level JSON-RPC method name; both are accepted. |
| 37 | +_SCHEDULABLE: dict[str, tuple[type[BaseTransactionParams], Callable[[Any], Transaction]]] = { |
| 38 | + "createAccount": (CreateAccountParams, _build_create_account_transaction), |
| 39 | + "transferCrypto": (TransferCryptoParams, _build_transfer_transaction), |
| 40 | + "submitMessage": (TopicMessageSubmitParams, _build_topic_message_submit_transaction), |
| 41 | + "submitTopicMessage": (TopicMessageSubmitParams, _build_topic_message_submit_transaction), |
| 42 | + "burnToken": (BurnTokenParams, _build_burn_token_transaction), |
| 43 | + "mintToken": (MintTokenParams, _build_mint_token_transaction), |
| 44 | + "approveAllowance": (ApproveAllowanceParams, _build_approve_allowance_transaction), |
| 45 | + "createTopic": (CreateTopicParams, _build_create_topic_transaction), |
| 46 | +} |
| 47 | + |
| 48 | + |
| 49 | +def _apply_schedulable_common_params(transaction: Transaction, common: CommonTransactionParams | None) -> None: |
| 50 | + """Apply the common params that survive into a SchedulableTransactionBody. |
| 51 | +
|
| 52 | + A SchedulableTransactionBody carries only transactionFee and memo, so the rest of |
| 53 | + apply_common_params() would be silently discarded: transactionId and |
| 54 | + validTransactionDuration are not part of the body, and signers would freeze and sign a |
| 55 | + transaction that is never submitted. Schedule signers belong on the outer |
| 56 | + ScheduleCreateTransaction, which create_schedule() already handles. |
| 57 | + """ |
| 58 | + if common is None: |
| 59 | + return |
| 60 | + |
| 61 | + if common.maxTransactionFee is not None: |
| 62 | + transaction.transaction_fee = int(common.maxTransactionFee) |
| 63 | + |
| 64 | + if common.memo is not None: |
| 65 | + transaction.set_transaction_memo(common.memo) |
| 66 | + |
| 67 | + |
| 68 | +def _build_scheduled_transaction(params: ScheduledTransactionParams, session_id: str) -> Transaction: |
| 69 | + """Build the inner transaction that a schedule wraps.""" |
| 70 | + schedulable = _SCHEDULABLE.get(params.method) |
| 71 | + if schedulable is None: |
| 72 | + raise JsonRpcError.invalid_params_error(f"Unsupported scheduled transaction method: {params.method}") |
| 73 | + |
| 74 | + params_class, build_transaction = schedulable |
| 75 | + |
| 76 | + # The inner params object carries no sessionId of its own, so inherit the outer one. |
| 77 | + inner_json = dict(params.params) |
| 78 | + inner_json["sessionId"] = session_id |
| 79 | + |
| 80 | + try: |
| 81 | + inner_params = cast(BaseTransactionParams, params_class.parse_json_params(inner_json)) |
| 82 | + except (TypeError, ValueError) as e: |
| 83 | + raise JsonRpcError.invalid_params_error(str(e)) from e |
| 84 | + |
| 85 | + transaction = build_transaction(inner_params) |
| 86 | + _apply_schedulable_common_params(transaction, inner_params.commonTransactionParams) |
| 87 | + |
| 88 | + return transaction |
| 89 | + |
| 90 | + |
| 91 | +def _build_create_schedule_transaction(params: CreateScheduleParams) -> ScheduleCreateTransaction: |
| 92 | + """Build a ScheduleCreateTransaction from TCK params.""" |
| 93 | + transaction = ScheduleCreateTransaction().set_grpc_deadline(DEFAULT_GRPC_TIMEOUT) |
| 94 | + |
| 95 | + if params.scheduledTransaction is not None: |
| 96 | + transaction.set_scheduled_transaction( |
| 97 | + _build_scheduled_transaction(params.scheduledTransaction, params.sessionId) |
| 98 | + ) |
| 99 | + |
| 100 | + if params.memo is not None: |
| 101 | + transaction.set_schedule_memo(params.memo) |
| 102 | + |
| 103 | + if params.adminKey is not None: |
| 104 | + transaction.set_admin_key(get_key_from_string(params.adminKey)) |
| 105 | + |
| 106 | + if params.payerAccountId is not None: |
| 107 | + # Passed through unchecked so an empty string surfaces as an SDK error. |
| 108 | + transaction.set_payer_account_id(AccountId.from_string(params.payerAccountId)) |
| 109 | + |
| 110 | + if params.expirationTime is not None: |
| 111 | + transaction.set_expiration_time(Timestamp(seconds=to_int(params.expirationTime), nanos=0)) |
| 112 | + |
| 113 | + if params.waitForExpiry is not None: |
| 114 | + transaction.set_wait_for_expiry(params.waitForExpiry) |
| 115 | + |
| 116 | + return transaction |
| 117 | + |
| 118 | + |
| 119 | +@rpc_method("createSchedule") |
| 120 | +def create_schedule(params: CreateScheduleParams) -> CreateScheduleResponse: |
| 121 | + """Create a schedule.""" |
| 122 | + client = get_client(params.sessionId) |
| 123 | + |
| 124 | + transaction = _build_create_schedule_transaction(params) |
| 125 | + |
| 126 | + if params.commonTransactionParams is not None: |
| 127 | + params.commonTransactionParams.apply_common_params(transaction, client) |
| 128 | + |
| 129 | + response = transaction.execute(client, wait_for_receipt=False) |
| 130 | + receipt: TransactionReceipt = response.get_receipt(client, validate_status=True) |
| 131 | + |
| 132 | + schedule_id = "" |
| 133 | + scheduled_transaction_id = None |
| 134 | + if receipt.status == ResponseCode.SUCCESS: |
| 135 | + if receipt.schedule_id is not None: |
| 136 | + schedule_id = str(receipt.schedule_id) |
| 137 | + if receipt.scheduled_transaction_id is not None: |
| 138 | + scheduled_transaction_id = str(receipt.scheduled_transaction_id) |
| 139 | + |
| 140 | + return CreateScheduleResponse(schedule_id, scheduled_transaction_id, ResponseCode(receipt.status).name) |
0 commit comments