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 announcement_id,
8 conditions::{CreateCoin, Memos},
9 Conditions,
10};
11use clvm_traits::ToClvm;
12use clvmr::{Allocator, NodePtr};
13
14use crate::{DriverError, SpendContext};
15
16#[derive(Debug, Clone)]
20#[must_use]
21pub struct Launcher {
22 coin: Coin,
23 conditions: Conditions,
24 singleton_amount: u64,
25}
26
27impl Launcher {
28 pub fn from_coin(coin: Coin, conditions: Conditions) -> Self {
30 Self {
31 coin,
32 conditions,
33 singleton_amount: coin.amount,
34 }
35 }
36
37 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 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 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 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 pub fn with_singleton_amount(mut self, singleton_amount: u64) -> Self {
92 self.singleton_amount = singleton_amount;
93 self
94 }
95
96 pub fn singleton_amount(&self) -> u64 {
98 self.singleton_amount
99 }
100
101 pub fn coin(&self) -> Coin {
103 self.coin
104 }
105
106 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}