1#![allow(clippy::result_large_err)]
2
3use std::collections::HashMap;
4use std::time::{SystemTime, UNIX_EPOCH};
5
6use crate::error::WalletError;
8pub use crate::types::{coin_records_to_states, SuccessResponse, XchServerCoin};
9use crate::types::{EveProof, LineageProof, Proof};
10use crate::xch_server_coin::{urls_from_conditions, MirrorArgs, MirrorSolution, NewXchServerCoin};
11use crate::{NetworkType, UnspentCoinStates};
12use chia::bls::{sign, verify, PublicKey, SecretKey, Signature};
13use chia::clvm_traits::{clvm_tuple, FromClvm, ToClvm};
14use chia::clvm_utils::tree_hash;
15use chia::consensus::consensus_constants::ConsensusConstants;
16use chia::consensus::flags::{DONT_VALIDATE_SIGNATURE, MEMPOOL_MODE};
17use chia::consensus::owned_conditions::OwnedSpendBundleConditions;
18use chia::consensus::run_block_generator::run_block_generator;
19use chia::consensus::solution_generator::solution_generator;
20use chia::protocol::{
21 Bytes, Bytes32, Coin, CoinSpend, CoinState, CoinStateFilters, RejectHeaderRequest,
22 RequestBlockHeader, RequestFeeEstimates, RespondBlockHeader, RespondFeeEstimates, SpendBundle,
23 TransactionAck,
24};
25use chia::puzzles::{
26 nft::NftMetadata,
27 standard::{StandardArgs, StandardSolution},
28 DeriveSynthetic,
29};
30use chia_puzzles::SINGLETON_LAUNCHER_HASH;
31use chia_wallet_sdk::client::Peer;
32use chia_wallet_sdk::driver::{
33 get_merkle_tree, DataStore, DataStoreMetadata, DelegatedPuzzle, Did, DidInfo, DriverError,
34 HashedPtr, IntermediateLauncher, Launcher, Layer, NftMint, OracleLayer, SpendContext,
35 SpendWithConditions, StandardLayer, WriterLayer,
36};
37use chia_wallet_sdk::signer::{AggSigConstants, RequiredSignature, SignerError};
38use chia_wallet_sdk::types::{
39 announcement_id,
40 conditions::{CreateCoin, MeltSingleton, Memos, UpdateDataStoreMerkleRoot},
41 Condition, Conditions, MAINNET_CONSTANTS, TESTNET11_CONSTANTS,
42};
43use chia_wallet_sdk::utils::{self, CoinSelectionError};
44use clvmr::Allocator;
45use hex_literal::hex;
46
47pub const DATASTORE_LAUNCHER_HINT: Bytes32 = Bytes32::new(hex!(
49 "
50 aa7e5b234e1d55967bf0a316395a2eab6cb3370332c0f251f0e44a5afb84fc68
51 "
52));
53
54pub const DIG_ASSET_ID: Bytes32 = Bytes32::new(hex!(
55 "a406d3a9de984d03c9591c10d917593b434d5263cabe2b42f6b367df16832f81"
56));
57
58pub const MAX_CLVM_COST: u64 = 11_000_000_000;
59
60pub async fn get_unspent_coin_states_by_hint(
61 peer: &Peer,
62 hint: Bytes32,
63 network_type: NetworkType,
64) -> Result<UnspentCoinStates, WalletError> {
65 let header_hash = match network_type {
66 NetworkType::Mainnet => MAINNET_CONSTANTS.genesis_challenge,
67 NetworkType::Testnet11 => TESTNET11_CONSTANTS.genesis_challenge,
68 };
69 get_unspent_coin_states(peer, hint, None, header_hash, true).await
70}
71
72pub async fn get_unspent_coin_states(
73 peer: &Peer,
74 puzzle_hash: Bytes32,
75 previous_height: Option<u32>,
76 previous_header_hash: Bytes32,
77 allow_hints: bool,
78) -> Result<UnspentCoinStates, WalletError> {
79 let mut coin_states = Vec::new();
80 let mut last_height = previous_height.unwrap_or_default();
81
82 let mut last_header_hash = previous_header_hash;
83
84 loop {
85 let response = peer
86 .request_puzzle_state(
87 vec![puzzle_hash],
88 if last_height == 0 {
89 None
90 } else {
91 Some(last_height)
92 },
93 last_header_hash,
94 CoinStateFilters {
95 include_spent: false,
96 include_unspent: true,
97 include_hinted: allow_hints,
98 min_amount: 1,
99 },
100 false,
101 )
102 .await
103 .map_err(WalletError::Client)?
104 .map_err(|_| WalletError::RejectPuzzleState)?;
105
106 last_height = response.height;
107 last_header_hash = response.header_hash;
108 coin_states.extend(
109 response
110 .coin_states
111 .into_iter()
112 .filter(|cs| cs.spent_height.is_none()),
113 );
114
115 if response.is_finished {
116 break;
117 }
118 }
119
120 Ok(UnspentCoinStates {
121 coin_states,
122 last_height,
123 last_header_hash,
124 })
125}
126
127pub fn select_coins(coins: Vec<Coin>, total_amount: u64) -> Result<Vec<Coin>, CoinSelectionError> {
128 utils::select_coins(coins.into_iter().collect(), total_amount)
129}
130
131fn spend_coins_together(
132 ctx: &mut SpendContext,
133 synthetic_key: PublicKey,
134 coins: &[Coin],
135 extra_conditions: Conditions,
136 output: i64,
137 change_puzzle_hash: Bytes32,
138) -> Result<(), WalletError> {
139 let p2 = StandardLayer::new(synthetic_key);
140
141 let change = i64::try_from(coins.iter().map(|coin| coin.amount).sum::<u64>()).unwrap() - output;
142 assert!(change >= 0);
143 let change = change as u64;
144
145 let first_coin_id = coins[0].coin_id();
146
147 for (i, &coin) in coins.iter().enumerate() {
148 if i == 0 {
149 let mut conditions = extra_conditions.clone();
150
151 if change > 0 {
152 conditions = conditions.create_coin(change_puzzle_hash, change, Memos::None);
153 }
154
155 p2.spend(ctx, coin, conditions)?;
156 } else {
157 p2.spend(
158 ctx,
159 coin,
160 Conditions::new().assert_concurrent_spend(first_coin_id),
161 )?;
162 }
163 }
164 Ok(())
165}
166
167pub fn send_xch(
168 synthetic_key: PublicKey,
169 coins: &[Coin],
170 outputs: &[(Bytes32, u64, Vec<Bytes>)],
171 fee: u64,
172) -> Result<Vec<CoinSpend>, WalletError> {
173 let mut ctx = SpendContext::new();
174
175 let mut conditions = Conditions::new().reserve_fee(fee);
176 let mut total_amount = fee;
177
178 for output in outputs {
179 let memos = ctx.alloc(&output.2)?;
180 conditions = conditions.create_coin(output.0, output.1, Memos::Some(memos));
181 total_amount += output.1;
182 }
183
184 spend_coins_together(
185 &mut ctx,
186 synthetic_key,
187 coins,
188 conditions,
189 total_amount.try_into().unwrap(),
190 StandardArgs::curry_tree_hash(synthetic_key).into(),
191 )?;
192
193 Ok(ctx.take())
194}
195
196pub fn create_server_coin(
197 synthetic_key: PublicKey,
198 selected_coins: Vec<Coin>,
199 hint: Bytes32,
200 uris: Vec<String>,
201 amount: u64,
202 fee: u64,
203) -> Result<NewXchServerCoin, WalletError> {
204 let puzzle_hash = StandardArgs::curry_tree_hash(synthetic_key).into();
205
206 let mut memos = Vec::with_capacity(uris.len() + 1);
207 memos.push(hint.to_vec());
208
209 for url in &uris {
210 memos.push(url.as_bytes().to_vec());
211 }
212
213 let mut ctx = SpendContext::new();
214
215 let memos = ctx.alloc(&memos)?;
216
217 let conditions = Conditions::new()
218 .create_coin(
219 MirrorArgs::curry_tree_hash().into(),
220 amount,
221 Memos::Some(memos),
222 )
223 .reserve_fee(fee);
224
225 spend_coins_together(
226 &mut ctx,
227 synthetic_key,
228 &selected_coins,
229 conditions,
230 (amount + fee).try_into().unwrap(),
231 puzzle_hash,
232 )?;
233
234 let server_coin = XchServerCoin {
235 coin: Coin::new(
236 selected_coins[0].coin_id(),
237 MirrorArgs::curry_tree_hash().into(),
238 amount,
239 ),
240 p2_puzzle_hash: puzzle_hash,
241 memo_urls: uris,
242 };
243
244 Ok(NewXchServerCoin {
245 coin_spends: ctx.take(),
246 server_coin,
247 })
248}
249
250pub async fn spend_xch_server_coins(
251 peer: &Peer,
252 synthetic_key: PublicKey,
253 selected_coins: Vec<Coin>,
254 total_fee: u64,
255 network: TargetNetwork,
256) -> Result<Vec<CoinSpend>, WalletError> {
257 let puzzle_hash = StandardArgs::curry_tree_hash(synthetic_key).into();
258
259 let mut fee_coins = Vec::new();
260 let mut server_coins = Vec::new();
261
262 for coin in selected_coins {
263 if coin.puzzle_hash == puzzle_hash {
264 fee_coins.push(coin);
265 } else {
266 server_coins.push(coin);
267 }
268 }
269
270 if server_coins.is_empty() {
271 return Ok(Vec::new());
272 }
273
274 assert!(!fee_coins.is_empty());
275
276 let parent_coins = peer
277 .request_coin_state(
278 server_coins.iter().map(|sc| sc.parent_coin_info).collect(),
279 None,
280 match network {
281 TargetNetwork::Mainnet => MAINNET_CONSTANTS.genesis_challenge,
282 TargetNetwork::Testnet11 => TESTNET11_CONSTANTS.genesis_challenge,
283 },
284 false,
285 )
286 .await?
287 .map_err(|_| WalletError::RejectCoinState)?
288 .coin_states;
289
290 let mut ctx = SpendContext::new();
291
292 let puzzle_reveal = ctx.curry(MirrorArgs::default())?;
293
294 let mut conditions = Conditions::new().reserve_fee(total_fee);
295 let mut total_fee: i64 = total_fee.try_into().unwrap();
296
297 for server_coin in server_coins {
298 let parent_coin = parent_coins
299 .iter()
300 .find(|cs| cs.coin.coin_id() == server_coin.parent_coin_info)
301 .copied()
302 .ok_or(WalletError::UnknownCoin)?;
303
304 if parent_coin.coin.puzzle_hash != puzzle_hash {
305 return Err(WalletError::Permission);
306 }
307
308 let parent_inner_puzzle = ctx.curry(StandardArgs::new(synthetic_key))?;
309
310 let puzzle_reveal = ctx.serialize(&puzzle_reveal)?;
311
312 let solution = ctx.serialize(&MirrorSolution {
313 parent_parent_id: parent_coin.coin.parent_coin_info,
314 parent_inner_puzzle,
315 parent_amount: parent_coin.coin.amount,
316 parent_solution: StandardSolution {
317 original_public_key: None,
318 delegated_puzzle: (),
319 solution: (),
320 },
321 })?;
322
323 total_fee -= i64::try_from(server_coin.amount).unwrap();
324 ctx.insert(CoinSpend::new(server_coin, puzzle_reveal, solution));
325
326 conditions = conditions.assert_concurrent_spend(server_coin.coin_id());
327 }
328
329 spend_coins_together(
330 &mut ctx,
331 synthetic_key,
332 &fee_coins,
333 conditions,
334 total_fee,
335 puzzle_hash,
336 )?;
337
338 Ok(ctx.take())
339}
340
341pub async fn fetch_xch_server_coin(
342 peer: &Peer,
343 coin_state: CoinState,
344 max_cost: u64,
345) -> Result<XchServerCoin, WalletError> {
346 let Some(created_height) = coin_state.created_height else {
347 return Err(WalletError::UnknownCoin);
348 };
349
350 let spend = peer
351 .request_puzzle_and_solution(coin_state.coin.parent_coin_info, created_height)
352 .await?
353 .map_err(|_| WalletError::RejectPuzzleSolution)?;
354
355 let mut allocator = Allocator::new();
356
357 let Ok(output) = spend
358 .puzzle
359 .run(&mut allocator, 0, max_cost, &spend.solution)
360 else {
361 return Err(WalletError::Clvm);
362 };
363
364 let Ok(conditions) = Vec::<Condition>::from_clvm(&allocator, output.1) else {
365 return Err(WalletError::Parse(
366 "Failed to get conditions from clvm allocator".to_string(),
367 ));
368 };
369
370 let Some(urls) = urls_from_conditions(&allocator, &coin_state.coin, &conditions) else {
371 return Err(WalletError::Parse(
372 "Failed to get urls from conditions".to_string(),
373 ));
374 };
375
376 let puzzle = spend
377 .puzzle
378 .to_clvm(&mut allocator)
379 .map_err(DriverError::ToClvm)?;
380
381 Ok(XchServerCoin {
382 coin: coin_state.coin,
383 p2_puzzle_hash: tree_hash(&allocator, puzzle).into(),
384 memo_urls: urls,
385 })
386}
387
388#[allow(clippy::too_many_arguments)]
389pub fn mint_store(
390 minter_synthetic_key: PublicKey,
391 selected_coins: Vec<Coin>,
392 root_hash: Bytes32,
393 label: Option<String>,
394 description: Option<String>,
395 bytes: Option<u64>,
396 size_proof: Option<String>,
397 owner_puzzle_hash: Bytes32,
398 delegated_puzzles: Vec<DelegatedPuzzle>,
399 fee: u64,
400) -> Result<SuccessResponse, WalletError> {
401 let minter_puzzle_hash: Bytes32 = StandardArgs::curry_tree_hash(minter_synthetic_key).into();
402 let total_amount_from_coins = selected_coins.iter().map(|c| c.amount).sum::<u64>();
403
404 let total_amount = fee + 1;
405
406 let mut ctx = SpendContext::new();
407
408 let p2 = StandardLayer::new(minter_synthetic_key);
409
410 let lead_coin = selected_coins[0];
411 let lead_coin_name = lead_coin.coin_id();
412
413 for coin in selected_coins.into_iter().skip(1) {
414 p2.spend(
415 &mut ctx,
416 coin,
417 Conditions::new().assert_concurrent_spend(lead_coin_name),
418 )?;
419 }
420
421 let (launch_singleton, datastore) = Launcher::new(lead_coin_name, 1).mint_datastore(
422 &mut ctx,
423 DataStoreMetadata {
424 root_hash,
425 label,
426 description,
427 bytes,
428 size_proof,
429 },
430 owner_puzzle_hash.into(),
431 delegated_puzzles,
432 )?;
433
434 let launch_singleton = Conditions::new().extend(
435 launch_singleton
436 .into_iter()
437 .map(|cond| {
438 if let Condition::CreateCoin(cc) = cond {
439 if cc.puzzle_hash == SINGLETON_LAUNCHER_HASH.into() {
440 let hint = ctx.hint(DATASTORE_LAUNCHER_HINT)?;
441
442 return Ok(Condition::CreateCoin(CreateCoin {
443 puzzle_hash: cc.puzzle_hash,
444 amount: cc.amount,
445 memos: hint,
446 }));
447 }
448
449 return Ok(Condition::CreateCoin(cc));
450 }
451
452 Ok(cond)
453 })
454 .collect::<Result<Vec<_>, WalletError>>()?,
455 );
456
457 let lead_coin_conditions = if total_amount_from_coins > total_amount {
458 let hint = ctx.hint(minter_puzzle_hash)?;
459
460 launch_singleton.create_coin(
461 minter_puzzle_hash,
462 total_amount_from_coins - total_amount,
463 hint,
464 )
465 } else {
466 launch_singleton
467 };
468 p2.spend(&mut ctx, lead_coin, lead_coin_conditions)?;
469
470 Ok(SuccessResponse {
471 coin_spends: ctx.take(),
472 new_datastore: datastore,
473 })
474}
475
476pub struct SyncStoreResponse {
477 pub latest_store: DataStore,
478 pub latest_height: u32,
479 pub root_hash_history: Option<Vec<(Bytes32, u64)>>,
480}
481
482pub async fn sync_store(
483 peer: &Peer,
484 store: &DataStore,
485 last_height: Option<u32>,
486 last_header_hash: Bytes32,
487 with_history: bool,
488) -> Result<SyncStoreResponse, WalletError> {
489 let mut latest_store = store.clone();
490 let mut history = vec![];
491
492 let response = peer
493 .request_coin_state(
494 vec![store.coin.coin_id()],
495 last_height,
496 last_header_hash,
497 false,
498 )
499 .await
500 .map_err(WalletError::Client)?
501 .map_err(|_| WalletError::RejectCoinState)?;
502 let mut last_coin_record = response
503 .coin_states
504 .into_iter()
505 .next()
506 .ok_or(WalletError::UnknownCoin)?;
507
508 let mut ctx = SpendContext::new(); while last_coin_record.spent_height.is_some() {
511 let puzzle_and_solution_req = peer
512 .request_puzzle_and_solution(
513 last_coin_record.coin.coin_id(),
514 last_coin_record.spent_height.unwrap(),
515 )
516 .await
517 .map_err(WalletError::Client)?
518 .map_err(|_| WalletError::RejectPuzzleSolution)?;
519
520 let cs = CoinSpend {
521 coin: last_coin_record.coin,
522 puzzle_reveal: puzzle_and_solution_req.puzzle,
523 solution: puzzle_and_solution_req.solution,
524 };
525
526 let new_store = DataStore::<DataStoreMetadata>::from_spend(
527 &mut ctx,
528 &cs,
529 &latest_store.info.delegated_puzzles,
530 )?
531 .ok_or(WalletError::Parse("Store from spend is None".to_string()))?;
532
533 if with_history {
534 let resp: Result<RespondBlockHeader, RejectHeaderRequest> = peer
535 .request_fallible(RequestBlockHeader {
536 height: last_coin_record.spent_height.unwrap(),
537 })
538 .await
539 .map_err(WalletError::Client)?;
540 let block_header = resp.map_err(|_| WalletError::RejectHeaderRequest)?;
541
542 history.push((
543 new_store.info.metadata.root_hash,
544 block_header
545 .header_block
546 .foliage_transaction_block
547 .unwrap()
548 .timestamp,
549 ));
550 }
551
552 let response = peer
553 .request_coin_state(
554 vec![new_store.coin.coin_id()],
555 last_height,
556 last_header_hash,
557 false,
558 )
559 .await
560 .map_err(WalletError::Client)?
561 .map_err(|_| WalletError::RejectCoinState)?;
562
563 last_coin_record = response
564 .coin_states
565 .into_iter()
566 .next()
567 .ok_or(WalletError::UnknownCoin)?;
568 latest_store = new_store;
569 }
570
571 Ok(SyncStoreResponse {
572 latest_store,
573 latest_height: last_coin_record
574 .created_height
575 .ok_or(WalletError::UnknownCoin)?,
576 root_hash_history: if with_history { Some(history) } else { None },
577 })
578}
579
580pub async fn sync_store_using_launcher_id(
581 peer: &Peer,
582 launcher_id: Bytes32,
583 last_height: Option<u32>,
584 last_header_hash: Bytes32,
585 with_history: bool,
586) -> Result<SyncStoreResponse, WalletError> {
587 let response = peer
588 .request_coin_state(vec![launcher_id], last_height, last_header_hash, false)
589 .await
590 .map_err(WalletError::Client)?
591 .map_err(|_| WalletError::RejectCoinState)?;
592 let last_coin_record = response
593 .coin_states
594 .into_iter()
595 .next()
596 .ok_or(WalletError::UnknownCoin)?;
597
598 let mut ctx = SpendContext::new(); let puzzle_and_solution_req = peer
601 .request_puzzle_and_solution(
602 last_coin_record.coin.coin_id(),
603 last_coin_record
604 .spent_height
605 .ok_or(WalletError::UnknownCoin)?,
606 )
607 .await
608 .map_err(WalletError::Client)?
609 .map_err(|_| WalletError::RejectPuzzleSolution)?;
610
611 let cs = CoinSpend {
612 coin: last_coin_record.coin,
613 puzzle_reveal: puzzle_and_solution_req.puzzle,
614 solution: puzzle_and_solution_req.solution,
615 };
616
617 let first_store = DataStore::<DataStoreMetadata>::from_spend(&mut ctx, &cs, &[])?
618 .ok_or(WalletError::Parse("Store from spend is None".to_string()))?;
619
620 let res = sync_store(
621 peer,
622 &first_store,
623 last_height,
624 last_header_hash,
625 with_history,
626 )
627 .await?;
628
629 let root_hash_history = if let Some(mut res_root_hash_history) = res.root_hash_history {
631 let spent_timestamp = if let Some(spent_height) = last_coin_record.spent_height {
632 let resp: Result<RespondBlockHeader, RejectHeaderRequest> = peer
633 .request_fallible(RequestBlockHeader {
634 height: spent_height,
635 })
636 .await
637 .map_err(WalletError::Client)?;
638 let resp = resp.map_err(|_| WalletError::RejectHeaderRequest)?;
639
640 resp.header_block
641 .foliage_transaction_block
642 .unwrap()
643 .timestamp
644 } else {
645 0
646 };
647
648 res_root_hash_history.insert(0, (first_store.info.metadata.root_hash, spent_timestamp));
649 Some(res_root_hash_history)
650 } else {
651 None
652 };
653
654 Ok(SyncStoreResponse {
655 latest_store: res.latest_store,
656 latest_height: res.latest_height,
657 root_hash_history,
658 })
659}
660
661pub async fn get_store_creation_height(
662 peer: &Peer,
663 launcher_id: Bytes32,
664 last_height: Option<u32>,
665 last_header_hash: Bytes32,
666) -> Result<u32, WalletError> {
667 let response = peer
668 .request_coin_state(vec![launcher_id], last_height, last_header_hash, false)
669 .await
670 .map_err(WalletError::Client)?
671 .map_err(|_| WalletError::RejectCoinState)?;
672 let last_coin_record = response
673 .coin_states
674 .into_iter()
675 .next()
676 .ok_or(WalletError::UnknownCoin)?;
677
678 last_coin_record
679 .created_height
680 .ok_or(WalletError::UnknownCoin)
681}
682
683#[derive(Clone, Debug)]
684pub enum DataStoreInnerSpend {
685 Owner(PublicKey),
686 Admin(PublicKey),
687 Writer(PublicKey),
688 }
690
691fn update_store_with_conditions(
692 ctx: &mut SpendContext,
693 conditions: Conditions,
694 datastore: DataStore,
695 inner_spend_info: DataStoreInnerSpend,
696 allow_admin: bool,
697 allow_writer: bool,
698) -> Result<SuccessResponse, WalletError> {
699 let inner_datastore_spend = match inner_spend_info {
700 DataStoreInnerSpend::Owner(pk) => {
701 StandardLayer::new(pk).spend_with_conditions(ctx, conditions)?
702 }
703 DataStoreInnerSpend::Admin(pk) => {
704 if !allow_admin {
705 return Err(WalletError::Permission);
706 }
707
708 StandardLayer::new(pk).spend_with_conditions(ctx, conditions)?
709 }
710 DataStoreInnerSpend::Writer(pk) => {
711 if !allow_writer {
712 return Err(WalletError::Permission);
713 }
714
715 WriterLayer::new(StandardLayer::new(pk)).spend(ctx, conditions)?
716 }
717 };
718
719 let parent_delegated_puzzles = datastore.info.delegated_puzzles.clone();
720 let new_spend = datastore.spend(ctx, inner_datastore_spend)?;
721
722 let new_datastore =
723 DataStore::<DataStoreMetadata>::from_spend(ctx, &new_spend, &parent_delegated_puzzles)?
724 .ok_or(WalletError::Parse("Store from spend is None".to_string()))?;
725
726 Ok(SuccessResponse {
727 coin_spends: vec![new_spend],
728 new_datastore,
729 })
730}
731
732pub fn update_store_ownership(
733 datastore: DataStore,
734 new_owner_puzzle_hash: Bytes32,
735 new_delegated_puzzles: Vec<DelegatedPuzzle>,
736 inner_spend_info: DataStoreInnerSpend,
737) -> Result<SuccessResponse, WalletError> {
738 let ctx = &mut SpendContext::new();
739
740 let update_condition: Condition = match inner_spend_info {
741 DataStoreInnerSpend::Owner(_) => {
742 DataStore::<DataStoreMetadata>::owner_create_coin_condition(
743 ctx,
744 datastore.info.launcher_id,
745 new_owner_puzzle_hash,
746 new_delegated_puzzles,
747 true,
748 )?
749 }
750 DataStoreInnerSpend::Admin(_) => {
751 let merkle_tree = get_merkle_tree(ctx, new_delegated_puzzles.clone())?;
752
753 let new_merkle_root_condition = UpdateDataStoreMerkleRoot {
754 new_merkle_root: merkle_tree.root(),
755 memos: DataStore::<DataStoreMetadata>::get_recreation_memos(
756 datastore.info.launcher_id,
757 new_owner_puzzle_hash.into(),
758 new_delegated_puzzles,
759 ),
760 }
761 .to_clvm(&mut **ctx)
762 .map_err(DriverError::ToClvm)?;
763
764 Condition::Other(new_merkle_root_condition)
765 }
766 _ => return Err(WalletError::Permission),
767 };
768
769 let update_conditions = Conditions::new().with(update_condition);
770
771 update_store_with_conditions(
772 ctx,
773 update_conditions,
774 datastore,
775 inner_spend_info,
776 true,
777 false,
778 )
779}
780
781pub fn update_store_metadata(
782 datastore: DataStore,
783 new_root_hash: Bytes32,
784 new_label: Option<String>,
785 new_description: Option<String>,
786 new_bytes: Option<u64>,
787 new_size_proof: Option<String>,
788 inner_spend_info: DataStoreInnerSpend,
789) -> Result<SuccessResponse, WalletError> {
790 let ctx = &mut SpendContext::new();
791
792 let new_metadata = DataStoreMetadata {
793 root_hash: new_root_hash,
794 label: new_label,
795 description: new_description,
796 bytes: new_bytes,
797 size_proof: new_size_proof,
798 };
799 let mut new_metadata_condition = Conditions::new().with(
800 DataStore::<DataStoreMetadata>::new_metadata_condition(ctx, new_metadata)?,
801 );
802
803 if let DataStoreInnerSpend::Owner(_) = inner_spend_info {
804 new_metadata_condition = new_metadata_condition.with(
805 DataStore::<DataStoreMetadata>::owner_create_coin_condition(
806 ctx,
807 datastore.info.launcher_id,
808 datastore.info.owner_puzzle_hash,
809 datastore.info.delegated_puzzles.clone(),
810 false,
811 )?,
812 );
813 }
814
815 update_store_with_conditions(
816 ctx,
817 new_metadata_condition,
818 datastore,
819 inner_spend_info,
820 true,
821 true,
822 )
823}
824
825pub fn melt_store(
826 datastore: DataStore,
827 owner_pk: PublicKey,
828) -> Result<Vec<CoinSpend>, WalletError> {
829 let ctx = &mut SpendContext::new();
830
831 let melt_conditions = Conditions::new()
832 .with(Condition::reserve_fee(1))
833 .with(Condition::Other(
834 MeltSingleton {}
835 .to_clvm(&mut **ctx)
836 .map_err(DriverError::ToClvm)?,
837 ));
838
839 let inner_datastore_spend =
840 StandardLayer::new(owner_pk).spend_with_conditions(ctx, melt_conditions)?;
841
842 let new_spend = datastore.spend(ctx, inner_datastore_spend)?;
843
844 Ok(vec![new_spend])
845}
846
847pub fn oracle_spend(
848 spender_synthetic_key: PublicKey,
849 selected_coins: Vec<Coin>,
850 datastore: DataStore,
851 fee: u64,
852) -> Result<SuccessResponse, WalletError> {
853 let Some(DelegatedPuzzle::Oracle(oracle_ph, oracle_fee)) = datastore
854 .info
855 .delegated_puzzles
856 .iter()
857 .find(|dp| matches!(dp, DelegatedPuzzle::Oracle(_, _)))
858 else {
859 return Err(WalletError::Permission);
860 };
861
862 let spender_puzzle_hash: Bytes32 = StandardArgs::curry_tree_hash(spender_synthetic_key).into();
863
864 let total_amount = oracle_fee + fee;
865
866 let ctx = &mut SpendContext::new();
867
868 let p2 = StandardLayer::new(spender_synthetic_key);
869
870 let lead_coin = selected_coins[0];
871 let lead_coin_name = lead_coin.coin_id();
872
873 let total_amount_from_coins = selected_coins.iter().map(|c| c.amount).sum::<u64>();
874 for coin in selected_coins.into_iter().skip(1) {
875 p2.spend(
876 ctx,
877 coin,
878 Conditions::new().assert_concurrent_spend(lead_coin_name),
879 )?;
880 }
881
882 let assert_oracle_conds = Conditions::new().assert_puzzle_announcement(announcement_id(
883 datastore.coin.puzzle_hash,
884 Bytes::new("$".into()),
885 ));
886
887 let mut lead_coin_conditions = assert_oracle_conds;
888 if total_amount_from_coins > total_amount {
889 let hint = ctx.hint(spender_puzzle_hash)?;
890
891 lead_coin_conditions = lead_coin_conditions.create_coin(
892 spender_puzzle_hash,
893 total_amount_from_coins - total_amount,
894 hint,
895 );
896 }
897 if fee > 0 {
898 lead_coin_conditions = lead_coin_conditions.reserve_fee(fee);
899 }
900 p2.spend(ctx, lead_coin, lead_coin_conditions)?;
901
902 let inner_datastore_spend = OracleLayer::new(*oracle_ph, *oracle_fee)
903 .ok_or(DriverError::OddOracleFee)?
904 .construct_spend(ctx, ())?;
905
906 let parent_delegated_puzzles = datastore.info.delegated_puzzles.clone();
907 let new_spend = datastore.spend(ctx, inner_datastore_spend)?;
908
909 let new_datastore = DataStore::from_spend(ctx, &new_spend, &parent_delegated_puzzles)?
910 .ok_or(WalletError::Parse("Store from spend is None".to_string()))?;
911 ctx.insert(new_spend.clone());
912
913 Ok(SuccessResponse {
914 coin_spends: ctx.take(),
915 new_datastore,
916 })
917}
918
919pub fn add_fee(
920 spender_synthetic_key: PublicKey,
921 selected_coins: Vec<Coin>,
922 coin_ids: Vec<Bytes32>,
923 fee: u64,
924) -> Result<Vec<CoinSpend>, WalletError> {
925 let spender_puzzle_hash: Bytes32 = StandardArgs::curry_tree_hash(spender_synthetic_key).into();
926 let total_amount_from_coins = selected_coins.iter().map(|c| c.amount).sum::<u64>();
927
928 let mut ctx = SpendContext::new();
929
930 let p2 = StandardLayer::new(spender_synthetic_key);
931
932 let lead_coin = selected_coins[0];
933 let lead_coin_name = lead_coin.coin_id();
934
935 for coin in selected_coins.into_iter().skip(1) {
936 p2.spend(
937 &mut ctx,
938 coin,
939 Conditions::new().assert_concurrent_spend(lead_coin_name),
940 )?;
941 }
942
943 let mut lead_coin_conditions = Conditions::new().reserve_fee(fee);
944 if total_amount_from_coins > fee {
945 let hint = ctx.hint(spender_puzzle_hash)?;
946
947 lead_coin_conditions = lead_coin_conditions.create_coin(
948 spender_puzzle_hash,
949 total_amount_from_coins - fee,
950 hint,
951 );
952 }
953 for coin_id in coin_ids {
954 lead_coin_conditions = lead_coin_conditions.assert_concurrent_spend(coin_id);
955 }
956
957 p2.spend(&mut ctx, lead_coin, lead_coin_conditions)?;
958
959 Ok(ctx.take())
960}
961
962pub fn public_key_to_synthetic_key(pk: PublicKey) -> PublicKey {
963 pk.derive_synthetic()
964}
965
966pub fn secret_key_to_synthetic_key(sk: SecretKey) -> SecretKey {
967 sk.derive_synthetic()
968}
969
970#[derive(Debug, Clone, Copy)]
971pub enum TargetNetwork {
972 Mainnet,
973 Testnet11,
974}
975
976impl TargetNetwork {
977 fn get_constants(&self) -> &ConsensusConstants {
978 match self {
979 TargetNetwork::Mainnet => &MAINNET_CONSTANTS,
980 TargetNetwork::Testnet11 => &TESTNET11_CONSTANTS,
981 }
982 }
983}
984
985pub fn sign_coin_spends(
986 coin_spends: Vec<CoinSpend>,
987 private_keys: Vec<SecretKey>,
988 network: TargetNetwork,
989) -> Result<Signature, SignerError> {
990 let mut allocator = Allocator::new();
991
992 let required_signatures = RequiredSignature::from_coin_spends(
993 &mut allocator,
994 &coin_spends,
995 &AggSigConstants::new(network.get_constants().agg_sig_me_additional_data),
996 )?;
997
998 let key_pairs = private_keys
999 .iter()
1000 .map(|sk| {
1001 (
1002 sk.public_key(),
1003 sk.clone(),
1004 sk.public_key().derive_synthetic(),
1005 sk.derive_synthetic(),
1006 )
1007 })
1008 .flat_map(|(pk1, sk1, pk2, sk2)| vec![(pk1, sk1), (pk2, sk2)])
1009 .collect::<HashMap<PublicKey, SecretKey>>();
1010
1011 let mut sig = Signature::default();
1012
1013 for required in required_signatures {
1014 let RequiredSignature::Bls(required) = required else {
1015 continue;
1016 };
1017
1018 let sk = key_pairs.get(&required.public_key);
1019
1020 if let Some(sk) = sk {
1021 sig += &sign(sk, required.message());
1022 }
1023 }
1024
1025 Ok(sig)
1026}
1027
1028pub async fn broadcast_spend_bundle(
1029 peer: &Peer,
1030 spend_bundle: SpendBundle,
1031) -> Result<TransactionAck, WalletError> {
1032 peer.send_transaction(spend_bundle)
1033 .await
1034 .map_err(WalletError::Client)
1035}
1036
1037pub async fn get_header_hash(peer: &Peer, height: u32) -> Result<Bytes32, WalletError> {
1038 let resp: Result<RespondBlockHeader, RejectHeaderRequest> = peer
1039 .request_fallible(RequestBlockHeader { height })
1040 .await
1041 .map_err(WalletError::Client)?;
1042
1043 resp.map_err(|_| WalletError::RejectHeaderRequest)
1044 .map(|resp| resp.header_block.header_hash())
1045}
1046
1047pub async fn get_fee_estimate(peer: &Peer, target_time_seconds: u64) -> Result<u64, WalletError> {
1048 let target_time_seconds = target_time_seconds
1049 + SystemTime::now()
1050 .duration_since(UNIX_EPOCH)
1051 .expect("Time went backwards")
1052 .as_secs();
1053
1054 let resp: RespondFeeEstimates = peer
1055 .request_infallible(RequestFeeEstimates {
1056 time_targets: vec![target_time_seconds],
1057 })
1058 .await
1059 .map_err(WalletError::Client)?;
1060 let fee_estimate_group = resp.estimates;
1061
1062 if let Some(error_message) = fee_estimate_group.error {
1063 return Err(WalletError::FeeEstimateRejection(error_message));
1064 }
1065
1066 if let Some(first_estimate) = fee_estimate_group.estimates.first() {
1067 if let Some(error_message) = &first_estimate.error {
1068 return Err(WalletError::FeeEstimateRejection(error_message.clone()));
1069 }
1070
1071 return Ok(first_estimate.estimated_fee_rate.mojos_per_clvm_cost);
1072 }
1073
1074 Err(WalletError::FeeEstimateRejection(
1075 "No fee estimates available".to_string(),
1076 ))
1077}
1078
1079pub async fn is_coin_spent(
1080 peer: &Peer,
1081 coin_id: Bytes32,
1082 last_height: Option<u32>,
1083 last_header_hash: Bytes32,
1084) -> Result<bool, WalletError> {
1085 let response = peer
1086 .request_coin_state(vec![coin_id], last_height, last_header_hash, false)
1087 .await
1088 .map_err(WalletError::Client)?
1089 .map_err(|_| WalletError::RejectCoinState)?;
1090
1091 if let Some(coin_state) = response.coin_states.first() {
1092 return Ok(coin_state.spent_height.is_some());
1093 }
1094
1095 Ok(false)
1096}
1097
1098pub fn make_message(msg: Bytes) -> Result<Bytes32, WalletError> {
1100 let mut alloc = Allocator::new();
1101 let thing_ptr = clvm_tuple!("Chia Signed Message", msg)
1102 .to_clvm(&mut alloc)
1103 .map_err(DriverError::ToClvm)?;
1104
1105 Ok(tree_hash(&alloc, thing_ptr).into())
1106}
1107
1108pub fn sign_message(message: Bytes, sk: SecretKey) -> Result<Signature, WalletError> {
1109 Ok(sign(&sk, make_message(message)?))
1110}
1111
1112pub fn verify_signature(
1113 message: Bytes,
1114 pk: PublicKey,
1115 sig: Signature,
1116) -> Result<bool, WalletError> {
1117 Ok(verify(&sig, &pk, make_message(message)?))
1118}
1119
1120pub fn get_cost(coin_spends: Vec<CoinSpend>) -> Result<u64, WalletError> {
1121 let mut alloc = Allocator::new();
1122
1123 let generator = solution_generator(
1124 coin_spends
1125 .into_iter()
1126 .map(|cs| (cs.coin, cs.puzzle_reveal, cs.solution)),
1127 )
1128 .map_err(WalletError::Io)?;
1129
1130 let conds = run_block_generator::<&[u8], _>(
1131 &mut alloc,
1132 &generator,
1133 [],
1134 u64::MAX,
1135 MEMPOOL_MODE | DONT_VALIDATE_SIGNATURE,
1136 &Signature::default(),
1137 None,
1138 TargetNetwork::Mainnet.get_constants(),
1139 )?;
1140
1141 let conds = OwnedSpendBundleConditions::from(&alloc, conds);
1142
1143 Ok(conds.cost)
1144}
1145
1146pub struct PossibleLaunchersResponse {
1147 pub launcher_ids: Vec<Bytes32>,
1148 pub last_height: u32,
1149 pub last_header_hash: Bytes32,
1150}
1151
1152pub async fn look_up_possible_launchers(
1153 peer: &Peer,
1154 previous_height: Option<u32>,
1155 previous_header_hash: Bytes32,
1156) -> Result<PossibleLaunchersResponse, WalletError> {
1157 let resp = get_unspent_coin_states(
1158 peer,
1159 DATASTORE_LAUNCHER_HINT,
1160 previous_height,
1161 previous_header_hash,
1162 true,
1163 )
1164 .await?;
1165
1166 Ok(PossibleLaunchersResponse {
1167 last_header_hash: resp.last_header_hash,
1168 last_height: resp.last_height,
1169 launcher_ids: resp
1170 .coin_states
1171 .into_iter()
1172 .filter_map(|coin_state| {
1173 if coin_state.coin.puzzle_hash == SINGLETON_LAUNCHER_HASH.into() {
1174 Some(coin_state.coin.coin_id())
1175 } else {
1176 None
1177 }
1178 })
1179 .collect(),
1180 })
1181}
1182
1183pub async fn subscribe_to_coin_states(
1184 peer: &Peer,
1185 coin_id: Bytes32,
1186 previous_height: Option<u32>,
1187 previous_header_hash: Bytes32,
1188) -> Result<Option<u32>, WalletError> {
1189 let response = peer
1190 .request_coin_state(vec![coin_id], previous_height, previous_header_hash, true)
1191 .await
1192 .map_err(WalletError::Client)?
1193 .map_err(|_| WalletError::RejectCoinState)?;
1194
1195 if let Some(coin_state) = response.coin_states.first() {
1196 return Ok(coin_state.spent_height);
1197 }
1198
1199 Err(WalletError::UnknownCoin)
1200}
1201
1202pub async fn unsubscribe_from_coin_states(
1203 peer: &Peer,
1204 coin_id: Bytes32,
1205) -> Result<(), WalletError> {
1206 peer.remove_coin_subscriptions(Some(vec![coin_id]))
1207 .await
1208 .map_err(WalletError::Client)?;
1209
1210 Ok(())
1211}
1212
1213#[allow(clippy::too_many_arguments)]
1230pub async fn mint_nft(
1231 peer: &Peer,
1232 synthetic_key: PublicKey,
1233 selected_coins: Vec<Coin>,
1234 did_string: &str,
1235 recipient_puzzle_hash: Bytes32,
1236 metadata: NftMetadata,
1237 _royalty_puzzle_hash: Option<Bytes32>,
1238 royalty_basis_points: u16,
1239 fee: u64,
1240 network: TargetNetwork,
1241) -> Result<Vec<CoinSpend>, WalletError> {
1242 let (did_proof, did_coin) =
1244 resolve_did_string_and_generate_proof(peer, did_string, network).await?;
1245 let mut ctx = SpendContext::new();
1246
1247 let did_proof = match did_proof {
1249 chia::puzzles::Proof::Eve(eve) => Proof::Eve(EveProof {
1250 parent_parent_coin_info: eve.parent_parent_coin_info,
1251 parent_amount: eve.parent_amount,
1252 }),
1253 chia::puzzles::Proof::Lineage(lineage) => Proof::Lineage(LineageProof {
1254 parent_parent_coin_info: lineage.parent_parent_coin_info,
1255 parent_inner_puzzle_hash: lineage.parent_inner_puzzle_hash,
1256 parent_amount: lineage.parent_amount,
1257 }),
1258 };
1259
1260 let public_key_bytes = synthetic_key.derive_synthetic().to_bytes();
1263 let mut public_key_hash = [0u8; 32];
1264 public_key_hash.copy_from_slice(&public_key_bytes[..32]);
1265 let mut meta_data_allocator = Allocator::new();
1266 let node_metadata = metadata.to_clvm(&mut meta_data_allocator)?;
1267 let metadata_hashed_ptr = HashedPtr::from_ptr(&meta_data_allocator, node_metadata);
1268 let did_info: DidInfo = DidInfo::new(
1269 did_coin.coin_id(),
1270 None,
1271 1,
1272 metadata_hashed_ptr,
1273 public_key_hash.into(),
1274 );
1275
1276 let did = Did::new(did_coin, did_proof, did_info);
1277
1278 let p2 = StandardLayer::new(synthetic_key);
1280
1281 let nft_mint = NftMint::new(
1283 metadata_hashed_ptr,
1284 recipient_puzzle_hash,
1285 royalty_basis_points,
1286 None, );
1288
1289 let (mint_conditions, _nft) = IntermediateLauncher::new(did_coin.coin_id(), 0, 1)
1291 .create(&mut ctx)?
1292 .mint_nft(&mut ctx, &nft_mint)?;
1293
1294 let _updated_did = did.update(&mut ctx, &p2, mint_conditions)?;
1296
1297 let total_input = selected_coins.iter().map(|coin| coin.amount).sum::<u64>();
1299 let total_needed = fee + 1; if total_input < total_needed {
1302 return Err(WalletError::InsufficientCoinAmount); }
1304
1305 let _change = total_input - total_needed;
1306 let change_puzzle_hash = StandardArgs::curry_tree_hash(synthetic_key).into();
1307
1308 spend_coins_together(
1310 &mut ctx,
1311 synthetic_key,
1312 &selected_coins,
1313 Conditions::new().reserve_fee(fee),
1314 total_needed as i64,
1315 change_puzzle_hash,
1316 )?;
1317
1318 Ok(ctx.take())
1319}
1320
1321pub async fn generate_did_proof(
1332 peer: &Peer,
1333 did_coin: Coin,
1334 network: TargetNetwork,
1335) -> Result<(chia::puzzles::Proof, Coin), WalletError> {
1336 let proof = generate_did_proof_from_chain(peer, did_coin, network).await?;
1337 Ok((proof, did_coin))
1338}
1339
1340pub fn generate_did_proof_manual(
1350 did_coin: Coin,
1351 parent_coin: Option<Coin>,
1352 parent_inner_puzzle_hash: Option<Bytes32>,
1353) -> Result<chia::puzzles::Proof, WalletError> {
1354 match parent_coin {
1355 None => {
1357 Ok(chia::puzzles::Proof::Eve(chia::puzzles::EveProof {
1361 parent_parent_coin_info: did_coin.parent_coin_info,
1362 parent_amount: 1, }))
1364 }
1365 Some(parent) => {
1367 let parent_inner_puzzle_hash = parent_inner_puzzle_hash.ok_or(WalletError::Parse(
1368 "Parent inner puzzle hash is required".to_string(),
1369 ))?; Ok(chia::puzzles::Proof::Lineage(chia::puzzles::LineageProof {
1372 parent_parent_coin_info: parent.parent_coin_info,
1373 parent_inner_puzzle_hash,
1374 parent_amount: parent.amount,
1375 }))
1376 }
1377 }
1378}
1379
1380pub async fn generate_did_proof_from_chain(
1390 peer: &Peer,
1391 did_coin: Coin,
1392 network: TargetNetwork,
1393) -> Result<chia::puzzles::Proof, WalletError> {
1394 let parent_coin_states = peer
1396 .request_coin_state(
1397 vec![did_coin.parent_coin_info],
1398 None,
1399 match network {
1400 TargetNetwork::Mainnet => MAINNET_CONSTANTS.genesis_challenge,
1401 TargetNetwork::Testnet11 => TESTNET11_CONSTANTS.genesis_challenge,
1402 },
1403 false,
1404 )
1405 .await?
1406 .map_err(|_| WalletError::RejectCoinState)?
1407 .coin_states;
1408
1409 let parent_coin_state = parent_coin_states.first().ok_or(WalletError::UnknownCoin)?;
1410
1411 if parent_coin_state.coin.puzzle_hash == SINGLETON_LAUNCHER_HASH.into() {
1413 return Ok(chia::puzzles::Proof::Eve(chia::puzzles::EveProof {
1415 parent_parent_coin_info: parent_coin_state.coin.parent_coin_info,
1416 parent_amount: parent_coin_state.coin.amount,
1417 }));
1418 }
1419
1420 let parent_spend_height = parent_coin_state
1422 .spent_height
1423 .ok_or(WalletError::UnknownCoin)?;
1424
1425 let _parent_spend = peer
1426 .request_puzzle_and_solution(parent_coin_state.coin.coin_id(), parent_spend_height)
1427 .await?
1428 .map_err(|_| WalletError::RejectPuzzleSolution)?;
1429
1430 let _allocator = Allocator::new();
1431
1432 Ok(chia::puzzles::Proof::Lineage(chia::puzzles::LineageProof {
1435 parent_parent_coin_info: parent_coin_state.coin.parent_coin_info,
1436 parent_inner_puzzle_hash: Bytes32::default(), parent_amount: parent_coin_state.coin.amount,
1438 }))
1439}
1440
1441pub fn create_simple_did(
1451 synthetic_key: PublicKey,
1452 selected_coins: Vec<Coin>,
1453 fee: u64,
1454) -> Result<(Vec<CoinSpend>, Coin), WalletError> {
1455 let mut ctx = SpendContext::new();
1456
1457 let p2 = StandardLayer::new(synthetic_key);
1458 let puzzle_hash = StandardArgs::curry_tree_hash(synthetic_key).into();
1459
1460 let total_input = selected_coins.iter().map(|coin| coin.amount).sum::<u64>();
1462 let total_needed = fee + 1; if total_input < total_needed {
1465 return Err(WalletError::InsufficientCoinAmount); }
1467
1468 let change = total_input - total_needed;
1469
1470 let first_coin = selected_coins[0];
1472 let launcher = Launcher::new(first_coin.coin_id(), 1);
1473
1474 let (create_did_conditions, did) = launcher.create_simple_did(&mut ctx, &p2)?;
1476
1477 let first_coin_id = first_coin.coin_id();
1479
1480 for (i, &coin) in selected_coins.iter().enumerate() {
1481 if i == 0 {
1482 let mut conditions = create_did_conditions.clone();
1484
1485 if change > 0 {
1486 let hint = ctx.hint(puzzle_hash)?;
1487 conditions = conditions.create_coin(puzzle_hash, change, hint);
1488 }
1489
1490 if fee > 0 {
1491 conditions = conditions.reserve_fee(fee);
1492 }
1493
1494 p2.spend(&mut ctx, coin, conditions)?;
1495 } else {
1496 p2.spend(
1498 &mut ctx,
1499 coin,
1500 Conditions::new().assert_concurrent_spend(first_coin_id),
1501 )?;
1502 }
1503 }
1504
1505 Ok((ctx.take(), did.coin))
1506}
1507
1508pub async fn resolve_did_string_and_generate_proof(
1518 peer: &Peer,
1519 did_string: &str,
1520 network: TargetNetwork,
1521) -> Result<(chia::puzzles::Proof, Coin), WalletError> {
1522 let parts: Vec<&str> = did_string.split(':').collect();
1524
1525 if parts.len() != 3 || parts[0] != "did" || parts[1] != "chia" {
1526 return Err(WalletError::Parse("Invalid DID string".to_string()));
1527 }
1528
1529 let bech32_part = parts[2];
1530
1531 use chia_wallet_sdk::utils::Address;
1533 let address = Address::decode(bech32_part)
1534 .map_err(|_| WalletError::Parse("Cannot decode address".to_string()))?;
1535
1536 let did_id = address.puzzle_hash;
1537
1538 let launcher_states = peer
1540 .request_coin_state(
1541 vec![did_id],
1542 None,
1543 match network {
1544 TargetNetwork::Mainnet => MAINNET_CONSTANTS.genesis_challenge,
1545 TargetNetwork::Testnet11 => TESTNET11_CONSTANTS.genesis_challenge,
1546 },
1547 false,
1548 )
1549 .await?
1550 .map_err(|_| WalletError::RejectCoinState)?
1551 .coin_states;
1552
1553 let launcher_state = launcher_states.first().ok_or(WalletError::UnknownCoin)?;
1554
1555 if launcher_state.coin.puzzle_hash != SINGLETON_LAUNCHER_HASH.into() {
1557 return Err(WalletError::PuzzleHashMismatch(
1558 "Coin puzzle hash does not match datastore singleton launcher hash".to_string(),
1559 ));
1560 }
1561
1562 let launcher_spend_height = launcher_state
1564 .spent_height
1565 .ok_or(WalletError::UnknownCoin)?;
1566
1567 let launcher_spend = peer
1568 .request_puzzle_and_solution(launcher_state.coin.coin_id(), launcher_spend_height)
1569 .await?
1570 .map_err(|_| WalletError::RejectPuzzleSolution)?;
1571
1572 let mut allocator = Allocator::new();
1573
1574 let launcher_puzzle = launcher_spend.puzzle.to_clvm(&mut allocator)?;
1576 let launcher_solution = launcher_spend.solution.to_clvm(&mut allocator)?;
1577
1578 let output = clvmr::run_program(
1579 &mut allocator,
1580 &clvmr::ChiaDialect::new(0),
1581 launcher_puzzle,
1582 launcher_solution,
1583 u64::MAX,
1584 )
1585 .map_err(|_| WalletError::Clvm)?;
1586
1587 let conditions =
1588 Vec::<Condition>::from_clvm(&allocator, output.1).map_err(|_| WalletError::Clvm)?;
1589
1590 let mut first_did_coin: Option<Coin> = None;
1592 for condition in conditions {
1593 if let Some(create_coin) = condition.into_create_coin() {
1594 if create_coin.amount % 2 == 1 {
1596 first_did_coin = Some(Coin::new(
1597 launcher_state.coin.coin_id(),
1598 create_coin.puzzle_hash,
1599 create_coin.amount,
1600 ));
1601 break;
1602 }
1603 }
1604 }
1605
1606 let first_did_coin = first_did_coin.ok_or(WalletError::UnknownCoin)?;
1607
1608 let mut current_did_coin = first_did_coin;
1610
1611 loop {
1612 let coin_states = peer
1614 .request_coin_state(
1615 vec![current_did_coin.coin_id()],
1616 None,
1617 match network {
1618 TargetNetwork::Mainnet => MAINNET_CONSTANTS.genesis_challenge,
1619 TargetNetwork::Testnet11 => TESTNET11_CONSTANTS.genesis_challenge,
1620 },
1621 false,
1622 )
1623 .await?
1624 .map_err(|_| WalletError::RejectCoinState)?
1625 .coin_states;
1626
1627 let coin_state = coin_states.first().ok_or(WalletError::UnknownCoin)?;
1628
1629 if coin_state.spent_height.is_none() {
1631 break;
1632 }
1633
1634 let spend_height = coin_state.spent_height.unwrap();
1636 let spend = peer
1637 .request_puzzle_and_solution(current_did_coin.coin_id(), spend_height)
1638 .await?
1639 .map_err(|_| WalletError::RejectPuzzleSolution)?;
1640
1641 let spend_puzzle = spend.puzzle.to_clvm(&mut allocator)?;
1643 let spend_solution = spend.solution.to_clvm(&mut allocator)?;
1644
1645 let spend_output = clvmr::run_program(
1646 &mut allocator,
1647 &clvmr::ChiaDialect::new(0),
1648 spend_puzzle,
1649 spend_solution,
1650 u64::MAX,
1651 )
1652 .map_err(|_| WalletError::Clvm)?;
1653
1654 let spend_conditions = Vec::<Condition>::from_clvm(&allocator, spend_output.1)
1655 .map_err(|_| WalletError::Clvm)?;
1656
1657 let mut child_did_coin: Option<Coin> = None;
1659 for condition in spend_conditions {
1660 if let Some(create_coin) = condition.into_create_coin() {
1661 if create_coin.amount % 2 == 1 {
1663 child_did_coin = Some(Coin::new(
1664 current_did_coin.coin_id(),
1665 create_coin.puzzle_hash,
1666 create_coin.amount,
1667 ));
1668 break;
1669 }
1670 }
1671 }
1672
1673 current_did_coin = child_did_coin.ok_or(WalletError::UnknownCoin)?;
1674 }
1675
1676 let proof = generate_did_proof_from_chain(peer, current_did_coin, network).await?;
1678
1679 Ok((proof, current_did_coin))
1680}