chia-blockchain 2.5.4rc1__py3-none-any.whl → 2.5.5rc1__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 (453) hide show
  1. chia/_tests/blockchain/blockchain_test_utils.py +2 -3
  2. chia/_tests/blockchain/test_augmented_chain.py +2 -3
  3. chia/_tests/blockchain/test_blockchain.py +261 -44
  4. chia/_tests/blockchain/test_blockchain_transactions.py +4 -3
  5. chia/_tests/blockchain/test_build_chains.py +197 -1
  6. chia/_tests/blockchain/test_get_block_generator.py +1 -1
  7. chia/_tests/blockchain/test_lookup_fork_chain.py +1 -1
  8. chia/_tests/clvm/benchmark_costs.py +1 -1
  9. chia/_tests/clvm/coin_store.py +3 -4
  10. chia/_tests/clvm/test_message_conditions.py +2 -2
  11. chia/_tests/clvm/test_puzzle_compression.py +2 -3
  12. chia/_tests/clvm/test_puzzles.py +1 -2
  13. chia/_tests/clvm/test_singletons.py +2 -3
  14. chia/_tests/clvm/test_spend_sim.py +7 -7
  15. chia/_tests/cmds/cmd_test_utils.py +30 -25
  16. chia/_tests/cmds/test_dev_gh.py +1 -1
  17. chia/_tests/cmds/test_farm_cmd.py +1 -1
  18. chia/_tests/cmds/test_show.py +1 -2
  19. chia/_tests/cmds/wallet/test_did.py +101 -56
  20. chia/_tests/cmds/wallet/test_nft.py +109 -84
  21. chia/_tests/cmds/wallet/test_notifications.py +1 -1
  22. chia/_tests/cmds/wallet/test_offer.toffer +1 -1
  23. chia/_tests/cmds/wallet/test_vcs.py +8 -8
  24. chia/_tests/cmds/wallet/test_wallet.py +100 -46
  25. chia/_tests/conftest.py +31 -20
  26. chia/_tests/connection_utils.py +1 -1
  27. chia/_tests/core/consensus/stores/__init__.py +0 -0
  28. chia/_tests/core/consensus/stores/test_coin_store_protocol.py +40 -0
  29. chia/_tests/core/consensus/test_block_creation.py +2 -31
  30. chia/_tests/core/consensus/test_pot_iterations.py +38 -3
  31. chia/_tests/core/custom_types/test_proof_of_space.py +154 -26
  32. chia/_tests/core/custom_types/test_spend_bundle.py +2 -3
  33. chia/_tests/core/daemon/test_daemon.py +80 -0
  34. chia/_tests/core/data_layer/test_data_layer.py +1 -1
  35. chia/_tests/core/data_layer/test_data_layer_util.py +1 -1
  36. chia/_tests/core/data_layer/test_data_rpc.py +14 -10
  37. chia/_tests/core/data_layer/test_data_store.py +5 -5
  38. chia/_tests/core/farmer/test_farmer_api.py +2 -2
  39. chia/_tests/core/full_node/full_sync/test_full_sync.py +446 -406
  40. chia/_tests/core/full_node/ram_db.py +3 -1
  41. chia/_tests/core/full_node/stores/test_block_store.py +28 -16
  42. chia/_tests/core/full_node/stores/test_coin_store.py +277 -185
  43. chia/_tests/core/full_node/stores/test_full_node_store.py +11 -4
  44. chia/_tests/core/full_node/stores/test_hint_store.py +2 -2
  45. chia/_tests/core/full_node/test_address_manager.py +200 -27
  46. chia/_tests/core/full_node/test_block_height_map.py +2 -2
  47. chia/_tests/core/full_node/test_conditions.py +7 -6
  48. chia/_tests/core/full_node/test_full_node.py +456 -40
  49. chia/_tests/core/full_node/test_generator_tools.py +32 -2
  50. chia/_tests/core/full_node/test_hint_management.py +1 -1
  51. chia/_tests/core/full_node/test_node_load.py +20 -21
  52. chia/_tests/core/full_node/test_performance.py +3 -4
  53. chia/_tests/core/full_node/test_prev_tx_block.py +43 -0
  54. chia/_tests/core/full_node/test_subscriptions.py +1 -2
  55. chia/_tests/core/full_node/test_transactions.py +9 -5
  56. chia/_tests/core/full_node/test_tx_processing_queue.py +1 -2
  57. chia/_tests/core/large_block.py +1 -2
  58. chia/_tests/core/make_block_generator.py +3 -4
  59. chia/_tests/core/mempool/test_mempool.py +36 -86
  60. chia/_tests/core/mempool/test_mempool_fee_estimator.py +1 -1
  61. chia/_tests/core/mempool/test_mempool_item_queries.py +1 -3
  62. chia/_tests/core/mempool/test_mempool_manager.py +421 -69
  63. chia/_tests/core/mempool/test_mempool_performance.py +3 -2
  64. chia/_tests/core/mempool/test_singleton_fast_forward.py +60 -131
  65. chia/_tests/core/server/flood.py +1 -1
  66. chia/_tests/core/server/test_dos.py +1 -1
  67. chia/_tests/core/server/test_node_discovery.py +41 -27
  68. chia/_tests/core/server/test_rate_limits.py +1 -1
  69. chia/_tests/core/server/test_server.py +1 -1
  70. chia/_tests/core/services/test_services.py +5 -5
  71. chia/_tests/core/ssl/test_ssl.py +1 -1
  72. chia/_tests/core/test_cost_calculation.py +6 -6
  73. chia/_tests/core/test_crawler.py +2 -2
  74. chia/_tests/core/test_crawler_rpc.py +1 -1
  75. chia/_tests/core/test_db_conversion.py +3 -1
  76. chia/_tests/core/test_db_validation.py +5 -3
  77. chia/_tests/core/test_farmer_harvester_rpc.py +15 -15
  78. chia/_tests/core/test_filter.py +4 -1
  79. chia/_tests/core/test_full_node_rpc.py +99 -82
  80. chia/_tests/core/test_program.py +2 -2
  81. chia/_tests/core/util/test_block_cache.py +1 -1
  82. chia/_tests/core/util/test_keychain.py +2 -2
  83. chia/_tests/core/util/test_lockfile.py +1 -1
  84. chia/_tests/core/util/test_log_exceptions.py +5 -5
  85. chia/_tests/core/util/test_streamable.py +81 -22
  86. chia/_tests/db/test_db_wrapper.py +1 -3
  87. chia/_tests/environments/wallet.py +5 -5
  88. chia/_tests/farmer_harvester/test_farmer.py +9 -7
  89. chia/_tests/farmer_harvester/test_farmer_harvester.py +11 -4
  90. chia/_tests/farmer_harvester/test_filter_prefix_bits.py +6 -5
  91. chia/_tests/farmer_harvester/test_third_party_harvesters.py +15 -9
  92. chia/_tests/fee_estimation/test_fee_estimation_integration.py +1 -2
  93. chia/_tests/fee_estimation/test_fee_estimation_rpc.py +7 -5
  94. chia/_tests/fee_estimation/test_fee_estimation_unit_tests.py +1 -1
  95. chia/_tests/generator/test_compression.py +1 -2
  96. chia/_tests/generator/test_rom.py +8 -4
  97. chia/_tests/plot_sync/test_plot_sync.py +3 -3
  98. chia/_tests/plot_sync/test_receiver.py +3 -3
  99. chia/_tests/plot_sync/test_sender.py +1 -1
  100. chia/_tests/plot_sync/test_sync_simulated.py +3 -3
  101. chia/_tests/plot_sync/util.py +2 -2
  102. chia/_tests/pools/test_pool_cmdline.py +48 -21
  103. chia/_tests/pools/test_pool_puzzles_lifecycle.py +2 -3
  104. chia/_tests/pools/test_pool_rpc.py +237 -105
  105. chia/_tests/pools/test_pool_wallet.py +11 -2
  106. chia/_tests/pools/test_wallet_pool_store.py +5 -4
  107. chia/_tests/rpc/test_rpc_client.py +1 -1
  108. chia/_tests/simulation/test_simulation.py +13 -8
  109. chia/_tests/simulation/test_simulator.py +2 -2
  110. chia/_tests/timelord/test_new_peak.py +191 -47
  111. chia/_tests/timelord/test_timelord.py +1 -1
  112. chia/_tests/tools/test_full_sync.py +0 -2
  113. chia/_tests/tools/test_run_block.py +3 -1
  114. chia/_tests/util/benchmark_cost.py +3 -3
  115. chia/_tests/util/benchmarks.py +2 -2
  116. chia/_tests/util/blockchain.py +11 -5
  117. chia/_tests/util/blockchain_mock.py +1 -4
  118. chia/_tests/util/coin_store.py +29 -0
  119. chia/_tests/util/constants.py +2 -18
  120. chia/_tests/util/full_sync.py +3 -3
  121. chia/_tests/util/generator_tools_testing.py +2 -3
  122. chia/_tests/util/key_tool.py +2 -3
  123. chia/_tests/util/misc.py +33 -31
  124. chia/_tests/util/network_protocol_data.py +19 -17
  125. chia/_tests/util/protocol_messages_bytes-v1.0 +0 -0
  126. chia/_tests/util/protocol_messages_json.py +3 -1
  127. chia/_tests/util/run_block.py +2 -2
  128. chia/_tests/util/setup_nodes.py +7 -7
  129. chia/_tests/util/spend_sim.py +47 -55
  130. chia/_tests/util/test_condition_tools.py +5 -4
  131. chia/_tests/util/test_config.py +2 -2
  132. chia/_tests/util/test_dump_keyring.py +1 -1
  133. chia/_tests/util/test_full_block_utils.py +12 -14
  134. chia/_tests/util/test_misc.py +2 -2
  135. chia/_tests/util/test_paginator.py +4 -4
  136. chia/_tests/util/test_priority_mutex.py +2 -2
  137. chia/_tests/util/test_replace_str_to_bytes.py +15 -5
  138. chia/_tests/util/test_ssl_check.py +1 -1
  139. chia/_tests/util/test_testnet_overrides.py +13 -3
  140. chia/_tests/util/time_out_assert.py +4 -2
  141. chia/_tests/wallet/cat_wallet/test_cat_lifecycle.py +1 -1
  142. chia/_tests/wallet/cat_wallet/test_cat_outer_puzzle.py +1 -2
  143. chia/_tests/wallet/cat_wallet/test_cat_wallet.py +352 -432
  144. chia/_tests/wallet/cat_wallet/test_offer_lifecycle.py +3 -6
  145. chia/_tests/wallet/cat_wallet/test_trades.py +53 -77
  146. chia/_tests/wallet/clawback/test_clawback_decorator.py +3 -1
  147. chia/_tests/wallet/clawback/test_clawback_lifecycle.py +3 -3
  148. chia/_tests/wallet/clawback/test_clawback_metadata.py +4 -2
  149. chia/_tests/wallet/conftest.py +11 -12
  150. chia/_tests/wallet/db_wallet/test_db_graftroot.py +11 -4
  151. chia/_tests/wallet/db_wallet/test_dl_offers.py +433 -130
  152. chia/_tests/wallet/db_wallet/test_dl_wallet.py +3 -3
  153. chia/_tests/wallet/did_wallet/test_did.py +2132 -2000
  154. chia/_tests/wallet/nft_wallet/config.py +1 -1
  155. chia/_tests/wallet/nft_wallet/test_nft_1_offers.py +1610 -742
  156. chia/_tests/wallet/nft_wallet/test_nft_bulk_mint.py +486 -907
  157. chia/_tests/wallet/nft_wallet/test_nft_lifecycle.py +4 -4
  158. chia/_tests/wallet/nft_wallet/test_nft_wallet.py +517 -294
  159. chia/_tests/wallet/rpc/test_dl_wallet_rpc.py +133 -62
  160. chia/_tests/wallet/rpc/test_wallet_rpc.py +305 -184
  161. chia/_tests/wallet/simple_sync/test_simple_sync_protocol.py +10 -6
  162. chia/_tests/wallet/sync/test_wallet_sync.py +89 -60
  163. chia/_tests/wallet/test_clvm_casts.py +88 -0
  164. chia/_tests/wallet/test_coin_management.py +1 -1
  165. chia/_tests/wallet/test_coin_selection.py +1 -1
  166. chia/_tests/wallet/test_conditions.py +1 -1
  167. chia/_tests/wallet/test_new_wallet_protocol.py +13 -11
  168. chia/_tests/wallet/test_notifications.py +5 -3
  169. chia/_tests/wallet/test_sign_coin_spends.py +6 -6
  170. chia/_tests/wallet/test_signer_protocol.py +13 -12
  171. chia/_tests/wallet/test_singleton.py +1 -1
  172. chia/_tests/wallet/test_singleton_lifecycle_fast.py +5 -7
  173. chia/_tests/wallet/test_util.py +2 -2
  174. chia/_tests/wallet/test_wallet.py +108 -29
  175. chia/_tests/wallet/test_wallet_action_scope.py +9 -2
  176. chia/_tests/wallet/test_wallet_blockchain.py +2 -3
  177. chia/_tests/wallet/test_wallet_key_val_store.py +1 -2
  178. chia/_tests/wallet/test_wallet_node.py +2 -4
  179. chia/_tests/wallet/test_wallet_retry.py +4 -2
  180. chia/_tests/wallet/test_wallet_state_manager.py +191 -5
  181. chia/_tests/wallet/test_wallet_test_framework.py +1 -1
  182. chia/_tests/wallet/vc_wallet/test_vc_lifecycle.py +8 -8
  183. chia/_tests/wallet/vc_wallet/test_vc_wallet.py +29 -12
  184. chia/_tests/wallet/wallet_block_tools.py +6 -6
  185. chia/_tests/weight_proof/test_weight_proof.py +10 -48
  186. chia/apis.py +1 -1
  187. chia/cmds/beta.py +1 -1
  188. chia/cmds/chia.py +9 -9
  189. chia/cmds/cmd_classes.py +12 -11
  190. chia/cmds/cmd_helpers.py +1 -1
  191. chia/cmds/cmds_util.py +12 -9
  192. chia/cmds/coin_funcs.py +2 -2
  193. chia/cmds/configure.py +2 -2
  194. chia/cmds/data.py +0 -2
  195. chia/cmds/data_funcs.py +1 -1
  196. chia/cmds/db_validate_func.py +1 -2
  197. chia/cmds/dev/__init__.py +0 -0
  198. chia/cmds/dev/data.py +273 -0
  199. chia/cmds/{gh.py → dev/gh.py} +5 -5
  200. chia/cmds/dev/main.py +22 -0
  201. chia/cmds/dev/mempool.py +78 -0
  202. chia/cmds/dev/mempool_funcs.py +63 -0
  203. chia/cmds/farm_funcs.py +5 -4
  204. chia/cmds/init_funcs.py +11 -11
  205. chia/cmds/keys.py +2 -2
  206. chia/cmds/keys_funcs.py +4 -4
  207. chia/cmds/netspace_funcs.py +1 -1
  208. chia/cmds/peer_funcs.py +2 -2
  209. chia/cmds/plotnft_funcs.py +72 -26
  210. chia/cmds/rpc.py +1 -1
  211. chia/cmds/show_funcs.py +5 -5
  212. chia/cmds/signer.py +8 -7
  213. chia/cmds/sim_funcs.py +8 -9
  214. chia/cmds/wallet.py +2 -2
  215. chia/cmds/wallet_funcs.py +165 -131
  216. chia/{util → consensus}/augmented_chain.py +1 -2
  217. chia/consensus/block_body_validation.py +54 -40
  218. chia/consensus/block_creation.py +42 -76
  219. chia/consensus/block_header_validation.py +32 -26
  220. chia/consensus/block_record.py +0 -3
  221. chia/consensus/blockchain.py +23 -32
  222. chia/consensus/blockchain_interface.py +1 -5
  223. chia/consensus/check_time_locks.py +57 -0
  224. chia/consensus/coin_store_protocol.py +151 -0
  225. chia/consensus/coinbase.py +0 -6
  226. chia/consensus/condition_costs.py +4 -0
  227. chia/{util → consensus}/condition_tools.py +4 -5
  228. chia/consensus/cost_calculator.py +1 -1
  229. chia/consensus/default_constants.py +32 -9
  230. chia/consensus/deficit.py +1 -3
  231. chia/consensus/difficulty_adjustment.py +1 -2
  232. chia/consensus/find_fork_point.py +1 -3
  233. chia/consensus/full_block_to_block_record.py +1 -6
  234. chia/{util → consensus}/generator_tools.py +1 -3
  235. chia/consensus/get_block_challenge.py +30 -7
  236. chia/consensus/make_sub_epoch_summary.py +1 -5
  237. chia/consensus/multiprocess_validation.py +21 -20
  238. chia/consensus/pot_iterations.py +74 -13
  239. chia/{util → consensus}/prev_transaction_block.py +1 -1
  240. chia/consensus/vdf_info_computation.py +1 -3
  241. chia/daemon/keychain_proxy.py +5 -5
  242. chia/daemon/server.py +22 -5
  243. chia/data_layer/data_layer.py +92 -51
  244. chia/{rpc → data_layer}/data_layer_rpc_api.py +1 -1
  245. chia/{rpc → data_layer}/data_layer_rpc_util.py +3 -6
  246. chia/data_layer/data_layer_util.py +4 -6
  247. chia/data_layer/data_layer_wallet.py +42 -69
  248. chia/data_layer/dl_wallet_store.py +12 -6
  249. chia/data_layer/download_data.py +3 -3
  250. chia/data_layer/s3_plugin_service.py +0 -1
  251. chia/farmer/farmer.py +3 -4
  252. chia/farmer/farmer_api.py +11 -7
  253. chia/{rpc → farmer}/farmer_rpc_client.py +1 -1
  254. chia/full_node/block_height_map.py +7 -6
  255. chia/full_node/block_store.py +5 -7
  256. chia/full_node/bundle_tools.py +1 -2
  257. chia/full_node/coin_store.py +143 -124
  258. chia/{types → full_node}/eligible_coin_spends.py +39 -70
  259. chia/full_node/fee_estimator.py +1 -1
  260. chia/full_node/fee_estimator_interface.py +0 -8
  261. chia/full_node/fee_tracker.py +25 -25
  262. chia/full_node/full_node.py +70 -53
  263. chia/full_node/full_node_api.py +57 -40
  264. chia/{rpc → full_node}/full_node_rpc_api.py +87 -8
  265. chia/{rpc → full_node}/full_node_rpc_client.py +7 -6
  266. chia/full_node/full_node_store.py +23 -8
  267. chia/full_node/mempool.py +206 -53
  268. chia/full_node/mempool_check_conditions.py +20 -63
  269. chia/full_node/mempool_manager.py +32 -42
  270. chia/full_node/subscriptions.py +1 -3
  271. chia/full_node/tx_processing_queue.py +50 -3
  272. chia/full_node/weight_proof.py +46 -37
  273. chia/harvester/harvester.py +1 -1
  274. chia/harvester/harvester_api.py +22 -7
  275. chia/introducer/introducer.py +1 -1
  276. chia/introducer/introducer_api.py +1 -1
  277. chia/plot_sync/exceptions.py +1 -1
  278. chia/plot_sync/receiver.py +1 -1
  279. chia/plot_sync/sender.py +2 -2
  280. chia/pools/pool_puzzles.py +13 -18
  281. chia/pools/pool_wallet.py +23 -46
  282. chia/protocols/farmer_protocol.py +11 -3
  283. chia/protocols/full_node_protocol.py +1 -4
  284. chia/protocols/harvester_protocol.py +3 -3
  285. chia/protocols/pool_protocol.py +1 -2
  286. chia/protocols/shared_protocol.py +3 -3
  287. chia/protocols/timelord_protocol.py +1 -3
  288. chia/protocols/wallet_protocol.py +3 -3
  289. chia/rpc/rpc_client.py +7 -8
  290. chia/rpc/rpc_server.py +3 -3
  291. chia/rpc/util.py +3 -1
  292. chia/seeder/crawler.py +1 -1
  293. chia/seeder/crawler_api.py +1 -1
  294. chia/seeder/dns_server.py +2 -0
  295. chia/seeder/start_crawler.py +3 -3
  296. chia/server/address_manager.py +286 -38
  297. chia/server/address_manager_store.py +0 -215
  298. chia/{types → server}/aliases.py +7 -7
  299. chia/server/api_protocol.py +1 -1
  300. chia/server/chia_policy.py +1 -1
  301. chia/server/node_discovery.py +76 -113
  302. chia/server/rate_limits.py +1 -1
  303. chia/server/resolve_peer_info.py +43 -0
  304. chia/server/server.py +5 -5
  305. chia/server/start_data_layer.py +4 -4
  306. chia/server/start_farmer.py +5 -4
  307. chia/server/start_full_node.py +5 -4
  308. chia/server/start_harvester.py +7 -5
  309. chia/server/start_introducer.py +2 -2
  310. chia/server/start_service.py +1 -1
  311. chia/server/start_timelord.py +7 -5
  312. chia/server/start_wallet.py +7 -5
  313. chia/server/ws_connection.py +1 -1
  314. chia/simulator/add_blocks_in_batches.py +2 -2
  315. chia/simulator/block_tools.py +245 -201
  316. chia/simulator/full_node_simulator.py +38 -10
  317. chia/simulator/setup_services.py +12 -12
  318. chia/simulator/simulator_full_node_rpc_api.py +2 -2
  319. chia/simulator/simulator_full_node_rpc_client.py +2 -2
  320. chia/simulator/simulator_test_tools.py +2 -2
  321. chia/simulator/start_simulator.py +1 -1
  322. chia/simulator/wallet_tools.py +10 -18
  323. chia/ssl/create_ssl.py +1 -1
  324. chia/timelord/iters_from_block.py +14 -14
  325. chia/timelord/timelord.py +15 -11
  326. chia/timelord/timelord_api.py +14 -2
  327. chia/timelord/timelord_state.py +20 -14
  328. chia/types/blockchain_format/program.py +53 -10
  329. chia/types/blockchain_format/proof_of_space.py +73 -19
  330. chia/types/coin_spend.py +3 -56
  331. chia/types/generator_types.py +28 -0
  332. chia/types/internal_mempool_item.py +1 -2
  333. chia/types/mempool_item.py +12 -7
  334. chia/types/unfinished_header_block.py +1 -2
  335. chia/types/validation_state.py +1 -2
  336. chia/types/weight_proof.py +1 -3
  337. chia/util/action_scope.py +3 -3
  338. chia/util/block_cache.py +1 -2
  339. chia/util/byte_types.py +1 -1
  340. chia/util/casts.py +21 -0
  341. chia/util/config.py +0 -37
  342. chia/util/db_wrapper.py +8 -1
  343. chia/util/errors.py +3 -2
  344. chia/util/initial-config.yaml +21 -5
  345. chia/util/keychain.py +6 -7
  346. chia/util/keyring_wrapper.py +5 -5
  347. chia/util/limited_semaphore.py +1 -1
  348. chia/util/priority_mutex.py +1 -1
  349. chia/util/streamable.py +63 -5
  350. chia/util/task_timing.py +1 -1
  351. chia/util/virtual_project_analysis.py +1 -1
  352. chia/wallet/cat_wallet/cat_info.py +7 -3
  353. chia/wallet/cat_wallet/cat_outer_puzzle.py +9 -5
  354. chia/wallet/cat_wallet/cat_utils.py +1 -1
  355. chia/wallet/cat_wallet/cat_wallet.py +44 -36
  356. chia/wallet/cat_wallet/lineage_store.py +7 -0
  357. chia/wallet/cat_wallet/r_cat_wallet.py +273 -0
  358. chia/wallet/conditions.py +5 -10
  359. chia/wallet/db_wallet/db_wallet_puzzles.py +4 -4
  360. chia/wallet/derivation_record.py +33 -0
  361. chia/wallet/derive_keys.py +3 -3
  362. chia/wallet/did_wallet/did_info.py +12 -3
  363. chia/wallet/did_wallet/did_wallet.py +132 -101
  364. chia/wallet/did_wallet/did_wallet_puzzles.py +9 -9
  365. chia/wallet/driver_protocol.py +3 -1
  366. chia/{types/spend_bundle.py → wallet/estimate_fees.py} +2 -7
  367. chia/wallet/nft_wallet/metadata_outer_puzzle.py +5 -3
  368. chia/wallet/nft_wallet/nft_puzzle_utils.py +1 -1
  369. chia/wallet/nft_wallet/nft_wallet.py +69 -112
  370. chia/wallet/nft_wallet/ownership_outer_puzzle.py +5 -3
  371. chia/wallet/nft_wallet/singleton_outer_puzzle.py +6 -4
  372. chia/wallet/nft_wallet/transfer_program_puzzle.py +4 -2
  373. chia/wallet/nft_wallet/uncurry_nft.py +4 -6
  374. chia/wallet/notification_manager.py +2 -3
  375. chia/wallet/outer_puzzles.py +7 -2
  376. chia/wallet/puzzle_drivers.py +1 -1
  377. chia/wallet/puzzles/clawback/drivers.py +5 -4
  378. chia/wallet/puzzles/p2_delegated_puzzle_or_hidden_puzzle.py +1 -1
  379. chia/wallet/puzzles/singleton_top_layer.py +2 -1
  380. chia/wallet/puzzles/singleton_top_layer_v1_1.py +2 -1
  381. chia/wallet/puzzles/tails.py +1 -3
  382. chia/wallet/signer_protocol.py +5 -6
  383. chia/wallet/singleton.py +5 -4
  384. chia/wallet/singleton_record.py +1 -1
  385. chia/wallet/trade_manager.py +18 -20
  386. chia/wallet/trade_record.py +3 -6
  387. chia/wallet/trading/offer.py +12 -13
  388. chia/wallet/uncurried_puzzle.py +2 -2
  389. chia/wallet/util/compute_additions.py +58 -0
  390. chia/wallet/util/compute_hints.py +3 -3
  391. chia/wallet/util/compute_memos.py +4 -4
  392. chia/wallet/util/curry_and_treehash.py +2 -1
  393. chia/wallet/util/debug_spend_bundle.py +1 -1
  394. chia/wallet/util/merkle_tree.py +1 -1
  395. chia/wallet/util/peer_request_cache.py +1 -2
  396. chia/wallet/util/tx_config.py +3 -8
  397. chia/wallet/util/wallet_sync_utils.py +10 -5
  398. chia/wallet/util/wallet_types.py +1 -0
  399. chia/wallet/vc_wallet/cr_cat_drivers.py +17 -18
  400. chia/wallet/vc_wallet/cr_cat_wallet.py +30 -28
  401. chia/wallet/vc_wallet/cr_outer_puzzle.py +5 -3
  402. chia/wallet/vc_wallet/vc_drivers.py +50 -8
  403. chia/wallet/vc_wallet/vc_store.py +3 -5
  404. chia/wallet/vc_wallet/vc_wallet.py +15 -22
  405. chia/wallet/wallet.py +36 -46
  406. chia/wallet/wallet_action_scope.py +73 -4
  407. chia/wallet/wallet_blockchain.py +1 -3
  408. chia/wallet/wallet_interested_store.py +1 -1
  409. chia/wallet/wallet_nft_store.py +3 -3
  410. chia/wallet/wallet_node.py +17 -16
  411. chia/wallet/wallet_node_api.py +4 -5
  412. chia/wallet/wallet_pool_store.py +1 -1
  413. chia/wallet/wallet_protocol.py +2 -0
  414. chia/wallet/wallet_puzzle_store.py +1 -1
  415. chia/{rpc → wallet}/wallet_request_types.py +670 -81
  416. chia/{rpc → wallet}/wallet_rpc_api.py +735 -766
  417. chia/{rpc → wallet}/wallet_rpc_client.py +268 -420
  418. chia/wallet/wallet_singleton_store.py +8 -7
  419. chia/wallet/wallet_spend_bundle.py +4 -3
  420. chia/wallet/wallet_state_manager.py +320 -191
  421. chia/wallet/wallet_weight_proof_handler.py +1 -2
  422. chia/wallet/wsm_apis.py +98 -0
  423. {chia_blockchain-2.5.4rc1.dist-info → chia_blockchain-2.5.5rc1.dist-info}/METADATA +7 -7
  424. {chia_blockchain-2.5.4rc1.dist-info → chia_blockchain-2.5.5rc1.dist-info}/RECORD +443 -436
  425. mozilla-ca/cacert.pem +3 -165
  426. chia/_tests/fee_estimation/test_mempoolitem_height_added.py +0 -145
  427. chia/cmds/dev.py +0 -18
  428. chia/types/blockchain_format/slots.py +0 -9
  429. chia/types/blockchain_format/sub_epoch_summary.py +0 -5
  430. chia/types/end_of_slot_bundle.py +0 -5
  431. chia/types/full_block.py +0 -5
  432. chia/types/header_block.py +0 -5
  433. chia/types/spend_bundle_conditions.py +0 -7
  434. chia/types/transaction_queue_entry.py +0 -56
  435. chia/types/unfinished_block.py +0 -5
  436. /chia/cmds/{installers.py → dev/installers.py} +0 -0
  437. /chia/cmds/{sim.py → dev/sim.py} +0 -0
  438. /chia/{util → cmds}/dump_keyring.py +0 -0
  439. /chia/{full_node → consensus}/signage_point.py +0 -0
  440. /chia/{rpc → data_layer}/data_layer_rpc_client.py +0 -0
  441. /chia/{rpc → farmer}/farmer_rpc_api.py +0 -0
  442. /chia/{util → full_node}/full_block_utils.py +0 -0
  443. /chia/{rpc → harvester}/harvester_rpc_api.py +0 -0
  444. /chia/{rpc → harvester}/harvester_rpc_client.py +0 -0
  445. /chia/{full_node → protocols}/fee_estimate.py +0 -0
  446. /chia/{server → protocols}/outbound_message.py +0 -0
  447. /chia/{rpc → seeder}/crawler_rpc_api.py +0 -0
  448. /chia/{util → simulator}/vdf_prover.py +0 -0
  449. /chia/{util → ssl}/ssl_check.py +0 -0
  450. /chia/{rpc → timelord}/timelord_rpc_api.py +0 -0
  451. {chia_blockchain-2.5.4rc1.dist-info → chia_blockchain-2.5.5rc1.dist-info}/LICENSE +0 -0
  452. {chia_blockchain-2.5.4rc1.dist-info → chia_blockchain-2.5.5rc1.dist-info}/WHEEL +0 -0
  453. {chia_blockchain-2.5.4rc1.dist-info → chia_blockchain-2.5.5rc1.dist-info}/entry_points.txt +0 -0
