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