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,1240 @@
1
+ # AUTO-GENERATED: oas_generator
2
+ import random
3
+ import time
4
+ from dataclasses import is_dataclass
5
+ from typing import Any, Literal, TypeVar, overload
6
+
7
+ import httpx
8
+ import msgpack
9
+
10
+ from algokit_common.serde import from_wire, to_wire
11
+
12
+ from . import models
13
+ from .config import ClientConfig
14
+ from .exceptions import UnexpectedStatusError
15
+ from .types import Headers
16
+
17
+ # HTTP status codes that warrant a retry (aligned with algokit-utils-ts)
18
+ _RETRY_STATUS_CODES: frozenset[int] = frozenset({408, 413, 429, 500, 502, 503, 504})
19
+ # Network error codes that warrant a retry (aligned with algokit-utils-ts)
20
+ _RETRY_ERROR_CODES: frozenset[str] = frozenset(
21
+ {
22
+ "ETIMEDOUT",
23
+ "ECONNRESET",
24
+ "EADDRINUSE",
25
+ "ECONNREFUSED",
26
+ "EPIPE",
27
+ "ENOTFOUND",
28
+ "ENETUNREACH",
29
+ "EAI_AGAIN",
30
+ "EPROTO",
31
+ }
32
+ )
33
+ _MAX_BACKOFF_MS: float = 10_000.0
34
+ _DEFAULT_MAX_TRIES: int = 5
35
+
36
+ ModelT = TypeVar("ModelT")
37
+ ListModelT = TypeVar("ListModelT")
38
+ PrimitiveT = TypeVar("PrimitiveT")
39
+
40
+ # Prefixed markers used when converting unhashable msgpack map keys into hashable tuples
41
+ _UNHASHABLE_PREFIXES: dict[str, str] = {
42
+ "dict": "__dict_key__",
43
+ "list": "__list_key__",
44
+ "set": "__set_key__",
45
+ "generic": "__unhashable__",
46
+ }
47
+
48
+
49
+ class KmdClient:
50
+ def __init__(self, config: ClientConfig | None = None, *, http_client: httpx.Client | None = None) -> None:
51
+ self._config = config or ClientConfig()
52
+ # Track whether a custom HTTP client was provided to avoid retry conflicts
53
+ self._uses_custom_client = http_client is not None
54
+ self._client = http_client or httpx.Client(
55
+ base_url=self._config.base_url,
56
+ timeout=self._config.timeout,
57
+ verify=self._config.verify,
58
+ )
59
+
60
+ def close(self) -> None:
61
+ self._client.close()
62
+
63
+ def _calculate_max_tries(self) -> int:
64
+ """Calculate maximum number of tries from config.max_retries."""
65
+ max_retries = self._config.max_retries
66
+ if not isinstance(max_retries, int) or max_retries < 0:
67
+ return _DEFAULT_MAX_TRIES
68
+ return max_retries + 1
69
+
70
+ def _should_retry(self, error: Exception | None, status_code: int | None, attempt: int, max_tries: int) -> bool:
71
+ """Determine if a request should be retried based on error/status and attempt count."""
72
+ if attempt >= max_tries:
73
+ return False
74
+
75
+ # Check HTTP status code
76
+ if status_code is not None and status_code in _RETRY_STATUS_CODES:
77
+ return True
78
+
79
+ # Check network error codes (aligned with algokit-utils-ts)
80
+ if error is not None:
81
+ error_code = self._extract_error_code(error)
82
+ if error_code and error_code in _RETRY_ERROR_CODES:
83
+ return True
84
+
85
+ return False
86
+
87
+ def _extract_error_code(self, error: BaseException) -> str | None:
88
+ """Extract error code from exception, checking common attributes."""
89
+ # Check for 'code' attribute (common in OS/network errors)
90
+ if hasattr(error, "code") and isinstance(error.code, str):
91
+ return error.code
92
+ # Check for errno attribute
93
+ if hasattr(error, "errno") and error.errno is not None:
94
+ import errno as errno_module
95
+
96
+ try:
97
+ return errno_module.errorcode.get(error.errno)
98
+ except (TypeError, AttributeError):
99
+ pass
100
+ # Check __cause__ for wrapped errors
101
+ if error.__cause__ is not None:
102
+ return self._extract_error_code(error.__cause__)
103
+ return None
104
+
105
+ def _request_with_retry(self, request_kwargs: dict[str, Any]) -> httpx.Response:
106
+ """Execute request with exponential backoff retry for transient failures.
107
+
108
+ When a custom HTTP client is provided, retries are disabled to avoid
109
+ conflicts with any retry mechanism the custom client may implement.
110
+ """
111
+ # Disable retries when using a custom HTTP client to avoid conflicts
112
+ # with the client's own retry mechanism
113
+ if self._uses_custom_client:
114
+ return self._client.request(**request_kwargs)
115
+
116
+ max_tries = self._calculate_max_tries()
117
+ attempt = 1
118
+ last_error: Exception | None = None
119
+
120
+ while attempt <= max_tries:
121
+ status_code: int | None = None
122
+ try:
123
+ response = self._client.request(**request_kwargs)
124
+ status_code = response.status_code
125
+ if not self._should_retry(None, status_code, attempt, max_tries):
126
+ return response
127
+ except httpx.TransportError as exc:
128
+ last_error = exc
129
+ if not self._should_retry(exc, None, attempt, max_tries):
130
+ raise
131
+
132
+ if attempt == 1:
133
+ backoff_ms = 0.0
134
+ else:
135
+ base_backoff = min(1000.0 * (2 ** (attempt - 1)), _MAX_BACKOFF_MS)
136
+ jitter = 0.5 + random.random() # Random value between 0.5 and 1.5
137
+ backoff_ms = base_backoff * jitter
138
+ if backoff_ms > 0:
139
+ time.sleep(backoff_ms / 1000.0)
140
+ attempt += 1
141
+
142
+ # Should not reach here, but satisfy type checker
143
+ if last_error:
144
+ raise last_error
145
+ raise RuntimeError(f"Request failed after {max_tries} attempt(s)")
146
+
147
+ # default
148
+
149
+ def create_wallet(
150
+ self,
151
+ body: models.CreateWalletRequest,
152
+ ) -> models.CreateWalletResponse:
153
+ """
154
+ Create a wallet
155
+ """
156
+
157
+ path = "/v1/wallet"
158
+ params: dict[str, Any] = {}
159
+ headers: Headers = self._config.resolve_headers()
160
+
161
+ accept_value: str | None = None
162
+
163
+ body_media_types = ["application/json"]
164
+
165
+ headers.setdefault("accept", accept_value or "application/json")
166
+ request_kwargs: dict[str, Any] = {
167
+ "method": "POST",
168
+ "url": path,
169
+ "params": params,
170
+ "headers": headers,
171
+ }
172
+
173
+ if body is not None:
174
+ self._assign_body(
175
+ request_kwargs,
176
+ body,
177
+ {
178
+ "model": "CreateWalletRequest",
179
+ },
180
+ body_media_types,
181
+ )
182
+
183
+ response = self._request_with_retry(request_kwargs)
184
+ if response.is_success:
185
+ return self._decode_response(response, model=models.CreateWalletResponse)
186
+
187
+ raise UnexpectedStatusError(response.status_code, response.text)
188
+
189
+ def delete_key(
190
+ self,
191
+ body: models.DeleteKeyRequest,
192
+ ) -> None:
193
+ """
194
+ Delete a key
195
+ """
196
+
197
+ path = "/v1/key"
198
+ params: dict[str, Any] = {}
199
+ headers: Headers = self._config.resolve_headers()
200
+
201
+ accept_value: str | None = None
202
+
203
+ body_media_types = ["application/json"]
204
+
205
+ headers.setdefault("accept", accept_value or "application/json")
206
+ request_kwargs: dict[str, Any] = {
207
+ "method": "DELETE",
208
+ "url": path,
209
+ "params": params,
210
+ "headers": headers,
211
+ }
212
+
213
+ if body is not None:
214
+ self._assign_body(
215
+ request_kwargs,
216
+ body,
217
+ {
218
+ "model": "DeleteKeyRequest",
219
+ },
220
+ body_media_types,
221
+ )
222
+
223
+ response = self._request_with_retry(request_kwargs)
224
+ if response.is_success:
225
+ return
226
+
227
+ raise UnexpectedStatusError(response.status_code, response.text)
228
+
229
+ def delete_multisig(
230
+ self,
231
+ body: models.DeleteMultisigRequest,
232
+ ) -> None:
233
+ """
234
+ Delete a multisig
235
+ """
236
+
237
+ path = "/v1/multisig"
238
+ params: dict[str, Any] = {}
239
+ headers: Headers = self._config.resolve_headers()
240
+
241
+ accept_value: str | None = None
242
+
243
+ body_media_types = ["application/json"]
244
+
245
+ headers.setdefault("accept", accept_value or "application/json")
246
+ request_kwargs: dict[str, Any] = {
247
+ "method": "DELETE",
248
+ "url": path,
249
+ "params": params,
250
+ "headers": headers,
251
+ }
252
+
253
+ if body is not None:
254
+ self._assign_body(
255
+ request_kwargs,
256
+ body,
257
+ {
258
+ "model": "DeleteMultisigRequest",
259
+ },
260
+ body_media_types,
261
+ )
262
+
263
+ response = self._request_with_retry(request_kwargs)
264
+ if response.is_success:
265
+ return
266
+
267
+ raise UnexpectedStatusError(response.status_code, response.text)
268
+
269
+ def export_key(
270
+ self,
271
+ body: models.ExportKeyRequest,
272
+ ) -> models.ExportKeyResponse:
273
+ """
274
+ Export a key
275
+ """
276
+
277
+ path = "/v1/key/export"
278
+ params: dict[str, Any] = {}
279
+ headers: Headers = self._config.resolve_headers()
280
+
281
+ accept_value: str | None = None
282
+
283
+ body_media_types = ["application/json"]
284
+
285
+ headers.setdefault("accept", accept_value or "application/json")
286
+ request_kwargs: dict[str, Any] = {
287
+ "method": "POST",
288
+ "url": path,
289
+ "params": params,
290
+ "headers": headers,
291
+ }
292
+
293
+ if body is not None:
294
+ self._assign_body(
295
+ request_kwargs,
296
+ body,
297
+ {
298
+ "model": "ExportKeyRequest",
299
+ },
300
+ body_media_types,
301
+ )
302
+
303
+ response = self._request_with_retry(request_kwargs)
304
+ if response.is_success:
305
+ return self._decode_response(response, model=models.ExportKeyResponse)
306
+
307
+ raise UnexpectedStatusError(response.status_code, response.text)
308
+
309
+ def export_master_key(
310
+ self,
311
+ body: models.ExportMasterKeyRequest,
312
+ ) -> models.ExportMasterKeyResponse:
313
+ """
314
+ Export the master derivation key from a wallet
315
+ """
316
+
317
+ path = "/v1/master-key/export"
318
+ params: dict[str, Any] = {}
319
+ headers: Headers = self._config.resolve_headers()
320
+
321
+ accept_value: str | None = None
322
+
323
+ body_media_types = ["application/json"]
324
+
325
+ headers.setdefault("accept", accept_value or "application/json")
326
+ request_kwargs: dict[str, Any] = {
327
+ "method": "POST",
328
+ "url": path,
329
+ "params": params,
330
+ "headers": headers,
331
+ }
332
+
333
+ if body is not None:
334
+ self._assign_body(
335
+ request_kwargs,
336
+ body,
337
+ {
338
+ "model": "ExportMasterKeyRequest",
339
+ },
340
+ body_media_types,
341
+ )
342
+
343
+ response = self._request_with_retry(request_kwargs)
344
+ if response.is_success:
345
+ return self._decode_response(response, model=models.ExportMasterKeyResponse)
346
+
347
+ raise UnexpectedStatusError(response.status_code, response.text)
348
+
349
+ def export_multisig(
350
+ self,
351
+ body: models.ExportMultisigRequest,
352
+ ) -> models.ExportMultisigResponse:
353
+ """
354
+ Export multisig address metadata
355
+ """
356
+
357
+ path = "/v1/multisig/export"
358
+ params: dict[str, Any] = {}
359
+ headers: Headers = self._config.resolve_headers()
360
+
361
+ accept_value: str | None = None
362
+
363
+ body_media_types = ["application/json"]
364
+
365
+ headers.setdefault("accept", accept_value or "application/json")
366
+ request_kwargs: dict[str, Any] = {
367
+ "method": "POST",
368
+ "url": path,
369
+ "params": params,
370
+ "headers": headers,
371
+ }
372
+
373
+ if body is not None:
374
+ self._assign_body(
375
+ request_kwargs,
376
+ body,
377
+ {
378
+ "model": "ExportMultisigRequest",
379
+ },
380
+ body_media_types,
381
+ )
382
+
383
+ response = self._request_with_retry(request_kwargs)
384
+ if response.is_success:
385
+ return self._decode_response(response, model=models.ExportMultisigResponse)
386
+
387
+ raise UnexpectedStatusError(response.status_code, response.text)
388
+
389
+ def generate_key(
390
+ self,
391
+ body: models.GenerateKeyRequest,
392
+ ) -> models.GenerateKeyResponse:
393
+ """
394
+ Generate a key
395
+ """
396
+
397
+ path = "/v1/key"
398
+ params: dict[str, Any] = {}
399
+ headers: Headers = self._config.resolve_headers()
400
+
401
+ accept_value: str | None = None
402
+
403
+ body_media_types = ["application/json"]
404
+
405
+ headers.setdefault("accept", accept_value or "application/json")
406
+ request_kwargs: dict[str, Any] = {
407
+ "method": "POST",
408
+ "url": path,
409
+ "params": params,
410
+ "headers": headers,
411
+ }
412
+
413
+ if body is not None:
414
+ self._assign_body(
415
+ request_kwargs,
416
+ body,
417
+ {
418
+ "model": "GenerateKeyRequest",
419
+ },
420
+ body_media_types,
421
+ )
422
+
423
+ response = self._request_with_retry(request_kwargs)
424
+ if response.is_success:
425
+ return self._decode_response(response, model=models.GenerateKeyResponse)
426
+
427
+ raise UnexpectedStatusError(response.status_code, response.text)
428
+
429
+ def get_version(
430
+ self,
431
+ *,
432
+ body: models.VersionsRequest | None = None,
433
+ ) -> models.VersionsResponse:
434
+ """
435
+ Retrieves the current version
436
+ """
437
+
438
+ path = "/versions"
439
+ params: dict[str, Any] = {}
440
+ headers: Headers = self._config.resolve_headers()
441
+
442
+ accept_value: str | None = None
443
+
444
+ body_media_types = ["application/json"]
445
+
446
+ headers.setdefault("accept", accept_value or "application/json")
447
+ request_kwargs: dict[str, Any] = {
448
+ "method": "GET",
449
+ "url": path,
450
+ "params": params,
451
+ "headers": headers,
452
+ }
453
+
454
+ if body is not None:
455
+ self._assign_body(
456
+ request_kwargs,
457
+ body,
458
+ {
459
+ "model": "VersionsRequest",
460
+ },
461
+ body_media_types,
462
+ )
463
+
464
+ response = self._request_with_retry(request_kwargs)
465
+ if response.is_success:
466
+ return self._decode_response(response, model=models.VersionsResponse)
467
+
468
+ raise UnexpectedStatusError(response.status_code, response.text)
469
+
470
+ def get_wallet_info(
471
+ self,
472
+ body: models.WalletInfoRequest,
473
+ ) -> models.WalletInfoResponse:
474
+ """
475
+ Get wallet info
476
+ """
477
+
478
+ path = "/v1/wallet/info"
479
+ params: dict[str, Any] = {}
480
+ headers: Headers = self._config.resolve_headers()
481
+
482
+ accept_value: str | None = None
483
+
484
+ body_media_types = ["application/json"]
485
+
486
+ headers.setdefault("accept", accept_value or "application/json")
487
+ request_kwargs: dict[str, Any] = {
488
+ "method": "POST",
489
+ "url": path,
490
+ "params": params,
491
+ "headers": headers,
492
+ }
493
+
494
+ if body is not None:
495
+ self._assign_body(
496
+ request_kwargs,
497
+ body,
498
+ {
499
+ "model": "WalletInfoRequest",
500
+ },
501
+ body_media_types,
502
+ )
503
+
504
+ response = self._request_with_retry(request_kwargs)
505
+ if response.is_success:
506
+ return self._decode_response(response, model=models.WalletInfoResponse)
507
+
508
+ raise UnexpectedStatusError(response.status_code, response.text)
509
+
510
+ def import_key(
511
+ self,
512
+ body: models.ImportKeyRequest,
513
+ ) -> models.ImportKeyResponse:
514
+ """
515
+ Import a key
516
+ """
517
+
518
+ path = "/v1/key/import"
519
+ params: dict[str, Any] = {}
520
+ headers: Headers = self._config.resolve_headers()
521
+
522
+ accept_value: str | None = None
523
+
524
+ body_media_types = ["application/json"]
525
+
526
+ headers.setdefault("accept", accept_value or "application/json")
527
+ request_kwargs: dict[str, Any] = {
528
+ "method": "POST",
529
+ "url": path,
530
+ "params": params,
531
+ "headers": headers,
532
+ }
533
+
534
+ if body is not None:
535
+ self._assign_body(
536
+ request_kwargs,
537
+ body,
538
+ {
539
+ "model": "ImportKeyRequest",
540
+ },
541
+ body_media_types,
542
+ )
543
+
544
+ response = self._request_with_retry(request_kwargs)
545
+ if response.is_success:
546
+ return self._decode_response(response, model=models.ImportKeyResponse)
547
+
548
+ raise UnexpectedStatusError(response.status_code, response.text)
549
+
550
+ def import_multisig(
551
+ self,
552
+ body: models.ImportMultisigRequest,
553
+ ) -> models.ImportMultisigResponse:
554
+ """
555
+ Import a multisig account
556
+ """
557
+
558
+ path = "/v1/multisig/import"
559
+ params: dict[str, Any] = {}
560
+ headers: Headers = self._config.resolve_headers()
561
+
562
+ accept_value: str | None = None
563
+
564
+ body_media_types = ["application/json"]
565
+
566
+ headers.setdefault("accept", accept_value or "application/json")
567
+ request_kwargs: dict[str, Any] = {
568
+ "method": "POST",
569
+ "url": path,
570
+ "params": params,
571
+ "headers": headers,
572
+ }
573
+
574
+ if body is not None:
575
+ self._assign_body(
576
+ request_kwargs,
577
+ body,
578
+ {
579
+ "model": "ImportMultisigRequest",
580
+ },
581
+ body_media_types,
582
+ )
583
+
584
+ response = self._request_with_retry(request_kwargs)
585
+ if response.is_success:
586
+ return self._decode_response(response, model=models.ImportMultisigResponse)
587
+
588
+ raise UnexpectedStatusError(response.status_code, response.text)
589
+
590
+ def init_wallet_handle(
591
+ self,
592
+ body: models.InitWalletHandleTokenRequest,
593
+ ) -> models.InitWalletHandleTokenResponse:
594
+ """
595
+ Initialize a wallet handle token
596
+ """
597
+
598
+ path = "/v1/wallet/init"
599
+ params: dict[str, Any] = {}
600
+ headers: Headers = self._config.resolve_headers()
601
+
602
+ accept_value: str | None = None
603
+
604
+ body_media_types = ["application/json"]
605
+
606
+ headers.setdefault("accept", accept_value or "application/json")
607
+ request_kwargs: dict[str, Any] = {
608
+ "method": "POST",
609
+ "url": path,
610
+ "params": params,
611
+ "headers": headers,
612
+ }
613
+
614
+ if body is not None:
615
+ self._assign_body(
616
+ request_kwargs,
617
+ body,
618
+ {
619
+ "model": "InitWalletHandleTokenRequest",
620
+ },
621
+ body_media_types,
622
+ )
623
+
624
+ response = self._request_with_retry(request_kwargs)
625
+ if response.is_success:
626
+ return self._decode_response(response, model=models.InitWalletHandleTokenResponse)
627
+
628
+ raise UnexpectedStatusError(response.status_code, response.text)
629
+
630
+ def list_keys_in_wallet(
631
+ self,
632
+ body: models.ListKeysRequest,
633
+ ) -> models.ListKeysResponse:
634
+ """
635
+ List keys in wallet
636
+ """
637
+
638
+ path = "/v1/key/list"
639
+ params: dict[str, Any] = {}
640
+ headers: Headers = self._config.resolve_headers()
641
+
642
+ accept_value: str | None = None
643
+
644
+ body_media_types = ["application/json"]
645
+
646
+ headers.setdefault("accept", accept_value or "application/json")
647
+ request_kwargs: dict[str, Any] = {
648
+ "method": "POST",
649
+ "url": path,
650
+ "params": params,
651
+ "headers": headers,
652
+ }
653
+
654
+ if body is not None:
655
+ self._assign_body(
656
+ request_kwargs,
657
+ body,
658
+ {
659
+ "model": "ListKeysRequest",
660
+ },
661
+ body_media_types,
662
+ )
663
+
664
+ response = self._request_with_retry(request_kwargs)
665
+ if response.is_success:
666
+ return self._decode_response(response, model=models.ListKeysResponse)
667
+
668
+ raise UnexpectedStatusError(response.status_code, response.text)
669
+
670
+ def list_multisig(
671
+ self,
672
+ body: models.ListMultisigRequest,
673
+ ) -> models.ListMultisigResponse:
674
+ """
675
+ List multisig accounts
676
+ """
677
+
678
+ path = "/v1/multisig/list"
679
+ params: dict[str, Any] = {}
680
+ headers: Headers = self._config.resolve_headers()
681
+
682
+ accept_value: str | None = None
683
+
684
+ body_media_types = ["application/json"]
685
+
686
+ headers.setdefault("accept", accept_value or "application/json")
687
+ request_kwargs: dict[str, Any] = {
688
+ "method": "POST",
689
+ "url": path,
690
+ "params": params,
691
+ "headers": headers,
692
+ }
693
+
694
+ if body is not None:
695
+ self._assign_body(
696
+ request_kwargs,
697
+ body,
698
+ {
699
+ "model": "ListMultisigRequest",
700
+ },
701
+ body_media_types,
702
+ )
703
+
704
+ response = self._request_with_retry(request_kwargs)
705
+ if response.is_success:
706
+ return self._decode_response(response, model=models.ListMultisigResponse)
707
+
708
+ raise UnexpectedStatusError(response.status_code, response.text)
709
+
710
+ def list_wallets(
711
+ self,
712
+ *,
713
+ body: models.ListWalletsRequest | None = None,
714
+ ) -> models.ListWalletsResponse:
715
+ """
716
+ List wallets
717
+ """
718
+
719
+ path = "/v1/wallets"
720
+ params: dict[str, Any] = {}
721
+ headers: Headers = self._config.resolve_headers()
722
+
723
+ accept_value: str | None = None
724
+
725
+ body_media_types = ["application/json"]
726
+
727
+ headers.setdefault("accept", accept_value or "application/json")
728
+ request_kwargs: dict[str, Any] = {
729
+ "method": "GET",
730
+ "url": path,
731
+ "params": params,
732
+ "headers": headers,
733
+ }
734
+
735
+ if body is not None:
736
+ self._assign_body(
737
+ request_kwargs,
738
+ body,
739
+ {
740
+ "model": "ListWalletsRequest",
741
+ },
742
+ body_media_types,
743
+ )
744
+
745
+ response = self._request_with_retry(request_kwargs)
746
+ if response.is_success:
747
+ return self._decode_response(response, model=models.ListWalletsResponse)
748
+
749
+ raise UnexpectedStatusError(response.status_code, response.text)
750
+
751
+ def release_wallet_handle_token(
752
+ self,
753
+ body: models.ReleaseWalletHandleTokenRequest,
754
+ ) -> None:
755
+ """
756
+ Release a wallet handle token
757
+ """
758
+
759
+ path = "/v1/wallet/release"
760
+ params: dict[str, Any] = {}
761
+ headers: Headers = self._config.resolve_headers()
762
+
763
+ accept_value: str | None = None
764
+
765
+ body_media_types = ["application/json"]
766
+
767
+ headers.setdefault("accept", accept_value or "application/json")
768
+ request_kwargs: dict[str, Any] = {
769
+ "method": "POST",
770
+ "url": path,
771
+ "params": params,
772
+ "headers": headers,
773
+ }
774
+
775
+ if body is not None:
776
+ self._assign_body(
777
+ request_kwargs,
778
+ body,
779
+ {
780
+ "model": "ReleaseWalletHandleTokenRequest",
781
+ },
782
+ body_media_types,
783
+ )
784
+
785
+ response = self._request_with_retry(request_kwargs)
786
+ if response.is_success:
787
+ return
788
+
789
+ raise UnexpectedStatusError(response.status_code, response.text)
790
+
791
+ def rename_wallet(
792
+ self,
793
+ body: models.RenameWalletRequest,
794
+ ) -> models.RenameWalletResponse:
795
+ """
796
+ Rename a wallet
797
+ """
798
+
799
+ path = "/v1/wallet/rename"
800
+ params: dict[str, Any] = {}
801
+ headers: Headers = self._config.resolve_headers()
802
+
803
+ accept_value: str | None = None
804
+
805
+ body_media_types = ["application/json"]
806
+
807
+ headers.setdefault("accept", accept_value or "application/json")
808
+ request_kwargs: dict[str, Any] = {
809
+ "method": "POST",
810
+ "url": path,
811
+ "params": params,
812
+ "headers": headers,
813
+ }
814
+
815
+ if body is not None:
816
+ self._assign_body(
817
+ request_kwargs,
818
+ body,
819
+ {
820
+ "model": "RenameWalletRequest",
821
+ },
822
+ body_media_types,
823
+ )
824
+
825
+ response = self._request_with_retry(request_kwargs)
826
+ if response.is_success:
827
+ return self._decode_response(response, model=models.RenameWalletResponse)
828
+
829
+ raise UnexpectedStatusError(response.status_code, response.text)
830
+
831
+ def renew_wallet_handle_token(
832
+ self,
833
+ body: models.RenewWalletHandleTokenRequest,
834
+ ) -> models.RenewWalletHandleTokenResponse:
835
+ """
836
+ Renew a wallet handle token
837
+ """
838
+
839
+ path = "/v1/wallet/renew"
840
+ params: dict[str, Any] = {}
841
+ headers: Headers = self._config.resolve_headers()
842
+
843
+ accept_value: str | None = None
844
+
845
+ body_media_types = ["application/json"]
846
+
847
+ headers.setdefault("accept", accept_value or "application/json")
848
+ request_kwargs: dict[str, Any] = {
849
+ "method": "POST",
850
+ "url": path,
851
+ "params": params,
852
+ "headers": headers,
853
+ }
854
+
855
+ if body is not None:
856
+ self._assign_body(
857
+ request_kwargs,
858
+ body,
859
+ {
860
+ "model": "RenewWalletHandleTokenRequest",
861
+ },
862
+ body_media_types,
863
+ )
864
+
865
+ response = self._request_with_retry(request_kwargs)
866
+ if response.is_success:
867
+ return self._decode_response(response, model=models.RenewWalletHandleTokenResponse)
868
+
869
+ raise UnexpectedStatusError(response.status_code, response.text)
870
+
871
+ def sign_multisig_program(
872
+ self,
873
+ body: models.SignProgramMultisigRequest,
874
+ ) -> models.SignProgramMultisigResponse:
875
+ """
876
+ Sign a program for a multisig account
877
+ """
878
+
879
+ path = "/v1/multisig/signprogram"
880
+ params: dict[str, Any] = {}
881
+ headers: Headers = self._config.resolve_headers()
882
+
883
+ accept_value: str | None = None
884
+
885
+ body_media_types = ["application/json"]
886
+
887
+ headers.setdefault("accept", accept_value or "application/json")
888
+ request_kwargs: dict[str, Any] = {
889
+ "method": "POST",
890
+ "url": path,
891
+ "params": params,
892
+ "headers": headers,
893
+ }
894
+
895
+ if body is not None:
896
+ self._assign_body(
897
+ request_kwargs,
898
+ body,
899
+ {
900
+ "model": "SignProgramMultisigRequest",
901
+ },
902
+ body_media_types,
903
+ )
904
+
905
+ response = self._request_with_retry(request_kwargs)
906
+ if response.is_success:
907
+ return self._decode_response(response, model=models.SignProgramMultisigResponse)
908
+
909
+ raise UnexpectedStatusError(response.status_code, response.text)
910
+
911
+ def sign_multisig_transaction(
912
+ self,
913
+ body: models.SignMultisigTxnRequest,
914
+ ) -> models.SignMultisigResponse:
915
+ """
916
+ Sign a multisig transaction
917
+ """
918
+
919
+ path = "/v1/multisig/sign"
920
+ params: dict[str, Any] = {}
921
+ headers: Headers = self._config.resolve_headers()
922
+
923
+ accept_value: str | None = None
924
+
925
+ body_media_types = ["application/json"]
926
+
927
+ headers.setdefault("accept", accept_value or "application/json")
928
+ request_kwargs: dict[str, Any] = {
929
+ "method": "POST",
930
+ "url": path,
931
+ "params": params,
932
+ "headers": headers,
933
+ }
934
+
935
+ if body is not None:
936
+ self._assign_body(
937
+ request_kwargs,
938
+ body,
939
+ {
940
+ "model": "SignMultisigTxnRequest",
941
+ },
942
+ body_media_types,
943
+ )
944
+
945
+ response = self._request_with_retry(request_kwargs)
946
+ if response.is_success:
947
+ return self._decode_response(response, model=models.SignMultisigResponse)
948
+
949
+ raise UnexpectedStatusError(response.status_code, response.text)
950
+
951
+ def sign_program(
952
+ self,
953
+ body: models.SignProgramRequest,
954
+ ) -> models.SignProgramResponse:
955
+ """
956
+ Sign program
957
+ """
958
+
959
+ path = "/v1/program/sign"
960
+ params: dict[str, Any] = {}
961
+ headers: Headers = self._config.resolve_headers()
962
+
963
+ accept_value: str | None = None
964
+
965
+ body_media_types = ["application/json"]
966
+
967
+ headers.setdefault("accept", accept_value or "application/json")
968
+ request_kwargs: dict[str, Any] = {
969
+ "method": "POST",
970
+ "url": path,
971
+ "params": params,
972
+ "headers": headers,
973
+ }
974
+
975
+ if body is not None:
976
+ self._assign_body(
977
+ request_kwargs,
978
+ body,
979
+ {
980
+ "model": "SignProgramRequest",
981
+ },
982
+ body_media_types,
983
+ )
984
+
985
+ response = self._request_with_retry(request_kwargs)
986
+ if response.is_success:
987
+ return self._decode_response(response, model=models.SignProgramResponse)
988
+
989
+ raise UnexpectedStatusError(response.status_code, response.text)
990
+
991
+ def sign_transaction(
992
+ self,
993
+ body: models.SignTxnRequest,
994
+ ) -> models.SignTransactionResponse:
995
+ """
996
+ Sign a transaction
997
+ """
998
+
999
+ path = "/v1/transaction/sign"
1000
+ params: dict[str, Any] = {}
1001
+ headers: Headers = self._config.resolve_headers()
1002
+
1003
+ accept_value: str | None = None
1004
+
1005
+ body_media_types = ["application/json"]
1006
+
1007
+ headers.setdefault("accept", accept_value or "application/json")
1008
+ request_kwargs: dict[str, Any] = {
1009
+ "method": "POST",
1010
+ "url": path,
1011
+ "params": params,
1012
+ "headers": headers,
1013
+ }
1014
+
1015
+ if body is not None:
1016
+ self._assign_body(
1017
+ request_kwargs,
1018
+ body,
1019
+ {
1020
+ "model": "SignTxnRequest",
1021
+ },
1022
+ body_media_types,
1023
+ )
1024
+
1025
+ response = self._request_with_retry(request_kwargs)
1026
+ if response.is_success:
1027
+ return self._decode_response(response, model=models.SignTransactionResponse)
1028
+
1029
+ raise UnexpectedStatusError(response.status_code, response.text)
1030
+
1031
+ def _assign_body(
1032
+ self,
1033
+ request_kwargs: dict[str, Any],
1034
+ payload: object,
1035
+ descriptor: dict[str, object],
1036
+ media_types: list[str],
1037
+ ) -> None:
1038
+ encoded = self._encode_payload(payload, descriptor)
1039
+ binary_types = {"application/x-binary", "application/octet-stream"}
1040
+ if bool(descriptor.get("is_binary")) or any(mt in binary_types for mt in media_types):
1041
+ if encoded is None:
1042
+ return
1043
+ request_kwargs["content"] = encoded
1044
+ if media_types:
1045
+ request_kwargs.setdefault("headers", {})["content-type"] = media_types[0]
1046
+ else:
1047
+ request_kwargs.setdefault("headers", {})["content-type"] = "application/octet-stream"
1048
+ elif "application/json" in media_types:
1049
+ request_kwargs["json"] = encoded
1050
+ elif "application/msgpack" in media_types:
1051
+ request_kwargs["content"] = msgpack.packb(encoded, use_bin_type=True)
1052
+ request_kwargs.setdefault("headers", {})["content-type"] = "application/msgpack"
1053
+ else:
1054
+ request_kwargs["json"] = encoded
1055
+
1056
+ def _encode_payload(self, payload: object, descriptor: dict[str, object]) -> object:
1057
+ if payload is None:
1058
+ return None
1059
+ if is_dataclass(payload):
1060
+ return to_wire(payload)
1061
+ list_model = descriptor.get("list_model")
1062
+ if list_model and isinstance(payload, list):
1063
+ return [to_wire(item) if is_dataclass(item) else item for item in payload]
1064
+ return payload
1065
+
1066
+ @overload
1067
+ def _decode_response(
1068
+ self,
1069
+ response: httpx.Response,
1070
+ *,
1071
+ model: type[ModelT],
1072
+ is_binary: bool = False,
1073
+ raw_msgpack: bool = False,
1074
+ ) -> ModelT: ...
1075
+
1076
+ @overload
1077
+ def _decode_response(
1078
+ self,
1079
+ response: httpx.Response,
1080
+ *,
1081
+ list_model: type[ListModelT],
1082
+ is_binary: bool = False,
1083
+ raw_msgpack: bool = False,
1084
+ ) -> list[ListModelT]: ...
1085
+
1086
+ @overload
1087
+ def _decode_response(
1088
+ self,
1089
+ response: httpx.Response,
1090
+ *,
1091
+ type_: type[PrimitiveT],
1092
+ is_binary: bool = False,
1093
+ raw_msgpack: bool = False,
1094
+ ) -> PrimitiveT: ...
1095
+
1096
+ @overload
1097
+ def _decode_response(
1098
+ self,
1099
+ response: httpx.Response,
1100
+ *,
1101
+ is_binary: Literal[True],
1102
+ raw_msgpack: bool = False,
1103
+ ) -> bytes: ...
1104
+
1105
+ @overload
1106
+ def _decode_response(
1107
+ self,
1108
+ response: httpx.Response,
1109
+ *,
1110
+ raw_msgpack: Literal[True],
1111
+ ) -> bytes: ...
1112
+
1113
+ @overload
1114
+ def _decode_response(
1115
+ self,
1116
+ response: httpx.Response,
1117
+ *,
1118
+ type_: None = None,
1119
+ is_binary: bool = False,
1120
+ raw_msgpack: bool = False,
1121
+ ) -> object: ...
1122
+
1123
+ def _decode_response(
1124
+ self,
1125
+ response: httpx.Response,
1126
+ *,
1127
+ model: type[Any] | None = None,
1128
+ list_model: type[Any] | None = None,
1129
+ type_: type[Any] | None = None,
1130
+ is_binary: bool = False,
1131
+ raw_msgpack: bool = False,
1132
+ ) -> object:
1133
+ if is_binary or raw_msgpack:
1134
+ return response.content
1135
+ content_type = response.headers.get("content-type", "application/json")
1136
+ if "msgpack" in content_type:
1137
+ # Handle msgpack unpacking with support for unhashable keys
1138
+ # Use Unpacker for more control over the unpacking process
1139
+ unpacker = msgpack.Unpacker(
1140
+ raw=True,
1141
+ strict_map_key=False,
1142
+ object_pairs_hook=self._msgpack_pairs_hook,
1143
+ )
1144
+ unpacker.feed(response.content)
1145
+ try:
1146
+ data = unpacker.unpack()
1147
+ except TypeError:
1148
+ # If unpacking fails due to unhashable keys, try without the hook
1149
+ # and handle in normalization
1150
+ unpacker = msgpack.Unpacker(raw=True, strict_map_key=False)
1151
+ unpacker.feed(response.content)
1152
+ data = unpacker.unpack()
1153
+ data = self._normalize_msgpack(data)
1154
+ elif content_type.startswith("application/json"):
1155
+ data = response.json()
1156
+ else:
1157
+ data = response.text
1158
+ if model is not None:
1159
+ return from_wire(model, data)
1160
+ if list_model is not None:
1161
+ return [from_wire(list_model, item) for item in data]
1162
+ if type_ is not None:
1163
+ return data
1164
+ return data
1165
+
1166
+ def _normalize_msgpack(self, value: object) -> object:
1167
+ # Handle pairs returned from msgpack_pairs_hook when keys are unhashable
1168
+ _pair_length = 2
1169
+ if isinstance(value, list) and value and isinstance(value[0], tuple | list) and len(value[0]) == _pair_length:
1170
+ # Convert to dict with normalized keys
1171
+ pairs_dict: dict[object, object] = {}
1172
+ for pair in value:
1173
+ if isinstance(pair, tuple | list) and len(pair) == _pair_length:
1174
+ k, v = pair
1175
+ # For unhashable keys (like dict keys), use a tuple representation
1176
+ try:
1177
+ normalized_key = self._coerce_msgpack_key(k)
1178
+ pairs_dict[normalized_key] = self._normalize_msgpack(v)
1179
+ except TypeError:
1180
+ # Key is unhashable - use tuple representation
1181
+ normalized_key = ("__unhashable__", id(k), str(k))
1182
+ pairs_dict[normalized_key] = self._normalize_msgpack(v)
1183
+ return pairs_dict
1184
+ if isinstance(value, dict):
1185
+ # Safely normalize maps: coerce string/bytes keys, but tolerate complex/unhashable keys
1186
+ try:
1187
+ normalized_dict: dict[object, object] = {}
1188
+ for key, item in value.items():
1189
+ normalized_dict[self._coerce_msgpack_key(key)] = self._normalize_msgpack(item)
1190
+ return normalized_dict
1191
+ except TypeError:
1192
+ # Some maps can decode to object/dict keys; keep original keys and
1193
+ # only normalize values to avoid "unhashable type: 'dict'" errors.
1194
+ for k, item in list(value.items()):
1195
+ value[k] = self._normalize_msgpack(item)
1196
+ return value
1197
+ if isinstance(value, list):
1198
+ return [self._normalize_msgpack(item) for item in value]
1199
+ return value
1200
+
1201
+ def _coerce_msgpack_key(self, key: object) -> object:
1202
+ if isinstance(key, bytes):
1203
+ try:
1204
+ return key.decode("utf-8")
1205
+ except UnicodeDecodeError:
1206
+ return key
1207
+ return key
1208
+
1209
+ def _msgpack_pairs_hook(self, pairs: list[tuple[object, object]] | list[list[object]]) -> dict[object, object]:
1210
+ # Convert pairs to dict, handling unhashable keys by converting them to hashable tuples
1211
+ out: dict[object, object] = {}
1212
+ _hashable_type_tuple = (str, int, float, bool, type(None), bytes)
1213
+
1214
+ for k, v in pairs:
1215
+ if isinstance(k, dict | list | set):
1216
+ # Convert unhashable key to hashable tuple
1217
+ hashable_key: tuple[str, object]
1218
+ if isinstance(k, dict):
1219
+ try:
1220
+ hashable_key = (_UNHASHABLE_PREFIXES["dict"], tuple(sorted(k.items())))
1221
+ except TypeError:
1222
+ hashable_key = (_UNHASHABLE_PREFIXES["dict"], str(k))
1223
+ elif isinstance(k, list):
1224
+ prefix = _UNHASHABLE_PREFIXES["list"]
1225
+ hashable_key = (prefix, tuple(k) if all(isinstance(x, _hashable_type_tuple) for x in k) else str(k))
1226
+ else: # set
1227
+ prefix = _UNHASHABLE_PREFIXES["set"]
1228
+ if all(isinstance(x, _hashable_type_tuple) for x in k):
1229
+ hashable_key = (prefix, tuple(sorted(k)))
1230
+ else:
1231
+ hashable_key = (prefix, str(k))
1232
+ out[hashable_key] = v
1233
+ else:
1234
+ # Key should be hashable, use as-is
1235
+ try:
1236
+ out[k] = v
1237
+ except TypeError:
1238
+ # Unexpected unhashable type, convert to tuple
1239
+ out[(_UNHASHABLE_PREFIXES["generic"], str(type(k).__name__), str(k))] = v
1240
+ return out