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