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.
Files changed (337) hide show
  1. algokit_abi/__init__.py +9 -0
  2. algokit_abi/_arc32_to_arc56.py +242 -0
  3. algokit_abi/_arc56_serde.py +161 -0
  4. algokit_abi/abi.py +667 -0
  5. algokit_abi/arc32.py +210 -0
  6. algokit_abi/arc56.py +821 -0
  7. algokit_abi/py.typed +0 -0
  8. algokit_algo25/__init__.py +38 -0
  9. algokit_algo25/_encoding.py +46 -0
  10. algokit_algo25/_wordlist.py +2065 -0
  11. algokit_algo25/exceptions.py +29 -0
  12. algokit_algo25/mnemonic.py +128 -0
  13. algokit_algo25/py.typed +0 -0
  14. algokit_algod_client/__init__.py +10 -0
  15. algokit_algod_client/client.py +1585 -0
  16. algokit_algod_client/config.py +36 -0
  17. algokit_algod_client/exceptions.py +59 -0
  18. algokit_algod_client/models/__init__.py +229 -0
  19. algokit_algod_client/models/_account.py +150 -0
  20. algokit_algod_client/models/_account_application_response.py +25 -0
  21. algokit_algod_client/models/_account_asset_response.py +25 -0
  22. algokit_algod_client/models/_account_participation.py +53 -0
  23. algokit_algod_client/models/_account_state_delta.py +30 -0
  24. algokit_algod_client/models/_allocations_for_genesis_file.py +23 -0
  25. algokit_algod_client/models/_allocations_for_genesis_file_state_model.py +42 -0
  26. algokit_algod_client/models/_application.py +23 -0
  27. algokit_algod_client/models/_application_initial_states.py +37 -0
  28. algokit_algod_client/models/_application_kvstorage.py +29 -0
  29. algokit_algod_client/models/_application_local_state.py +33 -0
  30. algokit_algod_client/models/_application_params.py +63 -0
  31. algokit_algod_client/models/_application_state_operation.py +41 -0
  32. algokit_algod_client/models/_application_state_schema.py +22 -0
  33. algokit_algod_client/models/_asset.py +23 -0
  34. algokit_algod_client/models/_asset_holding.py +29 -0
  35. algokit_algod_client/models/_asset_params.py +102 -0
  36. algokit_algod_client/models/_avm_key_value.py +28 -0
  37. algokit_algod_client/models/_avm_value.py +32 -0
  38. algokit_algod_client/models/_block.py +363 -0
  39. algokit_algod_client/models/_block_hash_response.py +14 -0
  40. algokit_algod_client/models/_block_txids_response.py +14 -0
  41. algokit_algod_client/models/_box.py +36 -0
  42. algokit_algod_client/models/_box_descriptor.py +24 -0
  43. algokit_algod_client/models/_boxes_response.py +21 -0
  44. algokit_algod_client/models/_build_version_contains_the_current_algod_build_version_information.py +34 -0
  45. algokit_algod_client/models/_compile_response.py +24 -0
  46. algokit_algod_client/models/_disassemble_response.py +14 -0
  47. algokit_algod_client/models/_error_response.py +22 -0
  48. algokit_algod_client/models/_eval_delta.py +32 -0
  49. algokit_algod_client/models/_eval_delta_key_value.py +28 -0
  50. algokit_algod_client/models/_genesis_file_in_json.py +53 -0
  51. algokit_algod_client/models/_get_block_time_stamp_offset_response.py +14 -0
  52. algokit_algod_client/models/_get_sync_round_response.py +14 -0
  53. algokit_algod_client/models/_ledger_state_delta.py +389 -0
  54. algokit_algod_client/models/_light_block_header_proof.py +32 -0
  55. algokit_algod_client/models/_node_status_response.py +118 -0
  56. algokit_algod_client/models/_pending_transaction_response.py +91 -0
  57. algokit_algod_client/models/_pending_transactions_response.py +29 -0
  58. algokit_algod_client/models/_post_transactions_response.py +14 -0
  59. algokit_algod_client/models/_scratch_change.py +23 -0
  60. algokit_algod_client/models/_serde_helpers.py +241 -0
  61. algokit_algod_client/models/_simulate_initial_states.py +25 -0
  62. algokit_algod_client/models/_simulate_request.py +54 -0
  63. algokit_algod_client/models/_simulate_request_transaction_group.py +25 -0
  64. algokit_algod_client/models/_simulate_response.py +44 -0
  65. algokit_algod_client/models/_simulate_trace_config.py +30 -0
  66. algokit_algod_client/models/_simulate_transaction_group_result.py +46 -0
  67. algokit_algod_client/models/_simulate_transaction_result.py +41 -0
  68. algokit_algod_client/models/_simulate_unnamed_resources_accessed.py +64 -0
  69. algokit_algod_client/models/_simulation_eval_overrides.py +40 -0
  70. algokit_algod_client/models/_simulation_opcode_trace_unit.py +55 -0
  71. algokit_algod_client/models/_simulation_transaction_exec_trace.py +82 -0
  72. algokit_algod_client/models/_source_map.py +30 -0
  73. algokit_algod_client/models/_state_delta.py +6 -0
  74. algokit_algod_client/models/_state_proof.py +28 -0
  75. algokit_algod_client/models/_state_proof_message.py +44 -0
  76. algokit_algod_client/models/_supply_response.py +26 -0
  77. algokit_algod_client/models/_teal_key_value.py +28 -0
  78. algokit_algod_client/models/_teal_key_value_store.py +6 -0
  79. algokit_algod_client/models/_teal_value.py +32 -0
  80. algokit_algod_client/models/_transaction_group_ledger_state_deltas_for_round_response.py +21 -0
  81. algokit_algod_client/models/_transaction_parameters_response.py +45 -0
  82. algokit_algod_client/models/_transaction_proof.py +44 -0
  83. algokit_algod_client/models/_version_contains_the_current_algod_version.py +38 -0
  84. algokit_algod_client/models/suggested_params.py +42 -0
  85. algokit_algod_client/py.typed +1 -0
  86. algokit_algod_client/types.py +7 -0
  87. algokit_algosdk/__init__.py +38 -0
  88. algokit_algosdk/account.py +32 -0
  89. algokit_algosdk/app_access.py +228 -0
  90. algokit_algosdk/box_reference.py +100 -0
  91. algokit_algosdk/constants.py +147 -0
  92. algokit_algosdk/encoding.py +89 -0
  93. algokit_algosdk/error.py +180 -0
  94. algokit_algosdk/logic.py +61 -0
  95. algokit_algosdk/logicsig.py +218 -0
  96. algokit_algosdk/mnemonic.py +216 -0
  97. algokit_algosdk/multisig.py +161 -0
  98. algokit_algosdk/py.typed +0 -0
  99. algokit_algosdk/transaction.py +596 -0
  100. algokit_algosdk/wordlist.py +2054 -0
  101. algokit_common/__init__.py +50 -0
  102. algokit_common/address.py +34 -0
  103. algokit_common/constants.py +47 -0
  104. algokit_common/hashing.py +25 -0
  105. algokit_common/py.typed +0 -0
  106. algokit_common/serde/__init__.py +40 -0
  107. algokit_common/serde/_core.py +610 -0
  108. algokit_common/serde/_primitives.py +135 -0
  109. algokit_common/source_map.py +158 -0
  110. algokit_indexer_client/__init__.py +10 -0
  111. algokit_indexer_client/client.py +1456 -0
  112. algokit_indexer_client/config.py +36 -0
  113. algokit_indexer_client/exceptions.py +59 -0
  114. algokit_indexer_client/models/__init__.py +148 -0
  115. algokit_indexer_client/models/_account.py +161 -0
  116. algokit_indexer_client/models/_account_participation.py +53 -0
  117. algokit_indexer_client/models/_account_response.py +19 -0
  118. algokit_indexer_client/models/_account_state_delta.py +29 -0
  119. algokit_indexer_client/models/_accounts_response.py +29 -0
  120. algokit_indexer_client/models/_application.py +35 -0
  121. algokit_indexer_client/models/_application_local_state.py +45 -0
  122. algokit_indexer_client/models/_application_local_states_response.py +29 -0
  123. algokit_indexer_client/models/_application_log_data.py +28 -0
  124. algokit_indexer_client/models/_application_logs_response.py +33 -0
  125. algokit_indexer_client/models/_application_params.py +62 -0
  126. algokit_indexer_client/models/_application_response.py +20 -0
  127. algokit_indexer_client/models/_application_state_schema.py +22 -0
  128. algokit_indexer_client/models/_applications_response.py +29 -0
  129. algokit_indexer_client/models/_asset.py +35 -0
  130. algokit_indexer_client/models/_asset_balances_response.py +29 -0
  131. algokit_indexer_client/models/_asset_holding.py +41 -0
  132. algokit_indexer_client/models/_asset_holdings_response.py +29 -0
  133. algokit_indexer_client/models/_asset_params.py +102 -0
  134. algokit_indexer_client/models/_asset_response.py +19 -0
  135. algokit_indexer_client/models/_assets_response.py +29 -0
  136. algokit_indexer_client/models/_block.py +150 -0
  137. algokit_indexer_client/models/_block_headers_response.py +29 -0
  138. algokit_indexer_client/models/_block_rewards.py +38 -0
  139. algokit_indexer_client/models/_block_upgrade_state.py +34 -0
  140. algokit_indexer_client/models/_block_upgrade_vote.py +26 -0
  141. algokit_indexer_client/models/_box.py +36 -0
  142. algokit_indexer_client/models/_box_descriptor.py +24 -0
  143. algokit_indexer_client/models/_box_reference.py +28 -0
  144. algokit_indexer_client/models/_boxes_response.py +29 -0
  145. algokit_indexer_client/models/_error_response.py +18 -0
  146. algokit_indexer_client/models/_eval_delta.py +32 -0
  147. algokit_indexer_client/models/_eval_delta_key_value.py +28 -0
  148. algokit_indexer_client/models/_hash_factory.py +14 -0
  149. algokit_indexer_client/models/_hb_proof_fields.py +57 -0
  150. algokit_indexer_client/models/_health_check.py +42 -0
  151. algokit_indexer_client/models/_holding_ref.py +23 -0
  152. algokit_indexer_client/models/_indexer_state_proof_message.py +40 -0
  153. algokit_indexer_client/models/_locals_ref.py +23 -0
  154. algokit_indexer_client/models/_merkle_array_proof.py +29 -0
  155. algokit_indexer_client/models/_mini_asset_holding.py +38 -0
  156. algokit_indexer_client/models/_on_completion.py +25 -0
  157. algokit_indexer_client/models/_participation_updates.py +22 -0
  158. algokit_indexer_client/models/_resource_ref.py +42 -0
  159. algokit_indexer_client/models/_serde_helpers.py +241 -0
  160. algokit_indexer_client/models/_state_delta.py +6 -0
  161. algokit_indexer_client/models/_state_proof_fields.py +57 -0
  162. algokit_indexer_client/models/_state_proof_participant.py +20 -0
  163. algokit_indexer_client/models/_state_proof_reveal.py +25 -0
  164. algokit_indexer_client/models/_state_proof_sig_slot.py +20 -0
  165. algokit_indexer_client/models/_state_proof_signature.py +37 -0
  166. algokit_indexer_client/models/_state_proof_tracking.py +32 -0
  167. algokit_indexer_client/models/_state_proof_verifier.py +24 -0
  168. algokit_indexer_client/models/_state_schema.py +25 -0
  169. algokit_indexer_client/models/_teal_key_value.py +28 -0
  170. algokit_indexer_client/models/_teal_key_value_store.py +6 -0
  171. algokit_indexer_client/models/_teal_value.py +32 -0
  172. algokit_indexer_client/models/_transaction.py +213 -0
  173. algokit_indexer_client/models/_transaction_application.py +105 -0
  174. algokit_indexer_client/models/_transaction_asset_config.py +31 -0
  175. algokit_indexer_client/models/_transaction_asset_freeze.py +29 -0
  176. algokit_indexer_client/models/_transaction_asset_transfer.py +41 -0
  177. algokit_indexer_client/models/_transaction_heartbeat.py +52 -0
  178. algokit_indexer_client/models/_transaction_keyreg.py +59 -0
  179. algokit_indexer_client/models/_transaction_payment.py +33 -0
  180. algokit_indexer_client/models/_transaction_response.py +19 -0
  181. algokit_indexer_client/models/_transaction_signature.py +35 -0
  182. algokit_indexer_client/models/_transaction_signature_logicsig.py +59 -0
  183. algokit_indexer_client/models/_transaction_signature_multisig.py +36 -0
  184. algokit_indexer_client/models/_transaction_signature_multisig_subsignature.py +28 -0
  185. algokit_indexer_client/models/_transaction_state_proof.py +32 -0
  186. algokit_indexer_client/models/_transactions_response.py +29 -0
  187. algokit_indexer_client/py.typed +1 -0
  188. algokit_indexer_client/types.py +7 -0
  189. algokit_kmd_client/__init__.py +10 -0
  190. algokit_kmd_client/client.py +1240 -0
  191. algokit_kmd_client/config.py +36 -0
  192. algokit_kmd_client/exceptions.py +59 -0
  193. algokit_kmd_client/models/__init__.py +112 -0
  194. algokit_kmd_client/models/_classical_signatures.py +4 -0
  195. algokit_kmd_client/models/_create_wallet_request.py +30 -0
  196. algokit_kmd_client/models/_create_wallet_response.py +19 -0
  197. algokit_kmd_client/models/_delete_key_request.py +27 -0
  198. algokit_kmd_client/models/_delete_multisig_request.py +27 -0
  199. algokit_kmd_client/models/_digest_represents_a32_byte_value_holding_the256_bit_hash_digest.py +4 -0
  200. algokit_kmd_client/models/_ed25519_public_key.py +4 -0
  201. algokit_kmd_client/models/_export_key_request.py +27 -0
  202. algokit_kmd_client/models/_export_key_response.py +24 -0
  203. algokit_kmd_client/models/_export_master_key_request.py +22 -0
  204. algokit_kmd_client/models/_export_master_key_response.py +18 -0
  205. algokit_kmd_client/models/_export_multisig_request.py +23 -0
  206. algokit_kmd_client/models/_export_multisig_response.py +26 -0
  207. algokit_kmd_client/models/_generate_key_request.py +18 -0
  208. algokit_kmd_client/models/_generate_key_response.py +19 -0
  209. algokit_kmd_client/models/_import_key_request.py +28 -0
  210. algokit_kmd_client/models/_import_key_response.py +19 -0
  211. algokit_kmd_client/models/_import_multisig_request.py +30 -0
  212. algokit_kmd_client/models/_import_multisig_response.py +19 -0
  213. algokit_kmd_client/models/_init_wallet_handle_token_request.py +22 -0
  214. algokit_kmd_client/models/_init_wallet_handle_token_response.py +18 -0
  215. algokit_kmd_client/models/_list_keys_request.py +18 -0
  216. algokit_kmd_client/models/_list_keys_response.py +18 -0
  217. algokit_kmd_client/models/_list_multisig_request.py +18 -0
  218. algokit_kmd_client/models/_list_multisig_response.py +18 -0
  219. algokit_kmd_client/models/_list_wallets_request.py +11 -0
  220. algokit_kmd_client/models/_list_wallets_response.py +25 -0
  221. algokit_kmd_client/models/_master_derivation_key.py +4 -0
  222. algokit_kmd_client/models/_multisig_sig.py +33 -0
  223. algokit_kmd_client/models/_multisig_subsig.py +23 -0
  224. algokit_kmd_client/models/_public_key.py +4 -0
  225. algokit_kmd_client/models/_release_wallet_handle_token_request.py +18 -0
  226. algokit_kmd_client/models/_rename_wallet_request.py +26 -0
  227. algokit_kmd_client/models/_rename_wallet_response.py +19 -0
  228. algokit_kmd_client/models/_renew_wallet_handle_token_request.py +18 -0
  229. algokit_kmd_client/models/_renew_wallet_handle_token_response.py +19 -0
  230. algokit_kmd_client/models/_serde_helpers.py +241 -0
  231. algokit_kmd_client/models/_sign_multisig_response.py +24 -0
  232. algokit_kmd_client/models/_sign_multisig_txn_request.py +45 -0
  233. algokit_kmd_client/models/_sign_program_multisig_request.py +50 -0
  234. algokit_kmd_client/models/_sign_program_multisig_response.py +24 -0
  235. algokit_kmd_client/models/_sign_program_request.py +37 -0
  236. algokit_kmd_client/models/_sign_program_response.py +24 -0
  237. algokit_kmd_client/models/_sign_transaction_response.py +24 -0
  238. algokit_kmd_client/models/_sign_txn_request.py +36 -0
  239. algokit_kmd_client/models/_signature.py +4 -0
  240. algokit_kmd_client/models/_tx_type.py +4 -0
  241. algokit_kmd_client/models/_versions_request.py +11 -0
  242. algokit_kmd_client/models/_versions_response.py +19 -0
  243. algokit_kmd_client/models/_wallet.py +38 -0
  244. algokit_kmd_client/models/_wallet_handle.py +24 -0
  245. algokit_kmd_client/models/_wallet_info_request.py +18 -0
  246. algokit_kmd_client/models/_wallet_info_response.py +19 -0
  247. algokit_kmd_client/py.typed +1 -0
  248. algokit_kmd_client/types.py +7 -0
  249. algokit_transact/__init__.py +190 -0
  250. algokit_transact/codec/__init__.py +0 -0
  251. algokit_transact/codec/msgpack.py +11 -0
  252. algokit_transact/codec/serde.py +7 -0
  253. algokit_transact/codec/signed.py +57 -0
  254. algokit_transact/codec/transaction.py +65 -0
  255. algokit_transact/exceptions.py +17 -0
  256. algokit_transact/logicsig.py +220 -0
  257. algokit_transact/models/__init__.py +0 -0
  258. algokit_transact/models/app_call.py +447 -0
  259. algokit_transact/models/asset_config.py +19 -0
  260. algokit_transact/models/asset_freeze.py +11 -0
  261. algokit_transact/models/asset_transfer.py +13 -0
  262. algokit_transact/models/common.py +17 -0
  263. algokit_transact/models/heartbeat.py +21 -0
  264. algokit_transact/models/key_registration.py +14 -0
  265. algokit_transact/models/payment.py +14 -0
  266. algokit_transact/models/signed_transaction.py +21 -0
  267. algokit_transact/models/state_proof.py +150 -0
  268. algokit_transact/models/transaction.py +88 -0
  269. algokit_transact/multisig.py +93 -0
  270. algokit_transact/ops/__init__.py +0 -0
  271. algokit_transact/ops/fees.py +47 -0
  272. algokit_transact/ops/group.py +28 -0
  273. algokit_transact/ops/ids.py +14 -0
  274. algokit_transact/ops/validate.py +503 -0
  275. algokit_transact/py.typed +0 -0
  276. algokit_transact/signer.py +195 -0
  277. algokit_transact/signing/__init__.py +0 -0
  278. algokit_transact/signing/logic_signature.py +19 -0
  279. algokit_transact/signing/multisig.py +84 -0
  280. algokit_transact/signing/types.py +39 -0
  281. algokit_transact/signing/validation.py +63 -0
  282. algokit_utils/__init__.py +23 -0
  283. algokit_utils/_debugging.py +304 -0
  284. algokit_utils/accounts/__init__.py +2 -0
  285. algokit_utils/accounts/account_manager.py +1051 -0
  286. algokit_utils/accounts/kmd_account_manager.py +206 -0
  287. algokit_utils/algo25.py +46 -0
  288. algokit_utils/algorand.py +383 -0
  289. algokit_utils/applications/__init__.py +7 -0
  290. algokit_utils/applications/abi.py +280 -0
  291. algokit_utils/applications/app_client.py +2193 -0
  292. algokit_utils/applications/app_deployer.py +788 -0
  293. algokit_utils/applications/app_factory.py +1140 -0
  294. algokit_utils/applications/app_manager.py +575 -0
  295. algokit_utils/applications/app_spec/__init__.py +6 -0
  296. algokit_utils/applications/enums.py +40 -0
  297. algokit_utils/assets/__init__.py +1 -0
  298. algokit_utils/assets/asset_manager.py +344 -0
  299. algokit_utils/clients/__init__.py +41 -0
  300. algokit_utils/clients/client_manager.py +756 -0
  301. algokit_utils/clients/dispenser_api_client.py +212 -0
  302. algokit_utils/common.py +40 -0
  303. algokit_utils/config.py +159 -0
  304. algokit_utils/errors/__init__.py +1 -0
  305. algokit_utils/errors/logic_error.py +160 -0
  306. algokit_utils/models/__init__.py +7 -0
  307. algokit_utils/models/account.py +12 -0
  308. algokit_utils/models/amount.py +198 -0
  309. algokit_utils/models/application.py +90 -0
  310. algokit_utils/models/network.py +29 -0
  311. algokit_utils/models/simulate.py +7 -0
  312. algokit_utils/models/state.py +53 -0
  313. algokit_utils/models/transaction.py +49 -0
  314. algokit_utils/protocols/__init__.py +3 -0
  315. algokit_utils/protocols/account.py +11 -0
  316. algokit_utils/protocols/signer.py +17 -0
  317. algokit_utils/protocols/typed_clients.py +110 -0
  318. algokit_utils/py.typed +0 -0
  319. algokit_utils/transact.py +195 -0
  320. algokit_utils/transactions/__init__.py +3 -0
  321. algokit_utils/transactions/builders/__init__.py +67 -0
  322. algokit_utils/transactions/builders/app.py +248 -0
  323. algokit_utils/transactions/builders/asset.py +256 -0
  324. algokit_utils/transactions/builders/common.py +263 -0
  325. algokit_utils/transactions/builders/keyreg.py +103 -0
  326. algokit_utils/transactions/builders/method_call.py +380 -0
  327. algokit_utils/transactions/builders/payment.py +43 -0
  328. algokit_utils/transactions/composer_resources.py +409 -0
  329. algokit_utils/transactions/fee_coverage.py +79 -0
  330. algokit_utils/transactions/helpers.py +9 -0
  331. algokit_utils/transactions/transaction_composer.py +1574 -0
  332. algokit_utils/transactions/transaction_creator.py +699 -0
  333. algokit_utils/transactions/transaction_sender.py +1240 -0
  334. algokit_utils/transactions/types.py +262 -0
  335. algokit_utils-5.0.0a3.dist-info/METADATA +105 -0
  336. algokit_utils-5.0.0a3.dist-info/RECORD +337 -0
  337. 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