chia_sdk_driver/primitives/
launcher.rs

1#![allow(clippy::missing_const_for_fn)]
2
3use chia_protocol::{Bytes32, Coin, CoinSpend, Program};
4use chia_puzzle_types::singleton::{LauncherSolution, SingletonArgs};
5use chia_puzzles::{SINGLETON_LAUNCHER, SINGLETON_LAUNCHER_HASH};
6use chia_sdk_types::{
7    announcement_id,
8    conditions::{CreateCoin, Memos},
9    Conditions,
10};
11use clvm_traits::ToClvm;
12use clvmr::{Allocator, NodePtr};
13
14use crate::{DriverError, SpendContext};
15
16/// A singleton launcher is a coin that is spent within the same block to create a singleton.
17/// The first coin that is created is known as an "eve" singleton.
18/// The [`Launcher`] type allows you to get the launcher id before committing to creating the singleton.
19#[derive(Debug, Clone)]
20#[must_use]
21pub struct Launcher {
22    coin: Coin,
23    conditions: Conditions,
24    singleton_amount: u64,
25}
26
27impl Launcher {
28    /// Creates a new [`Launcher`] with the specified launcher coin and parent spend conditions.
29    pub fn from_coin(coin: Coin, conditions: Conditions) -> Self {
30        Self {
31            coin,
32            conditions,
33            singleton_amount: coin.amount,
34        }
35    }
36
37    /// The parent coin specified when constructing the [`Launcher`] will create the launcher coin.
38    /// By default, no hint is used when creating the launcher coin. To specify a hint, use [`Launcher::with_memos`].
39    pub fn new(parent_coin_id: Bytes32, amount: u64) -> Self {
40        Self::from_coin(
41            Coin::new(parent_coin_id, SINGLETON_LAUNCHER_HASH.into(), amount),
42            Conditions::new().create_coin(SINGLETON_LAUNCHER_HASH.into(), amount, Memos::None),
43        )
44    }
45
46    /// The parent coin specified when constructing the [`Launcher`] will create the launcher coin.
47    /// The created launcher coin will be hinted to make identifying it easier later.
48    pub fn with_memos(parent_coin_id: Bytes32, amount: u64, memos: Memos<NodePtr>) -> Self {
49        Self::from_coin(
50            Coin::new(parent_coin_id, SINGLETON_LAUNCHER_HASH.into(), amount),
51            Conditions::new().create_coin(SINGLETON_LAUNCHER_HASH.into(), amount, memos),
52        )
53    }
54
55    /// The parent coin specified when constructing the [`Launcher`] will create the launcher coin.
56    /// By default, no hint is used when creating the coin. To specify a hint, use [`Launcher::create_early_with_memos`].
57    ///
58    /// This method is used to create the launcher coin immediately from the parent, then spend it later attached to any coin spend.
59    /// For example, this is useful for minting NFTs from intermediate coins created with an earlier instance of a DID.
60    pub fn create_early(parent_coin_id: Bytes32, amount: u64) -> (CreateCoin<NodePtr>, Self) {
61        (
62            CreateCoin::new(SINGLETON_LAUNCHER_HASH.into(), amount, Memos::None),
63            Self::from_coin(
64                Coin::new(parent_coin_id, SINGLETON_LAUNCHER_HASH.into(), amount),
65                Conditions::new(),
66            ),
67        )
68    }
69
70    /// The parent coin specified when constructing the [`Launcher`] will create the launcher coin.
71    /// The created launcher coin will be hinted to make identifying it easier later.
72    ///
73    /// This method is used to create the launcher coin immediately from the parent, then spend it later attached to any coin spend.
74    /// For example, this is useful for minting NFTs from intermediate coins created with an earlier instance of a DID.
75    pub fn create_early_with_memos(
76        parent_coin_id: Bytes32,
77        amount: u64,
78        memos: Memos<NodePtr>,
79    ) -> (CreateCoin<NodePtr>, Self) {
80        (
81            CreateCoin::new(SINGLETON_LAUNCHER_HASH.into(), amount, memos),
82            Self::from_coin(
83                Coin::new(parent_coin_id, SINGLETON_LAUNCHER_HASH.into(), amount),
84                Conditions::new(),
85            ),
86        )
87    }
88
89    /// Changes the singleton amount to differ from the launcher amount.
90    /// This is useful in situations where the launcher amount is 0 and the singleton amount is 1, for example.
91    pub fn with_singleton_amount(mut self, singleton_amount: u64) -> Self {
92        self.singleton_amount = singleton_amount;
93        self
94    }
95
96    /// Returns the current singleton amount.
97    pub fn singleton_amount(&self) -> u64 {
98        self.singleton_amount
99    }
100
101    /// The singleton launcher coin that will be created when the parent is spent.
102    pub fn coin(&self) -> Coin {
103        self.coin
104    }
105
106    /// Spends the launcher coin to create the eve singleton.
107    /// Includes an optional metadata value that is traditionally a list of key value pairs.
108    pub fn spend<T>(
109        self,
110        ctx: &mut SpendContext,
111        singleton_inner_puzzle_hash: Bytes32,
112        key_value_list: T,
113    ) -> Result<(Conditions, Coin), DriverError>
114    where
115        T: ToClvm<Allocator>,
116    {
117        let singleton_puzzle_hash =
118            SingletonArgs::curry_tree_hash(self.coin.coin_id(), singleton_inner_puzzle_hash.into())
119                .into();
120
121        let solution_ptr = ctx.alloc(&LauncherSolution {
122            singleton_puzzle_hash,
123            amount: self.singleton_amount,
124            key_value_list,
125        })?;
126
127        let solution = ctx.serialize(&solution_ptr)?;
128
129        ctx.insert(CoinSpend::new(
130            self.coin,
131            Program::from(SINGLETON_LAUNCHER.to_vec()),
132            solution,
133        ));
134
135        let singleton_coin = Coin::new(
136            self.coin.coin_id(),
137            singleton_puzzle_hash,
138            self.singleton_amount,
139        );
140
141        Ok((
142            self.conditions.assert_coin_announcement(announcement_id(
143                self.coin.coin_id(),
144                ctx.tree_hash(solution_ptr),
145            )),
146            singleton_coin,
147        ))
148    }
149}
150
151#[cfg(test)]
152mod tests {
153    use crate::StandardLayer;
154
155    use super::*;
156
157    use chia_sdk_test::Simulator;
158
159    #[test]
160    fn test_singleton_launcher() -> anyhow::Result<()> {
161        let mut sim = Simulator::new();
162
163        let alice = sim.bls(1);
164        let alice_p2 = StandardLayer::new(alice.pk);
165
166        let ctx = &mut SpendContext::new();
167        let launcher = Launcher::new(alice.coin.coin_id(), 1);
168        assert_eq!(launcher.coin.amount, 1);
169
170        let (conditions, singleton) = launcher.spend(ctx, Bytes32::default(), ())?;
171        alice_p2.spend(ctx, alice.coin, conditions)?;
172        assert_eq!(singleton.amount, 1);
173
174        sim.spend_coins(ctx.take(), &[alice.sk])?;
175
176        Ok(())
177    }
178
179    #[test]
180    fn test_singleton_launcher_custom_amount() -> anyhow::Result<()> {
181        let mut sim = Simulator::new();
182
183        let alice = sim.bls(1);
184        let alice_p2 = StandardLayer::new(alice.pk);
185
186        let ctx = &mut SpendContext::new();
187        let launcher = Launcher::new(alice.coin.coin_id(), 0);
188        assert_eq!(launcher.coin.amount, 0);
189
190        let (conditions, singleton) =
191            launcher
192                .with_singleton_amount(1)
193                .spend(ctx, Bytes32::default(), ())?;
194        alice_p2.spend(ctx, alice.coin, conditions)?;
195        assert_eq!(singleton.amount, 1);
196
197        sim.spend_coins(ctx.take(), &[alice.sk])?;
198
199        Ok(())
200    }
201}