From aedccaa1784cb1cf3596ebf4dcb15fac4ffc335b Mon Sep 17 00:00:00 2001 From: Jeff Cruikshank Date: Wed, 13 Jul 2022 11:55:28 -0700 Subject: [PATCH 1/2] Calculate NFT royalty amount --- chia/cmds/wallet_funcs.py | 63 ++++++++++++++++++++++++++- chia/wallet/nft_wallet/nft_puzzles.py | 1 + 2 files changed, 63 insertions(+), 1 deletion(-) diff --git a/chia/cmds/wallet_funcs.py b/chia/cmds/wallet_funcs.py index 88dac2bd8a..18daad4840 100644 --- a/chia/cmds/wallet_funcs.py +++ b/chia/cmds/wallet_funcs.py @@ -20,6 +20,7 @@ from chia.util.config import load_config from chia.util.default_root import DEFAULT_ROOT_PATH from chia.util.ints import uint16, uint32, uint64, uint128 from chia.wallet.did_wallet.did_info import DID_HRP +from chia.wallet.nft_wallet.nft_puzzles import NFT_METADATA_UPDATER_PUZZLE_HASH from chia.wallet.nft_wallet.nft_info import NFT_HRP, NFTInfo from chia.wallet.trade_record import TradeRecord from chia.wallet.trading.offer import Offer @@ -515,13 +516,33 @@ async def take_offer(args: dict, wallet_client: WalletRpcClient, fingerprint: in print("Please enter a valid offer file or hex blob") return - offered, requested, _ = offer.summary() + offered, requested, driver_dict = offer.summary() cat_name_resolver = wallet_client.cat_asset_id_to_name print("Summary:") print(" OFFERED:") await print_offer_summary(cat_name_resolver, offered) print(" REQUESTED:") await print_offer_summary(cat_name_resolver, requested) + + nft_coin_id: Optional[bytes32] = nft_coin_id_from_offer(driver_dict) + nft_royalty_percentage: int = ( + 0 if nft_coin_id is None else await get_nft_royalty_percentage(nft_coin_id, wallet_client) + ) + if nft_royalty_percentage > 0: + print("NFT Royalty Amount:") + nft_royalty_asset_id, nft_royalty_amount = calculate_nft_royalty_amount( + offered, requested, nft_coin_id, nft_royalty_percentage + ) + nft_royalty_currency = ( + "XCH" + if nft_royalty_asset_id == "xch" + else (await cat_name_resolver(bytes32.fromhex(nft_royalty_asset_id)))[1] + ) + nft_royalty_divisor = units["chia"] if nft_royalty_asset_id == "xch" else units["cat"] + print( + f" {Decimal(nft_royalty_amount) / nft_royalty_divisor} {nft_royalty_currency} ({nft_royalty_amount} mojos)" + ) + print(f"Included Fees: {Decimal(offer.bundle.fees()) / units['chia']}") if not examine_only: @@ -929,3 +950,43 @@ async def get_nft_info(args: Dict, wallet_client: WalletRpcClient, fingerprint: print_nft_info(nft_info) except Exception as e: print(f"Failed to get NFT info: {e}") + + +async def get_nft_royalty_percentage(nft_coin_id: bytes32, wallet_client: WalletRpcClient) -> int: + info = NFTInfo.from_json_dict((await wallet_client.get_nft_info(nft_coin_id.hex()))["nft_info"]) + return info.royalty_percentage + + +def calculate_nft_royalty_amount( + offered: Dict[str, Any], requested: Dict[str, Any], nft_coin_id: bytes32, nft_royalty_percentage: int +) -> Tuple[str, int]: + nft_asset_id = nft_coin_id.hex() + amount_dict: Dict[str, Any] = requested if nft_asset_id in offered else offered + amounts: List[Tuple[str, int]] = list(amount_dict.items()) + + if len(amounts) != 1 or not isinstance(amounts[0][1], int): + raise ValueError("Royalty enabled NFTs only support offering/requesting one NFT for one currency") + + royalty_amount: uint64 = uint64(amounts[0][1] * nft_royalty_percentage / 10000) + royalty_asset_id = amounts[0][0] + return royalty_asset_id, royalty_amount + + +def driver_dict_asset_is_nft(driver_dict: Dict[str, Any], asset_id: str) -> bool: + asset_dict: Dict[str, Any] = driver_dict[asset_id] + if asset_dict.get("type") == "singleton": + updater_hash_hexstr: Optional[str] = asset_dict.get("also", {}).get("updater_hash") + try: + updater_hash = bytes32.from_hexstr(updater_hash_hexstr) + return updater_hash == NFT_METADATA_UPDATER_PUZZLE_HASH + except ValueError: + # Failed to construct bytes32 from updater_hash_hexstr + pass + return False + + +def nft_coin_id_from_offer(driver_dict: Dict[str, Any]) -> Optional[bytes32]: + nft_asset_id: Optional[str] = next( + (key for key in driver_dict.keys() if driver_dict_asset_is_nft(driver_dict, key)), None + ) + return bytes32.fromhex(nft_asset_id) if nft_asset_id is not None else None diff --git a/chia/wallet/nft_wallet/nft_puzzles.py b/chia/wallet/nft_wallet/nft_puzzles.py index c25ac6e836..01b6b7985a 100644 --- a/chia/wallet/nft_wallet/nft_puzzles.py +++ b/chia/wallet/nft_wallet/nft_puzzles.py @@ -20,6 +20,7 @@ LAUNCHER_PUZZLE_HASH = LAUNCHER_PUZZLE.get_tree_hash() SINGLETON_MOD_HASH = SINGLETON_TOP_LAYER_MOD.get_tree_hash() NFT_STATE_LAYER_MOD_HASH = NFT_STATE_LAYER_MOD.get_tree_hash() NFT_METADATA_UPDATER = load_clvm("nft_metadata_updater_default.clvm") +NFT_METADATA_UPDATER_PUZZLE_HASH = NFT_METADATA_UPDATER.get_tree_hash() NFT_OWNERSHIP_LAYER = load_clvm("nft_ownership_layer.clvm") NFT_TRANSFER_PROGRAM_DEFAULT = load_clvm("nft_ownership_transfer_program_one_way_claim_with_royalties.clvm") STANDARD_PUZZLE_MOD = load_clvm("p2_delegated_puzzle_or_hidden_puzzle.clvm") From c891d5645fd20d7e5d30cafe06c0c84d7e5f496e Mon Sep 17 00:00:00 2001 From: Jeff Cruikshank Date: Wed, 13 Jul 2022 15:09:49 -0700 Subject: [PATCH 2/2] Show total amount to be paid for NFT offers --- chia/cmds/wallet_funcs.py | 35 +++++++++++++++++++++++++---------- 1 file changed, 25 insertions(+), 10 deletions(-) diff --git a/chia/cmds/wallet_funcs.py b/chia/cmds/wallet_funcs.py index 18daad4840..0cc8037b3f 100644 --- a/chia/cmds/wallet_funcs.py +++ b/chia/cmds/wallet_funcs.py @@ -524,28 +524,42 @@ async def take_offer(args: dict, wallet_client: WalletRpcClient, fingerprint: in print(" REQUESTED:") await print_offer_summary(cat_name_resolver, requested) + print() + nft_coin_id: Optional[bytes32] = nft_coin_id_from_offer(driver_dict) nft_royalty_percentage: int = ( 0 if nft_coin_id is None else await get_nft_royalty_percentage(nft_coin_id, wallet_client) ) + nft_total_amount_requested_str: Optional[str] = None if nft_royalty_percentage > 0: - print("NFT Royalty Amount:") - nft_royalty_asset_id, nft_royalty_amount = calculate_nft_royalty_amount( + print("NFT Royalty Fee:") + nft_royalty_asset_id, nft_royalty_amount, nft_total_amount_requested = calculate_nft_royalty_amount( offered, requested, nft_coin_id, nft_royalty_percentage ) - nft_royalty_currency = ( - "XCH" - if nft_royalty_asset_id == "xch" - else (await cat_name_resolver(bytes32.fromhex(nft_royalty_asset_id)))[1] - ) + nft_royalty_currency: str = "Unknown CAT" + if nft_royalty_asset_id == "xch": + nft_royalty_currency = "XCH" + else: + result = await cat_name_resolver(bytes32.fromhex(nft_royalty_asset_id)) + if result is not None: + nft_royalty_currency = result[1] + nft_royalty_divisor = units["chia"] if nft_royalty_asset_id == "xch" else units["cat"] + nft_total_amount_requested_str = ( + f"{Decimal(nft_total_amount_requested) / nft_royalty_divisor} {nft_royalty_currency}" + ) print( - f" {Decimal(nft_royalty_amount) / nft_royalty_divisor} {nft_royalty_currency} ({nft_royalty_amount} mojos)" + f" {Decimal(nft_royalty_amount) / nft_royalty_divisor} {nft_royalty_currency} " + f"({nft_royalty_amount} mojos)" ) print(f"Included Fees: {Decimal(offer.bundle.fees()) / units['chia']}") + if nft_total_amount_requested_str is not None: + print(f"Total Amount Requested: {nft_total_amount_requested_str}") + if not examine_only: + print() confirmation = input("Would you like to take this offer? (y/n): ") if confirmation in ["y", "yes"]: trade_record = await wallet_client.take_offer(offer, fee=fee) @@ -959,7 +973,7 @@ async def get_nft_royalty_percentage(nft_coin_id: bytes32, wallet_client: Wallet def calculate_nft_royalty_amount( offered: Dict[str, Any], requested: Dict[str, Any], nft_coin_id: bytes32, nft_royalty_percentage: int -) -> Tuple[str, int]: +) -> Tuple[str, int, int]: nft_asset_id = nft_coin_id.hex() amount_dict: Dict[str, Any] = requested if nft_asset_id in offered else offered amounts: List[Tuple[str, int]] = list(amount_dict.items()) @@ -969,7 +983,8 @@ def calculate_nft_royalty_amount( royalty_amount: uint64 = uint64(amounts[0][1] * nft_royalty_percentage / 10000) royalty_asset_id = amounts[0][0] - return royalty_asset_id, royalty_amount + total_amount_requested = (requested[royalty_asset_id] if amount_dict == requested else 0) + royalty_amount + return royalty_asset_id, royalty_amount, total_amount_requested def driver_dict_asset_is_nft(driver_dict: Dict[str, Any], asset_id: str) -> bool: