Merge pull request #11715 from trepca/nft1_ownership_layer

NFT1 minting and receiving
This commit is contained in:
William Allen
2022-06-02 05:12:31 -05:00
committed by GitHub
40 changed files with 1174 additions and 575 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_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_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_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_offers.py tests/wallet/nft_wallet/test_nft_puzzles.py tests/wallet/nft_wallet/test_nft_wallet.py
- name: Process coverage data
run: |
+4 -4
View File
@@ -4,7 +4,7 @@ from typing import List, Optional
from chia.types.blockchain_format.coin import Coin
from chia.types.blockchain_format.program import Program
from chia.types.blockchain_format.sized_bytes import bytes32
from chia.util.ints import uint32, uint64
from chia.util.ints import uint16, uint64
from chia.util.streamable import Streamable, streamable
from chia.wallet.lineage_proof import LineageProof
from chia.wallet.puzzles.load_clvm import load_clvm
@@ -23,10 +23,10 @@ class NFTInfo(Streamable):
nft_coin_id: bytes32
"""Current NFT coin ID"""
did_owner: str
did_owner: Optional[bytes32]
"""Owner DID"""
royalty: uint64
royalty: Optional[uint16]
"""Percentage of the transaction fee paid to the author, e.g. 1000 = 1%"""
data_uris: List[str]
@@ -79,4 +79,4 @@ class NFTCoinInfo(Streamable):
@dataclass(frozen=True)
class NFTWalletInfo(Streamable):
my_nft_coins: List[NFTCoinInfo]
did_wallet_id: Optional[uint32] = None
did_id: Optional[bytes32] = None
+134 -7
View File
@@ -1,14 +1,18 @@
import logging
from typing import Any, Dict, List
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
from chia.types.blockchain_format.program import Program
from chia.types.blockchain_format.program import Program, SerializedProgram
from chia.types.blockchain_format.sized_bytes import bytes32
from chia.util.ints import uint64
from chia.util.ints import uint16, uint64
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 solution_for_conditions
log = logging.getLogger(__name__)
SINGLETON_TOP_LAYER_MOD = load_clvm("singleton_top_layer_v1_1.clvm")
@@ -17,9 +21,11 @@ NFT_STATE_LAYER_MOD = load_clvm("nft_state_layer.clvm")
LAUNCHER_PUZZLE_HASH = LAUNCHER_PUZZLE.get_tree_hash()
SINGLETON_MOD_HASH = SINGLETON_TOP_LAYER_MOD.get_tree_hash()
NFT_STATE_LAYER_MOD_HASH = NFT_STATE_LAYER_MOD.get_tree_hash()
NFT_TRANSFER_PROGRAM = load_clvm("nft_transfer_program.clvm")
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")
STANDARD_PUZZLE_MOD = load_clvm("p2_delegated_puzzle_or_hidden_puzzle.clvm")
def create_nft_layer_puzzle_with_curry_params(
@@ -38,6 +44,13 @@ def create_nft_layer_puzzle_with_curry_params(
metadata,
metadata_updater_hash,
)
log.debug(
"Currying with: %s %s %s %s",
NFT_STATE_LAYER_MOD_HASH,
inner_puzzle.get_tree_hash(),
metadata_updater_hash,
metadata.get_tree_hash(),
)
return NFT_STATE_LAYER_MOD.curry(NFT_STATE_LAYER_MOD_HASH, metadata, metadata_updater_hash, inner_puzzle)
@@ -91,10 +104,10 @@ def get_nft_info_from_puzzle(nft_coin_info: NFTCoinInfo) -> NFTInfo:
license_uris.append(str(uri, "utf-8"))
nft_info = NFTInfo(
uncurried_nft.singleton_launcher_id.as_python(),
uncurried_nft.singleton_launcher_id,
nft_coin_info.coin.name(),
uncurried_nft.owner_did.as_python(),
uint64(uncurried_nft.trade_price_percentage.as_int()),
uncurried_nft.owner_did,
uncurried_nft.trade_price_percentage,
data_uris,
uncurried_nft.data_hash.as_python(),
meta_uris,
@@ -162,3 +175,117 @@ def update_metadata(metadata: Program, update_condition: Program) -> Program:
uri: Program = update_condition.rest().rest().first()
prepend_value(uri.first().as_python(), uri.rest(), new_metadata)
return metadata_to_program(new_metadata)
def create_ownership_layer_puzzle(
nft_id: bytes32,
did_id: bytes32,
p2_puzzle: Program,
percentage: uint16,
royalty_puzzle_hash: Optional[bytes32] = None,
) -> Program:
log.debug(f"Creating ownership layer puzzle with {nft_id} {did_id} {percentage} {p2_puzzle}")
singleton_struct = Program.to((SINGLETON_MOD_HASH, (nft_id, LAUNCHER_PUZZLE_HASH)))
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,
OFFER_MOD.get_tree_hash(),
CAT_MOD.get_tree_hash(),
)
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
)
return nft_ownership_layer_puzzle
def create_ownership_layer_transfer_solution(
new_did: bytes32,
new_did_inner_hash: bytes32,
trade_prices_list: List[List[int]],
new_pubkey: G1Element,
conditions: List[Any] = [],
) -> Program:
log.debug(f"Creating a transfer solution with: {new_did} {new_did_inner_hash} {trade_prices_list} {new_pubkey}")
puzhash = STANDARD_PUZZLE_MOD.curry(new_pubkey).get_tree_hash()
condition_list = [
[
51,
puzhash,
1,
[puzhash],
],
[-10, new_did, trade_prices_list, new_pubkey, [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)
return 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()
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)
log.debug("Checking condition code: %r", condition_code)
if condition_code == -24:
# metadata update
metadata = update_metadata(metadata, condition)
metadata = Program.to(metadata)
elif condition_code == 51 and int_from_bytes(condition.rest().rest().first().atom) == 1:
# destination puzhash
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
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)
conditions = solution.at("frfr").as_iter()
for change_did_condition in conditions:
if change_did_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),
)
return inner_puzzle
+205 -152
View File
@@ -5,7 +5,7 @@ from secrets import token_bytes
from typing import Any, Dict, List, Optional, Set, Tuple, Type, TypeVar
from blspy import AugSchemeMPL, G1Element, G2Element
from clvm.casts import int_from_bytes, int_to_bytes
from clvm.casts import int_to_bytes
from chia.protocols.wallet_protocol import CoinState
from chia.server.outbound_message import NodeType
@@ -17,12 +17,18 @@ from chia.types.blockchain_format.sized_bytes import bytes32
from chia.types.coin_spend import CoinSpend
from chia.types.spend_bundle import SpendBundle
from chia.util.condition_tools import conditions_dict_for_solution, pkm_pairs_for_conditions_dict
from chia.util.ints import uint8, uint32, uint64, uint128
from chia.util.ints import uint8, uint16, uint32, uint64, uint128
from chia.wallet.derivation_record import DerivationRecord
from chia.wallet.lineage_proof import LineageProof
from chia.wallet.nft_wallet import nft_puzzles
from chia.wallet.nft_wallet.nft_info import NFTCoinInfo, NFTWalletInfo
from chia.wallet.nft_wallet.nft_puzzles import NFT_METADATA_UPDATER, NFT_STATE_LAYER_MOD_HASH
from chia.wallet.nft_wallet.nft_puzzles import (
NFT_METADATA_UPDATER,
NFT_STATE_LAYER_MOD_HASH,
create_ownership_layer_puzzle,
create_ownership_layer_transfer_solution,
get_metadata_and_phs,
)
from chia.wallet.nft_wallet.uncurry_nft import UncurriedNFT
from chia.wallet.outer_puzzles import AssetType, match_puzzle
from chia.wallet.payment import Payment
@@ -44,8 +50,9 @@ from chia.wallet.util.wallet_types import WalletType
from chia.wallet.wallet import Wallet
from chia.wallet.wallet_info import WalletInfo
_T_NFTWallet = TypeVar("_T_NFTWallet", bound="NFTWallet")
STANDARD_PUZZLE_MOD = load_clvm("p2_delegated_puzzle_or_hidden_puzzle.clvm")
_T_NFTWallet = TypeVar("_T_NFTWallet", bound="NFTWallet")
OFFER_MOD = load_clvm("settlement_payments.clvm")
@@ -57,13 +64,14 @@ class NFTWallet:
nft_wallet_info: NFTWalletInfo
standard_wallet: Wallet
wallet_id: int
did_id: Optional[bytes32]
@classmethod
async def create_new_nft_wallet(
cls: Type[_T_NFTWallet],
wallet_state_manager: Any,
wallet: Wallet,
did_wallet_id: uint32 = None,
did_id: Optional[bytes32] = None,
name: str = "",
in_transaction: bool = False,
) -> _T_NFTWallet:
@@ -74,7 +82,7 @@ class NFTWallet:
self.standard_wallet = wallet
self.log = logging.getLogger(name if name else __name__)
self.wallet_state_manager = wallet_state_manager
self.nft_wallet_info = NFTWalletInfo([], did_wallet_id)
self.nft_wallet_info = NFTWalletInfo([], did_id)
info_as_string = json.dumps(self.nft_wallet_info.to_json_dict())
wallet_info = await wallet_state_manager.user_store.create_wallet(
"NFT Wallet" if not name else name,
@@ -91,12 +99,12 @@ class NFTWallet:
await self.wallet_state_manager.add_new_wallet(self, self.wallet_info.id, in_transaction=in_transaction)
self.log.debug("Generated a new NFT wallet: %s", self.__dict__)
if not did_wallet_id:
if not did_id:
# default profile wallet
self.log.debug("Standard NFT wallet created")
self.did_id = None
else:
# TODO: handle DID wallet puzhash
raise NotImplementedError()
self.did_id = did_id
return self
@classmethod
@@ -186,88 +194,49 @@ class NFTWallet:
self.log.debug("Puzzle solution received to wallet: %s", self.wallet_info)
coin_name = coin_spend.coin.name()
puzzle: Program = Program.from_bytes(bytes(coin_spend.puzzle_reveal))
full_solution: Program = Program.from_bytes(bytes(coin_spend.solution))
delegated_puz_solution: Program = Program.from_bytes(bytes(coin_spend.solution)).rest().rest().first().first()
# At this point, the puzzle must be a NFT puzzle.
# This method will be called only when the walle state manager uncurried this coin as a NFT puzzle.
# This method will be called only when the wallet state manager uncurried this coin as a NFT puzzle.
uncurried_nft = UncurriedNFT.uncurry(puzzle)
self.log.info(
f"found the info for NFT coin {coin_name} {uncurried_nft.inner_puzzle} {uncurried_nft.singleton_struct}"
)
singleton_id = bytes32(uncurried_nft.singleton_launcher_id.atom)
metadata = uncurried_nft.metadata
new_inner_puzzle = None
update_condition = None
singleton_id = bytes32(uncurried_nft.singleton_launcher_id)
parent_inner_puzhash = uncurried_nft.nft_state_layer.get_tree_hash()
self.log.debug("Before spend metadata: %s %s \n%s", metadata, singleton_id, disassemble(delegated_puz_solution))
if delegated_puz_solution.rest().as_python() == b"":
conds = puzzle.run(full_solution)
metadata, p2_puzzle_hash = get_metadata_and_phs(
uncurried_nft, puzzle, Program.from_bytes(bytes(coin_spend.solution))
)
self.log.debug("Got back puzhash from solution: %s", p2_puzzle_hash)
derivation_record: Optional[
DerivationRecord
] = await self.wallet_state_manager.puzzle_store.get_derivation_record_for_puzzle_hash(p2_puzzle_hash)
if derivation_record:
p2_puzzle = puzzle_for_pk(derivation_record.pubkey)
else:
conds = delegated_puz_solution.rest().first().rest()
# for condition in delegated_puz_solution.rest().first().rest().as_itexr():
for condition in conds.as_iter():
self.log.debug("Checking solution condition: %s", disassemble(condition))
if condition.list_len() <= 2:
# irrelevant condition
continue
condition_code = int_from_bytes(condition.first().atom)
self.log.debug("Checking condition code: %r", condition_code)
if condition_code == -24:
# metadata update
update_condition = condition
elif condition_code == 51 and int_from_bytes(condition.rest().rest().first().atom) == 1:
puzhash = bytes32(condition.rest().first().atom)
memo = bytes32(condition.as_python()[-1][0])
if memo != puzhash:
puzhash_for_derivation_record = memo
else:
puzhash_for_derivation_record = puzhash
self.log.debug("Got back puzhash from solution: %s", puzhash)
derivation_record: Optional[
DerivationRecord
] = await self.wallet_state_manager.puzzle_store.get_derivation_record_for_puzzle_hash(
puzhash_for_derivation_record
)
if derivation_record is None:
# we potentially sent it somewhere
await self.remove_coin(coin_spend.coin, in_transaction=in_transaction)
return
new_inner_puzzle = puzzle_for_pk(derivation_record.pubkey)
else:
raise ValueError("Invalid condition")
if new_inner_puzzle is None:
raise ValueError("Invalid puzzle")
if update_condition is not None:
metadata = nft_puzzles.update_metadata(metadata, update_condition)
parent_coin = None
coin_record = await self.wallet_state_manager.coin_store.get_coin_record(coin_name)
if coin_record is None:
coin_states: Optional[List[CoinState]] = await self.wallet_state_manager.wallet_node.get_coin_state(
[coin_name]
)
if coin_states is not None:
parent_coin = coin_states[0].coin
if coin_record is not None:
parent_coin = coin_record.coin
if parent_coin is None:
raise ValueError("Error in finding parent")
# 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.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)
else:
inner_puzzle = p2_puzzle
child_puzzle: Program = nft_puzzles.create_full_puzzle(
singleton_id,
metadata,
Program.to(metadata),
bytes32(uncurried_nft.metadata_updater_hash.atom),
new_inner_puzzle,
inner_puzzle,
)
self.log.debug(
"Created NFT full puzzle with inner: %s",
nft_puzzles.create_full_puzzle_with_nft_puzzle(singleton_id, uncurried_nft.inner_puzzle),
)
child_coin: Optional[Coin] = None
for new_coin in coin_spend.additions():
self.log.debug(
"Comparing addition: %s with %s, amount: %s ",
@@ -279,8 +248,21 @@ class NFTWallet:
child_coin = new_coin
break
else:
raise ValueError("Invalid NFT spend on %r" % coin_name)
raise ValueError(f"Couldn't regenerate puzzle reveal for NFT: {coin_spend}")
self.log.info("Adding a new NFT to wallet: %s", child_coin)
# all is well, lets add NFT to our local db
parent_coin = None
coin_record = await self.wallet_state_manager.coin_store.get_coin_record(coin_name)
if coin_record is None:
coin_states: Optional[List[CoinState]] = await self.wallet_state_manager.wallet_node.get_coin_state(
[coin_name]
)
if coin_states is not None:
parent_coin = coin_states[0].coin
if coin_record is not None:
parent_coin = coin_record.coin
if parent_coin is None:
raise ValueError("Error finding parent")
await self.add_coin(
child_coin,
child_puzzle,
@@ -297,7 +279,7 @@ class NFTWallet:
my_nft_coins.append(NFTCoinInfo(coin, lineage_proof, puzzle))
new_nft_wallet_info = NFTWalletInfo(
my_nft_coins,
self.nft_wallet_info.did_wallet_id,
self.nft_wallet_info.did_id,
)
await self.save_info(new_nft_wallet_info, in_transaction=in_transaction)
await self.wallet_state_manager.add_interested_coin_ids([coin.name()], in_transaction=in_transaction)
@@ -311,31 +293,51 @@ class NFTWallet:
my_nft_coins.remove(coin_info)
new_nft_wallet_info = NFTWalletInfo(
my_nft_coins,
self.nft_wallet_info.did_wallet_id,
self.nft_wallet_info.did_id,
)
await self.save_info(new_nft_wallet_info, in_transaction=in_transaction)
self.wallet_state_manager.state_changed("nft_coin_removed", self.wallet_info.id)
return
def puzzle_for_pk(self, pk: G1Element) -> Program:
if not self.nft_wallet_info.did_wallet_id:
inner_puzzle = self.standard_wallet.puzzle_for_pk(bytes(pk))
else:
raise NotImplementedError
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(
self,
nft_id: bytes32,
) -> Tuple[bytes32, SpendBundle]:
"""Get DID spend with announcement created we need to transfer NFT with did with current inner hash of DID
We also store `did_id` and then iterate to find the did wallet as we'd otherwise have to subscribe to
any changes to DID wallet and storing wallet_id is not guaranteed to be consistent on wallet crash/reset.
"""
for _, wallet in self.wallet_state_manager.wallets.items():
self.log.debug("Checking wallet type %s", wallet.type())
if wallet.type() == WalletType.DISTRIBUTED_ID:
self.log.debug("Found a DID wallet, checking did: %r == %r", wallet.get_my_DID(), self.did_id)
if bytes32.fromhex(wallet.get_my_DID()) == self.did_id:
self.log.debug("Creating announcement from DID for nft_id: %s", nft_id)
did_bundle = await wallet.create_message_spend(puzzle_announcements=[nft_id])
self.log.debug("Sending DID announcement from puzzle: %s", did_bundle.removals())
did_inner_hash = wallet.did_info.current_inner.get_tree_hash()
break
else:
raise ValueError(f"Missing DID Wallet for did_id: {self.did_id}")
return did_inner_hash, did_bundle
async def generate_new_nft(
self,
metadata: Program,
royalty_puzzle_hash: bytes32 = None,
target_puzzle_hash: bytes32 = None,
target_puzzle_hash: Optional[bytes32] = None,
royalty_puzzle_hash: Optional[bytes32] = None,
percentage: uint16 = uint16(0),
fee: uint64 = uint64(0),
) -> Optional[SpendBundle]:
# TODO Set royalty address after NFT1 chialisp finished
"""
This must be called under the wallet state manager lock
"""
@@ -346,11 +348,23 @@ class NFTWallet:
self.log.debug("Attempt to generate a new NFT")
origin = coins.copy().pop()
genesis_launcher_puz = nft_puzzles.LAUNCHER_PUZZLE
# nft_id == singleton_id == launcher_id == launcher_coin.name()
launcher_coin = Coin(origin.name(), genesis_launcher_puz.get_tree_hash(), uint64(amount))
self.log.debug("Generating NFT with launcher coin %s and metadata: %s", launcher_coin, metadata)
inner_puzzle = await self.standard_wallet.get_new_puzzle()
# singleton eve
p2_inner_puzzle = await self.standard_wallet.get_new_puzzle()
if not target_puzzle_hash:
target_puzzle_hash = p2_inner_puzzle.get_tree_hash()
if self.did_id:
self.log.debug("Creating NFT using DID: %s", self.did_id)
inner_puzzle = create_ownership_layer_puzzle(
launcher_coin.name(), self.did_id, p2_inner_puzzle, percentage, royalty_puzzle_hash=royalty_puzzle_hash
)
self.log.debug("Got back ownership inner puzzle: %s", disassemble(inner_puzzle))
else:
inner_puzzle = p2_inner_puzzle
# singleton eve puzzle
eve_fullpuz = nft_puzzles.create_full_puzzle(
launcher_coin.name(), metadata, NFT_METADATA_UPDATER.get_tree_hash(), inner_puzzle
)
@@ -385,29 +399,45 @@ class NFTWallet:
if tx_record is None or tx_record.spend_bundle is None:
return None
bundles_to_agg = [tx_record.spend_bundle, launcher_sb]
if not target_puzzle_hash:
target_puzzle_hash = inner_puzzle.get_tree_hash()
condition_list = [make_create_coin_condition(target_puzzle_hash, amount, [target_puzzle_hash])]
innersol = solution_for_conditions(condition_list)
# EVE SPEND BELOW
fullsol = Program.to(
[
[launcher_coin.parent_coin_info, launcher_coin.amount],
eve_coin.amount,
Program.to(
[
innersol,
amount,
0,
]
),
]
target_puzzle_hash = p2_inner_puzzle.get_tree_hash()
record: Optional[DerivationRecord] = None
# Create inner solution for eve spend
if self.did_id:
record = await self.wallet_state_manager.puzzle_store.get_derivation_record_for_puzzle_hash(
p2_inner_puzzle.get_tree_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(self.did_id, did_inner_hash, [], pubkey)
bundles_to_agg.append(did_bundle)
self.log.debug("Created an inner DID NFT solution: %s", disassemble(innersol))
else:
condition_list = [make_create_coin_condition(target_puzzle_hash, amount, [target_puzzle_hash])]
innersol = Program.to([solution_for_conditions(condition_list), 1])
# full singleton solution for eve spend
fullsol = Program.to([[launcher_coin.parent_coin_info, launcher_coin.amount], eve_coin.amount, innersol])
self.log.debug(
"Going to spend eve fullpuz with a solution: \n\n%s\n=================================\n\n%s",
disassemble(eve_fullpuz),
disassemble(fullsol),
)
list_of_coinspends = [CoinSpend(eve_coin, eve_fullpuz, fullsol)]
eve_spend_bundle = SpendBundle(list_of_coinspends, AugSchemeMPL.aggregate([]))
eve_spend_bundle = await self.sign(eve_spend_bundle)
full_spend = SpendBundle.aggregate([tx_record.spend_bundle, eve_spend_bundle, launcher_sb])
puzzle_hashes_to_sign = [p2_inner_puzzle.get_tree_hash()]
if record:
puzzle_hashes_to_sign.append(record.puzzle_hash)
eve_spend_bundle = await self.sign(eve_spend_bundle, puzzle_hashes_to_sign)
bundles_to_agg.append(eve_spend_bundle)
full_spend = SpendBundle.aggregate(bundles_to_agg)
nft_record = TransactionRecord(
confirmed_at_height=uint32(0),
created_at_time=uint64(int(time.time())),
@@ -429,68 +459,83 @@ class NFTWallet:
await self.standard_wallet.push_transaction(nft_record)
return nft_record.spend_bundle
async def sign(self, spend_bundle: SpendBundle) -> SpendBundle:
async def sign(self, spend_bundle: SpendBundle, puzzle_hashes: List[bytes32] = None) -> SpendBundle:
if puzzle_hashes is None:
puzzle_hashes = []
sigs: List[G2Element] = []
for spend in spend_bundle.coin_spends:
try:
uncurried_nft = UncurriedNFT.uncurry(spend.puzzle_reveal.to_program())
except Exception as e:
# This only happens while you changed the NFT chialisp but didn't update the UncurriedNFT class.
self.log.error(e)
else:
self.log.debug("Found a NFT state layer to sign")
puzzle_hash = uncurried_nft.inner_puzzle.get_tree_hash()
keys = await self.wallet_state_manager.get_keys(puzzle_hash)
pks = {}
if not puzzle_hashes:
try:
uncurried_nft = UncurriedNFT.uncurry(spend.puzzle_reveal.to_program())
except ValueError:
# not an NFT
pass
else:
self.log.debug("Found a NFT state layer to sign")
puzzle_hashes.append(uncurried_nft.p2_puzzle.get_tree_hash())
for ph in puzzle_hashes:
keys = await self.wallet_state_manager.get_keys(ph)
assert keys
_, private = keys
pks[bytes(keys[0])] = private = keys[1]
synthetic_secret_key = calculate_synthetic_secret_key(private, DEFAULT_HIDDEN_PUZZLE_HASH)
error, conditions, cost = conditions_dict_for_solution(
spend.puzzle_reveal.to_program(),
spend.solution.to_program(),
self.wallet_state_manager.constants.MAX_BLOCK_COST_CLVM,
)
if conditions is not None:
synthetic_pk = synthetic_secret_key.get_g1()
for pk, msg in pkm_pairs_for_conditions_dict(
conditions, spend.coin.name(), self.wallet_state_manager.constants.AGG_SIG_ME_ADDITIONAL_DATA
):
try:
assert bytes(synthetic_pk) == pk
sigs.append(AugSchemeMPL.sign(synthetic_secret_key, msg))
except AssertionError:
raise ValueError("This spend bundle cannot be signed by the NFT wallet")
synthetic_pk = synthetic_secret_key.get_g1()
pks[bytes(synthetic_pk)] = synthetic_secret_key
error, conditions, cost = conditions_dict_for_solution(
spend.puzzle_reveal.to_program(),
spend.solution.to_program(),
self.wallet_state_manager.constants.MAX_BLOCK_COST_CLVM,
)
if conditions is not None:
for pk, msg in pkm_pairs_for_conditions_dict(
conditions, spend.coin.name(), self.wallet_state_manager.constants.AGG_SIG_ME_ADDITIONAL_DATA
):
try:
sk = pks.get(pk)
if sk:
self.log.debug("Found key, signing for pk: %s", pk)
sigs.append(AugSchemeMPL.sign(sk, msg))
else:
self.log.warning("Couldn't find key for: %s", pk)
except AssertionError:
raise ValueError("This spend bundle cannot be signed by the NFT wallet")
agg_sig = AugSchemeMPL.aggregate(sigs)
return SpendBundle.aggregate([spend_bundle, SpendBundle([], agg_sig)])
async def _make_nft_transaction(
self, nft_coin_info: NFTCoinInfo, inner_solution: Program, fee: uint64 = uint64(0)
self,
nft_coin_info: NFTCoinInfo,
inner_solution: Program,
puzzle_hashes_to_sign: List[bytes32],
fee: uint64 = uint64(0),
additional_bundles: List[SpendBundle] = [],
) -> TransactionRecord:
# Update NFT status
await self.update_coin_status(nft_coin_info.coin.name(), True)
coin = nft_coin_info.coin
amount = coin.amount
if not additional_bundles:
additional_bundles = []
full_puzzle = nft_coin_info.full_puzzle
lineage_proof = nft_coin_info.lineage_proof
assert lineage_proof is not None
self.log.debug("Inner solution: %r", disassemble(inner_solution))
full_solution = Program.to(
[
[lineage_proof.parent_name, lineage_proof.inner_puzzle_hash, lineage_proof.amount],
coin.amount,
Program.to(
[
inner_solution,
amount,
0,
]
),
inner_solution,
]
)
list_of_coinspends = [CoinSpend(coin, full_puzzle.to_serialized_program(), full_solution)]
self.log.debug(
"Going to run a new NFT transaction: \nPuzzle:\n%s\n=================================\nSolution:\n%s",
disassemble(full_puzzle),
disassemble(full_solution),
)
spend_bundle = SpendBundle(list_of_coinspends, AugSchemeMPL.aggregate([]))
spend_bundle = await self.sign(spend_bundle)
full_spend = SpendBundle.aggregate([spend_bundle])
spend_bundle = await self.sign(spend_bundle, puzzle_hashes_to_sign)
full_spend = SpendBundle.aggregate([spend_bundle] + additional_bundles)
self.log.debug("Memos are: %r", list(compute_memos(full_spend).items()))
nft_record = TransactionRecord(
confirmed_at_height=uint32(0),
@@ -520,14 +565,16 @@ 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])]
condition_list.append([int_to_bytes(-24), NFT_METADATA_UPDATER, (key, uri)])
condition_list = [
make_create_coin_condition(puzzle_hash, coin.amount, [puzzle_hash]),
[int_to_bytes(-24), NFT_METADATA_UPDATER, (key, uri)],
]
self.log.info(
"Attempting to add urls to NFT coin %s in the metadata: %s", nft_coin_info, uncurried_nft.metadata
)
inner_solution = solution_for_conditions(condition_list)
nft_tx_record = await self._make_nft_transaction(nft_coin_info, inner_solution, fee)
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)
self.wallet_state_manager.state_changed("nft_coin_updated", self.wallet_info.id)
return nft_tx_record.spend_bundle
@@ -536,18 +583,24 @@ class NFTWallet:
self,
nft_coin_info: NFTCoinInfo,
puzzle_hash: bytes32,
did_hash=None,
fee: uint64 = uint64(0),
) -> Optional[SpendBundle]:
self.log.debug("Attempt to transfer a new NFT")
coin = nft_coin_info.coin
self.log.debug("Transfering NFT coin %r to puzhash: %s", nft_coin_info, puzzle_hash)
self.log.debug("Transferring NFT coin %r to puzhash: %s", nft_coin_info, puzzle_hash)
amount = coin.amount
condition_list = [make_create_coin_condition(puzzle_hash, amount, [bytes32(puzzle_hash)])]
self.log.debug("Condition for new coin: %r", condition_list)
inner_solution = solution_for_conditions(condition_list)
nft_tx_record = await self._make_nft_transaction(nft_coin_info, inner_solution, fee)
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])
self.log.debug("Solution for new coin: %r", disassemble(inner_solution))
nft_tx_record = await self._make_nft_transaction(
nft_coin_info,
inner_solution,
[puzzle_hash_to_sign],
fee,
)
await self.standard_wallet.push_transaction(nft_tx_record)
self.wallet_state_manager.state_changed("nft_coin_transferred", self.wallet_info.id)
return nft_tx_record.spend_bundle
@@ -572,7 +625,7 @@ class NFTWallet:
)
new_nft_wallet_info = NFTWalletInfo(
my_nft_coins,
self.nft_wallet_info.did_wallet_id,
self.nft_wallet_info.did_id,
)
await self.save_info(new_nft_wallet_info, in_transaction=in_transaction)
+63 -30
View File
@@ -1,13 +1,20 @@
from __future__ import annotations
import logging
from dataclasses import dataclass
from typing import Type, TypeVar
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
from chia.wallet.puzzles.load_clvm import load_clvm
log = logging.getLogger(__name__)
SINGLETON_TOP_LAYER_MOD = load_clvm("singleton_top_layer_v1_1.clvm")
NFT_MOD = load_clvm("nft_state_layer.clvm")
NFT_OWNERSHIP_LAYER = load_clvm("nft_ownership_layer.clvm")
_T_UncurriedNFT = TypeVar("_T_UncurriedNFT", bound="UncurriedNFT")
@@ -20,7 +27,7 @@ class UncurriedNFT:
This is the only place you need to change after modified the Chialisp curried parameters.
"""
nft_mod_hash: Program
nft_mod_hash: bytes32
"""NFT module hash"""
nft_state_layer: Program
@@ -32,28 +39,12 @@ class UncurriedNFT:
[singleton_mod_hash, singleton_launcher_id, launcher_puzhash]
"""
singleton_mod_hash: Program
singleton_launcher_id: Program
singleton_launcher_id: bytes32
launcher_puzhash: Program
owner_did: Program
"""Owner's DID"""
metadata_updater_hash: Program
"""Metadata updater puzzle hash"""
transfer_program_hash: Program
"""Puzzle hash of the transfer program"""
transfer_program_curry_params: Program
"""
Curried parameters of the transfer program
[royalty_address, trade_price_percentage, settlement_mod_hash, cat_mod_hash]
"""
royalty_address: Program
trade_price_percentage: Program
settlement_mod_hash: Program
cat_mod_hash: Program
metadata: Program
"""
NFT metadata
@@ -71,6 +62,26 @@ class UncurriedNFT:
inner_puzzle: Program
"""NFT state layer inner puzzle"""
p2_puzzle: Program
"""p2 puzzle of the owner, either for ownership layer or standard"""
# ownership layer fields
owner_did: Optional[bytes32]
"""Owner's DID"""
owner_pubkey: Optional[G1Element]
nft_inner_puzzle_hash: Optional[bytes32]
"""Puzzle hash of the ownership layer inner puzzle """
transfer_program: Optional[Program]
"""Puzzle hash of the transfer program"""
transfer_program_curry_params: Optional[Program]
"""
Curried parameters of the transfer program
[royalty_address, trade_price_percentage, settlement_mod_hash, cat_mod_hash]
"""
royalty_address: Optional[bytes32]
trade_price_percentage: Optional[uint16]
@classmethod
def uncurry(cls: Type[_T_UncurriedNFT], puzzle: Program) -> UncurriedNFT:
"""
@@ -95,7 +106,7 @@ class UncurriedNFT:
raise ValueError(f"Cannot uncurry NFT puzzle, failed on NFT state layer: Mod {mod}")
try:
# Set nft parameters
(nft_mod_hash, metadata, metadata_updater_hash, inner_puzzle) = curried_args.as_iter()
nft_mod_hash, metadata, metadata_updater_hash, inner_puzzle = curried_args.as_iter()
data_uris = Program.to([])
data_hash = Program.to(0)
meta_uris = Program.to([])
@@ -122,6 +133,27 @@ class UncurriedNFT:
series_number = kv_pair.rest()
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
royalty_percentage = None
nft_inner_puzzle_mod = None
mod, ol_args = inner_puzzle.uncurry()
if mod == NFT_OWNERSHIP_LAYER:
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, royalty_percentage, _, _ = transfer_program_args.as_iter()
royalty_percentage = uint16(royalty_percentage.as_int())
current_did = current_did.atom
pubkey = pubkey_sexp.atom
else:
log.debug("Creating a standard NFT puzzle")
p2_puzzle = inner_puzzle
except Exception as e:
raise ValueError(f"Cannot uncurry NFT state layer: Args {curried_args}") from e
return cls(
@@ -129,25 +161,26 @@ class UncurriedNFT:
nft_state_layer=nft_state_layer,
singleton_struct=singleton_struct,
singleton_mod_hash=singleton_mod_hash,
singleton_launcher_id=singleton_launcher_id,
singleton_launcher_id=singleton_launcher_id.atom,
launcher_puzhash=launcher_puzhash,
metadata=metadata,
data_uris=data_uris,
data_hash=data_hash,
p2_puzzle=p2_puzzle,
metadata_updater_hash=metadata_updater_hash,
meta_uris=meta_uris,
meta_hash=meta_hash,
license_uris=license_uris,
license_hash=license_hash,
series_number=series_number,
series_total=series_total,
metadata_updater_hash=metadata_updater_hash,
inner_puzzle=inner_puzzle,
# TODO Set/Remove following fields after NFT1 implemented
owner_did=Program.to([]),
transfer_program_hash=Program.to([]),
transfer_program_curry_params=Program.to([]),
royalty_address=Program.to([]),
trade_price_percentage=Program.to([]),
settlement_mod_hash=Program.to([]),
cat_mod_hash=Program.to([]),
# TODO: Set/Remove following fields after NFT1 implemented
owner_did=current_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,
)
+1
View File
@@ -0,0 +1 @@
ff02ffff01ff04ffff04ff38ffff04ffff02ff3affff04ff02ffff04ff05ffff04ffff02ff2effff04ff02ffff04ff17ffff04ffff02ff26ffff04ff02ffff04ff2fff80808080ff8080808080ff8080808080ffff04ff27ff80808080ffff02ff32ffff04ff02ffff04ff0bffff04ff2fffff01ff01808080808080ffff04ffff01ffffff32ff0233ffff0401ff0102ffffffff02ffff03ff05ffff01ff02ff22ffff04ff02ffff04ff0dffff04ffff0bff3cffff0bff34ff2480ffff0bff3cffff0bff3cffff0bff34ff2c80ff0980ffff0bff3cff0bffff0bff34ff8080808080ff8080808080ffff010b80ff0180ff02ffff03ff0bffff01ff04ffff04ff38ffff04ffff02ff2affff04ff02ffff04ff05ffff04ffff04ffff04ff17ff1380ff8080ff8080808080ffff01ff80808080ffff04ffff04ff10ffff04ff23ffff04ffff02ff3effff04ff02ffff04ffff04ff13ffff04ff17ff808080ff80808080ff80808080ffff02ff32ffff04ff02ffff04ff05ffff04ff1bffff04ffff10ff17ffff010180ff8080808080808080ff8080ff0180ffff02ff36ffff04ff02ffff04ff05ffff04ffff02ff3effff04ff02ffff04ff0bff80808080ff8080808080ff02ff36ffff04ff02ffff04ff05ffff04ffff02ff3effff04ff02ffff04ff0bff80808080ffff04ffff02ff3effff04ff02ffff04ff05ff80808080ff808080808080ffffff02ffff03ffff07ff0580ffff01ff04ff29ffff02ff26ffff04ff02ffff04ff0dff8080808080ff8080ff0180ff0bff3cffff0bff34ff2880ffff0bff3cffff0bff3cffff0bff34ff2c80ff0580ffff0bff3cffff02ff22ffff04ff02ffff04ff07ffff04ffff0bff34ff3480ff8080808080ffff0bff34ff8080808080ffff02ffff03ffff07ff0b80ffff01ff10ff13ffff02ff2effff04ff02ffff04ff05ffff04ff1bff808080808080ffff011580ff0180ff02ffff03ffff07ff0580ffff01ff0bffff0102ffff02ff3effff04ff02ffff04ff09ff80808080ffff02ff3effff04ff02ffff04ff0dff8080808080ffff01ff0bffff0101ff058080ff0180ff018080
+1
View File
@@ -0,0 +1 @@
ff02ffff01ff04ffff04ffff013effff04ffff02ff02ffff04ff02ffff04ff05ff80808080ff808080ff8080ffff04ffff01ff02ffff03ffff07ff0580ffff01ff0bffff0102ffff02ff02ffff04ff02ffff04ff09ff80808080ffff02ff02ffff04ff02ffff04ff0dff8080808080ffff01ff0bffff0101ff058080ff0180ff018080
-85
View File
@@ -1,85 +0,0 @@
(mod (
NFT_MOD_HASH
SINGLETON_STRUCT ; ((SINGLETON_MOD_HASH, (SINGLETON_LAUNCHER_ID, LAUNCHER_PUZZLE_HASH)))
CURRENT_OWNER_DID
TRANSFER_PROGRAM_MOD_HASH
TRANSFER_PROGRAM_CURRY_PARAMS
METADATA
my_did_inner_hash
new_did
new_did_inner_hash
transfer_program_reveal
transfer_program_solution
)
(include condition_codes.clvm)
(include curry-and-treehash.clinc)
(include singleton_truths.clib)
; takes a lisp tree and returns the hash of it
(defun sha256tree1 (TREE)
(if (l TREE)
(sha256 2 (sha256tree1 (f TREE)) (sha256tree1 (r TREE)))
(sha256 ONE TREE)
)
)
(defun-inline nft_puzzle_hash (NFT_MOD_HASH SINGLETON_STRUCT TRANSFER_PROGRAM_MOD_HASH next_owner_did transfer_program_curry_params metadata)
(puzzle-hash-of-curried-function NFT_MOD_HASH
(sha256tree1 metadata)
(sha256tree1 transfer_program_curry_params)
(sha256 ONE TRANSFER_PROGRAM_MOD_HASH)
(sha256 ONE next_owner_did)
(sha256tree1 SINGLETON_STRUCT)
(sha256 ONE NFT_MOD_HASH)
)
)
;; 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)
)
)
; if we find an update, recreate ourselves with the update
; otherwise recreate ourselves as we are
(defun parse_transfer_prog_output (NFT_MOD_HASH SINGLETON_STRUCT TRANSFER_PROGRAM_MOD_HASH TRANSFER_PROGRAM_CURRY_PARAMS METADATA new_did output found_update)
(if output
(if (= (f (f output)) -22)
(c
(list CREATE_COIN (nft_puzzle_hash NFT_MOD_HASH SINGLETON_STRUCT TRANSFER_PROGRAM_MOD_HASH new_did (f (r (r (f output)))) (f (r (f output)))) ONE (list new_did))
(parse_transfer_prog_output NFT_MOD_HASH SINGLETON_STRUCT TRANSFER_PROGRAM_MOD_HASH TRANSFER_PROGRAM_CURRY_PARAMS METADATA new_did (r output) 1)
)
(c (f output) (parse_transfer_prog_output NFT_MOD_HASH SINGLETON_STRUCT TRANSFER_PROGRAM_MOD_HASH TRANSFER_PROGRAM_CURRY_PARAMS METADATA new_did (r output) found_update))
)
(if found_update ; if we're at the end of the loop, check if we've already recreated ourselves or not
()
(list (list CREATE_COIN (nft_puzzle_hash NFT_MOD_HASH SINGLETON_STRUCT TRANSFER_PROGRAM_MOD_HASH new_did TRANSFER_PROGRAM_CURRY_PARAMS METADATA) ONE (list new_did)))
)
)
)
; main
(c
(list ASSERT_MY_AMOUNT ONE)
(if new_did
(if (= (sha256tree1 transfer_program_reveal) TRANSFER_PROGRAM_MOD_HASH)
(c
(list ASSERT_PUZZLE_ANNOUNCEMENT (sha256 (calculate_full_puzzle_hash (c (f SINGLETON_STRUCT) (c CURRENT_OWNER_DID (r (r SINGLETON_STRUCT)))) my_did_inner_hash) (sha256tree1 (list transfer_program_solution new_did))))
; (METADATA CURRY_PARAMS SINGLETON_STRUCT current_owner trade_price_list my_did_inner_hash new_did new_did_inner_hash my_nft_id solution)
(parse_transfer_prog_output NFT_MOD_HASH SINGLETON_STRUCT TRANSFER_PROGRAM_MOD_HASH TRANSFER_PROGRAM_CURRY_PARAMS METADATA new_did (a transfer_program_reveal (list METADATA TRANSFER_PROGRAM_CURRY_PARAMS SINGLETON_STRUCT CURRENT_OWNER_DID my_did_inner_hash new_did new_did_inner_hash transfer_program_solution)) 0)
)
(x)
)
(list
(list CREATE_COIN (nft_puzzle_hash NFT_MOD_HASH SINGLETON_STRUCT TRANSFER_PROGRAM_MOD_HASH CURRENT_OWNER_DID TRANSFER_PROGRAM_CURRY_PARAMS METADATA) ONE (list CURRENT_OWNER_DID))
(list ASSERT_PUZZLE_ANNOUNCEMENT (sha256 (calculate_full_puzzle_hash (c (f SINGLETON_STRUCT) (c CURRENT_OWNER_DID (r (r SINGLETON_STRUCT)))) my_did_inner_hash) 'a'))
(list CREATE_PUZZLE_ANNOUNCEMENT CURRENT_OWNER_DID)
)
)
)
)
@@ -1 +0,0 @@
ff02ffff01ff04ffff04ff10ffff04ff12ff808080ffff02ffff03ff8202ffffff01ff02ffff03ffff09ffff02ff3effff04ff02ffff04ff820bffff80808080ff2f80ffff01ff04ffff04ff28ffff04ffff0bffff02ff2effff04ff02ffff04ff13ffff04ff82017fffff04ffff02ff3effff04ff02ffff04ffff04ff13ffff04ff17ff3b8080ff80808080ff808080808080ffff02ff3effff04ff02ffff04ffff04ff8217ffffff04ff8202ffff808080ff8080808080ff808080ffff02ff36ffff04ff02ffff04ff05ffff04ff0bffff04ff2fffff04ff5fffff04ff81bfffff04ff8202ffffff04ffff02ff820bffffff04ff81bfffff04ff5fffff04ff0bffff04ff17ffff04ff82017fffff04ff8202ffffff04ff8205ffffff04ff8217ffff80808080808080808080ffff01ff808080808080808080808080ffff01ff088080ff0180ffff01ff04ffff04ff14ffff04ffff02ff2effff04ff02ffff04ff05ffff04ffff02ff3effff04ff02ffff04ff81bfff80808080ffff04ffff02ff3effff04ff02ffff04ff5fff80808080ffff04ffff0bff12ff2f80ffff04ffff0bff12ff1780ffff04ffff02ff3effff04ff02ffff04ff0bff80808080ffff04ffff0bff12ff0580ff80808080808080808080ffff04ff12ffff04ffff04ff17ff8080ff8080808080ffff04ffff04ff28ffff04ffff0bffff02ff2effff04ff02ffff04ff13ffff04ff82017fffff04ffff02ff3effff04ff02ffff04ffff04ff13ffff04ff17ff3b8080ff80808080ff808080808080ffff016180ff808080ffff04ffff04ff2cffff04ff17ff808080ff8080808080ff018080ffff04ffff01ffffff49ff3f02ff33ff3e04ffff01ff0102ffffff02ffff03ff05ffff01ff02ff26ffff04ff02ffff04ff0dffff04ffff0bff3affff0bff12ff3c80ffff0bff3affff0bff3affff0bff12ff2a80ff0980ffff0bff3aff0bffff0bff12ff8080808080ff8080808080ffff010b80ff0180ff02ffff03ff82017fffff01ff02ffff03ffff09ff82047fffff0181ea80ffff01ff04ffff04ff14ffff04ffff02ff2effff04ff02ffff04ff05ffff04ffff02ff3effff04ff02ffff04ff820a7fff80808080ffff04ffff02ff3effff04ff02ffff04ff82167fff80808080ffff04ffff0bff12ff1780ffff04ffff0bff12ff81bf80ffff04ffff02ff3effff04ff02ffff04ff0bff80808080ffff04ffff0bff12ff0580ff80808080808080808080ffff04ff12ffff04ffff04ff81bfff8080ff8080808080ffff02ff36ffff04ff02ffff04ff05ffff04ff0bffff04ff17ffff04ff2fffff04ff5fffff04ff81bfffff04ff82037fffff01ff018080808080808080808080ffff01ff04ff82027fffff02ff36ffff04ff02ffff04ff05ffff04ff0bffff04ff17ffff04ff2fffff04ff5fffff04ff81bfffff04ff82037fffff04ff8202ffff80808080808080808080808080ff0180ffff01ff02ffff03ff8202ffff80ffff01ff04ffff04ff14ffff04ffff02ff2effff04ff02ffff04ff05ffff04ffff02ff3effff04ff02ffff04ff5fff80808080ffff04ffff02ff3effff04ff02ffff04ff2fff80808080ffff04ffff0bff12ff1780ffff04ffff0bff12ff81bf80ffff04ffff02ff3effff04ff02ffff04ff0bff80808080ffff04ffff0bff12ff0580ff80808080808080808080ffff04ff12ffff04ffff04ff81bfff8080ff8080808080ff808080ff018080ff0180ffff0bff3affff0bff12ff3880ffff0bff3affff0bff3affff0bff12ff2a80ff0580ffff0bff3affff02ff26ffff04ff02ffff04ff07ffff04ffff0bff12ff1280ff8080808080ffff0bff12ff8080808080ff02ffff03ffff07ff0580ffff01ff0bffff0102ffff02ff3effff04ff02ffff04ff09ff80808080ffff02ff3effff04ff02ffff04ff0dff8080808080ffff01ff0bff12ff058080ff0180ff018080
@@ -1 +0,0 @@
ae8ee2bf9e0bcc189d4d8efa3c8281235981abd75ade592ba71930aa6881f820
@@ -1,20 +0,0 @@
(mod (CURRENT_METADATA solution)
; once we find 'u' we don't need to continue looping
(defun add_url (METADATA new_url)
(if METADATA
(if (= (f (f METADATA)) 'u')
(c (c 'u' (c new_url (r (f METADATA)))) (r METADATA))
(c (f METADATA) (add_url (r METADATA) new_url))
)
()
)
)
; main
; returns (new_metadata conditions)
(if solution
(list (add_url CURRENT_METADATA solution) 0)
(list CURRENT_METADATA 0)
)
)
@@ -1 +0,0 @@
ff02ffff01ff02ffff03ff0bffff01ff04ffff02ff02ffff04ff02ffff04ff05ffff04ff0bff8080808080ffff01ff808080ffff01ff04ff05ffff01ff80808080ff0180ffff04ffff01ff02ffff03ff05ffff01ff02ffff03ffff09ff11ffff017580ffff01ff04ffff04ffff0175ffff04ff0bff198080ff0d80ffff01ff04ff09ffff02ff02ffff04ff02ffff04ff0dffff04ff0bff80808080808080ff0180ff8080ff0180ff018080
@@ -0,0 +1,28 @@
(mod (CURRENT_METADATA METADATA_UPDATER_PUZZLE_HASH solution)
; solution is (new_url new_metadata_updater_puzhash)
; METADATA and METADATA_UPDATER_PUZZLE_HASH are passed in as truths from the layer above
; This program returns ((new_metadata new_metadata_updater_puzhash) conditions)
; NOTE THIS PROGRAM IS FOR TESTING ONLY - USE IN DEPLOYMENT AT YOUR OWN PERIL
; once we find 'u' we don't need to continue looping
(defun add_url (METADATA new_url)
(if METADATA
(if (= (f (f METADATA)) 'u')
(c (c 'u' (c new_url (r (f METADATA)))) (r METADATA))
(c (f METADATA) (add_url (r METADATA) new_url))
)
()
)
)
(defun-inline assert_bytes32 (value)
(= (strlen value) 32)
)
; main
; returns ((new_metadata new_metadata_updater_puzhash) conditions)
(list (list (if (f solution) (add_url CURRENT_METADATA (f solution)) CURRENT_METADATA) (if (assert_bytes32 (f (r solution))) (f (r solution)) METADATA_UPDATER_PUZZLE_HASH)) 0)
)
@@ -0,0 +1 @@
ff02ffff01ff04ffff04ffff02ffff03ff27ffff01ff02ff02ffff04ff02ffff04ff05ffff04ff27ff8080808080ffff010580ff0180ffff04ffff02ffff03ffff09ffff0dff5780ffff012080ffff0157ffff010b80ff0180ff808080ffff01ff808080ffff04ffff01ff02ffff03ff05ffff01ff02ffff03ffff09ff11ffff017580ffff01ff04ffff04ffff0175ffff04ff0bff198080ff0d80ffff01ff04ff09ffff02ff02ffff04ff02ffff04ff0dffff04ff0bff80808080808080ff0180ff8080ff0180ff018080
@@ -0,0 +1 @@
0b1ffba1601777c06b78ab38636e9624f2f8da73be9b36e0ce17c8d8ef3bad9f
@@ -0,0 +1,71 @@
(mod (
NFT_OWNERSHIP_LAYER_MOD_HASH
CURRENT_OWNER
TRANSFER_PROGRAM
INNER_PUZZLE
inner_solution
)
(include condition_codes.clvm)
(include curry-and-treehash.clinc)
(defconstant NEW_OWNER_CONDITION -10)
(defun sha256tree1
(TREE)
(if (l TREE)
(sha256 2 (sha256tree1 (f TREE)) (sha256tree1 (r TREE)))
(sha256 1 TREE)
)
)
(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
(sha256tree1 TRANSFER_PROGRAM)
(sha256 ONE new_owner)
(sha256 ONE NFT_OWNERSHIP_LAYER_MOD_HASH)
)
)
(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 ))
)
()
)
)
; 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
)
; 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))
)
; main
(transfer_program_loader
NFT_OWNERSHIP_LAYER_MOD_HASH
TRANSFER_PROGRAM
CURRENT_OWNER
(a INNER_PUZZLE inner_solution)
)
)
@@ -0,0 +1 @@
ff02ffff01ff02ff2effff04ff02ffff04ff05ffff04ff17ffff04ff0bffff04ffff02ff2fff5f80ff80808080808080ffff04ffff01ffffff02ff3304ffff81f601ff0102ffffff02ffff03ff05ffff01ff02ff12ffff04ff02ffff04ff0dffff04ffff0bff3cffff0bff34ff3880ffff0bff3cffff0bff3cffff0bff34ff2c80ff0980ffff0bff3cff0bffff0bff34ff8080808080ff8080808080ffff010b80ff0180ffff02ffff03ff17ffff01ff02ffff03ffff09ff47ff2480ffff01ff02ff05ffff04ff0bffff04ff2fffff04ff67ff8080808080ffff01ff02ff2affff04ff02ffff04ff05ffff04ff0bffff04ff37ffff04ff2fff8080808080808080ff0180ffff01ff088080ff0180ff02ff3effff04ff02ffff04ff05ffff04ff27ffff04ff81b7ffff04ffff02ffff03ff57ffff0157ffff010b80ff0180ff80808080808080ffffff0bff3cffff0bff34ff1080ffff0bff3cffff0bff3cffff0bff34ff2c80ff0580ffff0bff3cffff02ff12ffff04ff02ffff04ff07ffff04ffff0bff34ff3480ff8080808080ffff0bff34ff8080808080ff02ffff03ffff07ff0580ffff01ff0bffff0102ffff02ff36ffff04ff02ffff04ff09ff80808080ffff02ff36ffff04ff02ffff04ff0dff8080808080ffff01ff0bffff0101ff058080ff0180ffff02ff3affff04ff02ffff04ff05ffff04ff0bffff04ffff02ff2affff04ff02ffff04ff0bffff04ff17ffff04ff2fffff04ff2fff80808080808080ff808080808080ff02ffff03ff17ffff01ff02ffff03ffff09ff47ff2880ffff01ff02ffff03ffff09ffff18ff82016780ff3480ffff01ff04ffff04ff28ffff04ffff02ff26ffff04ff02ffff04ff05ffff04ff81a7ffff04ffff02ff36ffff04ff02ffff04ff2fff80808080ffff04ffff0bff34ff0b80ffff04ffff0bff34ff0580ff8080808080808080ff81e78080ff3780ffff01ff04ff27ffff02ff3effff04ff02ffff04ff05ffff04ff0bffff04ff37ffff04ff2fff808080808080808080ff0180ffff01ff04ff27ffff02ff3effff04ff02ffff04ff05ffff04ff0bffff04ff37ffff04ff2fff808080808080808080ff0180ff8080ff0180ff018080
@@ -0,0 +1 @@
59d2c5ac094c3e07628a044ba4bd2e2e7a9e9ad637ac39c4a4ae34aa8e72f7bb
@@ -1,83 +0,0 @@
(mod
(
; SINGLETON_STRUCT is ((SINGLETON_MOD_HASH, (SINGLETON_LAUNCHER_ID, LAUNCHER_PUZZLE_HASH)))
SINGLETON_STRUCT
; CURRY_PARAMS are: (ROYALTY_ADDRESS TRADE_PRICE_PERCENTAGE SETTLEMENT_MOD_HASH CAT_MOD_HASH)
CURRY_PARAMS
; DID of the current owner
CURRENT_OWNER
; Royalty program puzzle hash
ROYALTY_PROG_PUZHASH
; DID inner puzzle hash of the current owner
current_did_puzhash
; New NFT owner's DID
new_owner
; DID inner puzzle hash of the new owner
new_did_puzhash
; trade_prices_list is a list of (amount type) pairs
; type 0 is standard chia
; any other type is a bytes32 mod hash of a launcher
trade_prices_list
)
(include condition_codes.clvm)
(include curry-and-treehash.clinc)
(include sha256tree.clib)
(defconstant CHANGE_OWNERSHIP -22)
(defconstant TEN_THOUSAND 10000)
;; 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
(sha256tree 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 1 tail_hash)
(sha256 1 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))
)
; 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) (sha256tree (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 (list ROYALTY_ADDRESS TRADE_PRICE_PERCENTAGE SETTLEMENT_MOD_HASH CAT_MOD_HASH) (r trade_prices_list) my_nft_id)
)
()
)
)
; main
(list
(c (list CHANGE_OWNERSHIP new_owner)
(c (list ASSERT_PUZZLE_ANNOUNCEMENT (sha256 (calculate_full_puzzle_hash (get_singleton_struct SINGLETON_STRUCT CURRENT_OWNER) current_did_puzhash) (f (r SINGLETON_STRUCT)) (sha256tree royalty_prog_solution) new_owner))
(c (list ASSERT_PUZZLE_ANNOUNCEMENT (sha256 (calculate_full_puzzle_hash (get_singleton_struct SINGLETON_STRUCT new_owner) new_did_puzhash) (f (r SINGLETON_STRUCT)) (sha256tree royalty_prog_solution)))
(parse_trade_prices_list CURRY_PARAMS trade_prices_list (f (r SINGLETON_STRUCT)))
)
)
)
)
)
@@ -1 +0,0 @@
ff02ffff01ff04ffff04ffff04ff38ffff04ff81bfff808080ffff04ffff04ff10ffff04ffff0bffff02ff36ffff04ff02ffff04ff09ffff04ff5fffff04ffff02ff3effff04ff02ffff04ffff04ff09ffff04ff17ff1d8080ff80808080ff808080808080ff15ffff02ff3effff04ff02ffff01ff95726f79616c74795f70726f675f736f6c7574696f6e808080ff81bf80ff808080ffff04ffff04ff10ffff04ffff0bffff02ff36ffff04ff02ffff04ff09ffff04ff82017fffff04ffff02ff3effff04ff02ffff04ffff04ff09ffff04ff81bfff1d8080ff80808080ff808080808080ff15ffff02ff3effff04ff02ffff01ff95726f79616c74795f70726f675f736f6c7574696f6e80808080ff808080ffff02ff26ffff04ff02ffff04ff0bffff04ff8202ffffff04ff15ff808080808080808080ff8080ffff04ffff01ffffff3fff0281eaffff3304ff0101ffff822710ff02ff02ffff03ff05ffff01ff02ff3affff04ff02ffff04ff0dffff04ffff0bff2affff0bff2cff3480ffff0bff2affff0bff2affff0bff2cff3c80ff0980ffff0bff2aff0bffff0bff2cff8080808080ff8080808080ffff010b80ff0180ffffff02ffff03ff0bffff01ff04ffff02ffff03ff33ffff01ff04ff10ffff04ffff0bffff02ff36ffff04ff02ffff04ff5dffff04ff2dffff04ffff0bffff0101ff5380ffff04ffff0bffff0101ff5d80ff80808080808080ffff02ff3effff04ff02ffff04ffff04ff17ffff04ffff04ff09ffff04ffff05ffff14ffff12ff23ff1580ff128080ff808080ff808080ff8080808080ff808080ffff01ff04ff24ffff04ff09ffff04ffff02ff2effff04ff02ffff04ffff05ffff14ffff12ff23ff1580ff128080ff80808080ff8080808080ff0180ffff02ff26ffff04ff02ffff04ffff04ff09ffff04ff15ffff04ff2dffff04ff5dff8080808080ffff04ff1bffff04ff17ff80808080808080ff8080ff0180ff0bff2affff0bff2cff2880ffff0bff2affff0bff2affff0bff2cff3c80ff0580ffff0bff2affff02ff3affff04ff02ffff04ff07ffff04ffff0bff2cff2c80ff8080808080ffff0bff2cff8080808080ffff02ffff03ffff18ff05ffff010180ffff01ff11ff05ffff010180ffff010580ff0180ff02ffff03ffff07ff0580ffff01ff0bffff0102ffff02ff3effff04ff02ffff04ff09ff80808080ffff02ff3effff04ff02ffff04ff0dff8080808080ffff01ff0bffff0101ff058080ff0180ff018080
@@ -1 +0,0 @@
03557552e0bf8b200d8be8089f54786195ab8c07e3e07c39641f2f4f1fc2e48e
@@ -0,0 +1,89 @@
(mod
(
SINGLETON_STRUCT
ROYALTY_ADDRESS
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
)
)
; This is a metadata updater - 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)
)
)
;; 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 1 tail_hash)
(sha256 1 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))
)
; 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)
)
()
)
)
(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)))
)
(parse_trade_prices_list ROYALTY_ADDRESS TRADE_PRICE_PERCENTAGE SETTLEMENT_MOD_HASH CAT_MOD_HASH trade_prices_list (f (r SINGLETON_STRUCT)))
)
)
)
@@ -0,0 +1 @@
ff02ffff01ff04ff82027fffff04ff80ffff04ffff02ffff03ff82027fffff01ff04ffff04ff10ffff04ffff0bffff02ff36ffff04ff02ffff04ff09ffff04ff82137fffff04ffff02ff3effff04ff02ffff04ffff04ff09ffff04ff82027fff1d8080ff80808080ff808080808080ff1580ff808080ffff02ff26ffff04ff02ffff04ff0bffff04ff17ffff04ff2fffff04ff5fffff04ff82057fffff04ff15ff80808080808080808080ffff01ff02ff26ffff04ff02ffff04ff0bffff04ff17ffff04ff2fffff04ff5fffff04ff82057fffff04ff15ff80808080808080808080ff0180ff80808080ffff04ffff01ffffff3fff0233ff04ff0101ffff822710ff02ff02ffff03ff05ffff01ff02ff3affff04ff02ffff04ff0dffff04ffff0bff2affff0bff2cff1480ffff0bff2affff0bff2affff0bff2cff3c80ff0980ffff0bff2aff0bffff0bff2cff8080808080ff8080808080ffff010b80ff0180ffffff02ffff03ff5fffff01ff04ffff02ffff03ff82019fffff01ff04ff10ffff04ffff0bffff02ff36ffff04ff02ffff04ff2fffff04ff17ffff04ffff0bffff0101ff82029f80ffff04ffff0bffff0101ff2f80ff80808080808080ffff02ff3effff04ff02ffff04ffff04ff81bfffff04ffff04ff05ffff04ffff05ffff14ffff12ff82011fff0b80ff128080ff808080ff808080ff8080808080ff808080ffff01ff04ff38ffff04ff05ffff04ffff02ff2effff04ff02ffff04ffff05ffff14ffff12ff82011fff0b80ff128080ff80808080ff8080808080ff0180ffff02ff26ffff04ff02ffff04ff05ffff04ff0bffff04ff17ffff04ff2fffff04ff81dfffff04ff81bfff80808080808080808080ff8080ff0180ff0bff2affff0bff2cff2880ffff0bff2affff0bff2affff0bff2cff3c80ff0580ffff0bff2affff02ff3affff04ff02ffff04ff07ffff04ffff0bff2cff2c80ff8080808080ffff0bff2cff8080808080ffff02ffff03ffff18ff05ffff010180ffff01ff11ff05ffff010180ffff010580ff0180ff02ffff03ffff07ff0580ffff01ff0bffff0102ffff02ff3effff04ff02ffff04ff09ff80808080ffff02ff3effff04ff02ffff04ff0dff8080808080ffff01ff0bffff0101ff058080ff0180ff018080
@@ -0,0 +1 @@
3b0c0f181e51ee219d82eb99a89b31a864802904a18da9352bcf01c3fe52ba8c
@@ -0,0 +1,112 @@
(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)))
)
@@ -0,0 +1 @@
ff02ffff01ff04ff8209ffffff04ff80ffff04ffff02ff26ffff04ff02ffff04ff82017fffff04ff05ffff04ff0bffff04ff17ffff04ff2fffff04ff5fffff04ff81bfffff04ff8202ffffff04ff80ffff04ffff04ff8209ffffff04ff8215ffffff04ff822dffffff04ff825dffff8080808080ff80808080808080808080808080ff80808080ffff04ffff01ffffffff323fff0233ffff0401ff01822710ffffff02ff02ffff03ff05ffff01ff02ff32ffff04ff02ffff04ff0dffff04ffff0bff22ffff0bff34ff2480ffff0bff22ffff0bff22ffff0bff34ff2c80ff0980ffff0bff22ff0bffff0bff34ff8080808080ff8080808080ffff010b80ff0180ffff02ffff03ffff22ff82027fffff20ffff09ff82027fff81bf808080ffff01ff04ffff04ff30ffff04ffff0bffff02ff2effff04ff02ffff04ff09ffff04ff82277fffff04ffff02ff7effff04ff02ffff04ffff04ff09ffff04ff82027fff1d8080ff80808080ff808080808080ff1580ff808080ffff02ff36ffff04ff02ffff04ff0bffff04ff17ffff04ff2fffff04ff5fffff04ff82057fffff04ff15ff80808080808080808080ffff01ff02ff36ffff04ff02ffff04ff0bffff04ff17ffff04ff2fffff04ff5fffff04ff82057fffff04ff15ff80808080808080808080ff0180ff02ffff03ffff09ff820affffff02ff2effff04ff02ffff04ff0bffff04ffff0bff34ff822dff80ff808080808080ffff01ff04ff8202ffffff04ffff04ff20ffff04ff822dffffff04ffff02ff7effff04ff02ffff04ff825dffff80808080ff80808080ffff02ff2affff04ff02ffff04ff17ffff04ff2fffff04ff5fffff04ff81bfffff04ff82017fffff04ff05ffff04ff8205ffff808080808080808080808080ffff01ff088080ff0180ffffff02ffff03ff8202ffffff01ff02ffff03ffff09ff8208ffff3880ffff01ff02ffff03ffff18ff822cffff3480ffff01ff02ff3affff04ff02ffff04ff05ffff04ff0bffff04ff17ffff04ff2fffff04ff5fffff04ff81bfffff04ff82017fffff04ff8204ffffff04ff820bffff808080808080808080808080ffff01ff04ff8204ffffff02ff26ffff04ff02ffff04ff0bffff04ff17ffff04ff2fffff04ff5fffff04ff81bfffff04ff82017fffff04ff05ffff04ff8206ffffff04ff8205ffffff04ff820bffff808080808080808080808080808080ff0180ffff01ff02ff26ffff04ff02ffff04ff05ffff04ff0bffff04ff17ffff04ff2fffff04ff5fffff04ff81bfffff04ff82017fffff04ff8206ffffff04ff8205ffffff04ff820bffff8080808080808080808080808080ff0180ffff01ff088080ff0180ff02ffff03ff5fffff01ff04ffff02ffff03ff82019fffff01ff04ff30ffff04ffff0bffff02ff2effff04ff02ffff04ff2fffff04ff17ffff04ffff0bff34ff82029f80ffff04ffff0bff34ff2f80ff80808080808080ffff02ff7effff04ff02ffff04ffff04ff81bfffff04ffff04ff05ffff04ffff05ffff14ffff12ff82011fff0b80ff3c8080ff808080ff808080ff8080808080ff808080ffff01ff04ff38ffff04ff05ffff04ffff02ff5effff04ff02ffff04ffff05ffff14ffff12ff82011fff0b80ff3c8080ff80808080ff8080808080ff0180ffff02ff36ffff04ff02ffff04ff05ffff04ff0bffff04ff17ffff04ff2fffff04ff81dfffff04ff81bfff80808080808080808080ff8080ff0180ffff0bff22ffff0bff34ff2880ffff0bff22ffff0bff22ffff0bff34ff2c80ff0580ffff0bff22ffff02ff32ffff04ff02ffff04ff07ffff04ffff0bff34ff3480ff8080808080ffff0bff34ff8080808080ffff02ffff03ffff18ff05ff3480ffff01ff11ff05ff3480ffff010580ff0180ff02ffff03ffff07ff0580ffff01ff0bffff0102ffff02ff7effff04ff02ffff04ff09ff80808080ffff02ff7effff04ff02ffff04ff0dff8080808080ffff01ff0bff34ff058080ff0180ff018080
@@ -0,0 +1 @@
1ba78fd36e36ff5e5ade71864527d2ce3980b4b43ddf7cdc3f714945f4b734a7
+9 -19
View File
@@ -3,7 +3,7 @@
METADATA
METADATA_UPDATER_PUZZLE_HASH
INNER_PUZZLE
solution ; either to inner puzzle or metadata updater
solution
my_amount
)
@@ -27,31 +27,21 @@
)
)
(defun wrap_odd_create_coins (NFT_STATE_LAYER_MOD_HASH ((METADATA METADATA_UPDATER_PUZZLE_HASH) conditions) my_amount new_create_coin_condition)
; 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)
(if conditions
(if (= (f (f conditions)) CREATE_COIN)
(if (logand (f (r (r (f conditions)))) ONE)
(wrap_odd_create_coins NFT_STATE_LAYER_MOD_HASH (list (list METADATA METADATA_UPDATER_PUZZLE_HASH) (r conditions)) my_amount (f conditions))
(c (f conditions) (wrap_odd_create_coins NFT_STATE_LAYER_MOD_HASH (list (list METADATA METADATA_UPDATER_PUZZLE_HASH) (r conditions)) my_amount new_create_coin_condition))
(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 new_create_coin_condition))
(wrap_odd_create_coins NFT_STATE_LAYER_MOD_HASH (list (list METADATA METADATA_UPDATER_PUZZLE_HASH) (r conditions)) my_amount new_create_coin_condition)
(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 new_create_coin_condition
(list (c CREATE_COIN
(c (nft_state_layer_puzzle_hash
NFT_STATE_LAYER_MOD_HASH METADATA
METADATA_UPDATER_PUZZLE_HASH
(f (r new_create_coin_condition)))
(c my_amount
(r (r (r new_create_coin_condition)))
)
)
))
(x)
)
()
)
)
+1 -1
View File
@@ -1 +1 @@
ff02ffff01ff04ffff04ff10ffff04ff81bfff808080ffff02ff3effff04ff02ffff04ff05ffff04ffff02ff3affff04ff02ffff04ff17ffff04ff0bffff04ffff02ff2fff5f80ff808080808080ffff04ff81bfffff01ff8080808080808080ffff04ffff01ffffff49ff0233ffff0401ff0102ffffffff02ffff03ff05ffff01ff02ff22ffff04ff02ffff04ff0dffff04ffff0bff3cffff0bff34ff2480ffff0bff3cffff0bff3cffff0bff34ff2c80ff0980ffff0bff3cff0bffff0bff34ff8080808080ff8080808080ffff010b80ff0180ff02ffff03ff17ffff01ff02ffff03ffff09ff47ffff0181e880ffff01ff02ffff03ffff09ffff02ff2effff04ff02ffff04ff81a7ff80808080ff0580ffff01ff02ff81a7ffff04ff0bffff04ff05ffff04ff820167ff8080808080ffff01ff088080ff0180ffff01ff02ff32ffff04ff02ffff04ff05ffff04ff0bffff04ff37ff80808080808080ff0180ffff01ff04ffff04ff0bffff04ff05ff808080ffff01ff80808080ff0180ffff02ffff03ff05ffff01ff04ff09ffff02ff2affff04ff02ffff04ff0dffff04ff0bff808080808080ffff010b80ff0180ff02ff26ffff04ff02ffff04ffff02ff32ffff04ff02ffff04ff05ffff04ff0bffff04ff17ff808080808080ffff04ff17ff8080808080ffffff04ff09ffff04ffff02ff2affff04ff02ffff04ff15ffff04ff0bff8080808080ff808080ff0bff3cffff0bff34ff2880ffff0bff3cffff0bff3cffff0bff34ff2c80ff0580ffff0bff3cffff02ff22ffff04ff02ffff04ff07ffff04ffff0bff34ff3480ff8080808080ffff0bff34ff8080808080ffff02ffff03ffff07ff0580ffff01ff0bffff0102ffff02ff2effff04ff02ffff04ff09ff80808080ffff02ff2effff04ff02ffff04ff0dff8080808080ffff01ff0bffff0101ff058080ff0180ff02ffff03ff2bffff01ff02ffff03ffff09ff818bff3880ffff01ff02ffff03ffff18ff8202cbff3480ffff01ff02ff3effff04ff02ffff04ff05ffff04ffff04ffff04ff23ffff04ff53ff808080ffff04ff6bff808080ffff04ff17ffff04ff4bff80808080808080ffff01ff04ff4bffff02ff3effff04ff02ffff04ff05ffff04ffff04ffff04ff23ffff04ff53ff808080ffff04ff6bff808080ffff04ff17ffff04ff2fff808080808080808080ff0180ffff01ff02ffff03ffff15ff818bff8080ffff01ff04ff4bffff02ff3effff04ff02ffff04ff05ffff04ffff04ffff04ff23ffff04ff53ff808080ffff04ff6bff808080ffff04ff17ffff04ff2fff8080808080808080ffff01ff02ff3effff04ff02ffff04ff05ffff04ffff04ffff04ff23ffff04ff53ff808080ffff04ff6bff808080ffff04ff17ffff04ff2fff8080808080808080ff018080ff0180ffff01ff02ffff03ff2fffff01ff04ffff04ff38ffff04ffff02ff36ffff04ff02ffff04ff05ffff04ff81afffff04ffff0bff34ff5380ffff04ffff02ff2effff04ff02ffff04ff23ff80808080ffff04ffff0bff34ff0580ff8080808080808080ffff04ff17ff8201ef808080ff8080ffff01ff088080ff018080ff0180ff018080
ff02ffff01ff04ffff04ff10ffff04ff81bfff808080ffff02ff3effff04ff02ffff04ff05ffff04ffff02ff3affff04ff02ffff04ff17ffff04ff0bffff04ffff02ff2fff5f80ff808080808080ffff04ff81bfffff01ff8080808080808080ffff04ffff01ffffff49ff0233ffff0401ff0102ffffffff02ffff03ff05ffff01ff02ff22ffff04ff02ffff04ff0dffff04ffff0bff3cffff0bff34ff2480ffff0bff3cffff0bff3cffff0bff34ff2c80ff0980ffff0bff3cff0bffff0bff34ff8080808080ff8080808080ffff010b80ff0180ff02ffff03ff17ffff01ff02ffff03ffff09ff47ffff0181e880ffff01ff02ffff03ffff09ffff02ff2effff04ff02ffff04ff81a7ff80808080ff0580ffff01ff02ff81a7ffff04ff0bffff04ff05ffff04ff820167ff8080808080ffff01ff088080ff0180ffff01ff02ff32ffff04ff02ffff04ff05ffff04ff0bffff04ff37ff80808080808080ff0180ffff01ff04ffff04ff0bffff04ff05ff808080ffff01ff80808080ff0180ffff02ffff03ff05ffff01ff04ff09ffff02ff2affff04ff02ffff04ff0dffff04ff0bff808080808080ffff010b80ff0180ff02ff26ffff04ff02ffff04ffff02ff32ffff04ff02ffff04ff05ffff04ff0bffff04ff17ff808080808080ffff04ff17ff8080808080ffffff04ff09ffff04ffff02ff2affff04ff02ffff04ff15ffff04ff0bff8080808080ff808080ff0bff3cffff0bff34ff2880ffff0bff3cffff0bff3cffff0bff34ff2c80ff0580ffff0bff3cffff02ff22ffff04ff02ffff04ff07ffff04ffff0bff34ff3480ff8080808080ffff0bff34ff8080808080ffff02ffff03ffff07ff0580ffff01ff0bffff0102ffff02ff2effff04ff02ffff04ff09ff80808080ffff02ff2effff04ff02ffff04ff0dff8080808080ffff01ff0bffff0101ff058080ff0180ff02ffff03ff2bffff01ff02ffff03ffff09ff818bff3880ffff01ff02ffff03ffff18ff8202cbff3480ffff01ff04ffff04ff38ffff04ffff02ff36ffff04ff02ffff04ff05ffff04ff82014bffff04ffff0bff34ff5380ffff04ffff02ff2effff04ff02ffff04ff23ff80808080ffff04ffff0bff34ff0580ff8080808080808080ffff04ff17ff8203cb808080ffff02ff3effff04ff02ffff04ff05ffff04ffff04ffff04ff23ffff04ff53ff808080ffff04ff6bff808080ffff04ff17ff80808080808080ffff01ff04ff4bffff02ff3effff04ff02ffff04ff05ffff04ffff04ffff04ff23ffff04ff53ff808080ffff04ff6bff808080ffff04ff17ff8080808080808080ff0180ffff01ff02ffff03ffff15ff818bff8080ffff01ff04ff4bffff02ff3effff04ff02ffff04ff05ffff04ffff04ffff04ff23ffff04ff53ff808080ffff04ff6bff808080ffff04ff17ff80808080808080ffff01ff02ff3effff04ff02ffff04ff05ffff04ffff04ffff04ff23ffff04ff53ff808080ffff04ff6bff808080ffff04ff17ff80808080808080ff018080ff0180ff8080ff0180ff018080
@@ -1 +1 @@
dd8135d546e291df295b376aa89fc409c8c50d7f655d1ff4e845637901bc2f8f
3cb215c818b13f6d18d0f33a0b8d2ef156483982cfe4bc0a5937b038ef28b03b
@@ -1,139 +0,0 @@
(mod (METADATA CURRY_PARAMS SINGLETON_STRUCT current_owner my_did_inner_hash new_did new_did_inner_hash solution)
; METADATA and CURRY_PARAMS are not actually curried into the mod hash but are curried into the NFT layer and passed in
; This effectively achieves the same result - fixing the values - but means that the transfer program mod never changes
; CURRY_PARAMS are: (ROYALTY_ADDRESS TRADE_PRICE_PERCENTAGE SETTLEMENT_MOD_HASH CAT_MOD_HASH)
; SINGLETON_STRUCT is ((SINGLETON_MOD_HASH, (SINGLETON_LAUNCHER_ID, LAUNCHER_PUZZLE_HASH)))
; For this transfer program solution is (trade_prices_list new_url)
(include condition_codes.clvm)
(include curry-and-treehash.clinc)
(defconstant TEN_THOUSAND 10000)
; trade_prices_list is a list of (amount type) pairs
; type 0 is standard chia
; any other type is a bytes32 mod hash of a launcher
(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)
)
)
;; 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)
)
)
(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))
)
(defun sha256tree1 (TREE)
(if (l TREE)
(sha256 2 (sha256tree1 (f TREE)) (sha256tree1 (r TREE)))
(sha256 ONE TREE)
)
)
; 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 (list ROYALTY_ADDRESS TRADE_PRICE_PERCENTAGE SETTLEMENT_MOD_HASH CAT_MOD_HASH) (r trade_prices_list) my_nft_id)
)
()
)
)
; if we are part of a complex trade then we assert an announcement from the recipient of the trade_price and then parse the trade price list
(defun generate_conditions_for_trade (
SINGLETON_STRUCT
CURRENT_OWNER_DID
CURRY_PARAMS
my_did_inner_hash
new_did
new_did_inner_hash
trade_prices_list
trade_prices_list_hash
)
(c (list CREATE_PUZZLE_ANNOUNCEMENT trade_prices_list_hash)
(c (list ASSERT_PUZZLE_ANNOUNCEMENT (sha256 (calculate_full_puzzle_hash (c (f SINGLETON_STRUCT) (c new_did (r (r SINGLETON_STRUCT)))) new_did_inner_hash) trade_prices_list_hash (f (r SINGLETON_STRUCT))))
(parse_trade_prices_list CURRY_PARAMS trade_prices_list (f (r SINGLETON_STRUCT)))
)
)
)
; once we find 'u' we don't need to continue looping
(defun add_url (METADATA new_url)
(if METADATA
(if (= (f (f METADATA)) 'u')
(c (c 'u' (c new_url (r (f METADATA)))) (r METADATA))
(c (f METADATA) (add_url (r METADATA) new_url))
)
()
)
)
(defun check_if_add_url (METADATA CURRY_PARAMS solution output)
(if solution
(c (list -22 (add_url METADATA solution) CURRY_PARAMS) output)
output
)
)
(defun check_if_gift (
SINGLETON_STRUCT
CURRENT_OWNER_DID
CURRY_PARAMS
my_did_inner_hash
new_did
new_did_inner_hash
trade_prices_list
)
(if trade_prices_list
(generate_conditions_for_trade
SINGLETON_STRUCT
CURRENT_OWNER_DID
CURRY_PARAMS
my_did_inner_hash
new_did
new_did_inner_hash
trade_prices_list
(sha256tree1 trade_prices_list)
)
(list (list CREATE_PUZZLE_ANNOUNCEMENT 0))
)
)
; main
(check_if_add_url
METADATA
CURRY_PARAMS
(f (r solution))
(check_if_gift
SINGLETON_STRUCT
current_owner
CURRY_PARAMS
my_did_inner_hash
new_did
new_did_inner_hash
(f solution)
)
)
)
@@ -1 +0,0 @@
ff02ffff01ff02ff2affff04ff02ffff04ff05ffff04ff0bffff04ff820affffff04ffff02ff3affff04ff02ffff04ff17ffff04ff2fffff04ff0bffff04ff5fffff04ff81bfffff04ff82017fffff04ff8204ffff80808080808080808080ff80808080808080ffff04ffff01ffffffff3f02ff333effff0401ff01ff82271002ffffffff02ffff03ff05ffff01ff02ffff03ffff09ff11ffff017580ffff01ff04ffff04ffff0175ffff04ff0bff198080ff0d80ffff01ff04ff09ffff02ff22ffff04ff02ffff04ff0dffff04ff0bff80808080808080ff0180ff8080ff0180ff02ffff03ff05ffff01ff02ff32ffff04ff02ffff04ff0dffff04ffff0bff7cffff0bff34ff2480ffff0bff7cffff0bff7cffff0bff34ff2c80ff0980ffff0bff7cff0bffff0bff34ff8080808080ff8080808080ffff010b80ff0180ffff02ffff03ff17ffff01ff04ffff04ffff0181eaffff04ffff02ff22ffff04ff02ffff04ff05ffff04ff17ff8080808080ffff04ff0bff80808080ff2f80ffff012f80ff0180ff02ffff03ff82017fffff01ff02ff26ffff04ff02ffff04ff05ffff04ff0bffff04ff17ffff04ff2fffff04ff5fffff04ff81bfffff04ff82017fffff04ffff02ff7effff04ff02ffff04ff82017fff80808080ff8080808080808080808080ffff01ff04ffff04ff38ffff01ff808080ff808080ff0180ffffff04ffff04ff38ffff04ff8202ffff808080ffff04ffff04ff20ffff04ffff0bffff02ff2effff04ff02ffff04ff09ffff04ff81bfffff04ffff02ff7effff04ff02ffff04ffff04ff09ffff04ff5fff1d8080ff80808080ff808080808080ff8202ffff1580ff808080ffff02ff36ffff04ff02ffff04ff17ffff04ff82017fffff04ff15ff8080808080808080ff02ffff03ff0bffff01ff04ffff02ffff03ff33ffff01ff04ff20ffff04ffff0bffff02ff2effff04ff02ffff04ff5dffff04ff2dffff04ffff0bff34ff5380ffff04ffff0bff34ff5d80ff80808080808080ffff02ff7effff04ff02ffff04ffff04ff17ffff04ffff04ff09ffff04ffff05ffff14ffff12ff23ff1580ff5c8080ff808080ff808080ff8080808080ff808080ffff01ff04ff28ffff04ff09ffff04ffff02ff5effff04ff02ffff04ffff05ffff14ffff12ff23ff1580ff5c8080ff80808080ff8080808080ff0180ffff02ff36ffff04ff02ffff04ffff04ff09ffff04ff15ffff04ff2dffff04ff5dff8080808080ffff04ff1bffff04ff17ff80808080808080ff8080ff0180ffff0bff7cffff0bff34ff3080ffff0bff7cffff0bff7cffff0bff34ff2c80ff0580ffff0bff7cffff02ff32ffff04ff02ffff04ff07ffff04ffff0bff34ff3480ff8080808080ffff0bff34ff8080808080ffff02ffff03ffff18ff05ff3480ffff01ff11ff05ff3480ffff010580ff0180ff02ffff03ffff07ff0580ffff01ff0bffff0102ffff02ff7effff04ff02ffff04ff09ff80808080ffff02ff7effff04ff02ffff04ff0dff8080808080ffff01ff0bff34ff058080ff0180ff018080
@@ -1 +0,0 @@
519b806c31cce5280fa37695149521a301f13f5696d1fd1eeee1856c2d14965c
+4 -8
View File
@@ -40,7 +40,7 @@ from chia.wallet.derive_keys import master_sk_to_wallet_sk, master_sk_to_wallet_
from chia.wallet.did_wallet.did_wallet import DIDWallet
from chia.wallet.did_wallet.did_wallet_puzzles import DID_INNERPUZ_MOD, create_fullpuz, match_did_puzzle
from chia.wallet.key_val_store import KeyValStore
from chia.wallet.nft_wallet.nft_wallet import NFTWallet, NFTWalletInfo
from chia.wallet.nft_wallet.nft_wallet import NFTWallet
from chia.wallet.nft_wallet.uncurry_nft import UncurriedNFT
from chia.wallet.outer_puzzles import AssetType, match_puzzle
from chia.wallet.puzzle_drivers import PuzzleInfo
@@ -715,21 +715,17 @@ class WalletStateManager:
"""
wallet_id = None
wallet_type = None
self.log.debug("Handling NFT: %s", coin_spend)
for wallet_info in await self.get_all_wallet_info_entries():
if wallet_info.type == WalletType.NFT:
nft_wallet_info = NFTWalletInfo.from_json_dict(json.loads(wallet_info.data))
self.log.debug(
"Checking NFT wallet %r and inner puzzle %s",
wallet_info.name,
uncurried_nft.inner_puzzle.get_tree_hash(),
)
if not nft_wallet_info.did_wallet_id:
# standard NFT wallet
wallet_id = wallet_info.id
wallet_type = WalletType.NFT
break
wallet_id = wallet_info.id
wallet_type = WalletType.NFT
if wallet_id is None:
# TODO Modify this for NFT1
self.log.info("Cannot find a NFT wallet, creating a new one.")
+1 -1
View File
File diff suppressed because one or more lines are too long
+5 -5
View File
@@ -41,12 +41,12 @@ wallet_program_files = set(
"chia/wallet/puzzles/settlement_payments.clvm",
"chia/wallet/puzzles/genesis_by_coin_id.clvm",
"chia/wallet/puzzles/singleton_top_layer_v1_1.clvm",
"chia/wallet/puzzles/nft_innerpuz.clvm",
"chia/wallet/puzzles/nft_transfer_program.clvm",
"chia/wallet/puzzles/nft_ownership_transfer_program.clvm",
"chia/wallet/puzzles/nft_metadata_updater.clvm",
"chia/wallet/puzzles/nft_metadata_updater_default.clvm",
"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",
]
)
@@ -161,7 +161,7 @@ class TestClvmCompilation(TestCase):
self.assertEqual(
s.get_tree_hash().hex(),
existing_sha,
msg=f"Checked-in shatree hash file does not match shatree hash of loaded SerializedProgram: {prog_path}", # noqa
msg=f"Checked-in shatree hash file does not match hash of loaded SerializedProgram: {prog_path}",
)
self.assertEqual(
p.get_tree_hash().hex(),
+193 -11
View File
@@ -1,5 +1,6 @@
from chia.types.announcement import Announcement
from chia.types.blockchain_format.program import INFINITE_COST, Program
from chia.types.blockchain_format.sized_bytes import bytes32
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 puzzle_for_pk, solution_for_conditions
from chia.wallet.puzzles.puzzle_utils import make_create_coin_condition
@@ -9,16 +10,20 @@ SINGLETON_MOD = load_clvm("singleton_top_layer.clvm")
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")
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()
SINGLETON_MOD_HASH = SINGLETON_MOD.get_tree_hash()
OFFER_MOD = load_clvm("settlement_payments.clvm")
LAUNCHER_ID = Program.to("launcher-id").get_tree_hash()
NFT_METADATA_UPDATER = load_clvm("nft_metadata_updater_default.clvm")
LAUNCHER_ID = Program.to(b"launcher-id").get_tree_hash()
NFT_METADATA_UPDATER_DEFAULT = load_clvm("nft_metadata_updater_default.clvm")
NFT_METADATA_UPDATER_UPDATEABLE = load_clvm("nft_metadata_updater_updateable.clvm")
def test_new_nft_ownership_layer() -> None:
def test_new_nft_state_layer() -> None:
pubkey = int_to_public_key(1)
innerpuz = puzzle_for_pk(pubkey)
my_amount = 1
@@ -32,12 +37,11 @@ def test_new_nft_ownership_layer() -> None:
[
NFT_STATE_LAYER_MOD_HASH,
metadata,
NFT_METADATA_UPDATER.get_tree_hash(),
NFT_METADATA_UPDATER_DEFAULT.get_tree_hash(),
innerpuz,
# below here is the solution
solution_for_conditions(condition_list),
my_amount,
0,
]
)
@@ -48,7 +52,7 @@ def test_new_nft_ownership_layer() -> None:
assert (
res.rest().rest().first().rest().first().as_atom()
== NFT_STATE_LAYER_MOD.curry(
NFT_STATE_LAYER_MOD_HASH, metadata, NFT_METADATA_UPDATER.get_tree_hash(), destination
NFT_STATE_LAYER_MOD_HASH, metadata, NFT_METADATA_UPDATER_DEFAULT.get_tree_hash(), destination
).get_tree_hash()
)
@@ -59,7 +63,7 @@ def test_update_metadata() -> None:
my_amount = 1
destination: Program = puzzle_for_pk(int_to_public_key(2))
condition_list = [make_create_coin_condition(destination.get_tree_hash(), my_amount, [])]
condition_list.append([-24, NFT_METADATA_UPDATER, ("mu", "https://url2")])
condition_list.append([-24, NFT_METADATA_UPDATER_DEFAULT, ("mu", "https://url2")])
metadata = [
("u", ["https://www.chia.net/img/branding/chia-logo.svg"]),
@@ -74,7 +78,7 @@ def test_update_metadata() -> None:
[
NFT_STATE_LAYER_MOD_HASH,
metadata,
NFT_METADATA_UPDATER.get_tree_hash(),
NFT_METADATA_UPDATER_DEFAULT.get_tree_hash(),
innerpuz,
# below here is the solution
solution_for_conditions(condition_list),
@@ -92,12 +96,190 @@ def test_update_metadata() -> None:
("lh", 0xD4584AD463139FA8C0D9F68F4B59F185),
]
cost, res = NFT_STATE_LAYER_MOD.run_with_cost(INFINITE_COST, solution)
assert len(res.as_python()) == 3 # check that the negative conditions have been filtered out
assert res.first().first().as_int() == 73
assert res.first().rest().first().as_int() == 1
assert res.rest().rest().first().first().as_int() == 51
assert (
bytes32(res.rest().rest().first().rest().first().as_atom())
res.rest().rest().first().rest().first().as_atom()
== NFT_STATE_LAYER_MOD.curry(
NFT_STATE_LAYER_MOD_HASH, metadata, NFT_METADATA_UPDATER.get_tree_hash(), destination
NFT_STATE_LAYER_MOD_HASH, metadata, NFT_METADATA_UPDATER_DEFAULT.get_tree_hash(), destination
).get_tree_hash()
)
def test_transfer_program() -> None:
pubkey = int_to_public_key(1)
innerpuz = puzzle_for_pk(pubkey)
current_owner = Program.to("current_owner").get_tree_hash()
new_owner = Program.to("new_owner").get_tree_hash()
new_inner = Program.to("new_owner_inner").get_tree_hash()
new_pk = int_to_public_key(2)
nft_id = Program.to("nft").get_tree_hash()
SINGLETON_STRUCT = Program.to((SINGLETON_MOD_HASH, (nft_id, LAUNCHER_PUZZLE_HASH)))
curried_tp = NFT_TRANSFER_PROGRAM.curry(
STANDARD_PUZZLE_MOD.get_tree_hash(),
SINGLETON_STRUCT,
innerpuz.get_tree_hash(),
2000, # percentage with 2 decimal points 0 - 10000
OFFER_MOD.get_tree_hash(),
CAT_MOD.get_tree_hash(),
)
# Current_Owner
# conditions
# (
# new_owner
# trade_prices_list
# (new_did_inner_hash) ; this is the opaque transfer_program_solution
# )
# (CREATE_COIN p2dohp odd_number)
# (NEW_OWNER_CONDITION new_owner trade_prices_list new_pk transfer_program_solution)
trade_prices_list = [[200]]
conditions = Program.to(
[
[51, STANDARD_PUZZLE_MOD.curry(new_pk).get_tree_hash(), 1],
[-10, new_owner, trade_prices_list, new_pk, [new_inner]],
]
)
solution = Program.to([current_owner, conditions, [new_owner, trade_prices_list, new_pk, [new_inner]]])
cost, res = curried_tp.run_with_cost(INFINITE_COST, solution)
assert res.first().as_atom() == new_owner
assert res.rest().first().as_int() == 0
conditions = res.rest().rest().first()
assert conditions.first().first().as_int() == 51
assert conditions.first().rest().rest().first().as_int() == 1
assert conditions.rest().rest().first().first().as_int() == 63
SINGLETON_STRUCT = Program.to((SINGLETON_MOD_HASH, (new_owner, LAUNCHER_PUZZLE_HASH)))
new_owner_coin_puzhash = SINGLETON_MOD.curry(SINGLETON_STRUCT, "new_owner_inner").get_tree_hash()
assert (
conditions.rest().rest().first().rest().first().as_atom() == Announcement(new_owner_coin_puzhash, nft_id).name()
)
assert conditions.rest().rest().rest().first().first().as_int() == 51
assert conditions.rest().rest().rest().first().rest().rest().first().as_int() == 40
def test_ownership_layer() -> None:
pubkey = int_to_public_key(1)
innerpuz = puzzle_for_pk(pubkey)
destination = int_to_public_key(2)
new_did = Program.to("test").get_tree_hash()
new_did_inner_hash = Program.to("fake").get_tree_hash()
old_did = Program.to("test_2").get_tree_hash()
nft_id = Program.to("nft_id").get_tree_hash()
trade_prices_list = [[200]]
SINGLETON_STRUCT = Program.to((SINGLETON_MOD_HASH, (nft_id, LAUNCHER_PUZZLE_HASH)))
curried_tp = NFT_TRANSFER_PROGRAM.curry(
STANDARD_PUZZLE_MOD.get_tree_hash(),
SINGLETON_STRUCT,
innerpuz.get_tree_hash(),
2000, # percentage with 2 decimal points 0 - 10000
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,
)
condition_list = [
[
51,
STANDARD_PUZZLE_MOD.curry(destination).get_tree_hash(),
1,
[STANDARD_PUZZLE_MOD.curry(destination).get_tree_hash()],
],
[-10, new_did, trade_prices_list, destination, [new_did_inner_hash]],
]
solution = Program.to([solution_for_conditions(condition_list)])
cost, res = curried_ownership_layer.run_with_cost(INFINITE_COST, solution)
assert res.first().first().as_int() == 51
assert res.first().rest().rest().first().as_int() == 1
curried_inner = STANDARD_PUZZLE_MOD.curry(destination)
curried_ownership_layer = NFT_OWNERSHIP_LAYER.curry(
NFT_OWNERSHIP_LAYER.get_tree_hash(),
new_did,
curried_tp,
curried_inner,
)
assert res.first().rest().first().as_atom() == curried_ownership_layer.get_tree_hash()
assert res.first().rest().rest().rest().first().first().as_atom() == curried_inner.get_tree_hash()
assert res.rest().rest().rest().first().first().as_int() == 51
assert res.rest().rest().rest().first().rest().rest().first().as_int() == 40
def test_full_stack() -> None:
pubkey = int_to_public_key(1)
innerpuz = puzzle_for_pk(pubkey)
my_amount = 1
destination = int_to_public_key(2)
new_did = Program.to("test").get_tree_hash()
new_did_inner_hash = Program.to("fake").get_tree_hash()
old_did = Program.to("test_2").get_tree_hash()
nft_id = Program.to("nft_id").get_tree_hash()
trade_prices_list = [[200]]
SINGLETON_STRUCT = Program.to((SINGLETON_MOD_HASH, (nft_id, LAUNCHER_PUZZLE_HASH)))
curried_tp = NFT_TRANSFER_PROGRAM.curry(
STANDARD_PUZZLE_MOD.get_tree_hash(),
SINGLETON_STRUCT,
innerpuz.get_tree_hash(),
2000, # percentage with 2 decimal points 0 - 10000
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,
)
metadata = [
("u", ["https://www.chia.net/img/branding/chia-logo.svg"]),
("h", 0xD4584AD463139FA8C0D9F68F4B59F185),
]
curried_state_layer = NFT_STATE_LAYER_MOD.curry(
NFT_STATE_LAYER_MOD_HASH,
metadata,
NFT_METADATA_UPDATER_UPDATEABLE.get_tree_hash(),
curried_ownership_layer,
)
condition_list = [
[
51,
STANDARD_PUZZLE_MOD.curry(destination).get_tree_hash(),
1,
[STANDARD_PUZZLE_MOD.curry(destination).get_tree_hash()],
],
[-10, new_did, trade_prices_list, destination, [new_did_inner_hash]],
]
solution = Program.to(
[
[solution_for_conditions(condition_list)],
my_amount,
]
)
cost, res = curried_state_layer.run_with_cost(INFINITE_COST, solution)
assert res.rest().first().first().as_int() == 51
assert res.rest().first().rest().rest().first().as_int() == 1
curried_inner = STANDARD_PUZZLE_MOD.curry(destination)
curried_ownership_layer = NFT_OWNERSHIP_LAYER.curry(
NFT_OWNERSHIP_LAYER.get_tree_hash(),
new_did,
curried_tp,
curried_inner,
)
curried_state_layer = NFT_STATE_LAYER_MOD.curry(
NFT_STATE_LAYER_MOD_HASH,
metadata,
NFT_METADATA_UPDATER_UPDATEABLE.get_tree_hash(),
curried_ownership_layer,
)
assert res.rest().first().rest().first().as_atom() == curried_state_layer.get_tree_hash()
assert res.rest().first().rest().rest().rest().first().first().as_atom() == curried_inner.get_tree_hash()
+128
View File
@@ -0,0 +1,128 @@
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 (
create_full_puzzle,
create_nft_layer_puzzle_with_curry_params,
recurry_nft_puzzle,
)
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 puzzle_for_pk, solution_for_conditions
from tests.core.make_block_generator import int_to_public_key
SINGLETON_MOD = load_clvm("singleton_top_layer.clvm")
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")
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()
OFFER_MOD = load_clvm("settlement_payments.clvm")
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]:
destination = int_to_public_key(2)
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(),
1,
[STANDARD_PUZZLE_MOD.curry(destination).get_tree_hash()],
],
[-10, new_did, trade_prices_list, destination, [new_did_inner_hash]],
]
solution = Program.to(
[
[solution_for_conditions(condition_list)],
my_amount,
]
)
print(disassemble(solution))
return destination, solution
def make_a_new_ownership_layer_puzzle() -> Tuple[Program, Program]:
pubkey = int_to_public_key(1)
innerpuz = puzzle_for_pk(pubkey)
old_did = Program.to("test_2").get_tree_hash()
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,
)
return innerpuz, curried_ownership_layer
def make_a_new_nft_puzzle(curried_ownership_layer: Program, metadata: Program) -> Program:
curried_state_layer = NFT_STATE_LAYER_MOD.curry(
NFT_STATE_LAYER_MOD_HASH, metadata, NFT_METADATA_UPDATER_DEFAULT.get_tree_hash(), curried_ownership_layer
)
return curried_state_layer
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")
def test_transfer_puzzle_builder() -> None:
metadata = [
("u", ["https://www.chia.net/img/branding/chia-logo.svg"]),
("h", 0xD4584AD463139FA8C0D9F68F4B59F185),
]
destination, 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())
puzzle = create_full_puzzle(
Program.to(["singleton_id"]).get_tree_hash(),
Program.to(metadata),
NFT_METADATA_UPDATER_DEFAULT.get_tree_hash(),
ownership_puzzle,
)
clvm_puzzle_hash = get_updated_nft_puzzle(clvm_nft_puzzle, solution)
unft = uncurry_nft.UncurriedNFT.uncurry(puzzle)
assert unft.inner_puzzle == ownership_puzzle
ol_puzzle = recurry_nft_puzzle(unft, solution.first())
py_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()
+114
View File
@@ -14,6 +14,8 @@ from chia.types.blockchain_format.sized_bytes import bytes32
from chia.types.peer_info import PeerInfo
from chia.util.byte_types import hexstr_to_bytes
from chia.util.ints import uint16, uint32, uint64
from chia.wallet.did_wallet.did_wallet import DIDWallet
from chia.wallet.nft_wallet import uncurry_nft
from chia.wallet.nft_wallet.nft_wallet import NFTWallet
from chia.wallet.util.compute_memos import compute_memos
from chia.wallet.util.wallet_types import WalletType
@@ -517,3 +519,115 @@ async def test_nft_wallet_rpc_update_metadata(two_wallet_nodes: Any, trusted: An
raise
await asyncio.sleep(0.5)
time_left -= 0.5
@pytest.mark.parametrize(
"trusted",
[True],
)
@pytest.mark.asyncio
async def test_nft_with_did_wallet_creation(two_wallet_nodes: Any, trusted: Any) -> None:
num_blocks = 2
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
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)
for _ in range(1, num_blocks):
await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(ph))
for _ in range(1, num_blocks):
await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(ph))
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()
did_id = bytes32.fromhex(hex_did_id)
nft_wallet_0 = await NFTWallet.create_new_nft_wallet(
wallet_node_0.wallet_state_manager, wallet_0, name="NFT WALLET DID 1", did_id=did_id
)
metadata = Program.to(
[
("u", ["https://www.chia.net/img/branding/chia-logo.svg"]),
("h", bytes.fromhex("D4584AD463139FA8C0D9F68F4B59F185")),
]
)
await time_out_assert(10, wallet_0.get_unconfirmed_balance, 5999999999999)
await time_out_assert(10, wallet_0.get_confirmed_balance, 5999999999999)
sb = await nft_wallet_0.generate_new_nft(metadata)
assert sb
# 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))
await time_out_assert(10, len, 1, nft_wallet_0.nft_wallet_info.my_nft_coins)
metadata = Program.to(
[
("u", ["https://url1"]),
("h", "0xD4584AD463139FA8C0D9F68F4B59F181"),
]
)
await time_out_assert(10, wallet_0.get_unconfirmed_balance, 7999999999999 - 1)
await time_out_assert(10, wallet_0.get_confirmed_balance, 7999999999999 - 1)
sb = await nft_wallet_0.generate_new_nft(metadata)
assert sb
# 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))
await time_out_assert(10, len, 2, nft_wallet_0.nft_wallet_info.my_nft_coins)
# check if we uncurry well
last_nft_coin = nft_wallet_0.nft_wallet_info.my_nft_coins[-1]
unft = uncurry_nft.UncurriedNFT.uncurry(last_nft_coin.full_puzzle)
assert unft.data_uris == ["https://url1"]
assert unft.data_hash.atom == b"0xD4584AD463139FA8C0D9F68F4B59F181"
assert unft.owner_did == nft_wallet_0.nft_wallet_info.did_id
assert unft.owner_pubkey is not None
first_nft_coin = nft_wallet_0.nft_wallet_info.my_nft_coins[0]
unft = uncurry_nft.UncurriedNFT.uncurry(first_nft_coin.full_puzzle)
assert unft.data_uris == ["https://www.chia.net/img/branding/chia-logo.svg"]
assert unft.data_hash.atom == bytes.fromhex("D4584AD463139FA8C0D9F68F4B59F185")