mirror of
https://github.com/Chia-Network/chia-blockchain.git
synced 2026-09-05 02:24:21 -05:00
Merge pull request #11887 from Chia-Network/new_nft1_chialisp
New NFT chialisp
This commit is contained in:
@@ -95,7 +95,7 @@ jobs:
|
||||
- name: Test wallet-nft_wallet code with pytest
|
||||
run: |
|
||||
. ./activate
|
||||
venv/bin/coverage run --rcfile=.coveragerc --module pytest --durations=10 -n 0 -m "not benchmark" tests/wallet/nft_wallet/test_nft_clvm.py tests/wallet/nft_wallet/test_nft_offers.py tests/wallet/nft_wallet/test_nft_puzzles.py tests/wallet/nft_wallet/test_nft_wallet.py
|
||||
venv/bin/coverage run --rcfile=.coveragerc --module pytest --durations=10 -n 0 -m "not benchmark" tests/wallet/nft_wallet/test_nft_clvm.py tests/wallet/nft_wallet/test_nft_lifecycle.py tests/wallet/nft_wallet/test_nft_offers.py tests/wallet/nft_wallet/test_nft_puzzles.py tests/wallet/nft_wallet/test_nft_wallet.py
|
||||
|
||||
- name: Process coverage data
|
||||
run: |
|
||||
|
||||
@@ -94,7 +94,7 @@ jobs:
|
||||
- name: Test wallet-nft_wallet code with pytest
|
||||
run: |
|
||||
. ./activate
|
||||
venv/bin/coverage run --rcfile=.coveragerc --module pytest --durations=10 -n 0 -m "not benchmark" tests/wallet/nft_wallet/test_nft_clvm.py tests/wallet/nft_wallet/test_nft_offers.py tests/wallet/nft_wallet/test_nft_puzzles.py tests/wallet/nft_wallet/test_nft_wallet.py
|
||||
venv/bin/coverage run --rcfile=.coveragerc --module pytest --durations=10 -n 0 -m "not benchmark" tests/wallet/nft_wallet/test_nft_clvm.py tests/wallet/nft_wallet/test_nft_lifecycle.py tests/wallet/nft_wallet/test_nft_offers.py tests/wallet/nft_wallet/test_nft_puzzles.py tests/wallet/nft_wallet/test_nft_wallet.py
|
||||
|
||||
- name: Process coverage data
|
||||
run: |
|
||||
|
||||
@@ -26,9 +26,6 @@ class NFTInfo(Streamable):
|
||||
owner_did: Optional[bytes32]
|
||||
"""Owner DID"""
|
||||
|
||||
owner_pubkey: Optional[bytes]
|
||||
"""Pubkey of the NFT owner"""
|
||||
|
||||
royalty_percentage: Optional[uint16]
|
||||
"""Percentage of the transaction fee paid to the author, e.g. 1000 = 1%"""
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import logging
|
||||
from typing import Any, Dict, List, Optional, Tuple
|
||||
|
||||
from blspy import G1Element
|
||||
from clvm.casts import int_from_bytes
|
||||
from clvm_tools.binutils import disassemble
|
||||
|
||||
@@ -12,11 +11,7 @@ from chia.wallet.nft_wallet.nft_info import NFTCoinInfo, NFTInfo
|
||||
from chia.wallet.nft_wallet.uncurry_nft import UncurriedNFT
|
||||
from chia.wallet.puzzles.cat_loader import CAT_MOD
|
||||
from chia.wallet.puzzles.load_clvm import load_clvm
|
||||
from chia.wallet.puzzles.p2_delegated_puzzle_or_hidden_puzzle import (
|
||||
DEFAULT_HIDDEN_PUZZLE_HASH,
|
||||
calculate_synthetic_public_key,
|
||||
solution_for_conditions,
|
||||
)
|
||||
from chia.wallet.puzzles.p2_delegated_puzzle_or_hidden_puzzle import solution_for_conditions
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
SINGLETON_TOP_LAYER_MOD = load_clvm("singleton_top_layer_v1_1.clvm")
|
||||
@@ -28,7 +23,7 @@ NFT_STATE_LAYER_MOD_HASH = NFT_STATE_LAYER_MOD.get_tree_hash()
|
||||
OFFER_MOD = load_clvm("settlement_payments.clvm")
|
||||
NFT_METADATA_UPDATER = load_clvm("nft_metadata_updater_default.clvm")
|
||||
NFT_OWNERSHIP_LAYER = load_clvm("nft_ownership_layer.clvm")
|
||||
NFT_TRANSFER_PROGRAM_DEFAULT = load_clvm("nft_ownership_transfer_program_one_way_claim_with_royalties_new.clvm")
|
||||
NFT_TRANSFER_PROGRAM_DEFAULT = load_clvm("nft_ownership_transfer_program_one_way_claim_with_royalties.clvm")
|
||||
STANDARD_PUZZLE_MOD = load_clvm("p2_delegated_puzzle_or_hidden_puzzle.clvm")
|
||||
|
||||
|
||||
@@ -111,7 +106,6 @@ def get_nft_info_from_puzzle(nft_coin_info: NFTCoinInfo) -> NFTInfo:
|
||||
uncurried_nft.singleton_launcher_id,
|
||||
nft_coin_info.coin.name(),
|
||||
uncurried_nft.owner_did,
|
||||
uncurried_nft.owner_pubkey,
|
||||
uncurried_nft.trade_price_percentage,
|
||||
uncurried_nft.royalty_address,
|
||||
data_uris,
|
||||
@@ -185,6 +179,19 @@ def update_metadata(metadata: Program, update_condition: Program) -> Program:
|
||||
return metadata_to_program(new_metadata)
|
||||
|
||||
|
||||
def construct_ownership_layer(
|
||||
current_owner: Optional[bytes32],
|
||||
transfer_program: Program,
|
||||
inner_puzzle: Program,
|
||||
) -> Program:
|
||||
return NFT_OWNERSHIP_LAYER.curry(
|
||||
NFT_OWNERSHIP_LAYER.get_tree_hash(),
|
||||
current_owner,
|
||||
transfer_program,
|
||||
inner_puzzle,
|
||||
)
|
||||
|
||||
|
||||
def create_ownership_layer_puzzle(
|
||||
nft_id: bytes32,
|
||||
did_id: bytes,
|
||||
@@ -195,7 +202,7 @@ def create_ownership_layer_puzzle(
|
||||
log.debug(
|
||||
"Creating ownership layer puzzle with NFT_ID: %s DID_ID: %s Royalty_Percentage: %d P2_puzzle: %s",
|
||||
nft_id.hex(),
|
||||
did_id.hex(),
|
||||
did_id,
|
||||
percentage,
|
||||
p2_puzzle,
|
||||
)
|
||||
@@ -203,7 +210,6 @@ def create_ownership_layer_puzzle(
|
||||
if not royalty_puzzle_hash:
|
||||
royalty_puzzle_hash = p2_puzzle.get_tree_hash()
|
||||
transfer_program = NFT_TRANSFER_PROGRAM_DEFAULT.curry(
|
||||
STANDARD_PUZZLE_MOD.get_tree_hash(),
|
||||
singleton_struct,
|
||||
royalty_puzzle_hash,
|
||||
percentage,
|
||||
@@ -212,8 +218,8 @@ def create_ownership_layer_puzzle(
|
||||
)
|
||||
nft_inner_puzzle = p2_puzzle
|
||||
|
||||
nft_ownership_layer_puzzle = NFT_OWNERSHIP_LAYER.curry(
|
||||
NFT_OWNERSHIP_LAYER.get_tree_hash(), did_id, transfer_program, nft_inner_puzzle
|
||||
nft_ownership_layer_puzzle = construct_ownership_layer(
|
||||
bytes32(did_id) if did_id else None, transfer_program, nft_inner_puzzle
|
||||
)
|
||||
return nft_ownership_layer_puzzle
|
||||
|
||||
@@ -222,31 +228,28 @@ def create_ownership_layer_transfer_solution(
|
||||
new_did: bytes,
|
||||
new_did_inner_hash: bytes32,
|
||||
trade_prices_list: List[List[int]],
|
||||
new_pubkey: G1Element,
|
||||
new_puzhash: bytes32,
|
||||
) -> Program:
|
||||
log.debug(
|
||||
"Creating a transfer solution with: DID:%s Inner_puzhash:%s trade_price:%s pubkey:%s",
|
||||
"Creating a transfer solution with: DID:%s Inner_puzhash:%s trade_price:%s puzhash:%s",
|
||||
new_did.hex(),
|
||||
new_did_inner_hash.hex(),
|
||||
str(trade_prices_list),
|
||||
new_pubkey,
|
||||
new_puzhash.hex(),
|
||||
)
|
||||
synthetic_pk: bytes = bytes(calculate_synthetic_public_key(new_pubkey, DEFAULT_HIDDEN_PUZZLE_HASH))
|
||||
puzhash: bytes32 = STANDARD_PUZZLE_MOD.curry(synthetic_pk).get_tree_hash()
|
||||
condition_list = [
|
||||
[
|
||||
51,
|
||||
puzhash,
|
||||
new_puzhash,
|
||||
1,
|
||||
[puzhash],
|
||||
[new_puzhash],
|
||||
],
|
||||
[-10, new_did, trade_prices_list, synthetic_pk, [new_did_inner_hash]],
|
||||
[-10, new_did, trade_prices_list, new_did_inner_hash],
|
||||
]
|
||||
log.debug("Condition list raw: %r", condition_list)
|
||||
solution = Program.to(
|
||||
[
|
||||
[solution_for_conditions(condition_list)],
|
||||
1,
|
||||
]
|
||||
)
|
||||
log.debug("Generated transfer solution: %s", solution)
|
||||
@@ -254,19 +257,14 @@ def create_ownership_layer_transfer_solution(
|
||||
|
||||
|
||||
def get_metadata_and_phs(unft: UncurriedNFT, puzzle: Program, solution: SerializedProgram) -> Tuple[Program, bytes32]:
|
||||
full_solution: Program = Program.from_bytes(bytes(solution))
|
||||
delegated_puz_solution: Program = Program.from_bytes(bytes(solution)).rest().rest().first().first()
|
||||
if delegated_puz_solution.rest().as_python() == b"":
|
||||
conditions = puzzle.run(full_solution)
|
||||
else:
|
||||
conditions = delegated_puz_solution.rest().first().rest()
|
||||
conditions = unft.p2_puzzle.run(unft.get_innermost_solution(solution.to_program()))
|
||||
metadata = unft.metadata
|
||||
puzhash_for_derivation: Optional[bytes32] = None
|
||||
for condition in conditions.as_iter():
|
||||
if condition.list_len() < 2:
|
||||
# invalid condition
|
||||
continue
|
||||
condition_code = int_from_bytes(condition.first().atom)
|
||||
condition_code = condition.first().as_int()
|
||||
log.debug("Checking condition code: %r", condition_code)
|
||||
if condition_code == -24:
|
||||
# metadata update
|
||||
@@ -277,35 +275,26 @@ def get_metadata_and_phs(unft: UncurriedNFT, puzzle: Program, solution: Serializ
|
||||
if puzhash_for_derivation is not None:
|
||||
# ignore duplicated create coin conditions
|
||||
continue
|
||||
puzhash = bytes32(condition.rest().first().atom)
|
||||
memo = bytes32(condition.as_python()[-1][0])
|
||||
if memo != puzhash:
|
||||
puzhash_for_derivation = memo
|
||||
else:
|
||||
puzhash_for_derivation = puzhash
|
||||
puzhash_for_derivation = memo
|
||||
log.debug("Got back puzhash from solution: %s", puzhash_for_derivation)
|
||||
assert puzhash_for_derivation
|
||||
return metadata, puzhash_for_derivation
|
||||
|
||||
|
||||
def recurry_nft_puzzle(unft: UncurriedNFT, solution: Program) -> Program:
|
||||
log.debug("Generating NFT puzzle with ownership support: %s", solution)
|
||||
def recurry_nft_puzzle(unft: UncurriedNFT, solution: Program, sp2_puzzle: Program) -> Program:
|
||||
log.debug("Generating NFT puzzle with ownership support: %s", disassemble(solution))
|
||||
conditions = solution.at("frfr").as_iter()
|
||||
for change_did_condition in conditions:
|
||||
if change_did_condition.first().as_int() == -10:
|
||||
new_did_id = None
|
||||
new_puzhash = None
|
||||
for condition in conditions:
|
||||
if condition.first().as_int() == -10:
|
||||
# this is the change owner magic condition
|
||||
break
|
||||
else:
|
||||
raise ValueError("Not a valid puzzle")
|
||||
new_did_id = change_did_condition.at("rf").atom
|
||||
# trade_list_price = change_did_condition.at("rrf").as_python()
|
||||
# new_did_inner_hash = change_did_condition.at("rrrrf").atom
|
||||
new_pub_key = G1Element.from_bytes(change_did_condition.at("rrrf").atom)
|
||||
log.debug(f"Found NFT puzzle details: {new_did_id.hex()} ")
|
||||
inner_puzzle = NFT_OWNERSHIP_LAYER.curry(
|
||||
NFT_OWNERSHIP_LAYER.get_tree_hash(),
|
||||
new_did_id,
|
||||
unft.transfer_program,
|
||||
STANDARD_PUZZLE_MOD.curry(new_pub_key),
|
||||
)
|
||||
new_did_id = condition.at("rf").atom
|
||||
elif condition.first().as_int() == 51:
|
||||
new_puzhash = condition.at("rf").atom
|
||||
# assert new_puzhash and new_did_id
|
||||
log.debug(f"Found NFT puzzle details: {new_did_id} {new_puzhash}")
|
||||
assert unft.transfer_program
|
||||
inner_puzzle = construct_ownership_layer(new_did_id, unft.transfer_program, sp2_puzzle)
|
||||
return inner_puzzle
|
||||
|
||||
@@ -206,28 +206,21 @@ class NFTWallet:
|
||||
)
|
||||
singleton_id = uncurried_nft.singleton_launcher_id
|
||||
parent_inner_puzhash = uncurried_nft.nft_state_layer.get_tree_hash()
|
||||
metadata, p2_puzzle_hash = get_metadata_and_phs(
|
||||
uncurried_nft, puzzle, Program.from_bytes(bytes(coin_spend.solution))
|
||||
)
|
||||
metadata, p2_puzzle_hash = get_metadata_and_phs(uncurried_nft, puzzle, coin_spend.solution)
|
||||
self.log.debug("Got back puzhash from solution: %s", p2_puzzle_hash)
|
||||
self.log.debug("Got back updated metadata: %s", metadata)
|
||||
derivation_record: Optional[
|
||||
DerivationRecord
|
||||
] = await self.wallet_state_manager.puzzle_store.get_derivation_record_for_puzzle_hash(p2_puzzle_hash)
|
||||
|
||||
if derivation_record and not uncurried_nft.supports_did:
|
||||
p2_puzzle = puzzle_for_pk(derivation_record.pubkey)
|
||||
else:
|
||||
# we don't have this puzhash in puzzle store
|
||||
# either it's not our coin or it's a NFT with a DID
|
||||
p2_puzzle = None
|
||||
self.log.debug("Got back updated metadata: %s", metadata)
|
||||
if p2_puzzle is None and uncurried_nft.owner_pubkey is None:
|
||||
self.log.debug("Record for %s is: %s", p2_puzzle_hash, derivation_record)
|
||||
if derivation_record is None:
|
||||
self.log.info("Received a puzzle hash that is not ours, returning")
|
||||
# we transferred it to another wallet, remove the coin from our wallet
|
||||
await self.remove_coin(coin_spend.coin, in_transaction=in_transaction)
|
||||
return
|
||||
if p2_puzzle is None:
|
||||
inner_puzzle = nft_puzzles.recurry_nft_puzzle(uncurried_nft, delegated_puz_solution)
|
||||
p2_puzzle = puzzle_for_pk(derivation_record.pubkey)
|
||||
if uncurried_nft.supports_did:
|
||||
inner_puzzle = nft_puzzles.recurry_nft_puzzle(uncurried_nft, delegated_puz_solution, p2_puzzle)
|
||||
else:
|
||||
inner_puzzle = p2_puzzle
|
||||
child_puzzle: Program = nft_puzzles.create_full_puzzle(
|
||||
@@ -251,7 +244,7 @@ class NFTWallet:
|
||||
child_coin = new_coin
|
||||
break
|
||||
else:
|
||||
raise ValueError(f"Rebuild NFT doesn't match the actual puzzle hash: {child_puzzle}")
|
||||
raise ValueError("Couldn't generate child puzzle for NFT")
|
||||
launcher_coin_states: List[CoinState] = await self.wallet_state_manager.wallet_node.get_coin_state(
|
||||
[singleton_id]
|
||||
)
|
||||
@@ -319,9 +312,6 @@ class NFTWallet:
|
||||
def puzzle_for_pk(self, pk: G1Element) -> Program:
|
||||
inner_puzzle = self.standard_wallet.puzzle_for_pk(bytes(pk))
|
||||
provenance_puzzle = Program.to([NFT_STATE_LAYER_MOD_HASH, inner_puzzle])
|
||||
self.log.debug(
|
||||
"Wallet name %s generated a puzzle: %s", self.wallet_info.name, provenance_puzzle.get_tree_hash()
|
||||
)
|
||||
return provenance_puzzle
|
||||
|
||||
async def get_did_approval_info(
|
||||
@@ -425,17 +415,8 @@ class NFTWallet:
|
||||
record: Optional[DerivationRecord] = None
|
||||
# Create inner solution for eve spend
|
||||
if did_id is not None:
|
||||
record = await self.wallet_state_manager.puzzle_store.get_derivation_record_for_puzzle_hash(
|
||||
target_puzzle_hash
|
||||
)
|
||||
self.log.debug("Got back a pubkey record: %s", record)
|
||||
if not record:
|
||||
record = await self.wallet_state_manager.get_unused_derivation_record(self.id(), False)
|
||||
assert record
|
||||
did_inner_hash, did_bundle = await self.get_did_approval_info(launcher_coin.name())
|
||||
pubkey = record.pubkey
|
||||
self.log.debug("Going to use this pubkey for NFT mint: %s", pubkey)
|
||||
innersol = create_ownership_layer_transfer_solution(did_id, did_inner_hash, [], pubkey)
|
||||
innersol = create_ownership_layer_transfer_solution(did_id, did_inner_hash, [], target_puzzle_hash)
|
||||
bundles_to_agg.append(did_bundle)
|
||||
|
||||
self.log.debug("Created an inner DID NFT solution: %s", disassemble(innersol))
|
||||
@@ -583,16 +564,26 @@ class NFTWallet:
|
||||
|
||||
uncurried_nft = UncurriedNFT.uncurry(nft_coin_info.full_puzzle)
|
||||
|
||||
puzzle_hash = uncurried_nft.inner_puzzle.get_tree_hash()
|
||||
condition_list = [
|
||||
make_create_coin_condition(puzzle_hash, coin.amount, [puzzle_hash]),
|
||||
[int_to_bytes(-24), NFT_METADATA_UPDATER, (key, uri)],
|
||||
]
|
||||
puzzle_hash = uncurried_nft.p2_puzzle.get_tree_hash()
|
||||
|
||||
if uncurried_nft.supports_did:
|
||||
condition_list = [
|
||||
[51, puzzle_hash, coin.amount, [puzzle_hash]],
|
||||
[-24, NFT_METADATA_UPDATER, (key, uri)],
|
||||
[-10, [], [], []],
|
||||
]
|
||||
inner_solution = Program.to([[solution_for_conditions(condition_list)]])
|
||||
else:
|
||||
condition_list = [
|
||||
[51, puzzle_hash, coin.amount, [puzzle_hash]],
|
||||
[-24, NFT_METADATA_UPDATER, (key, uri)],
|
||||
]
|
||||
inner_solution = Program.to([solution_for_conditions(condition_list)])
|
||||
self.log.info(
|
||||
"Attempting to add urls to NFT coin %s in the metadata: %s", nft_coin_info, uncurried_nft.metadata
|
||||
"Attempting to add urls to NFT coin %s in the metadata: %s",
|
||||
nft_coin_info.coin.name(),
|
||||
uncurried_nft.metadata,
|
||||
)
|
||||
inner_solution = Program.to([solution_for_conditions(condition_list), 1])
|
||||
nft_tx_record = await self._make_nft_transaction(nft_coin_info, inner_solution, [puzzle_hash], fee)
|
||||
await self.standard_wallet.push_transaction(nft_tx_record)
|
||||
await self.update_coin_status(nft_coin_info.coin.name(), True)
|
||||
@@ -605,15 +596,19 @@ class NFTWallet:
|
||||
puzzle_hash: bytes32,
|
||||
fee: uint64 = uint64(0),
|
||||
) -> Optional[SpendBundle]:
|
||||
self.log.debug("Attempt to transfer a new NFT")
|
||||
self.log.info("Attempt to transfer a new NFT")
|
||||
coin = nft_coin_info.coin
|
||||
self.log.debug("Transferring NFT coin %r to puzhash: %s", nft_coin_info, puzzle_hash)
|
||||
self.log.debug("Transferring NFT coin %r to puzhash: %s", nft_coin_info.coin, puzzle_hash)
|
||||
|
||||
amount = coin.amount
|
||||
unft = UncurriedNFT.uncurry(nft_coin_info.full_puzzle)
|
||||
puzzle_hash_to_sign = unft.inner_puzzle.get_tree_hash()
|
||||
condition_list = [make_create_coin_condition(puzzle_hash, amount, [puzzle_hash])]
|
||||
inner_solution = Program.to([solution_for_conditions(condition_list), amount])
|
||||
puzzle_hash_to_sign = unft.p2_puzzle.get_tree_hash()
|
||||
if unft.supports_did:
|
||||
self.log.debug("Transferring NFT with ownership layer")
|
||||
inner_solution = create_ownership_layer_transfer_solution(int_to_bytes(0), int_to_bytes(0), [], puzzle_hash)
|
||||
else:
|
||||
condition_list = [make_create_coin_condition(puzzle_hash, amount, [puzzle_hash])]
|
||||
inner_solution = Program.to([solution_for_conditions(condition_list)])
|
||||
self.log.debug("Solution for new coin: %r", disassemble(inner_solution))
|
||||
nft_tx_record = await self._make_nft_transaction(
|
||||
nft_coin_info,
|
||||
|
||||
@@ -4,8 +4,6 @@ import logging
|
||||
from dataclasses import dataclass
|
||||
from typing import Optional, Type, TypeVar
|
||||
|
||||
from blspy import G1Element
|
||||
|
||||
from chia.types.blockchain_format.program import Program
|
||||
from chia.types.blockchain_format.sized_bytes import bytes32
|
||||
from chia.util.ints import uint16
|
||||
@@ -72,9 +70,6 @@ class UncurriedNFT:
|
||||
supports_did: bool
|
||||
"""If the inner puzzle support the DID"""
|
||||
|
||||
owner_pubkey: Optional[G1Element]
|
||||
"""Owner's Pubkey in the P2 puzzle"""
|
||||
|
||||
nft_inner_puzzle_hash: Optional[bytes32]
|
||||
"""Puzzle hash of the ownership layer inner puzzle """
|
||||
|
||||
@@ -141,7 +136,6 @@ class UncurriedNFT:
|
||||
if kv_pair.first().as_atom() == b"st":
|
||||
series_total = kv_pair.rest()
|
||||
current_did = None
|
||||
pubkey = None
|
||||
transfer_program = None
|
||||
transfer_program_args = None
|
||||
royalty_address = None
|
||||
@@ -153,17 +147,14 @@ class UncurriedNFT:
|
||||
supports_did = True
|
||||
log.debug("Parsing ownership layer")
|
||||
_, current_did, transfer_program, p2_puzzle = ol_args.as_iter()
|
||||
_, p2_args = p2_puzzle.uncurry()
|
||||
(pubkey_sexp,) = p2_args.as_iter()
|
||||
transfer_program_mod, transfer_program_args = transfer_program.uncurry()
|
||||
_, _, royalty_address_p, royalty_percentage, _, _ = transfer_program_args.as_iter()
|
||||
_, royalty_address_p, royalty_percentage, _, _ = transfer_program_args.as_iter()
|
||||
royalty_percentage = uint16(royalty_percentage.as_int())
|
||||
royalty_address = royalty_address_p.atom
|
||||
current_did = current_did.atom
|
||||
if current_did == b"":
|
||||
# For unassigned NFT, set owner DID to None
|
||||
current_did = None
|
||||
pubkey = pubkey_sexp.atom
|
||||
else:
|
||||
log.debug("Creating a standard NFT puzzle")
|
||||
p2_puzzle = inner_puzzle
|
||||
@@ -190,10 +181,16 @@ class UncurriedNFT:
|
||||
inner_puzzle=inner_puzzle,
|
||||
owner_did=current_did,
|
||||
supports_did=supports_did,
|
||||
owner_pubkey=pubkey,
|
||||
transfer_program=transfer_program,
|
||||
transfer_program_curry_params=transfer_program_args,
|
||||
royalty_address=royalty_address,
|
||||
trade_price_percentage=royalty_percentage,
|
||||
nft_inner_puzzle_hash=nft_inner_puzzle_mod,
|
||||
)
|
||||
|
||||
def get_innermost_solution(self, solution: Program) -> Program:
|
||||
state_layer_inner_solution: Program = solution.at("rrff")
|
||||
if self.supports_did:
|
||||
return state_layer_inner_solution.first() # type: ignore
|
||||
else:
|
||||
return state_layer_inner_solution
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
(include curry-and-treehash.clinc)
|
||||
|
||||
(defconstant NEW_OWNER_CONDITION -10)
|
||||
(defconstant ANNOUNCEMENT_PREFIX 0xad4cd55cf7ad6414) ; first 8 bytes of (sha256 "Ownership Layer")
|
||||
|
||||
(defun sha256tree1
|
||||
(TREE)
|
||||
@@ -19,6 +20,13 @@
|
||||
)
|
||||
)
|
||||
|
||||
(defmacro assert items
|
||||
(if (r items)
|
||||
(list if (f items) (c assert (r items)) (q . (x)))
|
||||
(f items)
|
||||
)
|
||||
)
|
||||
|
||||
(defun-inline nft_ownership_layer_puzzle_hash (NFT_OWNERSHIP_LAYER_MOD_HASH new_owner TRANSFER_PROGRAM inner_puzzle_hash)
|
||||
(puzzle-hash-of-curried-function NFT_OWNERSHIP_LAYER_MOD_HASH
|
||||
inner_puzzle_hash
|
||||
@@ -28,41 +36,78 @@
|
||||
)
|
||||
)
|
||||
|
||||
(defun wrap_odd_create_coins (NFT_OWNERSHIP_LAYER_MOD_HASH new_owner conditions TRANSFER_PROGRAM)
|
||||
(if conditions
|
||||
(if (= (f (f conditions)) CREATE_COIN)
|
||||
(if (= (logand (f (r (r (f conditions))))) ONE)
|
||||
(c (c CREATE_COIN (c (nft_ownership_layer_puzzle_hash NFT_OWNERSHIP_LAYER_MOD_HASH new_owner TRANSFER_PROGRAM (f (r (f conditions)))) (r (r (f conditions))))) (r conditions)) ; we can cancel after one because if there's more than one odd coin singleton layer will filter it
|
||||
(c (f conditions) (wrap_odd_create_coins NFT_OWNERSHIP_LAYER_MOD_HASH new_owner (r conditions) TRANSFER_PROGRAM))
|
||||
)
|
||||
(c (f conditions) (wrap_odd_create_coins NFT_OWNERSHIP_LAYER_MOD_HASH new_owner (r conditions) TRANSFER_PROGRAM ))
|
||||
)
|
||||
()
|
||||
(defun construct_end_conditions (NFT_OWNERSHIP_LAYER_MOD_HASH TRANSFER_PROGRAM odd_args (new_owner new_tp conditions))
|
||||
(c
|
||||
(c
|
||||
CREATE_COIN
|
||||
(c
|
||||
(nft_ownership_layer_puzzle_hash NFT_OWNERSHIP_LAYER_MOD_HASH new_owner (if new_tp new_tp TRANSFER_PROGRAM) (f odd_args))
|
||||
(r odd_args)
|
||||
)
|
||||
)
|
||||
conditions
|
||||
)
|
||||
)
|
||||
|
||||
; Find the magic conditions and remember the values communicated before moving to the next stage (recurrying self)
|
||||
(defun process_transfer_program (NFT_OWNERSHIP_LAYER_MOD_HASH TRANSFER_PROGRAM (new_owner optional_new_tp tp_conditions))
|
||||
(wrap_odd_create_coins NFT_OWNERSHIP_LAYER_MOD_HASH new_owner tp_conditions (if optional_new_tp optional_new_tp TRANSFER_PROGRAM)) ; the new owner can be set to 0, but if new tp is 0 then use the old tp
|
||||
(defun wrap_odd_create_coins (NFT_OWNERSHIP_LAYER_MOD_HASH TRANSFER_PROGRAM CURRENT_OWNER all_conditions conditions odd_args tp_output)
|
||||
(if conditions
|
||||
(if (= (f (f conditions)) CREATE_COIN)
|
||||
(if (= (logand (f (r (r (f conditions))))) ONE)
|
||||
(assert (not odd_args)
|
||||
; then
|
||||
(wrap_odd_create_coins NFT_OWNERSHIP_LAYER_MOD_HASH TRANSFER_PROGRAM CURRENT_OWNER all_conditions (r conditions) (r (f conditions)) tp_output)
|
||||
)
|
||||
(c (f conditions) (wrap_odd_create_coins NFT_OWNERSHIP_LAYER_MOD_HASH TRANSFER_PROGRAM CURRENT_OWNER all_conditions (r conditions) odd_args tp_output))
|
||||
)
|
||||
(if (= (f (f conditions)) NEW_OWNER_CONDITION)
|
||||
(assert (not tp_output)
|
||||
(c
|
||||
(list CREATE_PUZZLE_ANNOUNCEMENT (concat ANNOUNCEMENT_PREFIX (sha256tree1 (r (f conditions)))))
|
||||
(wrap_odd_create_coins NFT_OWNERSHIP_LAYER_MOD_HASH TRANSFER_PROGRAM CURRENT_OWNER all_conditions (r conditions) odd_args (a TRANSFER_PROGRAM (list CURRENT_OWNER all_conditions (r (f conditions)))))
|
||||
)
|
||||
)
|
||||
(if (= (f (f conditions)) CREATE_PUZZLE_ANNOUNCEMENT)
|
||||
(assert (all
|
||||
(not (= (substr (f (r (f conditions))) 0 8) ANNOUNCEMENT_PREFIX))
|
||||
(= 40 (strlen (f (r (f conditions)))))
|
||||
)
|
||||
; then
|
||||
(c (f conditions) (wrap_odd_create_coins NFT_OWNERSHIP_LAYER_MOD_HASH TRANSFER_PROGRAM CURRENT_OWNER all_conditions (r conditions) odd_args tp_output))
|
||||
)
|
||||
(c (f conditions) (wrap_odd_create_coins NFT_OWNERSHIP_LAYER_MOD_HASH TRANSFER_PROGRAM CURRENT_OWNER all_conditions (r conditions) odd_args tp_output))
|
||||
)
|
||||
)
|
||||
)
|
||||
(assert odd_args
|
||||
; then
|
||||
(construct_end_conditions NFT_OWNERSHIP_LAYER_MOD_HASH TRANSFER_PROGRAM odd_args
|
||||
(if tp_output
|
||||
tp_output
|
||||
(a TRANSFER_PROGRAM (list CURRENT_OWNER all_conditions ()))
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
; if we don't find the transfer program magic number then we should fail
|
||||
(defun loop_through_conditions_looking_for_transfer_solution (TRANSFER_PROGRAM CURRENT_OWNER conditions all_conditions)
|
||||
(if conditions
|
||||
(if (= (f (f conditions)) NEW_OWNER_CONDITION)
|
||||
(a TRANSFER_PROGRAM (list CURRENT_OWNER all_conditions (r (f conditions)))) ; (r (f conditions)) is (new_owner, trade_price, tp_solution)
|
||||
(loop_through_conditions_looking_for_transfer_solution TRANSFER_PROGRAM CURRENT_OWNER (r conditions) all_conditions)
|
||||
)
|
||||
(x)
|
||||
)
|
||||
)
|
||||
|
||||
(defun transfer_program_loader (NFT_OWNERSHIP_LAYER_MOD_HASH TRANSFER_PROGRAM CURRENT_OWNER conditions)
|
||||
(process_transfer_program NFT_OWNERSHIP_LAYER_MOD_HASH TRANSFER_PROGRAM (loop_through_conditions_looking_for_transfer_solution TRANSFER_PROGRAM CURRENT_OWNER conditions conditions))
|
||||
)
|
||||
(defun main (
|
||||
NFT_OWNERSHIP_LAYER_MOD_HASH
|
||||
TRANSFER_PROGRAM
|
||||
CURRENT_OWNER
|
||||
conditions
|
||||
)
|
||||
(wrap_odd_create_coins
|
||||
NFT_OWNERSHIP_LAYER_MOD_HASH
|
||||
TRANSFER_PROGRAM
|
||||
CURRENT_OWNER
|
||||
conditions
|
||||
conditions
|
||||
() () () () ()
|
||||
)
|
||||
)
|
||||
|
||||
; main
|
||||
(transfer_program_loader
|
||||
(main
|
||||
NFT_OWNERSHIP_LAYER_MOD_HASH
|
||||
TRANSFER_PROGRAM
|
||||
CURRENT_OWNER
|
||||
|
||||
@@ -1 +1 @@
|
||||
ff02ffff01ff02ff2effff04ff02ffff04ff05ffff04ff17ffff04ff0bffff04ffff02ff2fff5f80ff80808080808080ffff04ffff01ffffff02ff3304ffff81f601ff0102ffffff02ffff03ff05ffff01ff02ff12ffff04ff02ffff04ff0dffff04ffff0bff3cffff0bff34ff3880ffff0bff3cffff0bff3cffff0bff34ff2c80ff0980ffff0bff3cff0bffff0bff34ff8080808080ff8080808080ffff010b80ff0180ffff02ffff03ff17ffff01ff02ffff03ffff09ff47ff2480ffff01ff02ff05ffff04ff0bffff04ff2fffff04ff67ff8080808080ffff01ff02ff2affff04ff02ffff04ff05ffff04ff0bffff04ff37ffff04ff2fff8080808080808080ff0180ffff01ff088080ff0180ff02ff3effff04ff02ffff04ff05ffff04ff27ffff04ff81b7ffff04ffff02ffff03ff57ffff0157ffff010b80ff0180ff80808080808080ffffff0bff3cffff0bff34ff1080ffff0bff3cffff0bff3cffff0bff34ff2c80ff0580ffff0bff3cffff02ff12ffff04ff02ffff04ff07ffff04ffff0bff34ff3480ff8080808080ffff0bff34ff8080808080ff02ffff03ffff07ff0580ffff01ff0bffff0102ffff02ff36ffff04ff02ffff04ff09ff80808080ffff02ff36ffff04ff02ffff04ff0dff8080808080ffff01ff0bffff0101ff058080ff0180ffff02ff3affff04ff02ffff04ff05ffff04ff0bffff04ffff02ff2affff04ff02ffff04ff0bffff04ff17ffff04ff2fffff04ff2fff80808080808080ff808080808080ff02ffff03ff17ffff01ff02ffff03ffff09ff47ff2880ffff01ff02ffff03ffff09ffff18ff82016780ff3480ffff01ff04ffff04ff28ffff04ffff02ff26ffff04ff02ffff04ff05ffff04ff81a7ffff04ffff02ff36ffff04ff02ffff04ff2fff80808080ffff04ffff0bff34ff0b80ffff04ffff0bff34ff0580ff8080808080808080ff81e78080ff3780ffff01ff04ff27ffff02ff3effff04ff02ffff04ff05ffff04ff0bffff04ff37ffff04ff2fff808080808080808080ff0180ffff01ff04ff27ffff02ff3effff04ff02ffff04ff05ffff04ff0bffff04ff37ffff04ff2fff808080808080808080ff0180ff8080ff0180ff018080
|
||||
ff02ffff01ff02ff26ffff04ff02ffff04ff05ffff04ff17ffff04ff0bffff04ffff02ff2fff5f80ff80808080808080ffff04ffff01ffffff88ad4cd55cf7ad6414ff0233ffff3e04ff81f601ffffff0102ffff02ffff03ff05ffff01ff02ff2affff04ff02ffff04ff0dffff04ffff0bff32ffff0bff3cff3480ffff0bff32ffff0bff32ffff0bff3cff2280ff0980ffff0bff32ff0bffff0bff3cff8080808080ff8080808080ffff010b80ff0180ff04ffff04ff38ffff04ffff02ff36ffff04ff02ffff04ff05ffff04ff27ffff04ffff02ff2effff04ff02ffff04ffff02ffff03ff81afffff0181afffff010b80ff0180ff80808080ffff04ffff0bff3cff4f80ffff04ffff0bff3cff0580ff8080808080808080ff378080ff82016f80ffffff02ff3effff04ff02ffff04ff05ffff04ff0bffff04ff17ffff04ff2fffff04ff2fffff01ff80ff80ff80ff80ff808080808080808080ff0bff32ffff0bff3cff2880ffff0bff32ffff0bff32ffff0bff3cff2280ff0580ffff0bff32ffff02ff2affff04ff02ffff04ff07ffff04ffff0bff3cff3c80ff8080808080ffff0bff3cff8080808080ffff02ffff03ffff07ff0580ffff01ff0bffff0102ffff02ff2effff04ff02ffff04ff09ff80808080ffff02ff2effff04ff02ffff04ff0dff8080808080ffff01ff0bffff0101ff058080ff0180ff02ffff03ff5fffff01ff02ffff03ffff09ff82011fff3880ffff01ff02ffff03ffff09ffff18ff82059f80ff3c80ffff01ff02ffff03ffff20ff81bf80ffff01ff02ff3effff04ff02ffff04ff05ffff04ff0bffff04ff17ffff04ff2fffff04ff81dfffff04ff82019fffff04ff82017fff80808080808080808080ffff01ff088080ff0180ffff01ff04ff819fffff02ff3effff04ff02ffff04ff05ffff04ff0bffff04ff17ffff04ff2fffff04ff81dfffff04ff81bfffff04ff82017fff808080808080808080808080ff0180ffff01ff02ffff03ffff09ff82011fff2c80ffff01ff02ffff03ffff20ff82017f80ffff01ff04ffff04ff24ffff04ffff0eff10ffff02ff2effff04ff02ffff04ff82019fff8080808080ff808080ffff02ff3effff04ff02ffff04ff05ffff04ff0bffff04ff17ffff04ff2fffff04ff81dfffff04ff81bfffff04ffff02ff0bffff04ff17ffff04ff2fffff04ff82019fff8080808080ff8080808080808080808080ffff01ff088080ff0180ffff01ff02ffff03ffff09ff82011fff2480ffff01ff02ffff03ffff22ffff20ffff09ffff0cff82029fff80ffff010880ff108080ffff09ffff0128ffff0dff82029f808080ffff01ff04ff819fffff02ff3effff04ff02ffff04ff05ffff04ff0bffff04ff17ffff04ff2fffff04ff81dfffff04ff81bfffff04ff82017fff8080808080808080808080ffff01ff088080ff0180ffff01ff04ff819fffff02ff3effff04ff02ffff04ff05ffff04ff0bffff04ff17ffff04ff2fffff04ff81dfffff04ff81bfffff04ff82017fff808080808080808080808080ff018080ff018080ff0180ffff01ff02ffff03ff81bfffff01ff02ff3affff04ff02ffff04ff05ffff04ff0bffff04ff81bfffff04ffff02ffff03ff82017fffff0182017fffff01ff02ff0bffff04ff17ffff04ff2fffff01ff808080808080ff0180ff80808080808080ffff01ff088080ff018080ff0180ff018080
|
||||
@@ -1 +1 @@
|
||||
59d2c5ac094c3e07628a044ba4bd2e2e7a9e9ad637ac39c4a4ae34aa8e72f7bb
|
||||
dacf3308015a4dd77e45a3f79c8b3cea7ec637d05554894c3eb5f779b17f379b
|
||||
+39
-29
@@ -5,28 +5,23 @@
|
||||
TRADE_PRICE_PERCENTAGE
|
||||
SETTLEMENT_MOD_HASH
|
||||
CAT_MOD_HASH
|
||||
Current_Owner
|
||||
(
|
||||
new_owner
|
||||
trade_prices_list ; list of (amount type) pairs
|
||||
(new_did_inner_hash) ; this is the opaque transfer_program_solution
|
||||
)
|
||||
Current_Owner ; Truth
|
||||
conditions ; Truth
|
||||
solution ; created from the NFT's inner puzzle - solution is (new_owner trade_prices_list new_did_inner_hash)
|
||||
)
|
||||
|
||||
; This is a metadata updater - which must return (new_owner, Optional[new_transfer_program], conditions)
|
||||
; This is a transfer program - which must return (new_owner, Optional[new_transfer_program], conditions)
|
||||
|
||||
(include condition_codes.clvm)
|
||||
(include curry-and-treehash.clinc)
|
||||
|
||||
(defconstant CHANGE_OWNER -10)
|
||||
(defconstant RECURRY_TRANSFER_PROGRAM -22)
|
||||
(defconstant TEN_THOUSAND 10000)
|
||||
|
||||
(defun sha256tree1
|
||||
(TREE)
|
||||
(if (l TREE)
|
||||
(sha256 2 (sha256tree1 (f TREE)) (sha256tree1 (r TREE)))
|
||||
(sha256 1 TREE)
|
||||
(sha256 ONE TREE)
|
||||
)
|
||||
)
|
||||
|
||||
@@ -47,15 +42,11 @@
|
||||
(defun-inline cat_settlement_puzzle_hash (CAT_MOD_HASH tail_hash SETTLEMENT_MOD_HASH)
|
||||
(puzzle-hash-of-curried-function CAT_MOD_HASH
|
||||
SETTLEMENT_MOD_HASH
|
||||
(sha256 1 tail_hash)
|
||||
(sha256 1 CAT_MOD_HASH)
|
||||
(sha256 ONE tail_hash)
|
||||
(sha256 ONE CAT_MOD_HASH)
|
||||
)
|
||||
)
|
||||
|
||||
(defun round_down_to_even (value)
|
||||
(if (logand value 1) (- value 1) value)
|
||||
)
|
||||
|
||||
(defun-inline calculate_percentage (amount percentage)
|
||||
(f (divmod (* amount percentage) TEN_THOUSAND))
|
||||
)
|
||||
@@ -64,9 +55,15 @@
|
||||
(defun parse_trade_prices_list (ROYALTY_ADDRESS TRADE_PRICE_PERCENTAGE SETTLEMENT_MOD_HASH CAT_MOD_HASH trade_prices_list my_nft_id)
|
||||
(if trade_prices_list
|
||||
(c
|
||||
(if (r (f trade_prices_list))
|
||||
(list ASSERT_PUZZLE_ANNOUNCEMENT (sha256 (cat_settlement_puzzle_hash CAT_MOD_HASH (f (r (f trade_prices_list))) SETTLEMENT_MOD_HASH) (sha256tree1 (c my_nft_id (list (list ROYALTY_ADDRESS (calculate_percentage (f (f trade_prices_list)) TRADE_PRICE_PERCENTAGE)))))))
|
||||
(list CREATE_COIN ROYALTY_ADDRESS (round_down_to_even (calculate_percentage (f (f trade_prices_list)) TRADE_PRICE_PERCENTAGE)))
|
||||
(list
|
||||
ASSERT_PUZZLE_ANNOUNCEMENT
|
||||
(sha256
|
||||
(if (r (f trade_prices_list))
|
||||
(cat_settlement_puzzle_hash CAT_MOD_HASH (f (r (f trade_prices_list))) SETTLEMENT_MOD_HASH)
|
||||
SETTLEMENT_MOD_HASH
|
||||
)
|
||||
(sha256tree1 (c my_nft_id (list (list ROYALTY_ADDRESS (calculate_percentage (f (f trade_prices_list)) TRADE_PRICE_PERCENTAGE) (list ROYALTY_ADDRESS)))))
|
||||
)
|
||||
)
|
||||
(parse_trade_prices_list ROYALTY_ADDRESS TRADE_PRICE_PERCENTAGE SETTLEMENT_MOD_HASH CAT_MOD_HASH (r trade_prices_list) my_nft_id)
|
||||
)
|
||||
@@ -74,16 +71,29 @@
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
(list
|
||||
new_owner
|
||||
0
|
||||
(if new_owner
|
||||
(c
|
||||
(list ASSERT_PUZZLE_ANNOUNCEMENT (sha256 (calculate_full_puzzle_hash (c (f SINGLETON_STRUCT) (c new_owner (r (r SINGLETON_STRUCT)))) new_did_inner_hash) (f (r SINGLETON_STRUCT))))
|
||||
(parse_trade_prices_list ROYALTY_ADDRESS TRADE_PRICE_PERCENTAGE SETTLEMENT_MOD_HASH CAT_MOD_HASH trade_prices_list (f (r SINGLETON_STRUCT)))
|
||||
; main
|
||||
; Returning (new_owner new_transfer_program conditions)
|
||||
; solution is (new_owner trade_prices_list new_did_inner_hash)
|
||||
(if solution
|
||||
(list
|
||||
(f solution)
|
||||
0
|
||||
(if (all (f solution) (not (= (f solution) Current_Owner)))
|
||||
(c
|
||||
(list
|
||||
ASSERT_PUZZLE_ANNOUNCEMENT
|
||||
(sha256
|
||||
(calculate_full_puzzle_hash (get_singleton_struct SINGLETON_STRUCT (f solution)) (f (r (r solution))))
|
||||
(f (r SINGLETON_STRUCT))
|
||||
)
|
||||
)
|
||||
(parse_trade_prices_list ROYALTY_ADDRESS TRADE_PRICE_PERCENTAGE SETTLEMENT_MOD_HASH CAT_MOD_HASH (f (r solution)) (f (r SINGLETON_STRUCT)))
|
||||
)
|
||||
(parse_trade_prices_list ROYALTY_ADDRESS TRADE_PRICE_PERCENTAGE SETTLEMENT_MOD_HASH CAT_MOD_HASH (f (r solution)) (f (r SINGLETON_STRUCT)))
|
||||
)
|
||||
(parse_trade_prices_list ROYALTY_ADDRESS TRADE_PRICE_PERCENTAGE SETTLEMENT_MOD_HASH CAT_MOD_HASH trade_prices_list (f (r SINGLETON_STRUCT)))
|
||||
)
|
||||
)
|
||||
(list Current_Owner () ())
|
||||
)
|
||||
|
||||
|
||||
)
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
ff02ffff01ff04ff82027fffff04ff80ffff04ffff02ffff03ff82027fffff01ff04ffff04ff10ffff04ffff0bffff02ff36ffff04ff02ffff04ff09ffff04ff82137fffff04ffff02ff3effff04ff02ffff04ffff04ff09ffff04ff82027fff1d8080ff80808080ff808080808080ff1580ff808080ffff02ff26ffff04ff02ffff04ff0bffff04ff17ffff04ff2fffff04ff5fffff04ff82057fffff04ff15ff80808080808080808080ffff01ff02ff26ffff04ff02ffff04ff0bffff04ff17ffff04ff2fffff04ff5fffff04ff82057fffff04ff15ff80808080808080808080ff0180ff80808080ffff04ffff01ffffff3fff0233ff04ff0101ffff822710ff02ff02ffff03ff05ffff01ff02ff3affff04ff02ffff04ff0dffff04ffff0bff2affff0bff2cff1480ffff0bff2affff0bff2affff0bff2cff3c80ff0980ffff0bff2aff0bffff0bff2cff8080808080ff8080808080ffff010b80ff0180ffffff02ffff03ff5fffff01ff04ffff02ffff03ff82019fffff01ff04ff10ffff04ffff0bffff02ff36ffff04ff02ffff04ff2fffff04ff17ffff04ffff0bffff0101ff82029f80ffff04ffff0bffff0101ff2f80ff80808080808080ffff02ff3effff04ff02ffff04ffff04ff81bfffff04ffff04ff05ffff04ffff05ffff14ffff12ff82011fff0b80ff128080ff808080ff808080ff8080808080ff808080ffff01ff04ff38ffff04ff05ffff04ffff02ff2effff04ff02ffff04ffff05ffff14ffff12ff82011fff0b80ff128080ff80808080ff8080808080ff0180ffff02ff26ffff04ff02ffff04ff05ffff04ff0bffff04ff17ffff04ff2fffff04ff81dfffff04ff81bfff80808080808080808080ff8080ff0180ff0bff2affff0bff2cff2880ffff0bff2affff0bff2affff0bff2cff3c80ff0580ffff0bff2affff02ff3affff04ff02ffff04ff07ffff04ffff0bff2cff2c80ff8080808080ffff0bff2cff8080808080ffff02ffff03ffff18ff05ffff010180ffff01ff11ff05ffff010180ffff010580ff0180ff02ffff03ffff07ff0580ffff01ff0bffff0102ffff02ff3effff04ff02ffff04ff09ff80808080ffff02ff3effff04ff02ffff04ff0dff8080808080ffff01ff0bffff0101ff058080ff0180ff018080
|
||||
ff02ffff01ff02ffff03ff8202ffffff01ff04ff8204ffffff04ff80ffff04ffff02ffff03ffff22ff8204ffffff20ffff09ff8204ffff81bf808080ffff01ff04ffff04ff10ffff04ffff0bffff02ff2effff04ff02ffff04ff09ffff04ff8216ffffff04ffff02ff3effff04ff02ffff04ffff04ff09ffff04ff8204ffff1d8080ff80808080ff808080808080ff1580ff808080ffff02ff16ffff04ff02ffff04ff0bffff04ff17ffff04ff2fffff04ff5fffff04ff820affffff04ff15ff80808080808080808080ffff01ff02ff16ffff04ff02ffff04ff0bffff04ff17ffff04ff2fffff04ff5fffff04ff820affffff04ff15ff80808080808080808080ff0180ff80808080ffff01ff04ff81bfffff01ff80ff80808080ff0180ffff04ffff01ffffff3f02ff04ff0101ffff822710ff02ff02ffff03ff05ffff01ff02ff3affff04ff02ffff04ff0dffff04ffff0bff2affff0bff2cff1480ffff0bff2affff0bff2affff0bff2cff3c80ff0980ffff0bff2aff0bffff0bff2cff8080808080ff8080808080ffff010b80ff0180ffff02ffff03ff5fffff01ff04ffff04ff10ffff04ffff0bffff02ffff03ff82019fffff01ff02ff2effff04ff02ffff04ff2fffff04ff17ffff04ffff0bff2cff82029f80ffff04ffff0bff2cff2f80ff80808080808080ffff011780ff0180ffff02ff3effff04ff02ffff04ffff04ff81bfffff04ffff04ff05ffff04ffff05ffff14ffff12ff82011fff0b80ff128080ffff04ffff04ff05ff8080ff80808080ff808080ff8080808080ff808080ffff02ff16ffff04ff02ffff04ff05ffff04ff0bffff04ff17ffff04ff2fffff04ff81dfffff04ff81bfff80808080808080808080ff8080ff0180ffff0bff2affff0bff2cff1880ffff0bff2affff0bff2affff0bff2cff3c80ff0580ffff0bff2affff02ff3affff04ff02ffff04ff07ffff04ffff0bff2cff2c80ff8080808080ffff0bff2cff8080808080ff02ffff03ffff07ff0580ffff01ff0bffff0102ffff02ff3effff04ff02ffff04ff09ff80808080ffff02ff3effff04ff02ffff04ff0dff8080808080ffff01ff0bff2cff058080ff0180ff018080
|
||||
+1
-1
@@ -1 +1 @@
|
||||
3b0c0f181e51ee219d82eb99a89b31a864802904a18da9352bcf01c3fe52ba8c
|
||||
a1d8a6b55005270d3a27214d6ddb2762ec7d2814e67132c6b542fdd6874a6887
|
||||
|
||||
-112
@@ -1,112 +0,0 @@
|
||||
(mod
|
||||
(
|
||||
P2_DELEGATED_PUZZLE_OR_HIDDEN_PUZZLE_MOD_HASH
|
||||
SINGLETON_STRUCT
|
||||
ROYALTY_ADDRESS
|
||||
TRADE_PRICE_PERCENTAGE
|
||||
SETTLEMENT_MOD_HASH
|
||||
CAT_MOD_HASH
|
||||
Current_Owner ; Truth
|
||||
conditions ; Truth
|
||||
(new_owner trade_price new_pk tp_solution) ; solution - created from NFT's innerpuz
|
||||
)
|
||||
|
||||
; This is a transfer program - which must return (new_owner, Optional[new_transfer_program], conditions)
|
||||
|
||||
(include condition_codes.clvm)
|
||||
(include curry-and-treehash.clinc)
|
||||
|
||||
(defconstant TEN_THOUSAND 10000)
|
||||
|
||||
(defun sha256tree1
|
||||
(TREE)
|
||||
(if (l TREE)
|
||||
(sha256 2 (sha256tree1 (f TREE)) (sha256tree1 (r TREE)))
|
||||
(sha256 ONE TREE)
|
||||
)
|
||||
)
|
||||
|
||||
;; return the full puzzlehash for a singleton with the innerpuzzle curried in
|
||||
; puzzle-hash-of-curried-function is imported from curry-and-treehash.clinc
|
||||
(defun-inline calculate_full_puzzle_hash (SINGLETON_STRUCT inner_puzzle_hash)
|
||||
(puzzle-hash-of-curried-function (f SINGLETON_STRUCT)
|
||||
inner_puzzle_hash
|
||||
(sha256tree1 SINGLETON_STRUCT)
|
||||
)
|
||||
)
|
||||
|
||||
; Given a singleton ID, generate the singleton struct
|
||||
(defun-inline get_singleton_struct (SINGLETON_STRUCT singleton_id)
|
||||
(c (f SINGLETON_STRUCT) (c singleton_id (r (r SINGLETON_STRUCT))))
|
||||
)
|
||||
|
||||
(defun-inline cat_settlement_puzzle_hash (CAT_MOD_HASH tail_hash SETTLEMENT_MOD_HASH)
|
||||
(puzzle-hash-of-curried-function CAT_MOD_HASH
|
||||
SETTLEMENT_MOD_HASH
|
||||
(sha256 ONE tail_hash)
|
||||
(sha256 ONE CAT_MOD_HASH)
|
||||
)
|
||||
)
|
||||
|
||||
(defun round_down_to_even (value)
|
||||
(if (logand value ONE) (- value ONE) value)
|
||||
)
|
||||
|
||||
(defun-inline calculate_percentage (amount percentage)
|
||||
(f (divmod (* amount percentage) TEN_THOUSAND))
|
||||
)
|
||||
|
||||
; Loop of the trade prices list and either assert a puzzle announcement or generate xch
|
||||
(defun parse_trade_prices_list (ROYALTY_ADDRESS TRADE_PRICE_PERCENTAGE SETTLEMENT_MOD_HASH CAT_MOD_HASH trade_prices_list my_nft_id)
|
||||
(if trade_prices_list
|
||||
(c
|
||||
(if (r (f trade_prices_list))
|
||||
(list ASSERT_PUZZLE_ANNOUNCEMENT (sha256 (cat_settlement_puzzle_hash CAT_MOD_HASH (f (r (f trade_prices_list))) SETTLEMENT_MOD_HASH) (sha256tree1 (c my_nft_id (list (list ROYALTY_ADDRESS (calculate_percentage (f (f trade_prices_list)) TRADE_PRICE_PERCENTAGE)))))))
|
||||
(list CREATE_COIN ROYALTY_ADDRESS (round_down_to_even (calculate_percentage (f (f trade_prices_list)) TRADE_PRICE_PERCENTAGE)))
|
||||
)
|
||||
(parse_trade_prices_list ROYALTY_ADDRESS TRADE_PRICE_PERCENTAGE SETTLEMENT_MOD_HASH CAT_MOD_HASH (r trade_prices_list) my_nft_id)
|
||||
)
|
||||
()
|
||||
)
|
||||
)
|
||||
|
||||
(defun calculate_royalties (SINGLETON_STRUCT ROYALTY_ADDRESS TRADE_PRICE_PERCENTAGE SETTLEMENT_MOD_HASH CAT_MOD_HASH Current_Owner (new_owner trade_prices_list new_pk (new_did_inner_hash)))
|
||||
(if (all new_owner (not (= new_owner Current_Owner)))
|
||||
(c
|
||||
(list ASSERT_PUZZLE_ANNOUNCEMENT (sha256 (calculate_full_puzzle_hash (c (f SINGLETON_STRUCT) (c new_owner (r (r SINGLETON_STRUCT)))) new_did_inner_hash) (f (r SINGLETON_STRUCT))))
|
||||
(parse_trade_prices_list ROYALTY_ADDRESS TRADE_PRICE_PERCENTAGE SETTLEMENT_MOD_HASH CAT_MOD_HASH trade_prices_list (f (r SINGLETON_STRUCT)))
|
||||
)
|
||||
(parse_trade_prices_list ROYALTY_ADDRESS TRADE_PRICE_PERCENTAGE SETTLEMENT_MOD_HASH CAT_MOD_HASH trade_prices_list (f (r SINGLETON_STRUCT)))
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
; The new_owner condition look like this:
|
||||
; (new_owner trade_prices_list new_pk transfer_program_solution)
|
||||
|
||||
(defun check_found_values (Current_Owner P2_DELEGATED_PUZZLE_OR_HIDDEN_PUZZLE_MOD_HASH SINGLETON_STRUCT ROYALTY_ADDRESS TRADE_PRICE_PERCENTAGE SETTLEMENT_MOD_HASH CAT_MOD_HASH odd_create_coin new_owner_condition)
|
||||
; check create coin uses same key as is signing trade_prices
|
||||
(if (= (f (r odd_create_coin)) (puzzle-hash-of-curried-function P2_DELEGATED_PUZZLE_OR_HIDDEN_PUZZLE_MOD_HASH (sha256 ONE (f (r (r new_owner_condition))))))
|
||||
(c odd_create_coin (c (list AGG_SIG_ME (f (r (r new_owner_condition))) (sha256tree1 (f (r (r (r new_owner_condition)))))) (calculate_royalties SINGLETON_STRUCT ROYALTY_ADDRESS TRADE_PRICE_PERCENTAGE SETTLEMENT_MOD_HASH CAT_MOD_HASH Current_Owner new_owner_condition)))
|
||||
(x)
|
||||
)
|
||||
)
|
||||
|
||||
(defun loop_through_conditions (Current_Owner P2_DELEGATED_PUZZLE_OR_HIDDEN_PUZZLE_MOD_HASH SINGLETON_STRUCT ROYALTY_ADDRESS TRADE_PRICE_PERCENTAGE SETTLEMENT_MOD_HASH CAT_MOD_HASH conditions odd_create_coin new_owner_condition)
|
||||
(if conditions
|
||||
(if (= (f (f conditions)) CREATE_COIN)
|
||||
(if (logand (f (r (r (f conditions)))) ONE)
|
||||
(check_found_values Current_Owner P2_DELEGATED_PUZZLE_OR_HIDDEN_PUZZLE_MOD_HASH SINGLETON_STRUCT ROYALTY_ADDRESS TRADE_PRICE_PERCENTAGE SETTLEMENT_MOD_HASH CAT_MOD_HASH (f conditions) new_owner_condition)
|
||||
(c (f conditions) (loop_through_conditions P2_DELEGATED_PUZZLE_OR_HIDDEN_PUZZLE_MOD_HASH SINGLETON_STRUCT ROYALTY_ADDRESS TRADE_PRICE_PERCENTAGE SETTLEMENT_MOD_HASH CAT_MOD_HASH Current_Owner (r conditions) odd_create_coin new_owner_condition))
|
||||
)
|
||||
(loop_through_conditions Current_Owner P2_DELEGATED_PUZZLE_OR_HIDDEN_PUZZLE_MOD_HASH SINGLETON_STRUCT ROYALTY_ADDRESS TRADE_PRICE_PERCENTAGE SETTLEMENT_MOD_HASH CAT_MOD_HASH (r conditions) odd_create_coin new_owner_condition)
|
||||
)
|
||||
(x)
|
||||
)
|
||||
)
|
||||
|
||||
; main
|
||||
|
||||
(list new_owner 0 (loop_through_conditions Current_Owner P2_DELEGATED_PUZZLE_OR_HIDDEN_PUZZLE_MOD_HASH SINGLETON_STRUCT ROYALTY_ADDRESS TRADE_PRICE_PERCENTAGE SETTLEMENT_MOD_HASH CAT_MOD_HASH conditions 0 (list new_owner trade_price new_pk tp_solution)))
|
||||
|
||||
)
|
||||
-1
@@ -1 +0,0 @@
|
||||
ff02ffff01ff04ff8209ffffff04ff80ffff04ffff02ff26ffff04ff02ffff04ff82017fffff04ff05ffff04ff0bffff04ff17ffff04ff2fffff04ff5fffff04ff81bfffff04ff8202ffffff04ff80ffff04ffff04ff8209ffffff04ff8215ffffff04ff822dffffff04ff825dffff8080808080ff80808080808080808080808080ff80808080ffff04ffff01ffffffff323fff0233ffff0401ff01822710ffffff02ff02ffff03ff05ffff01ff02ff32ffff04ff02ffff04ff0dffff04ffff0bff22ffff0bff34ff2480ffff0bff22ffff0bff22ffff0bff34ff2c80ff0980ffff0bff22ff0bffff0bff34ff8080808080ff8080808080ffff010b80ff0180ffff02ffff03ffff22ff82027fffff20ffff09ff82027fff81bf808080ffff01ff04ffff04ff30ffff04ffff0bffff02ff2effff04ff02ffff04ff09ffff04ff82277fffff04ffff02ff7effff04ff02ffff04ffff04ff09ffff04ff82027fff1d8080ff80808080ff808080808080ff1580ff808080ffff02ff36ffff04ff02ffff04ff0bffff04ff17ffff04ff2fffff04ff5fffff04ff82057fffff04ff15ff80808080808080808080ffff01ff02ff36ffff04ff02ffff04ff0bffff04ff17ffff04ff2fffff04ff5fffff04ff82057fffff04ff15ff80808080808080808080ff0180ff02ffff03ffff09ff820affffff02ff2effff04ff02ffff04ff0bffff04ffff0bff34ff822dff80ff808080808080ffff01ff04ff8202ffffff04ffff04ff20ffff04ff822dffffff04ffff02ff7effff04ff02ffff04ff825dffff80808080ff80808080ffff02ff2affff04ff02ffff04ff17ffff04ff2fffff04ff5fffff04ff81bfffff04ff82017fffff04ff05ffff04ff8205ffff808080808080808080808080ffff01ff088080ff0180ffffff02ffff03ff8202ffffff01ff02ffff03ffff09ff8208ffff3880ffff01ff02ffff03ffff18ff822cffff3480ffff01ff02ff3affff04ff02ffff04ff05ffff04ff0bffff04ff17ffff04ff2fffff04ff5fffff04ff81bfffff04ff82017fffff04ff8204ffffff04ff820bffff808080808080808080808080ffff01ff04ff8204ffffff02ff26ffff04ff02ffff04ff0bffff04ff17ffff04ff2fffff04ff5fffff04ff81bfffff04ff82017fffff04ff05ffff04ff8206ffffff04ff8205ffffff04ff820bffff808080808080808080808080808080ff0180ffff01ff02ff26ffff04ff02ffff04ff05ffff04ff0bffff04ff17ffff04ff2fffff04ff5fffff04ff81bfffff04ff82017fffff04ff8206ffffff04ff8205ffffff04ff820bffff8080808080808080808080808080ff0180ffff01ff088080ff0180ff02ffff03ff5fffff01ff04ffff02ffff03ff82019fffff01ff04ff30ffff04ffff0bffff02ff2effff04ff02ffff04ff2fffff04ff17ffff04ffff0bff34ff82029f80ffff04ffff0bff34ff2f80ff80808080808080ffff02ff7effff04ff02ffff04ffff04ff81bfffff04ffff04ff05ffff04ffff05ffff14ffff12ff82011fff0b80ff3c8080ff808080ff808080ff8080808080ff808080ffff01ff04ff38ffff04ff05ffff04ffff02ff5effff04ff02ffff04ffff05ffff14ffff12ff82011fff0b80ff3c8080ff80808080ff8080808080ff0180ffff02ff36ffff04ff02ffff04ff05ffff04ff0bffff04ff17ffff04ff2fffff04ff81dfffff04ff81bfff80808080808080808080ff8080ff0180ffff0bff22ffff0bff34ff2880ffff0bff22ffff0bff22ffff0bff34ff2c80ff0580ffff0bff22ffff02ff32ffff04ff02ffff04ff07ffff04ffff0bff34ff3480ff8080808080ffff0bff34ff8080808080ffff02ffff03ffff18ff05ff3480ffff01ff11ff05ff3480ffff010580ff0180ff02ffff03ffff07ff0580ffff01ff0bffff0102ffff02ff7effff04ff02ffff04ff09ff80808080ffff02ff7effff04ff02ffff04ff0dff8080808080ffff01ff0bff34ff058080ff0180ff018080
|
||||
-1
@@ -1 +0,0 @@
|
||||
1ba78fd36e36ff5e5ade71864527d2ce3980b4b43ddf7cdc3f714945f4b734a7
|
||||
@@ -3,8 +3,7 @@
|
||||
METADATA
|
||||
METADATA_UPDATER_PUZZLE_HASH
|
||||
INNER_PUZZLE
|
||||
solution
|
||||
my_amount
|
||||
inner_solution
|
||||
)
|
||||
|
||||
(include condition_codes.clvm)
|
||||
@@ -18,6 +17,13 @@
|
||||
)
|
||||
)
|
||||
|
||||
(defmacro assert items
|
||||
(if (r items)
|
||||
(list if (f items) (c assert (r items)) (q . (x)))
|
||||
(f items)
|
||||
)
|
||||
)
|
||||
|
||||
(defun-inline nft_state_layer_puzzle_hash (NFT_STATE_LAYER_MOD_HASH METADATA METADATA_UPDATER_PUZZLE_HASH inner_puzzle_hash)
|
||||
(puzzle-hash-of-curried-function NFT_STATE_LAYER_MOD_HASH
|
||||
inner_puzzle_hash
|
||||
@@ -28,62 +34,55 @@
|
||||
)
|
||||
|
||||
|
||||
; this function does two thins - it wraps the odd value create coins, and it also filters out all negative conditions
|
||||
(defun wrap_odd_create_coins (NFT_STATE_LAYER_MOD_HASH ((METADATA METADATA_UPDATER_PUZZLE_HASH) conditions) my_amount)
|
||||
; this function does two things - it wraps the odd value create coins, and it also filters out all negative conditions
|
||||
; odd_coin_params is (puzhash amount ...)
|
||||
; new_metadata_info is ((METADATA METADATA_UPDATER_PUZZLE_HASH) conditions)
|
||||
(defun wrap_odd_create_coins (NFT_STATE_LAYER_MOD_HASH conditions odd_coin_params new_metadata_info metadata_seen)
|
||||
(if conditions
|
||||
(if (= (f (f conditions)) CREATE_COIN)
|
||||
(if (logand (f (r (r (f conditions)))) ONE)
|
||||
(c (c CREATE_COIN (c (nft_state_layer_puzzle_hash NFT_STATE_LAYER_MOD_HASH METADATA METADATA_UPDATER_PUZZLE_HASH (f (r (f conditions)))) (c my_amount (r (r (r (f conditions))))))) (wrap_odd_create_coins NFT_STATE_LAYER_MOD_HASH (list (list METADATA METADATA_UPDATER_PUZZLE_HASH) (r conditions)) my_amount))
|
||||
(c (f conditions) (wrap_odd_create_coins NFT_STATE_LAYER_MOD_HASH (list (list METADATA METADATA_UPDATER_PUZZLE_HASH) (r conditions)) my_amount))
|
||||
)
|
||||
(if (> (f (f conditions)) 0)
|
||||
(c (f conditions) (wrap_odd_create_coins NFT_STATE_LAYER_MOD_HASH (list (list METADATA METADATA_UPDATER_PUZZLE_HASH) (r conditions)) my_amount))
|
||||
(wrap_odd_create_coins NFT_STATE_LAYER_MOD_HASH (list (list METADATA METADATA_UPDATER_PUZZLE_HASH) (r conditions)) my_amount)
|
||||
)
|
||||
(if (logand (f (r (r (f conditions)))) ONE)
|
||||
(assert (not odd_coin_params)
|
||||
(wrap_odd_create_coins NFT_STATE_LAYER_MOD_HASH (r conditions) (r (f conditions)) new_metadata_info metadata_seen)
|
||||
)
|
||||
(c (f conditions) (wrap_odd_create_coins NFT_STATE_LAYER_MOD_HASH (r conditions) odd_coin_params new_metadata_info metadata_seen))
|
||||
)
|
||||
(if (= (f (f conditions)) -24)
|
||||
(wrap_odd_create_coins NFT_STATE_LAYER_MOD_HASH (r conditions) odd_coin_params
|
||||
(assert (all
|
||||
(= (sha256tree1 (f (r (f conditions)))) (f (r (f new_metadata_info))))
|
||||
(not metadata_seen)
|
||||
)
|
||||
; then
|
||||
(a (f (r (f conditions))) (list (f (f new_metadata_info)) (f (r (f new_metadata_info))) (f (r (r (f conditions))))))
|
||||
)
|
||||
1 ; the metadata update has been seen now
|
||||
)
|
||||
(c (f conditions) (wrap_odd_create_coins NFT_STATE_LAYER_MOD_HASH (r conditions) odd_coin_params new_metadata_info metadata_seen))
|
||||
)
|
||||
)
|
||||
()
|
||||
)
|
||||
)
|
||||
|
||||
; take two lists and merge them into one
|
||||
(defun merge_list (list_a list_b)
|
||||
(if list_a
|
||||
(c (f list_a) (merge_list (r list_a) list_b))
|
||||
list_b
|
||||
)
|
||||
)
|
||||
|
||||
(defun check_for_metadata_prog_reveal (METADATA_UPDATER_PUZZLE_HASH CURRENT_METADATA conditions_loop)
|
||||
(if conditions_loop
|
||||
(if (= (f (f conditions_loop)) -24)
|
||||
(if (= (sha256tree1 (f (r (f conditions_loop)))) METADATA_UPDATER_PUZZLE_HASH)
|
||||
(a (f (r (f conditions_loop))) (list CURRENT_METADATA METADATA_UPDATER_PUZZLE_HASH (f (r (r (f conditions_loop))))))
|
||||
(x)
|
||||
(c
|
||||
(c CREATE_COIN
|
||||
(c
|
||||
(nft_state_layer_puzzle_hash
|
||||
NFT_STATE_LAYER_MOD_HASH
|
||||
(f (f new_metadata_info))
|
||||
(f (r (f new_metadata_info)))
|
||||
(f odd_coin_params) ; metadata updater solution
|
||||
)
|
||||
(r odd_coin_params)
|
||||
)
|
||||
)
|
||||
(check_for_metadata_prog_reveal METADATA_UPDATER_PUZZLE_HASH CURRENT_METADATA (r conditions_loop))
|
||||
(f (r new_metadata_info)) ; metadata_updater conditions
|
||||
)
|
||||
(list (list CURRENT_METADATA METADATA_UPDATER_PUZZLE_HASH) 0)
|
||||
)
|
||||
)
|
||||
|
||||
(defun process_metadata_updater (metadata_result conditions)
|
||||
(c (f metadata_result) (list (merge_list (f (r metadata_result)) conditions)))
|
||||
)
|
||||
|
||||
(defun metadata_updater_loader (METADATA_UPDATER_PUZZLE_HASH CURRENT_METADATA conditions)
|
||||
(process_metadata_updater (check_for_metadata_prog_reveal METADATA_UPDATER_PUZZLE_HASH CURRENT_METADATA conditions) conditions)
|
||||
)
|
||||
|
||||
; main
|
||||
(c
|
||||
(list ASSERT_MY_AMOUNT my_amount)
|
||||
(wrap_odd_create_coins
|
||||
NFT_STATE_LAYER_MOD_HASH
|
||||
(metadata_updater_loader
|
||||
METADATA_UPDATER_PUZZLE_HASH METADATA (a INNER_PUZZLE solution)
|
||||
)
|
||||
my_amount
|
||||
0
|
||||
)
|
||||
(wrap_odd_create_coins
|
||||
NFT_STATE_LAYER_MOD_HASH
|
||||
(a INNER_PUZZLE inner_solution)
|
||||
()
|
||||
(list (list METADATA METADATA_UPDATER_PUZZLE_HASH) 0) ; if the magic condition is never seen, this is the information we us to recurry
|
||||
()
|
||||
)
|
||||
)
|
||||
|
||||
@@ -1 +1 @@
|
||||
ff02ffff01ff04ffff04ff10ffff04ff81bfff808080ffff02ff3effff04ff02ffff04ff05ffff04ffff02ff3affff04ff02ffff04ff17ffff04ff0bffff04ffff02ff2fff5f80ff808080808080ffff04ff81bfffff01ff8080808080808080ffff04ffff01ffffff49ff0233ffff0401ff0102ffffffff02ffff03ff05ffff01ff02ff22ffff04ff02ffff04ff0dffff04ffff0bff3cffff0bff34ff2480ffff0bff3cffff0bff3cffff0bff34ff2c80ff0980ffff0bff3cff0bffff0bff34ff8080808080ff8080808080ffff010b80ff0180ff02ffff03ff17ffff01ff02ffff03ffff09ff47ffff0181e880ffff01ff02ffff03ffff09ffff02ff2effff04ff02ffff04ff81a7ff80808080ff0580ffff01ff02ff81a7ffff04ff0bffff04ff05ffff04ff820167ff8080808080ffff01ff088080ff0180ffff01ff02ff32ffff04ff02ffff04ff05ffff04ff0bffff04ff37ff80808080808080ff0180ffff01ff04ffff04ff0bffff04ff05ff808080ffff01ff80808080ff0180ffff02ffff03ff05ffff01ff04ff09ffff02ff2affff04ff02ffff04ff0dffff04ff0bff808080808080ffff010b80ff0180ff02ff26ffff04ff02ffff04ffff02ff32ffff04ff02ffff04ff05ffff04ff0bffff04ff17ff808080808080ffff04ff17ff8080808080ffffff04ff09ffff04ffff02ff2affff04ff02ffff04ff15ffff04ff0bff8080808080ff808080ff0bff3cffff0bff34ff2880ffff0bff3cffff0bff3cffff0bff34ff2c80ff0580ffff0bff3cffff02ff22ffff04ff02ffff04ff07ffff04ffff0bff34ff3480ff8080808080ffff0bff34ff8080808080ffff02ffff03ffff07ff0580ffff01ff0bffff0102ffff02ff2effff04ff02ffff04ff09ff80808080ffff02ff2effff04ff02ffff04ff0dff8080808080ffff01ff0bffff0101ff058080ff0180ff02ffff03ff2bffff01ff02ffff03ffff09ff818bff3880ffff01ff02ffff03ffff18ff8202cbff3480ffff01ff04ffff04ff38ffff04ffff02ff36ffff04ff02ffff04ff05ffff04ff82014bffff04ffff0bff34ff5380ffff04ffff02ff2effff04ff02ffff04ff23ff80808080ffff04ffff0bff34ff0580ff8080808080808080ffff04ff17ff8203cb808080ffff02ff3effff04ff02ffff04ff05ffff04ffff04ffff04ff23ffff04ff53ff808080ffff04ff6bff808080ffff04ff17ff80808080808080ffff01ff04ff4bffff02ff3effff04ff02ffff04ff05ffff04ffff04ffff04ff23ffff04ff53ff808080ffff04ff6bff808080ffff04ff17ff8080808080808080ff0180ffff01ff02ffff03ffff15ff818bff8080ffff01ff04ff4bffff02ff3effff04ff02ffff04ff05ffff04ffff04ffff04ff23ffff04ff53ff808080ffff04ff6bff808080ffff04ff17ff80808080808080ffff01ff02ff3effff04ff02ffff04ff05ffff04ffff04ffff04ff23ffff04ff53ff808080ffff04ff6bff808080ffff04ff17ff80808080808080ff018080ff0180ff8080ff0180ff018080
|
||||
ff02ffff01ff02ff3effff04ff02ffff04ff05ffff04ffff02ff2fff5f80ffff04ff80ffff04ffff04ffff04ff0bffff04ff17ff808080ffff01ff808080ffff01ff8080808080808080ffff04ffff01ffffff0233ff04ff0101ffff02ff02ffff03ff05ffff01ff02ff1affff04ff02ffff04ff0dffff04ffff0bff12ffff0bff2cff1480ffff0bff12ffff0bff12ffff0bff2cff3c80ff0980ffff0bff12ff0bffff0bff2cff8080808080ff8080808080ffff010b80ff0180ffff0bff12ffff0bff2cff1080ffff0bff12ffff0bff12ffff0bff2cff3c80ff0580ffff0bff12ffff02ff1affff04ff02ffff04ff07ffff04ffff0bff2cff2c80ff8080808080ffff0bff2cff8080808080ffff02ffff03ffff07ff0580ffff01ff0bffff0102ffff02ff2effff04ff02ffff04ff09ff80808080ffff02ff2effff04ff02ffff04ff0dff8080808080ffff01ff0bffff0101ff058080ff0180ff02ffff03ff0bffff01ff02ffff03ffff09ff23ff1880ffff01ff02ffff03ffff18ff81b3ff2c80ffff01ff02ffff03ffff20ff1780ffff01ff02ff3effff04ff02ffff04ff05ffff04ff1bffff04ff33ffff04ff2fffff04ff5fff8080808080808080ffff01ff088080ff0180ffff01ff04ff13ffff02ff3effff04ff02ffff04ff05ffff04ff1bffff04ff17ffff04ff2fffff04ff5fff80808080808080808080ff0180ffff01ff02ffff03ffff09ff23ffff0181e880ffff01ff02ff3effff04ff02ffff04ff05ffff04ff1bffff04ff17ffff04ffff02ffff03ffff22ffff09ffff02ff2effff04ff02ffff04ff53ff80808080ff82014f80ffff20ff5f8080ffff01ff02ff53ffff04ff818fffff04ff82014fffff04ff81b3ff8080808080ffff01ff088080ff0180ffff01ff0180808080808080ffff01ff04ff13ffff02ff3effff04ff02ffff04ff05ffff04ff1bffff04ff17ffff04ff2fffff04ff5fff80808080808080808080ff018080ff0180ffff01ff04ffff04ff18ffff04ffff02ff16ffff04ff02ffff04ff05ffff04ff27ffff04ffff0bff2cff82014f80ffff04ffff02ff2effff04ff02ffff04ff818fff80808080ffff04ffff0bff2cff0580ff8080808080808080ff378080ff81af8080ff0180ff018080
|
||||
@@ -1 +1 @@
|
||||
3cb215c818b13f6d18d0f33a0b8d2ef156483982cfe4bc0a5937b038ef28b03b
|
||||
31f5e810e2ad078817e8b047306c877ad82c77511aa235179c4a98af413f162a
|
||||
|
||||
@@ -45,7 +45,6 @@ wallet_program_files = set(
|
||||
"chia/wallet/puzzles/nft_metadata_updater_updateable.clvm",
|
||||
"chia/wallet/puzzles/nft_state_layer.clvm",
|
||||
"chia/wallet/puzzles/nft_ownership_layer.clvm",
|
||||
"chia/wallet/puzzles/nft_ownership_transfer_program_one_way_claim_with_royalties_new.clvm",
|
||||
"chia/wallet/puzzles/nft_ownership_transfer_program_one_way_claim_with_royalties.clvm",
|
||||
]
|
||||
)
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import pytest
|
||||
|
||||
from chia.types.announcement import Announcement
|
||||
from chia.types.blockchain_format.program import INFINITE_COST, Program
|
||||
from chia.wallet.puzzles.cat_loader import CAT_MOD
|
||||
@@ -11,7 +13,7 @@ LAUNCHER_PUZZLE = load_clvm("singleton_launcher.clvm")
|
||||
DID_MOD = load_clvm("did_innerpuz.clvm")
|
||||
NFT_STATE_LAYER_MOD = load_clvm("nft_state_layer.clvm")
|
||||
NFT_OWNERSHIP_LAYER = load_clvm("nft_ownership_layer.clvm")
|
||||
NFT_TRANSFER_PROGRAM = load_clvm("nft_ownership_transfer_program_one_way_claim_with_royalties_new.clvm")
|
||||
NFT_TRANSFER_PROGRAM = load_clvm("nft_ownership_transfer_program_one_way_claim_with_royalties.clvm")
|
||||
STANDARD_PUZZLE_MOD = load_clvm("p2_delegated_puzzle_or_hidden_puzzle.clvm")
|
||||
LAUNCHER_PUZZLE_HASH = LAUNCHER_PUZZLE.get_tree_hash()
|
||||
NFT_STATE_LAYER_MOD_HASH = NFT_STATE_LAYER_MOD.get_tree_hash()
|
||||
@@ -23,6 +25,7 @@ NFT_METADATA_UPDATER_DEFAULT = load_clvm("nft_metadata_updater_default.clvm")
|
||||
NFT_METADATA_UPDATER_UPDATEABLE = load_clvm("nft_metadata_updater_updateable.clvm")
|
||||
|
||||
|
||||
@pytest.mark.skip
|
||||
def test_new_nft_state_layer() -> None:
|
||||
pubkey = int_to_public_key(1)
|
||||
innerpuz = puzzle_for_pk(pubkey)
|
||||
@@ -57,6 +60,7 @@ def test_new_nft_state_layer() -> None:
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.skip
|
||||
def test_update_metadata() -> None:
|
||||
pubkey = int_to_public_key(1)
|
||||
innerpuz = puzzle_for_pk(pubkey)
|
||||
@@ -108,6 +112,7 @@ def test_update_metadata() -> None:
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.skip
|
||||
def test_transfer_program() -> None:
|
||||
pubkey = int_to_public_key(1)
|
||||
innerpuz = puzzle_for_pk(pubkey)
|
||||
@@ -159,6 +164,7 @@ def test_transfer_program() -> None:
|
||||
assert conditions.rest().rest().rest().first().rest().rest().first().as_int() == 40
|
||||
|
||||
|
||||
@pytest.mark.skip
|
||||
def test_ownership_layer() -> None:
|
||||
pubkey = int_to_public_key(1)
|
||||
innerpuz = puzzle_for_pk(pubkey)
|
||||
@@ -211,6 +217,7 @@ def test_ownership_layer() -> None:
|
||||
assert res.rest().rest().rest().first().rest().rest().first().as_int() == 40
|
||||
|
||||
|
||||
@pytest.mark.skip
|
||||
def test_full_stack() -> None:
|
||||
pubkey = int_to_public_key(1)
|
||||
innerpuz = puzzle_for_pk(pubkey)
|
||||
|
||||
@@ -0,0 +1,351 @@
|
||||
import itertools
|
||||
from typing import List, Tuple
|
||||
|
||||
import pytest
|
||||
from blspy import G2Element
|
||||
|
||||
from chia.clvm.spend_sim import SimClient, SpendSim
|
||||
from chia.types.announcement import Announcement
|
||||
from chia.types.blockchain_format.program import Program
|
||||
from chia.types.blockchain_format.sized_bytes import bytes32
|
||||
from chia.types.coin_spend import CoinSpend
|
||||
from chia.types.mempool_inclusion_status import MempoolInclusionStatus
|
||||
from chia.types.spend_bundle import SpendBundle
|
||||
from chia.util.errors import Err
|
||||
from chia.wallet.nft_wallet.nft_puzzles import (
|
||||
NFT_METADATA_UPDATER,
|
||||
NFT_TRANSFER_PROGRAM_DEFAULT,
|
||||
construct_ownership_layer,
|
||||
create_nft_layer_puzzle_with_curry_params,
|
||||
metadata_to_program,
|
||||
)
|
||||
|
||||
ACS = Program.to(1)
|
||||
ACS_PH = ACS.get_tree_hash()
|
||||
|
||||
|
||||
@pytest.mark.asyncio()
|
||||
@pytest.mark.parametrize("metadata_updater", ["default"])
|
||||
async def test_state_layer(setup_sim: Tuple[SpendSim, SimClient], metadata_updater: str) -> None:
|
||||
sim, sim_client = setup_sim
|
||||
|
||||
try:
|
||||
if metadata_updater == "default":
|
||||
METADATA: Program = metadata_to_program(
|
||||
{
|
||||
b"u": ["hey hey"],
|
||||
b"lu": ["You have no permissions grr"],
|
||||
b"mu": ["This but off chain"],
|
||||
b"foo": ["Can't update this"],
|
||||
}
|
||||
)
|
||||
METADATA_UPDATER: Program = NFT_METADATA_UPDATER
|
||||
else:
|
||||
# TODO: Add test for updateable
|
||||
return
|
||||
METADATA_UPDATER_PUZZLE_HASH: bytes32 = METADATA_UPDATER.get_tree_hash()
|
||||
|
||||
state_layer_puzzle: Program = create_nft_layer_puzzle_with_curry_params(
|
||||
METADATA, METADATA_UPDATER_PUZZLE_HASH, ACS
|
||||
)
|
||||
state_layer_ph: bytes32 = state_layer_puzzle.get_tree_hash()
|
||||
await sim.farm_block(state_layer_ph)
|
||||
state_layer_coin = (
|
||||
await sim_client.get_coin_records_by_puzzle_hash(state_layer_ph, include_spent_coins=False)
|
||||
)[0].coin
|
||||
|
||||
generic_spend = CoinSpend(
|
||||
state_layer_coin,
|
||||
state_layer_puzzle,
|
||||
Program.to([[[51, ACS_PH, 1]]]),
|
||||
)
|
||||
generic_bundle = SpendBundle([generic_spend], G2Element())
|
||||
|
||||
result = await sim_client.push_tx(generic_bundle)
|
||||
assert result == (MempoolInclusionStatus.SUCCESS, None)
|
||||
await sim.farm_block()
|
||||
|
||||
if metadata_updater == "default":
|
||||
metadata_updater_solutions: List[Program] = [
|
||||
Program.to((b"u", "update")),
|
||||
Program.to((b"lu", "update")),
|
||||
Program.to((b"mu", "update")),
|
||||
Program.to((b"foo", "update")),
|
||||
]
|
||||
expected_metadatas: List[Program] = [
|
||||
metadata_to_program(
|
||||
{
|
||||
b"u": ["update", "hey hey"],
|
||||
b"lu": ["You have no permissions grr"],
|
||||
b"mu": ["This but off chain"],
|
||||
b"foo": ["Can't update this"],
|
||||
}
|
||||
),
|
||||
metadata_to_program(
|
||||
{
|
||||
b"u": ["update", "hey hey"],
|
||||
b"lu": ["update", "You have no permissions grr"],
|
||||
b"mu": ["This but off chain"],
|
||||
b"foo": ["Can't update this"],
|
||||
}
|
||||
),
|
||||
metadata_to_program(
|
||||
{
|
||||
b"u": ["update", "hey hey"],
|
||||
b"lu": ["update", "You have no permissions grr"],
|
||||
b"mu": ["update", "This but off chain"],
|
||||
b"foo": ["Can't update this"],
|
||||
}
|
||||
),
|
||||
metadata_to_program(
|
||||
{ # no change
|
||||
b"u": ["update", "hey hey"],
|
||||
b"lu": ["update", "You have no permissions grr"],
|
||||
b"mu": ["update", "This but off chain"],
|
||||
b"foo": ["Can't update this"],
|
||||
}
|
||||
),
|
||||
]
|
||||
else:
|
||||
return
|
||||
|
||||
for solution, metadata in zip(metadata_updater_solutions, expected_metadatas):
|
||||
state_layer_coin = (
|
||||
await sim_client.get_coin_records_by_parent_ids([state_layer_coin.name()], include_spent_coins=False)
|
||||
)[0].coin
|
||||
update_spend = CoinSpend(
|
||||
state_layer_coin,
|
||||
state_layer_puzzle,
|
||||
Program.to(
|
||||
[
|
||||
[
|
||||
[51, ACS_PH, 1],
|
||||
[-24, METADATA_UPDATER, solution],
|
||||
]
|
||||
]
|
||||
),
|
||||
)
|
||||
update_bundle = SpendBundle([update_spend], G2Element())
|
||||
result = await sim_client.push_tx(update_bundle)
|
||||
assert result == (MempoolInclusionStatus.SUCCESS, None)
|
||||
await sim.farm_block()
|
||||
state_layer_puzzle = create_nft_layer_puzzle_with_curry_params(metadata, METADATA_UPDATER_PUZZLE_HASH, ACS)
|
||||
finally:
|
||||
await sim.close() # type: ignore
|
||||
|
||||
|
||||
@pytest.mark.asyncio()
|
||||
async def test_ownership_layer(setup_sim: Tuple[SpendSim, SimClient]) -> None:
|
||||
sim, sim_client = setup_sim
|
||||
|
||||
try:
|
||||
TARGET_OWNER = bytes32([0] * 32)
|
||||
TARGET_TP = Program.to([8]) # (x)
|
||||
# (a (i 11 (q 4 19 (c 43 (q ()))) (q 8)) 1) or
|
||||
# (mod (_ _ solution) (if solution (list (f solution) (f (r solution)) ()) (x)))
|
||||
transfer_program = Program.to([2, [3, 11, [1, 4, 19, [4, 43, [1, []]]], [1, 8]], 1])
|
||||
|
||||
ownership_puzzle: Program = construct_ownership_layer(
|
||||
None,
|
||||
transfer_program,
|
||||
ACS,
|
||||
)
|
||||
ownership_ph: bytes32 = ownership_puzzle.get_tree_hash()
|
||||
await sim.farm_block(ownership_ph)
|
||||
ownership_coin = (await sim_client.get_coin_records_by_puzzle_hash(ownership_ph, include_spent_coins=False))[
|
||||
0
|
||||
].coin
|
||||
|
||||
generic_spend = CoinSpend(
|
||||
ownership_coin,
|
||||
ownership_puzzle,
|
||||
Program.to([[[51, ACS_PH, 1], [-10, [], []]]]),
|
||||
)
|
||||
generic_bundle = SpendBundle([generic_spend], G2Element())
|
||||
result = await sim_client.push_tx(generic_bundle)
|
||||
assert result == (MempoolInclusionStatus.SUCCESS, None)
|
||||
await sim.farm_block()
|
||||
ownership_coin = (await sim_client.get_coin_records_by_puzzle_hash(ownership_ph, include_spent_coins=False))[
|
||||
0
|
||||
].coin
|
||||
|
||||
skip_tp_spend = CoinSpend(
|
||||
ownership_coin,
|
||||
ownership_puzzle,
|
||||
Program.to([[[51, ACS_PH, 1]]]),
|
||||
)
|
||||
skip_tp_bundle = SpendBundle([skip_tp_spend], G2Element())
|
||||
|
||||
result = await sim_client.push_tx(skip_tp_bundle)
|
||||
assert result == (MempoolInclusionStatus.FAILED, Err.GENERATOR_RUNTIME_ERROR)
|
||||
with pytest.raises(ValueError, match="clvm raise"):
|
||||
skip_tp_spend.puzzle_reveal.to_program().run(skip_tp_spend.solution.to_program())
|
||||
|
||||
make_bad_announcement_spend = CoinSpend(
|
||||
ownership_coin,
|
||||
ownership_puzzle,
|
||||
Program.to(
|
||||
[
|
||||
[
|
||||
[51, ACS_PH, 1],
|
||||
[-10, TARGET_OWNER, TARGET_TP],
|
||||
[62, b"\xad\x4c\xd5\x5c\xf7\xad\x64\x14" + bytes32([0] * 32)],
|
||||
]
|
||||
]
|
||||
),
|
||||
)
|
||||
make_bad_announcement_bundle = SpendBundle([make_bad_announcement_spend], G2Element())
|
||||
|
||||
result = await sim_client.push_tx(make_bad_announcement_bundle)
|
||||
assert result == (MempoolInclusionStatus.FAILED, Err.GENERATOR_RUNTIME_ERROR)
|
||||
with pytest.raises(ValueError, match="clvm raise"):
|
||||
make_bad_announcement_spend.puzzle_reveal.to_program().run(
|
||||
make_bad_announcement_spend.solution.to_program()
|
||||
)
|
||||
|
||||
expected_announcement = Announcement(
|
||||
ownership_puzzle.get_tree_hash(),
|
||||
b"\xad\x4c\xd5\x5c\xf7\xad\x64\x14" + Program.to([TARGET_OWNER, TARGET_TP]).get_tree_hash(),
|
||||
)
|
||||
update_everything_spend = CoinSpend(
|
||||
ownership_coin,
|
||||
ownership_puzzle,
|
||||
Program.to([[[51, ACS_PH, 1], [-10, TARGET_OWNER, TARGET_TP], [63, expected_announcement.name()]]]),
|
||||
)
|
||||
update_everything_bundle = SpendBundle([update_everything_spend], G2Element())
|
||||
result = await sim_client.push_tx(update_everything_bundle)
|
||||
assert result == (MempoolInclusionStatus.SUCCESS, None)
|
||||
await sim.farm_block()
|
||||
assert (await sim_client.get_coin_records_by_parent_ids([ownership_coin.name()], include_spent_coins=False))[
|
||||
0
|
||||
].coin.puzzle_hash == construct_ownership_layer(
|
||||
TARGET_OWNER,
|
||||
TARGET_TP,
|
||||
ACS,
|
||||
).get_tree_hash()
|
||||
finally:
|
||||
await sim.close() # type: ignore
|
||||
|
||||
|
||||
@pytest.mark.asyncio()
|
||||
async def test_default_transfer_program(setup_sim: Tuple[SpendSim, SimClient]) -> None:
|
||||
sim, sim_client = setup_sim
|
||||
|
||||
try:
|
||||
# Now make the ownership coin
|
||||
FAKE_SINGLETON_MOD = Program.to([2, 5, 11]) # (a 5 11) | (mod (_ INNER_PUZ inner_sol) (a INNER_PUZ inner_sol))
|
||||
FAKE_CAT_MOD = Program.to([2, 11, 23]) # (a 11 23) or (mod (_ _ INNER_PUZ inner_sol) (a INNER_PUZ inner_sol))
|
||||
FAKE_LAUNCHER_ID = bytes32([0] * 32)
|
||||
FAKE_TAIL = bytes32([2] * 32)
|
||||
FAKE_SINGLETON_STRUCT = Program.to((FAKE_SINGLETON_MOD.get_tree_hash(), (FAKE_LAUNCHER_ID, FAKE_LAUNCHER_ID)))
|
||||
FAKE_SINGLETON = FAKE_SINGLETON_MOD.curry(FAKE_SINGLETON_STRUCT, ACS)
|
||||
FAKE_CAT = FAKE_CAT_MOD.curry(FAKE_CAT_MOD.get_tree_hash(), FAKE_TAIL, ACS)
|
||||
|
||||
ROYALTY_ADDRESS = bytes32([1] * 32)
|
||||
TRADE_PRICE_PERCENTAGE = 5000 # 50%
|
||||
transfer_program: Program = NFT_TRANSFER_PROGRAM_DEFAULT.curry(
|
||||
FAKE_SINGLETON_STRUCT,
|
||||
ROYALTY_ADDRESS,
|
||||
TRADE_PRICE_PERCENTAGE,
|
||||
ACS.get_tree_hash(),
|
||||
FAKE_CAT_MOD.get_tree_hash(),
|
||||
)
|
||||
ownership_puzzle: Program = construct_ownership_layer(
|
||||
None,
|
||||
transfer_program,
|
||||
ACS,
|
||||
)
|
||||
ownership_ph: bytes32 = ownership_puzzle.get_tree_hash()
|
||||
await sim.farm_block(ownership_ph)
|
||||
ownership_coin = (await sim_client.get_coin_records_by_puzzle_hash(ownership_ph, include_spent_coins=False))[
|
||||
0
|
||||
].coin
|
||||
|
||||
BLOCK_HEIGHT = sim.block_height
|
||||
|
||||
# Try a spend, no royalties, no owner update
|
||||
generic_spend = CoinSpend(
|
||||
ownership_coin,
|
||||
ownership_puzzle,
|
||||
Program.to([[[51, ACS_PH, 1]]]),
|
||||
)
|
||||
generic_bundle = SpendBundle([generic_spend], G2Element())
|
||||
result = await sim_client.push_tx(generic_bundle)
|
||||
assert result == (MempoolInclusionStatus.SUCCESS, None)
|
||||
await sim.farm_block()
|
||||
assert len(await sim_client.get_coin_records_by_puzzle_hash(ownership_ph, include_spent_coins=False)) > 0
|
||||
await sim.rewind(BLOCK_HEIGHT)
|
||||
|
||||
# Now try an owner update plus royalties
|
||||
await sim.farm_block(FAKE_SINGLETON.get_tree_hash())
|
||||
await sim.farm_block(FAKE_CAT.get_tree_hash())
|
||||
await sim.farm_block(ACS_PH)
|
||||
singleton_coin = (
|
||||
await sim_client.get_coin_records_by_puzzle_hash(FAKE_SINGLETON.get_tree_hash(), include_spent_coins=False)
|
||||
)[0].coin
|
||||
cat_coin = (
|
||||
await sim_client.get_coin_records_by_puzzle_hash(FAKE_CAT.get_tree_hash(), include_spent_coins=False)
|
||||
)[0].coin
|
||||
xch_coin = (await sim_client.get_coin_records_by_puzzle_hash(ACS_PH, include_spent_coins=False))[0].coin
|
||||
|
||||
ownership_spend = CoinSpend(
|
||||
ownership_coin,
|
||||
ownership_puzzle,
|
||||
Program.to([[[51, ACS_PH, 1], [-10, FAKE_LAUNCHER_ID, [[100], [100, FAKE_TAIL]], ACS_PH]]]),
|
||||
)
|
||||
|
||||
did_announcement_spend = CoinSpend(
|
||||
singleton_coin,
|
||||
FAKE_SINGLETON,
|
||||
Program.to([[[62, FAKE_LAUNCHER_ID]]]),
|
||||
)
|
||||
|
||||
expected_announcement_data = Program.to(
|
||||
(FAKE_LAUNCHER_ID, [[ROYALTY_ADDRESS, 50, [ROYALTY_ADDRESS]]])
|
||||
).get_tree_hash()
|
||||
xch_announcement_spend = CoinSpend(
|
||||
xch_coin,
|
||||
ACS,
|
||||
Program.to([[62, expected_announcement_data]]),
|
||||
)
|
||||
|
||||
cat_announcement_spend = CoinSpend(cat_coin, FAKE_CAT, Program.to([[[62, expected_announcement_data]]]))
|
||||
|
||||
# Make sure every combo except all of them work
|
||||
for i in range(1, 3):
|
||||
for announcement_combo in itertools.combinations(
|
||||
[did_announcement_spend, xch_announcement_spend, cat_announcement_spend], i
|
||||
):
|
||||
result = await sim_client.push_tx(SpendBundle([ownership_spend, *announcement_combo], G2Element()))
|
||||
assert result == (MempoolInclusionStatus.FAILED, Err.ASSERT_ANNOUNCE_CONSUMED_FAILED)
|
||||
|
||||
# Make sure all of them together pass
|
||||
full_bundle = SpendBundle(
|
||||
[ownership_spend, did_announcement_spend, xch_announcement_spend, cat_announcement_spend], G2Element()
|
||||
)
|
||||
result = await sim_client.push_tx(full_bundle)
|
||||
assert result == (MempoolInclusionStatus.SUCCESS, None)
|
||||
|
||||
# Finally, make sure we can just clear the DID label off
|
||||
new_ownership_puzzle: Program = construct_ownership_layer(
|
||||
FAKE_LAUNCHER_ID,
|
||||
transfer_program,
|
||||
ACS,
|
||||
)
|
||||
new_ownership_ph: bytes32 = new_ownership_puzzle.get_tree_hash()
|
||||
await sim.farm_block(new_ownership_ph)
|
||||
new_ownership_coin = (
|
||||
await sim_client.get_coin_records_by_puzzle_hash(new_ownership_ph, include_spent_coins=False)
|
||||
)[0].coin
|
||||
|
||||
empty_spend = CoinSpend(
|
||||
new_ownership_coin,
|
||||
new_ownership_puzzle,
|
||||
Program.to([[[51, ACS_PH, 1], [-10, [], [], []]]]),
|
||||
)
|
||||
empty_bundle = SpendBundle([empty_spend], G2Element())
|
||||
result = await sim_client.push_tx(empty_bundle)
|
||||
assert result == (MempoolInclusionStatus.SUCCESS, None)
|
||||
await sim.farm_block()
|
||||
finally:
|
||||
await sim.close() # type: ignore
|
||||
@@ -1,12 +1,12 @@
|
||||
from typing import Tuple
|
||||
|
||||
from clvm.casts import int_from_bytes
|
||||
from clvm_tools.binutils import disassemble
|
||||
|
||||
from chia.types.blockchain_format.program import Program
|
||||
from chia.types.blockchain_format.sized_bytes import bytes32
|
||||
from chia.wallet.nft_wallet import uncurry_nft
|
||||
from chia.wallet.nft_wallet.nft_puzzles import (
|
||||
construct_ownership_layer,
|
||||
create_full_puzzle,
|
||||
create_nft_layer_puzzle_with_curry_params,
|
||||
recurry_nft_puzzle,
|
||||
@@ -21,8 +21,7 @@ LAUNCHER_PUZZLE = load_clvm("singleton_launcher.clvm")
|
||||
DID_MOD = load_clvm("did_innerpuz.clvm")
|
||||
NFT_STATE_LAYER_MOD = load_clvm("nft_state_layer.clvm")
|
||||
NFT_OWNERSHIP_LAYER = load_clvm("nft_ownership_layer.clvm")
|
||||
NFT_TRANSFER_PROGRAM_DEFAULT = load_clvm("nft_ownership_transfer_program_one_way_claim_with_royalties_new.clvm")
|
||||
STANDARD_PUZZLE_MOD = load_clvm("p2_delegated_puzzle_or_hidden_puzzle.clvm")
|
||||
NFT_TRANSFER_PROGRAM_DEFAULT = load_clvm("nft_ownership_transfer_program_one_way_claim_with_royalties.clvm")
|
||||
LAUNCHER_PUZZLE_HASH = LAUNCHER_PUZZLE.get_tree_hash()
|
||||
NFT_STATE_LAYER_MOD_HASH = NFT_STATE_LAYER_MOD.get_tree_hash()
|
||||
SINGLETON_MOD_HASH = SINGLETON_MOD.get_tree_hash()
|
||||
@@ -32,30 +31,29 @@ LAUNCHER_ID = Program.to(b"launcher-id").get_tree_hash()
|
||||
NFT_METADATA_UPDATER_DEFAULT = load_clvm("nft_metadata_updater_default.clvm")
|
||||
|
||||
|
||||
def make_a_new_solution() -> Tuple[bytes, Program]:
|
||||
def make_a_new_solution() -> Tuple[Program, Program]:
|
||||
destination = int_to_public_key(2)
|
||||
p2_puzzle = puzzle_for_pk(destination)
|
||||
puzhash = p2_puzzle.get_tree_hash()
|
||||
new_did = Program.to("test").get_tree_hash()
|
||||
new_did_inner_hash = Program.to("fake").get_tree_hash()
|
||||
trade_prices_list = [[200]]
|
||||
my_amount = 1
|
||||
|
||||
condition_list = [
|
||||
[
|
||||
51,
|
||||
STANDARD_PUZZLE_MOD.curry(destination).get_tree_hash(),
|
||||
puzhash,
|
||||
1,
|
||||
[STANDARD_PUZZLE_MOD.curry(destination).get_tree_hash()],
|
||||
[puzhash],
|
||||
],
|
||||
[-10, new_did, trade_prices_list, destination, [new_did_inner_hash]],
|
||||
[-10, new_did, trade_prices_list, new_did_inner_hash],
|
||||
]
|
||||
solution = Program.to(
|
||||
[
|
||||
[solution_for_conditions(condition_list)],
|
||||
my_amount,
|
||||
]
|
||||
)
|
||||
print(disassemble(solution))
|
||||
return destination, solution
|
||||
return p2_puzzle, solution
|
||||
|
||||
|
||||
def make_a_new_ownership_layer_puzzle() -> Tuple[Program, Program]:
|
||||
@@ -65,20 +63,14 @@ def make_a_new_ownership_layer_puzzle() -> Tuple[Program, Program]:
|
||||
nft_id = Program.to("nft_id")
|
||||
SINGLETON_STRUCT = Program.to((SINGLETON_MOD_HASH, (nft_id, LAUNCHER_PUZZLE_HASH)))
|
||||
curried_tp = NFT_TRANSFER_PROGRAM_DEFAULT.curry(
|
||||
STANDARD_PUZZLE_MOD.get_tree_hash(),
|
||||
SINGLETON_STRUCT,
|
||||
innerpuz.get_tree_hash(),
|
||||
2000,
|
||||
OFFER_MOD.get_tree_hash(),
|
||||
CAT_MOD.get_tree_hash(),
|
||||
)
|
||||
curried_inner = STANDARD_PUZZLE_MOD.curry(pubkey)
|
||||
curried_ownership_layer = NFT_OWNERSHIP_LAYER.curry(
|
||||
NFT_OWNERSHIP_LAYER.get_tree_hash(),
|
||||
old_did,
|
||||
curried_tp,
|
||||
curried_inner,
|
||||
)
|
||||
curried_inner = innerpuz
|
||||
curried_ownership_layer = construct_ownership_layer(old_did, curried_tp, curried_inner)
|
||||
return innerpuz, curried_ownership_layer
|
||||
|
||||
|
||||
@@ -92,12 +84,10 @@ def make_a_new_nft_puzzle(curried_ownership_layer: Program, metadata: Program) -
|
||||
def get_updated_nft_puzzle(puzzle: Program, solution: Program) -> bytes32:
|
||||
result = puzzle.run(solution)
|
||||
for condition in result.as_iter():
|
||||
print("Condition: %s" % disassemble(condition))
|
||||
code = int_from_bytes(condition.first().atom)
|
||||
if code == 51:
|
||||
if int_from_bytes(condition.rest().rest().first().atom) == 1:
|
||||
# this is our new puzzle hash
|
||||
print("Found new coin: %s" % disassemble(condition))
|
||||
return bytes32(condition.rest().first().atom)
|
||||
raise ValueError("No create coin condition found")
|
||||
|
||||
@@ -107,10 +97,11 @@ def test_transfer_puzzle_builder() -> None:
|
||||
("u", ["https://www.chia.net/img/branding/chia-logo.svg"]),
|
||||
("h", 0xD4584AD463139FA8C0D9F68F4B59F185),
|
||||
]
|
||||
destination, solution = make_a_new_solution()
|
||||
sp2_puzzle, solution = make_a_new_solution()
|
||||
p2_puzzle, ownership_puzzle = make_a_new_ownership_layer_puzzle()
|
||||
clvm_nft_puzzle = make_a_new_nft_puzzle(ownership_puzzle, Program.to(metadata))
|
||||
print("NFT state layer: %r" % clvm_nft_puzzle.get_tree_hash())
|
||||
clvm_nft_puzzle = create_nft_layer_puzzle_with_curry_params(
|
||||
Program.to(metadata), NFT_METADATA_UPDATER_DEFAULT.get_tree_hash(), ownership_puzzle
|
||||
)
|
||||
puzzle = create_full_puzzle(
|
||||
Program.to(["singleton_id"]).get_tree_hash(),
|
||||
Program.to(metadata),
|
||||
@@ -119,10 +110,11 @@ def test_transfer_puzzle_builder() -> None:
|
||||
)
|
||||
clvm_puzzle_hash = get_updated_nft_puzzle(clvm_nft_puzzle, solution)
|
||||
unft = uncurry_nft.UncurriedNFT.uncurry(puzzle)
|
||||
assert unft.nft_state_layer == clvm_nft_puzzle
|
||||
assert unft.inner_puzzle == ownership_puzzle
|
||||
|
||||
ol_puzzle = recurry_nft_puzzle(unft, solution.first())
|
||||
py_puzzle = create_nft_layer_puzzle_with_curry_params(
|
||||
assert unft.p2_puzzle == p2_puzzle
|
||||
ol_puzzle = recurry_nft_puzzle(unft, solution.first(), sp2_puzzle)
|
||||
nft_puzzle = create_nft_layer_puzzle_with_curry_params(
|
||||
Program.to(metadata), NFT_METADATA_UPDATER_DEFAULT.get_tree_hash(), ol_puzzle
|
||||
)
|
||||
assert clvm_puzzle_hash == py_puzzle.get_tree_hash()
|
||||
assert clvm_puzzle_hash == nft_puzzle.get_tree_hash()
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import asyncio
|
||||
import logging
|
||||
from typing import Any
|
||||
|
||||
import pytest
|
||||
@@ -21,6 +22,8 @@ from chia.wallet.util.compute_memos import compute_memos
|
||||
from chia.wallet.util.wallet_types import WalletType
|
||||
from tests.time_out_assert import time_out_assert, time_out_assert_not_none
|
||||
|
||||
logging.getLogger("aiosqlite").setLevel(logging.INFO) # Too much logging on debug level
|
||||
|
||||
|
||||
async def tx_in_pool(mempool: MempoolManager, tx_id: bytes32) -> bool:
|
||||
tx = mempool.get_spendbundle(tx_id)
|
||||
@@ -677,7 +680,6 @@ async def test_nft_with_did_wallet_creation(two_wallet_nodes: Any, trusted: Any)
|
||||
assert did_nft["data_uris"][0] == "https://www.chia.net/img/branding/chia-logo.svg"
|
||||
assert did_nft["data_hash"] == "0xD4584AD463139FA8C0D9F68F4B59F185".lower()
|
||||
assert did_nft["owner_did"][2:] == hex_did_id
|
||||
assert did_nft["owner_pubkey"] is not None
|
||||
# Check unassigned NFT
|
||||
nft_wallets = await wallet_node_0.wallet_state_manager.get_all_wallet_info_entries(WalletType.NFT)
|
||||
assert len(nft_wallets) == 2
|
||||
@@ -692,7 +694,6 @@ async def test_nft_with_did_wallet_creation(two_wallet_nodes: Any, trusted: Any)
|
||||
assert non_did_nft["data_uris"][0] == "https://url1"
|
||||
assert non_did_nft["data_hash"] == "0xD4584AD463139FA8C0D9F68F4B59F181".lower()
|
||||
assert non_did_nft["owner_did"] is None
|
||||
assert non_did_nft["owner_pubkey"] is not None
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
@@ -815,3 +816,260 @@ async def test_nft_rpc_mint(two_wallet_nodes: Any, trusted: Any) -> None:
|
||||
assert did_nft.series_total == st
|
||||
assert did_nft.series_number == sn
|
||||
assert did_nft.royalty_percentage == royalty_percentage
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"trusted",
|
||||
[True, False],
|
||||
)
|
||||
@pytest.mark.asyncio
|
||||
async def test_nft_transfer_nft_with_did(two_wallet_nodes: Any, trusted: Any) -> None:
|
||||
num_blocks = 5
|
||||
full_nodes, wallets = two_wallet_nodes
|
||||
full_node_api: FullNodeSimulator = full_nodes[0]
|
||||
full_node_server = full_node_api.server
|
||||
wallet_node_0, server_0 = wallets[0]
|
||||
wallet_node_1, server_1 = wallets[1]
|
||||
wallet_0 = wallet_node_0.wallet_state_manager.main_wallet
|
||||
wallet_1 = wallet_node_1.wallet_state_manager.main_wallet
|
||||
api_0 = WalletRpcApi(wallet_node_0)
|
||||
ph = await wallet_0.get_new_puzzlehash()
|
||||
ph1 = await wallet_1.get_new_puzzlehash()
|
||||
|
||||
if trusted:
|
||||
wallet_node_0.config["trusted_peers"] = {
|
||||
full_node_api.full_node.server.node_id.hex(): full_node_api.full_node.server.node_id.hex()
|
||||
}
|
||||
wallet_node_1.config["trusted_peers"] = {
|
||||
full_node_api.full_node.server.node_id.hex(): full_node_api.full_node.server.node_id.hex()
|
||||
}
|
||||
else:
|
||||
wallet_node_0.config["trusted_peers"] = {}
|
||||
wallet_node_1.config["trusted_peers"] = {}
|
||||
|
||||
await server_0.start_client(PeerInfo("localhost", uint16(full_node_server._port)), None)
|
||||
await server_1.start_client(PeerInfo("localhost", uint16(full_node_server._port)), None)
|
||||
|
||||
for _ in range(1, num_blocks):
|
||||
await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(ph))
|
||||
|
||||
funds = sum(
|
||||
[calculate_pool_reward(uint32(i)) + calculate_base_farmer_reward(uint32(i)) for i in range(1, num_blocks - 1)]
|
||||
)
|
||||
|
||||
await time_out_assert(10, wallet_0.get_unconfirmed_balance, funds)
|
||||
await time_out_assert(10, wallet_0.get_confirmed_balance, funds)
|
||||
did_wallet: DIDWallet = await DIDWallet.create_new_did_wallet(
|
||||
wallet_node_0.wallet_state_manager, wallet_0, uint64(1)
|
||||
)
|
||||
spend_bundle_list = await wallet_node_0.wallet_state_manager.tx_store.get_unconfirmed_for_wallet(wallet_0.id())
|
||||
|
||||
spend_bundle = spend_bundle_list[0].spend_bundle
|
||||
await time_out_assert_not_none(5, full_node_api.full_node.mempool_manager.get_spendbundle, spend_bundle.name())
|
||||
|
||||
for _ in range(1, num_blocks):
|
||||
await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(ph))
|
||||
await time_out_assert(15, wallet_0.get_pending_change_balance, 0)
|
||||
hex_did_id = did_wallet.get_my_DID()
|
||||
|
||||
res = await api_0.create_new_wallet(dict(wallet_type="nft_wallet", name="NFT WALLET 1", did_id=hex_did_id))
|
||||
assert isinstance(res, dict)
|
||||
assert res.get("success")
|
||||
nft_wallet_0_id = res["wallet_id"]
|
||||
|
||||
await time_out_assert(5, did_wallet.get_confirmed_balance, 1)
|
||||
|
||||
# Create a NFT with DID
|
||||
resp = await api_0.nft_mint_nft(
|
||||
{
|
||||
"wallet_id": nft_wallet_0_id,
|
||||
"hash": "0xD4584AD463139FA8C0D9F68F4B59F185",
|
||||
"uris": ["https://www.chia.net/img/branding/chia-logo.svg"],
|
||||
}
|
||||
)
|
||||
assert resp.get("success")
|
||||
sb = resp["spend_bundle"]
|
||||
|
||||
# ensure hints are generated
|
||||
assert compute_memos(sb)
|
||||
await time_out_assert_not_none(5, full_node_api.full_node.mempool_manager.get_spendbundle, sb.name())
|
||||
|
||||
for i in range(1, num_blocks):
|
||||
await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(ph))
|
||||
|
||||
# Check DID NFT
|
||||
time_left = 5.0
|
||||
coins_response = {}
|
||||
while time_left > 0:
|
||||
coins_response = await api_0.nft_get_nfts(dict(wallet_id=nft_wallet_0_id))
|
||||
if coins_response.get("nft_list"):
|
||||
break
|
||||
await asyncio.sleep(0.5)
|
||||
time_left -= 0.5
|
||||
assert coins_response["nft_list"], isinstance(coins_response, dict)
|
||||
assert coins_response.get("success")
|
||||
coins = coins_response["nft_list"]
|
||||
assert len(coins) == 1
|
||||
|
||||
try:
|
||||
wallet_1.wallet_state_manager.wallets[2]
|
||||
raise AssertionError("NFT wallet shouldn't exist yet")
|
||||
except KeyError:
|
||||
# there shouldn't be a nft wallet yet
|
||||
pass
|
||||
resp = await api_0.nft_transfer_nft(
|
||||
dict(
|
||||
wallet_id=nft_wallet_0_id,
|
||||
target_address=encode_puzzle_hash(ph1, "xch"),
|
||||
nft_coin_id=coins[0].nft_coin_id.hex(),
|
||||
)
|
||||
)
|
||||
assert resp.get("success")
|
||||
sb = resp["spend_bundle"]
|
||||
assert compute_memos(sb)
|
||||
await time_out_assert_not_none(5, full_node_api.full_node.mempool_manager.get_spendbundle, sb.name())
|
||||
|
||||
for i in range(1, num_blocks):
|
||||
await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(ph1))
|
||||
time_left = 5.0
|
||||
while time_left > 0:
|
||||
coins_response = await api_0.nft_get_nfts(dict(wallet_id=nft_wallet_0_id))
|
||||
if len(coins_response["nft_list"]) == 0:
|
||||
break
|
||||
await asyncio.sleep(0.5)
|
||||
time_left -= 0.5
|
||||
else:
|
||||
raise AssertionError("NFT not transferred")
|
||||
|
||||
nft_wallet_1 = wallet_1.wallet_state_manager.wallets[2]
|
||||
await time_out_assert(15, len, 1, nft_wallet_1.nft_wallet_info.my_nft_coins)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"trusted",
|
||||
[True, False],
|
||||
)
|
||||
@pytest.mark.asyncio
|
||||
async def test_update_metadata_for_nft_did(two_wallet_nodes: Any, trusted: Any) -> None:
|
||||
num_blocks = 5
|
||||
full_nodes, wallets = two_wallet_nodes
|
||||
full_node_api: FullNodeSimulator = full_nodes[0]
|
||||
full_node_server = full_node_api.server
|
||||
wallet_node_0, server_0 = wallets[0]
|
||||
wallet_node_1, server_1 = wallets[1]
|
||||
wallet_0 = wallet_node_0.wallet_state_manager.main_wallet
|
||||
api_0 = WalletRpcApi(wallet_node_0)
|
||||
ph = await wallet_0.get_new_puzzlehash()
|
||||
|
||||
if trusted:
|
||||
wallet_node_0.config["trusted_peers"] = {
|
||||
full_node_api.full_node.server.node_id.hex(): full_node_api.full_node.server.node_id.hex()
|
||||
}
|
||||
wallet_node_1.config["trusted_peers"] = {
|
||||
full_node_api.full_node.server.node_id.hex(): full_node_api.full_node.server.node_id.hex()
|
||||
}
|
||||
else:
|
||||
wallet_node_0.config["trusted_peers"] = {}
|
||||
wallet_node_1.config["trusted_peers"] = {}
|
||||
|
||||
await server_0.start_client(PeerInfo("localhost", uint16(full_node_server._port)), None)
|
||||
await server_1.start_client(PeerInfo("localhost", uint16(full_node_server._port)), None)
|
||||
|
||||
for _ in range(1, num_blocks):
|
||||
await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(ph))
|
||||
|
||||
funds = sum(
|
||||
[calculate_pool_reward(uint32(i)) + calculate_base_farmer_reward(uint32(i)) for i in range(1, num_blocks - 1)]
|
||||
)
|
||||
|
||||
await time_out_assert(10, wallet_0.get_unconfirmed_balance, funds)
|
||||
await time_out_assert(10, wallet_0.get_confirmed_balance, funds)
|
||||
did_wallet: DIDWallet = await DIDWallet.create_new_did_wallet(
|
||||
wallet_node_0.wallet_state_manager, wallet_0, uint64(1)
|
||||
)
|
||||
spend_bundle_list = await wallet_node_0.wallet_state_manager.tx_store.get_unconfirmed_for_wallet(wallet_0.id())
|
||||
|
||||
spend_bundle = spend_bundle_list[0].spend_bundle
|
||||
await time_out_assert_not_none(5, full_node_api.full_node.mempool_manager.get_spendbundle, spend_bundle.name())
|
||||
|
||||
for _ in range(1, num_blocks):
|
||||
await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(ph))
|
||||
await time_out_assert(15, wallet_0.get_pending_change_balance, 0)
|
||||
hex_did_id = did_wallet.get_my_DID()
|
||||
|
||||
res = await api_0.create_new_wallet(dict(wallet_type="nft_wallet", name="NFT WALLET 1", did_id=hex_did_id))
|
||||
assert isinstance(res, dict)
|
||||
assert res.get("success")
|
||||
nft_wallet_0_id = res["wallet_id"]
|
||||
|
||||
await time_out_assert(5, did_wallet.get_confirmed_balance, 1)
|
||||
|
||||
# Create a NFT with DID
|
||||
resp = await api_0.nft_mint_nft(
|
||||
{
|
||||
"wallet_id": nft_wallet_0_id,
|
||||
"hash": "0xD4584AD463139FA8C0D9F68F4B59F185",
|
||||
"uris": ["https://www.chia.net/img/branding/chia-logo.svg"],
|
||||
"mu": ["https://www.chia.net/img/branding/chia-logo.svg"],
|
||||
}
|
||||
)
|
||||
assert resp.get("success")
|
||||
sb = resp["spend_bundle"]
|
||||
|
||||
# ensure hints are generated
|
||||
assert compute_memos(sb)
|
||||
await time_out_assert_not_none(5, full_node_api.full_node.mempool_manager.get_spendbundle, sb.name())
|
||||
|
||||
for i in range(1, num_blocks):
|
||||
await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(ph))
|
||||
|
||||
# Check DID NFT
|
||||
time_left = 5.0
|
||||
coins_response = {}
|
||||
while time_left > 0:
|
||||
coins_response = await api_0.nft_get_nfts(dict(wallet_id=nft_wallet_0_id))
|
||||
if coins_response.get("nft_list"):
|
||||
break
|
||||
await asyncio.sleep(0.5)
|
||||
time_left -= 0.5
|
||||
assert coins_response["nft_list"], isinstance(coins_response, dict)
|
||||
assert coins_response.get("success")
|
||||
coins = coins_response["nft_list"]
|
||||
assert len(coins) == 1
|
||||
|
||||
nft_coin_id = coins[0].nft_coin_id
|
||||
# add another URI
|
||||
tr1 = await api_0.nft_add_uri(
|
||||
{"wallet_id": nft_wallet_0_id, "nft_coin_id": nft_coin_id.hex(), "uri": "http://metadata", "key": "mu"}
|
||||
)
|
||||
|
||||
assert isinstance(tr1, dict)
|
||||
assert tr1.get("success")
|
||||
coins_response = await api_0.nft_get_nfts(dict(wallet_id=nft_wallet_0_id))
|
||||
assert coins_response["nft_list"][0].pending_transaction
|
||||
sb = tr1["spend_bundle"]
|
||||
await time_out_assert_not_none(15, full_node_api.full_node.mempool_manager.get_spendbundle, sb.name())
|
||||
for i in range(1, num_blocks):
|
||||
await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(ph))
|
||||
# check that new URI was added
|
||||
time_left = 5.0
|
||||
while time_left > 0:
|
||||
coins_response = await api_0.nft_get_nfts(dict(wallet_id=nft_wallet_0_id))
|
||||
try:
|
||||
assert isinstance(coins_response, dict)
|
||||
assert coins_response.get("success")
|
||||
coins = coins_response["nft_list"]
|
||||
assert len(coins) == 1
|
||||
coin = coins[0].to_json_dict()
|
||||
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
|
||||
except AssertionError:
|
||||
if time_left < 1:
|
||||
raise
|
||||
await asyncio.sleep(0.5)
|
||||
time_left -= 0.5
|
||||
|
||||
Reference in New Issue
Block a user