chia_sdk_driver/primitives/nft/
nft_launcher.rs

1use chia_protocol::Bytes32;
2use chia_puzzle_types::{EveProof, Proof};
3use chia_sdk_types::Conditions;
4use clvm_traits::clvm_quote;
5use clvmr::NodePtr;
6
7use crate::{
8    DriverError, HashedPtr, Launcher, SingletonInfo, Spend, SpendContext,
9    assignment_puzzle_announcement_id,
10};
11
12use super::{Nft, NftInfo, NftMint};
13
14impl Launcher {
15    pub fn mint_eve_nft(
16        self,
17        ctx: &mut SpendContext,
18        p2_puzzle_hash: Bytes32,
19        metadata: HashedPtr,
20        metadata_updater_puzzle_hash: Bytes32,
21        royalty_puzzle_hash: Bytes32,
22        royalty_basis_points: u16,
23    ) -> Result<(Conditions, Nft), DriverError> {
24        let launcher_coin = self.coin();
25
26        let nft_info = NftInfo::new(
27            launcher_coin.coin_id(),
28            metadata,
29            metadata_updater_puzzle_hash,
30            None,
31            royalty_puzzle_hash,
32            royalty_basis_points,
33            p2_puzzle_hash,
34        );
35
36        let inner_puzzle_hash = nft_info.inner_puzzle_hash();
37        let (launch_singleton, eve_coin) = self.spend(ctx, inner_puzzle_hash.into(), ())?;
38
39        let proof = Proof::Eve(EveProof {
40            parent_parent_coin_info: launcher_coin.parent_coin_info,
41            parent_amount: launcher_coin.amount,
42        });
43
44        Ok((
45            launch_singleton.create_puzzle_announcement(launcher_coin.coin_id().to_vec().into()),
46            Nft::new(eve_coin, proof, nft_info),
47        ))
48    }
49
50    pub fn mint_nft(
51        self,
52        ctx: &mut SpendContext,
53        mint: &NftMint,
54    ) -> Result<(Conditions, Nft), DriverError> {
55        let memos = ctx.hint(mint.p2_puzzle_hash)?;
56        let conditions = Conditions::new()
57            .create_coin(mint.p2_puzzle_hash, self.singleton_amount(), memos)
58            .extend(mint.transfer_condition.clone());
59
60        let inner_puzzle = ctx.alloc(&clvm_quote!(conditions))?;
61        let p2_puzzle_hash = ctx.tree_hash(inner_puzzle).into();
62        let inner_spend = Spend::new(inner_puzzle, NodePtr::NIL);
63
64        let (mint_eve_nft, eve_nft) = self.mint_eve_nft(
65            ctx,
66            p2_puzzle_hash,
67            mint.metadata,
68            mint.metadata_updater_puzzle_hash,
69            mint.royalty_puzzle_hash,
70            mint.royalty_basis_points,
71        )?;
72
73        let child = eve_nft.spend(ctx, inner_spend)?;
74
75        let mut did_conditions = Conditions::new();
76
77        if let Some(transfer_condition) = mint.transfer_condition.clone() {
78            did_conditions = did_conditions.assert_puzzle_announcement(
79                assignment_puzzle_announcement_id(eve_nft.coin.puzzle_hash, &transfer_condition),
80            );
81        }
82
83        Ok((mint_eve_nft.extend(did_conditions), child))
84    }
85}
86
87#[cfg(test)]
88mod tests {
89    use crate::{IntermediateLauncher, Launcher, SingletonInfo, StandardLayer};
90
91    use super::*;
92
93    use chia_consensus::spendbundle_conditions::get_conditions_from_spendbundle;
94    use chia_protocol::{Coin, SpendBundle};
95    use chia_puzzle_types::{Memos, nft::NftMetadata, standard::StandardArgs};
96    use chia_sdk_test::{BlsPair, Simulator, sign_transaction};
97    use chia_sdk_types::{TESTNET11_CONSTANTS, announcement_id, conditions::TransferNft};
98
99    #[test]
100    fn test_nft_mint_cost() -> anyhow::Result<()> {
101        let ctx = &mut SpendContext::new();
102
103        let alice = BlsPair::default();
104        let p2 = StandardLayer::new(alice.pk);
105
106        let coin = Coin::new(Bytes32::new([0; 32]), alice.puzzle_hash, 1);
107
108        let (create_did, did) = Launcher::new(coin.coin_id(), 1).create_simple_did(ctx, &p2)?;
109        p2.spend(ctx, coin, create_did)?;
110
111        // We don't want to count the DID creation.
112        ctx.take();
113
114        let metadata = ctx.alloc_hashed(&NftMetadata::default())?;
115
116        let coin = Coin::new(Bytes32::new([1; 32]), alice.puzzle_hash, 1);
117        let (mint_nft, _nft) = IntermediateLauncher::new(did.coin.coin_id(), 0, 1)
118            .create(ctx)?
119            .mint_nft(ctx, &NftMint::new(metadata, alice.puzzle_hash, 300, None))?;
120
121        let _ = did.update(
122            ctx,
123            &p2,
124            mint_nft.create_coin_announcement(b"$".to_vec().into()),
125        )?;
126
127        p2.spend(
128            ctx,
129            coin,
130            Conditions::new().assert_coin_announcement(announcement_id(did.coin.coin_id(), "$")),
131        )?;
132
133        let coin_spends = ctx.take();
134        let signature = sign_transaction(&coin_spends, &[alice.sk])?;
135        let spend_bundle = SpendBundle::new(coin_spends, signature);
136
137        let conds = get_conditions_from_spendbundle(
138            ctx,
139            &spend_bundle,
140            u64::MAX,
141            100_000_000,
142            &TESTNET11_CONSTANTS,
143        )?;
144
145        assert_eq!(conds.cost, 109_517_025);
146
147        Ok(())
148    }
149
150    #[test]
151    fn test_bulk_mint() -> anyhow::Result<()> {
152        let mut sim = Simulator::new();
153        let ctx = &mut SpendContext::new();
154
155        let alice = sim.bls(3);
156        let alice_p2 = StandardLayer::new(alice.pk);
157
158        let puzzle_hash = StandardArgs::curry_tree_hash(alice.pk).into();
159
160        let (create_did, did) =
161            Launcher::new(alice.coin.coin_id(), 1).create_simple_did(ctx, &alice_p2)?;
162        alice_p2.spend(ctx, alice.coin, create_did)?;
163
164        let metadata = ctx.alloc_hashed(&NftMetadata::default())?;
165
166        let mint = NftMint::new(
167            metadata,
168            puzzle_hash,
169            300,
170            Some(TransferNft::new(
171                Some(did.info.launcher_id),
172                Vec::new(),
173                Some(did.info.inner_puzzle_hash().into()),
174            )),
175        );
176
177        let mint_1 = IntermediateLauncher::new(did.coin.coin_id(), 0, 2)
178            .create(ctx)?
179            .mint_nft(ctx, &mint)?
180            .0;
181
182        let mint_2 = IntermediateLauncher::new(did.coin.coin_id(), 1, 2)
183            .create(ctx)?
184            .mint_nft(ctx, &mint)?
185            .0;
186
187        let _ = did.update(ctx, &alice_p2, mint_1.extend(mint_2))?;
188
189        sim.spend_coins(ctx.take(), &[alice.sk])?;
190
191        Ok(())
192    }
193
194    #[test]
195    fn test_nonstandard_intermediate_mint() -> anyhow::Result<()> {
196        let mut sim = Simulator::new();
197        let ctx = &mut SpendContext::new();
198
199        let alice = sim.bls(3);
200        let alice_p2 = StandardLayer::new(alice.pk);
201
202        let (create_did, did) =
203            Launcher::new(alice.coin.coin_id(), 1).create_simple_did(ctx, &alice_p2)?;
204        alice_p2.spend(ctx, alice.coin, create_did)?;
205
206        let intermediate_coin = Coin::new(did.coin.coin_id(), alice.puzzle_hash, 0);
207
208        let (create_launcher, launcher) = Launcher::create_early(intermediate_coin.coin_id(), 1);
209
210        let metadata = ctx.alloc_hashed(&NftMetadata::default())?;
211
212        let mint = NftMint::new(
213            metadata,
214            alice.puzzle_hash,
215            300,
216            Some(TransferNft::new(
217                Some(did.info.launcher_id),
218                Vec::new(),
219                Some(did.info.inner_puzzle_hash().into()),
220            )),
221        );
222
223        let (mint_nft, _nft) = launcher.mint_nft(ctx, &mint)?;
224
225        let _ = did.update(
226            ctx,
227            &alice_p2,
228            mint_nft.create_coin(alice.puzzle_hash, 0, Memos::None),
229        )?;
230        alice_p2.spend(
231            ctx,
232            intermediate_coin,
233            Conditions::new().with(create_launcher),
234        )?;
235
236        sim.spend_coins(ctx.take(), &[alice.sk])?;
237
238        Ok(())
239    }
240
241    #[test]
242    fn test_nonstandard_intermediate_mint_recreated_did() -> anyhow::Result<()> {
243        let mut sim = Simulator::new();
244        let ctx = &mut SpendContext::new();
245
246        let alice = sim.bls(3);
247        let alice_p2 = StandardLayer::new(alice.pk);
248
249        let (create_did, did) =
250            Launcher::new(alice.coin.coin_id(), 1).create_simple_did(ctx, &alice_p2)?;
251        alice_p2.spend(ctx, alice.coin, create_did)?;
252
253        let intermediate_coin = Coin::new(did.coin.coin_id(), alice.puzzle_hash, 0);
254
255        let (create_launcher, launcher) = Launcher::create_early(intermediate_coin.coin_id(), 1);
256        alice_p2.spend(
257            ctx,
258            intermediate_coin,
259            Conditions::new().with(create_launcher),
260        )?;
261
262        let metadata = ctx.alloc_hashed(&NftMetadata::default())?;
263
264        let mint = NftMint::new(
265            metadata,
266            alice.puzzle_hash,
267            300,
268            Some(TransferNft::new(
269                Some(did.info.launcher_id),
270                Vec::new(),
271                Some(did.info.inner_puzzle_hash().into()),
272            )),
273        );
274
275        let (mint_nft, _nft_info) = launcher.mint_nft(ctx, &mint)?;
276
277        let did = did.update(
278            ctx,
279            &alice_p2,
280            Conditions::new().create_coin(alice.puzzle_hash, 0, Memos::None),
281        )?;
282
283        let _ = did.update(ctx, &alice_p2, mint_nft)?;
284
285        sim.spend_coins(ctx.take(), &[alice.sk])?;
286
287        Ok(())
288    }
289}