mirror of
https://github.com/Chia-Network/chia-blockchain.git
synced 2026-08-24 10:05:29 -05:00
[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:
@@ -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,
|
||||
|
||||
@@ -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
@@ -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),
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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"]
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user