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