chia-blockchain 2.5.7rc4__py3-none-any.whl → 2.6.0rc2__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 (531) hide show
  1. chia/__init__.py +8 -4
  2. chia/_tests/blockchain/blockchain_test_utils.py +6 -8
  3. chia/_tests/blockchain/test_augmented_chain.py +4 -4
  4. chia/_tests/blockchain/test_blockchain.py +165 -190
  5. chia/_tests/blockchain/test_blockchain_transactions.py +5 -2
  6. chia/_tests/blockchain/test_build_chains.py +2 -4
  7. chia/_tests/blockchain/test_get_block_generator.py +2 -3
  8. chia/_tests/clvm/coin_store.py +4 -7
  9. chia/_tests/clvm/test_clvm_step.py +4 -4
  10. chia/_tests/clvm/test_puzzle_compression.py +2 -1
  11. chia/_tests/clvm/test_puzzle_drivers.py +2 -2
  12. chia/_tests/clvm/test_singletons.py +2 -4
  13. chia/_tests/clvm/test_spend_sim.py +2 -2
  14. chia/_tests/cmds/cmd_test_utils.py +27 -45
  15. chia/_tests/cmds/test_cmd_framework.py +6 -6
  16. chia/_tests/cmds/test_daemon.py +3 -3
  17. chia/_tests/cmds/test_show.py +4 -4
  18. chia/_tests/cmds/test_tx_config_args.py +1 -2
  19. chia/_tests/cmds/testing_classes.py +4 -5
  20. chia/_tests/cmds/wallet/test_did.py +24 -27
  21. chia/_tests/cmds/wallet/test_nft.py +12 -10
  22. chia/_tests/cmds/wallet/test_vcs.py +11 -12
  23. chia/_tests/cmds/wallet/test_wallet.py +134 -89
  24. chia/_tests/conftest.py +66 -31
  25. chia/_tests/connection_utils.py +2 -2
  26. chia/_tests/core/cmds/test_beta.py +4 -4
  27. chia/_tests/core/cmds/test_keys.py +2 -3
  28. chia/_tests/core/cmds/test_wallet.py +15 -15
  29. chia/_tests/core/consensus/test_pot_iterations.py +19 -73
  30. chia/_tests/core/custom_types/test_proof_of_space.py +124 -98
  31. chia/_tests/core/daemon/test_daemon.py +11 -11
  32. chia/_tests/core/data_layer/conftest.py +2 -2
  33. chia/_tests/core/data_layer/test_data_rpc.py +28 -14
  34. chia/_tests/core/data_layer/test_data_store.py +10 -10
  35. chia/_tests/core/data_layer/util.py +11 -11
  36. chia/_tests/core/farmer/test_farmer_api.py +2 -4
  37. chia/_tests/core/full_node/full_sync/test_full_sync.py +8 -7
  38. chia/_tests/core/full_node/stores/test_block_store.py +5 -4
  39. chia/_tests/core/full_node/stores/test_coin_store.py +5 -11
  40. chia/_tests/core/full_node/stores/test_full_node_store.py +8 -8
  41. chia/_tests/core/full_node/stores/test_hint_store.py +2 -2
  42. chia/_tests/core/full_node/test_block_height_map.py +3 -4
  43. chia/_tests/core/full_node/test_conditions.py +21 -23
  44. chia/_tests/core/full_node/test_full_node.py +273 -70
  45. chia/_tests/core/full_node/test_hard_fork_utils.py +92 -0
  46. chia/_tests/core/full_node/test_hint_management.py +2 -4
  47. chia/_tests/core/full_node/test_performance.py +0 -1
  48. chia/_tests/core/full_node/test_prev_tx_block.py +88 -11
  49. chia/_tests/core/full_node/test_transactions.py +1 -2
  50. chia/_tests/core/full_node/test_tx_processing_queue.py +198 -30
  51. chia/_tests/core/mempool/test_mempool.py +54 -50
  52. chia/_tests/core/mempool/test_mempool_fee_estimator.py +39 -39
  53. chia/_tests/core/mempool/test_mempool_fee_protocol.py +2 -6
  54. chia/_tests/core/mempool/test_mempool_manager.py +988 -854
  55. chia/_tests/core/mempool/test_singleton_fast_forward.py +6 -6
  56. chia/_tests/core/server/serve.py +7 -7
  57. chia/_tests/core/server/test_dos.py +1 -2
  58. chia/_tests/core/server/test_event_loop.py +12 -4
  59. chia/_tests/core/server/test_loop.py +7 -8
  60. chia/_tests/core/server/test_rate_limits.py +9 -8
  61. chia/_tests/core/server/test_server.py +61 -1
  62. chia/_tests/core/services/test_services.py +2 -2
  63. chia/_tests/core/ssl/test_ssl.py +2 -2
  64. chia/_tests/core/test_cost_calculation.py +2 -6
  65. chia/_tests/core/test_farmer_harvester_rpc.py +3 -5
  66. chia/_tests/core/test_filter.py +0 -1
  67. chia/_tests/core/test_full_node_rpc.py +2 -2
  68. chia/_tests/core/test_merkle_set.py +1 -2
  69. chia/_tests/core/test_seeder.py +4 -4
  70. chia/_tests/core/util/test_config.py +4 -4
  71. chia/_tests/core/util/test_jsonify.py +2 -2
  72. chia/_tests/core/util/test_keychain.py +3 -3
  73. chia/_tests/core/util/test_lockfile.py +2 -1
  74. chia/_tests/core/util/test_log_exceptions.py +1 -2
  75. chia/_tests/core/util/test_streamable.py +17 -17
  76. chia/_tests/db/test_db_wrapper.py +3 -2
  77. chia/_tests/environments/wallet.py +14 -14
  78. chia/_tests/ether.py +4 -3
  79. chia/_tests/farmer_harvester/test_farmer.py +41 -24
  80. chia/_tests/farmer_harvester/test_farmer_harvester.py +50 -17
  81. chia/_tests/farmer_harvester/test_filter_prefix_bits.py +27 -27
  82. chia/_tests/farmer_harvester/test_third_party_harvesters.py +21 -22
  83. chia/_tests/fee_estimation/test_fee_estimation_integration.py +18 -18
  84. chia/_tests/fee_estimation/test_fee_estimation_rpc.py +11 -9
  85. chia/_tests/harvester/test_harvester_api.py +11 -4
  86. chia/_tests/plot_sync/test_plot_sync.py +13 -11
  87. chia/_tests/plot_sync/test_receiver.py +11 -10
  88. chia/_tests/plot_sync/test_sync_simulated.py +2 -2
  89. chia/_tests/plot_sync/util.py +1 -2
  90. chia/_tests/plotting/test_plot_manager.py +7 -6
  91. chia/_tests/plotting/test_prover.py +30 -38
  92. chia/_tests/pools/test_pool_cmdline.py +4 -6
  93. chia/_tests/pools/test_pool_rpc.py +203 -61
  94. chia/_tests/pools/test_pool_wallet.py +3 -3
  95. chia/_tests/pools/test_wallet_pool_store.py +1 -4
  96. chia/_tests/process_junit.py +2 -2
  97. chia/_tests/rpc/test_rpc_client.py +4 -4
  98. chia/_tests/rpc/test_rpc_server.py +3 -3
  99. chia/_tests/simulation/test_simulation.py +12 -25
  100. chia/_tests/solver/test_solver_service.py +13 -4
  101. chia/_tests/testconfig.py +2 -2
  102. chia/_tests/timelord/test_new_peak.py +22 -11
  103. chia/_tests/tools/test_run_block.py +0 -2
  104. chia/_tests/tools/test_virtual_project.py +2 -1
  105. chia/_tests/util/benchmarks.py +1 -0
  106. chia/_tests/util/blockchain.py +38 -36
  107. chia/_tests/util/blockchain_mock.py +11 -11
  108. chia/_tests/util/build_network_protocol_files.py +2 -1
  109. chia/_tests/util/coin_store.py +2 -1
  110. chia/_tests/util/config.py +1 -1
  111. chia/_tests/util/db_connection.py +2 -3
  112. chia/_tests/util/full_sync.py +9 -11
  113. chia/_tests/util/gen_ssl_certs.py +4 -5
  114. chia/_tests/util/get_name_puzzle_conditions.py +2 -0
  115. chia/_tests/util/misc.py +24 -24
  116. chia/_tests/util/network_protocol_data.py +20 -3
  117. chia/_tests/util/protocol_messages_bytes-v1.0 +0 -0
  118. chia/_tests/util/protocol_messages_json.py +292 -3
  119. chia/_tests/util/setup_nodes.py +62 -47
  120. chia/_tests/util/spend_sim.py +57 -57
  121. chia/_tests/util/test_async_pool.py +2 -3
  122. chia/_tests/util/test_chia_version.py +1 -3
  123. chia/_tests/util/test_config.py +3 -3
  124. chia/_tests/util/test_full_block_utils.py +6 -3
  125. chia/_tests/util/test_limited_semaphore.py +1 -2
  126. chia/_tests/util/test_misc.py +2 -2
  127. chia/_tests/util/test_network.py +1 -2
  128. chia/_tests/util/test_priority_mutex.py +3 -3
  129. chia/_tests/util/test_recursive_replace.py +5 -6
  130. chia/_tests/util/test_replace_str_to_bytes.py +9 -10
  131. chia/_tests/util/test_testnet_overrides.py +3 -3
  132. chia/_tests/util/time_out_assert.py +2 -2
  133. chia/_tests/wallet/cat_wallet/test_cat_lifecycle.py +4 -6
  134. chia/_tests/wallet/cat_wallet/test_cat_outer_puzzle.py +2 -4
  135. chia/_tests/wallet/cat_wallet/test_cat_wallet.py +19 -13
  136. chia/_tests/wallet/cat_wallet/test_offer_lifecycle.py +13 -13
  137. chia/_tests/wallet/cat_wallet/test_trades.py +40 -38
  138. chia/_tests/wallet/clawback/test_clawback_lifecycle.py +2 -4
  139. chia/_tests/wallet/conftest.py +6 -6
  140. chia/_tests/wallet/db_wallet/test_db_graftroot.py +1 -1
  141. chia/_tests/wallet/db_wallet/test_dl_offers.py +34 -34
  142. chia/_tests/wallet/did_wallet/test_did.py +16 -6
  143. chia/_tests/wallet/nft_wallet/test_nft_1_offers.py +21 -21
  144. chia/_tests/wallet/nft_wallet/test_nft_bulk_mint.py +20 -6
  145. chia/_tests/wallet/nft_wallet/test_nft_offers.py +19 -21
  146. chia/_tests/wallet/nft_wallet/test_nft_puzzles.py +1 -2
  147. chia/_tests/wallet/nft_wallet/test_nft_wallet.py +121 -2
  148. chia/_tests/wallet/nft_wallet/test_ownership_outer_puzzle.py +6 -9
  149. chia/_tests/wallet/rpc/test_dl_wallet_rpc.py +44 -1
  150. chia/_tests/wallet/rpc/test_wallet_rpc.py +1672 -896
  151. chia/_tests/wallet/sync/test_wallet_sync.py +63 -60
  152. chia/_tests/wallet/test_clvm_streamable.py +2 -3
  153. chia/_tests/wallet/test_coin_management.py +2 -2
  154. chia/_tests/wallet/test_conditions.py +45 -51
  155. chia/_tests/wallet/test_debug_spend_bundle.py +2 -2
  156. chia/_tests/wallet/test_new_wallet_protocol.py +17 -17
  157. chia/_tests/wallet/test_notifications.py +14 -14
  158. chia/_tests/wallet/test_signer_protocol.py +5 -5
  159. chia/_tests/wallet/test_singleton_lifecycle_fast.py +4 -3
  160. chia/_tests/wallet/test_transaction_store.py +20 -20
  161. chia/_tests/wallet/test_util.py +2 -2
  162. chia/_tests/wallet/test_wallet.py +380 -228
  163. chia/_tests/wallet/test_wallet_action_scope.py +4 -4
  164. chia/_tests/wallet/test_wallet_blockchain.py +12 -12
  165. chia/_tests/wallet/test_wallet_coin_store.py +3 -4
  166. chia/_tests/wallet/test_wallet_node.py +16 -15
  167. chia/_tests/wallet/test_wallet_test_framework.py +2 -1
  168. chia/_tests/wallet/test_wallet_utils.py +2 -3
  169. chia/_tests/wallet/vc_wallet/test_cr_outer_puzzle.py +3 -5
  170. chia/_tests/wallet/vc_wallet/test_vc_lifecycle.py +14 -15
  171. chia/_tests/wallet/vc_wallet/test_vc_wallet.py +29 -24
  172. chia/_tests/wallet/wallet_block_tools.py +12 -11
  173. chia/_tests/weight_proof/config.py +1 -0
  174. chia/_tests/weight_proof/test_weight_proof.py +5 -4
  175. chia/apis/__init__.py +21 -0
  176. chia/apis/farmer_stub.py +102 -0
  177. chia/apis/full_node_stub.py +374 -0
  178. chia/apis/harvester_stub.py +57 -0
  179. chia/apis/introducer_stub.py +35 -0
  180. chia/apis/solver_stub.py +30 -0
  181. chia/apis/stub_protocol_registry.py +21 -0
  182. chia/apis/timelord_stub.py +39 -0
  183. chia/apis/wallet_stub.py +161 -0
  184. chia/cmds/beta.py +3 -4
  185. chia/cmds/beta_funcs.py +4 -3
  186. chia/cmds/check_wallet_db.py +4 -4
  187. chia/cmds/chia.py +1 -2
  188. chia/cmds/cmd_classes.py +11 -13
  189. chia/cmds/cmd_helpers.py +11 -11
  190. chia/cmds/cmds_util.py +15 -15
  191. chia/cmds/coin_funcs.py +6 -7
  192. chia/cmds/coins.py +2 -3
  193. chia/cmds/configure.py +1 -2
  194. chia/cmds/data.py +42 -42
  195. chia/cmds/data_funcs.py +81 -81
  196. chia/cmds/db.py +4 -5
  197. chia/cmds/db_backup_func.py +2 -2
  198. chia/cmds/db_upgrade_func.py +3 -3
  199. chia/cmds/db_validate_func.py +2 -2
  200. chia/cmds/dev/data.py +4 -4
  201. chia/cmds/dev/gh.py +5 -5
  202. chia/cmds/dev/installers.py +2 -3
  203. chia/cmds/dev/mempool.py +3 -4
  204. chia/cmds/dev/mempool_funcs.py +4 -4
  205. chia/cmds/dev/sim.py +8 -8
  206. chia/cmds/dump_keyring.py +3 -3
  207. chia/cmds/farm.py +6 -8
  208. chia/cmds/farm_funcs.py +25 -24
  209. chia/cmds/init_funcs.py +4 -4
  210. chia/cmds/keys.py +16 -18
  211. chia/cmds/keys_funcs.py +36 -36
  212. chia/cmds/netspace.py +1 -3
  213. chia/cmds/netspace_funcs.py +1 -2
  214. chia/cmds/options.py +3 -2
  215. chia/cmds/param_types.py +17 -16
  216. chia/cmds/passphrase.py +6 -7
  217. chia/cmds/passphrase_funcs.py +11 -13
  218. chia/cmds/peer.py +1 -3
  219. chia/cmds/peer_funcs.py +3 -3
  220. chia/cmds/plotnft.py +6 -7
  221. chia/cmds/plotnft_funcs.py +37 -26
  222. chia/cmds/rpc.py +3 -3
  223. chia/cmds/show.py +3 -5
  224. chia/cmds/show_funcs.py +9 -9
  225. chia/cmds/sim_funcs.py +25 -26
  226. chia/cmds/solver.py +1 -3
  227. chia/cmds/solver_funcs.py +1 -2
  228. chia/cmds/start_funcs.py +2 -2
  229. chia/cmds/wallet.py +76 -81
  230. chia/cmds/wallet_funcs.py +206 -177
  231. chia/consensus/augmented_chain.py +6 -6
  232. chia/consensus/block_body_validation.py +19 -15
  233. chia/consensus/block_creation.py +25 -21
  234. chia/consensus/block_header_validation.py +27 -13
  235. chia/consensus/block_height_map.py +3 -6
  236. chia/consensus/block_height_map_protocol.py +2 -2
  237. chia/consensus/block_record.py +2 -4
  238. chia/consensus/blockchain.py +58 -40
  239. chia/consensus/blockchain_interface.py +7 -7
  240. chia/consensus/coin_store_protocol.py +5 -6
  241. chia/consensus/condition_tools.py +4 -4
  242. chia/consensus/cost_calculator.py +2 -3
  243. chia/consensus/default_constants.py +19 -13
  244. chia/consensus/deficit.py +1 -3
  245. chia/consensus/difficulty_adjustment.py +3 -5
  246. chia/consensus/find_fork_point.py +2 -4
  247. chia/consensus/full_block_to_block_record.py +11 -13
  248. chia/consensus/generator_tools.py +2 -3
  249. chia/consensus/get_block_challenge.py +50 -26
  250. chia/consensus/get_block_generator.py +2 -3
  251. chia/consensus/make_sub_epoch_summary.py +8 -7
  252. chia/consensus/multiprocess_validation.py +31 -20
  253. chia/consensus/pos_quality.py +6 -23
  254. chia/consensus/pot_iterations.py +17 -44
  255. chia/consensus/signage_point.py +4 -5
  256. chia/consensus/vdf_info_computation.py +2 -4
  257. chia/daemon/client.py +8 -8
  258. chia/daemon/keychain_proxy.py +31 -37
  259. chia/daemon/server.py +32 -33
  260. chia/daemon/windows_signal.py +4 -3
  261. chia/data_layer/data_layer.py +86 -77
  262. chia/data_layer/data_layer_rpc_api.py +9 -9
  263. chia/data_layer/data_layer_rpc_client.py +13 -15
  264. chia/data_layer/data_layer_server.py +3 -3
  265. chia/data_layer/data_layer_util.py +14 -14
  266. chia/data_layer/data_layer_wallet.py +94 -101
  267. chia/data_layer/data_store.py +50 -50
  268. chia/data_layer/dl_wallet_store.py +9 -12
  269. chia/data_layer/download_data.py +8 -9
  270. chia/data_layer/s3_plugin_service.py +5 -9
  271. chia/data_layer/start_data_layer.py +5 -5
  272. chia/farmer/farmer.py +31 -31
  273. chia/farmer/farmer_api.py +45 -33
  274. chia/farmer/farmer_rpc_api.py +5 -4
  275. chia/farmer/farmer_rpc_client.py +6 -6
  276. chia/farmer/start_farmer.py +6 -6
  277. chia/full_node/block_store.py +13 -16
  278. chia/full_node/check_fork_next_block.py +1 -2
  279. chia/full_node/coin_store.py +15 -16
  280. chia/full_node/eligible_coin_spends.py +3 -3
  281. chia/full_node/fee_estimate_store.py +2 -3
  282. chia/full_node/fee_tracker.py +1 -2
  283. chia/full_node/full_block_utils.py +4 -4
  284. chia/full_node/full_node.py +239 -223
  285. chia/full_node/full_node_api.py +197 -152
  286. chia/full_node/full_node_rpc_api.py +34 -32
  287. chia/full_node/full_node_rpc_client.py +18 -19
  288. chia/full_node/full_node_store.py +45 -43
  289. chia/full_node/hard_fork_utils.py +44 -0
  290. chia/full_node/hint_management.py +2 -2
  291. chia/full_node/mempool.py +17 -19
  292. chia/full_node/mempool_manager.py +89 -42
  293. chia/full_node/pending_tx_cache.py +2 -3
  294. chia/full_node/start_full_node.py +5 -5
  295. chia/full_node/sync_store.py +3 -4
  296. chia/full_node/tx_processing_queue.py +120 -36
  297. chia/full_node/weight_proof.py +61 -48
  298. chia/harvester/harvester.py +25 -24
  299. chia/harvester/harvester_api.py +61 -38
  300. chia/harvester/harvester_rpc_api.py +10 -10
  301. chia/harvester/start_harvester.py +4 -4
  302. chia/introducer/introducer.py +3 -3
  303. chia/introducer/introducer_api.py +6 -4
  304. chia/introducer/start_introducer.py +4 -4
  305. chia/legacy/keyring.py +3 -3
  306. chia/plot_sync/delta.py +1 -2
  307. chia/plot_sync/receiver.py +20 -17
  308. chia/plot_sync/sender.py +15 -10
  309. chia/plotters/bladebit.py +7 -7
  310. chia/plotters/chiapos.py +2 -2
  311. chia/plotters/madmax.py +4 -4
  312. chia/plotters/plotters.py +4 -4
  313. chia/plotters/plotters_util.py +3 -3
  314. chia/plotting/cache.py +20 -14
  315. chia/plotting/check_plots.py +26 -35
  316. chia/plotting/create_plots.py +22 -23
  317. chia/plotting/manager.py +21 -14
  318. chia/plotting/prover.py +59 -42
  319. chia/plotting/util.py +16 -16
  320. chia/pools/pool_config.py +2 -1
  321. chia/pools/pool_puzzles.py +11 -12
  322. chia/pools/pool_wallet.py +34 -57
  323. chia/pools/pool_wallet_info.py +39 -10
  324. chia/protocols/farmer_protocol.py +8 -9
  325. chia/protocols/fee_estimate.py +3 -4
  326. chia/protocols/full_node_protocol.py +3 -4
  327. chia/protocols/harvester_protocol.py +27 -15
  328. chia/protocols/outbound_message.py +3 -3
  329. chia/protocols/pool_protocol.py +8 -9
  330. chia/protocols/shared_protocol.py +1 -2
  331. chia/protocols/solver_protocol.py +9 -2
  332. chia/protocols/timelord_protocol.py +4 -7
  333. chia/protocols/wallet_protocol.py +11 -12
  334. chia/rpc/rpc_client.py +9 -9
  335. chia/rpc/rpc_server.py +17 -17
  336. chia/rpc/util.py +2 -2
  337. chia/seeder/crawler.py +8 -8
  338. chia/seeder/crawler_api.py +21 -27
  339. chia/seeder/crawler_rpc_api.py +2 -2
  340. chia/seeder/dns_server.py +21 -21
  341. chia/seeder/start_crawler.py +4 -4
  342. chia/server/address_manager.py +15 -16
  343. chia/server/api_protocol.py +11 -11
  344. chia/server/chia_policy.py +46 -26
  345. chia/server/introducer_peers.py +2 -3
  346. chia/server/node_discovery.py +19 -19
  347. chia/server/rate_limit_numbers.py +4 -5
  348. chia/server/rate_limits.py +4 -4
  349. chia/server/resolve_peer_info.py +4 -4
  350. chia/server/server.py +49 -52
  351. chia/server/signal_handlers.py +6 -6
  352. chia/server/start_service.py +17 -17
  353. chia/server/upnp.py +4 -6
  354. chia/server/ws_connection.py +52 -37
  355. chia/simulator/add_blocks_in_batches.py +1 -3
  356. chia/simulator/block_tools.py +312 -200
  357. chia/simulator/full_node_simulator.py +56 -35
  358. chia/simulator/keyring.py +2 -3
  359. chia/simulator/setup_services.py +15 -15
  360. chia/simulator/simulator_full_node_rpc_api.py +1 -2
  361. chia/simulator/simulator_full_node_rpc_client.py +1 -2
  362. chia/simulator/simulator_protocol.py +1 -2
  363. chia/simulator/simulator_test_tools.py +3 -3
  364. chia/simulator/start_simulator.py +7 -7
  365. chia/simulator/wallet_tools.py +10 -10
  366. chia/solver/solver.py +10 -10
  367. chia/solver/solver_api.py +10 -8
  368. chia/solver/solver_rpc_api.py +2 -2
  369. chia/solver/start_solver.py +4 -4
  370. chia/ssl/cacert.pem +148 -90
  371. chia/ssl/chia_ca.crt +14 -10
  372. chia/ssl/chia_ca_old.crt +19 -0
  373. chia/ssl/create_ssl.py +4 -4
  374. chia/ssl/renewedselfsignedca.conf +4 -0
  375. chia/ssl/ssl_check.py +1 -2
  376. chia/timelord/iters_from_block.py +1 -4
  377. chia/timelord/start_timelord.py +4 -4
  378. chia/timelord/timelord.py +44 -40
  379. chia/timelord/timelord_api.py +6 -4
  380. chia/timelord/timelord_launcher.py +2 -2
  381. chia/timelord/timelord_rpc_api.py +2 -2
  382. chia/timelord/timelord_state.py +11 -12
  383. chia/types/block_protocol.py +1 -3
  384. chia/types/blockchain_format/coin.py +1 -3
  385. chia/types/blockchain_format/program.py +11 -8
  386. chia/types/blockchain_format/proof_of_space.py +123 -76
  387. chia/types/blockchain_format/tree_hash.py +3 -3
  388. chia/types/blockchain_format/vdf.py +1 -2
  389. chia/types/coin_spend.py +3 -3
  390. chia/types/mempool_item.py +5 -5
  391. chia/types/mempool_submission_status.py +2 -3
  392. chia/types/peer_info.py +1 -2
  393. chia/types/unfinished_header_block.py +3 -4
  394. chia/types/validation_state.py +1 -2
  395. chia/util/action_scope.py +8 -8
  396. chia/util/async_pool.py +5 -5
  397. chia/util/bech32m.py +1 -2
  398. chia/util/beta_metrics.py +2 -2
  399. chia/util/block_cache.py +4 -4
  400. chia/util/chia_logging.py +2 -2
  401. chia/util/chia_version.py +1 -2
  402. chia/util/config.py +15 -16
  403. chia/util/db_wrapper.py +26 -27
  404. chia/util/default_root.py +1 -2
  405. chia/util/errors.py +3 -3
  406. chia/util/file_keyring.py +14 -14
  407. chia/util/files.py +2 -3
  408. chia/util/hash.py +4 -4
  409. chia/util/initial-config.yaml +4 -5
  410. chia/util/inline_executor.py +2 -1
  411. chia/util/ip_address.py +1 -2
  412. chia/util/keychain.py +25 -27
  413. chia/util/keyring_wrapper.py +18 -19
  414. chia/util/lock.py +3 -4
  415. chia/util/log_exceptions.py +1 -2
  416. chia/util/lru_cache.py +2 -2
  417. chia/util/network.py +6 -6
  418. chia/util/path.py +2 -3
  419. chia/util/priority_mutex.py +2 -2
  420. chia/util/profiler.py +1 -2
  421. chia/util/safe_cancel_task.py +1 -2
  422. chia/util/streamable.py +24 -10
  423. chia/util/task_referencer.py +1 -1
  424. chia/util/timing.py +3 -3
  425. chia/util/virtual_project_analysis.py +6 -5
  426. chia/util/ws_message.py +2 -2
  427. chia/wallet/cat_wallet/cat_info.py +3 -4
  428. chia/wallet/cat_wallet/cat_outer_puzzle.py +12 -11
  429. chia/wallet/cat_wallet/cat_utils.py +3 -4
  430. chia/wallet/cat_wallet/cat_wallet.py +61 -83
  431. chia/wallet/cat_wallet/lineage_store.py +3 -4
  432. chia/wallet/cat_wallet/r_cat_wallet.py +19 -22
  433. chia/wallet/coin_selection.py +9 -10
  434. chia/wallet/conditions.py +142 -106
  435. chia/wallet/db_wallet/db_wallet_puzzles.py +4 -5
  436. chia/wallet/derivation_record.py +1 -2
  437. chia/wallet/derive_keys.py +2 -4
  438. chia/wallet/did_wallet/did_info.py +10 -11
  439. chia/wallet/did_wallet/did_wallet.py +36 -82
  440. chia/wallet/did_wallet/did_wallet_puzzles.py +7 -8
  441. chia/wallet/driver_protocol.py +5 -7
  442. chia/wallet/lineage_proof.py +4 -4
  443. chia/wallet/nft_wallet/metadata_outer_puzzle.py +11 -11
  444. chia/wallet/nft_wallet/nft_info.py +8 -9
  445. chia/wallet/nft_wallet/nft_puzzle_utils.py +8 -8
  446. chia/wallet/nft_wallet/nft_wallet.py +79 -116
  447. chia/wallet/nft_wallet/ownership_outer_puzzle.py +14 -14
  448. chia/wallet/nft_wallet/singleton_outer_puzzle.py +12 -11
  449. chia/wallet/nft_wallet/transfer_program_puzzle.py +11 -11
  450. chia/wallet/nft_wallet/uncurry_nft.py +10 -11
  451. chia/wallet/notification_manager.py +3 -3
  452. chia/wallet/notification_store.py +44 -61
  453. chia/wallet/outer_puzzles.py +6 -7
  454. chia/wallet/puzzle_drivers.py +34 -6
  455. chia/wallet/puzzles/clawback/drivers.py +6 -6
  456. chia/wallet/puzzles/deployed_puzzle_hashes.json +1 -54
  457. chia/wallet/puzzles/load_clvm.py +1 -1
  458. chia/wallet/puzzles/p2_delegated_puzzle_or_hidden_puzzle.py +1 -2
  459. chia/wallet/puzzles/singleton_top_layer.py +2 -3
  460. chia/wallet/puzzles/singleton_top_layer_v1_1.py +3 -4
  461. chia/wallet/puzzles/tails.py +3 -3
  462. chia/wallet/singleton.py +5 -7
  463. chia/wallet/singleton_record.py +3 -3
  464. chia/wallet/start_wallet.py +5 -5
  465. chia/wallet/trade_manager.py +37 -58
  466. chia/wallet/trade_record.py +4 -4
  467. chia/wallet/trading/offer.py +59 -46
  468. chia/wallet/trading/trade_store.py +8 -9
  469. chia/wallet/transaction_record.py +8 -8
  470. chia/wallet/uncurried_puzzle.py +1 -2
  471. chia/wallet/util/clvm_streamable.py +12 -12
  472. chia/wallet/util/compute_hints.py +4 -5
  473. chia/wallet/util/curry_and_treehash.py +1 -2
  474. chia/wallet/util/merkle_tree.py +2 -3
  475. chia/wallet/util/peer_request_cache.py +8 -8
  476. chia/wallet/util/signing.py +85 -0
  477. chia/wallet/util/tx_config.py +15 -6
  478. chia/wallet/util/wallet_sync_utils.py +14 -16
  479. chia/wallet/util/wallet_types.py +2 -2
  480. chia/wallet/vc_wallet/cr_cat_drivers.py +10 -11
  481. chia/wallet/vc_wallet/cr_cat_wallet.py +50 -68
  482. chia/wallet/vc_wallet/cr_outer_puzzle.py +14 -13
  483. chia/wallet/vc_wallet/vc_drivers.py +27 -27
  484. chia/wallet/vc_wallet/vc_store.py +5 -6
  485. chia/wallet/vc_wallet/vc_wallet.py +33 -61
  486. chia/wallet/wallet.py +50 -78
  487. chia/wallet/wallet_action_scope.py +11 -11
  488. chia/wallet/wallet_blockchain.py +12 -12
  489. chia/wallet/wallet_coin_record.py +12 -6
  490. chia/wallet/wallet_coin_store.py +24 -25
  491. chia/wallet/wallet_interested_store.py +3 -5
  492. chia/wallet/wallet_nft_store.py +10 -11
  493. chia/wallet/wallet_node.py +53 -61
  494. chia/wallet/wallet_node_api.py +5 -3
  495. chia/wallet/wallet_protocol.py +23 -23
  496. chia/wallet/wallet_puzzle_store.py +15 -18
  497. chia/wallet/wallet_request_types.py +778 -114
  498. chia/wallet/wallet_retry_store.py +1 -3
  499. chia/wallet/wallet_rpc_api.py +572 -909
  500. chia/wallet/wallet_rpc_client.py +87 -279
  501. chia/wallet/wallet_singleton_store.py +3 -4
  502. chia/wallet/wallet_state_manager.py +332 -106
  503. chia/wallet/wallet_transaction_store.py +11 -14
  504. chia/wallet/wallet_user_store.py +4 -6
  505. chia/wallet/wallet_weight_proof_handler.py +4 -4
  506. {chia_blockchain-2.5.7rc4.dist-info → chia_blockchain-2.6.0rc2.dist-info}/METADATA +6 -5
  507. {chia_blockchain-2.5.7rc4.dist-info → chia_blockchain-2.6.0rc2.dist-info}/RECORD +510 -517
  508. chia/apis.py +0 -21
  509. chia/consensus/check_time_locks.py +0 -57
  510. chia/data_layer/puzzles/__init__.py +0 -0
  511. chia/data_layer/puzzles/graftroot_dl_offers.clsp +0 -100
  512. chia/data_layer/puzzles/graftroot_dl_offers.clsp.hex +0 -1
  513. chia/types/coin_record.py +0 -44
  514. chia/wallet/nft_wallet/puzzles/__init__.py +0 -0
  515. chia/wallet/nft_wallet/puzzles/create_nft_launcher_from_did.clsp +0 -6
  516. chia/wallet/nft_wallet/puzzles/create_nft_launcher_from_did.clsp.hex +0 -1
  517. chia/wallet/nft_wallet/puzzles/nft_intermediate_launcher.clsp +0 -6
  518. chia/wallet/nft_wallet/puzzles/nft_intermediate_launcher.clsp.hex +0 -1
  519. chia/wallet/nft_wallet/puzzles/nft_metadata_updater_default.clsp +0 -30
  520. chia/wallet/nft_wallet/puzzles/nft_metadata_updater_default.clsp.hex +0 -1
  521. chia/wallet/nft_wallet/puzzles/nft_metadata_updater_updateable.clsp +0 -28
  522. chia/wallet/nft_wallet/puzzles/nft_metadata_updater_updateable.clsp.hex +0 -1
  523. chia/wallet/nft_wallet/puzzles/nft_ownership_layer.clsp +0 -100
  524. chia/wallet/nft_wallet/puzzles/nft_ownership_layer.clsp.hex +0 -1
  525. chia/wallet/nft_wallet/puzzles/nft_ownership_transfer_program_one_way_claim_with_royalties.clsp +0 -78
  526. chia/wallet/nft_wallet/puzzles/nft_ownership_transfer_program_one_way_claim_with_royalties.clsp.hex +0 -1
  527. chia/wallet/nft_wallet/puzzles/nft_state_layer.clsp +0 -74
  528. chia/wallet/nft_wallet/puzzles/nft_state_layer.clsp.hex +0 -1
  529. {chia_blockchain-2.5.7rc4.dist-info → chia_blockchain-2.6.0rc2.dist-info}/WHEEL +0 -0
  530. {chia_blockchain-2.5.7rc4.dist-info → chia_blockchain-2.6.0rc2.dist-info}/entry_points.txt +0 -0
  531. {chia_blockchain-2.5.7rc4.dist-info → chia_blockchain-2.6.0rc2.dist-info}/licenses/LICENSE +0 -0
