action_layer_driver/singleton/
driver.rs1use chia::protocol::{Bytes32, Coin, CoinSpend};
4use chia::puzzles::singleton::{SingletonArgs, SingletonSolution, SingletonStruct};
5use chia_wallet_sdk::driver::{Launcher, SpendContext};
6use clvm_traits::{FromClvm, ToClvm};
7use clvm_utils::{CurriedProgram, ToTreeHash, TreeHash};
8use clvmr::{Allocator, NodePtr};
9
10use crate::action_layer::ActionLayerConfig;
11use crate::error::DriverError;
12use crate::singleton::types::{LaunchResult, SingletonCoin, SingletonLineage};
13
14pub const SINGLETON_LAUNCHER_PUZZLE_HASH: [u8; 32] =
16 hex_literal::hex!("eff07522495060c066f66f32acc2a77e3a3e737aca8baea4d1a64ea4cdc13da9");
17
18pub struct SingletonDriver<S> {
29 singleton: Option<SingletonCoin>,
31
32 state: S,
34
35 action_config: ActionLayerConfig<S>,
37
38 hint: Bytes32,
40}
41
42impl<S> SingletonDriver<S>
43where
44 S: Clone + ToClvm<Allocator> + FromClvm<Allocator> + ToTreeHash,
45{
46 pub fn new(action_hashes: Vec<Bytes32>, hint: Bytes32, initial_state: S) -> Self {
52 let action_config = ActionLayerConfig::new(action_hashes, hint);
53 Self {
54 singleton: None,
55 state: initial_state,
56 action_config,
57 hint,
58 }
59 }
60
61 pub fn from_coin(
63 singleton: SingletonCoin,
64 state: S,
65 action_hashes: Vec<Bytes32>,
66 hint: Bytes32,
67 ) -> Self {
68 let action_config = ActionLayerConfig::new(action_hashes, hint);
69 Self {
70 singleton: Some(singleton),
71 state,
72 action_config,
73 hint,
74 }
75 }
76
77 pub fn launcher_id(&self) -> Option<Bytes32> {
83 self.singleton.as_ref().map(|s| s.launcher_id)
84 }
85
86 pub fn current_coin(&self) -> Option<&Coin> {
88 self.singleton.as_ref().map(|s| &s.coin)
89 }
90
91 pub fn state(&self) -> &S {
93 &self.state
94 }
95
96 pub fn state_mut(&mut self) -> &mut S {
98 &mut self.state
99 }
100
101 pub fn is_launched(&self) -> bool {
103 self.singleton.is_some()
104 }
105
106 pub fn hint(&self) -> Bytes32 {
108 self.hint
109 }
110
111 pub fn action_config(&self) -> &ActionLayerConfig<S> {
113 &self.action_config
114 }
115
116 pub fn inner_puzzle_hash(&self) -> TreeHash {
118 self.action_config.inner_puzzle_hash(&self.state)
119 }
120
121 pub fn inner_puzzle_hash_for_state(&self, state: &S) -> TreeHash {
123 self.action_config.inner_puzzle_hash(state)
124 }
125
126 pub fn singleton_puzzle_hash(&self) -> Option<TreeHash> {
128 self.launcher_id().map(|launcher_id| {
129 SingletonArgs::curry_tree_hash(launcher_id, self.inner_puzzle_hash())
130 })
131 }
132
133 pub fn singleton_puzzle_hash_for_state(&self, state: &S) -> Option<TreeHash> {
135 self.launcher_id().map(|launcher_id| {
136 SingletonArgs::curry_tree_hash(launcher_id, self.inner_puzzle_hash_for_state(state))
137 })
138 }
139
140 pub fn proof(&self) -> Option<chia::puzzles::Proof> {
142 self.singleton.as_ref().map(|s| s.proof())
143 }
144
145 pub fn update_action_hashes(&mut self, action_hashes: Vec<Bytes32>) {
151 self.action_config = ActionLayerConfig::new(action_hashes, self.hint);
152 }
153
154 pub fn launch(
163 &mut self,
164 ctx: &mut SpendContext,
165 funding_coin: &Coin,
166 amount: u64,
167 ) -> Result<LaunchResult, DriverError> {
168 if self.is_launched() {
169 return Err(DriverError::AlreadyLaunched);
170 }
171
172 let inner_hash: Bytes32 = self.inner_puzzle_hash().into();
173
174 let launcher = Launcher::new(funding_coin.coin_id(), amount);
175 let launcher_id = launcher.coin().coin_id();
176
177 let (launcher_conditions, singleton_coin) = launcher
178 .spend(ctx, inner_hash, ())
179 .map_err(|e| DriverError::Launcher(format!("{:?}", e)))?;
180
181 let lineage = SingletonLineage::eve(funding_coin.coin_id(), amount);
183 self.singleton = Some(SingletonCoin::new(launcher_id, singleton_coin, lineage));
184
185 Ok(LaunchResult {
186 launcher_id,
187 coin: singleton_coin,
188 conditions: launcher_conditions,
189 })
190 }
191
192 pub fn build_action_spend(
203 &self,
204 ctx: &mut SpendContext,
205 action_index: usize,
206 action_puzzle: NodePtr,
207 action_solution: NodePtr,
208 ) -> Result<(), DriverError> {
209 let singleton = self.singleton.as_ref().ok_or(DriverError::NotLaunched)?;
210
211 let (inner_puzzle, inner_solution) = self.action_config.build_action_spend(
213 ctx,
214 self.state.clone(),
215 action_index,
216 action_puzzle,
217 action_solution,
218 )?;
219
220 let singleton_puzzle = self.build_singleton_puzzle(ctx, inner_puzzle)?;
222
223 let singleton_solution = self.build_singleton_solution(
225 ctx,
226 singleton.proof(),
227 singleton.coin.amount,
228 inner_solution,
229 )?;
230
231 let puzzle_reveal = ctx
233 .serialize(&singleton_puzzle)
234 .map_err(|e| DriverError::Serialize(format!("{:?}", e)))?;
235 let solution = ctx
236 .serialize(&singleton_solution)
237 .map_err(|e| DriverError::Serialize(format!("{:?}", e)))?;
238
239 let coin_spend = CoinSpend::new(singleton.coin, puzzle_reveal, solution);
240 ctx.insert(coin_spend);
241
242 Ok(())
243 }
244
245 pub fn apply_spend(&mut self, new_state: S) {
249 if let Some(singleton) = &self.singleton {
250 let launcher_id = singleton.launcher_id;
251 let old_coin = singleton.coin;
252 let old_inner_hash = self.inner_puzzle_hash();
253
254 self.state = new_state;
256
257 let new_puzzle_hash: Bytes32 = self
259 .singleton_puzzle_hash()
260 .expect("singleton should exist")
261 .into();
262 let new_coin = Coin::new(old_coin.coin_id(), new_puzzle_hash, old_coin.amount);
263
264 let new_lineage = SingletonLineage::lineage(old_coin, old_inner_hash);
266
267 self.singleton = Some(SingletonCoin::new(launcher_id, new_coin, new_lineage));
268 }
269 }
270
271 pub fn mark_melted(&mut self) {
275 self.singleton = None;
276 }
277
278 pub fn expected_new_coin(&self, new_state: &S) -> Option<Coin> {
284 let singleton = self.singleton.as_ref()?;
285 let new_inner_hash = self.inner_puzzle_hash_for_state(new_state);
286 let new_puzzle_hash: Bytes32 =
287 SingletonArgs::curry_tree_hash(singleton.launcher_id, new_inner_hash).into();
288 Some(Coin::new(
289 singleton.coin.coin_id(),
290 new_puzzle_hash,
291 singleton.coin.amount,
292 ))
293 }
294
295 pub fn expected_child_launcher_id(&self) -> Option<Bytes32> {
298 let singleton = self.singleton.as_ref()?;
299 let child_launcher_coin = Coin::new(
300 singleton.coin.coin_id(),
301 Bytes32::new(SINGLETON_LAUNCHER_PUZZLE_HASH),
302 0,
303 );
304 Some(child_launcher_coin.coin_id())
305 }
306
307 fn build_singleton_puzzle(
309 &self,
310 ctx: &mut SpendContext,
311 inner_puzzle: NodePtr,
312 ) -> Result<NodePtr, DriverError> {
313 let launcher_id = self.launcher_id().ok_or(DriverError::NotLaunched)?;
314
315 let singleton_mod_hash = TreeHash::new(chia_puzzles::SINGLETON_TOP_LAYER_V1_1_HASH);
316 let singleton_ptr = ctx
317 .puzzle(singleton_mod_hash, &chia_puzzles::SINGLETON_TOP_LAYER_V1_1)
318 .map_err(|e| DriverError::PuzzleLoad(format!("singleton: {:?}", e)))?;
319
320 ctx.alloc(&CurriedProgram {
321 program: singleton_ptr,
322 args: SingletonArgs {
323 singleton_struct: SingletonStruct::new(launcher_id),
324 inner_puzzle,
325 },
326 })
327 .map_err(|e| DriverError::Alloc(format!("singleton curry: {:?}", e)))
328 }
329
330 fn build_singleton_solution(
332 &self,
333 ctx: &mut SpendContext,
334 proof: chia::puzzles::Proof,
335 amount: u64,
336 inner_solution: NodePtr,
337 ) -> Result<NodePtr, DriverError> {
338 ctx.alloc(&SingletonSolution {
339 lineage_proof: proof,
340 amount,
341 inner_solution,
342 })
343 .map_err(|e| DriverError::Alloc(format!("singleton solution: {:?}", e)))
344 }
345}
346
347impl<S: std::fmt::Debug> std::fmt::Debug for SingletonDriver<S> {
348 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
349 f.debug_struct("SingletonDriver")
350 .field(
351 "launcher_id",
352 &self.singleton.as_ref().map(|s| hex::encode(s.launcher_id)),
353 )
354 .field("is_launched", &self.singleton.is_some())
355 .field("state", &self.state)
356 .finish()
357 }
358}