chia-blockchain 2.4.4__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 (1028) hide show
  1. chia/__init__.py +10 -0
  2. chia/__main__.py +5 -0
  3. chia/_tests/README.md +53 -0
  4. chia/_tests/__init__.py +0 -0
  5. chia/_tests/blockchain/__init__.py +0 -0
  6. chia/_tests/blockchain/blockchain_test_utils.py +197 -0
  7. chia/_tests/blockchain/config.py +4 -0
  8. chia/_tests/blockchain/test_augmented_chain.py +147 -0
  9. chia/_tests/blockchain/test_blockchain.py +4100 -0
  10. chia/_tests/blockchain/test_blockchain_transactions.py +1050 -0
  11. chia/_tests/blockchain/test_build_chains.py +61 -0
  12. chia/_tests/blockchain/test_get_block_generator.py +72 -0
  13. chia/_tests/blockchain/test_lookup_fork_chain.py +195 -0
  14. chia/_tests/build-init-files.py +93 -0
  15. chia/_tests/build-job-matrix.py +204 -0
  16. chia/_tests/check_pytest_monitor_output.py +34 -0
  17. chia/_tests/check_sql_statements.py +73 -0
  18. chia/_tests/chia-start-sim +42 -0
  19. chia/_tests/clvm/__init__.py +0 -0
  20. chia/_tests/clvm/benchmark_costs.py +23 -0
  21. chia/_tests/clvm/coin_store.py +147 -0
  22. chia/_tests/clvm/test_chialisp_deserialization.py +101 -0
  23. chia/_tests/clvm/test_clvm_step.py +37 -0
  24. chia/_tests/clvm/test_condition_codes.py +13 -0
  25. chia/_tests/clvm/test_curry_and_treehash.py +57 -0
  26. chia/_tests/clvm/test_program.py +150 -0
  27. chia/_tests/clvm/test_puzzle_compression.py +144 -0
  28. chia/_tests/clvm/test_puzzle_drivers.py +45 -0
  29. chia/_tests/clvm/test_puzzles.py +247 -0
  30. chia/_tests/clvm/test_singletons.py +540 -0
  31. chia/_tests/clvm/test_spend_sim.py +181 -0
  32. chia/_tests/cmds/__init__.py +0 -0
  33. chia/_tests/cmds/cmd_test_utils.py +472 -0
  34. chia/_tests/cmds/config.py +3 -0
  35. chia/_tests/cmds/conftest.py +23 -0
  36. chia/_tests/cmds/test_click_types.py +195 -0
  37. chia/_tests/cmds/test_cmd_framework.py +400 -0
  38. chia/_tests/cmds/test_cmds_util.py +97 -0
  39. chia/_tests/cmds/test_daemon.py +92 -0
  40. chia/_tests/cmds/test_farm_cmd.py +67 -0
  41. chia/_tests/cmds/test_show.py +116 -0
  42. chia/_tests/cmds/test_sim.py +207 -0
  43. chia/_tests/cmds/test_timelock_args.py +75 -0
  44. chia/_tests/cmds/test_tx_config_args.py +153 -0
  45. chia/_tests/cmds/testing_classes.py +59 -0
  46. chia/_tests/cmds/wallet/__init__.py +0 -0
  47. chia/_tests/cmds/wallet/test_coins.py +195 -0
  48. chia/_tests/cmds/wallet/test_consts.py +47 -0
  49. chia/_tests/cmds/wallet/test_dao.py +565 -0
  50. chia/_tests/cmds/wallet/test_did.py +403 -0
  51. chia/_tests/cmds/wallet/test_nft.py +470 -0
  52. chia/_tests/cmds/wallet/test_notifications.py +124 -0
  53. chia/_tests/cmds/wallet/test_offer.toffer +1 -0
  54. chia/_tests/cmds/wallet/test_tx_decorators.py +27 -0
  55. chia/_tests/cmds/wallet/test_vcs.py +376 -0
  56. chia/_tests/cmds/wallet/test_wallet.py +1126 -0
  57. chia/_tests/cmds/wallet/test_wallet_check.py +111 -0
  58. chia/_tests/conftest.py +1304 -0
  59. chia/_tests/connection_utils.py +124 -0
  60. chia/_tests/core/__init__.py +0 -0
  61. chia/_tests/core/cmds/__init__.py +0 -0
  62. chia/_tests/core/cmds/test_beta.py +382 -0
  63. chia/_tests/core/cmds/test_keys.py +1734 -0
  64. chia/_tests/core/cmds/test_wallet.py +126 -0
  65. chia/_tests/core/config.py +3 -0
  66. chia/_tests/core/consensus/__init__.py +0 -0
  67. chia/_tests/core/consensus/test_block_creation.py +56 -0
  68. chia/_tests/core/consensus/test_pot_iterations.py +117 -0
  69. chia/_tests/core/custom_types/__init__.py +0 -0
  70. chia/_tests/core/custom_types/test_coin.py +109 -0
  71. chia/_tests/core/custom_types/test_proof_of_space.py +144 -0
  72. chia/_tests/core/custom_types/test_spend_bundle.py +71 -0
  73. chia/_tests/core/daemon/__init__.py +0 -0
  74. chia/_tests/core/daemon/config.py +4 -0
  75. chia/_tests/core/daemon/test_daemon.py +2128 -0
  76. chia/_tests/core/daemon/test_daemon_register.py +109 -0
  77. chia/_tests/core/daemon/test_keychain_proxy.py +100 -0
  78. chia/_tests/core/data_layer/__init__.py +0 -0
  79. chia/_tests/core/data_layer/config.py +5 -0
  80. chia/_tests/core/data_layer/conftest.py +105 -0
  81. chia/_tests/core/data_layer/test_data_cli.py +57 -0
  82. chia/_tests/core/data_layer/test_data_layer.py +83 -0
  83. chia/_tests/core/data_layer/test_data_layer_util.py +219 -0
  84. chia/_tests/core/data_layer/test_data_rpc.py +3865 -0
  85. chia/_tests/core/data_layer/test_data_store.py +2423 -0
  86. chia/_tests/core/data_layer/test_data_store_schema.py +381 -0
  87. chia/_tests/core/data_layer/test_plugin.py +91 -0
  88. chia/_tests/core/data_layer/util.py +232 -0
  89. chia/_tests/core/farmer/__init__.py +0 -0
  90. chia/_tests/core/farmer/config.py +3 -0
  91. chia/_tests/core/farmer/test_farmer_api.py +101 -0
  92. chia/_tests/core/full_node/__init__.py +0 -0
  93. chia/_tests/core/full_node/config.py +4 -0
  94. chia/_tests/core/full_node/dos/__init__.py +0 -0
  95. chia/_tests/core/full_node/dos/config.py +3 -0
  96. chia/_tests/core/full_node/full_sync/__init__.py +0 -0
  97. chia/_tests/core/full_node/full_sync/config.py +4 -0
  98. chia/_tests/core/full_node/full_sync/test_full_sync.py +448 -0
  99. chia/_tests/core/full_node/ram_db.py +27 -0
  100. chia/_tests/core/full_node/stores/__init__.py +0 -0
  101. chia/_tests/core/full_node/stores/config.py +4 -0
  102. chia/_tests/core/full_node/stores/test_block_store.py +488 -0
  103. chia/_tests/core/full_node/stores/test_coin_store.py +888 -0
  104. chia/_tests/core/full_node/stores/test_full_node_store.py +1215 -0
  105. chia/_tests/core/full_node/stores/test_hint_store.py +230 -0
  106. chia/_tests/core/full_node/stores/test_sync_store.py +135 -0
  107. chia/_tests/core/full_node/test_address_manager.py +588 -0
  108. chia/_tests/core/full_node/test_block_height_map.py +556 -0
  109. chia/_tests/core/full_node/test_conditions.py +558 -0
  110. chia/_tests/core/full_node/test_full_node.py +2445 -0
  111. chia/_tests/core/full_node/test_generator_tools.py +82 -0
  112. chia/_tests/core/full_node/test_hint_management.py +104 -0
  113. chia/_tests/core/full_node/test_node_load.py +34 -0
  114. chia/_tests/core/full_node/test_performance.py +182 -0
  115. chia/_tests/core/full_node/test_subscriptions.py +492 -0
  116. chia/_tests/core/full_node/test_transactions.py +203 -0
  117. chia/_tests/core/full_node/test_tx_processing_queue.py +154 -0
  118. chia/_tests/core/large_block.py +2388 -0
  119. chia/_tests/core/make_block_generator.py +72 -0
  120. chia/_tests/core/mempool/__init__.py +0 -0
  121. chia/_tests/core/mempool/config.py +4 -0
  122. chia/_tests/core/mempool/test_mempool.py +3180 -0
  123. chia/_tests/core/mempool/test_mempool_fee_estimator.py +104 -0
  124. chia/_tests/core/mempool/test_mempool_fee_protocol.py +55 -0
  125. chia/_tests/core/mempool/test_mempool_item_queries.py +192 -0
  126. chia/_tests/core/mempool/test_mempool_manager.py +2054 -0
  127. chia/_tests/core/mempool/test_mempool_performance.py +65 -0
  128. chia/_tests/core/mempool/test_singleton_fast_forward.py +567 -0
  129. chia/_tests/core/node_height.py +28 -0
  130. chia/_tests/core/server/__init__.py +0 -0
  131. chia/_tests/core/server/config.py +3 -0
  132. chia/_tests/core/server/flood.py +82 -0
  133. chia/_tests/core/server/serve.py +132 -0
  134. chia/_tests/core/server/test_capabilities.py +68 -0
  135. chia/_tests/core/server/test_dos.py +320 -0
  136. chia/_tests/core/server/test_event_loop.py +109 -0
  137. chia/_tests/core/server/test_loop.py +290 -0
  138. chia/_tests/core/server/test_node_discovery.py +74 -0
  139. chia/_tests/core/server/test_rate_limits.py +370 -0
  140. chia/_tests/core/server/test_server.py +225 -0
  141. chia/_tests/core/server/test_upnp.py +8 -0
  142. chia/_tests/core/services/__init__.py +0 -0
  143. chia/_tests/core/services/config.py +3 -0
  144. chia/_tests/core/services/test_services.py +166 -0
  145. chia/_tests/core/ssl/__init__.py +0 -0
  146. chia/_tests/core/ssl/config.py +3 -0
  147. chia/_tests/core/ssl/test_ssl.py +198 -0
  148. chia/_tests/core/test_coins.py +33 -0
  149. chia/_tests/core/test_cost_calculation.py +314 -0
  150. chia/_tests/core/test_crawler.py +175 -0
  151. chia/_tests/core/test_crawler_rpc.py +53 -0
  152. chia/_tests/core/test_daemon_rpc.py +24 -0
  153. chia/_tests/core/test_db_conversion.py +129 -0
  154. chia/_tests/core/test_db_validation.py +161 -0
  155. chia/_tests/core/test_farmer_harvester_rpc.py +504 -0
  156. chia/_tests/core/test_filter.py +37 -0
  157. chia/_tests/core/test_full_node_rpc.py +794 -0
  158. chia/_tests/core/test_merkle_set.py +343 -0
  159. chia/_tests/core/test_program.py +49 -0
  160. chia/_tests/core/test_rpc_util.py +87 -0
  161. chia/_tests/core/test_seeder.py +308 -0
  162. chia/_tests/core/test_setproctitle.py +13 -0
  163. chia/_tests/core/util/__init__.py +0 -0
  164. chia/_tests/core/util/config.py +4 -0
  165. chia/_tests/core/util/test_block_cache.py +44 -0
  166. chia/_tests/core/util/test_cached_bls.py +57 -0
  167. chia/_tests/core/util/test_config.py +337 -0
  168. chia/_tests/core/util/test_file_keyring_synchronization.py +105 -0
  169. chia/_tests/core/util/test_files.py +391 -0
  170. chia/_tests/core/util/test_jsonify.py +146 -0
  171. chia/_tests/core/util/test_keychain.py +514 -0
  172. chia/_tests/core/util/test_keyring_wrapper.py +490 -0
  173. chia/_tests/core/util/test_lockfile.py +380 -0
  174. chia/_tests/core/util/test_log_exceptions.py +187 -0
  175. chia/_tests/core/util/test_lru_cache.py +56 -0
  176. chia/_tests/core/util/test_significant_bits.py +40 -0
  177. chia/_tests/core/util/test_streamable.py +883 -0
  178. chia/_tests/db/__init__.py +0 -0
  179. chia/_tests/db/test_db_wrapper.py +565 -0
  180. chia/_tests/environments/__init__.py +0 -0
  181. chia/_tests/environments/common.py +35 -0
  182. chia/_tests/environments/full_node.py +47 -0
  183. chia/_tests/environments/wallet.py +368 -0
  184. chia/_tests/ether.py +19 -0
  185. chia/_tests/farmer_harvester/__init__.py +0 -0
  186. chia/_tests/farmer_harvester/config.py +3 -0
  187. chia/_tests/farmer_harvester/test_farmer.py +1264 -0
  188. chia/_tests/farmer_harvester/test_farmer_harvester.py +292 -0
  189. chia/_tests/farmer_harvester/test_filter_prefix_bits.py +130 -0
  190. chia/_tests/farmer_harvester/test_third_party_harvesters.py +501 -0
  191. chia/_tests/farmer_harvester/test_third_party_harvesters_data.json +29 -0
  192. chia/_tests/fee_estimation/__init__.py +0 -0
  193. chia/_tests/fee_estimation/config.py +3 -0
  194. chia/_tests/fee_estimation/test_fee_estimation_integration.py +262 -0
  195. chia/_tests/fee_estimation/test_fee_estimation_rpc.py +287 -0
  196. chia/_tests/fee_estimation/test_fee_estimation_unit_tests.py +145 -0
  197. chia/_tests/fee_estimation/test_mempoolitem_height_added.py +146 -0
  198. chia/_tests/generator/__init__.py +0 -0
  199. chia/_tests/generator/puzzles/__init__.py +0 -0
  200. chia/_tests/generator/puzzles/test_generator_deserialize.clsp +3 -0
  201. chia/_tests/generator/puzzles/test_generator_deserialize.clsp.hex +1 -0
  202. chia/_tests/generator/puzzles/test_multiple_generator_input_arguments.clsp +19 -0
  203. chia/_tests/generator/puzzles/test_multiple_generator_input_arguments.clsp.hex +1 -0
  204. chia/_tests/generator/test_compression.py +218 -0
  205. chia/_tests/generator/test_generator_types.py +44 -0
  206. chia/_tests/generator/test_rom.py +182 -0
  207. chia/_tests/plot_sync/__init__.py +0 -0
  208. chia/_tests/plot_sync/config.py +3 -0
  209. chia/_tests/plot_sync/test_delta.py +102 -0
  210. chia/_tests/plot_sync/test_plot_sync.py +617 -0
  211. chia/_tests/plot_sync/test_receiver.py +451 -0
  212. chia/_tests/plot_sync/test_sender.py +116 -0
  213. chia/_tests/plot_sync/test_sync_simulated.py +450 -0
  214. chia/_tests/plot_sync/util.py +67 -0
  215. chia/_tests/plotting/__init__.py +0 -0
  216. chia/_tests/plotting/config.py +3 -0
  217. chia/_tests/plotting/test_plot_manager.py +738 -0
  218. chia/_tests/plotting/util.py +13 -0
  219. chia/_tests/pools/__init__.py +0 -0
  220. chia/_tests/pools/config.py +5 -0
  221. chia/_tests/pools/test_pool_cmdline.py +23 -0
  222. chia/_tests/pools/test_pool_config.py +44 -0
  223. chia/_tests/pools/test_pool_puzzles_lifecycle.py +398 -0
  224. chia/_tests/pools/test_pool_rpc.py +1010 -0
  225. chia/_tests/pools/test_pool_wallet.py +201 -0
  226. chia/_tests/pools/test_wallet_pool_store.py +161 -0
  227. chia/_tests/process_junit.py +349 -0
  228. chia/_tests/rpc/__init__.py +0 -0
  229. chia/_tests/rpc/test_rpc_client.py +81 -0
  230. chia/_tests/simulation/__init__.py +0 -0
  231. chia/_tests/simulation/config.py +6 -0
  232. chia/_tests/simulation/test_simulation.py +501 -0
  233. chia/_tests/simulation/test_simulator.py +234 -0
  234. chia/_tests/simulation/test_start_simulator.py +106 -0
  235. chia/_tests/testconfig.py +13 -0
  236. chia/_tests/timelord/__init__.py +0 -0
  237. chia/_tests/timelord/config.py +3 -0
  238. chia/_tests/timelord/test_new_peak.py +437 -0
  239. chia/_tests/timelord/test_timelord.py +11 -0
  240. chia/_tests/tools/1315537.json +170 -0
  241. chia/_tests/tools/1315544.json +160 -0
  242. chia/_tests/tools/1315630.json +150 -0
  243. chia/_tests/tools/300000.json +105 -0
  244. chia/_tests/tools/442734.json +140 -0
  245. chia/_tests/tools/466212.json +130 -0
  246. chia/_tests/tools/__init__.py +0 -0
  247. chia/_tests/tools/config.py +5 -0
  248. chia/_tests/tools/test-blockchain-db.sqlite +0 -0
  249. chia/_tests/tools/test_full_sync.py +30 -0
  250. chia/_tests/tools/test_legacy_keyring.py +82 -0
  251. chia/_tests/tools/test_run_block.py +129 -0
  252. chia/_tests/util/__init__.py +0 -0
  253. chia/_tests/util/benchmark_cost.py +170 -0
  254. chia/_tests/util/benchmarks.py +154 -0
  255. chia/_tests/util/bip39_test_vectors.json +148 -0
  256. chia/_tests/util/blockchain.py +133 -0
  257. chia/_tests/util/blockchain_mock.py +132 -0
  258. chia/_tests/util/build_network_protocol_files.py +302 -0
  259. chia/_tests/util/clvm_generator.bin +0 -0
  260. chia/_tests/util/config.py +3 -0
  261. chia/_tests/util/constants.py +20 -0
  262. chia/_tests/util/db_connection.py +36 -0
  263. chia/_tests/util/full_sync.py +245 -0
  264. chia/_tests/util/gen_ssl_certs.py +115 -0
  265. chia/_tests/util/generator_tools_testing.py +47 -0
  266. chia/_tests/util/key_tool.py +37 -0
  267. chia/_tests/util/misc.py +722 -0
  268. chia/_tests/util/network_protocol_data.py +1074 -0
  269. chia/_tests/util/protocol_messages_bytes-v1.0 +0 -0
  270. chia/_tests/util/protocol_messages_json.py +2700 -0
  271. chia/_tests/util/rpc.py +23 -0
  272. chia/_tests/util/run_block.py +163 -0
  273. chia/_tests/util/setup_nodes.py +479 -0
  274. chia/_tests/util/split_managers.py +99 -0
  275. chia/_tests/util/temp_file.py +14 -0
  276. chia/_tests/util/test_action_scope.py +143 -0
  277. chia/_tests/util/test_async_pool.py +366 -0
  278. chia/_tests/util/test_build_job_matrix.py +43 -0
  279. chia/_tests/util/test_build_network_protocol_files.py +7 -0
  280. chia/_tests/util/test_chia_version.py +50 -0
  281. chia/_tests/util/test_collection.py +11 -0
  282. chia/_tests/util/test_condition_tools.py +231 -0
  283. chia/_tests/util/test_config.py +426 -0
  284. chia/_tests/util/test_dump_keyring.py +60 -0
  285. chia/_tests/util/test_errors.py +10 -0
  286. chia/_tests/util/test_full_block_utils.py +271 -0
  287. chia/_tests/util/test_installed.py +20 -0
  288. chia/_tests/util/test_limited_semaphore.py +52 -0
  289. chia/_tests/util/test_logging_filter.py +43 -0
  290. chia/_tests/util/test_misc.py +444 -0
  291. chia/_tests/util/test_network.py +74 -0
  292. chia/_tests/util/test_network_protocol_files.py +579 -0
  293. chia/_tests/util/test_network_protocol_json.py +266 -0
  294. chia/_tests/util/test_network_protocol_test.py +257 -0
  295. chia/_tests/util/test_paginator.py +72 -0
  296. chia/_tests/util/test_pprint.py +17 -0
  297. chia/_tests/util/test_priority_mutex.py +487 -0
  298. chia/_tests/util/test_recursive_replace.py +116 -0
  299. chia/_tests/util/test_replace_str_to_bytes.py +137 -0
  300. chia/_tests/util/test_service_groups.py +15 -0
  301. chia/_tests/util/test_ssl_check.py +31 -0
  302. chia/_tests/util/test_testnet_overrides.py +19 -0
  303. chia/_tests/util/test_tests_misc.py +38 -0
  304. chia/_tests/util/test_timing.py +37 -0
  305. chia/_tests/util/test_trusted_peer.py +51 -0
  306. chia/_tests/util/time_out_assert.py +154 -0
  307. chia/_tests/wallet/__init__.py +0 -0
  308. chia/_tests/wallet/cat_wallet/__init__.py +0 -0
  309. chia/_tests/wallet/cat_wallet/config.py +4 -0
  310. chia/_tests/wallet/cat_wallet/test_cat_lifecycle.py +468 -0
  311. chia/_tests/wallet/cat_wallet/test_cat_outer_puzzle.py +69 -0
  312. chia/_tests/wallet/cat_wallet/test_cat_wallet.py +1738 -0
  313. chia/_tests/wallet/cat_wallet/test_offer_lifecycle.py +291 -0
  314. chia/_tests/wallet/cat_wallet/test_trades.py +2578 -0
  315. chia/_tests/wallet/clawback/__init__.py +0 -0
  316. chia/_tests/wallet/clawback/config.py +3 -0
  317. chia/_tests/wallet/clawback/test_clawback_decorator.py +80 -0
  318. chia/_tests/wallet/clawback/test_clawback_lifecycle.py +292 -0
  319. chia/_tests/wallet/clawback/test_clawback_metadata.py +51 -0
  320. chia/_tests/wallet/config.py +4 -0
  321. chia/_tests/wallet/conftest.py +217 -0
  322. chia/_tests/wallet/dao_wallet/__init__.py +0 -0
  323. chia/_tests/wallet/dao_wallet/config.py +3 -0
  324. chia/_tests/wallet/dao_wallet/test_dao_clvm.py +1322 -0
  325. chia/_tests/wallet/dao_wallet/test_dao_wallets.py +3488 -0
  326. chia/_tests/wallet/db_wallet/__init__.py +0 -0
  327. chia/_tests/wallet/db_wallet/config.py +3 -0
  328. chia/_tests/wallet/db_wallet/test_db_graftroot.py +143 -0
  329. chia/_tests/wallet/db_wallet/test_dl_offers.py +491 -0
  330. chia/_tests/wallet/db_wallet/test_dl_wallet.py +823 -0
  331. chia/_tests/wallet/did_wallet/__init__.py +0 -0
  332. chia/_tests/wallet/did_wallet/config.py +4 -0
  333. chia/_tests/wallet/did_wallet/test_did.py +1481 -0
  334. chia/_tests/wallet/nft_wallet/__init__.py +0 -0
  335. chia/_tests/wallet/nft_wallet/config.py +4 -0
  336. chia/_tests/wallet/nft_wallet/test_nft_1_offers.py +1492 -0
  337. chia/_tests/wallet/nft_wallet/test_nft_bulk_mint.py +1014 -0
  338. chia/_tests/wallet/nft_wallet/test_nft_lifecycle.py +376 -0
  339. chia/_tests/wallet/nft_wallet/test_nft_offers.py +1209 -0
  340. chia/_tests/wallet/nft_wallet/test_nft_puzzles.py +172 -0
  341. chia/_tests/wallet/nft_wallet/test_nft_wallet.py +2558 -0
  342. chia/_tests/wallet/nft_wallet/test_ownership_outer_puzzle.py +70 -0
  343. chia/_tests/wallet/rpc/__init__.py +0 -0
  344. chia/_tests/wallet/rpc/config.py +4 -0
  345. chia/_tests/wallet/rpc/test_dl_wallet_rpc.py +287 -0
  346. chia/_tests/wallet/rpc/test_wallet_rpc.py +3106 -0
  347. chia/_tests/wallet/simple_sync/__init__.py +0 -0
  348. chia/_tests/wallet/simple_sync/config.py +3 -0
  349. chia/_tests/wallet/simple_sync/test_simple_sync_protocol.py +719 -0
  350. chia/_tests/wallet/sync/__init__.py +0 -0
  351. chia/_tests/wallet/sync/config.py +4 -0
  352. chia/_tests/wallet/sync/test_wallet_sync.py +1529 -0
  353. chia/_tests/wallet/test_address_type.py +189 -0
  354. chia/_tests/wallet/test_bech32m.py +45 -0
  355. chia/_tests/wallet/test_clvm_streamable.py +244 -0
  356. chia/_tests/wallet/test_coin_selection.py +589 -0
  357. chia/_tests/wallet/test_conditions.py +388 -0
  358. chia/_tests/wallet/test_debug_spend_bundle.py +76 -0
  359. chia/_tests/wallet/test_new_wallet_protocol.py +1176 -0
  360. chia/_tests/wallet/test_nft_store.py +193 -0
  361. chia/_tests/wallet/test_notifications.py +196 -0
  362. chia/_tests/wallet/test_offer_parsing_performance.py +48 -0
  363. chia/_tests/wallet/test_puzzle_store.py +133 -0
  364. chia/_tests/wallet/test_sign_coin_spends.py +159 -0
  365. chia/_tests/wallet/test_signer_protocol.py +948 -0
  366. chia/_tests/wallet/test_singleton.py +122 -0
  367. chia/_tests/wallet/test_singleton_lifecycle_fast.py +772 -0
  368. chia/_tests/wallet/test_singleton_store.py +152 -0
  369. chia/_tests/wallet/test_taproot.py +19 -0
  370. chia/_tests/wallet/test_transaction_store.py +941 -0
  371. chia/_tests/wallet/test_util.py +181 -0
  372. chia/_tests/wallet/test_wallet.py +2139 -0
  373. chia/_tests/wallet/test_wallet_action_scope.py +85 -0
  374. chia/_tests/wallet/test_wallet_blockchain.py +113 -0
  375. chia/_tests/wallet/test_wallet_coin_store.py +1002 -0
  376. chia/_tests/wallet/test_wallet_interested_store.py +43 -0
  377. chia/_tests/wallet/test_wallet_key_val_store.py +40 -0
  378. chia/_tests/wallet/test_wallet_node.py +783 -0
  379. chia/_tests/wallet/test_wallet_retry.py +95 -0
  380. chia/_tests/wallet/test_wallet_state_manager.py +252 -0
  381. chia/_tests/wallet/test_wallet_test_framework.py +275 -0
  382. chia/_tests/wallet/test_wallet_trade_store.py +218 -0
  383. chia/_tests/wallet/test_wallet_user_store.py +34 -0
  384. chia/_tests/wallet/test_wallet_utils.py +155 -0
  385. chia/_tests/wallet/vc_wallet/__init__.py +0 -0
  386. chia/_tests/wallet/vc_wallet/config.py +3 -0
  387. chia/_tests/wallet/vc_wallet/test_cr_outer_puzzle.py +70 -0
  388. chia/_tests/wallet/vc_wallet/test_vc_lifecycle.py +883 -0
  389. chia/_tests/wallet/vc_wallet/test_vc_wallet.py +801 -0
  390. chia/_tests/wallet/wallet_block_tools.py +327 -0
  391. chia/_tests/weight_proof/__init__.py +0 -0
  392. chia/_tests/weight_proof/config.py +3 -0
  393. chia/_tests/weight_proof/test_weight_proof.py +528 -0
  394. chia/clvm/__init__.py +0 -0
  395. chia/clvm/spend_sim.py +488 -0
  396. chia/cmds/__init__.py +0 -0
  397. chia/cmds/beta.py +183 -0
  398. chia/cmds/beta_funcs.py +133 -0
  399. chia/cmds/check_wallet_db.py +418 -0
  400. chia/cmds/chia.py +143 -0
  401. chia/cmds/cmd_classes.py +315 -0
  402. chia/cmds/cmds_util.py +498 -0
  403. chia/cmds/coin_funcs.py +260 -0
  404. chia/cmds/coins.py +220 -0
  405. chia/cmds/completion.py +49 -0
  406. chia/cmds/configure.py +331 -0
  407. chia/cmds/dao.py +1008 -0
  408. chia/cmds/dao_funcs.py +576 -0
  409. chia/cmds/data.py +707 -0
  410. chia/cmds/data_funcs.py +380 -0
  411. chia/cmds/db.py +86 -0
  412. chia/cmds/db_backup_func.py +77 -0
  413. chia/cmds/db_upgrade_func.py +452 -0
  414. chia/cmds/db_validate_func.py +184 -0
  415. chia/cmds/dev.py +16 -0
  416. chia/cmds/farm.py +87 -0
  417. chia/cmds/farm_funcs.py +207 -0
  418. chia/cmds/init.py +70 -0
  419. chia/cmds/init_funcs.py +367 -0
  420. chia/cmds/installers.py +129 -0
  421. chia/cmds/keys.py +510 -0
  422. chia/cmds/keys_funcs.py +864 -0
  423. chia/cmds/netspace.py +47 -0
  424. chia/cmds/netspace_funcs.py +53 -0
  425. chia/cmds/options.py +32 -0
  426. chia/cmds/param_types.py +228 -0
  427. chia/cmds/passphrase.py +130 -0
  428. chia/cmds/passphrase_funcs.py +346 -0
  429. chia/cmds/peer.py +50 -0
  430. chia/cmds/peer_funcs.py +129 -0
  431. chia/cmds/plotnft.py +206 -0
  432. chia/cmds/plotnft_funcs.py +374 -0
  433. chia/cmds/plots.py +222 -0
  434. chia/cmds/plotters.py +17 -0
  435. chia/cmds/rpc.py +188 -0
  436. chia/cmds/show.py +71 -0
  437. chia/cmds/show_funcs.py +214 -0
  438. chia/cmds/signer.py +304 -0
  439. chia/cmds/sim.py +217 -0
  440. chia/cmds/sim_funcs.py +509 -0
  441. chia/cmds/start.py +24 -0
  442. chia/cmds/start_funcs.py +112 -0
  443. chia/cmds/stop.py +61 -0
  444. chia/cmds/units.py +11 -0
  445. chia/cmds/wallet.py +1745 -0
  446. chia/cmds/wallet_funcs.py +1800 -0
  447. chia/consensus/__init__.py +0 -0
  448. chia/consensus/block_body_validation.py +515 -0
  449. chia/consensus/block_creation.py +525 -0
  450. chia/consensus/block_header_validation.py +1064 -0
  451. chia/consensus/block_record.py +32 -0
  452. chia/consensus/block_rewards.py +53 -0
  453. chia/consensus/block_root_validation.py +46 -0
  454. chia/consensus/blockchain.py +1100 -0
  455. chia/consensus/blockchain_interface.py +56 -0
  456. chia/consensus/coinbase.py +30 -0
  457. chia/consensus/condition_costs.py +9 -0
  458. chia/consensus/constants.py +49 -0
  459. chia/consensus/cost_calculator.py +15 -0
  460. chia/consensus/default_constants.py +90 -0
  461. chia/consensus/deficit.py +55 -0
  462. chia/consensus/difficulty_adjustment.py +412 -0
  463. chia/consensus/find_fork_point.py +111 -0
  464. chia/consensus/full_block_to_block_record.py +167 -0
  465. chia/consensus/get_block_challenge.py +106 -0
  466. chia/consensus/get_block_generator.py +26 -0
  467. chia/consensus/make_sub_epoch_summary.py +210 -0
  468. chia/consensus/multiprocess_validation.py +365 -0
  469. chia/consensus/pos_quality.py +19 -0
  470. chia/consensus/pot_iterations.py +67 -0
  471. chia/consensus/puzzles/__init__.py +0 -0
  472. chia/consensus/puzzles/chialisp_deserialisation.clsp +69 -0
  473. chia/consensus/puzzles/chialisp_deserialisation.clsp.hex +1 -0
  474. chia/consensus/puzzles/rom_bootstrap_generator.clsp +37 -0
  475. chia/consensus/puzzles/rom_bootstrap_generator.clsp.hex +1 -0
  476. chia/consensus/vdf_info_computation.py +156 -0
  477. chia/daemon/__init__.py +0 -0
  478. chia/daemon/client.py +233 -0
  479. chia/daemon/keychain_proxy.py +501 -0
  480. chia/daemon/keychain_server.py +365 -0
  481. chia/daemon/server.py +1616 -0
  482. chia/daemon/windows_signal.py +56 -0
  483. chia/data_layer/__init__.py +0 -0
  484. chia/data_layer/data_layer.py +1303 -0
  485. chia/data_layer/data_layer_api.py +25 -0
  486. chia/data_layer/data_layer_errors.py +50 -0
  487. chia/data_layer/data_layer_server.py +170 -0
  488. chia/data_layer/data_layer_util.py +985 -0
  489. chia/data_layer/data_layer_wallet.py +1315 -0
  490. chia/data_layer/data_store.py +2267 -0
  491. chia/data_layer/dl_wallet_store.py +407 -0
  492. chia/data_layer/download_data.py +389 -0
  493. chia/data_layer/puzzles/__init__.py +0 -0
  494. chia/data_layer/puzzles/graftroot_dl_offers.clsp +100 -0
  495. chia/data_layer/puzzles/graftroot_dl_offers.clsp.hex +1 -0
  496. chia/data_layer/s3_plugin_config.yml +33 -0
  497. chia/data_layer/s3_plugin_service.py +468 -0
  498. chia/data_layer/util/__init__.py +0 -0
  499. chia/data_layer/util/benchmark.py +108 -0
  500. chia/data_layer/util/plugin.py +41 -0
  501. chia/farmer/__init__.py +0 -0
  502. chia/farmer/farmer.py +920 -0
  503. chia/farmer/farmer_api.py +814 -0
  504. chia/full_node/__init__.py +0 -0
  505. chia/full_node/bitcoin_fee_estimator.py +85 -0
  506. chia/full_node/block_height_map.py +271 -0
  507. chia/full_node/block_store.py +570 -0
  508. chia/full_node/bundle_tools.py +19 -0
  509. chia/full_node/coin_store.py +646 -0
  510. chia/full_node/fee_estimate.py +54 -0
  511. chia/full_node/fee_estimate_store.py +24 -0
  512. chia/full_node/fee_estimation.py +93 -0
  513. chia/full_node/fee_estimator.py +90 -0
  514. chia/full_node/fee_estimator_constants.py +38 -0
  515. chia/full_node/fee_estimator_interface.py +42 -0
  516. chia/full_node/fee_history.py +26 -0
  517. chia/full_node/fee_tracker.py +564 -0
  518. chia/full_node/full_node.py +3052 -0
  519. chia/full_node/full_node_api.py +1974 -0
  520. chia/full_node/full_node_store.py +1033 -0
  521. chia/full_node/hint_management.py +56 -0
  522. chia/full_node/hint_store.py +94 -0
  523. chia/full_node/mempool.py +583 -0
  524. chia/full_node/mempool_check_conditions.py +177 -0
  525. chia/full_node/mempool_manager.py +858 -0
  526. chia/full_node/pending_tx_cache.py +112 -0
  527. chia/full_node/puzzles/__init__.py +0 -0
  528. chia/full_node/puzzles/block_program_zero.clsp +14 -0
  529. chia/full_node/puzzles/block_program_zero.clsp.hex +1 -0
  530. chia/full_node/puzzles/decompress_coin_spend_entry.clsp +5 -0
  531. chia/full_node/puzzles/decompress_coin_spend_entry.clsp.hex +1 -0
  532. chia/full_node/puzzles/decompress_coin_spend_entry_with_prefix.clsp +7 -0
  533. chia/full_node/puzzles/decompress_coin_spend_entry_with_prefix.clsp.hex +1 -0
  534. chia/full_node/puzzles/decompress_puzzle.clsp +6 -0
  535. chia/full_node/puzzles/decompress_puzzle.clsp.hex +1 -0
  536. chia/full_node/signage_point.py +16 -0
  537. chia/full_node/subscriptions.py +248 -0
  538. chia/full_node/sync_store.py +145 -0
  539. chia/full_node/tx_processing_queue.py +78 -0
  540. chia/full_node/weight_proof.py +1719 -0
  541. chia/harvester/__init__.py +0 -0
  542. chia/harvester/harvester.py +271 -0
  543. chia/harvester/harvester_api.py +374 -0
  544. chia/introducer/__init__.py +0 -0
  545. chia/introducer/introducer.py +120 -0
  546. chia/introducer/introducer_api.py +64 -0
  547. chia/legacy/__init__.py +0 -0
  548. chia/legacy/keyring.py +154 -0
  549. chia/plot_sync/__init__.py +0 -0
  550. chia/plot_sync/delta.py +61 -0
  551. chia/plot_sync/exceptions.py +56 -0
  552. chia/plot_sync/receiver.py +385 -0
  553. chia/plot_sync/sender.py +337 -0
  554. chia/plot_sync/util.py +43 -0
  555. chia/plotters/__init__.py +0 -0
  556. chia/plotters/bladebit.py +388 -0
  557. chia/plotters/chiapos.py +63 -0
  558. chia/plotters/madmax.py +224 -0
  559. chia/plotters/plotters.py +577 -0
  560. chia/plotters/plotters_util.py +131 -0
  561. chia/plotting/__init__.py +0 -0
  562. chia/plotting/cache.py +212 -0
  563. chia/plotting/check_plots.py +283 -0
  564. chia/plotting/create_plots.py +278 -0
  565. chia/plotting/manager.py +436 -0
  566. chia/plotting/util.py +324 -0
  567. chia/pools/__init__.py +0 -0
  568. chia/pools/pool_config.py +110 -0
  569. chia/pools/pool_puzzles.py +459 -0
  570. chia/pools/pool_wallet.py +926 -0
  571. chia/pools/pool_wallet_info.py +118 -0
  572. chia/pools/puzzles/__init__.py +0 -0
  573. chia/pools/puzzles/pool_member_innerpuz.clsp +70 -0
  574. chia/pools/puzzles/pool_member_innerpuz.clsp.hex +1 -0
  575. chia/pools/puzzles/pool_waitingroom_innerpuz.clsp +69 -0
  576. chia/pools/puzzles/pool_waitingroom_innerpuz.clsp.hex +1 -0
  577. chia/protocols/__init__.py +0 -0
  578. chia/protocols/farmer_protocol.py +102 -0
  579. chia/protocols/full_node_protocol.py +219 -0
  580. chia/protocols/harvester_protocol.py +216 -0
  581. chia/protocols/introducer_protocol.py +26 -0
  582. chia/protocols/pool_protocol.py +177 -0
  583. chia/protocols/protocol_message_types.py +139 -0
  584. chia/protocols/protocol_state_machine.py +87 -0
  585. chia/protocols/protocol_timing.py +7 -0
  586. chia/protocols/shared_protocol.py +86 -0
  587. chia/protocols/timelord_protocol.py +93 -0
  588. chia/protocols/wallet_protocol.py +401 -0
  589. chia/py.typed +0 -0
  590. chia/rpc/__init__.py +0 -0
  591. chia/rpc/crawler_rpc_api.py +75 -0
  592. chia/rpc/data_layer_rpc_api.py +639 -0
  593. chia/rpc/data_layer_rpc_client.py +188 -0
  594. chia/rpc/data_layer_rpc_util.py +62 -0
  595. chia/rpc/farmer_rpc_api.py +360 -0
  596. chia/rpc/farmer_rpc_client.py +86 -0
  597. chia/rpc/full_node_rpc_api.py +954 -0
  598. chia/rpc/full_node_rpc_client.py +292 -0
  599. chia/rpc/harvester_rpc_api.py +136 -0
  600. chia/rpc/harvester_rpc_client.py +54 -0
  601. chia/rpc/rpc_client.py +144 -0
  602. chia/rpc/rpc_server.py +447 -0
  603. chia/rpc/timelord_rpc_api.py +27 -0
  604. chia/rpc/util.py +293 -0
  605. chia/rpc/wallet_request_types.py +688 -0
  606. chia/rpc/wallet_rpc_api.py +4779 -0
  607. chia/rpc/wallet_rpc_client.py +1844 -0
  608. chia/seeder/__init__.py +0 -0
  609. chia/seeder/crawl_store.py +427 -0
  610. chia/seeder/crawler.py +423 -0
  611. chia/seeder/crawler_api.py +129 -0
  612. chia/seeder/dns_server.py +544 -0
  613. chia/seeder/peer_record.py +146 -0
  614. chia/seeder/start_crawler.py +88 -0
  615. chia/server/__init__.py +0 -0
  616. chia/server/address_manager.py +658 -0
  617. chia/server/address_manager_store.py +237 -0
  618. chia/server/api_protocol.py +11 -0
  619. chia/server/capabilities.py +24 -0
  620. chia/server/chia_policy.py +345 -0
  621. chia/server/introducer_peers.py +76 -0
  622. chia/server/node_discovery.py +718 -0
  623. chia/server/outbound_message.py +33 -0
  624. chia/server/rate_limit_numbers.py +204 -0
  625. chia/server/rate_limits.py +113 -0
  626. chia/server/server.py +720 -0
  627. chia/server/signal_handlers.py +117 -0
  628. chia/server/ssl_context.py +32 -0
  629. chia/server/start_data_layer.py +137 -0
  630. chia/server/start_farmer.py +86 -0
  631. chia/server/start_full_node.py +106 -0
  632. chia/server/start_harvester.py +80 -0
  633. chia/server/start_introducer.py +69 -0
  634. chia/server/start_service.py +328 -0
  635. chia/server/start_timelord.py +82 -0
  636. chia/server/start_wallet.py +109 -0
  637. chia/server/upnp.py +117 -0
  638. chia/server/ws_connection.py +752 -0
  639. chia/simulator/__init__.py +0 -0
  640. chia/simulator/block_tools.py +2053 -0
  641. chia/simulator/full_node_simulator.py +802 -0
  642. chia/simulator/keyring.py +128 -0
  643. chia/simulator/setup_services.py +505 -0
  644. chia/simulator/simulator_constants.py +13 -0
  645. chia/simulator/simulator_full_node_rpc_api.py +101 -0
  646. chia/simulator/simulator_full_node_rpc_client.py +62 -0
  647. chia/simulator/simulator_protocol.py +29 -0
  648. chia/simulator/simulator_test_tools.py +163 -0
  649. chia/simulator/socket.py +27 -0
  650. chia/simulator/ssl_certs.py +114 -0
  651. chia/simulator/ssl_certs_1.py +699 -0
  652. chia/simulator/ssl_certs_10.py +699 -0
  653. chia/simulator/ssl_certs_2.py +699 -0
  654. chia/simulator/ssl_certs_3.py +699 -0
  655. chia/simulator/ssl_certs_4.py +699 -0
  656. chia/simulator/ssl_certs_5.py +699 -0
  657. chia/simulator/ssl_certs_6.py +699 -0
  658. chia/simulator/ssl_certs_7.py +699 -0
  659. chia/simulator/ssl_certs_8.py +699 -0
  660. chia/simulator/ssl_certs_9.py +699 -0
  661. chia/simulator/start_simulator.py +135 -0
  662. chia/simulator/wallet_tools.py +245 -0
  663. chia/ssl/__init__.py +0 -0
  664. chia/ssl/chia_ca.crt +19 -0
  665. chia/ssl/chia_ca.key +28 -0
  666. chia/ssl/create_ssl.py +249 -0
  667. chia/ssl/dst_root_ca.pem +20 -0
  668. chia/timelord/__init__.py +0 -0
  669. chia/timelord/iters_from_block.py +50 -0
  670. chia/timelord/timelord.py +1202 -0
  671. chia/timelord/timelord_api.py +132 -0
  672. chia/timelord/timelord_launcher.py +188 -0
  673. chia/timelord/timelord_state.py +244 -0
  674. chia/timelord/types.py +22 -0
  675. chia/types/__init__.py +0 -0
  676. chia/types/aliases.py +35 -0
  677. chia/types/block_protocol.py +20 -0
  678. chia/types/blockchain_format/__init__.py +0 -0
  679. chia/types/blockchain_format/classgroup.py +5 -0
  680. chia/types/blockchain_format/coin.py +28 -0
  681. chia/types/blockchain_format/foliage.py +8 -0
  682. chia/types/blockchain_format/pool_target.py +5 -0
  683. chia/types/blockchain_format/program.py +270 -0
  684. chia/types/blockchain_format/proof_of_space.py +135 -0
  685. chia/types/blockchain_format/reward_chain_block.py +6 -0
  686. chia/types/blockchain_format/serialized_program.py +5 -0
  687. chia/types/blockchain_format/sized_bytes.py +11 -0
  688. chia/types/blockchain_format/slots.py +9 -0
  689. chia/types/blockchain_format/sub_epoch_summary.py +5 -0
  690. chia/types/blockchain_format/tree_hash.py +72 -0
  691. chia/types/blockchain_format/vdf.py +86 -0
  692. chia/types/clvm_cost.py +13 -0
  693. chia/types/coin_record.py +43 -0
  694. chia/types/coin_spend.py +115 -0
  695. chia/types/condition_opcodes.py +73 -0
  696. chia/types/condition_with_args.py +17 -0
  697. chia/types/eligible_coin_spends.py +364 -0
  698. chia/types/end_of_slot_bundle.py +5 -0
  699. chia/types/fee_rate.py +38 -0
  700. chia/types/full_block.py +5 -0
  701. chia/types/generator_types.py +14 -0
  702. chia/types/header_block.py +5 -0
  703. chia/types/internal_mempool_item.py +19 -0
  704. chia/types/mempool_inclusion_status.py +9 -0
  705. chia/types/mempool_item.py +85 -0
  706. chia/types/mempool_submission_status.py +30 -0
  707. chia/types/mojos.py +7 -0
  708. chia/types/peer_info.py +64 -0
  709. chia/types/signing_mode.py +29 -0
  710. chia/types/spend_bundle.py +31 -0
  711. chia/types/spend_bundle_conditions.py +7 -0
  712. chia/types/transaction_queue_entry.py +55 -0
  713. chia/types/unfinished_block.py +5 -0
  714. chia/types/unfinished_header_block.py +37 -0
  715. chia/types/weight_proof.py +50 -0
  716. chia/util/__init__.py +0 -0
  717. chia/util/action_scope.py +168 -0
  718. chia/util/api_decorators.py +89 -0
  719. chia/util/async_pool.py +224 -0
  720. chia/util/augmented_chain.py +130 -0
  721. chia/util/batches.py +39 -0
  722. chia/util/bech32m.py +123 -0
  723. chia/util/beta_metrics.py +118 -0
  724. chia/util/block_cache.py +56 -0
  725. chia/util/byte_types.py +10 -0
  726. chia/util/check_fork_next_block.py +32 -0
  727. chia/util/chia_logging.py +124 -0
  728. chia/util/chia_version.py +33 -0
  729. chia/util/collection.py +17 -0
  730. chia/util/condition_tools.py +201 -0
  731. chia/util/config.py +366 -0
  732. chia/util/cpu.py +20 -0
  733. chia/util/db_synchronous.py +21 -0
  734. chia/util/db_version.py +30 -0
  735. chia/util/db_wrapper.py +427 -0
  736. chia/util/default_root.py +10 -0
  737. chia/util/dump_keyring.py +93 -0
  738. chia/util/english.txt +2048 -0
  739. chia/util/errors.py +351 -0
  740. chia/util/file_keyring.py +480 -0
  741. chia/util/files.py +95 -0
  742. chia/util/full_block_utils.py +321 -0
  743. chia/util/generator_tools.py +62 -0
  744. chia/util/hash.py +29 -0
  745. chia/util/initial-config.yaml +675 -0
  746. chia/util/inline_executor.py +24 -0
  747. chia/util/ints.py +19 -0
  748. chia/util/json_util.py +41 -0
  749. chia/util/keychain.py +673 -0
  750. chia/util/keyring_wrapper.py +266 -0
  751. chia/util/limited_semaphore.py +39 -0
  752. chia/util/lock.py +47 -0
  753. chia/util/log_exceptions.py +29 -0
  754. chia/util/logging.py +34 -0
  755. chia/util/lru_cache.py +29 -0
  756. chia/util/math.py +20 -0
  757. chia/util/network.py +240 -0
  758. chia/util/paginator.py +46 -0
  759. chia/util/path.py +29 -0
  760. chia/util/permissions.py +19 -0
  761. chia/util/pprint.py +40 -0
  762. chia/util/prev_transaction_block.py +23 -0
  763. chia/util/priority_mutex.py +92 -0
  764. chia/util/profiler.py +194 -0
  765. chia/util/recursive_replace.py +22 -0
  766. chia/util/safe_cancel_task.py +14 -0
  767. chia/util/service_groups.py +47 -0
  768. chia/util/setproctitle.py +20 -0
  769. chia/util/significant_bits.py +30 -0
  770. chia/util/ssl_check.py +213 -0
  771. chia/util/streamable.py +654 -0
  772. chia/util/task_timing.py +378 -0
  773. chia/util/timing.py +64 -0
  774. chia/util/vdf_prover.py +31 -0
  775. chia/util/ws_message.py +66 -0
  776. chia/wallet/__init__.py +0 -0
  777. chia/wallet/cat_wallet/__init__.py +0 -0
  778. chia/wallet/cat_wallet/cat_constants.py +75 -0
  779. chia/wallet/cat_wallet/cat_info.py +47 -0
  780. chia/wallet/cat_wallet/cat_outer_puzzle.py +120 -0
  781. chia/wallet/cat_wallet/cat_utils.py +163 -0
  782. chia/wallet/cat_wallet/cat_wallet.py +869 -0
  783. chia/wallet/cat_wallet/dao_cat_info.py +28 -0
  784. chia/wallet/cat_wallet/dao_cat_wallet.py +669 -0
  785. chia/wallet/cat_wallet/lineage_store.py +74 -0
  786. chia/wallet/cat_wallet/puzzles/__init__.py +0 -0
  787. chia/wallet/cat_wallet/puzzles/cat_truths.clib +31 -0
  788. chia/wallet/cat_wallet/puzzles/cat_v2.clsp +397 -0
  789. chia/wallet/cat_wallet/puzzles/cat_v2.clsp.hex +1 -0
  790. chia/wallet/cat_wallet/puzzles/delegated_tail.clsp +25 -0
  791. chia/wallet/cat_wallet/puzzles/delegated_tail.clsp.hex +1 -0
  792. chia/wallet/cat_wallet/puzzles/everything_with_signature.clsp +15 -0
  793. chia/wallet/cat_wallet/puzzles/everything_with_signature.clsp.hex +1 -0
  794. chia/wallet/cat_wallet/puzzles/genesis_by_coin_id.clsp +26 -0
  795. chia/wallet/cat_wallet/puzzles/genesis_by_coin_id.clsp.hex +1 -0
  796. chia/wallet/cat_wallet/puzzles/genesis_by_coin_id_or_singleton.clsp +42 -0
  797. chia/wallet/cat_wallet/puzzles/genesis_by_coin_id_or_singleton.clsp.hex +1 -0
  798. chia/wallet/cat_wallet/puzzles/genesis_by_puzzle_hash.clsp +24 -0
  799. chia/wallet/cat_wallet/puzzles/genesis_by_puzzle_hash.clsp.hex +1 -0
  800. chia/wallet/coin_selection.py +188 -0
  801. chia/wallet/conditions.py +1326 -0
  802. chia/wallet/dao_wallet/__init__.py +0 -0
  803. chia/wallet/dao_wallet/dao_info.py +61 -0
  804. chia/wallet/dao_wallet/dao_utils.py +810 -0
  805. chia/wallet/dao_wallet/dao_wallet.py +2121 -0
  806. chia/wallet/db_wallet/__init__.py +0 -0
  807. chia/wallet/db_wallet/db_wallet_puzzles.py +107 -0
  808. chia/wallet/derivation_record.py +30 -0
  809. chia/wallet/derive_keys.py +146 -0
  810. chia/wallet/did_wallet/__init__.py +0 -0
  811. chia/wallet/did_wallet/did_info.py +39 -0
  812. chia/wallet/did_wallet/did_wallet.py +1485 -0
  813. chia/wallet/did_wallet/did_wallet_puzzles.py +220 -0
  814. chia/wallet/did_wallet/puzzles/__init__.py +0 -0
  815. chia/wallet/did_wallet/puzzles/did_innerpuz.clsp +135 -0
  816. chia/wallet/did_wallet/puzzles/did_innerpuz.clsp.hex +1 -0
  817. chia/wallet/driver_protocol.py +26 -0
  818. chia/wallet/key_val_store.py +55 -0
  819. chia/wallet/lineage_proof.py +58 -0
  820. chia/wallet/nft_wallet/__init__.py +0 -0
  821. chia/wallet/nft_wallet/metadata_outer_puzzle.py +92 -0
  822. chia/wallet/nft_wallet/nft_info.py +120 -0
  823. chia/wallet/nft_wallet/nft_puzzles.py +305 -0
  824. chia/wallet/nft_wallet/nft_wallet.py +1686 -0
  825. chia/wallet/nft_wallet/ownership_outer_puzzle.py +101 -0
  826. chia/wallet/nft_wallet/puzzles/__init__.py +0 -0
  827. chia/wallet/nft_wallet/puzzles/create_nft_launcher_from_did.clsp +6 -0
  828. chia/wallet/nft_wallet/puzzles/create_nft_launcher_from_did.clsp.hex +1 -0
  829. chia/wallet/nft_wallet/puzzles/nft_intermediate_launcher.clsp +6 -0
  830. chia/wallet/nft_wallet/puzzles/nft_intermediate_launcher.clsp.hex +1 -0
  831. chia/wallet/nft_wallet/puzzles/nft_metadata_updater_default.clsp +30 -0
  832. chia/wallet/nft_wallet/puzzles/nft_metadata_updater_default.clsp.hex +1 -0
  833. chia/wallet/nft_wallet/puzzles/nft_metadata_updater_updateable.clsp +28 -0
  834. chia/wallet/nft_wallet/puzzles/nft_metadata_updater_updateable.clsp.hex +1 -0
  835. chia/wallet/nft_wallet/puzzles/nft_ownership_layer.clsp +100 -0
  836. chia/wallet/nft_wallet/puzzles/nft_ownership_layer.clsp.hex +1 -0
  837. chia/wallet/nft_wallet/puzzles/nft_ownership_transfer_program_one_way_claim_with_royalties.clsp +78 -0
  838. chia/wallet/nft_wallet/puzzles/nft_ownership_transfer_program_one_way_claim_with_royalties.clsp.hex +1 -0
  839. chia/wallet/nft_wallet/puzzles/nft_state_layer.clsp +74 -0
  840. chia/wallet/nft_wallet/puzzles/nft_state_layer.clsp.hex +1 -0
  841. chia/wallet/nft_wallet/singleton_outer_puzzle.py +101 -0
  842. chia/wallet/nft_wallet/transfer_program_puzzle.py +82 -0
  843. chia/wallet/nft_wallet/uncurry_nft.py +217 -0
  844. chia/wallet/notification_manager.py +117 -0
  845. chia/wallet/notification_store.py +178 -0
  846. chia/wallet/outer_puzzles.py +84 -0
  847. chia/wallet/payment.py +34 -0
  848. chia/wallet/puzzle_drivers.py +118 -0
  849. chia/wallet/puzzles/__init__.py +0 -0
  850. chia/wallet/puzzles/augmented_condition.clsp +13 -0
  851. chia/wallet/puzzles/augmented_condition.clsp.hex +1 -0
  852. chia/wallet/puzzles/clawback/__init__.py +0 -0
  853. chia/wallet/puzzles/clawback/drivers.py +188 -0
  854. chia/wallet/puzzles/clawback/metadata.py +38 -0
  855. chia/wallet/puzzles/clawback/puzzle_decorator.py +67 -0
  856. chia/wallet/puzzles/condition_codes.clib +77 -0
  857. chia/wallet/puzzles/curry-and-treehash.clib +102 -0
  858. chia/wallet/puzzles/curry.clib +135 -0
  859. chia/wallet/puzzles/curry_by_index.clib +16 -0
  860. chia/wallet/puzzles/dao_cat_eve.clsp +17 -0
  861. chia/wallet/puzzles/dao_cat_eve.clsp.hex +1 -0
  862. chia/wallet/puzzles/dao_cat_launcher.clsp +36 -0
  863. chia/wallet/puzzles/dao_cat_launcher.clsp.hex +1 -0
  864. chia/wallet/puzzles/dao_finished_state.clsp +35 -0
  865. chia/wallet/puzzles/dao_finished_state.clsp.hex +1 -0
  866. chia/wallet/puzzles/dao_finished_state.clsp.hex.sha256tree +1 -0
  867. chia/wallet/puzzles/dao_lockup.clsp +288 -0
  868. chia/wallet/puzzles/dao_lockup.clsp.hex +1 -0
  869. chia/wallet/puzzles/dao_lockup.clsp.hex.sha256tree +1 -0
  870. chia/wallet/puzzles/dao_proposal.clsp +377 -0
  871. chia/wallet/puzzles/dao_proposal.clsp.hex +1 -0
  872. chia/wallet/puzzles/dao_proposal.clsp.hex.sha256tree +1 -0
  873. chia/wallet/puzzles/dao_proposal_timer.clsp +78 -0
  874. chia/wallet/puzzles/dao_proposal_timer.clsp.hex +1 -0
  875. chia/wallet/puzzles/dao_proposal_timer.clsp.hex.sha256tree +1 -0
  876. chia/wallet/puzzles/dao_proposal_validator.clsp +87 -0
  877. chia/wallet/puzzles/dao_proposal_validator.clsp.hex +1 -0
  878. chia/wallet/puzzles/dao_proposal_validator.clsp.hex.sha256tree +1 -0
  879. chia/wallet/puzzles/dao_spend_p2_singleton_v2.clsp +240 -0
  880. chia/wallet/puzzles/dao_spend_p2_singleton_v2.clsp.hex +1 -0
  881. chia/wallet/puzzles/dao_spend_p2_singleton_v2.clsp.hex.sha256tree +1 -0
  882. chia/wallet/puzzles/dao_treasury.clsp +115 -0
  883. chia/wallet/puzzles/dao_treasury.clsp.hex +1 -0
  884. chia/wallet/puzzles/dao_update_proposal.clsp +44 -0
  885. chia/wallet/puzzles/dao_update_proposal.clsp.hex +1 -0
  886. chia/wallet/puzzles/deployed_puzzle_hashes.json +67 -0
  887. chia/wallet/puzzles/json.clib +25 -0
  888. chia/wallet/puzzles/load_clvm.py +162 -0
  889. chia/wallet/puzzles/merkle_utils.clib +18 -0
  890. chia/wallet/puzzles/notification.clsp +7 -0
  891. chia/wallet/puzzles/notification.clsp.hex +1 -0
  892. chia/wallet/puzzles/p2_1_of_n.clsp +22 -0
  893. chia/wallet/puzzles/p2_1_of_n.clsp.hex +1 -0
  894. chia/wallet/puzzles/p2_conditions.clsp +3 -0
  895. chia/wallet/puzzles/p2_conditions.clsp.hex +1 -0
  896. chia/wallet/puzzles/p2_conditions.py +27 -0
  897. chia/wallet/puzzles/p2_delegated_conditions.clsp +18 -0
  898. chia/wallet/puzzles/p2_delegated_conditions.clsp.hex +1 -0
  899. chia/wallet/puzzles/p2_delegated_conditions.py +22 -0
  900. chia/wallet/puzzles/p2_delegated_puzzle.clsp +19 -0
  901. chia/wallet/puzzles/p2_delegated_puzzle.clsp.hex +1 -0
  902. chia/wallet/puzzles/p2_delegated_puzzle.py +35 -0
  903. chia/wallet/puzzles/p2_delegated_puzzle_or_hidden_puzzle.clsp +91 -0
  904. chia/wallet/puzzles/p2_delegated_puzzle_or_hidden_puzzle.clsp.hex +1 -0
  905. chia/wallet/puzzles/p2_delegated_puzzle_or_hidden_puzzle.py +161 -0
  906. chia/wallet/puzzles/p2_m_of_n_delegate_direct.clsp +108 -0
  907. chia/wallet/puzzles/p2_m_of_n_delegate_direct.clsp.hex +1 -0
  908. chia/wallet/puzzles/p2_m_of_n_delegate_direct.py +22 -0
  909. chia/wallet/puzzles/p2_parent.clsp +19 -0
  910. chia/wallet/puzzles/p2_parent.clsp.hex +1 -0
  911. chia/wallet/puzzles/p2_puzzle_hash.clsp +18 -0
  912. chia/wallet/puzzles/p2_puzzle_hash.clsp.hex +1 -0
  913. chia/wallet/puzzles/p2_puzzle_hash.py +28 -0
  914. chia/wallet/puzzles/p2_singleton.clsp +30 -0
  915. chia/wallet/puzzles/p2_singleton.clsp.hex +1 -0
  916. chia/wallet/puzzles/p2_singleton_aggregator.clsp +81 -0
  917. chia/wallet/puzzles/p2_singleton_aggregator.clsp.hex +1 -0
  918. chia/wallet/puzzles/p2_singleton_or_delayed_puzhash.clsp +50 -0
  919. chia/wallet/puzzles/p2_singleton_or_delayed_puzhash.clsp.hex +1 -0
  920. chia/wallet/puzzles/p2_singleton_via_delegated_puzzle.clsp +47 -0
  921. chia/wallet/puzzles/p2_singleton_via_delegated_puzzle.clsp.hex +1 -0
  922. chia/wallet/puzzles/puzzle_utils.py +34 -0
  923. chia/wallet/puzzles/settlement_payments.clsp +49 -0
  924. chia/wallet/puzzles/settlement_payments.clsp.hex +1 -0
  925. chia/wallet/puzzles/sha256tree.clib +11 -0
  926. chia/wallet/puzzles/singleton_launcher.clsp +16 -0
  927. chia/wallet/puzzles/singleton_launcher.clsp.hex +1 -0
  928. chia/wallet/puzzles/singleton_top_layer.clsp +177 -0
  929. chia/wallet/puzzles/singleton_top_layer.clsp.hex +1 -0
  930. chia/wallet/puzzles/singleton_top_layer.py +295 -0
  931. chia/wallet/puzzles/singleton_top_layer_v1_1.clsp +107 -0
  932. chia/wallet/puzzles/singleton_top_layer_v1_1.clsp.hex +1 -0
  933. chia/wallet/puzzles/singleton_top_layer_v1_1.py +344 -0
  934. chia/wallet/puzzles/singleton_truths.clib +21 -0
  935. chia/wallet/puzzles/tails.py +344 -0
  936. chia/wallet/puzzles/utility_macros.clib +48 -0
  937. chia/wallet/signer_protocol.py +126 -0
  938. chia/wallet/singleton.py +106 -0
  939. chia/wallet/singleton_record.py +30 -0
  940. chia/wallet/trade_manager.py +1088 -0
  941. chia/wallet/trade_record.py +67 -0
  942. chia/wallet/trading/__init__.py +0 -0
  943. chia/wallet/trading/offer.py +703 -0
  944. chia/wallet/trading/trade_status.py +13 -0
  945. chia/wallet/trading/trade_store.py +526 -0
  946. chia/wallet/transaction_record.py +143 -0
  947. chia/wallet/transaction_sorting.py +14 -0
  948. chia/wallet/uncurried_puzzle.py +17 -0
  949. chia/wallet/util/__init__.py +0 -0
  950. chia/wallet/util/address_type.py +55 -0
  951. chia/wallet/util/blind_signer_tl.py +168 -0
  952. chia/wallet/util/clvm_streamable.py +203 -0
  953. chia/wallet/util/compute_hints.py +66 -0
  954. chia/wallet/util/compute_memos.py +45 -0
  955. chia/wallet/util/curry_and_treehash.py +90 -0
  956. chia/wallet/util/debug_spend_bundle.py +234 -0
  957. chia/wallet/util/merkle_tree.py +100 -0
  958. chia/wallet/util/merkle_utils.py +102 -0
  959. chia/wallet/util/new_peak_queue.py +82 -0
  960. chia/wallet/util/notifications.py +12 -0
  961. chia/wallet/util/peer_request_cache.py +174 -0
  962. chia/wallet/util/puzzle_compression.py +96 -0
  963. chia/wallet/util/puzzle_decorator.py +100 -0
  964. chia/wallet/util/puzzle_decorator_type.py +7 -0
  965. chia/wallet/util/query_filter.py +60 -0
  966. chia/wallet/util/transaction_type.py +23 -0
  967. chia/wallet/util/tx_config.py +158 -0
  968. chia/wallet/util/wallet_sync_utils.py +348 -0
  969. chia/wallet/util/wallet_types.py +65 -0
  970. chia/wallet/vc_wallet/__init__.py +0 -0
  971. chia/wallet/vc_wallet/cr_cat_drivers.py +663 -0
  972. chia/wallet/vc_wallet/cr_cat_wallet.py +875 -0
  973. chia/wallet/vc_wallet/cr_outer_puzzle.py +102 -0
  974. chia/wallet/vc_wallet/cr_puzzles/__init__.py +0 -0
  975. chia/wallet/vc_wallet/cr_puzzles/conditions_w_fee_announce.clsp +3 -0
  976. chia/wallet/vc_wallet/cr_puzzles/conditions_w_fee_announce.clsp.hex +1 -0
  977. chia/wallet/vc_wallet/cr_puzzles/credential_restriction.clsp +304 -0
  978. chia/wallet/vc_wallet/cr_puzzles/credential_restriction.clsp.hex +1 -0
  979. chia/wallet/vc_wallet/cr_puzzles/flag_proofs_checker.clsp +45 -0
  980. chia/wallet/vc_wallet/cr_puzzles/flag_proofs_checker.clsp.hex +1 -0
  981. chia/wallet/vc_wallet/vc_drivers.py +838 -0
  982. chia/wallet/vc_wallet/vc_puzzles/__init__.py +0 -0
  983. chia/wallet/vc_wallet/vc_puzzles/covenant_layer.clsp +30 -0
  984. chia/wallet/vc_wallet/vc_puzzles/covenant_layer.clsp.hex +1 -0
  985. chia/wallet/vc_wallet/vc_puzzles/eml_covenant_morpher.clsp +75 -0
  986. chia/wallet/vc_wallet/vc_puzzles/eml_covenant_morpher.clsp.hex +1 -0
  987. chia/wallet/vc_wallet/vc_puzzles/eml_transfer_program_covenant_adapter.clsp +32 -0
  988. chia/wallet/vc_wallet/vc_puzzles/eml_transfer_program_covenant_adapter.clsp.hex +1 -0
  989. chia/wallet/vc_wallet/vc_puzzles/eml_update_metadata_with_DID.clsp +80 -0
  990. chia/wallet/vc_wallet/vc_puzzles/eml_update_metadata_with_DID.clsp.hex +1 -0
  991. chia/wallet/vc_wallet/vc_puzzles/exigent_metadata_layer.clsp +163 -0
  992. chia/wallet/vc_wallet/vc_puzzles/exigent_metadata_layer.clsp.hex +1 -0
  993. chia/wallet/vc_wallet/vc_puzzles/p2_announced_delegated_puzzle.clsp +16 -0
  994. chia/wallet/vc_wallet/vc_puzzles/p2_announced_delegated_puzzle.clsp.hex +1 -0
  995. chia/wallet/vc_wallet/vc_puzzles/standard_vc_backdoor_puzzle.clsp +74 -0
  996. chia/wallet/vc_wallet/vc_puzzles/standard_vc_backdoor_puzzle.clsp.hex +1 -0
  997. chia/wallet/vc_wallet/vc_puzzles/std_parent_morpher.clsp +23 -0
  998. chia/wallet/vc_wallet/vc_puzzles/std_parent_morpher.clsp.hex +1 -0
  999. chia/wallet/vc_wallet/vc_puzzles/viral_backdoor.clsp +64 -0
  1000. chia/wallet/vc_wallet/vc_puzzles/viral_backdoor.clsp.hex +1 -0
  1001. chia/wallet/vc_wallet/vc_store.py +263 -0
  1002. chia/wallet/vc_wallet/vc_wallet.py +638 -0
  1003. chia/wallet/wallet.py +698 -0
  1004. chia/wallet/wallet_action_scope.py +95 -0
  1005. chia/wallet/wallet_blockchain.py +244 -0
  1006. chia/wallet/wallet_coin_record.py +72 -0
  1007. chia/wallet/wallet_coin_store.py +351 -0
  1008. chia/wallet/wallet_info.py +36 -0
  1009. chia/wallet/wallet_interested_store.py +188 -0
  1010. chia/wallet/wallet_nft_store.py +279 -0
  1011. chia/wallet/wallet_node.py +1769 -0
  1012. chia/wallet/wallet_node_api.py +201 -0
  1013. chia/wallet/wallet_pool_store.py +120 -0
  1014. chia/wallet/wallet_protocol.py +90 -0
  1015. chia/wallet/wallet_puzzle_store.py +365 -0
  1016. chia/wallet/wallet_retry_store.py +70 -0
  1017. chia/wallet/wallet_singleton_store.py +258 -0
  1018. chia/wallet/wallet_spend_bundle.py +41 -0
  1019. chia/wallet/wallet_state_manager.py +2820 -0
  1020. chia/wallet/wallet_transaction_store.py +470 -0
  1021. chia/wallet/wallet_user_store.py +110 -0
  1022. chia/wallet/wallet_weight_proof_handler.py +126 -0
  1023. chia_blockchain-2.4.4.dist-info/LICENSE +201 -0
  1024. chia_blockchain-2.4.4.dist-info/METADATA +161 -0
  1025. chia_blockchain-2.4.4.dist-info/RECORD +1028 -0
  1026. chia_blockchain-2.4.4.dist-info/WHEEL +4 -0
  1027. chia_blockchain-2.4.4.dist-info/entry_points.txt +17 -0
  1028. mozilla-ca/cacert.pem +3666 -0