@@ -6,80 +6,24 @@ import logging
6
6
  from pathlib import Path
7
7
  from typing import TYPE_CHECKING, Any, Callable, ClassVar, Optional, Union, cast
8
8
 
9
- from chia_rs import AugSchemeMPL, Coin, G1Element, G2Element, PrivateKey
9
+ from chia_rs import AugSchemeMPL, Coin, CoinSpend, CoinState, G1Element, G2Element, PrivateKey
10
10
  from chia_rs.sized_bytes import bytes32
11
11
  from chia_rs.sized_ints import uint8, uint16, uint32, uint64
12
12
  from clvm_tools.binutils import assemble
13
13
 
14
14
  from chia.consensus.block_rewards import calculate_base_farmer_reward
15
15
  from chia.data_layer.data_layer_errors import LauncherCoinNotFoundError
16
- from chia.data_layer.data_layer_util import dl_verify_proof
17
- from chia.data_layer.data_layer_wallet import DataLayerWallet
16
+ from chia.data_layer.data_layer_util import DLProof, VerifyProofResponse, dl_verify_proof
17
+ from chia.data_layer.data_layer_wallet import DataLayerWallet, Mirror
18
18
  from chia.pools.pool_wallet import PoolWallet
19
19
  from chia.pools.pool_wallet_info import FARMING_TO_POOL, PoolState, PoolWalletInfo, create_pool_state
20
- from chia.protocols.wallet_protocol import CoinState
20
+ from chia.protocols.outbound_message import NodeType
21
21
  from chia.rpc.rpc_server import Endpoint, EndpointResult, default_get_connections
22
22
  from chia.rpc.util import ALL_TRANSLATION_LAYERS, RpcEndpoint, marshal
23
- from chia.rpc.wallet_request_types import (
24
- AddKey,
25
- AddKeyResponse,
26
- ApplySignatures,
27
- ApplySignaturesResponse,
28
- CheckDeleteKey,
29
- CheckDeleteKeyResponse,
30
- CombineCoins,
31
- CombineCoinsResponse,
32
- DeleteKey,
33
- Empty,
34
- ExecuteSigningInstructions,
35
- ExecuteSigningInstructionsResponse,
36
- GatherSigningInfo,
37
- GatherSigningInfoResponse,
38
- GenerateMnemonicResponse,
39
- GetHeightInfoResponse,
40
- GetLoggedInFingerprintResponse,
41
- GetNotifications,
42
- GetNotificationsResponse,
43
- GetPrivateKey,
44
- GetPrivateKeyFormat,
45
- GetPrivateKeyResponse,
46
- GetPublicKeysResponse,
47
- GetSyncStatusResponse,
48
- GetTimestampForHeight,
49
- GetTimestampForHeightResponse,
50
- LogIn,
51
- LogInResponse,
52
- PushTransactions,
53
- PushTransactionsResponse,
54
- PushTX,
55
- SetWalletResyncOnStartup,
56
- SplitCoins,
57
- SplitCoinsResponse,
58
- SubmitTransactions,
59
- SubmitTransactionsResponse,
60
- VCAddProofs,
61
- VCGet,
62
- VCGetList,
63
- VCGetListResponse,
64
- VCGetProofsForRoot,
65
- VCGetProofsForRootResponse,
66
- VCGetResponse,
67
- VCMint,
68
- VCMintResponse,
69
- VCProofsRPC,
70
- VCProofWithHash,
71
- VCRecordWithCoinID,
72
- VCRevoke,
73
- VCRevokeResponse,
74
- VCSpend,
75
- VCSpendResponse,
76
- )
77
- from chia.server.outbound_message import NodeType
78
23
  from chia.server.ws_connection import WSChiaConnection
79
24
  from chia.types.blockchain_format.coin import coin_as_list
80
- from chia.types.blockchain_format.program import INFINITE_COST, Program
25
+ from chia.types.blockchain_format.program import INFINITE_COST, Program, run_with_cost
81
26
  from chia.types.coin_record import CoinRecord
82
- from chia.types.coin_spend import CoinSpend
83
27
  from chia.types.signing_mode import CHIP_0002_SIGN_MESSAGE_PREFIX, SigningMode
84
28
  from chia.util.bech32m import decode_puzzle_hash, encode_puzzle_hash
85
29
  from chia.util.byte_types import hexstr_to_bytes
@@ -109,11 +53,10 @@ from chia.wallet.derive_keys import (
109
53
  MAX_POOL_WALLETS,
110
54
  master_sk_to_farmer_sk,
111
55
  master_sk_to_pool_sk,
112
- master_sk_to_singleton_owner_sk,
113
56
  match_address_to_sk,
114
57
  )
115
58
  from chia.wallet.did_wallet import did_wallet_puzzles
116
- from chia.wallet.did_wallet.did_info import DIDCoinData, DIDInfo
59
+ from chia.wallet.did_wallet.did_info import DIDCoinData, DIDInfo, did_recovery_is_nil
117
60
  from chia.wallet.did_wallet.did_wallet import DIDWallet
