Skip to main content

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// `WalletError` is this crate's single error type, so nearly every fallible function returns it by
18// value. chia-wallet-sdk 0.36 grew `ClientError` to ~136 bytes, which pushes `WalletError` past
19// clippy's 128-byte `result_large_err` threshold — an upstream size change, not a defect in these
20// signatures. Boxing the variant would shrink it, but that reshapes the crate's PUBLIC error enum
21// and every construction and match site with it, which is a deliberate refactor rather than part of
22// a dependency move. Tracked as a follow-up; suppressed here so the size of an upstream struct does
23// not silently become a reason to stop building.
24#![allow(clippy::result_large_err)]
25
26// Re-export core types from dependencies
27pub 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
36// Re-export async_api and constants modules at the top level for convenience
37pub use async_api::{connect_peer, connect_random, create_tls_connector, NetworkType};
38pub use constants::{get_mainnet_genesis_challenge, get_testnet11_genesis_challenge};
39
40// Internal modules
41mod dig_coin;
42mod dig_collateral_coin;
43mod error;
44pub mod types;
45pub mod wallet;
46pub mod xch_server_coin;
47
48// Re-export types from internal modules
49pub 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
65// Type aliases for convenience
66pub type Result<T> = std::result::Result<T, Box<dyn std::error::Error + Send + Sync>>;
67
68// Helper functions for common conversions
69use chia_puzzle_types::{standard::StandardArgs, DeriveSynthetic};
70// Helper functions for common conversions
71use 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
78/// Converts a master public key to a wallet synthetic key.
79pub fn master_public_key_to_wallet_synthetic_key(public_key: &PublicKey) -> PublicKey {
80    master_to_wallet_unhardened(public_key, 0).derive_synthetic()
81}
82
83/// Converts a master public key to the first puzzle hash.
84pub 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
89/// Converts a master secret key to a wallet synthetic secret key.
90pub 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
94/// Converts a secret key to its corresponding public key.
95pub fn secret_key_to_public_key(secret_key: &SecretKey) -> PublicKey {
96    secret_key.public_key()
97}
98
99/// Converts a synthetic key to its corresponding standard puzzle hash.
100pub fn synthetic_key_to_puzzle_hash(synthetic_key: &PublicKey) -> Bytes32 {
101    StandardArgs::curry_tree_hash(*synthetic_key).into()
102}
103
104/// Creates an admin delegated puzzle for a given key.
105pub fn admin_delegated_puzzle_from_key(synthetic_key: &PublicKey) -> DelegatedPuzzle {
106    DelegatedPuzzle::Admin(StandardArgs::curry_tree_hash(*synthetic_key))
107}
108
109/// Creates a writer delegated puzzle from a given key.
110pub fn writer_delegated_puzzle_from_key(synthetic_key: &PublicKey) -> DelegatedPuzzle {
111    DelegatedPuzzle::Writer(StandardArgs::curry_tree_hash(*synthetic_key))
112}
113
114/// Creates an oracle delegated puzzle.
115pub fn oracle_delegated_puzzle(oracle_puzzle_hash: Bytes32, oracle_fee: u64) -> DelegatedPuzzle {
116    DelegatedPuzzle::Oracle(oracle_puzzle_hash, oracle_fee)
117}
118
119/// Gets the coin ID for a given coin.
120pub fn get_coin_id(coin: &Coin) -> Bytes32 {
121    coin.coin_id()
122}
123
124/// Converts a puzzle hash to an address by encoding it using bech32m.
125pub 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
130/// Converts an address to a puzzle hash using bech32m.
131pub 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
136/// Converts hex-encoded spend bundle to coin spends.
137pub 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
144/// Converts a spend bundle to hex encoding.
145pub 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
151/// Adds an offset to a launcher id to make it deterministically unique from the original.
152pub 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/// Output for send_xch function
157#[derive(Debug, Clone)]
158pub struct Output {
159    pub puzzle_hash: Bytes32,
160    pub amount: u64,
161    pub memos: Vec<Bytes>,
162}
163
164/// Sends XCH to a given set of puzzle hashes (Rust API version).
165pub 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
184/// Selects coins using the knapsack algorithm (Rust API version).
185pub 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
189/// Adds a fee to any transaction (Rust API version).
190pub 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
204/// Signs coin spends using a list of keys (Rust API version).
205pub 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
221/// Signs a message using the provided private key (Rust API version).
222pub fn sign_message(message: &[u8], private_key: &SecretKey) -> Result<Signature> {
223    Ok(wallet::sign_message(message.into(), private_key.clone())?)
224}
225
226/// Verifies a signed message using the provided public key (Rust API version).
227pub 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
239/// Calculates the total cost of coin spends (Rust API version).
240pub fn get_cost(coin_spends: &[CoinSpend]) -> Result<u64> {
241    Ok(wallet::get_cost(coin_spends.to_vec())?)
242}
243
244/// Mints a new datastore (Rust API version).
245#[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
272/// Spends a store in oracle mode (Rust API version).
273pub 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/// Updates the metadata of a store (Rust API version).
288#[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
309/// Updates the ownership of a store (Rust API version).
310pub 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
324/// Melts a store (Rust API version).
325pub fn melt_store(store: Datastore, owner_pk: PublicKey) -> Result<Vec<CoinSpend>> {
326    Ok(wallet::melt_store(store, owner_pk)?)
327}
328
329/// Creates a server coin (Rust API version).
330pub 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
348/// Async functions for blockchain interaction (Rust API versions)
349pub 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    // DNS introducers and default ports for connecting to random peers.
358    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    /// Connects to a random peer on the specified network (Rust API version).
375    ///
376    /// The function performs DNS lookups using the network's introducers, picks a random
377    /// address from the returned list, and attempts to establish a connection. It will
378    /// try every resolved address until a connection succeeds.
379    pub async fn connect_random(
380        network: NetworkType,
381        cert_path: &str,
382        key_path: &str,
383    ) -> Result<Peer> {
384        // Load TLS certificate
385        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        // Introducers and default port per network
389        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        // Resolve all introducers to socket addresses
395        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        // Shuffle for randomness so every call has different order
407        {
408            let mut rng = rand::thread_rng();
409            addrs.shuffle(&mut rng);
410        }
411
412        // Try to connect in concurrent batches with timeout logic
413        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                // Spawn connection attempt with timeout
427                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                        // Successfully connected, return the peer
445                        return Ok(peer);
446                    }
447                    _ => {
448                        // Either timed out or failed; continue with others
449                    }
450                }
451            }
452        }
453
454        Err("Unable to connect to any discovered peer".into())
455    }
456
457    /// Creates a TLS connector for Chia peer connections (Rust API version).
458    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    /// Connects to a specific peer address (Rust API version).
467    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    /// Mints a new NFT using a DID string (Rust API version).
489    #[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    /// Generates a DID proof automatically (Rust API version).
524    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    /// Creates a simple DID (Rust API version).
539    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    /// Synchronizes a datastore (Rust API version).
552    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    /// Synchronizes a store using its launcher ID (Rust API version).
563    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    /// Gets all unspent coins hinted by the provided hint.
581    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    /// Gets all unspent coins for a puzzle hash (Rust API version).
590    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    /// Checks if a coin is spent on-chain (Rust API version).
607    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    /// Gets the header hash at a specific height (Rust API version).
617    pub async fn get_header_hash(peer: &Peer, height: u32) -> Result<Bytes32> {
618        Ok(wallet::get_header_hash(peer, height).await?)
619    }
620
621    /// Gets fee estimate for target time (Rust API version).
622    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    /// Broadcasts a spend bundle (Rust API version).
627    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
635/// Constants for different networks
636pub mod constants {
637    use chia_wallet_sdk::types::{MAINNET_CONSTANTS, TESTNET11_CONSTANTS};
638
639    /// Returns the mainnet genesis challenge.
640    pub fn get_mainnet_genesis_challenge() -> chia_protocol::Bytes32 {
641        MAINNET_CONSTANTS.genesis_challenge
642    }
643
644    /// Returns the testnet11 genesis challenge.
645    pub fn get_testnet11_genesis_challenge() -> chia_protocol::Bytes32 {
646        TESTNET11_CONSTANTS.genesis_challenge
647    }
648}
649
650/// Example usage of the Rust API
651#[cfg(test)]
652mod examples {
653    use super::*;
654
655    #[test]
656    fn example_key_operations() {
657        // Example: Generate keys and addresses
658        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        // Convert to address
664        let address = puzzle_hash_to_address(puzzle_hash, "xch").unwrap();
665        println!("Address: {}", address);
666
667        // Convert back
668        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        // Example of complete NFT minting workflow using Rust API
675
676        /*
677        // 1. Connect to a random peer
678        let peer = connect_random(
679            NetworkType::Mainnet,
680            "~/.chia/mainnet/config/ssl/wallet/wallet_node.crt",
681            "~/.chia/mainnet/config/ssl/wallet/wallet_node.key"
682        ).await.unwrap();
683
684        // 2. Set up your wallet keys (from mnemonic or existing keys)
685        let master_secret_key = SecretKey::from_bytes([1u8; 32]).unwrap(); // Your actual key
686        let master_public_key = secret_key_to_public_key(&master_secret_key);
687        let synthetic_key = master_public_key_to_wallet_synthetic_key(&master_public_key);
688        let puzzle_hash = master_public_key_to_first_puzzle_hash(&master_public_key);
689
690        // 3. Get unspent coins for the transaction
691        let unspent_coins = async_api::get_all_unspent_coins(
692            &peer,
693            puzzle_hash,
694            None,
695            get_mainnet_genesis_challenge(),
696        ).await.unwrap();
697
698        // 4. Select coins for the transaction
699        let fee = 1_000_000; // 1 million mojos
700        let selected_coins = select_coins(&unspent_coins.coin_states.iter().map(|cs| cs.coin).collect::<Vec<_>>(), fee + 1).unwrap();
701
702        // 5. Create NFT metadata
703        let metadata = chia_puzzle_types::nft::NftMetadata {
704            data_uris: vec!["https://example.com/nft.png".to_string()],
705            metadata_uris: vec!["https://example.com/metadata.json".to_string()],
706            ..Default::default()
707        };
708
709        // 6. Mint the NFT
710        let nft_spends = async_api::mint_nft(
711            &peer,
712            synthetic_key,
713            selected_coins,
714            "did:chia:1s8j4pquxfu5mhlldzu357qfqkwa9r35mdx5a0p0ehn76dr4ut4tqs0n6kv",
715            puzzle_hash, // Send NFT to yourself
716            metadata,
717            None, // No royalty address
718            300,  // 3% royalty
719            fee,
720            None, // Defaults to mainnet
721        ).await.unwrap();
722
723        // 7. Sign the transaction
724        let signature = sign_coin_spends(
725            &nft_spends,
726            &[master_secret_key_to_wallet_synthetic_secret_key(&master_secret_key)],
727            false, // mainnet
728        ).unwrap();
729
730        // 8. Create and broadcast spend bundle
731        let spend_bundle = SpendBundle::new(nft_spends, signature);
732        let result = async_api::broadcast_spend_bundle(&peer, spend_bundle).await.unwrap();
733
734        println!("NFT minting transaction broadcast: {:?}", result);
735        */
736    }
737}