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,2193 @@
1
+ import base64
2
+ import copy
3
+ import json
4
+ import os
5
+ from collections.abc import Callable, Sequence
6
+ from dataclasses import asdict, dataclass, fields, replace
7
+ from typing import TYPE_CHECKING, Any, Generic, Literal, TypedDict, TypeVar
8
+
9
+ from typing_extensions import assert_never
10
+
11
+ from algokit_abi import abi, arc32, arc56
12
+ from algokit_common import ProgramSourceMap, get_application_address
13
+ from algokit_transact.models.common import OnApplicationComplete
14
+ from algokit_transact.models.transaction import Transaction
15
+ from algokit_transact.signer import AddressWithTransactionSigner
16
+ from algokit_utils._debugging import PersistSourceMapInput, persist_sourcemaps
17
+ from algokit_utils.applications.abi import (
18
+ ABIReturn,
19
+ ABIStruct,
20
+ ABIType,
21
+ ABIValue,
22
+ Arc56ReturnValueType,
23
+ BoxABIValue,
24
+ get_abi_decoded_value,
25
+ get_abi_encoded_value,
26
+ )
27
+ from algokit_utils.config import config
28
+ from algokit_utils.errors.logic_error import LogicError, parse_logic_error
29
+ from algokit_utils.models.amount import AlgoAmount
30
+ from algokit_utils.models.application import (
31
+ AppSourceMaps,
32
+ AppState,
33
+ CompiledTeal,
34
+ )
35
+ from algokit_utils.models.state import BoxName, BoxValue
36
+ from algokit_utils.models.transaction import SendParams
37
+ from algokit_utils.transactions.transaction_composer import (
38
+ AppCallMethodCallParams,
39
+ AppCallParams,
40
+ AppCreateSchema,
41
+ AppDeleteMethodCallParams,
42
+ AppMethodCallTransactionArgument,
43
+ AppUpdateMethodCallParams,
44
+ AppUpdateParams,
45
+ BuiltTransactions,
46
+ PaymentParams,
47
+ SendTransactionComposerResults,
48
+ )
49
+ from algokit_utils.transactions.transaction_sender import (
50
+ SendAppTransactionResult,
51
+ SendAppUpdateTransactionResult,
52
+ SendSingleTransactionResult,
53
+ )
54
+
55
+ if TYPE_CHECKING:
56
+ from algokit_utils.algorand import AlgorandClient
57
+ from algokit_utils.applications.app_deployer import ApplicationLookup
58
+ from algokit_utils.applications.app_manager import AppManager
59
+ from algokit_utils.models.state import BoxIdentifier, BoxReference, TealTemplateParams
60
+ from algokit_utils.protocols.signer import TransactionSigner
61
+ else:
62
+ AlgorandClient = ApplicationLookup = AppManager = TransactionSigner = Any # type: ignore[assignment]
63
+ BoxIdentifier = BoxReference = TealTemplateParams = Any # type: ignore[assignment]
64
+
65
+ __all__ = [
66
+ "AppClient",
67
+ "AppClientBareCallCreateParams",
68
+ "AppClientBareCallParams",
69
+ "AppClientCompilationParams",
70
+ "AppClientCompilationResult",
71
+ "AppClientCreateSchema",
72
+ "AppClientMethodCallCreateParams",
73
+ "AppClientMethodCallParams",
74
+ "AppClientParams",
75
+ "AppSourceMaps",
76
+ "BaseAppClientMethodCallParams",
77
+ "CommonAppCallCreateParams",
78
+ "CommonAppCallParams",
79
+ "CreateOnComplete",
80
+ "FundAppAccountParams",
81
+ "get_constant_block_offset",
82
+ ]
83
+
84
+ # TEAL opcodes for constant blocks
85
+ BYTE_CBLOCK = 38 # bytecblock opcode
86
+ INT_CBLOCK = 32 # intcblock opcode
87
+ MAX_SIMULATE_OPCODE_BUDGET = (
88
+ 20_000 * 16
89
+ ) # https://github.com/algorand/go-algorand/blob/807b29a91c371d225e12b9287c5d56e9b33c4e4c/ledger/simulation/trace.go#L104
90
+
91
+ T = TypeVar("T") # For generic return type in _handle_call_errors
92
+
93
+ # Sentinel to detect missing arguments in clone() method of AppClient
94
+ _MISSING = object()
95
+
96
+
97
+ def get_constant_block_offset(program: bytes) -> int: # noqa: C901
98
+ """Calculate the offset after constant blocks in TEAL program.
99
+
100
+ Analyzes a compiled TEAL program to find the ending offset position after any bytecblock and intcblock operations.
101
+
102
+ :param program: The compiled TEAL program as bytes
103
+ :return: The maximum offset position after any constant block operations
104
+ """
105
+ bytes_list = list(program)
106
+ program_size = len(bytes_list)
107
+
108
+ # Remove version byte
109
+ bytes_list.pop(0)
110
+
111
+ # Track offsets
112
+ bytecblock_offset: int | None = None
113
+ intcblock_offset: int | None = None
114
+
115
+ while bytes_list:
116
+ # Get current byte
117
+ byte = bytes_list.pop(0)
118
+
119
+ # Check if byte is a constant block opcode
120
+ if byte in (BYTE_CBLOCK, INT_CBLOCK):
121
+ is_bytecblock = byte == BYTE_CBLOCK
122
+
123
+ # Get number of values in constant block
124
+ if not bytes_list:
125
+ break
126
+ values_remaining = bytes_list.pop(0)
127
+
128
+ # Process each value in the block
129
+ for _ in range(values_remaining):
130
+ if is_bytecblock:
131
+ # For bytecblock, next byte is length of element
132
+ if not bytes_list:
133
+ break
134
+ length = bytes_list.pop(0)
135
+ # Remove the bytes for this element
136
+ bytes_list = bytes_list[length:]
137
+ else:
138
+ # For intcblock, read until we find end of uvarint (MSB not set)
139
+ while bytes_list:
140
+ byte = bytes_list.pop(0)
141
+ if not (byte & 0x80): # Check if MSB is not set
142
+ break
143
+
144
+ # Update appropriate offset
145
+ if is_bytecblock:
146
+ bytecblock_offset = program_size - len(bytes_list) - 1
147
+ else:
148
+ intcblock_offset = program_size - len(bytes_list) - 1
149
+
150
+ # If next byte isn't a constant block opcode, we're done
151
+ if not bytes_list or bytes_list[0] not in (BYTE_CBLOCK, INT_CBLOCK):
152
+ break
153
+
154
+ # Return maximum offset
155
+ return max(bytecblock_offset or 0, intcblock_offset or 0)
156
+
157
+
158
+ CreateOnComplete = Literal[
159
+ OnApplicationComplete.NoOp,
160
+ OnApplicationComplete.UpdateApplication,
161
+ OnApplicationComplete.DeleteApplication,
162
+ OnApplicationComplete.OptIn,
163
+ OnApplicationComplete.CloseOut,
164
+ ]
165
+
166
+
167
+ @dataclass(kw_only=True, frozen=True)
168
+ class AppClientCompilationResult:
169
+ """Result of compiling an application's TEAL code.
170
+
171
+ Contains the compiled approval and clear state programs along with optional compilation artifacts.
172
+ """
173
+
174
+ approval_program: bytes
175
+ """The compiled approval program bytes"""
176
+ clear_state_program: bytes
177
+ """The compiled clear state program bytes"""
178
+ compiled_approval: CompiledTeal | None = None
179
+ """Optional compilation artifacts for approval program"""
180
+ compiled_clear: CompiledTeal | None = None
181
+ """Optional compilation artifacts for clear state program"""
182
+
183
+
184
+ class AppClientCompilationParams(TypedDict, total=False):
185
+ """Parameters for compiling an application's TEAL code.
186
+
187
+ :ivar deploy_time_params: Optional template parameters to use during compilation
188
+ :ivar updatable: Optional flag indicating if app should be updatable
189
+ :ivar deletable: Optional flag indicating if app should be deletable
190
+ """
191
+
192
+ deploy_time_params: TealTemplateParams | None
193
+ updatable: bool | None
194
+ deletable: bool | None
195
+
196
+
197
+ ArgsT = TypeVar("ArgsT")
198
+ MethodT = TypeVar("MethodT")
199
+
200
+
201
+ @dataclass(kw_only=True, frozen=True)
202
+ class CommonAppCallParams:
203
+ """Common configuration for app call transaction parameters"""
204
+
205
+ account_references: list[str] | None = None
206
+ """List of account addresses to reference"""
207
+ app_references: list[int] | None = None
208
+ """List of app IDs to reference"""
209
+ asset_references: list[int] | None = None
210
+ """List of asset IDs to reference"""
211
+ box_references: list[BoxReference | BoxIdentifier] | None = None
212
+ """List of box references to include"""
213
+ extra_fee: AlgoAmount | None = None
214
+ """Additional fee to add to transaction"""
215
+ lease: bytes | None = None
216
+ """Transaction lease value"""
217
+ max_fee: AlgoAmount | None = None
218
+ """Maximum fee allowed for transaction"""
219
+ note: bytes | None = None
220
+ """Custom note for the transaction"""
221
+ rekey_to: str | None = None
222
+ """Address to rekey account to"""
223
+ sender: str | None = None
224
+ """Sender address override"""
225
+ signer: TransactionSigner | None = None
226
+ """Custom transaction signer"""
227
+ static_fee: AlgoAmount | None = None
228
+ """Fixed fee for transaction"""
229
+ validity_window: int | None = None
230
+ """Number of rounds valid"""
231
+ first_valid_round: int | None = None
232
+ """First valid round number"""
233
+ last_valid_round: int | None = None
234
+ """Last valid round number"""
235
+ on_complete: OnApplicationComplete | None = None
236
+ """Optional on complete action"""
237
+
238
+
239
+ @dataclass(frozen=True)
240
+ class AppClientCreateSchema:
241
+ """Schema for application creation."""
242
+
243
+ extra_program_pages: int | None = None
244
+ """Optional number of extra program pages"""
245
+ schema: AppCreateSchema | None = None
246
+ """Optional application creation schema"""
247
+
248
+
249
+ @dataclass(kw_only=True, frozen=True)
250
+ class CommonAppCallCreateParams(AppClientCreateSchema, CommonAppCallParams):
251
+ """Common configuration for app create call transaction parameters."""
252
+
253
+ on_complete: CreateOnComplete | None = None
254
+ """Optional on complete action"""
255
+
256
+
257
+ @dataclass(kw_only=True, frozen=True)
258
+ class FundAppAccountParams(CommonAppCallParams):
259
+ """Parameters for funding an application's account."""
260
+
261
+ amount: AlgoAmount
262
+ """Amount to fund"""
263
+ close_remainder_to: str | None = None
264
+ """Optional address to close remainder to"""
265
+
266
+
267
+ @dataclass(kw_only=True, frozen=True)
268
+ class AppClientBareCallParams(CommonAppCallParams):
269
+ """Parameters for bare application calls."""
270
+
271
+ args: list[bytes] | None = None
272
+ """Optional arguments"""
273
+
274
+
275
+ @dataclass(frozen=True)
276
+ class AppClientBareCallCreateParams(CommonAppCallCreateParams):
277
+ """Parameters for creating application with bare call."""
278
+
279
+ on_complete: CreateOnComplete | None = None
280
+ """Optional on complete action"""
281
+
282
+
283
+ @dataclass(kw_only=True, frozen=True)
284
+ class BaseAppClientMethodCallParams(Generic[ArgsT, MethodT], CommonAppCallParams):
285
+ """Base parameters for application method calls."""
286
+
287
+ method: MethodT
288
+ """Method to call"""
289
+ args: ArgsT | None = None
290
+ """Arguments to pass to the application method call"""
291
+
292
+
293
+ @dataclass(kw_only=True, frozen=True)
294
+ class AppClientMethodCallParams(
295
+ BaseAppClientMethodCallParams[
296
+ Sequence[ABIValue | ABIStruct | AppMethodCallTransactionArgument | None],
297
+ str,
298
+ ]
299
+ ):
300
+ """Parameters for application method calls."""
301
+
302
+
303
+ @dataclass(frozen=True)
304
+ class AppClientMethodCallCreateParams(AppClientCreateSchema, AppClientMethodCallParams):
305
+ """Parameters for creating application with method call"""
306
+
307
+ on_complete: CreateOnComplete | None = None
308
+ """Optional on complete action"""
309
+
310
+
311
+ class _AppClientStateMethods:
312
+ def __init__(
313
+ self,
314
+ *,
315
+ get_all: Callable[[], dict[str, Any]],
316
+ get_value: Callable[[str, dict[str, AppState] | None], ABIValue | None],
317
+ get_map_value: Callable[[str, bytes | Any, dict[str, AppState] | None], Any],
318
+ get_map: Callable[[str], dict[str, ABIValue]],
319
+ ) -> None:
320
+ self._get_all = get_all
321
+ self._get_value = get_value
322
+ self._get_map_value = get_map_value
323
+ self._get_map = get_map
324
+
325
+ def get_all(self) -> dict[str, Any]:
326
+ return self._get_all()
327
+
328
+ def get_value(self, name: str, app_state: dict[str, AppState] | None = None) -> ABIValue | None:
329
+ return self._get_value(name, app_state)
330
+
331
+ def get_map_value(self, map_name: str, key: bytes | Any, app_state: dict[str, AppState] | None = None) -> Any: # noqa: ANN401
332
+ return self._get_map_value(map_name, key, app_state)
333
+
334
+ def get_map(self, map_name: str) -> dict[str, ABIValue]:
335
+ return self._get_map(map_name)
336
+
337
+
338
+ class _AppClientBoxMethods:
339
+ def __init__(
340
+ self,
341
+ *,
342
+ get_all: Callable[[], dict[str, Any]],
343
+ get_value: Callable[[str], ABIValue | None],
344
+ get_map_value: Callable[[str, bytes | Any], Any],
345
+ get_map: Callable[[str], dict[str, ABIValue]],
346
+ ) -> None:
347
+ self._get_all = get_all
348
+ self._get_value = get_value
349
+ self._get_map_value = get_map_value
350
+ self._get_map = get_map
351
+
352
+ def get_all(self) -> dict[str, Any]:
353
+ return self._get_all()
354
+
355
+ def get_value(self, name: str) -> ABIValue | None:
356
+ return self._get_value(name)
357
+
358
+ def get_map_value(self, map_name: str, key: bytes | Any) -> Any: # noqa: ANN401
359
+ return self._get_map_value(map_name, key)
360
+
361
+ def get_map(self, map_name: str) -> dict[str, ABIValue]:
362
+ return self._get_map(map_name)
363
+
364
+
365
+ class _StateAccessor:
366
+ def __init__(self, client: "AppClient") -> None:
367
+ self._client = client
368
+ self._algorand = client._algorand
369
+ self._app_id = client._app_id
370
+ self._app_spec = client._app_spec
371
+
372
+ def local_state(self, address: str) -> _AppClientStateMethods:
373
+ """Methods to access local state for the current app for a given address"""
374
+ return self._get_state_methods(
375
+ state_getter=lambda: self._algorand.app.get_local_state(self._app_id, address),
376
+ key_getter=lambda: self._app_spec.state.keys.local_state,
377
+ map_getter=lambda: self._app_spec.state.maps.local_state,
378
+ )
379
+
380
+ @property
381
+ def global_state(self) -> _AppClientStateMethods:
382
+ """Methods to access global state for the current app"""
383
+ return self._get_state_methods(
384
+ state_getter=lambda: self._algorand.app.get_global_state(self._app_id),
385
+ key_getter=lambda: self._app_spec.state.keys.global_state,
386
+ map_getter=lambda: self._app_spec.state.maps.global_state,
387
+ )
388
+
389
+ @property
390
+ def box(self) -> _AppClientBoxMethods:
391
+ """Methods to access box storage for the current app"""
392
+ return self._get_box_methods()
393
+
394
+ def _get_box_methods(self) -> _AppClientBoxMethods:
395
+ def get_all() -> dict[str, Any]:
396
+ """Returns all single-key box values in a dict keyed by the key name."""
397
+ return {key: get_value(key) for key in self._app_spec.state.keys.box}
398
+
399
+ def get_value(name: str) -> ABIValue | None:
400
+ """Returns a single box value for the current app with the value a decoded ABI value.
401
+
402
+ :param name: The name of the box value to retrieve
403
+ :return: The decoded ABI value from the box storage, or None if not found
404
+ """
405
+ metadata = self._app_spec.state.keys.box[name]
406
+ value = self._algorand.app.get_box_value(self._app_id, base64.b64decode(metadata.key))
407
+ return get_abi_decoded_value(value, metadata.value_type)
408
+
409
+ def get_map_value(map_name: str, key: bytes | Any) -> Any: # noqa: ANN401
410
+ """Get a value from a box map.
411
+
412
+ Retrieves a value from a box map storage using the provided map name and key.
413
+
414
+ :param map_name: The name of the map to read from
415
+ :param key: The key within the map (without any map prefix) as either bytes or a value
416
+ that will be converted to bytes by encoding it using the specified ABI key type
417
+ :return: The decoded value from the box map storage
418
+ """
419
+ metadata = self._app_spec.state.maps.box[map_name]
420
+ prefix = base64.b64decode(metadata.prefix or "")
421
+ encoded_key = get_abi_encoded_value(key, metadata.key_type)
422
+ full_key = base64.b64encode(prefix + encoded_key).decode("utf-8")
423
+ value = self._algorand.app.get_box_value(self._app_id, base64.b64decode(full_key))
424
+ return get_abi_decoded_value(value, metadata.value_type)
425
+
426
+ def get_map(map_name: str) -> dict[str, ABIValue]:
427
+ """Get all key-value pairs from a box map.
428
+
429
+ Retrieves all key-value pairs stored in a box map for the current app.
430
+
431
+ :param map_name: The name of the map to read from
432
+ :return: A dictionary mapping string keys to their corresponding ABI-decoded values
433
+ :raises ValueError: If there is an error decoding any key or value in the map
434
+ """
435
+ metadata = self._app_spec.state.maps.box[map_name]
436
+ prefix = base64.b64decode(metadata.prefix or "")
437
+ box_names = self._algorand.app.get_box_names(self._app_id)
438
+
439
+ result = {}
440
+ for box in box_names:
441
+ if not box.name_raw.startswith(prefix):
442
+ continue
443
+
444
+ try:
445
+ key = get_abi_decoded_value(box.name_raw[len(prefix) :], metadata.key_type)
446
+ value = get_abi_decoded_value(
447
+ self._algorand.app.get_box_value(self._app_id, box.name_raw),
448
+ metadata.value_type,
449
+ )
450
+ result[str(key)] = value
451
+ except Exception as e:
452
+ raise ValueError(f"Failed to decode value for key {box.name_raw.decode('utf-8')}") from e
453
+
454
+ return result
455
+
456
+ return _AppClientBoxMethods(
457
+ get_all=get_all,
458
+ get_value=get_value,
459
+ get_map_value=get_map_value,
460
+ get_map=get_map,
461
+ )
462
+
463
+ def _get_state_methods( # noqa: C901
464
+ self,
465
+ state_getter: Callable[[], dict[str, AppState]],
466
+ key_getter: Callable[[], dict[str, arc56.StorageKey]],
467
+ map_getter: Callable[[], dict[str, arc56.StorageMap]],
468
+ ) -> _AppClientStateMethods:
469
+ def get_all() -> dict[str, Any]:
470
+ state = state_getter()
471
+ keys = key_getter()
472
+ return {key: get_value(key, state) for key in keys}
473
+
474
+ def get_value(name: str, app_state: dict[str, AppState] | None = None) -> ABIValue | None:
475
+ state = app_state or state_getter()
476
+ key_info = key_getter()[name]
477
+ value = next((s for s in state.values() if s.key_base64 == key_info.key), None)
478
+
479
+ if value and value.value_raw:
480
+ return get_abi_decoded_value(value.value_raw, key_info.value_type)
481
+
482
+ return value.value if value else None
483
+
484
+ def get_map_value(map_name: str, key: bytes | Any, app_state: dict[str, AppState] | None = None) -> Any: # noqa: ANN401
485
+ state = app_state or state_getter()
486
+ metadata = map_getter()[map_name]
487
+
488
+ prefix = base64.b64decode(metadata.prefix or "")
489
+ encoded_key = get_abi_encoded_value(key, metadata.key_type)
490
+ full_key = base64.b64encode(prefix + encoded_key).decode("utf-8")
491
+ value = next((s for s in state.values() if s.key_base64 == full_key), None)
492
+ if value and value.value_raw:
493
+ return get_abi_decoded_value(value.value_raw, metadata.value_type)
494
+ return value.value if value else None
495
+
496
+ def get_map(map_name: str) -> dict[str, ABIValue]:
497
+ state = state_getter()
498
+ metadata = map_getter()[map_name]
499
+
500
+ prefix = base64.b64decode(metadata.prefix or "").decode("utf-8")
501
+
502
+ prefixed_state = {k: v for k, v in state.items() if k.startswith(prefix)}
503
+
504
+ decoded_map = {}
505
+
506
+ for key_encoded, value in prefixed_state.items():
507
+ key_bytes = key_encoded[len(prefix) :]
508
+ try:
509
+ decoded_key = get_abi_decoded_value(key_bytes, metadata.key_type)
510
+ except Exception as e:
511
+ raise ValueError(f"Failed to decode key {key_encoded}") from e
512
+
513
+ try:
514
+ if value and value.value_raw:
515
+ decoded_value = get_abi_decoded_value(value.value_raw, metadata.value_type)
516
+ else:
517
+ decoded_value = get_abi_decoded_value(value.value, metadata.value_type)
518
+ except Exception as e:
519
+ raise ValueError(f"Failed to decode value {value}") from e
520
+
521
+ decoded_map[str(decoded_key)] = decoded_value
522
+
523
+ return decoded_map
524
+
525
+ return _AppClientStateMethods(
526
+ get_all=get_all,
527
+ get_value=get_value,
528
+ get_map_value=get_map_value,
529
+ get_map=get_map,
530
+ )
531
+
532
+ def get_local_state(self, address: str) -> dict[str, AppState]:
533
+ return self._algorand.app.get_local_state(self._app_id, address)
534
+
535
+ def get_global_state(self) -> dict[str, AppState]:
536
+ return self._algorand.app.get_global_state(self._app_id)
537
+
538
+
539
+ class _BareParamsBuilder:
540
+ def __init__(self, client: "AppClient") -> None:
541
+ self._client = client
542
+ self._algorand = client._algorand
543
+ self._app_id = client._app_id
544
+ self._app_spec = client._app_spec
545
+
546
+ def _get_bare_params(
547
+ self, params: dict[str, Any] | None, on_complete: OnApplicationComplete | None = None
548
+ ) -> dict[str, Any]:
549
+ params = params or {}
550
+ sender = self._client._get_sender(params.get("sender"))
551
+ return {
552
+ **params,
553
+ "app_id": self._app_id,
554
+ "sender": sender,
555
+ "signer": self._client._get_signer(params.get("sender"), params.get("signer")),
556
+ "on_complete": on_complete or OnApplicationComplete.NoOp,
557
+ }
558
+
559
+ def update(
560
+ self,
561
+ params: AppClientBareCallParams | None = None,
562
+ ) -> AppUpdateParams:
563
+ """Create parameters for updating an application.
564
+
565
+ :param params: Optional compilation and send parameters, defaults to None
566
+ :return: Parameters for updating the application
567
+ """
568
+ call_params: AppUpdateParams = AppUpdateParams(
569
+ **self._get_bare_params(params.__dict__ if params else {}, OnApplicationComplete.UpdateApplication)
570
+ )
571
+ return call_params
572
+
573
+ def opt_in(self, params: AppClientBareCallParams | None = None) -> AppCallParams:
574
+ """Create parameters for opting into an application.
575
+
576
+ :param params: Optional send parameters, defaults to None
577
+ :return: Parameters for opting into the application
578
+ """
579
+ call_params: AppCallParams = AppCallParams(
580
+ **self._get_bare_params(params.__dict__ if params else {}, OnApplicationComplete.OptIn)
581
+ )
582
+ return call_params
583
+
584
+ def delete(self, params: AppClientBareCallParams | None = None) -> AppCallParams:
585
+ """Create parameters for deleting an application.
586
+
587
+ :param params: Optional send parameters, defaults to None
588
+ :return: Parameters for deleting the application
589
+ """
590
+ call_params: AppCallParams = AppCallParams(
591
+ **self._get_bare_params(params.__dict__ if params else {}, OnApplicationComplete.DeleteApplication)
592
+ )
593
+ return call_params
594
+
595
+ def clear_state(self, params: AppClientBareCallParams | None = None) -> AppCallParams:
596
+ """Create parameters for clearing application state.
597
+
598
+ :param params: Optional send parameters, defaults to None
599
+ :return: Parameters for clearing application state
600
+ """
601
+ call_params: AppCallParams = AppCallParams(
602
+ **self._get_bare_params(params.__dict__ if params else {}, OnApplicationComplete.ClearState)
603
+ )
604
+ return call_params
605
+
606
+ def close_out(self, params: AppClientBareCallParams | None = None) -> AppCallParams:
607
+ """Create parameters for closing out of an application.
608
+
609
+ :param params: Optional send parameters, defaults to None
610
+ :return: Parameters for closing out of the application
611
+ """
612
+ call_params: AppCallParams = AppCallParams(
613
+ **self._get_bare_params(params.__dict__ if params else {}, OnApplicationComplete.CloseOut)
614
+ )
615
+ return call_params
616
+
617
+ def call(
618
+ self,
619
+ params: AppClientBareCallParams | None = None,
620
+ on_complete: OnApplicationComplete | None = OnApplicationComplete.NoOp,
621
+ ) -> AppCallParams:
622
+ """Create parameters for calling an application.
623
+
624
+ :param params: Optional call parameters with on complete action, defaults to None
625
+ :param on_complete: The OnApplicationComplete action, defaults to OnApplicationComplete.NoOp
626
+ :return: Parameters for calling the application
627
+ """
628
+ call_params: AppCallParams = AppCallParams(
629
+ **self._get_bare_params(params.__dict__ if params else {}, on_complete or OnApplicationComplete.NoOp)
630
+ )
631
+ return call_params
632
+
633
+
634
+ class _MethodParamsBuilder:
635
+ def __init__(self, client: "AppClient") -> None:
636
+ self._client = client
637
+ self._algorand = client._algorand
638
+ self._app_id = client._app_id
639
+ self._app_spec = client._app_spec
640
+ self._bare_params_accessor = _BareParamsBuilder(client)
641
+
642
+ @property
643
+ def bare(self) -> _BareParamsBuilder:
644
+ return self._bare_params_accessor
645
+
646
+ def fund_app_account(self, params: FundAppAccountParams) -> PaymentParams:
647
+ """Create parameters for funding an application account.
648
+
649
+ :param params: Parameters for funding the application account
650
+ :return: Parameters for sending a payment transaction to fund the application account
651
+ """
652
+
653
+ def random_note() -> bytes:
654
+ return base64.b64encode(os.urandom(16))
655
+
656
+ return PaymentParams(
657
+ sender=self._client._get_sender(params.sender),
658
+ signer=self._client._get_signer(params.sender, params.signer),
659
+ receiver=self._client.app_address,
660
+ amount=params.amount,
661
+ rekey_to=params.rekey_to,
662
+ note=params.note or random_note(),
663
+ lease=params.lease,
664
+ static_fee=params.static_fee,
665
+ extra_fee=params.extra_fee,
666
+ max_fee=params.max_fee,
667
+ validity_window=params.validity_window,
668
+ first_valid_round=params.first_valid_round,
669
+ last_valid_round=params.last_valid_round,
670
+ close_remainder_to=params.close_remainder_to,
671
+ )
672
+
673
+ def opt_in(self, params: AppClientMethodCallParams) -> AppCallMethodCallParams:
674
+ """Create parameters for opting into an application.
675
+
676
+ :param params: Parameters for the opt-in call
677
+ :return: Parameters for opting into the application
678
+ """
679
+ input_params = self._get_abi_params(
680
+ params.__dict__, on_complete=params.on_complete or OnApplicationComplete.OptIn
681
+ )
682
+ return AppCallMethodCallParams(**input_params)
683
+
684
+ def call(self, params: AppClientMethodCallParams) -> AppCallMethodCallParams:
685
+ """Create parameters for calling an application method.
686
+
687
+ :param params: Parameters for the method call
688
+ :return: Parameters for calling the application method
689
+ """
690
+ input_params = self._get_abi_params(
691
+ params.__dict__, on_complete=params.on_complete or OnApplicationComplete.NoOp
692
+ )
693
+ return AppCallMethodCallParams(**input_params)
694
+
695
+ def delete(self, params: AppClientMethodCallParams) -> AppDeleteMethodCallParams:
696
+ """Create parameters for deleting an application.
697
+
698
+ :param params: Parameters for the delete call
699
+ :return: Parameters for deleting the application
700
+ """
701
+ input_params = self._get_abi_params(
702
+ params.__dict__, on_complete=params.on_complete or OnApplicationComplete.DeleteApplication
703
+ )
704
+ return AppDeleteMethodCallParams(**input_params)
705
+
706
+ def update(
707
+ self, params: AppClientMethodCallParams, compilation_params: AppClientCompilationParams | None = None
708
+ ) -> AppUpdateMethodCallParams:
709
+ """Create parameters for updating an application.
710
+
711
+ :param params: Parameters for the update call, optionally including compilation parameters
712
+ :param compilation_params: Parameters for the compilation, defaults to None
713
+ :return: Parameters for updating the application
714
+ """
715
+ compile_params = asdict(
716
+ self._client.compile(
717
+ app_spec=self._client.app_spec,
718
+ app_manager=self._algorand.app,
719
+ compilation_params=compilation_params,
720
+ )
721
+ )
722
+
723
+ input_params = {
724
+ **self._get_abi_params(
725
+ params.__dict__, on_complete=params.on_complete or OnApplicationComplete.UpdateApplication
726
+ ),
727
+ **compile_params,
728
+ }
729
+ # Filter input_params to include only fields valid for AppUpdateMethodCallParams
730
+ app_update_method_call_fields = {field.name for field in fields(AppUpdateMethodCallParams)}
731
+ filtered_input_params = {k: v for k, v in input_params.items() if k in app_update_method_call_fields}
732
+ return AppUpdateMethodCallParams(**filtered_input_params)
733
+
734
+ def close_out(self, params: AppClientMethodCallParams) -> AppCallMethodCallParams:
735
+ """Create parameters for closing out of an application.
736
+
737
+ :param params: Parameters for the close-out call
738
+ :return: Parameters for closing out of the application
739
+ """
740
+ input_params = self._get_abi_params(
741
+ params.__dict__, on_complete=params.on_complete or OnApplicationComplete.CloseOut
742
+ )
743
+ return AppCallMethodCallParams(**input_params)
744
+
745
+ def _get_abi_params(self, params: dict[str, Any], on_complete: OnApplicationComplete) -> dict[str, Any]:
746
+ input_params = copy.deepcopy(params)
747
+
748
+ input_params["app_id"] = self._app_id
749
+ input_params["on_complete"] = on_complete
750
+ input_params["sender"] = self._client._get_sender(params["sender"])
751
+ input_params["signer"] = self._client._get_signer(params["sender"], params["signer"])
752
+
753
+ if params.get("method"):
754
+ input_params["method"] = self._app_spec.get_abi_method(params["method"])
755
+ input_params["args"] = self._client._get_abi_args_with_default_values(
756
+ method_name_or_signature=params["method"],
757
+ args=params.get("args"),
758
+ sender=self._client._get_sender(input_params["sender"]),
759
+ )
760
+
761
+ return input_params
762
+
763
+
764
+ class _AppClientBareCallCreateTransactionMethods:
765
+ def __init__(self, client: "AppClient") -> None:
766
+ self._client = client
767
+ self._algorand = client._algorand
768
+
769
+ def update(self, params: AppClientBareCallParams | None = None) -> Transaction:
770
+ """Create a transaction to update an application.
771
+
772
+ Creates a transaction that will update an existing application with new approval and clear state programs.
773
+
774
+ :param params: Parameters for the update call including compilation and transaction options, defaults to None
775
+ :return: The constructed application update transaction
776
+ """
777
+ return self._algorand.create_transaction.app_update(
778
+ self._client.params.bare.update(params or AppClientBareCallParams())
779
+ )
780
+
781
+ def opt_in(self, params: AppClientBareCallParams | None = None) -> Transaction:
782
+ """Create a transaction to opt into an application.
783
+
784
+ Creates a transaction that will opt the sender account into using this application.
785
+
786
+ :param params: Parameters for the opt-in call including transaction options, defaults to None
787
+ :return: The constructed opt-in transaction
788
+ """
789
+ return self._algorand.create_transaction.app_call(
790
+ self._client.params.bare.opt_in(params or AppClientBareCallParams())
791
+ )
792
+
793
+ def delete(self, params: AppClientBareCallParams | None = None) -> Transaction:
794
+ """Create a transaction to delete an application.
795
+
796
+ Creates a transaction that will delete this application from the blockchain.
797
+
798
+ :param params: Parameters for the delete call including transaction options, defaults to None
799
+ :return: The constructed delete transaction
800
+ """
801
+ return self._algorand.create_transaction.app_call(
802
+ self._client.params.bare.delete(params or AppClientBareCallParams())
803
+ )
804
+
805
+ def clear_state(self, params: AppClientBareCallParams | None = None) -> Transaction:
806
+ """Create a transaction to clear application state.
807
+
808
+ Creates a transaction that will clear the sender's local state for this application.
809
+
810
+ :param params: Parameters for the clear state call including transaction options, defaults to None
811
+ :return: The constructed clear state transaction
812
+ """
813
+ return self._algorand.create_transaction.app_call(
814
+ self._client.params.bare.clear_state(params or AppClientBareCallParams())
815
+ )
816
+
817
+ def close_out(self, params: AppClientBareCallParams | None = None) -> Transaction:
818
+ """Create a transaction to close out of an application.
819
+
820
+ Creates a transaction that will close out the sender's participation in this application.
821
+
822
+ :param params: Parameters for the close out call including transaction options, defaults to None
823
+ :return: The constructed close out transaction
824
+ """
825
+ return self._algorand.create_transaction.app_call(
826
+ self._client.params.bare.close_out(params or AppClientBareCallParams())
827
+ )
828
+
829
+ def call(
830
+ self,
831
+ params: AppClientBareCallParams | None = None,
832
+ on_complete: OnApplicationComplete | None = OnApplicationComplete.NoOp,
833
+ ) -> Transaction:
834
+ """Create a transaction to call an application.
835
+
836
+ Creates a transaction that will call this application with the specified parameters.
837
+
838
+ :param params: Parameters for the application call including on complete action, defaults to None
839
+ :param on_complete: The OnApplicationComplete action, defaults to OnApplicationComplete.NoOp
840
+ :return: The constructed application call transaction
841
+ """
842
+ return self._algorand.create_transaction.app_call(
843
+ self._client.params.bare.call(
844
+ params or AppClientBareCallParams(), on_complete or OnApplicationComplete.NoOp
845
+ )
846
+ )
847
+
848
+
849
+ class _TransactionCreator:
850
+ def __init__(self, client: "AppClient") -> None:
851
+ self._client = client
852
+ self._algorand = client._algorand
853
+ self._app_id = client._app_id
854
+ self._app_spec = client._app_spec
855
+ self._bare_create_transaction_methods = _AppClientBareCallCreateTransactionMethods(client)
856
+
857
+ @property
858
+ def bare(self) -> _AppClientBareCallCreateTransactionMethods:
859
+ return self._bare_create_transaction_methods
860
+
861
+ def fund_app_account(self, params: FundAppAccountParams) -> Transaction:
862
+ """Create a transaction to fund an application account.
863
+
864
+ Creates a payment transaction to fund the application account with the specified parameters.
865
+
866
+ :param params: Parameters for funding the application account including amount and transaction options
867
+ :return: The constructed payment transaction
868
+ """
869
+ return self._algorand.create_transaction.payment(self._client.params.fund_app_account(params))
870
+
871
+ def opt_in(self, params: AppClientMethodCallParams) -> BuiltTransactions:
872
+ """Create a transaction to opt into an application.
873
+
874
+ Creates a transaction that will opt the sender into this application with the specified parameters.
875
+
876
+ :param params: Parameters for the opt-in call including method arguments and transaction options
877
+ :return: The constructed opt-in transaction(s)
878
+ """
879
+ return self._algorand.create_transaction.app_call_method_call(self._client.params.opt_in(params))
880
+
881
+ def update(self, params: AppClientMethodCallParams) -> BuiltTransactions:
882
+ """Create a transaction to update an application.
883
+
884
+ Creates a transaction that will update this application with new approval and clear state programs.
885
+
886
+ :param params: Parameters for the update call including method arguments and transaction options
887
+ :return: The constructed update transaction(s)
888
+ """
889
+ return self._algorand.create_transaction.app_update_method_call(self._client.params.update(params))
890
+
891
+ def delete(self, params: AppClientMethodCallParams) -> BuiltTransactions:
892
+ """Create a transaction to delete an application.
893
+
894
+ Creates a transaction that will delete this application.
895
+
896
+ :param params: Parameters for the delete call including method arguments and transaction options
897
+ :return: The constructed delete transaction(s)
898
+ """
899
+ return self._algorand.create_transaction.app_delete_method_call(self._client.params.delete(params))
900
+
901
+ def close_out(self, params: AppClientMethodCallParams) -> BuiltTransactions:
902
+ """Create a transaction to close out of an application.
903
+
904
+ Creates a transaction that will close out the sender's participation in this application.
905
+
906
+ :param params: Parameters for the close out call including method arguments and transaction options
907
+ :return: The constructed close out transaction(s)
908
+ """
909
+ return self._algorand.create_transaction.app_call_method_call(self._client.params.close_out(params))
910
+
911
+ def call(self, params: AppClientMethodCallParams) -> BuiltTransactions:
912
+ """Create a transaction to call an application.
913
+
914
+ Creates a transaction that will call this application with the specified parameters.
915
+
916
+ :param params: Parameters for the application call including method arguments and transaction options
917
+ :return: The constructed application call transaction(s)
918
+ """
919
+ return self._algorand.create_transaction.app_call_method_call(self._client.params.call(params))
920
+
921
+
922
+ class _AppClientBareSendAccessor:
923
+ def __init__(self, client: "AppClient") -> None:
924
+ self._client = client
925
+ self._algorand = client._algorand
926
+ self._app_id = client._app_id
927
+ self._app_spec = client._app_spec
928
+
929
+ def update(
930
+ self,
931
+ params: AppClientBareCallParams | None = None,
932
+ send_params: SendParams | None = None,
933
+ compilation_params: AppClientCompilationParams | None = None,
934
+ ) -> SendAppTransactionResult[ABIReturn]:
935
+ """Send an application update transaction.
936
+
937
+ Sends a transaction to update an existing application with new approval and clear state programs.
938
+
939
+ :param params: The parameters for the update call, including optional compilation parameters,
940
+ deploy time parameters, and transaction configuration
941
+ :param send_params: Send parameters, defaults to None
942
+ :param compilation_params: Parameters for the compilation, defaults to None
943
+ :return: The result of sending the transaction, including compilation artifacts and ABI return
944
+ value if applicable
945
+ """
946
+ params = params or AppClientBareCallParams()
947
+ compilation = compilation_params or AppClientCompilationParams()
948
+ compiled = self._client.compile_app(
949
+ {
950
+ "deploy_time_params": compilation.get("deploy_time_params"),
951
+ "updatable": compilation.get("updatable"),
952
+ "deletable": compilation.get("deletable"),
953
+ }
954
+ )
955
+ bare_params = self._client.params.bare.update(params)
956
+ bare_params.__setattr__("approval_program", bare_params.approval_program or compiled.compiled_approval)
957
+ bare_params.__setattr__("clear_state_program", bare_params.clear_state_program or compiled.compiled_clear)
958
+ call_result = self._client._handle_call_errors(lambda: self._algorand.send.app_update(bare_params, send_params))
959
+ return SendAppTransactionResult[ABIReturn](
960
+ **{**call_result.__dict__, **(compiled.__dict__ if compiled else {})},
961
+ abi_return=AppManager.get_abi_return(call_result.confirmation, getattr(params, "method", None)),
962
+ )
963
+
964
+ def opt_in(
965
+ self, params: AppClientBareCallParams | None = None, send_params: SendParams | None = None
966
+ ) -> SendAppTransactionResult[ABIReturn]:
967
+ """Send an application opt-in transaction.
968
+
969
+ Creates and sends a transaction that will opt the sender's account into this application.
970
+
971
+ :param params: Parameters for the opt-in call including transaction options, defaults to None
972
+ :param send_params: Send parameters, defaults to None
973
+ :return: The result of sending the transaction, including ABI return value if applicable
974
+ """
975
+ return self._client._handle_call_errors(
976
+ lambda: self._algorand.send.app_call(
977
+ self._client.params.bare.opt_in(params or AppClientBareCallParams()), send_params
978
+ )
979
+ )
980
+
981
+ def delete(
982
+ self, params: AppClientBareCallParams | None = None, send_params: SendParams | None = None
983
+ ) -> SendAppTransactionResult[ABIReturn]:
984
+ """Send an application delete transaction.
985
+
986
+ Creates and sends a transaction that will delete this application.
987
+
988
+ :param params: Parameters for the delete call including transaction options, defaults to None
989
+ :param send_params: Send parameters, defaults to None
990
+ :return: The result of sending the transaction, including ABI return value if applicable
991
+ """
992
+ return self._client._handle_call_errors(
993
+ lambda: self._algorand.send.app_call(
994
+ self._client.params.bare.delete(params or AppClientBareCallParams()), send_params
995
+ )
996
+ )
997
+
998
+ def clear_state(
999
+ self, params: AppClientBareCallParams | None = None, send_params: SendParams | None = None
1000
+ ) -> SendAppTransactionResult[ABIReturn]:
1001
+ """Send an application clear state transaction.
1002
+
1003
+ Creates and sends a transaction that will clear the sender's local state for this application.
1004
+
1005
+ :param params: Parameters for the clear state call including transaction options, defaults to None
1006
+ :param send_params: Send parameters, defaults to None
1007
+ :return: The result of sending the transaction, including ABI return value if applicable
1008
+ """
1009
+ return self._client._handle_call_errors(
1010
+ lambda: self._algorand.send.app_call(
1011
+ self._client.params.bare.clear_state(params or AppClientBareCallParams()), send_params
1012
+ )
1013
+ )
1014
+
1015
+ def close_out(
1016
+ self, params: AppClientBareCallParams | None = None, send_params: SendParams | None = None
1017
+ ) -> SendAppTransactionResult[ABIReturn]:
1018
+ """Send an application close out transaction.
1019
+
1020
+ Creates and sends a transaction that will close out the sender's participation in this application.
1021
+
1022
+ :param params: Parameters for the close out call including transaction options, defaults to None
1023
+ :param send_params: Send parameters, defaults to None
1024
+ :return: The result of sending the transaction, including ABI return value if applicable
1025
+ """
1026
+ return self._client._handle_call_errors(
1027
+ lambda: self._algorand.send.app_call(
1028
+ self._client.params.bare.close_out(params or AppClientBareCallParams()), send_params
1029
+ )
1030
+ )
1031
+
1032
+ def call(
1033
+ self,
1034
+ params: AppClientBareCallParams | None = None,
1035
+ on_complete: OnApplicationComplete | None = None,
1036
+ send_params: SendParams | None = None,
1037
+ ) -> SendAppTransactionResult[ABIReturn]:
1038
+ """Send an application call transaction.
1039
+
1040
+ Creates and sends a transaction that will call this application with the specified parameters.
1041
+
1042
+ :param params: Parameters for the application call including transaction options, defaults to None
1043
+ :param on_complete: The OnApplicationComplete action, defaults to None
1044
+ :param send_params: Send parameters, defaults to None
1045
+ :return: The result of sending the transaction, including ABI return value if applicable
1046
+ """
1047
+ return self._client._handle_call_errors(
1048
+ lambda: self._algorand.send.app_call(
1049
+ self._client.params.bare.call(params or AppClientBareCallParams(), on_complete), send_params
1050
+ )
1051
+ )
1052
+
1053
+
1054
+ class _TransactionSender:
1055
+ def __init__(self, client: "AppClient") -> None:
1056
+ self._client = client
1057
+ self._algorand = client._algorand
1058
+ self._app_id = client._app_id
1059
+ self._app_spec = client._app_spec
1060
+ self._bare_send_accessor = _AppClientBareSendAccessor(client)
1061
+
1062
+ @property
1063
+ def bare(self) -> _AppClientBareSendAccessor:
1064
+ """Get accessor for bare application calls.
1065
+
1066
+ :return: Accessor for making bare application calls without ABI encoding
1067
+ """
1068
+ return self._bare_send_accessor
1069
+
1070
+ def fund_app_account(
1071
+ self, params: FundAppAccountParams, send_params: SendParams | None = None
1072
+ ) -> SendSingleTransactionResult:
1073
+ """Send funds to the application account.
1074
+
1075
+ Creates and sends a payment transaction to fund the application account.
1076
+
1077
+ :param params: Parameters for funding the app account including amount and transaction options
1078
+ :param send_params: Send parameters, defaults to None
1079
+ :return: The result of sending the payment transaction
1080
+ """
1081
+ return self._client._handle_call_errors( # type: ignore[no-any-return]
1082
+ lambda: self._algorand.send.payment(self._client.params.fund_app_account(params), send_params)
1083
+ )
1084
+
1085
+ def opt_in(
1086
+ self, params: AppClientMethodCallParams, send_params: SendParams | None = None
1087
+ ) -> SendAppTransactionResult[Arc56ReturnValueType]:
1088
+ """Send an application opt-in transaction.
1089
+
1090
+ Creates and sends a transaction that will opt the sender into this application.
1091
+
1092
+ :param params: Parameters for the opt-in call including method and transaction options
1093
+ :param send_params: Send parameters, defaults to None
1094
+ :return: The result of sending the transaction, including ABI return value if applicable
1095
+ """
1096
+ return self._client._handle_call_errors(
1097
+ lambda: self._client._process_method_call_return(
1098
+ lambda: self._algorand.send.app_call_method_call(self._client.params.opt_in(params), send_params),
1099
+ self._app_spec.get_abi_method(params.method),
1100
+ )
1101
+ )
1102
+
1103
+ def delete(
1104
+ self, params: AppClientMethodCallParams, send_params: SendParams | None = None
1105
+ ) -> SendAppTransactionResult[Arc56ReturnValueType]:
1106
+ """Send an application delete transaction.
1107
+
1108
+ Creates and sends a transaction that will delete this application.
1109
+
1110
+ :param params: Parameters for the delete call including method and transaction options
1111
+ :param send_params: Send parameters, defaults to None
1112
+ :return: The result of sending the transaction, including ABI return value if applicable
1113
+ """
1114
+ return self._client._handle_call_errors(
1115
+ lambda: self._client._process_method_call_return(
1116
+ lambda: self._algorand.send.app_delete_method_call(self._client.params.delete(params), send_params),
1117
+ self._app_spec.get_abi_method(params.method),
1118
+ )
1119
+ )
1120
+
1121
+ def update(
1122
+ self,
1123
+ params: AppClientMethodCallParams,
1124
+ compilation_params: AppClientCompilationParams | None = None,
1125
+ send_params: SendParams | None = None,
1126
+ ) -> SendAppUpdateTransactionResult[Arc56ReturnValueType]:
1127
+ """Send an application update transaction.
1128
+
1129
+ Creates and sends a transaction that will update this application's program.
1130
+
1131
+ :param params: Parameters for the update call including method, compilation and transaction options
1132
+ :param compilation_params: Parameters for the compilation, defaults to None
1133
+ :param send_params: Send parameters, defaults to None
1134
+ :return: The result of sending the transaction, including ABI return value if applicable
1135
+ """
1136
+ result = self._client._handle_call_errors(
1137
+ lambda: self._client._process_method_call_return(
1138
+ lambda: self._algorand.send.app_update_method_call(
1139
+ self._client.params.update(params, compilation_params), send_params
1140
+ ),
1141
+ self._app_spec.get_abi_method(params.method),
1142
+ )
1143
+ )
1144
+ assert isinstance(result, SendAppUpdateTransactionResult)
1145
+ return result
1146
+
1147
+ def close_out(
1148
+ self, params: AppClientMethodCallParams, send_params: SendParams | None = None
1149
+ ) -> SendAppTransactionResult[Arc56ReturnValueType]:
1150
+ """Send an application close out transaction.
1151
+
1152
+ Creates and sends a transaction that will close out the sender's participation in this application.
1153
+
1154
+ :param params: Parameters for the close out call including method and transaction options
1155
+ :param send_params: Send parameters, defaults to None
1156
+ :return: The result of sending the transaction, including ABI return value if applicable
1157
+ """
1158
+ return self._client._handle_call_errors(
1159
+ lambda: self._client._process_method_call_return(
1160
+ lambda: self._algorand.send.app_call_method_call(self._client.params.close_out(params), send_params),
1161
+ self._app_spec.get_abi_method(params.method),
1162
+ )
1163
+ )
1164
+
1165
+ def call(
1166
+ self, params: AppClientMethodCallParams, send_params: SendParams | None = None
1167
+ ) -> SendAppTransactionResult[Arc56ReturnValueType]:
1168
+ """Send an application call transaction.
1169
+
1170
+ Creates and sends a transaction that will call this application with the specified parameters.
1171
+ For read-only calls, simulates the transaction instead of sending it.
1172
+
1173
+ :param params: Parameters for the application call including method and transaction options
1174
+ :param send_params: Send parameters
1175
+ :return: The result of sending or simulating the transaction, including ABI return value if applicable
1176
+ """
1177
+ is_read_only_call = (
1178
+ params.on_complete == OnApplicationComplete.NoOp or params.on_complete is None
1179
+ ) and self._app_spec.get_abi_method(params.method).readonly
1180
+
1181
+ if is_read_only_call:
1182
+ readonly_params = params
1183
+ readonly_send_params = send_params or SendParams()
1184
+ reported_fee = (
1185
+ params.static_fee.micro_algo if params.static_fee else self._algorand.get_suggested_params().min_fee
1186
+ )
1187
+ reset_reported_fee = False
1188
+
1189
+ # Read-only calls do not require fees to be paid, as they are only simulated on the network.
1190
+ # With maximum opcode budget provided, ensure_budget won't create inner transactions,
1191
+ # so fee coverage is no longer a concern for read-only calls.
1192
+ # If max_fee is provided, use it as static_fee for potential benefits.
1193
+ if readonly_send_params.get("cover_app_call_inner_transaction_fees") and params.max_fee is not None:
1194
+ readonly_params = replace(readonly_params, static_fee=params.max_fee, extra_fee=None)
1195
+ elif readonly_params.static_fee is None:
1196
+ fallback_fee = params.max_fee or AlgoAmount.from_micro_algo(MAX_SIMULATE_OPCODE_BUDGET)
1197
+ readonly_params = replace(readonly_params, static_fee=fallback_fee, extra_fee=None)
1198
+ reset_reported_fee = True
1199
+
1200
+ method_call_to_simulate = self._algorand.new_group().add_app_call_method_call(
1201
+ self._client.params.call(readonly_params)
1202
+ )
1203
+
1204
+ def run_simulate() -> SendTransactionComposerResults:
1205
+ try:
1206
+ return method_call_to_simulate.simulate(
1207
+ allow_unnamed_resources=readonly_send_params.get("populate_app_call_resources") or True,
1208
+ skip_signatures=True,
1209
+ allow_more_logs=True,
1210
+ allow_empty_signatures=True,
1211
+ extra_opcode_budget=MAX_SIMULATE_OPCODE_BUDGET,
1212
+ exec_trace_config=None,
1213
+ simulation_round=None,
1214
+ )
1215
+ except Exception as e:
1216
+ # For read-only calls with max opcode budget, fee issues should be rare
1217
+ # but we can still provide helpful error message if they occur
1218
+ if readonly_send_params.get("cover_app_call_inner_transaction_fees") and "fee too small" in str(e):
1219
+ raise ValueError(
1220
+ "Fees were too small. You may need to increase the transaction `maxFee`."
1221
+ ) from e
1222
+ raise
1223
+
1224
+ simulate_response = self._client._handle_call_errors(run_simulate)
1225
+
1226
+ wrapped_transactions = simulate_response.transactions
1227
+ if reset_reported_fee:
1228
+ wrapped_transactions = [replace(txn, fee=reported_fee) for txn in wrapped_transactions]
1229
+ return SendAppTransactionResult[Arc56ReturnValueType](
1230
+ tx_ids=simulate_response.tx_ids,
1231
+ transactions=wrapped_transactions,
1232
+ transaction=wrapped_transactions[-1],
1233
+ confirmation=simulate_response.confirmations[-1],
1234
+ confirmations=simulate_response.confirmations,
1235
+ group_id=simulate_response.group_id or "",
1236
+ returns=simulate_response.returns,
1237
+ abi_return=simulate_response.returns[-1].value,
1238
+ )
1239
+
1240
+ return self._client._handle_call_errors(
1241
+ lambda: self._client._process_method_call_return(
1242
+ lambda: self._algorand.send.app_call_method_call(self._client.params.call(params), send_params),
1243
+ self._app_spec.get_abi_method(params.method),
1244
+ )
1245
+ )
1246
+
1247
+
1248
+ @dataclass(kw_only=True, frozen=True)
1249
+ class AppClientParams:
1250
+ """Full parameters for creating an app client"""
1251
+
1252
+ app_spec: arc56.Arc56Contract | arc32.Arc32Contract | str
1253
+ """The application specification"""
1254
+ algorand: AlgorandClient
1255
+ """The Algorand client"""
1256
+ app_id: int
1257
+ """The application ID"""
1258
+ app_name: str | None = None
1259
+ """The application name"""
1260
+ default_sender: str | None = None
1261
+ """The default sender address"""
1262
+ default_signer: TransactionSigner | None = None
1263
+ """The default transaction signer"""
1264
+ approval_source_map: ProgramSourceMap | None = None
1265
+ """The approval source map"""
1266
+ clear_source_map: ProgramSourceMap | None = None
1267
+ """The clear source map"""
1268
+
1269
+
1270
+ class AppClient:
1271
+ """A client for interacting with an Algorand smart contract application.
1272
+
1273
+ Provides a high-level interface for interacting with Algorand smart contracts, including
1274
+ methods for calling application methods, managing state, and handling transactions.
1275
+
1276
+ :param params: Parameters for creating the app client
1277
+
1278
+ :example:
1279
+ >>> params = AppClientParams(
1280
+ ... app_spec=Arc56Contract.from_json(app_spec_json),
1281
+ ... algorand=algorand,
1282
+ ... app_id=1234567890,
1283
+ ... app_name="My App",
1284
+ ... default_sender="SENDERADDRESS",
1285
+ ... default_signer=TransactionSigner(
1286
+ ... account="SIGNERACCOUNT",
1287
+ ... private_key="SIGNERPRIVATEKEY",
1288
+ ... ),
1289
+ ... approval_source_map=ProgramSourceMap(
1290
+ ... source="APPROVALSOURCE",
1291
+ ... ),
1292
+ ... clear_source_map=ProgramSourceMap(
1293
+ ... source="CLEARSOURCE",
1294
+ ... ),
1295
+ ... )
1296
+ >>> client = AppClient(params)
1297
+ """
1298
+
1299
+ def __init__(self, params: AppClientParams) -> None:
1300
+ self._app_id = params.app_id
1301
+ self._app_spec = self.normalise_app_spec(params.app_spec)
1302
+ self._algorand = params.algorand
1303
+ self._app_address = get_application_address(self._app_id)
1304
+ self._app_name = params.app_name or self._app_spec.name
1305
+ self._default_sender = params.default_sender
1306
+ self._default_signer = params.default_signer
1307
+ self._approval_source_map = params.approval_source_map
1308
+ self._clear_source_map = params.clear_source_map
1309
+ self._last_compiled: dict[str, bytes] = {} # Track compiled programs for error filtering
1310
+ self._state_accessor = _StateAccessor(self)
1311
+ self._params_accessor = _MethodParamsBuilder(self)
1312
+ self._send_accessor = _TransactionSender(self)
1313
+ self._create_transaction_accessor = _TransactionCreator(self)
1314
+
1315
+ # Register the error transformer to handle app-specific logic errors
1316
+ self._algorand.register_error_transformer(self._handle_call_errors_transform)
1317
+
1318
+ @property
1319
+ def algorand(self) -> AlgorandClient:
1320
+ """Get the Algorand client instance.
1321
+
1322
+ :return: The Algorand client used by this app client
1323
+ """
1324
+ return self._algorand
1325
+
1326
+ @property
1327
+ def app_id(self) -> int:
1328
+ """Get the application ID.
1329
+
1330
+ :return: The ID of the Algorand application
1331
+ """
1332
+ return self._app_id
1333
+
1334
+ @property
1335
+ def app_address(self) -> str:
1336
+ """Get the application's Algorand address.
1337
+
1338
+ :return: The Algorand address associated with this application
1339
+ """
1340
+ return self._app_address
1341
+
1342
+ @property
1343
+ def app_name(self) -> str:
1344
+ """Get the application name.
1345
+
1346
+ :return: The name of the application
1347
+ """
1348
+ return self._app_name
1349
+
1350
+ @property
1351
+ def app_spec(self) -> arc56.Arc56Contract:
1352
+ """Get the application specification.
1353
+
1354
+ :return: The ARC-56 contract specification for this application
1355
+ """
1356
+ return self._app_spec
1357
+
1358
+ @property
1359
+ def state(self) -> _StateAccessor:
1360
+ """Get the state accessor.
1361
+
1362
+ :return: The state accessor for this application
1363
+ """
1364
+ return self._state_accessor
1365
+
1366
+ @property
1367
+ def params(self) -> _MethodParamsBuilder:
1368
+ """Get the method parameters builder.
1369
+
1370
+ :return: The method parameters builder for this application
1371
+
1372
+ :example:
1373
+ >>> # Create a transaction in the future using Algorand Client
1374
+ >>> my_method_call = app_client.params.call(AppClientMethodCallParams(
1375
+ method='my_method',
1376
+ args=[123, 'hello']))
1377
+ >>> # ...
1378
+ >>> await algorand.send.AppMethodCall(my_method_call)
1379
+ >>> # Define a nested transaction as an ABI argument
1380
+ >>> my_method_call = app_client.params.call(AppClientMethodCallParams(
1381
+ method='my_method',
1382
+ args=[123, 'hello']))
1383
+ >>> app_client.send.call(AppClientMethodCallParams(method='my_method2', args=[my_method_call]))
1384
+ """
1385
+ return self._params_accessor
1386
+
1387
+ @property
1388
+ def send(self) -> _TransactionSender:
1389
+ """Get the transaction sender.
1390
+
1391
+ :return: The transaction sender for this application
1392
+ """
1393
+ return self._send_accessor
1394
+
1395
+ @property
1396
+ def create_transaction(self) -> _TransactionCreator:
1397
+ """Get the transaction creator.
1398
+
1399
+ :return: The transaction creator for this application
1400
+ """
1401
+ return self._create_transaction_accessor
1402
+
1403
+ @staticmethod
1404
+ def normalise_app_spec(app_spec: arc56.Arc56Contract | arc32.Arc32Contract | str) -> arc56.Arc56Contract:
1405
+ """Normalize an application specification to ARC-56 format.
1406
+
1407
+ :param app_spec: The application specification to normalize. Can be raw arc32 or arc56 json,
1408
+ or an Arc32Contract or Arc56Contract instance
1409
+ :return: The normalized ARC-56 contract specification
1410
+ :raises ValueError: If the app spec format is invalid
1411
+
1412
+ :example:
1413
+ >>> spec = AppClient.normalise_app_spec(app_spec_json)
1414
+ """
1415
+ if isinstance(app_spec, str):
1416
+ spec_dict = json.loads(app_spec)
1417
+ spec = arc32.Arc32Contract.from_json(app_spec) if "hints" in spec_dict else spec_dict
1418
+ else:
1419
+ spec = app_spec
1420
+
1421
+ match spec:
1422
+ case arc56.Arc56Contract():
1423
+ return spec
1424
+ case arc32.Arc32Contract():
1425
+ from algokit_abi import arc32_to_arc56
1426
+
1427
+ return arc32_to_arc56(spec.to_json())
1428
+ case dict():
1429
+ return arc56.Arc56Contract.from_dict(spec)
1430
+ case _:
1431
+ raise ValueError("Invalid app spec format")
1432
+
1433
+ @staticmethod
1434
+ def from_network(
1435
+ app_spec: arc56.Arc56Contract | arc32.Arc32Contract | str,
1436
+ algorand: AlgorandClient,
1437
+ app_name: str | None = None,
1438
+ default_sender: str | None = None,
1439
+ default_signer: TransactionSigner | None = None,
1440
+ approval_source_map: ProgramSourceMap | None = None,
1441
+ clear_source_map: ProgramSourceMap | None = None,
1442
+ ) -> "AppClient":
1443
+ """Create an AppClient instance from network information.
1444
+
1445
+ :param app_spec: The application specification
1446
+ :param algorand: The Algorand client instance
1447
+ :param app_name: Optional application name
1448
+ :param default_sender: Optional default sender address
1449
+ :param default_signer: Optional default transaction signer
1450
+ :param approval_source_map: Optional approval program source map
1451
+ :param clear_source_map: Optional clear program source map
1452
+ :return: A new AppClient instance
1453
+ :raises Exception: If no app ID is found for the network
1454
+
1455
+ :example:
1456
+ >>> client = AppClient.from_network(
1457
+ ... app_spec=Arc56Contract.from_json(app_spec_json),
1458
+ ... algorand=algorand,
1459
+ ... app_name="My App",
1460
+ ... default_sender="SENDERADDRESS",
1461
+ ... default_signer=TransactionSigner(
1462
+ ... account="SIGNERACCOUNT",
1463
+ ... private_key="SIGNERPRIVATEKEY",
1464
+ ... ),
1465
+ ... approval_source_map=ProgramSourceMap(
1466
+ ... source="APPROVALSOURCE",
1467
+ ... ),
1468
+ ... clear_source_map=ProgramSourceMap(
1469
+ ... source="CLEARSOURCE",
1470
+ ... ),
1471
+ ... )
1472
+ """
1473
+ network = algorand.client.network()
1474
+ app_spec = AppClient.normalise_app_spec(app_spec)
1475
+ network_names = [network.genesis_hash]
1476
+
1477
+ if network.is_localnet:
1478
+ network_names.append("localnet")
1479
+ if network.is_mainnet:
1480
+ network_names.append("mainnet")
1481
+ if network.is_testnet:
1482
+ network_names.append("testnet")
1483
+
1484
+ available_app_spec_networks = list(app_spec.networks.keys()) if app_spec.networks else []
1485
+ network_index = next((i for i, n in enumerate(available_app_spec_networks) if n in network_names), None)
1486
+
1487
+ if network_index is None:
1488
+ raise Exception(f"No app ID found for network {json.dumps(network_names)} in the app spec")
1489
+
1490
+ app_id = app_spec.networks[available_app_spec_networks[network_index]].app_id # type: ignore[index]
1491
+
1492
+ return AppClient(
1493
+ AppClientParams(
1494
+ app_id=app_id,
1495
+ app_spec=app_spec,
1496
+ algorand=algorand,
1497
+ app_name=app_name,
1498
+ default_sender=default_sender,
1499
+ default_signer=default_signer,
1500
+ approval_source_map=approval_source_map,
1501
+ clear_source_map=clear_source_map,
1502
+ )
1503
+ )
1504
+
1505
+ @staticmethod
1506
+ def from_creator_and_name(
1507
+ creator_address: str,
1508
+ app_name: str,
1509
+ app_spec: arc56.Arc56Contract | arc32.Arc32Contract | str,
1510
+ algorand: AlgorandClient,
1511
+ default_sender: str | None = None,
1512
+ default_signer: TransactionSigner | None = None,
1513
+ approval_source_map: ProgramSourceMap | None = None,
1514
+ clear_source_map: ProgramSourceMap | None = None,
1515
+ ignore_cache: bool | None = None,
1516
+ app_lookup_cache: ApplicationLookup | None = None,
1517
+ ) -> "AppClient":
1518
+ """Create an AppClient instance from creator address and application name.
1519
+
1520
+ :param creator_address: The address of the application creator
1521
+ :param app_name: The name of the application
1522
+ :param app_spec: The application specification
1523
+ :param algorand: The Algorand client instance
1524
+ :param default_sender: Optional default sender address
1525
+ :param default_signer: Optional default transaction signer
1526
+ :param approval_source_map: Optional approval program source map
1527
+ :param clear_source_map: Optional clear program source map
1528
+ :param ignore_cache: Optional flag to ignore cache
1529
+ :param app_lookup_cache: Optional app lookup cache
1530
+ :return: A new AppClient instance
1531
+ :raises ValueError: If the app is not found for the creator and name
1532
+
1533
+ :example:
1534
+ >>> client = AppClient.from_creator_and_name(
1535
+ ... creator_address="CREATORADDRESS",
1536
+ ... app_name="APPNAME",
1537
+ ... app_spec=Arc56Contract.from_json(app_spec_json),
1538
+ ... algorand=algorand,
1539
+ ... )
1540
+ """
1541
+ app_spec_ = AppClient.normalise_app_spec(app_spec)
1542
+ app_lookup = app_lookup_cache or algorand.app_deployer.get_creator_apps_by_name(
1543
+ creator_address=creator_address, ignore_cache=ignore_cache or False
1544
+ )
1545
+ app_metadata = app_lookup.apps.get(app_name or app_spec_.name)
1546
+ if not app_metadata:
1547
+ raise ValueError(f"App not found for creator {creator_address} and name {app_name or app_spec_.name}")
1548
+
1549
+ return AppClient(
1550
+ AppClientParams(
1551
+ app_id=app_metadata.app_id,
1552
+ app_spec=app_spec_,
1553
+ algorand=algorand,
1554
+ app_name=app_name,
1555
+ default_sender=default_sender,
1556
+ default_signer=default_signer,
1557
+ approval_source_map=approval_source_map,
1558
+ clear_source_map=clear_source_map,
1559
+ )
1560
+ )
1561
+
1562
+ @staticmethod
1563
+ def compile(
1564
+ app_spec: arc56.Arc56Contract,
1565
+ app_manager: AppManager,
1566
+ compilation_params: AppClientCompilationParams | None = None,
1567
+ ) -> AppClientCompilationResult:
1568
+ """Compile the application's TEAL code.
1569
+
1570
+ :param app_spec: The application specification
1571
+ :param app_manager: The application manager instance
1572
+ :param compilation_params: Optional compilation parameters
1573
+ :return: The compilation result
1574
+ :raises ValueError: If attempting to compile without source or byte code
1575
+ """
1576
+ compilation_params = compilation_params or AppClientCompilationParams()
1577
+ deploy_time_params = compilation_params.get("deploy_time_params")
1578
+ updatable = compilation_params.get("updatable")
1579
+ deletable = compilation_params.get("deletable")
1580
+
1581
+ def is_base64(s: str) -> bool:
1582
+ try:
1583
+ return base64.b64encode(base64.b64decode(s)).decode() == s
1584
+ except Exception:
1585
+ return False
1586
+
1587
+ if not app_spec.source:
1588
+ if not app_spec.byte_code or not app_spec.byte_code.approval or not app_spec.byte_code.clear:
1589
+ raise ValueError(f"Attempt to compile app {app_spec.name} without source or byte_code")
1590
+
1591
+ return AppClientCompilationResult(
1592
+ approval_program=base64.b64decode(app_spec.byte_code.approval),
1593
+ clear_state_program=base64.b64decode(app_spec.byte_code.clear),
1594
+ )
1595
+
1596
+ compiled_approval = app_manager.compile_teal_template(
1597
+ app_spec.source.get_decoded_approval(),
1598
+ template_params=deploy_time_params,
1599
+ deployment_metadata=(
1600
+ {"updatable": updatable, "deletable": deletable}
1601
+ if updatable is not None or deletable is not None
1602
+ else None
1603
+ ),
1604
+ )
1605
+
1606
+ compiled_clear = app_manager.compile_teal_template(
1607
+ app_spec.source.get_decoded_clear(),
1608
+ template_params=deploy_time_params,
1609
+ )
1610
+
1611
+ if config.debug and config.project_root:
1612
+ persist_sourcemaps(
1613
+ sources=[
1614
+ PersistSourceMapInput(
1615
+ compiled_teal=compiled_approval, app_name=app_spec.name, file_name="approval.teal"
1616
+ ),
1617
+ PersistSourceMapInput(compiled_teal=compiled_clear, app_name=app_spec.name, file_name="clear.teal"),
1618
+ ],
1619
+ project_root=config.project_root,
1620
+ client=app_manager._algod,
1621
+ with_sources=True,
1622
+ )
1623
+
1624
+ return AppClientCompilationResult(
1625
+ approval_program=compiled_approval.compiled_base64_to_bytes,
1626
+ compiled_approval=compiled_approval,
1627
+ clear_state_program=compiled_clear.compiled_base64_to_bytes,
1628
+ compiled_clear=compiled_clear,
1629
+ )
1630
+
1631
+ @staticmethod
1632
+ def _expose_logic_error_static( # noqa: C901
1633
+ *,
1634
+ e: Exception,
1635
+ app_spec: arc56.Arc56Contract,
1636
+ is_clear_state_program: bool = False,
1637
+ approval_source_map: ProgramSourceMap | None = None,
1638
+ clear_source_map: ProgramSourceMap | None = None,
1639
+ program: bytes | None = None,
1640
+ approval_source_info: arc56.ProgramSourceInfo | None = None,
1641
+ clear_source_info: arc56.ProgramSourceInfo | None = None,
1642
+ ) -> LogicError | Exception:
1643
+ source_map = clear_source_map if is_clear_state_program else approval_source_map
1644
+
1645
+ error_details = parse_logic_error(str(e))
1646
+ if not error_details:
1647
+ return e
1648
+
1649
+ # The PC value to find in the ARC56 SourceInfo
1650
+ arc56_pc = error_details["pc"]
1651
+
1652
+ program_source_info = clear_source_info if is_clear_state_program else approval_source_info
1653
+
1654
+ # The offset to apply to the PC if using the cblocks pc offset method
1655
+ cblocks_offset = 0
1656
+
1657
+ # If the program uses cblocks offset, then we need to adjust the PC accordingly
1658
+ if program_source_info and program_source_info.pc_offset_method == arc56.PcOffsetMethod.CBLOCKS:
1659
+ if not program:
1660
+ raise Exception("Program bytes are required to calculate the ARC56 cblocks PC offset")
1661
+
1662
+ cblocks_offset = get_constant_block_offset(program)
1663
+ arc56_pc = error_details["pc"] - cblocks_offset
1664
+
1665
+ # Find the source info for this PC and get the error message
1666
+ source_info = None
1667
+ if program_source_info and program_source_info.source_info:
1668
+ source_info = next(
1669
+ (s for s in program_source_info.source_info if isinstance(s, arc56.SourceInfo) and arc56_pc in s.pc),
1670
+ None,
1671
+ )
1672
+ error_message = source_info.error_message if source_info else None
1673
+
1674
+ # If we have the source we can display the TEAL in the error message
1675
+ if hasattr(app_spec, "source"):
1676
+ program_source = (
1677
+ (
1678
+ app_spec.source.get_decoded_clear()
1679
+ if is_clear_state_program
1680
+ else app_spec.source.get_decoded_approval()
1681
+ )
1682
+ if app_spec.source
1683
+ else None
1684
+ )
1685
+ custom_get_line_for_pc = None
1686
+
1687
+ def get_line_for_pc(input_pc: int) -> int | None:
1688
+ if not program_source_info:
1689
+ return None
1690
+ teal = [line.teal for line in program_source_info.source_info if input_pc - cblocks_offset in line.pc]
1691
+ return teal[0] if teal else None
1692
+
1693
+ if not source_map:
1694
+ custom_get_line_for_pc = get_line_for_pc
1695
+
1696
+ if program_source:
1697
+ # Preserve traces from TransactionComposerError if available
1698
+ traces = getattr(e, "traces", None)
1699
+ e = LogicError(
1700
+ logic_error_str=str(e),
1701
+ program=program_source,
1702
+ source_map=source_map,
1703
+ transaction_id=error_details["transaction_id"],
1704
+ message=error_details["message"],
1705
+ pc=error_details["pc"],
1706
+ logic_error=e,
1707
+ get_line_for_pc=custom_get_line_for_pc,
1708
+ traces=traces,
1709
+ )
1710
+ if error_message:
1711
+ import re
1712
+
1713
+ message = e.logic_error_str if isinstance(e, LogicError) else str(e)
1714
+ app_id = re.search(r"(?<=app=)\d+", message)
1715
+ tx_id = re.search(r"(?<=transaction )\S+(?=:)", message)
1716
+ runtime_error_message = (
1717
+ f"Runtime error when executing {app_spec.name} "
1718
+ f"(appId: {app_id.group() if app_id else 'N/A'}) in transaction "
1719
+ f"{tx_id.group() if tx_id else 'N/A'}: {error_message}"
1720
+ )
1721
+ if isinstance(e, LogicError):
1722
+ e.message = runtime_error_message
1723
+ return e
1724
+ else:
1725
+ error = Exception(runtime_error_message)
1726
+ error.__cause__ = e
1727
+ return error
1728
+
1729
+ return e
1730
+
1731
+ def compile_app(
1732
+ self,
1733
+ compilation_params: AppClientCompilationParams | None = None,
1734
+ ) -> AppClientCompilationResult:
1735
+ """Compile the application's TEAL code.
1736
+
1737
+ :param compilation_params: Optional compilation parameters
1738
+ :return: The compilation result
1739
+ """
1740
+ result = AppClient.compile(self._app_spec, self._algorand.app, compilation_params)
1741
+
1742
+ if result.compiled_approval:
1743
+ self._approval_source_map = result.compiled_approval.source_map
1744
+ if result.compiled_clear:
1745
+ self._clear_source_map = result.compiled_clear.source_map
1746
+
1747
+ # Store compiled programs for new app error filtering
1748
+ self._last_compiled["approval"] = result.approval_program
1749
+ self._last_compiled["clear"] = result.clear_state_program
1750
+
1751
+ return result
1752
+
1753
+ def clone(
1754
+ self,
1755
+ app_name: str | None = _MISSING, # type: ignore[assignment]
1756
+ default_sender: str | None = _MISSING, # type: ignore[assignment]
1757
+ default_signer: TransactionSigner | None = _MISSING, # type: ignore[assignment]
1758
+ approval_source_map: ProgramSourceMap | None = _MISSING, # type: ignore[assignment]
1759
+ clear_source_map: ProgramSourceMap | None = _MISSING, # type: ignore[assignment]
1760
+ ) -> "AppClient":
1761
+ """Create a cloned AppClient instance with optionally overridden parameters.
1762
+
1763
+ :param app_name: Optional new application name
1764
+ :param default_sender: Optional new default sender
1765
+ :param default_signer: Optional new default signer
1766
+ :param approval_source_map: Optional new approval source map
1767
+ :param clear_source_map: Optional new clear source map
1768
+ :return: A new AppClient instance
1769
+
1770
+ :example:
1771
+ >>> client = AppClient(params)
1772
+ >>> cloned_client = client.clone(app_name="Cloned App", default_sender="NEW_SENDER")
1773
+ """
1774
+ return AppClient(
1775
+ AppClientParams(
1776
+ app_id=self._app_id,
1777
+ algorand=self._algorand,
1778
+ app_spec=self._app_spec,
1779
+ app_name=self._app_name if app_name is _MISSING else app_name,
1780
+ default_sender=self._default_sender if default_sender is _MISSING else default_sender,
1781
+ default_signer=self._default_signer if default_signer is _MISSING else default_signer,
1782
+ approval_source_map=(
1783
+ self._approval_source_map if approval_source_map is _MISSING else approval_source_map
1784
+ ),
1785
+ clear_source_map=(self._clear_source_map if clear_source_map is _MISSING else clear_source_map),
1786
+ )
1787
+ )
1788
+
1789
+ def export_source_maps(self) -> AppSourceMaps:
1790
+ """Export the application's source maps.
1791
+
1792
+ :return: The application's source maps
1793
+ :raises ValueError: If source maps haven't been loaded
1794
+ """
1795
+ if not self._approval_source_map or not self._clear_source_map:
1796
+ raise ValueError(
1797
+ "Unable to export source maps; they haven't been loaded into this client - "
1798
+ "you need to call create, update, or deploy first"
1799
+ )
1800
+
1801
+ return AppSourceMaps(
1802
+ approval_source_map=self._approval_source_map,
1803
+ clear_source_map=self._clear_source_map,
1804
+ )
1805
+
1806
+ def import_source_maps(self, source_maps: AppSourceMaps) -> None:
1807
+ """Import source maps for the application.
1808
+
1809
+ :param source_maps: The source maps to import
1810
+ :raises ValueError: If source maps are invalid or missing
1811
+ """
1812
+ if not source_maps.approval_source_map:
1813
+ raise ValueError("Approval source map is required")
1814
+ if not source_maps.clear_source_map:
1815
+ raise ValueError("Clear source map is required")
1816
+
1817
+ if not isinstance(source_maps.approval_source_map, dict | ProgramSourceMap):
1818
+ raise ValueError("Approval source map supplied is of invalid type. Must be a raw dict or `SourceMap`")
1819
+ if not isinstance(source_maps.clear_source_map, dict | ProgramSourceMap):
1820
+ raise ValueError("Clear source map supplied is of invalid type. Must be a raw dict or `SourceMap`")
1821
+
1822
+ self._approval_source_map = (
1823
+ ProgramSourceMap(source_map=source_maps.approval_source_map)
1824
+ if isinstance(source_maps.approval_source_map, dict)
1825
+ else source_maps.approval_source_map
1826
+ )
1827
+ self._clear_source_map = (
1828
+ ProgramSourceMap(source_map=source_maps.clear_source_map)
1829
+ if isinstance(source_maps.clear_source_map, dict)
1830
+ else source_maps.clear_source_map
1831
+ )
1832
+
1833
+ def get_local_state(self, address: str) -> dict[str, AppState]:
1834
+ """Get local state for an account.
1835
+
1836
+ :param address: The account address
1837
+ :return: The account's local state for this application
1838
+ """
1839
+ return self._state_accessor.get_local_state(address)
1840
+
1841
+ def get_global_state(self) -> dict[str, AppState]:
1842
+ """Get the application's global state.
1843
+
1844
+ :return: The application's global state
1845
+ :example:
1846
+ >>> global_state = client.get_global_state()
1847
+ """
1848
+ return self._state_accessor.get_global_state()
1849
+
1850
+ def get_box_names(self) -> list[BoxName]:
1851
+ """Get all box names for the application.
1852
+
1853
+ :return: List of box names
1854
+
1855
+ :example:
1856
+ >>> box_names = client.get_box_names()
1857
+ """
1858
+ return self._algorand.app.get_box_names(self._app_id)
1859
+
1860
+ def get_box_value(self, name: BoxIdentifier) -> bytes:
1861
+ """Get the value of a box.
1862
+
1863
+ :param name: The box identifier
1864
+ :return: The box value as bytes
1865
+
1866
+ :example:
1867
+ >>> box_value = client.get_box_value(box_name)
1868
+ """
1869
+ return self._algorand.app.get_box_value(self._app_id, name)
1870
+
1871
+ def get_box_value_from_abi_type(self, name: BoxIdentifier, abi_type: ABIType) -> ABIValue:
1872
+ """Get a box value decoded according to an ABI type.
1873
+
1874
+ :param name: The box identifier
1875
+ :param abi_type: The ABI type to decode as
1876
+ :return: The decoded box value
1877
+
1878
+ :example:
1879
+ >>> box_value = client.get_box_value_from_abi_type(box_name, abi_type)
1880
+ """
1881
+ return self._algorand.app.get_box_value_from_abi_type(self._app_id, name, abi_type)
1882
+
1883
+ def get_box_values(self, filter_func: Callable[[BoxName], bool] | None = None) -> list[BoxValue]:
1884
+ """Get values for multiple boxes.
1885
+
1886
+ :param filter_func: Optional function to filter box names
1887
+ :return: List of box values
1888
+
1889
+ :example:
1890
+ >>> box_values = client.get_box_values()
1891
+ """
1892
+ names = [n for n in self.get_box_names() if not filter_func or filter_func(n)]
1893
+ values = self._algorand.app.get_box_values(self.app_id, [n.name_raw for n in names])
1894
+ return [BoxValue(name=n, value=v) for n, v in zip(names, values, strict=False)]
1895
+
1896
+ def get_box_values_from_abi_type(
1897
+ self, abi_type: ABIType, filter_func: Callable[[BoxName], bool] | None = None
1898
+ ) -> list[BoxABIValue]:
1899
+ """Get multiple box values decoded according to an ABI type.
1900
+
1901
+ :param abi_type: The ABI type to decode as
1902
+ :param filter_func: Optional function to filter box names
1903
+ :return: List of decoded box values
1904
+
1905
+ :example:
1906
+ >>> box_values = client.get_box_values_from_abi_type(abi_type)
1907
+ """
1908
+ names = self.get_box_names()
1909
+ if filter_func:
1910
+ names = [name for name in names if filter_func(name)]
1911
+
1912
+ values = self._algorand.app.get_box_values_from_abi_type(
1913
+ self.app_id, [name.name_raw for name in names], abi_type
1914
+ )
1915
+
1916
+ return [BoxABIValue(name=name, value=values[i]) for i, name in enumerate(names)]
1917
+
1918
+ def fund_app_account(
1919
+ self, params: FundAppAccountParams, send_params: SendParams | None = None
1920
+ ) -> SendSingleTransactionResult:
1921
+ """Fund the application's account.
1922
+
1923
+ :param params: The funding parameters
1924
+ :param send_params: Send parameters, defaults to None
1925
+ :return: The transaction result
1926
+
1927
+ :example:
1928
+ >>> result = client.fund_app_account(params)
1929
+ """
1930
+ return self.send.fund_app_account(params, send_params)
1931
+
1932
+ def _expose_logic_error(self, e: Exception, *, is_clear_state_program: bool = False) -> Exception:
1933
+ source_info = None
1934
+ if hasattr(self._app_spec, "source_info") and self._app_spec.source_info:
1935
+ source_info = (
1936
+ self._app_spec.source_info.clear if is_clear_state_program else self._app_spec.source_info.approval
1937
+ )
1938
+
1939
+ pc_offset_method = source_info.pc_offset_method if source_info else None
1940
+
1941
+ program: bytes | None = None
1942
+ if pc_offset_method == "cblocks":
1943
+ app_info = self._algorand.app.get_by_id(self.app_id)
1944
+ program = app_info.clear_state_program if is_clear_state_program else app_info.approval_program
1945
+
1946
+ return AppClient._expose_logic_error_static(
1947
+ e=e,
1948
+ app_spec=self._app_spec,
1949
+ is_clear_state_program=is_clear_state_program,
1950
+ approval_source_map=self._approval_source_map,
1951
+ clear_source_map=self._clear_source_map,
1952
+ program=program,
1953
+ approval_source_info=(self._app_spec.source_info.approval if self._app_spec.source_info else None),
1954
+ clear_source_info=(self._app_spec.source_info.clear if self._app_spec.source_info else None),
1955
+ )
1956
+
1957
+ def _handle_call_errors(self, call: Callable[[], T]) -> T:
1958
+ try:
1959
+ return call()
1960
+ except Exception as e:
1961
+ raise self._expose_logic_error(e=e) from None
1962
+
1963
+ def _is_new_app_error_for_this_app(self, error: Exception) -> bool:
1964
+ """Check if an error from a new app (app_id=0) is for this specific app by comparing program bytecode."""
1965
+ if not hasattr(error, "sent_transactions") or not error.sent_transactions:
1966
+ return False
1967
+
1968
+ # Find the transaction that caused the error
1969
+ txn = None
1970
+ for t in error.sent_transactions:
1971
+ if hasattr(t, "get_txid") and t.get_txid() in str(error):
1972
+ txn = t
1973
+ break
1974
+
1975
+ app_call = getattr(txn, "app_call", None)
1976
+ if not txn or app_call is None:
1977
+ return False
1978
+
1979
+ return bool(
1980
+ app_call.clear_state_program == self._last_compiled.get("clear")
1981
+ and app_call.approval_program == self._last_compiled.get("approval")
1982
+ )
1983
+
1984
+ def _handle_call_errors_transform(self, error: Exception) -> Exception:
1985
+ """Error transformer function for app-specific logic errors.
1986
+
1987
+ This will be called by the transaction composer when errors occur during
1988
+ simulate or send operations to provide better error messages with source maps.
1989
+
1990
+ :param error: The error to potentially transform
1991
+ :return: The transformed error if it's an app logic error, otherwise the original error
1992
+ """
1993
+ try:
1994
+ # Check if this is a logic error that we can parse
1995
+ from algokit_utils.errors.logic_error import parse_logic_error
1996
+
1997
+ error_details = parse_logic_error(str(error))
1998
+ if not error_details:
1999
+ # Not a logic error, return unchanged
2000
+ return error
2001
+
2002
+ # Check if this error is for our app
2003
+ should_transform = False
2004
+
2005
+ if self._app_id == 0:
2006
+ # For new apps (app_id == 0), we can't use app ID filtering
2007
+ # Instead check the programs to identify if this is the correct app
2008
+ should_transform = self._is_new_app_error_for_this_app(error)
2009
+ else:
2010
+ # Only handle errors for this specific app
2011
+ app_id_string = f"app={self._app_id}"
2012
+ should_transform = app_id_string in str(error)
2013
+
2014
+ if not should_transform:
2015
+ # Error is not for this app, return unchanged
2016
+ return error
2017
+
2018
+ # This is a logic error for our app, transform it
2019
+ return self._expose_logic_error(e=error)
2020
+
2021
+ except Exception:
2022
+ # If transformation fails, return the original error
2023
+ return error
2024
+
2025
+ def _get_sender(self, sender: str | None) -> str:
2026
+ if not sender and not self._default_sender:
2027
+ raise Exception(
2028
+ f"No sender provided and no default sender present in app client for call to app {self.app_name}"
2029
+ )
2030
+ return sender or self._default_sender # type: ignore[return-value]
2031
+
2032
+ def _get_signer(
2033
+ self, sender: str | None, signer: TransactionSigner | AddressWithTransactionSigner | None
2034
+ ) -> TransactionSigner | AddressWithTransactionSigner | None:
2035
+ return signer or (self._default_signer if not sender or sender == self._default_sender else None)
2036
+
2037
+ def _get_bare_params(self, params: dict[str, Any], on_complete: OnApplicationComplete) -> dict[str, Any]:
2038
+ sender = self._get_sender(params.get("sender"))
2039
+ return {
2040
+ **params,
2041
+ "app_id": self._app_id,
2042
+ "sender": sender,
2043
+ "signer": self._get_signer(params.get("sender"), params.get("signer")),
2044
+ "on_complete": on_complete,
2045
+ }
2046
+
2047
+ def _get_abi_args_with_default_values(
2048
+ self,
2049
+ *,
2050
+ method_name_or_signature: str,
2051
+ args: Sequence[ABIValue | ABIStruct | AppMethodCallTransactionArgument | None] | None,
2052
+ sender: str,
2053
+ ) -> list[Any]:
2054
+ method = self._app_spec.get_abi_method(method_name_or_signature)
2055
+ result = list[ABIValue | ABIStruct | AppMethodCallTransactionArgument | None]()
2056
+
2057
+ if args and len(method.args) < len(args):
2058
+ raise ValueError(
2059
+ f"Unexpected arg at position {len(method.args)}. {method.name} only expects {len(method.args)} args"
2060
+ )
2061
+
2062
+ for i, method_arg in enumerate(method.args):
2063
+ arg_value = args[i] if args and i < len(args) else None
2064
+
2065
+ if arg_value is not None:
2066
+ result.append(arg_value)
2067
+ continue
2068
+
2069
+ default_value = method_arg.default_value
2070
+ arg_type = method_arg.type
2071
+ arg_name = method_arg.name or f"arg{i + 1}"
2072
+ if isinstance(arg_type, arc56.TransactionType):
2073
+ result.append(None)
2074
+ elif default_value:
2075
+ assert isinstance(arg_type, arc56.ReferenceType | abi.ABIType)
2076
+ result.append(self._get_abi_arg_default_value(arg_name, arg_type, default_value, sender))
2077
+ else:
2078
+ raise ValueError(f"No value provided for required argument {arg_name} in call to method {method.name}")
2079
+
2080
+ return result
2081
+
2082
+ def _get_abi_arg_default_value(
2083
+ self,
2084
+ arg_name: str,
2085
+ arg_type: abi.ABIType | arc56.ReferenceType,
2086
+ default_value: arc56.DefaultValue,
2087
+ sender: str,
2088
+ ) -> ABIValue:
2089
+ match default_value.source:
2090
+ case "literal":
2091
+ value_raw = base64.b64decode(default_value.data)
2092
+ value_type = default_value.type or arg_type
2093
+ return get_abi_decoded_value(value_raw, value_type)
2094
+
2095
+ case "method":
2096
+ default_method = self._app_spec.get_abi_method(default_value.data)
2097
+ empty_args = [None] * len(default_method.args)
2098
+ call_result = self.send.call(
2099
+ AppClientMethodCallParams(
2100
+ method=default_value.data,
2101
+ args=empty_args,
2102
+ sender=sender,
2103
+ )
2104
+ )
2105
+
2106
+ if call_result.abi_return is None:
2107
+ raise ValueError("Default value method call did not return a value")
2108
+ assert isinstance(default_method.returns.type, abi.ABIType)
2109
+ return call_result.abi_return
2110
+
2111
+ case "local" | "global" | "box":
2112
+ key = base64.b64decode(default_value.data)
2113
+ try:
2114
+ value, storage_key = self._get_storage_value(default_value.source, key, sender)
2115
+ except KeyError:
2116
+ raise ValueError(
2117
+ f"Key '{default_value.data}' not found in {default_value.source} "
2118
+ f"storage for argument {arg_name}"
2119
+ ) from None
2120
+
2121
+ decoded_value: ABIValue
2122
+ if isinstance(value, bytes):
2123
+ # special case to convert raw AVM bytes to a native string type suitable for encoding
2124
+ if storage_key.value_type == arc56.AVMType.BYTES and isinstance(arg_type, abi.StringType):
2125
+ decoded_value = value.decode("utf-8")
2126
+ else:
2127
+ decoded_value = get_abi_decoded_value(value, storage_key.value_type)
2128
+ else:
2129
+ decoded_value = value
2130
+ return decoded_value
2131
+ case _:
2132
+ assert_never(default_value.source)
2133
+
2134
+ def _get_storage_value(
2135
+ self, source: Literal["local", "global", "box"], key: bytes, sender: str
2136
+ ) -> tuple[bytes | int | str, arc56.StorageKey]:
2137
+ state_keys = self.app_spec.state.keys
2138
+ if source == "global":
2139
+ state = {s.key_raw: s for s in self.get_global_state().values()}[key]
2140
+ value = state.value_raw if state.value_raw is not None else state.value
2141
+ storage_keys = state_keys.global_state
2142
+ elif source == "local":
2143
+ state = {s.key_raw: s for s in self.get_local_state(sender).values()}[key]
2144
+ value = state.value_raw if state.value_raw is not None else state.value
2145
+ storage_keys = state_keys.local_state
2146
+ elif source == "box":
2147
+ value = self.get_box_value(key)
2148
+ storage_keys = state_keys.box
2149
+ else:
2150
+ assert_never(source)
2151
+
2152
+ key_base64 = base64.b64encode(key).decode("ascii")
2153
+ storage_key = {sk.key: sk for sk in storage_keys.values()}[key_base64]
2154
+ return value, storage_key
2155
+
2156
+ def _get_abi_params(self, params: dict[str, Any], on_complete: OnApplicationComplete) -> dict[str, Any]:
2157
+ sender = self._get_sender(params.get("sender"))
2158
+ method = self._app_spec.get_abi_method(params["method"])
2159
+ args = self._get_abi_args_with_default_values(
2160
+ method_name_or_signature=params["method"], args=params.get("args"), sender=sender
2161
+ )
2162
+ return {
2163
+ **params,
2164
+ "appId": self._app_id,
2165
+ "sender": sender,
2166
+ "signer": self._get_signer(params.get("sender"), params.get("signer")),
2167
+ "method": method,
2168
+ "onComplete": on_complete,
2169
+ "args": args,
2170
+ }
2171
+
2172
+ def _process_method_call_return(
2173
+ self,
2174
+ result: Callable[[], SendAppUpdateTransactionResult[ABIReturn] | SendAppTransactionResult[ABIReturn]],
2175
+ method: arc56.Method,
2176
+ ) -> SendAppUpdateTransactionResult[Arc56ReturnValueType] | SendAppTransactionResult[Arc56ReturnValueType]:
2177
+ _ = method # kept for compatibility
2178
+ result_value = result()
2179
+ abi_return_value: Arc56ReturnValueType
2180
+ if isinstance(result_value.abi_return, ABIReturn):
2181
+ if result_value.abi_return.decode_error:
2182
+ raise ValueError(result_value.abi_return.decode_error)
2183
+ abi_return_value = result_value.abi_return.value
2184
+ else:
2185
+ abi_return_value = None
2186
+
2187
+ if isinstance(result_value, SendAppUpdateTransactionResult):
2188
+ return SendAppUpdateTransactionResult[Arc56ReturnValueType](
2189
+ **{**result_value.__dict__, "abi_return": abi_return_value}
2190
+ )
2191
+ return SendAppTransactionResult[Arc56ReturnValueType](
2192
+ **{**result_value.__dict__, "abi_return": abi_return_value}
2193
+ )