Skip to content

Commit

Permalink
Make royalty fields and p2_puzzle updatable, separate logic from the …
Browse files Browse the repository at this point in the history
…immutable launcher

The dynamic launcher is now able to dynamically set both royalty details and the p2_puzzle hash.

This way the precommitted NFTs are basically blank slates and can be filled on demand.
  • Loading branch information
greimela committed Mar 28, 2024
1 parent b13f731 commit cef268f
Show file tree
Hide file tree
Showing 5 changed files with 195 additions and 51 deletions.
15 changes: 7 additions & 8 deletions secure_the_mint/puzzles/secure_the_mint_dynamic_launcher.clsp
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
(mod (SINGLETON_MOD_HASH SINGLETON_LAUNCHER_PUZHASH
NFT_STATE_LAYER_MOD_HASH METADATA_HASH METADATA_UPDATER_PUZZLE_HASH
NFT_STATE_LAYER_MOD_HASH METADATA_UPDATER_PUZZLE_HASH
NFT_OWNERSHIP_LAYER_MOD_HASH
NFT_OWNERSHIP_TRANSFER_PROGRAM_MOD_HASH ROYALTY_ADDRESS TRADE_PRICE_PERCENTAGE
P2_PUZZLE_HASH
NFT_OWNERSHIP_TRANSFER_PROGRAM_MOD_HASH
CREATOR_PUBLIC_KEY
mode ; 1 for mint, 0 for melt
my_id
updated_metadata_hash)
metadata_hash royalty_address trade_price_percentage p2_puzzle_hash)
(include condition_codes.clib)
(include curry-and-treehash.clib)

