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,1456 @@
|
|
|
1
|
+
# AUTO-GENERATED: oas_generator
|
|
2
|
+
import random
|
|
3
|
+
import time
|
|
4
|
+
from dataclasses import is_dataclass
|
|
5
|
+
from datetime import datetime
|
|
6
|
+
from typing import Any, Literal, TypeVar, overload
|
|
7
|
+
|
|
8
|
+
import httpx
|
|
9
|
+
import msgpack
|
|
10
|
+
|
|
11
|
+
from algokit_common.serde import from_wire, to_wire
|
|
12
|
+
|
|
13
|
+
from . import models
|
|
14
|
+
from .config import ClientConfig
|
|
15
|
+
from .exceptions import UnexpectedStatusError
|
|
16
|
+
from .types import Headers
|
|
17
|
+
|
|
18
|
+
# HTTP status codes that warrant a retry (aligned with algokit-utils-ts)
|
|
19
|
+
_RETRY_STATUS_CODES: frozenset[int] = frozenset({408, 413, 429, 500, 502, 503, 504})
|
|
20
|
+
# Network error codes that warrant a retry (aligned with algokit-utils-ts)
|
|
21
|
+
_RETRY_ERROR_CODES: frozenset[str] = frozenset(
|
|
22
|
+
{
|
|
23
|
+
"ETIMEDOUT",
|
|
24
|
+
"ECONNRESET",
|
|
25
|
+
"EADDRINUSE",
|
|
26
|
+
"ECONNREFUSED",
|
|
27
|
+
"EPIPE",
|
|
28
|
+
"ENOTFOUND",
|
|
29
|
+
"ENETUNREACH",
|
|
30
|
+
"EAI_AGAIN",
|
|
31
|
+
"EPROTO",
|
|
32
|
+
}
|
|
33
|
+
)
|
|
34
|
+
_MAX_BACKOFF_MS: float = 10_000.0
|
|
35
|
+
_DEFAULT_MAX_TRIES: int = 5
|
|
36
|
+
|
|
37
|
+
ModelT = TypeVar("ModelT")
|
|
38
|
+
ListModelT = TypeVar("ListModelT")
|
|
39
|
+
PrimitiveT = TypeVar("PrimitiveT")
|
|
40
|
+
|
|
41
|
+
# Prefixed markers used when converting unhashable msgpack map keys into hashable tuples
|
|
42
|
+
_UNHASHABLE_PREFIXES: dict[str, str] = {
|
|
43
|
+
"dict": "__dict_key__",
|
|
44
|
+
"list": "__list_key__",
|
|
45
|
+
"set": "__set_key__",
|
|
46
|
+
"generic": "__unhashable__",
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class IndexerClient:
|
|
51
|
+
def __init__(self, config: ClientConfig | None = None, *, http_client: httpx.Client | None = None) -> None:
|
|
52
|
+
self._config = config or ClientConfig()
|
|
53
|
+
# Track whether a custom HTTP client was provided to avoid retry conflicts
|
|
54
|
+
self._uses_custom_client = http_client is not None
|
|
55
|
+
self._client = http_client or httpx.Client(
|
|
56
|
+
base_url=self._config.base_url,
|
|
57
|
+
timeout=self._config.timeout,
|
|
58
|
+
verify=self._config.verify,
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
def close(self) -> None:
|
|
62
|
+
self._client.close()
|
|
63
|
+
|
|
64
|
+
def _calculate_max_tries(self) -> int:
|
|
65
|
+
"""Calculate maximum number of tries from config.max_retries."""
|
|
66
|
+
max_retries = self._config.max_retries
|
|
67
|
+
if not isinstance(max_retries, int) or max_retries < 0:
|
|
68
|
+
return _DEFAULT_MAX_TRIES
|
|
69
|
+
return max_retries + 1
|
|
70
|
+
|
|
71
|
+
def _should_retry(self, error: Exception | None, status_code: int | None, attempt: int, max_tries: int) -> bool:
|
|
72
|
+
"""Determine if a request should be retried based on error/status and attempt count."""
|
|
73
|
+
if attempt >= max_tries:
|
|
74
|
+
return False
|
|
75
|
+
|
|
76
|
+
# Check HTTP status code
|
|
77
|
+
if status_code is not None and status_code in _RETRY_STATUS_CODES:
|
|
78
|
+
return True
|
|
79
|
+
|
|
80
|
+
# Check network error codes (aligned with algokit-utils-ts)
|
|
81
|
+
if error is not None:
|
|
82
|
+
error_code = self._extract_error_code(error)
|
|
83
|
+
if error_code and error_code in _RETRY_ERROR_CODES:
|
|
84
|
+
return True
|
|
85
|
+
|
|
86
|
+
return False
|
|
87
|
+
|
|
88
|
+
def _extract_error_code(self, error: BaseException) -> str | None:
|
|
89
|
+
"""Extract error code from exception, checking common attributes."""
|
|
90
|
+
# Check for 'code' attribute (common in OS/network errors)
|
|
91
|
+
if hasattr(error, "code") and isinstance(error.code, str):
|
|
92
|
+
return error.code
|
|
93
|
+
# Check for errno attribute
|
|
94
|
+
if hasattr(error, "errno") and error.errno is not None:
|
|
95
|
+
import errno as errno_module
|
|
96
|
+
|
|
97
|
+
try:
|
|
98
|
+
return errno_module.errorcode.get(error.errno)
|
|
99
|
+
except (TypeError, AttributeError):
|
|
100
|
+
pass
|
|
101
|
+
# Check __cause__ for wrapped errors
|
|
102
|
+
if error.__cause__ is not None:
|
|
103
|
+
return self._extract_error_code(error.__cause__)
|
|
104
|
+
return None
|
|
105
|
+
|
|
106
|
+
def _request_with_retry(self, request_kwargs: dict[str, Any]) -> httpx.Response:
|
|
107
|
+
"""Execute request with exponential backoff retry for transient failures.
|
|
108
|
+
|
|
109
|
+
When a custom HTTP client is provided, retries are disabled to avoid
|
|
110
|
+
conflicts with any retry mechanism the custom client may implement.
|
|
111
|
+
"""
|
|
112
|
+
# Disable retries when using a custom HTTP client to avoid conflicts
|
|
113
|
+
# with the client's own retry mechanism
|
|
114
|
+
if self._uses_custom_client:
|
|
115
|
+
return self._client.request(**request_kwargs)
|
|
116
|
+
|
|
117
|
+
max_tries = self._calculate_max_tries()
|
|
118
|
+
attempt = 1
|
|
119
|
+
last_error: Exception | None = None
|
|
120
|
+
|
|
121
|
+
while attempt <= max_tries:
|
|
122
|
+
status_code: int | None = None
|
|
123
|
+
try:
|
|
124
|
+
response = self._client.request(**request_kwargs)
|
|
125
|
+
status_code = response.status_code
|
|
126
|
+
if not self._should_retry(None, status_code, attempt, max_tries):
|
|
127
|
+
return response
|
|
128
|
+
except httpx.TransportError as exc:
|
|
129
|
+
last_error = exc
|
|
130
|
+
if not self._should_retry(exc, None, attempt, max_tries):
|
|
131
|
+
raise
|
|
132
|
+
|
|
133
|
+
if attempt == 1:
|
|
134
|
+
backoff_ms = 0.0
|
|
135
|
+
else:
|
|
136
|
+
base_backoff = min(1000.0 * (2 ** (attempt - 1)), _MAX_BACKOFF_MS)
|
|
137
|
+
jitter = 0.5 + random.random() # Random value between 0.5 and 1.5
|
|
138
|
+
backoff_ms = base_backoff * jitter
|
|
139
|
+
if backoff_ms > 0:
|
|
140
|
+
time.sleep(backoff_ms / 1000.0)
|
|
141
|
+
attempt += 1
|
|
142
|
+
|
|
143
|
+
# Should not reach here, but satisfy type checker
|
|
144
|
+
if last_error:
|
|
145
|
+
raise last_error
|
|
146
|
+
raise RuntimeError(f"Request failed after {max_tries} attempt(s)")
|
|
147
|
+
|
|
148
|
+
# common
|
|
149
|
+
|
|
150
|
+
def make_health_check(
|
|
151
|
+
self,
|
|
152
|
+
) -> models.HealthCheck:
|
|
153
|
+
"""
|
|
154
|
+
Returns 200 if healthy.
|
|
155
|
+
"""
|
|
156
|
+
|
|
157
|
+
path = "/health"
|
|
158
|
+
params: dict[str, Any] = {}
|
|
159
|
+
headers: Headers = self._config.resolve_headers()
|
|
160
|
+
|
|
161
|
+
accept_value: str | None = None
|
|
162
|
+
|
|
163
|
+
headers.setdefault("accept", accept_value or "application/json")
|
|
164
|
+
request_kwargs: dict[str, Any] = {
|
|
165
|
+
"method": "GET",
|
|
166
|
+
"url": path,
|
|
167
|
+
"params": params,
|
|
168
|
+
"headers": headers,
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
response = self._request_with_retry(request_kwargs)
|
|
172
|
+
if response.is_success:
|
|
173
|
+
return self._decode_response(response, model=models.HealthCheck)
|
|
174
|
+
|
|
175
|
+
raise UnexpectedStatusError(response.status_code, response.text)
|
|
176
|
+
|
|
177
|
+
# lookup
|
|
178
|
+
|
|
179
|
+
def lookup_account_app_local_states(
|
|
180
|
+
self,
|
|
181
|
+
account_id: str,
|
|
182
|
+
*,
|
|
183
|
+
application_id: int | None = None,
|
|
184
|
+
include_all: bool | None = None,
|
|
185
|
+
limit: int | None = None,
|
|
186
|
+
next_: str | None = None,
|
|
187
|
+
) -> models.ApplicationLocalStatesResponse:
|
|
188
|
+
"""
|
|
189
|
+
Lookup an account's asset holdings, optionally for a specific ID.
|
|
190
|
+
"""
|
|
191
|
+
|
|
192
|
+
path = "/v2/accounts/{account-id}/apps-local-state"
|
|
193
|
+
path = path.replace("{account-id}", str(account_id))
|
|
194
|
+
|
|
195
|
+
params: dict[str, Any] = {}
|
|
196
|
+
headers: Headers = self._config.resolve_headers()
|
|
197
|
+
if application_id is not None:
|
|
198
|
+
params["application-id"] = application_id
|
|
199
|
+
|
|
200
|
+
if include_all is not None:
|
|
201
|
+
params["include-all"] = include_all
|
|
202
|
+
|
|
203
|
+
if limit is not None:
|
|
204
|
+
params["limit"] = limit
|
|
205
|
+
|
|
206
|
+
if next_ is not None:
|
|
207
|
+
params["next"] = next_
|
|
208
|
+
|
|
209
|
+
accept_value: str | None = None
|
|
210
|
+
|
|
211
|
+
headers.setdefault("accept", accept_value or "application/json")
|
|
212
|
+
request_kwargs: dict[str, Any] = {
|
|
213
|
+
"method": "GET",
|
|
214
|
+
"url": path,
|
|
215
|
+
"params": params,
|
|
216
|
+
"headers": headers,
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
response = self._request_with_retry(request_kwargs)
|
|
220
|
+
if response.is_success:
|
|
221
|
+
return self._decode_response(response, model=models.ApplicationLocalStatesResponse)
|
|
222
|
+
|
|
223
|
+
raise UnexpectedStatusError(response.status_code, response.text)
|
|
224
|
+
|
|
225
|
+
def lookup_account_assets(
|
|
226
|
+
self,
|
|
227
|
+
account_id: str,
|
|
228
|
+
*,
|
|
229
|
+
asset_id: int | None = None,
|
|
230
|
+
include_all: bool | None = None,
|
|
231
|
+
limit: int | None = None,
|
|
232
|
+
next_: str | None = None,
|
|
233
|
+
) -> models.AssetHoldingsResponse:
|
|
234
|
+
"""
|
|
235
|
+
Lookup an account's asset holdings, optionally for a specific ID.
|
|
236
|
+
"""
|
|
237
|
+
|
|
238
|
+
path = "/v2/accounts/{account-id}/assets"
|
|
239
|
+
path = path.replace("{account-id}", str(account_id))
|
|
240
|
+
|
|
241
|
+
params: dict[str, Any] = {}
|
|
242
|
+
headers: Headers = self._config.resolve_headers()
|
|
243
|
+
if asset_id is not None:
|
|
244
|
+
params["asset-id"] = asset_id
|
|
245
|
+
|
|
246
|
+
if include_all is not None:
|
|
247
|
+
params["include-all"] = include_all
|
|
248
|
+
|
|
249
|
+
if limit is not None:
|
|
250
|
+
params["limit"] = limit
|
|
251
|
+
|
|
252
|
+
if next_ is not None:
|
|
253
|
+
params["next"] = next_
|
|
254
|
+
|
|
255
|
+
accept_value: str | None = None
|
|
256
|
+
|
|
257
|
+
headers.setdefault("accept", accept_value or "application/json")
|
|
258
|
+
request_kwargs: dict[str, Any] = {
|
|
259
|
+
"method": "GET",
|
|
260
|
+
"url": path,
|
|
261
|
+
"params": params,
|
|
262
|
+
"headers": headers,
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
response = self._request_with_retry(request_kwargs)
|
|
266
|
+
if response.is_success:
|
|
267
|
+
return self._decode_response(response, model=models.AssetHoldingsResponse)
|
|
268
|
+
|
|
269
|
+
raise UnexpectedStatusError(response.status_code, response.text)
|
|
270
|
+
|
|
271
|
+
def lookup_account_by_id(
|
|
272
|
+
self,
|
|
273
|
+
account_id: str,
|
|
274
|
+
*,
|
|
275
|
+
round_: int | None = None,
|
|
276
|
+
include_all: bool | None = None,
|
|
277
|
+
exclude: list[str] | None = None,
|
|
278
|
+
) -> models.AccountResponse:
|
|
279
|
+
"""
|
|
280
|
+
Lookup account information.
|
|
281
|
+
"""
|
|
282
|
+
|
|
283
|
+
path = "/v2/accounts/{account-id}"
|
|
284
|
+
path = path.replace("{account-id}", str(account_id))
|
|
285
|
+
|
|
286
|
+
params: dict[str, Any] = {}
|
|
287
|
+
headers: Headers = self._config.resolve_headers()
|
|
288
|
+
if round_ is not None:
|
|
289
|
+
params["round"] = round_
|
|
290
|
+
|
|
291
|
+
if include_all is not None:
|
|
292
|
+
params["include-all"] = include_all
|
|
293
|
+
|
|
294
|
+
if exclude is not None:
|
|
295
|
+
params["exclude"] = exclude
|
|
296
|
+
|
|
297
|
+
accept_value: str | None = None
|
|
298
|
+
|
|
299
|
+
headers.setdefault("accept", accept_value or "application/json")
|
|
300
|
+
request_kwargs: dict[str, Any] = {
|
|
301
|
+
"method": "GET",
|
|
302
|
+
"url": path,
|
|
303
|
+
"params": params,
|
|
304
|
+
"headers": headers,
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
response = self._request_with_retry(request_kwargs)
|
|
308
|
+
if response.is_success:
|
|
309
|
+
return self._decode_response(response, model=models.AccountResponse)
|
|
310
|
+
|
|
311
|
+
raise UnexpectedStatusError(response.status_code, response.text)
|
|
312
|
+
|
|
313
|
+
def lookup_account_created_applications(
|
|
314
|
+
self,
|
|
315
|
+
account_id: str,
|
|
316
|
+
*,
|
|
317
|
+
application_id: int | None = None,
|
|
318
|
+
include_all: bool | None = None,
|
|
319
|
+
limit: int | None = None,
|
|
320
|
+
next_: str | None = None,
|
|
321
|
+
) -> models.ApplicationsResponse:
|
|
322
|
+
"""
|
|
323
|
+
Lookup an account's created application parameters, optionally for a specific ID.
|
|
324
|
+
"""
|
|
325
|
+
|
|
326
|
+
path = "/v2/accounts/{account-id}/created-applications"
|
|
327
|
+
path = path.replace("{account-id}", str(account_id))
|
|
328
|
+
|
|
329
|
+
params: dict[str, Any] = {}
|
|
330
|
+
headers: Headers = self._config.resolve_headers()
|
|
331
|
+
if application_id is not None:
|
|
332
|
+
params["application-id"] = application_id
|
|
333
|
+
|
|
334
|
+
if include_all is not None:
|
|
335
|
+
params["include-all"] = include_all
|
|
336
|
+
|
|
337
|
+
if limit is not None:
|
|
338
|
+
params["limit"] = limit
|
|
339
|
+
|
|
340
|
+
if next_ is not None:
|
|
341
|
+
params["next"] = next_
|
|
342
|
+
|
|
343
|
+
accept_value: str | None = None
|
|
344
|
+
|
|
345
|
+
headers.setdefault("accept", accept_value or "application/json")
|
|
346
|
+
request_kwargs: dict[str, Any] = {
|
|
347
|
+
"method": "GET",
|
|
348
|
+
"url": path,
|
|
349
|
+
"params": params,
|
|
350
|
+
"headers": headers,
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
response = self._request_with_retry(request_kwargs)
|
|
354
|
+
if response.is_success:
|
|
355
|
+
return self._decode_response(response, model=models.ApplicationsResponse)
|
|
356
|
+
|
|
357
|
+
raise UnexpectedStatusError(response.status_code, response.text)
|
|
358
|
+
|
|
359
|
+
def lookup_account_created_assets(
|
|
360
|
+
self,
|
|
361
|
+
account_id: str,
|
|
362
|
+
*,
|
|
363
|
+
asset_id: int | None = None,
|
|
364
|
+
include_all: bool | None = None,
|
|
365
|
+
limit: int | None = None,
|
|
366
|
+
next_: str | None = None,
|
|
367
|
+
) -> models.AssetsResponse:
|
|
368
|
+
"""
|
|
369
|
+
Lookup an account's created asset parameters, optionally for a specific ID.
|
|
370
|
+
"""
|
|
371
|
+
|
|
372
|
+
path = "/v2/accounts/{account-id}/created-assets"
|
|
373
|
+
path = path.replace("{account-id}", str(account_id))
|
|
374
|
+
|
|
375
|
+
params: dict[str, Any] = {}
|
|
376
|
+
headers: Headers = self._config.resolve_headers()
|
|
377
|
+
if asset_id is not None:
|
|
378
|
+
params["asset-id"] = asset_id
|
|
379
|
+
|
|
380
|
+
if include_all is not None:
|
|
381
|
+
params["include-all"] = include_all
|
|
382
|
+
|
|
383
|
+
if limit is not None:
|
|
384
|
+
params["limit"] = limit
|
|
385
|
+
|
|
386
|
+
if next_ is not None:
|
|
387
|
+
params["next"] = next_
|
|
388
|
+
|
|
389
|
+
accept_value: str | None = None
|
|
390
|
+
|
|
391
|
+
headers.setdefault("accept", accept_value or "application/json")
|
|
392
|
+
request_kwargs: dict[str, Any] = {
|
|
393
|
+
"method": "GET",
|
|
394
|
+
"url": path,
|
|
395
|
+
"params": params,
|
|
396
|
+
"headers": headers,
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
response = self._request_with_retry(request_kwargs)
|
|
400
|
+
if response.is_success:
|
|
401
|
+
return self._decode_response(response, model=models.AssetsResponse)
|
|
402
|
+
|
|
403
|
+
raise UnexpectedStatusError(response.status_code, response.text)
|
|
404
|
+
|
|
405
|
+
def lookup_account_transactions( # noqa: C901, PLR0912, PLR0913
|
|
406
|
+
self,
|
|
407
|
+
account_id: str,
|
|
408
|
+
*,
|
|
409
|
+
limit: int | None = None,
|
|
410
|
+
next_: str | None = None,
|
|
411
|
+
note_prefix: str | None = None,
|
|
412
|
+
tx_type: str | None = None,
|
|
413
|
+
sig_type: str | None = None,
|
|
414
|
+
txid: str | None = None,
|
|
415
|
+
round_: int | None = None,
|
|
416
|
+
min_round: int | None = None,
|
|
417
|
+
max_round: int | None = None,
|
|
418
|
+
asset_id: int | None = None,
|
|
419
|
+
before_time: datetime | None = None,
|
|
420
|
+
after_time: datetime | None = None,
|
|
421
|
+
currency_greater_than: int | None = None,
|
|
422
|
+
currency_less_than: int | None = None,
|
|
423
|
+
rekey_to: bool | None = None,
|
|
424
|
+
) -> models.TransactionsResponse:
|
|
425
|
+
"""
|
|
426
|
+
Lookup account transactions. Transactions are returned newest to oldest.
|
|
427
|
+
"""
|
|
428
|
+
|
|
429
|
+
path = "/v2/accounts/{account-id}/transactions"
|
|
430
|
+
path = path.replace("{account-id}", str(account_id))
|
|
431
|
+
|
|
432
|
+
params: dict[str, Any] = {}
|
|
433
|
+
headers: Headers = self._config.resolve_headers()
|
|
434
|
+
if limit is not None:
|
|
435
|
+
params["limit"] = limit
|
|
436
|
+
|
|
437
|
+
if next_ is not None:
|
|
438
|
+
params["next"] = next_
|
|
439
|
+
|
|
440
|
+
if note_prefix is not None:
|
|
441
|
+
params["note-prefix"] = note_prefix
|
|
442
|
+
|
|
443
|
+
if tx_type is not None:
|
|
444
|
+
params["tx-type"] = tx_type
|
|
445
|
+
|
|
446
|
+
if sig_type is not None:
|
|
447
|
+
params["sig-type"] = sig_type
|
|
448
|
+
|
|
449
|
+
if txid is not None:
|
|
450
|
+
params["txid"] = txid
|
|
451
|
+
|
|
452
|
+
if round_ is not None:
|
|
453
|
+
params["round"] = round_
|
|
454
|
+
|
|
455
|
+
if min_round is not None:
|
|
456
|
+
params["min-round"] = min_round
|
|
457
|
+
|
|
458
|
+
if max_round is not None:
|
|
459
|
+
params["max-round"] = max_round
|
|
460
|
+
|
|
461
|
+
if asset_id is not None:
|
|
462
|
+
params["asset-id"] = asset_id
|
|
463
|
+
|
|
464
|
+
if before_time is not None:
|
|
465
|
+
params["before-time"] = before_time
|
|
466
|
+
|
|
467
|
+
if after_time is not None:
|
|
468
|
+
params["after-time"] = after_time
|
|
469
|
+
|
|
470
|
+
if currency_greater_than is not None:
|
|
471
|
+
params["currency-greater-than"] = currency_greater_than
|
|
472
|
+
|
|
473
|
+
if currency_less_than is not None:
|
|
474
|
+
params["currency-less-than"] = currency_less_than
|
|
475
|
+
|
|
476
|
+
if rekey_to is not None:
|
|
477
|
+
params["rekey-to"] = rekey_to
|
|
478
|
+
|
|
479
|
+
accept_value: str | None = None
|
|
480
|
+
|
|
481
|
+
headers.setdefault("accept", accept_value or "application/json")
|
|
482
|
+
request_kwargs: dict[str, Any] = {
|
|
483
|
+
"method": "GET",
|
|
484
|
+
"url": path,
|
|
485
|
+
"params": params,
|
|
486
|
+
"headers": headers,
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
response = self._request_with_retry(request_kwargs)
|
|
490
|
+
if response.is_success:
|
|
491
|
+
return self._decode_response(response, model=models.TransactionsResponse)
|
|
492
|
+
|
|
493
|
+
raise UnexpectedStatusError(response.status_code, response.text)
|
|
494
|
+
|
|
495
|
+
def lookup_application_box_by_idand_name(
|
|
496
|
+
self,
|
|
497
|
+
application_id: int,
|
|
498
|
+
name: str,
|
|
499
|
+
) -> models.Box:
|
|
500
|
+
"""
|
|
501
|
+
Get box information for a given application.
|
|
502
|
+
"""
|
|
503
|
+
|
|
504
|
+
path = "/v2/applications/{application-id}/box"
|
|
505
|
+
path = path.replace("{application-id}", str(application_id))
|
|
506
|
+
|
|
507
|
+
params: dict[str, Any] = {}
|
|
508
|
+
headers: Headers = self._config.resolve_headers()
|
|
509
|
+
if name is not None:
|
|
510
|
+
params["name"] = name
|
|
511
|
+
|
|
512
|
+
accept_value: str | None = None
|
|
513
|
+
|
|
514
|
+
headers.setdefault("accept", accept_value or "application/json")
|
|
515
|
+
request_kwargs: dict[str, Any] = {
|
|
516
|
+
"method": "GET",
|
|
517
|
+
"url": path,
|
|
518
|
+
"params": params,
|
|
519
|
+
"headers": headers,
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
response = self._request_with_retry(request_kwargs)
|
|
523
|
+
if response.is_success:
|
|
524
|
+
return self._decode_response(response, model=models.Box)
|
|
525
|
+
|
|
526
|
+
raise UnexpectedStatusError(response.status_code, response.text)
|
|
527
|
+
|
|
528
|
+
def lookup_application_by_id(
|
|
529
|
+
self,
|
|
530
|
+
application_id: int,
|
|
531
|
+
*,
|
|
532
|
+
include_all: bool | None = None,
|
|
533
|
+
) -> models.ApplicationResponse:
|
|
534
|
+
"""
|
|
535
|
+
Lookup application.
|
|
536
|
+
"""
|
|
537
|
+
|
|
538
|
+
path = "/v2/applications/{application-id}"
|
|
539
|
+
path = path.replace("{application-id}", str(application_id))
|
|
540
|
+
|
|
541
|
+
params: dict[str, Any] = {}
|
|
542
|
+
headers: Headers = self._config.resolve_headers()
|
|
543
|
+
if include_all is not None:
|
|
544
|
+
params["include-all"] = include_all
|
|
545
|
+
|
|
546
|
+
accept_value: str | None = None
|
|
547
|
+
|
|
548
|
+
headers.setdefault("accept", accept_value or "application/json")
|
|
549
|
+
request_kwargs: dict[str, Any] = {
|
|
550
|
+
"method": "GET",
|
|
551
|
+
"url": path,
|
|
552
|
+
"params": params,
|
|
553
|
+
"headers": headers,
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
response = self._request_with_retry(request_kwargs)
|
|
557
|
+
if response.is_success:
|
|
558
|
+
return self._decode_response(response, model=models.ApplicationResponse)
|
|
559
|
+
|
|
560
|
+
raise UnexpectedStatusError(response.status_code, response.text)
|
|
561
|
+
|
|
562
|
+
def lookup_application_logs_by_id(
|
|
563
|
+
self,
|
|
564
|
+
application_id: int,
|
|
565
|
+
*,
|
|
566
|
+
limit: int | None = None,
|
|
567
|
+
next_: str | None = None,
|
|
568
|
+
txid: str | None = None,
|
|
569
|
+
min_round: int | None = None,
|
|
570
|
+
max_round: int | None = None,
|
|
571
|
+
sender_address: str | None = None,
|
|
572
|
+
) -> models.ApplicationLogsResponse:
|
|
573
|
+
"""
|
|
574
|
+
Lookup application logs.
|
|
575
|
+
"""
|
|
576
|
+
|
|
577
|
+
path = "/v2/applications/{application-id}/logs"
|
|
578
|
+
path = path.replace("{application-id}", str(application_id))
|
|
579
|
+
|
|
580
|
+
params: dict[str, Any] = {}
|
|
581
|
+
headers: Headers = self._config.resolve_headers()
|
|
582
|
+
if limit is not None:
|
|
583
|
+
params["limit"] = limit
|
|
584
|
+
|
|
585
|
+
if next_ is not None:
|
|
586
|
+
params["next"] = next_
|
|
587
|
+
|
|
588
|
+
if txid is not None:
|
|
589
|
+
params["txid"] = txid
|
|
590
|
+
|
|
591
|
+
if min_round is not None:
|
|
592
|
+
params["min-round"] = min_round
|
|
593
|
+
|
|
594
|
+
if max_round is not None:
|
|
595
|
+
params["max-round"] = max_round
|
|
596
|
+
|
|
597
|
+
if sender_address is not None:
|
|
598
|
+
params["sender-address"] = sender_address
|
|
599
|
+
|
|
600
|
+
accept_value: str | None = None
|
|
601
|
+
|
|
602
|
+
headers.setdefault("accept", accept_value or "application/json")
|
|
603
|
+
request_kwargs: dict[str, Any] = {
|
|
604
|
+
"method": "GET",
|
|
605
|
+
"url": path,
|
|
606
|
+
"params": params,
|
|
607
|
+
"headers": headers,
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
response = self._request_with_retry(request_kwargs)
|
|
611
|
+
if response.is_success:
|
|
612
|
+
return self._decode_response(response, model=models.ApplicationLogsResponse)
|
|
613
|
+
|
|
614
|
+
raise UnexpectedStatusError(response.status_code, response.text)
|
|
615
|
+
|
|
616
|
+
def lookup_asset_balances(
|
|
617
|
+
self,
|
|
618
|
+
asset_id: int,
|
|
619
|
+
*,
|
|
620
|
+
include_all: bool | None = None,
|
|
621
|
+
limit: int | None = None,
|
|
622
|
+
next_: str | None = None,
|
|
623
|
+
currency_greater_than: int | None = None,
|
|
624
|
+
currency_less_than: int | None = None,
|
|
625
|
+
) -> models.AssetBalancesResponse:
|
|
626
|
+
"""
|
|
627
|
+
Lookup the list of accounts who hold this asset
|
|
628
|
+
"""
|
|
629
|
+
|
|
630
|
+
path = "/v2/assets/{asset-id}/balances"
|
|
631
|
+
path = path.replace("{asset-id}", str(asset_id))
|
|
632
|
+
|
|
633
|
+
params: dict[str, Any] = {}
|
|
634
|
+
headers: Headers = self._config.resolve_headers()
|
|
635
|
+
if include_all is not None:
|
|
636
|
+
params["include-all"] = include_all
|
|
637
|
+
|
|
638
|
+
if limit is not None:
|
|
639
|
+
params["limit"] = limit
|
|
640
|
+
|
|
641
|
+
if next_ is not None:
|
|
642
|
+
params["next"] = next_
|
|
643
|
+
|
|
644
|
+
if currency_greater_than is not None:
|
|
645
|
+
params["currency-greater-than"] = currency_greater_than
|
|
646
|
+
|
|
647
|
+
if currency_less_than is not None:
|
|
648
|
+
params["currency-less-than"] = currency_less_than
|
|
649
|
+
|
|
650
|
+
accept_value: str | None = None
|
|
651
|
+
|
|
652
|
+
headers.setdefault("accept", accept_value or "application/json")
|
|
653
|
+
request_kwargs: dict[str, Any] = {
|
|
654
|
+
"method": "GET",
|
|
655
|
+
"url": path,
|
|
656
|
+
"params": params,
|
|
657
|
+
"headers": headers,
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
response = self._request_with_retry(request_kwargs)
|
|
661
|
+
if response.is_success:
|
|
662
|
+
return self._decode_response(response, model=models.AssetBalancesResponse)
|
|
663
|
+
|
|
664
|
+
raise UnexpectedStatusError(response.status_code, response.text)
|
|
665
|
+
|
|
666
|
+
def lookup_asset_by_id(
|
|
667
|
+
self,
|
|
668
|
+
asset_id: int,
|
|
669
|
+
*,
|
|
670
|
+
include_all: bool | None = None,
|
|
671
|
+
) -> models.AssetResponse:
|
|
672
|
+
"""
|
|
673
|
+
Lookup asset information.
|
|
674
|
+
"""
|
|
675
|
+
|
|
676
|
+
path = "/v2/assets/{asset-id}"
|
|
677
|
+
path = path.replace("{asset-id}", str(asset_id))
|
|
678
|
+
|
|
679
|
+
params: dict[str, Any] = {}
|
|
680
|
+
headers: Headers = self._config.resolve_headers()
|
|
681
|
+
if include_all is not None:
|
|
682
|
+
params["include-all"] = include_all
|
|
683
|
+
|
|
684
|
+
accept_value: str | None = None
|
|
685
|
+
|
|
686
|
+
headers.setdefault("accept", accept_value or "application/json")
|
|
687
|
+
request_kwargs: dict[str, Any] = {
|
|
688
|
+
"method": "GET",
|
|
689
|
+
"url": path,
|
|
690
|
+
"params": params,
|
|
691
|
+
"headers": headers,
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
response = self._request_with_retry(request_kwargs)
|
|
695
|
+
if response.is_success:
|
|
696
|
+
return self._decode_response(response, model=models.AssetResponse)
|
|
697
|
+
|
|
698
|
+
raise UnexpectedStatusError(response.status_code, response.text)
|
|
699
|
+
|
|
700
|
+
def lookup_asset_transactions( # noqa: C901, PLR0912, PLR0913
|
|
701
|
+
self,
|
|
702
|
+
asset_id: int,
|
|
703
|
+
*,
|
|
704
|
+
limit: int | None = None,
|
|
705
|
+
next_: str | None = None,
|
|
706
|
+
note_prefix: str | None = None,
|
|
707
|
+
tx_type: str | None = None,
|
|
708
|
+
sig_type: str | None = None,
|
|
709
|
+
txid: str | None = None,
|
|
710
|
+
round_: int | None = None,
|
|
711
|
+
min_round: int | None = None,
|
|
712
|
+
max_round: int | None = None,
|
|
713
|
+
before_time: datetime | None = None,
|
|
714
|
+
after_time: datetime | None = None,
|
|
715
|
+
currency_greater_than: int | None = None,
|
|
716
|
+
currency_less_than: int | None = None,
|
|
717
|
+
address: str | None = None,
|
|
718
|
+
address_role: str | None = None,
|
|
719
|
+
exclude_close_to: bool | None = None,
|
|
720
|
+
rekey_to: bool | None = None,
|
|
721
|
+
) -> models.TransactionsResponse:
|
|
722
|
+
"""
|
|
723
|
+
Lookup transactions for an asset. Transactions are returned oldest to newest.
|
|
724
|
+
"""
|
|
725
|
+
|
|
726
|
+
path = "/v2/assets/{asset-id}/transactions"
|
|
727
|
+
path = path.replace("{asset-id}", str(asset_id))
|
|
728
|
+
|
|
729
|
+
params: dict[str, Any] = {}
|
|
730
|
+
headers: Headers = self._config.resolve_headers()
|
|
731
|
+
if limit is not None:
|
|
732
|
+
params["limit"] = limit
|
|
733
|
+
|
|
734
|
+
if next_ is not None:
|
|
735
|
+
params["next"] = next_
|
|
736
|
+
|
|
737
|
+
if note_prefix is not None:
|
|
738
|
+
params["note-prefix"] = note_prefix
|
|
739
|
+
|
|
740
|
+
if tx_type is not None:
|
|
741
|
+
params["tx-type"] = tx_type
|
|
742
|
+
|
|
743
|
+
if sig_type is not None:
|
|
744
|
+
params["sig-type"] = sig_type
|
|
745
|
+
|
|
746
|
+
if txid is not None:
|
|
747
|
+
params["txid"] = txid
|
|
748
|
+
|
|
749
|
+
if round_ is not None:
|
|
750
|
+
params["round"] = round_
|
|
751
|
+
|
|
752
|
+
if min_round is not None:
|
|
753
|
+
params["min-round"] = min_round
|
|
754
|
+
|
|
755
|
+
if max_round is not None:
|
|
756
|
+
params["max-round"] = max_round
|
|
757
|
+
|
|
758
|
+
if before_time is not None:
|
|
759
|
+
params["before-time"] = before_time
|
|
760
|
+
|
|
761
|
+
if after_time is not None:
|
|
762
|
+
params["after-time"] = after_time
|
|
763
|
+
|
|
764
|
+
if currency_greater_than is not None:
|
|
765
|
+
params["currency-greater-than"] = currency_greater_than
|
|
766
|
+
|
|
767
|
+
if currency_less_than is not None:
|
|
768
|
+
params["currency-less-than"] = currency_less_than
|
|
769
|
+
|
|
770
|
+
if address is not None:
|
|
771
|
+
params["address"] = address
|
|
772
|
+
|
|
773
|
+
if address_role is not None:
|
|
774
|
+
params["address-role"] = address_role
|
|
775
|
+
|
|
776
|
+
if exclude_close_to is not None:
|
|
777
|
+
params["exclude-close-to"] = exclude_close_to
|
|
778
|
+
|
|
779
|
+
if rekey_to is not None:
|
|
780
|
+
params["rekey-to"] = rekey_to
|
|
781
|
+
|
|
782
|
+
accept_value: str | None = None
|
|
783
|
+
|
|
784
|
+
headers.setdefault("accept", accept_value or "application/json")
|
|
785
|
+
request_kwargs: dict[str, Any] = {
|
|
786
|
+
"method": "GET",
|
|
787
|
+
"url": path,
|
|
788
|
+
"params": params,
|
|
789
|
+
"headers": headers,
|
|
790
|
+
}
|
|
791
|
+
|
|
792
|
+
response = self._request_with_retry(request_kwargs)
|
|
793
|
+
if response.is_success:
|
|
794
|
+
return self._decode_response(response, model=models.TransactionsResponse)
|
|
795
|
+
|
|
796
|
+
raise UnexpectedStatusError(response.status_code, response.text)
|
|
797
|
+
|
|
798
|
+
def lookup_block(
|
|
799
|
+
self,
|
|
800
|
+
round_number: int,
|
|
801
|
+
*,
|
|
802
|
+
header_only: bool | None = None,
|
|
803
|
+
) -> models.Block:
|
|
804
|
+
"""
|
|
805
|
+
Lookup block.
|
|
806
|
+
"""
|
|
807
|
+
|
|
808
|
+
path = "/v2/blocks/{round-number}"
|
|
809
|
+
path = path.replace("{round-number}", str(round_number))
|
|
810
|
+
|
|
811
|
+
params: dict[str, Any] = {}
|
|
812
|
+
headers: Headers = self._config.resolve_headers()
|
|
813
|
+
if header_only is not None:
|
|
814
|
+
params["header-only"] = header_only
|
|
815
|
+
|
|
816
|
+
accept_value: str | None = None
|
|
817
|
+
|
|
818
|
+
headers.setdefault("accept", accept_value or "application/json")
|
|
819
|
+
request_kwargs: dict[str, Any] = {
|
|
820
|
+
"method": "GET",
|
|
821
|
+
"url": path,
|
|
822
|
+
"params": params,
|
|
823
|
+
"headers": headers,
|
|
824
|
+
}
|
|
825
|
+
|
|
826
|
+
response = self._request_with_retry(request_kwargs)
|
|
827
|
+
if response.is_success:
|
|
828
|
+
return self._decode_response(response, model=models.Block)
|
|
829
|
+
|
|
830
|
+
raise UnexpectedStatusError(response.status_code, response.text)
|
|
831
|
+
|
|
832
|
+
def lookup_transaction_by_id(
|
|
833
|
+
self,
|
|
834
|
+
txid: str,
|
|
835
|
+
) -> models.TransactionResponse:
|
|
836
|
+
"""
|
|
837
|
+
Lookup a single transaction.
|
|
838
|
+
"""
|
|
839
|
+
|
|
840
|
+
path = "/v2/transactions/{txid}"
|
|
841
|
+
path = path.replace("{txid}", str(txid))
|
|
842
|
+
|
|
843
|
+
params: dict[str, Any] = {}
|
|
844
|
+
headers: Headers = self._config.resolve_headers()
|
|
845
|
+
|
|
846
|
+
accept_value: str | None = None
|
|
847
|
+
|
|
848
|
+
headers.setdefault("accept", accept_value or "application/json")
|
|
849
|
+
request_kwargs: dict[str, Any] = {
|
|
850
|
+
"method": "GET",
|
|
851
|
+
"url": path,
|
|
852
|
+
"params": params,
|
|
853
|
+
"headers": headers,
|
|
854
|
+
}
|
|
855
|
+
|
|
856
|
+
response = self._request_with_retry(request_kwargs)
|
|
857
|
+
if response.is_success:
|
|
858
|
+
return self._decode_response(response, model=models.TransactionResponse)
|
|
859
|
+
|
|
860
|
+
raise UnexpectedStatusError(response.status_code, response.text)
|
|
861
|
+
|
|
862
|
+
# search
|
|
863
|
+
|
|
864
|
+
def search_for_accounts( # noqa: C901, PLR0913
|
|
865
|
+
self,
|
|
866
|
+
*,
|
|
867
|
+
asset_id: int | None = None,
|
|
868
|
+
limit: int | None = None,
|
|
869
|
+
next_: str | None = None,
|
|
870
|
+
currency_greater_than: int | None = None,
|
|
871
|
+
include_all: bool | None = None,
|
|
872
|
+
exclude: list[str] | None = None,
|
|
873
|
+
currency_less_than: int | None = None,
|
|
874
|
+
auth_addr: str | None = None,
|
|
875
|
+
round_: int | None = None,
|
|
876
|
+
application_id: int | None = None,
|
|
877
|
+
online_only: bool | None = None,
|
|
878
|
+
) -> models.AccountsResponse:
|
|
879
|
+
"""
|
|
880
|
+
Search for accounts.
|
|
881
|
+
"""
|
|
882
|
+
|
|
883
|
+
path = "/v2/accounts"
|
|
884
|
+
params: dict[str, Any] = {}
|
|
885
|
+
headers: Headers = self._config.resolve_headers()
|
|
886
|
+
if asset_id is not None:
|
|
887
|
+
params["asset-id"] = asset_id
|
|
888
|
+
|
|
889
|
+
if limit is not None:
|
|
890
|
+
params["limit"] = limit
|
|
891
|
+
|
|
892
|
+
if next_ is not None:
|
|
893
|
+
params["next"] = next_
|
|
894
|
+
|
|
895
|
+
if currency_greater_than is not None:
|
|
896
|
+
params["currency-greater-than"] = currency_greater_than
|
|
897
|
+
|
|
898
|
+
if include_all is not None:
|
|
899
|
+
params["include-all"] = include_all
|
|
900
|
+
|
|
901
|
+
if exclude is not None:
|
|
902
|
+
params["exclude"] = exclude
|
|
903
|
+
|
|
904
|
+
if currency_less_than is not None:
|
|
905
|
+
params["currency-less-than"] = currency_less_than
|
|
906
|
+
|
|
907
|
+
if auth_addr is not None:
|
|
908
|
+
params["auth-addr"] = auth_addr
|
|
909
|
+
|
|
910
|
+
if round_ is not None:
|
|
911
|
+
params["round"] = round_
|
|
912
|
+
|
|
913
|
+
if application_id is not None:
|
|
914
|
+
params["application-id"] = application_id
|
|
915
|
+
|
|
916
|
+
if online_only is not None:
|
|
917
|
+
params["online-only"] = online_only
|
|
918
|
+
|
|
919
|
+
accept_value: str | None = None
|
|
920
|
+
|
|
921
|
+
headers.setdefault("accept", accept_value or "application/json")
|
|
922
|
+
request_kwargs: dict[str, Any] = {
|
|
923
|
+
"method": "GET",
|
|
924
|
+
"url": path,
|
|
925
|
+
"params": params,
|
|
926
|
+
"headers": headers,
|
|
927
|
+
}
|
|
928
|
+
|
|
929
|
+
response = self._request_with_retry(request_kwargs)
|
|
930
|
+
if response.is_success:
|
|
931
|
+
return self._decode_response(response, model=models.AccountsResponse)
|
|
932
|
+
|
|
933
|
+
raise UnexpectedStatusError(response.status_code, response.text)
|
|
934
|
+
|
|
935
|
+
def search_for_application_boxes(
|
|
936
|
+
self,
|
|
937
|
+
application_id: int,
|
|
938
|
+
*,
|
|
939
|
+
limit: int | None = None,
|
|
940
|
+
next_: str | None = None,
|
|
941
|
+
) -> models.BoxesResponse:
|
|
942
|
+
"""
|
|
943
|
+
Get box names for a given application.
|
|
944
|
+
"""
|
|
945
|
+
|
|
946
|
+
path = "/v2/applications/{application-id}/boxes"
|
|
947
|
+
path = path.replace("{application-id}", str(application_id))
|
|
948
|
+
|
|
949
|
+
params: dict[str, Any] = {}
|
|
950
|
+
headers: Headers = self._config.resolve_headers()
|
|
951
|
+
if limit is not None:
|
|
952
|
+
params["limit"] = limit
|
|
953
|
+
|
|
954
|
+
if next_ is not None:
|
|
955
|
+
params["next"] = next_
|
|
956
|
+
|
|
957
|
+
accept_value: str | None = None
|
|
958
|
+
|
|
959
|
+
headers.setdefault("accept", accept_value or "application/json")
|
|
960
|
+
request_kwargs: dict[str, Any] = {
|
|
961
|
+
"method": "GET",
|
|
962
|
+
"url": path,
|
|
963
|
+
"params": params,
|
|
964
|
+
"headers": headers,
|
|
965
|
+
}
|
|
966
|
+
|
|
967
|
+
response = self._request_with_retry(request_kwargs)
|
|
968
|
+
if response.is_success:
|
|
969
|
+
return self._decode_response(response, model=models.BoxesResponse)
|
|
970
|
+
|
|
971
|
+
raise UnexpectedStatusError(response.status_code, response.text)
|
|
972
|
+
|
|
973
|
+
def search_for_applications(
|
|
974
|
+
self,
|
|
975
|
+
*,
|
|
976
|
+
application_id: int | None = None,
|
|
977
|
+
creator: str | None = None,
|
|
978
|
+
include_all: bool | None = None,
|
|
979
|
+
limit: int | None = None,
|
|
980
|
+
next_: str | None = None,
|
|
981
|
+
) -> models.ApplicationsResponse:
|
|
982
|
+
"""
|
|
983
|
+
Search for applications
|
|
984
|
+
"""
|
|
985
|
+
|
|
986
|
+
path = "/v2/applications"
|
|
987
|
+
params: dict[str, Any] = {}
|
|
988
|
+
headers: Headers = self._config.resolve_headers()
|
|
989
|
+
if application_id is not None:
|
|
990
|
+
params["application-id"] = application_id
|
|
991
|
+
|
|
992
|
+
if creator is not None:
|
|
993
|
+
params["creator"] = creator
|
|
994
|
+
|
|
995
|
+
if include_all is not None:
|
|
996
|
+
params["include-all"] = include_all
|
|
997
|
+
|
|
998
|
+
if limit is not None:
|
|
999
|
+
params["limit"] = limit
|
|
1000
|
+
|
|
1001
|
+
if next_ is not None:
|
|
1002
|
+
params["next"] = next_
|
|
1003
|
+
|
|
1004
|
+
accept_value: str | None = None
|
|
1005
|
+
|
|
1006
|
+
headers.setdefault("accept", accept_value or "application/json")
|
|
1007
|
+
request_kwargs: dict[str, Any] = {
|
|
1008
|
+
"method": "GET",
|
|
1009
|
+
"url": path,
|
|
1010
|
+
"params": params,
|
|
1011
|
+
"headers": headers,
|
|
1012
|
+
}
|
|
1013
|
+
|
|
1014
|
+
response = self._request_with_retry(request_kwargs)
|
|
1015
|
+
if response.is_success:
|
|
1016
|
+
return self._decode_response(response, model=models.ApplicationsResponse)
|
|
1017
|
+
|
|
1018
|
+
raise UnexpectedStatusError(response.status_code, response.text)
|
|
1019
|
+
|
|
1020
|
+
def search_for_assets(
|
|
1021
|
+
self,
|
|
1022
|
+
*,
|
|
1023
|
+
include_all: bool | None = None,
|
|
1024
|
+
limit: int | None = None,
|
|
1025
|
+
next_: str | None = None,
|
|
1026
|
+
creator: str | None = None,
|
|
1027
|
+
name: str | None = None,
|
|
1028
|
+
unit: str | None = None,
|
|
1029
|
+
asset_id: int | None = None,
|
|
1030
|
+
) -> models.AssetsResponse:
|
|
1031
|
+
"""
|
|
1032
|
+
Search for assets.
|
|
1033
|
+
"""
|
|
1034
|
+
|
|
1035
|
+
path = "/v2/assets"
|
|
1036
|
+
params: dict[str, Any] = {}
|
|
1037
|
+
headers: Headers = self._config.resolve_headers()
|
|
1038
|
+
if include_all is not None:
|
|
1039
|
+
params["include-all"] = include_all
|
|
1040
|
+
|
|
1041
|
+
if limit is not None:
|
|
1042
|
+
params["limit"] = limit
|
|
1043
|
+
|
|
1044
|
+
if next_ is not None:
|
|
1045
|
+
params["next"] = next_
|
|
1046
|
+
|
|
1047
|
+
if creator is not None:
|
|
1048
|
+
params["creator"] = creator
|
|
1049
|
+
|
|
1050
|
+
if name is not None:
|
|
1051
|
+
params["name"] = name
|
|
1052
|
+
|
|
1053
|
+
if unit is not None:
|
|
1054
|
+
params["unit"] = unit
|
|
1055
|
+
|
|
1056
|
+
if asset_id is not None:
|
|
1057
|
+
params["asset-id"] = asset_id
|
|
1058
|
+
|
|
1059
|
+
accept_value: str | None = None
|
|
1060
|
+
|
|
1061
|
+
headers.setdefault("accept", accept_value or "application/json")
|
|
1062
|
+
request_kwargs: dict[str, Any] = {
|
|
1063
|
+
"method": "GET",
|
|
1064
|
+
"url": path,
|
|
1065
|
+
"params": params,
|
|
1066
|
+
"headers": headers,
|
|
1067
|
+
}
|
|
1068
|
+
|
|
1069
|
+
response = self._request_with_retry(request_kwargs)
|
|
1070
|
+
if response.is_success:
|
|
1071
|
+
return self._decode_response(response, model=models.AssetsResponse)
|
|
1072
|
+
|
|
1073
|
+
raise UnexpectedStatusError(response.status_code, response.text)
|
|
1074
|
+
|
|
1075
|
+
def search_for_block_headers( # noqa: C901
|
|
1076
|
+
self,
|
|
1077
|
+
*,
|
|
1078
|
+
limit: int | None = None,
|
|
1079
|
+
next_: str | None = None,
|
|
1080
|
+
min_round: int | None = None,
|
|
1081
|
+
max_round: int | None = None,
|
|
1082
|
+
before_time: datetime | None = None,
|
|
1083
|
+
after_time: datetime | None = None,
|
|
1084
|
+
proposers: list[str] | None = None,
|
|
1085
|
+
expired: list[str] | None = None,
|
|
1086
|
+
absent: list[str] | None = None,
|
|
1087
|
+
) -> models.BlockHeadersResponse:
|
|
1088
|
+
"""
|
|
1089
|
+
Search for block headers. Block headers are returned in ascending round order.
|
|
1090
|
+
Transactions are not included in the output.
|
|
1091
|
+
"""
|
|
1092
|
+
|
|
1093
|
+
path = "/v2/block-headers"
|
|
1094
|
+
params: dict[str, Any] = {}
|
|
1095
|
+
headers: Headers = self._config.resolve_headers()
|
|
1096
|
+
if limit is not None:
|
|
1097
|
+
params["limit"] = limit
|
|
1098
|
+
|
|
1099
|
+
if next_ is not None:
|
|
1100
|
+
params["next"] = next_
|
|
1101
|
+
|
|
1102
|
+
if min_round is not None:
|
|
1103
|
+
params["min-round"] = min_round
|
|
1104
|
+
|
|
1105
|
+
if max_round is not None:
|
|
1106
|
+
params["max-round"] = max_round
|
|
1107
|
+
|
|
1108
|
+
if before_time is not None:
|
|
1109
|
+
params["before-time"] = before_time
|
|
1110
|
+
|
|
1111
|
+
if after_time is not None:
|
|
1112
|
+
params["after-time"] = after_time
|
|
1113
|
+
|
|
1114
|
+
if proposers is not None:
|
|
1115
|
+
params["proposers"] = proposers
|
|
1116
|
+
|
|
1117
|
+
if expired is not None:
|
|
1118
|
+
params["expired"] = expired
|
|
1119
|
+
|
|
1120
|
+
if absent is not None:
|
|
1121
|
+
params["absent"] = absent
|
|
1122
|
+
|
|
1123
|
+
accept_value: str | None = None
|
|
1124
|
+
|
|
1125
|
+
headers.setdefault("accept", accept_value or "application/json")
|
|
1126
|
+
request_kwargs: dict[str, Any] = {
|
|
1127
|
+
"method": "GET",
|
|
1128
|
+
"url": path,
|
|
1129
|
+
"params": params,
|
|
1130
|
+
"headers": headers,
|
|
1131
|
+
}
|
|
1132
|
+
|
|
1133
|
+
response = self._request_with_retry(request_kwargs)
|
|
1134
|
+
if response.is_success:
|
|
1135
|
+
return self._decode_response(response, model=models.BlockHeadersResponse)
|
|
1136
|
+
|
|
1137
|
+
raise UnexpectedStatusError(response.status_code, response.text)
|
|
1138
|
+
|
|
1139
|
+
def search_for_transactions( # noqa: C901, PLR0912, PLR0913
|
|
1140
|
+
self,
|
|
1141
|
+
*,
|
|
1142
|
+
limit: int | None = None,
|
|
1143
|
+
next_: str | None = None,
|
|
1144
|
+
note_prefix: str | None = None,
|
|
1145
|
+
tx_type: str | None = None,
|
|
1146
|
+
sig_type: str | None = None,
|
|
1147
|
+
group_id: str | None = None,
|
|
1148
|
+
txid: str | None = None,
|
|
1149
|
+
round_: int | None = None,
|
|
1150
|
+
min_round: int | None = None,
|
|
1151
|
+
max_round: int | None = None,
|
|
1152
|
+
asset_id: int | None = None,
|
|
1153
|
+
before_time: datetime | None = None,
|
|
1154
|
+
after_time: datetime | None = None,
|
|
1155
|
+
currency_greater_than: int | None = None,
|
|
1156
|
+
currency_less_than: int | None = None,
|
|
1157
|
+
address: str | None = None,
|
|
1158
|
+
address_role: str | None = None,
|
|
1159
|
+
exclude_close_to: bool | None = None,
|
|
1160
|
+
rekey_to: bool | None = None,
|
|
1161
|
+
application_id: int | None = None,
|
|
1162
|
+
) -> models.TransactionsResponse:
|
|
1163
|
+
"""
|
|
1164
|
+
Search for transactions. Transactions are returned oldest to newest unless the address
|
|
1165
|
+
parameter is used, in which case results are returned newest to oldest.
|
|
1166
|
+
"""
|
|
1167
|
+
|
|
1168
|
+
path = "/v2/transactions"
|
|
1169
|
+
params: dict[str, Any] = {}
|
|
1170
|
+
headers: Headers = self._config.resolve_headers()
|
|
1171
|
+
if limit is not None:
|
|
1172
|
+
params["limit"] = limit
|
|
1173
|
+
|
|
1174
|
+
if next_ is not None:
|
|
1175
|
+
params["next"] = next_
|
|
1176
|
+
|
|
1177
|
+
if note_prefix is not None:
|
|
1178
|
+
params["note-prefix"] = note_prefix
|
|
1179
|
+
|
|
1180
|
+
if tx_type is not None:
|
|
1181
|
+
params["tx-type"] = tx_type
|
|
1182
|
+
|
|
1183
|
+
if sig_type is not None:
|
|
1184
|
+
params["sig-type"] = sig_type
|
|
1185
|
+
|
|
1186
|
+
if group_id is not None:
|
|
1187
|
+
params["group-id"] = group_id
|
|
1188
|
+
|
|
1189
|
+
if txid is not None:
|
|
1190
|
+
params["txid"] = txid
|
|
1191
|
+
|
|
1192
|
+
if round_ is not None:
|
|
1193
|
+
params["round"] = round_
|
|
1194
|
+
|
|
1195
|
+
if min_round is not None:
|
|
1196
|
+
params["min-round"] = min_round
|
|
1197
|
+
|
|
1198
|
+
if max_round is not None:
|
|
1199
|
+
params["max-round"] = max_round
|
|
1200
|
+
|
|
1201
|
+
if asset_id is not None:
|
|
1202
|
+
params["asset-id"] = asset_id
|
|
1203
|
+
|
|
1204
|
+
if before_time is not None:
|
|
1205
|
+
params["before-time"] = before_time
|
|
1206
|
+
|
|
1207
|
+
if after_time is not None:
|
|
1208
|
+
params["after-time"] = after_time
|
|
1209
|
+
|
|
1210
|
+
if currency_greater_than is not None:
|
|
1211
|
+
params["currency-greater-than"] = currency_greater_than
|
|
1212
|
+
|
|
1213
|
+
if currency_less_than is not None:
|
|
1214
|
+
params["currency-less-than"] = currency_less_than
|
|
1215
|
+
|
|
1216
|
+
if address is not None:
|
|
1217
|
+
params["address"] = address
|
|
1218
|
+
|
|
1219
|
+
if address_role is not None:
|
|
1220
|
+
params["address-role"] = address_role
|
|
1221
|
+
|
|
1222
|
+
if exclude_close_to is not None:
|
|
1223
|
+
params["exclude-close-to"] = exclude_close_to
|
|
1224
|
+
|
|
1225
|
+
if rekey_to is not None:
|
|
1226
|
+
params["rekey-to"] = rekey_to
|
|
1227
|
+
|
|
1228
|
+
if application_id is not None:
|
|
1229
|
+
params["application-id"] = application_id
|
|
1230
|
+
|
|
1231
|
+
accept_value: str | None = None
|
|
1232
|
+
|
|
1233
|
+
headers.setdefault("accept", accept_value or "application/json")
|
|
1234
|
+
request_kwargs: dict[str, Any] = {
|
|
1235
|
+
"method": "GET",
|
|
1236
|
+
"url": path,
|
|
1237
|
+
"params": params,
|
|
1238
|
+
"headers": headers,
|
|
1239
|
+
}
|
|
1240
|
+
|
|
1241
|
+
response = self._request_with_retry(request_kwargs)
|
|
1242
|
+
if response.is_success:
|
|
1243
|
+
return self._decode_response(response, model=models.TransactionsResponse)
|
|
1244
|
+
|
|
1245
|
+
raise UnexpectedStatusError(response.status_code, response.text)
|
|
1246
|
+
|
|
1247
|
+
def _assign_body(
|
|
1248
|
+
self,
|
|
1249
|
+
request_kwargs: dict[str, Any],
|
|
1250
|
+
payload: object,
|
|
1251
|
+
descriptor: dict[str, object],
|
|
1252
|
+
media_types: list[str],
|
|
1253
|
+
) -> None:
|
|
1254
|
+
encoded = self._encode_payload(payload, descriptor)
|
|
1255
|
+
binary_types = {"application/x-binary", "application/octet-stream"}
|
|
1256
|
+
if bool(descriptor.get("is_binary")) or any(mt in binary_types for mt in media_types):
|
|
1257
|
+
if encoded is None:
|
|
1258
|
+
return
|
|
1259
|
+
request_kwargs["content"] = encoded
|
|
1260
|
+
if media_types:
|
|
1261
|
+
request_kwargs.setdefault("headers", {})["content-type"] = media_types[0]
|
|
1262
|
+
else:
|
|
1263
|
+
request_kwargs.setdefault("headers", {})["content-type"] = "application/octet-stream"
|
|
1264
|
+
elif "application/json" in media_types:
|
|
1265
|
+
request_kwargs["json"] = encoded
|
|
1266
|
+
elif "application/msgpack" in media_types:
|
|
1267
|
+
request_kwargs["content"] = msgpack.packb(encoded, use_bin_type=True)
|
|
1268
|
+
request_kwargs.setdefault("headers", {})["content-type"] = "application/msgpack"
|
|
1269
|
+
else:
|
|
1270
|
+
request_kwargs["json"] = encoded
|
|
1271
|
+
|
|
1272
|
+
def _encode_payload(self, payload: object, descriptor: dict[str, object]) -> object:
|
|
1273
|
+
if payload is None:
|
|
1274
|
+
return None
|
|
1275
|
+
if is_dataclass(payload):
|
|
1276
|
+
return to_wire(payload)
|
|
1277
|
+
list_model = descriptor.get("list_model")
|
|
1278
|
+
if list_model and isinstance(payload, list):
|
|
1279
|
+
return [to_wire(item) if is_dataclass(item) else item for item in payload]
|
|
1280
|
+
return payload
|
|
1281
|
+
|
|
1282
|
+
@overload
|
|
1283
|
+
def _decode_response(
|
|
1284
|
+
self,
|
|
1285
|
+
response: httpx.Response,
|
|
1286
|
+
*,
|
|
1287
|
+
model: type[ModelT],
|
|
1288
|
+
is_binary: bool = False,
|
|
1289
|
+
raw_msgpack: bool = False,
|
|
1290
|
+
) -> ModelT: ...
|
|
1291
|
+
|
|
1292
|
+
@overload
|
|
1293
|
+
def _decode_response(
|
|
1294
|
+
self,
|
|
1295
|
+
response: httpx.Response,
|
|
1296
|
+
*,
|
|
1297
|
+
list_model: type[ListModelT],
|
|
1298
|
+
is_binary: bool = False,
|
|
1299
|
+
raw_msgpack: bool = False,
|
|
1300
|
+
) -> list[ListModelT]: ...
|
|
1301
|
+
|
|
1302
|
+
@overload
|
|
1303
|
+
def _decode_response(
|
|
1304
|
+
self,
|
|
1305
|
+
response: httpx.Response,
|
|
1306
|
+
*,
|
|
1307
|
+
type_: type[PrimitiveT],
|
|
1308
|
+
is_binary: bool = False,
|
|
1309
|
+
raw_msgpack: bool = False,
|
|
1310
|
+
) -> PrimitiveT: ...
|
|
1311
|
+
|
|
1312
|
+
@overload
|
|
1313
|
+
def _decode_response(
|
|
1314
|
+
self,
|
|
1315
|
+
response: httpx.Response,
|
|
1316
|
+
*,
|
|
1317
|
+
is_binary: Literal[True],
|
|
1318
|
+
raw_msgpack: bool = False,
|
|
1319
|
+
) -> bytes: ...
|
|
1320
|
+
|
|
1321
|
+
@overload
|
|
1322
|
+
def _decode_response(
|
|
1323
|
+
self,
|
|
1324
|
+
response: httpx.Response,
|
|
1325
|
+
*,
|
|
1326
|
+
raw_msgpack: Literal[True],
|
|
1327
|
+
) -> bytes: ...
|
|
1328
|
+
|
|
1329
|
+
@overload
|
|
1330
|
+
def _decode_response(
|
|
1331
|
+
self,
|
|
1332
|
+
response: httpx.Response,
|
|
1333
|
+
*,
|
|
1334
|
+
type_: None = None,
|
|
1335
|
+
is_binary: bool = False,
|
|
1336
|
+
raw_msgpack: bool = False,
|
|
1337
|
+
) -> object: ...
|
|
1338
|
+
|
|
1339
|
+
def _decode_response(
|
|
1340
|
+
self,
|
|
1341
|
+
response: httpx.Response,
|
|
1342
|
+
*,
|
|
1343
|
+
model: type[Any] | None = None,
|
|
1344
|
+
list_model: type[Any] | None = None,
|
|
1345
|
+
type_: type[Any] | None = None,
|
|
1346
|
+
is_binary: bool = False,
|
|
1347
|
+
raw_msgpack: bool = False,
|
|
1348
|
+
) -> object:
|
|
1349
|
+
if is_binary or raw_msgpack:
|
|
1350
|
+
return response.content
|
|
1351
|
+
content_type = response.headers.get("content-type", "application/json")
|
|
1352
|
+
if "msgpack" in content_type:
|
|
1353
|
+
# Handle msgpack unpacking with support for unhashable keys
|
|
1354
|
+
# Use Unpacker for more control over the unpacking process
|
|
1355
|
+
unpacker = msgpack.Unpacker(
|
|
1356
|
+
raw=True,
|
|
1357
|
+
strict_map_key=False,
|
|
1358
|
+
object_pairs_hook=self._msgpack_pairs_hook,
|
|
1359
|
+
)
|
|
1360
|
+
unpacker.feed(response.content)
|
|
1361
|
+
try:
|
|
1362
|
+
data = unpacker.unpack()
|
|
1363
|
+
except TypeError:
|
|
1364
|
+
# If unpacking fails due to unhashable keys, try without the hook
|
|
1365
|
+
# and handle in normalization
|
|
1366
|
+
unpacker = msgpack.Unpacker(raw=True, strict_map_key=False)
|
|
1367
|
+
unpacker.feed(response.content)
|
|
1368
|
+
data = unpacker.unpack()
|
|
1369
|
+
data = self._normalize_msgpack(data)
|
|
1370
|
+
elif content_type.startswith("application/json"):
|
|
1371
|
+
data = response.json()
|
|
1372
|
+
else:
|
|
1373
|
+
data = response.text
|
|
1374
|
+
if model is not None:
|
|
1375
|
+
return from_wire(model, data)
|
|
1376
|
+
if list_model is not None:
|
|
1377
|
+
return [from_wire(list_model, item) for item in data]
|
|
1378
|
+
if type_ is not None:
|
|
1379
|
+
return data
|
|
1380
|
+
return data
|
|
1381
|
+
|
|
1382
|
+
def _normalize_msgpack(self, value: object) -> object:
|
|
1383
|
+
# Handle pairs returned from msgpack_pairs_hook when keys are unhashable
|
|
1384
|
+
_pair_length = 2
|
|
1385
|
+
if isinstance(value, list) and value and isinstance(value[0], tuple | list) and len(value[0]) == _pair_length:
|
|
1386
|
+
# Convert to dict with normalized keys
|
|
1387
|
+
pairs_dict: dict[object, object] = {}
|
|
1388
|
+
for pair in value:
|
|
1389
|
+
if isinstance(pair, tuple | list) and len(pair) == _pair_length:
|
|
1390
|
+
k, v = pair
|
|
1391
|
+
# For unhashable keys (like dict keys), use a tuple representation
|
|
1392
|
+
try:
|
|
1393
|
+
normalized_key = self._coerce_msgpack_key(k)
|
|
1394
|
+
pairs_dict[normalized_key] = self._normalize_msgpack(v)
|
|
1395
|
+
except TypeError:
|
|
1396
|
+
# Key is unhashable - use tuple representation
|
|
1397
|
+
normalized_key = ("__unhashable__", id(k), str(k))
|
|
1398
|
+
pairs_dict[normalized_key] = self._normalize_msgpack(v)
|
|
1399
|
+
return pairs_dict
|
|
1400
|
+
if isinstance(value, dict):
|
|
1401
|
+
# Safely normalize maps: coerce string/bytes keys, but tolerate complex/unhashable keys
|
|
1402
|
+
try:
|
|
1403
|
+
normalized_dict: dict[object, object] = {}
|
|
1404
|
+
for key, item in value.items():
|
|
1405
|
+
normalized_dict[self._coerce_msgpack_key(key)] = self._normalize_msgpack(item)
|
|
1406
|
+
return normalized_dict
|
|
1407
|
+
except TypeError:
|
|
1408
|
+
# Some maps can decode to object/dict keys; keep original keys and
|
|
1409
|
+
# only normalize values to avoid "unhashable type: 'dict'" errors.
|
|
1410
|
+
for k, item in list(value.items()):
|
|
1411
|
+
value[k] = self._normalize_msgpack(item)
|
|
1412
|
+
return value
|
|
1413
|
+
if isinstance(value, list):
|
|
1414
|
+
return [self._normalize_msgpack(item) for item in value]
|
|
1415
|
+
return value
|
|
1416
|
+
|
|
1417
|
+
def _coerce_msgpack_key(self, key: object) -> object:
|
|
1418
|
+
if isinstance(key, bytes):
|
|
1419
|
+
try:
|
|
1420
|
+
return key.decode("utf-8")
|
|
1421
|
+
except UnicodeDecodeError:
|
|
1422
|
+
return key
|
|
1423
|
+
return key
|
|
1424
|
+
|
|
1425
|
+
def _msgpack_pairs_hook(self, pairs: list[tuple[object, object]] | list[list[object]]) -> dict[object, object]:
|
|
1426
|
+
# Convert pairs to dict, handling unhashable keys by converting them to hashable tuples
|
|
1427
|
+
out: dict[object, object] = {}
|
|
1428
|
+
_hashable_type_tuple = (str, int, float, bool, type(None), bytes)
|
|
1429
|
+
|
|
1430
|
+
for k, v in pairs:
|
|
1431
|
+
if isinstance(k, dict | list | set):
|
|
1432
|
+
# Convert unhashable key to hashable tuple
|
|
1433
|
+
hashable_key: tuple[str, object]
|
|
1434
|
+
if isinstance(k, dict):
|
|
1435
|
+
try:
|
|
1436
|
+
hashable_key = (_UNHASHABLE_PREFIXES["dict"], tuple(sorted(k.items())))
|
|
1437
|
+
except TypeError:
|
|
1438
|
+
hashable_key = (_UNHASHABLE_PREFIXES["dict"], str(k))
|
|
1439
|
+
elif isinstance(k, list):
|
|
1440
|
+
prefix = _UNHASHABLE_PREFIXES["list"]
|
|
1441
|
+
hashable_key = (prefix, tuple(k) if all(isinstance(x, _hashable_type_tuple) for x in k) else str(k))
|
|
1442
|
+
else: # set
|
|
1443
|
+
prefix = _UNHASHABLE_PREFIXES["set"]
|
|
1444
|
+
if all(isinstance(x, _hashable_type_tuple) for x in k):
|
|
1445
|
+
hashable_key = (prefix, tuple(sorted(k)))
|
|
1446
|
+
else:
|
|
1447
|
+
hashable_key = (prefix, str(k))
|
|
1448
|
+
out[hashable_key] = v
|
|
1449
|
+
else:
|
|
1450
|
+
# Key should be hashable, use as-is
|
|
1451
|
+
try:
|
|
1452
|
+
out[k] = v
|
|
1453
|
+
except TypeError:
|
|
1454
|
+
# Unexpected unhashable type, convert to tuple
|
|
1455
|
+
out[(_UNHASHABLE_PREFIXES["generic"], str(type(k).__name__), str(k))] = v
|
|
1456
|
+
return out
|