chia_sdk_driver/primitives/
launcher.rs1#![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#[derive(Debug, Clone)]
19#[must_use]
20pub struct Launcher {
21 coin: Coin,
22 conditions: Conditions,
23 singleton_amount: u64,
24}
25
26impl Launcher {
27 pub fn from_coin(coin: Coin, conditions: Conditions) -> Self {
29 Self {
30 coin,
31 conditions,
32 singleton_amount: coin.amount,
33 }
34 }
35
36 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 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 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 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 pub fn with_singleton_amount(mut self, singleton_amount: u64) -> Self {
91 self.singleton_amount = singleton_amount;
92 self
93 }
94
95 pub fn singleton_amount(&self) -> u64 {
97 self.singleton_amount
98 }
99
100 pub fn coin(&self) -> Coin {
102 self.coin
103 }
104
105 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}