1pub use chia::bls::{master_to_wallet_unhardened, PublicKey, SecretKey, Signature};
19pub use chia::protocol::{Bytes, Bytes32, Coin, CoinSpend, CoinState, Program, SpendBundle};
20pub use chia::puzzles::{EveProof, LineageProof, Proof};
21pub use chia_wallet_sdk::client::Peer;
22pub use chia_wallet_sdk::driver::{
23 DataStore, DataStoreInfo, DataStoreMetadata, DelegatedPuzzle, P2ParentCoin,
24};
25pub use chia_wallet_sdk::utils::Address;
26
27pub use async_api::{connect_peer, connect_random, create_tls_connector, NetworkType};
29pub use constants::{get_mainnet_genesis_challenge, get_testnet11_genesis_challenge};
30
31mod error;
33pub mod types;
34pub mod wallet;
35pub mod xch_server_coin;
36
37pub use types::{
39 BlsPair, SimulatorPuzzle, SuccessResponse, UnspentCoinStates, UnspentCoinsResponse,
40};
41pub use wallet::{
42 create_simple_did, generate_did_proof, generate_did_proof_from_chain,
43 generate_did_proof_manual, get_fee_estimate, get_header_hash, get_store_creation_height,
44 get_unspent_coin_states, is_coin_spent, look_up_possible_launchers, mint_nft,
45 spend_xch_server_coins, subscribe_to_coin_states, sync_store, sync_store_using_launcher_id,
46 unsubscribe_from_coin_states, verify_signature, DataStoreInnerSpend, PossibleLaunchersResponse,
47 SyncStoreResponse, TargetNetwork,
48};
49pub use xch_server_coin::{morph_launcher_id, XchServerCoin};
50
51use hex_literal::hex;
52
53pub type Result<T> = std::result::Result<T, Box<dyn std::error::Error + Send + Sync>>;
55
56use chia::puzzles::{standard::StandardArgs, DeriveSynthetic};
58use chia_wallet_sdk::prelude::ToTreeHash;
59use xch_server_coin::NewXchServerCoin;
61
62pub const DIG_MIN_HEIGHT: u32 = 5777842;
63pub const DIG_MIN_HEIGHT_HEADER_HASH: Bytes32 = Bytes32::new(hex!(
64 "b29a4daac2434fd17a36e15ba1aac5d65012d4a66f99bed0bf2b5342e92e562c"
65));
66
67pub const DIG_STORE_LAUNCHER_ID_MORPH: &str = "DIG_STORE";
68
69pub fn morph_store_launcher_id(store_launcher_id: Bytes32) -> Bytes32 {
71 (store_launcher_id, DIG_STORE_LAUNCHER_ID_MORPH)
72 .tree_hash()
73 .into()
74}
75
76pub fn master_public_key_to_wallet_synthetic_key(public_key: &PublicKey) -> PublicKey {
78 master_to_wallet_unhardened(public_key, 0).derive_synthetic()
79}
80
81pub fn master_public_key_to_first_puzzle_hash(public_key: &PublicKey) -> Bytes32 {
83 let wallet_pk = master_to_wallet_unhardened(public_key, 0).derive_synthetic();
84 StandardArgs::curry_tree_hash(wallet_pk).into()
85}
86
87pub fn master_secret_key_to_wallet_synthetic_secret_key(secret_key: &SecretKey) -> SecretKey {
89 master_to_wallet_unhardened(secret_key, 0).derive_synthetic()
90}
91
92pub fn secret_key_to_public_key(secret_key: &SecretKey) -> PublicKey {
94 secret_key.public_key()
95}
96
97pub fn synthetic_key_to_puzzle_hash(synthetic_key: &PublicKey) -> Bytes32 {
99 StandardArgs::curry_tree_hash(*synthetic_key).into()
100}
101
102pub fn admin_delegated_puzzle_from_key(synthetic_key: &PublicKey) -> DelegatedPuzzle {
104 DelegatedPuzzle::Admin(StandardArgs::curry_tree_hash(*synthetic_key))
105}
106
107pub fn writer_delegated_puzzle_from_key(synthetic_key: &PublicKey) -> DelegatedPuzzle {
109 DelegatedPuzzle::Writer(StandardArgs::curry_tree_hash(*synthetic_key))
110}
111
112pub fn oracle_delegated_puzzle(oracle_puzzle_hash: Bytes32, oracle_fee: u64) -> DelegatedPuzzle {
114 DelegatedPuzzle::Oracle(oracle_puzzle_hash, oracle_fee)
115}
116
117pub fn get_coin_id(coin: &Coin) -> Bytes32 {
119 coin.coin_id()
120}
121
122pub fn puzzle_hash_to_address(puzzle_hash: Bytes32, prefix: &str) -> Result<String> {
124 use chia_wallet_sdk::utils::Address;
125 Ok(Address::new(puzzle_hash, prefix.to_string()).encode()?)
126}
127
128pub fn address_to_puzzle_hash(address: &str) -> Result<Bytes32> {
130 use chia_wallet_sdk::utils::Address;
131 Ok(Address::decode(address)?.puzzle_hash)
132}
133
134pub fn hex_spend_bundle_to_coin_spends(hex: &str) -> Result<Vec<CoinSpend>> {
136 use chia::traits::Streamable;
137 let bytes = hex::decode(hex)?;
138 let spend_bundle = SpendBundle::from_bytes(&bytes)?;
139 Ok(spend_bundle.coin_spends)
140}
141
142pub fn spend_bundle_to_hex(spend_bundle: &SpendBundle) -> Result<String> {
144 use chia::traits::Streamable;
145 let bytes = spend_bundle.to_bytes()?;
146 Ok(hex::encode(bytes))
147}
148
149pub fn morph_launcher_id_wrapper(launcher_id: Bytes32, offset: u64) -> Bytes32 {
151 xch_server_coin::morph_launcher_id(launcher_id, &offset.into())
152}
153
154#[derive(Debug, Clone)]
156pub struct Output {
157 pub puzzle_hash: Bytes32,
158 pub amount: u64,
159 pub memos: Vec<Bytes>,
160}
161
162pub fn send_xch(
164 synthetic_key: &PublicKey,
165 selected_coins: &[Coin],
166 outputs: &[Output],
167 fee: u64,
168) -> Result<Vec<CoinSpend>> {
169 let outputs: Vec<(Bytes32, u64, Vec<Bytes>)> = outputs
170 .iter()
171 .map(|output| (output.puzzle_hash, output.amount, output.memos.clone()))
172 .collect();
173
174 Ok(wallet::send_xch(
175 *synthetic_key,
176 selected_coins,
177 &outputs,
178 fee,
179 )?)
180}
181
182pub fn select_coins(all_coins: &[Coin], total_amount: u64) -> Result<Vec<Coin>> {
184 Ok(wallet::select_coins(all_coins.to_vec(), total_amount)?)
185}
186
187pub fn add_fee(
189 spender_synthetic_key: &PublicKey,
190 selected_coins: &[Coin],
191 assert_coin_ids: &[Bytes32],
192 fee: u64,
193) -> Result<Vec<CoinSpend>> {
194 Ok(wallet::add_fee(
195 *spender_synthetic_key,
196 selected_coins.to_vec(),
197 assert_coin_ids.to_vec(),
198 fee,
199 )?)
200}
201
202pub fn sign_coin_spends(
204 coin_spends: &[CoinSpend],
205 private_keys: &[SecretKey],
206 for_testnet: bool,
207) -> Result<Signature> {
208 Ok(wallet::sign_coin_spends(
209 coin_spends.to_vec(),
210 private_keys.to_vec(),
211 if for_testnet {
212 wallet::TargetNetwork::Testnet11
213 } else {
214 wallet::TargetNetwork::Mainnet
215 },
216 )?)
217}
218
219pub fn sign_message(message: &[u8], private_key: &SecretKey) -> Result<Signature> {
221 Ok(wallet::sign_message(message.into(), private_key.clone())?)
222}
223
224pub fn verify_signed_message(
226 signature: &Signature,
227 public_key: &PublicKey,
228 message: &[u8],
229) -> Result<bool> {
230 Ok(wallet::verify_signature(
231 message.into(),
232 *public_key,
233 signature.clone(),
234 )?)
235}
236
237pub fn get_cost(coin_spends: &[CoinSpend]) -> Result<u64> {
239 Ok(wallet::get_cost(coin_spends.to_vec())?)
240}
241
242#[allow(clippy::too_many_arguments)]
244pub fn mint_store(
245 minter_synthetic_key: PublicKey,
246 selected_coins: Vec<Coin>,
247 root_hash: Bytes32,
248 label: Option<String>,
249 description: Option<String>,
250 bytes: Option<u64>,
251 size_proof: Option<String>,
252 owner_puzzle_hash: Bytes32,
253 delegated_puzzles: Vec<DelegatedPuzzle>,
254 fee: u64,
255) -> Result<SuccessResponse> {
256 Ok(wallet::mint_store(
257 minter_synthetic_key,
258 selected_coins,
259 root_hash,
260 label,
261 description,
262 bytes,
263 size_proof,
264 owner_puzzle_hash,
265 delegated_puzzles,
266 fee,
267 )?)
268}
269
270pub fn oracle_spend(
272 spender_synthetic_key: PublicKey,
273 selected_coins: Vec<Coin>,
274 store: DataStore,
275 fee: u64,
276) -> Result<SuccessResponse> {
277 Ok(wallet::oracle_spend(
278 spender_synthetic_key,
279 selected_coins,
280 store,
281 fee,
282 )?)
283}
284
285#[allow(clippy::too_many_arguments)]
287pub fn update_store_metadata(
288 store: DataStore,
289 new_root_hash: Bytes32,
290 new_label: Option<String>,
291 new_description: Option<String>,
292 new_bytes: Option<u64>,
293 new_size_proof: Option<String>,
294 inner_spend_info: DataStoreInnerSpend,
295) -> Result<SuccessResponse> {
296 Ok(wallet::update_store_metadata(
297 store,
298 new_root_hash,
299 new_label,
300 new_description,
301 new_bytes,
302 new_size_proof,
303 inner_spend_info,
304 )?)
305}
306
307pub fn update_store_ownership(
309 store: DataStore,
310 new_owner_puzzle_hash: Bytes32,
311 new_delegated_puzzles: Vec<DelegatedPuzzle>,
312 inner_spend_info: wallet::DataStoreInnerSpend,
313) -> Result<SuccessResponse> {
314 Ok(wallet::update_store_ownership(
315 store,
316 new_owner_puzzle_hash,
317 new_delegated_puzzles,
318 inner_spend_info,
319 )?)
320}
321
322pub fn melt_store(store: DataStore, owner_pk: PublicKey) -> Result<Vec<CoinSpend>> {
324 Ok(wallet::melt_store(store, owner_pk)?)
325}
326
327pub fn create_server_coin(
329 synthetic_key: PublicKey,
330 selected_coins: Vec<Coin>,
331 hint: Bytes32,
332 uris: Vec<String>,
333 amount: u64,
334 fee: u64,
335) -> Result<NewXchServerCoin> {
336 Ok(wallet::create_server_coin(
337 synthetic_key,
338 selected_coins,
339 hint,
340 uris,
341 amount,
342 fee,
343 )?)
344}
345
346pub mod async_api {
348 use super::*;
349 use chia_wallet_sdk::prelude::Cat;
350 use futures_util::stream::{FuturesUnordered, StreamExt};
351 use rand::seq::SliceRandom;
352 use std::net::SocketAddr;
353 use tokio::net::lookup_host;
354 use tokio::time::{timeout, Duration};
355
356 const MAINNET_DNS_INTRODUCERS: &[&str] = &[
358 "dns-introducer.chia.net",
359 "chia.ctrlaltdel.ch",
360 "seeder.dexie.space",
361 "chia.hoffmang.com",
362 ];
363 const TESTNET11_DNS_INTRODUCERS: &[&str] = &["dns-introducer-testnet11.chia.net"];
364 const MAINNET_DEFAULT_PORT: u16 = 8444;
365 const TESTNET11_DEFAULT_PORT: u16 = 58444;
366
367 #[derive(Debug, Clone, Copy, PartialEq, Eq)]
368 pub enum NetworkType {
369 Mainnet,
370 Testnet11,
371 }
372
373 pub async fn connect_random(
379 network: NetworkType,
380 cert_path: &str,
381 key_path: &str,
382 ) -> Result<Peer> {
383 let cert = chia_wallet_sdk::client::load_ssl_cert(cert_path, key_path)?;
385 let tls = chia_wallet_sdk::client::create_native_tls_connector(&cert)?;
386
387 let (introducers, default_port) = match network {
389 NetworkType::Mainnet => (MAINNET_DNS_INTRODUCERS, MAINNET_DEFAULT_PORT),
390 NetworkType::Testnet11 => (TESTNET11_DNS_INTRODUCERS, TESTNET11_DEFAULT_PORT),
391 };
392
393 let mut addrs = Vec::new();
395 for introducer in introducers {
396 if let Ok(iter) = lookup_host((*introducer, default_port)).await {
397 addrs.extend(iter);
398 }
399 }
400
401 if addrs.is_empty() {
402 return Err("Failed to resolve any peer addresses from introducers".into());
403 }
404
405 {
407 let mut rng = rand::thread_rng();
408 addrs.shuffle(&mut rng);
409 }
410
411 const BATCH_SIZE: usize = 10;
413 const CONNECT_TIMEOUT: Duration = Duration::from_secs(8);
414
415 for chunk in addrs.chunks(BATCH_SIZE) {
416 let mut futures = FuturesUnordered::new();
417 for addr in chunk {
418 let addr = *addr;
419 let network_str = match network {
420 NetworkType::Mainnet => "mainnet",
421 NetworkType::Testnet11 => "testnet11",
422 };
423 let tls_clone = tls.clone();
424
425 futures.push(async move {
427 timeout(
428 CONNECT_TIMEOUT,
429 chia_wallet_sdk::client::connect_peer(
430 network_str.to_string(),
431 tls_clone,
432 addr,
433 chia_wallet_sdk::client::PeerOptions::default(),
434 ),
435 )
436 .await
437 });
438 }
439
440 while let Some(result) = futures.next().await {
441 match result {
442 Ok(Ok((peer, _receiver))) => {
443 return Ok(peer);
445 }
446 _ => {
447 }
449 }
450 }
451 }
452
453 Err("Unable to connect to any discovered peer".into())
454 }
455
456 pub fn create_tls_connector(
458 cert_path: &str,
459 key_path: &str,
460 ) -> Result<chia_wallet_sdk::client::Connector> {
461 let cert = chia_wallet_sdk::client::load_ssl_cert(cert_path, key_path)?;
462 Ok(chia_wallet_sdk::client::create_native_tls_connector(&cert)?)
463 }
464
465 pub async fn connect_peer(
467 network: NetworkType,
468 tls_connector: chia_wallet_sdk::client::Connector,
469 address: SocketAddr,
470 ) -> Result<Peer> {
471 let network_str = match network {
472 NetworkType::Mainnet => "mainnet",
473 NetworkType::Testnet11 => "testnet11",
474 };
475
476 let (peer, _receiver) = chia_wallet_sdk::client::connect_peer(
477 network_str.to_string(),
478 tls_connector,
479 address,
480 chia_wallet_sdk::client::PeerOptions::default(),
481 )
482 .await?;
483
484 Ok(peer)
485 }
486
487 #[allow(clippy::too_many_arguments)]
489 pub async fn mint_nft(
490 peer: &Peer,
491 synthetic_key: PublicKey,
492 selected_coins: Vec<Coin>,
493 did_string: &str,
494 recipient_puzzle_hash: Bytes32,
495 metadata: chia::puzzles::nft::NftMetadata,
496 royalty_puzzle_hash: Option<Bytes32>,
497 royalty_basis_points: u16,
498 fee: u64,
499 for_testnet: Option<bool>,
500 ) -> Result<Vec<CoinSpend>> {
501 let network = if for_testnet.unwrap_or(false) {
502 wallet::TargetNetwork::Testnet11
503 } else {
504 wallet::TargetNetwork::Mainnet
505 };
506
507 Ok(wallet::mint_nft(
508 peer,
509 synthetic_key,
510 selected_coins,
511 did_string,
512 recipient_puzzle_hash,
513 metadata,
514 royalty_puzzle_hash,
515 royalty_basis_points,
516 fee,
517 network,
518 )
519 .await?)
520 }
521
522 pub async fn generate_did_proof(
524 peer: &Peer,
525 did_coin: Coin,
526 for_testnet: bool,
527 ) -> Result<(Proof, Coin)> {
528 let network = if for_testnet {
529 wallet::TargetNetwork::Testnet11
530 } else {
531 wallet::TargetNetwork::Mainnet
532 };
533
534 Ok(wallet::generate_did_proof(peer, did_coin, network).await?)
535 }
536
537 pub fn create_simple_did(
539 synthetic_key: PublicKey,
540 selected_coins: Vec<Coin>,
541 fee: u64,
542 ) -> Result<(Vec<CoinSpend>, Coin)> {
543 Ok(wallet::create_simple_did(
544 synthetic_key,
545 selected_coins,
546 fee,
547 )?)
548 }
549
550 pub async fn sync_store(
552 peer: &Peer,
553 store: &DataStore,
554 last_height: Option<u32>,
555 last_header_hash: Bytes32,
556 with_history: bool,
557 ) -> Result<SyncStoreResponse> {
558 Ok(wallet::sync_store(peer, store, last_height, last_header_hash, with_history).await?)
559 }
560
561 pub async fn sync_store_from_launcher_id(
563 peer: &Peer,
564 launcher_id: Bytes32,
565 last_height: Option<u32>,
566 last_header_hash: Bytes32,
567 with_history: bool,
568 ) -> Result<SyncStoreResponse> {
569 Ok(wallet::sync_store_using_launcher_id(
570 peer,
571 launcher_id,
572 last_height,
573 last_header_hash,
574 with_history,
575 )
576 .await?)
577 }
578
579 pub async fn get_unspent_coins_by_hints(
581 peer: &Peer,
582 hint: Bytes32,
583 network: NetworkType,
584 ) -> Result<UnspentCoinStates> {
585 Ok(wallet::get_unspent_coin_states_by_hint(peer, hint, network).await?)
586 }
587
588 pub async fn get_all_unspent_coins(
590 peer: &Peer,
591 puzzle_hash: Bytes32,
592 previous_height: Option<u32>,
593 previous_header_hash: Bytes32,
594 ) -> Result<UnspentCoinStates> {
595 Ok(wallet::get_unspent_coin_states(
596 peer,
597 puzzle_hash,
598 previous_height,
599 previous_header_hash,
600 false,
601 )
602 .await?)
603 }
604
605 pub async fn is_coin_spent(
607 peer: &Peer,
608 coin_id: Bytes32,
609 last_height: Option<u32>,
610 header_hash: Bytes32,
611 ) -> Result<bool> {
612 Ok(wallet::is_coin_spent(peer, coin_id, last_height, header_hash).await?)
613 }
614
615 pub async fn get_header_hash(peer: &Peer, height: u32) -> Result<Bytes32> {
617 Ok(wallet::get_header_hash(peer, height).await?)
618 }
619
620 pub async fn get_fee_estimate(peer: &Peer, target_time_seconds: u64) -> Result<u64> {
622 Ok(wallet::get_fee_estimate(peer, target_time_seconds).await?)
623 }
624
625 pub async fn broadcast_spend_bundle(
627 peer: &Peer,
628 spend_bundle: SpendBundle,
629 ) -> Result<chia::protocol::TransactionAck> {
630 Ok(wallet::broadcast_spend_bundle(peer, spend_bundle).await?)
631 }
632
633 pub async fn prove_dig_cat_coin(
636 peer: &Peer,
637 coin: &Coin,
638 coin_created_height: u32,
639 ) -> Result<Cat> {
640 Ok(wallet::prove_dig_cat_coin(peer, coin, coin_created_height).await?)
641 }
642}
643
644pub mod constants {
646 use chia_wallet_sdk::types::{MAINNET_CONSTANTS, TESTNET11_CONSTANTS};
647
648 pub fn get_mainnet_genesis_challenge() -> chia::protocol::Bytes32 {
650 MAINNET_CONSTANTS.genesis_challenge
651 }
652
653 pub fn get_testnet11_genesis_challenge() -> chia::protocol::Bytes32 {
655 TESTNET11_CONSTANTS.genesis_challenge
656 }
657}
658
659#[cfg(test)]
661mod examples {
662 use super::*;
663
664 #[test]
665 fn example_key_operations() {
666 let secret_key = SecretKey::from_bytes(&[1u8; 32]).unwrap();
668 let public_key = secret_key_to_public_key(&secret_key);
669 let _synthetic_key = master_public_key_to_wallet_synthetic_key(&public_key);
670 let puzzle_hash = master_public_key_to_first_puzzle_hash(&public_key);
671
672 let address = puzzle_hash_to_address(puzzle_hash, "xch").unwrap();
674 println!("Address: {}", address);
675
676 let decoded_hash = address_to_puzzle_hash(&address).unwrap();
678 assert_eq!(puzzle_hash, decoded_hash);
679 }
680
681 #[tokio::test]
682 async fn example_nft_minting() {
683 }
746}