[CHIA-3606] Port create_offer_for_ids to @marshal (#20056)

* Port `create_offer_for_ids` to `@marshal`

* Self from typing_extensions

* Fix support for "" in PuzzleInfo/Solver
This commit is contained in:
Matt Hauff
2025-10-21 11:00:37 -07:00
committed by GitHub
parent 18c1010240
commit 0a26924688
10 changed files with 241 additions and 169 deletions
+63 -29
View File
@@ -29,6 +29,7 @@ from chia.protocols.outbound_message import NodeType
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 @@ from chia.wallet.wallet_request_types import (
CATSpend,
CATSpendResponse,
ClawbackPuzzleDecoratorOverride,
CreateOfferForIDs,
CreateOfferForIDsResponse,
DeleteUnconfirmedTransactions,
ExtendDerivationIndex,
@@ -79,6 +81,29 @@ from chia.wallet.wallet_request_types import (
)
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 @@ def test_make_offer(capsys: object, get_test_cli_clients: tuple[TestRpcClients,
"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,
+33 -31
View File
@@ -81,6 +81,7 @@ from chia.wallet.conditions import (
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
@@ -114,6 +115,7 @@ from chia.wallet.wallet_request_types import (
CheckOfferValidity,
ClawbackPuzzleDecoratorOverride,
CombineCoins,
CreateOfferForIDs,
DefaultCAT,
DeleteKey,
DeleteNotifications,
@@ -1586,28 +1588,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
@@ -1618,7 +1621,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()],
@@ -1662,7 +1665,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
@@ -1776,9 +1780,8 @@ async def test_offer_endpoints(wallet_environments: WalletTestFramework, wallet_
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
@@ -1814,14 +1817,12 @@ async def test_offer_endpoints(wallet_environments: WalletTestFramework, wallet_
)
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
@@ -1868,9 +1869,8 @@ async def test_offer_endpoints(wallet_environments: WalletTestFramework, wallet_
)
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
@@ -1886,9 +1886,11 @@ async def test_offer_endpoints(wallet_environments: WalletTestFramework, wallet_
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)),
)
+16 -12
View File
@@ -52,6 +52,7 @@ from chia.wallet.wallet_request_types import (
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),
+29 -23
View File
@@ -61,12 +61,14 @@ from chia.server.ws_connection import WSChiaConnection
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,
@@ -1148,34 +1150,38 @@ class DataLayer:
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,
+2
View File
@@ -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):
+32 -4
View File
@@ -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 @@ When you access a value in the dictionary, it will be deserialized to a str, int
"""
@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 @@ class PuzzleInfo:
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 @@ class Solver:
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:
+1 -1
View File
@@ -429,7 +429,7 @@ class TradeManager:
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:
+34 -2
View File
@@ -1,7 +1,7 @@
from __future__ import annotations
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
@@ -18,6 +18,7 @@ from chia.util.streamable import Streamable, streamable
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,
@@ -1829,9 +1830,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(
@@ -1846,6 +1857,27 @@ class _OfferEndpointResponse(TransactionEndpointResponse):
)
@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):
+23 -45
View File
@@ -4,7 +4,7 @@ import dataclasses
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
@@ -129,6 +129,8 @@ from chia.wallet.wallet_request_types import (
CombineCoinsResponse,
CreateNewDL,
CreateNewDLResponse,
CreateOfferForIDs,
CreateOfferForIDsResponse,
DefaultCAT,
DeleteKey,
DeleteNotifications,
@@ -2220,66 +2222,42 @@ class WalletRpcApi:
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"]
+8 -22
View File
@@ -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 @@ from chia.wallet.wallet_request_types import (
CombineCoinsResponse,
CreateNewDL,
CreateNewDLResponse,
CreateOfferForIDs,
CreateOfferForIDsResponse,
CreateSignedTransactionsResponse,
DeleteKey,
@@ -666,31 +667,16 @@ class WalletRpcClient(RpcClient):
# 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