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