mirror of
https://github.com/Chia-Network/chia-blockchain.git
synced 2026-09-05 02:24:21 -05:00
Merge pull request #11715 from trepca/nft1_ownership_layer
NFT1 minting and receiving
This commit is contained in:
@@ -95,7 +95,7 @@ jobs:
|
||||
- name: Test wallet-nft_wallet code with pytest
|
||||
run: |
|
||||
. ./activate
|
||||
venv/bin/coverage run --rcfile=.coveragerc --module pytest --durations=10 -n 0 -m "not benchmark" tests/wallet/nft_wallet/test_nft_clvm.py tests/wallet/nft_wallet/test_nft_offers.py tests/wallet/nft_wallet/test_nft_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,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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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,
|
||||
)
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
ff02ffff01ff04ffff04ff38ffff04ffff02ff3affff04ff02ffff04ff05ffff04ffff02ff2effff04ff02ffff04ff17ffff04ffff02ff26ffff04ff02ffff04ff2fff80808080ff8080808080ff8080808080ffff04ff27ff80808080ffff02ff32ffff04ff02ffff04ff0bffff04ff2fffff01ff01808080808080ffff04ffff01ffffff32ff0233ffff0401ff0102ffffffff02ffff03ff05ffff01ff02ff22ffff04ff02ffff04ff0dffff04ffff0bff3cffff0bff34ff2480ffff0bff3cffff0bff3cffff0bff34ff2c80ff0980ffff0bff3cff0bffff0bff34ff8080808080ff8080808080ffff010b80ff0180ff02ffff03ff0bffff01ff04ffff04ff38ffff04ffff02ff2affff04ff02ffff04ff05ffff04ffff04ffff04ff17ff1380ff8080ff8080808080ffff01ff80808080ffff04ffff04ff10ffff04ff23ffff04ffff02ff3effff04ff02ffff04ffff04ff13ffff04ff17ff808080ff80808080ff80808080ffff02ff32ffff04ff02ffff04ff05ffff04ff1bffff04ffff10ff17ffff010180ff8080808080808080ff8080ff0180ffff02ff36ffff04ff02ffff04ff05ffff04ffff02ff3effff04ff02ffff04ff0bff80808080ff8080808080ff02ff36ffff04ff02ffff04ff05ffff04ffff02ff3effff04ff02ffff04ff0bff80808080ffff04ffff02ff3effff04ff02ffff04ff05ff80808080ff808080808080ffffff02ffff03ffff07ff0580ffff01ff04ff29ffff02ff26ffff04ff02ffff04ff0dff8080808080ff8080ff0180ff0bff3cffff0bff34ff2880ffff0bff3cffff0bff3cffff0bff34ff2c80ff0580ffff0bff3cffff02ff22ffff04ff02ffff04ff07ffff04ffff0bff34ff3480ff8080808080ffff0bff34ff8080808080ffff02ffff03ffff07ff0b80ffff01ff10ff13ffff02ff2effff04ff02ffff04ff05ffff04ff1bff808080808080ffff011580ff0180ff02ffff03ffff07ff0580ffff01ff0bffff0102ffff02ff3effff04ff02ffff04ff09ff80808080ffff02ff3effff04ff02ffff04ff0dff8080808080ffff01ff0bffff0101ff058080ff0180ff018080
|
||||
@@ -0,0 +1 @@
|
||||
ff02ffff01ff04ffff04ffff013effff04ffff02ff02ffff04ff02ffff04ff05ff80808080ff808080ff8080ffff04ffff01ff02ffff03ffff07ff0580ffff01ff0bffff0102ffff02ff02ffff04ff02ffff04ff09ff80808080ffff02ff02ffff04ff02ffff04ff0dff8080808080ffff01ff0bffff0101ff058080ff0180ff018080
|
||||
@@ -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)))
|
||||
)
|
||||
)
|
||||
)
|
||||
+1
@@ -0,0 +1 @@
|
||||
ff02ffff01ff04ff82027fffff04ff80ffff04ffff02ffff03ff82027fffff01ff04ffff04ff10ffff04ffff0bffff02ff36ffff04ff02ffff04ff09ffff04ff82137fffff04ffff02ff3effff04ff02ffff04ffff04ff09ffff04ff82027fff1d8080ff80808080ff808080808080ff1580ff808080ffff02ff26ffff04ff02ffff04ff0bffff04ff17ffff04ff2fffff04ff5fffff04ff82057fffff04ff15ff80808080808080808080ffff01ff02ff26ffff04ff02ffff04ff0bffff04ff17ffff04ff2fffff04ff5fffff04ff82057fffff04ff15ff80808080808080808080ff0180ff80808080ffff04ffff01ffffff3fff0233ff04ff0101ffff822710ff02ff02ffff03ff05ffff01ff02ff3affff04ff02ffff04ff0dffff04ffff0bff2affff0bff2cff1480ffff0bff2affff0bff2affff0bff2cff3c80ff0980ffff0bff2aff0bffff0bff2cff8080808080ff8080808080ffff010b80ff0180ffffff02ffff03ff5fffff01ff04ffff02ffff03ff82019fffff01ff04ff10ffff04ffff0bffff02ff36ffff04ff02ffff04ff2fffff04ff17ffff04ffff0bffff0101ff82029f80ffff04ffff0bffff0101ff2f80ff80808080808080ffff02ff3effff04ff02ffff04ffff04ff81bfffff04ffff04ff05ffff04ffff05ffff14ffff12ff82011fff0b80ff128080ff808080ff808080ff8080808080ff808080ffff01ff04ff38ffff04ff05ffff04ffff02ff2effff04ff02ffff04ffff05ffff14ffff12ff82011fff0b80ff128080ff80808080ff8080808080ff0180ffff02ff26ffff04ff02ffff04ff05ffff04ff0bffff04ff17ffff04ff2fffff04ff81dfffff04ff81bfff80808080808080808080ff8080ff0180ff0bff2affff0bff2cff2880ffff0bff2affff0bff2affff0bff2cff3c80ff0580ffff0bff2affff02ff3affff04ff02ffff04ff07ffff04ffff0bff2cff2c80ff8080808080ffff0bff2cff8080808080ffff02ffff03ffff18ff05ffff010180ffff01ff11ff05ffff010180ffff010580ff0180ff02ffff03ffff07ff0580ffff01ff0bffff0102ffff02ff3effff04ff02ffff04ff09ff80808080ffff02ff3effff04ff02ffff04ff0dff8080808080ffff01ff0bffff0101ff058080ff0180ff018080
|
||||
+1
@@ -0,0 +1 @@
|
||||
3b0c0f181e51ee219d82eb99a89b31a864802904a18da9352bcf01c3fe52ba8c
|
||||
+112
@@ -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)))
|
||||
|
||||
)
|
||||
+1
@@ -0,0 +1 @@
|
||||
ff02ffff01ff04ff8209ffffff04ff80ffff04ffff02ff26ffff04ff02ffff04ff82017fffff04ff05ffff04ff0bffff04ff17ffff04ff2fffff04ff5fffff04ff81bfffff04ff8202ffffff04ff80ffff04ffff04ff8209ffffff04ff8215ffffff04ff822dffffff04ff825dffff8080808080ff80808080808080808080808080ff80808080ffff04ffff01ffffffff323fff0233ffff0401ff01822710ffffff02ff02ffff03ff05ffff01ff02ff32ffff04ff02ffff04ff0dffff04ffff0bff22ffff0bff34ff2480ffff0bff22ffff0bff22ffff0bff34ff2c80ff0980ffff0bff22ff0bffff0bff34ff8080808080ff8080808080ffff010b80ff0180ffff02ffff03ffff22ff82027fffff20ffff09ff82027fff81bf808080ffff01ff04ffff04ff30ffff04ffff0bffff02ff2effff04ff02ffff04ff09ffff04ff82277fffff04ffff02ff7effff04ff02ffff04ffff04ff09ffff04ff82027fff1d8080ff80808080ff808080808080ff1580ff808080ffff02ff36ffff04ff02ffff04ff0bffff04ff17ffff04ff2fffff04ff5fffff04ff82057fffff04ff15ff80808080808080808080ffff01ff02ff36ffff04ff02ffff04ff0bffff04ff17ffff04ff2fffff04ff5fffff04ff82057fffff04ff15ff80808080808080808080ff0180ff02ffff03ffff09ff820affffff02ff2effff04ff02ffff04ff0bffff04ffff0bff34ff822dff80ff808080808080ffff01ff04ff8202ffffff04ffff04ff20ffff04ff822dffffff04ffff02ff7effff04ff02ffff04ff825dffff80808080ff80808080ffff02ff2affff04ff02ffff04ff17ffff04ff2fffff04ff5fffff04ff81bfffff04ff82017fffff04ff05ffff04ff8205ffff808080808080808080808080ffff01ff088080ff0180ffffff02ffff03ff8202ffffff01ff02ffff03ffff09ff8208ffff3880ffff01ff02ffff03ffff18ff822cffff3480ffff01ff02ff3affff04ff02ffff04ff05ffff04ff0bffff04ff17ffff04ff2fffff04ff5fffff04ff81bfffff04ff82017fffff04ff8204ffffff04ff820bffff808080808080808080808080ffff01ff04ff8204ffffff02ff26ffff04ff02ffff04ff0bffff04ff17ffff04ff2fffff04ff5fffff04ff81bfffff04ff82017fffff04ff05ffff04ff8206ffffff04ff8205ffffff04ff820bffff808080808080808080808080808080ff0180ffff01ff02ff26ffff04ff02ffff04ff05ffff04ff0bffff04ff17ffff04ff2fffff04ff5fffff04ff81bfffff04ff82017fffff04ff8206ffffff04ff8205ffffff04ff820bffff8080808080808080808080808080ff0180ffff01ff088080ff0180ff02ffff03ff5fffff01ff04ffff02ffff03ff82019fffff01ff04ff30ffff04ffff0bffff02ff2effff04ff02ffff04ff2fffff04ff17ffff04ffff0bff34ff82029f80ffff04ffff0bff34ff2f80ff80808080808080ffff02ff7effff04ff02ffff04ffff04ff81bfffff04ffff04ff05ffff04ffff05ffff14ffff12ff82011fff0b80ff3c8080ff808080ff808080ff8080808080ff808080ffff01ff04ff38ffff04ff05ffff04ffff02ff5effff04ff02ffff04ffff05ffff14ffff12ff82011fff0b80ff3c8080ff80808080ff8080808080ff0180ffff02ff36ffff04ff02ffff04ff05ffff04ff0bffff04ff17ffff04ff2fffff04ff81dfffff04ff81bfff80808080808080808080ff8080ff0180ffff0bff22ffff0bff34ff2880ffff0bff22ffff0bff22ffff0bff34ff2c80ff0580ffff0bff22ffff02ff32ffff04ff02ffff04ff07ffff04ffff0bff34ff3480ff8080808080ffff0bff34ff8080808080ffff02ffff03ffff18ff05ff3480ffff01ff11ff05ff3480ffff010580ff0180ff02ffff03ffff07ff0580ffff01ff0bffff0102ffff02ff7effff04ff02ffff04ff09ff80808080ffff02ff7effff04ff02ffff04ff0dff8080808080ffff01ff0bff34ff058080ff0180ff018080
|
||||
+1
@@ -0,0 +1 @@
|
||||
1ba78fd36e36ff5e5ade71864527d2ce3980b4b43ddf7cdc3f714945f4b734a7
|
||||
@@ -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 @@
|
||||
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
|
||||
@@ -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.")
|
||||
|
||||
@@ -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(),
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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()
|
||||
@@ -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")
|
||||
|
||||
Reference in New Issue
Block a user