118
61
  from chia.wallet.did_wallet.did_wallet_puzzles import (
119
62
  DID_INNERPUZ_MOD,
@@ -163,6 +106,148 @@ from chia.wallet.wallet_coin_store import CoinRecordOrder, GetCoinRecords, unspe
163
106
  from chia.wallet.wallet_info import WalletInfo
164
107
  from chia.wallet.wallet_node import WalletNode
165
108
  from chia.wallet.wallet_protocol import WalletProtocol
109
+ from chia.wallet.wallet_request_types import (
110
+ AddKey,
111
+ AddKeyResponse,
112
+ ApplySignatures,
113
+ ApplySignaturesResponse,
114
+ CheckDeleteKey,
115
+ CheckDeleteKeyResponse,
116
+ CombineCoins,
117
+ CombineCoinsResponse,
118
+ CreateNewDL,
119
+ CreateNewDLResponse,
120
+ DeleteKey,
121
+ DIDCreateBackupFile,
122
+ DIDCreateBackupFileResponse,
123
+ DIDFindLostDID,
124
+ DIDFindLostDIDResponse,
125
+ DIDGetCurrentCoinInfo,
126
+ DIDGetCurrentCoinInfoResponse,
127
+ DIDGetDID,
128
+ DIDGetDIDResponse,
129
+ DIDGetInfo,
130
+ DIDGetInfoResponse,
131
+ DIDGetMetadata,
132
+ DIDGetMetadataResponse,
133
+ DIDGetPubkey,
134
+ DIDGetPubkeyResponse,
135
+ DIDGetRecoveryInfo,
136
+ DIDGetRecoveryInfoResponse,
137
+ DIDGetRecoveryList,
138
+ DIDGetRecoveryListResponse,
139
+ DIDGetWalletName,
140
+ DIDGetWalletNameResponse,
141
+ DIDMessageSpend,
142
+ DIDMessageSpendResponse,
143
+ DIDSetWalletName,
144
+ DIDSetWalletNameResponse,
145
+ DIDTransferDID,
146
+ DIDTransferDIDResponse,
147
+ DIDUpdateMetadata,
148
+ DIDUpdateMetadataResponse,
149
+ DIDUpdateRecoveryIDs,
150
+ DIDUpdateRecoveryIDsResponse,
151
+ DLDeleteMirror,
152
+ DLDeleteMirrorResponse,
153
+ DLGetMirrors,
154
+ DLGetMirrorsResponse,
155
+ DLHistory,
156
+ DLHistoryResponse,
157
+ DLLatestSingleton,
158
+ DLLatestSingletonResponse,
159
+ DLNewMirror,
160
+ DLNewMirrorResponse,
161
+ DLOwnedSingletonsResponse,
162
+ DLSingletonsByRoot,
163
+ DLSingletonsByRootResponse,
164
+ DLStopTracking,
165
+ DLTrackNew,
166
+ DLUpdateMultiple,
167
+ DLUpdateMultipleResponse,
168
+ DLUpdateRoot,
169
+ DLUpdateRootResponse,
170
+ Empty,
171
+ ExecuteSigningInstructions,
172
+ ExecuteSigningInstructionsResponse,
173
+ GatherSigningInfo,
174
+ GatherSigningInfoResponse,
175
+ GenerateMnemonicResponse,
176
+ GetHeightInfoResponse,
177
+ GetLoggedInFingerprintResponse,
178
+ GetNotifications,
179
+ GetNotificationsResponse,
180
+ GetPrivateKey,
181
+ GetPrivateKeyFormat,
182
+ GetPrivateKeyResponse,
183
+ GetPublicKeysResponse,
184
+ GetSyncStatusResponse,
185
+ GetTimestampForHeight,
186
+ GetTimestampForHeightResponse,
187
+ LogIn,
188
+ LogInResponse,
189
+ NFTAddURI,
190
+ NFTAddURIResponse,
191
+ NFTCalculateRoyalties,
192
+ NFTCalculateRoyaltiesResponse,
193
+ NFTCountNFTs,
194
+ NFTCountNFTsResponse,
195
+ NFTGetByDID,
196
+ NFTGetByDIDResponse,
197
+ NFTGetInfo,
198
+ NFTGetInfoResponse,
199
+ NFTGetNFTs,
200
+ NFTGetNFTsResponse,
201
+ NFTGetWalletDID,
202
+ NFTGetWalletDIDResponse,
203
+ NFTGetWalletsWithDIDsResponse,
204
+ NFTMintBulk,
205
+ NFTMintBulkResponse,
206
+ NFTMintNFTRequest,
207
+ NFTMintNFTResponse,
208
+ NFTSetDIDBulk,
209
+ NFTSetDIDBulkResponse,
210
+ NFTSetNFTDID,
211
+ NFTSetNFTDIDResponse,
212
+ NFTSetNFTStatus,
213
+ NFTTransferBulk,
214
+ NFTTransferBulkResponse,
215
+ NFTTransferNFT,
216
+ NFTTransferNFTResponse,
217
+ NFTWalletWithDID,
218
+ PushTransactions,
219
+ PushTransactionsResponse,
220
+ PushTX,
221
+ PWAbsorbRewards,
222
+ PWAbsorbRewardsResponse,
223
+ PWJoinPool,
224
+ PWJoinPoolResponse,
225
+ PWSelfPool,
226
+ PWSelfPoolResponse,
227
+ PWStatus,
228
+ PWStatusResponse,
229
+ SetWalletResyncOnStartup,
230
+ SplitCoins,
231
+ SplitCoinsResponse,
232
+ SubmitTransactions,
233
+ SubmitTransactionsResponse,
234
+ VCAddProofs,
235
+ VCGet,
236
+ VCGetList,
237
+ VCGetListResponse,
238
+ VCGetProofsForRoot,
239
+ VCGetProofsForRootResponse,
240
+ VCGetResponse,
241
+ VCMint,
242
+ VCMintResponse,
243
+ VCProofsRPC,
244
+ VCProofWithHash,
245
+ VCRecordWithCoinID,
246
+ VCRevoke,
247
+ VCRevokeResponse,
248
+ VCSpend,
249
+ VCSpendResponse,
250
+ )
166
251
  from chia.wallet.wallet_spend_bundle import WalletSpendBundle
167
252
 
168
253
  # Timeout for response from wallet/full node for sending a transaction
@@ -361,6 +446,27 @@ def tx_endpoint(
361
446
  return _inner
362
447
 
363
448
 
449
+ REPLACEABLE_TRANSACTION_RECORD = TransactionRecord(
450
+ confirmed_at_height=uint32(0),
451
+ created_at_time=uint64(0),
452
+ to_puzzle_hash=bytes32.zeros,
453
+ amount=uint64(0),
454
+ fee_amount=uint64(0),
455
+ confirmed=False,
456
+ sent=uint32(0),
457
+ spend_bundle=WalletSpendBundle([], G2Element()),
458
+ additions=[],
459
+ removals=[],
460
+ wallet_id=uint32(0),
461
+ sent_to=[],
462
+ trade_id=None,
463
+ type=uint32(0),
464
+ name=bytes32.zeros,
465
+ memos=[],
466
+ valid_times=ConditionValidTimes(),
467
+ )
468
+
469
+
364
470
  class WalletRpcApi:
365
471
  if TYPE_CHECKING:
366
472
  from chia.rpc.rpc_server import RpcApiProtocol
@@ -843,7 +949,7 @@ class WalletRpcApi:
843
949
  for tx in interface.side_effects.transactions
844
950
  if tx.spend_bundle is not None
845
951
  for cs in tx.spend_bundle.coin_spends
846
- for condition in cs.puzzle_reveal.run_with_cost(INFINITE_COST, cs.solution)[1].as_iter()
952
+ for condition in run_with_cost(cs.puzzle_reveal, INFINITE_COST, cs.solution)[1].as_iter()
847
953
  ]
848
954
  create_coin_announcement = next(
849
955
  condition
@@ -870,7 +976,7 @@ class WalletRpcApi:
870
976
  await self.service.wallet_state_manager.main_wallet.create_tandem_xch_tx(
871
977
  request.fee,
872
978
  inner_action_scope,
873
- (
979
+ extra_conditions=(
874
980
  *extra_conditions,
875
981
  CreateCoinAnnouncement(
876
982
  create_coin_announcement.msg, announcement_origin
@@ -1106,9 +1212,7 @@ class WalletRpcApi:
1106
1212
  if "initial_target_state" not in request:
1107
1213
  raise AttributeError("Daemon didn't send `initial_target_state`. Try updating the daemon.")
1108
1214
 
1109
- owner_puzzle_hash: bytes32 = await self.service.wallet_state_manager.main_wallet.get_puzzle_hash(
1110
- new=not action_scope.config.tx_config.reuse_puzhash
1111
- )
1215
+ owner_puzzle_hash: bytes32 = await action_scope.get_puzzle_hash(self.service.wallet_state_manager)
1112
1216
 
1113
1217
  from chia.pools.pool_wallet_info import initial_pool_state_from_dict
1114
1218
 
@@ -1121,17 +1225,15 @@ class WalletRpcApi:
1121
1225
  max_pwi = 1
1122
1226
  for _, wallet in self.service.wallet_state_manager.wallets.items():
1123
1227
  if wallet.type() == WalletType.POOLING_WALLET:
1124
- assert isinstance(wallet, PoolWallet)
1125
- pool_wallet_index = await wallet.get_pool_wallet_index()
1126
- max_pwi = max(max_pwi, pool_wallet_index)
1228
+ max_pwi += 1
1127
1229
 
1128
1230
  if max_pwi + 1 >= (MAX_POOL_WALLETS - 1):
1129
1231
  raise ValueError(f"Too many pool wallets ({max_pwi}), cannot create any more on this key.")
1130
1232
 
1131
- owner_sk: PrivateKey = master_sk_to_singleton_owner_sk(
1132
- self.service.wallet_state_manager.get_master_private_key(), uint32(max_pwi + 1)
1233
+ owner_pk: G1Element = self.service.wallet_state_manager.main_wallet.hardened_pubkey_for_path(
1234
+ # copied from chia.wallet.derive_keys. Could maybe be an exported constant in the future.
1235
+ [12381, 8444, 5, max_pwi]
1133
1236
  )
1134
- owner_pk: G1Element = owner_sk.get_g1()
1135
1237
 
1136
1238
  initial_target_state = initial_pool_state_from_dict(
1137
1239
  request["initial_target_state"], owner_pk, owner_puzzle_hash
@@ -1184,7 +1286,7 @@ class WalletRpcApi:
1184
1286
  wallet_balance["wallet_type"] = wallet.type()
1185
1287
  if self.service.logged_in_fingerprint is not None:
1186
1288
  wallet_balance["fingerprint"] = self.service.logged_in_fingerprint
1187
- if wallet.type() in {WalletType.CAT, WalletType.CRCAT}:
1289
+ if wallet.type() in {WalletType.CAT, WalletType.CRCAT, WalletType.RCAT}:
1188
1290
  assert isinstance(wallet, CATWallet)
1189
1291
  wallet_balance["asset_id"] = wallet.get_asset_id()
1190
1292
  if wallet.type() == WalletType.CRCAT:
@@ -1274,9 +1376,9 @@ class WalletRpcApi:
1274
1376
 
1275
1377
  outputs = [
1276
1378
  CreateCoin(
1277
- await wallet.get_puzzle_hash(new=True)
1278
- if isinstance(wallet, Wallet)
1279
- else await wallet.standard_wallet.get_puzzle_hash(new=True),
1379
+ await action_scope.get_puzzle_hash(
1380
+ self.service.wallet_state_manager, override_reuse_puzhash_with=False
1381
+ ),
1280
1382
  request.amount_per_coin,
1281
1383
  )
1282
1384
  for _ in range(request.number_of_coins)
@@ -1388,13 +1490,10 @@ class WalletRpcApi:
1388
1490
  )
1389
1491
  if isinstance(wallet, Wallet):
1390
1492
  primary_output_amount = uint64(primary_output_amount - request.fee)
1391
- main_wallet = wallet
1392
- else:
1393
- main_wallet = wallet.standard_wallet
1394
1493
 
1395
1494
  await wallet.generate_signed_transaction(
1396
1495
  [primary_output_amount],
1397
- [await main_wallet.get_puzzle_hash(new=not action_scope.config.tx_config.reuse_puzhash)],
1496
+ [await action_scope.get_puzzle_hash(self.service.wallet_state_manager)],
1398
1497
  action_scope,
1399
1498
  request.fee,
1400
1499
  coins=set(coins),
@@ -1480,13 +1579,13 @@ class WalletRpcApi:
1480
1579
  wallet = self.service.wallet_state_manager.wallets[wallet_id]
1481
1580
  selected = self.service.config["selected_network"]
1482
1581
  prefix = self.service.config["network_overrides"]["config"][selected]["address_prefix"]
1483
- if wallet.type() == WalletType.STANDARD_WALLET:
1484
- assert isinstance(wallet, Wallet)
1485
- raw_puzzle_hash = await wallet.get_puzzle_hash(create_new)
1486
- address = encode_puzzle_hash(raw_puzzle_hash, prefix)
1487
- elif wallet.type() in {WalletType.CAT, WalletType.CRCAT}:
1488
- assert isinstance(wallet, CATWallet)
1489
- raw_puzzle_hash = await wallet.standard_wallet.get_puzzle_hash(create_new)
1582
+ if wallet.type() in {WalletType.STANDARD_WALLET, WalletType.CAT, WalletType.CRCAT, WalletType.RCAT}:
1583
+ async with self.service.wallet_state_manager.new_action_scope(
1584
+ DEFAULT_TX_CONFIG, push=request.get("save_derivations", True)
1585
+ ) as action_scope:
1586
+ raw_puzzle_hash = await action_scope.get_puzzle_hash(
1587
+ self.service.wallet_state_manager, override_reuse_puzhash_with=not create_new
1588
+ )
1490
1589
  address = encode_puzzle_hash(raw_puzzle_hash, prefix)
1491
1590
  else:
1492
1591
  raise ValueError(f"Wallet type {wallet.type()} cannot create puzzle hashes")
@@ -1555,7 +1654,7 @@ class WalletRpcApi:
1555
1654
  wallet = self.service.wallet_state_manager.wallets[wallet_id]
1556
1655
 
1557
1656
  async with self.service.wallet_state_manager.lock:
1558
- if wallet.type() in {WalletType.CAT, WalletType.CRCAT}:
1657
+ if wallet.type() in {WalletType.CAT, WalletType.CRCAT, WalletType.RCAT}:
1559
1658
  assert isinstance(wallet, CATWallet)
1560
1659
  response = await self.cat_spend(request, hold_lock=False)
1561
1660
  transaction = response["transaction"]
@@ -1708,7 +1807,7 @@ class WalletRpcApi:
1708
1807
  wallet = state_mgr.wallets[wallet_id]
1709
1808
  async with state_mgr.lock:
1710
1809
  all_coin_records = await state_mgr.coin_store.get_unspent_coins_for_wallet(wallet_id)
1711
- if wallet.type() in {WalletType.CAT, WalletType.CRCAT}:
1810
+ if wallet.type() in {WalletType.CAT, WalletType.CRCAT, WalletType.RCAT}:
1712
1811
  assert isinstance(wallet, CATWallet)
1713
1812
  spendable_coins: list[WalletCoinRecord] = await wallet.get_cat_spendable_coins(all_coin_records)
1714
1813
  else:
@@ -1828,9 +1927,10 @@ class WalletRpcApi:
1828
1927
  # Since we've bumping the derivation index without having found any new puzzles, we want
1829
1928
  # to preserve the current last used index, so we call create_more_puzzle_hashes with
1830
1929
  # mark_existing_as_used=False
1831
- await self.service.wallet_state_manager.create_more_puzzle_hashes(
1930
+ result = await self.service.wallet_state_manager.create_more_puzzle_hashes(
1832
1931
  from_zero=False, mark_existing_as_used=False, up_to_index=index, num_additional_phs=0
1833
1932
  )
1933
+ await result.commit(self.service.wallet_state_manager)
1834
1934
 
1835
1935
  updated: Optional[uint32] = await self.service.wallet_state_manager.puzzle_store.get_last_derivation_path()
1836
1936
  updated_index = updated if updated is not None else None
@@ -2476,88 +2576,79 @@ class WalletRpcApi:
2476
2576
  # Distributed Identities
2477
2577
  ##########################################################################################
2478
2578
 
2479
- async def did_set_wallet_name(self, request: dict[str, Any]) -> EndpointResult:
2480
- wallet_id = uint32(request["wallet_id"])
2481
- wallet = self.service.wallet_state_manager.get_wallet(id=wallet_id, required_type=DIDWallet)
2482
- await wallet.set_name(str(request["name"]))
2483
- return {"success": True, "wallet_id": wallet_id}
2579
+ @marshal
2580
+ async def did_set_wallet_name(self, request: DIDSetWalletName) -> DIDSetWalletNameResponse:
2581
+ wallet = self.service.wallet_state_manager.get_wallet(id=request.wallet_id, required_type=DIDWallet)
2582
+ await wallet.set_name(request.name)
2583
+ return DIDSetWalletNameResponse(request.wallet_id)
2484
2584
 
2485
- async def did_get_wallet_name(self, request: dict[str, Any]) -> EndpointResult:
2486
- wallet_id = uint32(request["wallet_id"])
2487
- wallet = self.service.wallet_state_manager.get_wallet(id=wallet_id, required_type=DIDWallet)
2488
- name: str = wallet.get_name() # type: ignore[no-untyped-call] # Missing hint in `did_wallet.py`
2489
- return {"success": True, "wallet_id": wallet_id, "name": name}
2585
+ @marshal
2586
+ async def did_get_wallet_name(self, request: DIDGetWalletName) -> DIDGetWalletNameResponse:
2587
+ wallet = self.service.wallet_state_manager.get_wallet(id=request.wallet_id, required_type=DIDWallet)
2588
+ return DIDGetWalletNameResponse(request.wallet_id, wallet.get_name())
2490
2589
 
2491
2590
  @tx_endpoint(push=True)
2591
+ @marshal
2492
2592
  async def did_update_recovery_ids(
2493
2593
  self,
2494
- request: dict[str, Any],
2594
+ request: DIDUpdateRecoveryIDs,
2495
2595
  action_scope: WalletActionScope,
2496
2596
  extra_conditions: tuple[Condition, ...] = tuple(),
2497
- ) -> EndpointResult:
2498
- wallet_id = uint32(request["wallet_id"])
2499
- wallet = self.service.wallet_state_manager.get_wallet(id=wallet_id, required_type=DIDWallet)
2500
- recovery_list = []
2501
- for _ in request["new_list"]:
2502
- recovery_list.append(decode_puzzle_hash(_))
2503
- if "num_verifications_required" in request:
2504
- new_amount_verifications_required = uint64(request["num_verifications_required"])
2505
- else:
2506
- new_amount_verifications_required = uint64(len(recovery_list))
2597
+ ) -> DIDUpdateRecoveryIDsResponse:
2598
+ wallet = self.service.wallet_state_manager.get_wallet(id=request.wallet_id, required_type=DIDWallet)
2599
+ recovery_list = [decode_puzzle_hash(puzzle_hash) for puzzle_hash in request.new_list]
2600
+ new_amount_verifications_required = (
2601
+ request.num_verifications_required
2602
+ if request.num_verifications_required is not None
2603
+ else uint64(len(recovery_list))
2604
+ )
2507
2605
  async with self.service.wallet_state_manager.lock:
2508
2606
  update_success = await wallet.update_recovery_list(recovery_list, new_amount_verifications_required)
2509
2607
  # Update coin with new ID info
2510
2608
  if update_success:
2511
- await wallet.create_update_spend(
2512
- action_scope, fee=uint64(request.get("fee", 0)), extra_conditions=extra_conditions
2513
- )
2514
- return {
2515
- "success": True,
2516
- "transactions": None, # tx_endpoint wrapper will take care of this
2517
- }
2609
+ await wallet.create_update_spend(action_scope, fee=request.fee, extra_conditions=extra_conditions)
2610
+ # tx_endpoint will take care of default values here
2611
+ return DIDUpdateRecoveryIDsResponse([], [])
2518
2612
  else:
2519
- return {"success": False, "transactions": []} # pragma: no cover
2613
+ raise RuntimeError("updating recovery list failed")
2520
2614
 
2521
2615
  @tx_endpoint(push=False)
2616
+ @marshal
2522
2617
  async def did_message_spend(
2523
2618
  self,
2524
- request: dict[str, Any],
2619
+ request: DIDMessageSpend,
2525
2620
  action_scope: WalletActionScope,
2526
2621
  extra_conditions: tuple[Condition, ...] = tuple(),
2527
- ) -> EndpointResult:
2528
- wallet_id = uint32(request["wallet_id"])
2529
- wallet = self.service.wallet_state_manager.get_wallet(id=wallet_id, required_type=DIDWallet)
2622
+ ) -> DIDMessageSpendResponse:
2623
+ wallet = self.service.wallet_state_manager.get_wallet(id=request.wallet_id, required_type=DIDWallet)
2530
2624
 
2531
2625
  await wallet.create_message_spend(
2532
2626
  action_scope,
2533
2627
  extra_conditions=(
2534
2628
  *extra_conditions,
2535
- *(CreateCoinAnnouncement(hexstr_to_bytes(ca)) for ca in request.get("coin_announcements", [])),
2536
- *(CreatePuzzleAnnouncement(hexstr_to_bytes(pa)) for pa in request.get("puzzle_announcements", [])),
2629
+ *(CreateCoinAnnouncement(ca) for ca in request.coin_announcements),
2630
+ *(CreatePuzzleAnnouncement(pa) for pa in request.puzzle_announcements),
2537
2631
  ),
2538
2632
  )
2539
- return {
2540
- "success": True,
2541
- "spend_bundle": None, # tx_endpoint wrapper will take care of this
2542
- "transactions": None, # tx_endpoint wrapper will take care of this
2543
- }
2544
2633
 
2545
- async def did_get_info(self, request: dict[str, Any]) -> EndpointResult:
2546
- if "coin_id" not in request:
2547
- return {"success": False, "error": "Coin ID is required."}
2548
- coin_id = request["coin_id"]
2549
- if coin_id.startswith(AddressType.DID.hrp(self.service.config)):
2550
- coin_id = decode_puzzle_hash(coin_id)
2634
+ # tx_endpoint will take care of the default values here
2635
+ return DIDMessageSpendResponse([], [], WalletSpendBundle([], G2Element()))
2636
+
2637
+ @marshal
2638
+ async def did_get_info(self, request: DIDGetInfo) -> DIDGetInfoResponse:
2639
+ if request.coin_id.startswith(AddressType.DID.hrp(self.service.config)):
2640
+ coin_id = decode_puzzle_hash(request.coin_id)
2551
2641
  else:
2552
- coin_id = bytes32.from_hexstr(coin_id)
2642
+ coin_id = bytes32.from_hexstr(request.coin_id)
2553
2643
  # Get coin state
2554
2644
  peer = self.service.get_full_node_peer()
2555
- coin_spend, coin_state = await self.get_latest_singleton_coin_spend(peer, coin_id, request.get("latest", True))
2645
+ coin_spend, coin_state = await self.get_latest_singleton_coin_spend(peer, coin_id, request.latest)
2556
2646
  uncurried = uncurry_puzzle(coin_spend.puzzle_reveal)
2557
2647
  curried_args = match_did_puzzle(uncurried.mod, uncurried.args)
2558
2648
  if curried_args is None:
2559
- return {"success": False, "error": "The coin is not a DID."}
2649
+ raise ValueError("The coin is not a DID.")
2560
2650
  p2_puzzle, recovery_list_hash, num_verification, singleton_struct, metadata = curried_args
2651
+ recovery_list_hash_bytes = recovery_list_hash.as_atom()
2561
2652
  launcher_id = bytes32(singleton_struct.rest().first().as_atom())
2562
2653
  uncurried_p2 = uncurry_puzzle(p2_puzzle)
2563
2654
  (public_key,) = uncurried_p2.args.as_iter()
@@ -2566,49 +2657,48 @@ class WalletRpcApi:
2566
2657
  coin_memos = memos.get(coin_state.coin.name())
2567
2658
  if coin_memos is not None:
2568
2659
  for memo in coin_memos:
2569
- hints.append(memo.hex())
2570
- return {
2571
- "success": True,
2572
- "did_id": encode_puzzle_hash(launcher_id, AddressType.DID.hrp(self.service.config)),
2573
- "latest_coin": coin_state.coin.name().hex(),
2574
- "p2_address": encode_puzzle_hash(p2_puzzle.get_tree_hash(), AddressType.XCH.hrp(self.service.config)),
2575
- "public_key": public_key.as_atom().hex(),
2576
- "recovery_list_hash": recovery_list_hash.as_atom().hex(),
2577
- "num_verification": num_verification.as_int(),
2578
- "metadata": did_program_to_metadata(metadata),
2579
- "launcher_id": launcher_id.hex(),
2580
- "full_puzzle": coin_spend.puzzle_reveal,
2581
- "solution": coin_spend.solution.to_program().as_python(),
2582
- "hints": hints,
2583
- }
2660
+ hints.append(memo)
2661
+ return DIDGetInfoResponse(
2662
+ did_id=encode_puzzle_hash(launcher_id, AddressType.DID.hrp(self.service.config)),
2663
+ latest_coin=coin_state.coin.name(),
2664
+ p2_address=encode_puzzle_hash(p2_puzzle.get_tree_hash(), AddressType.XCH.hrp(self.service.config)),
2665
+ public_key=public_key.as_atom(),
2666
+ recovery_list_hash=bytes32(recovery_list_hash_bytes) if recovery_list_hash_bytes != b"" else None,
2667
+ num_verification=uint16(num_verification.as_int()),
2668
+ metadata=did_program_to_metadata(metadata),
2669
+ launcher_id=launcher_id,
2670
+ full_puzzle=Program.from_serialized(coin_spend.puzzle_reveal),
2671
+ solution=Program.from_serialized(coin_spend.solution),
2672
+ hints=hints,
2673
+ )
2584
2674
 
2585
- async def did_find_lost_did(self, request: dict[str, Any]) -> EndpointResult:
2675
+ @marshal
2676
+ async def did_find_lost_did(self, request: DIDFindLostDID) -> DIDFindLostDIDResponse:
2586
2677
  """
2587
2678
  Recover a missing or unspendable DID wallet by a coin id of the DID
2588
2679
  :param coin_id: It can be DID ID, launcher coin ID or any coin ID of the DID you want to find.
2589
2680
  The latest coin ID will take less time.
2590
2681
  :return:
2591
2682
  """
2592
- if "coin_id" not in request:
2593
- return {"success": False, "error": "DID coin ID is required."}
2594
- coin_id = request["coin_id"]
2595
2683
  # Check if we have a DID wallet for this
2596
- if coin_id.startswith(AddressType.DID.hrp(self.service.config)):
2597
- coin_id = decode_puzzle_hash(coin_id)
2684
+ if request.coin_id.startswith(AddressType.DID.hrp(self.service.config)):
2685
+ coin_id = decode_puzzle_hash(request.coin_id)
2598
2686
  else:
2599
- coin_id = bytes32.from_hexstr(coin_id)
2687
+ coin_id = bytes32.from_hexstr(request.coin_id)
2600
2688
  # Get coin state
2601
2689
  peer = self.service.get_full_node_peer()
2602
2690
  coin_spend, coin_state = await self.get_latest_singleton_coin_spend(peer, coin_id)
2603
2691
  uncurried = uncurry_puzzle(coin_spend.puzzle_reveal)
2604
2692
  curried_args = match_did_puzzle(uncurried.mod, uncurried.args)
2605
2693
  if curried_args is None:
2606
- return {"success": False, "error": "The coin is not a DID."}
2694
+ raise ValueError("The coin is not a DID.")
2607
2695
  p2_puzzle, recovery_list_hash, num_verification, singleton_struct, metadata = curried_args
2696
+ num_verification_int: Optional[uint16] = uint16(num_verification.as_int())
2697
+ assert num_verification_int is not None
2608
2698
  did_data: DIDCoinData = DIDCoinData(
2609
2699
  p2_puzzle,
2610
- bytes32(recovery_list_hash.as_atom()),
2611
- uint16(num_verification.as_int()),
2700
+ bytes32(recovery_list_hash.as_atom()) if recovery_list_hash != Program.to(None) else None,
2701
+ num_verification_int,
2612
2702
  singleton_struct,
2613
2703
  metadata,
2614
2704
  get_inner_puzzle_from_singleton(coin_spend.puzzle_reveal),
@@ -2636,7 +2726,7 @@ class WalletRpcApi:
2636
2726
 
2637
2727
  launcher_id = bytes32(singleton_struct.rest().first().as_atom())
2638
2728
  if derivation_record is None:
2639
- return {"success": False, "error": f"This DID {launcher_id} does not belong to the connected wallet"}
2729
+ raise ValueError(f"This DID {launcher_id} does not belong to the connected wallet")
2640
2730
  else:
2641
2731
  our_inner_puzzle: Program = self.service.wallet_state_manager.main_wallet.puzzle_for_pk(
2642
2732
  derivation_record.pubkey
@@ -2672,11 +2762,12 @@ class WalletRpcApi:
2672
2762
  did_puzzle = did_wallet.did_info.current_inner
2673
2763
  else:
2674
2764
  # Try override
2675
- if "recovery_list_hash" in request:
2676
- recovery_list_hash = Program.from_bytes(bytes.fromhex(request["recovery_list_hash"]))
2677
- num_verification = request.get("num_verification", num_verification)
2678
- if "metadata" in request:
2679
- metadata = metadata_to_program(request["metadata"])
2765
+ if request.recovery_list_hash is not None:
2766
+ recovery_list_hash = Program.from_bytes(request.recovery_list_hash)
2767
+ if request.num_verification is not None:
2768
+ num_verification_int = request.num_verification
2769
+ if request.metadata is not None:
2770
+ metadata = metadata_to_program(request.metadata)
2680
2771
  did_puzzle = DID_INNERPUZ_MOD.curry(
2681
2772
  our_inner_puzzle, recovery_list_hash, num_verification, singleton_struct, metadata
2682
2773
  )
@@ -2708,17 +2799,16 @@ class WalletRpcApi:
2708
2799
  )
2709
2800
 
2710
2801
  if not matched:
2711
- return {
2712
- "success": False,
2713
- "error": f"Cannot recover DID {launcher_id}"
2714
- f" because the last spend updated recovery_list_hash/num_verification/metadata.",
2715
- }
2802
+ raise RuntimeError(
2803
+ f"Cannot recover DID {launcher_id} "
2804
+ f"because the last spend updated recovery_list_hash/num_verification/metadata."
2805
+ )
2716
2806
 
2717
2807
  if did_wallet is None:
2718
2808
  # Create DID wallet
2719
2809
  response: list[CoinState] = await self.service.get_coin_state([launcher_id], peer=peer)
2720
2810
  if len(response) == 0:
2721
- return {"success": False, "error": f"Could not find the launch coin with ID: {launcher_id}"}
2811
+ raise ValueError(f"Could not find the launch coin with ID: {launcher_id}")
2722
2812
  launcher_coin: CoinState = response[0]
2723
2813
  did_wallet = await DIDWallet.create_new_did_wallet_from_coin_spend(
2724
2814
  self.service.wallet_state_manager,
@@ -2736,7 +2826,7 @@ class WalletRpcApi:
2736
2826
  inner_solution: Program = full_solution.rest().rest().first()
2737
2827
  recovery_list: list[bytes32] = []
2738
2828
  backup_required: int = num_verification.as_int()
2739
- if recovery_list_hash != NIL_TREEHASH:
2829
+ if not did_recovery_is_nil(recovery_list_hash):
2740
2830
  try:
2741
2831
  for did in inner_solution.rest().rest().rest().rest().rest().as_python():
2742
2832
  recovery_list.append(did[0])
@@ -2761,7 +2851,7 @@ class WalletRpcApi:
2761
2851
  try:
2762
2852
  coin = await did_wallet.get_coin()
2763
2853
  if coin.name() == coin_state.coin.name():
2764
- return {"success": True, "latest_coin_id": coin.name().hex()}
2854
+ return DIDFindLostDIDResponse(coin.name())
2765
2855
  except RuntimeError:
2766
2856
  # We don't have any coin for this wallet, add the coin
2767
2857
  pass
@@ -2779,70 +2869,61 @@ class WalletRpcApi:
2779
2869
  peer,
2780
2870
  did_data,
2781
2871
  )
2782
- return {"success": True, "latest_coin_id": coin_state.coin.name().hex()}
2872
+ return DIDFindLostDIDResponse(coin_state.coin.name())
2783
2873
 
2784
2874
  @tx_endpoint(push=True)
2875
+ @marshal
2785
2876
  async def did_update_metadata(
2786
2877
  self,
2787
- request: dict[str, Any],
2878
+ request: DIDUpdateMetadata,
2788
2879
  action_scope: WalletActionScope,
2789
2880
  extra_conditions: tuple[Condition, ...] = tuple(),
2790
- ) -> EndpointResult:
2791
- wallet_id = uint32(request["wallet_id"])
2792
- wallet = self.service.wallet_state_manager.get_wallet(id=wallet_id, required_type=DIDWallet)
2793
- metadata: dict[str, str] = {}
2794
- if "metadata" in request and type(request["metadata"]) is dict:
2795
- metadata = request["metadata"]
2881
+ ) -> DIDUpdateMetadataResponse:
2882
+ wallet = self.service.wallet_state_manager.get_wallet(id=request.wallet_id, required_type=DIDWallet)
2796
2883
  async with self.service.wallet_state_manager.lock:
2797
- update_success = await wallet.update_metadata(metadata)
2884
+ update_success = await wallet.update_metadata(request.metadata)
2798
2885
  # Update coin with new ID info
2799
2886
  if update_success:
2800
- await wallet.create_update_spend(
2801
- action_scope, uint64(request.get("fee", 0)), extra_conditions=extra_conditions
2887
+ await wallet.create_update_spend(action_scope, request.fee, extra_conditions=extra_conditions)
2888
+ # tx_endpoint wrapper will take care of these default values
2889
+ return DIDUpdateMetadataResponse(
2890
+ [],
2891
+ [],
2892
+ wallet_id=request.wallet_id,
2893
+ spend_bundle=WalletSpendBundle([], G2Element()),
2802
2894
  )
2803
- return {
2804
- "wallet_id": wallet_id,
2805
- "success": True,
2806
- "spend_bundle": None, # tx_endpoint wrapper will take care of this
2807
- "transactions": None, # tx_endpoint wrapper will take care of this
2808
- }
2809
2895
  else:
2810
- return {"success": False, "error": f"Couldn't update metadata with input: {metadata}"}
2896
+ raise ValueError(f"Couldn't update metadata with input: {request.metadata}")
2811
2897
 
2812
- async def did_get_did(self, request: dict[str, Any]) -> EndpointResult:
2813
- wallet_id = uint32(request["wallet_id"])
2814
- wallet = self.service.wallet_state_manager.get_wallet(id=wallet_id, required_type=DIDWallet)
2898
+ @marshal
2899
+ async def did_get_did(self, request: DIDGetDID) -> DIDGetDIDResponse:
2900
+ wallet = self.service.wallet_state_manager.get_wallet(id=request.wallet_id, required_type=DIDWallet)
2815
2901
  my_did: str = encode_puzzle_hash(bytes32.fromhex(wallet.get_my_DID()), AddressType.DID.hrp(self.service.config))
2816
2902
  async with self.service.wallet_state_manager.lock:
2817
2903
  try:
2818
2904
  coin = await wallet.get_coin()
2819
- return {"success": True, "wallet_id": wallet_id, "my_did": my_did, "coin_id": coin.name()}
2905
+ return DIDGetDIDResponse(wallet_id=request.wallet_id, my_did=my_did, coin_id=coin.name())
2820
2906
  except RuntimeError:
2821
- return {"success": True, "wallet_id": wallet_id, "my_did": my_did}
2907
+ return DIDGetDIDResponse(wallet_id=request.wallet_id, my_did=my_did)
2822
2908
 
2823
- async def did_get_recovery_list(self, request: dict[str, Any]) -> EndpointResult:
2824
- wallet_id = uint32(request["wallet_id"])
2825
- wallet = self.service.wallet_state_manager.get_wallet(id=wallet_id, required_type=DIDWallet)
2909
+ @marshal
2910
+ async def did_get_recovery_list(self, request: DIDGetRecoveryList) -> DIDGetRecoveryListResponse:
2911
+ wallet = self.service.wallet_state_manager.get_wallet(id=request.wallet_id, required_type=DIDWallet)
2826
2912
  recovery_list = wallet.did_info.backup_ids
2827
2913
  recovery_dids = []
2828
2914
  for backup_id in recovery_list:
2829
2915
  recovery_dids.append(encode_puzzle_hash(backup_id, AddressType.DID.hrp(self.service.config)))
2830
- return {
2831
- "success": True,
2832
- "wallet_id": wallet_id,
2833
- "recovery_list": recovery_dids,
2834
- "num_required": wallet.did_info.num_of_backup_ids_needed,
2835
- }
2916
+ return DIDGetRecoveryListResponse(
2917
+ wallet_id=request.wallet_id,
2918
+ recovery_list=recovery_dids,
2919
+ num_required=uint16(wallet.did_info.num_of_backup_ids_needed),
2920
+ )
2836
2921
 
2837
- async def did_get_metadata(self, request: dict[str, Any]) -> EndpointResult:
2838
- wallet_id = uint32(request["wallet_id"])
2839
- wallet = self.service.wallet_state_manager.get_wallet(id=wallet_id, required_type=DIDWallet)
2922
+ @marshal
2923
+ async def did_get_metadata(self, request: DIDGetMetadata) -> DIDGetMetadataResponse:
2924
+ wallet = self.service.wallet_state_manager.get_wallet(id=request.wallet_id, required_type=DIDWallet)
2840
2925
  metadata = json.loads(wallet.did_info.metadata)
2841
- return {
2842
- "success": True,
2843
- "wallet_id": wallet_id,
2844
- "metadata": metadata,
2845
- }
2926
+ return DIDGetMetadataResponse(wallet_id=request.wallet_id, metadata=metadata)
2846
2927
 
2847
2928
  # TODO: this needs a test
2848
2929
  # Don't need full @tx_endpoint decorator here, but "push" is still a valid option
@@ -2888,11 +2969,12 @@ class WalletRpcApi:
2888
2969
  "transactions": [tx.to_json_dict_convenience(self.service.config)],
2889
2970
  }
2890
2971
 
2891
- async def did_get_pubkey(self, request: dict[str, Any]) -> EndpointResult:
2892
- wallet_id = uint32(request["wallet_id"])
2893
- wallet = self.service.wallet_state_manager.get_wallet(id=wallet_id, required_type=DIDWallet)
2894
- pubkey = bytes((await wallet.wallet_state_manager.get_unused_derivation_record(wallet_id)).pubkey).hex()
2895
- return {"success": True, "pubkey": pubkey}
2972
+ @marshal
2973
+ async def did_get_pubkey(self, request: DIDGetPubkey) -> DIDGetPubkeyResponse:
2974
+ wallet = self.service.wallet_state_manager.get_wallet(id=request.wallet_id, required_type=DIDWallet)
2975
+ return DIDGetPubkeyResponse(
2976
+ (await wallet.wallet_state_manager.get_unused_derivation_record(request.wallet_id)).pubkey
2977
+ )
2896
2978
 
2897
2979
  # TODO: this needs a test
2898
2980
  @tx_endpoint(push=True)
@@ -2926,27 +3008,28 @@ class WalletRpcApi:
2926
3008
  else:
2927
3009
  return {"success": False}
2928
3010
 
2929
- async def did_get_information_needed_for_recovery(self, request: dict[str, Any]) -> EndpointResult:
2930
- wallet_id = uint32(request["wallet_id"])
2931
- did_wallet = self.service.wallet_state_manager.get_wallet(id=wallet_id, required_type=DIDWallet)
3011
+ @marshal
3012
+ async def did_get_information_needed_for_recovery(self, request: DIDGetRecoveryInfo) -> DIDGetRecoveryInfoResponse:
3013
+ did_wallet = self.service.wallet_state_manager.get_wallet(id=request.wallet_id, required_type=DIDWallet)
2932
3014
  my_did = encode_puzzle_hash(
2933
3015
  bytes32.from_hexstr(did_wallet.get_my_DID()), AddressType.DID.hrp(self.service.config)
2934
3016
  )
2935
3017
  assert did_wallet.did_info.temp_coin is not None
2936
- coin_name = did_wallet.did_info.temp_coin.name().hex()
2937
- return {
2938
- "success": True,
2939
- "wallet_id": wallet_id,
2940
- "my_did": my_did,
2941
- "coin_name": coin_name,
2942
- "newpuzhash": did_wallet.did_info.temp_puzhash,
2943
- "pubkey": did_wallet.did_info.temp_pubkey,
2944
- "backup_dids": did_wallet.did_info.backup_ids,
2945
- }
3018
+ coin_name = did_wallet.did_info.temp_coin.name()
3019
+ return DIDGetRecoveryInfoResponse(
3020
+ wallet_id=request.wallet_id,
3021
+ my_did=my_did,
3022
+ coin_name=coin_name,
3023
+ newpuzhash=did_wallet.did_info.temp_puzhash,
3024
+ pubkey=G1Element.from_bytes(did_wallet.did_info.temp_pubkey)
3025
+ if did_wallet.did_info.temp_pubkey is not None
3026
+ else None,
3027
+ backup_dids=did_wallet.did_info.backup_ids,
3028
+ )
2946
3029
 
2947
- async def did_get_current_coin_info(self, request: dict[str, Any]) -> EndpointResult:
2948
- wallet_id = uint32(request["wallet_id"])
2949
- did_wallet = self.service.wallet_state_manager.get_wallet(id=wallet_id, required_type=DIDWallet)
3030
+ @marshal
3031
+ async def did_get_current_coin_info(self, request: DIDGetCurrentCoinInfo) -> DIDGetCurrentCoinInfoResponse:
3032
+ did_wallet = self.service.wallet_state_manager.get_wallet(id=request.wallet_id, required_type=DIDWallet)
2950
3033
  my_did = encode_puzzle_hash(
2951
3034
  bytes32.from_hexstr(did_wallet.get_my_DID()), AddressType.DID.hrp(self.service.config)
2952
3035
  )
@@ -2954,210 +3037,177 @@ class WalletRpcApi:
2954
3037
  did_coin_threeple = await did_wallet.get_info_for_recovery()
2955
3038
  assert my_did is not None
2956
3039
  assert did_coin_threeple is not None
2957
- return {
2958
- "success": True,
2959
- "wallet_id": wallet_id,
2960
- "my_did": my_did,
2961
- "did_parent": did_coin_threeple[0],
2962
- "did_innerpuz": did_coin_threeple[1],
2963
- "did_amount": did_coin_threeple[2],
2964
- }
3040
+ return DIDGetCurrentCoinInfoResponse(
3041
+ wallet_id=request.wallet_id,
3042
+ my_did=my_did,
3043
+ did_parent=did_coin_threeple[0],
3044
+ did_innerpuz=did_coin_threeple[1],
3045
+ did_amount=did_coin_threeple[2],
3046
+ )
2965
3047
 
2966
- async def did_create_backup_file(self, request: dict[str, Any]) -> EndpointResult:
2967
- wallet_id = uint32(request["wallet_id"])
2968
- did_wallet = self.service.wallet_state_manager.get_wallet(id=wallet_id, required_type=DIDWallet)
2969
- return {"wallet_id": wallet_id, "success": True, "backup_data": did_wallet.create_backup()}
3048
+ @marshal
3049
+ async def did_create_backup_file(self, request: DIDCreateBackupFile) -> DIDCreateBackupFileResponse:
3050
+ did_wallet = self.service.wallet_state_manager.get_wallet(id=request.wallet_id, required_type=DIDWallet)
3051
+ return DIDCreateBackupFileResponse(wallet_id=request.wallet_id, backup_data=did_wallet.create_backup())
2970
3052
 
2971
3053
  @tx_endpoint(push=True)
3054
+ @marshal
2972
3055
  async def did_transfer_did(
2973
3056
  self,
2974
- request: dict[str, Any],
3057
+ request: DIDTransferDID,
2975
3058
  action_scope: WalletActionScope,
2976
3059
  extra_conditions: tuple[Condition, ...] = tuple(),
2977
- ) -> EndpointResult:
3060
+ ) -> DIDTransferDIDResponse:
2978
3061
  if await self.service.wallet_state_manager.synced() is False:
2979
3062
  raise ValueError("Wallet needs to be fully synced.")
2980
- wallet_id = uint32(request["wallet_id"])
2981
- did_wallet = self.service.wallet_state_manager.get_wallet(id=wallet_id, required_type=DIDWallet)
2982
- puzzle_hash: bytes32 = decode_puzzle_hash(request["inner_address"])
3063
+ did_wallet = self.service.wallet_state_manager.get_wallet(id=request.wallet_id, required_type=DIDWallet)
3064
+ puzzle_hash: bytes32 = decode_puzzle_hash(request.inner_address)
2983
3065
  async with self.service.wallet_state_manager.lock:
2984
3066
  await did_wallet.transfer_did(
2985
3067
  puzzle_hash,
2986
- uint64(request.get("fee", 0)),
2987
- request.get("with_recovery_info", True),
3068
+ request.fee,
3069
+ request.with_recovery_info,
2988
3070
  action_scope,
2989
3071
  extra_conditions=extra_conditions,
2990
3072
  )
2991
3073
 
2992
- return {
2993
- "success": True,
2994
- "transaction": None, # tx_endpoint wrapper will take care of this
2995
- "transactions": None, # tx_endpoint wrapper will take care of this
2996
- "transaction_id": None, # tx_endpoint wrapper will take care of this
2997
- }
3074
+ # The tx_endpoint wrapper will take care of these default values
3075
+ return DIDTransferDIDResponse([], [], transaction=REPLACEABLE_TRANSACTION_RECORD, transaction_id=bytes32.zeros)
2998
3076
 
2999
3077
  ##########################################################################################
3000
3078
  # NFT Wallet
3001
3079
  ##########################################################################################
3002
3080
  @tx_endpoint(push=True)
3081
+ @marshal
3003
3082
  async def nft_mint_nft(
3004
3083
  self,
3005
- request: dict[str, Any],
3084
+ request: NFTMintNFTRequest,
3006
3085
  action_scope: WalletActionScope,
3007
3086
  extra_conditions: tuple[Condition, ...] = tuple(),
3008
- ) -> EndpointResult:
3087
+ ) -> NFTMintNFTResponse:
3009
3088
  log.debug("Got minting RPC request: %s", request)
3010
- wallet_id = uint32(request["wallet_id"])
3011
3089
  assert self.service.wallet_state_manager
3012
- nft_wallet = self.service.wallet_state_manager.get_wallet(id=wallet_id, required_type=NFTWallet)
3013
- royalty_address = request.get("royalty_address")
3014
- royalty_amount = uint16(request.get("royalty_percentage", 0))
3015
- if royalty_amount == 10000:
3090
+ nft_wallet = self.service.wallet_state_manager.get_wallet(id=request.wallet_id, required_type=NFTWallet)
3091
+ if request.royalty_amount == 10000:
3016
3092
  raise ValueError("Royalty percentage cannot be 100%")
3017
- if isinstance(royalty_address, str):
3018
- royalty_puzhash = decode_puzzle_hash(royalty_address)
3019
- elif royalty_address is None:
3020
- royalty_puzhash = await nft_wallet.standard_wallet.get_puzzle_hash(
3021
- new=not action_scope.config.tx_config.reuse_puzhash
3022
- )
3093
+ if request.royalty_address is not None:
3094
+ royalty_puzhash = decode_puzzle_hash(request.royalty_address)
3023
3095
  else:
3024
- royalty_puzhash = royalty_address
3025
- target_address = request.get("target_address")
3026
- if isinstance(target_address, str):
3027
- target_puzhash = decode_puzzle_hash(target_address)
3028
- elif target_address is None:
3029
- target_puzhash = await nft_wallet.standard_wallet.get_puzzle_hash(
3030
- new=not action_scope.config.tx_config.reuse_puzhash
3031
- )
3096
+ royalty_puzhash = await action_scope.get_puzzle_hash(self.service.wallet_state_manager)
3097
+ if request.target_address is not None:
3098
+ target_puzhash = decode_puzzle_hash(request.target_address)
3032
3099
  else:
3033
- target_puzhash = target_address
3034
- if "uris" not in request:
3035
- return {"success": False, "error": "Data URIs is required"}
3036
- if not isinstance(request["uris"], list):
3037
- return {"success": False, "error": "Data URIs must be a list"}
3038
- if not isinstance(request.get("meta_uris", []), list):
3039
- return {"success": False, "error": "Metadata URIs must be a list"}
3040
- if not isinstance(request.get("license_uris", []), list):
3041
- return {"success": False, "error": "License URIs must be a list"}
3100
+ target_puzhash = await action_scope.get_puzzle_hash(self.service.wallet_state_manager)
3042
3101
  metadata_list = [
3043
- ("u", request["uris"]),
3044
- ("h", hexstr_to_bytes(request["hash"])),
3045
- ("mu", request.get("meta_uris", [])),
3046
- ("lu", request.get("license_uris", [])),
3047
- ("sn", uint64(request.get("edition_number", 1))),
3048
- ("st", uint64(request.get("edition_total", 1))),
3102
+ ("u", request.uris),
3103
+ ("h", request.hash),
3104
+ ("mu", request.meta_uris),
3105
+ ("lu", request.license_uris),
3106
+ ("sn", request.edition_number),
3107
+ ("st", request.edition_total),
3049
3108
  ]
3050
- if "meta_hash" in request and len(request["meta_hash"]) > 0:
3051
- metadata_list.append(("mh", hexstr_to_bytes(request["meta_hash"])))
3052
- if "license_hash" in request and len(request["license_hash"]) > 0:
3053
- metadata_list.append(("lh", hexstr_to_bytes(request["license_hash"])))
3109
+ if request.meta_hash is not None:
3110
+ metadata_list.append(("mh", request.meta_hash))
3111
+ if request.license_hash is not None:
3112
+ metadata_list.append(("lh", request.license_hash))
3054
3113
  metadata = Program.to(metadata_list)
3055
- fee = uint64(request.get("fee", 0))
3056
- did_id = request.get("did_id", None)
3057
- if did_id is not None:
3058
- if did_id == "":
3059
- did_id = b""
3114
+ if request.did_id is not None:
3115
+ if request.did_id == "":
3116
+ did_id: Optional[bytes] = b""
3060
3117
  else:
3061
- did_id = decode_puzzle_hash(did_id)
3118
+ did_id = decode_puzzle_hash(request.did_id)
3119
+ else:
3120
+ did_id = request.did_id
3062
3121
 
3063
3122
  nft_id = await nft_wallet.generate_new_nft(
3064
3123
  metadata,
3065
3124
  action_scope,
3066
3125
  target_puzhash,
3067
3126
  royalty_puzhash,
3068
- royalty_amount,
3127
+ request.royalty_amount,
3069
3128
  did_id,
3070
- fee,
3129
+ request.fee,
3071
3130
  extra_conditions=extra_conditions,
3072
3131
  )
3073
3132
  nft_id_bech32 = encode_puzzle_hash(nft_id, AddressType.NFT.hrp(self.service.config))
3074
- return {
3075
- "wallet_id": wallet_id,
3076
- "success": True,
3077
- "spend_bundle": None, # tx_endpoint wrapper will take care of this
3078
- "nft_id": nft_id_bech32,
3079
- "transactions": None, # tx_endpoint wrapper will take care of this
3080
- }
3133
+ return NFTMintNFTResponse(
3134
+ [],
3135
+ [],
3136
+ wallet_id=request.wallet_id,
3137
+ spend_bundle=WalletSpendBundle([], G2Element()), # tx_endpoint wrapper will take care of this
3138
+ nft_id=nft_id_bech32,
3139
+ )
3081
3140
 
3082
- async def nft_count_nfts(self, request: dict[str, Any]) -> EndpointResult:
3083
- wallet_id = request.get("wallet_id", None)
3141
+ @marshal
3142
+ async def nft_count_nfts(self, request: NFTCountNFTs) -> NFTCountNFTsResponse:
3084
3143
  count = 0
3085
- if wallet_id is not None:
3144
+ if request.wallet_id is not None:
3086
3145
  try:
3087
- nft_wallet = self.service.wallet_state_manager.get_wallet(id=wallet_id, required_type=NFTWallet)
3146
+ nft_wallet = self.service.wallet_state_manager.get_wallet(id=request.wallet_id, required_type=NFTWallet)
3088
3147
  except KeyError:
3089
3148
  # wallet not found
3090
- return {"success": False, "error": f"Wallet {wallet_id} not found."}
3149
+ raise ValueError(f"Wallet {request.wallet_id} not found.")
3091
3150
  count = await nft_wallet.get_nft_count()
3092
3151
  else:
3093
3152
  count = await self.service.wallet_state_manager.nft_store.count()
3094
- return {"wallet_id": wallet_id, "success": True, "count": count}
3153
+ return NFTCountNFTsResponse(request.wallet_id, uint64(count))
3095
3154
 
3096
- async def nft_get_nfts(self, request: dict[str, Any]) -> EndpointResult:
3097
- wallet_id = request.get("wallet_id", None)
3155
+ @marshal
3156
+ async def nft_get_nfts(self, request: NFTGetNFTs) -> NFTGetNFTsResponse:
3098
3157
  nfts: list[NFTCoinInfo] = []
3099
- if wallet_id is not None:
3100
- nft_wallet = self.service.wallet_state_manager.get_wallet(id=wallet_id, required_type=NFTWallet)
3158
+ if request.wallet_id is not None:
3159
+ nft_wallet = self.service.wallet_state_manager.get_wallet(id=request.wallet_id, required_type=NFTWallet)
3101
3160
  else:
3102
3161
  nft_wallet = None
3103
- try:
3104
- start_index = int(request.get("start_index", 0))
3105
- except (TypeError, ValueError):
3106
- start_index = 0
3107
- try:
3108
- count = int(request.get("num", 50))
3109
- except (TypeError, ValueError):
3110
- count = 50
3111
3162
  nft_info_list = []
3112
3163
  if nft_wallet is not None:
3113
- nfts = await nft_wallet.get_current_nfts(start_index=start_index, count=count)
3164
+ nfts = await nft_wallet.get_current_nfts(start_index=request.start_index, count=request.num)
3114
3165
  else:
3115
- nfts = await self.service.wallet_state_manager.nft_store.get_nft_list(start_index=start_index, count=count)
3166
+ nfts = await self.service.wallet_state_manager.nft_store.get_nft_list(
3167
+ start_index=request.start_index, count=request.num
3168
+ )
3116
3169
  for nft in nfts:
3117
3170
  nft_info = await nft_puzzle_utils.get_nft_info_from_puzzle(nft, self.service.wallet_state_manager.config)
3118
3171
  nft_info_list.append(nft_info)
3119
- return {"wallet_id": wallet_id, "success": True, "nft_list": nft_info_list}
3172
+ return NFTGetNFTsResponse(request.wallet_id, nft_info_list)
3120
3173
 
3121
3174
  @tx_endpoint(push=True)
3175
+ @marshal
3122
3176
  async def nft_set_nft_did(
3123
3177
  self,
3124
- request: dict[str, Any],
3178
+ request: NFTSetNFTDID,
3125
3179
  action_scope: WalletActionScope,
3126
3180
  extra_conditions: tuple[Condition, ...] = tuple(),
3127
- ) -> EndpointResult:
3128
- wallet_id = uint32(request["wallet_id"])
3129
- nft_wallet = self.service.wallet_state_manager.get_wallet(id=wallet_id, required_type=NFTWallet)
3130
- did_id = request.get("did_id", b"")
3131
- if did_id != b"":
3132
- did_id = decode_puzzle_hash(did_id)
3133
- nft_coin_info = await nft_wallet.get_nft_coin_by_id(bytes32.from_hexstr(request["nft_coin_id"]))
3181
+ ) -> NFTSetNFTDIDResponse:
3182
+ nft_wallet = self.service.wallet_state_manager.get_wallet(id=request.wallet_id, required_type=NFTWallet)
3183
+ if request.did_id is not None:
3184
+ did_id: bytes = decode_puzzle_hash(request.did_id)
3185
+ else:
3186
+ did_id = b""
3187
+ nft_coin_info = await nft_wallet.get_nft_coin_by_id(request.nft_coin_id)
3134
3188
  if not (
3135
3189
  await nft_puzzle_utils.get_nft_info_from_puzzle(nft_coin_info, self.service.wallet_state_manager.config)
3136
3190
  ).supports_did:
3137
- return {"success": False, "error": "The NFT doesn't support setting a DID."}
3191
+ raise ValueError("The NFT doesn't support setting a DID.")
3138
3192
 
3139
- fee = uint64(request.get("fee", 0))
3140
3193
  await nft_wallet.set_nft_did(
3141
3194
  nft_coin_info,
3142
3195
  did_id,
3143
3196
  action_scope,
3144
- fee=fee,
3197
+ fee=request.fee,
3145
3198
  extra_conditions=extra_conditions,
3146
3199
  )
3147
- return {
3148
- "wallet_id": wallet_id,
3149
- "success": True,
3150
- "spend_bundle": None, # tx_endpoint wrapper will take care of this
3151
- "transactions": None, # tx_endpoint wrapper will take care of this
3152
- }
3200
+ # tx_endpoint wrapper takes care of setting most of these default values
3201
+ return NFTSetNFTDIDResponse([], [], request.wallet_id, WalletSpendBundle([], G2Element()))
3153
3202
 
3154
3203
  @tx_endpoint(push=True)
3204
+ @marshal
3155
3205
  async def nft_set_did_bulk(
3156
3206
  self,
3157
- request: dict[str, Any],
3207
+ request: NFTSetDIDBulk,
3158
3208
  action_scope: WalletActionScope,
3159
3209
  extra_conditions: tuple[Condition, ...] = tuple(),
3160
- ) -> EndpointResult:
3210
+ ) -> NFTSetDIDBulkResponse:
3161
3211
  """
3162
3212
  Bulk set DID for NFTs across different wallets.
3163
3213
  accepted `request` dict keys:
@@ -3167,40 +3217,34 @@ class WalletRpcApi:
3167
3217
  :param request:
3168
3218
  :return:
3169
3219
  """
3170
- if len(request["nft_coin_list"]) > MAX_NFT_CHUNK_SIZE:
3171
- return {"success": False, "error": f"You can only set {MAX_NFT_CHUNK_SIZE} NFTs at once"}
3172
- did_id = request.get("did_id", b"")
3173
- if did_id != b"":
3174
- did_id = decode_puzzle_hash(did_id)
3220
+ if len(request.nft_coin_list) > MAX_NFT_CHUNK_SIZE:
3221
+ raise ValueError(f"You can only set {MAX_NFT_CHUNK_SIZE} NFTs at once")
3222
+
3223
+ if request.did_id is not None:
3224
+ did_id: bytes = decode_puzzle_hash(request.did_id)
3225
+ else:
3226
+ did_id = b""
3175
3227
  nft_dict: dict[uint32, list[NFTCoinInfo]] = {}
3176
3228
  coin_ids = []
3177
3229
  nft_ids = []
3178
- fee = uint64(request.get("fee", 0))
3179
3230
 
3180
3231
  nft_wallet: NFTWallet
3181
- for nft_coin in request["nft_coin_list"]:
3182
- if "nft_coin_id" not in nft_coin or "wallet_id" not in nft_coin:
3183
- log.error(f"Cannot set DID for NFT :{nft_coin}, missing nft_coin_id or wallet_id.")
3184
- continue
3185
- wallet_id = uint32(nft_coin["wallet_id"])
3186
- nft_wallet = self.service.wallet_state_manager.get_wallet(id=wallet_id, required_type=NFTWallet)
3187
- nft_coin_id = nft_coin["nft_coin_id"]
3188
- if nft_coin_id.startswith(AddressType.NFT.hrp(self.service.config)):
3189
- nft_id = decode_puzzle_hash(nft_coin_id)
3190
- nft_coin_info = await nft_wallet.get_nft(nft_id)
3232
+ for nft_coin in request.nft_coin_list:
3233
+ nft_wallet = self.service.wallet_state_manager.get_wallet(id=nft_coin.wallet_id, required_type=NFTWallet)
3234
+ if nft_coin.nft_coin_id.startswith(AddressType.NFT.hrp(self.service.config)):
3235
+ nft_coin_info = await nft_wallet.get_nft(decode_puzzle_hash(nft_coin.nft_coin_id))
3191
3236
  else:
3192
- nft_coin_id = bytes32.from_hexstr(nft_coin_id)
3193
- nft_coin_info = await nft_wallet.get_nft_coin_by_id(nft_coin_id)
3237
+ nft_coin_info = await nft_wallet.get_nft_coin_by_id(bytes32.from_hexstr(nft_coin.nft_coin_id))
3194
3238
  assert nft_coin_info is not None
3195
3239
  if not (
3196
3240
  await nft_puzzle_utils.get_nft_info_from_puzzle(nft_coin_info, self.service.wallet_state_manager.config)
3197
3241
  ).supports_did:
3198
3242
  log.warning(f"Skipping NFT {nft_coin_info.nft_id.hex()}, doesn't support setting a DID.")
3199
3243
  continue
3200
- if wallet_id in nft_dict:
3201
- nft_dict[wallet_id].append(nft_coin_info)
3244
+ if nft_coin.wallet_id in nft_dict:
3245
+ nft_dict[nft_coin.wallet_id].append(nft_coin_info)
3202
3246
  else:
3203
- nft_dict[wallet_id] = [nft_coin_info]
3247
+ nft_dict[nft_coin.wallet_id] = [nft_coin_info]
3204
3248
  nft_ids.append(nft_coin_info.nft_id)
3205
3249
  first = True
3206
3250
  for wallet_id, nft_list in nft_dict.items():
@@ -3209,7 +3253,7 @@ class WalletRpcApi:
3209
3253
  await nft_wallet.set_bulk_nft_did(nft_list, did_id, action_scope, extra_conditions=extra_conditions)
3210
3254
  else:
3211
3255
  await nft_wallet.set_bulk_nft_did(
3212
- nft_list, did_id, action_scope, fee, nft_ids, extra_conditions=extra_conditions
3256
+ nft_list, did_id, action_scope, request.fee, nft_ids, extra_conditions=extra_conditions
3213
3257
  )
3214
3258
  for coin in nft_list:
3215
3259
  coin_ids.append(coin.coin.name())
@@ -3221,21 +3265,22 @@ class WalletRpcApi:
3221
3265
  self.service.wallet_state_manager.state_changed("nft_coin_did_set", wallet_id)
3222
3266
 
3223
3267
  async with action_scope.use() as interface:
3224
- return {
3225
- "wallet_id": list(nft_dict.keys()),
3226
- "success": True,
3227
- "spend_bundle": None, # Backwards compat code in @tx_endpoint wrapper will fix this
3228
- "tx_num": len(interface.side_effects.transactions),
3229
- "transactions": None, # tx_endpoint wrapper will take care of this
3230
- }
3268
+ return NFTSetDIDBulkResponse(
3269
+ [],
3270
+ [],
3271
+ wallet_id=list(nft_dict.keys()),
3272
+ spend_bundle=WalletSpendBundle([], G2Element()),
3273
+ tx_num=uint16(len(interface.side_effects.transactions)),
3274
+ )
3231
3275
 
3232
3276
  @tx_endpoint(push=True)
3277
+ @marshal
3233
3278
  async def nft_transfer_bulk(
3234
3279
  self,
3235
- request: dict[str, Any],
3280
+ request: NFTTransferBulk,
3236
3281
  action_scope: WalletActionScope,
3237
3282
  extra_conditions: tuple[Condition, ...] = tuple(),
3238
- ) -> EndpointResult:
3283
+ ) -> NFTTransferBulkResponse:
3239
3284
  """
3240
3285
  Bulk transfer NFTs to an address.
3241
3286
  accepted `request` dict keys:
@@ -3245,36 +3290,26 @@ class WalletRpcApi:
3245
3290
  :param request:
3246
3291
  :return:
3247
3292
  """
3248
- if len(request["nft_coin_list"]) > MAX_NFT_CHUNK_SIZE:
3249
- return {"success": False, "error": f"You can only transfer {MAX_NFT_CHUNK_SIZE} NFTs at once"}
3250
- address = request["target_address"]
3251
- if isinstance(address, str):
3252
- puzzle_hash = decode_puzzle_hash(address)
3253
- else:
3254
- return dict(success=False, error="target_address parameter missing")
3293
+ if len(request.nft_coin_list) > MAX_NFT_CHUNK_SIZE:
3294
+ raise ValueError(f"You can only transfer {MAX_NFT_CHUNK_SIZE} NFTs at once")
3295
+ address = request.target_address
3296
+ puzzle_hash = decode_puzzle_hash(address)
3255
3297
  nft_dict: dict[uint32, list[NFTCoinInfo]] = {}
3256
3298
  coin_ids = []
3257
- fee = uint64(request.get("fee", 0))
3258
3299
 
3259
3300
  nft_wallet: NFTWallet
3260
- for nft_coin in request["nft_coin_list"]:
3261
- if "nft_coin_id" not in nft_coin or "wallet_id" not in nft_coin:
3262
- log.error(f"Cannot transfer NFT :{nft_coin}, missing nft_coin_id or wallet_id.")
3263
- continue
3264
- wallet_id = uint32(nft_coin["wallet_id"])
3265
- nft_wallet = self.service.wallet_state_manager.get_wallet(id=wallet_id, required_type=NFTWallet)
3266
- nft_coin_id = nft_coin["nft_coin_id"]
3301
+ for nft_coin in request.nft_coin_list:
3302
+ nft_wallet = self.service.wallet_state_manager.get_wallet(id=nft_coin.wallet_id, required_type=NFTWallet)
3303
+ nft_coin_id = nft_coin.nft_coin_id
3267
3304
  if nft_coin_id.startswith(AddressType.NFT.hrp(self.service.config)):
3268
- nft_id = decode_puzzle_hash(nft_coin_id)
3269
- nft_coin_info = await nft_wallet.get_nft(nft_id)
3305
+ nft_coin_info = await nft_wallet.get_nft(decode_puzzle_hash(nft_coin_id))
3270
3306
  else:
3271
- nft_coin_id = bytes32.from_hexstr(nft_coin_id)
3272
- nft_coin_info = await nft_wallet.get_nft_coin_by_id(nft_coin_id)
3307
+ nft_coin_info = await nft_wallet.get_nft_coin_by_id(bytes32.from_hexstr(nft_coin_id))
3273
3308
  assert nft_coin_info is not None
3274
- if wallet_id in nft_dict:
3275
- nft_dict[wallet_id].append(nft_coin_info)
3309
+ if nft_coin.wallet_id in nft_dict:
3310
+ nft_dict[nft_coin.wallet_id].append(nft_coin_info)
3276
3311
  else:
3277
- nft_dict[wallet_id] = [nft_coin_info]
3312
+ nft_dict[nft_coin.wallet_id] = [nft_coin_info]
3278
3313
  first = True
3279
3314
  for wallet_id, nft_list in nft_dict.items():
3280
3315
  nft_wallet = self.service.wallet_state_manager.get_wallet(id=wallet_id, required_type=NFTWallet)
@@ -3284,7 +3319,7 @@ class WalletRpcApi:
3284
3319
  )
3285
3320
  else:
3286
3321
  await nft_wallet.bulk_transfer_nft(
3287
- nft_list, puzzle_hash, action_scope, fee, extra_conditions=extra_conditions
3322
+ nft_list, puzzle_hash, action_scope, request.fee, extra_conditions=extra_conditions
3288
3323
  )
3289
3324
  for coin in nft_list:
3290
3325
  coin_ids.append(coin.coin.name())
@@ -3295,33 +3330,35 @@ class WalletRpcApi:
3295
3330
  for wallet_id in nft_dict.keys():
3296
3331
  self.service.wallet_state_manager.state_changed("nft_coin_did_set", wallet_id)
3297
3332
  async with action_scope.use() as interface:
3298
- return {
3299
- "wallet_id": list(nft_dict.keys()),
3300
- "success": True,
3301
- "spend_bundle": None, # tx_endpoint wrapper will take care of this
3302
- "tx_num": len(interface.side_effects.transactions),
3303
- "transactions": None, # tx_endpoint wrapper will take care of this
3304
- }
3333
+ return NFTTransferBulkResponse(
3334
+ [],
3335
+ [],
3336
+ wallet_id=list(nft_dict.keys()),
3337
+ spend_bundle=WalletSpendBundle([], G2Element()),
3338
+ tx_num=uint16(len(interface.side_effects.transactions)),
3339
+ )
3305
3340
 
3306
- async def nft_get_by_did(self, request: dict[str, Any]) -> EndpointResult:
3341
+ @marshal
3342
+ async def nft_get_by_did(self, request: NFTGetByDID) -> NFTGetByDIDResponse:
3307
3343
  did_id: Optional[bytes32] = None
3308
- if request.get("did_id", None) is not None:
3309
- did_id = decode_puzzle_hash(request["did_id"])
3344
+ if request.did_id is not None:
3345
+ did_id = decode_puzzle_hash(request.did_id)
3310
3346
  for wallet in self.service.wallet_state_manager.wallets.values():
3311
3347
  if isinstance(wallet, NFTWallet) and wallet.get_did() == did_id:
3312
- return {"wallet_id": wallet.wallet_id, "success": True}
3313
- return {"error": f"Cannot find a NFT wallet DID = {did_id}", "success": False}
3348
+ return NFTGetByDIDResponse(uint32(wallet.wallet_id))
3349
+ raise ValueError(f"Cannot find a NFT wallet DID = {did_id}")
3314
3350
 
3315
- async def nft_get_wallet_did(self, request: dict[str, Any]) -> EndpointResult:
3316
- wallet_id = uint32(request["wallet_id"])
3317
- nft_wallet = self.service.wallet_state_manager.get_wallet(id=wallet_id, required_type=NFTWallet)
3351
+ @marshal
3352
+ async def nft_get_wallet_did(self, request: NFTGetWalletDID) -> NFTGetWalletDIDResponse:
3353
+ nft_wallet = self.service.wallet_state_manager.get_wallet(id=request.wallet_id, required_type=NFTWallet)
3318
3354
  did_bytes: Optional[bytes32] = nft_wallet.get_did()
3319
3355
  did_id = ""
3320
3356
  if did_bytes is not None:
3321
3357
  did_id = encode_puzzle_hash(did_bytes, AddressType.DID.hrp(self.service.config))
3322
- return {"success": True, "did_id": None if len(did_id) == 0 else did_id}
3358
+ return NFTGetWalletDIDResponse(None if len(did_id) == 0 else did_id)
3323
3359
 
3324
- async def nft_get_wallets_with_dids(self, request: dict[str, Any]) -> EndpointResult:
3360
+ @marshal
3361
+ async def nft_get_wallets_with_dids(self, request: Empty) -> NFTGetWalletsWithDIDsResponse:
3325
3362
  all_wallets = self.service.wallet_state_manager.wallets.values()
3326
3363
  did_wallets_by_did_id: dict[bytes32, uint32] = {}
3327
3364
 
@@ -3331,7 +3368,7 @@ class WalletRpcApi:
3331
3368
  if wallet.did_info.origin_coin is not None:
3332
3369
  did_wallets_by_did_id[wallet.did_info.origin_coin.name()] = wallet.id()
3333
3370
 
3334
- did_nft_wallets: list[dict[str, Any]] = []
3371
+ did_nft_wallets: list[NFTWalletWithDID] = []
3335
3372
  for wallet in all_wallets:
3336
3373
  if isinstance(wallet, NFTWallet):
3337
3374
  nft_wallet_did: Optional[bytes32] = wallet.get_did()
@@ -3341,97 +3378,78 @@ class WalletRpcApi:
3341
3378
  log.warning(f"NFT wallet {wallet.id()} has DID {nft_wallet_did.hex()} but no DID wallet")
3342
3379
  else:
3343
3380
  did_nft_wallets.append(
3344
- {
3345
- "wallet_id": wallet.id(),
3346
- "did_id": encode_puzzle_hash(nft_wallet_did, AddressType.DID.hrp(self.service.config)),
3347
- "did_wallet_id": did_wallet_id,
3348
- }
3381
+ NFTWalletWithDID(
3382
+ wallet_id=wallet.id(),
3383
+ did_id=encode_puzzle_hash(nft_wallet_did, AddressType.DID.hrp(self.service.config)),
3384
+ did_wallet_id=did_wallet_id,
3385
+ )
3349
3386
  )
3350
- return {"success": True, "nft_wallets": did_nft_wallets}
3387
+ return NFTGetWalletsWithDIDsResponse(did_nft_wallets)
3351
3388
 
3352
- async def nft_set_nft_status(self, request: dict[str, Any]) -> EndpointResult:
3353
- wallet_id: uint32 = uint32(request["wallet_id"])
3354
- coin_id: bytes32 = bytes32.from_hexstr(request["coin_id"])
3355
- status: bool = request["in_transaction"]
3389
+ @marshal
3390
+ async def nft_set_nft_status(self, request: NFTSetNFTStatus) -> Empty:
3356
3391
  assert self.service.wallet_state_manager is not None
3357
- nft_wallet = self.service.wallet_state_manager.get_wallet(id=wallet_id, required_type=NFTWallet)
3358
- await nft_wallet.update_coin_status(coin_id, status)
3359
- return {"success": True}
3392
+ nft_wallet = self.service.wallet_state_manager.get_wallet(id=request.wallet_id, required_type=NFTWallet)
3393
+ await nft_wallet.update_coin_status(request.coin_id, request.in_transaction)
3394
+ return Empty()
3360
3395
 
3361
3396
  @tx_endpoint(push=True)
3397
+ @marshal
3362
3398
  async def nft_transfer_nft(
3363
3399
  self,
3364
- request: dict[str, Any],
3400
+ request: NFTTransferNFT,
3365
3401
  action_scope: WalletActionScope,
3366
3402
  extra_conditions: tuple[Condition, ...] = tuple(),
3367
- ) -> EndpointResult:
3368
- wallet_id = uint32(request["wallet_id"])
3369
- address = request["target_address"]
3370
- if isinstance(address, str):
3371
- puzzle_hash = decode_puzzle_hash(address)
3403
+ ) -> NFTTransferNFTResponse:
3404
+ puzzle_hash = decode_puzzle_hash(request.target_address)
3405
+ nft_wallet = self.service.wallet_state_manager.get_wallet(id=request.wallet_id, required_type=NFTWallet)
3406
+ nft_coin_id = request.nft_coin_id
3407
+ if nft_coin_id.startswith(AddressType.NFT.hrp(self.service.config)):
3408
+ nft_coin_info = await nft_wallet.get_nft(decode_puzzle_hash(nft_coin_id))
3372
3409
  else:
3373
- return dict(success=False, error="target_address parameter missing")
3374
- nft_wallet = self.service.wallet_state_manager.get_wallet(id=wallet_id, required_type=NFTWallet)
3375
- try:
3376
- nft_coin_id = request["nft_coin_id"]
3377
- if nft_coin_id.startswith(AddressType.NFT.hrp(self.service.config)):
3378
- nft_id = decode_puzzle_hash(nft_coin_id)
3379
- nft_coin_info = await nft_wallet.get_nft(nft_id)
3380
- else:
3381
- nft_coin_id = bytes32.from_hexstr(nft_coin_id)
3382
- nft_coin_info = await nft_wallet.get_nft_coin_by_id(nft_coin_id)
3383
- assert nft_coin_info is not None
3410
+ nft_coin_info = await nft_wallet.get_nft_coin_by_id(bytes32.from_hexstr(nft_coin_id))
3411
+ assert nft_coin_info is not None
3384
3412
 
3385
- fee = uint64(request.get("fee", 0))
3386
- await nft_wallet.generate_signed_transaction(
3387
- [uint64(nft_coin_info.coin.amount)],
3388
- [puzzle_hash],
3389
- action_scope,
3390
- coins={nft_coin_info.coin},
3391
- fee=fee,
3392
- new_owner=b"",
3393
- new_did_inner_hash=b"",
3394
- extra_conditions=extra_conditions,
3395
- )
3396
- await nft_wallet.update_coin_status(nft_coin_info.coin.name(), True)
3397
- return {
3398
- "wallet_id": wallet_id,
3399
- "success": True,
3400
- "spend_bundle": None, # tx_endpoint wrapper will take care of this
3401
- "transactions": None, # tx_endpoint wrapper will take care of this
3402
- }
3403
- except Exception as e:
3404
- log.exception(f"Failed to transfer NFT: {e}")
3405
- return {"success": False, "error": str(e)}
3406
-
3407
- async def nft_get_info(self, request: dict[str, Any]) -> EndpointResult:
3408
- if "coin_id" not in request:
3409
- return {"success": False, "error": "Coin ID is required."}
3410
- coin_id = request["coin_id"]
3411
- if coin_id.startswith(AddressType.NFT.hrp(self.service.config)):
3412
- coin_id = decode_puzzle_hash(coin_id)
3413
+ await nft_wallet.generate_signed_transaction(
3414
+ [uint64(nft_coin_info.coin.amount)],
3415
+ [puzzle_hash],
3416
+ action_scope,
3417
+ coins={nft_coin_info.coin},
3418
+ fee=request.fee,
3419
+ new_owner=b"",
3420
+ new_did_inner_hash=b"",
3421
+ extra_conditions=extra_conditions,
3422
+ )
3423
+ await nft_wallet.update_coin_status(nft_coin_info.coin.name(), True)
3424
+ # tx_endpoint takes care of filling in default values here
3425
+ return NFTTransferNFTResponse([], [], request.wallet_id, WalletSpendBundle([], G2Element()))
3426
+
3427
+ @marshal
3428
+ async def nft_get_info(self, request: NFTGetInfo) -> NFTGetInfoResponse:
3429
+ if request.coin_id.startswith(AddressType.NFT.hrp(self.service.config)):
3430
+ coin_id = decode_puzzle_hash(request.coin_id)
3413
3431
  else:
3414
3432
  try:
3415
- coin_id = bytes32.from_hexstr(coin_id)
3433
+ coin_id = bytes32.from_hexstr(request.coin_id)
3416
3434
  except ValueError:
3417
- return {"success": False, "error": f"Invalid Coin ID format for 'coin_id': {request['coin_id']!r}"}
3435
+ raise ValueError(f"Invalid Coin ID format for 'coin_id': {request.coin_id!r}")
3418
3436
  # Get coin state
3419
3437
  peer = self.service.get_full_node_peer()
3420
- coin_spend, coin_state = await self.get_latest_singleton_coin_spend(peer, coin_id, request.get("latest", True))
3438
+ coin_spend, coin_state = await self.get_latest_singleton_coin_spend(peer, coin_id, request.latest)
3421
3439
  # convert to NFTInfo
3422
3440
  # Check if the metadata is updated
3423
3441
  full_puzzle: Program = Program.from_bytes(bytes(coin_spend.puzzle_reveal))
3424
3442
 
3425
3443
  uncurried_nft: Optional[UncurriedNFT] = UncurriedNFT.uncurry(*full_puzzle.uncurry())
3426
3444
  if uncurried_nft is None:
3427
- return {"success": False, "error": "The coin is not a NFT."}
3445
+ raise ValueError("The coin is not a NFT.")
3428
3446
  metadata, p2_puzzle_hash = get_metadata_and_phs(uncurried_nft, coin_spend.solution)
3429
3447
  # Note: This is not the actual unspent NFT full puzzle.
3430
3448
  # There is no way to rebuild the full puzzle in a different wallet.
3431
3449
  # But it shouldn't have impact on generating the NFTInfo, since inner_puzzle is not used there.
3432
3450
  if uncurried_nft.supports_did:
3433
3451
  inner_puzzle = nft_puzzle_utils.recurry_nft_puzzle(
3434
- uncurried_nft, coin_spend.solution.to_program(), uncurried_nft.p2_puzzle
3452
+ uncurried_nft, Program.from_serialized(coin_spend.solution), uncurried_nft.p2_puzzle
3435
3453
  )
3436
3454
  else:
3437
3455
  inner_puzzle = uncurried_nft.p2_puzzle
@@ -3448,10 +3466,7 @@ class WalletRpcApi:
3448
3466
  [uncurried_nft.singleton_launcher_id], peer=peer
3449
3467
  )