@@ -3,12 +3,14 @@ from __future__ import annotations
3
3
  import dataclasses
4
4
  import json
5
5
  import logging
6
+ from collections.abc import Callable
7
+ from itertools import count
6
8
  from pathlib import Path
7
- from typing import TYPE_CHECKING, Any, Callable, ClassVar, Optional, Union, cast
9
+ from typing import TYPE_CHECKING, Any, ClassVar, cast
8
10
 
9
- from chia_rs import AugSchemeMPL, Coin, CoinSpend, CoinState, G1Element, G2Element, PrivateKey
11
+ from chia_rs import AugSchemeMPL, Coin, CoinRecord, CoinSpend, CoinState, G1Element, G2Element, PrivateKey
10
12
  from chia_rs.sized_bytes import bytes32
11
- from chia_rs.sized_ints import uint8, uint16, uint32, uint64
13
+ from chia_rs.sized_ints import uint16, uint32, uint64
12
14
  from clvm_tools.binutils import assemble
13
15
 
14
16
  from chia.consensus.block_rewards import calculate_base_farmer_reward
@@ -16,41 +18,39 @@ from chia.data_layer.data_layer_errors import LauncherCoinNotFoundError
16
18
  from chia.data_layer.data_layer_util import DLProof, VerifyProofResponse, dl_verify_proof
17
19
  from chia.data_layer.data_layer_wallet import DataLayerWallet, Mirror
18
20
  from chia.pools.pool_wallet import PoolWallet
19
- from chia.pools.pool_wallet_info import FARMING_TO_POOL, PoolState, PoolWalletInfo, create_pool_state
21
+ from chia.pools.pool_wallet_info import (
22
+ FARMING_TO_POOL,
23
+ PoolState,
24
+ PoolWalletInfo,
25
+ create_pool_state,
26
+ initial_pool_state_from_dict,
27
+ )
20
28
  from chia.protocols.outbound_message import NodeType
21
29
  from chia.rpc.rpc_server import Endpoint, EndpointResult, default_get_connections
22
30
  from chia.rpc.util import ALL_TRANSLATION_LAYERS, RpcEndpoint, marshal
23
31
  from chia.server.ws_connection import WSChiaConnection
24
- from chia.types.blockchain_format.coin import coin_as_list
25
- from chia.types.blockchain_format.program import INFINITE_COST, Program, run_with_cost
26
- from chia.types.coin_record import CoinRecord
27
- from chia.types.signing_mode import CHIP_0002_SIGN_MESSAGE_PREFIX, SigningMode
32
+ from chia.types.blockchain_format.program import Program
28
33
  from chia.util.bech32m import decode_puzzle_hash, encode_puzzle_hash
29
- from chia.util.byte_types import hexstr_to_bytes
30
34
  from chia.util.config import load_config
31
35
  from chia.util.errors import KeychainIsLocked
32
- from chia.util.hash import std_hash
33
36
  from chia.util.keychain import bytes_to_mnemonic, generate_mnemonic
34
- from chia.util.path import path_from_root
35
- from chia.util.streamable import Streamable, UInt32Range, streamable
37
+ from chia.util.streamable import UInt32Range
36
38
  from chia.util.ws_message import WsRpcMessage, create_payload_dict
