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