3450
3468
  if launcher_coin is None or len(launcher_coin) < 1 or launcher_coin[0].spent_height is None:
3451
- return {
3452
- "success": False,
3453
- "error": f"Launcher coin record 0x{uncurried_nft.singleton_launcher_id.hex()} not found",
3454
- }
3469
+ raise ValueError(f"Launcher coin record 0x{uncurried_nft.singleton_launcher_id.hex()} not found")
3455
3470
  minter_did = await self.service.wallet_state_manager.get_minter_did(launcher_coin[0].coin, peer)
3456
3471
 
3457
3472
  nft_info: NFTInfo = await nft_puzzle_utils.get_nft_info_from_puzzle(
@@ -3468,179 +3483,136 @@ class WalletRpcApi:
3468
3483
  )
3469
3484
  # This is a bit hacky, it should just come out like this, but this works for this RPC
3470
3485
  nft_info = dataclasses.replace(nft_info, p2_address=p2_puzzle_hash)
3471
- return {"success": True, "nft_info": nft_info}
3486
+ return NFTGetInfoResponse(nft_info)
3472
3487
 
3473
3488
  @tx_endpoint(push=True)
3489
+ @marshal
3474
3490
  async def nft_add_uri(
3475
3491
  self,
3476
- request: dict[str, Any],
3492
+ request: NFTAddURI,
3477
3493
  action_scope: WalletActionScope,
3478
3494
  extra_conditions: tuple[Condition, ...] = tuple(),
3479
- ) -> EndpointResult:
3480
- wallet_id = uint32(request["wallet_id"])
3495
+ ) -> NFTAddURIResponse:
3481
3496
  # Note metadata updater can only add one uri for one field per spend.
