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