Merge pull request #11887 from Chia-Network/new_nft1_chialisp

New NFT chialisp
This commit is contained in:
William Allen
2022-06-13 23:17:00 -05:00
committed by GitHub
23 changed files with 891 additions and 366 deletions
@@ -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: |
-3
View File
@@ -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%"""
+40 -51
View File
@@ -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
+35 -40
View File
@@ -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,
+8 -11
View File
@@ -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
+73 -28
View File
@@ -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
@@ -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 @@
ff02ffff01ff04ff82027fffff04ff80ffff04ffff02ffff03ff82027fffff01ff04ffff04ff10ffff04ffff0bffff02ff36ffff04ff02ffff04ff09ffff04ff82137fffff04ffff02ff3effff04ff02ffff04ffff04ff09ffff04ff82027fff1d8080ff80808080ff808080808080ff1580ff808080ffff02ff26ffff04ff02ffff04ff0bffff04ff17ffff04ff2fffff04ff5fffff04ff82057fffff04ff15ff80808080808080808080ffff01ff02ff26ffff04ff02ffff04ff0bffff04ff17ffff04ff2fffff04ff5fffff04ff82057fffff04ff15ff80808080808080808080ff0180ff80808080ffff04ffff01ffffff3fff0233ff04ff0101ffff822710ff02ff02ffff03ff05ffff01ff02ff3affff04ff02ffff04ff0dffff04ffff0bff2affff0bff2cff1480ffff0bff2affff0bff2affff0bff2cff3c80ff0980ffff0bff2aff0bffff0bff2cff8080808080ff8080808080ffff010b80ff0180ffffff02ffff03ff5fffff01ff04ffff02ffff03ff82019fffff01ff04ff10ffff04ffff0bffff02ff36ffff04ff02ffff04ff2fffff04ff17ffff04ffff0bffff0101ff82029f80ffff04ffff0bffff0101ff2f80ff80808080808080ffff02ff3effff04ff02ffff04ffff04ff81bfffff04ffff04ff05ffff04ffff05ffff14ffff12ff82011fff0b80ff128080ff808080ff808080ff8080808080ff808080ffff01ff04ff38ffff04ff05ffff04ffff02ff2effff04ff02ffff04ffff05ffff14ffff12ff82011fff0b80ff128080ff80808080ff8080808080ff0180ffff02ff26ffff04ff02ffff04ff05ffff04ff0bffff04ff17ffff04ff2fffff04ff81dfffff04ff81bfff80808080808080808080ff8080ff0180ff0bff2affff0bff2cff2880ffff0bff2affff0bff2affff0bff2cff3c80ff0580ffff0bff2affff02ff3affff04ff02ffff04ff07ffff04ffff0bff2cff2c80ff8080808080ffff0bff2cff8080808080ffff02ffff03ffff18ff05ffff010180ffff01ff11ff05ffff010180ffff010580ff0180ff02ffff03ffff07ff0580ffff01ff0bffff0102ffff02ff3effff04ff02ffff04ff09ff80808080ffff02ff3effff04ff02ffff04ff0dff8080808080ffff01ff0bffff0101ff058080ff0180ff018080
ff02ffff01ff02ffff03ff8202ffffff01ff04ff8204ffffff04ff80ffff04ffff02ffff03ffff22ff8204ffffff20ffff09ff8204ffff81bf808080ffff01ff04ffff04ff10ffff04ffff0bffff02ff2effff04ff02ffff04ff09ffff04ff8216ffffff04ffff02ff3effff04ff02ffff04ffff04ff09ffff04ff8204ffff1d8080ff80808080ff808080808080ff1580ff808080ffff02ff16ffff04ff02ffff04ff0bffff04ff17ffff04ff2fffff04ff5fffff04ff820affffff04ff15ff80808080808080808080ffff01ff02ff16ffff04ff02ffff04ff0bffff04ff17ffff04ff2fffff04ff5fffff04ff820affffff04ff15ff80808080808080808080ff0180ff80808080ffff01ff04ff81bfffff01ff80ff80808080ff0180ffff04ffff01ffffff3f02ff04ff0101ffff822710ff02ff02ffff03ff05ffff01ff02ff3affff04ff02ffff04ff0dffff04ffff0bff2affff0bff2cff1480ffff0bff2affff0bff2affff0bff2cff3c80ff0980ffff0bff2aff0bffff0bff2cff8080808080ff8080808080ffff010b80ff0180ffff02ffff03ff5fffff01ff04ffff04ff10ffff04ffff0bffff02ffff03ff82019fffff01ff02ff2effff04ff02ffff04ff2fffff04ff17ffff04ffff0bff2cff82029f80ffff04ffff0bff2cff2f80ff80808080808080ffff011780ff0180ffff02ff3effff04ff02ffff04ffff04ff81bfffff04ffff04ff05ffff04ffff05ffff14ffff12ff82011fff0b80ff128080ffff04ffff04ff05ff8080ff80808080ff808080ff8080808080ff808080ffff02ff16ffff04ff02ffff04ff05ffff04ff0bffff04ff17ffff04ff2fffff04ff81dfffff04ff81bfff80808080808080808080ff8080ff0180ffff0bff2affff0bff2cff1880ffff0bff2affff0bff2affff0bff2cff3c80ff0580ffff0bff2affff02ff3affff04ff02ffff04ff07ffff04ffff0bff2cff2c80ff8080808080ffff0bff2cff8080808080ff02ffff03ffff07ff0580ffff01ff0bffff0102ffff02ff3effff04ff02ffff04ff09ff80808080ffff02ff3effff04ff02ffff04ff0dff8080808080ffff01ff0bff2cff058080ff0180ff018080
@@ -1 +1 @@
3b0c0f181e51ee219d82eb99a89b31a864802904a18da9352bcf01c3fe52ba8c
a1d8a6b55005270d3a27214d6ddb2762ec7d2814e67132c6b542fdd6874a6887
@@ -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 +0,0 @@
ff02ffff01ff04ff8209ffffff04ff80ffff04ffff02ff26ffff04ff02ffff04ff82017fffff04ff05ffff04ff0bffff04ff17ffff04ff2fffff04ff5fffff04ff81bfffff04ff8202ffffff04ff80ffff04ffff04ff8209ffffff04ff8215ffffff04ff822dffffff04ff825dffff8080808080ff80808080808080808080808080ff80808080ffff04ffff01ffffffff323fff0233ffff0401ff01822710ffffff02ff02ffff03ff05ffff01ff02ff32ffff04ff02ffff04ff0dffff04ffff0bff22ffff0bff34ff2480ffff0bff22ffff0bff22ffff0bff34ff2c80ff0980ffff0bff22ff0bffff0bff34ff8080808080ff8080808080ffff010b80ff0180ffff02ffff03ffff22ff82027fffff20ffff09ff82027fff81bf808080ffff01ff04ffff04ff30ffff04ffff0bffff02ff2effff04ff02ffff04ff09ffff04ff82277fffff04ffff02ff7effff04ff02ffff04ffff04ff09ffff04ff82027fff1d8080ff80808080ff808080808080ff1580ff808080ffff02ff36ffff04ff02ffff04ff0bffff04ff17ffff04ff2fffff04ff5fffff04ff82057fffff04ff15ff80808080808080808080ffff01ff02ff36ffff04ff02ffff04ff0bffff04ff17ffff04ff2fffff04ff5fffff04ff82057fffff04ff15ff80808080808080808080ff0180ff02ffff03ffff09ff820affffff02ff2effff04ff02ffff04ff0bffff04ffff0bff34ff822dff80ff808080808080ffff01ff04ff8202ffffff04ffff04ff20ffff04ff822dffffff04ffff02ff7effff04ff02ffff04ff825dffff80808080ff80808080ffff02ff2affff04ff02ffff04ff17ffff04ff2fffff04ff5fffff04ff81bfffff04ff82017fffff04ff05ffff04ff8205ffff808080808080808080808080ffff01ff088080ff0180ffffff02ffff03ff8202ffffff01ff02ffff03ffff09ff8208ffff3880ffff01ff02ffff03ffff18ff822cffff3480ffff01ff02ff3affff04ff02ffff04ff05ffff04ff0bffff04ff17ffff04ff2fffff04ff5fffff04ff81bfffff04ff82017fffff04ff8204ffffff04ff820bffff808080808080808080808080ffff01ff04ff8204ffffff02ff26ffff04ff02ffff04ff0bffff04ff17ffff04ff2fffff04ff5fffff04ff81bfffff04ff82017fffff04ff05ffff04ff8206ffffff04ff8205ffffff04ff820bffff808080808080808080808080808080ff0180ffff01ff02ff26ffff04ff02ffff04ff05ffff04ff0bffff04ff17ffff04ff2fffff04ff5fffff04ff81bfffff04ff82017fffff04ff8206ffffff04ff8205ffffff04ff820bffff8080808080808080808080808080ff0180ffff01ff088080ff0180ff02ffff03ff5fffff01ff04ffff02ffff03ff82019fffff01ff04ff30ffff04ffff0bffff02ff2effff04ff02ffff04ff2fffff04ff17ffff04ffff0bff34ff82029f80ffff04ffff0bff34ff2f80ff80808080808080ffff02ff7effff04ff02ffff04ffff04ff81bfffff04ffff04ff05ffff04ffff05ffff14ffff12ff82011fff0b80ff3c8080ff808080ff808080ff8080808080ff808080ffff01ff04ff38ffff04ff05ffff04ffff02ff5effff04ff02ffff04ffff05ffff14ffff12ff82011fff0b80ff3c8080ff80808080ff8080808080ff0180ffff02ff36ffff04ff02ffff04ff05ffff04ff0bffff04ff17ffff04ff2fffff04ff81dfffff04ff81bfff80808080808080808080ff8080ff0180ffff0bff22ffff0bff34ff2880ffff0bff22ffff0bff22ffff0bff34ff2c80ff0580ffff0bff22ffff02ff32ffff04ff02ffff04ff07ffff04ffff0bff34ff3480ff8080808080ffff0bff34ff8080808080ffff02ffff03ffff18ff05ff3480ffff01ff11ff05ff3480ffff010580ff0180ff02ffff03ffff07ff0580ffff01ff0bffff0102ffff02ff7effff04ff02ffff04ff09ff80808080ffff02ff7effff04ff02ffff04ff0dff8080808080ffff01ff0bff34ff058080ff0180ff018080
@@ -1 +0,0 @@
1ba78fd36e36ff5e5ade71864527d2ce3980b4b43ddf7cdc3f714945f4b734a7
+49 -50
View File
@@ -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
View File
@@ -1 +1 @@
ff02ffff01ff04ffff04ff10ffff04ff81bfff808080ffff02ff3effff04ff02ffff04ff05ffff04ffff02ff3affff04ff02ffff04ff17ffff04ff0bffff04ffff02ff2fff5f80ff808080808080ffff04ff81bfffff01ff8080808080808080ffff04ffff01ffffff49ff0233ffff0401ff0102ffffffff02ffff03ff05ffff01ff02ff22ffff04ff02ffff04ff0dffff04ffff0bff3cffff0bff34ff2480ffff0bff3cffff0bff3cffff0bff34ff2c80ff0980ffff0bff3cff0bffff0bff34ff8080808080ff8080808080ffff010b80ff0180ff02ffff03ff17ffff01ff02ffff03ffff09ff47ffff0181e880ffff01ff02ffff03ffff09ffff02ff2effff04ff02ffff04ff81a7ff80808080ff0580ffff01ff02ff81a7ffff04ff0bffff04ff05ffff04ff820167ff8080808080ffff01ff088080ff0180ffff01ff02ff32ffff04ff02ffff04ff05ffff04ff0bffff04ff37ff80808080808080ff0180ffff01ff04ffff04ff0bffff04ff05ff808080ffff01ff80808080ff0180ffff02ffff03ff05ffff01ff04ff09ffff02ff2affff04ff02ffff04ff0dffff04ff0bff808080808080ffff010b80ff0180ff02ff26ffff04ff02ffff04ffff02ff32ffff04ff02ffff04ff05ffff04ff0bffff04ff17ff808080808080ffff04ff17ff8080808080ffffff04ff09ffff04ffff02ff2affff04ff02ffff04ff15ffff04ff0bff8080808080ff808080ff0bff3cffff0bff34ff2880ffff0bff3cffff0bff3cffff0bff34ff2c80ff0580ffff0bff3cffff02ff22ffff04ff02ffff04ff07ffff04ffff0bff34ff3480ff8080808080ffff0bff34ff8080808080ffff02ffff03ffff07ff0580ffff01ff0bffff0102ffff02ff2effff04ff02ffff04ff09ff80808080ffff02ff2effff04ff02ffff04ff0dff8080808080ffff01ff0bffff0101ff058080ff0180ff02ffff03ff2bffff01ff02ffff03ffff09ff818bff3880ffff01ff02ffff03ffff18ff8202cbff3480ffff01ff04ffff04ff38ffff04ffff02ff36ffff04ff02ffff04ff05ffff04ff82014bffff04ffff0bff34ff5380ffff04ffff02ff2effff04ff02ffff04ff23ff80808080ffff04ffff0bff34ff0580ff8080808080808080ffff04ff17ff8203cb808080ffff02ff3effff04ff02ffff04ff05ffff04ffff04ffff04ff23ffff04ff53ff808080ffff04ff6bff808080ffff04ff17ff80808080808080ffff01ff04ff4bffff02ff3effff04ff02ffff04ff05ffff04ffff04ffff04ff23ffff04ff53ff808080ffff04ff6bff808080ffff04ff17ff8080808080808080ff0180ffff01ff02ffff03ffff15ff818bff8080ffff01ff04ff4bffff02ff3effff04ff02ffff04ff05ffff04ffff04ffff04ff23ffff04ff53ff808080ffff04ff6bff808080ffff04ff17ff80808080808080ffff01ff02ff3effff04ff02ffff04ff05ffff04ffff04ffff04ff23ffff04ff53ff808080ffff04ff6bff808080ffff04ff17ff80808080808080ff018080ff0180ff8080ff0180ff018080
ff02ffff01ff02ff3effff04ff02ffff04ff05ffff04ffff02ff2fff5f80ffff04ff80ffff04ffff04ffff04ff0bffff04ff17ff808080ffff01ff808080ffff01ff8080808080808080ffff04ffff01ffffff0233ff04ff0101ffff02ff02ffff03ff05ffff01ff02ff1affff04ff02ffff04ff0dffff04ffff0bff12ffff0bff2cff1480ffff0bff12ffff0bff12ffff0bff2cff3c80ff0980ffff0bff12ff0bffff0bff2cff8080808080ff8080808080ffff010b80ff0180ffff0bff12ffff0bff2cff1080ffff0bff12ffff0bff12ffff0bff2cff3c80ff0580ffff0bff12ffff02ff1affff04ff02ffff04ff07ffff04ffff0bff2cff2c80ff8080808080ffff0bff2cff8080808080ffff02ffff03ffff07ff0580ffff01ff0bffff0102ffff02ff2effff04ff02ffff04ff09ff80808080ffff02ff2effff04ff02ffff04ff0dff8080808080ffff01ff0bffff0101ff058080ff0180ff02ffff03ff0bffff01ff02ffff03ffff09ff23ff1880ffff01ff02ffff03ffff18ff81b3ff2c80ffff01ff02ffff03ffff20ff1780ffff01ff02ff3effff04ff02ffff04ff05ffff04ff1bffff04ff33ffff04ff2fffff04ff5fff8080808080808080ffff01ff088080ff0180ffff01ff04ff13ffff02ff3effff04ff02ffff04ff05ffff04ff1bffff04ff17ffff04ff2fffff04ff5fff80808080808080808080ff0180ffff01ff02ffff03ffff09ff23ffff0181e880ffff01ff02ff3effff04ff02ffff04ff05ffff04ff1bffff04ff17ffff04ffff02ffff03ffff22ffff09ffff02ff2effff04ff02ffff04ff53ff80808080ff82014f80ffff20ff5f8080ffff01ff02ff53ffff04ff818fffff04ff82014fffff04ff81b3ff8080808080ffff01ff088080ff0180ffff01ff0180808080808080ffff01ff04ff13ffff02ff3effff04ff02ffff04ff05ffff04ff1bffff04ff17ffff04ff2fffff04ff5fff80808080808080808080ff018080ff0180ffff01ff04ffff04ff18ffff04ffff02ff16ffff04ff02ffff04ff05ffff04ff27ffff04ffff0bff2cff82014f80ffff04ffff02ff2effff04ff02ffff04ff818fff80808080ffff04ffff0bff2cff0580ff8080808080808080ff378080ff81af8080ff0180ff018080
@@ -1 +1 @@
3cb215c818b13f6d18d0f33a0b8d2ef156483982cfe4bc0a5937b038ef28b03b
31f5e810e2ad078817e8b047306c877ad82c77511aa235179c4a98af413f162a
-1
View File
@@ -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",
]
)
+8 -1
View File
@@ -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
+20 -28
View File
@@ -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()
+260 -2
View File
@@ -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