diff --git a/chia/_tests/cmds/wallet/test_wallet.py b/chia/_tests/cmds/wallet/test_wallet.py index b217b879156b..d0e97cfc57ff 100644 --- a/chia/_tests/cmds/wallet/test_wallet.py +++ b/chia/_tests/cmds/wallet/test_wallet.py @@ -29,6 +29,7 @@ from chia.types.signing_mode import SigningMode from chia.util.bech32m import encode_puzzle_hash from chia.wallet.conditions import Condition, ConditionValidTimes +from chia.wallet.puzzle_drivers import PuzzleInfo from chia.wallet.trade_record import TradeRecord from chia.wallet.trading.offer import Offer from chia.wallet.trading.trade_status import TradeStatus @@ -49,6 +50,7 @@ CATSpend, CATSpendResponse, ClawbackPuzzleDecoratorOverride, + CreateOfferForIDs, CreateOfferForIDsResponse, DeleteUnconfirmedTransactions, ExtendDerivationIndex, @@ -79,6 +81,29 @@ ) from chia.wallet.wallet_spend_bundle import WalletSpendBundle +TEMP = PuzzleInfo( + { + "type": "singleton", + "launcher_id": "0x0101010101010101010101010101010101010101010101010101010101010101", + "launcher_ph": "0xeff07522495060c066f66f32acc2a77e3a3e737aca8baea4d1a64ea4cdc13da9", + "also": { + "type": "metadata", + "metadata": "", + "updater_hash": "0x0707070707070707070707070707070707070707070707070707070707070707", + "also": { + "type": "ownership", + "owner": "()", + "transfer_program": { + "type": "royalty transfer program", + "launcher_id": "0x0101010101010101010101010101010101010101010101010101010101010101", + "royalty_address": "0x0303030303030303030303030303030303030303030303030303030303030303", + "royalty_percentage": "1000", + }, + }, + }, + } +) + test_offer_file_path = importlib_resources.files(__name__.rpartition(".")[0]).joinpath("test_offer.toffer") test_offer_file_bech32 = test_offer_file_path.read_text(encoding="utf-8") test_offer_id: str = "0xdfb7e8643376820ec995b0bcdb3fc1f764c16b814df5e074631263fcf1e00839" @@ -763,17 +788,22 @@ def test_make_offer(capsys: object, get_test_cli_clients: tuple[TestRpcClients, class MakeOfferRpcClient(TestWalletRpcClient): async def create_offer_for_ids( self, - offer_dict: dict[uint32, int], + request: CreateOfferForIDs, tx_config: TXConfig, - driver_dict: Optional[dict[str, Any]] = None, - solver: Optional[dict[str, Any]] = None, - fee: uint64 = uint64(0), - validate_only: bool = False, + extra_conditions: tuple[Condition, ...] = tuple(), timelock_info: ConditionValidTimes = ConditionValidTimes(), ) -> CreateOfferForIDsResponse: self.add_to_log( "create_offer_for_ids", - (offer_dict, tx_config, driver_dict, solver, fee, validate_only, timelock_info), + ( + request.offer, + tx_config, + request.driver_dict, + request.solver, + request.fee, + request.validate_only, + timelock_info, + ), ) created_offer = Offer({}, WalletSpendBundle([], G2Element()), {}) @@ -866,35 +896,39 @@ async def create_offer_for_ids( "create_offer_for_ids": [ ( { - 1: -10000000000000, - 3: -100000, - "0404040404040404040404040404040404040404040404040404040404040404": -100000, - "0202020202020202020202020202020202020202020202020202020202020202": 10000, - "0101010101010101010101010101010101010101010101010101010101010101": 1, + "1": "-10000000000000", + "3": "-100000", + "0404040404040404040404040404040404040404040404040404040404040404": "-100000", + "0202020202020202020202020202020202020202020202020202020202020202": "10000", + "0101010101010101010101010101010101010101010101010101010101010101": "1", }, DEFAULT_TX_CONFIG.override(reuse_puzhash=True), { - "0101010101010101010101010101010101010101010101010101010101010101": { - "type": "singleton", - "launcher_id": "0x0101010101010101010101010101010101010101010101010101010101010101", - "launcher_ph": "0xeff07522495060c066f66f32acc2a77e3a3e737aca8baea4d1a64ea4cdc13da9", - "also": { - "type": "metadata", - "metadata": "", - "updater_hash": "0x0707070707070707070707070707070707070707070707070707070707070707", + bytes32([1] * 32): PuzzleInfo( + { + "type": "singleton", + "launcher_id": "0x0101010101010101010101010101010101010101010101010101010101010101", + "launcher_ph": "0xeff07522495060c066f66f32acc2a77e3a3e737aca8baea4d1a64ea4cdc13da9", "also": { - "type": "ownership", - "owner": "()", - "transfer_program": { - "type": "royalty transfer program", - "launcher_id": "0x0101010101010101010101010101010101010101010101010101010101010101", - "royalty_address": "0x0303030303030303030303030303030303030303030" - "303030303030303030303", - "royalty_percentage": "1000", + "type": "metadata", + "metadata": "", + "updater_hash": "0x0707070707070707070707070707070707070707070707070707070707070707", + "also": { + "type": "ownership", + "owner": "()", + "transfer_program": { + "type": "royalty transfer program", + "launcher_id": ( + "0x0101010101010101010101010101010101010101010101010101010101010101" + ), + "royalty_address": "0x0303030303030303030303030303030303030303030" + "303030303030303030303", + "royalty_percentage": "1000", + }, }, }, - }, - } + } + ) }, None, 500000000000, diff --git a/chia/_tests/wallet/rpc/test_wallet_rpc.py b/chia/_tests/wallet/rpc/test_wallet_rpc.py index f4d1d4b1bc81..2e15fa7ede5b 100644 --- a/chia/_tests/wallet/rpc/test_wallet_rpc.py +++ b/chia/_tests/wallet/rpc/test_wallet_rpc.py @@ -80,6 +80,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.puzzle_drivers import PuzzleInfo from chia.wallet.puzzles.clawback.metadata import AutoClaimSettings from chia.wallet.puzzles.p2_delegated_puzzle_or_hidden_puzzle import puzzle_hash_for_pk from chia.wallet.signer_protocol import UnsignedTransaction @@ -113,6 +114,7 @@ CheckOfferValidity, ClawbackPuzzleDecoratorOverride, CombineCoins, + CreateOfferForIDs, DefaultCAT, DeleteKey, DeleteNotifications, @@ -1546,28 +1548,29 @@ async def test_offer_endpoints(wallet_environments: WalletTestFramework, wallet_ ) # Create an offer of 5 chia for one CAT await env_1.rpc_client.create_offer_for_ids( - {uint32(1): -5, cat_asset_id.hex(): 1}, wallet_environments.tx_config, validate_only=True + CreateOfferForIDs(offer={str(1): "-5", cat_asset_id.hex(): "1"}, validate_only=True), + tx_config=wallet_environments.tx_config, ) all_offers = await env_1.rpc_client.get_all_offers() assert len(all_offers) == 0 - driver_dict: dict[str, Any] = { - cat_asset_id.hex(): { - "type": "CAT", - "tail": "0x" + cat_asset_id.hex(), - **( - {} - if wallet_type is CATWallet - else {"also": {"type": "revocation layer", "hidden_puzzle_hash": "0x" + bytes32.zeros.hex()}} - ), - } + driver_dict = { + cat_asset_id: PuzzleInfo( + { + "type": "CAT", + "tail": "0x" + cat_asset_id.hex(), + **( + {} + if wallet_type is CATWallet + else {"also": {"type": "revocation layer", "hidden_puzzle_hash": "0x" + bytes32.zeros.hex()}} + ), + } + ) } create_res = await env_1.rpc_client.create_offer_for_ids( - {uint32(1): -5, cat_asset_id.hex(): 1}, - wallet_environments.tx_config, - driver_dict=driver_dict, - fee=uint64(1), + CreateOfferForIDs(offer={str(1): "-5", cat_asset_id.hex(): "1"}, driver_dict=driver_dict, fee=uint64(1)), + tx_config=wallet_environments.tx_config, ) offer = create_res.offer @@ -1578,7 +1581,7 @@ async def test_offer_endpoints(wallet_environments: WalletTestFramework, wallet_ assert summary == { "offered": {"xch": 5}, "requested": {cat_asset_id.hex(): 1}, - "infos": driver_dict, + "infos": {key.hex(): info.info for key, info in driver_dict.items()}, "fees": 1, "additions": [c.name().hex() for c in offer.additions()], "removals": [c.name().hex() for c in offer.removals()], @@ -1622,7 +1625,8 @@ async def test_offer_endpoints(wallet_environments: WalletTestFramework, wallet_ assert TradeStatus(trade_record.status) == TradeStatus.PENDING_CANCEL create_res = await env_1.rpc_client.create_offer_for_ids( - {uint32(1): -5, cat_wallet_id: 1}, wallet_environments.tx_config, fee=uint64(1) + CreateOfferForIDs(offer={str(1): "-5", str(cat_wallet_id): "1"}, fee=uint64(1)), + tx_config=wallet_environments.tx_config, ) all_offers = await env_1.rpc_client.get_all_offers() assert len(all_offers) == 2 @@ -1736,9 +1740,8 @@ def only_ids(trades: list[TradeRecord]) -> list[bytes32]: assert len(all_offers) == 2 await env_1.rpc_client.create_offer_for_ids( - {uint32(1): -5, cat_asset_id.hex(): 1}, - wallet_environments.tx_config, - driver_dict=driver_dict, + CreateOfferForIDs(offer={str(1): "-5", cat_asset_id.hex(): "1"}, driver_dict=driver_dict), + tx_config=wallet_environments.tx_config, ) assert ( len([o for o in await env_1.rpc_client.get_all_offers() if o.status == TradeStatus.PENDING_ACCEPT.value]) == 2 @@ -1774,14 +1777,12 @@ def only_ids(trades: list[TradeRecord]) -> list[bytes32]: ) await env_1.rpc_client.create_offer_for_ids( - {uint32(1): -5, cat_asset_id.hex(): 1}, - wallet_environments.tx_config, - driver_dict=driver_dict, + CreateOfferForIDs(offer={str(1): "-5", cat_asset_id.hex(): "1"}, driver_dict=driver_dict), + tx_config=wallet_environments.tx_config, ) await env_1.rpc_client.create_offer_for_ids( - {uint32(1): 5, cat_asset_id.hex(): -1}, - wallet_environments.tx_config, - driver_dict=driver_dict, + CreateOfferForIDs(offer={str(1): "5", cat_asset_id.hex(): "-1"}, driver_dict=driver_dict), + tx_config=wallet_environments.tx_config, ) assert ( len([o for o in await env_1.rpc_client.get_all_offers() if o.status == TradeStatus.PENDING_ACCEPT.value]) == 2 @@ -1828,9 +1829,8 @@ def only_ids(trades: list[TradeRecord]) -> list[bytes32]: ) await env_1.rpc_client.create_offer_for_ids( - {uint32(1): 5, cat_asset_id.hex(): -1}, - wallet_environments.tx_config, - driver_dict=driver_dict, + CreateOfferForIDs(offer={str(1): "5", cat_asset_id.hex(): "-1"}, driver_dict=driver_dict), + tx_config=wallet_environments.tx_config, ) assert ( len([o for o in await env_1.rpc_client.get_all_offers() if o.status == TradeStatus.PENDING_ACCEPT.value]) == 1 @@ -1846,9 +1846,11 @@ def only_ids(trades: list[TradeRecord]) -> list[bytes32]: with pytest.raises(ValueError, match="not currently supported"): await env_1.rpc_client.create_offer_for_ids( - {uint32(1): -5, cat_asset_id.hex(): 1}, + CreateOfferForIDs( + offer={str(1): "-5", cat_asset_id.hex(): "1"}, + driver_dict=driver_dict, + ), wallet_environments.tx_config, - driver_dict=driver_dict, timelock_info=ConditionValidTimes(min_secs_since_created=uint64(1)), ) diff --git a/chia/cmds/wallet_funcs.py b/chia/cmds/wallet_funcs.py index b8170b043e10..2f17ecd7fd91 100644 --- a/chia/cmds/wallet_funcs.py +++ b/chia/cmds/wallet_funcs.py @@ -52,6 +52,7 @@ CATSpend, CATSpendResponse, ClawbackPuzzleDecoratorOverride, + CreateOfferForIDs, DeleteNotifications, DeleteUnconfirmedTransactions, DIDFindLostDID, @@ -507,8 +508,8 @@ async def make_offer( if offers == [] or requests == []: print("Not creating offer: Must be offering and requesting at least one asset") else: - offer_dict: dict[Union[uint32, str], int] = {} - driver_dict: dict[str, Any] = {} + offer_dict: dict[str, str] = {} + driver_dict: dict[bytes32, PuzzleInfo] = {} printable_dict: dict[str, tuple[str, int, int]] = {} # dict[asset_name, tuple[amount, unit, multiplier]] royalty_assets: list[RoyaltyAsset] = [] fungible_assets: list[FungibleAsset] = [] @@ -516,7 +517,7 @@ async def make_offer( name, amount = tuple(item.split(":")[0:2]) try: b32_id = bytes32.from_hexstr(name) - id: Union[uint32, str] = b32_id.hex() + id: str = b32_id.hex() result = await wallet_client.cat_asset_id_to_name(CATAssetIDToName(b32_id)) if result.name is not None: name = result.name @@ -535,7 +536,7 @@ async def make_offer( id = info.launcher_id.hex() assert isinstance(id, str) if item in requests: - driver_dict[id] = { + puzzle_info_dict: dict[str, Any] = { "type": "singleton", "launcher_id": "0x" + id, "launcher_ph": "0x" + info.launcher_puzhash.hex(), @@ -548,7 +549,7 @@ async def make_offer( if info.supports_did: assert info.royalty_puzzle_hash is not None assert info.royalty_percentage is not None - driver_dict[id]["also"]["also"] = { + puzzle_info_dict["also"]["also"] = { "type": "ownership", "owner": "()", "transfer_program": { @@ -565,17 +566,18 @@ async def make_offer( info.royalty_percentage, ) ) + driver_dict[info.launcher_id] = PuzzleInfo(puzzle_info_dict) else: id = decode_puzzle_hash(name).hex() assert hrp is not None unit = units[hrp] except ValueError: - id = uint32(name) - if id == 1: + id = str(uint32(name)) + if id == "1": name = "XCH" unit = units["chia"] else: - name = (await wallet_client.get_cat_name(CATGetName(id))).name + name = (await wallet_client.get_cat_name(CATGetName(uint32(name)))).name unit = units["cat"] if item in offers: fungible_assets.append(FungibleAsset(name, uint64(abs(int(Decimal(amount) * unit))))) @@ -585,7 +587,7 @@ async def make_offer( print("Not creating offer: Cannot offer and request the same asset in a trade") break else: - offer_dict[id] = int(Decimal(amount) * unit) * multiplier + offer_dict[id] = str(int(Decimal(amount) * unit) * multiplier) else: print("Creating Offer") print("--------------") @@ -642,9 +644,11 @@ async def make_offer( with filepath.open(mode="w") as file: res = await wallet_client.create_offer_for_ids( - offer_dict, - driver_dict=driver_dict, - fee=fee, + CreateOfferForIDs( + offer=offer_dict, + driver_dict=driver_dict, + fee=fee, + ), tx_config=CMDTXConfigLoader( reuse_puzhash=reuse_puzhash, ).to_tx_config(units["chia"], config, fingerprint), diff --git a/chia/data_layer/data_layer.py b/chia/data_layer/data_layer.py index f5b74c3fe494..293470f275d0 100644 --- a/chia/data_layer/data_layer.py +++ b/chia/data_layer/data_layer.py @@ -63,12 +63,14 @@ from chia.util.async_pool import Job, QueuedAsyncPool from chia.util.path import path_from_root from chia.util.task_referencer import create_referenced_task +from chia.wallet.puzzle_drivers import Solver from chia.wallet.trade_record import TradeRecord from chia.wallet.trading.offer import Offer as TradingOffer from chia.wallet.transaction_record import TransactionRecord from chia.wallet.util.tx_config import DEFAULT_TX_CONFIG from chia.wallet.wallet_request_types import ( CreateNewDL, + CreateOfferForIDs, DLDeleteMirror, DLGetMirrors, DLHistory, @@ -1137,34 +1139,38 @@ async def make_offer( async with self.data_store.transaction(): our_store_proofs = await self.process_offered_stores(offer_stores=maker) - offer_dict: dict[Union[uint32, str], int] = { - **{offer_store.store_id.hex(): -1 for offer_store in maker}, - **{offer_store.store_id.hex(): 1 for offer_store in taker}, + offer_dict: dict[str, str] = { + **{offer_store.store_id.hex(): "-1" for offer_store in maker}, + **{offer_store.store_id.hex(): "1" for offer_store in taker}, } - solver: dict[str, Any] = { - "0x" + our_offer_store.store_id.hex(): { - "new_root": "0x" + our_store_proofs[our_offer_store.store_id].proofs[0].root().hex(), - "dependencies": [ - { - "launcher_id": "0x" + their_offer_store.store_id.hex(), - "values_to_prove": [ - "0x" + leaf_hash(key=entry.key, value=entry.value).hex() - for entry in their_offer_store.inclusions - ], - } - for their_offer_store in taker - ], + solver = Solver( + { + "0x" + our_offer_store.store_id.hex(): { + "new_root": "0x" + our_store_proofs[our_offer_store.store_id].proofs[0].root().hex(), + "dependencies": [ + { + "launcher_id": "0x" + their_offer_store.store_id.hex(), + "values_to_prove": [ + "0x" + leaf_hash(key=entry.key, value=entry.value).hex() + for entry in their_offer_store.inclusions + ], + } + for their_offer_store in taker + ], + } + for our_offer_store in maker } - for our_offer_store in maker - } + ) res = await self.wallet_rpc.create_offer_for_ids( - offer_dict=offer_dict, - solver=solver, - driver_dict={}, - fee=fee, - validate_only=False, + CreateOfferForIDs( + offer=offer_dict, + solver=solver, + driver_dict={}, + fee=fee, + validate_only=False, + ), # TODO: probably shouldn't be default but due to peculiarities in the RPC, we're using a stop gap. # This is not a change in behavior, the default was already implicit. tx_config=DEFAULT_TX_CONFIG, diff --git a/chia/util/streamable.py b/chia/util/streamable.py index eda72959aceb..9dd6c0abe180 100644 --- a/chia/util/streamable.py +++ b/chia/util/streamable.py @@ -294,6 +294,8 @@ def recurse_jsonify( """ if next_recursion_step is None: next_recursion_step = recurse_jsonify + if getattr(d, "json_serialization_override", None) is not None: + return d.json_serialization_override(d) if dataclasses.is_dataclass(d): new_dict = {} for field in dataclasses.fields(d): diff --git a/chia/wallet/puzzle_drivers.py b/chia/wallet/puzzle_drivers.py index f99f60de7322..5fa42035e16f 100644 --- a/chia/wallet/puzzle_drivers.py +++ b/chia/wallet/puzzle_drivers.py @@ -1,11 +1,11 @@ from __future__ import annotations -from dataclasses import dataclass from typing import Any, Optional from clvm.SExp import SExp from clvm_tools.binutils import assemble, type_for_atom from ir.Type import Type +from typing_extensions import Self from chia.types.blockchain_format.program import Program from chia.util.casts import int_from_bytes @@ -17,7 +17,6 @@ """ -@dataclass(frozen=True) class PuzzleInfo: """ There are two 'magic' keys in a PuzzleInfo object: @@ -27,6 +26,10 @@ class PuzzleInfo: info: dict[str, Any] + def __init__(self, info: dict[str, Any]) -> None: + self.info = info + self.__post_init__() + def __post_init__(self) -> None: if "type" not in self.info: raise ValueError("A type is required to initialize a puzzle driver") @@ -74,11 +77,25 @@ def check_type(self, types: list[str]) -> bool: else: return False + # Methods to make this a valid Streamable member + # Should not be being serialized as bytes + stream = None + parse = None + + def to_json_dict(self) -> dict[str, Any]: + return self.info + + @classmethod + def from_json_dict(cls, json_dict: dict[str, Any]) -> Self: + return cls(json_dict) + -@dataclass(frozen=True) class Solver: info: dict[str, Any] + def __init__(self, info: dict[str, Any]) -> None: + self.info = info + def __getitem__(self, item: str) -> Any: value = self.info[item] return decode_info_value(Solver, value) @@ -92,6 +109,17 @@ def __eq__(self, other: object) -> bool: return False return True + # Methods to make this a valid Streamable member + stream = None + parse = None + + def to_json_dict(self) -> dict[str, Any]: + return self.info + + @classmethod + def from_json_dict(cls, json_dict: dict[str, Any]) -> Self: + return cls(json_dict) + def decode_info_value(cls: Any, value: Any) -> Any: if isinstance(value, dict): @@ -101,7 +129,7 @@ def decode_info_value(cls: Any, value: Any) -> Any: elif isinstance(value, Program) and value.atom is None: return value else: - if value == "()": # special case + if value in {"()", ""}: # special case return Program.to([]) expression: SExp = assemble(value) if expression.atom is None: diff --git a/chia/wallet/trade_manager.py b/chia/wallet/trade_manager.py index cddaacf85f49..72a8a95b426e 100644 --- a/chia/wallet/trade_manager.py +++ b/chia/wallet/trade_manager.py @@ -429,7 +429,7 @@ async def create_offer_for_ids( validate_only: bool = False, extra_conditions: tuple[Condition, ...] = tuple(), taking: bool = False, - ) -> Union[tuple[Literal[True], TradeRecord, None], tuple[Literal[False], None, str]]: + ) -> tuple[Literal[True], TradeRecord, None]: if driver_dict is None: driver_dict = {} if solver is None: diff --git a/chia/wallet/wallet_request_types.py b/chia/wallet/wallet_request_types.py index 7d8e6ea66538..e6f3eba3f47e 100644 --- a/chia/wallet/wallet_request_types.py +++ b/chia/wallet/wallet_request_types.py @@ -2,7 +2,7 @@ import sys from dataclasses import dataclass, field -from typing import Any, BinaryIO, Optional, final +from typing import Any, BinaryIO, Optional, Union, final from chia_rs import Coin, G1Element, G2Element, PrivateKey from chia_rs.sized_bytes import bytes32 @@ -19,6 +19,7 @@ from chia.wallet.conditions import Condition, ConditionValidTimes, conditions_to_json_dicts from chia.wallet.nft_wallet.nft_info import NFTInfo from chia.wallet.notification_store import Notification +from chia.wallet.puzzle_drivers import PuzzleInfo, Solver from chia.wallet.signer_protocol import ( SignedTransaction, SigningInstructions, @@ -1837,9 +1838,19 @@ class CreateSignedTransactionsResponse(TransactionEndpointResponse): @streamable @dataclass(frozen=True) class _OfferEndpointResponse(TransactionEndpointResponse): - offer: Offer + offer: Offer # gotta figure out how to ignore this in streamable trade_record: TradeRecord + def to_json_dict(self) -> dict[str, Any]: + old_offer_override = getattr(self.offer, "json_serialization_override", None) + object.__setattr__(self.offer, "json_serialization_override", lambda o: o.to_bech32()) + try: + response = {**super().to_json_dict(), "trade_record": self.trade_record.to_json_dict_convenience()} + except Exception: + object.__setattr__(self.offer, "json_serialization_override", old_offer_override) + raise + return response + @classmethod def from_json_dict(cls, json_dict: dict[str, Any]) -> Self: tx_endpoint: TransactionEndpointResponse = json_deserialize_with_clvm_streamable( @@ -1854,6 +1865,27 @@ def from_json_dict(cls, json_dict: dict[str, Any]) -> Self: ) +@streamable +@dataclass(frozen=True) +class CreateOfferForIDs(TransactionEndpointRequest): + # a hack for dict[str, int] because streamable doesn't support negative ints + offer: dict[str, str] = field(default_factory=default_raise) + driver_dict: Optional[dict[bytes32, PuzzleInfo]] = None + solver: Optional[Solver] = None + validate_only: bool = False + + @property + def offer_spec(self) -> dict[Union[int, bytes32], int]: + modified_offer: dict[Union[int, bytes32], int] = {} + for wallet_identifier, change in self.offer.items(): + if len(wallet_identifier) > 16: # wallet IDs are uint32 therefore no longer than 8 bytes :P + modified_offer[bytes32.from_hexstr(wallet_identifier)] = int(change) + else: + modified_offer[int(wallet_identifier)] = int(change) + + return modified_offer + + @streamable @dataclass(frozen=True) class CreateOfferForIDsResponse(_OfferEndpointResponse): diff --git a/chia/wallet/wallet_rpc_api.py b/chia/wallet/wallet_rpc_api.py index ce30a6f60b68..93617c462683 100644 --- a/chia/wallet/wallet_rpc_api.py +++ b/chia/wallet/wallet_rpc_api.py @@ -4,7 +4,7 @@ import json import logging from pathlib import Path -from typing import TYPE_CHECKING, Any, Callable, ClassVar, Optional, Union, cast +from typing import TYPE_CHECKING, Any, Callable, ClassVar, Optional, cast from chia_rs import AugSchemeMPL, Coin, CoinSpend, CoinState, G1Element, G2Element, PrivateKey from chia_rs.sized_bytes import bytes32 @@ -130,6 +130,8 @@ CombineCoinsResponse, CreateNewDL, CreateNewDLResponse, + CreateOfferForIDs, + CreateOfferForIDsResponse, DefaultCAT, DeleteKey, DeleteNotifications, @@ -2217,66 +2219,42 @@ async def cat_asset_id_to_name(self, request: CATAssetIDToName) -> CATAssetIDToN return CATAssetIDToNameResponse(wallet_id=wallet.id(), name=wallet.get_name()) @tx_endpoint(push=False) + @marshal async def create_offer_for_ids( self, - request: dict[str, Any], + request: CreateOfferForIDs, action_scope: WalletActionScope, extra_conditions: tuple[Condition, ...] = tuple(), - ) -> EndpointResult: + ) -> CreateOfferForIDsResponse: if action_scope.config.push: - raise ValueError("Cannot push an incomplete spend") # pragma: no cover - - offer: dict[str, int] = request["offer"] - fee: uint64 = uint64(request.get("fee", 0)) - validate_only: bool = request.get("validate_only", False) - driver_dict_str: Optional[dict[str, Any]] = request.get("driver_dict", None) - marshalled_solver = request.get("solver") - solver: Optional[Solver] - if marshalled_solver is None: - solver = None - else: - solver = Solver(info=marshalled_solver) + raise ValueError("Cannot push an incomplete spend") # This driver_dict construction is to maintain backward compatibility where everything is assumed to be a CAT driver_dict: dict[bytes32, PuzzleInfo] = {} - if driver_dict_str is None: - for key, amount in offer.items(): - if amount > 0: - try: - driver_dict[bytes32.from_hexstr(key)] = PuzzleInfo( - {"type": AssetType.CAT.value, "tail": "0x" + key} - ) - except ValueError: - pass + if request.driver_dict is None: + for key, amount in request.offer_spec.items(): + if amount > 0 and isinstance(key, bytes32): + driver_dict[key] = PuzzleInfo({"type": AssetType.CAT.value, "tail": "0x" + key.hex()}) else: - for key, value in driver_dict_str.items(): - driver_dict[bytes32.from_hexstr(key)] = PuzzleInfo(value) - - modified_offer: dict[Union[int, bytes32], int] = {} - for wallet_identifier, change in offer.items(): - try: - modified_offer[bytes32.from_hexstr(wallet_identifier)] = change - except ValueError: - modified_offer[int(wallet_identifier)] = change + driver_dict = request.driver_dict async with self.service.wallet_state_manager.lock: result = await self.service.wallet_state_manager.trade_manager.create_offer_for_ids( - modified_offer, + request.offer_spec, action_scope, driver_dict, - solver=solver, - fee=fee, - validate_only=validate_only, + solver=request.solver, + fee=request.fee, + validate_only=request.validate_only, extra_conditions=extra_conditions, ) - if result[0]: - _success, trade_record, _error = result - return { - "offer": Offer.from_bytes(trade_record.offer).to_bech32(), - "trade_record": trade_record.to_json_dict_convenience(), - "transactions": None, # tx_endpoint wrapper will take care of this - } - raise ValueError(result[2]) + + return CreateOfferForIDsResponse( + [], + [], + offer=Offer.from_bytes(result[1].offer), + trade_record=result[1], + ) async def get_offer_summary(self, request: dict[str, Any]) -> EndpointResult: offer_hex: str = request["offer"] diff --git a/chia/wallet/wallet_rpc_client.py b/chia/wallet/wallet_rpc_client.py index b8c1522d1776..c3d183d3bc85 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 +from typing import Any, Optional from chia_rs.sized_bytes import bytes32 from chia_rs.sized_ints import uint32, uint64 @@ -41,6 +41,7 @@ CombineCoinsResponse, CreateNewDL, CreateNewDLResponse, + CreateOfferForIDs, CreateOfferForIDsResponse, CreateSignedTransactionsResponse, DeleteKey, @@ -666,31 +667,16 @@ async def cat_spend( # Offers async def create_offer_for_ids( self, - offer_dict: dict[Union[uint32, str], int], + request: CreateOfferForIDs, tx_config: TXConfig, - driver_dict: Optional[dict[str, Any]] = None, - solver: Optional[dict[str, Any]] = None, - fee: int = 0, - validate_only: bool = False, extra_conditions: tuple[Condition, ...] = tuple(), timelock_info: ConditionValidTimes = ConditionValidTimes(), ) -> CreateOfferForIDsResponse: - send_dict: dict[str, int] = {str(key): value for key, value in offer_dict.items()} - - req = { - "offer": send_dict, - "validate_only": validate_only, - "fee": fee, - "extra_conditions": conditions_to_json_dicts(extra_conditions), - **tx_config.to_json_dict(), - **timelock_info.to_json_dict(), - } - if driver_dict is not None: - req["driver_dict"] = driver_dict - if solver is not None: - req["solver"] = solver - res = await self.fetch("create_offer_for_ids", req) - return json_deserialize_with_clvm_streamable(res, CreateOfferForIDsResponse) + return CreateOfferForIDsResponse.from_json_dict( + await self.fetch( + "create_offer_for_ids", request.json_serialize_for_transport(tx_config, extra_conditions, timelock_info) + ) + ) async def get_offer_summary( self, offer: Offer, advanced: bool = False