3482
3497
  # If you want to add multiple uris for one field, you need to spend multiple times.
3483
- nft_wallet = self.service.wallet_state_manager.get_wallet(id=wallet_id, required_type=NFTWallet)
3484
- uri = request["uri"]
3485
- key = request["key"]
3486
- nft_coin_id = request["nft_coin_id"]
3487
- if nft_coin_id.startswith(AddressType.NFT.hrp(self.service.config)):
3488
- nft_coin_id = decode_puzzle_hash(nft_coin_id)
3498
+ nft_wallet = self.service.wallet_state_manager.get_wallet(id=request.wallet_id, required_type=NFTWallet)
3499
+ if request.nft_coin_id.startswith(AddressType.NFT.hrp(self.service.config)):
3500
+ nft_coin_id = decode_puzzle_hash(request.nft_coin_id)
3489
3501
  else:
3490
- nft_coin_id = bytes32.from_hexstr(nft_coin_id)
3502
+ nft_coin_id = bytes32.from_hexstr(request.nft_coin_id)
3491
3503
  nft_coin_info = await nft_wallet.get_nft_coin_by_id(nft_coin_id)
3492
3504
 
3493
- fee = uint64(request.get("fee", 0))
3494
3505
  await nft_wallet.update_metadata(
3495
- nft_coin_info, key, uri, action_scope, fee=fee, extra_conditions=extra_conditions
3506
+ nft_coin_info, request.key, request.uri, action_scope, fee=request.fee, extra_conditions=extra_conditions
3496
3507
  )
