datalayer_driver/
lib.rs

1//! # DataLayer Driver
2//!
3//! Native Chia DataLayer Driver for storing and retrieving data in Chia blockchain.
4//!
5//! This crate provides Rust APIs for interacting with Chia's DataLayer,
6//! including minting, updating, and syncing data stores.
7//!
8//! ## Features
9//!
10//! - Mint new data stores
11//! - Update store metadata and ownership
12//! - Sync stores from the blockchain
13//! - Oracle spend functionality
14//! - Server coin management
15//! - Fee management utilities
16
17// Re-export core types from dependencies
18pub 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
27// Re-export async_api and constants modules at the top level for convenience
28pub use async_api::{connect_peer, connect_random, create_tls_connector, NetworkType};
29pub use constants::{get_mainnet_genesis_challenge, get_testnet11_genesis_challenge};
30
31// Internal modules
32mod error;
33pub mod types;
34pub mod wallet;
35pub mod xch_server_coin;
36
37// Re-export types from internal modules
38pub 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
53// Type aliases for convenience
54pub type Result<T> = std::result::Result<T, Box<dyn std::error::Error + Send + Sync>>;
55
56// Helper functions for common conversions
57use chia::puzzles::{standard::StandardArgs, DeriveSynthetic};
58use chia_wallet_sdk::prelude::ToTreeHash;
59// Helper functions for common conversions
60use 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
69/// Morphs a DIG store launcher ID into the DIG namespace. Store launcher IDs should be morphed when hinted on coins
70pub 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
76/// Converts a master public key to a wallet synthetic key.
77pub fn master_public_key_to_wallet_synthetic_key(public_key: &PublicKey) -> PublicKey {
78    master_to_wallet_unhardened(public_key, 0).derive_synthetic()
79}
80
81/// Converts a master public key to the first puzzle hash.
82pub 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
87/// Converts a master secret key to a wallet synthetic secret key.
88pub 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
92/// Converts a secret key to its corresponding public key.
93pub fn secret_key_to_public_key(secret_key: &SecretKey) -> PublicKey {
94    secret_key.public_key()
95}
96
97/// Converts a synthetic key to its corresponding standard puzzle hash.
98pub fn synthetic_key_to_puzzle_hash(synthetic_key: &PublicKey) -> Bytes32 {
99    StandardArgs::curry_tree_hash(*synthetic_key).into()
100}
101
102/// Creates an admin delegated puzzle for a given key.
103pub fn admin_delegated_puzzle_from_key(synthetic_key: &PublicKey) -> DelegatedPuzzle {
104    DelegatedPuzzle::Admin(StandardArgs::curry_tree_hash(*synthetic_key))
105}
106
107/// Creates a writer delegated puzzle from a given key.
108pub fn writer_delegated_puzzle_from_key(synthetic_key: &PublicKey) -> DelegatedPuzzle {
109    DelegatedPuzzle::Writer(StandardArgs::curry_tree_hash(*synthetic_key))
110}
111
112/// Creates an oracle delegated puzzle.
113pub fn oracle_delegated_puzzle(oracle_puzzle_hash: Bytes32, oracle_fee: u64) -> DelegatedPuzzle {
114    DelegatedPuzzle::Oracle(oracle_puzzle_hash, oracle_fee)
115}
116
117/// Gets the coin ID for a given coin.
118pub fn get_coin_id(coin: &Coin) -> Bytes32 {
119    coin.coin_id()
120}
121
122/// Converts a puzzle hash to an address by encoding it using bech32m.
123pub 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
128/// Converts an address to a puzzle hash using bech32m.
129pub 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
134/// Converts hex-encoded spend bundle to coin spends.
135pub 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
142/// Converts a spend bundle to hex encoding.
143pub 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
149/// Adds an offset to a launcher id to make it deterministically unique from the original.
150pub 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/// Output for send_xch function
155#[derive(Debug, Clone)]
156pub struct Output {
157    pub puzzle_hash: Bytes32,
158    pub amount: u64,
159    pub memos: Vec<Bytes>,
160}
161
162/// Sends XCH to a given set of puzzle hashes (Rust API version).
163pub 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
182/// Selects coins using the knapsack algorithm (Rust API version).
183pub 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
187/// Adds a fee to any transaction (Rust API version).
188pub 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
202/// Signs coin spends using a list of keys (Rust API version).
203pub 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
219/// Signs a message using the provided private key (Rust API version).
220pub fn sign_message(message: &[u8], private_key: &SecretKey) -> Result<Signature> {
221    Ok(wallet::sign_message(message.into(), private_key.clone())?)
222}
223
224/// Verifies a signed message using the provided public key (Rust API version).
225pub 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
237/// Calculates the total cost of coin spends (Rust API version).
238pub fn get_cost(coin_spends: &[CoinSpend]) -> Result<u64> {
239    Ok(wallet::get_cost(coin_spends.to_vec())?)
240}
241
242/// Mints a new datastore (Rust API version).
243#[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
270/// Spends a store in oracle mode (Rust API version).
271pub 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/// Updates the metadata of a store (Rust API version).
286#[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
307/// Updates the ownership of a store (Rust API version).
308pub 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
322/// Melts a store (Rust API version).
323pub fn melt_store(store: DataStore, owner_pk: PublicKey) -> Result<Vec<CoinSpend>> {
324    Ok(wallet::melt_store(store, owner_pk)?)
325}
326
327/// Creates a server coin (Rust API version).
328pub 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
346/// Async functions for blockchain interaction (Rust API versions)
347pub 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    // DNS introducers and default ports for connecting to random peers.
357    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    /// Connects to a random peer on the specified network (Rust API version).
374    ///
375    /// The function performs DNS lookups using the network's introducers, picks a random
376    /// address from the returned list, and attempts to establish a connection. It will
377    /// try every resolved address until a connection succeeds.
378    pub async fn connect_random(
379        network: NetworkType,
380        cert_path: &str,
381        key_path: &str,
382    ) -> Result<Peer> {
383        // Load TLS certificate
384        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        // Introducers and default port per network
388        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        // Resolve all introducers to socket addresses
394        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        // Shuffle for randomness so every call has different order
406        {
407            let mut rng = rand::thread_rng();
408            addrs.shuffle(&mut rng);
409        }
410
411        // Try to connect in concurrent batches with timeout logic
412        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                // Spawn connection attempt with timeout
426                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                        // Successfully connected, return the peer
444                        return Ok(peer);
445                    }
446                    _ => {
447                        // Either timed out or failed; continue with others
448                    }
449                }
450            }
451        }
452
453        Err("Unable to connect to any discovered peer".into())
454    }
455
456    /// Creates a TLS connector for Chia peer connections (Rust API version).
457    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    /// Connects to a specific peer address (Rust API version).
466    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    /// Mints a new NFT using a DID string (Rust API version).
488    #[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    /// Generates a DID proof automatically (Rust API version).
523    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    /// Creates a simple DID (Rust API version).
538    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    /// Synchronizes a datastore (Rust API version).
551    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    /// Synchronizes a store using its launcher ID (Rust API version).
562    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    /// Gets all unspent coins hinted by the provided hint.
580    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    /// Gets all unspent coins for a puzzle hash (Rust API version).
589    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    /// Checks if a coin is spent on-chain (Rust API version).
606    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    /// Gets the header hash at a specific height (Rust API version).
616    pub async fn get_header_hash(peer: &Peer, height: u32) -> Result<Bytes32> {
617        Ok(wallet::get_header_hash(peer, height).await?)
618    }
619
620    /// Gets fee estimate for target time (Rust API version).
621    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    /// Broadcasts a spend bundle (Rust API version).
626    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    /// Utility function to validate that a coin is a $DIG CAT coin. Returns an instantiated Cat
634    /// utility for the coin if it's a valid $DIG CAT
635    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
644/// Constants for different networks
645pub mod constants {
646    use chia_wallet_sdk::types::{MAINNET_CONSTANTS, TESTNET11_CONSTANTS};
647
648    /// Returns the mainnet genesis challenge.
649    pub fn get_mainnet_genesis_challenge() -> chia::protocol::Bytes32 {
650        MAINNET_CONSTANTS.genesis_challenge
651    }
652
653    /// Returns the testnet11 genesis challenge.
654    pub fn get_testnet11_genesis_challenge() -> chia::protocol::Bytes32 {
655        TESTNET11_CONSTANTS.genesis_challenge
656    }
657}
658
659/// Example usage of the Rust API
660#[cfg(test)]
661mod examples {
662    use super::*;
663
664    #[test]
665    fn example_key_operations() {
666        // Example: Generate keys and addresses
667        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        // Convert to address
673        let address = puzzle_hash_to_address(puzzle_hash, "xch").unwrap();
674        println!("Address: {}", address);
675
676        // Convert back
677        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        // Example of complete NFT minting workflow using Rust API
684
685        /*
686        // 1. Connect to a random peer
687        let peer = connect_random(
688            NetworkType::Mainnet,
689            "~/.chia/mainnet/config/ssl/wallet/wallet_node.crt",
690            "~/.chia/mainnet/config/ssl/wallet/wallet_node.key"
691        ).await.unwrap();
692
693        // 2. Set up your wallet keys (from mnemonic or existing keys)
694        let master_secret_key = SecretKey::from_bytes([1u8; 32]).unwrap(); // Your actual key
695        let master_public_key = secret_key_to_public_key(&master_secret_key);
696        let synthetic_key = master_public_key_to_wallet_synthetic_key(&master_public_key);
697        let puzzle_hash = master_public_key_to_first_puzzle_hash(&master_public_key);
698
699        // 3. Get unspent coins for the transaction
700        let unspent_coins = async_api::get_all_unspent_coins(
701            &peer,
702            puzzle_hash,
703            None,
704            get_mainnet_genesis_challenge(),
705        ).await.unwrap();
706
707        // 4. Select coins for the transaction
708        let fee = 1_000_000; // 1 million mojos
709        let selected_coins = select_coins(&unspent_coins.coin_states.iter().map(|cs| cs.coin).collect::<Vec<_>>(), fee + 1).unwrap();
710
711        // 5. Create NFT metadata
712        let metadata = chia::puzzles::nft::NftMetadata {
713            data_uris: vec!["https://example.com/nft.png".to_string()],
714            metadata_uris: vec!["https://example.com/metadata.json".to_string()],
715            ..Default::default()
716        };
717
718        // 6. Mint the NFT
719        let nft_spends = async_api::mint_nft(
720            &peer,
721            synthetic_key,
722            selected_coins,
723            "did:chia:1s8j4pquxfu5mhlldzu357qfqkwa9r35mdx5a0p0ehn76dr4ut4tqs0n6kv",
724            puzzle_hash, // Send NFT to yourself
725            metadata,
726            None, // No royalty address
727            300,  // 3% royalty
728            fee,
729            None, // Defaults to mainnet
730        ).await.unwrap();
731
732        // 7. Sign the transaction
733        let signature = sign_coin_spends(
734            &nft_spends,
735            &[master_secret_key_to_wallet_synthetic_secret_key(&master_secret_key)],
736            false, // mainnet
737        ).unwrap();
738
739        // 8. Create and broadcast spend bundle
740        let spend_bundle = SpendBundle::new(nft_spends, signature);
741        let result = async_api::broadcast_spend_bundle(&peer, spend_bundle).await.unwrap();
742
743        println!("NFT minting transaction broadcast: {:?}", result);
744        */
745    }
746}