@@ -0,0 +1,4779 @@
1
+ from __future__ import annotations
2
+
3
+ import dataclasses
4
+ import json
5
+ import logging
6
+ import zlib
7
+ from pathlib import Path
8
+ from typing import Any, ClassVar, Dict, List, Optional, Set, Tuple, Union
9
+
10
+ from chia_rs import AugSchemeMPL, G1Element, G2Element, PrivateKey
11
+ from clvm_tools.binutils import assemble
12
+
13
+ from chia.consensus.block_rewards import calculate_base_farmer_reward
14
+ from chia.data_layer.data_layer_errors import LauncherCoinNotFoundError
15
+ from chia.data_layer.data_layer_util import dl_verify_proof
16
+ from chia.data_layer.data_layer_wallet import DataLayerWallet
17
+ from chia.pools.pool_wallet import PoolWallet
18
+ from chia.pools.pool_wallet_info import FARMING_TO_POOL, PoolState, PoolWalletInfo, create_pool_state
19
+ from chia.protocols.wallet_protocol import CoinState
20
+ from chia.rpc.rpc_server import Endpoint, EndpointResult, default_get_connections
21
+ from chia.rpc.util import marshal, tx_endpoint
22
+ from chia.rpc.wallet_request_types import (
23
+ AddKey,
24
+ AddKeyResponse,
25
+ ApplySignatures,
26
+ ApplySignaturesResponse,
27
+ CheckDeleteKey,
28
+ CheckDeleteKeyResponse,
29
+ CombineCoins,
30
+ CombineCoinsResponse,
31
+ DeleteKey,
32
+ Empty,
33
+ ExecuteSigningInstructions,
34
+ ExecuteSigningInstructionsResponse,
35
+ GatherSigningInfo,
36
+ GatherSigningInfoResponse,
37
+ GenerateMnemonicResponse,
38
+ GetLoggedInFingerprintResponse,
39
+ GetNotifications,
40
+ GetNotificationsResponse,
41
+ GetPrivateKey,
42
+ GetPrivateKeyFormat,
43
+ GetPrivateKeyResponse,
44
+ GetPublicKeysResponse,
45
+ LogIn,
46
+ LogInResponse,
47
+ SplitCoins,
48
+ SplitCoinsResponse,
49
+ SubmitTransactions,
50
+ SubmitTransactionsResponse,
51
+ )
52
+ from chia.server.outbound_message import NodeType
53
+ from chia.server.ws_connection import WSChiaConnection
54
+ from chia.types.blockchain_format.coin import Coin, coin_as_list
55
+ from chia.types.blockchain_format.program import INFINITE_COST, Program
56
+ from chia.types.blockchain_format.sized_bytes import bytes32
57
+ from chia.types.coin_record import CoinRecord
58
+ from chia.types.coin_spend import CoinSpend
59
+ from chia.types.signing_mode import CHIP_0002_SIGN_MESSAGE_PREFIX, SigningMode
60
+ from chia.util.bech32m import decode_puzzle_hash, encode_puzzle_hash
61
+ from chia.util.byte_types import hexstr_to_bytes
62
+ from chia.util.config import load_config, str2bool
63
+ from chia.util.errors import KeychainIsLocked
64
+ from chia.util.hash import std_hash
65
+ from chia.util.ints import uint8, uint16, uint32, uint64
66
+ from chia.util.keychain import bytes_to_mnemonic, generate_mnemonic
67
+ from chia.util.path import path_from_root
68
+ from chia.util.streamable import Streamable, UInt32Range, streamable
69
+ from chia.util.ws_message import WsRpcMessage, create_payload_dict
70
+ from chia.wallet.cat_wallet.cat_constants import DEFAULT_CATS
71
+ from chia.wallet.cat_wallet.cat_info import CRCATInfo
72
+ from chia.wallet.cat_wallet.cat_wallet import CATWallet
73
+ from chia.wallet.cat_wallet.dao_cat_info import LockedCoinInfo
74
+ from chia.wallet.cat_wallet.dao_cat_wallet import DAOCATWallet
75
+ from chia.wallet.conditions import (
76
+ AssertCoinAnnouncement,
77
+ AssertPuzzleAnnouncement,
78
+ Condition,
79
+ CreateCoinAnnouncement,
80
+ CreatePuzzleAnnouncement,
81
+ parse_conditions_non_consensus,
82
+ )
83
+ from chia.wallet.dao_wallet.dao_info import DAORules
84
+ from chia.wallet.dao_wallet.dao_utils import (
85
+ generate_mint_proposal_innerpuz,
86
+ generate_simple_proposal_innerpuz,
87
+ generate_update_proposal_innerpuz,
88
+ get_treasury_rules_from_puzzle,
89
+ )
90
+ from chia.wallet.dao_wallet.dao_wallet import DAOWallet
91
+ from chia.wallet.derive_keys import (
92
+ MAX_POOL_WALLETS,
93
+ master_sk_to_farmer_sk,
94
+ master_sk_to_pool_sk,
95
+ master_sk_to_singleton_owner_sk,
96
+ match_address_to_sk,
97
+ )
98
+ from chia.wallet.did_wallet import did_wallet_puzzles
99
+ from chia.wallet.did_wallet.did_info import DIDCoinData, DIDInfo
100
+ from chia.wallet.did_wallet.did_wallet import DIDWallet
101
+ from chia.wallet.did_wallet.did_wallet_puzzles import (
102
+ DID_INNERPUZ_MOD,
103
+ did_program_to_metadata,
104
+ match_did_puzzle,
105
+ metadata_to_program,
106
+ )
107
+ from chia.wallet.nft_wallet import nft_puzzles
108
+ from chia.wallet.nft_wallet.nft_info import NFTCoinInfo, NFTInfo
109
+ from chia.wallet.nft_wallet.nft_puzzles import get_metadata_and_phs
110
+ from chia.wallet.nft_wallet.nft_wallet import NFTWallet
111
+ from chia.wallet.nft_wallet.uncurry_nft import UncurriedNFT
112
+ from chia.wallet.notification_store import Notification
113
+ from chia.wallet.outer_puzzles import AssetType
114
+ from chia.wallet.payment import Payment
115
+ from chia.wallet.puzzle_drivers import PuzzleInfo, Solver
116
+ from chia.wallet.puzzles import p2_delegated_conditions
117
+ from chia.wallet.puzzles.clawback.metadata import AutoClaimSettings, ClawbackMetadata
118
+ from chia.wallet.puzzles.p2_delegated_puzzle_or_hidden_puzzle import puzzle_hash_for_synthetic_public_key
119
+ from chia.wallet.signer_protocol import SigningResponse
120
+ from chia.wallet.singleton import (
121
+ SINGLETON_LAUNCHER_PUZZLE_HASH,
122
+ create_singleton_puzzle,
123
+ get_inner_puzzle_from_singleton,
124
+ )
125
+ from chia.wallet.trade_record import TradeRecord
126
+ from chia.wallet.trading.offer import Offer
127
+ from chia.wallet.transaction_record import TransactionRecord
128
+ from chia.wallet.uncurried_puzzle import uncurry_puzzle
129
+ from chia.wallet.util.address_type import AddressType, is_valid_address
130
+ from chia.wallet.util.compute_hints import compute_spend_hints_and_additions
131
+ from chia.wallet.util.compute_memos import compute_memos
132
+ from chia.wallet.util.curry_and_treehash import NIL_TREEHASH
133
+ from chia.wallet.util.query_filter import FilterMode, HashFilter, TransactionTypeFilter
134
+ from chia.wallet.util.transaction_type import CLAWBACK_INCOMING_TRANSACTION_TYPES, TransactionType
135
+ from chia.wallet.util.tx_config import DEFAULT_TX_CONFIG, TXConfig, TXConfigLoader
136
+ from chia.wallet.util.wallet_sync_utils import fetch_coin_spend_for_coin_state
137
+ from chia.wallet.util.wallet_types import CoinType, WalletType
138
+ from chia.wallet.vc_wallet.cr_cat_drivers import ProofsChecker
139
+ from chia.wallet.vc_wallet.cr_cat_wallet import CRCATWallet
140
+ from chia.wallet.vc_wallet.vc_store import VCProofs
141
+ from chia.wallet.vc_wallet.vc_wallet import VCWallet
142
+ from chia.wallet.wallet import Wallet
143
+ from chia.wallet.wallet_action_scope import WalletActionScope
144
+ from chia.wallet.wallet_coin_record import WalletCoinRecord
145
+ from chia.wallet.wallet_coin_store import CoinRecordOrder, GetCoinRecords, unspent_range
146
+ from chia.wallet.wallet_info import WalletInfo
147
+ from chia.wallet.wallet_node import WalletNode
148
+ from chia.wallet.wallet_protocol import WalletProtocol
149
+ from chia.wallet.wallet_spend_bundle import WalletSpendBundle
150
+
151
+ # Timeout for response from wallet/full node for sending a transaction
152
+ TIMEOUT = 30
153
+ MAX_DERIVATION_INDEX_DELTA = 1000
154
+ MAX_NFT_CHUNK_SIZE = 25
155
+
156
+ log = logging.getLogger(__name__)
157
+
158
+
159
+ class WalletRpcApi:
160
+ max_get_coin_records_limit: ClassVar[uint32] = uint32(1000)
161
+ max_get_coin_records_filter_items: ClassVar[uint32] = uint32(1000)
162
+
163
+ def __init__(self, wallet_node: WalletNode):
164
+ assert wallet_node is not None
165
+ self.service = wallet_node
166
+ self.service_name = "chia_wallet"
167
+
168
+ def get_routes(self) -> Dict[str, Endpoint]:
169
+ return {
170
+ # Key management
171
+ "/log_in": self.log_in,
172
+ "/get_logged_in_fingerprint": self.get_logged_in_fingerprint,
173
+ "/get_public_keys": self.get_public_keys,
174
+ "/get_private_key": self.get_private_key,
175
+ "/generate_mnemonic": self.generate_mnemonic,
176
+ "/add_key": self.add_key,
177
+ "/delete_key": self.delete_key,
178
+ "/check_delete_key": self.check_delete_key,
179
+ "/delete_all_keys": self.delete_all_keys,
180
+ # Wallet node
181
+ "/set_wallet_resync_on_startup": self.set_wallet_resync_on_startup,
182
+ "/get_sync_status": self.get_sync_status,
183
+ "/get_height_info": self.get_height_info,
184
+ "/push_tx": self.push_tx,
185
+ "/push_transactions": self.push_transactions,
186
+ "/get_timestamp_for_height": self.get_timestamp_for_height,
187
+ "/set_auto_claim": self.set_auto_claim,
188
+ "/get_auto_claim": self.get_auto_claim,
189
+ # Wallet management
190
+ "/get_wallets": self.get_wallets,
191
+ "/create_new_wallet": self.create_new_wallet,
192
+ # Wallet
193
+ "/get_wallet_balance": self.get_wallet_balance,
194
+ "/get_wallet_balances": self.get_wallet_balances,
195
+ "/get_transaction": self.get_transaction,
196
+ "/get_transactions": self.get_transactions,
197
+ "/get_transaction_count": self.get_transaction_count,
198
+ "/get_next_address": self.get_next_address,
199
+ "/send_transaction": self.send_transaction,
200
+ "/send_transaction_multi": self.send_transaction_multi,
201
+ "/spend_clawback_coins": self.spend_clawback_coins,
202
+ "/get_coin_records": self.get_coin_records,
203
+ "/get_farmed_amount": self.get_farmed_amount,
204
+ "/create_signed_transaction": self.create_signed_transaction,
205
+ "/delete_unconfirmed_transactions": self.delete_unconfirmed_transactions,
206
+ "/select_coins": self.select_coins,
207
+ "/get_spendable_coins": self.get_spendable_coins,
208
+ "/get_coin_records_by_names": self.get_coin_records_by_names,
209
+ "/get_current_derivation_index": self.get_current_derivation_index,
210
+ "/extend_derivation_index": self.extend_derivation_index,
211
+ "/get_notifications": self.get_notifications,
212
+ "/delete_notifications": self.delete_notifications,
213
+ "/send_notification": self.send_notification,
214
+ "/sign_message_by_address": self.sign_message_by_address,
215
+ "/sign_message_by_id": self.sign_message_by_id,
216
+ "/verify_signature": self.verify_signature,
217
+ "/get_transaction_memo": self.get_transaction_memo,
218
+ "/split_coins": self.split_coins,
219
+ "/combine_coins": self.combine_coins,
220
+ # CATs and trading
221
+ "/cat_set_name": self.cat_set_name,
222
+ "/cat_asset_id_to_name": self.cat_asset_id_to_name,
223
+ "/cat_get_name": self.cat_get_name,
224
+ "/get_stray_cats": self.get_stray_cats,
225
+ "/cat_spend": self.cat_spend,
226
+ "/cat_get_asset_id": self.cat_get_asset_id,
227
+ "/create_offer_for_ids": self.create_offer_for_ids,
228
+ "/get_offer_summary": self.get_offer_summary,
229
+ "/check_offer_validity": self.check_offer_validity,
230
+ "/take_offer": self.take_offer,
231
+ "/get_offer": self.get_offer,
232
+ "/get_all_offers": self.get_all_offers,
233
+ "/get_offers_count": self.get_offers_count,
234
+ "/cancel_offer": self.cancel_offer,
235
+ "/cancel_offers": self.cancel_offers,
236
+ "/get_cat_list": self.get_cat_list,
237
+ # DID Wallet
238
+ "/did_set_wallet_name": self.did_set_wallet_name,
239
+ "/did_get_wallet_name": self.did_get_wallet_name,
240
+ "/did_update_recovery_ids": self.did_update_recovery_ids,
241
+ "/did_update_metadata": self.did_update_metadata,
242
+ "/did_get_pubkey": self.did_get_pubkey,
243
+ "/did_get_did": self.did_get_did,
244
+ "/did_recovery_spend": self.did_recovery_spend,
245
+ "/did_get_recovery_list": self.did_get_recovery_list,
246
+ "/did_get_metadata": self.did_get_metadata,
247
+ "/did_create_attest": self.did_create_attest,
248
+ "/did_get_information_needed_for_recovery": self.did_get_information_needed_for_recovery,
249
+ "/did_get_current_coin_info": self.did_get_current_coin_info,
250
+ "/did_create_backup_file": self.did_create_backup_file,
251
+ "/did_transfer_did": self.did_transfer_did,
252
+ "/did_message_spend": self.did_message_spend,
253
+ "/did_get_info": self.did_get_info,
254
+ "/did_find_lost_did": self.did_find_lost_did,
255
+ # DAO Wallets
256
+ "/dao_get_proposals": self.dao_get_proposals,
257
+ "/dao_create_proposal": self.dao_create_proposal,
258
+ "/dao_parse_proposal": self.dao_parse_proposal,
259
+ "/dao_vote_on_proposal": self.dao_vote_on_proposal,
260
+ "/dao_get_treasury_balance": self.dao_get_treasury_balance,
261
+ "/dao_get_treasury_id": self.dao_get_treasury_id,
262
+ "/dao_get_rules": self.dao_get_rules,
263
+ "/dao_close_proposal": self.dao_close_proposal,
264
+ "/dao_exit_lockup": self.dao_exit_lockup,
265
+ "/dao_adjust_filter_level": self.dao_adjust_filter_level,
266
+ "/dao_add_funds_to_treasury": self.dao_add_funds_to_treasury,
267
+ "/dao_send_to_lockup": self.dao_send_to_lockup,
268
+ "/dao_get_proposal_state": self.dao_get_proposal_state,
269
+ "/dao_free_coins_from_finished_proposals": self.dao_free_coins_from_finished_proposals,
270
+ # NFT Wallet
271
+ "/nft_mint_nft": self.nft_mint_nft,
272
+ "/nft_count_nfts": self.nft_count_nfts,
273
+ "/nft_get_nfts": self.nft_get_nfts,
274
+ "/nft_get_by_did": self.nft_get_by_did,
275
+ "/nft_set_nft_did": self.nft_set_nft_did,
276
+ "/nft_set_nft_status": self.nft_set_nft_status,
277
+ "/nft_get_wallet_did": self.nft_get_wallet_did,
278
+ "/nft_get_wallets_with_dids": self.nft_get_wallets_with_dids,
279
+ "/nft_get_info": self.nft_get_info,
280
+ "/nft_transfer_nft": self.nft_transfer_nft,
281
+ "/nft_add_uri": self.nft_add_uri,
282
+ "/nft_calculate_royalties": self.nft_calculate_royalties,
283
+ "/nft_mint_bulk": self.nft_mint_bulk,
284
+ "/nft_set_did_bulk": self.nft_set_did_bulk,
285
+ "/nft_transfer_bulk": self.nft_transfer_bulk,
286
+ # Pool Wallet
287
+ "/pw_join_pool": self.pw_join_pool,
288
+ "/pw_self_pool": self.pw_self_pool,
289
+ "/pw_absorb_rewards": self.pw_absorb_rewards,
290
+ "/pw_status": self.pw_status,
291
+ # DL Wallet
292
+ "/create_new_dl": self.create_new_dl,
293
+ "/dl_track_new": self.dl_track_new,
294
+ "/dl_stop_tracking": self.dl_stop_tracking,
295
+ "/dl_latest_singleton": self.dl_latest_singleton,
296
+ "/dl_singletons_by_root": self.dl_singletons_by_root,
297
+ "/dl_update_root": self.dl_update_root,
298
+ "/dl_update_multiple": self.dl_update_multiple,
299
+ "/dl_history": self.dl_history,
300
+ "/dl_owned_singletons": self.dl_owned_singletons,
301
+ "/dl_get_mirrors": self.dl_get_mirrors,
302
+ "/dl_new_mirror": self.dl_new_mirror,
303
+ "/dl_delete_mirror": self.dl_delete_mirror,
304
+ "/dl_verify_proof": self.dl_verify_proof,
305
+ # Verified Credential
306
+ "/vc_mint": self.vc_mint,
307
+ "/vc_get": self.vc_get,
308
+ "/vc_get_list": self.vc_get_list,
309
+ "/vc_spend": self.vc_spend,
310
+ "/vc_add_proofs": self.vc_add_proofs,
311
+ "/vc_get_proofs_for_root": self.vc_get_proofs_for_root,
312
+ "/vc_revoke": self.vc_revoke,
313
+ # CR-CATs
314
+ "/crcat_approve_pending": self.crcat_approve_pending,
315
+ # Signer Protocol
316
+ "/gather_signing_info": self.gather_signing_info,
317
+ "/apply_signatures": self.apply_signatures,
318
+ "/submit_transactions": self.submit_transactions,
319
+ # Not technically Signer Protocol but related
320
+ "/execute_signing_instructions": self.execute_signing_instructions,
321
+ }
322
+
323
+ def get_connections(self, request_node_type: Optional[NodeType]) -> List[Dict[str, Any]]:
324
+ return default_get_connections(server=self.service.server, request_node_type=request_node_type)
325
+
326
+ async def _state_changed(self, change: str, change_data: Optional[Dict[str, Any]]) -> List[WsRpcMessage]:
327
+ """
328
+ Called by the WalletNode or WalletStateManager when something has changed in the wallet. This
329
+ gives us an opportunity to send notifications to all connected clients via WebSocket.
330
+ """
331
+ payloads = []
332
+ if change in {"sync_changed", "coin_added", "add_connection", "close_connection"}:
333
+ # Metrics is the only current consumer for this event
334
+ payloads.append(create_payload_dict(change, change_data, self.service_name, "metrics"))
335
+
336
+ payloads.append(create_payload_dict("state_changed", change_data, self.service_name, "wallet_ui"))
337
+
338
+ return payloads
339
+
340
+ async def _stop_wallet(self) -> None:
341
+ """
342
+ Stops a currently running wallet/key, which allows starting the wallet with a new key.
343
+ Each key has it's own wallet database.
344
+ """
345
+ if self.service is not None:
346
+ self.service._close()
347
+ await self.service._await_closed(shutting_down=False)
348
+
349
+ async def _convert_tx_puzzle_hash(self, tx: TransactionRecord) -> TransactionRecord:
350
+ return dataclasses.replace(
351
+ tx,
352
+ to_puzzle_hash=(
353
+ await self.service.wallet_state_manager.convert_puzzle_hash(tx.wallet_id, tx.to_puzzle_hash)
354
+ ),
355
+ )
356
+
357
+ async def get_latest_singleton_coin_spend(
358
+ self, peer: WSChiaConnection, coin_id: bytes32, latest: bool = True
359
+ ) -> Tuple[CoinSpend, CoinState]:
360
+ coin_state_list: List[CoinState] = await self.service.wallet_state_manager.wallet_node.get_coin_state(
361
+ [coin_id], peer=peer
362
+ )
363
+ if coin_state_list is None or len(coin_state_list) < 1:
364
+ raise ValueError(f"Coin record 0x{coin_id.hex()} not found")
365
+ coin_state: CoinState = coin_state_list[0]
366
+ if latest:
367
+ # Find the unspent coin
368
+ while coin_state.spent_height is not None:
369
+ coin_state_list = await self.service.wallet_state_manager.wallet_node.fetch_children(
370
+ coin_state.coin.name(), peer=peer
371
+ )
372
+ odd_coin = None
373
+ for coin in coin_state_list:
374
+ if coin.coin.amount % 2 == 1:
375
+ if odd_coin is not None:
376
+ raise ValueError("This is not a singleton, multiple children coins found.")
377
+ odd_coin = coin
378
+ if odd_coin is None:
379
+ raise ValueError("Cannot find child coin, please wait then retry.")
380
+ coin_state = odd_coin
381
+ # Get parent coin
382
+ parent_coin_state_list: List[CoinState] = await self.service.wallet_state_manager.wallet_node.get_coin_state(
383
+ [coin_state.coin.parent_coin_info], peer=peer
384
+ )
385
+ if parent_coin_state_list is None or len(parent_coin_state_list) < 1:
386
+ raise ValueError(f"Parent coin record 0x{coin_state.coin.parent_coin_info.hex()} not found")
387
+ parent_coin_state: CoinState = parent_coin_state_list[0]
388
+ coin_spend = await fetch_coin_spend_for_coin_state(parent_coin_state, peer)
389
+ return coin_spend, coin_state
390
+
391
+ ##########################################################################################
392
+ # Key management
393
+ ##########################################################################################
394
+
395
+ @marshal
396
+ async def log_in(self, request: LogIn) -> LogInResponse:
397
+ """
398
+ Logs in the wallet with a specific key.
399
+ """
400
+
401
+ if self.service.logged_in_fingerprint == request.fingerprint:
402
+ return LogInResponse(request.fingerprint)
403
+
404
+ await self._stop_wallet()
405
+ started = await self.service._start_with_fingerprint(request.fingerprint)
406
+ if started is True:
407
+ return LogInResponse(request.fingerprint)
408
+
409
+ raise ValueError(f"fingerprint {request.fingerprint} not found in keychain or keychain is empty")
410
+
411
+ @marshal
412
+ async def get_logged_in_fingerprint(self, request: Empty) -> GetLoggedInFingerprintResponse:
413
+ return GetLoggedInFingerprintResponse(uint32.construct_optional(self.service.logged_in_fingerprint))
414
+
415
+ @marshal
416
+ async def get_public_keys(self, request: Empty) -> GetPublicKeysResponse:
417
+ try:
418
+ fingerprints = [
419
+ uint32(sk.get_g1().get_fingerprint())
420
+ for (sk, seed) in await self.service.keychain_proxy.get_all_private_keys()
421
+ ]
422
+ except KeychainIsLocked:
423
+ return GetPublicKeysResponse(keyring_is_locked=True)
424
+ except Exception as e:
425
+ raise Exception(
426
+ "Error while getting keys. If the issue persists, restart all services."
427
+ f" Original error: {type(e).__name__}: {e}"
428
+ ) from e
429
+ else:
430
+ return GetPublicKeysResponse(keyring_is_locked=False, public_key_fingerprints=fingerprints)
431
+
432
+ async def _get_private_key(self, fingerprint: int) -> Tuple[Optional[PrivateKey], Optional[bytes]]:
433
+ try:
434
+ all_keys = await self.service.keychain_proxy.get_all_private_keys()
435
+ for sk, seed in all_keys:
436
+ if sk.get_g1().get_fingerprint() == fingerprint:
437
+ return sk, seed
438
+ except Exception as e:
439
+ log.error(f"Failed to get private key by fingerprint: {e}")
440
+ return None, None
441
+
442
+ @marshal
443
+ async def get_private_key(self, request: GetPrivateKey) -> GetPrivateKeyResponse:
444
+ sk, seed = await self._get_private_key(request.fingerprint)
445
+ if sk is not None:
446
+ s = bytes_to_mnemonic(seed) if seed is not None else None
447
+ return GetPrivateKeyResponse(
448
+ private_key=GetPrivateKeyFormat(
449
+ fingerprint=request.fingerprint,
450
+ sk=sk,
451
+ pk=sk.get_g1(),
452
+ farmer_pk=master_sk_to_farmer_sk(sk).get_g1(),
453
+ pool_pk=master_sk_to_pool_sk(sk).get_g1(),
454
+ seed=s,
455
+ )
456
+ )
457
+
458
+ raise ValueError(f"Could not get a private key for fingerprint {request.fingerprint}")
459
+
460
+ @marshal
461
+ async def generate_mnemonic(self, request: Empty) -> GenerateMnemonicResponse:
462
+ return GenerateMnemonicResponse(generate_mnemonic().split(" "))
463
+
464
+ @marshal
465
+ async def add_key(self, request: AddKey) -> AddKeyResponse:
466
+ # Adding a key from 24 word mnemonic
467
+ try:
468
+ sk = await self.service.keychain_proxy.add_key(" ".join(request.mnemonic))
469
+ except KeyError as e:
470
+ raise ValueError(f"The word '{e.args[0]}' is incorrect.")
471
+
472
+ fingerprint = uint32(sk.get_g1().get_fingerprint())
473
+ await self._stop_wallet()
474
+
475
+ # Makes sure the new key is added to config properly
476
+ started = False
477
+ try:
478
+ await self.service.keychain_proxy.check_keys(self.service.root_path)
479
+ except Exception as e:
480
+ log.error(f"Failed to check_keys after adding a new key: {e}")
481
+ started = await self.service._start_with_fingerprint(fingerprint=fingerprint)
482
+ if started is True:
483
+ return AddKeyResponse(fingerprint=fingerprint)
484
+ raise ValueError("Failed to start")
485
+
486
+ @marshal
487
+ async def delete_key(self, request: DeleteKey) -> Empty:
488
+ await self._stop_wallet()
489
+ try:
490
+ await self.service.keychain_proxy.delete_key_by_fingerprint(request.fingerprint)
491
+ except Exception as e:
492
+ log.error(f"Failed to delete key by fingerprint: {e}")
493
+ raise e
494
+ path = path_from_root(
495
+ self.service.root_path,
496
+ f"{self.service.config['database_path']}-{request.fingerprint}",
497
+ )
498
+ if path.exists():
499
+ path.unlink()
500
+ return Empty()
501
+
502
+ async def _check_key_used_for_rewards(
503
+ self, new_root: Path, sk: PrivateKey, max_ph_to_search: int
504
+ ) -> Tuple[bool, bool]:
505
+ """Checks if the given key is used for either the farmer rewards or pool rewards
506
+ returns a tuple of two booleans
507
+ The first is true if the key is used as the Farmer rewards, otherwise false
508
+ The second is true if the key is used as the Pool rewards, otherwise false
509
+ Returns both false if the key cannot be found with the given fingerprint
510
+ """
511
+ if sk is None:
512
+ return False, False
513
+
514
+ config: Dict[str, Any] = load_config(new_root, "config.yaml")
515
+ farmer_target = config["farmer"].get("xch_target_address", "")
516
+ pool_target = config["pool"].get("xch_target_address", "")
517
+ address_to_check: List[bytes32] = []
518
+
519
+ try:
520
+ farmer_decoded = decode_puzzle_hash(farmer_target)
521
+ address_to_check.append(farmer_decoded)
522
+ except ValueError:
523
+ farmer_decoded = None
524
+
525
+ try:
526
+ pool_decoded = decode_puzzle_hash(pool_target)
527
+ address_to_check.append(pool_decoded)
528
+ except ValueError:
529
+ pool_decoded = None
530
+
531
+ found_addresses: Set[bytes32] = match_address_to_sk(sk, address_to_check, max_ph_to_search)
532
+ found_farmer = False
533
+ found_pool = False
534
+
535
+ if farmer_decoded is not None:
536
+ found_farmer = farmer_decoded in found_addresses
537
+
538
+ if pool_decoded is not None:
539
+ found_pool = pool_decoded in found_addresses
540
+
541
+ return found_farmer, found_pool
542
+
543
+ @marshal
544
+ async def check_delete_key(self, request: CheckDeleteKey) -> CheckDeleteKeyResponse:
545
+ """Check the key use prior to possible deletion
546
+ checks whether key is used for either farm or pool rewards
547
+ checks if any wallets have a non-zero balance
548
+ """
549
+ used_for_farmer: bool = False
550
+ used_for_pool: bool = False
551
+ wallet_balance: bool = False
552
+
553
+ sk, _ = await self._get_private_key(request.fingerprint)
554
+ if sk is not None:
555
+ used_for_farmer, used_for_pool = await self._check_key_used_for_rewards(
556
+ self.service.root_path, sk, request.max_ph_to_search
557
+ )
558
+
559
+ if self.service.logged_in_fingerprint != request.fingerprint:
560
+ await self._stop_wallet()
561
+ await self.service._start_with_fingerprint(fingerprint=request.fingerprint)
562
+
563
+ wallets: List[WalletInfo] = await self.service.wallet_state_manager.get_all_wallet_info_entries()
564
+ for w in wallets:
565
+ wallet = self.service.wallet_state_manager.wallets[w.id]
566
+ unspent = await self.service.wallet_state_manager.coin_store.get_unspent_coins_for_wallet(w.id)
567
+ balance = await wallet.get_confirmed_balance(unspent)
568
+ pending_balance = await wallet.get_unconfirmed_balance(unspent)
569
+
570
+ if (balance + pending_balance) > 0:
571
+ wallet_balance = True
572
+ break
573
+
574
+ return CheckDeleteKeyResponse(
575
+ fingerprint=request.fingerprint,
576
+ used_for_farmer_rewards=used_for_farmer,
577
+ used_for_pool_rewards=used_for_pool,
578
+ wallet_balance=wallet_balance,
579
+ )
580
+
581
+ @marshal
582
+ async def delete_all_keys(self, request: Empty) -> Empty:
583
+ await self._stop_wallet()
584
+ try:
585
+ await self.service.keychain_proxy.delete_all_keys()
586
+ except Exception as e:
587
+ log.error(f"Failed to delete all keys: {e}")
588
+ raise e
589
+ path = path_from_root(self.service.root_path, self.service.config["database_path"])
590
+ if path.exists():
591
+ path.unlink()
592
+ return Empty()
593
+
594
+ ##########################################################################################
595
+ # Wallet Node
596
+ ##########################################################################################
597
+ async def set_wallet_resync_on_startup(self, request: Dict[str, Any]) -> Dict[str, Any]:
598
+ """
599
+ Resync the current logged in wallet. The transaction and offer records will be kept.
600
+ :param request: optionally pass in `enable` as bool to enable/disable resync
601
+ :return:
602
+ """
603
+ assert self.service.wallet_state_manager is not None
604
+ try:
605
+ enable = bool(request.get("enable", True))
606
+ except ValueError:
607
+ raise ValueError("Please provide a boolean value for `enable` parameter in request")
608
+ fingerprint = self.service.logged_in_fingerprint
609
+ if fingerprint is not None:
610
+ self.service.set_resync_on_startup(fingerprint, enable)
611
+ else:
612
+ raise ValueError("You need to login into wallet to use this RPC call")
613
+ return {"success": True}
614
+
615
+ async def get_sync_status(self, request: Dict[str, Any]) -> EndpointResult:
616
+ sync_mode = self.service.wallet_state_manager.sync_mode
617
+ has_pending_queue_items = self.service.new_peak_queue.has_pending_data_process_items()
618
+ syncing = sync_mode or has_pending_queue_items
619
+ synced = await self.service.wallet_state_manager.synced()
620
+ return {"synced": synced, "syncing": syncing, "genesis_initialized": True}
621
+
622
+ async def get_height_info(self, request: Dict[str, Any]) -> EndpointResult:
623
+ height = await self.service.wallet_state_manager.blockchain.get_finished_sync_up_to()
624
+ return {"height": height}
625
+
626
+ async def push_tx(self, request: Dict[str, Any]) -> EndpointResult:
627
+ nodes = self.service.server.get_connections(NodeType.FULL_NODE)
628
+ if len(nodes) == 0:
629
+ raise ValueError("Wallet is not currently connected to any full node peers")
630
+ await self.service.push_tx(WalletSpendBundle.from_bytes(hexstr_to_bytes(request["spend_bundle"])))
631
+ return {}
632
+
633
+ @tx_endpoint(push=True)
634
+ async def push_transactions(
635
+ self,
636
+ request: Dict[str, Any],
637
+ action_scope: WalletActionScope,
638
+ extra_conditions: Tuple[Condition, ...] = tuple(),
639
+ ) -> EndpointResult:
640
+ if not action_scope.config.push:
641
+ raise ValueError("Cannot push transactions if push is False")
642
+ async with action_scope.use() as interface:
643
+ for transaction_hexstr_or_json in request["transactions"]:
644
+ if isinstance(transaction_hexstr_or_json, str):
645
+ tx = TransactionRecord.from_bytes(hexstr_to_bytes(transaction_hexstr_or_json))
646
+ interface.side_effects.transactions.append(tx)
647
+ else:
648
+ try:
649
+ tx = TransactionRecord.from_json_dict_convenience(transaction_hexstr_or_json)
650
+ except AttributeError:
651
+ tx = TransactionRecord.from_json_dict(transaction_hexstr_or_json)
652
+ interface.side_effects.transactions.append(tx)
653
+
654
+ if request.get("fee", 0) != 0:
655
+ all_conditions_and_origins = [
656
+ (condition, cs.coin.name())
657
+ for tx in interface.side_effects.transactions
658
+ if tx.spend_bundle is not None
659
+ for cs in tx.spend_bundle.coin_spends
660
+ for condition in cs.puzzle_reveal.run_with_cost(INFINITE_COST, cs.solution)[1].as_iter()
661
+ ]
662
+ create_coin_announcement = next(
663
+ condition
664
+ for condition in parse_conditions_non_consensus(
665
+ [con for con, coin in all_conditions_and_origins], abstractions=False
666
+ )
667
+ if isinstance(condition, CreateCoinAnnouncement)
668
+ )
669
+ announcement_origin = next(
670
+ coin
671
+ for condition, coin in all_conditions_and_origins
672
+ if condition == create_coin_announcement.to_program()
673
+ )
674
+ async with self.service.wallet_state_manager.new_action_scope(
675
+ dataclasses.replace(
676
+ action_scope.config.tx_config,
677
+ excluded_coin_ids=[
678
+ *action_scope.config.tx_config.excluded_coin_ids,
679
+ *(c.name() for tx in interface.side_effects.transactions for c in tx.removals),
680
+ ],
681
+ ),
682
+ push=False,
683
+ ) as inner_action_scope:
684
+ await self.service.wallet_state_manager.main_wallet.create_tandem_xch_tx(
685
+ uint64(request["fee"]),
686
+ inner_action_scope,
687
+ (
688
+ *extra_conditions,
689
+ CreateCoinAnnouncement(
690
+ create_coin_announcement.msg, announcement_origin
691
+ ).corresponding_assertion(),
692
+ ),
693
+ )
694
+
695
+ interface.side_effects.transactions.extend(inner_action_scope.side_effects.transactions)
696
+
697
+ return {}
698
+
699
+ async def get_timestamp_for_height(self, request: Dict[str, Any]) -> EndpointResult:
700
+ return {"timestamp": await self.service.get_timestamp_for_height(uint32(request["height"]))}
701
+
702
+ async def set_auto_claim(self, request: Dict[str, Any]) -> EndpointResult:
703
+ """
704
+ Set auto claim merkle coins config
705
+ :param request: Example {"enable": true, "tx_fee": 100000, "min_amount": 0, "batch_size": 50}
706
+ :return:
707
+ """
708
+ return self.service.set_auto_claim(AutoClaimSettings.from_json_dict(request))
709
+
710
+ async def get_auto_claim(self, request: Dict[str, Any]) -> EndpointResult:
711
+ """
712
+ Get auto claim merkle coins config
713
+ :param request: None
714
+ :return:
715
+ """
716
+ auto_claim_settings = AutoClaimSettings.from_json_dict(
717
+ self.service.wallet_state_manager.config.get("auto_claim", {})
718
+ )
719
+ return auto_claim_settings.to_json_dict()
720
+
721
+ ##########################################################################################
722
+ # Wallet Management
723
+ ##########################################################################################
724
+
725
+ async def get_wallets(self, request: Dict[str, Any]) -> EndpointResult:
726
+ include_data: bool = request.get("include_data", True)
727
+ wallet_type: Optional[WalletType] = None
728
+ if "type" in request:
729
+ wallet_type = WalletType(request["type"])
730
+
731
+ wallets: List[WalletInfo] = await self.service.wallet_state_manager.get_all_wallet_info_entries(wallet_type)
732
+ if not include_data:
733
+ result: List[WalletInfo] = []
734
+ for wallet in wallets:
735
+ result.append(WalletInfo(wallet.id, wallet.name, wallet.type, ""))
736
+ wallets = result
737
+ response: EndpointResult = {"wallets": wallets}
738
+ if include_data:
739
+ response = {
740
+ "wallets": [
741
+ (
742
+ wallet
743
+ if wallet.type != WalletType.CRCAT
744
+ else {
745
+ **wallet.to_json_dict(),
746
+ "authorized_providers": [
747
+ p.hex() for p in CRCATInfo.from_bytes(bytes.fromhex(wallet.data)).authorized_providers
748
+ ],
749
+ "flags_needed": CRCATInfo.from_bytes(bytes.fromhex(wallet.data)).proofs_checker.flags,
750
+ }
751
+ )
752
+ for wallet in response["wallets"]
753
+ ]
754
+ }
755
+ if self.service.logged_in_fingerprint is not None:
756
+ response["fingerprint"] = self.service.logged_in_fingerprint
757
+ return response
758
+
759
+ @tx_endpoint(push=True)
760
+ async def create_new_wallet(
761
+ self,
762
+ request: Dict[str, Any],
763
+ action_scope: WalletActionScope,
764
+ extra_conditions: Tuple[Condition, ...] = tuple(),
765
+ ) -> EndpointResult:
766
+ wallet_state_manager = self.service.wallet_state_manager
767
+
768
+ if await self.service.wallet_state_manager.synced() is False:
769
+ raise ValueError("Wallet needs to be fully synced.")
770
+ main_wallet = wallet_state_manager.main_wallet
771
+ fee = uint64(request.get("fee", 0))
772
+
773
+ if request["wallet_type"] == "cat_wallet":
774
+ # If not provided, the name will be autogenerated based on the tail hash.
775
+ name = request.get("name", None)
776
+ if request["mode"] == "new":
777
+ if request.get("test", False):
778
+ if not action_scope.config.push:
779
+ raise ValueError("Test CAT minting must be pushed automatically") # pragma: no cover
780
+ async with self.service.wallet_state_manager.lock:
781
+ cat_wallet = await CATWallet.create_new_cat_wallet(
782
+ wallet_state_manager,
783
+ main_wallet,
784
+ {"identifier": "genesis_by_id"},
785
+ uint64(request["amount"]),
786
+ action_scope,
787
+ fee,
788
+ name,
789
+ )
790
+ asset_id = cat_wallet.get_asset_id()
791
+ self.service.wallet_state_manager.state_changed("wallet_created")
792
+ return {
793
+ "type": cat_wallet.type(),
794
+ "asset_id": asset_id,
795
+ "wallet_id": cat_wallet.id(),
796
+ "transactions": None, # tx_endpoint wrapper will take care of this
797
+ }
798
+ else:
799
+ raise ValueError(
800
+ "Support for this RPC mode has been dropped."
801
+ " Please use the CAT Admin Tool @ https://github.com/Chia-Network/CAT-admin-tool instead."
802
+ )
803
+
804
+ elif request["mode"] == "existing":
805
+ async with self.service.wallet_state_manager.lock:
806
+ cat_wallet = await CATWallet.get_or_create_wallet_for_cat(
807
+ wallet_state_manager, main_wallet, request["asset_id"], name
808
+ )
809
+ return {"type": cat_wallet.type(), "asset_id": request["asset_id"], "wallet_id": cat_wallet.id()}
810
+
811
+ else: # undefined mode
812
+ pass
813
+
814
+ elif request["wallet_type"] == "did_wallet":
815
+ if request["did_type"] == "new":
816
+ backup_dids = []
817
+ num_needed = 0
818
+ for d in request["backup_dids"]:
819
+ backup_dids.append(decode_puzzle_hash(d))
820
+ if len(backup_dids) > 0:
821
+ num_needed = uint64(request["num_of_backup_ids_needed"])
822
+ metadata: Dict[str, str] = {}
823
+ if "metadata" in request:
824
+ if type(request["metadata"]) is dict:
825
+ metadata = request["metadata"]
826
+
827
+ async with self.service.wallet_state_manager.lock:
828
+ did_wallet_name: str = request.get("wallet_name", None)
829
+ if did_wallet_name is not None:
830
+ did_wallet_name = did_wallet_name.strip()
831
+ did_wallet: DIDWallet = await DIDWallet.create_new_did_wallet(
832
+ wallet_state_manager,
833
+ main_wallet,
834
+ uint64(request["amount"]),
835
+ action_scope,
836
+ backup_dids,
837
+ uint64(num_needed),
838
+ metadata,
839
+ did_wallet_name,
840
+ uint64(request.get("fee", 0)),
841
+ extra_conditions=extra_conditions,
842
+ )
843
+
844
+ my_did_id = encode_puzzle_hash(
845
+ bytes32.fromhex(did_wallet.get_my_DID()), AddressType.DID.hrp(self.service.config)
846
+ )
847
+ nft_wallet_name = did_wallet_name
848
+ if nft_wallet_name is not None:
849
+ nft_wallet_name = f"{nft_wallet_name} NFT Wallet"
850
+ await NFTWallet.create_new_nft_wallet(
851
+ wallet_state_manager,
852
+ main_wallet,
853
+ bytes32.fromhex(did_wallet.get_my_DID()),
854
+ nft_wallet_name,
855
+ )
856
+ return {
857
+ "success": True,
858
+ "type": did_wallet.type(),
859
+ "my_did": my_did_id,
860
+ "wallet_id": did_wallet.id(),
861
+ "transactions": None, # tx_endpoint wrapper will take care of this
862
+ }
863
+
864
+ elif request["did_type"] == "recovery":
865
+ async with self.service.wallet_state_manager.lock:
866
+ did_wallet = await DIDWallet.create_new_did_wallet_from_recovery(
867
+ wallet_state_manager, main_wallet, request["backup_data"]
868
+ )
869
+ assert did_wallet.did_info.temp_coin is not None
870
+ assert did_wallet.did_info.temp_puzhash is not None
871
+ assert did_wallet.did_info.temp_pubkey is not None
872
+ my_did = did_wallet.get_my_DID()
873
+ coin_name = did_wallet.did_info.temp_coin.name().hex()
874
+ coin_list = coin_as_list(did_wallet.did_info.temp_coin)
875
+ newpuzhash = did_wallet.did_info.temp_puzhash
876
+ pubkey = did_wallet.did_info.temp_pubkey
877
+ return {
878
+ "success": True,
879
+ "type": did_wallet.type(),
880
+ "my_did": my_did,
881
+ "wallet_id": did_wallet.id(),
882
+ "coin_name": coin_name,
883
+ "coin_list": coin_list,
884
+ "newpuzhash": newpuzhash.hex(),
885
+ "pubkey": pubkey.hex(),
886
+ "backup_dids": did_wallet.did_info.backup_ids,
887
+ "num_verifications_required": did_wallet.did_info.num_of_backup_ids_needed,
888
+ }
889
+ else: # undefined did_type
890
+ pass
891
+ elif request["wallet_type"] == "dao_wallet":
892
+ name = request.get("name", None)
893
+ mode = request.get("mode", None)
894
+ if mode == "new":
895
+ dao_rules_json = request.get("dao_rules", None)
896
+ if dao_rules_json:
897
+ dao_rules = DAORules.from_json_dict(dao_rules_json)
898
+ else:
899
+ raise ValueError("DAO rules must be specified for wallet creation")
900
+ async with self.service.wallet_state_manager.lock:
901
+ dao_wallet = await DAOWallet.create_new_dao_and_wallet(
902
+ wallet_state_manager,
903
+ main_wallet,
904
+ uint64(request.get("amount_of_cats", None)),
905
+ dao_rules,
906
+ action_scope,
907
+ uint64(request.get("filter_amount", 1)),
908
+ name,
909
+ uint64(request.get("fee", 0)),
910
+ uint64(request.get("fee_for_cat", 0)),
911
+ )
912
+ elif mode == "existing":
913
+ # async with self.service.wallet_state_manager.lock:
914
+ dao_wallet = await DAOWallet.create_new_dao_wallet_for_existing_dao(
915
+ wallet_state_manager,
916
+ main_wallet,
917
+ bytes32.from_hexstr(request.get("treasury_id", None)),
918
+ uint64(request.get("filter_amount", 1)),
919
+ name,
920
+ )
921
+ else:
922
+ raise Exception(f"Invalid DAO wallet mode: {mode!r}")
923
+
924
+ return {
925
+ "success": True,
926
+ "type": dao_wallet.type(),
927
+ "wallet_id": dao_wallet.id(),
928
+ "treasury_id": dao_wallet.dao_info.treasury_id,
929
+ "cat_wallet_id": dao_wallet.dao_info.cat_wallet_id,
930
+ "dao_cat_wallet_id": dao_wallet.dao_info.dao_cat_wallet_id,
931
+ "transactions": None, # tx_endpoint wrapper will take care of this
932
+ }
933
+ elif request["wallet_type"] == "nft_wallet":
934
+ for wallet in self.service.wallet_state_manager.wallets.values():
935
+ did_id: Optional[bytes32] = None
936
+ if "did_id" in request and request["did_id"] is not None:
937
+ did_id = decode_puzzle_hash(request["did_id"])
938
+ if wallet.type() == WalletType.NFT:
939
+ assert isinstance(wallet, NFTWallet)
940
+ if wallet.get_did() == did_id:
941
+ log.info("NFT wallet already existed, skipping.")
942
+ return {
943
+ "success": True,
944
+ "type": wallet.type(),
945
+ "wallet_id": wallet.id(),
946
+ }
947
+
948
+ async with self.service.wallet_state_manager.lock:
949
+ nft_wallet: NFTWallet = await NFTWallet.create_new_nft_wallet(
950
+ wallet_state_manager, main_wallet, did_id, request.get("name", None)
951
+ )
952
+ return {
953
+ "success": True,
954
+ "type": nft_wallet.type(),
955
+ "wallet_id": nft_wallet.id(),
956
+ }
957
+ elif request["wallet_type"] == "pool_wallet":
958
+ if request["mode"] == "new":
959
+ if "initial_target_state" not in request:
960
+ raise AttributeError("Daemon didn't send `initial_target_state`. Try updating the daemon.")
961
+
962
+ owner_puzzle_hash: bytes32 = await self.service.wallet_state_manager.main_wallet.get_puzzle_hash(True)
963
+
964
+ from chia.pools.pool_wallet_info import initial_pool_state_from_dict
965
+
966
+ async with self.service.wallet_state_manager.lock:
967
+ # We assign a pseudo unique id to each pool wallet, so that each one gets its own deterministic
968
+ # owner and auth keys. The public keys will go on the blockchain, and the private keys can be found
969
+ # using the root SK and trying each index from zero. The indexes are not fully unique though,
970
+ # because the PoolWallet is not created until the tx gets confirmed on chain. Therefore if we
971
+ # make multiple pool wallets at the same time, they will have the same ID.
972
+ max_pwi = 1
973
+ for _, wallet in self.service.wallet_state_manager.wallets.items():
974
+ if wallet.type() == WalletType.POOLING_WALLET:
975
+ assert isinstance(wallet, PoolWallet)
976
+ pool_wallet_index = await wallet.get_pool_wallet_index()
977
+ max_pwi = max(max_pwi, pool_wallet_index)
978
+
979
+ if max_pwi + 1 >= (MAX_POOL_WALLETS - 1):
980
+ raise ValueError(f"Too many pool wallets ({max_pwi}), cannot create any more on this key.")
981
+
982
+ owner_sk: PrivateKey = master_sk_to_singleton_owner_sk(
983
+ self.service.wallet_state_manager.get_master_private_key(), uint32(max_pwi + 1)
984
+ )
985
+ owner_pk: G1Element = owner_sk.get_g1()
986
+
987
+ initial_target_state = initial_pool_state_from_dict(
988
+ request["initial_target_state"], owner_pk, owner_puzzle_hash
989
+ )
990
+ assert initial_target_state is not None
991
+
992
+ try:
993
+ delayed_address = None
994
+ if "p2_singleton_delayed_ph" in request:
995
+ delayed_address = bytes32.from_hexstr(request["p2_singleton_delayed_ph"])
996
+
997
+ p2_singleton_puzzle_hash, launcher_id = await PoolWallet.create_new_pool_wallet_transaction(
998
+ wallet_state_manager,
999
+ main_wallet,
1000
+ initial_target_state,
1001
+ action_scope,
1002
+ fee,
1003
+ request.get("p2_singleton_delay_time", None),
1004
+ delayed_address,
1005
+ extra_conditions=extra_conditions,
1006
+ )
1007
+
1008
+ except Exception as e:
1009
+ raise ValueError(str(e))
1010
+ return {
1011
+ "total_fee": fee * 2,
1012
+ "transaction": None, # tx_endpoint wrapper will take care of this
1013
+ "transactions": None, # tx_endpoint wrapper will take care of this
1014
+ "launcher_id": launcher_id.hex(),
1015
+ "p2_singleton_puzzle_hash": p2_singleton_puzzle_hash.hex(),
1016
+ }
1017
+ elif request["mode"] == "recovery":
1018
+ raise ValueError("Need upgraded singleton for on-chain recovery")
1019
+
1020
+ else: # undefined wallet_type
1021
+ pass
1022
+
1023
+ # TODO: rework this function to report detailed errors for each error case
1024
+ return {"success": False, "error": "invalid request"}
1025
+
1026
+ ##########################################################################################
1027
+ # Wallet
1028
+ ##########################################################################################
1029
+
1030
+ async def _get_wallet_balance(self, wallet_id: uint32) -> Dict[str, Any]:
1031
+ wallet = self.service.wallet_state_manager.wallets[wallet_id]
1032
+ balance = await self.service.get_balance(wallet_id)
1033
+ wallet_balance = balance.to_json_dict()
1034
+ wallet_balance["wallet_id"] = wallet_id
1035
+ wallet_balance["wallet_type"] = wallet.type()
1036
+ if self.service.logged_in_fingerprint is not None:
1037
+ wallet_balance["fingerprint"] = self.service.logged_in_fingerprint
1038
+ if wallet.type() in {WalletType.CAT, WalletType.CRCAT}:
1039
+ assert isinstance(wallet, CATWallet)
1040
+ wallet_balance["asset_id"] = wallet.get_asset_id()
1041
+ if wallet.type() == WalletType.CRCAT:
1042
+ assert isinstance(wallet, CRCATWallet)
1043
+ wallet_balance["pending_approval_balance"] = await wallet.get_pending_approval_balance()
1044
+
1045
+ return wallet_balance
1046
+
1047
+ async def get_wallet_balance(self, request: Dict[str, Any]) -> EndpointResult:
1048
+ wallet_id = uint32(int(request["wallet_id"]))
1049
+ wallet_balance = await self._get_wallet_balance(wallet_id)
1050
+ return {"wallet_balance": wallet_balance}
1051
+
1052
+ async def get_wallet_balances(self, request: Dict[str, Any]) -> EndpointResult:
1053
+ try:
1054
+ wallet_ids: List[uint32] = [uint32(int(wallet_id)) for wallet_id in request["wallet_ids"]]
1055
+ except (TypeError, KeyError):
1056
+ wallet_ids = list(self.service.wallet_state_manager.wallets.keys())
1057
+ wallet_balances: Dict[uint32, Dict[str, Any]] = {}
1058
+ for wallet_id in wallet_ids:
1059
+ wallet_balances[wallet_id] = await self._get_wallet_balance(wallet_id)
1060
+ return {"wallet_balances": wallet_balances}
1061
+
1062
+ async def get_transaction(self, request: Dict[str, Any]) -> EndpointResult:
1063
+ transaction_id: bytes32 = bytes32.from_hexstr(request["transaction_id"])
1064
+ tr: Optional[TransactionRecord] = await self.service.wallet_state_manager.get_transaction(transaction_id)
1065
+ if tr is None:
1066
+ raise ValueError(f"Transaction 0x{transaction_id.hex()} not found")
1067
+
1068
+ return {
1069
+ "transaction": (await self._convert_tx_puzzle_hash(tr)).to_json_dict_convenience(self.service.config),
1070
+ "transaction_id": tr.name,
1071
+ }
1072
+
1073
+ async def get_transaction_memo(self, request: Dict[str, Any]) -> EndpointResult:
1074
+ transaction_id: bytes32 = bytes32.from_hexstr(request["transaction_id"])
1075
+ tr: Optional[TransactionRecord] = await self.service.wallet_state_manager.get_transaction(transaction_id)
1076
+ if tr is None:
1077
+ raise ValueError(f"Transaction 0x{transaction_id.hex()} not found")
1078
+ if tr.spend_bundle is None or len(tr.spend_bundle.coin_spends) == 0:
1079
+ if tr.type == uint32(TransactionType.INCOMING_TX.value):
1080
+ # Fetch incoming tx coin spend
1081
+ peer = self.service.get_full_node_peer()
1082
+ assert len(tr.additions) == 1
1083
+ coin_state_list: List[CoinState] = await self.service.wallet_state_manager.wallet_node.get_coin_state(
1084
+ [tr.additions[0].parent_coin_info], peer=peer
1085
+ )
1086
+ assert len(coin_state_list) == 1
1087
+ coin_spend = await fetch_coin_spend_for_coin_state(coin_state_list[0], peer)
1088
+ tr = dataclasses.replace(tr, spend_bundle=WalletSpendBundle([coin_spend], G2Element()))
1089
+ else:
1090
+ raise ValueError(f"Transaction 0x{transaction_id.hex()} doesn't have any coin spend.")
1091
+ assert tr.spend_bundle is not None
1092
+ memos: Dict[bytes32, List[bytes]] = compute_memos(tr.spend_bundle)
1093
+ response = {}
1094
+ # Convert to hex string
1095
+ for coin_id, memo_list in memos.items():
1096
+ response[coin_id.hex()] = [memo.hex() for memo in memo_list]
1097
+ return {transaction_id.hex(): response}
1098
+
1099
+ @tx_endpoint(push=False)
1100
+ @marshal
1101
+ async def split_coins(
1102
+ self, request: SplitCoins, action_scope: WalletActionScope, extra_conditions: Tuple[Condition, ...] = tuple()
1103
+ ) -> SplitCoinsResponse:
1104
+ if request.number_of_coins > 500:
1105
+ raise ValueError(f"{request.number_of_coins} coins is greater then the maximum limit of 500 coins.")
1106
+
1107
+ optional_coin = await self.service.wallet_state_manager.coin_store.get_coin_record(request.target_coin_id)
1108
+ if optional_coin is None:
1109
+ raise ValueError(f"Could not find coin with ID {request.target_coin_id}")
1110
+ else:
1111
+ coin = optional_coin.coin
1112
+
1113
+ total_amount = request.amount_per_coin * request.number_of_coins
1114
+
1115
+ if coin.amount < total_amount:
1116
+ raise ValueError(
1117
+ f"Coin amount: {coin.amount} is less than the total amount of the split: {total_amount}, exiting."
1118
+ )
1119
+
1120
+ if request.wallet_id not in self.service.wallet_state_manager.wallets:
1121
+ raise ValueError(f"Wallet with ID {request.wallet_id} does not exist")
1122
+ wallet = self.service.wallet_state_manager.wallets[request.wallet_id]
1123
+ if not isinstance(wallet, (Wallet, CATWallet)):
1124
+ raise ValueError("Cannot split coins from non-fungible wallet types")
1125
+
1126
+ outputs = [
1127
+ Payment(await wallet.get_puzzle_hash(new=True), request.amount_per_coin)
1128
+ for _ in range(request.number_of_coins)
1129
+ ]
1130
+ if len(outputs) == 0:
1131
+ return SplitCoinsResponse([], [])
1132
+
1133
+ # TODO: unify GST API
1134
+ if wallet.type() == WalletType.STANDARD_WALLET:
1135
+ assert isinstance(wallet, Wallet)
1136
+ if coin.amount < total_amount + request.fee:
1137
+ async with action_scope.use() as interface:
1138
+ interface.side_effects.selected_coins.append(coin)
1139
+ coins = await wallet.select_coins(
1140
+ uint64(total_amount + request.fee - coin.amount),
1141
+ action_scope,
1142
+ )
1143
+ coins.add(coin)
1144
+ else:
1145
+ coins = {coin}
1146
+ await wallet.generate_signed_transaction(
1147
+ outputs[0].amount,
1148
+ outputs[0].puzzle_hash,
1149
+ action_scope,
1150
+ request.fee,
1151
+ coins,
1152
+ outputs[1:] if len(outputs) > 1 else None,
1153
+ extra_conditions=extra_conditions,
1154
+ )
1155
+ else:
1156
+ assert isinstance(wallet, CATWallet)
1157
+ await wallet.generate_signed_transaction(
1158
+ [output.amount for output in outputs],
1159
+ [output.puzzle_hash for output in outputs],
1160
+ action_scope,
1161
+ request.fee,
1162
+ coins={coin},
1163
+ extra_conditions=extra_conditions,
1164
+ )
1165
+
1166
+ return SplitCoinsResponse([], []) # tx_endpoint will take care to fill this out
1167
+
1168
+ @tx_endpoint(push=False)
1169
+ @marshal
1170
+ async def combine_coins(
1171
+ self, request: CombineCoins, action_scope: WalletActionScope, extra_conditions: Tuple[Condition, ...] = tuple()
1172
+ ) -> CombineCoinsResponse:
1173
+
1174
+ # Some "number of coins" validation
1175
+ if request.number_of_coins > request.coin_num_limit:
1176
+ raise ValueError(
1177
+ f"{request.number_of_coins} coins is greater then the maximum limit of {request.coin_num_limit} coins."
1178
+ )
1179
+ if request.number_of_coins < 1:
1180
+ raise ValueError("You need at least two coins to combine")
1181
+ if len(request.target_coin_ids) > request.number_of_coins:
1182
+ raise ValueError("More coin IDs specified than desired number of coins to combine")
1183
+
1184
+ if request.wallet_id not in self.service.wallet_state_manager.wallets:
1185
+ raise ValueError(f"Wallet with ID {request.wallet_id} does not exist")
1186
+ wallet = self.service.wallet_state_manager.wallets[request.wallet_id]
1187
+ if not isinstance(wallet, (Wallet, CATWallet)):
1188
+ raise ValueError("Cannot combine coins from non-fungible wallet types")
1189
+
1190
+ coins: List[Coin] = []
1191
+
1192
+ # First get the coin IDs specified
1193
+ if request.target_coin_ids != []:
1194
+ coins.extend(
1195
+ cr.coin
1196
+ for cr in (
1197
+ await self.service.wallet_state_manager.coin_store.get_coin_records(
1198
+ wallet_id=request.wallet_id,
1199
+ coin_id_filter=HashFilter(request.target_coin_ids, mode=uint8(FilterMode.include.value)),
1200
+ )
1201
+ ).records
1202
+ )
1203
+
1204
+ async with action_scope.use() as interface:
1205
+ interface.side_effects.selected_coins.extend(coins)
1206
+
1207
+ # Next let's select enough coins to meet the target + fee if there is one
1208
+ fungible_amount_needed = uint64(0) if request.target_coin_amount is None else request.target_coin_amount
1209
+ if isinstance(wallet, Wallet):
1210
+ fungible_amount_needed = uint64(fungible_amount_needed + request.fee)
1211
+ amount_selected = sum(c.amount for c in coins)
1212
+ if amount_selected < fungible_amount_needed: # implicit fungible_amount_needed > 0 here
1213
+ coins.extend(
1214
+ await wallet.select_coins(
1215
+ amount=uint64(fungible_amount_needed - amount_selected), action_scope=action_scope
1216
+ )
1217
+ )
1218
+
1219
+ if len(coins) > request.number_of_coins:
1220
+ raise ValueError(
1221
+ f"Options specified cannot be met without selecting more coins than specified: {len(coins)}"
1222
+ )
1223
+
1224
+ # Now let's select enough coins to get to the target number to combine
1225
+ if len(coins) < request.number_of_coins:
1226
+ async with action_scope.use() as interface:
1227
+ coins.extend(
1228
+ cr.coin
1229
+ for cr in (
1230
+ await self.service.wallet_state_manager.coin_store.get_coin_records(
1231
+ wallet_id=request.wallet_id,
1232
+ limit=uint32(request.number_of_coins - len(coins)),
1233
+ order=CoinRecordOrder.amount,
1234
+ coin_id_filter=HashFilter(
1235
+ [c.name() for c in interface.side_effects.selected_coins],
1236
+ mode=uint8(FilterMode.exclude.value),
1237
+ ),
1238
+ reverse=request.largest_first,
1239
+ )
1240
+ ).records
1241
+ )
1242
+
1243
+ async with action_scope.use() as interface:
1244
+ interface.side_effects.selected_coins.extend(coins)
1245
+
1246
+ primary_output_amount = (
1247
+ uint64(sum(c.amount for c in coins)) if request.target_coin_amount is None else request.target_coin_amount
1248
+ )
1249
+ if isinstance(wallet, Wallet):
1250
+ primary_output_amount = uint64(primary_output_amount - request.fee)
1251
+ await wallet.generate_signed_transaction(
1252
+ primary_output_amount,
1253
+ await wallet.get_puzzle_hash(new=action_scope.config.tx_config.reuse_puzhash),
1254
+ action_scope,
1255
+ request.fee,
1256
+ set(coins),
1257
+ extra_conditions=extra_conditions,
1258
+ )
1259
+ else:
1260
+ assert isinstance(wallet, CATWallet)
1261
+ await wallet.generate_signed_transaction(
1262
+ [primary_output_amount],
1263
+ [await wallet.get_puzzle_hash(new=action_scope.config.tx_config.reuse_puzhash)],
1264
+ action_scope,
1265
+ request.fee,
1266
+ coins=set(coins),
1267
+ extra_conditions=extra_conditions,
1268
+ )
1269
+
1270
+ return CombineCoinsResponse([], []) # tx_endpoint will take care to fill this out
1271
+
1272
+ async def get_transactions(self, request: Dict[str, Any]) -> EndpointResult:
1273
+ wallet_id = int(request["wallet_id"])
1274
+
1275
+ start = request.get("start", 0)
1276
+ end = request.get("end", 50)
1277
+ sort_key = request.get("sort_key", None)
1278
+ reverse = request.get("reverse", False)
1279
+
1280
+ to_address = request.get("to_address", None)
1281
+ to_puzzle_hash: Optional[bytes32] = None
1282
+ if to_address is not None:
1283
+ to_puzzle_hash = decode_puzzle_hash(to_address)
1284
+ type_filter = None
1285
+ if "type_filter" in request:
1286
+ type_filter = TransactionTypeFilter.from_json_dict(request["type_filter"])
1287
+
1288
+ transactions = await self.service.wallet_state_manager.tx_store.get_transactions_between(
1289
+ wallet_id,
1290
+ start,
1291
+ end,
1292
+ sort_key=sort_key,
1293
+ reverse=reverse,
1294
+ to_puzzle_hash=to_puzzle_hash,
1295
+ type_filter=type_filter,
1296
+ confirmed=request.get("confirmed", None),
1297
+ )
1298
+ tx_list = []
1299
+ # Format for clawback transactions
1300
+ for tr in transactions:
1301
+ tx = (await self._convert_tx_puzzle_hash(tr)).to_json_dict_convenience(self.service.config)
1302
+ tx_list.append(tx)
1303
+ if tx["type"] not in CLAWBACK_INCOMING_TRANSACTION_TYPES:
1304
+ continue
1305
+ coin: Coin = tr.additions[0]
1306
+ record: Optional[WalletCoinRecord] = await self.service.wallet_state_manager.coin_store.get_coin_record(
1307
+ coin.name()
1308
+ )
1309
+ if record is None:
1310
+ log.error(f"Cannot find coin record for type {tx['type']} transaction {tx['name']}")
1311
+ continue
1312
+ try:
1313
+ tx["metadata"] = record.parsed_metadata().to_json_dict()
1314
+ except ValueError as e:
1315
+ log.error(f"Could not parse coin record metadata: {type(e).__name__} {e}")
1316
+ continue
1317
+ tx["metadata"]["coin_id"] = coin.name().hex()
1318
+ tx["metadata"]["spent"] = record.spent
1319
+ return {
1320
+ "transactions": tx_list,
1321
+ "wallet_id": wallet_id,
1322
+ }
1323
+
1324
+ async def get_transaction_count(self, request: Dict[str, Any]) -> EndpointResult:
1325
+ wallet_id = int(request["wallet_id"])
1326
+ type_filter = None
1327
+ if "type_filter" in request:
1328
+ type_filter = TransactionTypeFilter.from_json_dict(request["type_filter"])
1329
+ count = await self.service.wallet_state_manager.tx_store.get_transaction_count_for_wallet(
1330
+ wallet_id, confirmed=request.get("confirmed", None), type_filter=type_filter
1331
+ )
1332
+ return {
1333
+ "count": count,
1334
+ "wallet_id": wallet_id,
1335
+ }
1336
+
1337
+ async def get_next_address(self, request: Dict[str, Any]) -> EndpointResult:
1338
+ """
1339
+ Returns a new address
1340
+ """
1341
+ if request["new_address"] is True:
1342
+ create_new = True
1343
+ else:
1344
+ create_new = False
1345
+ wallet_id = uint32(int(request["wallet_id"]))
1346
+ wallet = self.service.wallet_state_manager.wallets[wallet_id]
1347
+ selected = self.service.config["selected_network"]
1348
+ prefix = self.service.config["network_overrides"]["config"][selected]["address_prefix"]
1349
+ if wallet.type() == WalletType.STANDARD_WALLET:
1350
+ assert isinstance(wallet, Wallet)
1351
+ raw_puzzle_hash = await wallet.get_puzzle_hash(create_new)
1352
+ address = encode_puzzle_hash(raw_puzzle_hash, prefix)
1353
+ elif wallet.type() in {WalletType.CAT, WalletType.CRCAT}:
1354
+ assert isinstance(wallet, CATWallet)
1355
+ raw_puzzle_hash = await wallet.standard_wallet.get_puzzle_hash(create_new)
1356
+ address = encode_puzzle_hash(raw_puzzle_hash, prefix)
1357
+ else:
1358
+ raise ValueError(f"Wallet type {wallet.type()} cannot create puzzle hashes")
1359
+
1360
+ return {
1361
+ "wallet_id": wallet_id,
1362
+ "address": address,
1363
+ }
1364
+
1365
+ @tx_endpoint(push=True)
1366
+ async def send_transaction(
1367
+ self,
1368
+ request: Dict[str, Any],
1369
+ action_scope: WalletActionScope,
1370
+ extra_conditions: Tuple[Condition, ...] = tuple(),
1371
+ ) -> EndpointResult:
1372
+ if await self.service.wallet_state_manager.synced() is False:
1373
+ raise ValueError("Wallet needs to be fully synced before sending transactions")
1374
+
1375
+ wallet_id = uint32(request["wallet_id"])
1376
+ wallet = self.service.wallet_state_manager.get_wallet(id=wallet_id, required_type=Wallet)
1377
+
1378
+ if not isinstance(request["amount"], int) or not isinstance(request["fee"], int):
1379
+ raise ValueError("An integer amount or fee is required (too many decimals)")
1380
+ amount: uint64 = uint64(request["amount"])
1381
+ address = request["address"]
1382
+ selected_network = self.service.config["selected_network"]
1383
+ expected_prefix = self.service.config["network_overrides"]["config"][selected_network]["address_prefix"]
1384
+ if address[0 : len(expected_prefix)] != expected_prefix:
1385
+ raise ValueError("Unexpected Address Prefix")
1386
+ puzzle_hash: bytes32 = decode_puzzle_hash(address)
1387
+
1388
+ memos: List[bytes] = []
1389
+ if "memos" in request:
1390
+ memos = [mem.encode("utf-8") for mem in request["memos"]]
1391
+
1392
+ fee: uint64 = uint64(request.get("fee", 0))
1393
+
1394
+ async with self.service.wallet_state_manager.lock:
1395
+ await wallet.generate_signed_transaction(
1396
+ amount,
1397
+ puzzle_hash,
1398
+ action_scope,
1399
+ fee,
1400
+ memos=memos,
1401
+ puzzle_decorator_override=request.get("puzzle_decorator", None),
1402
+ extra_conditions=extra_conditions,
1403
+ )
1404
+
1405
+ # Transaction may not have been included in the mempool yet. Use get_transaction to check.
1406
+ return {
1407
+ "transaction": None, # tx_endpoint wrapper will take care of this
1408
+ "transactions": None, # tx_endpoint wrapper will take care of this
1409
+ "transaction_id": None, # tx_endpoint wrapper will take care of this
1410
+ }
1411
+
1412
+ async def send_transaction_multi(self, request: Dict[str, Any]) -> EndpointResult:
1413
+ if await self.service.wallet_state_manager.synced() is False:
1414
+ raise ValueError("Wallet needs to be fully synced before sending transactions")
1415
+
1416
+ # This is required because this is a "@tx_endpoint" that calls other @tx_endpoints
1417
+ request.setdefault("push", True)
1418
+ request.setdefault("merge_spends", True)
1419
+
1420
+ wallet_id = uint32(request["wallet_id"])
1421
+ wallet = self.service.wallet_state_manager.wallets[wallet_id]
1422
+
1423
+ async with self.service.wallet_state_manager.lock:
1424
+ if wallet.type() in {WalletType.CAT, WalletType.CRCAT}:
1425
+ assert isinstance(wallet, CATWallet)
1426
+ response = await self.cat_spend(request, hold_lock=False)
1427
+ transaction = response["transaction"]
1428
+ transactions = response["transactions"]
1429
+ else:
1430
+ response = await self.create_signed_transaction(request, hold_lock=False)
1431
+ transaction = response["signed_tx"]
1432
+ transactions = response["transactions"]
1433
+
1434
+ # Transaction may not have been included in the mempool yet. Use get_transaction to check.
1435
+ return {
1436
+ "transaction": transaction,
1437
+ "transaction_id": TransactionRecord.from_json_dict_convenience(transaction).name,
1438
+ "transactions": transactions,
1439
+ "unsigned_transactions": response["unsigned_transactions"],
1440
+ }
1441
+
1442
+ @tx_endpoint(push=True, merge_spends=False)
1443
+ async def spend_clawback_coins(
1444
+ self,
1445
+ request: Dict[str, Any],
1446
+ action_scope: WalletActionScope,
1447
+ extra_conditions: Tuple[Condition, ...] = tuple(),
1448
+ ) -> EndpointResult:
1449
+ """Spend clawback coins that were sent (to claw them back) or received (to claim them).
1450
+
1451
+ :param coin_ids: list of coin ids to be spent
1452
+ :param batch_size: number of coins to spend per bundle
1453
+ :param fee: transaction fee in mojos
1454
+ :return:
1455
+ """
1456
+ if "coin_ids" not in request:
1457
+ raise ValueError("Coin IDs are required.")
1458
+ coin_ids: List[bytes32] = [bytes32.from_hexstr(coin) for coin in request["coin_ids"]]
1459
+ tx_fee: uint64 = uint64(request.get("fee", 0))
1460
+ # Get inner puzzle
1461
+ coin_records = await self.service.wallet_state_manager.coin_store.get_coin_records(
1462
+ coin_id_filter=HashFilter.include(coin_ids),
1463
+ coin_type=CoinType.CLAWBACK,
1464
+ wallet_type=WalletType.STANDARD_WALLET,
1465
+ spent_range=UInt32Range(stop=uint32(0)),
1466
+ )
1467
+
1468
+ coins: Dict[Coin, ClawbackMetadata] = {}
1469
+ batch_size = request.get(
1470
+ "batch_size", self.service.wallet_state_manager.config.get("auto_claim", {}).get("batch_size", 50)
1471
+ )
1472
+ for coin_id, coin_record in coin_records.coin_id_to_record.items():
1473
+ try:
1474
+ metadata = coin_record.parsed_metadata()
1475
+ assert isinstance(metadata, ClawbackMetadata)
1476
+ coins[coin_record.coin] = metadata
1477
+ if len(coins) >= batch_size:
1478
+ await self.service.wallet_state_manager.spend_clawback_coins(
1479
+ coins,
1480
+ tx_fee,
1481
+ action_scope,
1482
+ request.get("force", False),
1483
+ extra_conditions=extra_conditions,
1484
+ )
1485
+ async with action_scope.use() as interface:
1486
+ # TODO: editing this is not ideal. Action scopes should know what coins have been spent.
1487
+ action_scope._config = dataclasses.replace(
1488
+ action_scope._config,
1489
+ tx_config=dataclasses.replace(
1490
+ action_scope._config.tx_config,
1491
+ excluded_coin_ids=[
1492
+ *action_scope._config.tx_config.excluded_coin_ids,
1493
+ *(c.name() for tx in interface.side_effects.transactions for c in tx.removals),
1494
+ ],
1495
+ ),
1496
+ )
1497
+ coins = {}
1498
+ except Exception as e:
1499
+ log.error(f"Failed to spend clawback coin {coin_id.hex()}: %s", e)
1500
+ if len(coins) > 0:
1501
+ await self.service.wallet_state_manager.spend_clawback_coins(
1502
+ coins,
1503
+ tx_fee,
1504
+ action_scope,
1505
+ request.get("force", False),
1506
+ extra_conditions=extra_conditions,
1507
+ )
1508
+
1509
+ return {
1510
+ "success": True,
1511
+ "transaction_ids": None, # tx_endpoint wrapper will take care of this
1512
+ "transactions": None, # tx_endpoint wrapper will take care of this
1513
+ }
1514
+
1515
+ async def delete_unconfirmed_transactions(self, request: Dict[str, Any]) -> EndpointResult:
1516
+ wallet_id = uint32(request["wallet_id"])
1517
+ if wallet_id not in self.service.wallet_state_manager.wallets:
1518
+ raise ValueError(f"Wallet id {wallet_id} does not exist")
1519
+ if await self.service.wallet_state_manager.synced() is False:
1520
+ raise ValueError("Wallet needs to be fully synced.")
1521
+
1522
+ async with self.service.wallet_state_manager.db_wrapper.writer():
1523
+ await self.service.wallet_state_manager.tx_store.delete_unconfirmed_transactions(wallet_id)
1524
+ wallet = self.service.wallet_state_manager.wallets[wallet_id]
1525
+ if wallet.type() == WalletType.POOLING_WALLET.value:
1526
+ assert isinstance(wallet, PoolWallet)
1527
+ wallet.target_state = None
1528
+ return {}
1529
+
1530
+ async def select_coins(
1531
+ self,
1532
+ request: Dict[str, Any],
1533
+ ) -> EndpointResult:
1534
+ assert self.service.logged_in_fingerprint is not None
1535
+ tx_config_loader: TXConfigLoader = TXConfigLoader.from_json_dict(request)
1536
+
1537
+ # Some backwards compat fill-ins
1538
+ if tx_config_loader.excluded_coin_ids is None:
1539
+ excluded_coins: Optional[List[Dict[str, Any]]] = request.get("excluded_coins", request.get("exclude_coins"))
1540
+ if excluded_coins is not None:
1541
+ tx_config_loader = tx_config_loader.override(
1542
+ excluded_coin_ids=[Coin.from_json_dict(c).name() for c in excluded_coins],
1543
+ )
1544
+
1545
+ tx_config: TXConfig = tx_config_loader.autofill(
1546
+ constants=self.service.wallet_state_manager.constants,
1547
+ )
1548
+
1549
+ if await self.service.wallet_state_manager.synced() is False:
1550
+ raise ValueError("Wallet needs to be fully synced before selecting coins")
1551
+
1552
+ amount = uint64(request["amount"])
1553
+ wallet_id = uint32(request["wallet_id"])
1554
+
1555
+ wallet = self.service.wallet_state_manager.wallets[wallet_id]
1556
+ async with self.service.wallet_state_manager.new_action_scope(tx_config, push=False) as action_scope:
1557
+ selected_coins = await wallet.select_coins(amount, action_scope)
1558
+
1559
+ return {"coins": [coin.to_json_dict() for coin in selected_coins]}
1560
+
1561
+ async def get_spendable_coins(self, request: Dict[str, Any]) -> EndpointResult:
1562
+ if await self.service.wallet_state_manager.synced() is False:
1563
+ raise ValueError("Wallet needs to be fully synced before getting all coins")
1564
+
1565
+ wallet_id = uint32(request["wallet_id"])
1566
+ min_coin_amount = uint64(request.get("min_coin_amount", 0))
1567
+ max_coin_amount: uint64 = uint64(request.get("max_coin_amount", 0))
1568
+ if max_coin_amount == 0:
1569
+ max_coin_amount = uint64(self.service.wallet_state_manager.constants.MAX_COIN_AMOUNT)
1570
+ excluded_coin_amounts: Optional[List[uint64]] = request.get("excluded_coin_amounts")
1571
+ if excluded_coin_amounts is not None:
1572
+ excluded_coin_amounts = [uint64(a) for a in excluded_coin_amounts]
1573
+ else:
1574
+ excluded_coin_amounts = []
1575
+ excluded_coins_input: Optional[Dict[str, Dict[str, Any]]] = request.get("excluded_coins")
1576
+ if excluded_coins_input is not None:
1577
+ excluded_coins = [Coin.from_json_dict(json_coin) for json_coin in excluded_coins_input]
1578
+ else:
1579
+ excluded_coins = []
1580
+ excluded_coin_ids_input: Optional[List[str]] = request.get("excluded_coin_ids")
1581
+ if excluded_coin_ids_input is not None:
1582
+ excluded_coin_ids = [bytes32.from_hexstr(hex_id) for hex_id in excluded_coin_ids_input]
1583
+ else:
1584
+ excluded_coin_ids = []
1585
+ state_mgr = self.service.wallet_state_manager
1586
+ wallet = state_mgr.wallets[wallet_id]
1587
+ async with state_mgr.lock:
1588
+ all_coin_records = await state_mgr.coin_store.get_unspent_coins_for_wallet(wallet_id)
1589
+ if wallet.type() in {WalletType.CAT, WalletType.CRCAT}:
1590
+ assert isinstance(wallet, CATWallet)
1591
+ spendable_coins: List[WalletCoinRecord] = await wallet.get_cat_spendable_coins(all_coin_records)
1592
+ else:
1593
+ spendable_coins = list(await state_mgr.get_spendable_coins_for_wallet(wallet_id, all_coin_records))
1594
+
1595
+ # Now we get the unconfirmed transactions and manually derive the additions and removals.
1596
+ unconfirmed_transactions: List[TransactionRecord] = await state_mgr.tx_store.get_unconfirmed_for_wallet(
1597
+ wallet_id
1598
+ )
1599
+ unconfirmed_removal_ids: Dict[bytes32, uint64] = {
1600
+ coin.name(): transaction.created_at_time
1601
+ for transaction in unconfirmed_transactions
1602
+ for coin in transaction.removals
1603
+ }
1604
+ unconfirmed_additions: List[Coin] = [
1605
+ coin
1606
+ for transaction in unconfirmed_transactions
1607
+ for coin in transaction.additions
1608
+ if await state_mgr.does_coin_belong_to_wallet(coin, wallet_id)
1609
+ ]
1610
+ valid_spendable_cr: List[CoinRecord] = []
1611
+ unconfirmed_removals: List[CoinRecord] = []
1612
+ for coin_record in all_coin_records:
1613
+ if coin_record.name() in unconfirmed_removal_ids:
1614
+ unconfirmed_removals.append(coin_record.to_coin_record(unconfirmed_removal_ids[coin_record.name()]))
1615
+ for coin_record in spendable_coins: # remove all the unconfirmed coins, exclude coins and dust.
1616
+ if coin_record.name() in unconfirmed_removal_ids:
1617
+ continue
1618
+ if coin_record.coin in excluded_coins:
1619
+ continue
1620
+ if coin_record.name() in excluded_coin_ids:
1621
+ continue
1622
+ if coin_record.coin.amount < min_coin_amount or coin_record.coin.amount > max_coin_amount:
1623
+ continue
1624
+ if coin_record.coin.amount in excluded_coin_amounts:
1625
+ continue
1626
+ c_r = await state_mgr.get_coin_record_by_wallet_record(coin_record)
1627
+ assert c_r is not None and c_r.coin == coin_record.coin # this should never happen
1628
+ valid_spendable_cr.append(c_r)
1629
+
1630
+ return {
1631
+ "confirmed_records": [cr.to_json_dict() for cr in valid_spendable_cr],
1632
+ "unconfirmed_removals": [cr.to_json_dict() for cr in unconfirmed_removals],
1633
+ "unconfirmed_additions": [coin.to_json_dict() for coin in unconfirmed_additions],
1634
+ }
1635
+
1636
+ async def get_coin_records_by_names(self, request: Dict[str, Any]) -> EndpointResult:
1637
+ if await self.service.wallet_state_manager.synced() is False:
1638
+ raise ValueError("Wallet needs to be fully synced before finding coin information")
1639
+
1640
+ if "names" not in request:
1641
+ raise ValueError("Names not in request")
1642
+ coin_ids = [bytes32.from_hexstr(name) for name in request["names"]]
1643
+ kwargs: Dict[str, Any] = {
1644
+ "coin_id_filter": HashFilter.include(coin_ids),
1645
+ }
1646
+
1647
+ confirmed_range = UInt32Range()
1648
+ if "start_height" in request:
1649
+ confirmed_range = dataclasses.replace(confirmed_range, start=uint32(request["start_height"]))
1650
+ if "end_height" in request:
1651
+ confirmed_range = dataclasses.replace(confirmed_range, stop=uint32(request["end_height"]))
1652
+ if confirmed_range != UInt32Range():
1653
+ kwargs["confirmed_range"] = confirmed_range
1654
+
1655
+ if "include_spent_coins" in request and not str2bool(request["include_spent_coins"]):
1656
+ kwargs["spent_range"] = unspent_range
1657
+
1658
+ async with self.service.wallet_state_manager.lock:
1659
+ coin_records: List[CoinRecord] = await self.service.wallet_state_manager.get_coin_records_by_coin_ids(
1660
+ **kwargs
1661
+ )
1662
+ missed_coins: List[str] = [
1663
+ "0x" + c_id.hex() for c_id in coin_ids if c_id not in [cr.name for cr in coin_records]
1664
+ ]
1665
+ if missed_coins:
1666
+ raise ValueError(f"Coin ID's: {missed_coins} not found.")
1667
+
1668
+ return {"coin_records": [cr.to_json_dict() for cr in coin_records]}
1669
+
1670
+ async def get_current_derivation_index(self, request: Dict[str, Any]) -> Dict[str, Any]:
1671
+ assert self.service.wallet_state_manager is not None
1672
+
1673
+ index: Optional[uint32] = await self.service.wallet_state_manager.puzzle_store.get_last_derivation_path()
1674
+
1675
+ return {"success": True, "index": index}
1676
+
1677
+ async def extend_derivation_index(self, request: Dict[str, Any]) -> Dict[str, Any]:
1678
+ assert self.service.wallet_state_manager is not None
1679
+
1680
+ # Require a new max derivation index
1681
+ if "index" not in request:
1682
+ raise ValueError("Derivation index is required")
1683
+
1684
+ # Require that the wallet is fully synced
1685
+ synced = await self.service.wallet_state_manager.synced()
1686
+ if synced is False:
1687
+ raise ValueError("Wallet needs to be fully synced before extending derivation index")
1688
+
1689
+ index = uint32(request["index"])
1690
+ current: Optional[uint32] = await self.service.wallet_state_manager.puzzle_store.get_last_derivation_path()
1691
+
1692
+ # Additional sanity check that the wallet is synced
1693
+ if current is None:
1694
+ raise ValueError("No current derivation record found, unable to extend index")
1695
+
1696
+ # Require that the new index is greater than the current index
1697
+ if index <= current:
1698
+ raise ValueError(f"New derivation index must be greater than current index: {current}")
1699
+
1700
+ if index - current > MAX_DERIVATION_INDEX_DELTA:
1701
+ raise ValueError(
1702
+ "Too many derivations requested. "
1703
+ f"Use a derivation index less than {current + MAX_DERIVATION_INDEX_DELTA + 1}"
1704
+ )
1705
+
1706
+ # Since we've bumping the derivation index without having found any new puzzles, we want
1707
+ # to preserve the current last used index, so we call create_more_puzzle_hashes with
1708
+ # mark_existing_as_used=False
1709
+ await self.service.wallet_state_manager.create_more_puzzle_hashes(
1710
+ from_zero=False, mark_existing_as_used=False, up_to_index=index, num_additional_phs=0
1711
+ )
1712
+
1713
+ updated: Optional[uint32] = await self.service.wallet_state_manager.puzzle_store.get_last_derivation_path()
1714
+ updated_index = updated if updated is not None else None
1715
+
1716
+ return {"success": True, "index": updated_index}
1717
+
1718
+ @marshal
1719
+ async def get_notifications(self, request: GetNotifications) -> GetNotificationsResponse:
1720
+ if request.ids is None:
1721
+ notifications: List[Notification] = (
1722
+ await self.service.wallet_state_manager.notification_manager.notification_store.get_all_notifications(
1723
+ pagination=(request.start, request.end)
1724
+ )
1725
+ )
1726
+ else:
1727
+ notifications = (
1728
+ await self.service.wallet_state_manager.notification_manager.notification_store.get_notifications(
1729
+ request.ids
1730
+ )
1731
+ )
1732
+
1733
+ return GetNotificationsResponse(notifications)
1734
+
1735
+ async def delete_notifications(self, request: Dict[str, Any]) -> EndpointResult:
1736
+ ids: Optional[List[str]] = request.get("ids", None)
1737
+ if ids is None:
1738
+ await self.service.wallet_state_manager.notification_manager.notification_store.delete_all_notifications()
1739
+ else:
1740
+ await self.service.wallet_state_manager.notification_manager.notification_store.delete_notifications(
1741
+ [bytes32.from_hexstr(id) for id in ids]
1742
+ )
1743
+
1744
+ return {}
1745
+
1746
+ @tx_endpoint(push=True)
1747
+ async def send_notification(
1748
+ self,
1749
+ request: Dict[str, Any],
1750
+ action_scope: WalletActionScope,
1751
+ extra_conditions: Tuple[Condition, ...] = tuple(),
1752
+ ) -> EndpointResult:
1753
+ await self.service.wallet_state_manager.notification_manager.send_new_notification(
1754
+ bytes32.from_hexstr(request["target"]),
1755
+ bytes.fromhex(request["message"]),
1756
+ uint64(request["amount"]),
1757
+ action_scope,
1758
+ request.get("fee", uint64(0)),
1759
+ extra_conditions=extra_conditions,
1760
+ )
1761
+
1762
+ return {"tx": None, "transactions": None} # tx_endpoint wrapper will take care of this
1763
+
1764
+ async def verify_signature(self, request: Dict[str, Any]) -> EndpointResult:
1765
+ """
1766
+ Given a public key, message and signature, verify if it is valid.
1767
+ :param request:
1768
+ :return:
1769
+ """
1770
+ input_message: str = request["message"]
1771
+ signing_mode_str: Optional[str] = request.get("signing_mode")
1772
+ # Default to BLS_MESSAGE_AUGMENTATION_HEX_INPUT as this RPC was originally designed to verify
1773
+ # signatures made by `chia keys sign`, which uses BLS_MESSAGE_AUGMENTATION_HEX_INPUT
1774
+ if signing_mode_str is None:
1775
+ signing_mode = SigningMode.BLS_MESSAGE_AUGMENTATION_HEX_INPUT
1776
+ else:
1777
+ try:
1778
+ signing_mode = SigningMode(signing_mode_str)
1779
+ except ValueError:
1780
+ raise ValueError(f"Invalid signing mode: {signing_mode_str!r}")
1781
+
1782
+ if signing_mode == SigningMode.CHIP_0002 or signing_mode == SigningMode.CHIP_0002_P2_DELEGATED_CONDITIONS:
1783
+ # CHIP-0002 message signatures are made over the tree hash of:
1784
+ # ("Chia Signed Message", message)
1785
+ message_to_verify: bytes = Program.to((CHIP_0002_SIGN_MESSAGE_PREFIX, input_message)).get_tree_hash()
1786
+ elif signing_mode == SigningMode.BLS_MESSAGE_AUGMENTATION_HEX_INPUT:
1787
+ # Message is expected to be a hex string
1788
+ message_to_verify = hexstr_to_bytes(input_message)
1789
+ elif signing_mode == SigningMode.BLS_MESSAGE_AUGMENTATION_UTF8_INPUT:
1790
+ # Message is expected to be a UTF-8 string
1791
+ message_to_verify = bytes(input_message, "utf-8")
1792
+ else:
1793
+ raise ValueError(f"Unsupported signing mode: {signing_mode_str!r}")
1794
+
1795
+ # Verify using the BLS message augmentation scheme
1796
+ is_valid = AugSchemeMPL.verify(
1797
+ G1Element.from_bytes(hexstr_to_bytes(request["pubkey"])),
1798
+ message_to_verify,
1799
+ G2Element.from_bytes(hexstr_to_bytes(request["signature"])),
1800
+ )
1801
+ address = request.get("address")
1802
+ if address is not None:
1803
+ # For signatures made by the sign_message_by_address/sign_message_by_id
1804
+ # endpoints, the "address" field should contain the p2_address of the NFT/DID
1805
+ # that was used to sign the message.
1806
+ puzzle_hash: bytes32 = decode_puzzle_hash(address)
1807
+ expected_puzzle_hash: Optional[bytes32] = None
1808
+ if signing_mode == SigningMode.CHIP_0002_P2_DELEGATED_CONDITIONS:
1809
+ puzzle = p2_delegated_conditions.puzzle_for_pk(Program.to(hexstr_to_bytes(request["pubkey"])))
1810
+ expected_puzzle_hash = bytes32(puzzle.get_tree_hash())
1811
+ else:
1812
+ expected_puzzle_hash = puzzle_hash_for_synthetic_public_key(
1813
+ G1Element.from_bytes(hexstr_to_bytes(request["pubkey"]))
1814
+ )
1815
+ if puzzle_hash != expected_puzzle_hash:
1816
+ return {"isValid": False, "error": "Public key doesn't match the address"}
1817
+ if is_valid:
1818
+ return {"isValid": is_valid}
1819
+ else:
1820
+ return {"isValid": False, "error": "Signature is invalid."}
1821
+
1822
+ async def sign_message_by_address(self, request: Dict[str, Any]) -> EndpointResult:
1823
+ """
1824
+ Given a derived P2 address, sign the message by its private key.
1825
+ :param request:
1826
+ :return:
1827
+ """
1828
+ puzzle_hash: bytes32 = decode_puzzle_hash(request["address"])
1829
+ is_hex: bool = request.get("is_hex", False)
1830
+ if isinstance(is_hex, str):
1831
+ is_hex = True if is_hex.lower() == "true" else False
1832
+ safe_mode: bool = request.get("safe_mode", True)
1833
+ if isinstance(safe_mode, str):
1834
+ safe_mode = True if safe_mode.lower() == "true" else False
1835
+ mode: SigningMode = SigningMode.CHIP_0002
1836
+ if is_hex and safe_mode:
1837
+ mode = SigningMode.CHIP_0002_HEX_INPUT
1838
+ elif not is_hex and not safe_mode:
1839
+ mode = SigningMode.BLS_MESSAGE_AUGMENTATION_UTF8_INPUT
1840
+ elif is_hex and not safe_mode:
1841
+ mode = SigningMode.BLS_MESSAGE_AUGMENTATION_HEX_INPUT
1842
+ pubkey, signature = await self.service.wallet_state_manager.main_wallet.sign_message(
1843
+ request["message"], puzzle_hash, mode
1844
+ )
1845
+ return {
1846
+ "success": True,
1847
+ "pubkey": str(pubkey),
1848
+ "signature": str(signature),
1849
+ "signing_mode": mode.value,
1850
+ }
1851
+
1852
+ async def sign_message_by_id(self, request: Dict[str, Any]) -> EndpointResult:
1853
+ """
1854
+ Given a NFT/DID ID, sign the message by the P2 private key.
1855
+ :param request:
1856
+ :return:
1857
+ """
1858
+ entity_id: bytes32 = decode_puzzle_hash(request["id"])
1859
+ selected_wallet: Optional[WalletProtocol[Any]] = None
1860
+ is_hex: bool = request.get("is_hex", False)
1861
+ if isinstance(is_hex, str):
1862
+ is_hex = True if is_hex.lower() == "true" else False
1863
+ safe_mode: bool = request.get("safe_mode", True)
1864
+ if isinstance(safe_mode, str):
1865
+ safe_mode = True if safe_mode.lower() == "true" else False
1866
+ mode: SigningMode = SigningMode.CHIP_0002
1867
+ if is_hex and safe_mode:
1868
+ mode = SigningMode.CHIP_0002_HEX_INPUT
1869
+ elif not is_hex and not safe_mode:
1870
+ mode = SigningMode.BLS_MESSAGE_AUGMENTATION_UTF8_INPUT
1871
+ elif is_hex and not safe_mode:
1872
+ mode = SigningMode.BLS_MESSAGE_AUGMENTATION_HEX_INPUT
1873
+ if is_valid_address(request["id"], {AddressType.DID}, self.service.config):
1874
+ for wallet in self.service.wallet_state_manager.wallets.values():
1875
+ if wallet.type() == WalletType.DECENTRALIZED_ID.value:
1876
+ assert isinstance(wallet, DIDWallet)
1877
+ assert wallet.did_info.origin_coin is not None
1878
+ if wallet.did_info.origin_coin.name() == entity_id:
1879
+ selected_wallet = wallet
1880
+ break
1881
+ if selected_wallet is None:
1882
+ return {"success": False, "error": f"DID for {entity_id.hex()} doesn't exist."}
1883
+ assert isinstance(selected_wallet, DIDWallet)
1884
+ pubkey, signature = await selected_wallet.sign_message(request["message"], mode)
1885
+ latest_coin_id = (await selected_wallet.get_coin()).name()
1886
+ elif is_valid_address(request["id"], {AddressType.NFT}, self.service.config):
1887
+ target_nft: Optional[NFTCoinInfo] = None
1888
+ for wallet in self.service.wallet_state_manager.wallets.values():
1889
+ if wallet.type() == WalletType.NFT.value:
1890
+ assert isinstance(wallet, NFTWallet)
1891
+ nft: Optional[NFTCoinInfo] = await wallet.get_nft(entity_id)
1892
+ if nft is not None:
1893
+ selected_wallet = wallet
1894
+ target_nft = nft
1895
+ break
1896
+ if selected_wallet is None or target_nft is None:
1897
+ return {"success": False, "error": f"NFT for {entity_id.hex()} doesn't exist."}
1898
+
1899
+ assert isinstance(selected_wallet, NFTWallet)
1900
+ pubkey, signature = await selected_wallet.sign_message(request["message"], target_nft, mode)
1901
+ latest_coin_id = target_nft.coin.name()
1902
+ else:
1903
+ return {"success": False, "error": f'Unknown ID type, {request["id"]}'}
1904
+
1905
+ return {
1906
+ "success": True,
1907
+ "pubkey": str(pubkey),
1908
+ "signature": str(signature),
1909
+ "latest_coin_id": latest_coin_id.hex() if latest_coin_id is not None else None,
1910
+ "signing_mode": mode.value,
1911
+ }
1912
+
1913
+ ##########################################################################################
1914
+ # CATs and Trading
1915
+ ##########################################################################################
1916
+
1917
+ async def get_cat_list(self, request: Dict[str, Any]) -> EndpointResult:
1918
+ return {"cat_list": list(DEFAULT_CATS.values())}
1919
+
1920
+ async def cat_set_name(self, request: Dict[str, Any]) -> EndpointResult:
1921
+ wallet_id = uint32(request["wallet_id"])
1922
+ wallet = self.service.wallet_state_manager.get_wallet(id=wallet_id, required_type=CATWallet)
1923
+ await wallet.set_name(str(request["name"]))
1924
+ return {"wallet_id": wallet_id}
1925
+
1926
+ async def cat_get_name(self, request: Dict[str, Any]) -> EndpointResult:
1927
+ wallet_id = uint32(request["wallet_id"])
1928
+ wallet = self.service.wallet_state_manager.get_wallet(id=wallet_id, required_type=CATWallet)
1929
+ name: str = wallet.get_name()
1930
+ return {"wallet_id": wallet_id, "name": name}
1931
+
1932
+ async def get_stray_cats(self, request: Dict[str, Any]) -> EndpointResult:
1933
+ """
1934
+ Get a list of all unacknowledged CATs
1935
+ :param request: RPC request
1936
+ :return: A list of unacknowledged CATs
1937
+ """
1938
+ cats = await self.service.wallet_state_manager.interested_store.get_unacknowledged_tokens()
1939
+ return {"stray_cats": cats}
1940
+
1941
+ @tx_endpoint(push=True)
1942
+ async def cat_spend(
1943
+ self,
1944
+ request: Dict[str, Any],
1945
+ action_scope: WalletActionScope,
1946
+ extra_conditions: Tuple[Condition, ...] = tuple(),
1947
+ hold_lock: bool = True,
1948
+ ) -> EndpointResult:
1949
+ if await self.service.wallet_state_manager.synced() is False:
1950
+ raise ValueError("Wallet needs to be fully synced.")
1951
+ wallet_id = uint32(request["wallet_id"])
1952
+ wallet = self.service.wallet_state_manager.get_wallet(id=wallet_id, required_type=CATWallet)
1953
+
1954
+ amounts: List[uint64] = []
1955
+ puzzle_hashes: List[bytes32] = []
1956
+ memos: List[List[bytes]] = []
1957
+ additions: Optional[List[Dict[str, Any]]] = request.get("additions")
1958
+ if not isinstance(request["fee"], int) or (additions is None and not isinstance(request["amount"], int)):
1959
+ raise ValueError("An integer amount or fee is required (too many decimals)")
1960
+ if additions is not None:
1961
+ for addition in additions:
1962
+ receiver_ph = bytes32.from_hexstr(addition["puzzle_hash"])
1963
+ if len(receiver_ph) != 32:
1964
+ raise ValueError(f"Address must be 32 bytes. {receiver_ph.hex()}")
1965
+ amount = uint64(addition["amount"])
1966
+ if amount > self.service.constants.MAX_COIN_AMOUNT:
1967
+ raise ValueError(f"Coin amount cannot exceed {self.service.constants.MAX_COIN_AMOUNT}")
1968
+ amounts.append(amount)
1969
+ puzzle_hashes.append(receiver_ph)
1970
+ if "memos" in addition:
1971
+ memos.append([mem.encode("utf-8") for mem in addition["memos"]])
1972
+ else:
1973
+ amounts.append(uint64(request["amount"]))
1974
+ puzzle_hashes.append(decode_puzzle_hash(request["inner_address"]))
1975
+ if "memos" in request:
1976
+ memos.append([mem.encode("utf-8") for mem in request["memos"]])
1977
+ coins: Optional[Set[Coin]] = None
1978
+ if "coins" in request and len(request["coins"]) > 0:
1979
+ coins = {Coin.from_json_dict(coin_json) for coin_json in request["coins"]}
1980
+ fee: uint64 = uint64(request.get("fee", 0))
1981
+
1982
+ cat_discrepancy_params: Tuple[Optional[int], Optional[str], Optional[str]] = (
1983
+ request.get("extra_delta", None),
1984
+ request.get("tail_reveal", None),
1985
+ request.get("tail_solution", None),
1986
+ )
1987
+ cat_discrepancy: Optional[Tuple[int, Program, Program]] = None
1988
+ if cat_discrepancy_params != (None, None, None):
1989
+ if None in cat_discrepancy_params:
1990
+ raise ValueError("Specifying extra_delta, tail_reveal, or tail_solution requires specifying the others")
1991
+ else:
1992
+ assert cat_discrepancy_params[0] is not None
1993
+ assert cat_discrepancy_params[1] is not None
1994
+ assert cat_discrepancy_params[2] is not None
1995
+ cat_discrepancy = (
1996
+ cat_discrepancy_params[0], # mypy sanitization
1997
+ Program.fromhex(cat_discrepancy_params[1]),
1998
+ Program.fromhex(cat_discrepancy_params[2]),
1999
+ )
2000
+ if hold_lock:
2001
+ async with self.service.wallet_state_manager.lock:
2002
+ await wallet.generate_signed_transaction(
2003
+ amounts,
2004
+ puzzle_hashes,
2005
+ action_scope,
2006
+ fee,
2007
+ cat_discrepancy=cat_discrepancy,
2008
+ coins=coins,
2009
+ memos=memos if memos else None,
2010
+ extra_conditions=extra_conditions,
2011
+ )
2012
+ else:
2013
+ await wallet.generate_signed_transaction(
2014
+ amounts,
2015
+ puzzle_hashes,
2016
+ action_scope,
2017
+ fee,
2018
+ cat_discrepancy=cat_discrepancy,
2019
+ coins=coins,
2020
+ memos=memos if memos else None,
2021
+ extra_conditions=extra_conditions,
2022
+ )
2023
+
2024
+ return {
2025
+ "transaction": None, # tx_endpoint wrapper will take care of this
2026
+ "transactions": None, # tx_endpoint wrapper will take care of this
2027
+ "transaction_id": None, # tx_endpoint wrapper will take care of this
2028
+ }
2029
+
2030
+ async def cat_get_asset_id(self, request: Dict[str, Any]) -> EndpointResult:
2031
+ wallet_id = uint32(request["wallet_id"])
2032
+ wallet = self.service.wallet_state_manager.get_wallet(id=wallet_id, required_type=CATWallet)
2033
+ asset_id: str = wallet.get_asset_id()
2034
+ return {"asset_id": asset_id, "wallet_id": wallet_id}
2035
+
2036
+ async def cat_asset_id_to_name(self, request: Dict[str, Any]) -> EndpointResult:
2037
+ wallet = await self.service.wallet_state_manager.get_wallet_for_asset_id(request["asset_id"])
2038
+ if wallet is None:
2039
+ if request["asset_id"] in DEFAULT_CATS:
2040
+ return {"wallet_id": None, "name": DEFAULT_CATS[request["asset_id"]]["name"]}
2041
+ else:
2042
+ raise ValueError("The asset ID specified does not belong to a wallet")
2043
+ else:
2044
+ return {"wallet_id": wallet.id(), "name": (wallet.get_name())}
2045
+
2046
+ @tx_endpoint(push=False)
2047
+ async def create_offer_for_ids(
2048
+ self,
2049
+ request: Dict[str, Any],
2050
+ action_scope: WalletActionScope,
2051
+ extra_conditions: Tuple[Condition, ...] = tuple(),
2052
+ ) -> EndpointResult:
2053
+ if action_scope.config.push:
2054
+ raise ValueError("Cannot push an incomplete spend") # pragma: no cover
2055
+
2056
+ offer: Dict[str, int] = request["offer"]
2057
+ fee: uint64 = uint64(request.get("fee", 0))
2058
+ validate_only: bool = request.get("validate_only", False)
2059
+ driver_dict_str: Optional[Dict[str, Any]] = request.get("driver_dict", None)
2060
+ marshalled_solver = request.get("solver")
2061
+ solver: Optional[Solver]
2062
+ if marshalled_solver is None:
2063
+ solver = None
2064
+ else:
2065
+ solver = Solver(info=marshalled_solver)
2066
+
2067
+ # This driver_dict construction is to maintain backward compatibility where everything is assumed to be a CAT
2068
+ driver_dict: Dict[bytes32, PuzzleInfo] = {}
2069
+ if driver_dict_str is None:
2070
+ for key, amount in offer.items():
2071
+ if amount > 0:
2072
+ try:
2073
+ driver_dict[bytes32.from_hexstr(key)] = PuzzleInfo(
2074
+ {"type": AssetType.CAT.value, "tail": "0x" + key}
2075
+ )
2076
+ except ValueError:
2077
+ pass
2078
+ else:
2079
+ for key, value in driver_dict_str.items():
2080
+ driver_dict[bytes32.from_hexstr(key)] = PuzzleInfo(value)
2081
+
2082
+ modified_offer: Dict[Union[int, bytes32], int] = {}
2083
+ for key in offer:
2084
+ try:
2085
+ modified_offer[bytes32.from_hexstr(key)] = offer[key]
2086
+ except ValueError:
2087
+ modified_offer[int(key)] = offer[key]
2088
+
2089
+ async with self.service.wallet_state_manager.lock:
2090
+ result = await self.service.wallet_state_manager.trade_manager.create_offer_for_ids(
2091
+ modified_offer,
2092
+ action_scope,
2093
+ driver_dict,
2094
+ solver=solver,
2095
+ fee=fee,
2096
+ validate_only=validate_only,
2097
+ extra_conditions=extra_conditions,
2098
+ )
2099
+ if result[0]:
2100
+ success, trade_record, error = result
2101
+ return {
2102
+ "offer": Offer.from_bytes(trade_record.offer).to_bech32(),
2103
+ "trade_record": trade_record.to_json_dict_convenience(),
2104
+ "transactions": None, # tx_endpoint wrapper will take care of this
2105
+ }
2106
+ raise ValueError(result[2])
2107
+
2108
+ async def get_offer_summary(self, request: Dict[str, Any]) -> EndpointResult:
2109
+ offer_hex: str = request["offer"]
2110
+
2111
+ ###
2112
+ # This is temporary code, delete it when we no longer care about incorrectly parsing old offers
2113
+ # There's also temp code in test_wallet_rpc.py
2114
+ from chia.util.bech32m import bech32_decode, convertbits
2115
+ from chia.wallet.util.puzzle_compression import OFFER_MOD_OLD, decompress_object_with_puzzles
2116
+
2117
+ hrpgot, data = bech32_decode(offer_hex, max_length=len(offer_hex))
2118
+ if data is None:
2119
+ raise ValueError("Invalid Offer")
2120
+ decoded = convertbits(list(data), 5, 8, False)
2121
+ decoded_bytes = bytes(decoded)
2122
+ try:
2123
+ decompressed_bytes = decompress_object_with_puzzles(decoded_bytes)
2124
+ except zlib.error:
2125
+ decompressed_bytes = decoded_bytes
2126
+ if bytes(OFFER_MOD_OLD) in decompressed_bytes:
2127
+ raise ValueError("Old offer format is no longer supported")
2128
+ ###
2129
+
2130
+ offer = Offer.from_bech32(offer_hex)
2131
+ offered, requested, infos, valid_times = offer.summary()
2132
+
2133
+ if request.get("advanced", False):
2134
+ response = {
2135
+ "summary": {
2136
+ "offered": offered,
2137
+ "requested": requested,
2138
+ "fees": offer.fees(),
2139
+ "infos": infos,
2140
+ "additions": [c.name().hex() for c in offer.additions()],
2141
+ "removals": [c.name().hex() for c in offer.removals()],
2142
+ "valid_times": {
2143
+ k: v
2144
+ for k, v in valid_times.to_json_dict().items()
2145
+ if k
2146
+ not in (
2147
+ "max_secs_after_created",
2148
+ "min_secs_since_created",
2149
+ "max_blocks_after_created",
2150
+ "min_blocks_since_created",
2151
+ )
2152
+ },
2153
+ },
2154
+ "id": offer.name(),
2155
+ }
2156
+ else:
2157
+ response = {
2158
+ "summary": await self.service.wallet_state_manager.trade_manager.get_offer_summary(offer),
2159
+ "id": offer.name(),
2160
+ }
2161
+
2162
+ # This is a bit of a hack in favor of returning some more manageable information about CR-CATs
2163
+ # A more general solution surely exists, but I'm not sure what it is right now
2164
+ return {
2165
+ **response,
2166
+ "summary": {
2167
+ **response["summary"], # type: ignore[dict-item]
2168
+ "infos": {
2169
+ key: (
2170
+ {
2171
+ **info,
2172
+ "also": {
2173
+ **info["also"],
2174
+ "flags": ProofsChecker.from_program(
2175
+ uncurry_puzzle(Program(assemble(info["also"]["proofs_checker"])))
2176
+ ).flags,
2177
+ },
2178
+ }
2179
+ if "also" in info and "proofs_checker" in info["also"]
2180
+ else info
2181
+ )
2182
+ for key, info in response["summary"]["infos"].items() # type: ignore[index]
2183
+ },
2184
+ },
2185
+ }
2186
+
2187
+ async def check_offer_validity(self, request: Dict[str, Any]) -> EndpointResult:
2188
+ offer_hex: str = request["offer"]
2189
+
2190
+ ###
2191
+ # This is temporary code, delete it when we no longer care about incorrectly parsing old offers
2192
+ # There's also temp code in test_wallet_rpc.py
2193
+ from chia.util.bech32m import bech32_decode, convertbits
2194
+ from chia.wallet.util.puzzle_compression import OFFER_MOD_OLD, decompress_object_with_puzzles
2195
+
2196
+ hrpgot, data = bech32_decode(offer_hex, max_length=len(offer_hex))
2197
+ if data is None:
2198
+ raise ValueError("Invalid Offer") # pragma: no cover
2199
+ decoded = convertbits(list(data), 5, 8, False)
2200
+ decoded_bytes = bytes(decoded)
2201
+ try:
2202
+ decompressed_bytes = decompress_object_with_puzzles(decoded_bytes)
2203
+ except zlib.error:
2204
+ decompressed_bytes = decoded_bytes
2205
+ if bytes(OFFER_MOD_OLD) in decompressed_bytes:
2206
+ raise ValueError("Old offer format is no longer supported")
2207
+ ###
2208
+
2209
+ offer = Offer.from_bech32(offer_hex)
2210
+ peer = self.service.get_full_node_peer()
2211
+ return {
2212
+ "valid": (await self.service.wallet_state_manager.trade_manager.check_offer_validity(offer, peer)),
2213
+ "id": offer.name(),
2214
+ }
2215
+
2216
+ @tx_endpoint(push=True)
2217
+ async def take_offer(
2218
+ self,
2219
+ request: Dict[str, Any],
2220
+ action_scope: WalletActionScope,
2221
+ extra_conditions: Tuple[Condition, ...] = tuple(),
2222
+ ) -> EndpointResult:
2223
+ offer_hex: str = request["offer"]
2224
+
2225
+ ###
2226
+ # This is temporary code, delete it when we no longer care about incorrectly parsing old offers
2227
+ # There's also temp code in test_wallet_rpc.py
2228
+ from chia.util.bech32m import bech32_decode, convertbits
2229
+ from chia.wallet.util.puzzle_compression import OFFER_MOD_OLD, decompress_object_with_puzzles
2230
+
2231
+ hrpgot, data = bech32_decode(offer_hex, max_length=len(offer_hex))
2232
+ if data is None:
2233
+ raise ValueError("Invalid Offer") # pragma: no cover
2234
+ decoded = convertbits(list(data), 5, 8, False)
2235
+ decoded_bytes = bytes(decoded)
2236
+ try:
2237
+ decompressed_bytes = decompress_object_with_puzzles(decoded_bytes)
2238
+ except zlib.error:
2239
+ decompressed_bytes = decoded_bytes
2240
+ if bytes(OFFER_MOD_OLD) in decompressed_bytes:
2241
+ raise ValueError("Old offer format is no longer supported")
2242
+ ###
2243
+
2244
+ offer = Offer.from_bech32(offer_hex)
2245
+ fee: uint64 = uint64(request.get("fee", 0))
2246
+ maybe_marshalled_solver: Optional[Dict[str, Any]] = request.get("solver")
2247
+ solver: Optional[Solver]
2248
+ if maybe_marshalled_solver is None:
2249
+ solver = None
2250
+ else:
2251
+ solver = Solver(info=maybe_marshalled_solver)
2252
+
2253
+ peer = self.service.get_full_node_peer()
2254
+ trade_record = await self.service.wallet_state_manager.trade_manager.respond_to_offer(
2255
+ offer,
2256
+ peer,
2257
+ action_scope,
2258
+ fee=fee,
2259
+ solver=solver,
2260
+ extra_conditions=extra_conditions,
2261
+ )
2262
+
2263
+ async with action_scope.use() as interface:
2264
+ interface.side_effects.signing_responses.append(
2265
+ SigningResponse(bytes(offer._bundle.aggregated_signature), trade_record.trade_id)
2266
+ )
2267
+
2268
+ return {
2269
+ "trade_record": trade_record.to_json_dict_convenience(),
2270
+ "offer": Offer.from_bytes(trade_record.offer).to_bech32(),
2271
+ "transactions": None, # tx_endpoint wrapper will take care of this
2272
+ "signing_responses": None, # tx_endpoint wrapper will take care of this
2273
+ }
2274
+
2275
+ async def get_offer(self, request: Dict[str, Any]) -> EndpointResult:
2276
+ trade_mgr = self.service.wallet_state_manager.trade_manager
2277
+
2278
+ trade_id = bytes32.from_hexstr(request["trade_id"])
2279
+ file_contents: bool = request.get("file_contents", False)
2280
+ trade_record: Optional[TradeRecord] = await trade_mgr.get_trade_by_id(bytes32(trade_id))
2281
+ if trade_record is None:
2282
+ raise ValueError(f"No trade with trade id: {trade_id.hex()}")
2283
+
2284
+ offer_to_return: bytes = trade_record.offer if trade_record.taken_offer is None else trade_record.taken_offer
2285
+ offer_value: Optional[str] = Offer.from_bytes(offer_to_return).to_bech32() if file_contents else None
2286
+ return {"trade_record": trade_record.to_json_dict_convenience(), "offer": offer_value}
2287
+
2288
+ async def get_all_offers(self, request: Dict[str, Any]) -> EndpointResult:
2289
+ trade_mgr = self.service.wallet_state_manager.trade_manager
2290
+
2291
+ start: int = request.get("start", 0)
2292
+ end: int = request.get("end", 10)
2293
+ exclude_my_offers: bool = request.get("exclude_my_offers", False)
2294
+ exclude_taken_offers: bool = request.get("exclude_taken_offers", False)
2295
+ include_completed: bool = request.get("include_completed", False)
2296
+ sort_key: Optional[str] = request.get("sort_key", None)
2297
+ reverse: bool = request.get("reverse", False)
2298
+ file_contents: bool = request.get("file_contents", False)
2299
+
2300
+ all_trades = await trade_mgr.trade_store.get_trades_between(
2301
+ start,
2302
+ end,
2303
+ sort_key=sort_key,
2304
+ reverse=reverse,
2305
+ exclude_my_offers=exclude_my_offers,
2306
+ exclude_taken_offers=exclude_taken_offers,
2307
+ include_completed=include_completed,
2308
+ )
2309
+ result = []
2310
+ offer_values: Optional[List[str]] = [] if file_contents else None
2311
+ for trade in all_trades:
2312
+ result.append(trade.to_json_dict_convenience())
2313
+ if file_contents and offer_values is not None:
2314
+ offer_to_return: bytes = trade.offer if trade.taken_offer is None else trade.taken_offer
2315
+ offer_values.append(Offer.from_bytes(offer_to_return).to_bech32())
2316
+
2317
+ return {"trade_records": result, "offers": offer_values}
2318
+
2319
+ async def get_offers_count(self, request: Dict[str, Any]) -> EndpointResult:
2320
+ trade_mgr = self.service.wallet_state_manager.trade_manager
2321
+
2322
+ (total, my_offers_count, taken_offers_count) = await trade_mgr.trade_store.get_trades_count()
2323
+
2324
+ return {"total": total, "my_offers_count": my_offers_count, "taken_offers_count": taken_offers_count}
2325
+
2326
+ @tx_endpoint(push=True)
2327
+ async def cancel_offer(
2328
+ self,
2329
+ request: Dict[str, Any],
2330
+ action_scope: WalletActionScope,
2331
+ extra_conditions: Tuple[Condition, ...] = tuple(),
2332
+ ) -> EndpointResult:
2333
+ wsm = self.service.wallet_state_manager
2334
+ secure = request["secure"]
2335
+ trade_id = bytes32.from_hexstr(request["trade_id"])
2336
+ fee: uint64 = uint64(request.get("fee", 0))
2337
+ async with self.service.wallet_state_manager.lock:
2338
+ await wsm.trade_manager.cancel_pending_offers(
2339
+ [bytes32(trade_id)], action_scope, fee=fee, secure=secure, extra_conditions=extra_conditions
2340
+ )
2341
+
2342
+ return {"transactions": None} # tx_endpoint wrapper will take care of this
2343
+
2344
+ @tx_endpoint(push=True, merge_spends=False)
2345
+ async def cancel_offers(
2346
+ self,
2347
+ request: Dict[str, Any],
2348
+ action_scope: WalletActionScope,
2349
+ extra_conditions: Tuple[Condition, ...] = tuple(),
2350
+ ) -> EndpointResult:
2351
+ secure = request["secure"]
2352
+ batch_fee: uint64 = uint64(request.get("batch_fee", 0))
2353
+ batch_size = request.get("batch_size", 5)
2354
+ cancel_all = request.get("cancel_all", False)
2355
+ if cancel_all:
2356
+ asset_id = None
2357
+ else:
2358
+ asset_id = request.get("asset_id", "xch")
2359
+
2360
+ start: int = 0
2361
+ end: int = start + batch_size
2362
+ trade_mgr = self.service.wallet_state_manager.trade_manager
2363
+ log.info(f"Start cancelling offers for {'asset_id: ' + asset_id if asset_id is not None else 'all'} ...")
2364
+ # Traverse offers page by page
2365
+ key = None
2366
+ if asset_id is not None and asset_id != "xch":
2367
+ key = bytes32.from_hexstr(asset_id)
2368
+ while True:
2369
+ records: Dict[bytes32, TradeRecord] = {}
2370
+ trades = await trade_mgr.trade_store.get_trades_between(
2371
+ start,
2372
+ end,
2373
+ reverse=True,
2374
+ exclude_my_offers=False,
2375
+ exclude_taken_offers=True,
2376
+ include_completed=False,
2377
+ )
2378
+ for trade in trades:
2379
+ if cancel_all:
2380
+ records[trade.trade_id] = trade
2381
+ continue
2382
+ if trade.offer and trade.offer != b"":
2383
+ offer = Offer.from_bytes(trade.offer)
2384
+ if key in offer.arbitrage():
2385
+ records[trade.trade_id] = trade
2386
+ continue
2387
+
2388
+ if len(records) == 0:
2389
+ break
2390
+
2391
+ async with self.service.wallet_state_manager.lock:
2392
+ await trade_mgr.cancel_pending_offers(
2393
+ list(records.keys()),
2394
+ action_scope,
2395
+ batch_fee,
2396
+ secure,
2397
+ records,
2398
+ extra_conditions=extra_conditions,
2399
+ )
2400
+
2401
+ log.info(f"Cancelled offers {start} to {end} ...")
2402
+ # If fewer records were returned than requested, we're done
2403
+ if len(trades) < batch_size:
2404
+ break
2405
+ start = end
2406
+ end += batch_size
2407
+
2408
+ return {"transactions": None} # tx_endpoint wrapper will take care of this
2409
+
2410
+ ##########################################################################################
2411
+ # Distributed Identities
2412
+ ##########################################################################################
2413
+
2414
+ async def did_set_wallet_name(self, request: Dict[str, Any]) -> EndpointResult:
2415
+ wallet_id = uint32(request["wallet_id"])
2416
+ wallet = self.service.wallet_state_manager.get_wallet(id=wallet_id, required_type=DIDWallet)
2417
+ await wallet.set_name(str(request["name"]))
2418
+ return {"success": True, "wallet_id": wallet_id}
2419
+
2420
+ async def did_get_wallet_name(self, request: Dict[str, Any]) -> EndpointResult:
2421
+ wallet_id = uint32(request["wallet_id"])
2422
+ wallet = self.service.wallet_state_manager.get_wallet(id=wallet_id, required_type=DIDWallet)
2423
+ name: str = wallet.get_name() # type: ignore[no-untyped-call] # Missing hint in `did_wallet.py`
2424
+ return {"success": True, "wallet_id": wallet_id, "name": name}
2425
+
2426
+ @tx_endpoint(push=True)
2427
+ async def did_update_recovery_ids(
2428
+ self,
2429
+ request: Dict[str, Any],
2430
+ action_scope: WalletActionScope,
2431
+ extra_conditions: Tuple[Condition, ...] = tuple(),
2432
+ ) -> EndpointResult:
2433
+ wallet_id = uint32(request["wallet_id"])
2434
+ wallet = self.service.wallet_state_manager.get_wallet(id=wallet_id, required_type=DIDWallet)
2435
+ recovery_list = []
2436
+ for _ in request["new_list"]:
2437
+ recovery_list.append(decode_puzzle_hash(_))
2438
+ if "num_verifications_required" in request:
2439
+ new_amount_verifications_required = uint64(request["num_verifications_required"])
2440
+ else:
2441
+ new_amount_verifications_required = uint64(len(recovery_list))
2442
+ async with self.service.wallet_state_manager.lock:
2443
+ update_success = await wallet.update_recovery_list(recovery_list, new_amount_verifications_required)
2444
+ # Update coin with new ID info
2445
+ if update_success:
2446
+ await wallet.create_update_spend(
2447
+ action_scope, fee=uint64(request.get("fee", 0)), extra_conditions=extra_conditions
2448
+ )
2449
+ return {
2450
+ "success": True,
2451
+ "transactions": None, # tx_endpoint wrapper will take care of this
2452
+ }
2453
+ else:
2454
+ return {"success": False, "transactions": []} # pragma: no cover
2455
+
2456
+ @tx_endpoint(push=False)
2457
+ async def did_message_spend(
2458
+ self,
2459
+ request: Dict[str, Any],
2460
+ action_scope: WalletActionScope,
2461
+ extra_conditions: Tuple[Condition, ...] = tuple(),
2462
+ ) -> EndpointResult:
2463
+ wallet_id = uint32(request["wallet_id"])
2464
+ wallet = self.service.wallet_state_manager.get_wallet(id=wallet_id, required_type=DIDWallet)
2465
+
2466
+ await wallet.create_message_spend(
2467
+ action_scope,
2468
+ extra_conditions=(
2469
+ *extra_conditions,
2470
+ *(CreateCoinAnnouncement(hexstr_to_bytes(ca)) for ca in request.get("coin_announcements", [])),
2471
+ *(CreatePuzzleAnnouncement(hexstr_to_bytes(pa)) for pa in request.get("puzzle_announcements", [])),
2472
+ ),
2473
+ )
2474
+ return {
2475
+ "success": True,
2476
+ "spend_bundle": None, # tx_endpoint wrapper will take care of this
2477
+ "transactions": None, # tx_endpoint wrapper will take care of this
2478
+ }
2479
+
2480
+ async def did_get_info(self, request: Dict[str, Any]) -> EndpointResult:
2481
+ if "coin_id" not in request:
2482
+ return {"success": False, "error": "Coin ID is required."}
2483
+ coin_id = request["coin_id"]
2484
+ if coin_id.startswith(AddressType.DID.hrp(self.service.config)):
2485
+ coin_id = decode_puzzle_hash(coin_id)
2486
+ else:
2487
+ coin_id = bytes32.from_hexstr(coin_id)
2488
+ # Get coin state
2489
+ peer = self.service.get_full_node_peer()
2490
+ coin_spend, coin_state = await self.get_latest_singleton_coin_spend(peer, coin_id, request.get("latest", True))
2491
+ uncurried = uncurry_puzzle(coin_spend.puzzle_reveal)
2492
+ curried_args = match_did_puzzle(uncurried.mod, uncurried.args)
2493
+ if curried_args is None:
2494
+ return {"success": False, "error": "The coin is not a DID."}
2495
+ p2_puzzle, recovery_list_hash, num_verification, singleton_struct, metadata = curried_args
2496
+ launcher_id = bytes32(singleton_struct.rest().first().as_atom())
2497
+ uncurried_p2 = uncurry_puzzle(p2_puzzle)
2498
+ (public_key,) = uncurried_p2.args.as_iter()
2499
+ memos = compute_memos(WalletSpendBundle([coin_spend], G2Element()))
2500
+ hints = []
2501
+ coin_memos = memos.get(coin_state.coin.name())
2502
+ if coin_memos is not None:
2503
+ for memo in coin_memos:
2504
+ hints.append(memo.hex())
2505
+ return {
2506
+ "success": True,
2507
+ "did_id": encode_puzzle_hash(launcher_id, AddressType.DID.hrp(self.service.config)),
2508
+ "latest_coin": coin_state.coin.name().hex(),
2509
+ "p2_address": encode_puzzle_hash(p2_puzzle.get_tree_hash(), AddressType.XCH.hrp(self.service.config)),
2510
+ "public_key": public_key.as_atom().hex(),
2511
+ "recovery_list_hash": recovery_list_hash.as_atom().hex(),
2512
+ "num_verification": num_verification.as_int(),
2513
+ "metadata": did_program_to_metadata(metadata),
2514
+ "launcher_id": launcher_id.hex(),
2515
+ "full_puzzle": coin_spend.puzzle_reveal,
2516
+ "solution": coin_spend.solution.to_program().as_python(),
2517
+ "hints": hints,
2518
+ }
2519
+
2520
+ async def did_find_lost_did(self, request: Dict[str, Any]) -> EndpointResult:
2521
+ """
2522
+ Recover a missing or unspendable DID wallet by a coin id of the DID
2523
+ :param coin_id: It can be DID ID, launcher coin ID or any coin ID of the DID you want to find.
2524
+ The latest coin ID will take less time.
2525
+ :return:
2526
+ """
2527
+ if "coin_id" not in request:
2528
+ return {"success": False, "error": "DID coin ID is required."}
2529
+ coin_id = request["coin_id"]
2530
+ # Check if we have a DID wallet for this
2531
+ if coin_id.startswith(AddressType.DID.hrp(self.service.config)):
2532
+ coin_id = decode_puzzle_hash(coin_id)
2533
+ else:
2534
+ coin_id = bytes32.from_hexstr(coin_id)
2535
+ # Get coin state
2536
+ peer = self.service.get_full_node_peer()
2537
+ coin_spend, coin_state = await self.get_latest_singleton_coin_spend(peer, coin_id)
2538
+ uncurried = uncurry_puzzle(coin_spend.puzzle_reveal)
2539
+ curried_args = match_did_puzzle(uncurried.mod, uncurried.args)
2540
+ if curried_args is None:
2541
+ return {"success": False, "error": "The coin is not a DID."}
2542
+ p2_puzzle, recovery_list_hash, num_verification, singleton_struct, metadata = curried_args
2543
+ did_data: DIDCoinData = DIDCoinData(
2544
+ p2_puzzle,
2545
+ bytes32(recovery_list_hash.as_atom()),
2546
+ uint16(num_verification.as_int()),
2547
+ singleton_struct,
2548
+ metadata,
2549
+ get_inner_puzzle_from_singleton(coin_spend.puzzle_reveal),
2550
+ coin_state,
2551
+ )
2552
+ hinted_coins, _ = compute_spend_hints_and_additions(coin_spend)
2553
+ # Hint is required, if it doesn't have any hint then it should be invalid
2554
+ hint: Optional[bytes32] = None
2555
+ for hinted_coin in hinted_coins.values():
2556
+ if hinted_coin.coin.amount % 2 == 1 and hinted_coin.hint is not None:
2557
+ hint = hinted_coin.hint
2558
+ break
2559
+ if hint is None:
2560
+ # This is an invalid DID, check if we are owner
2561
+ derivation_record = (
2562
+ await self.service.wallet_state_manager.puzzle_store.get_derivation_record_for_puzzle_hash(
2563
+ p2_puzzle.get_tree_hash()
2564
+ )
2565
+ )
2566
+ else:
2567
+ derivation_record = (
2568
+ await self.service.wallet_state_manager.puzzle_store.get_derivation_record_for_puzzle_hash(hint)
2569
+ )
2570
+
2571
+ launcher_id = bytes32(singleton_struct.rest().first().as_atom())
2572
+ if derivation_record is None:
2573
+ return {"success": False, "error": f"This DID {launcher_id} is not belong to the connected wallet"}
2574
+ else:
2575
+ our_inner_puzzle: Program = self.service.wallet_state_manager.main_wallet.puzzle_for_pk(
2576
+ derivation_record.pubkey
2577
+ )
2578
+ did_puzzle = DID_INNERPUZ_MOD.curry(
2579
+ our_inner_puzzle, recovery_list_hash, num_verification, singleton_struct, metadata
2580
+ )
2581
+ full_puzzle = create_singleton_puzzle(did_puzzle, launcher_id)
2582
+ did_puzzle_empty_recovery = DID_INNERPUZ_MOD.curry(
2583
+ our_inner_puzzle, NIL_TREEHASH, uint64(0), singleton_struct, metadata
2584
+ )
2585
+ # Check if we have the DID wallet
2586
+ did_wallet: Optional[DIDWallet] = None
2587
+ for wallet in self.service.wallet_state_manager.wallets.values():
2588
+ if isinstance(wallet, DIDWallet):
2589
+ assert wallet.did_info.origin_coin is not None
2590
+ if wallet.did_info.origin_coin.name() == launcher_id:
2591
+ did_wallet = wallet
2592
+ break
2593
+
2594
+ full_puzzle_empty_recovery = create_singleton_puzzle(did_puzzle_empty_recovery, launcher_id)
2595
+ if full_puzzle.get_tree_hash() != coin_state.coin.puzzle_hash:
2596
+ # It's unclear whether this path is ever reached, and there is no coverage in the DID wallet tests
2597
+ if full_puzzle_empty_recovery.get_tree_hash() == coin_state.coin.puzzle_hash:
2598
+ did_puzzle = did_puzzle_empty_recovery
2599
+ elif (
2600
+ did_wallet is not None
2601
+ and did_wallet.did_info.current_inner is not None
2602
+ and create_singleton_puzzle(did_wallet.did_info.current_inner, launcher_id).get_tree_hash()
2603
+ == coin_state.coin.puzzle_hash
2604
+ ):
2605
+ # Check if the old wallet has the inner puzzle
2606
+ did_puzzle = did_wallet.did_info.current_inner
2607
+ else:
2608
+ # Try override
2609
+ if "recovery_list_hash" in request:
2610
+ recovery_list_hash = Program.from_bytes(bytes.fromhex(request["recovery_list_hash"]))
2611
+ num_verification = request.get("num_verification", num_verification)
2612
+ if "metadata" in request:
2613
+ metadata = metadata_to_program(request["metadata"])
2614
+ did_puzzle = DID_INNERPUZ_MOD.curry(
2615
+ our_inner_puzzle, recovery_list_hash, num_verification, singleton_struct, metadata
2616
+ )
2617
+ full_puzzle = create_singleton_puzzle(did_puzzle, launcher_id)
2618
+ matched = True
2619
+ if full_puzzle.get_tree_hash() != coin_state.coin.puzzle_hash:
2620
+ matched = False
2621
+ # Brute force addresses
2622
+ index = 0
2623
+ derivation_record = await self.service.wallet_state_manager.puzzle_store.get_derivation_record(
2624
+ uint32(index), uint32(1), False
2625
+ )
2626
+ while derivation_record is not None:
2627
+ our_inner_puzzle = self.service.wallet_state_manager.main_wallet.puzzle_for_pk(
2628
+ derivation_record.pubkey
2629
+ )
2630
+ did_puzzle = DID_INNERPUZ_MOD.curry(
2631
+ our_inner_puzzle, recovery_list_hash, num_verification, singleton_struct, metadata
2632
+ )
2633
+ full_puzzle = create_singleton_puzzle(did_puzzle, launcher_id)
2634
+ if full_puzzle.get_tree_hash() == coin_state.coin.puzzle_hash:
2635
+ matched = True
2636
+ break
2637
+ index += 1
2638
+ derivation_record = (
2639
+ await self.service.wallet_state_manager.puzzle_store.get_derivation_record(
2640
+ uint32(index), uint32(1), False
2641
+ )
2642
+ )
2643
+
2644
+ if not matched:
2645
+ return {
2646
+ "success": False,
2647
+ "error": f"Cannot recover DID {launcher_id}"
2648
+ f" because the last spend updated recovery_list_hash/num_verification/metadata.",
2649
+ }
2650
+
2651
+ if did_wallet is None:
2652
+ # Create DID wallet
2653
+ response: List[CoinState] = await self.service.get_coin_state([launcher_id], peer=peer)
2654
+ if len(response) == 0:
2655
+ return {"success": False, "error": f"Could not find the launch coin with ID: {launcher_id}"}
2656
+ launcher_coin: CoinState = response[0]
2657
+ did_wallet = await DIDWallet.create_new_did_wallet_from_coin_spend(
2658
+ self.service.wallet_state_manager,
2659
+ self.service.wallet_state_manager.main_wallet,
2660
+ launcher_coin.coin,
2661
+ did_puzzle,
2662
+ coin_spend,
2663
+ f"DID {encode_puzzle_hash(launcher_id, AddressType.DID.hrp(self.service.config))}",
2664
+ )
2665
+ else:
2666
+ assert did_wallet.did_info.current_inner is not None
2667
+ if did_wallet.did_info.current_inner.get_tree_hash() != did_puzzle.get_tree_hash():
2668
+ # Inner DID puzzle doesn't match, we need to update the DID info
2669
+ full_solution: Program = Program.from_bytes(bytes(coin_spend.solution))
2670
+ inner_solution: Program = full_solution.rest().rest().first()
2671
+ recovery_list: List[bytes32] = []
2672
+ backup_required: int = num_verification.as_int()
2673
+ if recovery_list_hash != NIL_TREEHASH:
2674
+ try:
2675
+ for did in inner_solution.rest().rest().rest().rest().rest().as_python():
2676
+ recovery_list.append(did[0])
2677
+ except Exception:
2678
+ # We cannot recover the recovery list, but it's okay to leave it blank
2679
+ pass
2680
+ did_info: DIDInfo = DIDInfo(
2681
+ did_wallet.did_info.origin_coin,
2682
+ recovery_list,
2683
+ uint64(backup_required),
2684
+ [],
2685
+ did_puzzle,
2686
+ None,
2687
+ None,
2688
+ None,
2689
+ False,
2690
+ json.dumps(did_wallet_puzzles.did_program_to_metadata(metadata)),
2691
+ )
2692
+ await did_wallet.save_info(did_info)
2693
+ await self.service.wallet_state_manager.update_wallet_puzzle_hashes(did_wallet.wallet_info.id)
2694
+
2695
+ try:
2696
+ coin = await did_wallet.get_coin()
2697
+ if coin.name() == coin_state.coin.name():
2698
+ return {"success": True, "latest_coin_id": coin.name().hex()}
2699
+ except RuntimeError:
2700
+ # We don't have any coin for this wallet, add the coin
2701
+ pass
2702
+
2703
+ wallet_id = did_wallet.id()
2704
+ wallet_type = did_wallet.type()
2705
+ assert coin_state.created_height is not None
2706
+ coin_record: WalletCoinRecord = WalletCoinRecord(
2707
+ coin_state.coin, uint32(coin_state.created_height), uint32(0), False, False, wallet_type, wallet_id
2708
+ )
2709
+ await self.service.wallet_state_manager.coin_store.add_coin_record(coin_record, coin_state.coin.name())
2710
+ await did_wallet.coin_added(
2711
+ coin_state.coin,
2712
+ uint32(coin_state.created_height),
2713
+ peer,
2714
+ did_data,
2715
+ )
2716
+ return {"success": True, "latest_coin_id": coin_state.coin.name().hex()}
2717
+
2718
+ @tx_endpoint(push=True)
2719
+ async def did_update_metadata(
2720
+ self,
2721
+ request: Dict[str, Any],
2722
+ action_scope: WalletActionScope,
2723
+ extra_conditions: Tuple[Condition, ...] = tuple(),
2724
+ ) -> EndpointResult:
2725
+ wallet_id = uint32(request["wallet_id"])
2726
+ wallet = self.service.wallet_state_manager.get_wallet(id=wallet_id, required_type=DIDWallet)
2727
+ metadata: Dict[str, str] = {}
2728
+ if "metadata" in request and type(request["metadata"]) is dict:
2729
+ metadata = request["metadata"]
2730
+ async with self.service.wallet_state_manager.lock:
2731
+ update_success = await wallet.update_metadata(metadata)
2732
+ # Update coin with new ID info
2733
+ if update_success:
2734
+ await wallet.create_update_spend(
2735
+ action_scope, uint64(request.get("fee", 0)), extra_conditions=extra_conditions
2736
+ )
2737
+ return {
2738
+ "wallet_id": wallet_id,
2739
+ "success": True,
2740
+ "spend_bundle": None, # tx_endpoint wrapper will take care of this
2741
+ "transactions": None, # tx_endpoint wrapper will take care of this
2742
+ }
2743
+ else:
2744
+ return {"success": False, "error": f"Couldn't update metadata with input: {metadata}"}
2745
+
2746
+ async def did_get_did(self, request: Dict[str, Any]) -> EndpointResult:
2747
+ wallet_id = uint32(request["wallet_id"])
2748
+ wallet = self.service.wallet_state_manager.get_wallet(id=wallet_id, required_type=DIDWallet)
2749
+ my_did: str = encode_puzzle_hash(bytes32.fromhex(wallet.get_my_DID()), AddressType.DID.hrp(self.service.config))
2750
+ async with self.service.wallet_state_manager.lock:
2751
+ try:
2752
+ coin = await wallet.get_coin()
2753
+ return {"success": True, "wallet_id": wallet_id, "my_did": my_did, "coin_id": coin.name()}
2754
+ except RuntimeError:
2755
+ return {"success": True, "wallet_id": wallet_id, "my_did": my_did}
2756
+
2757
+ async def did_get_recovery_list(self, request: Dict[str, Any]) -> EndpointResult:
2758
+ wallet_id = uint32(request["wallet_id"])
2759
+ wallet = self.service.wallet_state_manager.get_wallet(id=wallet_id, required_type=DIDWallet)
2760
+ recovery_list = wallet.did_info.backup_ids
2761
+ recovery_dids = []
2762
+ for backup_id in recovery_list:
2763
+ recovery_dids.append(encode_puzzle_hash(backup_id, AddressType.DID.hrp(self.service.config)))
2764
+ return {
2765
+ "success": True,
2766
+ "wallet_id": wallet_id,
2767
+ "recovery_list": recovery_dids,
2768
+ "num_required": wallet.did_info.num_of_backup_ids_needed,
2769
+ }
2770
+
2771
+ async def did_get_metadata(self, request: Dict[str, Any]) -> EndpointResult:
2772
+ wallet_id = uint32(request["wallet_id"])
2773
+ wallet = self.service.wallet_state_manager.get_wallet(id=wallet_id, required_type=DIDWallet)
2774
+ metadata = json.loads(wallet.did_info.metadata)
2775
+ return {
2776
+ "success": True,
2777
+ "wallet_id": wallet_id,
2778
+ "metadata": metadata,
2779
+ }
2780
+
2781
+ # TODO: this needs a test
2782
+ # Don't need full @tx_endpoint decorator here, but "push" is still a valid option
2783
+ async def did_recovery_spend(self, request: Dict[str, Any]) -> EndpointResult: # pragma: no cover
2784
+ wallet_id = uint32(request["wallet_id"])
2785
+ wallet = self.service.wallet_state_manager.get_wallet(id=wallet_id, required_type=DIDWallet)
2786
+ if len(request["attest_data"]) < wallet.did_info.num_of_backup_ids_needed:
2787
+ return {"success": False, "reason": "insufficient messages"}
2788
+ async with self.service.wallet_state_manager.lock:
2789
+ (
2790
+ info_list,
2791
+ message_spend_bundle,
2792
+ ) = await wallet.load_attest_files_for_recovery_spend(request["attest_data"])
2793
+
2794
+ if "pubkey" in request:
2795
+ pubkey = G1Element.from_bytes(hexstr_to_bytes(request["pubkey"]))
2796
+ else:
2797
+ assert wallet.did_info.temp_pubkey is not None
2798
+ pubkey = G1Element.from_bytes(wallet.did_info.temp_pubkey)
2799
+
2800
+ if "puzhash" in request:
2801
+ puzhash = bytes32.from_hexstr(request["puzhash"])
2802
+ else:
2803
+ assert wallet.did_info.temp_puzhash is not None
2804
+ puzhash = wallet.did_info.temp_puzhash
2805
+
2806
+ assert wallet.did_info.temp_coin is not None
2807
+ async with self.service.wallet_state_manager.new_action_scope(
2808
+ DEFAULT_TX_CONFIG, push=request.get("push", True)
2809
+ ) as action_scope:
2810
+ await wallet.recovery_spend(
2811
+ wallet.did_info.temp_coin,
2812
+ puzhash,
2813
+ info_list,
2814
+ pubkey,
2815
+ message_spend_bundle,
2816
+ action_scope,
2817
+ )
2818
+ [tx] = action_scope.side_effects.transactions
2819
+ return {
2820
+ "success": True,
2821
+ "spend_bundle": tx.spend_bundle,
2822
+ "transactions": [tx.to_json_dict_convenience(self.service.config)],
2823
+ }
2824
+
2825
+ async def did_get_pubkey(self, request: Dict[str, Any]) -> EndpointResult:
2826
+ wallet_id = uint32(request["wallet_id"])
2827
+ wallet = self.service.wallet_state_manager.get_wallet(id=wallet_id, required_type=DIDWallet)
2828
+ pubkey = bytes((await wallet.wallet_state_manager.get_unused_derivation_record(wallet_id)).pubkey).hex()
2829
+ return {"success": True, "pubkey": pubkey}
2830
+
2831
+ # TODO: this needs a test
2832
+ @tx_endpoint(push=True)
2833
+ async def did_create_attest(
2834
+ self,
2835
+ request: Dict[str, Any],
2836
+ action_scope: WalletActionScope,
2837
+ extra_conditions: Tuple[Condition, ...] = tuple(),
2838
+ ) -> EndpointResult: # pragma: no cover
2839
+ wallet_id = uint32(request["wallet_id"])
2840
+ wallet = self.service.wallet_state_manager.get_wallet(id=wallet_id, required_type=DIDWallet)
2841
+ async with self.service.wallet_state_manager.lock:
2842
+ info = await wallet.get_info_for_recovery()
2843
+ coin = bytes32.from_hexstr(request["coin_name"])
2844
+ pubkey = G1Element.from_bytes(hexstr_to_bytes(request["pubkey"]))
2845
+ message_spend_bundle, attest_data = await wallet.create_attestment(
2846
+ coin,
2847
+ bytes32.from_hexstr(request["puzhash"]),
2848
+ pubkey,
2849
+ action_scope,
2850
+ extra_conditions=extra_conditions,
2851
+ )
2852
+ if info is not None:
2853
+ return {
2854
+ "success": True,
2855
+ "message_spend_bundle": bytes(message_spend_bundle).hex(),
2856
+ "info": [info[0].hex(), info[1].hex(), info[2]],
2857
+ "attest_data": attest_data,
2858
+ "transactions": None, # tx_endpoint wrapper will take care of this
2859
+ }
2860
+ else:
2861
+ return {"success": False}
2862
+
2863
+ async def did_get_information_needed_for_recovery(self, request: Dict[str, Any]) -> EndpointResult:
2864
+ wallet_id = uint32(request["wallet_id"])
2865
+ did_wallet = self.service.wallet_state_manager.get_wallet(id=wallet_id, required_type=DIDWallet)
2866
+ my_did = encode_puzzle_hash(
2867
+ bytes32.from_hexstr(did_wallet.get_my_DID()), AddressType.DID.hrp(self.service.config)
2868
+ )
2869
+ assert did_wallet.did_info.temp_coin is not None
2870
+ coin_name = did_wallet.did_info.temp_coin.name().hex()
2871
+ return {
2872
+ "success": True,
2873
+ "wallet_id": wallet_id,
2874
+ "my_did": my_did,
2875
+ "coin_name": coin_name,
2876
+ "newpuzhash": did_wallet.did_info.temp_puzhash,
2877
+ "pubkey": did_wallet.did_info.temp_pubkey,
2878
+ "backup_dids": did_wallet.did_info.backup_ids,
2879
+ }
2880
+
2881
+ async def did_get_current_coin_info(self, request: Dict[str, Any]) -> EndpointResult:
2882
+ wallet_id = uint32(request["wallet_id"])
2883
+ did_wallet = self.service.wallet_state_manager.get_wallet(id=wallet_id, required_type=DIDWallet)
2884
+ my_did = encode_puzzle_hash(
2885
+ bytes32.from_hexstr(did_wallet.get_my_DID()), AddressType.DID.hrp(self.service.config)
2886
+ )
2887
+
2888
+ did_coin_threeple = await did_wallet.get_info_for_recovery()
2889
+ assert my_did is not None
2890
+ assert did_coin_threeple is not None
2891
+ return {
2892
+ "success": True,
2893
+ "wallet_id": wallet_id,
2894
+ "my_did": my_did,
2895
+ "did_parent": did_coin_threeple[0],
2896
+ "did_innerpuz": did_coin_threeple[1],
2897
+ "did_amount": did_coin_threeple[2],
2898
+ }
2899
+
2900
+ async def did_create_backup_file(self, request: Dict[str, Any]) -> EndpointResult:
2901
+ wallet_id = uint32(request["wallet_id"])
2902
+ did_wallet = self.service.wallet_state_manager.get_wallet(id=wallet_id, required_type=DIDWallet)
2903
+ return {"wallet_id": wallet_id, "success": True, "backup_data": did_wallet.create_backup()}
2904
+
2905
+ @tx_endpoint(push=True)
2906
+ async def did_transfer_did(
2907
+ self,
2908
+ request: Dict[str, Any],
2909
+ action_scope: WalletActionScope,
2910
+ extra_conditions: Tuple[Condition, ...] = tuple(),
2911
+ ) -> EndpointResult:
2912
+ if await self.service.wallet_state_manager.synced() is False:
2913
+ raise ValueError("Wallet needs to be fully synced.")
2914
+ wallet_id = uint32(request["wallet_id"])
2915
+ did_wallet = self.service.wallet_state_manager.get_wallet(id=wallet_id, required_type=DIDWallet)
2916
+ puzzle_hash: bytes32 = decode_puzzle_hash(request["inner_address"])
2917
+ async with self.service.wallet_state_manager.lock:
2918
+ await did_wallet.transfer_did(
2919
+ puzzle_hash,
2920
+ uint64(request.get("fee", 0)),
2921
+ request.get("with_recovery_info", True),
2922
+ action_scope,
2923
+ extra_conditions=extra_conditions,
2924
+ )
2925
+
2926
+ return {
2927
+ "success": True,
2928
+ "transaction": None, # tx_endpoint wrapper will take care of this
2929
+ "transactions": None, # tx_endpoint wrapper will take care of this
2930
+ "transaction_id": None, # tx_endpoint wrapper will take care of this
2931
+ }
2932
+
2933
+ ##########################################################################################
2934
+ # DAO Wallet
2935
+ ##########################################################################################
2936
+
2937
+ async def dao_adjust_filter_level(self, request: Dict[str, Any]) -> EndpointResult:
2938
+ wallet_id = uint32(request["wallet_id"])
2939
+ dao_wallet = self.service.wallet_state_manager.get_wallet(id=wallet_id, required_type=DAOWallet)
2940
+ await dao_wallet.adjust_filter_level(uint64(request["filter_level"]))
2941
+ return {
2942
+ "success": True,
2943
+ "dao_info": dao_wallet.dao_info,
2944
+ }
2945
+
2946
+ @tx_endpoint(push=True)
2947
+ async def dao_add_funds_to_treasury(
2948
+ self,
2949
+ request: Dict[str, Any],
2950
+ action_scope: WalletActionScope,
2951
+ extra_conditions: Tuple[Condition, ...] = tuple(),
2952
+ ) -> EndpointResult:
2953
+ wallet_id = uint32(request["wallet_id"])
2954
+ dao_wallet = self.service.wallet_state_manager.get_wallet(id=wallet_id, required_type=DAOWallet)
2955
+ funding_wallet_id = uint32(request["funding_wallet_id"])
2956
+ wallet_type = self.service.wallet_state_manager.wallets[funding_wallet_id].type()
2957
+ amount = request.get("amount")
2958
+ assert amount
2959
+ if wallet_type not in [WalletType.STANDARD_WALLET, WalletType.CAT]: # pragma: no cover
2960
+ raise ValueError(f"Cannot fund a treasury with assets from a {wallet_type.name} wallet")
2961
+ await dao_wallet.create_add_funds_to_treasury_spend(
2962
+ uint64(amount),
2963
+ action_scope,
2964
+ fee=uint64(request.get("fee", 0)),
2965
+ funding_wallet_id=funding_wallet_id,
2966
+ extra_conditions=extra_conditions,
2967
+ )
2968
+ return {
2969
+ "success": True,
2970
+ "tx_id": None, # tx_endpoint wrapper will take care of this
2971
+ "tx": None, # tx_endpoint wrapper will take care of this
2972
+ "transactions": None, # tx_endpoint wrapper will take care of this
2973
+ }
2974
+
2975
+ async def dao_get_treasury_balance(self, request: Dict[str, Any]) -> EndpointResult:
2976
+ wallet_id = uint32(request["wallet_id"])
2977
+ dao_wallet = self.service.wallet_state_manager.get_wallet(id=wallet_id, required_type=DAOWallet)
2978
+ assert dao_wallet is not None
2979
+ asset_list = dao_wallet.dao_info.assets
2980
+ balances = {}
2981
+ for asset_id in asset_list:
2982
+ balance = await dao_wallet.get_balance_by_asset_type(asset_id=asset_id)
2983
+ if asset_id is None:
2984
+ balances["xch"] = balance
2985
+ else:
2986
+ balances[asset_id.hex()] = balance
2987
+ return {"success": True, "balances": balances}
2988
+
2989
+ async def dao_get_treasury_id(self, request: Dict[str, Any]) -> EndpointResult:
2990
+ wallet_id = uint32(request["wallet_id"])
2991
+ dao_wallet = self.service.wallet_state_manager.get_wallet(id=wallet_id, required_type=DAOWallet)
2992
+ assert dao_wallet is not None
2993
+ treasury_id = dao_wallet.dao_info.treasury_id
2994
+ return {"treasury_id": treasury_id}
2995
+
2996
+ async def dao_get_rules(self, request: Dict[str, Any]) -> EndpointResult:
2997
+ wallet_id = uint32(request["wallet_id"])
2998
+ dao_wallet = self.service.wallet_state_manager.get_wallet(id=wallet_id, required_type=DAOWallet)
2999
+ assert dao_wallet is not None
3000
+ rules = dao_wallet.dao_rules
3001
+ return {"rules": rules}
3002
+
3003
+ @tx_endpoint(push=True)
3004
+ async def dao_send_to_lockup(
3005
+ self,
3006
+ request: Dict[str, Any],
3007
+ action_scope: WalletActionScope,
3008
+ extra_conditions: Tuple[Condition, ...] = tuple(),
3009
+ ) -> EndpointResult:
3010
+ wallet_id = uint32(request["wallet_id"])
3011
+ dao_wallet = self.service.wallet_state_manager.get_wallet(id=wallet_id, required_type=DAOWallet)
3012
+ dao_cat_wallet = self.service.wallet_state_manager.get_wallet(
3013
+ id=dao_wallet.dao_info.dao_cat_wallet_id, required_type=DAOCATWallet
3014
+ )
3015
+ amount = uint64(request["amount"])
3016
+ fee = uint64(request.get("fee", 0))
3017
+ await dao_cat_wallet.enter_dao_cat_voting_mode(
3018
+ amount,
3019
+ action_scope,
3020
+ fee=fee,
3021
+ extra_conditions=extra_conditions,
3022
+ )
3023
+ return {
3024
+ "success": True,
3025
+ "tx_id": None,
3026
+ "txs": None,
3027
+ "transactions": None, # tx_endpoint wrapper will take care of this
3028
+ }
3029
+
3030
+ async def dao_get_proposals(self, request: Dict[str, Any]) -> EndpointResult:
3031
+ wallet_id = uint32(request["wallet_id"])
3032
+ include_closed = request.get("include_closed", True)
3033
+ dao_wallet = self.service.wallet_state_manager.get_wallet(id=wallet_id, required_type=DAOWallet)
3034
+ assert dao_wallet is not None
3035
+ proposal_list = dao_wallet.dao_info.proposals_list
3036
+ if not include_closed:
3037
+ proposal_list = [prop for prop in proposal_list if not prop.closed]
3038
+ dao_rules = get_treasury_rules_from_puzzle(dao_wallet.dao_info.current_treasury_innerpuz)
3039
+ return {
3040
+ "success": True,
3041
+ "proposals": proposal_list,
3042
+ "proposal_timelock": dao_rules.proposal_timelock,
3043
+ "soft_close_length": dao_rules.soft_close_length,
3044
+ }
3045
+
3046
+ async def dao_get_proposal_state(self, request: Dict[str, Any]) -> EndpointResult:
3047
+ wallet_id = uint32(request["wallet_id"])
3048
+ dao_wallet = self.service.wallet_state_manager.get_wallet(id=wallet_id, required_type=DAOWallet)
3049
+ assert dao_wallet is not None
3050
+ state = await dao_wallet.get_proposal_state(bytes32.from_hexstr(request["proposal_id"]))
3051
+ return {"success": True, "state": state}
3052
+
3053
+ @tx_endpoint(push=True)
3054
+ async def dao_exit_lockup(
3055
+ self,
3056
+ request: Dict[str, Any],
3057
+ action_scope: WalletActionScope,
3058
+ extra_conditions: Tuple[Condition, ...] = tuple(),
3059
+ ) -> EndpointResult:
3060
+ wallet_id = uint32(request["wallet_id"])
3061
+ dao_wallet = self.service.wallet_state_manager.get_wallet(id=wallet_id, required_type=DAOWallet)
3062
+ assert dao_wallet is not None
3063
+ dao_cat_wallet = self.service.wallet_state_manager.get_wallet(
3064
+ id=dao_wallet.dao_info.dao_cat_wallet_id, required_type=DAOCATWallet
3065
+ )
3066
+ assert dao_cat_wallet is not None
3067
+ if request["coins"]: # pragma: no cover
3068
+ coin_list = [Coin.from_json_dict(coin) for coin in request["coins"]]
3069
+ coins: List[LockedCoinInfo] = []
3070
+ for lci in dao_cat_wallet.dao_cat_info.locked_coins:
3071
+ if lci.coin in coin_list:
3072
+ coins.append(lci)
3073
+ else:
3074
+ coins = []
3075
+ for lci in dao_cat_wallet.dao_cat_info.locked_coins:
3076
+ if lci.active_votes == []:
3077
+ coins.append(lci)
3078
+ fee = uint64(request.get("fee", 0))
3079
+ if not coins: # pragma: no cover
3080
+ raise ValueError("There are not coins available to exit lockup")
3081
+ await dao_cat_wallet.exit_vote_state(
3082
+ coins,
3083
+ action_scope,
3084
+ fee=fee,
3085
+ extra_conditions=extra_conditions,
3086
+ )
3087
+ return {
3088
+ "success": True,
3089
+ "tx_id": None, # tx_endpoint wrapper will take care of this
3090
+ "tx": None, # tx_endpoint wrapper will take care of this
3091
+ "transactions": None, # tx_endpoint wrapper will take care of this
3092
+ }
3093
+
3094
+ @tx_endpoint(push=True)
3095
+ async def dao_create_proposal(
3096
+ self,
3097
+ request: Dict[str, Any],
3098
+ action_scope: WalletActionScope,
3099
+ extra_conditions: Tuple[Condition, ...] = tuple(),
3100
+ ) -> EndpointResult:
3101
+ wallet_id = uint32(request["wallet_id"])
3102
+ dao_wallet = self.service.wallet_state_manager.get_wallet(id=wallet_id, required_type=DAOWallet)
3103
+ assert dao_wallet is not None
3104
+
3105
+ if request["proposal_type"] == "spend":
3106
+ amounts: List[uint64] = []
3107
+ puzzle_hashes: List[bytes32] = []
3108
+ asset_types: List[Optional[bytes32]] = []
3109
+ additions: Optional[List[Dict[str, Any]]] = request.get("additions")
3110
+ if additions is not None:
3111
+ for addition in additions:
3112
+ if "asset_id" in addition:
3113
+ asset_id = bytes32.from_hexstr(addition["asset_id"])
3114
+ else:
3115
+ asset_id = None
3116
+ receiver_ph = bytes32.from_hexstr(addition["puzzle_hash"])
3117
+ amount = uint64(addition["amount"])
3118
+ amounts.append(amount)
3119
+ puzzle_hashes.append(receiver_ph)
3120
+ asset_types.append(asset_id)
3121
+ else: # pragma: no cover
3122
+ amounts.append(uint64(request["amount"]))
3123
+ puzzle_hashes.append(decode_puzzle_hash(request["inner_address"]))
3124
+ if request["asset_id"] is not None:
3125
+ asset_types.append(bytes32.from_hexstr(request["asset_id"]))
3126
+ else:
3127
+ asset_types.append(None)
3128
+ proposed_puzzle = generate_simple_proposal_innerpuz(
3129
+ dao_wallet.dao_info.treasury_id, puzzle_hashes, amounts, asset_types
3130
+ )
3131
+
3132
+ elif request["proposal_type"] == "update":
3133
+ rules = dao_wallet.dao_rules
3134
+ prop = request["new_dao_rules"]
3135
+ new_rules = DAORules(
3136
+ proposal_timelock=prop.get("proposal_timelock") or rules.proposal_timelock,
3137
+ soft_close_length=prop.get("soft_close_length") or rules.soft_close_length,
3138
+ attendance_required=prop.get("attendance_required") or rules.attendance_required,
3139
+ proposal_minimum_amount=prop.get("proposal_minimum_amount") or rules.proposal_minimum_amount,
3140
+ pass_percentage=prop.get("pass_percentage") or rules.pass_percentage,
3141
+ self_destruct_length=prop.get("self_destruct_length") or rules.self_destruct_length,
3142
+ oracle_spend_delay=prop.get("oracle_spend_delay") or rules.oracle_spend_delay,
3143
+ )
3144
+
3145
+ current_innerpuz = dao_wallet.dao_info.current_treasury_innerpuz
3146
+ assert current_innerpuz is not None
3147
+ proposed_puzzle = await generate_update_proposal_innerpuz(current_innerpuz, new_rules)
3148
+ elif request["proposal_type"] == "mint":
3149
+ amount_of_cats = uint64(request["amount"])
3150
+ mint_address = decode_puzzle_hash(request["cat_target_address"])
3151
+ cat_wallet = self.service.wallet_state_manager.get_wallet(
3152
+ id=dao_wallet.dao_info.cat_wallet_id, required_type=CATWallet
3153
+ )
3154
+ proposed_puzzle = await generate_mint_proposal_innerpuz(
3155
+ dao_wallet.dao_info.treasury_id,
3156
+ cat_wallet.cat_info.limitations_program_hash,
3157
+ amount_of_cats,
3158
+ mint_address,
3159
+ )
3160
+ else: # pragma: no cover
3161
+ return {"success": False, "error": "Unknown proposal type."}
3162
+
3163
+ vote_amount = request.get("vote_amount")
3164
+ fee = uint64(request.get("fee", 0))
3165
+ await dao_wallet.generate_new_proposal(
3166
+ proposed_puzzle,
3167
+ action_scope,
3168
+ vote_amount=vote_amount,
3169
+ fee=fee,
3170
+ extra_conditions=extra_conditions,
3171
+ )
3172
+ async with action_scope.use() as interface:
3173
+ found: bool = False
3174
+ for tx in interface.side_effects.transactions:
3175
+ for coin in tx.removals:
3176
+ if coin.puzzle_hash == SINGLETON_LAUNCHER_PUZZLE_HASH:
3177
+ proposal_id = coin.name()
3178
+ found = True
3179
+ if found:
3180
+ break
3181
+ else: # pragma: no cover
3182
+ raise ValueError("Could not find proposal ID in transaction")
3183
+ return {
3184
+ "success": True,
3185
+ # Semantics guarantee proposal_id here
3186
+ "proposal_id": proposal_id, # pylint: disable=possibly-used-before-assignment
3187
+ "tx_id": None, # tx_endpoint wrapper will take care of this
3188
+ "tx": None, # tx_endpoint wrapper will take care of this
3189
+ "transactions": None, # tx_endpoint wrapper will take care of this
3190
+ }
3191
+
3192
+ @tx_endpoint(push=True)
3193
+ async def dao_vote_on_proposal(
3194
+ self,
3195
+ request: Dict[str, Any],
3196
+ action_scope: WalletActionScope,
3197
+ extra_conditions: Tuple[Condition, ...] = tuple(),
3198
+ ) -> EndpointResult:
3199
+ wallet_id = uint32(request["wallet_id"])
3200
+ dao_wallet = self.service.wallet_state_manager.get_wallet(id=wallet_id, required_type=DAOWallet)
3201
+ assert dao_wallet is not None
3202
+ vote_amount = None
3203
+ if "vote_amount" in request:
3204
+ vote_amount = uint64(request["vote_amount"])
3205
+ fee = uint64(request.get("fee", 0))
3206
+ await dao_wallet.generate_proposal_vote_spend(
3207
+ bytes32.from_hexstr(request["proposal_id"]),
3208
+ vote_amount,
3209
+ request["is_yes_vote"], # bool
3210
+ action_scope,
3211
+ fee,
3212
+ extra_conditions=extra_conditions,
3213
+ )
3214
+ return {
3215
+ "success": True,
3216
+ "tx_id": None, # tx_endpoint wrapper will take care of this
3217
+ "tx": None, # tx_endpoint wrapper will take care of this
3218
+ "transactions": None, # tx_endpoint wrapper will take care of this
3219
+ }
3220
+
3221
+ async def dao_parse_proposal(self, request: Dict[str, Any]) -> EndpointResult:
3222
+ wallet_id = uint32(request["wallet_id"])
3223
+ dao_wallet = self.service.wallet_state_manager.get_wallet(id=wallet_id, required_type=DAOWallet)
3224
+ assert dao_wallet is not None
3225
+ proposal_id = bytes32.from_hexstr(request["proposal_id"])
3226
+ proposal_dictionary = await dao_wallet.parse_proposal(proposal_id)
3227
+ assert proposal_dictionary is not None
3228
+ return {"success": True, "proposal_dictionary": proposal_dictionary}
3229
+
3230
+ @tx_endpoint(push=True)
3231
+ async def dao_close_proposal(
3232
+ self,
3233
+ request: Dict[str, Any],
3234
+ action_scope: WalletActionScope,
3235
+ extra_conditions: Tuple[Condition, ...] = tuple(),
3236
+ ) -> EndpointResult:
3237
+ wallet_id = uint32(request["wallet_id"])
3238
+ dao_wallet = self.service.wallet_state_manager.get_wallet(id=wallet_id, required_type=DAOWallet)
3239
+ assert dao_wallet is not None
3240
+ fee = uint64(request.get("fee", 0))
3241
+ if "genesis_id" in request: # pragma: no cover
3242
+ genesis_id = bytes32.from_hexstr(request["genesis_id"])
3243
+ else:
3244
+ genesis_id = None
3245
+ self_destruct = request.get("self_destruct", None)
3246
+ await dao_wallet.create_proposal_close_spend(
3247
+ bytes32.from_hexstr(request["proposal_id"]),
3248
+ action_scope,
3249
+ genesis_id,
3250
+ fee=fee,
3251
+ self_destruct=self_destruct,
3252
+ extra_conditions=extra_conditions,
3253
+ )
3254
+ return {
3255
+ "success": True,
3256
+ "tx_id": None, # tx_endpoint wrapper will take care of this
3257
+ "tx": None, # tx_endpoint wrapper will take care of this
3258
+ "transactions": None, # tx_endpoint wrapper will take care of this
3259
+ }
3260
+
3261
+ @tx_endpoint(push=True)
3262
+ async def dao_free_coins_from_finished_proposals(
3263
+ self,
3264
+ request: Dict[str, Any],
3265
+ action_scope: WalletActionScope,
3266
+ extra_conditions: Tuple[Condition, ...] = tuple(),
3267
+ ) -> EndpointResult:
3268
+ wallet_id = uint32(request["wallet_id"])
3269
+ fee = uint64(request.get("fee", 0))
3270
+ dao_wallet = self.service.wallet_state_manager.get_wallet(id=wallet_id, required_type=DAOWallet)
3271
+ assert dao_wallet is not None
3272
+ await dao_wallet.free_coins_from_finished_proposals(
3273
+ action_scope,
3274
+ fee=fee,
3275
+ extra_conditions=extra_conditions,
3276
+ )
3277
+
3278
+ return {
3279
+ "success": True,
3280
+ "tx_id": None, # tx_endpoint wrapper will take care of this
3281
+ "tx": None, # tx_endpoint wrapper will take care of this
3282
+ "transactions": None, # tx_endpoint wrapper will take care of this
3283
+ }
3284
+
3285
+ ##########################################################################################
3286
+ # NFT Wallet
3287
+ ##########################################################################################
3288
+ @tx_endpoint(push=True)
3289
+ async def nft_mint_nft(
3290
+ self,
3291
+ request: Dict[str, Any],
3292
+ action_scope: WalletActionScope,
3293
+ extra_conditions: Tuple[Condition, ...] = tuple(),
3294
+ ) -> EndpointResult:
3295
+ log.debug("Got minting RPC request: %s", request)
3296
+ wallet_id = uint32(request["wallet_id"])
3297
+ assert self.service.wallet_state_manager
3298
+ nft_wallet = self.service.wallet_state_manager.get_wallet(id=wallet_id, required_type=NFTWallet)
3299
+ royalty_address = request.get("royalty_address")
3300
+ royalty_amount = uint16(request.get("royalty_percentage", 0))
3301
+ if royalty_amount == 10000:
3302
+ raise ValueError("Royalty percentage cannot be 100%")
3303
+ if isinstance(royalty_address, str):
3304
+ royalty_puzhash = decode_puzzle_hash(royalty_address)
3305
+ elif royalty_address is None:
3306
+ royalty_puzhash = await nft_wallet.standard_wallet.get_new_puzzlehash()
3307
+ else:
3308
+ royalty_puzhash = royalty_address
3309
+ target_address = request.get("target_address")
3310
+ if isinstance(target_address, str):
3311
+ target_puzhash = decode_puzzle_hash(target_address)
3312
+ elif target_address is None:
3313
+ target_puzhash = await nft_wallet.standard_wallet.get_new_puzzlehash()
3314
+ else:
3315
+ target_puzhash = target_address
3316
+ if "uris" not in request:
3317
+ return {"success": False, "error": "Data URIs is required"}
3318
+ if not isinstance(request["uris"], list):
3319
+ return {"success": False, "error": "Data URIs must be a list"}
3320
+ if not isinstance(request.get("meta_uris", []), list):
3321
+ return {"success": False, "error": "Metadata URIs must be a list"}
3322
+ if not isinstance(request.get("license_uris", []), list):
3323
+ return {"success": False, "error": "License URIs must be a list"}
3324
+ metadata_list = [
3325
+ ("u", request["uris"]),
3326
+ ("h", hexstr_to_bytes(request["hash"])),
3327
+ ("mu", request.get("meta_uris", [])),
3328
+ ("lu", request.get("license_uris", [])),
3329
+ ("sn", uint64(request.get("edition_number", 1))),
3330
+ ("st", uint64(request.get("edition_total", 1))),
3331
+ ]
3332
+ if "meta_hash" in request and len(request["meta_hash"]) > 0:
3333
+ metadata_list.append(("mh", hexstr_to_bytes(request["meta_hash"])))
3334
+ if "license_hash" in request and len(request["license_hash"]) > 0:
3335
+ metadata_list.append(("lh", hexstr_to_bytes(request["license_hash"])))
3336
+ metadata = Program.to(metadata_list)
3337
+ fee = uint64(request.get("fee", 0))
3338
+ did_id = request.get("did_id", None)
3339
+ if did_id is not None:
3340
+ if did_id == "":
3341
+ did_id = b""
3342
+ else:
3343
+ did_id = decode_puzzle_hash(did_id)
3344
+
3345
+ nft_id = await nft_wallet.generate_new_nft(
3346
+ metadata,
3347
+ action_scope,
3348
+ target_puzhash,
3349
+ royalty_puzhash,
3350
+ royalty_amount,
3351
+ did_id,
3352
+ fee,
3353
+ extra_conditions=extra_conditions,
3354
+ )
3355
+ nft_id_bech32 = encode_puzzle_hash(nft_id, AddressType.NFT.hrp(self.service.config))
3356
+ return {
3357
+ "wallet_id": wallet_id,
3358
+ "success": True,
3359
+ "spend_bundle": None, # tx_endpoint wrapper will take care of this
3360
+ "nft_id": nft_id_bech32,
3361
+ "transactions": None, # tx_endpoint wrapper will take care of this
3362
+ }
3363
+
3364
+ async def nft_count_nfts(self, request: Dict[str, Any]) -> EndpointResult:
3365
+ wallet_id = request.get("wallet_id", None)
3366
+ count = 0
3367
+ if wallet_id is not None:
3368
+ try:
3369
+ nft_wallet = self.service.wallet_state_manager.get_wallet(id=wallet_id, required_type=NFTWallet)
3370
+ except KeyError:
3371
+ # wallet not found
3372
+ return {"success": False, "error": f"Wallet {wallet_id} not found."}
3373
+ count = await nft_wallet.get_nft_count()
3374
+ else:
3375
+ count = await self.service.wallet_state_manager.nft_store.count()
3376
+ return {"wallet_id": wallet_id, "success": True, "count": count}
3377
+
3378
+ async def nft_get_nfts(self, request: Dict[str, Any]) -> EndpointResult:
3379
+ wallet_id = request.get("wallet_id", None)
3380
+ nfts: List[NFTCoinInfo] = []
3381
+ if wallet_id is not None:
3382
+ nft_wallet = self.service.wallet_state_manager.get_wallet(id=wallet_id, required_type=NFTWallet)
3383
+ else:
3384
+ nft_wallet = None
3385
+ try:
3386
+ start_index = int(request.get("start_index", 0))
3387
+ except (TypeError, ValueError):
3388
+ start_index = 0
3389
+ try:
3390
+ count = int(request.get("num", 50))
3391
+ except (TypeError, ValueError):
3392
+ count = 50
3393
+ nft_info_list = []
3394
+ if nft_wallet is not None:
3395
+ nfts = await nft_wallet.get_current_nfts(start_index=start_index, count=count)
3396
+ else:
3397
+ nfts = await self.service.wallet_state_manager.nft_store.get_nft_list(start_index=start_index, count=count)
3398
+ for nft in nfts:
3399
+ nft_info = await nft_puzzles.get_nft_info_from_puzzle(nft, self.service.wallet_state_manager.config)
3400
+ nft_info_list.append(nft_info)
3401
+ return {"wallet_id": wallet_id, "success": True, "nft_list": nft_info_list}
3402
+
3403
+ @tx_endpoint(push=True)
3404
+ async def nft_set_nft_did(
3405
+ self,
3406
+ request: Dict[str, Any],
3407
+ action_scope: WalletActionScope,
3408
+ extra_conditions: Tuple[Condition, ...] = tuple(),
3409
+ ) -> EndpointResult:
3410
+ wallet_id = uint32(request["wallet_id"])
3411
+ nft_wallet = self.service.wallet_state_manager.get_wallet(id=wallet_id, required_type=NFTWallet)
3412
+ did_id = request.get("did_id", b"")
3413
+ if did_id != b"":
3414
+ did_id = decode_puzzle_hash(did_id)
3415
+ nft_coin_info = await nft_wallet.get_nft_coin_by_id(bytes32.from_hexstr(request["nft_coin_id"]))
3416
+ if not (
3417
+ await nft_puzzles.get_nft_info_from_puzzle(nft_coin_info, self.service.wallet_state_manager.config)
3418
+ ).supports_did:
3419
+ return {"success": False, "error": "The NFT doesn't support setting a DID."}
3420
+
3421
+ fee = uint64(request.get("fee", 0))
3422
+ await nft_wallet.set_nft_did(
3423
+ nft_coin_info,
3424
+ did_id,
3425
+ action_scope,
3426
+ fee=fee,
3427
+ extra_conditions=extra_conditions,
3428
+ )
3429
+ return {
3430
+ "wallet_id": wallet_id,
3431
+ "success": True,
3432
+ "spend_bundle": None, # tx_endpoint wrapper will take care of this
3433
+ "transactions": None, # tx_endpoint wrapper will take care of this
3434
+ }
3435
+
3436
+ @tx_endpoint(push=True)
3437
+ async def nft_set_did_bulk(
3438
+ self,
3439
+ request: Dict[str, Any],
3440
+ action_scope: WalletActionScope,
3441
+ extra_conditions: Tuple[Condition, ...] = tuple(),
3442
+ ) -> EndpointResult:
3443
+ """
3444
+ Bulk set DID for NFTs across different wallets.
3445
+ accepted `request` dict keys:
3446
+ - required `nft_coin_list`: [{"nft_coin_id": COIN_ID/NFT_ID, "wallet_id": WALLET_ID},....]
3447
+ - optional `fee`, in mojos, defaults to 0
3448
+ - optional `did_id`, defaults to no DID, meaning it will reset the NFT's DID
3449
+ :param request:
3450
+ :return:
3451
+ """
3452
+ if len(request["nft_coin_list"]) > MAX_NFT_CHUNK_SIZE:
3453
+ return {"success": False, "error": f"You can only set {MAX_NFT_CHUNK_SIZE} NFTs at once"}
3454
+ did_id = request.get("did_id", b"")
3455
+ if did_id != b"":
3456
+ did_id = decode_puzzle_hash(did_id)
3457
+ nft_dict: Dict[uint32, List[NFTCoinInfo]] = {}
3458
+ coin_ids = []
3459
+ nft_ids = []
3460
+ fee = uint64(request.get("fee", 0))
3461
+
3462
+ nft_wallet: NFTWallet
3463
+ for nft_coin in request["nft_coin_list"]:
3464
+ if "nft_coin_id" not in nft_coin or "wallet_id" not in nft_coin:
3465
+ log.error(f"Cannot set DID for NFT :{nft_coin}, missing nft_coin_id or wallet_id.")
3466
+ continue
3467
+ wallet_id = uint32(nft_coin["wallet_id"])
3468
+ nft_wallet = self.service.wallet_state_manager.get_wallet(id=wallet_id, required_type=NFTWallet)
3469
+ nft_coin_id = nft_coin["nft_coin_id"]
3470
+ if nft_coin_id.startswith(AddressType.NFT.hrp(self.service.config)):
3471
+ nft_id = decode_puzzle_hash(nft_coin_id)
3472
+ nft_coin_info = await nft_wallet.get_nft(nft_id)
3473
+ else:
3474
+ nft_coin_id = bytes32.from_hexstr(nft_coin_id)
3475
+ nft_coin_info = await nft_wallet.get_nft_coin_by_id(nft_coin_id)
3476
+ assert nft_coin_info is not None
3477
+ if not (
3478
+ await nft_puzzles.get_nft_info_from_puzzle(nft_coin_info, self.service.wallet_state_manager.config)
3479
+ ).supports_did:
3480
+ log.warning(f"Skipping NFT {nft_coin_info.nft_id.hex()}, doesn't support setting a DID.")
3481
+ continue
3482
+ if wallet_id in nft_dict:
3483
+ nft_dict[wallet_id].append(nft_coin_info)
3484
+ else:
3485
+ nft_dict[wallet_id] = [nft_coin_info]
3486
+ nft_ids.append(nft_coin_info.nft_id)
3487
+ first = True
3488
+ for wallet_id, nft_list in nft_dict.items():
3489
+ nft_wallet = self.service.wallet_state_manager.get_wallet(id=wallet_id, required_type=NFTWallet)
3490
+ if not first:
3491
+ await nft_wallet.set_bulk_nft_did(nft_list, did_id, action_scope, extra_conditions=extra_conditions)
3492
+ else:
3493
+ await nft_wallet.set_bulk_nft_did(
3494
+ nft_list, did_id, action_scope, fee, nft_ids, extra_conditions=extra_conditions
3495
+ )
3496
+ for coin in nft_list:
3497
+ coin_ids.append(coin.coin.name())
3498
+ first = False
3499
+
3500
+ for id in coin_ids:
3501
+ await nft_wallet.update_coin_status(id, True)
3502
+ for wallet_id in nft_dict.keys():
3503
+ self.service.wallet_state_manager.state_changed("nft_coin_did_set", wallet_id)
3504
+
3505
+ async with action_scope.use() as interface:
3506
+ return {
3507
+ "wallet_id": list(nft_dict.keys()),
3508
+ "success": True,
3509
+ "spend_bundle": None, # Backwards compat code in @tx_endpoint wrapper will fix this
3510
+ "tx_num": len(interface.side_effects.transactions),
3511
+ "transactions": None, # tx_endpoint wrapper will take care of this
3512
+ }
3513
+
3514
+ @tx_endpoint(push=True)
3515
+ async def nft_transfer_bulk(
3516
+ self,
3517
+ request: Dict[str, Any],
3518
+ action_scope: WalletActionScope,
3519
+ extra_conditions: Tuple[Condition, ...] = tuple(),
3520
+ ) -> EndpointResult:
3521
+ """
3522
+ Bulk transfer NFTs to an address.
3523
+ accepted `request` dict keys:
3524
+ - required `nft_coin_list`: [{"nft_coin_id": COIN_ID/NFT_ID, "wallet_id": WALLET_ID},....]
3525
+ - required `target_address`, Transfer NFTs to this address
3526
+ - optional `fee`, in mojos, defaults to 0
3527
+ :param request:
3528
+ :return:
3529
+ """
3530
+ if len(request["nft_coin_list"]) > MAX_NFT_CHUNK_SIZE:
3531
+ return {"success": False, "error": f"You can only transfer {MAX_NFT_CHUNK_SIZE} NFTs at once"}
3532
+ address = request["target_address"]
3533
+ if isinstance(address, str):
3534
+ puzzle_hash = decode_puzzle_hash(address)
3535
+ else:
3536
+ return dict(success=False, error="target_address parameter missing")
3537
+ nft_dict: Dict[uint32, List[NFTCoinInfo]] = {}
3538
+ coin_ids = []
3539
+ fee = uint64(request.get("fee", 0))
3540
+
3541
+ nft_wallet: NFTWallet
3542
+ for nft_coin in request["nft_coin_list"]:
3543
+ if "nft_coin_id" not in nft_coin or "wallet_id" not in nft_coin:
3544
+ log.error(f"Cannot transfer NFT :{nft_coin}, missing nft_coin_id or wallet_id.")
3545
+ continue
3546
+ wallet_id = uint32(nft_coin["wallet_id"])
3547
+ nft_wallet = self.service.wallet_state_manager.get_wallet(id=wallet_id, required_type=NFTWallet)
3548
+ nft_coin_id = nft_coin["nft_coin_id"]
3549
+ if nft_coin_id.startswith(AddressType.NFT.hrp(self.service.config)):
3550
+ nft_id = decode_puzzle_hash(nft_coin_id)
3551
+ nft_coin_info = await nft_wallet.get_nft(nft_id)
3552
+ else:
3553
+ nft_coin_id = bytes32.from_hexstr(nft_coin_id)
3554
+ nft_coin_info = await nft_wallet.get_nft_coin_by_id(nft_coin_id)
3555
+ assert nft_coin_info is not None
3556
+ if wallet_id in nft_dict:
3557
+ nft_dict[wallet_id].append(nft_coin_info)
3558
+ else:
3559
+ nft_dict[wallet_id] = [nft_coin_info]
3560
+ first = True
3561
+ for wallet_id, nft_list in nft_dict.items():
3562
+ nft_wallet = self.service.wallet_state_manager.get_wallet(id=wallet_id, required_type=NFTWallet)
3563
+ if not first:
3564
+ await nft_wallet.bulk_transfer_nft(
3565
+ nft_list, puzzle_hash, action_scope, extra_conditions=extra_conditions
3566
+ )
3567
+ else:
3568
+ await nft_wallet.bulk_transfer_nft(
3569
+ nft_list, puzzle_hash, action_scope, fee, extra_conditions=extra_conditions
3570
+ )
3571
+ for coin in nft_list:
3572
+ coin_ids.append(coin.coin.name())
3573
+ first = False
3574
+
3575
+ for id in coin_ids:
3576
+ await nft_wallet.update_coin_status(id, True)
3577
+ for wallet_id in nft_dict.keys():
3578
+ self.service.wallet_state_manager.state_changed("nft_coin_did_set", wallet_id)
3579
+ async with action_scope.use() as interface:
3580
+ return {
3581
+ "wallet_id": list(nft_dict.keys()),
3582
+ "success": True,
3583
+ "spend_bundle": None, # tx_endpoint wrapper will take care of this
3584
+ "tx_num": len(interface.side_effects.transactions),
3585
+ "transactions": None, # tx_endpoint wrapper will take care of this
3586
+ }
3587
+
3588
+ async def nft_get_by_did(self, request: Dict[str, Any]) -> EndpointResult:
3589
+ did_id: Optional[bytes32] = None
3590
+ if request.get("did_id", None) is not None:
3591
+ did_id = decode_puzzle_hash(request["did_id"])
3592
+ for wallet in self.service.wallet_state_manager.wallets.values():
3593
+ if isinstance(wallet, NFTWallet) and wallet.get_did() == did_id:
3594
+ return {"wallet_id": wallet.wallet_id, "success": True}
3595
+ return {"error": f"Cannot find a NFT wallet DID = {did_id}", "success": False}
3596
+
3597
+ async def nft_get_wallet_did(self, request: Dict[str, Any]) -> EndpointResult:
3598
+ wallet_id = uint32(request["wallet_id"])
3599
+ nft_wallet = self.service.wallet_state_manager.get_wallet(id=wallet_id, required_type=NFTWallet)
3600
+ did_bytes: Optional[bytes32] = nft_wallet.get_did()
3601
+ did_id = ""
3602
+ if did_bytes is not None:
3603
+ did_id = encode_puzzle_hash(did_bytes, AddressType.DID.hrp(self.service.config))
3604
+ return {"success": True, "did_id": None if len(did_id) == 0 else did_id}
3605
+
3606
+ async def nft_get_wallets_with_dids(self, request: Dict[str, Any]) -> EndpointResult:
3607
+ all_wallets = self.service.wallet_state_manager.wallets.values()
3608
+ did_wallets_by_did_id: Dict[bytes32, uint32] = {}
3609
+
3610
+ for wallet in all_wallets:
3611
+ if wallet.type() == WalletType.DECENTRALIZED_ID:
3612
+ assert isinstance(wallet, DIDWallet)
3613
+ if wallet.did_info.origin_coin is not None:
3614
+ did_wallets_by_did_id[wallet.did_info.origin_coin.name()] = wallet.id()
3615
+
3616
+ did_nft_wallets: List[Dict[str, Any]] = []
3617
+ for wallet in all_wallets:
3618
+ if isinstance(wallet, NFTWallet):
3619
+ nft_wallet_did: Optional[bytes32] = wallet.get_did()
3620
+ if nft_wallet_did is not None:
3621
+ did_wallet_id: uint32 = did_wallets_by_did_id.get(nft_wallet_did, uint32(0))
3622
+ if did_wallet_id == 0:
3623
+ log.warning(f"NFT wallet {wallet.id()} has DID {nft_wallet_did.hex()} but no DID wallet")
3624
+ else:
3625
+ did_nft_wallets.append(
3626
+ {
3627
+ "wallet_id": wallet.id(),
3628
+ "did_id": encode_puzzle_hash(nft_wallet_did, AddressType.DID.hrp(self.service.config)),
3629
+ "did_wallet_id": did_wallet_id,
3630
+ }
3631
+ )
3632
+ return {"success": True, "nft_wallets": did_nft_wallets}
3633
+
3634
+ async def nft_set_nft_status(self, request: Dict[str, Any]) -> EndpointResult:
3635
+ wallet_id: uint32 = uint32(request["wallet_id"])
3636
+ coin_id: bytes32 = bytes32.from_hexstr(request["coin_id"])
3637
+ status: bool = request["in_transaction"]
3638
+ assert self.service.wallet_state_manager is not None
3639
+ nft_wallet = self.service.wallet_state_manager.get_wallet(id=wallet_id, required_type=NFTWallet)
3640
+ await nft_wallet.update_coin_status(coin_id, status)
3641
+ return {"success": True}
3642
+
3643
+ @tx_endpoint(push=True)
3644
+ async def nft_transfer_nft(
3645
+ self,
3646
+ request: Dict[str, Any],
3647
+ action_scope: WalletActionScope,
3648
+ extra_conditions: Tuple[Condition, ...] = tuple(),
3649
+ ) -> EndpointResult:
3650
+ wallet_id = uint32(request["wallet_id"])
3651
+ address = request["target_address"]
3652
+ if isinstance(address, str):
3653
+ puzzle_hash = decode_puzzle_hash(address)
3654
+ else:
3655
+ return dict(success=False, error="target_address parameter missing")
3656
+ nft_wallet = self.service.wallet_state_manager.get_wallet(id=wallet_id, required_type=NFTWallet)
3657
+ try:
3658
+ nft_coin_id = request["nft_coin_id"]
3659
+ if nft_coin_id.startswith(AddressType.NFT.hrp(self.service.config)):
3660
+ nft_id = decode_puzzle_hash(nft_coin_id)
3661
+ nft_coin_info = await nft_wallet.get_nft(nft_id)
3662
+ else:
3663
+ nft_coin_id = bytes32.from_hexstr(nft_coin_id)
3664
+ nft_coin_info = await nft_wallet.get_nft_coin_by_id(nft_coin_id)
3665
+ assert nft_coin_info is not None
3666
+
3667
+ fee = uint64(request.get("fee", 0))
3668
+ await nft_wallet.generate_signed_transaction(
3669
+ [uint64(nft_coin_info.coin.amount)],
3670
+ [puzzle_hash],
3671
+ action_scope,
3672
+ coins={nft_coin_info.coin},
3673
+ fee=fee,
3674
+ new_owner=b"",
3675
+ new_did_inner_hash=b"",
3676
+ extra_conditions=extra_conditions,
3677
+ )
3678
+ await nft_wallet.update_coin_status(nft_coin_info.coin.name(), True)
3679
+ return {
3680
+ "wallet_id": wallet_id,
3681
+ "success": True,
3682
+ "spend_bundle": None, # tx_endpoint wrapper will take care of this
3683
+ "transactions": None, # tx_endpoint wrapper will take care of this
3684
+ }
3685
+ except Exception as e:
3686
+ log.exception(f"Failed to transfer NFT: {e}")
3687
+ return {"success": False, "error": str(e)}
3688
+
3689
+ async def nft_get_info(self, request: Dict[str, Any]) -> EndpointResult:
3690
+ if "coin_id" not in request:
3691
+ return {"success": False, "error": "Coin ID is required."}
3692
+ coin_id = request["coin_id"]
3693
+ if coin_id.startswith(AddressType.NFT.hrp(self.service.config)):
3694
+ coin_id = decode_puzzle_hash(coin_id)
3695
+ else:
3696
+ try:
3697
+ coin_id = bytes32.from_hexstr(coin_id)
3698
+ except ValueError:
3699
+ return {"success": False, "error": f"Invalid Coin ID format for 'coin_id': {request['coin_id']!r}"}
3700
+ # Get coin state
3701
+ peer = self.service.get_full_node_peer()
3702
+ coin_spend, coin_state = await self.get_latest_singleton_coin_spend(peer, coin_id, request.get("latest", True))
3703
+ # convert to NFTInfo
3704
+ # Check if the metadata is updated
3705
+ full_puzzle: Program = Program.from_bytes(bytes(coin_spend.puzzle_reveal))
3706
+
3707
+ uncurried_nft: Optional[UncurriedNFT] = UncurriedNFT.uncurry(*full_puzzle.uncurry())
3708
+ if uncurried_nft is None:
3709
+ return {"success": False, "error": "The coin is not a NFT."}
3710
+ metadata, p2_puzzle_hash = get_metadata_and_phs(uncurried_nft, coin_spend.solution)
3711
+ # Note: This is not the actual unspent NFT full puzzle.
3712
+ # There is no way to rebuild the full puzzle in a different wallet.
3713
+ # But it shouldn't have impact on generating the NFTInfo, since inner_puzzle is not used there.
3714
+ if uncurried_nft.supports_did:
3715
+ inner_puzzle = nft_puzzles.recurry_nft_puzzle(
3716
+ uncurried_nft, coin_spend.solution.to_program(), uncurried_nft.p2_puzzle
3717
+ )
3718
+ else:
3719
+ inner_puzzle = uncurried_nft.p2_puzzle
3720
+
3721
+ full_puzzle = nft_puzzles.create_full_puzzle(
3722
+ uncurried_nft.singleton_launcher_id,
3723
+ metadata,
3724
+ bytes32(uncurried_nft.metadata_updater_hash.as_atom()),
3725
+ inner_puzzle,
3726
+ )
3727
+
3728
+ # Get launcher coin
3729
+ launcher_coin: List[CoinState] = await self.service.wallet_state_manager.wallet_node.get_coin_state(
3730
+ [uncurried_nft.singleton_launcher_id], peer=peer
3731
+ )
3732
+ if launcher_coin is None or len(launcher_coin) < 1 or launcher_coin[0].spent_height is None:
3733
+ return {
3734
+ "success": False,
3735
+ "error": f"Launcher coin record 0x{uncurried_nft.singleton_launcher_id.hex()} not found",
3736
+ }
3737
+ minter_did = await self.service.wallet_state_manager.get_minter_did(launcher_coin[0].coin, peer)
3738
+
3739
+ nft_info: NFTInfo = await nft_puzzles.get_nft_info_from_puzzle(
3740
+ NFTCoinInfo(
3741
+ uncurried_nft.singleton_launcher_id,
3742
+ coin_state.coin,
3743
+ None,
3744
+ full_puzzle,
3745
+ uint32(launcher_coin[0].spent_height),
3746
+ minter_did,
3747
+ uint32(coin_state.created_height) if coin_state.created_height else uint32(0),
3748
+ ),
3749
+ self.service.wallet_state_manager.config,
3750
+ )
3751
+ # This is a bit hacky, it should just come out like this, but this works for this RPC
3752
+ nft_info = dataclasses.replace(nft_info, p2_address=p2_puzzle_hash)
3753
+ return {"success": True, "nft_info": nft_info}
3754
+
3755
+ @tx_endpoint(push=True)
3756
+ async def nft_add_uri(
3757
+ self,
3758
+ request: Dict[str, Any],
3759
+ action_scope: WalletActionScope,
3760
+ extra_conditions: Tuple[Condition, ...] = tuple(),
3761
+ ) -> EndpointResult:
3762
+ wallet_id = uint32(request["wallet_id"])
3763
+ # Note metadata updater can only add one uri for one field per spend.
3764
+ # If you want to add multiple uris for one field, you need to spend multiple times.
3765
+ nft_wallet = self.service.wallet_state_manager.get_wallet(id=wallet_id, required_type=NFTWallet)
3766
+ uri = request["uri"]
3767
+ key = request["key"]
3768
+ nft_coin_id = request["nft_coin_id"]
3769
+ if nft_coin_id.startswith(AddressType.NFT.hrp(self.service.config)):
3770
+ nft_coin_id = decode_puzzle_hash(nft_coin_id)
3771
+ else:
3772
+ nft_coin_id = bytes32.from_hexstr(nft_coin_id)
3773
+ nft_coin_info = await nft_wallet.get_nft_coin_by_id(nft_coin_id)
3774
+
3775
+ fee = uint64(request.get("fee", 0))
3776
+ await nft_wallet.update_metadata(
3777
+ nft_coin_info, key, uri, action_scope, fee=fee, extra_conditions=extra_conditions
3778
+ )
3779
+ return {
3780
+ "wallet_id": wallet_id,
3781
+ "success": True,
3782
+ "spend_bundle": None, # tx_endpoint wrapper will take care of this
3783
+ "transactions": None, # tx_endpoint wrapper will take care of this
3784
+ }
3785
+
3786
+ async def nft_calculate_royalties(self, request: Dict[str, Any]) -> EndpointResult:
3787
+ return NFTWallet.royalty_calculation(
3788
+ {
3789
+ asset["asset"]: (asset["royalty_address"], uint16(asset["royalty_percentage"]))
3790
+ for asset in request.get("royalty_assets", [])
3791
+ },
3792
+ {asset["asset"]: uint64(asset["amount"]) for asset in request.get("fungible_assets", [])},
3793
+ )
3794
+
3795
+ @tx_endpoint(push=False)
3796
+ async def nft_mint_bulk(
3797
+ self,
3798
+ request: Dict[str, Any],
3799
+ action_scope: WalletActionScope,
3800
+ extra_conditions: Tuple[Condition, ...] = tuple(),
3801
+ ) -> EndpointResult:
3802
+ if action_scope.config.push:
3803
+ raise ValueError("Automatic pushing of nft minting transactions not yet available") # pragma: no cover
3804
+ if await self.service.wallet_state_manager.synced() is False:
3805
+ raise ValueError("Wallet needs to be fully synced.")
3806
+ wallet_id = uint32(request["wallet_id"])
3807
+ nft_wallet = self.service.wallet_state_manager.get_wallet(id=wallet_id, required_type=NFTWallet)
3808
+ royalty_address = request.get("royalty_address", None)
3809
+ if isinstance(royalty_address, str) and royalty_address != "":
3810
+ royalty_puzhash = decode_puzzle_hash(royalty_address)
3811
+ elif royalty_address in [None, ""]:
3812
+ royalty_puzhash = await nft_wallet.standard_wallet.get_new_puzzlehash()
3813
+ else:
3814
+ royalty_puzhash = bytes32.from_hexstr(royalty_address)
3815
+ royalty_percentage = request.get("royalty_percentage", None)
3816
+ if royalty_percentage is None:
3817
+ royalty_percentage = uint16(0)
3818
+ else:
3819
+ royalty_percentage = uint16(int(royalty_percentage))
3820
+ metadata_list = []
3821
+ for meta in request["metadata_list"]:
3822
+ if "uris" not in meta.keys():
3823
+ return {"success": False, "error": "Data URIs is required"}
3824
+ if not isinstance(meta["uris"], list):
3825
+ return {"success": False, "error": "Data URIs must be a list"}
3826
+ if not isinstance(meta.get("meta_uris", []), list):
3827
+ return {"success": False, "error": "Metadata URIs must be a list"}
3828
+ if not isinstance(meta.get("license_uris", []), list):
3829
+ return {"success": False, "error": "License URIs must be a list"}
3830
+ nft_metadata = [
3831
+ ("u", meta["uris"]),
3832
+ ("h", hexstr_to_bytes(meta["hash"])),
3833
+ ("mu", meta.get("meta_uris", [])),
3834
+ ("lu", meta.get("license_uris", [])),
3835
+ ("sn", uint64(meta.get("edition_number", 1))),
3836
+ ("st", uint64(meta.get("edition_total", 1))),
3837
+ ]
3838
+ if "meta_hash" in meta and len(meta["meta_hash"]) > 0:
3839
+ nft_metadata.append(("mh", hexstr_to_bytes(meta["meta_hash"])))
3840
+ if "license_hash" in meta and len(meta["license_hash"]) > 0:
3841
+ nft_metadata.append(("lh", hexstr_to_bytes(meta["license_hash"])))
3842
+ metadata_program = Program.to(nft_metadata)
3843
+ metadata_dict = {
3844
+ "program": metadata_program,
3845
+ "royalty_pc": royalty_percentage,
3846
+ "royalty_ph": royalty_puzhash,
3847
+ }
3848
+ metadata_list.append(metadata_dict)
3849
+ target_address_list = request.get("target_list", None)
3850
+ target_list = []
3851
+ if target_address_list:
3852
+ for target in target_address_list:
3853
+ target_list.append(decode_puzzle_hash(target))
3854
+ mint_number_start = request.get("mint_number_start", 1)
3855
+ mint_total = request.get("mint_total", None)
3856
+ xch_coin_list = request.get("xch_coins", None)
3857
+ xch_coins = None
3858
+ if xch_coin_list:
3859
+ xch_coins = {Coin.from_json_dict(xch_coin) for xch_coin in xch_coin_list}
3860
+ xch_change_target = request.get("xch_change_target", None)
3861
+ if xch_change_target is not None:
3862
+ if xch_change_target[:2] == "xch":
3863
+ xch_change_ph = decode_puzzle_hash(xch_change_target)
3864
+ else:
3865
+ xch_change_ph = bytes32.from_hexstr(xch_change_target)
3866
+ else:
3867
+ xch_change_ph = None
3868
+ new_innerpuzhash = request.get("new_innerpuzhash", None)
3869
+ new_p2_puzhash = request.get("new_p2_puzhash", None)
3870
+ did_coin_dict = request.get("did_coin", None)
3871
+ if did_coin_dict:
3872
+ did_coin = Coin.from_json_dict(did_coin_dict)
3873
+ else:
3874
+ did_coin = None
3875
+ did_lineage_parent_hex = request.get("did_lineage_parent", None)
3876
+ if did_lineage_parent_hex:
3877
+ did_lineage_parent = bytes32.from_hexstr(did_lineage_parent_hex)
3878
+ else:
3879
+ did_lineage_parent = None
3880
+ mint_from_did = request.get("mint_from_did", False)
3881
+ fee = uint64(request.get("fee", 0))
3882
+
3883
+ if mint_from_did:
3884
+ await nft_wallet.mint_from_did(
3885
+ metadata_list,
3886
+ mint_number_start=mint_number_start,
3887
+ mint_total=mint_total,
3888
+ target_list=target_list,
3889
+ xch_coins=xch_coins,
3890
+ xch_change_ph=xch_change_ph,
3891
+ new_innerpuzhash=new_innerpuzhash,
3892
+ new_p2_puzhash=new_p2_puzhash,
3893
+ did_coin=did_coin,
3894
+ did_lineage_parent=did_lineage_parent,
3895
+ fee=fee,
3896
+ action_scope=action_scope,
3897
+ extra_conditions=extra_conditions,
3898
+ )
3899
+ else:
3900
+ await nft_wallet.mint_from_xch(
3901
+ metadata_list,
3902
+ mint_number_start=mint_number_start,
3903
+ mint_total=mint_total,
3904
+ target_list=target_list,
3905
+ xch_coins=xch_coins,
3906
+ xch_change_ph=xch_change_ph,
3907
+ fee=fee,
3908
+ action_scope=action_scope,
3909
+ extra_conditions=extra_conditions,
3910
+ )
3911
+ async with action_scope.use() as interface:
3912
+ sb = WalletSpendBundle.aggregate(
3913
+ [tx.spend_bundle for tx in interface.side_effects.transactions if tx.spend_bundle is not None]
3914
+ )
3915
+ nft_id_list = []
3916
+ for cs in sb.coin_spends:
3917
+ if cs.coin.puzzle_hash == nft_puzzles.LAUNCHER_PUZZLE_HASH:
3918
+ nft_id_list.append(encode_puzzle_hash(cs.coin.name(), AddressType.NFT.hrp(self.service.config)))
3919
+
3920
+ return {
3921
+ "success": True,
3922
+ "spend_bundle": None, # tx_endpoint wrapper will take care of this
3923
+ "nft_id_list": nft_id_list,
3924
+ "transactions": None, # tx_endpoint wrapper will take care of this
3925
+ }
3926
+
3927
+ async def get_coin_records(self, request: Dict[str, Any]) -> EndpointResult:
3928
+ parsed_request = GetCoinRecords.from_json_dict(request)
3929
+
3930
+ if parsed_request.limit != uint32.MAXIMUM and parsed_request.limit > self.max_get_coin_records_limit:
3931
+ raise ValueError(f"limit of {self.max_get_coin_records_limit} exceeded: {parsed_request.limit}")
3932
+
3933
+ for filter_name, filter in {
3934
+ "coin_id_filter": parsed_request.coin_id_filter,
3935
+ "puzzle_hash_filter": parsed_request.puzzle_hash_filter,
3936
+ "parent_coin_id_filter": parsed_request.parent_coin_id_filter,
3937
+ "amount_filter": parsed_request.amount_filter,
3938
+ }.items():
3939
+ if filter is None:
3940
+ continue
3941
+ if len(filter.values) > self.max_get_coin_records_filter_items:
3942
+ raise ValueError(
3943
+ f"{filter_name} max items {self.max_get_coin_records_filter_items} exceeded: {len(filter.values)}"
3944
+ )
3945
+
3946
+ result = await self.service.wallet_state_manager.coin_store.get_coin_records(
3947
+ offset=parsed_request.offset,
3948
+ limit=parsed_request.limit,
3949
+ wallet_id=parsed_request.wallet_id,
3950
+ wallet_type=None if parsed_request.wallet_type is None else WalletType(parsed_request.wallet_type),
3951
+ coin_type=None if parsed_request.coin_type is None else CoinType(parsed_request.coin_type),
3952
+ coin_id_filter=parsed_request.coin_id_filter,
3953
+ puzzle_hash_filter=parsed_request.puzzle_hash_filter,
3954
+ parent_coin_id_filter=parsed_request.parent_coin_id_filter,
3955
+ amount_filter=parsed_request.amount_filter,
3956
+ amount_range=parsed_request.amount_range,
3957
+ confirmed_range=parsed_request.confirmed_range,
3958
+ spent_range=parsed_request.spent_range,
3959
+ order=CoinRecordOrder(parsed_request.order),
3960
+ reverse=parsed_request.reverse,
3961
+ include_total_count=parsed_request.include_total_count,
3962
+ )
3963
+
3964
+ return {
3965
+ "coin_records": [coin_record.to_json_dict_parsed_metadata() for coin_record in result.records],
3966
+ "total_count": result.total_count,
3967
+ }
3968
+
3969
+ async def get_farmed_amount(self, request: Dict[str, Any]) -> EndpointResult:
3970
+ tx_records: List[TransactionRecord] = await self.service.wallet_state_manager.tx_store.get_farming_rewards()
3971
+ amount = 0
3972
+ pool_reward_amount = 0
3973
+ farmer_reward_amount = 0
3974
+ fee_amount = 0
3975
+ blocks_won = 0
3976
+ last_height_farmed = uint32(0)
3977
+ for record in tx_records:
3978
+ if record.wallet_id not in self.service.wallet_state_manager.wallets:
3979
+ continue
3980
+ if record.type == TransactionType.COINBASE_REWARD.value:
3981
+ if self.service.wallet_state_manager.wallets[record.wallet_id].type() == WalletType.POOLING_WALLET:
3982
+ # Don't add pool rewards for pool wallets.
3983
+ continue
3984
+ pool_reward_amount += record.amount
3985
+ height = record.height_farmed(self.service.constants.GENESIS_CHALLENGE)
3986
+ # .get_farming_rewards() above queries for only confirmed records. This
3987
+ # could be hinted by making TransactionRecord generic but streamable can't
3988
+ # handle that presently. Existing code would have raised an exception
3989
+ # anyway if this were to fail and we already have an assert below.
3990
+ assert height is not None
3991
+ if record.type == TransactionType.FEE_REWARD.value:
3992
+ base_farmer_reward = calculate_base_farmer_reward(height)
3993
+ fee_amount += record.amount - base_farmer_reward
3994
+ farmer_reward_amount += base_farmer_reward
3995
+ blocks_won += 1
3996
+ last_height_farmed = max(last_height_farmed, height)
3997
+ amount += record.amount
3998
+
3999
+ last_time_farmed = uint64(
4000
+ await self.service.get_timestamp_for_height(last_height_farmed) if last_height_farmed > 0 else 0
4001
+ )
4002
+ assert amount == pool_reward_amount + farmer_reward_amount + fee_amount
4003
+ return {
4004
+ "farmed_amount": amount,
4005
+ "pool_reward_amount": pool_reward_amount,
4006
+ "farmer_reward_amount": farmer_reward_amount,
4007
+ "fee_amount": fee_amount,
4008
+ "last_height_farmed": last_height_farmed,
4009
+ "last_time_farmed": last_time_farmed,
4010
+ "blocks_won": blocks_won,
4011
+ }
4012
+
4013
+ @tx_endpoint(push=False)
4014
+ async def create_signed_transaction(
4015
+ self,
4016
+ request: Dict[str, Any],
4017
+ action_scope: WalletActionScope,
4018
+ extra_conditions: Tuple[Condition, ...] = tuple(),
4019
+ hold_lock: bool = True,
4020
+ ) -> EndpointResult:
4021
+ if "wallet_id" in request:
4022
+ wallet_id = uint32(request["wallet_id"])
4023
+ wallet = self.service.wallet_state_manager.wallets[wallet_id]
4024
+ else:
4025
+ wallet = self.service.wallet_state_manager.main_wallet
4026
+
4027
+ assert isinstance(
4028
+ wallet, (Wallet, CATWallet, CRCATWallet)
4029
+ ), "create_signed_transaction only works for standard and CAT wallets"
4030
+
4031
+ if "additions" not in request or len(request["additions"]) < 1:
4032
+ raise ValueError("Specify additions list")
4033
+
4034
+ additions: List[Dict[str, Any]] = request["additions"]
4035
+ amount_0: uint64 = uint64(additions[0]["amount"])
4036
+ assert amount_0 <= self.service.constants.MAX_COIN_AMOUNT
4037
+ puzzle_hash_0 = bytes32.from_hexstr(additions[0]["puzzle_hash"])
4038
+ if len(puzzle_hash_0) != 32:
4039
+ raise ValueError(f"Address must be 32 bytes. {puzzle_hash_0.hex()}")
4040
+
4041
+ memos_0 = [] if "memos" not in additions[0] else [mem.encode("utf-8") for mem in additions[0]["memos"]]
4042
+
4043
+ additional_outputs: List[Payment] = []
4044
+ for addition in additions[1:]:
4045
+ receiver_ph = bytes32.from_hexstr(addition["puzzle_hash"])
4046
+ if len(receiver_ph) != 32:
4047
+ raise ValueError(f"Address must be 32 bytes. {receiver_ph.hex()}")
4048
+ amount = uint64(addition["amount"])
4049
+ if amount > self.service.constants.MAX_COIN_AMOUNT:
4050
+ raise ValueError(f"Coin amount cannot exceed {self.service.constants.MAX_COIN_AMOUNT}")
4051
+ memos = [] if "memos" not in addition else [mem.encode("utf-8") for mem in addition["memos"]]
4052
+ additional_outputs.append(Payment(receiver_ph, amount, memos))
4053
+
4054
+ fee: uint64 = uint64(request.get("fee", 0))
4055
+
4056
+ coins = None
4057
+ if "coins" in request and len(request["coins"]) > 0:
4058
+ coins = {Coin.from_json_dict(coin_json) for coin_json in request["coins"]}
4059
+
4060
+ async def _generate_signed_transaction() -> EndpointResult:
4061
+ if isinstance(wallet, Wallet):
4062
+ await wallet.generate_signed_transaction(
4063
+ amount_0,
4064
+ bytes32(puzzle_hash_0),
4065
+ action_scope,
4066
+ fee,
4067
+ coins=coins,
4068
+ primaries=additional_outputs,
4069
+ memos=memos_0,
4070
+ extra_conditions=(
4071
+ *extra_conditions,
4072
+ *(
4073
+ AssertCoinAnnouncement(
4074
+ asserted_id=bytes32.from_hexstr(ca["coin_id"]),
4075
+ asserted_msg=(
4076
+ hexstr_to_bytes(ca["message"])
4077
+ if request.get("morph_bytes") is None
4078
+ else std_hash(hexstr_to_bytes(ca["morph_bytes"]) + hexstr_to_bytes(ca["message"]))
4079
+ ),
4080
+ )
4081
+ for ca in request.get("coin_announcements", [])
4082
+ ),
4083
+ *(
4084
+ AssertPuzzleAnnouncement(
4085
+ asserted_ph=bytes32.from_hexstr(pa["puzzle_hash"]),
4086
+ asserted_msg=(
4087
+ hexstr_to_bytes(pa["message"])
4088
+ if request.get("morph_bytes") is None
4089
+ else std_hash(hexstr_to_bytes(pa["morph_bytes"]) + hexstr_to_bytes(pa["message"]))
4090
+ ),
4091
+ )
4092
+ for pa in request.get("puzzle_announcements", [])
4093
+ ),
4094
+ ),
4095
+ )
4096
+ # tx_endpoint wrapper will take care of this
4097
+ return {"signed_txs": None, "signed_tx": None, "transactions": None}
4098
+
4099
+ else:
4100
+ assert isinstance(wallet, CATWallet)
4101
+
4102
+ await wallet.generate_signed_transaction(
4103
+ [amount_0] + [output.amount for output in additional_outputs],
4104
+ [bytes32(puzzle_hash_0)] + [output.puzzle_hash for output in additional_outputs],
4105
+ action_scope,
4106
+ fee,
4107
+ coins=coins,
4108
+ memos=[memos_0] + [output.memos for output in additional_outputs],
4109
+ extra_conditions=(
4110
+ *extra_conditions,
4111
+ *(
4112
+ AssertCoinAnnouncement(
4113
+ asserted_id=bytes32.from_hexstr(ca["coin_id"]),
4114
+ asserted_msg=(
4115
+ hexstr_to_bytes(ca["message"])
4116
+ if request.get("morph_bytes") is None
4117
+ else std_hash(hexstr_to_bytes(ca["morph_bytes"]) + hexstr_to_bytes(ca["message"]))
4118
+ ),
4119
+ )
4120
+ for ca in request.get("coin_announcements", [])
4121
+ ),
4122
+ *(
4123
+ AssertPuzzleAnnouncement(
4124
+ asserted_ph=bytes32.from_hexstr(pa["puzzle_hash"]),
4125
+ asserted_msg=(
4126
+ hexstr_to_bytes(pa["message"])
4127
+ if request.get("morph_bytes") is None
4128
+ else std_hash(hexstr_to_bytes(pa["morph_bytes"]) + hexstr_to_bytes(pa["message"]))
4129
+ ),
4130
+ )
4131
+ for pa in request.get("puzzle_announcements", [])
4132
+ ),
4133
+ ),
4134
+ )
4135
+ # tx_endpoint wrapper will take care of this
4136
+ return {"signed_txs": None, "signed_tx": None, "transactions": None}
4137
+
4138
+ if hold_lock:
4139
+ async with self.service.wallet_state_manager.lock:
4140
+ return await _generate_signed_transaction()
4141
+ else:
4142
+ return await _generate_signed_transaction()
4143
+
4144
+ ##########################################################################################
4145
+ # Pool Wallet
4146
+ ##########################################################################################
4147
+ @tx_endpoint(push=True)
4148
+ async def pw_join_pool(
4149
+ self,
4150
+ request: Dict[str, Any],
4151
+ action_scope: WalletActionScope,
4152
+ extra_conditions: Tuple[Condition, ...] = tuple(),
4153
+ ) -> EndpointResult:
4154
+ fee = uint64(request.get("fee", 0))
4155
+ wallet_id = uint32(request["wallet_id"])
4156
+ wallet = self.service.wallet_state_manager.get_wallet(id=wallet_id, required_type=PoolWallet)
4157
+
4158
+ pool_wallet_info: PoolWalletInfo = await wallet.get_current_state()
4159
+ owner_pubkey = pool_wallet_info.current.owner_pubkey
4160
+ target_puzzlehash = None
4161
+
4162
+ if await self.service.wallet_state_manager.synced() is False:
4163
+ raise ValueError("Wallet needs to be fully synced.")
4164
+
4165
+ if "target_puzzlehash" in request:
4166
+ target_puzzlehash = bytes32.from_hexstr(request["target_puzzlehash"])
4167
+ assert target_puzzlehash is not None
4168
+ new_target_state: PoolState = create_pool_state(
4169
+ FARMING_TO_POOL,
4170
+ target_puzzlehash,
4171
+ owner_pubkey,
4172
+ request["pool_url"],
4173
+ uint32(request["relative_lock_height"]),
4174
+ )
4175
+
4176
+ async with self.service.wallet_state_manager.lock:
4177
+ total_fee = await wallet.join_pool(new_target_state, fee, action_scope)
4178
+ return {
4179
+ "total_fee": total_fee,
4180
+ "transaction": None, # tx_endpoint wrapper will take care of this
4181
+ "fee_transaction": None, # tx_endpoint wrapper will take care of this
4182
+ "transactions": None, # tx_endpoint wrapper will take care of this
4183
+ }
4184
+
4185
+ @tx_endpoint(push=True)
4186
+ async def pw_self_pool(
4187
+ self,
4188
+ request: Dict[str, Any],
4189
+ action_scope: WalletActionScope,
4190
+ extra_conditions: Tuple[Condition, ...] = tuple(),
4191
+ ) -> EndpointResult:
4192
+ # Leaving a pool requires two state transitions.
4193
+ # First we transition to PoolSingletonState.LEAVING_POOL
4194
+ # Then we transition to FARMING_TO_POOL or SELF_POOLING
4195
+ fee = uint64(request.get("fee", 0))
4196
+ wallet_id = uint32(request["wallet_id"])
4197
+ wallet = self.service.wallet_state_manager.get_wallet(id=wallet_id, required_type=PoolWallet)
4198
+
4199
+ if await self.service.wallet_state_manager.synced() is False:
4200
+ raise ValueError("Wallet needs to be fully synced.")
4201
+
4202
+ async with self.service.wallet_state_manager.lock:
4203
+ total_fee = await wallet.self_pool(fee, action_scope)
4204
+ return {
4205
+ "total_fee": total_fee,
4206
+ "transaction": None, # tx_endpoint wrapper will take care of this
4207
+ "fee_transaction": None, # tx_endpoint wrapper will take care of this
4208
+ "transactions": None, # tx_endpoint wrapper will take care of this
4209
+ }
4210
+
4211
+ @tx_endpoint(push=True)
4212
+ async def pw_absorb_rewards(
4213
+ self,
4214
+ request: Dict[str, Any],
4215
+ action_scope: WalletActionScope,
4216
+ extra_conditions: Tuple[Condition, ...] = tuple(),
4217
+ ) -> EndpointResult:
4218
+ """Perform a sweep of the p2_singleton rewards controlled by the pool wallet singleton"""
4219
+ if await self.service.wallet_state_manager.synced() is False:
4220
+ raise ValueError("Wallet needs to be fully synced before collecting rewards")
4221
+ fee = uint64(request.get("fee", 0))
4222
+ max_spends_in_tx = request.get("max_spends_in_tx", None)
4223
+ wallet_id = uint32(request["wallet_id"])
4224
+ wallet = self.service.wallet_state_manager.get_wallet(id=wallet_id, required_type=PoolWallet)
4225
+
4226
+ assert isinstance(wallet, PoolWallet)
4227
+ async with self.service.wallet_state_manager.lock:
4228
+ await wallet.claim_pool_rewards(fee, max_spends_in_tx, action_scope)
4229
+ state: PoolWalletInfo = await wallet.get_current_state()
4230
+ return {
4231
+ "state": state.to_json_dict(),
4232
+ "transaction": None, # tx_endpoint wrapper will take care of this
4233
+ "fee_transaction": None, # tx_endpoint wrapper will take care of this
4234
+ "transactions": None, # tx_endpoint wrapper will take care of this
4235
+ }
4236
+
4237
+ async def pw_status(self, request: Dict[str, Any]) -> EndpointResult:
4238
+ """Return the complete state of the Pool wallet with id `request["wallet_id"]`"""
4239
+ wallet_id = uint32(request["wallet_id"])
4240
+ wallet = self.service.wallet_state_manager.get_wallet(id=wallet_id, required_type=PoolWallet)
4241
+
4242
+ assert isinstance(wallet, PoolWallet)
4243
+ state: PoolWalletInfo = await wallet.get_current_state()
4244
+ unconfirmed_transactions: List[TransactionRecord] = await wallet.get_unconfirmed_transactions()
4245
+ return {
4246
+ "state": state.to_json_dict(),
4247
+ "unconfirmed_transactions": unconfirmed_transactions,
4248
+ }
4249
+
4250
+ ##########################################################################################
4251
+ # DataLayer Wallet
4252
+ ##########################################################################################
4253
+ @tx_endpoint(push=True)
4254
+ async def create_new_dl(
4255
+ self,
4256
+ request: Dict[str, Any],
4257
+ action_scope: WalletActionScope,
4258
+ extra_conditions: Tuple[Condition, ...] = tuple(),
4259
+ ) -> EndpointResult:
4260
+ """Initialize the DataLayer Wallet (only one can exist)"""
4261
+ if self.service.wallet_state_manager is None:
4262
+ raise ValueError("The wallet service is not currently initialized")
4263
+
4264
+ try:
4265
+ dl_wallet = self.service.wallet_state_manager.get_dl_wallet()
4266
+ except ValueError:
4267
+ async with self.service.wallet_state_manager.lock:
4268
+ dl_wallet = await DataLayerWallet.create_new_dl_wallet(self.service.wallet_state_manager)
4269
+
4270
+ try:
4271
+ async with self.service.wallet_state_manager.lock:
4272
+ launcher_id = await dl_wallet.generate_new_reporter(
4273
+ bytes32.from_hexstr(request["root"]),
4274
+ action_scope,
4275
+ fee=request.get("fee", uint64(0)),
4276
+ extra_conditions=extra_conditions,
4277
+ )
4278
+ except ValueError as e:
4279
+ log.error(f"Error while generating new reporter {e}")
4280
+ return {"success": False, "error": str(e)}
4281
+
4282
+ return {
4283
+ "transactions": None, # tx_endpoint wrapper will take care of this
4284
+ "launcher_id": launcher_id,
4285
+ }
4286
+
4287
+ async def dl_track_new(self, request: Dict[str, Any]) -> EndpointResult:
4288
+ """Initialize the DataLayer Wallet (only one can exist)"""
4289
+ if self.service.wallet_state_manager is None:
4290
+ raise ValueError("The wallet service is not currently initialized")
4291
+ try:
4292
+ dl_wallet = self.service.wallet_state_manager.get_dl_wallet()
4293
+ except ValueError:
4294
+ async with self.service.wallet_state_manager.lock:
4295
+ dl_wallet = await DataLayerWallet.create_new_dl_wallet(
4296
+ self.service.wallet_state_manager,
4297
+ )
4298
+ peer_list = self.service.get_full_node_peers_in_order()
4299
+ peer_length = len(peer_list)
4300
+ for i, peer in enumerate(peer_list):
4301
+ try:
4302
+ await dl_wallet.track_new_launcher_id(
4303
+ bytes32.from_hexstr(request["launcher_id"]),
4304
+ peer,
4305
+ )
4306
+ except LauncherCoinNotFoundError as e:
4307
+ if i == peer_length - 1:
4308
+ raise e # raise the error if we've tried all peers
4309
+ continue # try some other peers, maybe someone has it
4310
+ return {}
4311
+
4312
+ async def dl_stop_tracking(self, request: Dict[str, Any]) -> EndpointResult:
4313
+ """Initialize the DataLayer Wallet (only one can exist)"""
4314
+ if self.service.wallet_state_manager is None:
4315
+ raise ValueError("The wallet service is not currently initialized")
4316
+
4317
+ dl_wallet = self.service.wallet_state_manager.get_dl_wallet()
4318
+ await dl_wallet.stop_tracking_singleton(bytes32.from_hexstr(request["launcher_id"]))
4319
+ return {}
4320
+
4321
+ async def dl_latest_singleton(self, request: Dict[str, Any]) -> EndpointResult:
4322
+ """Get the singleton record for the latest singleton of a launcher ID"""
4323
+ if self.service.wallet_state_manager is None:
4324
+ raise ValueError("The wallet service is not currently initialized")
4325
+
4326
+ only_confirmed = request.get("only_confirmed")
4327
+ if only_confirmed is None:
4328
+ only_confirmed = False
4329
+ wallet = self.service.wallet_state_manager.get_dl_wallet()
4330
+ record = await wallet.get_latest_singleton(bytes32.from_hexstr(request["launcher_id"]), only_confirmed)
4331
+ return {"singleton": None if record is None else record.to_json_dict()}
4332
+
4333
+ async def dl_singletons_by_root(self, request: Dict[str, Any]) -> EndpointResult:
4334
+ """Get the singleton records that contain the specified root"""
4335
+ if self.service.wallet_state_manager is None:
4336
+ raise ValueError("The wallet service is not currently initialized")
4337
+
4338
+ wallet = self.service.wallet_state_manager.get_dl_wallet()
4339
+ records = await wallet.get_singletons_by_root(
4340
+ bytes32.from_hexstr(request["launcher_id"]), bytes32.from_hexstr(request["root"])
4341
+ )
4342
+ records_json = [rec.to_json_dict() for rec in records]
4343
+ return {"singletons": records_json}
4344
+
4345
+ @tx_endpoint(push=True)
4346
+ async def dl_update_root(
4347
+ self,
4348
+ request: Dict[str, Any],
4349
+ action_scope: WalletActionScope,
4350
+ extra_conditions: Tuple[Condition, ...] = tuple(),
4351
+ ) -> EndpointResult:
4352
+ """Get the singleton record for the latest singleton of a launcher ID"""
4353
+ if self.service.wallet_state_manager is None:
4354
+ raise ValueError("The wallet service is not currently initialized")
4355
+
4356
+ wallet = self.service.wallet_state_manager.get_dl_wallet()
4357
+ async with self.service.wallet_state_manager.lock:
4358
+ await wallet.create_update_state_spend(
4359
+ bytes32.from_hexstr(request["launcher_id"]),
4360
+ bytes32.from_hexstr(request["new_root"]),
4361
+ action_scope,
4362
+ fee=uint64(request.get("fee", 0)),
4363
+ extra_conditions=extra_conditions,
4364
+ )
4365
+
4366
+ return {
4367
+ "tx_record": None, # tx_endpoint wrapper will take care of this
4368
+ "transactions": None, # tx_endpoint wrapper will take care of this
4369
+ }
4370
+
4371
+ @tx_endpoint(push=True)
4372
+ async def dl_update_multiple(
4373
+ self,
4374
+ request: Dict[str, Any],
4375
+ action_scope: WalletActionScope,
4376
+ extra_conditions: Tuple[Condition, ...] = tuple(),
4377
+ ) -> EndpointResult:
4378
+ """Update multiple singletons with new merkle roots"""
4379
+ if self.service.wallet_state_manager is None:
4380
+ return {"success": False, "error": "not_initialized"}
4381
+
4382
+ wallet = self.service.wallet_state_manager.get_dl_wallet()
4383
+ async with self.service.wallet_state_manager.lock:
4384
+ # TODO: This method should optionally link the singletons with announcements.
4385
+ # Otherwise spends are vulnerable to signature subtraction.
4386
+ fee_per_launcher = uint64(request.get("fee", 0) // len(request["updates"]))
4387
+ for launcher, root in request["updates"].items():
4388
+ await wallet.create_update_state_spend(
4389
+ bytes32.from_hexstr(launcher),
4390
+ bytes32.from_hexstr(root),
4391
+ action_scope,
4392
+ fee=fee_per_launcher,
4393
+ extra_conditions=extra_conditions,
4394
+ )
4395
+
4396
+ return {
4397
+ "transactions": None, # tx_endpoint wrapper will take care of this
4398
+ }
4399
+
4400
+ async def dl_history(self, request: Dict[str, Any]) -> EndpointResult:
4401
+ """Get the singleton record for the latest singleton of a launcher ID"""
4402
+ if self.service.wallet_state_manager is None:
4403
+ raise ValueError("The wallet service is not currently initialized")
4404
+
4405
+ wallet = self.service.wallet_state_manager.get_dl_wallet()
4406
+ additional_kwargs = {}
4407
+
4408
+ if "min_generation" in request:
4409
+ additional_kwargs["min_generation"] = uint32(request["min_generation"])
4410
+ if "max_generation" in request:
4411
+ additional_kwargs["max_generation"] = uint32(request["max_generation"])
4412
+ if "num_results" in request:
4413
+ additional_kwargs["num_results"] = uint32(request["num_results"])
4414
+
4415
+ history = await wallet.get_history(bytes32.from_hexstr(request["launcher_id"]), **additional_kwargs)
4416
+ history_json = [rec.to_json_dict() for rec in history]
4417
+ return {"history": history_json, "count": len(history_json)}
4418
+
4419
+ async def dl_owned_singletons(self, request: Dict[str, Any]) -> EndpointResult:
4420
+ """Get all owned singleton records"""
4421
+ if self.service.wallet_state_manager is None:
4422
+ raise ValueError("The wallet service is not currently initialized")
4423
+
4424
+ try:
4425
+ wallet = self.service.wallet_state_manager.get_dl_wallet()
4426
+ except ValueError:
4427
+ return {"success": False, "error": "no DataLayer wallet available"}
4428
+
4429
+ singletons = await wallet.get_owned_singletons()
4430
+ singletons_json = [singleton.to_json_dict() for singleton in singletons]
4431
+
4432
+ return {"singletons": singletons_json, "count": len(singletons_json)}
4433
+
4434
+ async def dl_get_mirrors(self, request: Dict[str, Any]) -> EndpointResult:
4435
+ """Get all of the mirrors for a specific singleton"""
4436
+ if self.service.wallet_state_manager is None:
4437
+ raise ValueError("The wallet service is not currently initialized")
4438
+
4439
+ wallet = self.service.wallet_state_manager.get_dl_wallet()
4440
+ mirrors_json = []
4441
+ for mirror in await wallet.get_mirrors_for_launcher(bytes32.from_hexstr(request["launcher_id"])):
4442
+ mirrors_json.append(mirror.to_json_dict())
4443
+
4444
+ return {"mirrors": mirrors_json}
4445
+
4446
+ @tx_endpoint(push=True)
4447
+ async def dl_new_mirror(
4448
+ self,
4449
+ request: Dict[str, Any],
4450
+ action_scope: WalletActionScope,
4451
+ extra_conditions: Tuple[Condition, ...] = tuple(),
4452
+ ) -> EndpointResult:
4453
+ """Add a new on chain message for a specific singleton"""
4454
+ if self.service.wallet_state_manager is None:
4455
+ raise ValueError("The wallet service is not currently initialized")
4456
+
4457
+ dl_wallet = self.service.wallet_state_manager.get_dl_wallet()
4458
+ async with self.service.wallet_state_manager.lock:
4459
+ await dl_wallet.create_new_mirror(
4460
+ bytes32.from_hexstr(request["launcher_id"]),
4461
+ request["amount"],
4462
+ [bytes(url, "utf8") for url in request["urls"]],
4463
+ action_scope,
4464
+ fee=request.get("fee", uint64(0)),
4465
+ extra_conditions=extra_conditions,
4466
+ )
4467
+
4468
+ return {
4469
+ "transactions": None, # tx_endpoint wrapper will take care of this
4470
+ }
4471
+
4472
+ @tx_endpoint(push=True)
4473
+ async def dl_delete_mirror(
4474
+ self,
4475
+ request: Dict[str, Any],
4476
+ action_scope: WalletActionScope,
4477
+ extra_conditions: Tuple[Condition, ...] = tuple(),
4478
+ ) -> EndpointResult:
4479
+ """Remove an existing mirror for a specific singleton"""
4480
+ if self.service.wallet_state_manager is None:
4481
+ raise ValueError("The wallet service is not currently initialized")
4482
+
4483
+ dl_wallet = self.service.wallet_state_manager.get_dl_wallet()
4484
+
4485
+ async with self.service.wallet_state_manager.lock:
4486
+ await dl_wallet.delete_mirror(
4487
+ bytes32.from_hexstr(request["coin_id"]),
4488
+ self.service.get_full_node_peer(),
4489
+ action_scope,
4490
+ fee=request.get("fee", uint64(0)),
4491
+ extra_conditions=extra_conditions,
4492
+ )
4493
+
4494
+ return {
4495
+ "transactions": None, # tx_endpoint wrapper will take care of this
4496
+ }
4497
+
4498
+ async def dl_verify_proof(
4499
+ self,
4500
+ request: Dict[str, Any],
4501
+ ) -> EndpointResult:
4502
+ """Verify a proof of inclusion for a DL singleton"""
4503
+ res = await dl_verify_proof(
4504
+ request,
4505
+ peer=self.service.get_full_node_peer(),
4506
+ wallet_node=self.service.wallet_state_manager.wallet_node,
4507
+ )
4508
+
4509
+ return res
4510
+
4511
+ ##########################################################################################
4512
+ # Verified Credential
4513
+ ##########################################################################################
4514
+ @tx_endpoint(push=True)
4515
+ async def vc_mint(
4516
+ self,
4517
+ request: Dict[str, Any],
4518
+ action_scope: WalletActionScope,
4519
+ extra_conditions: Tuple[Condition, ...] = tuple(),
4520
+ ) -> EndpointResult:
4521
+ """
4522
+ Mint a verified credential using the assigned DID
4523
+ :param request: We require 'did_id' that will be minting the VC and options for a new 'target_address' as well
4524
+ as a 'fee' for the mint tx
4525
+ :return: a 'vc_record' containing all the information of the soon-to-be-confirmed vc as well as any relevant
4526
+ 'transactions'
4527
+ """
4528
+
4529
+ @streamable
4530
+ @dataclasses.dataclass(frozen=True)
4531
+ class VCMint(Streamable):
4532
+ did_id: str
4533
+ target_address: Optional[str] = None
4534
+ fee: uint64 = uint64(0)
4535
+
4536
+ parsed_request = VCMint.from_json_dict(request)
4537
+
4538
+ did_id = decode_puzzle_hash(parsed_request.did_id)
4539
+ puzhash: Optional[bytes32] = None
4540
+ if parsed_request.target_address is not None:
4541
+ puzhash = decode_puzzle_hash(parsed_request.target_address)
4542
+
4543
+ vc_wallet: VCWallet = await self.service.wallet_state_manager.get_or_create_vc_wallet()
4544
+ vc_record = await vc_wallet.launch_new_vc(
4545
+ did_id, action_scope, puzhash, parsed_request.fee, extra_conditions=extra_conditions
4546
+ )
4547
+ return {
4548
+ "vc_record": vc_record.to_json_dict(),
4549
+ "transactions": None, # tx_endpoint wrapper will take care of this
4550
+ }
4551
+
4552
+ async def vc_get(self, request: Dict[str, Any]) -> EndpointResult:
4553
+ """
4554
+ Given a launcher ID get the verified credential
4555
+ :param request: the 'vc_id' launcher id of a verifiable credential
4556
+ :return: the 'vc_record' representing the specified verifiable credential
4557
+ """
4558
+
4559
+ @streamable
4560
+ @dataclasses.dataclass(frozen=True)
4561
+ class VCGet(Streamable):
4562
+ vc_id: bytes32
4563
+
4564
+ parsed_request = VCGet.from_json_dict(request)
4565
+
4566
+ vc_record = await self.service.wallet_state_manager.vc_store.get_vc_record(parsed_request.vc_id)
4567
+ return {"vc_record": vc_record}
4568
+
4569
+ async def vc_get_list(self, request: Dict[str, Any]) -> EndpointResult:
4570
+ """
4571
+ Get a list of verified credentials
4572
+ :param request: optional parameters for pagination 'start' and 'count'
4573
+ :return: all 'vc_records' in the specified range and any 'proofs' associated with the roots contained within
4574
+ """
4575
+
4576
+ @streamable
4577
+ @dataclasses.dataclass(frozen=True)
4578
+ class VCGetList(Streamable):
4579
+ start: uint32 = uint32(0)
4580
+ end: uint32 = uint32(50)
4581
+
4582
+ parsed_request = VCGetList.from_json_dict(request)
4583
+
4584
+ vc_list = await self.service.wallet_state_manager.vc_store.get_vc_record_list(
4585
+ parsed_request.start, parsed_request.end
4586
+ )
4587
+ return {
4588
+ "vc_records": [{"coin_id": "0x" + vc.vc.coin.name().hex(), **vc.to_json_dict()} for vc in vc_list],
4589
+ "proofs": {
4590
+ rec.vc.proof_hash.hex(): None if fetched_proof is None else fetched_proof.key_value_pairs
4591
+ for rec in vc_list
4592
+ if rec.vc.proof_hash is not None
4593
+ for fetched_proof in (
4594
+ await self.service.wallet_state_manager.vc_store.get_proofs_for_root(rec.vc.proof_hash),
4595
+ )
4596
+ },
4597
+ }
4598
+
4599
+ @tx_endpoint(push=True)
4600
+ async def vc_spend(
4601
+ self,
4602
+ request: Dict[str, Any],
4603
+ action_scope: WalletActionScope,
4604
+ extra_conditions: Tuple[Condition, ...] = tuple(),
4605
+ ) -> EndpointResult:
4606
+ """
4607
+ Spend a verified credential
4608
+ :param request: Required 'vc_id' launcher id of the vc we wish to spend. Optional parameters for a 'new_puzhash'
4609
+ for the vc to end up at and 'new_proof_hash' & 'provider_inner_puzhash' which can be used to update the vc's
4610
+ proofs. Also standard 'fee' & 'reuse_puzhash' parameters for the transaction.
4611
+ :return: a list of all relevant 'transactions' (TransactionRecord) that this spend generates (VC TX + fee TX)
4612
+ """
4613
+
4614
+ @streamable
4615
+ @dataclasses.dataclass(frozen=True)
4616
+ class VCSpend(Streamable):
4617
+ vc_id: bytes32
4618
+ new_puzhash: Optional[bytes32] = None
4619
+ new_proof_hash: Optional[bytes32] = None
4620
+ provider_inner_puzhash: Optional[bytes32] = None
4621
+ fee: uint64 = uint64(0)
4622
+
4623
+ parsed_request = VCSpend.from_json_dict(request)
4624
+
4625
+ vc_wallet: VCWallet = await self.service.wallet_state_manager.get_or_create_vc_wallet()
4626
+
4627
+ await vc_wallet.generate_signed_transaction(
4628
+ parsed_request.vc_id,
4629
+ action_scope,
4630
+ parsed_request.fee,
4631
+ parsed_request.new_puzhash,
4632
+ new_proof_hash=parsed_request.new_proof_hash,
4633
+ provider_inner_puzhash=parsed_request.provider_inner_puzhash,
4634
+ extra_conditions=extra_conditions,
4635
+ )
4636
+
4637
+ return {
4638
+ "transactions": None, # tx_endpoint wrapper will take care of this
4639
+ }
4640
+
4641
+ async def vc_add_proofs(self, request: Dict[str, Any]) -> EndpointResult:
4642
+ """
4643
+ Add a set of proofs to the DB that can be used when spending a VC. VCs are near useless until their proofs have
4644
+ been added.
4645
+ :param request: 'proofs' is a dictionary of key/value pairs
4646
+ :return:
4647
+ """
4648
+ vc_wallet: VCWallet = await self.service.wallet_state_manager.get_or_create_vc_wallet()
4649
+
4650
+ await vc_wallet.store.add_vc_proofs(VCProofs(request["proofs"]))
4651
+
4652
+ return {}
4653
+
4654
+ async def vc_get_proofs_for_root(self, request: Dict[str, Any]) -> EndpointResult:
4655
+ """
4656
+ Given a specified vc root, get any proofs associated with that root.
4657
+ :param request: must specify 'root' representing the tree hash of some set of proofs
4658
+ :return: a dictionary of root hashes mapped to dictionaries of key value pairs of 'proofs'
4659
+ """
4660
+
4661
+ @streamable
4662
+ @dataclasses.dataclass(frozen=True)
4663
+ class VCGetProofsForRoot(Streamable):
4664
+ root: bytes32
4665
+
4666
+ parsed_request = VCGetProofsForRoot.from_json_dict(request)
4667
+ vc_wallet: VCWallet = await self.service.wallet_state_manager.get_or_create_vc_wallet()
4668
+
4669
+ vc_proofs: Optional[VCProofs] = await vc_wallet.store.get_proofs_for_root(parsed_request.root)
4670
+ if vc_proofs is None:
4671
+ raise ValueError("no proofs found for specified root") # pragma: no cover
4672
+ return {"proofs": vc_proofs.key_value_pairs}
4673
+
4674
+ @tx_endpoint(push=True)
4675
+ async def vc_revoke(
4676
+ self,
4677
+ request: Dict[str, Any],
4678
+ action_scope: WalletActionScope,
4679
+ extra_conditions: Tuple[Condition, ...] = tuple(),
4680
+ ) -> EndpointResult:
4681
+ """
4682
+ Revoke an on chain VC provided the correct DID is available
4683
+ :param request: required 'vc_parent_id' for the VC coin. Standard transaction params 'fee' & 'reuse_puzhash'.
4684
+ :return: a list of all relevant 'transactions' (TransactionRecord) that this spend generates (VC TX + fee TX)
4685
+ """
4686
+
4687
+ @streamable
4688
+ @dataclasses.dataclass(frozen=True)
4689
+ class VCRevoke(Streamable):
4690
+ vc_parent_id: bytes32
4691
+ fee: uint64 = uint64(0)
4692
+
4693
+ parsed_request = VCRevoke.from_json_dict(request)
4694
+ vc_wallet: VCWallet = await self.service.wallet_state_manager.get_or_create_vc_wallet()
4695
+
4696
+ await vc_wallet.revoke_vc(
4697
+ parsed_request.vc_parent_id,
4698
+ self.service.get_full_node_peer(),
4699
+ action_scope,
4700
+ parsed_request.fee,
4701
+ extra_conditions=extra_conditions,
4702
+ )
4703
+
4704
+ return {
4705
+ "transactions": None, # tx_endpoint wrapper will take care of this
4706
+ }
4707
+
4708
+ @tx_endpoint(push=True)
4709
+ async def crcat_approve_pending(
4710
+ self,
4711
+ request: Dict[str, Any],
4712
+ action_scope: WalletActionScope,
4713
+ extra_conditions: Tuple[Condition, ...] = tuple(),
4714
+ ) -> EndpointResult:
4715
+ """
4716
+ Moving any "pending approval" CR-CATs into the spendable balance of the wallet
4717
+ :param request: Required 'wallet_id'. Optional 'min_amount_to_claim' (default: full balance).
4718
+ Standard transaction params 'fee' & 'reuse_puzhash'.
4719
+ :return: a list of all relevant 'transactions' (TransactionRecord) that this spend generates:
4720
+ (CRCAT TX + fee TX)
4721
+ """
4722
+
4723
+ @streamable
4724
+ @dataclasses.dataclass(frozen=True)
4725
+ class CRCATApprovePending(Streamable):
4726
+ wallet_id: uint32
4727
+ min_amount_to_claim: uint64
4728
+ fee: uint64 = uint64(0)
4729
+
4730
+ parsed_request = CRCATApprovePending.from_json_dict(request)
4731
+ cr_cat_wallet = self.service.wallet_state_manager.wallets[parsed_request.wallet_id]
4732
+ assert isinstance(cr_cat_wallet, CRCATWallet)
4733
+
4734
+ await cr_cat_wallet.claim_pending_approval_balance(
4735
+ parsed_request.min_amount_to_claim,
4736
+ action_scope,
4737
+ fee=parsed_request.fee,
4738
+ extra_conditions=extra_conditions,
4739
+ )
4740
+
4741
+ return {
4742
+ "transactions": None, # tx_endpoint wrapper will take care of this
4743
+ }
4744
+
4745
+ @marshal
4746
+ async def gather_signing_info(
4747
+ self,
4748
+ request: GatherSigningInfo,
4749
+ ) -> GatherSigningInfoResponse:
4750
+ return GatherSigningInfoResponse(await self.service.wallet_state_manager.gather_signing_info(request.spends))
4751
+
4752
+ @marshal
4753
+ async def apply_signatures(
4754
+ self,
4755
+ request: ApplySignatures,
4756
+ ) -> ApplySignaturesResponse:
4757
+ return ApplySignaturesResponse(
4758
+ [await self.service.wallet_state_manager.apply_signatures(request.spends, request.signing_responses)]
4759
+ )
4760
+
4761
+ @marshal
4762
+ async def submit_transactions(
4763
+ self,
4764
+ request: SubmitTransactions,
4765
+ ) -> SubmitTransactionsResponse:
4766
+ return SubmitTransactionsResponse(
4767
+ await self.service.wallet_state_manager.submit_transactions(request.signed_transactions)
4768
+ )
4769
+
4770
+ @marshal
4771
+ async def execute_signing_instructions(
4772
+ self,
4773
+ request: ExecuteSigningInstructions,
4774
+ ) -> ExecuteSigningInstructionsResponse:
4775
+ return ExecuteSigningInstructionsResponse(
4776
+ await self.service.wallet_state_manager.execute_signing_instructions(
4777
+ request.signing_instructions, request.partial_allowed
4778
+ )
4779
+ )