3497
- return {
3498
- "wallet_id": wallet_id,
3499
- "success": True,
3500
- "spend_bundle": None, # tx_endpoint wrapper will take care of this
3501
- "transactions": None, # tx_endpoint wrapper will take care of this
3502
- }
3508
+ # tx_endpoint takes care of setting the default values here
3509
+ return NFTAddURIResponse([], [], request.wallet_id, WalletSpendBundle([], G2Element()))
3503
3510
 
3504
- async def nft_calculate_royalties(self, request: dict[str, Any]) -> EndpointResult:
3505
- return NFTWallet.royalty_calculation(
3506
- {
3507
- asset["asset"]: (asset["royalty_address"], uint16(asset["royalty_percentage"]))
3508
- for asset in request.get("royalty_assets", [])
3509
- },
3510
- {asset["asset"]: uint64(asset["amount"]) for asset in request.get("fungible_assets", [])},
3511
+ @marshal
3512
+ async def nft_calculate_royalties(self, request: NFTCalculateRoyalties) -> NFTCalculateRoyaltiesResponse:
3513
+ return NFTCalculateRoyaltiesResponse.from_json_dict(
3514
+ NFTWallet.royalty_calculation(
3515
+ {
3516
+ asset.asset: (asset.royalty_address, uint16(asset.royalty_percentage))
3517
+ for asset in request.royalty_assets
3518
+ },
3519
+ {asset.asset: asset.amount for asset in request.fungible_assets},
3520
+ )
3511
3521
  )
3512
3522
 
3513
3523
  @tx_endpoint(push=False)
3524
+ @marshal
3514
3525
  async def nft_mint_bulk(
3515
3526
  self,
3516
- request: dict[str, Any],
3527
+ request: NFTMintBulk,
3517
3528
  action_scope: WalletActionScope,
3518
3529
  extra_conditions: tuple[Condition, ...] = tuple(),
3519
- ) -> EndpointResult:
3530
+ ) -> NFTMintBulkResponse:
3520
3531
  if action_scope.config.push:
3521
3532
  raise ValueError("Automatic pushing of nft minting transactions not yet available") # pragma: no cover
3522
3533
  if await self.service.wallet_state_manager.synced() is False:
3523
3534
  raise ValueError("Wallet needs to be fully synced.")
3524
- wallet_id = uint32(request["wallet_id"])
3525
- nft_wallet = self.service.wallet_state_manager.get_wallet(id=wallet_id, required_type=NFTWallet)
3526
- royalty_address = request.get("royalty_address", None)
3527
- if isinstance(royalty_address, str) and royalty_address != "":
3528
- royalty_puzhash = decode_puzzle_hash(royalty_address)
3529
- elif royalty_address in {None, ""}:
3530
- royalty_puzhash = await nft_wallet.standard_wallet.get_new_puzzlehash()
3535
+ nft_wallet = self.service.wallet_state_manager.get_wallet(id=request.wallet_id, required_type=NFTWallet)
3536
+ if request.royalty_address in {None, ""}:
3537
+ royalty_puzhash = await action_scope.get_puzzle_hash(self.service.wallet_state_manager)
3531
3538
  else:
3532
- royalty_puzhash = bytes32.from_hexstr(royalty_address)
3533
- royalty_percentage = request.get("royalty_percentage", None)
3534
- if royalty_percentage is None:
3535
- royalty_percentage = uint16(0)
3536
- else:
3537
- royalty_percentage = uint16(int(royalty_percentage))
3539
+ assert request.royalty_address is not None # hello mypy
3540
+ royalty_puzhash = decode_puzzle_hash(request.royalty_address)
3538
3541
  metadata_list = []
3539
- for meta in request["metadata_list"]:
3540
- if "uris" not in meta.keys():
3541
- return {"success": False, "error": "Data URIs is required"}
3542
- if not isinstance(meta["uris"], list):
3543
- return {"success": False, "error": "Data URIs must be a list"}
3544
- if not isinstance(meta.get("meta_uris", []), list):
3545
- return {"success": False, "error": "Metadata URIs must be a list"}
3546
- if not isinstance(meta.get("license_uris", []), list):
3547
- return {"success": False, "error": "License URIs must be a list"}
3542
+ for meta in request.metadata_list:
3548
3543
  nft_metadata = [
3549
- ("u", meta["uris"]),
3550
- ("h", hexstr_to_bytes(meta["hash"])),
3551
- ("mu", meta.get("meta_uris", [])),
3552
- ("lu", meta.get("license_uris", [])),
3553
- ("sn", uint64(meta.get("edition_number", 1))),
3554
- ("st", uint64(meta.get("edition_total", 1))),
3544
+ ("u", meta.uris),
3545
+ ("h", meta.hash),
3546
+ ("mu", meta.meta_uris),
3547
+ ("lu", meta.license_uris),
3548
+ ("sn", meta.edition_number),
3549
+ ("st", meta.edition_total),
3555
3550
  ]
3556
- if "meta_hash" in meta and len(meta["meta_hash"]) > 0:
3557
- nft_metadata.append(("mh", hexstr_to_bytes(meta["meta_hash"])))
3558
- if "license_hash" in meta and len(meta["license_hash"]) > 0:
3559
- nft_metadata.append(("lh", hexstr_to_bytes(meta["license_hash"])))
3551
+ if meta.meta_hash is not None:
3552
+ nft_metadata.append(("mh", meta.meta_hash))
3553
+ if meta.license_hash is not None:
3554
+ nft_metadata.append(("lh", meta.license_hash))
3560
3555
  metadata_program = Program.to(nft_metadata)
3561
3556
  metadata_dict = {
3562
3557
  "program": metadata_program,
3563
- "royalty_pc": royalty_percentage,
3558
+ "royalty_pc": request.royalty_percentage,
3564
3559
  "royalty_ph": royalty_puzhash,
3565
3560
  }
3566
3561
  metadata_list.append(metadata_dict)
3567
- target_address_list = request.get("target_list", None)
3568
- target_list = []
3569
- if target_address_list:
3570
- for target in target_address_list:
3571
- target_list.append(decode_puzzle_hash(target))
3572
- mint_number_start = request.get("mint_number_start", 1)
3573
- mint_total = request.get("mint_total", None)
3574
- xch_coin_list = request.get("xch_coins", None)
3575
- xch_coins = None
3576
- if xch_coin_list:
3577
- xch_coins = {Coin.from_json_dict(xch_coin) for xch_coin in xch_coin_list}
3578
- xch_change_target = request.get("xch_change_target", None)
3579
- if xch_change_target is not None:
3580
- if xch_change_target[:2] == "xch":
3581
- xch_change_ph = decode_puzzle_hash(xch_change_target)
3562
+ target_list = [decode_puzzle_hash(target) for target in request.target_list]
3563
+ if request.xch_change_target is not None:
3564
+ if request.xch_change_target.startswith("xch"):
3565
+ xch_change_ph = decode_puzzle_hash(request.xch_change_target)
3582
3566
  else:
3583
- xch_change_ph = bytes32.from_hexstr(xch_change_target)
3567
+ xch_change_ph = bytes32.from_hexstr(request.xch_change_target)
3584
3568
  else:
3585
3569
  xch_change_ph = None
3586
- new_innerpuzhash = request.get("new_innerpuzhash", None)
3587
- new_p2_puzhash = request.get("new_p2_puzhash", None)
3588
- did_coin_dict = request.get("did_coin", None)
3589
- if did_coin_dict:
3590
- did_coin = Coin.from_json_dict(did_coin_dict)
3591
- else:
3592
- did_coin = None
3593
- did_lineage_parent_hex = request.get("did_lineage_parent", None)
3594
- if did_lineage_parent_hex:
3595
- did_lineage_parent = bytes32.from_hexstr(did_lineage_parent_hex)
3596
- else:
3597
- did_lineage_parent = None
3598
- mint_from_did = request.get("mint_from_did", False)
3599
- fee = uint64(request.get("fee", 0))
3600
3570
 
3601
- if mint_from_did:
3571
+ if request.mint_from_did:
3602
3572
  await nft_wallet.mint_from_did(
3603
3573
  metadata_list,
3604
- mint_number_start=mint_number_start,
3605
- mint_total=mint_total,
3574
+ mint_number_start=request.mint_number_start,
3575
+ mint_total=request.mint_total,
3606
3576
  target_list=target_list,
3607
- xch_coins=xch_coins,
3577
+ xch_coins=set(request.xch_coins) if request.xch_coins is not None else None,
3608
3578
  xch_change_ph=xch_change_ph,
3609
- new_innerpuzhash=new_innerpuzhash,
3610
- new_p2_puzhash=new_p2_puzhash,
3611
- did_coin=did_coin,
3612
- did_lineage_parent=did_lineage_parent,
3613
- fee=fee,
3579
+ new_innerpuzhash=request.new_innerpuzhash,
3580
+ new_p2_puzhash=request.new_p2_puzhash,
3581
+ did_coin=request.did_coin,
3582
+ did_lineage_parent=request.did_lineage_parent,
3583
+ fee=request.fee,
3614
3584
  action_scope=action_scope,
3615
3585
  extra_conditions=extra_conditions,
3616
3586
  )
3617
3587
  else:
3618
3588
  await nft_wallet.mint_from_xch(
3619
3589
  metadata_list,
3620
- mint_number_start=mint_number_start,
3621
- mint_total=mint_total,
3590
+ mint_number_start=request.mint_number_start,
3591
+ mint_total=request.mint_total,
3622
3592
  target_list=target_list,
3623
- xch_coins=xch_coins,
3593
+ xch_coins=set(request.xch_coins) if request.xch_coins is not None else None,
3624
3594
  xch_change_ph=xch_change_ph,
3625
- fee=fee,
3595
+ fee=request.fee,
3626
3596
  action_scope=action_scope,
3627
3597
  extra_conditions=extra_conditions,
3628
3598
  )
3629
3599
  async with action_scope.use() as interface:
3630
3600
  sb = WalletSpendBundle.aggregate(
3631
3601
  [tx.spend_bundle for tx in interface.side_effects.transactions if tx.spend_bundle is not None]
3602
+ + [sb for sb in interface.side_effects.extra_spends]
3632
3603
  )
3633
3604
  nft_id_list = []
3634
3605
  for cs in sb.coin_spends:
3635
3606
  if cs.coin.puzzle_hash == SINGLETON_LAUNCHER_PUZZLE_HASH:
3636
3607
  nft_id_list.append(encode_puzzle_hash(cs.coin.name(), AddressType.NFT.hrp(self.service.config)))
3637
3608
 
3638
- return {
3639
- "success": True,
3640
- "spend_bundle": None, # tx_endpoint wrapper will take care of this
3641
- "nft_id_list": nft_id_list,
3642
- "transactions": None, # tx_endpoint wrapper will take care of this
3643
- }
3609
+ # tx_endpoint will take care of the default values here
3610
+ return NFTMintBulkResponse(
3611
+ [],
3612
+ [],
3613
+ WalletSpendBundle([], G2Element()),
3614
+ nft_id_list,
3615
+ )
3644
3616
 
3645
3617
  async def get_coin_records(self, request: dict[str, Any]) -> EndpointResult:
3646
3618
  parsed_request = GetCoinRecords.from_json_dict(request)
@@ -3822,118 +3794,119 @@ class WalletRpcApi:
3822
3794
  # Pool Wallet
3823
3795
  ##########################################################################################
3824
3796
  @tx_endpoint(push=True)
3797
+ @marshal
3825
3798
  async def pw_join_pool(
3826
3799
  self,
3827
- request: dict[str, Any],
3800
+ request: PWJoinPool,
3828
3801
  action_scope: WalletActionScope,
3829
3802
  extra_conditions: tuple[Condition, ...] = tuple(),
3830
- ) -> EndpointResult:
3831
- fee = uint64(request.get("fee", 0))
3832
- wallet_id = uint32(request["wallet_id"])
3833
- wallet = self.service.wallet_state_manager.get_wallet(id=wallet_id, required_type=PoolWallet)
3834
-
3835
- pool_wallet_info: PoolWalletInfo = await wallet.get_current_state()
3836
- owner_pubkey = pool_wallet_info.current.owner_pubkey
3837
- target_puzzlehash = None
3803
+ ) -> PWJoinPoolResponse:
3804
+ wallet = self.service.wallet_state_manager.get_wallet(id=request.wallet_id, required_type=PoolWallet)
3838
3805
 
3839
3806
  if await self.service.wallet_state_manager.synced() is False:
3840
3807
  raise ValueError("Wallet needs to be fully synced.")
3841
3808
 
3842
- if "target_puzzlehash" in request:
3843
- target_puzzlehash = bytes32.from_hexstr(request["target_puzzlehash"])
3844
- assert target_puzzlehash is not None
3809
+ pool_wallet_info: PoolWalletInfo = await wallet.get_current_state()
3810
+ if (
3811
+ pool_wallet_info.current.state == FARMING_TO_POOL.value
3812
+ and pool_wallet_info.current.pool_url == request.pool_url
3813
+ ):
3814
+ raise ValueError(f"Already farming to pool {pool_wallet_info.current.pool_url}")
3815
+
3816
+ owner_pubkey = pool_wallet_info.current.owner_pubkey
3845
3817
  new_target_state: PoolState = create_pool_state(
3846
3818
  FARMING_TO_POOL,
3847
- target_puzzlehash,
3819
+ request.target_puzzlehash,
3848
3820
  owner_pubkey,
3849
- request["pool_url"],
3850
- uint32(request["relative_lock_height"]),
3821
+ request.pool_url,
3822
+ request.relative_lock_height,
3851
3823
  )
3852
3824
 
3853
- async with self.service.wallet_state_manager.lock:
3854
- total_fee = await wallet.join_pool(new_target_state, fee, action_scope)
3855
- return {
3856
- "total_fee": total_fee,
3857
- "transaction": None, # tx_endpoint wrapper will take care of this
3858
- "fee_transaction": None, # tx_endpoint wrapper will take care of this
3859
- "transactions": None, # tx_endpoint wrapper will take care of this
3860
- }
3825
+ total_fee = await wallet.join_pool(new_target_state, request.fee, action_scope)
3826
+ # tx_endpoint will take care of filling in these default values
3827
+ return PWJoinPoolResponse(
3828
+ [],
3829
+ [],
3830
+ total_fee=total_fee,
3831
+ transaction=REPLACEABLE_TRANSACTION_RECORD,
3832
+ fee_transaction=REPLACEABLE_TRANSACTION_RECORD,
3833
+ )
3861
3834
 
3862
3835
  @tx_endpoint(push=True)
3836
+ @marshal
3863
3837
  async def pw_self_pool(
3864
3838
  self,
3865
- request: dict[str, Any],
3839
+ request: PWSelfPool,
3866
3840
  action_scope: WalletActionScope,
3867
3841
  extra_conditions: tuple[Condition, ...] = tuple(),
3868
- ) -> EndpointResult:
3842
+ ) -> PWSelfPoolResponse:
3869
3843
  # Leaving a pool requires two state transitions.
3870
3844
  # First we transition to PoolSingletonState.LEAVING_POOL
3871
3845
  # Then we transition to FARMING_TO_POOL or SELF_POOLING
3872
- fee = uint64(request.get("fee", 0))
3873
- wallet_id = uint32(request["wallet_id"])
3874
- wallet = self.service.wallet_state_manager.get_wallet(id=wallet_id, required_type=PoolWallet)
3846
+ wallet = self.service.wallet_state_manager.get_wallet(id=request.wallet_id, required_type=PoolWallet)
3875
3847
 
3876
3848
  if await self.service.wallet_state_manager.synced() is False:
3877
3849
  raise ValueError("Wallet needs to be fully synced.")
3878
3850
 
3879
- async with self.service.wallet_state_manager.lock:
3880
- total_fee = await wallet.self_pool(fee, action_scope)
3881
- return {
3882
- "total_fee": total_fee,
3883
- "transaction": None, # tx_endpoint wrapper will take care of this
3884
- "fee_transaction": None, # tx_endpoint wrapper will take care of this
3885
- "transactions": None, # tx_endpoint wrapper will take care of this
3886
- }
3851
+ total_fee = await wallet.self_pool(request.fee, action_scope)
3852
+ # tx_endpoint will take care of filling in these default values
3853
+ return PWSelfPoolResponse(
3854
+ [],
3855
+ [],
3856
+ total_fee=total_fee,
3857
+ transaction=REPLACEABLE_TRANSACTION_RECORD,
3858
+ fee_transaction=REPLACEABLE_TRANSACTION_RECORD,
3859
+ )
3887
3860
 
3888
3861
  @tx_endpoint(push=True)
3862
+ @marshal
3889
3863
  async def pw_absorb_rewards(
3890
3864
  self,
3891
- request: dict[str, Any],
3865
+ request: PWAbsorbRewards,
3892
3866
  action_scope: WalletActionScope,
3893
3867
  extra_conditions: tuple[Condition, ...] = tuple(),
3894
- ) -> EndpointResult:
3868
+ ) -> PWAbsorbRewardsResponse:
3895
3869
  """Perform a sweep of the p2_singleton rewards controlled by the pool wallet singleton"""
3896
3870
  if await self.service.wallet_state_manager.synced() is False:
3897
3871
  raise ValueError("Wallet needs to be fully synced before collecting rewards")
3898
- fee = uint64(request.get("fee", 0))
3899
- max_spends_in_tx = request.get("max_spends_in_tx", None)
3900
- wallet_id = uint32(request["wallet_id"])
3901
- wallet = self.service.wallet_state_manager.get_wallet(id=wallet_id, required_type=PoolWallet)
3872
+ wallet = self.service.wallet_state_manager.get_wallet(id=request.wallet_id, required_type=PoolWallet)
3902
3873
 
3903
3874
  assert isinstance(wallet, PoolWallet)
3904
3875
  async with self.service.wallet_state_manager.lock:
