chia_sdk_driver/layers/
royalty_transfer_layer.rs

1use std::convert::Infallible;
2
3use chia_protocol::Bytes32;
4use chia_puzzle_types::{nft::NftRoyaltyTransferPuzzleArgs, singleton::SingletonStruct};
5use chia_puzzles::{
6    NFT_OWNERSHIP_TRANSFER_PROGRAM_ONE_WAY_CLAIM_WITH_ROYALTIES_HASH, SINGLETON_LAUNCHER_HASH,
7    SINGLETON_TOP_LAYER_V1_1_HASH,
8};
9use clvm_traits::FromClvm;
10use clvm_utils::{ToTreeHash, TreeHash};
11use clvmr::{Allocator, NodePtr};
12
13use crate::{DriverError, Layer, Puzzle, SpendContext};
14
15/// The royalty transfer [`Layer`] is used to transfer NFTs with royalties.
16/// When an NFT is transferred, a percentage of the transfer amount is paid to an address.
17/// This address can for example be the creator, or a royalty split puzzle.
18#[derive(Debug, Clone, Copy, PartialEq, Eq)]
19pub struct RoyaltyTransferLayer {
20    /// The launcher id of the NFT this transfer program belongs to.
21    pub launcher_id: Bytes32,
22    /// The puzzle hash that receives royalties paid when transferring this NFT.
23    pub royalty_puzzle_hash: Bytes32,
24    /// The percentage of the transfer amount that is paid as royalties.
25    /// This is represented in ten thousandths, so a value of 300 means 3%.
26    pub royalty_basis_points: u16,
27}
28
29impl RoyaltyTransferLayer {
30    pub fn new(
31        launcher_id: Bytes32,
32        royalty_puzzle_hash: Bytes32,
33        royalty_basis_points: u16,
34    ) -> Self {
35        Self {
36            launcher_id,
37            royalty_puzzle_hash,
38            royalty_basis_points,
39        }
40    }
41}
42
43impl Layer for RoyaltyTransferLayer {
44    type Solution = Infallible;
45
46    fn construct_puzzle(&self, ctx: &mut SpendContext) -> Result<NodePtr, DriverError> {
47        ctx.curry(NftRoyaltyTransferPuzzleArgs {
48            singleton_struct: SingletonStruct::new(self.launcher_id),
49            royalty_puzzle_hash: self.royalty_puzzle_hash,
50            royalty_ten_thousandths: self.royalty_basis_points,
51        })
52    }
53
54    fn parse_puzzle(allocator: &Allocator, puzzle: Puzzle) -> Result<Option<Self>, DriverError> {
55        let Some(puzzle) = puzzle.as_curried() else {
56            return Ok(None);
57        };
58
59        if puzzle.mod_hash
60            != NFT_OWNERSHIP_TRANSFER_PROGRAM_ONE_WAY_CLAIM_WITH_ROYALTIES_HASH.into()
61        {
62            return Ok(None);
63        }
64
65        let args = NftRoyaltyTransferPuzzleArgs::from_clvm(allocator, puzzle.args)?;
66
67        if args.singleton_struct.mod_hash != SINGLETON_TOP_LAYER_V1_1_HASH.into()
68            || args.singleton_struct.launcher_puzzle_hash != SINGLETON_LAUNCHER_HASH.into()
69        {
70            return Err(DriverError::InvalidSingletonStruct);
71        }
72
73        Ok(Some(Self {
74            launcher_id: args.singleton_struct.launcher_id,
75            royalty_puzzle_hash: args.royalty_puzzle_hash,
76            royalty_basis_points: args.royalty_ten_thousandths,
77        }))
78    }
79
80    fn construct_solution(
81        &self,
82        _ctx: &mut SpendContext,
83        _solution: Self::Solution,
84    ) -> Result<NodePtr, DriverError> {
85        panic!("RoyaltyTransferLayer does not have a solution");
86    }
87
88    fn parse_solution(
89        _allocator: &clvmr::Allocator,
90        _solution: NodePtr,
91    ) -> Result<Self::Solution, DriverError> {
92        panic!("RoyaltyTransferLayer does not have a solution");
93    }
94}
95
96impl ToTreeHash for RoyaltyTransferLayer {
97    fn tree_hash(&self) -> TreeHash {
98        NftRoyaltyTransferPuzzleArgs::curry_tree_hash(
99            self.launcher_id,
100            self.royalty_puzzle_hash,
101            self.royalty_basis_points,
102        )
103    }
104}