mirror of
https://github.com/Chia-Network/chia-blockchain.git
synced 2026-08-24 10:05:29 -05:00
* [LABS-170] Add automatic reorg testing to `WalletTestFramework` * Actually exempt reorgs when specified * Comments by @cursor * Fix CAT wallet tests * Reorg exempt offer tests * Fix DID tests * Fix NFTWallet tests * Fix RPC tests * Fix VC tests * Exempt pool tests * Add reorg_exempt to top level conftest * Fix tests (nd maybe break them?)
3342 lines
120 KiB
Python
3342 lines
120 KiB
Python
from __future__ import annotations
|
|
|
|
import asyncio
|
|
import time
|
|
from collections.abc import Callable
|
|
from typing import Any
|
|
|
|
import pytest
|
|
from chia_rs import AugSchemeMPL
|
|
from chia_rs.sized_bytes import bytes32
|
|
from chia_rs.sized_ints import uint32, uint64
|
|
|
|
from chia._tests.cmds.test_cmd_framework import check_click_parsing
|
|
from chia._tests.conftest import ConsensusMode
|
|
from chia._tests.environments.wallet import (
|
|
STANDARD_TX_ENDPOINT_ARGS,
|
|
WalletEnvironment,
|
|
WalletStateTransition,
|
|
WalletTestFramework,
|
|
)
|
|
from chia._tests.util.time_out_assert import time_out_assert
|
|
from chia.cmds.cmd_classes import ChiaCliContext
|
|
from chia.cmds.cmd_helpers import NeedsTXConfig, NeedsWalletRPC, TransactionsOut, WalletClientInfo
|
|
from chia.cmds.param_types import CliAddress
|
|
from chia.cmds.wallet import (
|
|
AddUriToNftCMD,
|
|
CreateNftWalletCMD,
|
|
GetNftInfoCMD,
|
|
ListNftsCMD,
|
|
MintNftCMD,
|
|
NftSignMessageCMD,
|
|
SetNftDidCMD,
|
|
TransferNftCMD,
|
|
)
|
|
from chia.rpc.rpc_client import ResponseFailureError
|
|
from chia.simulator.simulator_protocol import ReorgProtocol
|
|
from chia.types.blockchain_format.program import Program
|
|
from chia.types.signing_mode import CHIP_0002_SIGN_MESSAGE_PREFIX
|
|
from chia.util.bech32m import decode_puzzle_hash, encode_puzzle_hash
|
|
from chia.util.byte_types import hexstr_to_bytes
|
|
from chia.util.timing import adjusted_timeout
|
|
from chia.wallet.did_wallet.did_wallet import DIDWallet
|
|
from chia.wallet.nft_wallet.nft_info import NFTInfo
|
|
from chia.wallet.nft_wallet.nft_wallet import NFTWallet
|
|
from chia.wallet.util.address_type import AddressType
|
|
from chia.wallet.util.compute_memos import compute_memos
|
|
from chia.wallet.util.wallet_types import WalletType
|
|
from chia.wallet.wallet_request_types import (
|
|
NFTCoin,
|
|
NFTCountNFTs,
|
|
NFTGetByDID,
|
|
NFTGetInfo,
|
|
NFTGetNFTs,
|
|
NFTGetWalletDID,
|
|
NFTMintNFTRequest,
|
|
NFTSetDIDBulk,
|
|
NFTSetNFTDID,
|
|
NFTSetNFTStatus,
|
|
NFTTransferBulk,
|
|
NFTWalletWithDID,
|
|
SignMessageByID,
|
|
)
|
|
from chia.wallet.wallet_rpc_api import MAX_NFT_CHUNK_SIZE
|
|
from chia.wallet.wallet_state_manager import WalletStateManager
|
|
|
|
|
|
async def get_nft_count(wallet: NFTWallet) -> int:
|
|
return await wallet.get_nft_count()
|
|
|
|
|
|
async def get_wallet_number(manager: WalletStateManager) -> int:
|
|
return len(manager.wallets)
|
|
|
|
|
|
# TODO: This is not a very paradigmatic function and should be updated
|
|
async def wait_rpc_state_condition(
|
|
timeout: float,
|
|
async_function: Any,
|
|
params: list[Any],
|
|
condition_func: Callable[[dict[str, Any]], bool],
|
|
) -> dict[str, Any]:
|
|
__tracebackhide__ = True
|
|
|
|
timeout = adjusted_timeout(timeout=timeout)
|
|
|
|
start = time.monotonic()
|
|
|
|
while True:
|
|
resp = await async_function(*params)
|
|
assert isinstance(resp, dict)
|
|
if condition_func(resp):
|
|
return resp
|
|
|
|
now = time.monotonic()
|
|
elapsed = now - start
|
|
if elapsed >= timeout:
|
|
raise asyncio.TimeoutError(
|
|
f"timed out while waiting for {async_function.__name__}(): {elapsed} >= {timeout}",
|
|
)
|
|
|
|
await asyncio.sleep(0.3)
|
|
|
|
|
|
async def create_nft_wallet_via_cli(
|
|
wallet_environments: WalletTestFramework,
|
|
env: WalletEnvironment,
|
|
*,
|
|
name: str | None = None,
|
|
did_id: CliAddress | None = None,
|
|
) -> None:
|
|
await CreateNftWalletCMD(
|
|
rpc_info=wallet_environments.cmd_tx_endpoint_args(env)["rpc_info"],
|
|
did_id=did_id,
|
|
name=name,
|
|
).run()
|
|
|
|
|
|
async def mint_nft_via_cli(
|
|
wallet_environments: WalletTestFramework,
|
|
env: WalletEnvironment,
|
|
*,
|
|
wallet_id: int,
|
|
hash: str,
|
|
uris: str | list[str],
|
|
royalty_address: CliAddress | None = None,
|
|
target_address: CliAddress | None = None,
|
|
no_did_ownership: bool = False,
|
|
metadata_hash: str | None = None,
|
|
metadata_uris: str | None = None,
|
|
license_hash: str | None = None,
|
|
license_uris: str | None = None,
|
|
edition_total: int = 1,
|
|
edition_number: int = 1,
|
|
fee: uint64 = uint64(0),
|
|
royalty_percentage_fraction: int = 0,
|
|
push: bool = True,
|
|
) -> None:
|
|
await MintNftCMD(
|
|
**{
|
|
**wallet_environments.cmd_tx_endpoint_args(env),
|
|
"wallet_id": wallet_id,
|
|
"hash": hash,
|
|
"uris": ",".join(uris) if isinstance(uris, list) else uris,
|
|
"royalty_address": royalty_address,
|
|
"target_address": target_address,
|
|
"no_did_ownership": no_did_ownership,
|
|
"metadata_hash": metadata_hash,
|
|
"metadata_uris": metadata_uris,
|
|
"license_hash": license_hash,
|
|
"license_uris": license_uris,
|
|
"edition_total": edition_total,
|
|
"edition_number": edition_number,
|
|
"fee": fee,
|
|
"royalty_percentage_fraction": royalty_percentage_fraction,
|
|
"push": push,
|
|
}
|
|
).run()
|
|
|
|
|
|
async def add_uri_to_nft_via_cli(
|
|
wallet_environments: WalletTestFramework,
|
|
env: WalletEnvironment,
|
|
*,
|
|
wallet_id: int,
|
|
nft_coin_id: str,
|
|
uri: str | None = None,
|
|
metadata_uri: str | None = None,
|
|
license_uri: str | None = None,
|
|
fee: uint64 = uint64(0),
|
|
push: bool = True,
|
|
) -> None:
|
|
await AddUriToNftCMD(
|
|
**{
|
|
**wallet_environments.cmd_tx_endpoint_args(env),
|
|
"wallet_id": wallet_id,
|
|
"nft_coin_id": nft_coin_id,
|
|
"uri": uri,
|
|
"metadata_uri": metadata_uri,
|
|
"license_uri": license_uri,
|
|
"fee": fee,
|
|
"push": push,
|
|
}
|
|
).run()
|
|
|
|
|
|
async def transfer_nft_via_cli(
|
|
wallet_environments: WalletTestFramework,
|
|
env: WalletEnvironment,
|
|
*,
|
|
wallet_id: int,
|
|
nft_coin_id: str,
|
|
target_address: CliAddress,
|
|
fee: uint64 = uint64(0),
|
|
push: bool = True,
|
|
) -> None:
|
|
await TransferNftCMD(
|
|
**{
|
|
**wallet_environments.cmd_tx_endpoint_args(env),
|
|
"wallet_id": wallet_id,
|
|
"nft_coin_id": nft_coin_id,
|
|
"target_address": target_address,
|
|
"fee": fee,
|
|
"push": push,
|
|
}
|
|
).run()
|
|
|
|
|
|
async def set_nft_did_via_cli(
|
|
wallet_environments: WalletTestFramework,
|
|
env: WalletEnvironment,
|
|
*,
|
|
wallet_id: int,
|
|
did_id: str,
|
|
nft_coin_id: str,
|
|
fee: uint64 = uint64(0),
|
|
push: bool = True,
|
|
) -> None:
|
|
await SetNftDidCMD(
|
|
**{
|
|
**wallet_environments.cmd_tx_endpoint_args(env),
|
|
"wallet_id": wallet_id,
|
|
"did_id": did_id,
|
|
"nft_coin_id": nft_coin_id,
|
|
"fee": fee,
|
|
"push": push,
|
|
}
|
|
).run()
|
|
|
|
|
|
@pytest.mark.limit_consensus_modes(allowed=[ConsensusMode.PLAIN], reason="irrelevant")
|
|
@pytest.mark.parametrize("wallet_environments", [{"num_environments": 2, "blocks_needed": [1, 1]}], indirect=True)
|
|
@pytest.mark.anyio
|
|
async def test_nft_wallet_creation_automatically(wallet_environments: WalletTestFramework) -> None:
|
|
env_0 = wallet_environments.environments[0]
|
|
env_1 = wallet_environments.environments[1]
|
|
wallet_node_0 = env_0.node
|
|
wallet_node_1 = env_1.node
|
|
wallet_0 = env_0.xch_wallet
|
|
wallet_1 = env_1.xch_wallet
|
|
|
|
env_0.wallet_aliases = {
|
|
"xch": 1,
|
|
"nft": 2,
|
|
}
|
|
env_1.wallet_aliases = {
|
|
"xch": 1,
|
|
"nft": 2,
|
|
}
|
|
|
|
nft_wallet_0 = await NFTWallet.create_new_nft_wallet(
|
|
wallet_node_0.wallet_state_manager, wallet_0, name="NFT WALLET 1"
|
|
)
|
|
metadata = Program.to(
|
|
[
|
|
("u", ["https://www.chia.net/img/branding/chia-logo.svg"]),
|
|
("h", "0xD4584AD463139FA8C0D9F68F4B59F185D4584AD463139FA8C0D9F68F4B59F185"),
|
|
]
|
|
)
|
|
|
|
async with nft_wallet_0.wallet_state_manager.new_action_scope(
|
|
wallet_environments.tx_config, push=True
|
|
) as action_scope:
|
|
await nft_wallet_0.generate_new_nft(metadata, action_scope)
|
|
|
|
await wallet_environments.process_pending_states(
|
|
[
|
|
WalletStateTransition(
|
|
pre_block_balance_updates={
|
|
"xch": {
|
|
"confirmed_wallet_balance": 0,
|
|
"unconfirmed_wallet_balance": -1,
|
|
"<=#spendable_balance": -1,
|
|
"<=#max_send_amount": -1,
|
|
"pending_coin_removal_count": 1,
|
|
">=#pending_change": 1, # any amount increase
|
|
"unspent_coin_count": 0,
|
|
},
|
|
"nft": {
|
|
"init": True,
|
|
"confirmed_wallet_balance": 0,
|
|
"unconfirmed_wallet_balance": 0,
|
|
"spendable_balance": 0,
|
|
"max_send_amount": 0,
|
|
"pending_coin_removal_count": 1, # a bit weird but correct?
|
|
"pending_change": 0,
|
|
"unspent_coin_count": 0,
|
|
},
|
|
},
|
|
post_block_balance_updates={
|
|
"xch": {
|
|
"confirmed_wallet_balance": -1,
|
|
"unconfirmed_wallet_balance": 0,
|
|
">=#spendable_balance": 1, # any amount increase
|
|
">=#max_send_amount": 1, # any amount increase
|
|
"pending_coin_removal_count": -1,
|
|
"<=#pending_change": -1, # any amount decrease
|
|
"unspent_coin_count": 0,
|
|
},
|
|
"nft": {
|
|
"pending_coin_removal_count": -1,
|
|
"unspent_coin_count": 1,
|
|
},
|
|
},
|
|
),
|
|
WalletStateTransition(),
|
|
]
|
|
)
|
|
|
|
await time_out_assert(30, get_nft_count, 1, nft_wallet_0)
|
|
coins = await nft_wallet_0.get_current_nfts()
|
|
assert len(coins) == 1, "nft not generated"
|
|
|
|
async with wallet_1.wallet_state_manager.new_action_scope(wallet_environments.tx_config, push=True) as action_scope:
|
|
wallet_1_ph = await action_scope.get_puzzle_hash(wallet_1.wallet_state_manager)
|
|
async with nft_wallet_0.wallet_state_manager.new_action_scope(
|
|
wallet_environments.tx_config, push=True
|
|
) as action_scope:
|
|
await nft_wallet_0.generate_signed_transaction(
|
|
[uint64(coins[0].coin.amount)],
|
|
[wallet_1_ph],
|
|
action_scope,
|
|
coins={coins[0].coin},
|
|
)
|
|
|
|
await wallet_environments.process_pending_states(
|
|
[
|
|
WalletStateTransition(
|
|
pre_block_balance_updates={
|
|
"xch": {},
|
|
"nft": {
|
|
"pending_coin_removal_count": 1,
|
|
},
|
|
},
|
|
post_block_balance_updates={
|
|
"xch": {},
|
|
"nft": {
|
|
"pending_coin_removal_count": -1,
|
|
"unspent_coin_count": -1,
|
|
},
|
|
},
|
|
),
|
|
WalletStateTransition(
|
|
pre_block_balance_updates={
|
|
"xch": {},
|
|
},
|
|
post_block_balance_updates={
|
|
"xch": {},
|
|
"nft": {
|
|
"init": True,
|
|
"unspent_coin_count": 1,
|
|
},
|
|
},
|
|
),
|
|
]
|
|
)
|
|
|
|
async def num_wallets() -> int:
|
|
return len(await wallet_node_1.wallet_state_manager.get_all_wallet_info_entries())
|
|
|
|
await time_out_assert(30, num_wallets, 2)
|
|
# Get the new NFT wallet
|
|
nft_wallets = await wallet_node_1.wallet_state_manager.get_all_wallet_info_entries(WalletType.NFT)
|
|
assert len(nft_wallets) == 1
|
|
nft_wallet_1 = wallet_node_1.wallet_state_manager.wallets[nft_wallets[0].id]
|
|
assert isinstance(nft_wallet_1, NFTWallet)
|
|
await time_out_assert(30, get_nft_count, 0, nft_wallet_0)
|
|
await time_out_assert(30, get_nft_count, 1, nft_wallet_1)
|
|
|
|
assert await nft_wallet_0.get_nft_count() == 0
|
|
assert await nft_wallet_1.get_nft_count() == 1
|
|
|
|
|
|
@pytest.mark.limit_consensus_modes(allowed=[ConsensusMode.PLAIN], reason="irrelevant")
|
|
@pytest.mark.parametrize("wallet_environments", [{"num_environments": 2, "blocks_needed": [1, 1]}], indirect=True)
|
|
@pytest.mark.anyio
|
|
async def test_nft_wallet_creation_and_transfer(wallet_environments: WalletTestFramework) -> None:
|
|
env_0 = wallet_environments.environments[0]
|
|
env_1 = wallet_environments.environments[1]
|
|
full_node_api = wallet_environments.full_node
|
|
wallet_node_0 = env_0.node
|
|
wallet_node_1 = env_1.node
|
|
wallet_0 = env_0.xch_wallet
|
|
wallet_1 = env_1.xch_wallet
|
|
|
|
env_0.wallet_aliases = {
|
|
"xch": 1,
|
|
"nft": 2,
|
|
}
|
|
env_1.wallet_aliases = {
|
|
"xch": 1,
|
|
"nft": 2,
|
|
}
|
|
|
|
nft_wallet_0 = await NFTWallet.create_new_nft_wallet(
|
|
wallet_node_0.wallet_state_manager, wallet_0, name="NFT WALLET 1"
|
|
)
|
|
metadata = Program.to(
|
|
[
|
|
("u", ["https://www.chia.net/img/branding/chia-logo.svg"]),
|
|
("h", "0xD4584AD463139FA8C0D9F68F4B59F185D4584AD463139FA8C0D9F68F4B59F185"),
|
|
]
|
|
)
|
|
async with nft_wallet_0.wallet_state_manager.new_action_scope(
|
|
wallet_environments.tx_config, push=True
|
|
) as action_scope:
|
|
await nft_wallet_0.generate_new_nft(metadata, action_scope)
|
|
for tx in action_scope.side_effects.transactions:
|
|
if tx.spend_bundle is not None:
|
|
# ensure hints are generated
|
|
assert len(compute_memos(tx.spend_bundle)) > 0
|
|
|
|
await wallet_environments.process_pending_states(
|
|
[
|
|
WalletStateTransition(
|
|
pre_block_balance_updates={
|
|
"xch": {
|
|
"confirmed_wallet_balance": 0,
|
|
"unconfirmed_wallet_balance": -1,
|
|
"<=#spendable_balance": -1,
|
|
"<=#max_send_amount": -1,
|
|
"pending_coin_removal_count": 1,
|
|
">=#pending_change": 1, # any amount increase
|
|
"unspent_coin_count": 0,
|
|
},
|
|
"nft": {
|
|
"init": True,
|
|
"confirmed_wallet_balance": 0,
|
|
"unconfirmed_wallet_balance": 0,
|
|
"spendable_balance": 0,
|
|
"max_send_amount": 0,
|
|
"pending_coin_removal_count": 1,
|
|
"pending_change": 0,
|
|
"unspent_coin_count": 0,
|
|
},
|
|
},
|
|
post_block_balance_updates={
|
|
"xch": {
|
|
"confirmed_wallet_balance": -1,
|
|
"unconfirmed_wallet_balance": 0,
|
|
">=#spendable_balance": 1, # any amount increase
|
|
">=#max_send_amount": 1, # any amount increase
|
|
"pending_coin_removal_count": -1,
|
|
"<=#pending_change": -1, # any amount decrease
|
|
"unspent_coin_count": 0,
|
|
},
|
|
"nft": {
|
|
"pending_coin_removal_count": -1,
|
|
"unspent_coin_count": 1,
|
|
},
|
|
},
|
|
),
|
|
WalletStateTransition(),
|
|
]
|
|
)
|
|
|
|
await time_out_assert(10, get_nft_count, 1, nft_wallet_0)
|
|
|
|
# Test Reorg mint
|
|
height = full_node_api.full_node.blockchain.get_peak_height()
|
|
assert height is not None
|
|
await full_node_api.reorg_from_index_to_new_index(
|
|
ReorgProtocol(uint32(height - 1), uint32(height + 1), bytes32.zeros, None)
|
|
)
|
|
await time_out_assert(60, full_node_api.full_node.blockchain.get_peak_height, height + 1)
|
|
await full_node_api.wait_for_wallet_synced(wallet_node=wallet_node_0, peak_height=uint32(height + 1), timeout=10)
|
|
await full_node_api.wait_for_wallet_synced(wallet_node=wallet_node_1, peak_height=uint32(height + 1), timeout=10)
|
|
await env_0.change_balances(
|
|
{
|
|
"xch": {
|
|
"set_remainder": True, # Not testing XCH reorg functionality in this test
|
|
},
|
|
"nft": {
|
|
# State back to before confirmation
|
|
"unspent_coin_count": -1,
|
|
"pending_coin_removal_count": 1,
|
|
},
|
|
}
|
|
)
|
|
await env_0.check_balances()
|
|
|
|
await time_out_assert(30, get_nft_count, 0, nft_wallet_0)
|
|
await time_out_assert(30, get_wallet_number, 2, wallet_node_0.wallet_state_manager)
|
|
|
|
new_metadata = Program.to([("u", ["https://www.test.net/logo.svg"]), ("h", "0xD4584AD463139FA8C0D9F68F4B59F181")])
|
|
|
|
async with nft_wallet_0.wallet_state_manager.new_action_scope(
|
|
wallet_environments.tx_config, push=True
|
|
) as action_scope:
|
|
await nft_wallet_0.generate_new_nft(new_metadata, action_scope)
|
|
for tx in action_scope.side_effects.transactions:
|
|
if tx.spend_bundle is not None:
|
|
# ensure hints are generated
|
|
assert len(compute_memos(tx.spend_bundle)) > 0
|
|
|
|
await wallet_environments.process_pending_states(
|
|
[
|
|
WalletStateTransition(
|
|
pre_block_balance_updates={
|
|
"xch": {
|
|
"confirmed_wallet_balance": 0,
|
|
"unconfirmed_wallet_balance": -1,
|
|
"<=#spendable_balance": -1,
|
|
"<=#max_send_amount": -1,
|
|
"pending_coin_removal_count": 1,
|
|
">=#pending_change": 1, # any amount increase
|
|
"unspent_coin_count": 0,
|
|
},
|
|
"nft": {
|
|
"pending_coin_removal_count": 1,
|
|
},
|
|
},
|
|
post_block_balance_updates={
|
|
"xch": {
|
|
"confirmed_wallet_balance": -2,
|
|
"unconfirmed_wallet_balance": 0,
|
|
">=#spendable_balance": 1, # any amount increase
|
|
">=#max_send_amount": 1, # any amount increase
|
|
"pending_coin_removal_count": -2,
|
|
"<=#pending_change": -1, # any amount decrease
|
|
"unspent_coin_count": 0,
|
|
},
|
|
"nft": {
|
|
"pending_coin_removal_count": -2,
|
|
"unspent_coin_count": 2,
|
|
},
|
|
},
|
|
),
|
|
WalletStateTransition(),
|
|
]
|
|
)
|
|
|
|
await time_out_assert(30, get_nft_count, 2, nft_wallet_0)
|
|
coins = await nft_wallet_0.get_current_nfts()
|
|
assert len(coins) == 2, "nft not generated"
|
|
|
|
nft_wallet_1 = await NFTWallet.create_new_nft_wallet(
|
|
wallet_node_1.wallet_state_manager, wallet_1, name="NFT WALLET 2"
|
|
)
|
|
async with wallet_1.wallet_state_manager.new_action_scope(wallet_environments.tx_config, push=True) as action_scope:
|
|
wallet_1_ph = await action_scope.get_puzzle_hash(wallet_1.wallet_state_manager)
|
|
async with nft_wallet_0.wallet_state_manager.new_action_scope(
|
|
wallet_environments.tx_config, push=True
|
|
) as action_scope:
|
|
await nft_wallet_0.generate_signed_transaction(
|
|
[uint64(coins[1].coin.amount)],
|
|
[wallet_1_ph],
|
|
action_scope,
|
|
coins={coins[1].coin},
|
|
)
|
|
assert action_scope.side_effects.transactions[0].spend_bundle is not None
|
|
|
|
assert len(compute_memos(action_scope.side_effects.transactions[0].spend_bundle)) > 0
|
|
|
|
await wallet_environments.process_pending_states(
|
|
[
|
|
WalletStateTransition(
|
|
pre_block_balance_updates={
|
|
"nft": {
|
|
"pending_coin_removal_count": 1,
|
|
}
|
|
},
|
|
post_block_balance_updates={
|
|
"nft": {
|
|
"pending_coin_removal_count": -1,
|
|
"unspent_coin_count": -1,
|
|
}
|
|
},
|
|
),
|
|
WalletStateTransition(
|
|
pre_block_balance_updates={
|
|
"nft": {
|
|
"init": True,
|
|
"confirmed_wallet_balance": 0,
|
|
"unconfirmed_wallet_balance": 0,
|
|
"spendable_balance": 0,
|
|
"max_send_amount": 0,
|
|
"pending_coin_removal_count": 0,
|
|
"pending_change": 0,
|
|
"unspent_coin_count": 0,
|
|
}
|
|
},
|
|
post_block_balance_updates={
|
|
"nft": {
|
|
"unspent_coin_count": 1,
|
|
}
|
|
},
|
|
),
|
|
]
|
|
)
|
|
|
|
await time_out_assert(30, get_nft_count, 1, nft_wallet_0)
|
|
await time_out_assert(30, get_nft_count, 1, nft_wallet_1)
|
|
|
|
coins = await nft_wallet_1.get_current_nfts()
|
|
assert len(coins) == 1
|
|
|
|
# Send it back to original owner
|
|
async with wallet_0.wallet_state_manager.new_action_scope(wallet_environments.tx_config, push=True) as action_scope:
|
|
wallet_0_ph = await action_scope.get_puzzle_hash(wallet_0.wallet_state_manager)
|
|
async with nft_wallet_1.wallet_state_manager.new_action_scope(
|
|
wallet_environments.tx_config, push=True
|
|
) as action_scope:
|
|
await nft_wallet_1.generate_signed_transaction(
|
|
[uint64(coins[0].coin.amount)],
|
|
[wallet_0_ph],
|
|
action_scope,
|
|
coins={coins[0].coin},
|
|
)
|
|
assert action_scope.side_effects.transactions[0].spend_bundle is not None
|
|
|
|
assert len(compute_memos(action_scope.side_effects.transactions[0].spend_bundle)) > 0
|
|
|
|
await wallet_environments.process_pending_states(
|
|
[
|
|
WalletStateTransition(
|
|
pre_block_balance_updates={},
|
|
post_block_balance_updates={
|
|
"nft": {
|
|
"unspent_coin_count": 1,
|
|
}
|
|
},
|
|
),
|
|
WalletStateTransition(
|
|
pre_block_balance_updates={
|
|
"nft": {
|
|
"pending_coin_removal_count": 1,
|
|
}
|
|
},
|
|
post_block_balance_updates={
|
|
"nft": {
|
|
"pending_coin_removal_count": -1,
|
|
"unspent_coin_count": -1,
|
|
}
|
|
},
|
|
),
|
|
]
|
|
)
|
|
|
|
await time_out_assert(30, get_nft_count, 2, nft_wallet_0)
|
|
await time_out_assert(30, get_nft_count, 0, nft_wallet_1)
|
|
|
|
# Test Reorg
|
|
height = full_node_api.full_node.blockchain.get_peak_height()
|
|
assert height is not None
|
|
await full_node_api.reorg_from_index_to_new_index(
|
|
ReorgProtocol(uint32(height - 1), uint32(height + 2), bytes32.zeros, None)
|
|
)
|
|
|
|
await full_node_api.wait_for_self_synced()
|
|
await full_node_api.wait_for_wallet_synced(wallet_node=wallet_node_0, peak_height=uint32(height + 2))
|
|
await full_node_api.wait_for_wallet_synced(wallet_node=wallet_node_1, peak_height=uint32(height + 2))
|
|
|
|
await env_0.change_balances(
|
|
{
|
|
"nft": {
|
|
"unspent_coin_count": -1,
|
|
}
|
|
}
|
|
)
|
|
await env_1.change_balances(
|
|
{
|
|
"nft": {
|
|
"pending_coin_removal_count": 1,
|
|
"unspent_coin_count": 1,
|
|
}
|
|
}
|
|
)
|
|
await env_0.check_balances()
|
|
await env_1.check_balances()
|
|
|
|
await time_out_assert(30, get_nft_count, 1, nft_wallet_0)
|
|
await time_out_assert(30, get_nft_count, 1, nft_wallet_1)
|
|
|
|
# Test an error case
|
|
with pytest.raises(ResponseFailureError, match="The NFT doesn't support setting a DID"):
|
|
await env_1.rpc_client.set_nft_did(
|
|
NFTSetNFTDID(
|
|
wallet_id=uint32(env_1.wallet_aliases["nft"]),
|
|
did_id=None,
|
|
nft_coin_id=(
|
|
await env_1.rpc_client.list_nfts(NFTGetNFTs(wallet_id=uint32(env_1.wallet_aliases["nft"])))
|
|
)
|
|
.nft_list[0]
|
|
.nft_coin_id,
|
|
),
|
|
tx_config=wallet_environments.tx_config,
|
|
)
|
|
|
|
|
|
@pytest.mark.limit_consensus_modes(allowed=[ConsensusMode.PLAIN], reason="irrelevant")
|
|
@pytest.mark.parametrize("wallet_environments", [{"num_environments": 1, "blocks_needed": [1]}], indirect=True)
|
|
@pytest.mark.anyio
|
|
async def test_nft_wallet_rpc_creation_and_list(wallet_environments: WalletTestFramework) -> None:
|
|
env = wallet_environments.environments[0]
|
|
wallet = env.xch_wallet
|
|
|
|
env.wallet_aliases = {
|
|
"xch": 1,
|
|
"nft": 2,
|
|
}
|
|
|
|
await create_nft_wallet_via_cli(wallet_environments, env, name="NFT WALLET 1")
|
|
assert env.wallet_aliases["nft"] in env.wallet_state_manager.wallets
|
|
|
|
async with wallet.wallet_state_manager.new_action_scope(wallet_environments.tx_config, push=True) as action_scope:
|
|
wallet_ph = await action_scope.get_puzzle_hash(wallet.wallet_state_manager)
|
|
royalty_address = CliAddress(
|
|
wallet_ph,
|
|
encode_puzzle_hash(wallet_ph, AddressType.XCH.hrp(env.node.config)),
|
|
AddressType.XCH,
|
|
)
|
|
await mint_nft_via_cli(
|
|
wallet_environments,
|
|
env,
|
|
wallet_id=env.wallet_aliases["nft"],
|
|
royalty_address=royalty_address,
|
|
hash="0xD4584AD463139FA8C0D9F68F4B59F185D4584AD463139FA8C0D9F68F4B59F185",
|
|
uris=["https://www.chia.net/img/branding/chia-logo.svg"],
|
|
no_did_ownership=True,
|
|
)
|
|
|
|
await wallet_environments.process_pending_states(
|
|
[
|
|
WalletStateTransition(
|
|
pre_block_balance_updates={
|
|
"xch": {"set_remainder": True}, # tested above
|
|
"nft": {"init": True, "pending_coin_removal_count": 1},
|
|
},
|
|
post_block_balance_updates={
|
|
"xch": {"set_remainder": True}, # tested above
|
|
"nft": {
|
|
"pending_coin_removal_count": -1,
|
|
"unspent_coin_count": 1,
|
|
},
|
|
},
|
|
)
|
|
]
|
|
)
|
|
|
|
await wait_rpc_state_condition(
|
|
30, env.rpc_client.fetch, ["nft_get_nfts", dict(wallet_id=env.wallet_aliases["nft"])], lambda x: x["nft_list"]
|
|
)
|
|
await mint_nft_via_cli(
|
|
wallet_environments,
|
|
env,
|
|
wallet_id=env.wallet_aliases["nft"],
|
|
royalty_address=royalty_address,
|
|
hash="0xD4584AD463139FA8C0D9F68F4B59F184D4584AD463139FA8C0D9F68F4B59F184",
|
|
uris=["https://chialisp.com/img/logo.svg"],
|
|
metadata_uris=(
|
|
"https://bafybeigzcazxeu7epmm4vtkuadrvysv74lbzzbl2evphtae6k57yhgynp4.ipfs.nftstorage.link/6590.json"
|
|
),
|
|
metadata_hash="0x6a9cb99b7b9a987309e8dd4fd14a7ca2423858585da68cc9ec689669dd6dd6ab",
|
|
no_did_ownership=True,
|
|
)
|
|
|
|
await wallet_environments.process_pending_states(
|
|
[
|
|
WalletStateTransition(
|
|
pre_block_balance_updates={
|
|
"xch": {"set_remainder": True}, # tested above
|
|
"nft": {"pending_coin_removal_count": 1},
|
|
},
|
|
post_block_balance_updates={
|
|
"xch": {"set_remainder": True}, # tested above
|
|
"nft": {
|
|
"pending_coin_removal_count": -1,
|
|
"unspent_coin_count": 1,
|
|
},
|
|
},
|
|
)
|
|
]
|
|
)
|
|
|
|
coins_response = await wait_rpc_state_condition(
|
|
30,
|
|
env.rpc_client.fetch,
|
|
["nft_get_nfts", dict(wallet_id=env.wallet_aliases["nft"])],
|
|
lambda x: x["success"] and len(x["nft_list"]) == 2,
|
|
)
|
|
coins: list[NFTInfo] = [NFTInfo.from_json_dict(d) for d in coins_response["nft_list"]]
|
|
uris = []
|
|
for coin in coins:
|
|
assert not coin.supports_did
|
|
uris.append(coin.data_uris[0])
|
|
assert coin.mint_height > 0
|
|
assert len(uris) == 2
|
|
assert "https://chialisp.com/img/logo.svg" in uris
|
|
|
|
coins_response = await wait_rpc_state_condition(
|
|
5,
|
|
env.rpc_client.fetch,
|
|
["nft_get_nfts", {"wallet_id": env.wallet_aliases["nft"], "start_index": 1, "num": 1}],
|
|
lambda x: x["success"] and len(x["nft_list"]) == 1,
|
|
)
|
|
coins = [NFTInfo.from_json_dict(d) for d in coins_response["nft_list"]]
|
|
assert len(coins) == 1
|
|
assert coins[0].data_hash.hex() == "0xD4584AD463139FA8C0D9F68F4B59F184D4584AD463139FA8C0D9F68F4B59F184"[2:].lower()
|
|
|
|
# test counts
|
|
assert (await env.rpc_client.count_nfts(NFTCountNFTs(wallet_id=uint32(env.wallet_aliases["nft"])))).count == 2
|
|
assert (await env.rpc_client.count_nfts(NFTCountNFTs())).count == 2
|
|
with pytest.raises(ResponseFailureError, match="Wallet with id 50 does not exist"):
|
|
await env.rpc_client.count_nfts(NFTCountNFTs(wallet_id=uint32(50)))
|
|
|
|
|
|
@pytest.mark.limit_consensus_modes(allowed=[ConsensusMode.PLAIN], reason="irrelevant")
|
|
@pytest.mark.parametrize("wallet_environments", [{"num_environments": 1, "blocks_needed": [1]}], indirect=True)
|
|
@pytest.mark.anyio
|
|
async def test_sign_message_by_nft_id(
|
|
wallet_environments: WalletTestFramework, capsys: pytest.CaptureFixture[str]
|
|
) -> None:
|
|
env = wallet_environments.environments[0]
|
|
wallet = env.xch_wallet
|
|
|
|
env.wallet_aliases = {
|
|
"xch": 1,
|
|
"nft": 2,
|
|
}
|
|
|
|
await create_nft_wallet_via_cli(wallet_environments, env, name="NFT WALLET 1")
|
|
assert env.wallet_aliases["nft"] in env.wallet_state_manager.wallets
|
|
|
|
async with wallet.wallet_state_manager.new_action_scope(wallet_environments.tx_config, push=True) as action_scope:
|
|
wallet_ph = await action_scope.get_puzzle_hash(wallet.wallet_state_manager)
|
|
royalty_address = CliAddress(
|
|
wallet_ph,
|
|
encode_puzzle_hash(wallet_ph, AddressType.XCH.hrp(env.node.config)),
|
|
AddressType.XCH,
|
|
)
|
|
await mint_nft_via_cli(
|
|
wallet_environments,
|
|
env,
|
|
wallet_id=env.wallet_aliases["nft"],
|
|
royalty_address=royalty_address,
|
|
hash="0xD4584AD463139FA8C0D9F68F4B59F185D4584AD463139FA8C0D9F68F4B59F185",
|
|
uris=["https://www.chia.net/img/branding/chia-logo.svg"],
|
|
no_did_ownership=True,
|
|
)
|
|
|
|
await wallet_environments.process_pending_states(
|
|
[
|
|
WalletStateTransition(
|
|
pre_block_balance_updates={
|
|
"xch": {"set_remainder": True}, # tested above
|
|
"nft": {"init": True, "pending_coin_removal_count": 1},
|
|
},
|
|
post_block_balance_updates={
|
|
"xch": {"set_remainder": True}, # tested above
|
|
"nft": {
|
|
"pending_coin_removal_count": -1,
|
|
"unspent_coin_count": 1,
|
|
},
|
|
},
|
|
)
|
|
]
|
|
)
|
|
|
|
nft_list = await env.rpc_client.list_nfts(NFTGetNFTs(wallet_id=uint32(env.wallet_aliases["nft"])))
|
|
nft_id = nft_list.nft_list[0].nft_id
|
|
nft_cli_address = CliAddress(decode_puzzle_hash(nft_id), nft_id, AddressType.NFT)
|
|
|
|
# Test CLI signing
|
|
message = "Hello World"
|
|
capsys.readouterr()
|
|
await NftSignMessageCMD(
|
|
rpc_info=wallet_environments.cmd_tx_endpoint_args(env)["rpc_info"],
|
|
nft_id=nft_cli_address,
|
|
hex_message=message.encode().hex(),
|
|
).run()
|
|
output = capsys.readouterr().out
|
|
assert f"Message: {message.encode().hex()}" in output
|
|
assert "Public Key:" in output
|
|
assert "Signature:" in output
|
|
|
|
# Test general string
|
|
message = "Hello World"
|
|
response = await env.rpc_client.sign_message_by_id(
|
|
SignMessageByID(
|
|
id=nft_id,
|
|
message=message,
|
|
)
|
|
)
|
|
puzzle: Program = Program.to((CHIP_0002_SIGN_MESSAGE_PREFIX, message))
|
|
assert AugSchemeMPL.verify(
|
|
response.pubkey,
|
|
puzzle.get_tree_hash(),
|
|
response.signature,
|
|
)
|
|
# Test hex string
|
|
message = "0123456789ABCDEF"
|
|
response = await env.rpc_client.sign_message_by_id(
|
|
SignMessageByID(
|
|
id=nft_id,
|
|
message=message,
|
|
is_hex=True,
|
|
)
|
|
)
|
|
puzzle = Program.to((CHIP_0002_SIGN_MESSAGE_PREFIX, bytes.fromhex(message)))
|
|
|
|
assert AugSchemeMPL.verify(
|
|
response.pubkey,
|
|
puzzle.get_tree_hash(),
|
|
response.signature,
|
|
)
|
|
|
|
# Test BLS sign string
|
|
message = "Hello World"
|
|
response = await env.rpc_client.sign_message_by_id(
|
|
SignMessageByID(
|
|
id=nft_id,
|
|
message=message,
|
|
is_hex=False,
|
|
safe_mode=False,
|
|
)
|
|
)
|
|
|
|
assert AugSchemeMPL.verify(
|
|
response.pubkey,
|
|
bytes(message, "utf-8"),
|
|
response.signature,
|
|
)
|
|
# Test BLS sign hex
|
|
message = "0123456789ABCDEF"
|
|
response = await env.rpc_client.sign_message_by_id(
|
|
SignMessageByID(
|
|
id=nft_id,
|
|
message=message,
|
|
is_hex=True,
|
|
safe_mode=False,
|
|
)
|
|
)
|
|
|
|
assert AugSchemeMPL.verify(
|
|
response.pubkey,
|
|
hexstr_to_bytes(message),
|
|
response.signature,
|
|
)
|
|
|
|
|
|
@pytest.mark.limit_consensus_modes(allowed=[ConsensusMode.PLAIN], reason="irrelevant")
|
|
@pytest.mark.parametrize("wallet_environments", [{"num_environments": 1, "blocks_needed": [1]}], indirect=True)
|
|
@pytest.mark.anyio
|
|
async def test_nft_wallet_rpc_update_metadata(
|
|
wallet_environments: WalletTestFramework, capsys: pytest.CaptureFixture[str]
|
|
) -> None:
|
|
env = wallet_environments.environments[0]
|
|
wallet_node = env.node
|
|
wallet = env.xch_wallet
|
|
|
|
env.wallet_aliases = {
|
|
"xch": 1,
|
|
"nft": 2,
|
|
}
|
|
|
|
nft_wallet = await NFTWallet.create_new_nft_wallet(wallet_node.wallet_state_manager, wallet, name="NFT WALLET 1")
|
|
|
|
await ListNftsCMD(
|
|
rpc_info=NeedsWalletRPC(
|
|
client_info=WalletClientInfo(
|
|
client=env.rpc_client,
|
|
fingerprint=env.wallet_state_manager.root_pubkey.get_fingerprint(),
|
|
config=env.wallet_state_manager.config,
|
|
)
|
|
),
|
|
wallet_id=nft_wallet.id(),
|
|
).run()
|
|
output = capsys.readouterr().out
|
|
assert "No NFTs found for wallet" in output
|
|
|
|
await mint_nft_via_cli(
|
|
wallet_environments,
|
|
env,
|
|
wallet_id=nft_wallet.id(),
|
|
hash="0xD4584AD463139FA8C0D9F68F4B59F185D4584AD463139FA8C0D9F68F4B59F185",
|
|
uris=["https://www.chia.net/img/branding/chia-logo.svg"],
|
|
no_did_ownership=True,
|
|
)
|
|
|
|
await wallet_environments.process_pending_states(
|
|
[
|
|
WalletStateTransition(
|
|
pre_block_balance_updates={
|
|
"xch": {"set_remainder": True},
|
|
"nft": {"init": True, "pending_coin_removal_count": 1},
|
|
},
|
|
post_block_balance_updates={
|
|
"xch": {"set_remainder": True},
|
|
"nft": {"pending_coin_removal_count": -1, "unspent_coin_count": 1},
|
|
},
|
|
)
|
|
]
|
|
)
|
|
|
|
await ListNftsCMD(
|
|
rpc_info=NeedsWalletRPC(
|
|
client_info=WalletClientInfo(
|
|
client=env.rpc_client,
|
|
fingerprint=env.wallet_state_manager.root_pubkey.get_fingerprint(),
|
|
config=env.wallet_state_manager.config,
|
|
)
|
|
),
|
|
wallet_id=nft_wallet.id(),
|
|
start_index=0,
|
|
num=1,
|
|
).run()
|
|
output = capsys.readouterr().out
|
|
assert "NFT content hash: d4584ad463139fa8c0d9f68f4b59f185d4584ad463139fa8c0d9f68f4b59f18" in output
|
|
assert (
|
|
'On-chain data/info: ((117 "https://www.chia.net/img/branding/chia-logo.svg") '
|
|
"(104 . 0xd4584ad463139fa8c0d9f68f4b59f185d4584ad463139fa8c0d9f68f4b59f185) "
|
|
"(28021) (27765) (29550 . 1) (29556 . 1))" in output
|
|
)
|
|
|
|
current_nft_coin_id = output.split("Current NFT coin ID:")[1].strip().split("\n")[0]
|
|
nft_coin_id = encode_puzzle_hash(bytes32.from_hexstr(current_nft_coin_id), AddressType.NFT.hrp(env.node.config))
|
|
await add_uri_to_nft_via_cli(
|
|
wallet_environments,
|
|
env,
|
|
wallet_id=nft_wallet.id(),
|
|
nft_coin_id=nft_coin_id,
|
|
metadata_uri="http://metadata",
|
|
)
|
|
|
|
coins = (
|
|
await env.rpc_client.list_nfts(NFTGetNFTs(wallet_id=nft_wallet.id(), start_index=uint32(0), num=uint32(1)))
|
|
).nft_list
|
|
assert coins[0].pending_transaction
|
|
|
|
await wallet_environments.process_pending_states(
|
|
[
|
|
WalletStateTransition(
|
|
pre_block_balance_updates={
|
|
"xch": {},
|
|
"nft": {"pending_coin_removal_count": 1},
|
|
},
|
|
post_block_balance_updates={
|
|
"xch": {},
|
|
"nft": {"pending_coin_removal_count": -1},
|
|
},
|
|
),
|
|
]
|
|
)
|
|
|
|
# check that new URI was added
|
|
coins = (
|
|
await env.rpc_client.list_nfts(NFTGetNFTs(wallet_id=nft_wallet.id(), start_index=uint32(0), num=uint32(1)))
|
|
).nft_list
|
|
assert len(coins) == 1
|
|
coin = coins[0]
|
|
assert coin.mint_height > 0
|
|
uris = coin.data_uris
|
|
assert len(uris) == 1
|
|
assert "https://www.chia.net/img/branding/chia-logo.svg" in uris
|
|
assert len(coin.metadata_uris) == 1
|
|
assert "http://metadata" == coin.metadata_uris[0]
|
|
assert len(coin.license_uris) == 0
|
|
|
|
# add yet another URI, this time using a hex nft_coin_id
|
|
await add_uri_to_nft_via_cli(
|
|
wallet_environments,
|
|
env,
|
|
wallet_id=nft_wallet.id(),
|
|
nft_coin_id=coin.nft_coin_id.hex(),
|
|
uri="http://data",
|
|
)
|
|
|
|
await wallet_environments.process_pending_states(
|
|
[
|
|
WalletStateTransition(
|
|
pre_block_balance_updates={
|
|
"xch": {},
|
|
"nft": {"pending_coin_removal_count": 1},
|
|
},
|
|
post_block_balance_updates={
|
|
"xch": {},
|
|
"nft": {"pending_coin_removal_count": -1},
|
|
},
|
|
),
|
|
]
|
|
)
|
|
|
|
coins = (
|
|
await env.rpc_client.list_nfts(NFTGetNFTs(wallet_id=nft_wallet.id(), start_index=uint32(0), num=uint32(1)))
|
|
).nft_list
|
|
assert len(coins) == 1
|
|
coin = coins[0]
|
|
assert coin.mint_height > 0
|
|
uris = coin.data_uris
|
|
assert len(uris) == 2
|
|
assert len(coin.metadata_uris) == 1
|
|
assert "http://data" == coin.data_uris[0]
|
|
|
|
|
|
@pytest.mark.limit_consensus_modes(allowed=[ConsensusMode.PLAIN], reason="irrelevant")
|
|
@pytest.mark.parametrize("wallet_environments", [{"num_environments": 1, "blocks_needed": [1]}], indirect=True)
|
|
@pytest.mark.anyio
|
|
async def test_nft_with_did_wallet_creation(
|
|
wallet_environments: WalletTestFramework, capsys: pytest.CaptureFixture[str]
|
|
) -> None:
|
|
env = wallet_environments.environments[0]
|
|
wallet_node = env.node
|
|
wallet = env.xch_wallet
|
|
|
|
env.wallet_aliases = {
|
|
"xch": 1,
|
|
"did": 2,
|
|
"nft_w_did": 3,
|
|
"nft_no_did": 4,
|
|
}
|
|
|
|
async with env.wallet_state_manager.new_action_scope(wallet_environments.tx_config, push=True) as action_scope:
|
|
did_wallet = await DIDWallet.create_new_did_wallet(env.wallet_state_manager, wallet, uint64(1), action_scope)
|
|
|
|
# use "set_remainder" here because this is more of a DID test issue
|
|
await wallet_environments.process_pending_states(
|
|
[
|
|
WalletStateTransition(
|
|
pre_block_balance_updates={
|
|
"xch": {"set_remainder": True},
|
|
"did": {"init": True, "set_remainder": True},
|
|
},
|
|
post_block_balance_updates={
|
|
"xch": {"set_remainder": True},
|
|
"did": {"set_remainder": True},
|
|
},
|
|
)
|
|
]
|
|
)
|
|
|
|
hex_did_id = did_wallet.get_my_DID()
|
|
did_id = bytes32.from_hexstr(hex_did_id)
|
|
hmr_did_id = encode_puzzle_hash(bytes32.from_hexstr(hex_did_id), AddressType.DID.hrp(env.node.config))
|
|
|
|
nft_wallet = await NFTWallet.create_new_nft_wallet(
|
|
wallet_node.wallet_state_manager, wallet, name="NFT WALLET 1", did_id=did_id
|
|
)
|
|
|
|
# this shouldn't work
|
|
res = await env.rpc_client.fetch(
|
|
"create_new_wallet", dict(wallet_type="nft_wallet", name="NFT WALLET 1", did_id=hmr_did_id)
|
|
)
|
|
assert res["wallet_id"] == nft_wallet.id()
|
|
|
|
await wallet_environments.process_pending_states(
|
|
[
|
|
WalletStateTransition(
|
|
pre_block_balance_updates={"nft_w_did": {"init": True}},
|
|
post_block_balance_updates={},
|
|
)
|
|
]
|
|
)
|
|
|
|
# now create NFT wallet with P2 standard puzzle for inner puzzle
|
|
await create_nft_wallet_via_cli(wallet_environments, env, name="NFT WALLET 0")
|
|
nft_wallet_p2_puzzle = env.wallet_aliases["nft_no_did"]
|
|
assert nft_wallet_p2_puzzle != nft_wallet.id()
|
|
|
|
with pytest.raises(ResponseFailureError, match="Cannot find a NFT wallet DID"):
|
|
await env.rpc_client.get_nft_wallet_by_did(NFTGetByDID(did_id=encode_puzzle_hash(bytes32.zeros, "did")))
|
|
wallet_by_did_response = await env.rpc_client.get_nft_wallet_by_did(NFTGetByDID(did_id=hmr_did_id))
|
|
assert nft_wallet.id() == wallet_by_did_response.wallet_id
|
|
|
|
await wallet_environments.process_pending_states(
|
|
[
|
|
WalletStateTransition(
|
|
pre_block_balance_updates={"nft_no_did": {"init": True}},
|
|
post_block_balance_updates={},
|
|
)
|
|
]
|
|
)
|
|
|
|
assert (await env.rpc_client.get_nft_wallets_with_dids()).nft_wallets == [
|
|
NFTWalletWithDID(wallet_id=nft_wallet.id(), did_id=hmr_did_id, did_wallet_id=did_wallet.id())
|
|
]
|
|
|
|
get_did_res = await env.rpc_client.get_nft_wallet_did(NFTGetWalletDID(wallet_id=nft_wallet.id()))
|
|
assert get_did_res.did_id == hmr_did_id
|
|
|
|
# Create a NFT with DID
|
|
async with wallet.wallet_state_manager.new_action_scope(wallet_environments.tx_config, push=True) as action_scope:
|
|
nft_ph = await action_scope.get_puzzle_hash(wallet.wallet_state_manager)
|
|
await mint_nft_via_cli(
|
|
wallet_environments,
|
|
env,
|
|
wallet_id=nft_wallet.id(),
|
|
target_address=CliAddress(nft_ph, encode_puzzle_hash(nft_ph, "txch"), AddressType.XCH),
|
|
hash="0xD4584AD463139FA8C0D9F68F4B59F185D4584AD463139FA8C0D9F68F4B59F185",
|
|
uris=["https://www.chia.net/img/branding/chia-logo.svg"],
|
|
no_did_ownership=True,
|
|
)
|
|
assert (
|
|
"Failed to mint NFT: Disabling DID ownership is not supported for this NFT wallet, it does have a DID"
|
|
in capsys.readouterr().out
|
|
)
|
|
await mint_nft_via_cli(
|
|
wallet_environments,
|
|
env,
|
|
wallet_id=nft_wallet.id(),
|
|
target_address=CliAddress(nft_ph, encode_puzzle_hash(nft_ph, "txch"), AddressType.XCH),
|
|
hash="0xD4584AD463139FA8C0D9F68F4B59F185D4584AD463139FA8C0D9F68F4B59F185",
|
|
uris=["https://www.chia.net/img/branding/chia-logo.svg"],
|
|
)
|
|
|
|
await wallet_environments.process_pending_states(
|
|
[
|
|
WalletStateTransition(
|
|
pre_block_balance_updates={
|
|
"xch": {"set_remainder": True},
|
|
"did": {
|
|
"spendable_balance": -1,
|
|
"pending_change": 1,
|
|
"pending_coin_removal_count": 1,
|
|
"max_send_amount": -1,
|
|
},
|
|
"nft_w_did": {"pending_coin_removal_count": 1},
|
|
},
|
|
post_block_balance_updates={
|
|
"xch": {"set_remainder": True},
|
|
"did": {
|
|
"spendable_balance": 1,
|
|
"pending_change": -1,
|
|
"pending_coin_removal_count": -1,
|
|
"max_send_amount": 1,
|
|
},
|
|
"nft_w_did": {"pending_coin_removal_count": -1, "unspent_coin_count": 1},
|
|
},
|
|
)
|
|
]
|
|
)
|
|
# Create a NFT without DID, this will go the unassigned NFT wallet
|
|
resp = await env.rpc_client.mint_nft(
|
|
request=NFTMintNFTRequest(
|
|
wallet_id=nft_wallet.id(),
|
|
royalty_address=None,
|
|
target_address=None,
|
|
hash=bytes32.from_hexstr("0xD4584AD463139FA8C0D9F68F4B59F181D4584AD463139FA8C0D9F68F4B59F181"),
|
|
uris=["https://url1"],
|
|
did_id="",
|
|
push=True,
|
|
),
|
|
tx_config=wallet_environments.tx_config,
|
|
)
|
|
|
|
# ensure hints are generated
|
|
assert len(compute_memos(resp.spend_bundle)) > 0
|
|
|
|
# TODO: the "pending_coin_removal_count" here is a bit weird. I think it's right
|
|
# but it might be worth refactoring the minting flow generally to only add transaction
|
|
# records for the xch wallet rather than some arbitrary nft wallet.
|
|
await wallet_environments.process_pending_states(
|
|
[
|
|
WalletStateTransition(
|
|
pre_block_balance_updates={
|
|
"xch": {"set_remainder": True},
|
|
"did": {},
|
|
"nft_w_did": {"pending_coin_removal_count": 1},
|
|
"nft_no_did": {},
|
|
},
|
|
post_block_balance_updates={
|
|
"xch": {"set_remainder": True},
|
|
"did": {},
|
|
"nft_w_did": {"pending_coin_removal_count": -1},
|
|
"nft_no_did": {"unspent_coin_count": 1},
|
|
},
|
|
)
|
|
]
|
|
)
|
|
# Check DID NFT
|
|
coins: list[NFTInfo] = (
|
|
await env.rpc_client.list_nfts(NFTGetNFTs(wallet_id=nft_wallet.id(), start_index=uint32(0), num=uint32(1)))
|
|
).nft_list
|
|
assert len(coins) == 1
|
|
did_nft = coins[0]
|
|
assert did_nft.mint_height > 0
|
|
assert did_nft.supports_did
|
|
assert did_nft.data_uris[0] == "https://www.chia.net/img/branding/chia-logo.svg"
|
|
assert did_nft.data_hash == bytes32.from_hexstr(
|
|
"0xD4584AD463139FA8C0D9F68F4B59F185D4584AD463139FA8C0D9F68F4B59F185"
|
|
)
|
|
assert did_nft.owner_did is not None
|
|
assert did_nft.owner_did.hex() == hex_did_id
|
|
# Check unassigned NFT
|
|
nft_wallets = await env.wallet_state_manager.get_all_wallet_info_entries(WalletType.NFT)
|
|
assert len(nft_wallets) == 2
|
|
coins = (
|
|
await env.rpc_client.list_nfts(
|
|
NFTGetNFTs(wallet_id=uint32(nft_wallet_p2_puzzle), start_index=uint32(0), num=uint32(1))
|
|
)
|
|
).nft_list
|
|
assert len(coins) == 1
|
|
non_did_nft = coins[0]
|
|
assert non_did_nft.mint_height > 0
|
|
assert non_did_nft.supports_did
|
|
assert non_did_nft.data_uris[0] == "https://url1"
|
|
assert non_did_nft.data_hash == bytes32.from_hexstr(
|
|
"0xD4584AD463139FA8C0D9F68F4B59F181D4584AD463139FA8C0D9F68F4B59F181"
|
|
)
|
|
assert non_did_nft.owner_did is None
|
|
|
|
|
|
@pytest.mark.limit_consensus_modes(allowed=[ConsensusMode.PLAIN], reason="irrelevant")
|
|
@pytest.mark.parametrize("wallet_environments", [{"num_environments": 1, "blocks_needed": [1]}], indirect=True)
|
|
@pytest.mark.anyio
|
|
async def test_nft_rpc_mint(wallet_environments: WalletTestFramework) -> None:
|
|
env = wallet_environments.environments[0]
|
|
wallet = env.xch_wallet
|
|
|
|
env.wallet_aliases = {
|
|
"xch": 1,
|
|
"did": 2,
|
|
"nft_w_did": 3,
|
|
"nft_no_did": 4,
|
|
}
|
|
|
|
async with env.wallet_state_manager.new_action_scope(wallet_environments.tx_config, push=True) as action_scope:
|
|
did_wallet = await DIDWallet.create_new_did_wallet(env.wallet_state_manager, wallet, uint64(1), action_scope)
|
|
|
|
# use "set_remainder" here because this is more of a DID test issue
|
|
await wallet_environments.process_pending_states(
|
|
[
|
|
WalletStateTransition(
|
|
pre_block_balance_updates={
|
|
"xch": {"set_remainder": True},
|
|
"did": {"init": True, "set_remainder": True},
|
|
},
|
|
post_block_balance_updates={
|
|
"xch": {"set_remainder": True},
|
|
"did": {"set_remainder": True},
|
|
},
|
|
)
|
|
]
|
|
)
|
|
|
|
did_id = encode_puzzle_hash(bytes32.from_hexstr(did_wallet.get_my_DID()), AddressType.DID.hrp(env.node.config))
|
|
|
|
await create_nft_wallet_via_cli(
|
|
wallet_environments,
|
|
env,
|
|
name="NFT WALLET 1",
|
|
did_id=CliAddress(decode_puzzle_hash(did_id), did_id, AddressType.DID),
|
|
)
|
|
assert env.wallet_aliases["nft_w_did"] in env.wallet_state_manager.wallets
|
|
|
|
await wallet_environments.process_pending_states(
|
|
[
|
|
WalletStateTransition(
|
|
pre_block_balance_updates={"nft_w_did": {"init": True}},
|
|
post_block_balance_updates={},
|
|
)
|
|
]
|
|
)
|
|
|
|
# Create a NFT with DID
|
|
async with wallet.wallet_state_manager.new_action_scope(wallet_environments.tx_config, push=True) as action_scope:
|
|
royalty_address = await action_scope.get_puzzle_hash(wallet.wallet_state_manager)
|
|
royalty_cli = CliAddress(
|
|
royalty_address,
|
|
encode_puzzle_hash(royalty_address, AddressType.XCH.hrp(env.node.config)),
|
|
AddressType.XCH,
|
|
)
|
|
data_hash_param = "0xD4584AD463139FA8C0D9F68F4B59F185D4584AD463139FA8C0D9F68F4B59F185"
|
|
license_uris = ["http://mylicenseuri"]
|
|
license_hash = "0xcafef00dcafef00dcafef00dcafef00dcafef00dcafef00dcafef00dcafef00d"
|
|
meta_uris = ["http://metauri"]
|
|
meta_hash = "0xdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef"
|
|
royalty_percentage = 200
|
|
sn = 10
|
|
st = 100
|
|
await mint_nft_via_cli(
|
|
wallet_environments,
|
|
env,
|
|
wallet_id=env.wallet_aliases["nft_w_did"],
|
|
royalty_address=royalty_cli,
|
|
target_address=royalty_cli,
|
|
hash=data_hash_param,
|
|
uris=["https://www.chia.net/img/branding/chia-logo.svg"],
|
|
metadata_hash=meta_hash,
|
|
metadata_uris=",".join(meta_uris),
|
|
license_hash=license_hash,
|
|
license_uris=",".join(license_uris),
|
|
edition_total=st,
|
|
edition_number=sn,
|
|
royalty_percentage_fraction=royalty_percentage,
|
|
)
|
|
|
|
await wallet_environments.process_pending_states(
|
|
[
|
|
WalletStateTransition(
|
|
pre_block_balance_updates={
|
|
"xch": {"set_remainder": True},
|
|
"did": {
|
|
"spendable_balance": -1,
|
|
"pending_change": 1,
|
|
"pending_coin_removal_count": 1,
|
|
"max_send_amount": -1,
|
|
},
|
|
"nft_w_did": {"pending_coin_removal_count": 1},
|
|
},
|
|
post_block_balance_updates={
|
|
"xch": {"set_remainder": True},
|
|
"did": {
|
|
"spendable_balance": 1,
|
|
"pending_change": -1,
|
|
"pending_coin_removal_count": -1,
|
|
"max_send_amount": 1,
|
|
},
|
|
"nft_w_did": {"pending_coin_removal_count": -1, "unspent_coin_count": 1},
|
|
},
|
|
)
|
|
]
|
|
)
|
|
|
|
coins: list[NFTInfo] = (
|
|
await env.rpc_client.list_nfts(
|
|
NFTGetNFTs(wallet_id=uint32(env.wallet_aliases["nft_w_did"]), start_index=uint32(0), num=uint32(1))
|
|
)
|
|
).nft_list
|
|
assert len(coins) == 1
|
|
did_nft = coins[0]
|
|
assert did_nft.royalty_puzzle_hash == royalty_address
|
|
assert did_nft.data_hash == bytes.fromhex(data_hash_param[2:])
|
|
assert did_nft.metadata_hash == bytes.fromhex(meta_hash[2:])
|
|
assert did_nft.metadata_uris == meta_uris
|
|
assert did_nft.license_uris == license_uris
|
|
assert did_nft.license_hash == bytes.fromhex(license_hash[2:])
|
|
assert did_nft.edition_total == st
|
|
assert did_nft.edition_number == sn
|
|
assert did_nft.royalty_percentage == royalty_percentage
|
|
|
|
|
|
@pytest.mark.limit_consensus_modes(allowed=[ConsensusMode.PLAIN], reason="irrelevant")
|
|
@pytest.mark.parametrize("wallet_environments", [{"num_environments": 2, "blocks_needed": [1, 1]}], indirect=True)
|
|
@pytest.mark.anyio
|
|
async def test_nft_transfer_nft_with_did(wallet_environments: WalletTestFramework) -> None:
|
|
env_0 = wallet_environments.environments[0]
|
|
env_1 = wallet_environments.environments[1]
|
|
wallet_0 = env_0.xch_wallet
|
|
wallet_1 = env_1.xch_wallet
|
|
|
|
env_0.wallet_aliases = {
|
|
"xch": 1,
|
|
"did": 2,
|
|
"nft": 3,
|
|
}
|
|
env_1.wallet_aliases = {
|
|
"xch": 1,
|
|
"did": 2,
|
|
"nft": 3,
|
|
"nft_w_did": 4,
|
|
}
|
|
# Create DID
|
|
async with wallet_0.wallet_state_manager.new_action_scope(wallet_environments.tx_config, push=True) as action_scope:
|
|
did_wallet = await DIDWallet.create_new_did_wallet(
|
|
env_0.wallet_state_manager, wallet_0, uint64(1), action_scope
|
|
)
|
|
|
|
# use "set_remainder" here because this is more of a DID test issue
|
|
await wallet_environments.process_pending_states(
|
|
[
|
|
WalletStateTransition(
|
|
pre_block_balance_updates={
|
|
"xch": {"set_remainder": True},
|
|
"did": {"init": True, "set_remainder": True},
|
|
},
|
|
post_block_balance_updates={
|
|
"xch": {"set_remainder": True},
|
|
"did": {"set_remainder": True},
|
|
},
|
|
),
|
|
WalletStateTransition(),
|
|
]
|
|
)
|
|
|
|
hex_did_id = did_wallet.get_my_DID()
|
|
hmr_did_id = encode_puzzle_hash(bytes32.from_hexstr(hex_did_id), AddressType.DID.hrp(env_0.node.config))
|
|
|
|
# Create NFT wallet
|
|
await create_nft_wallet_via_cli(
|
|
wallet_environments,
|
|
env_0,
|
|
name="NFT WALLET 1",
|
|
did_id=CliAddress(
|
|
bytes32.from_hexstr(hex_did_id),
|
|
hmr_did_id,
|
|
AddressType.DID,
|
|
),
|
|
)
|
|
assert env_0.wallet_aliases["nft"] in env_0.wallet_state_manager.wallets
|
|
|
|
await wallet_environments.process_pending_states(
|
|
[
|
|
WalletStateTransition(
|
|
pre_block_balance_updates={
|
|
"nft": {"init": True},
|
|
},
|
|
post_block_balance_updates={},
|
|
),
|
|
WalletStateTransition(),
|
|
]
|
|
)
|
|
|
|
# Create a NFT with DID
|
|
fee = 100
|
|
await mint_nft_via_cli(
|
|
wallet_environments,
|
|
env_0,
|
|
wallet_id=env_0.wallet_aliases["nft"],
|
|
hash="0xD4584AD463139FA8C0D9F68F4B59F185D4584AD463139FA8C0D9F68F4B59F185",
|
|
uris=["https://www.chia.net/img/branding/chia-logo.svg"],
|
|
fee=uint64(fee),
|
|
)
|
|
|
|
await wallet_environments.process_pending_states(
|
|
[
|
|
WalletStateTransition(
|
|
pre_block_balance_updates={
|
|
"xch": {
|
|
"unconfirmed_wallet_balance": -fee - 1,
|
|
"<=#spendable_balance": -fee - 1,
|
|
">=#pending_change": 1, # any amount increase
|
|
"<=#max_send_amount": -fee - 1,
|
|
"pending_coin_removal_count": 1,
|
|
},
|
|
"did": {
|
|
"spendable_balance": -1,
|
|
"pending_change": 1,
|
|
"pending_coin_removal_count": 1,
|
|
"max_send_amount": -1,
|
|
},
|
|
"nft": {"pending_coin_removal_count": 1},
|
|
},
|
|
post_block_balance_updates={
|
|
"xch": {
|
|
"confirmed_wallet_balance": -fee - 1,
|
|
">=#spendable_balance": 1, # any amount increase
|
|
"<=#pending_change": -1, # any amount decrease
|
|
">=#max_send_amount": 1, # any amount increase
|
|
"pending_coin_removal_count": -1,
|
|
},
|
|
"did": {
|
|
"spendable_balance": 1,
|
|
"pending_change": -1,
|
|
"pending_coin_removal_count": -1,
|
|
"max_send_amount": 1,
|
|
},
|
|
"nft": {"pending_coin_removal_count": -1, "unspent_coin_count": 1},
|
|
},
|
|
),
|
|
WalletStateTransition(),
|
|
]
|
|
)
|
|
|
|
# Check DID NFT
|
|
coins: list[NFTInfo] = (
|
|
await env_0.rpc_client.list_nfts(
|
|
NFTGetNFTs(wallet_id=uint32(env_0.wallet_aliases["nft"]), start_index=uint32(0), num=uint32(1))
|
|
)
|
|
).nft_list
|
|
assert len(coins) == 1
|
|
coin = coins[0]
|
|
assert coin.owner_did is not None
|
|
assert coin.owner_did.hex() == hex_did_id
|
|
|
|
assert len(env_1.wallet_state_manager.wallets) == 1, "NFT wallet shouldn't exist yet"
|
|
assert len(env_0.wallet_state_manager.wallets) == 3
|
|
|
|
# transfer DID to the other wallet
|
|
async with wallet_1.wallet_state_manager.new_action_scope(wallet_environments.tx_config, push=True) as action_scope:
|
|
wallet_1_ph = await action_scope.get_puzzle_hash(wallet_1.wallet_state_manager)
|
|
async with did_wallet.wallet_state_manager.new_action_scope(
|
|
wallet_environments.tx_config, push=True
|
|
) as action_scope:
|
|
await did_wallet.transfer_did(wallet_1_ph, uint64(0), action_scope)
|
|
|
|
await wallet_environments.process_pending_states(
|
|
[
|
|
WalletStateTransition(
|
|
pre_block_balance_updates={
|
|
"did": {
|
|
"unconfirmed_wallet_balance": -1,
|
|
"spendable_balance": -1,
|
|
"pending_coin_removal_count": 1,
|
|
"max_send_amount": -1,
|
|
}
|
|
},
|
|
post_block_balance_updates={}, # DID wallet is deleted
|
|
),
|
|
WalletStateTransition(
|
|
pre_block_balance_updates={},
|
|
post_block_balance_updates={
|
|
"did": {
|
|
"init": True,
|
|
"set_remainder": True, # only important to test creation
|
|
}
|
|
},
|
|
),
|
|
],
|
|
# TODO: there's a bug here where the user store autoincrement means this has a new ID after deleted
|
|
# Instead of 2, it becomes 3 because there was a 2 at some point (is my best guess)
|
|
reorg_exempt=True,
|
|
)
|
|
|
|
# Transfer NFT, wallet will be deleted
|
|
async with wallet_1.wallet_state_manager.new_action_scope(wallet_environments.tx_config, push=True) as action_scope:
|
|
wallet_1_ph = await action_scope.get_puzzle_hash(wallet_1.wallet_state_manager)
|
|
await transfer_nft_via_cli(
|
|
wallet_environments,
|
|
env_0,
|
|
wallet_id=env_0.wallet_aliases["nft"],
|
|
nft_coin_id=coin.nft_coin_id.hex(),
|
|
target_address=CliAddress(
|
|
wallet_1_ph,
|
|
encode_puzzle_hash(wallet_1_ph, "xch"),
|
|
AddressType.XCH,
|
|
),
|
|
fee=uint64(fee),
|
|
)
|
|
|
|
await wallet_environments.process_pending_states(
|
|
[
|
|
WalletStateTransition(
|
|
pre_block_balance_updates={
|
|
"xch": {
|
|
"unconfirmed_wallet_balance": -fee,
|
|
"<=#spendable_balance": -fee,
|
|
">=#pending_change": 1, # any amount increase
|
|
"<=#max_send_amount": -fee,
|
|
"pending_coin_removal_count": 1,
|
|
},
|
|
"nft": {"pending_coin_removal_count": 1},
|
|
},
|
|
post_block_balance_updates={
|
|
"xch": {
|
|
"confirmed_wallet_balance": -fee,
|
|
">=#spendable_balance": 1, # any amount increase
|
|
"<=#pending_change": -1, # any amount decrease
|
|
">=#max_send_amount": 1, # any amount increase
|
|
"pending_coin_removal_count": -1,
|
|
}
|
|
# nft wallet deleted
|
|
},
|
|
),
|
|
WalletStateTransition(
|
|
pre_block_balance_updates={},
|
|
post_block_balance_updates={
|
|
"nft": {"init": True, "unspent_coin_count": 1},
|
|
},
|
|
),
|
|
]
|
|
)
|
|
|
|
# Check if the NFT owner DID is reset
|
|
wallet_by_did_response = await env_1.rpc_client.get_nft_wallet_by_did(NFTGetByDID())
|
|
assert env_1.wallet_aliases["nft"] == wallet_by_did_response.wallet_id
|
|
coins = (
|
|
await env_1.rpc_client.list_nfts(
|
|
NFTGetNFTs(wallet_id=uint32(env_1.wallet_aliases["nft"]), start_index=uint32(0), num=uint32(1))
|
|
)
|
|
).nft_list
|
|
assert len(coins) == 1
|
|
coin = coins[0]
|
|
assert coin.owner_did is None
|
|
assert coin.minter_did is not None
|
|
assert coin.minter_did.hex() == hex_did_id
|
|
nft_coin_id = coin.nft_coin_id
|
|
|
|
# Set DID
|
|
await set_nft_did_via_cli(
|
|
wallet_environments,
|
|
env_1,
|
|
wallet_id=env_1.wallet_aliases["nft"],
|
|
did_id=hmr_did_id,
|
|
nft_coin_id=nft_coin_id.hex(),
|
|
fee=uint64(fee),
|
|
)
|
|
|
|
await wallet_environments.process_pending_states(
|
|
[
|
|
WalletStateTransition(),
|
|
WalletStateTransition(
|
|
pre_block_balance_updates={
|
|
"xch": {
|
|
"unconfirmed_wallet_balance": -fee,
|
|
"<=#spendable_balance": -fee,
|
|
">=#pending_change": 1, # any amount increase
|
|
"<=#max_send_amount": -fee,
|
|
"pending_coin_removal_count": 1,
|
|
},
|
|
"did": {
|
|
"spendable_balance": -1,
|
|
"pending_change": 1,
|
|
"pending_coin_removal_count": 1,
|
|
"max_send_amount": -1,
|
|
},
|
|
"nft": {"pending_coin_removal_count": 1},
|
|
},
|
|
post_block_balance_updates={
|
|
"xch": {
|
|
"confirmed_wallet_balance": -fee,
|
|
">=#spendable_balance": 1, # any amount increase
|
|
"<=#pending_change": -1, # any amount decrease
|
|
">=#max_send_amount": 1, # any amount increase
|
|
"pending_coin_removal_count": -1,
|
|
},
|
|
"did": {
|
|
"spendable_balance": 1,
|
|
"pending_change": -1,
|
|
"pending_coin_removal_count": -1,
|
|
"max_send_amount": 1,
|
|
},
|
|
"nft": {"pending_coin_removal_count": -1, "unspent_coin_count": -1},
|
|
"nft_w_did": {"init": True, "unspent_coin_count": 1},
|
|
},
|
|
),
|
|
]
|
|
)
|
|
|
|
wallet_by_did_response = await env_1.rpc_client.get_nft_wallet_by_did(NFTGetByDID(did_id=hmr_did_id))
|
|
assert env_1.wallet_aliases["nft_w_did"] == wallet_by_did_response.wallet_id
|
|
# Check NFT DID is set now
|
|
coins = (
|
|
await env_1.rpc_client.list_nfts(
|
|
NFTGetNFTs(wallet_id=uint32(env_1.wallet_aliases["nft_w_did"]), start_index=uint32(0), num=uint32(1))
|
|
)
|
|
).nft_list
|
|
assert len(coins) == 1
|
|
coin = coins[0]
|
|
assert coin.owner_did is not None
|
|
assert coin.owner_did.hex() == hex_did_id
|
|
|
|
|
|
@pytest.mark.limit_consensus_modes(allowed=[ConsensusMode.PLAIN], reason="irrelevant")
|
|
@pytest.mark.parametrize("wallet_environments", [{"num_environments": 1, "blocks_needed": [1]}], indirect=True)
|
|
@pytest.mark.anyio
|
|
async def test_update_metadata_for_nft_did(wallet_environments: WalletTestFramework) -> None:
|
|
env = wallet_environments.environments[0]
|
|
wallet = env.xch_wallet
|
|
|
|
env.wallet_aliases = {
|
|
"xch": 1,
|
|
"did": 2,
|
|
"nft": 3,
|
|
}
|
|
|
|
async with env.wallet_state_manager.new_action_scope(wallet_environments.tx_config, push=True) as action_scope:
|
|
did_wallet = await DIDWallet.create_new_did_wallet(env.wallet_state_manager, wallet, uint64(1), action_scope)
|
|
|
|
# use "set_remainder" here because this is more of a DID test issue
|
|
await wallet_environments.process_pending_states(
|
|
[
|
|
WalletStateTransition(
|
|
pre_block_balance_updates={
|
|
"xch": {"set_remainder": True},
|
|
"did": {"init": True, "set_remainder": True},
|
|
},
|
|
post_block_balance_updates={
|
|
"xch": {"set_remainder": True},
|
|
"did": {"set_remainder": True},
|
|
},
|
|
),
|
|
]
|
|
)
|
|
|
|
hex_did_id = did_wallet.get_my_DID()
|
|
hmr_did_id = encode_puzzle_hash(bytes32.from_hexstr(hex_did_id), AddressType.DID.hrp(env.node.config))
|
|
|
|
# Create NFT wallet
|
|
await create_nft_wallet_via_cli(
|
|
wallet_environments,
|
|
env,
|
|
name="NFT WALLET 1",
|
|
did_id=CliAddress(
|
|
bytes32.from_hexstr(hex_did_id),
|
|
hmr_did_id,
|
|
AddressType.DID,
|
|
),
|
|
)
|
|
assert env.wallet_aliases["nft"] in env.wallet_state_manager.wallets
|
|
|
|
await wallet_environments.process_pending_states(
|
|
[
|
|
WalletStateTransition(
|
|
pre_block_balance_updates={
|
|
"nft": {"init": True},
|
|
},
|
|
post_block_balance_updates={},
|
|
),
|
|
]
|
|
)
|
|
|
|
# Create a NFT with DID
|
|
await mint_nft_via_cli(
|
|
wallet_environments,
|
|
env,
|
|
wallet_id=env.wallet_aliases["nft"],
|
|
hash="0xD4584AD463139FA8C0D9F68F4B59F185D4584AD463139FA8C0D9F68F4B59F185",
|
|
uris=["https://www.chia.net/img/branding/chia-logo.svg"],
|
|
metadata_uris="http://metadata",
|
|
)
|
|
|
|
await wallet_environments.process_pending_states(
|
|
[
|
|
WalletStateTransition(
|
|
pre_block_balance_updates={
|
|
"xch": {
|
|
"unconfirmed_wallet_balance": -1,
|
|
"<=#spendable_balance": -1,
|
|
">=#pending_change": 1, # any amount increase
|
|
"<=#max_send_amount": -1,
|
|
"pending_coin_removal_count": 1,
|
|
},
|
|
"did": {
|
|
"spendable_balance": -1,
|
|
"pending_change": 1,
|
|
"pending_coin_removal_count": 1,
|
|
"max_send_amount": -1,
|
|
},
|
|
"nft": {"pending_coin_removal_count": 1},
|
|
},
|
|
post_block_balance_updates={
|
|
"xch": {
|
|
"confirmed_wallet_balance": -1,
|
|
">=#spendable_balance": 1, # any amount increase
|
|
"<=#pending_change": -1, # any amount decrease
|
|
">=#max_send_amount": 1, # any amount increase
|
|
"pending_coin_removal_count": -1,
|
|
},
|
|
"did": {
|
|
"spendable_balance": 1,
|
|
"pending_change": -1,
|
|
"pending_coin_removal_count": -1,
|
|
"max_send_amount": 1,
|
|
},
|
|
"nft": {"pending_coin_removal_count": -1, "unspent_coin_count": 1},
|
|
},
|
|
),
|
|
]
|
|
)
|
|
|
|
# Check DID NFT
|
|
|
|
coins = (
|
|
await env.rpc_client.list_nfts(
|
|
NFTGetNFTs(wallet_id=uint32(env.wallet_aliases["nft"]), start_index=uint32(0), num=uint32(1))
|
|
)
|
|
).nft_list
|
|
assert len(coins) == 1
|
|
coin = coins[0]
|
|
assert coin.minter_did is not None
|
|
assert coin.minter_did.hex() == hex_did_id
|
|
nft_coin_id = coin.nft_coin_id
|
|
|
|
# add another URI
|
|
fee = 100
|
|
await add_uri_to_nft_via_cli(
|
|
wallet_environments,
|
|
env,
|
|
wallet_id=env.wallet_aliases["nft"],
|
|
nft_coin_id=nft_coin_id.hex(),
|
|
license_uri="http://license",
|
|
fee=uint64(fee),
|
|
)
|
|
|
|
coins = (
|
|
await env.rpc_client.list_nfts(
|
|
NFTGetNFTs(wallet_id=uint32(env.wallet_aliases["nft"]), start_index=uint32(0), num=uint32(1))
|
|
)
|
|
).nft_list
|
|
assert len(coins) == 1
|
|
coin = coins[0]
|
|
assert coin.pending_transaction
|
|
|
|
await wallet_environments.process_pending_states(
|
|
[
|
|
WalletStateTransition(
|
|
pre_block_balance_updates={
|
|
"xch": {
|
|
"unconfirmed_wallet_balance": -fee,
|
|
"<=#spendable_balance": -fee,
|
|
">=#pending_change": 1, # any amount increase
|
|
"<=#max_send_amount": -fee,
|
|
"pending_coin_removal_count": 1,
|
|
},
|
|
"nft": {"pending_coin_removal_count": 1},
|
|
},
|
|
post_block_balance_updates={
|
|
"xch": {
|
|
"confirmed_wallet_balance": -fee,
|
|
">=#spendable_balance": 1, # any amount increase
|
|
"<=#pending_change": -1, # any amount decrease
|
|
">=#max_send_amount": 1, # any amount increase
|
|
"pending_coin_removal_count": -1,
|
|
},
|
|
"nft": {"pending_coin_removal_count": -1},
|
|
},
|
|
),
|
|
]
|
|
)
|
|
|
|
# check that new URI was added
|
|
coins = (
|
|
await env.rpc_client.list_nfts(
|
|
NFTGetNFTs(wallet_id=uint32(env.wallet_aliases["nft"]), start_index=uint32(0), num=uint32(1))
|
|
)
|
|
).nft_list
|
|
assert len(coins) == 1
|
|
|
|
assert coins[0].minter_did is not None
|
|
assert coins[0].minter_did.hex() == hex_did_id
|
|
assert coins[0].mint_height > 0
|
|
uris = coins[0].data_uris
|
|
assert len(uris) == 1
|
|
assert "https://www.chia.net/img/branding/chia-logo.svg" in uris
|
|
assert len(coins[0].metadata_uris) == 1
|
|
assert "http://metadata" == coins[0].metadata_uris[0]
|
|
assert len(coins[0].license_uris) == 1
|
|
assert "http://license" == coins[0].license_uris[0]
|
|
|
|
|
|
@pytest.mark.limit_consensus_modes(allowed=[ConsensusMode.PLAIN], reason="irrelevant")
|
|
@pytest.mark.parametrize("wallet_environments", [{"num_environments": 1, "blocks_needed": [1]}], indirect=True)
|
|
@pytest.mark.anyio
|
|
async def test_nft_bulk_set_did(wallet_environments: WalletTestFramework) -> None:
|
|
env = wallet_environments.environments[0]
|
|
wallet = env.xch_wallet
|
|
|
|
env.wallet_aliases = {
|
|
"xch": 1,
|
|
"did": 2,
|
|
"nft_w_did": 3,
|
|
"nft_no_did": 4,
|
|
}
|
|
|
|
async with env.wallet_state_manager.new_action_scope(wallet_environments.tx_config, push=True) as action_scope:
|
|
did_wallet = await DIDWallet.create_new_did_wallet(env.wallet_state_manager, wallet, uint64(1), action_scope)
|
|
|
|
# use "set_remainder" here because this is more of a DID test issue
|
|
await wallet_environments.process_pending_states(
|
|
[
|
|
WalletStateTransition(
|
|
pre_block_balance_updates={
|
|
"xch": {"set_remainder": True},
|
|
"did": {"init": True, "set_remainder": True},
|
|
},
|
|
post_block_balance_updates={
|
|
"xch": {"set_remainder": True},
|
|
"did": {"set_remainder": True},
|
|
},
|
|
),
|
|
]
|
|
)
|
|
|
|
hex_did_id = did_wallet.get_my_DID()
|
|
hmr_did_id = encode_puzzle_hash(bytes32.from_hexstr(hex_did_id), AddressType.DID.hrp(env.node.config))
|
|
await create_nft_wallet_via_cli(
|
|
wallet_environments,
|
|
env,
|
|
name="NFT WALLET 1",
|
|
did_id=CliAddress(
|
|
bytes32.from_hexstr(hex_did_id),
|
|
hmr_did_id,
|
|
AddressType.DID,
|
|
),
|
|
)
|
|
assert env.wallet_aliases["nft_w_did"] in env.wallet_state_manager.wallets
|
|
await create_nft_wallet_via_cli(wallet_environments, env, name="NFT WALLET 2")
|
|
assert env.wallet_aliases["nft_no_did"] in env.wallet_state_manager.wallets
|
|
|
|
await wallet_environments.process_pending_states(
|
|
[
|
|
WalletStateTransition(
|
|
pre_block_balance_updates={
|
|
"nft_w_did": {"init": True},
|
|
"nft_no_did": {"init": True},
|
|
},
|
|
post_block_balance_updates={},
|
|
),
|
|
]
|
|
)
|
|
|
|
# Create an NFT with DID
|
|
await mint_nft_via_cli(
|
|
wallet_environments,
|
|
env,
|
|
wallet_id=env.wallet_aliases["nft_w_did"],
|
|
hash="0xD4584AD463139FA8C0D9F68F4B59F185D4584AD463139FA8C0D9F68F4B59F185",
|
|
uris=["https://www.chia.net/img/branding/chia-logo.svg"],
|
|
metadata_uris="https://www.chia.net/img/branding/chia-logo.svg",
|
|
)
|
|
|
|
await wallet_environments.process_pending_states(
|
|
[
|
|
WalletStateTransition(
|
|
pre_block_balance_updates={
|
|
"xch": {
|
|
"unconfirmed_wallet_balance": -1,
|
|
"<=#spendable_balance": -1,
|
|
">=#pending_change": 1, # any amount increase
|
|
"<=#max_send_amount": -1,
|
|
"pending_coin_removal_count": 1,
|
|
},
|
|
"did": {
|
|
"spendable_balance": -1,
|
|
"pending_change": 1,
|
|
"pending_coin_removal_count": 1,
|
|
"max_send_amount": -1,
|
|
},
|
|
"nft_w_did": {"pending_coin_removal_count": 1},
|
|
},
|
|
post_block_balance_updates={
|
|
"xch": {
|
|
"confirmed_wallet_balance": -1,
|
|
">=#spendable_balance": 1, # any amount increase
|
|
"<=#pending_change": -1, # any amount decrease
|
|
">=#max_send_amount": 1, # any amount increase
|
|
"pending_coin_removal_count": -1,
|
|
},
|
|
"did": {
|
|
"spendable_balance": 1,
|
|
"pending_change": -1,
|
|
"pending_coin_removal_count": -1,
|
|
"max_send_amount": 1,
|
|
},
|
|
"nft_w_did": {"pending_coin_removal_count": -1, "unspent_coin_count": 1},
|
|
},
|
|
)
|
|
]
|
|
)
|
|
|
|
# And one w/o
|
|
await mint_nft_via_cli(
|
|
wallet_environments,
|
|
env,
|
|
wallet_id=env.wallet_aliases["nft_no_did"],
|
|
hash="0xD4584AD463139FA8C0D9F68F4B59F185D4584AD463139FA8C0D9F68F4B59F185",
|
|
uris=["https://www.chia.net/img/branding/chia-logo.svg"],
|
|
metadata_uris="https://www.chia.net/img/branding/chia-logo.svg",
|
|
)
|
|
|
|
await wallet_environments.process_pending_states(
|
|
[
|
|
WalletStateTransition(
|
|
pre_block_balance_updates={
|
|
"xch": {
|
|
"unconfirmed_wallet_balance": -1,
|
|
"<=#spendable_balance": -1,
|
|
">=#pending_change": 1, # any amount increase
|
|
"<=#max_send_amount": -1,
|
|
"pending_coin_removal_count": 1,
|
|
},
|
|
"nft_no_did": {"pending_coin_removal_count": 1},
|
|
},
|
|
post_block_balance_updates={
|
|
"xch": {
|
|
"confirmed_wallet_balance": -1,
|
|
">=#spendable_balance": 1, # any amount increase
|
|
"<=#pending_change": -1, # any amount decrease
|
|
">=#max_send_amount": 1, # any amount increase
|
|
"pending_coin_removal_count": -1,
|
|
},
|
|
"nft_no_did": {"pending_coin_removal_count": -1, "unspent_coin_count": 1},
|
|
},
|
|
)
|
|
]
|
|
)
|
|
|
|
# Make a second one w/ DID to test "bulk" updating in same wallet
|
|
await mint_nft_via_cli(
|
|
wallet_environments,
|
|
env,
|
|
wallet_id=env.wallet_aliases["nft_w_did"],
|
|
hash="0xD4584AD463139FA8C0D9F68F4B59F185D4584AD463139FA8C0D9F68F4B59F185",
|
|
uris=["https://www.chia.net/img/branding/chia-logo.svg"],
|
|
metadata_uris="https://www.chia.net/img/branding/chia-logo.svg",
|
|
)
|
|
|
|
await wallet_environments.process_pending_states(
|
|
[
|
|
WalletStateTransition(
|
|
pre_block_balance_updates={
|
|
"xch": {
|
|
"unconfirmed_wallet_balance": -1,
|
|
"<=#spendable_balance": -1,
|
|
">=#pending_change": 1, # any amount increase
|
|
"<=#max_send_amount": -1,
|
|
"pending_coin_removal_count": 1,
|
|
},
|
|
"did": {
|
|
"spendable_balance": -1,
|
|
"pending_change": 1,
|
|
"pending_coin_removal_count": 1,
|
|
"max_send_amount": -1,
|
|
},
|
|
"nft_w_did": {"pending_coin_removal_count": 1},
|
|
},
|
|
post_block_balance_updates={
|
|
"xch": {
|
|
"confirmed_wallet_balance": -1,
|
|
">=#spendable_balance": 1, # any amount increase
|
|
"<=#pending_change": -1, # any amount decrease
|
|
">=#max_send_amount": 1, # any amount increase
|
|
"pending_coin_removal_count": -1,
|
|
},
|
|
"did": {
|
|
"spendable_balance": 1,
|
|
"pending_change": -1,
|
|
"pending_coin_removal_count": -1,
|
|
"max_send_amount": 1,
|
|
},
|
|
"nft_w_did": {"pending_coin_removal_count": -1, "unspent_coin_count": 1},
|
|
},
|
|
)
|
|
]
|
|
)
|
|
|
|
# Check DID NFT
|
|
coins = (
|
|
await env.rpc_client.list_nfts(
|
|
NFTGetNFTs(wallet_id=uint32(env.wallet_aliases["nft_w_did"]), start_index=uint32(0), num=uint32(2))
|
|
)
|
|
).nft_list
|
|
assert len(coins) == 2
|
|
nft1 = coins[0]
|
|
nft12 = coins[1]
|
|
assert nft1.owner_did is not None
|
|
assert nft12.owner_did is not None
|
|
coins = (
|
|
await env.rpc_client.list_nfts(
|
|
NFTGetNFTs(wallet_id=uint32(env.wallet_aliases["nft_no_did"]), start_index=uint32(0), num=uint32(1))
|
|
)
|
|
).nft_list
|
|
assert len(coins) == 1
|
|
nft2 = coins[0]
|
|
assert nft2.owner_did is None
|
|
nft_coin_list = [
|
|
NFTCoin(wallet_id=uint32(env.wallet_aliases["nft_w_did"]), nft_coin_id=nft1.nft_coin_id.hex()),
|
|
NFTCoin(
|
|
wallet_id=uint32(env.wallet_aliases["nft_w_did"]), nft_coin_id=encode_puzzle_hash(nft12.launcher_id, "nft")
|
|
),
|
|
NFTCoin(wallet_id=uint32(env.wallet_aliases["nft_no_did"]), nft_coin_id=nft2.nft_coin_id.hex()),
|
|
]
|
|
fee = uint64(1000)
|
|
with pytest.raises(ResponseFailureError, match="You can only set"):
|
|
await env.rpc_client.set_nft_did_bulk(
|
|
NFTSetDIDBulk(
|
|
did_id=hmr_did_id, nft_coin_list=[nft_coin_list[0]] * (MAX_NFT_CHUNK_SIZE + 1), fee=fee, push=True
|
|
),
|
|
wallet_environments.tx_config,
|
|
)
|
|
set_did_bulk_resp = await env.rpc_client.set_nft_did_bulk(
|
|
NFTSetDIDBulk(did_id=hmr_did_id, nft_coin_list=nft_coin_list, fee=fee, push=True),
|
|
wallet_environments.tx_config,
|
|
)
|
|
assert len(set_did_bulk_resp.spend_bundle.coin_spends) == 5
|
|
assert set_did_bulk_resp.tx_num == 5 # 1 for each NFT being spent (3), 1 for fee tx, 1 for did tx
|
|
coins = (
|
|
await env.rpc_client.list_nfts(
|
|
NFTGetNFTs(wallet_id=uint32(env.wallet_aliases["nft_w_did"]), start_index=uint32(0), num=uint32(2))
|
|
)
|
|
).nft_list
|
|
assert len(coins) == 2
|
|
nft1 = coins[0]
|
|
nft12 = coins[1]
|
|
assert nft1.pending_transaction
|
|
assert nft12.pending_transaction
|
|
|
|
await wallet_environments.process_pending_states(
|
|
[
|
|
WalletStateTransition(
|
|
pre_block_balance_updates={
|
|
"xch": {
|
|
"unconfirmed_wallet_balance": -fee,
|
|
"<=#spendable_balance": -fee,
|
|
">=#pending_change": 1, # any amount increase
|
|
"<=#max_send_amount": -fee,
|
|
"pending_coin_removal_count": 1,
|
|
},
|
|
"did": {
|
|
"spendable_balance": -1,
|
|
"pending_change": 1,
|
|
"pending_coin_removal_count": 1,
|
|
"max_send_amount": -1,
|
|
},
|
|
"nft_w_did": {"pending_coin_removal_count": 2},
|
|
"nft_no_did": {"pending_coin_removal_count": 1},
|
|
},
|
|
post_block_balance_updates={
|
|
"xch": {
|
|
"confirmed_wallet_balance": -fee,
|
|
">=#spendable_balance": 1, # any amount increase
|
|
"<=#pending_change": -1, # any amount decrease
|
|
">=#max_send_amount": 1, # any amount increase
|
|
"pending_coin_removal_count": -1,
|
|
},
|
|
"did": {
|
|
"spendable_balance": 1,
|
|
"pending_change": -1,
|
|
"pending_coin_removal_count": -1,
|
|
"max_send_amount": 1,
|
|
},
|
|
"nft_w_did": {"pending_coin_removal_count": -2, "unspent_coin_count": 1},
|
|
"nft_no_did": {"pending_coin_removal_count": -1, "unspent_coin_count": -1},
|
|
},
|
|
)
|
|
]
|
|
)
|
|
|
|
wallet_by_did_response = await env.rpc_client.get_nft_wallet_by_did(NFTGetByDID(did_id=hmr_did_id))
|
|
assert env.wallet_aliases["nft_w_did"] == wallet_by_did_response.wallet_id
|
|
coins = (
|
|
await env.rpc_client.list_nfts(
|
|
NFTGetNFTs(wallet_id=uint32(env.wallet_aliases["nft_w_did"]), start_index=uint32(0), num=uint32(3))
|
|
)
|
|
).nft_list
|
|
assert len(coins) == 3
|
|
nft1 = coins[0]
|
|
nft12 = coins[1]
|
|
nft13 = coins[2]
|
|
nft_wallet_to_check = env.wallet_state_manager.wallets[uint32(env.wallet_aliases["nft_w_did"])]
|
|
assert isinstance(nft_wallet_to_check, NFTWallet)
|
|
assert await nft_wallet_to_check.get_nft_count() == 3
|
|
|
|
assert nft1.owner_did is not None
|
|
assert nft1.owner_did.hex() == hex_did_id
|
|
assert nft12.owner_did is not None
|
|
assert nft12.owner_did.hex() == hex_did_id
|
|
assert nft13.owner_did is not None
|
|
assert nft13.owner_did.hex() == hex_did_id
|
|
|
|
|
|
@pytest.mark.limit_consensus_modes(allowed=[ConsensusMode.PLAIN], reason="irrelevant")
|
|
@pytest.mark.parametrize("wallet_environments", [{"num_environments": 2, "blocks_needed": [1, 1]}], indirect=True)
|
|
@pytest.mark.anyio
|
|
async def test_nft_bulk_transfer(wallet_environments: WalletTestFramework) -> None:
|
|
env_0 = wallet_environments.environments[0]
|
|
env_1 = wallet_environments.environments[1]
|
|
wallet_0 = env_0.xch_wallet
|
|
wallet_1 = env_1.xch_wallet
|
|
|
|
env_0.wallet_aliases = {
|
|
"xch": 1,
|
|
"did": 2,
|
|
"nft_w_did": 3,
|
|
"nft_no_did": 4,
|
|
}
|
|
env_1.wallet_aliases = {
|
|
"xch": 1,
|
|
"nft": 2,
|
|
}
|
|
async with env_0.wallet_state_manager.new_action_scope(wallet_environments.tx_config, push=True) as action_scope:
|
|
did_wallet = await DIDWallet.create_new_did_wallet(
|
|
env_0.wallet_state_manager, wallet_0, uint64(1), action_scope
|
|
)
|
|
|
|
# use "set_remainder" here because this is more of a DID test issue
|
|
await wallet_environments.process_pending_states(
|
|
[
|
|
WalletStateTransition(
|
|
pre_block_balance_updates={
|
|
"xch": {"set_remainder": True},
|
|
"did": {"init": True, "set_remainder": True},
|
|
},
|
|
post_block_balance_updates={
|
|
"xch": {"set_remainder": True},
|
|
"did": {"set_remainder": True},
|
|
},
|
|
),
|
|
WalletStateTransition(),
|
|
]
|
|
)
|
|
|
|
hex_did_id = did_wallet.get_my_DID()
|
|
hmr_did_id = encode_puzzle_hash(bytes32.from_hexstr(hex_did_id), AddressType.DID.hrp(env_0.node.config))
|
|
|
|
await create_nft_wallet_via_cli(
|
|
wallet_environments,
|
|
env_0,
|
|
name="NFT WALLET 1",
|
|
did_id=CliAddress(
|
|
bytes32.from_hexstr(hex_did_id),
|
|
hmr_did_id,
|
|
AddressType.DID,
|
|
),
|
|
)
|
|
await create_nft_wallet_via_cli(wallet_environments, env_0, name="NFT WALLET 2")
|
|
assert env_0.wallet_aliases["nft_w_did"] in env_0.wallet_state_manager.wallets
|
|
assert env_0.wallet_aliases["nft_no_did"] in env_0.wallet_state_manager.wallets
|
|
|
|
await wallet_environments.process_pending_states(
|
|
[
|
|
WalletStateTransition(
|
|
pre_block_balance_updates={
|
|
"nft_w_did": {"init": True},
|
|
"nft_no_did": {"init": True},
|
|
},
|
|
post_block_balance_updates={},
|
|
),
|
|
WalletStateTransition(),
|
|
]
|
|
)
|
|
|
|
# Create an NFT with DID
|
|
await mint_nft_via_cli(
|
|
wallet_environments,
|
|
env_0,
|
|
wallet_id=env_0.wallet_aliases["nft_w_did"],
|
|
hash="0xD4584AD463139FA8C0D9F68F4B59F185D4584AD463139FA8C0D9F68F4B59F185",
|
|
uris=["https://www.chia.net/img/branding/chia-logo.svg"],
|
|
metadata_uris="https://www.chia.net/img/branding/chia-logo.svg",
|
|
)
|
|
|
|
await wallet_environments.process_pending_states(
|
|
[
|
|
WalletStateTransition(
|
|
pre_block_balance_updates={
|
|
"xch": {
|
|
"unconfirmed_wallet_balance": -1,
|
|
"<=#spendable_balance": -1,
|
|
">=#pending_change": 1, # any amount increase
|
|
"<=#max_send_amount": -1,
|
|
"pending_coin_removal_count": 1,
|
|
},
|
|
"did": {
|
|
"spendable_balance": -1,
|
|
"pending_change": 1,
|
|
"pending_coin_removal_count": 1,
|
|
"max_send_amount": -1,
|
|
},
|
|
"nft_w_did": {"pending_coin_removal_count": 1},
|
|
},
|
|
post_block_balance_updates={
|
|
"xch": {
|
|
"confirmed_wallet_balance": -1,
|
|
">=#spendable_balance": 1, # any amount increase
|
|
"<=#pending_change": -1, # any amount decrease
|
|
">=#max_send_amount": 1, # any amount increase
|
|
"pending_coin_removal_count": -1,
|
|
},
|
|
"did": {
|
|
"spendable_balance": 1,
|
|
"pending_change": -1,
|
|
"pending_coin_removal_count": -1,
|
|
"max_send_amount": 1,
|
|
},
|
|
"nft_w_did": {"pending_coin_removal_count": -1, "unspent_coin_count": 1},
|
|
},
|
|
)
|
|
]
|
|
)
|
|
|
|
# And one w/o
|
|
await mint_nft_via_cli(
|
|
wallet_environments,
|
|
env_0,
|
|
wallet_id=env_0.wallet_aliases["nft_no_did"],
|
|
hash="0xD4584AD463139FA8C0D9F68F4B59F185D4584AD463139FA8C0D9F68F4B59F185",
|
|
uris=["https://www.chia.net/img/branding/chia-logo.svg"],
|
|
metadata_uris="https://www.chia.net/img/branding/chia-logo.svg",
|
|
)
|
|
|
|
await wallet_environments.process_pending_states(
|
|
[
|
|
WalletStateTransition(
|
|
pre_block_balance_updates={
|
|
"xch": {
|
|
"unconfirmed_wallet_balance": -1,
|
|
"<=#spendable_balance": -1,
|
|
">=#pending_change": 1, # any amount increase
|
|
"<=#max_send_amount": -1,
|
|
"pending_coin_removal_count": 1,
|
|
},
|
|
"nft_no_did": {"pending_coin_removal_count": 1},
|
|
},
|
|
post_block_balance_updates={
|
|
"xch": {
|
|
"confirmed_wallet_balance": -1,
|
|
">=#spendable_balance": 1, # any amount increase
|
|
"<=#pending_change": -1, # any amount decrease
|
|
">=#max_send_amount": 1, # any amount increase
|
|
"pending_coin_removal_count": -1,
|
|
},
|
|
"nft_no_did": {"pending_coin_removal_count": -1, "unspent_coin_count": 1},
|
|
},
|
|
)
|
|
]
|
|
)
|
|
|
|
# Make a second one w/ DID to test "bulk" updating in same wallet
|
|
await mint_nft_via_cli(
|
|
wallet_environments,
|
|
env_0,
|
|
wallet_id=env_0.wallet_aliases["nft_w_did"],
|
|
hash="0xD4584AD463139FA8C0D9F68F4B59F185D4584AD463139FA8C0D9F68F4B59F185",
|
|
uris=["https://www.chia.net/img/branding/chia-logo.svg"],
|
|
metadata_uris="https://www.chia.net/img/branding/chia-logo.svg",
|
|
)
|
|
|
|
await wallet_environments.process_pending_states(
|
|
[
|
|
WalletStateTransition(
|
|
pre_block_balance_updates={
|
|
"xch": {
|
|
"unconfirmed_wallet_balance": -1,
|
|
"<=#spendable_balance": -1,
|
|
">=#pending_change": 1, # any amount increase
|
|
"<=#max_send_amount": -1,
|
|
"pending_coin_removal_count": 1,
|
|
},
|
|
"did": {
|
|
"spendable_balance": -1,
|
|
"pending_change": 1,
|
|
"pending_coin_removal_count": 1,
|
|
"max_send_amount": -1,
|
|
},
|
|
"nft_w_did": {"pending_coin_removal_count": 1},
|
|
},
|
|
post_block_balance_updates={
|
|
"xch": {
|
|
"confirmed_wallet_balance": -1,
|
|
">=#spendable_balance": 1, # any amount increase
|
|
"<=#pending_change": -1, # any amount decrease
|
|
">=#max_send_amount": 1, # any amount increase
|
|
"pending_coin_removal_count": -1,
|
|
},
|
|
"did": {
|
|
"spendable_balance": 1,
|
|
"pending_change": -1,
|
|
"pending_coin_removal_count": -1,
|
|
"max_send_amount": 1,
|
|
},
|
|
"nft_w_did": {"pending_coin_removal_count": -1, "unspent_coin_count": 1},
|
|
},
|
|
)
|
|
]
|
|
)
|
|
|
|
# Check DID NFT
|
|
coins = (
|
|
await env_0.rpc_client.list_nfts(
|
|
NFTGetNFTs(wallet_id=uint32(env_0.wallet_aliases["nft_w_did"]), start_index=uint32(0), num=uint32(2))
|
|
)
|
|
).nft_list
|
|
assert len(coins) == 2
|
|
nft1 = coins[0]
|
|
nft12 = coins[1]
|
|
assert nft1.owner_did is not None
|
|
assert nft12.owner_did is not None
|
|
coins = (
|
|
await env_0.rpc_client.list_nfts(
|
|
NFTGetNFTs(wallet_id=uint32(env_0.wallet_aliases["nft_no_did"]), start_index=uint32(0), num=uint32(1))
|
|
)
|
|
).nft_list
|
|
assert len(coins) == 1
|
|
nft2 = coins[0]
|
|
assert nft2.owner_did is None
|
|
nft_coin_list = [
|
|
NFTCoin(wallet_id=uint32(env_0.wallet_aliases["nft_w_did"]), nft_coin_id=nft1.nft_coin_id.hex()),
|
|
NFTCoin(
|
|
wallet_id=uint32(env_0.wallet_aliases["nft_w_did"]),
|
|
nft_coin_id=encode_puzzle_hash(nft12.launcher_id, "nft"),
|
|
),
|
|
NFTCoin(wallet_id=uint32(env_0.wallet_aliases["nft_no_did"]), nft_coin_id=nft2.nft_coin_id.hex()),
|
|
]
|
|
|
|
async with wallet_1.wallet_state_manager.new_action_scope(wallet_environments.tx_config, push=True) as action_scope:
|
|
wallet_1_ph = await action_scope.get_puzzle_hash(wallet_1.wallet_state_manager)
|
|
fee = uint64(1000)
|
|
address = encode_puzzle_hash(wallet_1_ph, AddressType.XCH.hrp(env_1.node.config))
|
|
with pytest.raises(ResponseFailureError, match="You can only transfer"):
|
|
await env_0.rpc_client.transfer_nft_bulk(
|
|
NFTTransferBulk(target_address=address, nft_coin_list=[nft_coin_list[0]] * (MAX_NFT_CHUNK_SIZE + 1)),
|
|
wallet_environments.tx_config,
|
|
)
|
|
bulk_transfer_resp = await env_0.rpc_client.transfer_nft_bulk(
|
|
NFTTransferBulk(target_address=address, nft_coin_list=nft_coin_list, fee=fee, push=True),
|
|
wallet_environments.tx_config,
|
|
)
|
|
assert len(bulk_transfer_resp.spend_bundle.coin_spends) == 4
|
|
assert bulk_transfer_resp.tx_num == 4
|
|
|
|
await wallet_environments.process_pending_states(
|
|
[
|
|
WalletStateTransition(
|
|
pre_block_balance_updates={
|
|
"xch": {
|
|
"unconfirmed_wallet_balance": -fee,
|
|
"<=#spendable_balance": -fee,
|
|
">=#pending_change": 1, # any amount increase
|
|
"<=#max_send_amount": -fee,
|
|
"pending_coin_removal_count": 1,
|
|
},
|
|
"did": {},
|
|
"nft_w_did": {"pending_coin_removal_count": 2},
|
|
"nft_no_did": {"pending_coin_removal_count": 1},
|
|
},
|
|
post_block_balance_updates={
|
|
"xch": {
|
|
"confirmed_wallet_balance": -fee,
|
|
">=#spendable_balance": 1, # any amount increase
|
|
"<=#pending_change": -1, # any amount decrease
|
|
">=#max_send_amount": 1, # any amount increase
|
|
"pending_coin_removal_count": -1,
|
|
},
|
|
"did": {},
|
|
"nft_w_did": {"pending_coin_removal_count": -2, "unspent_coin_count": -2},
|
|
"nft_no_did": {"pending_coin_removal_count": -1, "unspent_coin_count": -1},
|
|
},
|
|
),
|
|
WalletStateTransition(
|
|
pre_block_balance_updates={
|
|
"xch": {},
|
|
},
|
|
post_block_balance_updates={
|
|
"xch": {},
|
|
"nft": {"init": True, "unspent_coin_count": 3},
|
|
},
|
|
),
|
|
]
|
|
)
|
|
|
|
await time_out_assert(30, get_wallet_number, 2, env_1.wallet_state_manager)
|
|
coins = (
|
|
await env_1.rpc_client.list_nfts(
|
|
NFTGetNFTs(wallet_id=uint32(env_1.wallet_aliases["nft"]), start_index=uint32(0), num=uint32(3))
|
|
)
|
|
).nft_list
|
|
assert len(coins) == 3
|
|
nft0 = coins[0]
|
|
nft02 = coins[1]
|
|
nft03 = coins[2]
|
|
nft_set = {nft1.launcher_id, nft12.launcher_id, nft2.launcher_id}
|
|
assert nft0.launcher_id in nft_set
|
|
assert nft02.launcher_id in nft_set
|
|
assert nft03.launcher_id in nft_set
|
|
assert nft0.owner_did is None
|
|
assert nft02.owner_did is None
|
|
assert nft03.owner_did is None
|
|
|
|
|
|
@pytest.mark.limit_consensus_modes(allowed=[ConsensusMode.PLAIN], reason="irrelevant")
|
|
@pytest.mark.parametrize("wallet_environments", [{"num_environments": 1, "blocks_needed": [1]}], indirect=True)
|
|
@pytest.mark.anyio
|
|
async def test_nft_set_did(wallet_environments: WalletTestFramework, capsys: pytest.CaptureFixture[str]) -> None:
|
|
env = wallet_environments.environments[0]
|
|
wallet = env.xch_wallet
|
|
|
|
env.wallet_aliases = {
|
|
"xch": 1,
|
|
"did1": 2,
|
|
"nft_w_did1": 3,
|
|
"nft_no_did": 4,
|
|
"did2": 5,
|
|
"nft_w_did2": 6,
|
|
}
|
|
|
|
async with env.wallet_state_manager.new_action_scope(wallet_environments.tx_config, push=True) as action_scope:
|
|
did_wallet = await DIDWallet.create_new_did_wallet(env.wallet_state_manager, wallet, uint64(1), action_scope)
|
|
|
|
# use "set_remainder" here because this is more of a DID test issue
|
|
await wallet_environments.process_pending_states(
|
|
[
|
|
WalletStateTransition(
|
|
pre_block_balance_updates={
|
|
"xch": {"set_remainder": True},
|
|
"did1": {"init": True, "set_remainder": True},
|
|
},
|
|
post_block_balance_updates={
|
|
"xch": {"set_remainder": True},
|
|
"did1": {"set_remainder": True},
|
|
},
|
|
)
|
|
]
|
|
)
|
|
|
|
hex_did_id = did_wallet.get_my_DID()
|
|
hmr_did_id = encode_puzzle_hash(bytes32.from_hexstr(hex_did_id), AddressType.DID.hrp(env.node.config))
|
|
|
|
await create_nft_wallet_via_cli(
|
|
wallet_environments,
|
|
env,
|
|
name="NFT WALLET 1",
|
|
did_id=CliAddress(
|
|
bytes32.from_hexstr(hex_did_id),
|
|
hmr_did_id,
|
|
AddressType.DID,
|
|
),
|
|
)
|
|
assert env.wallet_aliases["nft_w_did1"] in env.wallet_state_manager.wallets
|
|
|
|
await wallet_environments.process_pending_states(
|
|
[WalletStateTransition(pre_block_balance_updates={"nft_w_did1": {"init": True}})]
|
|
)
|
|
|
|
await env.rpc_client.mint_nft(
|
|
request=NFTMintNFTRequest(
|
|
wallet_id=uint32(env.wallet_aliases["nft_w_did1"]),
|
|
royalty_address=None,
|
|
target_address=None, # doesn't matter so we'll just reuse
|
|
hash=bytes32.from_hexstr("0xD4584AD463139FA8C0D9F68F4B59F185D4584AD463139FA8C0D9F68F4B59F185"),
|
|
uris=["https://www.chia.net/img/branding/chia-logo.svg"],
|
|
meta_uris=["https://www.chia.net/img/branding/chia-logo.svg"],
|
|
did_id="",
|
|
push=True,
|
|
),
|
|
tx_config=wallet_environments.tx_config,
|
|
)
|
|
|
|
await wallet_environments.process_pending_states(
|
|
[
|
|
WalletStateTransition(
|
|
pre_block_balance_updates={
|
|
"xch": {
|
|
"unconfirmed_wallet_balance": -1,
|
|
"<=#spendable_balance": -1,
|
|
">=#pending_change": 1, # any amount increase
|
|
"<=#max_send_amount": -1,
|
|
"pending_coin_removal_count": 1,
|
|
},
|
|
"nft_w_did1": {"pending_coin_removal_count": 1},
|
|
},
|
|
post_block_balance_updates={
|
|
"xch": {
|
|
"confirmed_wallet_balance": -1,
|
|
">=#spendable_balance": 1, # any amount increase
|
|
"<=#pending_change": -1, # any amount decrease
|
|
">=#max_send_amount": 1, # any amount increase
|
|
"pending_coin_removal_count": -1,
|
|
},
|
|
"nft_w_did1": {"pending_coin_removal_count": -1},
|
|
"nft_no_did": {"init": True, "unspent_coin_count": 1},
|
|
},
|
|
)
|
|
]
|
|
)
|
|
|
|
# Check DID NFT
|
|
coins = (
|
|
await env.rpc_client.list_nfts(
|
|
NFTGetNFTs(wallet_id=uint32(env.wallet_aliases["nft_no_did"]), start_index=uint32(0), num=uint32(1))
|
|
)
|
|
).nft_list
|
|
assert len(coins) == 1
|
|
coin = coins[0]
|
|
assert coin.owner_did is None
|
|
nft_coin_id = coin.nft_coin_id
|
|
|
|
# Test set None -> DID1
|
|
async with env.wallet_state_manager.new_action_scope(wallet_environments.tx_config, push=True) as action_scope:
|
|
did_wallet2 = await DIDWallet.create_new_did_wallet(env.wallet_state_manager, wallet, uint64(1), action_scope)
|
|
|
|
# use "set_remainder" here because this is more of a DID test issue
|
|
await wallet_environments.process_pending_states(
|
|
[
|
|
WalletStateTransition(
|
|
pre_block_balance_updates={
|
|
"xch": {"set_remainder": True},
|
|
"did2": {"init": True, "set_remainder": True},
|
|
},
|
|
post_block_balance_updates={
|
|
"xch": {"set_remainder": True},
|
|
"did2": {"set_remainder": True},
|
|
},
|
|
)
|
|
]
|
|
)
|
|
|
|
await set_nft_did_via_cli(
|
|
wallet_environments,
|
|
env,
|
|
wallet_id=env.wallet_aliases["nft_no_did"],
|
|
did_id=hmr_did_id,
|
|
nft_coin_id=nft_coin_id.hex(),
|
|
fee=uint64(0),
|
|
)
|
|
|
|
await wallet_environments.process_pending_states(
|
|
[
|
|
WalletStateTransition(
|
|
pre_block_balance_updates={
|
|
"xch": {},
|
|
"did1": {
|
|
"spendable_balance": -1,
|
|
"pending_change": 1,
|
|
"pending_coin_removal_count": 1,
|
|
"max_send_amount": -1,
|
|
},
|
|
"nft_no_did": {"pending_coin_removal_count": 1},
|
|
},
|
|
post_block_balance_updates={
|
|
"xch": {},
|
|
"did1": {
|
|
"spendable_balance": 1,
|
|
"pending_change": -1,
|
|
"pending_coin_removal_count": -1,
|
|
"max_send_amount": 1,
|
|
},
|
|
"nft_no_did": {"pending_coin_removal_count": -1, "unspent_coin_count": -1},
|
|
"nft_w_did1": {"unspent_coin_count": 1},
|
|
},
|
|
)
|
|
]
|
|
)
|
|
|
|
nft_wallet_to_check = env.wallet_state_manager.wallets[uint32(env.wallet_aliases["nft_no_did"])]
|
|
assert isinstance(nft_wallet_to_check, NFTWallet)
|
|
assert len(await nft_wallet_to_check.get_current_nfts()) == 0
|
|
|
|
coins = (
|
|
await env.rpc_client.list_nfts(
|
|
NFTGetNFTs(wallet_id=uint32(env.wallet_aliases["nft_w_did1"]), start_index=uint32(0), num=uint32(1))
|
|
)
|
|
).nft_list
|
|
assert len(coins) == 1
|
|
coin = coins[0]
|
|
assert coin.owner_did is not None
|
|
assert coin.owner_did.hex() == hex_did_id
|
|
nft_coin_id = coin.nft_coin_id
|
|
|
|
capsys.readouterr()
|
|
await GetNftInfoCMD(
|
|
rpc_info=NeedsWalletRPC(
|
|
client_info=WalletClientInfo(
|
|
client=env.rpc_client,
|
|
fingerprint=env.wallet_state_manager.root_pubkey.get_fingerprint(),
|
|
config=env.wallet_state_manager.config,
|
|
)
|
|
),
|
|
nft_coin_id=nft_coin_id.hex(),
|
|
).run()
|
|
output = capsys.readouterr().out
|
|
current_nft_coin_id = output.split("Current NFT coin ID:")[1].strip().split("\n")[0]
|
|
assert current_nft_coin_id == nft_coin_id.hex()
|
|
|
|
# Test set DID1 -> DID2
|
|
hex_did_id2 = did_wallet2.get_my_DID()
|
|
hmr_did_id2 = encode_puzzle_hash(bytes32.from_hexstr(hex_did_id2), AddressType.DID.hrp(env.node.config))
|
|
await set_nft_did_via_cli(
|
|
wallet_environments,
|
|
env,
|
|
wallet_id=env.wallet_aliases["nft_w_did1"],
|
|
did_id=hmr_did_id2,
|
|
nft_coin_id=nft_coin_id.hex(),
|
|
fee=uint64(0),
|
|
)
|
|
|
|
await wallet_environments.process_pending_states(
|
|
[
|
|
WalletStateTransition(
|
|
pre_block_balance_updates={
|
|
"xch": {},
|
|
"did2": {
|
|
"spendable_balance": -1,
|
|
"pending_change": 1,
|
|
"pending_coin_removal_count": 1,
|
|
"max_send_amount": -1,
|
|
},
|
|
"nft_w_did1": {"pending_coin_removal_count": 1},
|
|
},
|
|
post_block_balance_updates={
|
|
"xch": {},
|
|
"did2": {
|
|
"spendable_balance": 1,
|
|
"pending_change": -1,
|
|
"pending_coin_removal_count": -1,
|
|
"max_send_amount": 1,
|
|
},
|
|
"nft_w_did1": {"pending_coin_removal_count": -1, "unspent_coin_count": -1},
|
|
"nft_w_did2": {"init": True, "unspent_coin_count": 1},
|
|
},
|
|
)
|
|
]
|
|
)
|
|
|
|
# Check NFT DID
|
|
coins = (
|
|
await env.rpc_client.list_nfts(
|
|
NFTGetNFTs(wallet_id=uint32(env.wallet_aliases["nft_w_did2"]), start_index=uint32(0), num=uint32(1))
|
|
)
|
|
).nft_list
|
|
assert len(coins) == 1
|
|
coin = coins[0]
|
|
assert coin.owner_did is not None
|
|
assert coin.owner_did.hex() == hex_did_id2
|
|
nft_coin_id = coin.nft_coin_id
|
|
nft_get_info_res = await env.rpc_client.get_nft_info(NFTGetInfo(coin_id=nft_coin_id.hex(), latest=True))
|
|
assert coins[0] == nft_get_info_res.nft_info
|
|
|
|
# Test set DID2 -> None
|
|
await env.rpc_client.set_nft_did(
|
|
NFTSetNFTDID(
|
|
wallet_id=uint32(env.wallet_aliases["nft_w_did2"]),
|
|
did_id=None,
|
|
nft_coin_id=nft_coin_id,
|
|
fee=uint64(0),
|
|
push=True,
|
|
),
|
|
tx_config=wallet_environments.tx_config,
|
|
)
|
|
|
|
await wallet_environments.process_pending_states(
|
|
[
|
|
WalletStateTransition(
|
|
pre_block_balance_updates={
|
|
"xch": {},
|
|
"nft_w_did2": {"pending_coin_removal_count": 1},
|
|
},
|
|
post_block_balance_updates={
|
|
"xch": {},
|
|
"nft_w_did2": {"pending_coin_removal_count": -1, "unspent_coin_count": -1},
|
|
"nft_no_did": {"unspent_coin_count": 1},
|
|
},
|
|
)
|
|
]
|
|
)
|
|
|
|
# Check NFT DID
|
|
coins = (
|
|
await env.rpc_client.list_nfts(
|
|
NFTGetNFTs(wallet_id=uint32(env.wallet_aliases["nft_no_did"]), start_index=uint32(0), num=uint32(1))
|
|
)
|
|
).nft_list
|
|
assert len(coins) == 1
|
|
coin = coins[0]
|
|
assert coin.owner_did is None
|
|
nft_get_info_res = await env.rpc_client.get_nft_info(NFTGetInfo(coin_id=nft_coin_id.hex(), latest=True))
|
|
assert coins[0] == nft_get_info_res.nft_info
|
|
|
|
|
|
@pytest.mark.limit_consensus_modes(allowed=[ConsensusMode.PLAIN], reason="irrelevant")
|
|
@pytest.mark.parametrize("wallet_environments", [{"num_environments": 1, "blocks_needed": [1]}], indirect=True)
|
|
@pytest.mark.anyio
|
|
async def test_set_nft_status(wallet_environments: WalletTestFramework) -> None:
|
|
env = wallet_environments.environments[0]
|
|
|
|
env.wallet_aliases = {
|
|
"xch": 1,
|
|
"nft": 2,
|
|
}
|
|
|
|
await create_nft_wallet_via_cli(wallet_environments, env, name="NFT WALLET 1")
|
|
|
|
await wallet_environments.process_pending_states(
|
|
[
|
|
WalletStateTransition(
|
|
pre_block_balance_updates={
|
|
"xch": {},
|
|
"nft": {"init": True},
|
|
},
|
|
post_block_balance_updates={
|
|
"xch": {},
|
|
"nft": {},
|
|
},
|
|
)
|
|
]
|
|
)
|
|
|
|
# Create a NFT without DID
|
|
await mint_nft_via_cli(
|
|
wallet_environments,
|
|
env,
|
|
wallet_id=env.wallet_aliases["nft"],
|
|
hash="0xD4584AD463139FA8C0D9F68F4B59F185D4584AD463139FA8C0D9F68F4B59F185",
|
|
uris=["https://www.chia.net/img/branding/chia-logo.svg"],
|
|
metadata_uris="https://www.chia.net/img/branding/chia-logo.svg",
|
|
)
|
|
|
|
await wallet_environments.process_pending_states(
|
|
[
|
|
WalletStateTransition(
|
|
pre_block_balance_updates={
|
|
"xch": {
|
|
"unconfirmed_wallet_balance": -1,
|
|
"<=#spendable_balance": -1,
|
|
">=#pending_change": 1, # any amount increase
|
|
"<=#max_send_amount": -1,
|
|
"pending_coin_removal_count": 1,
|
|
},
|
|
"nft": {"init": True, "pending_coin_removal_count": 1},
|
|
},
|
|
post_block_balance_updates={
|
|
"xch": {
|
|
"confirmed_wallet_balance": -1,
|
|
">=#spendable_balance": 1, # any amount increase
|
|
"<=#pending_change": -1, # any amount decrease
|
|
">=#max_send_amount": 1, # any amount increase
|
|
"pending_coin_removal_count": -1,
|
|
},
|
|
"nft": {"pending_coin_removal_count": -1, "unspent_coin_count": 1},
|
|
},
|
|
)
|
|
]
|
|
)
|
|
|
|
# Check DID NFT
|
|
coins = (
|
|
await env.rpc_client.list_nfts(
|
|
NFTGetNFTs(wallet_id=uint32(env.wallet_aliases["nft"]), start_index=uint32(0), num=uint32(1))
|
|
)
|
|
).nft_list
|
|
assert len(coins) == 1
|
|
coin = coins[0]
|
|
assert coin.owner_did is None
|
|
assert not coin.pending_transaction
|
|
nft_coin_id = coin.nft_coin_id
|
|
# Set status
|
|
await env.rpc_client.set_nft_status(
|
|
NFTSetNFTStatus(wallet_id=uint32(env.wallet_aliases["nft"]), coin_id=nft_coin_id, in_transaction=True)
|
|
)
|
|
coins = (
|
|
await env.rpc_client.list_nfts(
|
|
NFTGetNFTs(wallet_id=uint32(env.wallet_aliases["nft"]), start_index=uint32(0), num=uint32(1))
|
|
)
|
|
).nft_list
|
|
assert len(coins) == 1
|
|
coin = coins[0]
|
|
assert coin.pending_transaction
|
|
|
|
|
|
@pytest.mark.limit_consensus_modes(allowed=[ConsensusMode.PLAIN], reason="irrelevant")
|
|
@pytest.mark.parametrize(
|
|
"wallet_environments",
|
|
[{"num_environments": 1, "blocks_needed": [1], "reuse_puzhash": True, "trusted": True}],
|
|
indirect=True,
|
|
)
|
|
@pytest.mark.anyio
|
|
async def test_nft_sign_message(wallet_environments: WalletTestFramework) -> None:
|
|
env = wallet_environments.environments[0]
|
|
|
|
env.wallet_aliases = {
|
|
"xch": 1,
|
|
"nft": 2,
|
|
}
|
|
|
|
await create_nft_wallet_via_cli(wallet_environments, env, name="NFT WALLET 1")
|
|
|
|
await wallet_environments.process_pending_states(
|
|
[
|
|
WalletStateTransition(
|
|
pre_block_balance_updates={
|
|
"xch": {},
|
|
"nft": {"init": True},
|
|
},
|
|
post_block_balance_updates={
|
|
"xch": {},
|
|
"nft": {},
|
|
},
|
|
)
|
|
]
|
|
)
|
|
|
|
# Create a NFT without DID
|
|
await mint_nft_via_cli(
|
|
wallet_environments,
|
|
env,
|
|
wallet_id=env.wallet_aliases["nft"],
|
|
hash="0xD4584AD463139FA8C0D9F68F4B59F185D4584AD463139FA8C0D9F68F4B59F185",
|
|
uris=["https://www.chia.net/img/branding/chia-logo.svg"],
|
|
metadata_uris="https://www.chia.net/img/branding/chia-logo.svg",
|
|
)
|
|
|
|
await wallet_environments.process_pending_states(
|
|
[
|
|
WalletStateTransition(
|
|
pre_block_balance_updates={
|
|
"xch": {
|
|
"unconfirmed_wallet_balance": -1,
|
|
"<=#spendable_balance": -1,
|
|
">=#pending_change": 1, # any amount increase
|
|
"<=#max_send_amount": -1,
|
|
"pending_coin_removal_count": 1,
|
|
},
|
|
"nft": {"init": True, "pending_coin_removal_count": 1},
|
|
},
|
|
post_block_balance_updates={
|
|
"xch": {
|
|
"confirmed_wallet_balance": -1,
|
|
">=#spendable_balance": 1, # any amount increase
|
|
"<=#pending_change": -1, # any amount decrease
|
|
">=#max_send_amount": 1, # any amount increase
|
|
"pending_coin_removal_count": -1,
|
|
},
|
|
"nft": {"pending_coin_removal_count": -1, "unspent_coin_count": 1},
|
|
},
|
|
)
|
|
]
|
|
)
|
|
|
|
# Check DID NFT
|
|
coins = (
|
|
await env.rpc_client.list_nfts(
|
|
NFTGetNFTs(wallet_id=uint32(env.wallet_aliases["nft"]), start_index=uint32(0), num=uint32(1))
|
|
)
|
|
).nft_list
|
|
assert len(coins) == 1
|
|
coin = coins[0]
|
|
assert coin.owner_did is None
|
|
assert not coin.pending_transaction
|
|
# Test general string
|
|
message = "Hello World"
|
|
sign_by_id_res = await env.rpc_client.sign_message_by_id(
|
|
SignMessageByID(id=encode_puzzle_hash(coin.launcher_id, AddressType.NFT.value), message=message)
|
|
)
|
|
puzzle = Program.to((CHIP_0002_SIGN_MESSAGE_PREFIX, message))
|
|
assert AugSchemeMPL.verify(
|
|
sign_by_id_res.pubkey,
|
|
puzzle.get_tree_hash(),
|
|
sign_by_id_res.signature,
|
|
)
|
|
# Test hex string
|
|
message = "0123456789ABCDEF"
|
|
sign_by_id_res = await env.rpc_client.sign_message_by_id(
|
|
SignMessageByID(id=encode_puzzle_hash(coin.launcher_id, AddressType.NFT.value), message=message, is_hex=True)
|
|
)
|
|
puzzle = Program.to((CHIP_0002_SIGN_MESSAGE_PREFIX, bytes.fromhex(message)))
|
|
assert AugSchemeMPL.verify(
|
|
sign_by_id_res.pubkey,
|
|
puzzle.get_tree_hash(),
|
|
sign_by_id_res.signature,
|
|
)
|
|
# Test BLS sign string
|
|
message = "Hello World"
|
|
sign_by_id_res = await env.rpc_client.sign_message_by_id(
|
|
SignMessageByID(
|
|
id=encode_puzzle_hash(coin.launcher_id, AddressType.NFT.value),
|
|
message=message,
|
|
is_hex=False,
|
|
safe_mode=False,
|
|
)
|
|
)
|
|
|
|
assert AugSchemeMPL.verify(
|
|
sign_by_id_res.pubkey,
|
|
bytes(message, "utf-8"),
|
|
sign_by_id_res.signature,
|
|
)
|
|
# Test BLS sign hex
|
|
message = "0123456789ABCDEF"
|
|
sign_by_id_res = await env.rpc_client.sign_message_by_id(
|
|
SignMessageByID(
|
|
id=encode_puzzle_hash(coin.launcher_id, AddressType.NFT.value),
|
|
message=message,
|
|
is_hex=True,
|
|
safe_mode=False,
|
|
)
|
|
)
|
|
|
|
assert AugSchemeMPL.verify(
|
|
sign_by_id_res.pubkey,
|
|
bytes.fromhex(message),
|
|
sign_by_id_res.signature,
|
|
)
|
|
|
|
|
|
@pytest.mark.limit_consensus_modes(reason="irrelevant")
|
|
@pytest.mark.parametrize(
|
|
"wallet_environments",
|
|
[{"num_environments": 1, "blocks_needed": [1], "reuse_puzhash": True, "trusted": True}],
|
|
indirect=True,
|
|
)
|
|
@pytest.mark.anyio
|
|
async def test_nft_command_errors(wallet_environments: WalletTestFramework, capsys: pytest.CaptureFixture[str]) -> None:
|
|
env = wallet_environments.environments[0]
|
|
capsys.readouterr()
|
|
|
|
await create_nft_wallet_via_cli(
|
|
wallet_environments,
|
|
env,
|
|
did_id=CliAddress(
|
|
bytes32.zeros,
|
|
"not a real address",
|
|
AddressType.DID,
|
|
),
|
|
name="NFT WALLET 1",
|
|
)
|
|
assert "Failed to create NFT wallet" in capsys.readouterr().out
|
|
|
|
await add_uri_to_nft_via_cli(
|
|
wallet_environments,
|
|
env,
|
|
wallet_id=0,
|
|
nft_coin_id=bytes32.zeros.hex(),
|
|
metadata_uri="http://metadata",
|
|
license_uri="http://license",
|
|
)
|
|
assert "Failed to add URI to NFT: You must provide only one of the URI flags" in capsys.readouterr().out
|
|
|
|
await add_uri_to_nft_via_cli(
|
|
wallet_environments,
|
|
env,
|
|
wallet_id=0,
|
|
nft_coin_id=bytes32.zeros.hex(),
|
|
)
|
|
assert "Failed to add URI to NFT: You must provide at least one of the URI flags" in capsys.readouterr().out
|
|
|
|
await transfer_nft_via_cli(
|
|
wallet_environments,
|
|
env,
|
|
wallet_id=0,
|
|
nft_coin_id=bytes32.zeros.hex(),
|
|
target_address=CliAddress(
|
|
bytes32.zeros,
|
|
"not an address",
|
|
AddressType.XCH,
|
|
),
|
|
)
|
|
assert "Failed to transfer NFT" in capsys.readouterr().out
|
|
|
|
await ListNftsCMD(
|
|
rpc_info=NeedsWalletRPC(
|
|
client_info=WalletClientInfo(
|
|
client=env.rpc_client,
|
|
fingerprint=env.wallet_state_manager.root_pubkey.get_fingerprint(),
|
|
config=env.wallet_state_manager.config,
|
|
)
|
|
),
|
|
wallet_id=0,
|
|
start_index=0,
|
|
num=1,
|
|
).run()
|
|
assert "Failed to list NFTs" in capsys.readouterr().out
|
|
|
|
await set_nft_did_via_cli(
|
|
wallet_environments,
|
|
env,
|
|
wallet_id=0,
|
|
did_id="",
|
|
nft_coin_id=bytes32.zeros.hex(),
|
|
)
|
|
assert "Failed to set DID on NFT" in capsys.readouterr().out
|
|
|
|
await GetNftInfoCMD(
|
|
rpc_info=NeedsWalletRPC(
|
|
client_info=WalletClientInfo(
|
|
client=env.rpc_client,
|
|
fingerprint=env.wallet_state_manager.root_pubkey.get_fingerprint(),
|
|
config=env.wallet_state_manager.config,
|
|
)
|
|
),
|
|
nft_coin_id=bytes32.zeros.hex(),
|
|
).run()
|
|
assert "Failed to get NFT info" in capsys.readouterr().out
|
|
|
|
|
|
def test_nft_command_parsing() -> None:
|
|
bare_rpc = NeedsWalletRPC(client_info=None, wallet_rpc_port=None, fingerprint=None)
|
|
reuse_tx_config = NeedsTXConfig(
|
|
coins_to_exclude=tuple(), coins_to_include=tuple(), amounts_to_exclude=tuple(), reuse=True
|
|
)
|
|
did_puzzle_hash = bytes32([1] * 32)
|
|
did_address = encode_puzzle_hash(did_puzzle_hash, "did:chia:")
|
|
nft_puzzle_hash = bytes32([2] * 32)
|
|
nft_address = encode_puzzle_hash(nft_puzzle_hash, "nft")
|
|
target_puzzle_hash = bytes32([3] * 32)
|
|
target_address = encode_puzzle_hash(target_puzzle_hash, "txch")
|
|
nft_coin_id = bytes32([4] * 32)
|
|
content_hash = bytes32([5] * 32)
|
|
message = b"hello nft world!!"
|
|
|
|
check_click_parsing(
|
|
CreateNftWalletCMD(
|
|
rpc_info=bare_rpc,
|
|
did_id=CliAddress(did_puzzle_hash, did_address, AddressType.DID),
|
|
name="testnft",
|
|
),
|
|
"-n",
|
|
"testnft",
|
|
"--did-id",
|
|
did_address,
|
|
context=ChiaCliContext(expected_prefix="txch"),
|
|
)
|
|
|
|
check_click_parsing(
|
|
NftSignMessageCMD(
|
|
rpc_info=bare_rpc,
|
|
nft_id=CliAddress(nft_puzzle_hash, nft_address, AddressType.NFT),
|
|
hex_message=message.hex(),
|
|
),
|
|
"-i",
|
|
nft_address,
|
|
"-m",
|
|
message.hex(),
|
|
context=ChiaCliContext(expected_prefix="txch"),
|
|
)
|
|
|
|
check_click_parsing(
|
|
MintNftCMD(
|
|
**{
|
|
**STANDARD_TX_ENDPOINT_ARGS,
|
|
"wallet_id": 3,
|
|
"royalty_address": CliAddress(target_puzzle_hash, target_address, AddressType.XCH),
|
|
"target_address": CliAddress(target_puzzle_hash, target_address, AddressType.XCH),
|
|
"no_did_ownership": True,
|
|
"hash": content_hash.hex(),
|
|
"uris": "https://example.com/a,https://example.com/b",
|
|
"metadata_hash": content_hash.hex(),
|
|
"metadata_uris": "https://example.com/meta",
|
|
"license_hash": content_hash.hex(),
|
|
"license_uris": "https://example.com/license",
|
|
"edition_total": 10,
|
|
"edition_number": 2,
|
|
"royalty_percentage_fraction": 175,
|
|
"fee": uint64(100_000_000_000),
|
|
"valid_at": 100,
|
|
"expires_at": 150,
|
|
}
|
|
),
|
|
"-i",
|
|
"3",
|
|
"-ra",
|
|
target_address,
|
|
"-ta",
|
|
target_address,
|
|
"--no-did-ownership",
|
|
"-nh",
|
|
content_hash.hex(),
|
|
"-u",
|
|
"https://example.com/a,https://example.com/b",
|
|
"-mh",
|
|
content_hash.hex(),
|
|
"-mu",
|
|
"https://example.com/meta",
|
|
"-lh",
|
|
content_hash.hex(),
|
|
"-lu",
|
|
"https://example.com/license",
|
|
"-et",
|
|
"10",
|
|
"-en",
|
|
"2",
|
|
"-rp",
|
|
"175",
|
|
"-m",
|
|
"0.1",
|
|
"--valid-at",
|
|
"100",
|
|
"--expires-at",
|
|
"150",
|
|
context=ChiaCliContext(expected_prefix="txch"),
|
|
)
|
|
|
|
check_click_parsing(
|
|
AddUriToNftCMD(
|
|
rpc_info=bare_rpc,
|
|
transaction_writer=TransactionsOut(transaction_file_out=None),
|
|
tx_config_loader=reuse_tx_config,
|
|
wallet_id=3,
|
|
nft_coin_id=nft_coin_id.hex(),
|
|
uri="https://example.com/new",
|
|
metadata_uri=None,
|
|
license_uri=None,
|
|
push=True,
|
|
valid_at=100,
|
|
expires_at=150,
|
|
fee=uint64(0),
|
|
),
|
|
"-i",
|
|
"3",
|
|
"--nft-coin-id",
|
|
nft_coin_id.hex(),
|
|
"-u",
|
|
"https://example.com/new",
|
|
"--reuse",
|
|
"--valid-at",
|
|
"100",
|
|
"--expires-at",
|
|
"150",
|
|
)
|
|
|
|
check_click_parsing(
|
|
TransferNftCMD(
|
|
rpc_info=bare_rpc,
|
|
tx_config_loader=reuse_tx_config,
|
|
transaction_writer=TransactionsOut(transaction_file_out=None),
|
|
wallet_id=3,
|
|
nft_coin_id=nft_coin_id.hex(),
|
|
target_address=CliAddress(target_puzzle_hash, target_address, AddressType.XCH),
|
|
fee=uint64(500_000_000_000),
|
|
push=True,
|
|
valid_at=100,
|
|
expires_at=150,
|
|
),
|
|
"-i",
|
|
"3",
|
|
"--nft-coin-id",
|
|
nft_coin_id.hex(),
|
|
"-ta",
|
|
target_address,
|
|
"-m",
|
|
"0.5",
|
|
"--reuse",
|
|
"--valid-at",
|
|
"100",
|
|
"--expires-at",
|
|
"150",
|
|
context=ChiaCliContext(expected_prefix="txch"),
|
|
)
|
|
|
|
check_click_parsing(ListNftsCMD(rpc_info=bare_rpc, wallet_id=3, num=50, start_index=0), "-i", "3")
|
|
|
|
check_click_parsing(
|
|
ListNftsCMD(rpc_info=bare_rpc, wallet_id=3, num=10, start_index=5),
|
|
"-i",
|
|
"3",
|
|
"--num",
|
|
"10",
|
|
"--start-index",
|
|
"5",
|
|
)
|
|
|
|
check_click_parsing(
|
|
SetNftDidCMD(
|
|
rpc_info=bare_rpc,
|
|
tx_config_loader=reuse_tx_config,
|
|
transaction_writer=TransactionsOut(transaction_file_out=None),
|
|
wallet_id=3,
|
|
did_id=did_address,
|
|
nft_coin_id=nft_coin_id.hex(),
|
|
fee=uint64(0),
|
|
push=True,
|
|
valid_at=100,
|
|
expires_at=150,
|
|
),
|
|
"-i",
|
|
"3",
|
|
"--did-id",
|
|
did_address,
|
|
"--nft-coin-id",
|
|
nft_coin_id.hex(),
|
|
"--reuse",
|
|
"--valid-at",
|
|
"100",
|
|
"--expires-at",
|
|
"150",
|
|
)
|
|
|
|
check_click_parsing(
|
|
GetNftInfoCMD(rpc_info=bare_rpc, nft_coin_id=nft_coin_id.hex()),
|
|
"--nft-coin-id",
|
|
nft_coin_id.hex(),
|
|
)
|