algokit-utils 5.0.0a3__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- algokit_abi/__init__.py +9 -0
- algokit_abi/_arc32_to_arc56.py +242 -0
- algokit_abi/_arc56_serde.py +161 -0
- algokit_abi/abi.py +667 -0
- algokit_abi/arc32.py +210 -0
- algokit_abi/arc56.py +821 -0
- algokit_abi/py.typed +0 -0
- algokit_algo25/__init__.py +38 -0
- algokit_algo25/_encoding.py +46 -0
- algokit_algo25/_wordlist.py +2065 -0
- algokit_algo25/exceptions.py +29 -0
- algokit_algo25/mnemonic.py +128 -0
- algokit_algo25/py.typed +0 -0
- algokit_algod_client/__init__.py +10 -0
- algokit_algod_client/client.py +1585 -0
- algokit_algod_client/config.py +36 -0
- algokit_algod_client/exceptions.py +59 -0
- algokit_algod_client/models/__init__.py +229 -0
- algokit_algod_client/models/_account.py +150 -0
- algokit_algod_client/models/_account_application_response.py +25 -0
- algokit_algod_client/models/_account_asset_response.py +25 -0
- algokit_algod_client/models/_account_participation.py +53 -0
- algokit_algod_client/models/_account_state_delta.py +30 -0
- algokit_algod_client/models/_allocations_for_genesis_file.py +23 -0
- algokit_algod_client/models/_allocations_for_genesis_file_state_model.py +42 -0
- algokit_algod_client/models/_application.py +23 -0
- algokit_algod_client/models/_application_initial_states.py +37 -0
- algokit_algod_client/models/_application_kvstorage.py +29 -0
- algokit_algod_client/models/_application_local_state.py +33 -0
- algokit_algod_client/models/_application_params.py +63 -0
- algokit_algod_client/models/_application_state_operation.py +41 -0
- algokit_algod_client/models/_application_state_schema.py +22 -0
- algokit_algod_client/models/_asset.py +23 -0
- algokit_algod_client/models/_asset_holding.py +29 -0
- algokit_algod_client/models/_asset_params.py +102 -0
- algokit_algod_client/models/_avm_key_value.py +28 -0
- algokit_algod_client/models/_avm_value.py +32 -0
- algokit_algod_client/models/_block.py +363 -0
- algokit_algod_client/models/_block_hash_response.py +14 -0
- algokit_algod_client/models/_block_txids_response.py +14 -0
- algokit_algod_client/models/_box.py +36 -0
- algokit_algod_client/models/_box_descriptor.py +24 -0
- algokit_algod_client/models/_boxes_response.py +21 -0
- algokit_algod_client/models/_build_version_contains_the_current_algod_build_version_information.py +34 -0
- algokit_algod_client/models/_compile_response.py +24 -0
- algokit_algod_client/models/_disassemble_response.py +14 -0
- algokit_algod_client/models/_error_response.py +22 -0
- algokit_algod_client/models/_eval_delta.py +32 -0
- algokit_algod_client/models/_eval_delta_key_value.py +28 -0
- algokit_algod_client/models/_genesis_file_in_json.py +53 -0
- algokit_algod_client/models/_get_block_time_stamp_offset_response.py +14 -0
- algokit_algod_client/models/_get_sync_round_response.py +14 -0
- algokit_algod_client/models/_ledger_state_delta.py +389 -0
- algokit_algod_client/models/_light_block_header_proof.py +32 -0
- algokit_algod_client/models/_node_status_response.py +118 -0
- algokit_algod_client/models/_pending_transaction_response.py +91 -0
- algokit_algod_client/models/_pending_transactions_response.py +29 -0
- algokit_algod_client/models/_post_transactions_response.py +14 -0
- algokit_algod_client/models/_scratch_change.py +23 -0
- algokit_algod_client/models/_serde_helpers.py +241 -0
- algokit_algod_client/models/_simulate_initial_states.py +25 -0
- algokit_algod_client/models/_simulate_request.py +54 -0
- algokit_algod_client/models/_simulate_request_transaction_group.py +25 -0
- algokit_algod_client/models/_simulate_response.py +44 -0
- algokit_algod_client/models/_simulate_trace_config.py +30 -0
- algokit_algod_client/models/_simulate_transaction_group_result.py +46 -0
- algokit_algod_client/models/_simulate_transaction_result.py +41 -0
- algokit_algod_client/models/_simulate_unnamed_resources_accessed.py +64 -0
- algokit_algod_client/models/_simulation_eval_overrides.py +40 -0
- algokit_algod_client/models/_simulation_opcode_trace_unit.py +55 -0
- algokit_algod_client/models/_simulation_transaction_exec_trace.py +82 -0
- algokit_algod_client/models/_source_map.py +30 -0
- algokit_algod_client/models/_state_delta.py +6 -0
- algokit_algod_client/models/_state_proof.py +28 -0
- algokit_algod_client/models/_state_proof_message.py +44 -0
- algokit_algod_client/models/_supply_response.py +26 -0
- algokit_algod_client/models/_teal_key_value.py +28 -0
- algokit_algod_client/models/_teal_key_value_store.py +6 -0
- algokit_algod_client/models/_teal_value.py +32 -0
- algokit_algod_client/models/_transaction_group_ledger_state_deltas_for_round_response.py +21 -0
- algokit_algod_client/models/_transaction_parameters_response.py +45 -0
- algokit_algod_client/models/_transaction_proof.py +44 -0
- algokit_algod_client/models/_version_contains_the_current_algod_version.py +38 -0
- algokit_algod_client/models/suggested_params.py +42 -0
- algokit_algod_client/py.typed +1 -0
- algokit_algod_client/types.py +7 -0
- algokit_algosdk/__init__.py +38 -0
- algokit_algosdk/account.py +32 -0
- algokit_algosdk/app_access.py +228 -0
- algokit_algosdk/box_reference.py +100 -0
- algokit_algosdk/constants.py +147 -0
- algokit_algosdk/encoding.py +89 -0
- algokit_algosdk/error.py +180 -0
- algokit_algosdk/logic.py +61 -0
- algokit_algosdk/logicsig.py +218 -0
- algokit_algosdk/mnemonic.py +216 -0
- algokit_algosdk/multisig.py +161 -0
- algokit_algosdk/py.typed +0 -0
- algokit_algosdk/transaction.py +596 -0
- algokit_algosdk/wordlist.py +2054 -0
- algokit_common/__init__.py +50 -0
- algokit_common/address.py +34 -0
- algokit_common/constants.py +47 -0
- algokit_common/hashing.py +25 -0
- algokit_common/py.typed +0 -0
- algokit_common/serde/__init__.py +40 -0
- algokit_common/serde/_core.py +610 -0
- algokit_common/serde/_primitives.py +135 -0
- algokit_common/source_map.py +158 -0
- algokit_indexer_client/__init__.py +10 -0
- algokit_indexer_client/client.py +1456 -0
- algokit_indexer_client/config.py +36 -0
- algokit_indexer_client/exceptions.py +59 -0
- algokit_indexer_client/models/__init__.py +148 -0
- algokit_indexer_client/models/_account.py +161 -0
- algokit_indexer_client/models/_account_participation.py +53 -0
- algokit_indexer_client/models/_account_response.py +19 -0
- algokit_indexer_client/models/_account_state_delta.py +29 -0
- algokit_indexer_client/models/_accounts_response.py +29 -0
- algokit_indexer_client/models/_application.py +35 -0
- algokit_indexer_client/models/_application_local_state.py +45 -0
- algokit_indexer_client/models/_application_local_states_response.py +29 -0
- algokit_indexer_client/models/_application_log_data.py +28 -0
- algokit_indexer_client/models/_application_logs_response.py +33 -0
- algokit_indexer_client/models/_application_params.py +62 -0
- algokit_indexer_client/models/_application_response.py +20 -0
- algokit_indexer_client/models/_application_state_schema.py +22 -0
- algokit_indexer_client/models/_applications_response.py +29 -0
- algokit_indexer_client/models/_asset.py +35 -0
- algokit_indexer_client/models/_asset_balances_response.py +29 -0
- algokit_indexer_client/models/_asset_holding.py +41 -0
- algokit_indexer_client/models/_asset_holdings_response.py +29 -0
- algokit_indexer_client/models/_asset_params.py +102 -0
- algokit_indexer_client/models/_asset_response.py +19 -0
- algokit_indexer_client/models/_assets_response.py +29 -0
- algokit_indexer_client/models/_block.py +150 -0
- algokit_indexer_client/models/_block_headers_response.py +29 -0
- algokit_indexer_client/models/_block_rewards.py +38 -0
- algokit_indexer_client/models/_block_upgrade_state.py +34 -0
- algokit_indexer_client/models/_block_upgrade_vote.py +26 -0
- algokit_indexer_client/models/_box.py +36 -0
- algokit_indexer_client/models/_box_descriptor.py +24 -0
- algokit_indexer_client/models/_box_reference.py +28 -0
- algokit_indexer_client/models/_boxes_response.py +29 -0
- algokit_indexer_client/models/_error_response.py +18 -0
- algokit_indexer_client/models/_eval_delta.py +32 -0
- algokit_indexer_client/models/_eval_delta_key_value.py +28 -0
- algokit_indexer_client/models/_hash_factory.py +14 -0
- algokit_indexer_client/models/_hb_proof_fields.py +57 -0
- algokit_indexer_client/models/_health_check.py +42 -0
- algokit_indexer_client/models/_holding_ref.py +23 -0
- algokit_indexer_client/models/_indexer_state_proof_message.py +40 -0
- algokit_indexer_client/models/_locals_ref.py +23 -0
- algokit_indexer_client/models/_merkle_array_proof.py +29 -0
- algokit_indexer_client/models/_mini_asset_holding.py +38 -0
- algokit_indexer_client/models/_on_completion.py +25 -0
- algokit_indexer_client/models/_participation_updates.py +22 -0
- algokit_indexer_client/models/_resource_ref.py +42 -0
- algokit_indexer_client/models/_serde_helpers.py +241 -0
- algokit_indexer_client/models/_state_delta.py +6 -0
- algokit_indexer_client/models/_state_proof_fields.py +57 -0
- algokit_indexer_client/models/_state_proof_participant.py +20 -0
- algokit_indexer_client/models/_state_proof_reveal.py +25 -0
- algokit_indexer_client/models/_state_proof_sig_slot.py +20 -0
- algokit_indexer_client/models/_state_proof_signature.py +37 -0
- algokit_indexer_client/models/_state_proof_tracking.py +32 -0
- algokit_indexer_client/models/_state_proof_verifier.py +24 -0
- algokit_indexer_client/models/_state_schema.py +25 -0
- algokit_indexer_client/models/_teal_key_value.py +28 -0
- algokit_indexer_client/models/_teal_key_value_store.py +6 -0
- algokit_indexer_client/models/_teal_value.py +32 -0
- algokit_indexer_client/models/_transaction.py +213 -0
- algokit_indexer_client/models/_transaction_application.py +105 -0
- algokit_indexer_client/models/_transaction_asset_config.py +31 -0
- algokit_indexer_client/models/_transaction_asset_freeze.py +29 -0
- algokit_indexer_client/models/_transaction_asset_transfer.py +41 -0
- algokit_indexer_client/models/_transaction_heartbeat.py +52 -0
- algokit_indexer_client/models/_transaction_keyreg.py +59 -0
- algokit_indexer_client/models/_transaction_payment.py +33 -0
- algokit_indexer_client/models/_transaction_response.py +19 -0
- algokit_indexer_client/models/_transaction_signature.py +35 -0
- algokit_indexer_client/models/_transaction_signature_logicsig.py +59 -0
- algokit_indexer_client/models/_transaction_signature_multisig.py +36 -0
- algokit_indexer_client/models/_transaction_signature_multisig_subsignature.py +28 -0
- algokit_indexer_client/models/_transaction_state_proof.py +32 -0
- algokit_indexer_client/models/_transactions_response.py +29 -0
- algokit_indexer_client/py.typed +1 -0
- algokit_indexer_client/types.py +7 -0
- algokit_kmd_client/__init__.py +10 -0
- algokit_kmd_client/client.py +1240 -0
- algokit_kmd_client/config.py +36 -0
- algokit_kmd_client/exceptions.py +59 -0
- algokit_kmd_client/models/__init__.py +112 -0
- algokit_kmd_client/models/_classical_signatures.py +4 -0
- algokit_kmd_client/models/_create_wallet_request.py +30 -0
- algokit_kmd_client/models/_create_wallet_response.py +19 -0
- algokit_kmd_client/models/_delete_key_request.py +27 -0
- algokit_kmd_client/models/_delete_multisig_request.py +27 -0
- algokit_kmd_client/models/_digest_represents_a32_byte_value_holding_the256_bit_hash_digest.py +4 -0
- algokit_kmd_client/models/_ed25519_public_key.py +4 -0
- algokit_kmd_client/models/_export_key_request.py +27 -0
- algokit_kmd_client/models/_export_key_response.py +24 -0
- algokit_kmd_client/models/_export_master_key_request.py +22 -0
- algokit_kmd_client/models/_export_master_key_response.py +18 -0
- algokit_kmd_client/models/_export_multisig_request.py +23 -0
- algokit_kmd_client/models/_export_multisig_response.py +26 -0
- algokit_kmd_client/models/_generate_key_request.py +18 -0
- algokit_kmd_client/models/_generate_key_response.py +19 -0
- algokit_kmd_client/models/_import_key_request.py +28 -0
- algokit_kmd_client/models/_import_key_response.py +19 -0
- algokit_kmd_client/models/_import_multisig_request.py +30 -0
- algokit_kmd_client/models/_import_multisig_response.py +19 -0
- algokit_kmd_client/models/_init_wallet_handle_token_request.py +22 -0
- algokit_kmd_client/models/_init_wallet_handle_token_response.py +18 -0
- algokit_kmd_client/models/_list_keys_request.py +18 -0
- algokit_kmd_client/models/_list_keys_response.py +18 -0
- algokit_kmd_client/models/_list_multisig_request.py +18 -0
- algokit_kmd_client/models/_list_multisig_response.py +18 -0
- algokit_kmd_client/models/_list_wallets_request.py +11 -0
- algokit_kmd_client/models/_list_wallets_response.py +25 -0
- algokit_kmd_client/models/_master_derivation_key.py +4 -0
- algokit_kmd_client/models/_multisig_sig.py +33 -0
- algokit_kmd_client/models/_multisig_subsig.py +23 -0
- algokit_kmd_client/models/_public_key.py +4 -0
- algokit_kmd_client/models/_release_wallet_handle_token_request.py +18 -0
- algokit_kmd_client/models/_rename_wallet_request.py +26 -0
- algokit_kmd_client/models/_rename_wallet_response.py +19 -0
- algokit_kmd_client/models/_renew_wallet_handle_token_request.py +18 -0
- algokit_kmd_client/models/_renew_wallet_handle_token_response.py +19 -0
- algokit_kmd_client/models/_serde_helpers.py +241 -0
- algokit_kmd_client/models/_sign_multisig_response.py +24 -0
- algokit_kmd_client/models/_sign_multisig_txn_request.py +45 -0
- algokit_kmd_client/models/_sign_program_multisig_request.py +50 -0
- algokit_kmd_client/models/_sign_program_multisig_response.py +24 -0
- algokit_kmd_client/models/_sign_program_request.py +37 -0
- algokit_kmd_client/models/_sign_program_response.py +24 -0
- algokit_kmd_client/models/_sign_transaction_response.py +24 -0
- algokit_kmd_client/models/_sign_txn_request.py +36 -0
- algokit_kmd_client/models/_signature.py +4 -0
- algokit_kmd_client/models/_tx_type.py +4 -0
- algokit_kmd_client/models/_versions_request.py +11 -0
- algokit_kmd_client/models/_versions_response.py +19 -0
- algokit_kmd_client/models/_wallet.py +38 -0
- algokit_kmd_client/models/_wallet_handle.py +24 -0
- algokit_kmd_client/models/_wallet_info_request.py +18 -0
- algokit_kmd_client/models/_wallet_info_response.py +19 -0
- algokit_kmd_client/py.typed +1 -0
- algokit_kmd_client/types.py +7 -0
- algokit_transact/__init__.py +190 -0
- algokit_transact/codec/__init__.py +0 -0
- algokit_transact/codec/msgpack.py +11 -0
- algokit_transact/codec/serde.py +7 -0
- algokit_transact/codec/signed.py +57 -0
- algokit_transact/codec/transaction.py +65 -0
- algokit_transact/exceptions.py +17 -0
- algokit_transact/logicsig.py +220 -0
- algokit_transact/models/__init__.py +0 -0
- algokit_transact/models/app_call.py +447 -0
- algokit_transact/models/asset_config.py +19 -0
- algokit_transact/models/asset_freeze.py +11 -0
- algokit_transact/models/asset_transfer.py +13 -0
- algokit_transact/models/common.py +17 -0
- algokit_transact/models/heartbeat.py +21 -0
- algokit_transact/models/key_registration.py +14 -0
- algokit_transact/models/payment.py +14 -0
- algokit_transact/models/signed_transaction.py +21 -0
- algokit_transact/models/state_proof.py +150 -0
- algokit_transact/models/transaction.py +88 -0
- algokit_transact/multisig.py +93 -0
- algokit_transact/ops/__init__.py +0 -0
- algokit_transact/ops/fees.py +47 -0
- algokit_transact/ops/group.py +28 -0
- algokit_transact/ops/ids.py +14 -0
- algokit_transact/ops/validate.py +503 -0
- algokit_transact/py.typed +0 -0
- algokit_transact/signer.py +195 -0
- algokit_transact/signing/__init__.py +0 -0
- algokit_transact/signing/logic_signature.py +19 -0
- algokit_transact/signing/multisig.py +84 -0
- algokit_transact/signing/types.py +39 -0
- algokit_transact/signing/validation.py +63 -0
- algokit_utils/__init__.py +23 -0
- algokit_utils/_debugging.py +304 -0
- algokit_utils/accounts/__init__.py +2 -0
- algokit_utils/accounts/account_manager.py +1051 -0
- algokit_utils/accounts/kmd_account_manager.py +206 -0
- algokit_utils/algo25.py +46 -0
- algokit_utils/algorand.py +383 -0
- algokit_utils/applications/__init__.py +7 -0
- algokit_utils/applications/abi.py +280 -0
- algokit_utils/applications/app_client.py +2193 -0
- algokit_utils/applications/app_deployer.py +788 -0
- algokit_utils/applications/app_factory.py +1140 -0
- algokit_utils/applications/app_manager.py +575 -0
- algokit_utils/applications/app_spec/__init__.py +6 -0
- algokit_utils/applications/enums.py +40 -0
- algokit_utils/assets/__init__.py +1 -0
- algokit_utils/assets/asset_manager.py +344 -0
- algokit_utils/clients/__init__.py +41 -0
- algokit_utils/clients/client_manager.py +756 -0
- algokit_utils/clients/dispenser_api_client.py +212 -0
- algokit_utils/common.py +40 -0
- algokit_utils/config.py +159 -0
- algokit_utils/errors/__init__.py +1 -0
- algokit_utils/errors/logic_error.py +160 -0
- algokit_utils/models/__init__.py +7 -0
- algokit_utils/models/account.py +12 -0
- algokit_utils/models/amount.py +198 -0
- algokit_utils/models/application.py +90 -0
- algokit_utils/models/network.py +29 -0
- algokit_utils/models/simulate.py +7 -0
- algokit_utils/models/state.py +53 -0
- algokit_utils/models/transaction.py +49 -0
- algokit_utils/protocols/__init__.py +3 -0
- algokit_utils/protocols/account.py +11 -0
- algokit_utils/protocols/signer.py +17 -0
- algokit_utils/protocols/typed_clients.py +110 -0
- algokit_utils/py.typed +0 -0
- algokit_utils/transact.py +195 -0
- algokit_utils/transactions/__init__.py +3 -0
- algokit_utils/transactions/builders/__init__.py +67 -0
- algokit_utils/transactions/builders/app.py +248 -0
- algokit_utils/transactions/builders/asset.py +256 -0
- algokit_utils/transactions/builders/common.py +263 -0
- algokit_utils/transactions/builders/keyreg.py +103 -0
- algokit_utils/transactions/builders/method_call.py +380 -0
- algokit_utils/transactions/builders/payment.py +43 -0
- algokit_utils/transactions/composer_resources.py +409 -0
- algokit_utils/transactions/fee_coverage.py +79 -0
- algokit_utils/transactions/helpers.py +9 -0
- algokit_utils/transactions/transaction_composer.py +1574 -0
- algokit_utils/transactions/transaction_creator.py +699 -0
- algokit_utils/transactions/transaction_sender.py +1240 -0
- algokit_utils/transactions/types.py +262 -0
- algokit_utils-5.0.0a3.dist-info/METADATA +105 -0
- algokit_utils-5.0.0a3.dist-info/RECORD +337 -0
- algokit_utils-5.0.0a3.dist-info/WHEEL +4 -0
|
@@ -0,0 +1,503 @@
|
|
|
1
|
+
from collections.abc import Mapping
|
|
2
|
+
from dataclasses import dataclass
|
|
3
|
+
from enum import Enum
|
|
4
|
+
|
|
5
|
+
from algokit_common.constants import (
|
|
6
|
+
MAX_ACCOUNT_REFERENCES,
|
|
7
|
+
MAX_APP_ARGS,
|
|
8
|
+
MAX_APP_REFERENCES,
|
|
9
|
+
MAX_ARGS_SIZE,
|
|
10
|
+
MAX_ASSET_DECIMALS,
|
|
11
|
+
MAX_ASSET_NAME_LENGTH,
|
|
12
|
+
MAX_ASSET_REFERENCES,
|
|
13
|
+
MAX_ASSET_UNIT_NAME_LENGTH,
|
|
14
|
+
MAX_ASSET_URL_LENGTH,
|
|
15
|
+
MAX_BOX_REFERENCES,
|
|
16
|
+
MAX_EXTRA_PROGRAM_PAGES,
|
|
17
|
+
MAX_GLOBAL_STATE_KEYS,
|
|
18
|
+
MAX_LOCAL_STATE_KEYS,
|
|
19
|
+
MAX_OVERALL_REFERENCES,
|
|
20
|
+
PROGRAM_PAGE_SIZE,
|
|
21
|
+
)
|
|
22
|
+
from algokit_transact.exceptions import TransactionValidationError
|
|
23
|
+
from algokit_transact.models.app_call import AppCallTransactionFields
|
|
24
|
+
from algokit_transact.models.asset_config import AssetConfigTransactionFields
|
|
25
|
+
from algokit_transact.models.asset_freeze import AssetFreezeTransactionFields
|
|
26
|
+
from algokit_transact.models.asset_transfer import AssetTransferTransactionFields
|
|
27
|
+
from algokit_transact.models.common import OnApplicationComplete
|
|
28
|
+
from algokit_transact.models.key_registration import KeyRegistrationTransactionFields
|
|
29
|
+
from algokit_transact.models.transaction import Transaction
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class ValidationIssueCode(Enum):
|
|
33
|
+
REQUIRED_FIELD = "required_field"
|
|
34
|
+
FIELD_TOO_LONG = "field_too_long"
|
|
35
|
+
IMMUTABLE_FIELD = "immutable_field"
|
|
36
|
+
ZERO_VALUE_FIELD = "zero_value_field"
|
|
37
|
+
ARBITRARY_CONSTRAINT = "arbitrary_constraint"
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
@dataclass(slots=True, frozen=True)
|
|
41
|
+
class ValidationIssue:
|
|
42
|
+
code: ValidationIssueCode
|
|
43
|
+
message: str
|
|
44
|
+
field: str | None = None
|
|
45
|
+
context: Mapping[str, object] | None = None
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def _issue(
|
|
49
|
+
code: ValidationIssueCode,
|
|
50
|
+
message: str,
|
|
51
|
+
*,
|
|
52
|
+
field: str | None = None,
|
|
53
|
+
context: Mapping[str, object] | None = None,
|
|
54
|
+
) -> ValidationIssue:
|
|
55
|
+
return ValidationIssue(code=code, message=message, field=field, context=context)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def validate_transaction(transaction: Transaction) -> None:
|
|
59
|
+
if not transaction.sender:
|
|
60
|
+
raise TransactionValidationError("Transaction sender is required")
|
|
61
|
+
|
|
62
|
+
type_fields = [
|
|
63
|
+
transaction.payment,
|
|
64
|
+
transaction.asset_transfer,
|
|
65
|
+
transaction.asset_config,
|
|
66
|
+
transaction.application_call,
|
|
67
|
+
transaction.key_registration,
|
|
68
|
+
transaction.asset_freeze,
|
|
69
|
+
transaction.heartbeat,
|
|
70
|
+
transaction.state_proof,
|
|
71
|
+
]
|
|
72
|
+
match sum(1 for f in type_fields if f is not None):
|
|
73
|
+
case 0:
|
|
74
|
+
raise TransactionValidationError("No transaction type specific field is set")
|
|
75
|
+
case n if n > 1:
|
|
76
|
+
raise TransactionValidationError("Multiple transaction type specific fields set")
|
|
77
|
+
|
|
78
|
+
issues: list[ValidationIssue] = []
|
|
79
|
+
type_label: str | None = None
|
|
80
|
+
|
|
81
|
+
match transaction:
|
|
82
|
+
case Transaction(application_call=app_call) if app_call is not None:
|
|
83
|
+
issues.extend(validate_app_call_fields(app_call))
|
|
84
|
+
type_label = "App call"
|
|
85
|
+
case Transaction(asset_config=asset_config) if asset_config is not None:
|
|
86
|
+
issues.extend(validate_asset_config_fields(asset_config))
|
|
87
|
+
type_label = "Asset config"
|
|
88
|
+
case Transaction(asset_transfer=asset_transfer) if asset_transfer is not None:
|
|
89
|
+
issues.extend(validate_asset_transfer_fields(asset_transfer))
|
|
90
|
+
type_label = "Asset transfer"
|
|
91
|
+
case Transaction(asset_freeze=asset_freeze) if asset_freeze is not None:
|
|
92
|
+
issues.extend(validate_asset_freeze_fields(asset_freeze))
|
|
93
|
+
type_label = "Asset freeze"
|
|
94
|
+
case Transaction(key_registration=key_registration) if key_registration is not None:
|
|
95
|
+
issues.extend(validate_key_registration_fields(key_registration))
|
|
96
|
+
type_label = "Key registration"
|
|
97
|
+
|
|
98
|
+
if issues and type_label:
|
|
99
|
+
messages = "\n".join(issue.message for issue in issues)
|
|
100
|
+
raise TransactionValidationError(f"{type_label} validation failed: {messages}", issues=issues)
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def validate_app_call_fields(app_call: AppCallTransactionFields) -> list[ValidationIssue]:
|
|
104
|
+
issues: list[ValidationIssue] = []
|
|
105
|
+
|
|
106
|
+
if app_call.app_id == 0:
|
|
107
|
+
issues.extend(_validate_app_creation(app_call))
|
|
108
|
+
else:
|
|
109
|
+
issues.extend(_validate_app_operation(app_call))
|
|
110
|
+
|
|
111
|
+
issues.extend(_validate_app_common_fields(app_call))
|
|
112
|
+
return issues
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def _validate_app_creation(app_call: AppCallTransactionFields) -> list[ValidationIssue]:
|
|
116
|
+
issues: list[ValidationIssue] = []
|
|
117
|
+
issues.extend(_require_creation_programs(app_call))
|
|
118
|
+
issues.extend(_validate_program_sizes(app_call))
|
|
119
|
+
issues.extend(_validate_state_schema_limits(app_call))
|
|
120
|
+
return issues
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def _require_creation_programs(app_call: AppCallTransactionFields) -> list[ValidationIssue]:
|
|
124
|
+
issues: list[ValidationIssue] = []
|
|
125
|
+
match (app_call.approval_program, app_call.clear_state_program):
|
|
126
|
+
case (None | b"", _):
|
|
127
|
+
issues.append(
|
|
128
|
+
_issue(ValidationIssueCode.REQUIRED_FIELD, "Approval program is required", field="Approval program")
|
|
129
|
+
)
|
|
130
|
+
case (_, None | b""):
|
|
131
|
+
issues.append(
|
|
132
|
+
_issue(
|
|
133
|
+
ValidationIssueCode.REQUIRED_FIELD, "Clear state program is required", field="Clear state program"
|
|
134
|
+
)
|
|
135
|
+
)
|
|
136
|
+
return issues
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def _validate_program_sizes(app_call: AppCallTransactionFields) -> list[ValidationIssue]:
|
|
140
|
+
issues: list[ValidationIssue] = []
|
|
141
|
+
extra_pages = app_call.extra_program_pages or 0
|
|
142
|
+
max_program_size = PROGRAM_PAGE_SIZE * (1 + extra_pages)
|
|
143
|
+
approval_size = len(app_call.approval_program or b"")
|
|
144
|
+
clear_state_size = len(app_call.clear_state_program or b"")
|
|
145
|
+
combined_size = approval_size + clear_state_size
|
|
146
|
+
|
|
147
|
+
match extra_pages:
|
|
148
|
+
case n if n > MAX_EXTRA_PROGRAM_PAGES:
|
|
149
|
+
issues.append(
|
|
150
|
+
_issue(
|
|
151
|
+
ValidationIssueCode.FIELD_TOO_LONG,
|
|
152
|
+
f"Extra program pages cannot exceed {MAX_EXTRA_PROGRAM_PAGES} pages, got {n}",
|
|
153
|
+
field="Extra program pages",
|
|
154
|
+
context={"max": MAX_EXTRA_PROGRAM_PAGES, "actual": n, "unit": "pages"},
|
|
155
|
+
)
|
|
156
|
+
)
|
|
157
|
+
|
|
158
|
+
match approval_size:
|
|
159
|
+
case size if size > max_program_size:
|
|
160
|
+
issues.append(
|
|
161
|
+
_issue(
|
|
162
|
+
ValidationIssueCode.FIELD_TOO_LONG,
|
|
163
|
+
f"Approval program cannot exceed {max_program_size} bytes",
|
|
164
|
+
field="Approval program",
|
|
165
|
+
context={"max": max_program_size, "actual": size, "unit": "bytes"},
|
|
166
|
+
)
|
|
167
|
+
)
|
|
168
|
+
|
|
169
|
+
match clear_state_size:
|
|
170
|
+
case size if size > max_program_size:
|
|
171
|
+
issues.append(
|
|
172
|
+
_issue(
|
|
173
|
+
ValidationIssueCode.FIELD_TOO_LONG,
|
|
174
|
+
f"Clear state program cannot exceed {max_program_size} bytes",
|
|
175
|
+
field="Clear state program",
|
|
176
|
+
context={"max": max_program_size, "actual": size, "unit": "bytes"},
|
|
177
|
+
)
|
|
178
|
+
)
|
|
179
|
+
|
|
180
|
+
match combined_size:
|
|
181
|
+
case size if size > max_program_size:
|
|
182
|
+
issues.append(
|
|
183
|
+
_issue(
|
|
184
|
+
ValidationIssueCode.FIELD_TOO_LONG,
|
|
185
|
+
f"Combined approval and clear state programs cannot exceed {max_program_size} bytes",
|
|
186
|
+
field="Combined program size",
|
|
187
|
+
context={"max": max_program_size, "actual": size, "unit": "bytes"},
|
|
188
|
+
)
|
|
189
|
+
)
|
|
190
|
+
|
|
191
|
+
return issues
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
def _validate_state_schema_limits(app_call: AppCallTransactionFields) -> list[ValidationIssue]:
|
|
195
|
+
issues: list[ValidationIssue] = []
|
|
196
|
+
if app_call.global_state_schema is not None:
|
|
197
|
+
total = app_call.global_state_schema.num_uints + app_call.global_state_schema.num_byte_slices
|
|
198
|
+
if total > MAX_GLOBAL_STATE_KEYS:
|
|
199
|
+
issues.append(
|
|
200
|
+
_issue(
|
|
201
|
+
ValidationIssueCode.FIELD_TOO_LONG,
|
|
202
|
+
f"Global state schema cannot exceed {MAX_GLOBAL_STATE_KEYS} keys",
|
|
203
|
+
field="Global state schema",
|
|
204
|
+
context={"max": MAX_GLOBAL_STATE_KEYS, "actual": total, "unit": "keys"},
|
|
205
|
+
)
|
|
206
|
+
)
|
|
207
|
+
|
|
208
|
+
if app_call.local_state_schema is not None:
|
|
209
|
+
total = app_call.local_state_schema.num_uints + app_call.local_state_schema.num_byte_slices
|
|
210
|
+
if total > MAX_LOCAL_STATE_KEYS:
|
|
211
|
+
issues.append(
|
|
212
|
+
_issue(
|
|
213
|
+
ValidationIssueCode.FIELD_TOO_LONG,
|
|
214
|
+
f"Local state schema cannot exceed {MAX_LOCAL_STATE_KEYS} keys",
|
|
215
|
+
field="Local state schema",
|
|
216
|
+
context={"max": MAX_LOCAL_STATE_KEYS, "actual": total, "unit": "keys"},
|
|
217
|
+
)
|
|
218
|
+
)
|
|
219
|
+
|
|
220
|
+
return issues
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
def _validate_app_operation(app_call: AppCallTransactionFields) -> list[ValidationIssue]:
|
|
224
|
+
issues: list[ValidationIssue] = []
|
|
225
|
+
|
|
226
|
+
def immutable(field: str) -> None:
|
|
227
|
+
issues.append(
|
|
228
|
+
_issue(ValidationIssueCode.IMMUTABLE_FIELD, f"{field} is immutable and cannot be changed", field=field)
|
|
229
|
+
)
|
|
230
|
+
|
|
231
|
+
match app_call.on_complete:
|
|
232
|
+
case OnApplicationComplete.UpdateApplication:
|
|
233
|
+
if not app_call.approval_program:
|
|
234
|
+
issues.append(
|
|
235
|
+
_issue(ValidationIssueCode.REQUIRED_FIELD, "Approval program is required", field="Approval program")
|
|
236
|
+
)
|
|
237
|
+
if not app_call.clear_state_program:
|
|
238
|
+
issues.append(
|
|
239
|
+
_issue(
|
|
240
|
+
ValidationIssueCode.REQUIRED_FIELD,
|
|
241
|
+
"Clear state program is required",
|
|
242
|
+
field="Clear state program",
|
|
243
|
+
)
|
|
244
|
+
)
|
|
245
|
+
|
|
246
|
+
if app_call.global_state_schema is not None:
|
|
247
|
+
immutable("Global state schema")
|
|
248
|
+
if app_call.local_state_schema is not None:
|
|
249
|
+
immutable("Local state schema")
|
|
250
|
+
if app_call.extra_program_pages is not None:
|
|
251
|
+
immutable("Extra program pages")
|
|
252
|
+
|
|
253
|
+
return issues
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
def _validate_app_common_fields(app_call: AppCallTransactionFields) -> list[ValidationIssue]:
|
|
257
|
+
return [
|
|
258
|
+
*_validate_app_args_limits(app_call),
|
|
259
|
+
*_validate_reference_limits(app_call),
|
|
260
|
+
*_validate_box_reference_constraints(app_call),
|
|
261
|
+
*_validate_total_reference_limit(app_call),
|
|
262
|
+
]
|
|
263
|
+
|
|
264
|
+
|
|
265
|
+
def _validate_app_args_limits(app_call: AppCallTransactionFields) -> list[ValidationIssue]:
|
|
266
|
+
issues: list[ValidationIssue] = []
|
|
267
|
+
if app_call.args is None:
|
|
268
|
+
return issues
|
|
269
|
+
if len(app_call.args) > MAX_APP_ARGS:
|
|
270
|
+
issues.append(
|
|
271
|
+
_issue(
|
|
272
|
+
ValidationIssueCode.FIELD_TOO_LONG,
|
|
273
|
+
f"Args cannot exceed {MAX_APP_ARGS} arguments",
|
|
274
|
+
field="Args",
|
|
275
|
+
context={"max": MAX_APP_ARGS, "actual": len(app_call.args), "unit": "arguments"},
|
|
276
|
+
)
|
|
277
|
+
)
|
|
278
|
+
total_size = sum(len(arg) for arg in app_call.args)
|
|
279
|
+
if total_size > MAX_ARGS_SIZE:
|
|
280
|
+
issues.append(
|
|
281
|
+
_issue(
|
|
282
|
+
ValidationIssueCode.FIELD_TOO_LONG,
|
|
283
|
+
f"Args total size cannot exceed {MAX_ARGS_SIZE} bytes",
|
|
284
|
+
field="Args total size",
|
|
285
|
+
context={"max": MAX_ARGS_SIZE, "actual": total_size, "unit": "bytes"},
|
|
286
|
+
)
|
|
287
|
+
)
|
|
288
|
+
return issues
|
|
289
|
+
|
|
290
|
+
|
|
291
|
+
def _validate_reference_limits(app_call: AppCallTransactionFields) -> list[ValidationIssue]:
|
|
292
|
+
issues: list[ValidationIssue] = []
|
|
293
|
+
reference_limits = (
|
|
294
|
+
("Account references", app_call.account_references, MAX_ACCOUNT_REFERENCES),
|
|
295
|
+
("App references", app_call.app_references, MAX_APP_REFERENCES),
|
|
296
|
+
("Asset references", app_call.asset_references, MAX_ASSET_REFERENCES),
|
|
297
|
+
)
|
|
298
|
+
for label, refs, limit in reference_limits:
|
|
299
|
+
if refs is not None and len(refs) > limit:
|
|
300
|
+
issues.append(
|
|
301
|
+
_issue(
|
|
302
|
+
ValidationIssueCode.FIELD_TOO_LONG,
|
|
303
|
+
f"{label} cannot exceed {limit} refs",
|
|
304
|
+
field=label,
|
|
305
|
+
context={"max": limit, "actual": len(refs), "unit": "refs"},
|
|
306
|
+
)
|
|
307
|
+
)
|
|
308
|
+
|
|
309
|
+
return issues
|
|
310
|
+
|
|
311
|
+
|
|
312
|
+
def _validate_box_reference_constraints(app_call: AppCallTransactionFields) -> list[ValidationIssue]:
|
|
313
|
+
issues: list[ValidationIssue] = []
|
|
314
|
+
box_refs = app_call.box_references or ()
|
|
315
|
+
if not box_refs:
|
|
316
|
+
return issues
|
|
317
|
+
if len(box_refs) > MAX_BOX_REFERENCES:
|
|
318
|
+
issues.append(
|
|
319
|
+
_issue(
|
|
320
|
+
ValidationIssueCode.FIELD_TOO_LONG,
|
|
321
|
+
f"Box references cannot exceed {MAX_BOX_REFERENCES} refs",
|
|
322
|
+
field="Box references",
|
|
323
|
+
context={"max": MAX_BOX_REFERENCES, "actual": len(box_refs), "unit": "refs"},
|
|
324
|
+
)
|
|
325
|
+
)
|
|
326
|
+
allowed_app_ids: set[int] = {app_call.app_id, 0}
|
|
327
|
+
allowed_app_ids.update(app_call.app_references or ())
|
|
328
|
+
for ref in box_refs:
|
|
329
|
+
if ref.app_id not in allowed_app_ids:
|
|
330
|
+
issues.append(
|
|
331
|
+
_issue(
|
|
332
|
+
ValidationIssueCode.ARBITRARY_CONSTRAINT,
|
|
333
|
+
f"Box reference for app ID {ref.app_id} must reference the current app or an app reference",
|
|
334
|
+
field="Box references",
|
|
335
|
+
)
|
|
336
|
+
)
|
|
337
|
+
return issues
|
|
338
|
+
|
|
339
|
+
|
|
340
|
+
def _validate_total_reference_limit(app_call: AppCallTransactionFields) -> list[ValidationIssue]:
|
|
341
|
+
total_refs = (
|
|
342
|
+
len(app_call.account_references or ())
|
|
343
|
+
+ len(app_call.app_references or ())
|
|
344
|
+
+ len(app_call.asset_references or ())
|
|
345
|
+
+ len(app_call.box_references or ())
|
|
346
|
+
)
|
|
347
|
+
if total_refs <= MAX_OVERALL_REFERENCES:
|
|
348
|
+
return []
|
|
349
|
+
return [
|
|
350
|
+
_issue(
|
|
351
|
+
ValidationIssueCode.FIELD_TOO_LONG,
|
|
352
|
+
f"Total references cannot exceed {MAX_OVERALL_REFERENCES} refs",
|
|
353
|
+
field="Total references",
|
|
354
|
+
context={"max": MAX_OVERALL_REFERENCES, "actual": total_refs, "unit": "refs"},
|
|
355
|
+
)
|
|
356
|
+
]
|
|
357
|
+
|
|
358
|
+
|
|
359
|
+
def validate_asset_config_fields(asset_config: AssetConfigTransactionFields) -> list[ValidationIssue]:
|
|
360
|
+
if asset_config.asset_id == 0:
|
|
361
|
+
return _validate_asset_creation(asset_config)
|
|
362
|
+
return _validate_asset_configuration(asset_config)
|
|
363
|
+
|
|
364
|
+
|
|
365
|
+
def _validate_asset_creation(asset_config: AssetConfigTransactionFields) -> list[ValidationIssue]:
|
|
366
|
+
issues: list[ValidationIssue] = []
|
|
367
|
+
|
|
368
|
+
def check_len(name: str, value: str | None, limit: int) -> None:
|
|
369
|
+
match value:
|
|
370
|
+
case str(s) if len(s) > limit:
|
|
371
|
+
issues.append(
|
|
372
|
+
_issue(
|
|
373
|
+
ValidationIssueCode.FIELD_TOO_LONG,
|
|
374
|
+
f"{name} cannot exceed {limit} bytes, got {len(s)}",
|
|
375
|
+
field=name,
|
|
376
|
+
context={"max": limit, "actual": len(s), "unit": "bytes"},
|
|
377
|
+
)
|
|
378
|
+
)
|
|
379
|
+
|
|
380
|
+
match asset_config.total:
|
|
381
|
+
case None:
|
|
382
|
+
issues.append(_issue(ValidationIssueCode.REQUIRED_FIELD, "Total is required", field="Total"))
|
|
383
|
+
|
|
384
|
+
match asset_config.decimals:
|
|
385
|
+
case int(d) if d > MAX_ASSET_DECIMALS:
|
|
386
|
+
issues.append(
|
|
387
|
+
_issue(
|
|
388
|
+
ValidationIssueCode.FIELD_TOO_LONG,
|
|
389
|
+
f"Decimals cannot exceed {MAX_ASSET_DECIMALS} decimal places, got {d}",
|
|
390
|
+
field="Decimals",
|
|
391
|
+
context={"max": MAX_ASSET_DECIMALS, "actual": d, "unit": "decimal places"},
|
|
392
|
+
)
|
|
393
|
+
)
|
|
394
|
+
|
|
395
|
+
check_len("Unit name", asset_config.unit_name, MAX_ASSET_UNIT_NAME_LENGTH)
|
|
396
|
+
check_len("Asset name", asset_config.asset_name, MAX_ASSET_NAME_LENGTH)
|
|
397
|
+
check_len("Url", asset_config.url, MAX_ASSET_URL_LENGTH)
|
|
398
|
+
|
|
399
|
+
return issues
|
|
400
|
+
|
|
401
|
+
|
|
402
|
+
def _validate_asset_configuration(asset_config: AssetConfigTransactionFields) -> list[ValidationIssue]:
|
|
403
|
+
immutable_fields = [
|
|
404
|
+
("total", "Total"),
|
|
405
|
+
("decimals", "Decimals"),
|
|
406
|
+
("default_frozen", "Default frozen"),
|
|
407
|
+
("asset_name", "Asset name"),
|
|
408
|
+
("unit_name", "Unit name"),
|
|
409
|
+
("url", "Url"),
|
|
410
|
+
("metadata_hash", "Metadata hash"),
|
|
411
|
+
]
|
|
412
|
+
|
|
413
|
+
return [
|
|
414
|
+
_issue(
|
|
415
|
+
ValidationIssueCode.IMMUTABLE_FIELD,
|
|
416
|
+
f"{label} is immutable and cannot be changed",
|
|
417
|
+
field=label,
|
|
418
|
+
)
|
|
419
|
+
for attr, label in immutable_fields
|
|
420
|
+
if getattr(asset_config, attr) is not None
|
|
421
|
+
]
|
|
422
|
+
|
|
423
|
+
|
|
424
|
+
def validate_asset_transfer_fields(asset_transfer: AssetTransferTransactionFields) -> list[ValidationIssue]:
|
|
425
|
+
issues: list[ValidationIssue] = []
|
|
426
|
+
|
|
427
|
+
if asset_transfer.asset_id == 0:
|
|
428
|
+
issues.append(
|
|
429
|
+
_issue(
|
|
430
|
+
ValidationIssueCode.ZERO_VALUE_FIELD,
|
|
431
|
+
"Asset ID must not be 0",
|
|
432
|
+
field="Asset ID",
|
|
433
|
+
)
|
|
434
|
+
)
|
|
435
|
+
|
|
436
|
+
return issues
|
|
437
|
+
|
|
438
|
+
|
|
439
|
+
def validate_asset_freeze_fields(asset_freeze: AssetFreezeTransactionFields) -> list[ValidationIssue]:
|
|
440
|
+
issues: list[ValidationIssue] = []
|
|
441
|
+
|
|
442
|
+
if asset_freeze.asset_id == 0:
|
|
443
|
+
issues.append(
|
|
444
|
+
_issue(
|
|
445
|
+
ValidationIssueCode.ZERO_VALUE_FIELD,
|
|
446
|
+
"Asset ID must not be 0",
|
|
447
|
+
field="Asset ID",
|
|
448
|
+
)
|
|
449
|
+
)
|
|
450
|
+
|
|
451
|
+
return issues
|
|
452
|
+
|
|
453
|
+
|
|
454
|
+
def validate_key_registration_fields(key_reg: KeyRegistrationTransactionFields) -> list[ValidationIssue]:
|
|
455
|
+
issues: list[ValidationIssue] = []
|
|
456
|
+
|
|
457
|
+
has_participation_fields = any(
|
|
458
|
+
field is not None
|
|
459
|
+
for field in (
|
|
460
|
+
key_reg.vote_key,
|
|
461
|
+
key_reg.selection_key,
|
|
462
|
+
key_reg.state_proof_key,
|
|
463
|
+
key_reg.vote_first,
|
|
464
|
+
key_reg.vote_last,
|
|
465
|
+
key_reg.vote_key_dilution,
|
|
466
|
+
)
|
|
467
|
+
)
|
|
468
|
+
|
|
469
|
+
if not has_participation_fields:
|
|
470
|
+
return issues
|
|
471
|
+
|
|
472
|
+
required_fields = [
|
|
473
|
+
(key_reg.vote_key, "Vote key"),
|
|
474
|
+
(key_reg.selection_key, "Selection key"),
|
|
475
|
+
(key_reg.state_proof_key, "State proof key"),
|
|
476
|
+
(key_reg.vote_first, "Vote first"),
|
|
477
|
+
(key_reg.vote_last, "Vote last"),
|
|
478
|
+
(key_reg.vote_key_dilution, "Vote key dilution"),
|
|
479
|
+
]
|
|
480
|
+
|
|
481
|
+
for value, field_name in required_fields:
|
|
482
|
+
if value is None:
|
|
483
|
+
issues.append(_issue(ValidationIssueCode.REQUIRED_FIELD, f"{field_name} is required", field=field_name))
|
|
484
|
+
|
|
485
|
+
if key_reg.vote_first is not None and key_reg.vote_last is not None and key_reg.vote_first >= key_reg.vote_last:
|
|
486
|
+
issues.append(
|
|
487
|
+
_issue(
|
|
488
|
+
ValidationIssueCode.ARBITRARY_CONSTRAINT,
|
|
489
|
+
"Vote first must be less than vote last",
|
|
490
|
+
field="Vote first",
|
|
491
|
+
)
|
|
492
|
+
)
|
|
493
|
+
|
|
494
|
+
if key_reg.non_participation:
|
|
495
|
+
issues.append(
|
|
496
|
+
_issue(
|
|
497
|
+
ValidationIssueCode.ARBITRARY_CONSTRAINT,
|
|
498
|
+
"Online key registration cannot have non participation flag set",
|
|
499
|
+
field="Non participation",
|
|
500
|
+
)
|
|
501
|
+
)
|
|
502
|
+
|
|
503
|
+
return issues
|
|
File without changes
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
"""Transaction and data signing types and utilities."""
|
|
2
|
+
|
|
3
|
+
import base64
|
|
4
|
+
from collections.abc import Callable, Sequence
|
|
5
|
+
from dataclasses import dataclass
|
|
6
|
+
from typing import Protocol, runtime_checkable
|
|
7
|
+
|
|
8
|
+
import nacl.signing
|
|
9
|
+
|
|
10
|
+
from algokit_common import address_from_public_key, public_key_from_address
|
|
11
|
+
from algokit_common.constants import (
|
|
12
|
+
EMPTY_SIGNATURE,
|
|
13
|
+
LOGIC_DATA_DOMAIN_SEPARATOR,
|
|
14
|
+
MULTISIG_PROGRAM_DOMAIN_SEPARATOR,
|
|
15
|
+
MX_BYTES_DOMAIN_SEPARATOR,
|
|
16
|
+
PROGRAM_DOMAIN_SEPARATOR,
|
|
17
|
+
)
|
|
18
|
+
from algokit_transact.codec.signed import encode_signed_transaction
|
|
19
|
+
from algokit_transact.codec.transaction import encode_transaction
|
|
20
|
+
from algokit_transact.models.signed_transaction import SignedTransaction
|
|
21
|
+
from algokit_transact.models.transaction import Transaction
|
|
22
|
+
|
|
23
|
+
# Type aliases for signing functions
|
|
24
|
+
BytesSigner = Callable[[bytes], bytes]
|
|
25
|
+
TransactionSigner = Callable[[Sequence[Transaction], Sequence[int]], list[bytes]]
|
|
26
|
+
DelegatedLsigSigner = Callable[[bytes, bytes | None], bytes]
|
|
27
|
+
ProgramDataSigner = Callable[[bytes, bytes], bytes]
|
|
28
|
+
MxBytesSigner = Callable[[bytes], bytes]
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
@runtime_checkable
|
|
32
|
+
class Addressable(Protocol):
|
|
33
|
+
"""Protocol for objects with an address."""
|
|
34
|
+
|
|
35
|
+
@property
|
|
36
|
+
def addr(self) -> str: ...
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
@runtime_checkable
|
|
40
|
+
class AddressWithTransactionSigner(Addressable, Protocol):
|
|
41
|
+
"""Protocol for objects with transaction signing capability."""
|
|
42
|
+
|
|
43
|
+
@property
|
|
44
|
+
def signer(self) -> TransactionSigner: ...
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
@runtime_checkable
|
|
48
|
+
class AddressWithDelegatedLsigSigner(Addressable, Protocol):
|
|
49
|
+
"""Protocol for objects with logic signature delegation signing."""
|
|
50
|
+
|
|
51
|
+
@property
|
|
52
|
+
def delegated_lsig_signer(self) -> DelegatedLsigSigner: ...
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
@runtime_checkable
|
|
56
|
+
class AddressWithProgramDataSigner(Addressable, Protocol):
|
|
57
|
+
"""Protocol for objects with program data signing capability."""
|
|
58
|
+
|
|
59
|
+
@property
|
|
60
|
+
def program_data_signer(self) -> ProgramDataSigner: ...
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
@runtime_checkable
|
|
64
|
+
class AddressWithMxBytesSigner(Addressable, Protocol):
|
|
65
|
+
"""Protocol for objects with MX-prefixed bytes signing capability."""
|
|
66
|
+
|
|
67
|
+
@property
|
|
68
|
+
def mx_bytes_signer(self) -> MxBytesSigner: ...
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
@dataclass(frozen=True, slots=True)
|
|
72
|
+
class AddressWithSigners:
|
|
73
|
+
"""Container for an address with all signing capabilities."""
|
|
74
|
+
|
|
75
|
+
addr: str
|
|
76
|
+
signer: TransactionSigner
|
|
77
|
+
delegated_lsig_signer: DelegatedLsigSigner
|
|
78
|
+
program_data_signer: ProgramDataSigner
|
|
79
|
+
bytes_signer: BytesSigner
|
|
80
|
+
mx_bytes_signer: MxBytesSigner
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def generate_address_with_signers(
|
|
84
|
+
ed25519_pubkey: bytes,
|
|
85
|
+
raw_ed25519_signer: BytesSigner,
|
|
86
|
+
*,
|
|
87
|
+
sending_address: str | None = None,
|
|
88
|
+
) -> AddressWithSigners:
|
|
89
|
+
"""Generate domain-separated signers from an ed25519 pubkey and raw signer.
|
|
90
|
+
|
|
91
|
+
Args:
|
|
92
|
+
ed25519_pubkey: The ed25519 public key bytes.
|
|
93
|
+
raw_ed25519_signer: A function that signs raw bytes with the ed25519 private key.
|
|
94
|
+
sending_address: Optional address to use as the sending address. If provided,
|
|
95
|
+
this will be the `addr` in the returned object, and the address derived
|
|
96
|
+
from `ed25519_pubkey` will be used as the auth_address when signing
|
|
97
|
+
transactions where the sender differs from the sending_address.
|
|
98
|
+
|
|
99
|
+
Returns:
|
|
100
|
+
An AddressWithSigners containing the address and all signing functions.
|
|
101
|
+
"""
|
|
102
|
+
auth_addr = address_from_public_key(ed25519_pubkey)
|
|
103
|
+
addr = sending_address if sending_address is not None else auth_addr
|
|
104
|
+
|
|
105
|
+
def transaction_signer(txn_group: Sequence[Transaction], indexes_to_sign: Sequence[int]) -> list[bytes]:
|
|
106
|
+
result: list[bytes] = []
|
|
107
|
+
for index in indexes_to_sign:
|
|
108
|
+
txn = txn_group[index]
|
|
109
|
+
bytes_to_sign = encode_transaction(txn)
|
|
110
|
+
signature = raw_ed25519_signer(bytes_to_sign)
|
|
111
|
+
stxn = SignedTransaction(
|
|
112
|
+
txn=txn,
|
|
113
|
+
sig=signature,
|
|
114
|
+
auth_address=auth_addr if txn.sender != auth_addr else None,
|
|
115
|
+
)
|
|
116
|
+
result.append(encode_signed_transaction(stxn))
|
|
117
|
+
return result
|
|
118
|
+
|
|
119
|
+
def delegated_lsig_signer(program: bytes, msig_address: bytes | None = None) -> bytes:
|
|
120
|
+
if msig_address is not None:
|
|
121
|
+
payload = MULTISIG_PROGRAM_DOMAIN_SEPARATOR.encode() + msig_address + program
|
|
122
|
+
else:
|
|
123
|
+
payload = PROGRAM_DOMAIN_SEPARATOR.encode() + program
|
|
124
|
+
return raw_ed25519_signer(payload)
|
|
125
|
+
|
|
126
|
+
def program_data_signer(data: bytes, program_address: bytes) -> bytes:
|
|
127
|
+
payload = LOGIC_DATA_DOMAIN_SEPARATOR.encode() + program_address + data
|
|
128
|
+
return raw_ed25519_signer(payload)
|
|
129
|
+
|
|
130
|
+
def mx_bytes_signer(data: bytes) -> bytes:
|
|
131
|
+
payload = MX_BYTES_DOMAIN_SEPARATOR.encode() + data
|
|
132
|
+
return raw_ed25519_signer(payload)
|
|
133
|
+
|
|
134
|
+
return AddressWithSigners(
|
|
135
|
+
addr=addr,
|
|
136
|
+
signer=transaction_signer,
|
|
137
|
+
delegated_lsig_signer=delegated_lsig_signer,
|
|
138
|
+
program_data_signer=program_data_signer,
|
|
139
|
+
bytes_signer=raw_ed25519_signer,
|
|
140
|
+
mx_bytes_signer=mx_bytes_signer,
|
|
141
|
+
)
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
def make_empty_transaction_signer() -> TransactionSigner:
|
|
145
|
+
"""Create a signer that returns empty signatures (for simulation only)."""
|
|
146
|
+
|
|
147
|
+
def empty_signer(txn_group: Sequence[Transaction], indexes_to_sign: Sequence[int]) -> list[bytes]:
|
|
148
|
+
result: list[bytes] = []
|
|
149
|
+
for index in indexes_to_sign:
|
|
150
|
+
stxn = SignedTransaction(
|
|
151
|
+
txn=txn_group[index],
|
|
152
|
+
sig=EMPTY_SIGNATURE,
|
|
153
|
+
)
|
|
154
|
+
result.append(encode_signed_transaction(stxn))
|
|
155
|
+
return result
|
|
156
|
+
|
|
157
|
+
return empty_signer
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
def bytes_to_sign_for_delegation(program: bytes, msig_public_key: bytes | None = None) -> bytes:
|
|
161
|
+
"""Get bytes to sign for delegating a logic signature."""
|
|
162
|
+
if msig_public_key is not None:
|
|
163
|
+
return MULTISIG_PROGRAM_DOMAIN_SEPARATOR.encode() + msig_public_key + program
|
|
164
|
+
return PROGRAM_DOMAIN_SEPARATOR.encode() + program
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
def program_data_to_sign(data: bytes, program_address: str) -> bytes:
|
|
168
|
+
"""Get bytes to sign for program data."""
|
|
169
|
+
program_public_key = public_key_from_address(program_address)
|
|
170
|
+
return LOGIC_DATA_DOMAIN_SEPARATOR.encode() + program_public_key + data
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
def make_basic_account_transaction_signer(private_key: str) -> TransactionSigner:
|
|
174
|
+
"""Create a transaction signer from a base64-encoded private key.
|
|
175
|
+
|
|
176
|
+
Args:
|
|
177
|
+
private_key: Base64-encoded 64-byte private key (first 32 bytes are seed,
|
|
178
|
+
last 32 bytes are public key).
|
|
179
|
+
|
|
180
|
+
Returns:
|
|
181
|
+
A TransactionSigner function that can sign transactions.
|
|
182
|
+
"""
|
|
183
|
+
# Decode the base64 private key (64 bytes: 32-byte seed + 32-byte public key)
|
|
184
|
+
key_bytes = base64.b64decode(private_key)
|
|
185
|
+
seed = key_bytes[:32]
|
|
186
|
+
public_key = key_bytes[32:]
|
|
187
|
+
|
|
188
|
+
# Create signing key from seed
|
|
189
|
+
signing_key = nacl.signing.SigningKey(seed)
|
|
190
|
+
|
|
191
|
+
def raw_signer(bytes_to_sign: bytes) -> bytes:
|
|
192
|
+
signed = signing_key.sign(bytes_to_sign)
|
|
193
|
+
return signed.signature
|
|
194
|
+
|
|
195
|
+
return generate_address_with_signers(public_key, raw_signer).signer
|
|
File without changes
|