3905
- await wallet.claim_pool_rewards(fee, max_spends_in_tx, action_scope)
3876
+ await wallet.claim_pool_rewards(request.fee, request.max_spends_in_tx, action_scope)
3906
3877
  state: PoolWalletInfo = await wallet.get_current_state()
3907
- return {
3908
- "state": state.to_json_dict(),
3909
- "transaction": None, # tx_endpoint wrapper will take care of this
3910
- "fee_transaction": None, # tx_endpoint wrapper will take care of this
3911
- "transactions": None, # tx_endpoint wrapper will take care of this
3912
- }
3878
+ return PWAbsorbRewardsResponse(
3879
+ [],
3880
+ [],
3881
+ state=state,
3882
+ transaction=REPLACEABLE_TRANSACTION_RECORD,
3883
+ fee_transaction=REPLACEABLE_TRANSACTION_RECORD,
3884
+ )
3913
3885
 
3914
- async def pw_status(self, request: dict[str, Any]) -> EndpointResult:
3886
+ @marshal
3887
+ async def pw_status(self, request: PWStatus) -> PWStatusResponse:
3915
3888
  """Return the complete state of the Pool wallet with id `request["wallet_id"]`"""
3916
- wallet_id = uint32(request["wallet_id"])
3917
- wallet = self.service.wallet_state_manager.get_wallet(id=wallet_id, required_type=PoolWallet)
3889
+ wallet = self.service.wallet_state_manager.get_wallet(id=request.wallet_id, required_type=PoolWallet)
3918
3890
 
3919
3891
  assert isinstance(wallet, PoolWallet)
3920
3892
  state: PoolWalletInfo = await wallet.get_current_state()
3921
3893
  unconfirmed_transactions: list[TransactionRecord] = await wallet.get_unconfirmed_transactions()
3922
- return {
3923
- "state": state.to_json_dict(),
3924
- "unconfirmed_transactions": unconfirmed_transactions,
3925
- }
3894
+ return PWStatusResponse(
3895
+ state=state,
3896
+ unconfirmed_transactions=unconfirmed_transactions,
3897
+ )
3926
3898
 
3927
3899
  ##########################################################################################
3928
3900
  # DataLayer Wallet
3929
3901
  ##########################################################################################
3930
3902
  @tx_endpoint(push=True)
3903
+ @marshal
3931
3904
  async def create_new_dl(
3932
3905
  self,
3933
- request: dict[str, Any],
3906
+ request: CreateNewDL,
3934
3907
  action_scope: WalletActionScope,
3935
3908
  extra_conditions: tuple[Condition, ...] = tuple(),
3936
- ) -> EndpointResult:
3909
+ ) -> CreateNewDLResponse:
3937
3910
  """Initialize the DataLayer Wallet (only one can exist)"""
3938
3911
  if self.service.wallet_state_manager is None:
3939
3912
  raise ValueError("The wallet service is not currently initialized")
@@ -3944,24 +3917,19 @@ class WalletRpcApi:
3944
3917
  async with self.service.wallet_state_manager.lock:
3945
3918
  dl_wallet = await DataLayerWallet.create_new_dl_wallet(self.service.wallet_state_manager)
3946
3919
 
3947
- try:
3948
- async with self.service.wallet_state_manager.lock:
3949
- launcher_id = await dl_wallet.generate_new_reporter(
3950
- bytes32.from_hexstr(request["root"]),
3951
- action_scope,
3952
- fee=request.get("fee", uint64(0)),
3953
- extra_conditions=extra_conditions,
3954
- )
3955
- except ValueError as e:
3956
- log.error(f"Error while generating new reporter {e}")
3957
- return {"success": False, "error": str(e)}
3920
+ async with self.service.wallet_state_manager.lock:
3921
+ launcher_id = await dl_wallet.generate_new_reporter(
3922
+ request.root,
3923
+ action_scope,
3924
+ fee=request.fee,
3925
+ extra_conditions=extra_conditions,
3926
+ )
3958
3927
 
3959
- return {
3960
- "transactions": None, # tx_endpoint wrapper will take care of this
3961
- "launcher_id": launcher_id,
3962
- }
3928
+ # tx_endpoint will take care of these default values
3929
+ return CreateNewDLResponse([], [], launcher_id=launcher_id)
3963
3930
 
3964
- async def dl_track_new(self, request: dict[str, Any]) -> EndpointResult:
3931
+ @marshal
3932
+ async def dl_track_new(self, request: DLTrackNew) -> Empty:
3965
3933
  """Initialize the DataLayer Wallet (only one can exist)"""
3966
3934
  if self.service.wallet_state_manager is None:
3967
3935
  raise ValueError("The wallet service is not currently initialized")
@@ -3977,55 +3945,53 @@ class WalletRpcApi:
3977
3945
  for i, peer in enumerate(peer_list):
3978
3946
  try:
3979
3947
  await dl_wallet.track_new_launcher_id(
3980
- bytes32.from_hexstr(request["launcher_id"]),
3948
+ request.launcher_id,
3981
3949
  peer,
3982
3950
  )
3983
3951
  except LauncherCoinNotFoundError as e:
3984
3952
  if i == peer_length - 1:
3985
3953
  raise e # raise the error if we've tried all peers
3986
3954
  continue # try some other peers, maybe someone has it
3987
- return {}
3955
+ return Empty()
3988
3956
 
3989
- async def dl_stop_tracking(self, request: dict[str, Any]) -> EndpointResult:
3957
+ @marshal
3958
+ async def dl_stop_tracking(self, request: DLStopTracking) -> Empty:
3990
3959
  """Initialize the DataLayer Wallet (only one can exist)"""
3991
3960
  if self.service.wallet_state_manager is None:
3992
3961
  raise ValueError("The wallet service is not currently initialized")
3993
3962
 
3994
3963
  dl_wallet = self.service.wallet_state_manager.get_dl_wallet()
3995
- await dl_wallet.stop_tracking_singleton(bytes32.from_hexstr(request["launcher_id"]))
3996
- return {}
3964
+ await dl_wallet.stop_tracking_singleton(request.launcher_id)
3965
+ return Empty()
3997
3966
 
3998
- async def dl_latest_singleton(self, request: dict[str, Any]) -> EndpointResult:
3967
+ @marshal
3968
+ async def dl_latest_singleton(self, request: DLLatestSingleton) -> DLLatestSingletonResponse:
3999
3969
  """Get the singleton record for the latest singleton of a launcher ID"""
4000
3970
  if self.service.wallet_state_manager is None:
4001
3971
  raise ValueError("The wallet service is not currently initialized")
4002
3972
 
4003
- only_confirmed = request.get("only_confirmed")
4004
- if only_confirmed is None:
4005
- only_confirmed = False
4006
3973
  wallet = self.service.wallet_state_manager.get_dl_wallet()
4007
- record = await wallet.get_latest_singleton(bytes32.from_hexstr(request["launcher_id"]), only_confirmed)
4008
- return {"singleton": None if record is None else record.to_json_dict()}
3974
+ record = await wallet.get_latest_singleton(request.launcher_id, request.only_confirmed)
3975
+ return DLLatestSingletonResponse(record)
4009
3976
 
4010
- async def dl_singletons_by_root(self, request: dict[str, Any]) -> EndpointResult:
3977
+ @marshal
3978
+ async def dl_singletons_by_root(self, request: DLSingletonsByRoot) -> DLSingletonsByRootResponse:
4011
3979
  """Get the singleton records that contain the specified root"""
4012
3980
  if self.service.wallet_state_manager is None:
4013
3981
  raise ValueError("The wallet service is not currently initialized")
4014
3982
 
4015
3983
  wallet = self.service.wallet_state_manager.get_dl_wallet()
4016
- records = await wallet.get_singletons_by_root(
4017
- bytes32.from_hexstr(request["launcher_id"]), bytes32.from_hexstr(request["root"])
4018
- )
4019
- records_json = [rec.to_json_dict() for rec in records]
4020
- return {"singletons": records_json}
3984
+ records = await wallet.get_singletons_by_root(request.launcher_id, request.root)
3985
+ return DLSingletonsByRootResponse(records)
4021
3986
 
4022
3987
  @tx_endpoint(push=True)
3988
+ @marshal
4023
3989
  async def dl_update_root(
4024
3990
  self,
4025
- request: dict[str, Any],
3991
+ request: DLUpdateRoot,
4026
3992
  action_scope: WalletActionScope,
4027
3993
  extra_conditions: tuple[Condition, ...] = tuple(),
4028
- ) -> EndpointResult:
3994
+ ) -> DLUpdateRootResponse:
4029
3995
  """Get the singleton record for the latest singleton of a launcher ID"""
4030
3996
  if self.service.wallet_state_manager is None:
4031
3997
  raise ValueError("The wallet service is not currently initialized")
@@ -4033,48 +3999,55 @@ class WalletRpcApi:
4033
3999
  wallet = self.service.wallet_state_manager.get_dl_wallet()
4034
4000
  async with self.service.wallet_state_manager.lock:
4035
4001
  await wallet.create_update_state_spend(
4036
- bytes32.from_hexstr(request["launcher_id"]),
4037
- bytes32.from_hexstr(request["new_root"]),
4002
+ request.launcher_id,
4003
+ request.new_root,
4038
4004
  action_scope,
4039
- fee=uint64(request.get("fee", 0)),
4005
+ fee=request.fee,
4040
4006
  extra_conditions=extra_conditions,
4041
4007
  )
4042
4008
 
4043
- return {
4044
- "tx_record": None, # tx_endpoint wrapper will take care of this
4045
- "transactions": None, # tx_endpoint wrapper will take care of this
4046
- }
4009
+ # tx_endpoint will take care of default values here
4010
+ return DLUpdateRootResponse(
4011
+ [],
4012
+ [],
4013
+ REPLACEABLE_TRANSACTION_RECORD,
4014
+ )
4047
4015
 
4048
4016
  @tx_endpoint(push=True)
4017
+ @marshal
4049
4018
  async def dl_update_multiple(
4050
4019
  self,
4051
- request: dict[str, Any],
4020
+ request: DLUpdateMultiple,
4052
4021
  action_scope: WalletActionScope,
4053
4022
  extra_conditions: tuple[Condition, ...] = tuple(),
4054
- ) -> EndpointResult:
4023
+ ) -> DLUpdateMultipleResponse:
4055
4024
  """Update multiple singletons with new merkle roots"""
4056
4025
  if self.service.wallet_state_manager is None:
4057
- return {"success": False, "error": "not_initialized"}
4026
+ raise RuntimeError("not initialized")
4058
4027
 
4059
4028
  wallet = self.service.wallet_state_manager.get_dl_wallet()
4060
4029
  async with self.service.wallet_state_manager.lock:
4061
4030
  # TODO: This method should optionally link the singletons with announcements.
4062
4031
  # Otherwise spends are vulnerable to signature subtraction.
4063
- fee_per_launcher = uint64(request.get("fee", 0) // len(request["updates"]))
4064
- for launcher, root in request["updates"].items():
4032
+ # TODO: This method should natively support spending many and attaching one fee
4033
+ fee_per_launcher = uint64(request.fee // len(request.updates.launcher_root_pairs))
4034
+ for launcher_root_pair in request.updates.launcher_root_pairs:
4065
4035
  await wallet.create_update_state_spend(
4066
- bytes32.from_hexstr(launcher),
4067
- bytes32.from_hexstr(root),
4036
+ launcher_root_pair.launcher_id,
4037
+ launcher_root_pair.new_root,
4068
4038
  action_scope,
4069
4039
  fee=fee_per_launcher,
4070
4040
  extra_conditions=extra_conditions,
4071
4041
  )
4072
4042
 
4073
- return {
4074
- "transactions": None, # tx_endpoint wrapper will take care of this
4075
- }
4043
+ # tx_endpoint will take care of default values here
4044
+ return DLUpdateMultipleResponse(
4045
+ [],
4046
+ [],
4047
+ )
4076
4048
 
4077
- async def dl_history(self, request: dict[str, Any]) -> EndpointResult:
4049
+ @marshal
4050
+ async def dl_history(self, request: DLHistory) -> DLHistoryResponse:
4078
4051
  """Get the singleton record for the latest singleton of a launcher ID"""
4079
4052
  if self.service.wallet_state_manager is None:
4080
4053
  raise ValueError("The wallet service is not currently initialized")
@@ -4082,51 +4055,44 @@ class WalletRpcApi:
4082
4055
  wallet = self.service.wallet_state_manager.get_dl_wallet()
4083
4056
  additional_kwargs = {}
4084
4057
 
4085
- if "min_generation" in request:
4086
- additional_kwargs["min_generation"] = uint32(request["min_generation"])
4087
- if "max_generation" in request:
4088
- additional_kwargs["max_generation"] = uint32(request["max_generation"])
4089
- if "num_results" in request:
4090
- additional_kwargs["num_results"] = uint32(request["num_results"])
4058
+ if request.min_generation is not None:
4059
+ additional_kwargs["min_generation"] = uint32(request.min_generation)
4060
+ if request.max_generation is not None:
4061
+ additional_kwargs["max_generation"] = uint32(request.max_generation)
4062
+ if request.num_results is not None:
4063
+ additional_kwargs["num_results"] = uint32(request.num_results)
4091
4064
 
4092
- history = await wallet.get_history(bytes32.from_hexstr(request["launcher_id"]), **additional_kwargs)
4093
- history_json = [rec.to_json_dict() for rec in history]
4094
- return {"history": history_json, "count": len(history_json)}
4065
+ history = await wallet.get_history(request.launcher_id, **additional_kwargs)
4066
+ return DLHistoryResponse(history, uint32(len(history)))
4095
4067
 
4096
- async def dl_owned_singletons(self, request: dict[str, Any]) -> EndpointResult:
4068
+ @marshal
4069
+ async def dl_owned_singletons(self, request: Empty) -> DLOwnedSingletonsResponse:
4097
4070
  """Get all owned singleton records"""
4098
4071
  if self.service.wallet_state_manager is None:
4099
4072
  raise ValueError("The wallet service is not currently initialized")
4100
4073
 
4101
- try:
4102
- wallet = self.service.wallet_state_manager.get_dl_wallet()
4103
- except ValueError:
4104
- return {"success": False, "error": "no DataLayer wallet available"}
4105
-
4074
+ wallet = self.service.wallet_state_manager.get_dl_wallet()
4106
4075
  singletons = await wallet.get_owned_singletons()
4107
- singletons_json = [singleton.to_json_dict() for singleton in singletons]
4108
4076
 
4109
- return {"singletons": singletons_json, "count": len(singletons_json)}
4077
+ return DLOwnedSingletonsResponse(singletons, uint32(len(singletons)))
4110
4078
 
4111
- async def dl_get_mirrors(self, request: dict[str, Any]) -> EndpointResult:
4079
+ @marshal
4080
+ async def dl_get_mirrors(self, request: DLGetMirrors) -> DLGetMirrorsResponse:
4112
4081
  """Get all of the mirrors for a specific singleton"""
4113
4082
  if self.service.wallet_state_manager is None:
4114
4083
  raise ValueError("The wallet service is not currently initialized")
4115
4084
 
4116
4085
  wallet = self.service.wallet_state_manager.get_dl_wallet()
4117
- mirrors_json = []
4118
- for mirror in await wallet.get_mirrors_for_launcher(bytes32.from_hexstr(request["launcher_id"])):
4119
- mirrors_json.append(mirror.to_json_dict())
4120
-
4121
- return {"mirrors": mirrors_json}
4086
+ return DLGetMirrorsResponse(await wallet.get_mirrors_for_launcher(request.launcher_id))
4122
4087
 
4123
4088
  @tx_endpoint(push=True)
4089
+ @marshal
4124
4090
  async def dl_new_mirror(
4125
4091
  self,
4126
- request: dict[str, Any],
4092
+ request: DLNewMirror,
4127
4093
  action_scope: WalletActionScope,
4128
4094
  extra_conditions: tuple[Condition, ...] = tuple(),
4129
- ) -> EndpointResult:
4095
+ ) -> DLNewMirrorResponse:
4130
4096
  """Add a new on chain message for a specific singleton"""
4131
4097
  if self.service.wallet_state_manager is None:
4132
4098
  raise ValueError("The wallet service is not currently initialized")
@@ -4134,48 +4100,53 @@ class WalletRpcApi:
4134
4100
  dl_wallet = self.service.wallet_state_manager.get_dl_wallet()
4135
4101
  async with self.service.wallet_state_manager.lock:
4136
4102
  await dl_wallet.create_new_mirror(
4137
- bytes32.from_hexstr(request["launcher_id"]),
4138
- request["amount"],
4139
- [bytes(url, "utf8") for url in request["urls"]],
4103
+ request.launcher_id,
4104
+ request.amount,
4105
+ Mirror.encode_urls(request.urls),
4140
4106
  action_scope,
4141
- fee=request.get("fee", uint64(0)),
4107
+ fee=request.fee,
4142
4108
  extra_conditions=extra_conditions,
4143
4109
  )
4144
4110
 
4145
- return {
4146
- "transactions": None, # tx_endpoint wrapper will take care of this
4147
- }
4111
+ # tx_endpoint will take care of default values here
4112
+ return DLNewMirrorResponse(
4113
+ [],
4114
+ [],
4115
+ )
4148
4116
 
4149
4117
  @tx_endpoint(push=True)
4118
+ @marshal
4150
4119
  async def dl_delete_mirror(
4151
4120
  self,
4152
- request: dict[str, Any],
4121
+ request: DLDeleteMirror,
4153
4122
  action_scope: WalletActionScope,
4154
4123
  extra_conditions: tuple[Condition, ...] = tuple(),
4155
- ) -> EndpointResult:
4124
+ ) -> DLDeleteMirrorResponse:
4156
4125
  """Remove an existing mirror for a specific singleton"""
4157
4126
  if self.service.wallet_state_manager is None:
4158
4127
  raise ValueError("The wallet service is not currently initialized")
4159
4128
 
4160
4129
  dl_wallet = self.service.wallet_state_manager.get_dl_wallet()
4161
-
4162
4130
  async with self.service.wallet_state_manager.lock:
4163
4131
  await dl_wallet.delete_mirror(
4164
- bytes32.from_hexstr(request["coin_id"]),
4132
+ request.coin_id,
4165
4133
  self.service.get_full_node_peer(),
4166
4134
  action_scope,
4167
- fee=request.get("fee", uint64(0)),
4135
+ fee=request.fee,
4168
4136
  extra_conditions=extra_conditions,
4169
4137
  )
4170
4138
 
4171
- return {
4172
- "transactions": None, # tx_endpoint wrapper will take care of this
4173
- }
4139
+ # tx_endpoint will take care of default values here
4140
+ return DLDeleteMirrorResponse(
4141
+ [],
4142
+ [],
4143
+ )
4174
4144
 
4145
+ @marshal
4175
4146
  async def dl_verify_proof(
4176
4147
  self,
4177
- request: dict[str, Any],
4178
- ) -> EndpointResult:
4148
+ request: DLProof,
4149
+ ) -> VerifyProofResponse:
4179
4150
  """Verify a proof of inclusion for a DL singleton"""
4180
4151
  res = await dl_verify_proof(
4181
4152
  request,
@@ -4270,9 +4241,7 @@ class WalletRpcApi:
4270
4241
  [
4271
4242
  request.new_puzhash
4272
4243
  if request.new_puzhash is not None
4273
- else await vc_wallet.standard_wallet.get_puzzle_hash(
4274
- new=not action_scope.config.tx_config.reuse_puzhash
4275
- )
4244
+ else await action_scope.get_puzzle_hash(self.service.wallet_state_manager)
4276
4245
  ],
4277
4246
  action_scope,
4278
4247
  request.fee,