22
33import dataclasses
44from dataclasses import dataclass , field
5- from typing import Self
5+ from typing import ClassVar , Self
66
7+ from chia_rs import G1Element
78from chia_rs .chia_rs import Coin , CoinSpend
89from chia_rs .sized_bytes import bytes32
910from chia_rs .sized_ints import uint32 , uint64
1011
11- from chia .types .blockchain_format .program import Program
12+ from chia .types .blockchain_format .program import Program , run
1213from chia .types .coin_spend import make_spend
1314from chia .wallet .conditions import (
1415 AssertCoinAnnouncement ,
1819 MessageParticipant ,
1920 ReserveFee ,
2021 SendMessage ,
22+ parse_conditions_non_consensus ,
2123)
2224from chia .wallet .lineage_proof import LineageProof
2325from chia .wallet .puzzles .custody .custody_architecture import (
3638 puzzle_for_singleton ,
3739 solution_for_singleton ,
3840)
41+ from chia .wallet .uncurried_puzzle import UncurriedPuzzle , uncurry_puzzle
3942
4043CLAIM_POOL_REWARDS_DELEGATED_PUZZLE = load_clvm_maybe_recompile (
4144 "claim_pool_rewards_dpuz.clsp" , package_or_requirement = "chia.pools"
@@ -83,11 +86,12 @@ class SelfCustody:
8386
8487 def puzzle_with_restrictions (self ) -> PuzzleWithRestrictions :
8588 return PuzzleWithRestrictions (
86- nonce = 0 ,
87- restrictions = [],
88- puzzle = self .member ,
89+ nonce = 0 , restrictions = [], puzzle = self .member , additional_memos = self .memos (nonce = 0 )
8990 )
9091
92+ def memos (self , nonce : int ) -> Program :
93+ return Program .to ([self .member .synthetic_key ])
94+
9195 def puzzle (self , nonce : int ) -> Program :
9296 return self .puzzle_with_restrictions ().puzzle_reveal ()
9397
@@ -199,8 +203,12 @@ def puzzle_with_restrictions(self) -> PuzzleWithRestrictions:
199203 self .pool_puzzle_with_restrictions (),
200204 ],
201205 ),
206+ additional_memos = self .memos (nonce = 0 ),
202207 )
203208
209+ def memos (self , nonce : int ) -> Program :
210+ return Program .to ([self .self_custody .member .synthetic_key , self .pool_puzzle_hash , self .timelock ])
211+
204212 def puzzle (self , nonce : int ) -> Program :
205213 return self .puzzle_with_restrictions ().puzzle_reveal ()
206214
@@ -267,20 +275,21 @@ class PlotNFT:
267275 coin : Coin
268276 singleton_lineage_proof : LineageProof
269277 puzzle : PlotNFTPuzzle
278+ singleton_mod : ClassVar [Program ] = SINGLETON_MOD
279+ singleton_launcher : ClassVar [Program ] = SINGLETON_LAUNCHER
270280
271- @staticmethod
281+ @classmethod
272282 def origin_coin_info (
283+ cls ,
273284 origin_coins : list [Coin ],
274- singleton_mod : Program = SINGLETON_MOD ,
275- singleton_launcher : Program = SINGLETON_LAUNCHER ,
276285 ) -> tuple [Coin , Coin , SingletonStruct ]:
277286 origin_coin = origin_coins [0 ]
278287
279- launcher_hash = singleton_launcher .get_tree_hash ()
288+ launcher_hash = cls . singleton_launcher .get_tree_hash ()
280289 launcher_coin = Coin (origin_coin .name (), launcher_hash , uint64 (1 ))
281290 launcher_id = launcher_coin .name ()
282291 singleton_struct = SingletonStruct (
283- singleton_mod = singleton_mod , launcher_id = launcher_id , singleton_launcher = singleton_launcher
292+ singleton_mod = cls . singleton_mod , launcher_id = launcher_id , singleton_launcher = cls . singleton_launcher
284293 )
285294
286295 return origin_coin , launcher_coin , singleton_struct
@@ -291,33 +300,32 @@ def launch(
291300 * ,
292301 origin_coins : list [Coin ],
293302 custody : SelfCustody | PoolingCustody ,
294- memos : list [bytes ],
295303 fee : uint64 = uint64 (0 ),
296304 extra_conditions : tuple [Condition , ...] = tuple (),
297- singleton_mod : Program = SINGLETON_MOD ,
298- singleton_launcher : Program = SINGLETON_LAUNCHER ,
299305 ) -> tuple [list [Program ], list [CoinSpend ], Self ]:
300- mod_hash = singleton_mod .get_tree_hash ()
301- launcher_hash = singleton_launcher .get_tree_hash ()
302- origin_coin , launcher_coin , singleton_struct = cls .origin_coin_info (
303- origin_coins , singleton_mod , singleton_launcher
304- )
306+ mod_hash = cls .singleton_mod .get_tree_hash ()
307+ launcher_hash = cls .singleton_launcher .get_tree_hash ()
308+ origin_coin , launcher_coin , singleton_struct = cls .origin_coin_info (origin_coins )
305309 launcher_id = launcher_coin .name ()
306310
307311 plotnft_puzzle = PlotNFTPuzzle (singleton_struct = singleton_struct , inner_custody = custody )
308312 rev_puzzle = Program .to (
309313 (
310314 1 ,
311315 [
312- CreateCoin (plotnft_puzzle .inner_custody .puzzle_hash (nonce = 0 ), uint64 (1 ), memos = memos ).to_program (),
316+ CreateCoin (
317+ plotnft_puzzle .inner_custody .puzzle_hash (nonce = 0 ),
318+ uint64 (1 ),
319+ memo_blob = plotnft_puzzle .inner_custody .puzzle_with_restrictions ().memo (),
320+ ).to_program (),
313321 CreateCoinAnnouncement (msg = b"" ).to_program (),
314322 ],
315323 )
316324 )
317325 full_rev_singleton_puzzle = puzzle_for_singleton (
318326 launcher_id ,
319327 rev_puzzle ,
320- singleton_mod = singleton_mod ,
328+ singleton_mod = cls . singleton_mod ,
321329 launcher_hash = launcher_hash ,
322330 singleton_mod_hash = mod_hash ,
323331 )
@@ -335,7 +343,7 @@ def launch(
335343 ]
336344 launcher_spend = make_spend (
337345 launcher_coin ,
338- singleton_launcher ,
346+ cls . singleton_launcher ,
339347 launcher_solution ,
340348 )
341349 rev_spend = make_spend (
@@ -361,6 +369,90 @@ def launch(
361369 ),
362370 )
363371
372+ @classmethod
373+ def get_next_from_coin_spend (
374+ cls ,
375+ * ,
376+ coin_spend : CoinSpend ,
377+ genesis_challenge : bytes32 ,
378+ pre_uncurry : UncurriedPuzzle | None = None ,
379+ ) -> Self :
380+ if pre_uncurry is None :
381+ singleton = uncurry_puzzle (coin_spend .puzzle_reveal )
382+ else :
383+ singleton = pre_uncurry
384+
385+ if singleton .mod != cls .singleton_mod :
386+ raise ValueError ("Invalid singleton mod for next PlotNFT" )
387+ if singleton .args .at ("frr" ) != cls .singleton_launcher .get_tree_hash (): # TODO: optimize
388+ raise ValueError ("Invalid singleton launcher for next PlotNFT" )
389+
390+ singleton_struct = SingletonStruct (
391+ singleton_mod = cls .singleton_mod ,
392+ singleton_launcher = cls .singleton_launcher ,
393+ launcher_id = bytes32 (singleton .args .at ("frf" ).as_atom ()),
394+ )
395+
396+ inner_puzzle = singleton .args .at ("rf" )
397+ inner_conditions = parse_conditions_non_consensus (
398+ run (inner_puzzle , Program .from_serialized (coin_spend .solution ).at ("rrf" )).as_iter ()
399+ )
400+ singleton_create_coin = next (condition for condition in inner_conditions if isinstance (condition , CreateCoin ))
401+ if singleton_create_coin .memo_blob is None :
402+ raise ValueError ("Invalid memoization of PlotNFT" )
403+ unknown_inner_puzzle = PuzzleWithRestrictions .from_memo (singleton_create_coin .memo_blob )
404+ assert unknown_inner_puzzle .additional_memos is not None
405+ self_custody = SelfCustody (
406+ member = BLSWithTaprootMember (
407+ synthetic_key = G1Element .from_bytes (unknown_inner_puzzle .additional_memos .at ("f" ).as_atom ())
408+ )
409+ )
410+ if isinstance (unknown_inner_puzzle .puzzle , MofN ):
411+ unknown_inner_puzzle .fill_in_unknown_puzzles (
412+ {SendMessageBanned ().puzzle_hash (nonce = 0 ): SendMessageBanned ()}
413+ )
414+ pool_puzzle_hash = bytes32 (unknown_inner_puzzle .additional_memos .at ("rf" ).as_atom ())
415+ timelock = uint64 (unknown_inner_puzzle .additional_memos .at ("rrf" ).as_int ())
416+
417+ custody : SelfCustody | PoolingCustody = PoolingCustody (
418+ singleton_struct = singleton_struct ,
419+ self_custody = self_custody ,
420+ pool_puzzle_hash = pool_puzzle_hash ,
421+ reward_puzhash = RewardPuzzle (singleton_id = singleton_struct .launcher_id ).puzzle_hash (),
422+ timelock = timelock ,
423+ exiting = ValidatorStackRestriction (
424+ required_wrappers = [Timelock (timelock ), SendMessageBanned ()]
425+ ).puzzle_hash (nonce = 0 )
426+ in unknown_inner_puzzle .unknown_puzzles ,
427+ genesis_challenge = genesis_challenge ,
428+ )
429+ else :
430+ custody = self_custody
431+
432+ return cls (
433+ coin = Coin (
434+ coin_spend .coin .name (),
435+ puzzle_for_singleton (
436+ launcher_id = singleton_struct .launcher_id ,
437+ inner_puz = custody .puzzle (nonce = 0 ),
438+ # TODO: optimize
439+ singleton_mod = cls .singleton_mod ,
440+ launcher_hash = cls .singleton_launcher .get_tree_hash (),
441+ singleton_mod_hash = cls .singleton_mod .get_tree_hash (),
442+ ).get_tree_hash (),
443+ coin_spend .coin .amount ,
444+ ),
445+ singleton_lineage_proof = LineageProof (
446+ parent_name = coin_spend .coin .parent_coin_info ,
447+ inner_puzzle_hash = inner_puzzle .get_tree_hash (),
448+ amount = coin_spend .coin .amount ,
449+ ),
450+ puzzle = PlotNFTPuzzle (
451+ singleton_struct = singleton_struct ,
452+ inner_custody = custody ,
453+ ),
454+ )
455+
364456 def singleton_action_spend (self , inner_solution : Program ) -> CoinSpend :
365457 return make_spend (
366458 coin = self .coin ,
0 commit comments