diff --git a/chia/_tests/cmds/cmd_test_utils.py b/chia/_tests/cmds/cmd_test_utils.py index 1829790dc1be..4a732aeda9ed 100644 --- a/chia/_tests/cmds/cmd_test_utils.py +++ b/chia/_tests/cmds/cmd_test_utils.py @@ -33,6 +33,10 @@ from chia.wallet.util.tx_config import TXConfig from chia.wallet.util.wallet_types import WalletType from chia.wallet.wallet_request_types import ( + CATAssetIDToName, + CATAssetIDToNameResponse, + CATGetName, + CATGetNameResponse, GetSyncStatusResponse, GetTransaction, GetTransactionResponse, @@ -146,9 +150,9 @@ async def get_transaction(self, request: GetTransaction) -> GetTransactionRespon bytes32([2] * 32), ) - async def get_cat_name(self, wallet_id: int) -> str: - self.add_to_log("get_cat_name", (wallet_id,)) - return "test" + str(wallet_id) + async def get_cat_name(self, request: CATGetName) -> CATGetNameResponse: + self.add_to_log("get_cat_name", (request.wallet_id,)) + return CATGetNameResponse(request.wallet_id, "test" + str(request.wallet_id)) async def sign_message_by_address(self, request: SignMessageByAddress) -> SignMessageByAddressResponse: self.add_to_log("sign_message_by_address", (request.address, request.message)) @@ -182,15 +186,15 @@ async def sign_message_by_id(self, request: SignMessageByID) -> SignMessageByIDR signing_mode = SigningMode.CHIP_0002.value return SignMessageByIDResponse(pubkey, signature, bytes32.zeros, signing_mode) - async def cat_asset_id_to_name(self, asset_id: bytes32) -> Optional[tuple[Optional[uint32], str]]: + async def cat_asset_id_to_name(self, request: CATAssetIDToName) -> CATAssetIDToNameResponse: """ if bytes32([1] * 32), return (uint32(2), "test1"), if bytes32([1] * 32), return (uint32(3), "test2") """ - self.add_to_log("cat_asset_id_to_name", (asset_id,)) + self.add_to_log("cat_asset_id_to_name", (request.asset_id,)) for i in range(256): - if asset_id == get_bytes32(i): - return uint32(i + 1), "test" + str(i) - return None + if request.asset_id == get_bytes32(i): + return CATAssetIDToNameResponse(uint32(i + 1), "test" + str(i)) + return CATAssetIDToNameResponse(wallet_id=None, name=None) async def get_nft_info(self, request: NFTGetInfo) -> NFTGetInfoResponse: self.add_to_log("get_nft_info", (request.coin_id, request.latest)) diff --git a/chia/_tests/cmds/wallet/test_notifications.py b/chia/_tests/cmds/wallet/test_notifications.py index cde0ca62b0c1..65e6e773da59 100644 --- a/chia/_tests/cmds/wallet/test_notifications.py +++ b/chia/_tests/cmds/wallet/test_notifications.py @@ -1,18 +1,23 @@ from __future__ import annotations from pathlib import Path -from typing import cast from chia_rs.sized_bytes import bytes32 from chia_rs.sized_ints import uint32, uint64 from chia._tests.cmds.cmd_test_utils import TestRpcClients, TestWalletRpcClient, logType, run_cli_command_and_assert -from chia._tests.cmds.wallet.test_consts import FINGERPRINT, FINGERPRINT_ARG, get_bytes32 +from chia._tests.cmds.wallet.test_consts import FINGERPRINT, FINGERPRINT_ARG, STD_TX, STD_UTX, get_bytes32 from chia.util.bech32m import encode_puzzle_hash -from chia.wallet.conditions import ConditionValidTimes +from chia.wallet.conditions import Condition, ConditionValidTimes from chia.wallet.notification_store import Notification -from chia.wallet.transaction_record import TransactionRecord -from chia.wallet.wallet_request_types import DeleteNotifications, GetNotifications, GetNotificationsResponse +from chia.wallet.util.tx_config import TXConfig +from chia.wallet.wallet_request_types import ( + DeleteNotifications, + GetNotifications, + GetNotificationsResponse, + SendNotification, + SendNotificationResponse, +) test_condition_valid_times: ConditionValidTimes = ConditionValidTimes(min_time=uint64(100), max_time=uint64(150)) @@ -26,20 +31,17 @@ def test_notifications_send(capsys: object, get_test_cli_clients: tuple[TestRpcC class NotificationsSendRpcClient(TestWalletRpcClient): async def send_notification( self, - target: bytes32, - msg: bytes, - amount: uint64, - fee: uint64 = uint64(0), - push: bool = True, + request: SendNotification, + tx_config: TXConfig, + extra_conditions: tuple[Condition, ...] = tuple(), timelock_info: ConditionValidTimes = ConditionValidTimes(), - ) -> TransactionRecord: - self.add_to_log("send_notification", (target, msg, amount, fee, push, timelock_info)) - - class FakeTransactionRecord: - def __init__(self, name: str) -> None: - self.name = name + ) -> SendNotificationResponse: + self.add_to_log( + "send_notification", + (request.target, request.message, request.amount, request.fee, request.push, timelock_info), + ) - return cast(TransactionRecord, FakeTransactionRecord(get_bytes32(2).hex())) + return SendNotificationResponse([STD_UTX], [STD_TX], tx=STD_TX) inst_rpc_client = NotificationsSendRpcClient() test_rpc_clients.wallet_rpc_client = inst_rpc_client diff --git a/chia/_tests/cmds/wallet/test_wallet.py b/chia/_tests/cmds/wallet/test_wallet.py index 073fd806928d..7babd3757b83 100644 --- a/chia/_tests/cmds/wallet/test_wallet.py +++ b/chia/_tests/cmds/wallet/test_wallet.py @@ -43,6 +43,10 @@ from chia.wallet.wallet_request_types import ( BalanceResponse, CancelOfferResponse, + CATAssetIDToName, + CATAssetIDToNameResponse, + CATSetName, + CATSetNameResponse, CATSpendResponse, ClawbackPuzzleDecoratorOverride, CreateOfferForIDsResponse, @@ -683,8 +687,9 @@ async def create_wallet_for_existing_cat(self, asset_id: bytes) -> dict[str, int self.add_to_log("create_wallet_for_existing_cat", (asset_id,)) return {"wallet_id": 3} - async def set_cat_name(self, wallet_id: int, name: str) -> None: - self.add_to_log("set_cat_name", (wallet_id, name)) + async def set_cat_name(self, request: CATSetName) -> CATSetNameResponse: + self.add_to_log("set_cat_name", (request.wallet_id, request.name)) + return CATSetNameResponse(wallet_id=request.wallet_id) inst_rpc_client = AddTokenRpcClient() test_rpc_clients.wallet_rpc_client = inst_rpc_client @@ -1051,14 +1056,14 @@ async def take_offer( ), ) - async def cat_asset_id_to_name(self, asset_id: bytes32) -> Optional[tuple[Optional[uint32], str]]: - self.add_to_log("cat_asset_id_to_name", (asset_id,)) - if asset_id == cat_offered_id: - return uint32(2), "offered cat" - elif asset_id == cat_requested_id: - return uint32(3), "requested cat" + async def cat_asset_id_to_name(self, request: CATAssetIDToName) -> CATAssetIDToNameResponse: + self.add_to_log("cat_asset_id_to_name", (request.asset_id,)) + if request.asset_id == cat_offered_id: + return CATAssetIDToNameResponse(uint32(2), "offered cat") + elif request.asset_id == cat_requested_id: + return CATAssetIDToNameResponse(uint32(3), "requested cat") else: - return None + return CATAssetIDToNameResponse(wallet_id=None, name=None) inst_rpc_client = TakeOfferRpcClient() test_rpc_clients.wallet_rpc_client = inst_rpc_client diff --git a/chia/_tests/core/cmds/test_wallet.py b/chia/_tests/core/cmds/test_wallet.py index 218b1b0ee39f..9a5fed2b5fe6 100644 --- a/chia/_tests/core/cmds/test_wallet.py +++ b/chia/_tests/core/cmds/test_wallet.py @@ -1,12 +1,13 @@ from __future__ import annotations -from typing import Any, Optional +from typing import Any import pytest from chia_rs.sized_bytes import bytes32 from chia_rs.sized_ints import uint32 from chia.cmds.wallet_funcs import print_offer_summary +from chia.wallet.wallet_request_types import CATAssetIDToName, CATAssetIDToNameResponse TEST_DUCKSAUCE_ASSET_ID = "1000000000000000000000000000000000000000000000000000000000000001" TEST_CRUNCHBERRIES_ASSET_ID = "1000000000000000000000000000000000000000000000000000000000000002" @@ -19,8 +20,8 @@ } -async def cat_name_resolver(asset_id: bytes32) -> Optional[tuple[Optional[uint32], str]]: - return TEST_ASSET_ID_NAME_MAPPING.get(asset_id) +async def cat_name_resolver(request: CATAssetIDToName) -> CATAssetIDToNameResponse: + return CATAssetIDToNameResponse(*TEST_ASSET_ID_NAME_MAPPING.get(request.asset_id, (None, None))) @pytest.mark.anyio diff --git a/chia/_tests/core/data_layer/test_data_rpc.py b/chia/_tests/core/data_layer/test_data_rpc.py index 1f62a63878ec..4d6277503726 100644 --- a/chia/_tests/core/data_layer/test_data_rpc.py +++ b/chia/_tests/core/data_layer/test_data_rpc.py @@ -73,7 +73,7 @@ from chia.wallet.util.tx_config import DEFAULT_TX_CONFIG from chia.wallet.wallet import Wallet from chia.wallet.wallet_node import WalletNode -from chia.wallet.wallet_request_types import DLLatestSingleton +from chia.wallet.wallet_request_types import CheckOfferValidity, DLLatestSingleton from chia.wallet.wallet_rpc_api import WalletRpcApi from chia.wallet.wallet_service import WalletService @@ -222,7 +222,9 @@ async def check_singleton_confirmed(dl: DataLayer, store_id: bytes32) -> bool: async def process_block_and_check_offer_validity(offer: TradingOffer, offer_setup: OfferSetup) -> bool: await offer_setup.full_node_api.farm_blocks_to_puzzlehash(count=1, guarantee_transaction_blocks=True) - return (await offer_setup.maker.data_layer.wallet_rpc.check_offer_validity(offer=offer))[1] + return ( + await offer_setup.maker.data_layer.wallet_rpc.check_offer_validity(CheckOfferValidity(offer=offer.to_bech32())) + ).valid async def run_cli_cmd(*args: str, root_path: Path) -> asyncio.subprocess.Process: @@ -1825,9 +1827,11 @@ async def test_make_and_cancel_offer(offer_setup: OfferSetup, reference: MakeAnd for _ in range(10): if not ( await offer_setup.maker.data_layer.wallet_rpc.check_offer_validity( - offer=TradingOffer.from_bytes(hexstr_to_bytes(maker_response["offer"]["offer"])), + CheckOfferValidity( + offer=TradingOffer.from_bytes(hexstr_to_bytes(maker_response["offer"]["offer"])).to_bech32() + ), ) - )[1]: + ).valid: break await offer_setup.full_node_api.farm_blocks_to_puzzlehash(count=1, guarantee_transaction_blocks=True) await asyncio.sleep(0.5) diff --git a/chia/_tests/wallet/rpc/test_wallet_rpc.py b/chia/_tests/wallet/rpc/test_wallet_rpc.py index 8c5082d3b5df..6669dcad454d 100644 --- a/chia/_tests/wallet/rpc/test_wallet_rpc.py +++ b/chia/_tests/wallet/rpc/test_wallet_rpc.py @@ -104,7 +104,12 @@ from chia.wallet.wallet_protocol import WalletProtocol from chia.wallet.wallet_request_types import ( AddKey, + CATAssetIDToName, + CATGetAssetID, + CATGetName, + CATSetName, CheckDeleteKey, + CheckOfferValidity, ClawbackPuzzleDecoratorOverride, CombineCoins, DefaultCAT, @@ -144,6 +149,7 @@ PushTX, RoyaltyAsset, SelectCoins, + SendNotification, SendTransaction, SetWalletResyncOnStartup, SpendClawbackCoins, @@ -1202,25 +1208,23 @@ async def test_cat_endpoints(wallet_environments: WalletTestFramework, wallet_ty env_0.wallet_states[uint32(env_0.wallet_aliases["cat0"])].balance.to_json_dict().items() <= (await env_0.rpc_client.get_wallet_balance(GetWalletBalance(cat_0_id))).wallet_balance.to_json_dict().items() ) - asset_id = await env_0.rpc_client.get_cat_asset_id(cat_0_id) - assert (await env_0.rpc_client.get_cat_name(cat_0_id)) == wallet_type.default_wallet_name_for_unknown_cat( - asset_id.hex() - ) - await env_0.rpc_client.set_cat_name(cat_0_id, "My cat") - assert (await env_0.rpc_client.get_cat_name(cat_0_id)) == "My cat" - result = await env_0.rpc_client.cat_asset_id_to_name(asset_id) - assert result is not None - wid, name = result - assert wid == cat_0_id - assert name == "My cat" - result = await env_0.rpc_client.cat_asset_id_to_name(bytes32.zeros) - assert result is None + asset_id = (await env_0.rpc_client.get_cat_asset_id(CATGetAssetID(cat_0_id))).asset_id + assert ( + await env_0.rpc_client.get_cat_name(CATGetName(cat_0_id)) + ).name == wallet_type.default_wallet_name_for_unknown_cat(asset_id.hex()) + await env_0.rpc_client.set_cat_name(CATSetName(cat_0_id, "My cat")) + assert (await env_0.rpc_client.get_cat_name(CATGetName(cat_0_id))).name == "My cat" + asset_to_name_response = await env_0.rpc_client.cat_asset_id_to_name(CATAssetIDToName(asset_id)) + assert asset_to_name_response.wallet_id == cat_0_id + assert asset_to_name_response.name == "My cat" + asset_to_name_response = await env_0.rpc_client.cat_asset_id_to_name(CATAssetIDToName(bytes32.zeros)) + assert asset_to_name_response.name is None verified_asset_id = next(iter(DEFAULT_CATS.items()))[1]["asset_id"] - result = await env_0.rpc_client.cat_asset_id_to_name(bytes32.from_hexstr(verified_asset_id)) - assert result is not None - should_be_none, name = result - assert should_be_none is None - assert name == next(iter(DEFAULT_CATS.items()))[1]["name"] + asset_to_name_response = await env_0.rpc_client.cat_asset_id_to_name( + CATAssetIDToName(bytes32.from_hexstr(verified_asset_id)) + ) + assert asset_to_name_response.wallet_id is None + assert asset_to_name_response.name == next(iter(DEFAULT_CATS.items()))[1]["name"] # Creates a second wallet with the same CAT res = await env_1.rpc_client.create_wallet_for_existing_cat(asset_id) @@ -1394,8 +1398,8 @@ async def test_cat_endpoints(wallet_environments: WalletTestFramework, wallet_ty await env_0.wallet_state_manager.interested_store.add_unacknowledged_token( asset_id, "Unknown", uint32(10000), bytes32(b"\00" * 32) ) - cats = await env_0.rpc_client.get_stray_cats() - assert len(cats) == 1 + stray_cats_response = await env_0.rpc_client.get_stray_cats() + assert len(stray_cats_response.stray_cats) == 1 # Test CAT coin selection select_coins_response = await env_0.rpc_client.select_coins( @@ -1563,8 +1567,9 @@ async def test_offer_endpoints(wallet_environments: WalletTestFramework, wallet_ } assert advanced_summary == summary - id, _valid = await env_1.rpc_client.check_offer_validity(offer) - assert id == offer.name() + offer_validity_response = await env_1.rpc_client.check_offer_validity(CheckOfferValidity(offer.to_bech32())) + assert offer_validity_response.id == offer.name() + assert offer_validity_response.valid all_offers = await env_1.rpc_client.get_all_offers(file_contents=True) assert len(all_offers) == 1 @@ -2621,21 +2626,25 @@ async def test_notification_rpcs(wallet_rpc_environment: WalletRpcTestEnvironmen env.wallet_2.node.config["enable_notifications"] = True env.wallet_2.node.config["required_notification_amount"] = 100000000000 async with wallet_2.wallet_state_manager.new_action_scope(DEFAULT_TX_CONFIG, push=True) as action_scope: - tx = await client.send_notification( - await action_scope.get_puzzle_hash(wallet_2.wallet_state_manager), - b"hello", - uint64(100000000000), - fee=uint64(100000000000), + response = await client.send_notification( + SendNotification( + target=(await action_scope.get_puzzle_hash(wallet_2.wallet_state_manager)), + message=b"hello", + amount=uint64(100000000000), + fee=uint64(100000000000), + push=True, + ), + tx_config=DEFAULT_TX_CONFIG, ) - assert tx.spend_bundle is not None + assert response.tx.spend_bundle is not None await time_out_assert( 5, full_node_api.full_node.mempool_manager.get_spendbundle, - tx.spend_bundle, - tx.spend_bundle.name(), + response.tx.spend_bundle, + response.tx.spend_bundle.name(), ) - await farm_transaction(full_node_api, wallet_node, tx.spend_bundle) + await farm_transaction(full_node_api, wallet_node, response.tx.spend_bundle) await time_out_assert(20, env.wallet_2.wallet.get_confirmed_balance, uint64(100000000000)) notification = (await client_2.get_notifications(GetNotifications())).notifications[0] @@ -2648,21 +2657,25 @@ async def test_notification_rpcs(wallet_rpc_environment: WalletRpcTestEnvironmen assert [] == (await client_2.get_notifications(GetNotifications([notification.id]))).notifications async with wallet_2.wallet_state_manager.new_action_scope(DEFAULT_TX_CONFIG, push=True) as action_scope: - tx = await client.send_notification( - await action_scope.get_puzzle_hash(wallet_2.wallet_state_manager), - b"hello", - uint64(100000000000), - fee=uint64(100000000000), + response = await client.send_notification( + SendNotification( + target=(await action_scope.get_puzzle_hash(wallet_2.wallet_state_manager)), + message=b"hello", + amount=uint64(100000000000), + fee=uint64(100000000000), + push=True, + ), + tx_config=DEFAULT_TX_CONFIG, ) - assert tx.spend_bundle is not None + assert response.tx.spend_bundle is not None await time_out_assert( 5, full_node_api.full_node.mempool_manager.get_spendbundle, - tx.spend_bundle, - tx.spend_bundle.name(), + response.tx.spend_bundle, + response.tx.spend_bundle.name(), ) - await farm_transaction(full_node_api, wallet_node, tx.spend_bundle) + await farm_transaction(full_node_api, wallet_node, response.tx.spend_bundle) await time_out_assert(20, env.wallet_2.wallet.get_confirmed_balance, uint64(200000000000)) notification = (await client_2.get_notifications(GetNotifications())).notifications[0] diff --git a/chia/cmds/wallet_funcs.py b/chia/cmds/wallet_funcs.py index f111500df6e3..1f05cee22e3c 100644 --- a/chia/cmds/wallet_funcs.py +++ b/chia/cmds/wallet_funcs.py @@ -45,6 +45,10 @@ from chia.wallet.vc_wallet.vc_store import VCProofs from chia.wallet.wallet_coin_store import GetCoinRecords from chia.wallet.wallet_request_types import ( + CATAssetIDToName, + CATAssetIDToNameResponse, + CATGetName, + CATSetName, CATSpendResponse, ClawbackPuzzleDecoratorOverride, DeleteNotifications, @@ -74,6 +78,7 @@ NFTSetNFTDID, NFTTransferNFT, RoyaltyAsset, + SendNotification, SendTransaction, SendTransactionResponse, SignMessageByAddress, @@ -91,7 +96,7 @@ ) from chia.wallet.wallet_rpc_client import WalletRpcClient -CATNameResolver = Callable[[bytes32], Awaitable[Optional[tuple[Optional[uint32], str]]]] +CATNameResolver = Callable[[CATAssetIDToName], Awaitable[CATAssetIDToNameResponse]] transaction_type_descriptions = { TransactionType.INCOMING_TX: "received", @@ -183,7 +188,7 @@ async def get_unit_name_for_wallet_id( }: name: str = config["network_overrides"]["config"][config["selected_network"]]["address_prefix"].upper() elif wallet_type in {WalletType.CAT, WalletType.CRCAT, WalletType.RCAT}: - name = await wallet_client.get_cat_name(wallet_id=wallet_id) + name = (await wallet_client.get_cat_name(CATGetName(wallet_id=uint32(wallet_id)))).name else: raise LookupError(f"Operation is not supported for Wallet type {wallet_type.name}") @@ -468,21 +473,19 @@ async def add_token( root_path: pathlib.Path, wallet_rpc_port: Optional[int], fp: Optional[int], asset_id: bytes32, token_name: str ) -> None: async with get_wallet_client(root_path, wallet_rpc_port, fp) as (wallet_client, fingerprint, _): - existing_info: Optional[tuple[Optional[uint32], str]] = await wallet_client.cat_asset_id_to_name(asset_id) - if existing_info is None: - wallet_id = None - old_name = None - else: - wallet_id, old_name = existing_info + existing_info = await wallet_client.cat_asset_id_to_name(CATAssetIDToName(asset_id)) - if wallet_id is None: + if existing_info.wallet_id is None: response = await wallet_client.create_wallet_for_existing_cat(asset_id) wallet_id = response["wallet_id"] - await wallet_client.set_cat_name(wallet_id, token_name) + await wallet_client.set_cat_name(CATSetName(wallet_id, token_name)) print(f"Successfully added {token_name} with wallet id {wallet_id} on key {fingerprint}") else: - await wallet_client.set_cat_name(wallet_id, token_name) - print(f"Successfully renamed {old_name} with wallet_id {wallet_id} on key {fingerprint} to {token_name}") + await wallet_client.set_cat_name(CATSetName(existing_info.wallet_id, token_name)) + print( + f"Successfully renamed {existing_info.name} with wallet_id {existing_info.wallet_id}" + f" on key {fingerprint} to {token_name}" + ) async def make_offer( @@ -511,9 +514,9 @@ async def make_offer( try: b32_id = bytes32.from_hexstr(name) id: Union[uint32, str] = b32_id.hex() - result = await wallet_client.cat_asset_id_to_name(b32_id) - if result is not None: - name = result[1] + result = await wallet_client.cat_asset_id_to_name(CATAssetIDToName(b32_id)) + if result.name is not None: + name = result.name else: name = "Unknown CAT" unit = units["cat"] @@ -569,7 +572,7 @@ async def make_offer( name = "XCH" unit = units["chia"] else: - name = await wallet_client.get_cat_name(id) + name = (await wallet_client.get_cat_name(CATGetName(id))).name unit = units["cat"] if item in offers: fungible_assets.append(FungibleAsset(name, uint64(abs(int(Decimal(amount) * unit))))) @@ -677,10 +680,10 @@ async def print_offer_summary( description = " [Typically represents change returned from the included fee]" else: unit = units["cat"] - result = await cat_name_resolver(bytes32.from_hexstr(asset_id)) - if result is not None: - wid = str(result[0]) - name = result[1] + result = await cat_name_resolver(CATAssetIDToName(bytes32.from_hexstr(asset_id))) + if result.name is not None: + wid = str(result.wallet_id) + name = result.name output: str = f" - {name}" mojo_str: str = f"{mojo_amount} {'mojo' if mojo_amount == 1 else 'mojos'}" if len(wid) > 0: @@ -843,9 +846,9 @@ async def take_offer( if fungible_asset_id is None: nft_royalty_currency = network_xch else: - result = await wallet_client.cat_asset_id_to_name(fungible_asset_id) - if result is not None: - nft_royalty_currency = result[1] + result = await wallet_client.cat_asset_id_to_name(CATAssetIDToName(fungible_asset_id)) + if result.name is not None: + nft_royalty_currency = result.name fungible_assets.append( FungibleAsset(nft_royalty_currency, uint64(requested[fungible_asset_id_str])) ) @@ -1576,19 +1579,25 @@ async def send_notification( async with get_wallet_client(root_path, wallet_rpc_port, fp) as (wallet_client, fingerprint, _): amount: uint64 = cli_amount.convert_amount(units["chia"]) - tx = await wallet_client.send_notification( - address.puzzle_hash, - message, - amount, - fee, - push=push, + response = await wallet_client.send_notification( + SendNotification( + address.puzzle_hash, + message, + amount, + fee=fee, + push=push, + ), + tx_config=DEFAULT_TX_CONFIG, timelock_info=condition_valid_times, ) if push: print("Notification sent successfully.") - print(f"To get status, use command: chia wallet get_transaction -f {fingerprint} -tx 0x{tx.name}") - return [tx] + print( + "To get status, use command: chia wallet get_transaction" + f" -f {fingerprint} -tx 0x{response.transactions[0].name}" + ) + return response.transactions async def get_notifications( diff --git a/chia/wallet/wallet_request_types.py b/chia/wallet/wallet_request_types.py index d40fcfc8df75..2bc67bcabc44 100644 --- a/chia/wallet/wallet_request_types.py +++ b/chia/wallet/wallet_request_types.py @@ -594,6 +594,86 @@ class GetCATListResponse(Streamable): cat_list: list[DefaultCAT] +@streamable +@dataclass(frozen=True) +class CATSetName(Streamable): + wallet_id: uint32 + name: str + + +@streamable +@dataclass(frozen=True) +class CATSetNameResponse(Streamable): + wallet_id: uint32 + + +@streamable +@dataclass(frozen=True) +class CATGetName(Streamable): + wallet_id: uint32 + + +@streamable +@dataclass(frozen=True) +class CATGetNameResponse(Streamable): + wallet_id: uint32 + name: str + + +@streamable +@dataclass(frozen=True) +class StrayCAT(Streamable): + asset_id: bytes32 + name: str + first_seen_height: uint32 + sender_puzzle_hash: bytes32 + + +@streamable +@dataclass(frozen=True) +class GetStrayCATsResponse(Streamable): + stray_cats: list[StrayCAT] + + +@streamable +@dataclass(frozen=True) +class CATGetAssetID(Streamable): + wallet_id: uint32 + + +@streamable +@dataclass(frozen=True) +class CATGetAssetIDResponse(Streamable): + wallet_id: uint32 + asset_id: bytes32 + + +@streamable +@dataclass(frozen=True) +class CATAssetIDToName(Streamable): + asset_id: bytes32 + + +@streamable +@dataclass(frozen=True) +class CATAssetIDToNameResponse(Streamable): + wallet_id: Optional[uint32] + name: Optional[str] + + +@streamable +@dataclass(frozen=True) +class CheckOfferValidity(Streamable): + offer: str + + +@streamable +@dataclass(frozen=True) +class CheckOfferValidityResponse(Streamable): + valid: bool + id: bytes32 + + @streamable @dataclass(frozen=True) class DIDSetWalletName(Streamable): @@ -1234,6 +1314,20 @@ class SpendClawbackCoinsResponse(TransactionEndpointResponse): transaction_ids: list[bytes32] +@streamable +@dataclass(frozen=True) +class SendNotification(TransactionEndpointRequest): + target: bytes32 = field(default_factory=default_raise) + message: bytes = field(default_factory=default_raise) + amount: uint64 = uint64(0) + + +@streamable +@dataclass(frozen=True) +class SendNotificationResponse(TransactionEndpointResponse): + tx: TransactionRecord + + @streamable @dataclass(frozen=True) class PushTransactions(TransactionEndpointRequest): diff --git a/chia/wallet/wallet_rpc_api.py b/chia/wallet/wallet_rpc_api.py index 0d4a97610ff2..254784913c92 100644 --- a/chia/wallet/wallet_rpc_api.py +++ b/chia/wallet/wallet_rpc_api.py @@ -112,12 +112,23 @@ ApplySignatures, ApplySignaturesResponse, BalanceResponse, + CATAssetIDToName, + CATAssetIDToNameResponse, + CATGetAssetID, + CATGetAssetIDResponse, + CATGetName, + CATGetNameResponse, + CATSetName, + CATSetNameResponse, CheckDeleteKey, CheckDeleteKeyResponse, + CheckOfferValidity, + CheckOfferValidityResponse, CombineCoins, CombineCoinsResponse, CreateNewDL, CreateNewDLResponse, + DefaultCAT, DeleteKey, DeleteNotifications, DeleteUnconfirmedTransactions, @@ -172,6 +183,7 @@ GatherSigningInfo, GatherSigningInfoResponse, GenerateMnemonicResponse, + GetCATListResponse, GetCoinRecordsByNames, GetCoinRecordsByNamesResponse, GetCurrentDerivationIndexResponse, @@ -181,12 +193,14 @@ GetNextAddressResponse, GetNotifications, GetNotificationsResponse, + GetOffersCountResponse, GetPrivateKey, GetPrivateKeyFormat, GetPrivateKeyResponse, GetPublicKeysResponse, GetSpendableCoins, GetSpendableCoinsResponse, + GetStrayCATsResponse, GetSyncStatusResponse, GetTimestampForHeight, GetTimestampForHeightResponse, @@ -248,6 +262,8 @@ PWStatusResponse, SelectCoins, SelectCoinsResponse, + SendNotification, + SendNotificationResponse, SendTransaction, SendTransactionResponse, SetWalletResyncOnStartup, @@ -259,6 +275,7 @@ SpendClawbackCoinsResponse, SplitCoins, SplitCoinsResponse, + StrayCAT, SubmitTransactions, SubmitTransactionsResponse, TransactionRecordWithMetadata, @@ -1937,22 +1954,24 @@ async def delete_notifications(self, request: DeleteNotifications) -> Empty: return Empty() @tx_endpoint(push=True) + @marshal async def send_notification( self, - request: dict[str, Any], + request: SendNotification, action_scope: WalletActionScope, extra_conditions: tuple[Condition, ...] = tuple(), - ) -> EndpointResult: + ) -> SendNotificationResponse: await self.service.wallet_state_manager.notification_manager.send_new_notification( - bytes32.from_hexstr(request["target"]), - bytes.fromhex(request["message"]), - uint64(request["amount"]), + request.target, + request.message, + request.amount, action_scope, - request.get("fee", uint64(0)), + request.fee, extra_conditions=extra_conditions, ) - return {"tx": None, "transactions": None} # tx_endpoint wrapper will take care of this + # tx_endpoint will take care of these default values + return SendNotificationResponse([], [], tx=REPLACEABLE_TRANSACTION_RECORD) @marshal async def verify_signature(self, request: VerifySignature) -> VerifySignatureResponse: @@ -2091,29 +2110,31 @@ async def sign_message_by_id(self, request: SignMessageByID) -> SignMessageByIDR # CATs and Trading ########################################################################################## - async def get_cat_list(self, request: dict[str, Any]) -> EndpointResult: - return {"cat_list": list(DEFAULT_CATS.values())} + @marshal + async def get_cat_list(self, request: Empty) -> GetCATListResponse: + return GetCATListResponse([DefaultCAT.from_json_dict(default_cat) for default_cat in DEFAULT_CATS.values()]) - async def cat_set_name(self, request: dict[str, Any]) -> EndpointResult: - wallet_id = uint32(request["wallet_id"]) - wallet = self.service.wallet_state_manager.get_wallet(id=wallet_id, required_type=CATWallet) - await wallet.set_name(str(request["name"])) - return {"wallet_id": wallet_id} + @marshal + async def cat_set_name(self, request: CATSetName) -> CATSetNameResponse: + wallet = self.service.wallet_state_manager.get_wallet(id=request.wallet_id, required_type=CATWallet) + await wallet.set_name(request.name) + return CATSetNameResponse(wallet_id=request.wallet_id) - async def cat_get_name(self, request: dict[str, Any]) -> EndpointResult: - wallet_id = uint32(request["wallet_id"]) - wallet = self.service.wallet_state_manager.get_wallet(id=wallet_id, required_type=CATWallet) + @marshal + async def cat_get_name(self, request: CATGetName) -> CATGetNameResponse: + wallet = self.service.wallet_state_manager.get_wallet(id=request.wallet_id, required_type=CATWallet) name: str = wallet.get_name() - return {"wallet_id": wallet_id, "name": name} + return CATGetNameResponse(wallet_id=request.wallet_id, name=name) - async def get_stray_cats(self, request: dict[str, Any]) -> EndpointResult: + @marshal + async def get_stray_cats(self, request: Empty) -> GetStrayCATsResponse: """ Get a list of all unacknowledged CATs :param request: RPC request :return: A list of unacknowledged CATs """ cats = await self.service.wallet_state_manager.interested_store.get_unacknowledged_tokens() - return {"stray_cats": cats} + return GetStrayCATsResponse(stray_cats=[StrayCAT.from_json_dict(cat) for cat in cats]) @tx_endpoint(push=True) async def cat_spend( @@ -2204,21 +2225,22 @@ async def cat_spend( "transaction_id": None, # tx_endpoint wrapper will take care of this } - async def cat_get_asset_id(self, request: dict[str, Any]) -> EndpointResult: - wallet_id = uint32(request["wallet_id"]) - wallet = self.service.wallet_state_manager.get_wallet(id=wallet_id, required_type=CATWallet) + @marshal + async def cat_get_asset_id(self, request: CATGetAssetID) -> CATGetAssetIDResponse: + wallet = self.service.wallet_state_manager.get_wallet(id=request.wallet_id, required_type=CATWallet) asset_id: str = wallet.get_asset_id() - return {"asset_id": asset_id, "wallet_id": wallet_id} + return CATGetAssetIDResponse(asset_id=bytes32.from_hexstr(asset_id), wallet_id=request.wallet_id) - async def cat_asset_id_to_name(self, request: dict[str, Any]) -> EndpointResult: - wallet = await self.service.wallet_state_manager.get_wallet_for_asset_id(request["asset_id"]) + @marshal + async def cat_asset_id_to_name(self, request: CATAssetIDToName) -> CATAssetIDToNameResponse: + wallet = await self.service.wallet_state_manager.get_wallet_for_asset_id(request.asset_id.hex()) if wallet is None: - if request["asset_id"] in DEFAULT_CATS: - return {"wallet_id": None, "name": DEFAULT_CATS[request["asset_id"]]["name"]} + if request.asset_id.hex() in DEFAULT_CATS: + return CATAssetIDToNameResponse(wallet_id=None, name=DEFAULT_CATS[request.asset_id.hex()]["name"]) else: - raise ValueError("The asset ID specified does not belong to a wallet") + return CATAssetIDToNameResponse(wallet_id=None, name=None) else: - return {"wallet_id": wallet.id(), "name": (wallet.get_name())} + return CATAssetIDToNameResponse(wallet_id=wallet.id(), name=wallet.get_name()) @tx_endpoint(push=False) async def create_offer_for_ids( @@ -2342,15 +2364,14 @@ async def get_offer_summary(self, request: dict[str, Any]) -> EndpointResult: }, } - async def check_offer_validity(self, request: dict[str, Any]) -> EndpointResult: - offer_hex: str = request["offer"] - - offer = Offer.from_bech32(offer_hex) + @marshal + async def check_offer_validity(self, request: CheckOfferValidity) -> CheckOfferValidityResponse: + offer = Offer.from_bech32(request.offer) peer = self.service.get_full_node_peer() - return { - "valid": (await self.service.wallet_state_manager.trade_manager.check_offer_validity(offer, peer)), - "id": offer.name(), - } + return CheckOfferValidityResponse( + valid=(await self.service.wallet_state_manager.trade_manager.check_offer_validity(offer, peer)), + id=offer.name(), + ) @tx_endpoint(push=True) async def take_offer( @@ -2436,12 +2457,15 @@ async def get_all_offers(self, request: dict[str, Any]) -> EndpointResult: return {"trade_records": result, "offers": offer_values} - async def get_offers_count(self, request: dict[str, Any]) -> EndpointResult: + @marshal + async def get_offers_count(self, request: Empty) -> GetOffersCountResponse: trade_mgr = self.service.wallet_state_manager.trade_manager (total, my_offers_count, taken_offers_count) = await trade_mgr.trade_store.get_trades_count() - return {"total": total, "my_offers_count": my_offers_count, "taken_offers_count": taken_offers_count} + return GetOffersCountResponse( + total=uint16(total), my_offers_count=uint16(my_offers_count), taken_offers_count=uint16(taken_offers_count) + ) @tx_endpoint(push=True) async def cancel_offer( diff --git a/chia/wallet/wallet_rpc_client.py b/chia/wallet/wallet_rpc_client.py index 14d2a2fcd515..a417427febdb 100644 --- a/chia/wallet/wallet_rpc_client.py +++ b/chia/wallet/wallet_rpc_client.py @@ -1,6 +1,6 @@ from __future__ import annotations -from typing import Any, Optional, Union, cast +from typing import Any, Optional, Union from chia_rs.sized_bytes import bytes32 from chia_rs.sized_ints import uint32, uint64 @@ -24,9 +24,19 @@ ApplySignaturesResponse, CancelOfferResponse, CancelOffersResponse, + CATAssetIDToName, + CATAssetIDToNameResponse, + CATGetAssetID, + CATGetAssetIDResponse, + CATGetName, + CATGetNameResponse, + CATSetName, + CATSetNameResponse, CATSpendResponse, CheckDeleteKey, CheckDeleteKeyResponse, + CheckOfferValidity, + CheckOfferValidityResponse, CombineCoins, CombineCoinsResponse, CreateNewDL, @@ -102,6 +112,7 @@ GetPublicKeysResponse, GetSpendableCoins, GetSpendableCoinsResponse, + GetStrayCATsResponse, GetSyncStatusResponse, GetTimestampForHeight, GetTimestampForHeightResponse, @@ -162,6 +173,8 @@ PWStatusResponse, SelectCoins, SelectCoinsResponse, + SendNotification, + SendNotificationResponse, SendTransaction, SendTransactionMultiResponse, SendTransactionResponse, @@ -622,37 +635,20 @@ async def create_wallet_for_existing_cat(self, asset_id: bytes) -> dict[str, Any request = {"wallet_type": "cat_wallet", "asset_id": asset_id.hex(), "mode": "existing"} return await self.fetch("create_new_wallet", request) - async def get_cat_asset_id(self, wallet_id: int) -> bytes32: - request = {"wallet_id": wallet_id} - return bytes32.from_hexstr((await self.fetch("cat_get_asset_id", request))["asset_id"]) - - async def get_stray_cats(self) -> list[dict[str, Any]]: - response = await self.fetch("get_stray_cats", {}) - # TODO: casting due to lack of type checked deserialization - return cast(list[dict[str, Any]], response["stray_cats"]) - - async def cat_asset_id_to_name(self, asset_id: bytes32) -> Optional[tuple[Optional[uint32], str]]: - request = {"asset_id": asset_id.hex()} - try: - res = await self.fetch("cat_asset_id_to_name", request) - except ValueError: # This happens if the asset_id is unknown - return None - - wallet_id: Optional[uint32] = None if res["wallet_id"] is None else uint32(res["wallet_id"]) - return wallet_id, res["name"] - - async def get_cat_name(self, wallet_id: int) -> str: - request = {"wallet_id": wallet_id} - response = await self.fetch("cat_get_name", request) - # TODO: casting due to lack of type checked deserialization - return cast(str, response["name"]) - - async def set_cat_name(self, wallet_id: int, name: str) -> None: - request: dict[str, Any] = { - "wallet_id": wallet_id, - "name": name, - } - await self.fetch("cat_set_name", request) + async def get_cat_asset_id(self, request: CATGetAssetID) -> CATGetAssetIDResponse: + return CATGetAssetIDResponse.from_json_dict(await self.fetch("cat_get_asset_id", request.to_json_dict())) + + async def get_stray_cats(self) -> GetStrayCATsResponse: + return GetStrayCATsResponse.from_json_dict(await self.fetch("get_stray_cats", {})) + + async def cat_asset_id_to_name(self, request: CATAssetIDToName) -> CATAssetIDToNameResponse: + return CATAssetIDToNameResponse.from_json_dict(await self.fetch("cat_asset_id_to_name", request.to_json_dict())) + + async def get_cat_name(self, request: CATGetName) -> CATGetNameResponse: + return CATGetNameResponse.from_json_dict(await self.fetch("cat_get_name", request.to_json_dict())) + + async def set_cat_name(self, request: CATSetName) -> CATSetNameResponse: + return CATSetNameResponse.from_json_dict(await self.fetch("cat_set_name", request.to_json_dict())) async def cat_spend( self, @@ -734,9 +730,10 @@ async def get_offer_summary( res = await self.fetch("get_offer_summary", {"offer": offer.to_bech32(), "advanced": advanced}) return bytes32.from_hexstr(res["id"]), res["summary"] - async def check_offer_validity(self, offer: Offer) -> tuple[bytes32, bool]: - res = await self.fetch("check_offer_validity", {"offer": offer.to_bech32()}) - return bytes32.from_hexstr(res["id"]), res["valid"] + async def check_offer_validity(self, request: CheckOfferValidity) -> CheckOfferValidityResponse: + return CheckOfferValidityResponse.from_json_dict( + await self.fetch("check_offer_validity", request.to_json_dict()) + ) async def take_offer( self, @@ -1088,27 +1085,16 @@ async def delete_notifications(self, request: DeleteNotifications) -> None: async def send_notification( self, - target: bytes32, - msg: bytes, - amount: uint64, - fee: uint64 = uint64(0), + request: SendNotification, + tx_config: TXConfig, extra_conditions: tuple[Condition, ...] = tuple(), timelock_info: ConditionValidTimes = ConditionValidTimes(), - push: bool = True, - ) -> TransactionRecord: - response = await self.fetch( - "send_notification", - { - "target": target.hex(), - "message": msg.hex(), - "amount": amount, - "fee": fee, - "extra_conditions": conditions_to_json_dicts(extra_conditions), - "push": push, - **timelock_info.to_json_dict(), - }, + ) -> SendNotificationResponse: + return SendNotificationResponse.from_json_dict( + await self.fetch( + "send_notification", request.json_serialize_for_transport(tx_config, extra_conditions, timelock_info) + ) ) - return TransactionRecord.from_json_dict(response["tx"]) async def sign_message_by_address(self, request: SignMessageByAddress) -> SignMessageByAddressResponse: return SignMessageByAddressResponse.from_json_dict(