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,788 @@
1
+ import base64
2
+ import dataclasses
3
+ import json
4
+ from dataclasses import asdict, dataclass, fields
5
+ from typing import Literal, TypeVar
6
+
7
+ from algokit_algod_client import models as algod_models
8
+ from algokit_common import get_application_address
9
+ from algokit_indexer_client import IndexerClient
10
+ from algokit_utils.applications.abi import ABIReturn
11
+ from algokit_utils.applications.app_manager import AppManager
12
+ from algokit_utils.applications.enums import OnSchemaBreak, OnUpdate, OperationPerformed
13
+ from algokit_utils.config import config
14
+ from algokit_utils.models.state import TealTemplateParams
15
+ from algokit_utils.models.transaction import SendParams
16
+ from algokit_utils.transactions.transaction_composer import (
17
+ AppCreateMethodCallParams,
18
+ AppCreateParams,
19
+ AppDeleteMethodCallParams,
20
+ AppDeleteParams,
21
+ AppUpdateMethodCallParams,
22
+ AppUpdateParams,
23
+ TransactionComposer,
24
+ calculate_extra_program_pages,
25
+ )
26
+ from algokit_utils.transactions.transaction_sender import (
27
+ AlgorandClientTransactionSender,
28
+ SendAppCreateTransactionResult,
29
+ SendAppTransactionResult,
30
+ SendAppUpdateTransactionResult,
31
+ )
32
+
33
+ __all__ = [
34
+ "APP_DEPLOY_NOTE_DAPP",
35
+ "AppDeployParams",
36
+ "AppDeployResult",
37
+ "AppDeployer",
38
+ "AppDeploymentMetaData",
39
+ "ApplicationLookup",
40
+ "ApplicationMetaData",
41
+ "ApplicationReference",
42
+ "OnSchemaBreak",
43
+ "OnUpdate",
44
+ "OperationPerformed",
45
+ ]
46
+
47
+
48
+ APP_DEPLOY_NOTE_DAPP: str = "ALGOKIT_DEPLOYER"
49
+
50
+
51
+ @dataclasses.dataclass
52
+ class AppDeploymentMetaData:
53
+ """Metadata about an application stored in a transaction note during creation."""
54
+
55
+ name: str
56
+ version: str
57
+ deletable: bool | None
58
+ updatable: bool | None
59
+
60
+ def dictify(self) -> dict[str, str | bool]:
61
+ return {k: v for k, v in asdict(self).items() if v is not None}
62
+
63
+
64
+ @dataclasses.dataclass(frozen=True)
65
+ class ApplicationReference:
66
+ """Information about an Algorand app"""
67
+
68
+ app_id: int
69
+ app_address: str
70
+
71
+
72
+ @dataclasses.dataclass(frozen=True)
73
+ class ApplicationMetaData:
74
+ """Complete metadata about a deployed app"""
75
+
76
+ reference: ApplicationReference
77
+ deploy_metadata: AppDeploymentMetaData
78
+ created_round: int
79
+ updated_round: int
80
+ deleted: bool = False
81
+
82
+ @property
83
+ def app_id(self) -> int:
84
+ return self.reference.app_id
85
+
86
+ @property
87
+ def app_address(self) -> str:
88
+ return self.reference.app_address
89
+
90
+ @property
91
+ def name(self) -> str:
92
+ return self.deploy_metadata.name
93
+
94
+ @property
95
+ def version(self) -> str:
96
+ return self.deploy_metadata.version
97
+
98
+ @property
99
+ def deletable(self) -> bool | None:
100
+ return self.deploy_metadata.deletable
101
+
102
+ @property
103
+ def updatable(self) -> bool | None:
104
+ return self.deploy_metadata.updatable
105
+
106
+
107
+ @dataclasses.dataclass
108
+ class ApplicationLookup:
109
+ """Cache of {py:class}`ApplicationMetaData` for a specific `creator`
110
+
111
+ Can be used as an argument to {py:class}`AppClient` to reduce the number of calls when deploying multiple
112
+ apps or discovering multiple app_ids
113
+ """
114
+
115
+ creator: str
116
+ apps: dict[str, ApplicationMetaData] = dataclasses.field(default_factory=dict)
117
+
118
+
119
+ @dataclass(kw_only=True)
120
+ class AppDeployParams:
121
+ """Parameters for deploying an app"""
122
+
123
+ metadata: AppDeploymentMetaData
124
+ """The deployment metadata"""
125
+ deploy_time_params: TealTemplateParams | None = None
126
+ """Optional template parameters to use during compilation"""
127
+ on_schema_break: (Literal["replace", "fail", "append"] | OnSchemaBreak) | None = None
128
+ """Optional on schema break action"""
129
+ on_update: (Literal["update", "replace", "fail", "append"] | OnUpdate) | None = None
130
+ """Optional on update action"""
131
+ create_params: AppCreateParams | AppCreateMethodCallParams
132
+ """The creation parameters"""
133
+ update_params: AppUpdateParams | AppUpdateMethodCallParams
134
+ """The update parameters"""
135
+ delete_params: AppDeleteParams | AppDeleteMethodCallParams
136
+ """The deletion parameters"""
137
+ existing_deployments: ApplicationLookup | None = None
138
+ """Optional existing deployments"""
139
+ ignore_cache: bool = False
140
+ """Whether to ignore the cache"""
141
+ max_fee: int | None = None
142
+ """Optional maximum fee"""
143
+ send_params: SendParams | None = None
144
+ """Optional send parameters"""
145
+
146
+
147
+ # Union type for all possible deploy results
148
+ @dataclass(frozen=True)
149
+ class AppDeployResult:
150
+ """The result of a deployment"""
151
+
152
+ app: ApplicationMetaData
153
+ """The application metadata"""
154
+ operation_performed: OperationPerformed
155
+ """The operation performed"""
156
+ create_result: SendAppCreateTransactionResult[ABIReturn] | None = None
157
+ """The create result"""
158
+ update_result: SendAppUpdateTransactionResult[ABIReturn] | None = None
159
+ """The update result"""
160
+ delete_result: SendAppTransactionResult[ABIReturn] | None = None
161
+ """The delete result"""
162
+
163
+
164
+ class AppDeployer:
165
+ """Manages deployment and deployment metadata of applications
166
+
167
+ :param app_manager: The app manager to use
168
+ :param transaction_sender: The transaction sender to use
169
+ :param indexer: The indexer to use
170
+
171
+ :example:
172
+ >>> deployer = AppDeployer(app_manager, transaction_sender, indexer)
173
+ """
174
+
175
+ def __init__(
176
+ self,
177
+ app_manager: AppManager,
178
+ transaction_sender: AlgorandClientTransactionSender,
179
+ indexer: IndexerClient | None = None,
180
+ ):
181
+ self._app_manager = app_manager
182
+ self._transaction_sender = transaction_sender
183
+ self._indexer = indexer
184
+ self._app_lookups: dict[str, ApplicationLookup] = {}
185
+
186
+ def deploy(self, deployment: AppDeployParams) -> AppDeployResult:
187
+ """Idempotently deploy (create if not exists, update if changed) an app against the given name for the given
188
+ creator account, including deploy-time TEAL template placeholder substitutions (if specified).
189
+
190
+ To understand the architecture decisions behind this functionality please see
191
+ https://github.com/algorandfoundation/algokit-cli/blob/main/docs/architecture-decisions/2023-01-12_smart-contract-deployment.md
192
+
193
+ **Note:** When using the return from this function be sure to check `operation_performed` to get access to
194
+ return properties like `transaction`, `confirmation` and `delete_result`.
195
+
196
+ **Note:** if there is a breaking state schema change to an existing app (and `on_schema_break` is set to
197
+ `'replace'`) the existing app will be deleted and re-created.
198
+
199
+ **Note:** if there is an update (different TEAL code) to an existing app (and `on_update` is set to `'replace'`)
200
+ the existing app will be deleted and re-created.
201
+
202
+ :param deployment: The arguments to control the app deployment
203
+ :returns: The result of the deployment
204
+ :raises ValueError: If the app spec format is invalid
205
+
206
+ :example:
207
+ >>> deployer.deploy(AppDeployParams(
208
+ ... create_params=AppCreateParams(
209
+ ... sender='SENDER_ADDRESS',
210
+ ... approval_program='APPROVAL PROGRAM',
211
+ ... clear_state_program='CLEAR PROGRAM',
212
+ ... schema={
213
+ ... 'global_byte_slices': 0,
214
+ ... 'global_ints': 0,
215
+ ... 'local_byte_slices': 0,
216
+ ... 'local_ints': 0
217
+ ... }
218
+ ... ),
219
+ ... update_params=AppUpdateParams(
220
+ ... sender='SENDER_ADDRESS'
221
+ ... ),
222
+ ... delete_params=AppDeleteParams(
223
+ ... sender='SENDER_ADDRESS'
224
+ ... ),
225
+ ... metadata=AppDeploymentMetaData(
226
+ ... name='my_app',
227
+ ... version='2.0',
228
+ ... updatable=False,
229
+ ... deletable=False
230
+ ... ),
231
+ ... on_schema_break=OnSchemaBreak.AppendApp,
232
+ ... on_update=OnUpdate.AppendApp
233
+ ... )
234
+ ... )
235
+ """
236
+
237
+ # Create new instances with updated notes
238
+ send_params = deployment.send_params or SendParams()
239
+ suppress_log = send_params.get("suppress_log") or False
240
+
241
+ config.logger.info(
242
+ f'Idempotently deploying app "{deployment.metadata.name}" from creator '
243
+ f"{deployment.create_params.sender} using {len(deployment.create_params.approval_program)} bytes of "
244
+ f"{'teal code' if isinstance(deployment.create_params.approval_program, str) else 'AVM bytecode'} and "
245
+ f"{len(deployment.create_params.clear_state_program)} bytes of "
246
+ f"{'teal code' if isinstance(deployment.create_params.clear_state_program, str) else 'AVM bytecode'}",
247
+ extra={"suppress_log": suppress_log},
248
+ )
249
+ note = TransactionComposer.arc2_note(
250
+ {
251
+ "dapp_name": APP_DEPLOY_NOTE_DAPP,
252
+ "format": "j",
253
+ "data": deployment.metadata.dictify(),
254
+ }
255
+ )
256
+ create_params = dataclasses.replace(deployment.create_params, note=note)
257
+ update_params = dataclasses.replace(deployment.update_params, note=note)
258
+
259
+ deployment = dataclasses.replace(
260
+ deployment,
261
+ create_params=create_params,
262
+ update_params=update_params,
263
+ )
264
+
265
+ # Validate inputs
266
+ if (
267
+ deployment.existing_deployments
268
+ and deployment.existing_deployments.creator != deployment.create_params.sender
269
+ ):
270
+ raise ValueError(
271
+ f"Received invalid existingDeployments value for creator "
272
+ f"{deployment.existing_deployments.creator} when attempting to deploy "
273
+ f"for creator {deployment.create_params.sender}"
274
+ )
275
+
276
+ if not deployment.existing_deployments and not self._indexer:
277
+ raise ValueError(
278
+ "Didn't receive an indexer client when this AppManager was created, "
279
+ "but also didn't receive an existingDeployments cache - one of them must be provided"
280
+ )
281
+
282
+ # Compile code if needed
283
+ approval_program = deployment.create_params.approval_program
284
+ clear_program = deployment.create_params.clear_state_program
285
+
286
+ if isinstance(approval_program, str):
287
+ compiled_approval = self._app_manager.compile_teal_template(
288
+ approval_program,
289
+ deployment.deploy_time_params,
290
+ deployment.metadata.__dict__,
291
+ )
292
+ approval_program = compiled_approval.compiled_base64_to_bytes
293
+
294
+ if isinstance(clear_program, str):
295
+ compiled_clear = self._app_manager.compile_teal_template(
296
+ clear_program,
297
+ deployment.deploy_time_params,
298
+ )
299
+ clear_program = compiled_clear.compiled_base64_to_bytes
300
+
301
+ # Get existing app metadata
302
+ apps = deployment.existing_deployments or self.get_creator_apps_by_name(
303
+ creator_address=deployment.create_params.sender,
304
+ ignore_cache=deployment.ignore_cache,
305
+ )
306
+
307
+ existing_app = apps.apps.get(deployment.metadata.name)
308
+ if not existing_app or existing_app.deleted:
309
+ return self._create_app(
310
+ deployment=deployment,
311
+ approval_program=approval_program,
312
+ clear_program=clear_program,
313
+ )
314
+
315
+ # Check for changes
316
+ existing_app_record = self._app_manager.get_by_id(existing_app.app_id)
317
+
318
+ existing_approval = base64.b64encode(existing_app_record.approval_program).decode()
319
+ existing_clear = base64.b64encode(existing_app_record.clear_state_program).decode()
320
+ extra_pages = existing_app_record.extra_program_pages or 0
321
+
322
+ calculate_extra_program_pages(existing_app_record.approval_program, existing_app_record.clear_state_program)
323
+
324
+ new_approval = base64.b64encode(approval_program).decode()
325
+ new_clear = base64.b64encode(clear_program).decode()
326
+ new_extra_pages = calculate_extra_program_pages(approval_program, clear_program)
327
+
328
+ is_update = new_approval != existing_approval or new_clear != existing_clear
329
+ is_schema_break = (
330
+ existing_app_record.local_ints
331
+ < (deployment.create_params.schema.get("local_ints", 0) if deployment.create_params.schema else 0)
332
+ or existing_app_record.global_ints
333
+ < (deployment.create_params.schema.get("global_ints", 0) if deployment.create_params.schema else 0)
334
+ or existing_app_record.local_byte_slices
335
+ < (deployment.create_params.schema.get("local_byte_slices", 0) if deployment.create_params.schema else 0)
336
+ or existing_app_record.global_byte_slices
337
+ < (deployment.create_params.schema.get("global_byte_slices", 0) if deployment.create_params.schema else 0)
338
+ or extra_pages < new_extra_pages
339
+ )
340
+
341
+ if is_schema_break:
342
+ config.logger.warning(
343
+ f"Detected a breaking app schema change in app {existing_app.app_id}:",
344
+ extra={
345
+ "from": {
346
+ "global_ints": existing_app_record.global_ints,
347
+ "global_byte_slices": existing_app_record.global_byte_slices,
348
+ "local_ints": existing_app_record.local_ints,
349
+ "local_byte_slices": existing_app_record.local_byte_slices,
350
+ },
351
+ "to": deployment.create_params.schema,
352
+ "suppress_log": suppress_log,
353
+ },
354
+ )
355
+
356
+ return self._handle_schema_break(
357
+ deployment=deployment,
358
+ existing_app=existing_app,
359
+ approval_program=approval_program,
360
+ clear_program=clear_program,
361
+ )
362
+
363
+ if is_update:
364
+ config.logger.info(
365
+ f"Detected a TEAL update in app {existing_app.app_id} for creator {deployment.create_params.sender}",
366
+ extra={"suppress_log": suppress_log},
367
+ )
368
+ return self._handle_update(
369
+ deployment=deployment,
370
+ existing_app=existing_app,
371
+ approval_program=approval_program,
372
+ clear_program=clear_program,
373
+ )
374
+
375
+ config.logger.debug("No detected changes in app, nothing to do.", extra={"suppress_log": suppress_log})
376
+ return AppDeployResult(
377
+ app=existing_app,
378
+ operation_performed=OperationPerformed.Nothing,
379
+ )
380
+
381
+ def _create_app(
382
+ self,
383
+ deployment: AppDeployParams,
384
+ approval_program: bytes,
385
+ clear_program: bytes,
386
+ ) -> AppDeployResult:
387
+ """Create a new application"""
388
+
389
+ if isinstance(deployment.create_params, AppCreateMethodCallParams):
390
+ create_result = self._transaction_sender.app_create_method_call(
391
+ extend(
392
+ AppCreateMethodCallParams,
393
+ deployment.create_params,
394
+ approval_program=approval_program,
395
+ clear_state_program=clear_program,
396
+ ),
397
+ send_params=deployment.send_params,
398
+ )
399
+ else:
400
+ create_result = self._transaction_sender.app_create(
401
+ extend(
402
+ AppCreateParams,
403
+ deployment.create_params,
404
+ approval_program=approval_program,
405
+ clear_state_program=clear_program,
406
+ ),
407
+ send_params=deployment.send_params,
408
+ )
409
+
410
+ app_metadata = ApplicationMetaData(
411
+ reference=ApplicationReference(
412
+ app_id=create_result.app_id, app_address=get_application_address(create_result.app_id)
413
+ ),
414
+ deploy_metadata=deployment.metadata,
415
+ created_round=_get_confirmed_round(create_result.confirmation),
416
+ updated_round=_get_confirmed_round(create_result.confirmation),
417
+ deleted=False,
418
+ )
419
+
420
+ self._update_app_lookup(deployment.create_params.sender, app_metadata)
421
+ config.logger.debug(
422
+ f"Sent transaction ID {create_result.app_id} (AppCreate) from {deployment.create_params.sender}",
423
+ extra={
424
+ "suppress_log": deployment.send_params.get("suppress_log") or False if deployment.send_params else False
425
+ },
426
+ )
427
+
428
+ return AppDeployResult(
429
+ app=app_metadata,
430
+ operation_performed=OperationPerformed.Create,
431
+ create_result=create_result,
432
+ )
433
+
434
+ def _replace_app(
435
+ self,
436
+ deployment: AppDeployParams,
437
+ existing_app: ApplicationMetaData,
438
+ approval_program: bytes,
439
+ clear_program: bytes,
440
+ ) -> AppDeployResult:
441
+ composer = self._transaction_sender.new_group()
442
+
443
+ # Add create transaction
444
+ has_abi_create = isinstance(deployment.create_params, AppCreateMethodCallParams)
445
+ if has_abi_create:
446
+ composer.add_app_create_method_call(
447
+ AppCreateMethodCallParams(
448
+ **{
449
+ **deployment.create_params.__dict__,
450
+ "approval_program": approval_program,
451
+ "clear_state_program": clear_program,
452
+ }
453
+ )
454
+ )
455
+ else:
456
+ composer.add_app_create(
457
+ AppCreateParams(
458
+ **{
459
+ **deployment.create_params.__dict__,
460
+ "approval_program": approval_program,
461
+ "clear_state_program": clear_program,
462
+ }
463
+ )
464
+ )
465
+
466
+ # Add delete transaction
467
+ has_abi_delete = isinstance(deployment.delete_params, AppDeleteMethodCallParams)
468
+ if has_abi_delete:
469
+ delete_call_params = AppDeleteMethodCallParams(
470
+ **{
471
+ **deployment.delete_params.__dict__,
472
+ "app_id": existing_app.app_id,
473
+ }
474
+ )
475
+ composer.add_app_delete_method_call(delete_call_params)
476
+ else:
477
+ delete_params = AppDeleteParams(
478
+ **{
479
+ **deployment.delete_params.__dict__,
480
+ "app_id": existing_app.app_id,
481
+ }
482
+ )
483
+ composer.add_app_delete(delete_params)
484
+
485
+ result = composer.send(deployment.send_params)
486
+
487
+ create_result = SendAppCreateTransactionResult[ABIReturn].from_composer_result(
488
+ result, is_abi=has_abi_create, index=0
489
+ )
490
+ delete_result = SendAppTransactionResult[ABIReturn].from_composer_result(
491
+ result, is_abi=has_abi_delete, index=-1
492
+ )
493
+
494
+ app_id_raw = result.confirmations[0].app_id
495
+ if app_id_raw is None:
496
+ raise ValueError("Could not determine app_id from transaction confirmation")
497
+ assert app_id_raw is not None
498
+ app_id = int(app_id_raw)
499
+ app_metadata = ApplicationMetaData(
500
+ reference=ApplicationReference(app_id=app_id, app_address=get_application_address(app_id)),
501
+ deploy_metadata=deployment.metadata,
502
+ created_round=_get_confirmed_round(result.confirmations[0]),
503
+ updated_round=_get_confirmed_round(result.confirmations[0]),
504
+ deleted=False,
505
+ )
506
+ self._update_app_lookup(deployment.create_params.sender, app_metadata)
507
+ config.logger.debug(
508
+ f"Group transaction sent: Replaced app {existing_app.app_id} with new app {app_id} from "
509
+ f"{deployment.create_params.sender} (Composer group count: {composer.count()})",
510
+ extra={
511
+ "suppress_log": deployment.send_params.get("suppress_log") or False if deployment.send_params else False
512
+ },
513
+ )
514
+
515
+ return AppDeployResult(
516
+ app=app_metadata,
517
+ operation_performed=OperationPerformed.Replace,
518
+ create_result=create_result,
519
+ update_result=None,
520
+ delete_result=delete_result,
521
+ )
522
+
523
+ def _update_app(
524
+ self,
525
+ deployment: AppDeployParams,
526
+ existing_app: ApplicationMetaData,
527
+ approval_program: bytes,
528
+ clear_program: bytes,
529
+ ) -> AppDeployResult:
530
+ """Update an existing application"""
531
+
532
+ if isinstance(deployment.update_params, AppUpdateMethodCallParams):
533
+ result = self._transaction_sender.app_update_method_call(
534
+ AppUpdateMethodCallParams(
535
+ **{
536
+ **deployment.update_params.__dict__,
537
+ "app_id": existing_app.app_id,
538
+ "approval_program": approval_program,
539
+ "clear_state_program": clear_program,
540
+ }
541
+ ),
542
+ send_params=deployment.send_params,
543
+ )
544
+ else:
545
+ result = self._transaction_sender.app_update(
546
+ AppUpdateParams(
547
+ **{
548
+ **deployment.update_params.__dict__,
549
+ "app_id": existing_app.app_id,
550
+ "approval_program": approval_program,
551
+ "clear_state_program": clear_program,
552
+ }
553
+ ),
554
+ send_params=deployment.send_params,
555
+ )
556
+
557
+ app_metadata = ApplicationMetaData(
558
+ reference=ApplicationReference(app_id=existing_app.app_id, app_address=existing_app.app_address),
559
+ deploy_metadata=deployment.metadata,
560
+ created_round=existing_app.created_round,
561
+ updated_round=_get_confirmed_round(result.confirmation),
562
+ deleted=False,
563
+ )
564
+
565
+ self._update_app_lookup(deployment.create_params.sender, app_metadata)
566
+ config.logger.debug(
567
+ f"Sent transaction ID {existing_app.app_id} (AppUpdate) from {deployment.create_params.sender}",
568
+ extra={
569
+ "suppress_log": deployment.send_params.get("suppress_log") or False if deployment.send_params else False
570
+ },
571
+ )
572
+
573
+ return AppDeployResult(
574
+ app=app_metadata,
575
+ operation_performed=OperationPerformed.Update,
576
+ update_result=result,
577
+ )
578
+
579
+ def _handle_schema_break(
580
+ self,
581
+ deployment: AppDeployParams,
582
+ existing_app: ApplicationMetaData,
583
+ approval_program: bytes,
584
+ clear_program: bytes,
585
+ ) -> AppDeployResult:
586
+ suppress_log = deployment.send_params.get("suppress_log") or False if deployment.send_params else False
587
+
588
+ if deployment.on_schema_break in (OnSchemaBreak.Fail, "fail") or deployment.on_schema_break is None:
589
+ raise ValueError(
590
+ "Schema break detected and on_schema_break=OnSchemaBreak.Fail, stopping deployment. "
591
+ "If you want to try deleting and recreating the app then "
592
+ "re-run with on_schema_break=OnSchemaBreak.ReplaceApp"
593
+ )
594
+
595
+ if deployment.on_schema_break in (OnSchemaBreak.AppendApp, "append"):
596
+ config.logger.info(
597
+ "on_schema_break=AppendApp, will attempt to create a new app", extra={"suppress_log": suppress_log}
598
+ )
599
+ return self._create_app(deployment, approval_program, clear_program)
600
+
601
+ if existing_app.deletable:
602
+ config.logger.info(
603
+ "App is deletable and on_schema_break=ReplaceApp, will attempt to create new app and delete old app",
604
+ extra={"suppress_log": suppress_log},
605
+ )
606
+ else:
607
+ config.logger.info(
608
+ "App is not deletable but on_schema_break=ReplaceApp, will attempt to create a new app and "
609
+ "delete the old app, delete will most likely fail",
610
+ extra={
611
+ "suppress_log": suppress_log,
612
+ },
613
+ )
614
+
615
+ return self._replace_app(deployment, existing_app, approval_program, clear_program)
616
+
617
+ def _handle_update(
618
+ self,
619
+ deployment: AppDeployParams,
620
+ existing_app: ApplicationMetaData,
621
+ approval_program: bytes,
622
+ clear_program: bytes,
623
+ ) -> AppDeployResult:
624
+ suppress_log = deployment.send_params.get("suppress_log") or False if deployment.send_params else False
625
+
626
+ if deployment.on_update in (OnUpdate.Fail, "fail") or deployment.on_update is None:
627
+ raise ValueError(
628
+ "Update detected and on_update=Fail, stopping deployment. Try a different on_update value to not fail."
629
+ )
630
+
631
+ if deployment.on_update in (OnUpdate.AppendApp, "append"):
632
+ config.logger.info(
633
+ "on_update=AppendApp, will attempt to create a new app", extra={"suppress_log": suppress_log}
634
+ )
635
+ return self._create_app(deployment, approval_program, clear_program)
636
+
637
+ if deployment.on_update in (OnUpdate.UpdateApp, "update"):
638
+ if existing_app.updatable:
639
+ config.logger.info(
640
+ "App is updatable and on_update=UpdateApp, updating app...",
641
+ extra={"suppress_log": suppress_log},
642
+ )
643
+ else:
644
+ config.logger.warning(
645
+ "App is not updatable but on_update=UpdateApp, will attempt to update app, "
646
+ "update will most likely fail",
647
+ extra={"suppress_log": suppress_log},
648
+ )
649
+ return self._update_app(deployment, existing_app, approval_program, clear_program)
650
+
651
+ if deployment.on_update in (OnUpdate.ReplaceApp, "replace"):
652
+ if existing_app.deletable:
653
+ config.logger.warning(
654
+ "App is deletable and on_update=ReplaceApp, will attempt to create new app and delete old app",
655
+ extra={"suppress_log": suppress_log},
656
+ )
657
+ else:
658
+ config.logger.warning(
659
+ "App is not deletable but on_update=ReplaceApp, will attempt to create a new app and "
660
+ "delete the old app, delete will most likely fail",
661
+ extra={"suppress_log": suppress_log},
662
+ )
663
+ return self._replace_app(deployment, existing_app, approval_program, clear_program)
664
+
665
+ raise ValueError(f"Unsupported on_update value: {deployment.on_update}")
666
+
667
+ def _update_app_lookup(self, sender: str, app_metadata: ApplicationMetaData) -> None:
668
+ """Update the app lookup cache"""
669
+
670
+ lookup = self._app_lookups.get(sender)
671
+ if not lookup:
672
+ self._app_lookups[sender] = ApplicationLookup(
673
+ creator=sender,
674
+ apps={app_metadata.name: app_metadata},
675
+ )
676
+ else:
677
+ lookup.apps[app_metadata.name] = app_metadata
678
+
679
+ def get_creator_apps_by_name(self, *, creator_address: str, ignore_cache: bool = False) -> ApplicationLookup:
680
+ """Returns a lookup of name => app metadata (id, address, ...metadata) for all apps created by the given account
681
+ that have an [ARC-2](https://github.com/algorandfoundation/ARCs/blob/main/ARCs/arc-0002.md) `AppDeployNote` as
682
+ the transaction note of the app creation transaction.
683
+
684
+ This function caches the result for the given creator account so that subsequent calls won't require an indexer
685
+ lookup.
686
+
687
+ If the `AppManager` instance wasn't created with an indexer client, this function will throw an error.
688
+
689
+ :param creator_address: The address of the account that is the creator of the apps you want to search for
690
+ :param ignore_cache: Whether or not to ignore the cache and force a lookup, default: use the cache
691
+ :returns: A name-based lookup of the app metadata
692
+ :raises ValueError: If the app spec format is invalid
693
+ :example:
694
+ >>> result = await deployer.get_creator_apps_by_name(creator)
695
+ """
696
+
697
+ if not ignore_cache and creator_address in self._app_lookups:
698
+ return self._app_lookups[creator_address]
699
+
700
+ if not self._indexer:
701
+ raise ValueError(
702
+ "Didn't receive an indexer client when this AppManager was created, "
703
+ "but received a call to get_creator_apps"
704
+ )
705
+
706
+ app_lookup: dict[str, ApplicationMetaData] = {}
707
+
708
+ # Get all apps created by account
709
+ # TODO: See if empty iterable responses can be changed to empty lists instead of None
710
+ created_apps = self._indexer.search_for_applications(creator=creator_address).applications or []
711
+
712
+ encoded_note_prefix = base64.b64encode(APP_DEPLOY_NOTE_DAPP.encode()).decode()
713
+
714
+ for app in created_apps:
715
+ app_id = app.id_
716
+
717
+ # Get creation transaction
718
+ creation_txns = self._indexer.search_for_transactions(
719
+ application_id=app_id,
720
+ min_round=app.created_at_round,
721
+ address=creator_address,
722
+ address_role="sender",
723
+ note_prefix=encoded_note_prefix,
724
+ limit=1,
725
+ ).transactions
726
+
727
+ if not creation_txns:
728
+ continue
729
+
730
+ creation_txn = creation_txns[0]
731
+
732
+ try:
733
+ if not creation_txn.note:
734
+ continue
735
+ note = base64.b64decode(creation_txn.note).decode()
736
+ if not note.startswith(f"{APP_DEPLOY_NOTE_DAPP}:j"):
737
+ continue
738
+
739
+ metadata = json.loads(note[len(APP_DEPLOY_NOTE_DAPP) + 2 :])
740
+
741
+ if metadata.get("name") and creation_txn.confirmed_round:
742
+ app_lookup[metadata["name"]] = ApplicationMetaData(
743
+ reference=ApplicationReference(app_id=app_id, app_address=get_application_address(app_id)),
744
+ deploy_metadata=AppDeploymentMetaData(
745
+ name=metadata["name"],
746
+ version=metadata.get("version", "1.0"),
747
+ deletable=metadata.get("deletable"),
748
+ updatable=metadata.get("updatable"),
749
+ ),
750
+ created_round=creation_txn.confirmed_round,
751
+ updated_round=creation_txn.confirmed_round,
752
+ deleted=app.deleted if app.deleted is not None else False,
753
+ )
754
+ except Exception as e:
755
+ config.logger.warning(
756
+ f"Error processing app {app_id} for creator {creator_address}: {e}",
757
+ )
758
+ continue
759
+
760
+ lookup = ApplicationLookup(creator=creator_address, apps=app_lookup)
761
+ self._app_lookups[creator_address] = lookup
762
+ return lookup
763
+
764
+
765
+ _T = TypeVar("_T")
766
+
767
+
768
+ def extend(new_type: type[_T], base_instance: object, **changes: object) -> _T:
769
+ assert dataclasses.is_dataclass(new_type), "expected dataclass type"
770
+ base_type = type(base_instance)
771
+ assert dataclasses.is_dataclass(base_type), "expected dataclass instance"
772
+ old_type_fields = {f.name: f for f in fields(base_type)}
773
+ new_type_fields = fields(new_type)
774
+ for a in new_type_fields:
775
+ if not a.init:
776
+ continue
777
+ if a.name not in changes and a.name in old_type_fields:
778
+ changes[a.name] = getattr(base_instance, a.name)
779
+
780
+ return new_type(**changes)
781
+
782
+
783
+ def _get_confirmed_round(confirmation: algod_models.PendingTransactionResponse | None) -> int:
784
+ """Extract the confirmed round from a typed response model."""
785
+
786
+ if confirmation is None:
787
+ return 0
788
+ return confirmation.confirmed_round or 0