chia_sdk_driver/actions/
mint_nft.rs

1use chia_protocol::Bytes32;
2
3use crate::{
4    Asset, Deltas, DriverError, HashedPtr, Id, SingletonSpends, SpendAction, SpendContext,
5    SpendKind, Spends,
6};
7
8#[derive(Debug, Clone, Copy)]
9pub struct MintNftAction {
10    pub parent_id: Id,
11    pub metadata: HashedPtr,
12    pub metadata_updater_puzzle_hash: Bytes32,
13    pub royalty_puzzle_hash: Bytes32,
14    pub royalty_basis_points: u16,
15    pub amount: u64,
16}
17
18impl MintNftAction {
19    pub fn new(
20        parent_id: Id,
21        metadata: HashedPtr,
22        metadata_updater_puzzle_hash: Bytes32,
23        royalty_puzzle_hash: Bytes32,
24        royalty_basis_points: u16,
25        amount: u64,
26    ) -> Self {
27        Self {
28            parent_id,
29            metadata,
30            metadata_updater_puzzle_hash,
31            royalty_puzzle_hash,
32            royalty_basis_points,
33            amount,
34        }
35    }
36}
37
38impl Default for MintNftAction {
39    fn default() -> Self {
40        Self::new(
41            Id::Xch,
42            HashedPtr::NIL,
43            Bytes32::default(),
44            Bytes32::default(),
45            0,
46            1,
47        )
48    }
49}
50
51impl SpendAction for MintNftAction {
52    fn calculate_delta(&self, deltas: &mut Deltas, index: usize) {
53        deltas.update(Id::Xch).output += self.amount;
54        deltas.update(Id::New(index)).input += self.amount;
55
56        if matches!(self.parent_id, Id::Xch) {
57            deltas.set_needed(Id::Xch);
58        } else {
59            let did = deltas.update(self.parent_id);
60            did.input += 1;
61            did.output += 1;
62        }
63    }
64
65    fn spend(
66        &self,
67        ctx: &mut SpendContext,
68        spends: &mut Spends,
69        index: usize,
70    ) -> Result<(), DriverError> {
71        let (p2_puzzle_hash, source_kind, launcher) = if matches!(self.parent_id, Id::Xch) {
72            let (source, launcher) = spends.xch.create_launcher(self.amount)?;
73            let source = &mut spends.xch.items[source];
74            (source.asset.p2_puzzle_hash(), &mut source.kind, launcher)
75        } else {
76            let did = spends
77                .dids
78                .get_mut(&self.parent_id)
79                .ok_or(DriverError::InvalidAssetId)?;
80            let (source, launcher) = did.create_launcher(self.amount)?;
81            let p2_puzzle_hash = did.last()?.asset.p2_puzzle_hash();
82            let source = &mut did.lineage[source];
83            (p2_puzzle_hash, &mut source.kind, launcher)
84        };
85
86        let (parent_conditions, eve_nft) = launcher.mint_eve_nft(
87            ctx,
88            p2_puzzle_hash,
89            self.metadata,
90            self.metadata_updater_puzzle_hash,
91            self.royalty_puzzle_hash,
92            self.royalty_basis_points,
93        )?;
94
95        match source_kind {
96            SpendKind::Conditions(spend) => {
97                spend.add_conditions(parent_conditions);
98            }
99            SpendKind::Settlement(_) => {
100                return Err(DriverError::CannotEmitConditions);
101            }
102        }
103
104        spends
105            .nfts
106            .insert(Id::New(index), SingletonSpends::new(eve_nft, true));
107
108        Ok(())
109    }
110}
111
112#[cfg(test)]
113mod tests {
114    use anyhow::Result;
115    use chia_sdk_test::Simulator;
116    use indexmap::indexmap;
117
118    use crate::{Action, Relation};
119
120    use super::*;
121
122    #[test]
123    fn test_action_mint_nft() -> Result<()> {
124        let mut sim = Simulator::new();
125        let mut ctx = SpendContext::new();
126
127        let alice = sim.bls(1);
128
129        let mut spends = Spends::new(alice.puzzle_hash);
130        spends.add(alice.coin);
131
132        let deltas = spends.apply(&mut ctx, &[Action::mint_empty_nft()])?;
133
134        let outputs = spends.finish_with_keys(
135            &mut ctx,
136            &deltas,
137            Relation::None,
138            &indexmap! { alice.puzzle_hash => alice.pk },
139        )?;
140
141        sim.spend_coins(ctx.take(), &[alice.sk])?;
142
143        let nft = outputs.nfts[&Id::New(0)];
144        assert_ne!(sim.coin_state(nft.coin.coin_id()), None);
145        assert_eq!(nft.info.p2_puzzle_hash, alice.puzzle_hash);
146
147        Ok(())
148    }
149
150    #[test]
151    fn test_action_mint_nft_from_did() -> Result<()> {
152        let mut sim = Simulator::new();
153        let mut ctx = SpendContext::new();
154
155        let alice = sim.bls(2);
156
157        let mut spends = Spends::new(alice.puzzle_hash);
158        spends.add(alice.coin);
159
160        let deltas = spends.apply(
161            &mut ctx,
162            &[
163                Action::create_empty_did(),
164                Action::mint_empty_nft_from_did(Id::New(0)),
165            ],
166        )?;
167
168        let outputs = spends.finish_with_keys(
169            &mut ctx,
170            &deltas,
171            Relation::None,
172            &indexmap! { alice.puzzle_hash => alice.pk },
173        )?;
174
175        sim.spend_coins(ctx.take(), &[alice.sk])?;
176
177        let did = outputs.dids[&Id::New(0)];
178        assert_ne!(sim.coin_state(did.coin.coin_id()), None);
179        assert_eq!(did.info.p2_puzzle_hash, alice.puzzle_hash);
180
181        let nft = outputs.nfts[&Id::New(1)];
182        assert_ne!(sim.coin_state(nft.coin.coin_id()), None);
183        assert_eq!(nft.info.p2_puzzle_hash, alice.puzzle_hash);
184
185        Ok(())
186    }
187}