Expand Down Expand Up @@ -83,18 +82,18 @@
(list
(calculate_full_puzzle_hash
(c SINGLETON_MOD_HASH (c (calculate_coin_id my_id SINGLETON_LAUNCHER_PUZHASH 1) SINGLETON_LAUNCHER_PUZHASH))
NFT_STATE_LAYER_MOD_HASH (if updated_metadata_hash updated_metadata_hash METADATA_HASH) METADATA_UPDATER_PUZZLE_HASH
NFT_STATE_LAYER_MOD_HASH metadata_hash METADATA_UPDATER_PUZZLE_HASH
NFT_OWNERSHIP_LAYER_MOD_HASH
NFT_OWNERSHIP_TRANSFER_PROGRAM_MOD_HASH ROYALTY_ADDRESS TRADE_PRICE_PERCENTAGE
P2_PUZZLE_HASH
NFT_OWNERSHIP_TRANSFER_PROGRAM_MOD_HASH royalty_address trade_price_percentage
p2_puzzle_hash
)
1
()
)
)
)
)
(if updated_metadata_hash (list AGG_SIG_ME CREATOR_PUBLIC_KEY updated_metadata_hash) (list REMARK))
(list AGG_SIG_ME CREATOR_PUBLIC_KEY (sha256 metadata_hash royalty_address trade_price_percentage p2_puzzle_hash))
)
(list ; melt
(list AGG_SIG_ME CREATOR_PUBLIC_KEY 1)
Expand Down
Original file line number Diff line number Diff line change
@@ -1 +1 @@
ff02ffff01ff02ffff03ff822fffffff01ff04ffff04ff38ffff04ff825fffff808080ffff04ffff04ff34ffff04ff0bffff01ff01808080ffff04ffff04ff28ffff04ffff0bffff02ff36ffff04ff02ffff04ff825fffffff04ff0bffff01ff018080808080ffff02ff3effff04ff02ffff04ffff04ffff02ff2effff04ff02ffff04ff05ffff04ffff02ff2effff04ff02ffff04ff17ffff04ffff02ff2effff04ff02ffff04ff81bfffff04ff820bffffff04ffff02ff2effff04ff02ffff04ff82017fffff04ffff0bff3cff8205ff80ffff04ffff0bff3cff8202ff80ffff04ffff02ff3effff04ff02ffff04ffff04ff05ffff04ffff02ff36ffff04ff02ffff04ff825fffffff04ff0bffff01ff018080808080ff0b8080ff80808080ff80808080808080ffff04ffff0bff3cff8080ffff04ffff0bff3cff81bf80ff8080808080808080ffff04ffff0bff3cff5f80ffff04ffff02ffff03ff82bfffffff0182bfffffff012f80ff0180ffff04ffff0bff3cff1780ff8080808080808080ffff04ffff02ff3effff04ff02ffff04ffff04ff05ffff04ffff02ff36ffff04ff02ffff04ff825fffffff04ff0bffff01ff018080808080ff0b8080ff80808080ff808080808080ffff01ff01ff808080ff8080808080ff808080ffff04ffff02ffff03ff82bfffffff01ff04ff10ffff04ff8217ffffff04ff82bfffff80808080ffff01ff04ff32ff808080ff0180ff8080808080ffff01ff04ffff04ff10ffff04ff8217ffffff01ff01808080ff808080ff0180ffff04ffff01ffffff32ff3d46ffff0233ff0401ffffff0101ff0220ffffff02ffff03ff05ffff01ff02ff26ffff04ff02ffff04ff0dffff04ffff0bff2affff0bff3cff2c80ffff0bff2affff0bff2affff0bff3cff2280ff0980ffff0bff2aff0bffff0bff3cff8080808080ff8080808080ffff010b80ff0180ff02ffff03ffff22ffff09ffff0dff0580ff3a80ffff09ffff0dff0b80ff3a80ffff15ff17ffff0181ff8080ffff01ff0bff05ff0bff1780ffff01ff088080ff0180ffff0bff2affff0bff3cff2480ffff0bff2affff0bff2affff0bff3cff2280ff0580ffff0bff2affff02ff26ffff04ff02ffff04ff07ffff04ffff0bff3cff3c80ff8080808080ffff0bff3cff8080808080ff02ffff03ffff07ff0580ffff01ff0bffff0102ffff02ff3effff04ff02ffff04ff09ff80808080ffff02ff3effff04ff02ffff04ff0dff8080808080ffff01ff0bffff0101ff058080ff0180ff018080
ff02ffff01ff02ffff03ff8202ffffff01ff04ffff04ff38ffff04ff8205ffff808080ffff04ffff04ff34ffff04ff0bffff01ff01808080ffff04ffff04ff28ffff04ffff0bffff02ff36ffff04ff02ffff04ff8205ffffff04ff0bffff01ff018080808080ffff02ff3effff04ff02ffff04ffff04ffff02ff2effff04ff02ffff04ff05ffff04ffff02ff2effff04ff02ffff04ff17ffff04ffff02ff2effff04ff02ffff04ff5fffff04ff825fffffff04ffff02ff2effff04ff02ffff04ff81bfffff04ffff0bff3cff822fff80ffff04ffff0bff3cff8217ff80ffff04ffff02ff3effff04ff02ffff04ffff04ff05ffff04ffff02ff36ffff04ff02ffff04ff8205ffffff04ff0bffff01ff018080808080ff0b8080ff80808080ff80808080808080ffff04ffff0bff3cff8080ffff04ffff0bff3cff5f80ff8080808080808080ffff04ffff0bff3cff2f80ffff04ff820bffffff04ffff0bff3cff1780ff8080808080808080ffff04ffff02ff3effff04ff02ffff04ffff04ff05ffff04ffff02ff36ffff04ff02ffff04ff8205ffffff04ff0bffff01ff018080808080ff0b8080ff80808080ff808080808080ffff01ff01ff808080ff8080808080ff808080ffff04ffff04ff10ffff04ff82017fffff04ffff0bff820bffff8217ffff822fffff825fff80ff80808080ff8080808080ffff01ff04ffff04ff10ffff04ff82017fffff01ff01808080ff808080ff0180ffff04ffff01ffffff32ff3d46ffff0233ff0401ffff01ff0220ffffff02ffff03ff05ffff01ff02ff26ffff04ff02ffff04ff0dffff04ffff0bff2affff0bff3cff2c80ffff0bff2affff0bff2affff0bff3cff1280ff0980ffff0bff2aff0bffff0bff3cff8080808080ff8080808080ffff010b80ff0180ff02ffff03ffff22ffff09ffff0dff0580ff3a80ffff09ffff0dff0b80ff3a80ffff15ff17ffff0181ff8080ffff01ff0bff05ff0bff1780ffff01ff088080ff0180ffff0bff2affff0bff3cff2480ffff0bff2affff0bff2affff0bff3cff1280ff0580ffff0bff2affff02ff26ffff04ff02ffff04ff07ffff04ffff0bff3cff3c80ff8080808080ffff0bff3cff8080808080ff02ffff03ffff07ff0580ffff01ff0bffff0102ffff02ff3effff04ff02ffff04ff09ff80808080ffff02ff3effff04ff02ffff04ff0dff8080808080ffff01ff0bffff0101ff058080ff0180ff018080
182 changes: 163 additions & 19 deletions secure_the_mint/secure_the_mint.py
Original file line number Diff line number Diff line change
Expand Up @@ -119,11 +119,10 @@ def get_nft_puzzle(
self,
launcher_coin: Coin,
p2_puzzle: Program,
updated_metadata: Optional[Program] = None,
) -> Program:
return nft_puzzles.create_full_puzzle(
launcher_coin.name(),
updated_metadata or self.metadata,
self.metadata,
NFT_METADATA_UPDATER.get_tree_hash(),
create_ownership_layer_puzzle(
launcher_coin.name(),
Expand All @@ -137,7 +136,113 @@ def get_nft_puzzle(
def to_coin_spends(
self,
pre_launcher_parent_id: bytes32,
updated_metadata: Optional[Program] = None,
) -> List[CoinSpend]:
amount = uint64(1)
pre_launcher_coin = Coin(
pre_launcher_parent_id, self.pre_launcher_puzzle.get_tree_hash(), amount
)
mode = 1 # 1 for mint, 0 for melt
pre_launcher_solution = Program.to(([mode, pre_launcher_coin.name()]))
pre_launcher_spend = CoinSpend(
pre_launcher_coin,
self.pre_launcher_puzzle,
pre_launcher_solution,
)
launcher_coin = Coin(
pre_launcher_coin.name(), SINGLETON_LAUNCHER_PUZZLE_HASH, amount
)
eve_puzzle = self.get_nft_puzzle(launcher_coin, self.eve_p2_puzzle)

launcher_solution = Program.to([eve_puzzle.get_tree_hash(), amount, []])
launcher_spend = CoinSpend(
launcher_coin, SINGLETON_LAUNCHER_PUZZLE, launcher_solution
)

eve_coin = Coin(launcher_coin.name(), eve_puzzle.get_tree_hash(), amount)
innersol = Program.to([eve_coin.name()])
ownership_layer_solution = Program.to([innersol]) # supports DID
nft_layer_solution = Program.to([ownership_layer_solution])
singleton_solution = Program.to(
[
[launcher_coin.parent_coin_info, uint64(launcher_coin.amount)],
amount,
nft_layer_solution,
]
)

eve_spend = CoinSpend(eve_coin, eve_puzzle, singleton_solution)

return [pre_launcher_spend, launcher_spend, eve_spend]

def to_offer(
self,
pre_launcher_parent_id: bytes32,
) -> Offer:
if self.requested_payments is None:
raise Exception("This target does not request a payment")

coin_spends = self.to_coin_spends(pre_launcher_parent_id)

launcher_coin = coin_spends[1].coin
eve_coin = coin_spends[2].coin
notarized_payments: Dict[Optional[bytes32], List[NotarizedPayment]] = {}
for asset_id, payments in self.requested_payments.items():
assert not asset_id # Only XCH payments for now
notarized_payments[asset_id] = []
for p in payments:
puzzle_hash, amount, memos = tuple(p.as_condition_args())
notarized_payments[asset_id].append(
NotarizedPayment(puzzle_hash, amount, memos, eve_coin.name())
)

bundle = SpendBundle(coin_spends, G2Element())
puzzle_info: Optional[PuzzleInfo] = match_puzzle(
uncurry_puzzle(coin_spends[2].puzzle_reveal)
)
offer = Offer(notarized_payments, bundle, {launcher_coin.name(): puzzle_info})
return offer


class DynamicMintSpends:
pre_launcher_puzzle: Program
creator_puzzle_hash: bytes32

def __init__(
self,
pre_launcher_puzzle: Program,
creator_puzzle_hash: bytes32,
) -> None:
self.pre_launcher_puzzle = pre_launcher_puzzle
self.creator_puzzle_hash = creator_puzzle_hash

def get_nft_puzzle(
self,
launcher_coin: Coin,
metadata: Program,
royalty_percentage: uint16,
royalty_puzzle_hash: bytes32,
p2_puzzle: Program,
) -> Program:
return nft_puzzles.create_full_puzzle(
launcher_coin.name(),
metadata,
NFT_METADATA_UPDATER.get_tree_hash(),
create_ownership_layer_puzzle(
launcher_coin.name(),
b"",
p2_puzzle,
royalty_percentage,
royalty_puzzle_hash=royalty_puzzle_hash,
),
)

def to_coin_spends(
self,
pre_launcher_parent_id: bytes32,
metadata: Program,
royalty_percentage_times_100: uint16,
royalty_puzzle_hash: bytes32,
p2_puzzle: Program,
) -> List[CoinSpend]:
amount = uint64(1)
pre_launcher_coin = Coin(
Expand All @@ -146,8 +251,14 @@ def to_coin_spends(
mode = 1 # 1 for mint, 0 for melt
pre_launcher_solution = Program.to(
(
[mode, pre_launcher_coin.name()]
+ ([updated_metadata.get_tree_hash()] if updated_metadata else [])
[
mode,
pre_launcher_coin.name(),
metadata.get_tree_hash(),
royalty_puzzle_hash,
royalty_percentage_times_100,
p2_puzzle.get_tree_hash(),
]
)
)
pre_launcher_spend = CoinSpend(
Expand All @@ -159,7 +270,11 @@ def to_coin_spends(
pre_launcher_coin.name(), SINGLETON_LAUNCHER_PUZZLE_HASH, amount
)
eve_puzzle = self.get_nft_puzzle(
launcher_coin, self.eve_p2_puzzle, updated_metadata
launcher_coin,
metadata,
royalty_percentage_times_100,
royalty_puzzle_hash,
p2_puzzle,
)

launcher_solution = Program.to([eve_puzzle.get_tree_hash(), amount, []])
Expand All @@ -186,18 +301,24 @@ def to_coin_spends(
def to_offer(
self,
pre_launcher_parent_id: bytes32,
updated_metadata: Optional[Program] = None,
creator_signature: Optional[G2Element] = None,
metadata: Program,
royalty_percentage_times_100: uint16,
royalty_puzzle_hash: bytes32,
p2_puzzle: Program,
requested_payments: Dict[Optional[bytes32], List[Payment]],
) -> Offer:
if self.requested_payments is None:
raise Exception("This target does not request a payment")

coin_spends = self.to_coin_spends(pre_launcher_parent_id, updated_metadata)
coin_spends = self.to_coin_spends(
pre_launcher_parent_id,
metadata,
royalty_percentage_times_100,
royalty_puzzle_hash,
p2_puzzle,
)

launcher_coin = coin_spends[1].coin
eve_coin = coin_spends[2].coin
notarized_payments: Dict[Optional[bytes32], List[NotarizedPayment]] = {}
for asset_id, payments in self.requested_payments.items():
for asset_id, payments in requested_payments.items():
assert not asset_id # Only XCH payments for now
notarized_payments[asset_id] = []
for p in payments:
Expand All @@ -206,7 +327,7 @@ def to_offer(
NotarizedPayment(puzzle_hash, amount, memos, eve_coin.name())
)

bundle = SpendBundle(coin_spends, creator_signature or G2Element())
bundle = SpendBundle(coin_spends, G2Element())
puzzle_info: Optional[PuzzleInfo] = match_puzzle(
uncurry_puzzle(coin_spends[2].puzzle_reveal)
)
Expand Down Expand Up @@ -316,7 +437,6 @@ def read_secure_the_bag_targets(
royalty_percentage_times_100: uint16,
creator_public_key: Optional[bytes32] = None,
requested_mojos: Optional[uint64] = None,
allow_update_on_mint: bool = False,
) -> Tuple[List[Target], Dict[bytes32, MintSpends]]:
targets: List[Target] = []
mint_spends: Dict[bytes32, MintSpends] = {}
Expand Down Expand Up @@ -362,10 +482,7 @@ def read_secure_the_bag_targets(
requested_payments = None
p2_puzzle = DIRECT_DELEGATE.curry(target_puzzle_hash)

pre_launcher_mod = (
DYNAMIC_PRE_LAUNCHER_MOD if allow_update_on_mint else PRE_LAUNCHER_MOD
)
pre_launcher_puzzle = pre_launcher_mod.curry(
pre_launcher_puzzle = PRE_LAUNCHER_MOD.curry(
SINGLETON_MOD_HASH,
LAUNCHER_PUZZLE_HASH,
NFT_STATE_LAYER_MOD_HASH,
Expand All @@ -392,6 +509,33 @@ def read_secure_the_bag_targets(
return targets, mint_spends


def create_dynamic_launcher_targets(
creator_public_key: bytes32,
count: int,
) -> Tuple[List[Target], Dict[bytes32, DynamicMintSpends]]:
mint_spends: Dict[bytes32, DynamicMintSpends] = {}
targets: List[Target] = []

for i in range(count):
pre_launcher_puzzle = DYNAMIC_PRE_LAUNCHER_MOD.curry(
SINGLETON_MOD_HASH,
LAUNCHER_PUZZLE_HASH,
NFT_STATE_LAYER_MOD_HASH,
NFT_METADATA_UPDATER.get_tree_hash(),
NFT_OWNERSHIP_LAYER_HASH,
NFT_TRANSFER_PROGRAM_DEFAULT.get_tree_hash(),
creator_public_key,
)
pre_launcher_target = Target(pre_launcher_puzzle.get_tree_hash(), uint64(1))
targets.append(pre_launcher_target)
mint_spends[pre_launcher_puzzle.get_tree_hash()] = DynamicMintSpends(
pre_launcher_puzzle,
creator_public_key,
)

return targets, mint_spends


def read_metadata_csv(
file_path: str,
has_header: Optional[bool] = False,
Expand Down
4 changes: 0 additions & 4 deletions tests/secure_the_mint/metadata_updated.csv

This file was deleted.

43 changes: 24 additions & 19 deletions tests/secure_the_mint/test_secure_the_mint.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@
parent_of_puzzle_hash,
read_secure_the_bag_targets,
secure_the_bag,
create_dynamic_launcher_targets,
DynamicMintSpends,
)


Expand Down Expand Up @@ -478,45 +480,41 @@ def test_dynamic_read_secure_the_bag_targets() -> None:
target_puzzle_hash = bytes32.fromhex(
"4bc6435b409bcbabe53870dae0f03755f6aabb4594c5915ec983acf12a5d1fba"
)
melt_public_key = bytes32.fromhex(
creator_public_key = bytes32.fromhex(
"4bc6435b409bcbabe53870dae0f03755f6aabb4594c5915ec983acf12a5d1fba"
)
targets, mint_spends = read_secure_the_bag_targets(
"./tests/secure_the_mint/metadata.csv",
target_puzzle_hash,
target_puzzle_hash,
uint16(5 * 100),
melt_public_key,
requested_mojos,
allow_update_on_mint=True,
)
targets, mint_spends = create_dynamic_launcher_targets(creator_public_key, 3)

updated_targets, updated_mint_spends = read_secure_the_bag_targets(
"./tests/secure_the_mint/metadata_updated.csv",
"./tests/secure_the_mint/metadata.csv",
target_puzzle_hash,
target_puzzle_hash,
uint16(5 * 100),
melt_public_key,
creator_public_key,
requested_mojos,
allow_update_on_mint=True,
)

assert len(targets) == 3
assert len(mint_spends) == 3

pre_launcher_parent_id = bytes32.fromhex(
"f3153d27c1d14581971203f10082fa2db2fbc0fd786a9b210e43f227eca499b5"
)

mint_spend_0 = mint_spends.get(targets[0].puzzle_hash)
updated_metadata = updated_mint_spends[updated_targets[0].puzzle_hash].metadata
coin_spends_0 = mint_spend_0.to_coin_spends(pre_launcher_parent_id, updated_metadata)
updated_mint_spend_0 = updated_mint_spends[updated_targets[0].puzzle_hash]
coin_spends_0 = mint_spend_0.to_coin_spends(
pre_launcher_parent_id,
updated_mint_spend_0.metadata,
updated_mint_spend_0.royalty_percentage,
updated_mint_spend_0.royalty_puzzle_hash,
updated_mint_spend_0.eve_p2_puzzle,
)
pre_launcher_spend = coin_spends_0[0]
assert pre_launcher_spend.coin.parent_coin_info == pre_launcher_parent_id
assert pre_launcher_spend.coin.amount == 1
if requested_mojos:
assert bytes32(pre_launcher_spend.coin.puzzle_hash) == bytes32.fromhex(
"b8d65b74b86cb863dc97aed08f65a7bff8666614fd02b08053a6bebc88ff6c79"
"cefcec774cba59261a670071285ae7ee14f3f10c69988e2b001434dc686c87c7"
)
else:
assert bytes32(pre_launcher_spend.coin.puzzle_hash) == bytes32.fromhex(
Expand All @@ -542,7 +540,7 @@ def test_dynamic_read_secure_the_bag_targets() -> None:
assert eve_spend.coin.amount == 1

uncurried_nft = UncurriedNFT.uncurry(*eve_spend.puzzle_reveal.uncurry())
assert uncurried_nft.metadata == updated_mint_spends[updated_targets[0].puzzle_hash].metadata
assert uncurried_nft.metadata == updated_mint_spend_0.metadata

_, pre_launcher_conditions = pre_launcher_spend.puzzle_reveal.run_with_cost(
INFINITE_COST, pre_launcher_spend.solution
Expand All @@ -566,7 +564,14 @@ def test_dynamic_read_secure_the_bag_targets() -> None:
)

if requested_mojos:
offer = mint_spend_0.to_offer(pre_launcher_parent_id, updated_metadata)
offer = mint_spend_0.to_offer(
pre_launcher_parent_id,
updated_mint_spend_0.metadata,
updated_mint_spend_0.royalty_percentage,
updated_mint_spend_0.royalty_puzzle_hash,
updated_mint_spend_0.eve_p2_puzzle,
updated_mint_spend_0.requested_payments,
)
assert len(offer.requested_payments[None]) == 1
assert offer.requested_payments[None][0].amount == requested_mojos
assert offer.requested_payments[None][0].puzzle_hash == target_puzzle_hash
Expand Down

0 comments on commit cef268f

Please sign in to comment.