37
39
  from chia.wallet.cat_wallet.cat_constants import DEFAULT_CATS
38
40
  from chia.wallet.cat_wallet.cat_info import CRCATInfo
39
41
  from chia.wallet.cat_wallet.cat_wallet import CATWallet
42
+ from chia.wallet.cat_wallet.r_cat_wallet import RCATWallet
40
43
  from chia.wallet.conditions import (
41
- AssertCoinAnnouncement,
42
- AssertPuzzleAnnouncement,
44
+ AssertConcurrentSpend,
43
45
  Condition,
44
46
  ConditionValidTimes,
45
47
  CreateCoin,
46
48
  CreateCoinAnnouncement,
47
49
  CreatePuzzleAnnouncement,
48
50
  conditions_from_json_dicts,
49
- parse_conditions_non_consensus,
50
51
  parse_timelock_info,
51
52
  )
52
53
  from chia.wallet.derive_keys import (
53
- MAX_POOL_WALLETS,
54
54
  master_sk_to_farmer_sk,
55
55
  master_sk_to_pool_sk,
56
56
  match_address_to_sk,
@@ -69,12 +69,9 @@ from chia.wallet.nft_wallet.nft_info import NFTCoinInfo, NFTInfo
69
69
  from chia.wallet.nft_wallet.nft_puzzle_utils import get_metadata_and_phs
70
70
  from chia.wallet.nft_wallet.nft_wallet import NFTWallet
71
71
  from chia.wallet.nft_wallet.uncurry_nft import UncurriedNFT
72
- from chia.wallet.notification_store import Notification
73
72
  from chia.wallet.outer_puzzles import AssetType
74
- from chia.wallet.puzzle_drivers import PuzzleInfo, Solver
75
- from chia.wallet.puzzles import p2_delegated_conditions
76
- from chia.wallet.puzzles.clawback.metadata import AutoClaimSettings, ClawbackMetadata
77
- from chia.wallet.puzzles.p2_delegated_puzzle_or_hidden_puzzle import puzzle_hash_for_synthetic_public_key
73
+ from chia.wallet.puzzle_drivers import PuzzleInfo
74
+ from chia.wallet.puzzles.clawback.metadata import AutoClaimSettings
78
75
  from chia.wallet.signer_protocol import SigningResponse
79
76
  from chia.wallet.singleton import (
80
77
  SINGLETON_LAUNCHER_PUZZLE_HASH,
@@ -82,7 +79,7 @@ from chia.wallet.singleton import (
82
79
  get_inner_puzzle_from_singleton,
83
80
  )
84
81
  from chia.wallet.trade_record import TradeRecord
85
- from chia.wallet.trading.offer import Offer
82
+ from chia.wallet.trading.offer import Offer, OfferSummary
86
83
  from chia.wallet.transaction_record import TransactionRecord
87
84
  from chia.wallet.uncurried_puzzle import uncurry_puzzle
88
85
  from chia.wallet.util.address_type import AddressType, is_valid_address
@@ -90,7 +87,8 @@ from chia.wallet.util.clvm_streamable import json_serialize_with_clvm_streamable
90
87
  from chia.wallet.util.compute_hints import compute_spend_hints_and_additions
91
88
  from chia.wallet.util.compute_memos import compute_memos
92
89
  from chia.wallet.util.curry_and_treehash import NIL_TREEHASH
93
- from chia.wallet.util.query_filter import FilterMode, HashFilter
90
+ from chia.wallet.util.query_filter import HashFilter
91
+ from chia.wallet.util.signing import sign_message, verify_signature
94
92
  from chia.wallet.util.transaction_type import CLAWBACK_INCOMING_TRANSACTION_TYPES, TransactionType
95
93
  from chia.wallet.util.tx_config import DEFAULT_TX_CONFIG, TXConfig, TXConfigLoader
96
94
  from chia.wallet.util.wallet_sync_utils import fetch_coin_spend_for_coin_state
@@ -101,17 +99,20 @@ from chia.wallet.vc_wallet.vc_store import VCProofs
101
99
  from chia.wallet.vc_wallet.vc_wallet import VCWallet
102
100
  from chia.wallet.wallet import Wallet
103
101
  from chia.wallet.wallet_action_scope import WalletActionScope
104
- from chia.wallet.wallet_coin_record import WalletCoinRecord
102
+ from chia.wallet.wallet_coin_record import WalletCoinRecord, WalletCoinRecordMetadataParsingError
105
103
  from chia.wallet.wallet_coin_store import CoinRecordOrder, GetCoinRecords, unspent_range
106
104
  from chia.wallet.wallet_info import WalletInfo
107
- from chia.wallet.wallet_node import WalletNode
108
- from chia.wallet.wallet_protocol import WalletProtocol
105
+ from chia.wallet.wallet_node import WalletNode, get_wallet_db_path
109
106
  from chia.wallet.wallet_request_types import (
110
107
  AddKey,
111
108
  AddKeyResponse,
112
109
  ApplySignatures,
113
110
  ApplySignaturesResponse,
114
111
  BalanceResponse,
112
+ CancelOffer,
113
+ CancelOfferResponse,
114
+ CancelOffers,
115
+ CancelOffersResponse,
115
116
  CATAssetIDToName,
116
117
  CATAssetIDToNameResponse,
117
118
  CATGetAssetID,
@@ -128,8 +129,17 @@ from chia.wallet.wallet_request_types import (
128
129
  CheckOfferValidityResponse,
129
130
  CombineCoins,
130
131
  CombineCoinsResponse,
132
+ CRCATApprovePending,
133
+ CRCATApprovePendingResponse,
131
134
  CreateNewDL,
132
135
  CreateNewDLResponse,
136
+ CreateNewWallet,
137
+ CreateNewWalletResponse,
138
+ CreateNewWalletType,
139
+ CreateOfferForIDs,
140
+ CreateOfferForIDsResponse,
141
+ CreateSignedTransaction,
142
+ CreateSignedTransactionsResponse,
133
143
  DefaultCAT,
134
144
  DeleteKey,
135
145
  DeleteNotifications,
@@ -156,6 +166,7 @@ from chia.wallet.wallet_request_types import (
156
166
  DIDSetWalletNameResponse,
157
167
  DIDTransferDID,
158
168
  DIDTransferDIDResponse,
169
+ DIDType,
159
170
  DIDUpdateMetadata,
160
171
  DIDUpdateMetadataResponse,
161
172
  DLDeleteMirror,
@@ -185,17 +196,25 @@ from chia.wallet.wallet_request_types import (
185
196
  GatherSigningInfo,
186
197
  GatherSigningInfoResponse,
187
198
  GenerateMnemonicResponse,
199
+ GetAllOffers,
200
+ GetAllOffersResponse,
188
201
  GetCATListResponse,
189
202
  GetCoinRecordsByNames,
190
203
  GetCoinRecordsByNamesResponse,
191
204
  GetCurrentDerivationIndexResponse,
205
+ GetFarmedAmount,
206
+ GetFarmedAmountResponse,
192
207
  GetHeightInfoResponse,
193
208
  GetLoggedInFingerprintResponse,
194
209
  GetNextAddress,
195
210
  GetNextAddressResponse,
196
211
  GetNotifications,
197
212
  GetNotificationsResponse,
213
+ GetOffer,
214
+ GetOfferResponse,
198
215
  GetOffersCountResponse,
216
+ GetOfferSummary,
217
+ GetOfferSummaryResponse,
199
218
  GetPrivateKey,
200
219
  GetPrivateKeyFormat,
201
220
  GetPrivateKeyResponse,
@@ -267,6 +286,8 @@ from chia.wallet.wallet_request_types import (
267
286
  SendNotification,
268
287
  SendNotificationResponse,
269
288
  SendTransaction,
289
+ SendTransactionMulti,
290
+ SendTransactionMultiResponse,
270
291
  SendTransactionResponse,
271
292
  SetWalletResyncOnStartup,
272
293
  SignMessageByAddress,
@@ -280,6 +301,8 @@ from chia.wallet.wallet_request_types import (
280
301
  StrayCAT,
281
302
  SubmitTransactions,
282
303
  SubmitTransactionsResponse,
304
+ TakeOffer,
305
+ TakeOfferResponse,
283
306
  TransactionRecordWithMetadata,
284
307
  VCAddProofs,
285
308
  VCGet,
@@ -299,6 +322,7 @@ from chia.wallet.wallet_request_types import (
299
322
  VCSpendResponse,
300
323
  VerifySignature,
301
324
  VerifySignatureResponse,
325
+ WalletCreationMode,
302
326
  WalletInfoResponse,
303
327
  )
304
328
  from chia.wallet.wallet_spend_bundle import WalletSpendBundle
@@ -319,6 +343,9 @@ def tx_endpoint(
319
343
  async def rpc_endpoint(
320
344
  self: WalletRpcApi, request: dict[str, Any], *args: object, **kwargs: object
321
345
  ) -> EndpointResult:
346
+ if await self.service.wallet_state_manager.synced() is False:
347
+ raise ValueError("Wallet needs to be fully synced before making transactions.")
348
+
322
349
  assert self.service.logged_in_fingerprint is not None
323
350
  tx_config_loader: TXConfigLoader = TXConfigLoader.from_json_dict(request)
324
351
 
@@ -332,7 +359,7 @@ def tx_endpoint(
332
359
  excluded_coin_amounts=request.get("exclude_coin_amounts"),
333
360
  )
334
361
  if tx_config_loader.excluded_coin_ids is None:
335
- excluded_coins: Optional[list[dict[str, Any]]] = request.get(
362
+ excluded_coins: list[dict[str, Any]] | None = request.get(
336
363
  "exclude_coins", request.get("excluded_coins")
337
364
  )
338
365
  if excluded_coins is not None:
@@ -360,25 +387,40 @@ def tx_endpoint(
360
387
  ):
361
388
  raise ValueError("Relative timelocks are not currently supported in the RPC")
362
389
 
363
- async with self.service.wallet_state_manager.new_action_scope(
364
- tx_config,
365
- push=request.get("push", push),
366
- merge_spends=request.get("merge_spends", merge_spends),
367
- sign=request.get("sign", self.service.config.get("auto_sign_txs", True)),
368
- ) as action_scope:
390
+ if "action_scope_override" in kwargs:
369
391
  response: EndpointResult = await func(
370
392
  self,
371
393
  request,
372
394
  *args,
373
- action_scope,
395
+ kwargs["action_scope_override"],
374
396
  extra_conditions=extra_conditions,
375
- **kwargs,
397
+ **{k: v for k, v in kwargs.items() if k != "action_scope_override"},
376
398
  )
399
+ action_scope = cast(WalletActionScope, kwargs["action_scope_override"])
400
+ else:
401
+ async with self.service.wallet_state_manager.new_action_scope(
402
+ tx_config,
403
+ push=request.get("push", push),
404
+ merge_spends=request.get("merge_spends", merge_spends),
405
+ sign=request.get("sign", self.service.config.get("auto_sign_txs", True)),
406
+ ) as action_scope:
407
+ response = await func(
408
+ self,
409
+ request,
410
+ *args,
411
+ action_scope,
412
+ extra_conditions=extra_conditions,
413
+ **kwargs,
414
+ )
377
415
 
378
416
  if func.__name__ == "create_new_wallet" and "transactions" not in response:
379
417
  # unfortunately, this API isn't solely a tx endpoint
380
418
  return response
381
419
 
420
+ if "action_scope_override" in kwargs:
421
+ # deferring to parent action scope
422
+ return response
423
+
382
424
  unsigned_txs = await self.service.wallet_state_manager.gather_signing_info_for_txs(
383
425
  action_scope.side_effects.transactions
384
426
  )
@@ -667,10 +709,10 @@ class WalletRpcApi:
667
709
  "/execute_signing_instructions": self.execute_signing_instructions,
668
710
  }
669
711
 
670
- def get_connections(self, request_node_type: Optional[NodeType]) -> list[dict[str, Any]]:
712
+ def get_connections(self, request_node_type: NodeType | None) -> list[dict[str, Any]]:
671
713
  return default_get_connections(server=self.service.server, request_node_type=request_node_type)
672
714
 
673
- async def _state_changed(self, change: str, change_data: Optional[dict[str, Any]]) -> list[WsRpcMessage]:
715
+ async def _state_changed(self, change: str, change_data: dict[str, Any] | None) -> list[WsRpcMessage]:
674
716
  """
675
717
  Called by the WalletNode or WalletStateManager when something has changed in the wallet. This
676
718
  gives us an opportunity to send notifications to all connected clients via WebSocket.
@@ -762,10 +804,7 @@ class WalletRpcApi:
762
804
  @marshal
763
805
  async def get_public_keys(self, request: Empty) -> GetPublicKeysResponse:
764
806
  try:
765
- fingerprints = [
766
- uint32(sk.get_g1().get_fingerprint())
767
- for (sk, seed) in await self.service.keychain_proxy.get_all_private_keys()
768
- ]
807
+ fingerprints = [key_data.fingerprint for key_data in await self.service.keychain_proxy.get_keys()]
769
808
  except KeychainIsLocked:
770
809
  return GetPublicKeysResponse(keyring_is_locked=True)
771
810
  except Exception as e:
@@ -776,7 +815,7 @@ class WalletRpcApi:
776
815
  else:
777
816
  return GetPublicKeysResponse(keyring_is_locked=False, public_key_fingerprints=fingerprints)
778
817
 
779
- async def _get_private_key(self, fingerprint: int) -> tuple[Optional[PrivateKey], Optional[bytes]]:
818
+ async def _get_private_key(self, fingerprint: int) -> tuple[PrivateKey | None, bytes | None]:
780
819
  try:
781
820
  all_keys = await self.service.keychain_proxy.get_all_private_keys()
782
821
  for sk, seed in all_keys:
@@ -838,9 +877,10 @@ class WalletRpcApi:
838
877
  except Exception as e:
839
878
  log.error(f"Failed to delete key by fingerprint: {e}")
840
879
  raise e
841
- path = path_from_root(
880
+ path = get_wallet_db_path(
842
881
  self.service.root_path,
843
- f"{self.service.config['database_path']}-{request.fingerprint}",
882
+ self.service.config,
883
+ str(request.fingerprint),
844
884
  )
845
885
  if path.exists():
846
886
  path.unlink()
@@ -928,14 +968,20 @@ class WalletRpcApi:
928
968
  @marshal
929
969
  async def delete_all_keys(self, request: Empty) -> Empty:
930
970
  await self._stop_wallet()
971
+ all_key_datas = await self.service.keychain_proxy.get_keys()
931
972
  try:
932
973
  await self.service.keychain_proxy.delete_all_keys()
933
974
  except Exception as e:
934
975
  log.error(f"Failed to delete all keys: {e}")
935
976
  raise e
936
- path = path_from_root(self.service.root_path, self.service.config["database_path"])
937
- if path.exists():
938
- path.unlink()
977
+ for key_data in all_key_datas:
978
+ path = get_wallet_db_path(
979
+ self.service.root_path,
980
+ self.service.config,
981
+ str(key_data.fingerprint),
982
+ )
983
+ if path.exists():
984
+ path.unlink()
939
985
  return Empty()
940
986
 
941
987
  ##########################################################################################
@@ -987,50 +1033,21 @@ class WalletRpcApi:
987
1033
  ) -> PushTransactionsResponse:
988
1034
  if not action_scope.config.push:
989
1035
  raise ValueError("Cannot push transactions if push is False")
1036
+ tx_removals = [c for tx in request.transactions for c in tx.removals]
990
1037
  async with action_scope.use() as interface:
991
1038
  interface.side_effects.transactions.extend(request.transactions)
992
- if request.fee != 0:
993
- all_conditions_and_origins = [
994
- (condition, cs.coin.name())
995
- for tx in interface.side_effects.transactions
996
- if tx.spend_bundle is not None
997
- for cs in tx.spend_bundle.coin_spends
998
- for condition in run_with_cost(cs.puzzle_reveal, INFINITE_COST, cs.solution)[1].as_iter()
999
- ]
1000
- create_coin_announcement = next(
1001
- condition
1002
- for condition in parse_conditions_non_consensus(
1003
- [con for con, coin in all_conditions_and_origins], abstractions=False
1004
- )
1005
- if isinstance(condition, CreateCoinAnnouncement)
1006
- )
1007
- announcement_origin = next(
1008
- coin
1009
- for condition, coin in all_conditions_and_origins
1010
- if condition == create_coin_announcement.to_program()
1011
- )
1012
- async with self.service.wallet_state_manager.new_action_scope(
1013
- dataclasses.replace(
1014
- action_scope.config.tx_config,
1015
- excluded_coin_ids=[
1016
- *action_scope.config.tx_config.excluded_coin_ids,
1017
- *(c.name() for tx in interface.side_effects.transactions for c in tx.removals),
1018
- ],
1019
- ),
1020
- push=False,
1021
- ) as inner_action_scope:
1022
- await self.service.wallet_state_manager.main_wallet.create_tandem_xch_tx(
1023
- request.fee,
1024
- inner_action_scope,
1025
- extra_conditions=(
1026
- *extra_conditions,
1027
- CreateCoinAnnouncement(
1028
- create_coin_announcement.msg, announcement_origin
1029
- ).corresponding_assertion(),
1030
- ),
1031
- )
1032
-
1033
- interface.side_effects.transactions.extend(inner_action_scope.side_effects.transactions)
1039
+ interface.side_effects.selected_coins.extend(tx_removals)
1040
+ if request.fee != 0:
1041
+ await self.service.wallet_state_manager.main_wallet.create_tandem_xch_tx(
1042
+ request.fee,
1043
+ action_scope,
1044
+ extra_conditions=(
1045
+ *extra_conditions,
1046
+ AssertConcurrentSpend(tx_removals[0].name()),
1047
+ ),
1048
+ )
1049
+ elif extra_conditions != tuple():
1050
+ raise ValueError("Cannot add conditions to a transaction if no new fee spend is being added")
1034
1051
 
1035
1052
  return PushTransactionsResponse([], []) # tx_endpoint takes care of this
1036
1053
 
@@ -1065,7 +1082,7 @@ class WalletRpcApi:
1065
1082
 
1066
1083
  @marshal
1067
1084
  async def get_wallets(self, request: GetWallets) -> GetWalletsResponse:
1068
- wallet_type: Optional[WalletType] = None
1085
+ wallet_type: WalletType | None = None
1069
1086
  if request.type is not None:
1070
1087
  wallet_type = WalletType(request.type)
1071
1088
 
@@ -1099,81 +1116,67 @@ class WalletRpcApi:
1099
1116
  return GetWalletsResponse(wallet_infos, uint32.construct_optional(self.service.logged_in_fingerprint))
1100
1117
 
1101
1118
  @tx_endpoint(push=True)
1102
- async def create_new_wallet(
1119
+ @marshal
1120
+ # Semantics guarantee returning on all paths, or else an error.
1121
+ # It's probably not great to add a bunch of unreachable code for the sake of mypy.
1122
+ async def create_new_wallet( # type: ignore[return]
1103
1123
  self,
1104
- request: dict[str, Any],
1124
+ request: CreateNewWallet,
1105
1125
  action_scope: WalletActionScope,
1106
1126
  extra_conditions: tuple[Condition, ...] = tuple(),
1107
- ) -> EndpointResult:
1127
+ ) -> CreateNewWalletResponse:
1108
1128
  wallet_state_manager = self.service.wallet_state_manager
1109
-
1110
- if await self.service.wallet_state_manager.synced() is False:
1111
- raise ValueError("Wallet needs to be fully synced.")
1112
1129
  main_wallet = wallet_state_manager.main_wallet
1113
- fee = uint64(request.get("fee", 0))
1114
-
1115
- if request["wallet_type"] == "cat_wallet":
1116
- # If not provided, the name will be autogenerated based on the tail hash.
1117
- name = request.get("name", None)
1118
- if request["mode"] == "new":
1119
- if request.get("test", False):
1120
- if not action_scope.config.push:
1121
- raise ValueError("Test CAT minting must be pushed automatically") # pragma: no cover
1122
- async with self.service.wallet_state_manager.lock:
1123
- cat_wallet = await CATWallet.create_new_cat_wallet(
1124
- wallet_state_manager,
1125
- main_wallet,
1126
- {"identifier": "genesis_by_id"},
1127
- uint64(request["amount"]),
1128
- action_scope,
1129
- fee,
1130
- name,
1131
- )
1132
- asset_id = cat_wallet.get_asset_id()
1133
- self.service.wallet_state_manager.state_changed("wallet_created")
1134
- return {
1135
- "type": cat_wallet.type(),
1136
- "asset_id": asset_id,
1137
- "wallet_id": cat_wallet.id(),
1138
- "transactions": None, # tx_endpoint wrapper will take care of this
1139
- }
1140
- else:
1141
- raise ValueError(
1142
- "Support for this RPC mode has been dropped."
1143
- " Please use the CAT Admin Tool @ https://github.com/Chia-Network/CAT-admin-tool instead."
1130
+
1131
+ if request.wallet_type == CreateNewWalletType.CAT_WALLET:
1132
+ if request.mode == WalletCreationMode.NEW:
1133
+ if not action_scope.config.push:
1134
+ raise ValueError("Test CAT minting must be pushed automatically") # pragma: no cover
1135
+ async with self.service.wallet_state_manager.lock:
1136
+ cat_wallet = await CATWallet.create_new_cat_wallet(
1137
+ wallet_state_manager,
1138
+ main_wallet,
1139
+ {"identifier": "genesis_by_id"},
1140
+ # mypy doesn't know about our __post_init__
1141
+ request.amount, # type: ignore[arg-type]
1142
+ action_scope,
1143
+ request.fee,
1144
+ request.name,
1144
1145
  )
1146
+ asset_id = cat_wallet.get_asset_id()
1147
+ self.service.wallet_state_manager.state_changed("wallet_created")
1148
+ return CreateNewWalletResponse(
1149
+ [], [], type=cat_wallet.type().name, asset_id=asset_id, wallet_id=cat_wallet.id()
1150
+ )
1145
1151
 
1146
- elif request["mode"] == "existing":
1152
+ elif request.mode == WalletCreationMode.EXISTING:
1147
1153
  async with self.service.wallet_state_manager.lock:
1154
+ assert request.asset_id is not None # mypy doesn't know about our __post_init__
1148
1155
  cat_wallet = await CATWallet.get_or_create_wallet_for_cat(
1149
- wallet_state_manager, main_wallet, request["asset_id"], name
1156
+ wallet_state_manager, main_wallet, request.asset_id, request.name
1150
1157
  )
1151
- return {"type": cat_wallet.type(), "asset_id": request["asset_id"], "wallet_id": cat_wallet.id()}
1152
-
1153
- else: # undefined mode
1154
- pass
1155
-
1156
- elif request["wallet_type"] == "did_wallet":
1157
- if request["did_type"] == "new":
1158
- if "backup_dids" in request and request["backup_dids"] != []:
1159
- raise ValueError("Recovery options are no longer supported. `backup_dids` cannot be set.")
1160
- metadata: dict[str, str] = {}
1161
- if "metadata" in request:
1162
- if type(request["metadata"]) is dict:
1163
- metadata = request["metadata"]
1164
-
1158
+ return CreateNewWalletResponse(
1159
+ [],
1160
+ [],
1161
+ type=cat_wallet.type().name,
1162
+ asset_id=request.asset_id,
1163
+ wallet_id=cat_wallet.id(),
1164
+ )
1165
+ elif request.wallet_type == CreateNewWalletType.DID_WALLET:
1166
+ if request.did_type == DIDType.NEW:
1165
1167
  async with self.service.wallet_state_manager.lock:
1166
- did_wallet_name: Optional[str] = request.get("wallet_name", None)
1167
- if did_wallet_name is not None:
1168
- did_wallet_name = did_wallet_name.strip()
1168
+ did_wallet_name = None
1169
+ if request.wallet_name is not None:
1170
+ did_wallet_name = request.wallet_name.strip()
1171
+ assert request.amount is not None # mypy doesn't know about our __post_init__
1169
1172
  did_wallet: DIDWallet = await DIDWallet.create_new_did_wallet(
1170
1173
  wallet_state_manager,
1171
1174
  main_wallet,
1172
- uint64(request["amount"]),
1175
+ request.amount,
1173
1176
  action_scope,
1174
- metadata,
1177
+ request.metadata,
1175
1178
  did_wallet_name,
1176
- uint64(request.get("fee", 0)),
1179
+ request.fee,
1177
1180
  extra_conditions=extra_conditions,
1178
1181
  )
1179
1182
 
@@ -1189,131 +1192,95 @@ class WalletRpcApi:
1189
1192
  bytes32.fromhex(did_wallet.get_my_DID()),
1190
1193
  nft_wallet_name,
1191
1194
  )
1192
- return {
1193
- "success": True,
1194
- "type": did_wallet.type(),
1195
- "my_did": my_did_id,
1196
- "wallet_id": did_wallet.id(),
1197
- "transactions": None, # tx_endpoint wrapper will take care of this
1198
- }
1195
+ return CreateNewWalletResponse(
1196
+ [], [], type=did_wallet.type().name, my_did=my_did_id, wallet_id=did_wallet.id()
1197
+ )
1199
1198
 
1200
- elif request["did_type"] == "recovery":
1199
+ elif request.did_type == DIDType.RECOVERY:
1201
1200
  async with self.service.wallet_state_manager.lock:
1201
+ assert request.backup_data is not None # mypy doesn't know about our __post_init__
1202
1202
  did_wallet = await DIDWallet.create_new_did_wallet_from_recovery(
1203
- wallet_state_manager, main_wallet, request["backup_data"]
1203
+ wallet_state_manager, main_wallet, request.backup_data
1204
1204
  )
1205
1205
  assert did_wallet.did_info.temp_coin is not None
1206
1206
  assert did_wallet.did_info.temp_puzhash is not None
1207
1207
  assert did_wallet.did_info.temp_pubkey is not None
1208
1208
  my_did = did_wallet.get_my_DID()
1209
- coin_name = did_wallet.did_info.temp_coin.name().hex()
1210
- coin_list = coin_as_list(did_wallet.did_info.temp_coin)
1209
+ coin_name = did_wallet.did_info.temp_coin.name()
1211
1210
  newpuzhash = did_wallet.did_info.temp_puzhash
1212
1211
  pubkey = did_wallet.did_info.temp_pubkey
1213
- return {
1214
- "success": True,
1215
- "type": did_wallet.type(),
1216
- "my_did": my_did,
1217
- "wallet_id": did_wallet.id(),
1218
- "coin_name": coin_name,
1219
- "coin_list": coin_list,
1220
- "newpuzhash": newpuzhash.hex(),
1221
- "pubkey": pubkey.hex(),
1222
- "backup_dids": did_wallet.did_info.backup_ids,
1223
- "num_verifications_required": did_wallet.did_info.num_of_backup_ids_needed,
1224
- }
1225
- else: # undefined did_type
1226
- pass
1227
- elif request["wallet_type"] == "nft_wallet":
1212
+ return CreateNewWalletResponse(
1213
+ [],
1214
+ [],
1215
+ type=did_wallet.type().name,
1216
+ my_did=my_did,
1217
+ wallet_id=did_wallet.id(),
1218
+ coin_name=coin_name,
1219
+ coin_list=did_wallet.did_info.temp_coin,
1220
+ newpuzhash=newpuzhash,
1221
+ pubkey=G1Element.from_bytes(pubkey),
1222
+ backup_dids=did_wallet.did_info.backup_ids,
1223
+ num_verifications_required=did_wallet.did_info.num_of_backup_ids_needed,
1224
+ )
1225
+ elif request.wallet_type == CreateNewWalletType.NFT_WALLET:
1226
+ did_id: bytes32 | None = None
1227
+ if request.did_id is not None:
1228
+ did_id = decode_puzzle_hash(request.did_id)
1228
1229
  for wallet in self.service.wallet_state_manager.wallets.values():
1229
- did_id: Optional[bytes32] = None
1230
- if "did_id" in request and request["did_id"] is not None:
1231
- did_id = decode_puzzle_hash(request["did_id"])
1232
1230
  if wallet.type() == WalletType.NFT:
1233
1231
  assert isinstance(wallet, NFTWallet)
1234
1232
  if wallet.get_did() == did_id:
1235
1233
  log.info("NFT wallet already existed, skipping.")
1236
- return {
1237
- "success": True,
1238
- "type": wallet.type(),
1239
- "wallet_id": wallet.id(),
1240
- }
1234
+ return CreateNewWalletResponse(
1235
+ [],
1236
+ [],
1237
+ type=wallet.type().name,
1238
+ wallet_id=wallet.id(),
1239
+ )
1241
1240
 
1242
1241
  async with self.service.wallet_state_manager.lock:
1243
1242
  nft_wallet: NFTWallet = await NFTWallet.create_new_nft_wallet(
1244
- wallet_state_manager, main_wallet, did_id, request.get("name", None)
1243
+ wallet_state_manager, main_wallet, did_id, request.name
1245
1244
  )
1246
- return {
1247
- "success": True,
1248
- "type": nft_wallet.type(),
1249
- "wallet_id": nft_wallet.id(),
1250
- }
1251
- elif request["wallet_type"] == "pool_wallet":
1252
- if request["mode"] == "new":
1253
- if "initial_target_state" not in request:
1254
- raise AttributeError("Daemon didn't send `initial_target_state`. Try updating the daemon.")
1255
-
1256
- owner_puzzle_hash: bytes32 = await action_scope.get_puzzle_hash(self.service.wallet_state_manager)
1257
-
1258
- from chia.pools.pool_wallet_info import initial_pool_state_from_dict
1259
-
1245
+ return CreateNewWalletResponse(
1246
+ [],
1247
+ [],
1248
+ type=nft_wallet.type().name,
1249
+ wallet_id=nft_wallet.id(),
1250
+ )
1251
+ elif request.wallet_type == CreateNewWalletType.POOL_WALLET:
1252
+ if request.mode == WalletCreationMode.NEW:
1260
1253
  async with self.service.wallet_state_manager.lock:
1261
- # We assign a pseudo unique id to each pool wallet, so that each one gets its own deterministic
1262
- # owner and auth keys. The public keys will go on the blockchain, and the private keys can be found
1263
- # using the root SK and trying each index from zero. The indexes are not fully unique though,
1264
- # because the PoolWallet is not created until the tx gets confirmed on chain. Therefore if we
1265
- # make multiple pool wallets at the same time, they will have the same ID.
1266
- max_pwi = 1
1267
- for _, wallet in self.service.wallet_state_manager.wallets.items():
1268
- if wallet.type() == WalletType.POOLING_WALLET:
1269
- max_pwi += 1
1270
-
1271
- if max_pwi + 1 >= (MAX_POOL_WALLETS - 1):
1272
- raise ValueError(f"Too many pool wallets ({max_pwi}), cannot create any more on this key.")
1273
-
1274
- owner_pk: G1Element = self.service.wallet_state_manager.main_wallet.hardened_pubkey_for_path(
1275
- # copied from chia.wallet.derive_keys. Could maybe be an exported constant in the future.
1276
- [12381, 8444, 5, max_pwi]
1277
- )
1278
-
1254
+ assert request.initial_target_state is not None # mypy doesn't know about our __post_init__
1279
1255
  initial_target_state = initial_pool_state_from_dict(
1280
- request["initial_target_state"], owner_pk, owner_puzzle_hash
1256
+ request.initial_target_state,
1257
+ self.service.wallet_state_manager.new_pool_wallet_pubkey(),
1258
+ await action_scope.get_puzzle_hash(self.service.wallet_state_manager),
1281
1259
  )
1282
1260
  assert initial_target_state is not None
1283
1261
 
1284
- try:
1285
- delayed_address = None
1286
- if "p2_singleton_delayed_ph" in request:
1287
- delayed_address = bytes32.from_hexstr(request["p2_singleton_delayed_ph"])
1288
-
1289
- p2_singleton_puzzle_hash, launcher_id = await PoolWallet.create_new_pool_wallet_transaction(
1290
- wallet_state_manager,
1291
- main_wallet,
1292
- initial_target_state,
1293
- action_scope,
1294
- fee,
1295
- request.get("p2_singleton_delay_time", None),
1296
- delayed_address,
1297
- extra_conditions=extra_conditions,
1298
- )
1299
-
1300
- except Exception as e:
1301
- raise ValueError(str(e))
1302
- return {
1303
- "total_fee": fee * 2,
1304
- "transaction": None, # tx_endpoint wrapper will take care of this
1305
- "transactions": None, # tx_endpoint wrapper will take care of this
1306
- "launcher_id": launcher_id.hex(),
1307
- "p2_singleton_puzzle_hash": p2_singleton_puzzle_hash.hex(),
1308
- }
1309
- elif request["mode"] == "recovery":
1310
- raise ValueError("Need upgraded singleton for on-chain recovery")
1311
-
1312
- else: # undefined wallet_type
1313
- pass
1262
+ p2_singleton_puzzle_hash, launcher_id = await PoolWallet.create_new_pool_wallet_transaction(
1263
+ wallet_state_manager,
1264
+ main_wallet,
1265
+ initial_target_state,
1266
+ action_scope,
1267
+ request.fee,
1268
+ request.p2_singleton_delay_time,
1269
+ request.p2_singleton_delayed_ph,
1270
+ extra_conditions=extra_conditions,
1271
+ )
1314
1272
 
1315
- # TODO: rework this function to report detailed errors for each error case
1316
- return {"success": False, "error": "invalid request"}
1273
+ return CreateNewWalletResponse(
1274
+ [],
1275
+ [],
1276
+ transaction=REPLACEABLE_TRANSACTION_RECORD,
1277
+ total_fee=uint64(request.fee * 2),
1278
+ launcher_id=launcher_id,
1279
+ p2_singleton_puzzle_hash=p2_singleton_puzzle_hash,
1280
+ # irrelevant, will be replaced in serialization
1281
+ type=WalletType.POOLING_WALLET.name,
1282
+ wallet_id=uint32(0),
1283
+ )
1317
1284
 
1318
1285
  ##########################################################################################
1319
1286
  # Wallet
@@ -1329,7 +1296,7 @@ class WalletRpcApi:
1329
1296
  wallet_balance["fingerprint"] = self.service.logged_in_fingerprint
1330
1297
  if wallet.type() in {WalletType.CAT, WalletType.CRCAT, WalletType.RCAT}:
1331
1298
  assert isinstance(wallet, CATWallet)
1332
- wallet_balance["asset_id"] = wallet.get_asset_id()
1299
+ wallet_balance["asset_id"] = wallet.get_asset_id().hex()
1333
1300
  if wallet.type() == WalletType.CRCAT:
1334
1301
  assert isinstance(wallet, CRCATWallet)
1335
1302
  wallet_balance["pending_approval_balance"] = await wallet.get_pending_approval_balance()
@@ -1352,9 +1319,7 @@ class WalletRpcApi:
1352
1319
 
1353
1320
  @marshal
1354
1321
  async def get_transaction(self, request: GetTransaction) -> GetTransactionResponse:
1355
- tr: Optional[TransactionRecord] = await self.service.wallet_state_manager.get_transaction(
1356
- request.transaction_id
1357
- )
1322
+ tr: TransactionRecord | None = await self.service.wallet_state_manager.get_transaction(request.transaction_id)
1358
1323
  if tr is None:
1359
1324
  raise ValueError(f"Transaction 0x{request.transaction_id.hex()} not found")
1360
1325
 
@@ -1366,7 +1331,7 @@ class WalletRpcApi:
1366
1331
  @marshal
1367
1332
  async def get_transaction_memo(self, request: GetTransactionMemo) -> GetTransactionMemoResponse:
1368
1333
  transaction_id: bytes32 = request.transaction_id
1369
- tr: Optional[TransactionRecord] = await self.service.wallet_state_manager.get_transaction(transaction_id)
1334
+ tr: TransactionRecord | None = await self.service.wallet_state_manager.get_transaction(transaction_id)
1370
1335
  if tr is None:
1371
1336
  raise ValueError(f"Transaction 0x{transaction_id.hex()} not found")
1372
1337
  if tr.spend_bundle is None or len(tr.spend_bundle.coin_spends) == 0:
@@ -1390,57 +1355,13 @@ class WalletRpcApi:
1390
1355
  async def split_coins(
1391
1356
  self, request: SplitCoins, action_scope: WalletActionScope, extra_conditions: tuple[Condition, ...] = tuple()
1392
1357
  ) -> SplitCoinsResponse:
1393
- if request.number_of_coins > 500:
1394
- raise ValueError(f"{request.number_of_coins} coins is greater then the maximum limit of 500 coins.")
1395
-
1396
- optional_coin = await self.service.wallet_state_manager.coin_store.get_coin_record(request.target_coin_id)
1397
- if optional_coin is None:
1398
- raise ValueError(f"Could not find coin with ID {request.target_coin_id}")
1399
- else:
1400
- coin = optional_coin.coin
1401
-
1402
- total_amount = request.amount_per_coin * request.number_of_coins
1403
-
1404
- if coin.amount < total_amount:
1405
- raise ValueError(
1406
- f"Coin amount: {coin.amount} is less than the total amount of the split: {total_amount}, exiting."
1407
- )
1408
-
1409
- if request.wallet_id not in self.service.wallet_state_manager.wallets:
1410
- raise ValueError(f"Wallet with ID {request.wallet_id} does not exist")
1411
- wallet = self.service.wallet_state_manager.wallets[request.wallet_id]
1412
- if not isinstance(wallet, (Wallet, CATWallet)):
1413
- raise ValueError("Cannot split coins from non-fungible wallet types")
1414
-
1415
- outputs = [
1416
- CreateCoin(
1417
- await action_scope.get_puzzle_hash(
1418
- self.service.wallet_state_manager, override_reuse_puzhash_with=False
1419
- ),
1420
- request.amount_per_coin,
1421
- )
1422
- for _ in range(request.number_of_coins)
1423
- ]
1424
- if len(outputs) == 0:
1425
- return SplitCoinsResponse([], [])
1426
-
1427
- if wallet.type() == WalletType.STANDARD_WALLET and coin.amount < total_amount + request.fee:
1428
- async with action_scope.use() as interface:
1429
- interface.side_effects.selected_coins.append(coin)
1430
- coins = await wallet.select_coins(
1431
- uint64(total_amount + request.fee - coin.amount),
1432
- action_scope,
1433
- )
1434
- coins.add(coin)
1435
- else:
1436
- coins = {coin}
1437
-
1438
- await wallet.generate_signed_transaction(
1439
- [output.amount for output in outputs],
1440
- [output.puzzle_hash for output in outputs],
1441
- action_scope,
1442
- request.fee,
1443
- coins=coins,
1358
+ await self.service.wallet_state_manager.split_coins(
1359
+ action_scope=action_scope,
1360
+ wallet_id=request.wallet_id,
1361
+ target_coin_id=request.target_coin_id,
1362
+ amount_per_coin=request.amount_per_coin,
1363
+ number_of_coins=request.number_of_coins,
1364
+ fee=request.fee,
1444
1365
  extra_conditions=extra_conditions,
1445
1366
  )
1446
1367
 
@@ -1451,98 +1372,22 @@ class WalletRpcApi:
1451
1372
  async def combine_coins(
1452
1373
  self, request: CombineCoins, action_scope: WalletActionScope, extra_conditions: tuple[Condition, ...] = tuple()
1453
1374
  ) -> CombineCoinsResponse:
1454
- # Some "number of coins" validation
1455
- if request.number_of_coins > request.coin_num_limit:
1456
- raise ValueError(
1457
- f"{request.number_of_coins} coins is greater then the maximum limit of {request.coin_num_limit} coins."
1458
- )
1459
- if request.number_of_coins < 1:
1460
- raise ValueError("You need at least two coins to combine")
1461
- if len(request.target_coin_ids) > request.number_of_coins:
1462
- raise ValueError("More coin IDs specified than desired number of coins to combine")
1463
-
1464
- if request.wallet_id not in self.service.wallet_state_manager.wallets:
1465
- raise ValueError(f"Wallet with ID {request.wallet_id} does not exist")
1466
- wallet = self.service.wallet_state_manager.wallets[request.wallet_id]
1467
- if not isinstance(wallet, (Wallet, CATWallet)):
1468
- raise ValueError("Cannot combine coins from non-fungible wallet types")
1469
-
1470
- coins: list[Coin] = []
1471
-
1472
- # First get the coin IDs specified
1473
- if request.target_coin_ids != []:
1474
- coins.extend(
1475
- cr.coin
1476
- for cr in (
1477
- await self.service.wallet_state_manager.coin_store.get_coin_records(
1478
- wallet_id=request.wallet_id,
1479
- coin_id_filter=HashFilter(request.target_coin_ids, mode=uint8(FilterMode.include.value)),
1480
- )
1481
- ).records
1482
- )
1483
-
1484
- async with action_scope.use() as interface:
1485
- interface.side_effects.selected_coins.extend(coins)
1486
-
1487
- # Next let's select enough coins to meet the target + fee if there is one
1488
- fungible_amount_needed = uint64(0) if request.target_coin_amount is None else request.target_coin_amount
1489
- if isinstance(wallet, Wallet):
1490
- fungible_amount_needed = uint64(fungible_amount_needed + request.fee)
1491
- amount_selected = sum(c.amount for c in coins)
1492
- if amount_selected < fungible_amount_needed: # implicit fungible_amount_needed > 0 here
1493
- coins.extend(
1494
- await wallet.select_coins(
1495
- amount=uint64(fungible_amount_needed - amount_selected), action_scope=action_scope
1496
- )
1497
- )
1498
-
1499
- if len(coins) > request.number_of_coins:
1500
- raise ValueError(
1501
- f"Options specified cannot be met without selecting more coins than specified: {len(coins)}"
1502
- )
1503
-
1504
- # Now let's select enough coins to get to the target number to combine
1505
- if len(coins) < request.number_of_coins:
1506
- async with action_scope.use() as interface:
1507
- coins.extend(
1508
- cr.coin
1509
- for cr in (
1510
- await self.service.wallet_state_manager.coin_store.get_coin_records(
1511
- wallet_id=request.wallet_id,
1512
- limit=uint32(request.number_of_coins - len(coins)),
1513
- order=CoinRecordOrder.amount,
1514
- coin_id_filter=HashFilter(
1515
- [c.name() for c in interface.side_effects.selected_coins],
1516
- mode=uint8(FilterMode.exclude.value),
1517
- ),
1518
- reverse=request.largest_first,
1519
- )
1520
- ).records
1521
- )
1522
-
1523
- async with action_scope.use() as interface:
1524
- interface.side_effects.selected_coins.extend(coins)
1525
-
1526
- primary_output_amount = (
1527
- uint64(sum(c.amount for c in coins)) if request.target_coin_amount is None else request.target_coin_amount
1528
- )
1529
- if isinstance(wallet, Wallet):
1530
- primary_output_amount = uint64(primary_output_amount - request.fee)
1531
-
1532
- await wallet.generate_signed_transaction(
1533
- [primary_output_amount],
1534
- [await action_scope.get_puzzle_hash(self.service.wallet_state_manager)],
1535
- action_scope,
1536
- request.fee,
1537
- coins=set(coins),
1375
+ await self.service.wallet_state_manager.combine_coins(
1376
+ action_scope=action_scope,
1377
+ wallet_id=request.wallet_id,
1378
+ number_of_coins=request.number_of_coins,
1379
+ largest_first=request.largest_first,
1380
+ coin_num_limit=request.coin_num_limit,
1381
+ fee=request.fee,
1382
+ target_coin_amount=request.target_coin_amount,
1383
+ target_coin_ids=request.target_coin_ids if request.target_coin_ids != [] else None,
1538
1384
  extra_conditions=extra_conditions,
1539
1385
  )
1540
-
1541
1386
  return CombineCoinsResponse([], []) # tx_endpoint will take care to fill this out
1542
1387
 
1543
1388
  @marshal
1544
1389
  async def get_transactions(self, request: GetTransactions) -> GetTransactionsResponse:
1545
- to_puzzle_hash: Optional[bytes32] = None
1390
+ to_puzzle_hash: bytes32 | None = None
1546
1391
  if request.to_address is not None:
1547
1392
  to_puzzle_hash = decode_puzzle_hash(request.to_address)
1548
1393
 
@@ -1564,7 +1409,7 @@ class WalletRpcApi:
1564
1409
  if tx["type"] not in CLAWBACK_INCOMING_TRANSACTION_TYPES:
1565
1410
  continue
1566
1411
  coin: Coin = tr.additions[0]
1567
- record: Optional[WalletCoinRecord] = await self.service.wallet_state_manager.coin_store.get_coin_record(
1412
+ record: WalletCoinRecord | None = await self.service.wallet_state_manager.coin_store.get_coin_record(
1568
1413
  coin.name()
1569
1414
  )
1570
1415
  if record is None:
@@ -1624,9 +1469,6 @@ class WalletRpcApi:
1624
1469
  action_scope: WalletActionScope,
1625
1470
  extra_conditions: tuple[Condition, ...] = tuple(),
1626
1471
  ) -> SendTransactionResponse:
1627
- if await self.service.wallet_state_manager.synced() is False:
1628
- raise ValueError("Wallet needs to be fully synced before sending transactions")
1629
-
1630
1472
  wallet = self.service.wallet_state_manager.get_wallet(id=request.wallet_id, required_type=Wallet)
1631
1473
 
1632
1474
  # TODO: Add support for multiple puzhash/amount/memo sets
@@ -1651,35 +1493,41 @@ class WalletRpcApi:
1651
1493
  # tx_endpoint will take care of the default values here
1652
1494
  return SendTransactionResponse([], [], transaction=REPLACEABLE_TRANSACTION_RECORD, transaction_id=bytes32.zeros)
1653
1495
 
1654
- async def send_transaction_multi(self, request: dict[str, Any]) -> EndpointResult:
1496
+ @tx_endpoint(push=True)
1497
+ @marshal
1498
+ async def send_transaction_multi(
1499
+ self,
1500
+ request: SendTransactionMulti,
1501
+ action_scope: WalletActionScope,
1502
+ extra_conditions: tuple[Condition, ...] = tuple(),
1503
+ ) -> SendTransactionMultiResponse:
1655
1504
  if await self.service.wallet_state_manager.synced() is False:
1656
1505
  raise ValueError("Wallet needs to be fully synced before sending transactions")
1657
1506
 
1658
- # This is required because this is a "@tx_endpoint" that calls other @tx_endpoints
1659
- request.setdefault("push", True)
1660
- request.setdefault("merge_spends", True)
1661
-
1662
- wallet_id = uint32(request["wallet_id"])
1663
- wallet = self.service.wallet_state_manager.wallets[wallet_id]
1507
+ wallet = self.service.wallet_state_manager.wallets[request.wallet_id]
1664
1508
 
1665
1509
  async with self.service.wallet_state_manager.lock:
1666
- if wallet.type() in {WalletType.CAT, WalletType.CRCAT, WalletType.RCAT}:
1667
- assert isinstance(wallet, CATWallet)
1668
- response = await self.cat_spend(request, hold_lock=False)
1669
- transaction = response["transaction"]
1670
- transactions = response["transactions"]
1510
+ if issubclass(type(wallet), CATWallet):
1511
+ await self.cat_spend(
1512
+ request.convert_to_proxy(CATSpend).json_serialize_for_transport(
1513
+ action_scope.config.tx_config, extra_conditions, ConditionValidTimes()
1514
+ ),
1515
+ hold_lock=False,
1516
+ action_scope_override=action_scope,
1517
+ )
1671
1518
  else:
1672
- response = await self.create_signed_transaction(request, hold_lock=False)
1673
- transaction = response["signed_tx"]
1674
- transactions = response["transactions"]
1519
+ await self.create_signed_transaction(
1520
+ request.convert_to_proxy(CreateSignedTransaction).json_serialize_for_transport(
1521
+ action_scope.config.tx_config, extra_conditions, ConditionValidTimes()
1522
+ ),
1523
+ hold_lock=False,
1524
+ action_scope_override=action_scope,
1525
+ )
1675
1526
 
1676
- # Transaction may not have been included in the mempool yet. Use get_transaction to check.
1677
- return {
1678
- "transaction": transaction,
1679
- "transaction_id": TransactionRecord.from_json_dict(transaction).name,
1680
- "transactions": transactions,
1681
- "unsigned_transactions": response["unsigned_transactions"],
1682
- }
1527
+ # tx_endpoint will take care of these values
1528
+ return SendTransactionMultiResponse(
1529
+ [], [], transaction=REPLACEABLE_TRANSACTION_RECORD, transaction_id=bytes32.zeros
1530
+ )
1683
1531
 
1684
1532
  @tx_endpoint(push=True, merge_spends=False)
1685
1533
  @marshal
@@ -1696,7 +1544,6 @@ class WalletRpcApi:
1696
1544
  :param fee: transaction fee in mojos
1697
1545
  :return:
1698
1546
  """
1699
- # Get inner puzzle
1700
1547
  coin_records = await self.service.wallet_state_manager.coin_store.get_coin_records(
1701
1548
  coin_id_filter=HashFilter.include(request.coin_ids),
1702
1549
  coin_type=CoinType.CLAWBACK,
@@ -1704,31 +1551,23 @@ class WalletRpcApi:
1704
1551
  spent_range=UInt32Range(stop=uint32(0)),
1705
1552
  )
1706
1553
 
1707
- coins: dict[Coin, ClawbackMetadata] = {}
1708
1554
  batch_size = (
1709
1555
  request.batch_size
1710
1556
  if request.batch_size is not None
1711
1557
  else self.service.wallet_state_manager.config.get("auto_claim", {}).get("batch_size", 50)
1712
1558
  )
1713
- for coin_id, coin_record in coin_records.coin_id_to_record.items():
1559
+ records_list = list(coin_records.coin_id_to_record.values())
1560
+ for i in range(0, len(records_list), batch_size):
1714
1561
  try:
1715
- metadata = coin_record.parsed_metadata()
1716
- assert isinstance(metadata, ClawbackMetadata)
1717
- coins[coin_record.coin] = metadata
1718
- if len(coins) >= batch_size:
1719
- await self.service.wallet_state_manager.spend_clawback_coins(
1720
- coins,
1721
- request.fee,
1722
- action_scope,
1723
- request.force,
1724
- extra_conditions=extra_conditions,
1725
- )
1726
- coins = {}
1727
- except Exception as e:
1728
- log.error(f"Failed to spend clawback coin {coin_id.hex()}: %s", e)
1729
- if len(coins) > 0:
1562
+ coin_batch = {
1563
+ coin_record.coin: coin_record.parsed_metadata() for coin_record in records_list[i : i + batch_size]
1564
+ }
1565
+ except WalletCoinRecordMetadataParsingError as e:
1566
+ log.error("Failed to spend clawback coin: %s", e)
1567
+ continue
1730
1568
  await self.service.wallet_state_manager.spend_clawback_coins(
1731
- coins,
1569
+ # Semantically, we're guaranteed the right type here, but the typing isn't there
1570
+ coin_batch, # type: ignore[arg-type]
1732
1571
  request.fee,
1733
1572
  action_scope,
1734
1573
  request.force,
@@ -1796,58 +1635,43 @@ class WalletRpcApi:
1796
1635
  raise ValueError("Wallet needs to be fully synced before getting all coins")
1797
1636
 
1798
1637
  state_mgr = self.service.wallet_state_manager
1799
- wallet = state_mgr.wallets[request.wallet_id]
1800
1638
  async with state_mgr.lock:
1801
- all_coin_records = await state_mgr.coin_store.get_unspent_coins_for_wallet(request.wallet_id)
1802
- if wallet.type() in {WalletType.CAT, WalletType.CRCAT, WalletType.RCAT}:
1803
- assert isinstance(wallet, CATWallet)
1804
- spendable_coins: list[WalletCoinRecord] = await wallet.get_cat_spendable_coins(all_coin_records)
1805
- else:
1806
- spendable_coins = list(
1807
- await state_mgr.get_spendable_coins_for_wallet(request.wallet_id, all_coin_records)
1639
+ # Removals
1640
+ unconfirmed_removals = await state_mgr.unconfirmed_additions_or_removals_for_wallet(
1641
+ wallet_id=request.wallet_id, get="removals"
1642
+ )
1643
+ unconfirmed_removal_ids = {coin.name() for coin in unconfirmed_removals}
1644
+ removal_records: list[CoinRecord] = []
1645
+ for coin_record in (
1646
+ await state_mgr.coin_store.get_coin_records(
1647
+ coin_id_filter=HashFilter.include(list(unconfirmed_removal_ids))
1808
1648
  )
1649
+ ).records:
1650
+ removal_records.append(await state_mgr.get_coin_record_by_wallet_record(coin_record))
1809
1651
 
1810
- # Now we get the unconfirmed transactions and manually derive the additions and removals.
1811
- unconfirmed_transactions: list[TransactionRecord] = await state_mgr.tx_store.get_unconfirmed_for_wallet(
1812
- request.wallet_id
1652
+ # Additions
1653
+ unconfirmed_additions = await state_mgr.unconfirmed_additions_or_removals_for_wallet(
1654
+ wallet_id=request.wallet_id, get="additions"
1655
+ )
1656
+
1657
+ # Spendable coins
1658
+ unfiltered_spendable_coin_records = await state_mgr.get_spendable_coins_for_wallet(
1659
+ request.wallet_id, pending_removals=unconfirmed_removal_ids
1660
+ )
1661
+ filtered_spendable_coins = request.autofill(
1662
+ constants=self.service.wallet_state_manager.constants
1663
+ ).filter_coins({cr.coin for cr in unfiltered_spendable_coin_records})
1664
+ filtered_spendable_coin_records = list(
1665
+ cr for cr in unfiltered_spendable_coin_records if cr.coin in filtered_spendable_coins
1813
1666
  )
1814
- unconfirmed_removal_ids: dict[bytes32, uint64] = {
1815
- coin.name(): transaction.created_at_time
1816
- for transaction in unconfirmed_transactions
1817
- for coin in transaction.removals
1818
- }
1819
- unconfirmed_additions: list[Coin] = [
1820
- coin
1821
- for transaction in unconfirmed_transactions
1822
- for coin in transaction.additions
1823
- if await state_mgr.does_coin_belong_to_wallet(coin, request.wallet_id)
1824
- ]
1825
1667
  valid_spendable_cr: list[CoinRecord] = []
1826
- unconfirmed_removals: list[CoinRecord] = []
1827
- for coin_record in all_coin_records:
1828
- if coin_record.name() in unconfirmed_removal_ids:
1829
- unconfirmed_removals.append(coin_record.to_coin_record(unconfirmed_removal_ids[coin_record.name()]))
1830
-
1831
- cs_config = request.autofill(constants=self.service.wallet_state_manager.constants)
1832
- for coin_record in spendable_coins: # remove all the unconfirmed coins, exclude coins and dust.
1833
- if coin_record.name() in unconfirmed_removal_ids:
1834
- continue
1835
- if coin_record.coin.name() in cs_config.excluded_coin_ids:
1836
- continue
1837
- if (coin_record.coin.amount < cs_config.min_coin_amount) or (
1838
- coin_record.coin.amount > cs_config.max_coin_amount
1839
- ):
1840
- continue
1841
- if coin_record.coin.amount in cs_config.excluded_coin_amounts:
1842
- continue
1843
- c_r = await state_mgr.get_coin_record_by_wallet_record(coin_record)
1844
- assert c_r is not None and c_r.coin == coin_record.coin # this should never happen
1845
- valid_spendable_cr.append(c_r)
1668
+ for coin_record in filtered_spendable_coin_records:
1669
+ valid_spendable_cr.append(await state_mgr.get_coin_record_by_wallet_record(coin_record))
1846
1670
 
1847
1671
  return GetSpendableCoinsResponse(
1848
1672
  confirmed_records=valid_spendable_cr,
1849
- unconfirmed_removals=unconfirmed_removals,
1850
- unconfirmed_additions=unconfirmed_additions,
1673
+ unconfirmed_removals=removal_records,
1674
+ unconfirmed_additions=list(unconfirmed_additions),
1851
1675
  )
1852
1676
 
1853
1677
  @marshal
@@ -1886,7 +1710,7 @@ class WalletRpcApi:
1886
1710
  async def get_current_derivation_index(self, request: Empty) -> GetCurrentDerivationIndexResponse:
1887
1711
  assert self.service.wallet_state_manager is not None
1888
1712
 
1889
- index: Optional[uint32] = await self.service.wallet_state_manager.puzzle_store.get_last_derivation_path()
1713
+ index: uint32 | None = await self.service.wallet_state_manager.puzzle_store.get_last_derivation_path()
1890
1714
 
1891
1715
  return GetCurrentDerivationIndexResponse(index)
1892
1716
 
@@ -1899,7 +1723,7 @@ class WalletRpcApi:
1899
1723
  if synced is False:
1900
1724
  raise ValueError("Wallet needs to be fully synced before extending derivation index")
1901
1725
 
1902
- current: Optional[uint32] = await self.service.wallet_state_manager.puzzle_store.get_last_derivation_path()
1726
+ current: uint32 | None = await self.service.wallet_state_manager.puzzle_store.get_last_derivation_path()
1903
1727
 
1904
1728
  # Additional sanity check that the wallet is synced
1905
1729
  if current is None:
@@ -1929,29 +1753,17 @@ class WalletRpcApi:
1929
1753
 
1930
1754
  @marshal
1931
1755
  async def get_notifications(self, request: GetNotifications) -> GetNotificationsResponse:
1932
- if request.ids is None:
1933
- notifications: list[
1934
- Notification
1935
- ] = await self.service.wallet_state_manager.notification_manager.notification_store.get_all_notifications(
1936
- pagination=(request.start, request.end)
1756
+ return GetNotificationsResponse(
1757
+ await self.service.wallet_state_manager.notification_manager.notification_store.get_notifications(
1758
+ coin_ids=request.ids, pagination=(request.start, request.end)
1937
1759
  )
1938
- else:
1939
- notifications = (
1940
- await self.service.wallet_state_manager.notification_manager.notification_store.get_notifications(
1941
- request.ids
1942
- )
1943
- )
1944
-
1945
- return GetNotificationsResponse(notifications)
1760
+ )
1946
1761
 
1947
1762
  @marshal
1948
1763
  async def delete_notifications(self, request: DeleteNotifications) -> Empty:
1949
- if request.ids is None:
1950
- await self.service.wallet_state_manager.notification_manager.notification_store.delete_all_notifications()
1951
- else:
1952
- await self.service.wallet_state_manager.notification_manager.notification_store.delete_notifications(
1953
- request.ids
1954
- )
1764
+ await self.service.wallet_state_manager.notification_manager.notification_store.delete_notifications(
1765
+ coin_ids=request.ids
1766
+ )
1955
1767
 
1956
1768
  return Empty()
1957
1769
 
@@ -1977,57 +1789,13 @@ class WalletRpcApi:
1977
1789
 
1978
1790
  @marshal
1979
1791
  async def verify_signature(self, request: VerifySignature) -> VerifySignatureResponse:
1980
- """
1981
- Given a public key, message and signature, verify if it is valid.
1982
- :param request:
1983
- :return:
1984
- """
1985
- # Default to BLS_MESSAGE_AUGMENTATION_HEX_INPUT as this RPC was originally designed to verify
1986
- # signatures made by `chia keys sign`, which uses BLS_MESSAGE_AUGMENTATION_HEX_INPUT
1987
- if request.signing_mode is None:
1988
- signing_mode = SigningMode.BLS_MESSAGE_AUGMENTATION_HEX_INPUT
1989
- else:
1990
- try:
1991
- signing_mode = SigningMode(request.signing_mode)
1992
- except ValueError:
1993
- raise ValueError(f"Invalid signing mode: {request.signing_mode!r}")
1994
-
1995
- if signing_mode in {SigningMode.CHIP_0002, SigningMode.CHIP_0002_P2_DELEGATED_CONDITIONS}:
1996
- # CHIP-0002 message signatures are made over the tree hash of:
1997
- # ("Chia Signed Message", message)
1998
- message_to_verify: bytes = Program.to((CHIP_0002_SIGN_MESSAGE_PREFIX, request.message)).get_tree_hash()
1999
- elif signing_mode == SigningMode.BLS_MESSAGE_AUGMENTATION_HEX_INPUT:
2000
- # Message is expected to be a hex string
2001
- message_to_verify = hexstr_to_bytes(request.message)
2002
- elif signing_mode == SigningMode.BLS_MESSAGE_AUGMENTATION_UTF8_INPUT:
2003
- # Message is expected to be a UTF-8 string
2004
- message_to_verify = bytes(request.message, "utf-8")
2005
- else:
2006
- raise ValueError(f"Unsupported signing mode: {request.signing_mode!r}")
2007
-
2008
- # Verify using the BLS message augmentation scheme
2009
- is_valid = AugSchemeMPL.verify(
2010
- request.pubkey,
2011
- message_to_verify,
2012
- request.signature,
1792
+ return verify_signature(
1793
+ signing_mode=request.signing_mode_enum,
1794
+ public_key=request.pubkey,
1795
+ message=request.message,
1796
+ signature=request.signature,
1797
+ address=request.address,
2013
1798
  )
2014
- if request.address is not None:
2015
- # For signatures made by the sign_message_by_address/sign_message_by_id
2016
- # endpoints, the "address" field should contain the p2_address of the NFT/DID
2017
- # that was used to sign the message.
2018
- puzzle_hash: bytes32 = decode_puzzle_hash(request.address)
2019
- expected_puzzle_hash: Optional[bytes32] = None
2020
- if signing_mode == SigningMode.CHIP_0002_P2_DELEGATED_CONDITIONS:
2021
- puzzle = p2_delegated_conditions.puzzle_for_pk(Program.to(request.pubkey))
2022
- expected_puzzle_hash = bytes32(puzzle.get_tree_hash())
2023
- else:
2024
- expected_puzzle_hash = puzzle_hash_for_synthetic_public_key(request.pubkey)
2025
- if puzzle_hash != expected_puzzle_hash:
2026
- return VerifySignatureResponse(isValid=False, error="Public key doesn't match the address")
2027
- if is_valid:
2028
- return VerifySignatureResponse(isValid=is_valid)
2029
- else:
2030
- return VerifySignatureResponse(isValid=False, error="Signature is invalid.")
2031
1799
 
2032
1800
  @marshal
2033
1801
  async def sign_message_by_address(self, request: SignMessageByAddress) -> SignMessageByAddressResponse:
@@ -2036,21 +1804,18 @@ class WalletRpcApi:
2036
1804
  :param request:
2037
1805
  :return:
2038
1806
  """
2039
- puzzle_hash: bytes32 = decode_puzzle_hash(request.address)
2040
- mode: SigningMode = SigningMode.CHIP_0002
2041
- if request.is_hex and request.safe_mode:
2042
- mode = SigningMode.CHIP_0002_HEX_INPUT
2043
- elif not request.is_hex and not request.safe_mode:
2044
- mode = SigningMode.BLS_MESSAGE_AUGMENTATION_UTF8_INPUT
2045
- elif request.is_hex and not request.safe_mode:
2046
- mode = SigningMode.BLS_MESSAGE_AUGMENTATION_HEX_INPUT
2047
- pubkey, signature = await self.service.wallet_state_manager.main_wallet.sign_message(
2048
- request.message, puzzle_hash, mode
1807
+ synthetic_secret_key = self.service.wallet_state_manager.main_wallet.convert_secret_key_to_synthetic(
1808
+ await self.service.wallet_state_manager.get_private_key(decode_puzzle_hash(request.address))
1809
+ )
1810
+ signing_response = sign_message(
1811
+ secret_key=synthetic_secret_key,
1812
+ message=request.message,
1813
+ mode=request.signing_mode_enum,
2049
1814
  )
2050
1815
  return SignMessageByAddressResponse(
2051
- pubkey=pubkey,
2052
- signature=signature,
2053
- signing_mode=mode.value,
1816
+ pubkey=signing_response.pubkey,
1817
+ signature=signing_response.signature,
1818
+ signing_mode=request.signing_mode_enum.value,
2054
1819
  )
2055
1820
 
2056
1821
  @marshal
@@ -2061,53 +1826,67 @@ class WalletRpcApi:
2061
1826
  :return:
2062
1827
  """
2063
1828
  entity_id: bytes32 = decode_puzzle_hash(request.id)
2064
- selected_wallet: Optional[WalletProtocol[Any]] = None
2065
- mode: SigningMode = SigningMode.CHIP_0002
2066
- if request.is_hex and request.safe_mode:
2067
- mode = SigningMode.CHIP_0002_HEX_INPUT
2068
- elif not request.is_hex and not request.safe_mode:
2069
- mode = SigningMode.BLS_MESSAGE_AUGMENTATION_UTF8_INPUT
2070
- elif request.is_hex and not request.safe_mode:
2071
- mode = SigningMode.BLS_MESSAGE_AUGMENTATION_HEX_INPUT
2072
1829
  if is_valid_address(request.id, {AddressType.DID}, self.service.config):
1830
+ did_wallet: DIDWallet | None = None
2073
1831
  for wallet in self.service.wallet_state_manager.wallets.values():
2074
1832
  if wallet.type() == WalletType.DECENTRALIZED_ID.value:
2075
1833
  assert isinstance(wallet, DIDWallet)
2076
1834
  assert wallet.did_info.origin_coin is not None
2077
1835
  if wallet.did_info.origin_coin.name() == entity_id:
2078
- selected_wallet = wallet
1836
+ did_wallet = wallet
2079
1837
  break
2080
- if selected_wallet is None:
1838
+ if did_wallet is None:
2081
1839
  raise ValueError(f"DID for {entity_id.hex()} doesn't exist.")
2082
- assert isinstance(selected_wallet, DIDWallet)
2083
- pubkey, signature = await selected_wallet.sign_message(request.message, mode)
2084
- latest_coin_id = (await selected_wallet.get_coin()).name()
1840
+ synthetic_secret_key = self.service.wallet_state_manager.main_wallet.convert_secret_key_to_synthetic(
1841
+ await self.service.wallet_state_manager.get_private_key(await did_wallet.current_p2_puzzle_hash())
1842
+ )
1843
+ latest_coin_id = (await did_wallet.get_coin()).name()
1844
+ signing_response = sign_message(
1845
+ secret_key=synthetic_secret_key,
1846
+ message=request.message,
1847
+ mode=request.signing_mode_enum,
1848
+ )
1849
+ return SignMessageByIDResponse(
1850
+ pubkey=signing_response.pubkey,
1851
+ signature=signing_response.signature,
1852
+ signing_mode=request.signing_mode_enum.value,
1853
+ latest_coin_id=latest_coin_id,
1854
+ )
2085
1855
  elif is_valid_address(request.id, {AddressType.NFT}, self.service.config):
2086
- target_nft: Optional[NFTCoinInfo] = None
1856
+ nft_wallet: NFTWallet | None = None
1857
+ target_nft: NFTCoinInfo | None = None
2087
1858
  for wallet in self.service.wallet_state_manager.wallets.values():
2088
1859
  if wallet.type() == WalletType.NFT.value:
2089
1860
  assert isinstance(wallet, NFTWallet)
2090
- nft: Optional[NFTCoinInfo] = await wallet.get_nft(entity_id)
1861
+ nft: NFTCoinInfo | None = await wallet.get_nft(entity_id)
2091
1862
  if nft is not None:
2092
- selected_wallet = wallet
1863
+ nft_wallet = wallet
2093
1864
  target_nft = nft
2094
1865
  break
2095
- if selected_wallet is None or target_nft is None:
1866
+ if nft_wallet is None or target_nft is None:
2096
1867
  raise ValueError(f"NFT for {entity_id.hex()} doesn't exist.")
2097
1868
 
2098
- assert isinstance(selected_wallet, NFTWallet)
2099
- pubkey, signature = await selected_wallet.sign_message(request.message, target_nft, mode)
1869
+ assert isinstance(nft_wallet, NFTWallet)
1870
+ synthetic_secret_key = self.service.wallet_state_manager.main_wallet.convert_secret_key_to_synthetic(
1871
+ await self.service.wallet_state_manager.get_private_key(
1872
+ await nft_wallet.current_p2_puzzle_hash(target_nft)
1873
+ )
1874
+ )
2100
1875
  latest_coin_id = target_nft.coin.name()
1876
+ signing_response = sign_message(
1877
+ secret_key=synthetic_secret_key,
1878
+ message=request.message,
1879
+ mode=request.signing_mode_enum,
1880
+ )
1881
+ return SignMessageByIDResponse(
1882
+ pubkey=signing_response.pubkey,
1883
+ signature=signing_response.signature,
1884
+ signing_mode=request.signing_mode_enum.value,
1885
+ latest_coin_id=latest_coin_id,
1886
+ )
2101
1887
  else:
2102
1888
  raise ValueError(f"Unknown ID type, {request.id}")
2103
1889
 
2104
- return SignMessageByIDResponse(
2105
- pubkey=pubkey,
2106
- signature=signature,
2107
- latest_coin_id=latest_coin_id,
2108
- signing_mode=mode.value,
2109
- )
2110
-
2111
1890
  ##########################################################################################
2112
1891
  # CATs and Trading
2113
1892
  ##########################################################################################
@@ -2147,8 +1926,6 @@ class WalletRpcApi:
2147
1926
  extra_conditions: tuple[Condition, ...] = tuple(),
2148
1927
  hold_lock: bool = True,
2149
1928
  ) -> CATSpendResponse:
2150
- if await self.service.wallet_state_manager.synced() is False:
2151
- raise ValueError("Wallet needs to be fully synced.")
2152
1929
  wallet = self.service.wallet_state_manager.get_wallet(id=request.wallet_id, required_type=CATWallet)
2153
1930
 
2154
1931
  amounts: list[uint64] = []
@@ -2168,7 +1945,7 @@ class WalletRpcApi:
2168
1945
  puzzle_hashes.append(decode_puzzle_hash(request.inner_address)) # type: ignore[arg-type]
2169
1946
  if request.memos is not None:
2170
1947
  memos.append([mem.encode("utf-8") for mem in request.memos])
2171
- coins: Optional[set[Coin]] = None
1948
+ coins: set[Coin] | None = None
2172
1949
  if request.coins is not None and len(request.coins) > 0:
2173
1950
  coins = set(request.coins)
2174
1951
 
@@ -2202,12 +1979,12 @@ class WalletRpcApi:
2202
1979
  @marshal
2203
1980
  async def cat_get_asset_id(self, request: CATGetAssetID) -> CATGetAssetIDResponse:
2204
1981
  wallet = self.service.wallet_state_manager.get_wallet(id=request.wallet_id, required_type=CATWallet)
2205
- asset_id: str = wallet.get_asset_id()
2206
- return CATGetAssetIDResponse(asset_id=bytes32.from_hexstr(asset_id), wallet_id=request.wallet_id)
1982
+ asset_id = wallet.get_asset_id()
1983
+ return CATGetAssetIDResponse(asset_id=asset_id, wallet_id=request.wallet_id)
2207
1984
 
2208
1985
  @marshal
2209
1986
  async def cat_asset_id_to_name(self, request: CATAssetIDToName) -> CATAssetIDToNameResponse:
2210
- wallet = await self.service.wallet_state_manager.get_wallet_for_asset_id(request.asset_id.hex())
1987
+ wallet = await self.service.wallet_state_manager.get_wallet_for_asset_id(request.asset_id)
2211
1988
  if wallet is None:
2212
1989
  if request.asset_id.hex() in DEFAULT_CATS:
2213
1990
  return CATAssetIDToNameResponse(wallet_id=None, name=DEFAULT_CATS[request.asset_id.hex()]["name"])
@@ -2217,126 +1994,98 @@ class WalletRpcApi:
2217
1994
  return CATAssetIDToNameResponse(wallet_id=wallet.id(), name=wallet.get_name())
2218
1995
 
2219
1996
  @tx_endpoint(push=False)
1997
+ @marshal
2220
1998
  async def create_offer_for_ids(
2221
1999
  self,
2222
- request: dict[str, Any],
2000
+ request: CreateOfferForIDs,
2223
2001
  action_scope: WalletActionScope,
2224
2002
  extra_conditions: tuple[Condition, ...] = tuple(),
2225
- ) -> EndpointResult:
2003
+ ) -> CreateOfferForIDsResponse:
2226
2004
  if action_scope.config.push:
2227
- raise ValueError("Cannot push an incomplete spend") # pragma: no cover
2228
-
2229
- offer: dict[str, int] = request["offer"]
2230
- fee: uint64 = uint64(request.get("fee", 0))
2231
- validate_only: bool = request.get("validate_only", False)
2232
- driver_dict_str: Optional[dict[str, Any]] = request.get("driver_dict", None)
2233
- marshalled_solver = request.get("solver")
2234
- solver: Optional[Solver]
2235
- if marshalled_solver is None:
2236
- solver = None
2237
- else:
2238
- solver = Solver(info=marshalled_solver)
2005
+ raise ValueError("Cannot push an incomplete spend")
2239
2006
 
2240
2007
  # This driver_dict construction is to maintain backward compatibility where everything is assumed to be a CAT
2241
2008
  driver_dict: dict[bytes32, PuzzleInfo] = {}
2242
- if driver_dict_str is None:
2243
- for key, amount in offer.items():
2244
- if amount > 0:
2245
- try:
2246
- driver_dict[bytes32.from_hexstr(key)] = PuzzleInfo(
2247
- {"type": AssetType.CAT.value, "tail": "0x" + key}
2248
- )
2249
- except ValueError:
2250
- pass
2009
+ if request.driver_dict is None:
2010
+ for key, amount in request.offer_spec.items():
2011
+ if amount > 0 and isinstance(key, bytes32):
2012
+ driver_dict[key] = PuzzleInfo({"type": AssetType.CAT.value, "tail": "0x" + key.hex()})
2251
2013
  else:
2252
- for key, value in driver_dict_str.items():
2253
- driver_dict[bytes32.from_hexstr(key)] = PuzzleInfo(value)
2254
-
2255
- modified_offer: dict[Union[int, bytes32], int] = {}
2256
- for wallet_identifier, change in offer.items():
2257
- try:
2258
- modified_offer[bytes32.from_hexstr(wallet_identifier)] = change
2259
- except ValueError:
2260
- modified_offer[int(wallet_identifier)] = change
2014
+ driver_dict = request.driver_dict
2261
2015
 
2262
2016
  async with self.service.wallet_state_manager.lock:
2263
2017
  result = await self.service.wallet_state_manager.trade_manager.create_offer_for_ids(
2264
- modified_offer,
2018
+ request.offer_spec,
2265
2019
  action_scope,
2266
2020
  driver_dict,
2267
- solver=solver,
2268
- fee=fee,
2269
- validate_only=validate_only,
2021
+ solver=request.solver,
2022
+ fee=request.fee,
2023
+ validate_only=request.validate_only,
2270
2024
  extra_conditions=extra_conditions,
2271
2025
  )
2272
- if result[0]:
2273
- _success, trade_record, _error = result
2274
- return {
2275
- "offer": Offer.from_bytes(trade_record.offer).to_bech32(),
2276
- "trade_record": trade_record.to_json_dict_convenience(),
2277
- "transactions": None, # tx_endpoint wrapper will take care of this
2278
- }
2279
- raise ValueError(result[2])
2280
-
2281
- async def get_offer_summary(self, request: dict[str, Any]) -> EndpointResult:
2282
- offer_hex: str = request["offer"]
2283
-
2284
- offer = Offer.from_bech32(offer_hex)
2285
- offered, requested, infos, valid_times = offer.summary()
2286
-
2287
- if request.get("advanced", False):
2288
- response = {
2289
- "summary": {
2290
- "offered": offered,
2291
- "requested": requested,
2292
- "fees": offer.fees(),
2293
- "infos": infos,
2294
- "additions": [c.name().hex() for c in offer.additions()],
2295
- "removals": [c.name().hex() for c in offer.removals()],
2296
- "valid_times": {
2297
- k: v
2298
- for k, v in valid_times.to_json_dict().items()
2299
- if k
2300
- not in {
2301
- "max_secs_after_created",
2302
- "min_secs_since_created",
2303
- "max_blocks_after_created",
2304
- "min_blocks_since_created",
2305
- }
2306
- },
2307
- },
2308
- "id": offer.name(),
2309
- }
2026
+
2027
+ return CreateOfferForIDsResponse(
2028
+ [],
2029
+ [],
2030
+ offer=Offer.from_bytes(result[1].offer),
2031
+ trade_record=result[1],
2032
+ )
2033
+
2034
+ @marshal
2035
+ async def get_offer_summary(self, request: GetOfferSummary) -> GetOfferSummaryResponse:
2036
+ dl_summary = None
2037
+ if not request.advanced:
2038
+ dl_summary = await self.service.wallet_state_manager.trade_manager.get_dl_offer_summary(
2039
+ request.parsed_offer
2040
+ )
2041
+ if dl_summary is not None:
2042
+ response = GetOfferSummaryResponse(
2043
+ data_layer_summary=dl_summary,
2044
+ id=request.parsed_offer.name(),
2045
+ )
2310
2046
  else:
2311
- response = {
2312
- "summary": await self.service.wallet_state_manager.trade_manager.get_offer_summary(offer),
2313
- "id": offer.name(),
2314
- }
2047
+ offered, requested, infos, valid_times = request.parsed_offer.summary()
2048
+ response = GetOfferSummaryResponse(
2049
+ summary=OfferSummary(
2050
+ offered=offered,
2051
+ requested=requested,
2052
+ fees=uint64(request.parsed_offer.fees()),
2053
+ infos=infos,
2054
+ additions=[c.name() for c in request.parsed_offer.additions()],
2055
+ removals=[c.name() for c in request.parsed_offer.removals()],
2056
+ valid_times=valid_times.only_absolutes(),
2057
+ ),
2058
+ id=request.parsed_offer.name(),
2059
+ )
2315
2060
 
2316
2061
  # This is a bit of a hack in favor of returning some more manageable information about CR-CATs
2317
2062
  # A more general solution surely exists, but I'm not sure what it is right now
2318
- return {
2319
- **response,
2320
- "summary": {
2321
- **response["summary"], # type: ignore[dict-item]
2322
- "infos": {
2063
+ return dataclasses.replace(
2064
+ response,
2065
+ summary=dataclasses.replace(
2066
+ response.summary,
2067
+ infos={
2323
2068
  key: (
2324
- {
2325
- **info,
2326
- "also": {
2327
- **info["also"],
2328
- "flags": ProofsChecker.from_program(
2329
- uncurry_puzzle(Program(assemble(info["also"]["proofs_checker"])))
2330
- ).flags,
2331
- },
2332
- }
2333
- if "also" in info and "proofs_checker" in info["also"]
2069
+ PuzzleInfo(
2070
+ {
2071
+ **info.info,
2072
+ "also": {
2073
+ **info.info["also"],
2074
+ "flags": ProofsChecker.from_program(
2075
+ uncurry_puzzle(Program(assemble(info.info["also"]["proofs_checker"])))
2076
+ ).flags,
2077
+ },
2078
+ }
2079
+ )
2080
+ if "also" in info.info and "proofs_checker" in info.info["also"]
2334
2081
  else info
2335
2082
  )
2336
- for key, info in response["summary"]["infos"].items() # type: ignore[index]
2083
+ for key, info in response.summary.infos.items()
2337
2084
  },
2338
- },
2339
- }
2085
+ )
2086
+ if response.summary is not None
2087
+ else None,
2088
+ )
2340
2089
 
2341
2090
  @marshal
2342
2091
  async def check_offer_validity(self, request: CheckOfferValidity) -> CheckOfferValidityResponse:
@@ -2348,88 +2097,76 @@ class WalletRpcApi:
2348
2097
  )
2349
2098
 
2350
2099
  @tx_endpoint(push=True)
2100
+ @marshal
2351
2101
  async def take_offer(
2352
2102
  self,
2353
- request: dict[str, Any],
2103
+ request: TakeOffer,
2354
2104
  action_scope: WalletActionScope,
2355
2105
  extra_conditions: tuple[Condition, ...] = tuple(),
2356
- ) -> EndpointResult:
2357
- offer_hex: str = request["offer"]
2358
-
2359
- offer = Offer.from_bech32(offer_hex)
2360
- fee: uint64 = uint64(request.get("fee", 0))
2361
- maybe_marshalled_solver: Optional[dict[str, Any]] = request.get("solver")
2362
- solver: Optional[Solver]
2363
- if maybe_marshalled_solver is None:
2364
- solver = None
2365
- else:
2366
- solver = Solver(info=maybe_marshalled_solver)
2367
-
2106
+ ) -> TakeOfferResponse:
2368
2107
  peer = self.service.get_full_node_peer()
2369
2108
  trade_record = await self.service.wallet_state_manager.trade_manager.respond_to_offer(
2370
- offer,
2109
+ request.parsed_offer,
2371
2110
  peer,
2372
2111
  action_scope,
2373
- fee=fee,
2374
- solver=solver,
2112
+ fee=request.fee,
2113
+ solver=request.solver,
2375
2114
  extra_conditions=extra_conditions,
2376
2115
  )
2377
2116
 
2378
2117
  async with action_scope.use() as interface:
2379
2118
  interface.side_effects.signing_responses.append(
2380
- SigningResponse(bytes(offer._bundle.aggregated_signature), trade_record.trade_id)
2119
+ SigningResponse(bytes(request.parsed_offer._bundle.aggregated_signature), trade_record.trade_id)
2381
2120
  )
2382
2121
 
2383
- return {
2384
- "trade_record": trade_record.to_json_dict_convenience(),
2385
- "offer": Offer.from_bytes(trade_record.offer).to_bech32(),
2386
- "transactions": None, # tx_endpoint wrapper will take care of this
2387
- "signing_responses": None, # tx_endpoint wrapper will take care of this
2388
- }
2122
+ return TakeOfferResponse(
2123
+ [], # tx_endpoint will fill in this default value
2124
+ [], # tx_endpoint will fill in this default value
2125
+ Offer.from_bytes(trade_record.offer),
2126
+ trade_record,
2127
+ )
2389
2128
 
2390
- async def get_offer(self, request: dict[str, Any]) -> EndpointResult:
2129
+ @marshal
2130
+ async def get_offer(self, request: GetOffer) -> GetOfferResponse:
2391
2131
  trade_mgr = self.service.wallet_state_manager.trade_manager
2392
2132
 
2393
- trade_id = bytes32.from_hexstr(request["trade_id"])
2394
- file_contents: bool = request.get("file_contents", False)
2395
- trade_record: Optional[TradeRecord] = await trade_mgr.get_trade_by_id(bytes32(trade_id))
2133
+ trade_record: TradeRecord | None = await trade_mgr.get_trade_by_id(request.trade_id)
2396
2134
  if trade_record is None:
2397
- raise ValueError(f"No trade with trade id: {trade_id.hex()}")
2135
+ raise ValueError(f"No trade with trade id: {request.trade_id.hex()}")
2398
2136
 
2399
2137
  offer_to_return: bytes = trade_record.offer if trade_record.taken_offer is None else trade_record.taken_offer
2400
- offer_value: Optional[str] = Offer.from_bytes(offer_to_return).to_bech32() if file_contents else None
2401
- return {"trade_record": trade_record.to_json_dict_convenience(), "offer": offer_value}
2138
+ offer: str | None = Offer.from_bytes(offer_to_return).to_bech32() if request.file_contents else None
2139
+ return GetOfferResponse(
2140
+ offer,
2141
+ trade_record,
2142
+ )
2402
2143
 
2403
- async def get_all_offers(self, request: dict[str, Any]) -> EndpointResult:
2144
+ @marshal
2145
+ async def get_all_offers(self, request: GetAllOffers) -> GetAllOffersResponse:
2404
2146
  trade_mgr = self.service.wallet_state_manager.trade_manager
2405
2147
 
2406
- start: int = request.get("start", 0)
2407
- end: int = request.get("end", 10)
2408
- exclude_my_offers: bool = request.get("exclude_my_offers", False)
2409
- exclude_taken_offers: bool = request.get("exclude_taken_offers", False)
2410
- include_completed: bool = request.get("include_completed", False)
2411
- sort_key: Optional[str] = request.get("sort_key", None)
2412
- reverse: bool = request.get("reverse", False)
2413
- file_contents: bool = request.get("file_contents", False)
2414
-
2415
2148
  all_trades = await trade_mgr.trade_store.get_trades_between(
2416
- start,
2417
- end,
2418
- sort_key=sort_key,
2419
- reverse=reverse,
2420
- exclude_my_offers=exclude_my_offers,
2421
- exclude_taken_offers=exclude_taken_offers,
2422
- include_completed=include_completed,
2149
+ request.start,
2150
+ request.end,
2151
+ sort_key=request.sort_key,
2152
+ reverse=request.reverse,
2153
+ exclude_my_offers=request.exclude_my_offers,
2154
+ exclude_taken_offers=request.exclude_taken_offers,
2155
+ include_completed=request.include_completed,
2423
2156
  )
2424
2157
  result = []
2425
- offer_values: Optional[list[str]] = [] if file_contents else None
2158
+ offer_values: list[str] | None = [] if request.file_contents else None
2426
2159
  for trade in all_trades:
2427
- result.append(trade.to_json_dict_convenience())
2428
- if file_contents and offer_values is not None:
2160
+ result.append(trade)
2161
+ if request.file_contents:
2429
2162
  offer_to_return: bytes = trade.offer if trade.taken_offer is None else trade.taken_offer
2430
- offer_values.append(Offer.from_bytes(offer_to_return).to_bech32())
2163
+ # semantics guarantee this to be not None
2164
+ offer_values.append(Offer.from_bytes(offer_to_return).to_bech32()) # type: ignore[union-attr]
2431
2165
 
2432
- return {"trade_records": result, "offers": offer_values}
2166
+ return GetAllOffersResponse(
2167
+ trade_records=result,
2168
+ offers=offer_values,
2169
+ )
2433
2170
 
2434
2171
  @marshal
2435
2172
  async def get_offers_count(self, request: Empty) -> GetOffersCountResponse:
@@ -2442,88 +2179,67 @@ class WalletRpcApi:
2442
2179
  )
2443
2180
 
2444
2181
  @tx_endpoint(push=True)
2182
+ @marshal
2445
2183
  async def cancel_offer(
2446
2184
  self,
2447
- request: dict[str, Any],
2185
+ request: CancelOffer,
2448
2186
  action_scope: WalletActionScope,
2449
2187
  extra_conditions: tuple[Condition, ...] = tuple(),
2450
- ) -> EndpointResult:
2188
+ ) -> CancelOfferResponse:
2451
2189
  wsm = self.service.wallet_state_manager
2452
- secure = request["secure"]
2453
- trade_id = bytes32.from_hexstr(request["trade_id"])
2454
- fee: uint64 = uint64(request.get("fee", 0))
2455
2190
  async with self.service.wallet_state_manager.lock:
2456
2191
  await wsm.trade_manager.cancel_pending_offers(
2457
- [trade_id], action_scope, fee=fee, secure=secure, extra_conditions=extra_conditions
2192
+ [request.trade_id],
2193
+ action_scope,
2194
+ fee=request.fee,
2195
+ secure=request.secure,
2196
+ extra_conditions=extra_conditions,
2458
2197
  )
2459
2198
 
2460
- return {"transactions": None} # tx_endpoint wrapper will take care of this
2199
+ return CancelOfferResponse([], []) # tx_endpoint will fill in default values here
2461
2200
 
2462
2201
  @tx_endpoint(push=True, merge_spends=False)
2202
+ @marshal
2463
2203
  async def cancel_offers(
2464
2204
  self,
2465
- request: dict[str, Any],
2205
+ request: CancelOffers,
2466
2206
  action_scope: WalletActionScope,
2467
2207
  extra_conditions: tuple[Condition, ...] = tuple(),
2468
- ) -> EndpointResult:
2469
- secure = request["secure"]
2470
- batch_fee: uint64 = uint64(request.get("batch_fee", 0))
2471
- batch_size = request.get("batch_size", 5)
2472
- cancel_all = request.get("cancel_all", False)
2473
- if cancel_all:
2474
- asset_id = None
2475
- else:
2476
- asset_id = request.get("asset_id", "xch")
2477
-
2478
- start: int = 0
2479
- end: int = start + batch_size
2208
+ ) -> CancelOffersResponse:
2480
2209
  trade_mgr = self.service.wallet_state_manager.trade_manager
2481
- log.info(f"Start cancelling offers for {'asset_id: ' + asset_id if asset_id is not None else 'all'} ...")
2210
+ log.info(f"Start cancelling offers for {'all' if request.cancel_all else 'asset_id: ' + request.asset_id} ...")
2482
2211
  # Traverse offers page by page
2483
- key = None
2484
- if asset_id is not None and asset_id != "xch":
2485
- key = bytes32.from_hexstr(asset_id)
2486
- while True:
2487
- records: dict[bytes32, TradeRecord] = {}
2488
- trades = await trade_mgr.trade_store.get_trades_between(
2489
- start,
2490
- end,
2491
- reverse=True,
2492
- exclude_my_offers=False,
2493
- exclude_taken_offers=True,
2494
- include_completed=False,
2495
- )
2496
- for trade in trades:
2497
- if cancel_all:
2498
- records[trade.trade_id] = trade
2499
- continue
2500
- if trade.offer and trade.offer != b"":
2501
- offer = Offer.from_bytes(trade.offer)
2502
- if key in offer.arbitrage():
2503
- records[trade.trade_id] = trade
2504
- continue
2212
+ for start in count(0, request.batch_size):
2213
+ records = {
2214
+ record.trade_id: record
2215
+ for record in await trade_mgr.trade_store.get_trades_between(
2216
+ start,
2217
+ start + request.batch_size,
2218
+ reverse=True,
2219
+ exclude_my_offers=False,
2220
+ exclude_taken_offers=True,
2221
+ include_completed=False,
2222
+ )
2223
+ if request.cancel_all
2224
+ or (record.offer != b"" and request.query_key in Offer.from_bytes(record.offer).arbitrage())
2225
+ }
2505
2226
 
2506
- if len(records) == 0:
2227
+ if records == {}:
2507
2228
  break
2508
2229
 
2509
2230
  async with self.service.wallet_state_manager.lock:
2510
2231
  await trade_mgr.cancel_pending_offers(
2511
2232
  list(records.keys()),
2512
2233
  action_scope,
2513
- batch_fee,
2514
- secure,
2234
+ request.batch_fee,
2235
+ request.secure,
2515
2236
  records,
2516
2237
  extra_conditions=extra_conditions,
2517
2238
  )
2518
2239
 
2519
- log.info(f"Cancelled offers {start} to {end} ...")
2520
- # If fewer records were returned than requested, we're done
2521
- if len(trades) < batch_size:
2522
- break
2523
- start = end
2524
- end += batch_size
2240
+ log.info(f"Created offer cancellations for {start} to {start + request.batch_size} ...")
2525
2241
 
2526
- return {"transactions": None} # tx_endpoint wrapper will take care of this
2242
+ return CancelOffersResponse([], []) # tx_endpoint wrapper will take care of this
2527
2243
 
2528
2244
  ##########################################################################################
2529
2245
  # Distributed Identities
@@ -2621,11 +2337,11 @@ class WalletRpcApi:
2621
2337
  if curried_args is None:
2622
2338
  raise ValueError("The coin is not a DID.")
2623
2339
  p2_puzzle, recovery_list_hash, num_verification, singleton_struct, metadata = curried_args
2624
- num_verification_int: Optional[uint16] = uint16(num_verification.as_int())
2340
+ num_verification_int: uint16 | None = uint16(num_verification.as_int())
2625
2341
  assert num_verification_int is not None
2626
2342
  did_data: DIDCoinData = DIDCoinData(
2627
2343
  p2_puzzle,
2628
- bytes32(recovery_list_hash.as_atom()) if recovery_list_hash != Program.to(None) else None,
2344
+ bytes32(recovery_list_hash.as_atom()) if recovery_list_hash != Program.NIL else None,
2629
2345
  num_verification_int,
2630
2346
  singleton_struct,
2631
2347
  metadata,
@@ -2634,7 +2350,7 @@ class WalletRpcApi:
2634
2350
  )
2635
2351
  hinted_coins, _ = compute_spend_hints_and_additions(coin_spend)
2636
2352
  # Hint is required, if it doesn't have any hint then it should be invalid
2637
- hint: Optional[bytes32] = None
2353
+ hint: bytes32 | None = None
2638
2354
  for hinted_coin in hinted_coins.values():
2639
2355
  if hinted_coin.coin.amount % 2 == 1 and hinted_coin.hint is not None:
2640
2356
  hint = hinted_coin.hint
@@ -2667,7 +2383,7 @@ class WalletRpcApi:
2667
2383
  our_inner_puzzle, NIL_TREEHASH, uint64(0), singleton_struct, metadata
2668
2384
  )
2669
2385
  # Check if we have the DID wallet
2670
- did_wallet: Optional[DIDWallet] = None
2386
+ did_wallet: DIDWallet | None = None
2671
2387
  for wallet in self.service.wallet_state_manager.wallets.values():
2672
2388
  if isinstance(wallet, DIDWallet):
2673
2389
  assert wallet.did_info.origin_coin is not None
@@ -2881,8 +2597,6 @@ class WalletRpcApi:
2881
2597
  action_scope: WalletActionScope,
2882
2598
  extra_conditions: tuple[Condition, ...] = tuple(),
2883
2599
  ) -> DIDTransferDIDResponse:
2884
- if await self.service.wallet_state_manager.synced() is False:
2885
- raise ValueError("Wallet needs to be fully synced.")
2886
2600
  did_wallet = self.service.wallet_state_manager.get_wallet(id=request.wallet_id, required_type=DIDWallet)
2887
2601
  puzzle_hash: bytes32 = decode_puzzle_hash(request.inner_address)
2888
2602
  async with self.service.wallet_state_manager.lock:
@@ -2935,7 +2649,7 @@ class WalletRpcApi:
2935
2649
  metadata = Program.to(metadata_list)
2936
2650
  if request.did_id is not None:
2937
2651
  if request.did_id == "":
2938
- did_id: Optional[bytes] = b""
2652
+ did_id: bytes | None = b""
2939
2653
  else:
2940
2654
  did_id = decode_puzzle_hash(request.did_id)
2941
2655
  else:
@@ -2964,12 +2678,9 @@ class WalletRpcApi:
2964
2678
  async def nft_count_nfts(self, request: NFTCountNFTs) -> NFTCountNFTsResponse:
2965
2679
  count = 0
2966
2680
  if request.wallet_id is not None:
2967
- try:
2968
- nft_wallet = self.service.wallet_state_manager.get_wallet(id=request.wallet_id, required_type=NFTWallet)
2969
- except KeyError:
2970
- # wallet not found
2971
- raise ValueError(f"Wallet {request.wallet_id} not found.")
2972
- count = await nft_wallet.get_nft_count()
2681
+ count = await self.service.wallet_state_manager.get_wallet(
2682
+ id=request.wallet_id, required_type=NFTWallet
2683
+ ).get_nft_count()
2973
2684
  else:
2974
2685
  count = await self.service.wallet_state_manager.nft_store.count()
2975
2686
  return NFTCountNFTsResponse(request.wallet_id, uint64(count))
@@ -3162,7 +2873,7 @@ class WalletRpcApi:
3162
2873
 
3163
2874
  @marshal
3164
2875
  async def nft_get_by_did(self, request: NFTGetByDID) -> NFTGetByDIDResponse:
3165
- did_id: Optional[bytes32] = None
2876
+ did_id: bytes32 | None = None
3166
2877
  if request.did_id is not None:
3167
2878
  did_id = decode_puzzle_hash(request.did_id)
3168
2879
  for wallet in self.service.wallet_state_manager.wallets.values():
@@ -3173,7 +2884,7 @@ class WalletRpcApi:
3173
2884
  @marshal
3174
2885
  async def nft_get_wallet_did(self, request: NFTGetWalletDID) -> NFTGetWalletDIDResponse:
3175
2886
  nft_wallet = self.service.wallet_state_manager.get_wallet(id=request.wallet_id, required_type=NFTWallet)
3176
- did_bytes: Optional[bytes32] = nft_wallet.get_did()
2887
+ did_bytes: bytes32 | None = nft_wallet.get_did()
3177
2888
  did_id = ""
3178
2889
  if did_bytes is not None:
3179
2890
  did_id = encode_puzzle_hash(did_bytes, AddressType.DID.hrp(self.service.config))
@@ -3193,7 +2904,7 @@ class WalletRpcApi:
3193
2904
  did_nft_wallets: list[NFTWalletWithDID] = []
3194
2905
  for wallet in all_wallets:
3195
2906
  if isinstance(wallet, NFTWallet):
3196
- nft_wallet_did: Optional[bytes32] = wallet.get_did()
2907
+ nft_wallet_did: bytes32 | None = wallet.get_did()
3197
2908
  if nft_wallet_did is not None:
3198
2909
  did_wallet_id: uint32 = did_wallets_by_did_id.get(nft_wallet_did, uint32(0))
3199
2910
  if did_wallet_id == 0:
@@ -3262,7 +2973,7 @@ class WalletRpcApi:
3262
2973
  # Check if the metadata is updated
3263
2974
  full_puzzle: Program = Program.from_bytes(bytes(coin_spend.puzzle_reveal))
3264
2975
 
3265
- uncurried_nft: Optional[UncurriedNFT] = UncurriedNFT.uncurry(*full_puzzle.uncurry())
2976
+ uncurried_nft: UncurriedNFT | None = UncurriedNFT.uncurry(*full_puzzle.uncurry())
3266
2977
  if uncurried_nft is None:
3267
2978
  raise ValueError("The coin is not a NFT.")
3268
2979
  metadata, p2_puzzle_hash = get_metadata_and_phs(uncurried_nft, coin_spend.solution)
@@ -3352,8 +3063,6 @@ class WalletRpcApi:
3352
3063
  ) -> NFTMintBulkResponse:
3353
3064
  if action_scope.config.push:
3354
3065
  raise ValueError("Automatic pushing of nft minting transactions not yet available") # pragma: no cover
3355
- if await self.service.wallet_state_manager.synced() is False:
3356
- raise ValueError("Wallet needs to be fully synced.")
3357
3066
  nft_wallet = self.service.wallet_state_manager.get_wallet(id=request.wallet_id, required_type=NFTWallet)
3358
3067
  if request.royalty_address in {None, ""}:
3359
3068
  royalty_puzhash = await action_scope.get_puzzle_hash(self.service.wallet_state_manager)
@@ -3383,7 +3092,7 @@ class WalletRpcApi:
3383
3092
  metadata_list.append(metadata_dict)
3384
3093
  target_list = [decode_puzzle_hash(target) for target in request.target_list]
3385
3094
  if request.xch_change_target is not None:
3386
- if request.xch_change_target.startswith("xch"):
3095
+ if request.xch_change_target.startswith(AddressType.XCH.hrp(self.service.config)):
3387
3096
  xch_change_ph = decode_puzzle_hash(request.xch_change_target)
3388
3097
  else:
3389
3098
  xch_change_ph = bytes32.from_hexstr(request.xch_change_target)
@@ -3478,7 +3187,8 @@ class WalletRpcApi:
3478
3187
  "total_count": result.total_count,
3479
3188
  }
3480
3189
 
3481
- async def get_farmed_amount(self, request: dict[str, Any]) -> EndpointResult:
3190
+ @marshal
3191
+ async def get_farmed_amount(self, request: GetFarmedAmount) -> GetFarmedAmountResponse:
3482
3192
  tx_records: list[TransactionRecord] = await self.service.wallet_state_manager.tx_store.get_farming_rewards()
3483
3193
  amount = 0
3484
3194
  pool_reward_amount = 0
@@ -3487,14 +3197,12 @@ class WalletRpcApi:
3487
3197
  blocks_won = 0
3488
3198
  last_height_farmed = uint32(0)
3489
3199
 
3490
- include_pool_rewards = request.get("include_pool_rewards", False)
3491
-
3492
3200
  for record in tx_records:
3493
3201
  if record.wallet_id not in self.service.wallet_state_manager.wallets:
3494
3202
  continue
3495
3203
  if record.type == TransactionType.COINBASE_REWARD.value:
3496
3204
  if (
3497
- not include_pool_rewards
3205
+ not request.include_pool_rewards
3498
3206
  and self.service.wallet_state_manager.wallets[record.wallet_id].type() == WalletType.POOLING_WALLET
3499
3207
  ):
3500
3208
  # Don't add pool rewards for pool wallets unless explicitly requested
@@ -3514,103 +3222,74 @@ class WalletRpcApi:
3514
3222
  last_height_farmed = max(last_height_farmed, height)
3515
3223
  amount += record.amount
3516
3224
 
3517
- last_time_farmed = uint64(
3225
+ last_time_farmed = (
3518
3226
  await self.service.get_timestamp_for_height(last_height_farmed) if last_height_farmed > 0 else 0
3519
3227
  )
3520
3228
  assert amount == pool_reward_amount + farmer_reward_amount + fee_amount
3521
- return {
3522
- "farmed_amount": amount,
3523
- "pool_reward_amount": pool_reward_amount,
3524
- "farmer_reward_amount": farmer_reward_amount,
3525
- "fee_amount": fee_amount,
3526
- "last_height_farmed": last_height_farmed,
3527
- "last_time_farmed": last_time_farmed,
3528
- "blocks_won": blocks_won,
3529
- }
3229
+ return GetFarmedAmountResponse(
3230
+ farmed_amount=uint64(amount),
3231
+ pool_reward_amount=uint64(pool_reward_amount),
3232
+ farmer_reward_amount=uint64(farmer_reward_amount),
3233
+ fee_amount=uint64(fee_amount),
3234
+ last_height_farmed=uint32(last_height_farmed),
3235
+ last_time_farmed=uint64(last_time_farmed),
3236
+ blocks_won=uint32(blocks_won),
3237
+ )
3530
3238
 
3531
3239
  @tx_endpoint(push=False)
3240
+ @marshal
3532
3241
  async def create_signed_transaction(
3533
3242
  self,
3534
- request: dict[str, Any],
3243
+ request: CreateSignedTransaction,
3535
3244
  action_scope: WalletActionScope,
3536
3245
  extra_conditions: tuple[Condition, ...] = tuple(),
3537
3246
  hold_lock: bool = True,
3538
- ) -> EndpointResult:
3539
- if "wallet_id" in request:
3540
- wallet_id = uint32(request["wallet_id"])
3541
- wallet = self.service.wallet_state_manager.wallets[wallet_id]
3247
+ ) -> CreateSignedTransactionsResponse:
3248
+ if request.wallet_id is not None:
3249
+ wallet = self.service.wallet_state_manager.wallets[request.wallet_id]
3542
3250
  else:
3543
3251
  wallet = self.service.wallet_state_manager.main_wallet
3544
3252
 
3545
- assert isinstance(wallet, (Wallet, CATWallet, CRCATWallet)), (
3253
+ assert isinstance(wallet, (Wallet, CATWallet, CRCATWallet, RCATWallet)), (
3546
3254
  "create_signed_transaction only works for standard and CAT wallets"
3547
3255
  )
3548
3256
 
3549
- if "additions" not in request or len(request["additions"]) < 1:
3257
+ if len(request.additions) < 1:
3550
3258
  raise ValueError("Specify additions list")
3551
3259
 
3552
- additions: list[dict[str, Any]] = request["additions"]
3553
- amount_0: uint64 = uint64(additions[0]["amount"])
3260
+ amount_0: uint64 = uint64(request.additions[0].amount)
3554
3261
  assert amount_0 <= self.service.constants.MAX_COIN_AMOUNT
3555
- puzzle_hash_0 = bytes32.from_hexstr(additions[0]["puzzle_hash"])
3262
+ puzzle_hash_0 = request.additions[0].puzzle_hash
3556
3263
  if len(puzzle_hash_0) != 32:
3557
3264
  raise ValueError(f"Address must be 32 bytes. {puzzle_hash_0.hex()}")
3558
3265
 
3559
- memos_0 = [] if "memos" not in additions[0] else [mem.encode("utf-8") for mem in additions[0]["memos"]]
3266
+ memos_0 = (
3267
+ [] if request.additions[0].memos is None else [mem.encode("utf-8") for mem in request.additions[0].memos]
3268
+ )
3560
3269
 
3561
3270
  additional_outputs: list[CreateCoin] = []
3562
- for addition in additions[1:]:
3563
- receiver_ph = bytes32.from_hexstr(addition["puzzle_hash"])
3564
- if len(receiver_ph) != 32:
3565
- raise ValueError(f"Address must be 32 bytes. {receiver_ph.hex()}")
3566
- amount = uint64(addition["amount"])
3567
- if amount > self.service.constants.MAX_COIN_AMOUNT:
3271
+ for addition in request.additions[1:]:
3272
+ if addition.amount > self.service.constants.MAX_COIN_AMOUNT:
3568
3273
  raise ValueError(f"Coin amount cannot exceed {self.service.constants.MAX_COIN_AMOUNT}")
3569
- memos = [] if "memos" not in addition else [mem.encode("utf-8") for mem in addition["memos"]]
3570
- additional_outputs.append(CreateCoin(receiver_ph, amount, memos))
3571
-
3572
- fee: uint64 = uint64(request.get("fee", 0))
3274
+ memos = [] if addition.memos is None else [mem.encode("utf-8") for mem in addition.memos]
3275
+ additional_outputs.append(CreateCoin(addition.puzzle_hash, addition.amount, memos))
3573
3276
 
3574
- coins = None
3575
- if "coins" in request and len(request["coins"]) > 0:
3576
- coins = {Coin.from_json_dict(coin_json) for coin_json in request["coins"]}
3577
-
3578
- async def _generate_signed_transaction() -> EndpointResult:
3277
+ async def _generate_signed_transaction() -> CreateSignedTransactionsResponse:
3579
3278
  await wallet.generate_signed_transaction(
3580
3279
  [amount_0] + [output.amount for output in additional_outputs],
3581
3280
  [bytes32(puzzle_hash_0)] + [output.puzzle_hash for output in additional_outputs],
3582
3281
  action_scope,
3583
- fee,
3584
- coins=coins,
3282
+ request.fee,
3283
+ coins=request.coin_set,
3585
3284
  memos=[memos_0] + [output.memos if output.memos is not None else [] for output in additional_outputs],
3586
3285
  extra_conditions=(
3587
3286
  *extra_conditions,
3588
- *(
3589
- AssertCoinAnnouncement(
3590
- asserted_id=bytes32.from_hexstr(ca["coin_id"]),
3591
- asserted_msg=(
3592
- hexstr_to_bytes(ca["message"])
3593
- if request.get("morph_bytes") is None
3594
- else std_hash(hexstr_to_bytes(ca["morph_bytes"]) + hexstr_to_bytes(ca["message"]))
3595
- ),
3596
- )
3597
- for ca in request.get("coin_announcements", [])
3598
- ),
3599
- *(
3600
- AssertPuzzleAnnouncement(
3601
- asserted_ph=bytes32.from_hexstr(pa["puzzle_hash"]),
3602
- asserted_msg=(
3603
- hexstr_to_bytes(pa["message"])
3604
- if request.get("morph_bytes") is None
3605
- else std_hash(hexstr_to_bytes(pa["morph_bytes"]) + hexstr_to_bytes(pa["message"]))
3606
- ),
3607
- )
3608
- for pa in request.get("puzzle_announcements", [])
3609
- ),
3287
+ *request.asserted_coin_announcements,
3288
+ *request.asserted_puzzle_announcements,
3610
3289
  ),
3611
3290
  )
3612
- # tx_endpoint wrapper will take care of this
3613
- return {"signed_txs": None, "signed_tx": None, "transactions": None}
3291
+ # tx_endpoint wrapper will take care of these default values
3292
+ return CreateSignedTransactionsResponse([], [], [], REPLACEABLE_TRANSACTION_RECORD)
3614
3293
 
3615
3294
  if hold_lock:
3616
3295
  async with self.service.wallet_state_manager.lock:
@@ -3631,9 +3310,6 @@ class WalletRpcApi:
3631
3310
  ) -> PWJoinPoolResponse:
3632
3311
  wallet = self.service.wallet_state_manager.get_wallet(id=request.wallet_id, required_type=PoolWallet)
3633
3312
 
3634
- if await self.service.wallet_state_manager.synced() is False:
3635
- raise ValueError("Wallet needs to be fully synced.")
3636
-
3637
3313
  pool_wallet_info: PoolWalletInfo = await wallet.get_current_state()
3638
3314
  if (
3639
3315
  pool_wallet_info.current.state == FARMING_TO_POOL.value
@@ -3673,9 +3349,6 @@ class WalletRpcApi:
3673
3349
  # Then we transition to FARMING_TO_POOL or SELF_POOLING
3674
3350
  wallet = self.service.wallet_state_manager.get_wallet(id=request.wallet_id, required_type=PoolWallet)
3675
3351
 
3676
- if await self.service.wallet_state_manager.synced() is False:
3677
- raise ValueError("Wallet needs to be fully synced.")
3678
-
3679
3352
  total_fee = await wallet.self_pool(request.fee, action_scope)
3680
3353
  # tx_endpoint will take care of filling in these default values
3681
3354
  return PWSelfPoolResponse(
@@ -3695,8 +3368,6 @@ class WalletRpcApi:
3695
3368
  extra_conditions: tuple[Condition, ...] = tuple(),
3696
3369
  ) -> PWAbsorbRewardsResponse:
3697
3370
  """Perform a sweep of the p2_singleton rewards controlled by the pool wallet singleton"""
3698
- if await self.service.wallet_state_manager.synced() is False:
3699
- raise ValueError("Wallet needs to be fully synced before collecting rewards")
3700
3371
  wallet = self.service.wallet_state_manager.get_wallet(id=request.wallet_id, required_type=PoolWallet)
3701
3372
 
3702
3373
  assert isinstance(wallet, PoolWallet)
@@ -4003,7 +3674,7 @@ class WalletRpcApi:
4003
3674
  'transactions'
4004
3675
  """
4005
3676
  did_id = decode_puzzle_hash(request.did_id)
4006
- puzhash: Optional[bytes32] = None
3677
+ puzhash: bytes32 | None = None
4007
3678
  if request.target_address is not None:
4008
3679
  puzhash = decode_puzzle_hash(request.target_address)
4009
3680
 
@@ -4105,7 +3776,7 @@ class WalletRpcApi:
4105
3776
 
4106
3777
  vc_wallet: VCWallet = await self.service.wallet_state_manager.get_or_create_vc_wallet()
4107
3778
 
4108
- vc_proofs: Optional[VCProofs] = await vc_wallet.store.get_proofs_for_root(request.root)
3779
+ vc_proofs: VCProofs | None = await vc_wallet.store.get_proofs_for_root(request.root)
4109
3780
  if vc_proofs is None:
4110
3781
  raise ValueError("no proofs found for specified root") # pragma: no cover
4111
3782
  return VCGetProofsForRootResponse.from_vc_proofs(vc_proofs)
@@ -4137,12 +3808,13 @@ class WalletRpcApi:
4137
3808
  return VCRevokeResponse([], []) # tx_endpoint takes care of filling this out
4138
3809
 
4139
3810
  @tx_endpoint(push=True)
3811
+ @marshal
4140
3812
  async def crcat_approve_pending(
4141
3813
  self,
4142
- request: dict[str, Any],
3814
+ request: CRCATApprovePending,
4143
3815
  action_scope: WalletActionScope,
4144
3816
  extra_conditions: tuple[Condition, ...] = tuple(),
4145
- ) -> EndpointResult:
3817
+ ) -> CRCATApprovePendingResponse:
4146
3818
  """
4147
3819
  Moving any "pending approval" CR-CATs into the spendable balance of the wallet
4148
3820
  :param request: Required 'wallet_id'. Optional 'min_amount_to_claim' (default: full balance).
@@ -4151,27 +3823,18 @@ class WalletRpcApi:
4151
3823
  (CRCAT TX + fee TX)
4152
3824
  """
4153
3825
 
4154
- @streamable
4155
- @dataclasses.dataclass(frozen=True)
4156
- class CRCATApprovePending(Streamable):
4157
- wallet_id: uint32
4158
- min_amount_to_claim: uint64
4159
- fee: uint64 = uint64(0)
4160
-
4161
- parsed_request = CRCATApprovePending.from_json_dict(request)
4162
- cr_cat_wallet = self.service.wallet_state_manager.wallets[parsed_request.wallet_id]
3826
+ cr_cat_wallet = self.service.wallet_state_manager.wallets[request.wallet_id]
4163
3827
  assert isinstance(cr_cat_wallet, CRCATWallet)
4164
3828
 
4165
3829
  await cr_cat_wallet.claim_pending_approval_balance(
4166
- parsed_request.min_amount_to_claim,
3830
+ request.min_amount_to_claim,
4167
3831
  action_scope,
4168
- fee=parsed_request.fee,
3832
+ fee=request.fee,
4169
3833
  extra_conditions=extra_conditions,
4170
3834
  )
4171
3835
 
4172
- return {
4173
- "transactions": None, # tx_endpoint wrapper will take care of this
4174
- }
3836
+ # tx_endpoint will take care of default values here
3837
+ return CRCATApprovePendingResponse([], [])
4175
3838
 
4176
3839
  @marshal
4177
3840
  async def gather_signing_info(