From 810f0bfdaaf62c83ae85ffaa45099d2a6c2c3050 Mon Sep 17 00:00:00 2001 From: Matt Hauff Date: Thu, 12 Sep 2024 11:07:55 -0700 Subject: [PATCH] [CHIA-1204] Add missing client endpoints for corresponding Wallet RPC endpoints (#18578) * Add client endpoints for `get/set_auto_claim` * Add client endpoint for `verify_signature` * Add client endpoint for `get_transaction_memo` * Add client endpoint for `get_offers_count` * Add client endpoint for `get_cat_list` * Add client endpoint for `did_get_pubkey` * Port `test_creation_from_backup_file` to `WalletTestFramework` * Add client endpoint for `did_get_information_needed_for_recovery` * Add client endpoint for `did_get_current_coin_info` * Add client endpoint for `nft_get_by_did` * Add client endpoint for `nft_set_nft_status` * Add client endpoint for `nft_get_wallets_with_dids` * Add client endpoint for `nft_set_did_bulk` * Add client endpoint for `nft_transfer_bulk` * Make sure we have a proper tx_id in test_wallet_make_transaction_with_memo. * Reuse request's address after checking it. * Minor readability improvement. --------- Co-authored-by: Amine Khaldi --- .../wallet/cat_wallet/test_cat_wallet.py | 19 +- chia/_tests/wallet/did_wallet/test_did.py | 341 ++++++++++++++---- .../wallet/nft_wallet/test_nft_wallet.py | 77 ++-- chia/_tests/wallet/rpc/test_wallet_rpc.py | 97 +++-- chia/_tests/wallet/test_wallet.py | 23 +- chia/rpc/wallet_request_types.py | 197 ++++++++++ chia/rpc/wallet_rpc_api.py | 7 +- chia/rpc/wallet_rpc_client.py | 74 +++- 8 files changed, 662 insertions(+), 173 deletions(-) diff --git a/chia/_tests/wallet/cat_wallet/test_cat_wallet.py b/chia/_tests/wallet/cat_wallet/test_cat_wallet.py index d067e9bdc154..041b6fc24539 100644 --- a/chia/_tests/wallet/cat_wallet/test_cat_wallet.py +++ b/chia/_tests/wallet/cat_wallet/test_cat_wallet.py @@ -9,6 +9,7 @@ from chia._tests.environments.wallet import WalletEnvironment, WalletStateTransition, WalletTestFramework from chia._tests.util.time_out_assert import time_out_assert, time_out_assert_not_none from chia.protocols.wallet_protocol import CoinState +from chia.rpc.wallet_request_types import GetTransactionMemo from chia.simulator.simulator_protocol import ReorgProtocol from chia.types.blockchain_format.coin import Coin, coin_as_list from chia.types.blockchain_format.program import Program @@ -242,8 +243,6 @@ async def test_cat_spend(wallet_environments: WalletTestFramework) -> None: env_2: WalletEnvironment = wallet_environments.environments[1] wallet_node = env_1.node wallet_node_2 = env_2.node - api_0 = env_1.rpc_api - api_1 = env_2.rpc_api wallet = env_1.xch_wallet wallet2 = env_2.xch_wallet full_node_api = wallet_environments.full_node @@ -327,11 +326,11 @@ async def test_cat_spend(wallet_environments: WalletTestFramework) -> None: if tx_record.wallet_id == cat_wallet.id(): assert tx_record.to_puzzle_hash == cat_2_hash if tx_record.spend_bundle is not None: - tx_id = tx_record.name.hex() + tx_id = tx_record.name assert tx_id is not None - memos = await api_0.get_transaction_memo({"transaction_id": tx_id}) - assert len(memos[tx_id]) == 2 # One for tx, one for change - assert list(memos[tx_id].values())[0][0] == cat_2_hash.hex() + memos = await env_1.rpc_client.get_transaction_memo(GetTransactionMemo(transaction_id=tx_id)) + assert len(memos.coins_with_memos) == 2 + assert memos.coins_with_memos[1].memos[0] == cat_2_hash await wallet_environments.process_pending_states( [ @@ -406,10 +405,10 @@ async def test_cat_spend(wallet_environments: WalletTestFramework) -> None: coins = await cat_wallet_2.select_coins(uint64(60), action_scope) assert len(coins) == 1 coin = coins.pop() - tx_id = coin.name().hex() - memos = await api_1.get_transaction_memo(dict(transaction_id=tx_id)) - assert len(memos[tx_id]) == 2 - assert list(memos[tx_id].values())[0][0] == cat_2_hash.hex() + tx_id = coin.name() + memos = await env_2.rpc_client.get_transaction_memo(GetTransactionMemo(transaction_id=tx_id)) + assert len(memos.coins_with_memos) == 2 + assert memos.coins_with_memos[1].memos[0] == cat_2_hash cat_hash = await cat_wallet.get_new_inner_hash() async with cat_wallet_2.wallet_state_manager.new_action_scope(DEFAULT_TX_CONFIG, push=True) as action_scope: await cat_wallet_2.generate_signed_transaction([uint64(15)], [cat_hash], action_scope) diff --git a/chia/_tests/wallet/did_wallet/test_did.py b/chia/_tests/wallet/did_wallet/test_did.py index 93d3e0eee7e0..1546dae650f9 100644 --- a/chia/_tests/wallet/did_wallet/test_did.py +++ b/chia/_tests/wallet/did_wallet/test_did.py @@ -10,6 +10,7 @@ from chia._tests.environments.wallet import WalletStateTransition, WalletTestFramework from chia._tests.util.setup_nodes import OldSimulatorsAndWallets from chia._tests.util.time_out_assert import time_out_assert +from chia.rpc.wallet_request_types import DIDGetCurrentCoinInfo, DIDGetRecoveryInfo from chia.rpc.wallet_rpc_api import WalletRpcApi from chia.simulator.simulator_protocol import FarmNewBlockProtocol from chia.types.blockchain_format.program import Program @@ -109,90 +110,208 @@ async def test_creation_from_coin_spend( == json.loads(all_node_1_wallets[1].data)["current_inner"] ) + # TODO: Porting this test to this fixture revealed some balance peculiarities. Fix them. @pytest.mark.parametrize( - "trusted", - [True, False], + "wallet_environments", + [ + { + "num_environments": 3, + "blocks_needed": [1, 1, 1], + } + ], + indirect=True, ) @pytest.mark.anyio - async def test_creation_from_backup_file(self, self_hostname, three_wallet_nodes, trusted): - full_nodes, wallets, _ = three_wallet_nodes - full_node_api = full_nodes[0] - full_node_server = full_node_api.server - wallet_node_0, server_0 = wallets[0] - wallet_node_1, server_1 = wallets[1] - wallet_node_2, server_2 = wallets[2] - wallet_0 = wallet_node_0.wallet_state_manager.main_wallet - wallet_1 = wallet_node_1.wallet_state_manager.main_wallet - wallet_2 = wallet_node_2.wallet_state_manager.main_wallet - - if trusted: - wallet_node_0.config["trusted_peers"] = { - full_node_api.full_node.server.node_id.hex(): full_node_api.full_node.server.node_id.hex() - } - wallet_node_1.config["trusted_peers"] = { - full_node_api.full_node.server.node_id.hex(): full_node_api.full_node.server.node_id.hex() - } - wallet_node_2.config["trusted_peers"] = { - full_node_api.full_node.server.node_id.hex(): full_node_api.full_node.server.node_id.hex() - } - else: - wallet_node_0.config["trusted_peers"] = {} - wallet_node_1.config["trusted_peers"] = {} - wallet_node_2.config["trusted_peers"] = {} - await server_0.start_client(PeerInfo(self_hostname, full_node_server.get_port()), None) - await server_1.start_client(PeerInfo(self_hostname, full_node_server.get_port()), None) - await server_2.start_client(PeerInfo(self_hostname, full_node_server.get_port()), None) - - await full_node_api.farm_blocks_to_wallet(1, wallet_0) - await full_node_api.farm_blocks_to_wallet(1, wallet_1) - await full_node_api.farm_blocks_to_wallet(1, wallet_2) + @pytest.mark.limit_consensus_modes(reason="irrelevant") + async def test_creation_from_backup_file(self, wallet_environments: WalletTestFramework) -> None: + env_0 = wallet_environments.environments[0] + env_1 = wallet_environments.environments[1] + env_2 = wallet_environments.environments[2] + + env_0.wallet_aliases = { + "xch": 1, + "did": 2, + } + env_1.wallet_aliases = { + "xch": 1, + "did": 2, + } + env_2.wallet_aliases = { + "xch": 1, + "did": 2, + } # Wallet1 sets up DIDWallet1 without any backup set - async with wallet_0.wallet_state_manager.new_action_scope(DEFAULT_TX_CONFIG, push=True) as action_scope: + async with env_0.wallet_state_manager.new_action_scope( + wallet_environments.tx_config, push=True + ) as action_scope: did_wallet_0: DIDWallet = await DIDWallet.create_new_did_wallet( - wallet_node_0.wallet_state_manager, wallet_0, uint64(101), action_scope + env_0.wallet_state_manager, env_0.xch_wallet, uint64(101), action_scope ) - await full_node_api.process_transaction_records(records=action_scope.side_effects.transactions) - await full_node_api.wait_for_wallets_synced(wallet_nodes=[wallet_node_0, wallet_node_1, wallet_node_2]) + await wallet_environments.process_pending_states( + [ + WalletStateTransition( + pre_block_balance_updates={ + "xch": { + "unconfirmed_wallet_balance": -101, + "<=#spendable_balance": -101, + "<=#max_send_amount": -101, + ">=#pending_change": 1, + "pending_coin_removal_count": 1, + }, + "did": { + "init": True, + "unconfirmed_wallet_balance": 101, + "pending_change": 202, # TODO: this is not correct, fix this + "pending_coin_removal_count": 2, # TODO: this might not be correct + }, + }, + post_block_balance_updates={ + "xch": { + "confirmed_wallet_balance": -101, + ">=#spendable_balance": 1, + ">=#max_send_amount": 1, + "<=#pending_change": -1, + "pending_coin_removal_count": -1, + }, + "did": { + "confirmed_wallet_balance": 101, + "spendable_balance": 101, + "max_send_amount": 101, + "unspent_coin_count": 1, + "pending_change": -202, # TODO: this is not correct, fix this + "pending_coin_removal_count": -2, # TODO: this might not be correct + }, + }, + ), + WalletStateTransition( + pre_block_balance_updates={}, + post_block_balance_updates={}, + ), + WalletStateTransition( + pre_block_balance_updates={}, + post_block_balance_updates={}, + ), + ] + ) - await time_out_assert(15, did_wallet_0.get_confirmed_balance, 101) - await time_out_assert(15, did_wallet_0.get_unconfirmed_balance, 101) - await time_out_assert(15, did_wallet_0.get_pending_change_balance, 0) # Wallet1 sets up DIDWallet_1 with DIDWallet_0 as backup - backup_ids = [bytes.fromhex(did_wallet_0.get_my_DID())] + backup_ids = [bytes32.from_hexstr(did_wallet_0.get_my_DID())] - async with wallet_1.wallet_state_manager.new_action_scope(DEFAULT_TX_CONFIG, push=True) as action_scope: + async with env_1.wallet_state_manager.new_action_scope( + wallet_environments.tx_config, push=True + ) as action_scope: did_wallet_1: DIDWallet = await DIDWallet.create_new_did_wallet( - wallet_node_1.wallet_state_manager, wallet_1, uint64(201), action_scope, backup_ids + env_1.wallet_state_manager, env_1.xch_wallet, uint64(201), action_scope, backup_ids ) - await full_node_api.process_transaction_records(records=action_scope.side_effects.transactions) - await full_node_api.wait_for_wallets_synced(wallet_nodes=[wallet_node_0, wallet_node_1, wallet_node_2]) - - await time_out_assert(15, did_wallet_1.get_confirmed_balance, 201) - await time_out_assert(15, did_wallet_1.get_unconfirmed_balance, 201) - await time_out_assert(15, did_wallet_1.get_pending_change_balance, 0) + await wallet_environments.process_pending_states( + [ + WalletStateTransition( + pre_block_balance_updates={}, + post_block_balance_updates={}, + ), + WalletStateTransition( + pre_block_balance_updates={ + "xch": { + "unconfirmed_wallet_balance": -201, + "<=#spendable_balance": -201, + "<=#max_send_amount": -201, + ">=#pending_change": 1, + "pending_coin_removal_count": 1, + }, + "did": { + "init": True, + "unconfirmed_wallet_balance": 201, + "pending_change": 402, # TODO: this is not correct, fix this + "pending_coin_removal_count": 2, # TODO: this might not be correct + }, + }, + post_block_balance_updates={ + "xch": { + "confirmed_wallet_balance": -201, + ">=#spendable_balance": 1, + ">=#max_send_amount": 1, + "<=#pending_change": -1, + "pending_coin_removal_count": -1, + }, + "did": { + "confirmed_wallet_balance": 201, + "spendable_balance": 201, + "max_send_amount": 201, + "unspent_coin_count": 1, + "pending_change": -402, # TODO: this is not correct, fix this + "pending_coin_removal_count": -2, # TODO: this might not be correct + }, + }, + ), + WalletStateTransition( + pre_block_balance_updates={}, + post_block_balance_updates={}, + ), + ] + ) backup_data = did_wallet_1.create_backup() # Wallet2 recovers DIDWallet2 to a new set of keys - async with wallet_node_2.wallet_state_manager.lock: - did_wallet_2 = await DIDWallet.create_new_did_wallet_from_recovery( - wallet_node_2.wallet_state_manager, wallet_2, backup_data - ) - coin = await did_wallet_1.get_coin() - assert did_wallet_2.did_info.temp_coin == coin - newpuzhash = await did_wallet_2.get_new_did_inner_hash() - pubkey = bytes( - (await did_wallet_2.wallet_state_manager.get_unused_derivation_record(did_wallet_2.wallet_info.id)).pubkey + await env_2.rpc_client.create_new_did_wallet( + uint64(1), + DEFAULT_TX_CONFIG, + type="recovery", + backup_data=backup_data, + ) + did_wallet_2 = env_2.wallet_state_manager.get_wallet(id=uint32(2), required_type=DIDWallet) + recovery_info = await env_2.rpc_client.did_get_recovery_info( + DIDGetRecoveryInfo(uint32(env_2.wallet_aliases["did"])) + ) + assert recovery_info.wallet_id == env_2.wallet_aliases["did"] + assert recovery_info.backup_dids == backup_ids + current_coin_info_response = await env_0.rpc_client.did_get_current_coin_info( + DIDGetCurrentCoinInfo(uint32(env_0.wallet_aliases["did"])) ) - async with did_wallet_0.wallet_state_manager.new_action_scope(DEFAULT_TX_CONFIG, push=True) as action_scope: + # TODO: this check is kind of weak, we should research when this endpoint might actually be useful + assert current_coin_info_response.wallet_id == env_0.wallet_aliases["did"] + async with env_0.wallet_state_manager.new_action_scope( + wallet_environments.tx_config, push=True + ) as action_scope: message_spend_bundle, attest_data = await did_wallet_0.create_attestment( - did_wallet_2.did_info.temp_coin.name(), newpuzhash, pubkey, action_scope + recovery_info.coin_name, recovery_info.newpuzhash, recovery_info.pubkey, action_scope ) - await full_node_api.process_transaction_records(records=action_scope.side_effects.transactions) - await full_node_api.wait_for_wallets_synced(wallet_nodes=[wallet_node_0, wallet_node_1, wallet_node_2]) + + await wallet_environments.process_pending_states( + [ + WalletStateTransition( + pre_block_balance_updates={ + "did": { + "spendable_balance": -101, + "pending_change": 101, + "pending_coin_removal_count": 1, + } + }, + post_block_balance_updates={ + "did": { + "spendable_balance": 101, + "pending_change": -101, + "pending_coin_removal_count": -1, + } + }, + ), + WalletStateTransition( + pre_block_balance_updates={}, + post_block_balance_updates={}, + ), + WalletStateTransition( + pre_block_balance_updates={ + "did": { + "init": True, + } + }, + post_block_balance_updates={}, + ), + ] + ) ( test_info_list, @@ -200,38 +319,104 @@ async def test_creation_from_backup_file(self, self_hostname, three_wallet_nodes ) = await did_wallet_2.load_attest_files_for_recovery_spend([attest_data]) assert message_spend_bundle == test_message_spend_bundle - async with did_wallet_2.wallet_state_manager.new_action_scope(DEFAULT_TX_CONFIG, push=True) as action_scope: + async with env_2.wallet_state_manager.new_action_scope( + wallet_environments.tx_config, push=True + ) as action_scope: + assert did_wallet_2.did_info.temp_coin is not None await did_wallet_2.recovery_spend( did_wallet_2.did_info.temp_coin, - newpuzhash, + recovery_info.newpuzhash, test_info_list, - pubkey, + recovery_info.pubkey, test_message_spend_bundle, action_scope, ) - await full_node_api.process_transaction_records(action_scope.side_effects.transactions) - await full_node_api.wait_for_wallets_synced(wallet_nodes=[wallet_node_0, wallet_node_1, wallet_node_2]) - - await time_out_assert(45, did_wallet_2.get_confirmed_balance, 201) - await time_out_assert(45, did_wallet_2.get_unconfirmed_balance, 201) + await wallet_environments.process_pending_states( + [ + WalletStateTransition( + pre_block_balance_updates={}, + post_block_balance_updates={}, + ), + WalletStateTransition( + pre_block_balance_updates={}, + post_block_balance_updates={ + "did": { + "confirmed_wallet_balance": -201, + "unconfirmed_wallet_balance": -201, + "spendable_balance": -201, + "max_send_amount": -201, + "unspent_coin_count": -1, + } + }, + ), + WalletStateTransition( + pre_block_balance_updates={ + "did": { + "unconfirmed_wallet_balance": 201, + "pending_coin_removal_count": 2, + } + }, + post_block_balance_updates={ + "did": { + "confirmed_wallet_balance": 201, + "spendable_balance": 201, + "max_send_amount": 201, + "unspent_coin_count": 1, + "pending_coin_removal_count": -2, + } + }, + ), + ] + ) for wallet in [did_wallet_0, did_wallet_1, did_wallet_2]: assert wallet.wallet_state_manager.wallets[wallet.id()] == wallet - some_ph = 32 * b"\2" - async with did_wallet_2.wallet_state_manager.new_action_scope(DEFAULT_TX_CONFIG, push=True) as action_scope: + some_ph = bytes32(32 * b"\2") + async with env_2.wallet_state_manager.new_action_scope( + wallet_environments.tx_config, push=True + ) as action_scope: await did_wallet_2.create_exit_spend(some_ph, action_scope) - await full_node_api.process_transaction_records(records=action_scope.side_effects.transactions) - await full_node_api.wait_for_wallets_synced(wallet_nodes=[wallet_node_0, wallet_node_1, wallet_node_2]) + + await wallet_environments.process_pending_states( + [ + WalletStateTransition( + pre_block_balance_updates={}, + post_block_balance_updates={}, + ), + WalletStateTransition( + pre_block_balance_updates={}, + post_block_balance_updates={}, + ), + WalletStateTransition( + pre_block_balance_updates={ + "did": { + "unconfirmed_wallet_balance": -201, + "spendable_balance": -201, + # "max_send_amount": -201, # TODO: Uncomment this + "pending_coin_removal_count": 1, + } + }, + post_block_balance_updates={ + "did": { + "confirmed_wallet_balance": -201, + "max_send_amount": -201, # TODO: Delete this when uncommented above + "unspent_coin_count": -1, + "pending_coin_removal_count": -1, + } + }, + ), + ] + ) async def get_coins_with_ph() -> bool: - coins = await full_node_api.full_node.coin_store.get_coin_records_by_puzzle_hash(True, some_ph) + coins = await wallet_environments.full_node.full_node.coin_store.get_coin_records_by_puzzle_hash( + True, some_ph + ) return len(coins) == 1 await time_out_assert(15, get_coins_with_ph, True) - await time_out_assert(45, did_wallet_2.get_confirmed_balance, 0) - await time_out_assert(45, did_wallet_2.get_unconfirmed_balance, 0) for wallet in [did_wallet_0, did_wallet_1]: assert wallet.wallet_state_manager.wallets[wallet.id()] == wallet diff --git a/chia/_tests/wallet/nft_wallet/test_nft_wallet.py b/chia/_tests/wallet/nft_wallet/test_nft_wallet.py index 71bfc71d9002..26c9ba2bc877 100644 --- a/chia/_tests/wallet/nft_wallet/test_nft_wallet.py +++ b/chia/_tests/wallet/nft_wallet/test_nft_wallet.py @@ -12,6 +12,14 @@ from chia._tests.environments.wallet import WalletStateTransition, WalletTestFramework from chia._tests.util.time_out_assert import time_out_assert from chia.rpc.rpc_client import ResponseFailureError +from chia.rpc.wallet_request_types import ( + NFTCoin, + NFTGetByDID, + NFTSetDIDBulk, + NFTSetNFTStatus, + NFTTransferBulk, + NFTWalletWithDID, +) from chia.simulator.simulator_protocol import ReorgProtocol from chia.types.blockchain_format.program import Program from chia.types.blockchain_format.sized_bytes import bytes32 @@ -26,7 +34,6 @@ from chia.wallet.util.address_type import AddressType from chia.wallet.util.compute_memos import compute_memos from chia.wallet.util.wallet_types import WalletType -from chia.wallet.wallet_spend_bundle import WalletSpendBundle from chia.wallet.wallet_state_manager import WalletStateManager @@ -818,8 +825,8 @@ async def test_nft_with_did_wallet_creation(wallet_environments: WalletTestFrame assert res["wallet_id"] != nft_wallet.id() nft_wallet_p2_puzzle = res["wallet_id"] - res = await env.rpc_client.fetch("nft_get_by_did", {"did_id": hmr_did_id}) - assert nft_wallet.id() == res["wallet_id"] + wallet_by_did_response = await env.rpc_client.get_nft_wallet_by_did(NFTGetByDID(did_id=hmr_did_id)) + assert nft_wallet.id() == wallet_by_did_response.wallet_id await wallet_environments.process_pending_states( [ @@ -830,10 +837,8 @@ async def test_nft_with_did_wallet_creation(wallet_environments: WalletTestFrame ] ) - res = await env.rpc_client.fetch("nft_get_wallets_with_dids", {}) - assert res.get("success") - assert res.get("nft_wallets") == [ - {"wallet_id": nft_wallet.id(), "did_id": hmr_did_id, "did_wallet_id": did_wallet.id()} + assert (await env.rpc_client.get_nft_wallets_with_dids()).nft_wallets == [ + NFTWalletWithDID(wallet_id=nft_wallet.id(), did_id=hmr_did_id, did_wallet_id=did_wallet.id()) ] res = await env.rpc_client.get_nft_wallet_did(wallet_id=nft_wallet.id()) @@ -1273,9 +1278,8 @@ async def test_nft_transfer_nft_with_did(wallet_environments: WalletTestFramewor ) # Check if the NFT owner DID is reset - res = await env_1.rpc_client.fetch("nft_get_by_did", {}) - assert res.get("success") - assert env_1.wallet_aliases["nft"] == res["wallet_id"] + wallet_by_did_response = await env_1.rpc_client.get_nft_wallet_by_did(NFTGetByDID()) + assert env_1.wallet_aliases["nft"] == wallet_by_did_response.wallet_id coins = (await env_1.rpc_client.list_nfts(env_1.wallet_aliases["nft"], start_index=0, num=1))["nft_list"] assert len(coins) == 1 coin = NFTInfo.from_json_dict(coins[0]) @@ -1332,9 +1336,8 @@ async def test_nft_transfer_nft_with_did(wallet_environments: WalletTestFramewor ] ) - res = await env_1.rpc_client.fetch("nft_get_by_did", {"did_id": hmr_did_id}) - assert res.get("success") - assert env_1.wallet_aliases["nft_w_did"] == res["wallet_id"] + wallet_by_did_response = await env_1.rpc_client.get_nft_wallet_by_did(NFTGetByDID(did_id=hmr_did_id)) + assert env_1.wallet_aliases["nft_w_did"] == wallet_by_did_response.wallet_id # Check NFT DID is set now coins = (await env_1.rpc_client.list_nfts(env_1.wallet_aliases["nft_w_did"], start_index=0, num=1))["nft_list"] assert len(coins) == 1 @@ -1725,19 +1728,16 @@ async def test_nft_bulk_set_did(wallet_environments: WalletTestFramework) -> Non nft2 = NFTInfo.from_json_dict(coins[0]) assert nft2.owner_did is None nft_coin_list = [ - {"wallet_id": env.wallet_aliases["nft_w_did"], "nft_coin_id": nft1.nft_coin_id.hex()}, - {"wallet_id": env.wallet_aliases["nft_w_did"], "nft_coin_id": nft12.nft_coin_id.hex()}, - {"wallet_id": env.wallet_aliases["nft_no_did"], "nft_coin_id": nft2.nft_coin_id.hex()}, - {"wallet_id": env.wallet_aliases["nft_no_did"]}, - {"nft_coin_id": nft2.nft_coin_id.hex()}, + NFTCoin(wallet_id=uint32(env.wallet_aliases["nft_w_did"]), nft_coin_id=nft1.nft_coin_id.hex()), + NFTCoin(wallet_id=uint32(env.wallet_aliases["nft_w_did"]), nft_coin_id=nft12.nft_coin_id.hex()), + NFTCoin(wallet_id=uint32(env.wallet_aliases["nft_no_did"]), nft_coin_id=nft2.nft_coin_id.hex()), ] - fee = 1000 - res = await env.rpc_client.fetch("nft_set_did_bulk", dict(did_id=hmr_did_id, nft_coin_list=nft_coin_list, fee=fee)) - sb = WalletSpendBundle.from_json_dict(res["spend_bundle"]) - assert len(sb.coin_spends) == 5 - tx_num = res["tx_num"] - assert isinstance(tx_num, int) - assert tx_num == 5 # 1 for each NFT being spent (3), 1 for fee tx, 1 for did tx + fee = uint64(1000) + set_did_bulk_resp = await env.rpc_client.set_nft_did_bulk( + NFTSetDIDBulk(did_id=hmr_did_id, nft_coin_list=nft_coin_list, fee=fee, push=True) + ) + assert len(set_did_bulk_resp.spend_bundle.coin_spends) == 5 + assert set_did_bulk_resp.tx_num == 5 # 1 for each NFT being spent (3), 1 for fee tx, 1 for did tx coins = (await env.rpc_client.list_nfts(env.wallet_aliases["nft_w_did"], start_index=0, num=2))["nft_list"] assert len(coins) == 2 nft1 = NFTInfo.from_json_dict(coins[0]) @@ -1784,9 +1784,8 @@ async def test_nft_bulk_set_did(wallet_environments: WalletTestFramework) -> Non ] ) - res = await env.rpc_client.fetch("nft_get_by_did", {"did_id": hmr_did_id}) - assert res.get("success") - assert env.wallet_aliases["nft_w_did"] == res.get("wallet_id") + wallet_by_did_response = await env.rpc_client.get_nft_wallet_by_did(NFTGetByDID(did_id=hmr_did_id)) + assert env.wallet_aliases["nft_w_did"] == wallet_by_did_response.wallet_id coins = (await env.rpc_client.list_nfts(env.wallet_aliases["nft_w_did"], start_index=0, num=3))["nft_list"] assert len(coins) == 3 nft1 = NFTInfo.from_json_dict(coins[0]) @@ -2024,20 +2023,18 @@ async def test_nft_bulk_transfer(wallet_environments: WalletTestFramework) -> No nft2 = NFTInfo.from_json_dict(coins[0]) assert nft2.owner_did is None nft_coin_list = [ - {"wallet_id": env_0.wallet_aliases["nft_w_did"], "nft_coin_id": nft1.nft_coin_id.hex()}, - {"wallet_id": env_0.wallet_aliases["nft_w_did"], "nft_coin_id": nft12.nft_coin_id.hex()}, - {"wallet_id": env_0.wallet_aliases["nft_no_did"], "nft_coin_id": nft2.nft_coin_id.hex()}, - {"wallet_id": env_0.wallet_aliases["nft_no_did"]}, - {"nft_coin_id": nft2.nft_coin_id.hex()}, + NFTCoin(wallet_id=uint32(env_0.wallet_aliases["nft_w_did"]), nft_coin_id=nft1.nft_coin_id.hex()), + NFTCoin(wallet_id=uint32(env_0.wallet_aliases["nft_w_did"]), nft_coin_id=nft12.nft_coin_id.hex()), + NFTCoin(wallet_id=uint32(env_0.wallet_aliases["nft_no_did"]), nft_coin_id=nft2.nft_coin_id.hex()), ] - fee = 1000 + fee = uint64(1000) address = encode_puzzle_hash(await wallet_1.get_puzzle_hash(new=False), AddressType.XCH.hrp(env_1.node.config)) - res = await env_0.rpc_client.fetch( - "nft_transfer_bulk", dict(target_address=address, nft_coin_list=nft_coin_list, fee=fee) + bulk_transfer_resp = await env_0.rpc_client.transfer_nft_bulk( + NFTTransferBulk(target_address=address, nft_coin_list=nft_coin_list, fee=fee, push=True) ) - assert len(res["spend_bundle"]["coin_spends"]) == 4 - assert res["tx_num"] == 4 + assert len(bulk_transfer_resp.spend_bundle.coin_spends) == 4 + assert bulk_transfer_resp.tx_num == 4 await wallet_environments.process_pending_states( [ @@ -2419,8 +2416,8 @@ async def test_set_nft_status(wallet_environments: WalletTestFramework) -> None: assert not coin.pending_transaction nft_coin_id = coin.nft_coin_id # Set status - res = await env.rpc_client.fetch( - "nft_set_nft_status", dict(wallet_id=env.wallet_aliases["nft"], coin_id=nft_coin_id.hex(), in_transaction=True) + await env.rpc_client.set_nft_status( + NFTSetNFTStatus(wallet_id=uint32(env.wallet_aliases["nft"]), coin_id=nft_coin_id, in_transaction=True) ) coins = (await env.rpc_client.list_nfts(env.wallet_aliases["nft"], start_index=0, num=1))["nft_list"] assert len(coins) == 1 diff --git a/chia/_tests/wallet/rpc/test_wallet_rpc.py b/chia/_tests/wallet/rpc/test_wallet_rpc.py index e33e57063710..1a008cf38c5e 100644 --- a/chia/_tests/wallet/rpc/test_wallet_rpc.py +++ b/chia/_tests/wallet/rpc/test_wallet_rpc.py @@ -10,7 +10,7 @@ import aiosqlite import pytest -from chia_rs import G2Element +from chia_rs import G1Element, G2Element from chia._tests.conftest import ConsensusMode from chia._tests.environments.wallet import WalletStateTransition, WalletTestFramework @@ -46,7 +46,15 @@ from chia.rpc.full_node_rpc_client import FullNodeRpcClient from chia.rpc.rpc_client import ResponseFailureError from chia.rpc.rpc_server import RpcServer -from chia.rpc.wallet_request_types import CombineCoins, GetNotifications, SplitCoins, SplitCoinsResponse +from chia.rpc.wallet_request_types import ( + CombineCoins, + DIDGetPubkey, + GetNotifications, + SplitCoins, + SplitCoinsResponse, + VerifySignature, + VerifySignatureResponse, +) from chia.rpc.wallet_rpc_api import WalletRpcApi from chia.rpc.wallet_rpc_client import WalletRpcClient from chia.server.server import ChiaServer @@ -79,6 +87,7 @@ from chia.wallet.derive_keys import master_sk_to_wallet_sk, master_sk_to_wallet_sk_unhardened from chia.wallet.did_wallet.did_wallet import DIDWallet from chia.wallet.nft_wallet.nft_wallet import NFTWallet +from chia.wallet.puzzles.clawback.metadata import AutoClaimSettings from chia.wallet.signer_protocol import UnsignedTransaction from chia.wallet.trading.trade_status import TradeStatus from chia.wallet.transaction_record import TransactionRecord @@ -289,13 +298,6 @@ async def get_unconfirmed_balance(client: WalletRpcClient, wallet_id: int): return (await client.get_wallet_balance(wallet_id))["unconfirmed_wallet_balance"] -def update_verify_signature_request(request: Dict[str, Any], prefix_hex_values: bool): - updated_request = request.copy() - updated_request["pubkey"] = ("0x" if prefix_hex_values else "") + updated_request["pubkey"] - updated_request["signature"] = ("0x" if prefix_hex_values else "") + updated_request["signature"] - return updated_request - - @pytest.mark.anyio async def test_send_transaction(wallet_rpc_environment: WalletRpcTestEnvironment): env: WalletRpcTestEnvironment = wallet_rpc_environment @@ -836,7 +838,14 @@ async def test_spend_clawback_coins(wallet_rpc_environment: WalletRpcTestEnviron resp = await wallet_1_rpc.spend_clawback_coins([fake_coin.name()], 100) assert resp["transaction_ids"] == [] # Test claim spend - await wallet_2_api.set_auto_claim({"enabled": False, "tx_fee": 100, "min_amount": 0, "batch_size": 1}) + await wallet_2_rpc.set_auto_claim( + AutoClaimSettings( + enabled=False, + tx_fee=uint64(100), + min_amount=uint64(0), + batch_size=uint16(1), + ) + ) resp = await wallet_2_rpc.spend_clawback_coins([clawback_coin_id_1, clawback_coin_id_2], 100) assert resp["success"] assert len(resp["transaction_ids"]) == 2 @@ -1156,6 +1165,9 @@ async def test_cat_endpoints(wallet_rpc_environment: WalletRpcTestEnvironment): ) assert len(selected_coins) > 0 + # Test get_cat_list + assert len(DEFAULT_CATS) == len((await client.get_cat_list()).cat_list) + @pytest.mark.anyio async def test_offer_endpoints(wallet_rpc_environment: WalletRpcTestEnvironment): @@ -1245,6 +1257,11 @@ async def test_offer_endpoints(wallet_rpc_environment: WalletRpcTestEnvironment) assert TradeStatus(all_offers[0].status) == TradeStatus.PENDING_ACCEPT assert all_offers[0].offer == bytes(offer) + offer_count = await wallet_1_rpc.get_offers_count() + assert offer_count.total == 1 + assert offer_count.my_offers_count == 1 + assert offer_count.taken_offers_count == 0 + trade_record = (await wallet_2_rpc.take_offer(offer, DEFAULT_TX_CONFIG, fee=uint64(1))).trade_record assert TradeStatus(trade_record.status) == TradeStatus.PENDING_CONFIRM @@ -1264,6 +1281,10 @@ async def test_offer_endpoints(wallet_rpc_environment: WalletRpcTestEnvironment) ) all_offers = await wallet_1_rpc.get_all_offers() assert len(all_offers) == 2 + offer_count = await wallet_1_rpc.get_offers_count() + assert offer_count.total == 2 + assert offer_count.my_offers_count == 2 + assert offer_count.taken_offers_count == 0 new_trade_record = create_res.trade_record await farm_transaction_block(full_node_api, wallet_node) @@ -1565,6 +1586,10 @@ async def num_wallets() -> int: assert next_did_coin.parent_coin_info == last_did_coin.name() assert next_did_coin.puzzle_hash == last_did_coin.puzzle_hash + # Test did_get_pubkey + pubkey_res = await wallet_2_rpc.get_did_pubkey(DIDGetPubkey(did_wallet_2.id())) + assert isinstance(pubkey_res.pubkey, G1Element) + @pytest.mark.anyio async def test_nft_endpoints(wallet_rpc_environment: WalletRpcTestEnvironment): @@ -2170,7 +2195,7 @@ async def test_notification_rpcs(wallet_rpc_environment: WalletRpcTestEnvironmen "6034d8782d10ef148d" ), }, - {"isValid": True}, + VerifySignatureResponse(isValid=True), ), ( # chia wallet sign_message -m $(echo -n 'Happy happy joy joy' | xxd -p) @@ -2188,7 +2213,7 @@ async def test_notification_rpcs(wallet_rpc_environment: WalletRpcTestEnvironmen ), "signing_mode": SigningMode.CHIP_0002.value, }, - {"isValid": True}, + VerifySignatureResponse(isValid=True), ), ( # chia wallet sign_message -m $(echo -n 'Happy happy joy joy' | xxd -p) @@ -2207,7 +2232,7 @@ async def test_notification_rpcs(wallet_rpc_environment: WalletRpcTestEnvironmen "signing_mode": SigningMode.CHIP_0002.value, "address": "xch1e2pcue5q7t4sg8gygz3aht369sk78rzzs92zx65ktn9a9qurw35saajvkh", }, - {"isValid": True}, + VerifySignatureResponse(isValid=True), ), ( { @@ -2224,7 +2249,7 @@ async def test_notification_rpcs(wallet_rpc_environment: WalletRpcTestEnvironmen "signing_mode": SigningMode.CHIP_0002_P2_DELEGATED_CONDITIONS.value, "address": "xch1hh9phcc8tt703dla70qthlhrxswy88va04zvc7vd8cx2v6a5ywyst8mgul", }, - {"isValid": True}, + VerifySignatureResponse(isValid=True), ), # Negative tests ( @@ -2241,7 +2266,7 @@ async def test_notification_rpcs(wallet_rpc_environment: WalletRpcTestEnvironmen "6034d8782d10ef148d" ), }, - {"isValid": False, "error": "Signature is invalid."}, + VerifySignatureResponse(isValid=False, error="Signature is invalid."), ), ( # Valid signature but address doesn't match pubkey @@ -2259,7 +2284,7 @@ async def test_notification_rpcs(wallet_rpc_environment: WalletRpcTestEnvironmen "signing_mode": SigningMode.CHIP_0002.value, "address": "xch1d0rekc2javy5gpruzmcnk4e4qq834jzlvxt5tcgl2ylt49t26gdsjen7t0", }, - {"isValid": False, "error": "Public key doesn't match the address"}, + VerifySignatureResponse(isValid=False, error="Public key doesn't match the address"), ), ( { @@ -2275,12 +2300,13 @@ async def test_notification_rpcs(wallet_rpc_environment: WalletRpcTestEnvironmen ), "address": "xch1hh9phcc8tt703dla70qthlhrxswy88va04zvc7vd8cx2v6a5ywyst8mgul", }, - {"isValid": False, "error": "Public key doesn't match the address"}, + VerifySignatureResponse(isValid=False, error="Public key doesn't match the address"), ), ], ) @pytest.mark.parametrize("prefix_hex_strings", [True, False], ids=["with 0x", "no 0x"]) @pytest.mark.anyio +@pytest.mark.limit_consensus_modes(reason="irrelevant") async def test_verify_signature( wallet_rpc_environment: WalletRpcTestEnvironment, rpc_request: Dict[str, Any], @@ -2289,13 +2315,17 @@ async def test_verify_signature( ): rpc_server: Optional[RpcServer] = wallet_rpc_environment.wallet_1.service.rpc_server assert rpc_server is not None - api: WalletRpcApi = cast(WalletRpcApi, rpc_server.rpc_api) - req = update_verify_signature_request(rpc_request, prefix_hex_strings) - res = await api.verify_signature(req) + updated_request = rpc_request.copy() + updated_request["pubkey"] = ("0x" if prefix_hex_strings else "") + updated_request["pubkey"] + updated_request["signature"] = ("0x" if prefix_hex_strings else "") + updated_request["signature"] + res = await wallet_rpc_environment.wallet_1.rpc_client.verify_signature( + VerifySignature.from_json_dict(updated_request) + ) assert res == rpc_response @pytest.mark.anyio +@pytest.mark.limit_consensus_modes(reason="irrelevant") async def test_set_auto_claim(wallet_rpc_environment: WalletRpcTestEnvironment): env: WalletRpcTestEnvironment = wallet_rpc_environment full_node_api: FullNodeSimulator = env.full_node.api @@ -2306,31 +2336,34 @@ async def test_set_auto_claim(wallet_rpc_environment: WalletRpcTestEnvironment): req = {"enabled": False, "tx_fee": -1, "min_amount": 100} has_exception = False try: - res = await api.set_auto_claim(req) + # Manually using API to test error condition + await api.set_auto_claim(req) except ConversionError: has_exception = True assert has_exception req = {"enabled": False, "batch_size": 0, "min_amount": 100} - res = await api.set_auto_claim(req) - assert not res["enabled"] - assert res["tx_fee"] == 0 - assert res["min_amount"] == 100 - assert res["batch_size"] == 50 + res = await env.wallet_1.rpc_client.set_auto_claim( + AutoClaimSettings(enabled=False, batch_size=uint16(0), min_amount=uint64(100)) + ) + assert not res.enabled + assert res.tx_fee == 0 + assert res.min_amount == 100 + assert res.batch_size == 50 @pytest.mark.anyio +@pytest.mark.limit_consensus_modes(reason="irrelevant") async def test_get_auto_claim(wallet_rpc_environment: WalletRpcTestEnvironment): env: WalletRpcTestEnvironment = wallet_rpc_environment full_node_api: FullNodeSimulator = env.full_node.api rpc_server: Optional[RpcServer] = wallet_rpc_environment.wallet_1.service.rpc_server await generate_funds(full_node_api, env.wallet_1) assert rpc_server is not None - api: WalletRpcApi = cast(WalletRpcApi, rpc_server.rpc_api) - res = await api.get_auto_claim({}) - assert not res["enabled"] - assert res["tx_fee"] == 0 - assert res["min_amount"] == 0 - assert res["batch_size"] == 50 + res = await env.wallet_1.rpc_client.get_auto_claim() + assert not res.enabled + assert res.tx_fee == 0 + assert res.min_amount == 0 + assert res.batch_size == 50 @pytest.mark.anyio diff --git a/chia/_tests/wallet/test_wallet.py b/chia/_tests/wallet/test_wallet.py index 65a246e03889..1b8705ca80d1 100644 --- a/chia/_tests/wallet/test_wallet.py +++ b/chia/_tests/wallet/test_wallet.py @@ -9,6 +9,7 @@ from chia._tests.environments.wallet import WalletStateTransition, WalletTestFramework from chia._tests.util.time_out_assert import time_out_assert +from chia.rpc.wallet_request_types import GetTransactionMemo from chia.server.server import ChiaServer from chia.simulator.block_tools import BlockTools from chia.simulator.full_node_simulator import FullNodeSimulator @@ -1512,11 +1513,9 @@ async def test_wallet_make_transaction_with_memo(self, wallet_environments: Wall fees = estimate_fees(tx.spend_bundle) assert fees == tx_fee - tx_id = tx.name.hex() - memos = await env_0.rpc_api.get_transaction_memo(dict(transaction_id=tx_id)) - # test json serialization - assert len(memos[tx_id]) == 1 - assert list(memos[tx_id].values())[0][0] == ph_2.hex() + memos = await env_0.rpc_client.get_transaction_memo(GetTransactionMemo(transaction_id=tx.name)) + assert len(memos.coins_with_memos) == 1 + assert memos.coins_with_memos[0].memos[0] == ph_2 await wallet_environments.process_pending_states( [ @@ -1556,12 +1555,18 @@ async def test_wallet_make_transaction_with_memo(self, wallet_environments: Wall ] ) + tx_id = None for coin in tx.additions: if coin.amount == tx_amount: - tx_id = coin.name().hex() - memos = await env_1.rpc_api.get_transaction_memo(dict(transaction_id=tx_id)) - assert len(memos[tx_id]) == 1 - assert list(memos[tx_id].values())[0][0] == ph_2.hex() + tx_id = coin.name() + assert tx_id is not None + memos = await env_1.rpc_client.get_transaction_memo(GetTransactionMemo(transaction_id=tx_id)) + assert len(memos.coins_with_memos) == 1 + assert memos.coins_with_memos[0].memos[0] == ph_2 + # test json serialization + assert memos.to_json_dict() == { + tx_id.hex(): {memos.coins_with_memos[0].coin_id.hex(): [memos.coins_with_memos[0].memos[0].hex()]} + } @pytest.mark.parametrize( "wallet_environments", diff --git a/chia/rpc/wallet_request_types.py b/chia/rpc/wallet_request_types.py index b7ba8f20d0ed..8f8384d4aa69 100644 --- a/chia/rpc/wallet_request_types.py +++ b/chia/rpc/wallet_request_types.py @@ -6,6 +6,7 @@ from dataclasses import dataclass, field from typing import Any, Dict, List, Optional, Type, TypeVar +from chia_rs import G1Element, G2Element from typing_extensions import dataclass_transform from chia.types.blockchain_format.sized_bytes import bytes32 @@ -55,6 +56,172 @@ class GetNotificationsResponse(Streamable): notifications: List[Notification] +@streamable +@dataclass(frozen=True) +class VerifySignature(Streamable): + message: str + pubkey: G1Element + signature: G2Element + signing_mode: Optional[str] = None + address: Optional[str] = None + + +@streamable +@dataclass(frozen=True) +class VerifySignatureResponse(Streamable): + isValid: bool + error: Optional[str] = None + + +@streamable +@dataclass(frozen=True) +class GetTransactionMemo(Streamable): + transaction_id: bytes32 + + +# utility type for GetTransactionMemoResponse +@streamable +@dataclass(frozen=True) +class CoinIDWithMemos(Streamable): + coin_id: bytes32 + memos: List[bytes] + + +@streamable +@dataclass(frozen=True) +class GetTransactionMemoResponse(Streamable): + transaction_id: bytes32 + coins_with_memos: List[CoinIDWithMemos] + + # TODO: deprecate the kinda silly format of this RPC and delete these functions + def to_json_dict(self) -> Dict[str, Any]: + return { + self.transaction_id.hex(): { + cwm.coin_id.hex(): [memo.hex() for memo in cwm.memos] for cwm in self.coins_with_memos + } + } + + @classmethod + def from_json_dict(cls, json_dict: Dict[str, Any]) -> GetTransactionMemoResponse: + return cls( + bytes32.from_hexstr(list(json_dict.keys())[0]), + [ + CoinIDWithMemos(bytes32.from_hexstr(coin_id), [bytes32.from_hexstr(memo) for memo in memos]) + for coin_id, memos in list(json_dict.values())[0].items() + ], + ) + + +@streamable +@dataclass(frozen=True) +class GetOffersCountResponse(Streamable): + total: uint16 + my_offers_count: uint16 + taken_offers_count: uint16 + + +@streamable +@dataclass(frozen=True) +class DefaultCAT(Streamable): + asset_id: bytes32 + name: str + symbol: str + + +@streamable +@dataclass(frozen=True) +class GetCATListResponse(Streamable): + cat_list: List[DefaultCAT] + + +@streamable +@dataclass(frozen=True) +class DIDGetPubkey(Streamable): + wallet_id: uint32 + + +@streamable +@dataclass(frozen=True) +class DIDGetPubkeyResponse(Streamable): + pubkey: G1Element + + +@streamable +@dataclass(frozen=True) +class DIDGetRecoveryInfo(Streamable): + wallet_id: uint32 + + +@streamable +@dataclass(frozen=True) +class DIDGetRecoveryInfoResponse(Streamable): + wallet_id: uint32 + my_did: str + coin_name: bytes32 + newpuzhash: bytes32 + pubkey: G1Element + backup_dids: List[bytes32] + + +@streamable +@dataclass(frozen=True) +class DIDGetCurrentCoinInfo(Streamable): + wallet_id: uint32 + + +@streamable +@dataclass(frozen=True) +class DIDGetCurrentCoinInfoResponse(Streamable): + wallet_id: uint32 + my_did: str + did_parent: bytes32 + did_innerpuz: bytes32 + did_amount: uint64 + + +@streamable +@dataclass(frozen=True) +class NFTGetByDID(Streamable): + did_id: Optional[str] = None + + +@streamable +@dataclass(frozen=True) +class NFTGetByDIDResponse(Streamable): + wallet_id: uint32 + + +@streamable +@dataclass(frozen=True) +class NFTSetNFTStatus(Streamable): + wallet_id: uint32 + coin_id: bytes32 + in_transaction: bool + + +# utility for NFTGetWalletsWithDIDsResponse +@streamable +@dataclass(frozen=True) +class NFTWalletWithDID(Streamable): + wallet_id: uint32 + did_id: str + did_wallet_id: uint32 + + +@streamable +@dataclass(frozen=True) +class NFTGetWalletsWithDIDsResponse(Streamable): + nft_wallets: List[NFTWalletWithDID] + + +# utility for NFTSetDIDBulk +@streamable +@dataclass(frozen=True) +class NFTCoin(Streamable): + nft_coin_id: str + wallet_id: uint32 + + @streamable @dataclass(frozen=True) class GatherSigningInfo(Streamable): @@ -154,6 +321,36 @@ class CombineCoinsResponse(TransactionEndpointResponse): pass +@streamable +@kw_only_dataclass +class NFTSetDIDBulk(TransactionEndpointRequest): + nft_coin_list: List[NFTCoin] = field(default_factory=default_raise) + did_id: Optional[str] = None + + +@streamable +@dataclass(frozen=True) +class NFTSetDIDBulkResponse(TransactionEndpointResponse): + wallet_id: List[uint32] + tx_num: uint16 + spend_bundle: WalletSpendBundle + + +@streamable +@kw_only_dataclass +class NFTTransferBulk(TransactionEndpointRequest): + nft_coin_list: List[NFTCoin] = field(default_factory=default_raise) + target_address: str = field(default_factory=default_raise) + + +@streamable +@dataclass(frozen=True) +class NFTTransferBulkResponse(TransactionEndpointResponse): + wallet_id: List[uint32] + tx_num: uint16 + spend_bundle: WalletSpendBundle + + # TODO: The section below needs corresponding request types # TODO: The section below should be added to the API (currently only for client) @streamable diff --git a/chia/rpc/wallet_rpc_api.py b/chia/rpc/wallet_rpc_api.py index a7fbb60f361c..1a19089bef7b 100644 --- a/chia/rpc/wallet_rpc_api.py +++ b/chia/rpc/wallet_rpc_api.py @@ -1790,11 +1790,12 @@ async def verify_signature(self, request: Dict[str, Any]) -> EndpointResult: message_to_verify, G2Element.from_bytes(hexstr_to_bytes(request["signature"])), ) - if "address" in request: + address = request.get("address") + if address is not None: # For signatures made by the sign_message_by_address/sign_message_by_id # endpoints, the "address" field should contain the p2_address of the NFT/DID # that was used to sign the message. - puzzle_hash: bytes32 = decode_puzzle_hash(request["address"]) + puzzle_hash: bytes32 = decode_puzzle_hash(address) expected_puzzle_hash: Optional[bytes32] = None if signing_mode == SigningMode.CHIP_0002_P2_DELEGATED_CONDITIONS: puzzle = p2_delegated_conditions.puzzle_for_pk(Program.to(hexstr_to_bytes(request["pubkey"]))) @@ -3578,7 +3579,7 @@ async def nft_transfer_bulk( async def nft_get_by_did(self, request: Dict[str, Any]) -> EndpointResult: did_id: Optional[bytes32] = None - if "did_id" in request: + if request.get("did_id", None) is not None: did_id = decode_puzzle_hash(request["did_id"]) for wallet in self.service.wallet_state_manager.wallets.values(): if isinstance(wallet, NFTWallet) and wallet.get_did() == did_id: diff --git a/chia/rpc/wallet_rpc_client.py b/chia/rpc/wallet_rpc_client.py index eb0b747932ee..7e0ab602a869 100644 --- a/chia/rpc/wallet_rpc_client.py +++ b/chia/rpc/wallet_rpc_client.py @@ -24,6 +24,12 @@ DAOFreeCoinsFromFinishedProposalsResponse, DAOSendToLockupResponse, DAOVoteOnProposalResponse, + DIDGetCurrentCoinInfo, + DIDGetCurrentCoinInfoResponse, + DIDGetPubkey, + DIDGetPubkeyResponse, + DIDGetRecoveryInfo, + DIDGetRecoveryInfoResponse, DIDMessageSpendResponse, DIDTransferDIDResponse, DIDUpdateMetadataResponse, @@ -32,12 +38,24 @@ ExecuteSigningInstructionsResponse, GatherSigningInfo, GatherSigningInfoResponse, + GetCATListResponse, GetNotifications, GetNotificationsResponse, + GetOffersCountResponse, + GetTransactionMemo, + GetTransactionMemoResponse, NFTAddURIResponse, + NFTGetByDID, + NFTGetByDIDResponse, + NFTGetWalletsWithDIDsResponse, NFTMintBulkResponse, NFTMintNFTResponse, + NFTSetDIDBulk, + NFTSetDIDBulkResponse, NFTSetNFTDIDResponse, + NFTSetNFTStatus, + NFTTransferBulk, + NFTTransferBulkResponse, NFTTransferNFTResponse, SendTransactionMultiResponse, SendTransactionResponse, @@ -49,6 +67,8 @@ VCMintResponse, VCRevokeResponse, VCSpendResponse, + VerifySignature, + VerifySignatureResponse, ) from chia.types.blockchain_format.coin import Coin from chia.types.blockchain_format.program import Program @@ -57,6 +77,7 @@ from chia.util.bech32m import encode_puzzle_hash from chia.util.ints import uint16, uint32, uint64 from chia.wallet.conditions import Condition, ConditionValidTimes, conditions_to_json_dicts +from chia.wallet.puzzles.clawback.metadata import AutoClaimSettings from chia.wallet.trade_record import TradeRecord from chia.wallet.trading.offer import Offer from chia.wallet.transaction_record import TransactionRecord @@ -168,6 +189,12 @@ async def get_timestamp_for_height(self, height: uint32) -> uint64: # TODO: casting due to lack of type checked deserialization return cast(uint64, response["timestamp"]) + async def set_auto_claim(self, request: AutoClaimSettings) -> AutoClaimSettings: + return AutoClaimSettings.from_json_dict(await self.fetch("set_auto_claim", {**request.to_json_dict()})) + + async def get_auto_claim(self) -> AutoClaimSettings: + return AutoClaimSettings.from_json_dict(await self.fetch("get_auto_claim", {})) + # Wallet Management APIs async def get_wallets(self, wallet_type: Optional[WalletType] = None) -> List[Dict[str, Any]]: if wallet_type is None: @@ -426,19 +453,22 @@ async def create_new_did_wallet( name: Optional[str] = "DID Wallet", backup_ids: List[str] = [], required_num: int = 0, + type: str = "new", + backup_data: str = "", push: bool = True, extra_conditions: Tuple[Condition, ...] = tuple(), timelock_info: ConditionValidTimes = ConditionValidTimes(), ) -> Dict[str, Any]: request = { "wallet_type": "did_wallet", - "did_type": "new", + "did_type": type, "backup_dids": backup_ids, "num_of_backup_ids_needed": required_num, "amount": amount, "fee": fee, "wallet_name": name, "push": push, + "backup_data": backup_data, "extra_conditions": conditions_to_json_dicts(extra_conditions), **tx_config.to_json_dict(), **timelock_info.to_json_dict(), @@ -526,6 +556,9 @@ async def update_did_metadata( response = await self.fetch("did_update_metadata", request) return json_deserialize_with_clvm_streamable(response, DIDUpdateMetadataResponse) + async def get_did_pubkey(self, request: DIDGetPubkey) -> DIDGetPubkeyResponse: + return DIDGetPubkeyResponse.from_json_dict(await self.fetch("did_get_pubkey", request.to_json_dict())) + async def get_did_metadata(self, wallet_id: int) -> Dict[str, Any]: request = {"wallet_id": wallet_id} response = await self.fetch("did_get_metadata", request) @@ -575,6 +608,16 @@ async def did_create_attest( response = await self.fetch("did_create_attest", request) return response + async def did_get_recovery_info(self, request: DIDGetRecoveryInfo) -> DIDGetRecoveryInfoResponse: + return DIDGetRecoveryInfoResponse.from_json_dict( + await self.fetch("did_get_information_needed_for_recovery", request.to_json_dict()) + ) + + async def did_get_current_coin_info(self, request: DIDGetCurrentCoinInfo) -> DIDGetCurrentCoinInfoResponse: + return DIDGetCurrentCoinInfoResponse.from_json_dict( + await self.fetch("did_get_current_coin_info", request.to_json_dict()) + ) + async def did_recovery_spend(self, wallet_id: int, attest_filenames: str) -> Dict[str, Any]: request = {"wallet_id": wallet_id, "attest_filenames": attest_filenames} response = await self.fetch("did_recovery_spend", request) @@ -875,6 +918,9 @@ async def get_all_offers( return records + async def get_offers_count(self) -> GetOffersCountResponse: + return GetOffersCountResponse.from_json_dict(await self.fetch("get_offers_count", {})) + async def cancel_offer( self, trade_id: bytes32, @@ -930,6 +976,9 @@ async def cancel_offers( return json_deserialize_with_clvm_streamable(res, CancelOffersResponse) + async def get_cat_list(self) -> GetCATListResponse: + return GetCATListResponse.from_json_dict(await self.fetch("get_cat_list", {})) + # NFT wallet async def create_new_nft_wallet(self, did_id: Optional[str], name: Optional[str] = None) -> Dict[str, Any]: request = {"wallet_type": "nft_wallet", "did_id": did_id, "name": name} @@ -1061,6 +1110,9 @@ async def list_nfts(self, wallet_id: int, num: int = 50, start_index: int = 0) - response = await self.fetch("nft_get_nfts", request) return response + async def get_nft_wallet_by_did(self, request: NFTGetByDID) -> NFTGetByDIDResponse: + return NFTGetByDIDResponse.from_json_dict(await self.fetch("nft_get_by_did", request.to_json_dict())) + async def set_nft_did( self, wallet_id: int, @@ -1086,11 +1138,17 @@ async def set_nft_did( response = await self.fetch("nft_set_nft_did", request) return json_deserialize_with_clvm_streamable(response, NFTSetNFTDIDResponse) + async def set_nft_status(self, request: NFTSetNFTStatus) -> None: + await self.fetch("nft_set_nft_status", request.to_json_dict()) + async def get_nft_wallet_did(self, wallet_id: int) -> Dict[str, Any]: request = {"wallet_id": wallet_id} response = await self.fetch("nft_get_wallet_did", request) return response + async def get_nft_wallets_with_dids(self) -> NFTGetWalletsWithDIDsResponse: + return NFTGetWalletsWithDIDsResponse.from_json_dict(await self.fetch("nft_get_wallets_with_dids", {})) + async def nft_mint_bulk( self, wallet_id: int, @@ -1135,6 +1193,12 @@ async def nft_mint_bulk( response = await self.fetch("nft_mint_bulk", request) return json_deserialize_with_clvm_streamable(response, NFTMintBulkResponse) + async def set_nft_did_bulk(self, request: NFTSetDIDBulk) -> NFTSetDIDBulkResponse: + return NFTSetDIDBulkResponse.from_json_dict(await self.fetch("nft_set_did_bulk", request.to_json_dict())) + + async def transfer_nft_bulk(self, request: NFTTransferBulk) -> NFTTransferBulkResponse: + return NFTTransferBulkResponse.from_json_dict(await self.fetch("nft_transfer_bulk", request.to_json_dict())) + # DataLayer async def create_new_dl( self, @@ -1329,6 +1393,14 @@ async def sign_message_by_id( ) return response["pubkey"], response["signature"], response["signing_mode"] + async def verify_signature(self, request: VerifySignature) -> VerifySignatureResponse: + return VerifySignatureResponse.from_json_dict(await self.fetch("verify_signature", {**request.to_json_dict()})) + + async def get_transaction_memo(self, request: GetTransactionMemo) -> GetTransactionMemoResponse: + return GetTransactionMemoResponse.from_json_dict( + await self.fetch("get_transaction_memo", {**request.to_json_dict()}) + ) + # DAOs async def create_new_dao_wallet( self,