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::{DataStore, DataStoreInfo, DataStoreMetadata, DelegatedPuzzle};
23pub use chia_wallet_sdk::utils::Address;
24
25// Re-export async_api and constants modules at the top level for convenience
26pub use async_api::{connect_peer, connect_random, create_tls_connector, NetworkType};
27pub use constants::{get_mainnet_genesis_challenge, get_testnet11_genesis_challenge};
28
29// Internal modules
30pub mod rust;
31pub mod server_coin;
32pub mod wallet;
33
34// Re-export types from internal modules
35pub 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
49// Type aliases for convenience
50pub type Result<T> = std::result::Result<T, Box<dyn std::error::Error + Send + Sync>>;
51
52// Helper functions for common conversions
53use chia::puzzles::{standard::StandardArgs, DeriveSynthetic};
54
55/// Converts a master public key to a wallet synthetic key.
56pub fn master_public_key_to_wallet_synthetic_key(public_key: &PublicKey) -> PublicKey {
57    master_to_wallet_unhardened(public_key, 0).derive_synthetic()
58}
59
60/// Converts a master public key to the first puzzle hash.
61pub 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
66/// Converts a master secret key to a wallet synthetic secret key.
67pub 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
71/// Converts a secret key to its corresponding public key.
72pub fn secret_key_to_public_key(secret_key: &SecretKey) -> PublicKey {
73    secret_key.public_key()
74}
75
76/// Converts a synthetic key to its corresponding standard puzzle hash.
77pub fn synthetic_key_to_puzzle_hash(synthetic_key: &PublicKey) -> Bytes32 {
78    StandardArgs::curry_tree_hash(*synthetic_key).into()
79}
80
81/// Creates an admin delegated puzzle for a given key.
82pub fn admin_delegated_puzzle_from_key(synthetic_key: &PublicKey) -> DelegatedPuzzle {
83    DelegatedPuzzle::Admin(StandardArgs::curry_tree_hash(*synthetic_key))
84}
85
86/// Creates a writer delegated puzzle from a given key.
87pub fn writer_delegated_puzzle_from_key(synthetic_key: &PublicKey) -> DelegatedPuzzle {
88    DelegatedPuzzle::Writer(StandardArgs::curry_tree_hash(*synthetic_key))
89}
90
91/// Creates an oracle delegated puzzle.
92pub fn oracle_delegated_puzzle(oracle_puzzle_hash: Bytes32, oracle_fee: u64) -> DelegatedPuzzle {
93    DelegatedPuzzle::Oracle(oracle_puzzle_hash, oracle_fee)
94}
95
96/// Gets the coin ID for a given coin.
97pub fn get_coin_id(coin: &Coin) -> Bytes32 {
98    coin.coin_id()
99}
100
101/// Converts a puzzle hash to an address by encoding it using bech32m.
102pub 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
107/// Converts an address to a puzzle hash using bech32m.
108pub 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
113/// Converts hex-encoded spend bundle to coin spends.
114pub 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
121/// Converts a spend bundle to hex encoding.
122pub 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
128/// Adds an offset to a launcher id to make it deterministically unique from the original.
129pub fn morph_launcher_id_wrapper(launcher_id: Bytes32, offset: u64) -> Bytes32 {
130    server_coin::morph_launcher_id(launcher_id, &offset.into())
131}
132
133/// Output for send_xch function
134#[derive(Debug, Clone)]
135pub struct Output {
136    pub puzzle_hash: Bytes32,
137    pub amount: u64,
138    pub memos: Vec<Bytes>,
139}
140
141/// Sends XCH to a given set of puzzle hashes (Rust API version).
142pub 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
161/// Selects coins using the knapsack algorithm (Rust API version).
162pub 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
166/// Adds a fee to any transaction (Rust API version).
167pub 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
181/// Signs coin spends using a list of keys (Rust API version).
182pub 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
198/// Signs a message using the provided private key (Rust API version).
199pub fn sign_message(message: &[u8], private_key: &SecretKey) -> Result<Signature> {
200    Ok(wallet::sign_message(message.into(), private_key.clone())?)
201}
202
203/// Verifies a signed message using the provided public key (Rust API version).
204pub 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
216/// Calculates the total cost of coin spends (Rust API version).
217pub fn get_cost(coin_spends: &[CoinSpend]) -> Result<u64> {
218    Ok(wallet::get_cost(coin_spends.to_vec())?)
219}
220
221/// Mints a new datastore (Rust API version).
222#[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
249/// Spends a store in oracle mode (Rust API version).
250pub 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/// Updates the metadata of a store (Rust API version).
265#[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
286/// Updates the ownership of a store (Rust API version).
287pub 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
301/// Melts a store (Rust API version).
302pub fn melt_store(store: DataStore, owner_pk: PublicKey) -> Result<Vec<CoinSpend>> {
303    Ok(wallet::melt_store(store, owner_pk)?)
304}
305
306/// Creates a server coin (Rust API version).
307pub 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
325/// Async functions for blockchain interaction (Rust API versions)
326pub 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    // DNS introducers and default ports for connecting to random peers.
335    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    /// Connects to a random peer on the specified network (Rust API version).
352    ///
353    /// The function performs DNS lookups using the network's introducers, picks a random
354    /// address from the returned list, and attempts to establish a connection. It will
355    /// try every resolved address until a connection succeeds.
356    pub async fn connect_random(
357        network: NetworkType,
358        cert_path: &str,
359        key_path: &str,
360    ) -> Result<Peer> {
361        // Load TLS certificate
362        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        // Introducers and default port per network
366        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        // Resolve all introducers to socket addresses
372        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        // Shuffle for randomness so every call has different order
384        {
385            let mut rng = rand::thread_rng();
386            addrs.shuffle(&mut rng);
387        }
388
389        // Try to connect in concurrent batches with timeout logic
390        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                // Spawn connection attempt with timeout
404                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                        // Successfully connected, return the peer
422                        return Ok(peer);
423                    }
424                    _ => {
425                        // Either timed out or failed; continue with others
426                    }
427                }
428            }
429        }
430
431        Err("Unable to connect to any discovered peer".into())
432    }
433
434    /// Creates a TLS connector for Chia peer connections (Rust API version).
435    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    /// Connects to a specific peer address (Rust API version).
444    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    /// Mints a new NFT using a DID string (Rust API version).
466    #[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    /// Generates a DID proof automatically (Rust API version).
501    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    /// Creates a simple DID (Rust API version).
516    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    /// Synchronizes a datastore (Rust API version).
529    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    /// Synchronizes a store using its launcher ID (Rust API version).
540    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    /// Gets all unspent coins for a puzzle hash (Rust API version).
558    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    /// Checks if a coin is spent on-chain (Rust API version).
575    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    /// Gets the header hash at a specific height (Rust API version).
585    pub async fn get_header_hash(peer: &Peer, height: u32) -> Result<Bytes32> {
586        Ok(wallet::get_header_hash(peer, height).await?)
587    }
588
589    /// Gets fee estimate for target time (Rust API version).
590    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    /// Broadcasts a spend bundle (Rust API version).
595    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
603/// Constants for different networks
604pub mod constants {
605    use chia_wallet_sdk::types::{MAINNET_CONSTANTS, TESTNET11_CONSTANTS};
606
607    /// Returns the mainnet genesis challenge.
608    pub fn get_mainnet_genesis_challenge() -> chia::protocol::Bytes32 {
609        MAINNET_CONSTANTS.genesis_challenge
610    }
611
612    /// Returns the testnet11 genesis challenge.
613    pub fn get_testnet11_genesis_challenge() -> chia::protocol::Bytes32 {
614        TESTNET11_CONSTANTS.genesis_challenge
615    }
616}
617
618/// Example usage of the Rust API
619#[cfg(test)]
620mod examples {
621    use super::*;
622
623    #[test]
624    fn example_key_operations() {
625        // Example: Generate keys and addresses
626        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        // Convert to address
632        let address = puzzle_hash_to_address(puzzle_hash, "xch").unwrap();
633        println!("Address: {}", address);
634
635        // Convert back
636        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        // Example of complete NFT minting workflow using Rust API
643
644        /*
645        // 1. Connect to a random peer
646        let peer = connect_random(
647            NetworkType::Mainnet,
648            "~/.chia/mainnet/config/ssl/wallet/wallet_node.crt",
649            "~/.chia/mainnet/config/ssl/wallet/wallet_node.key"
650        ).await.unwrap();
651
652        // 2. Set up your wallet keys (from mnemonic or existing keys)
653        let master_secret_key = SecretKey::from_bytes([1u8; 32]).unwrap(); // Your actual key
654        let master_public_key = secret_key_to_public_key(&master_secret_key);
655        let synthetic_key = master_public_key_to_wallet_synthetic_key(&master_public_key);
656        let puzzle_hash = master_public_key_to_first_puzzle_hash(&master_public_key);
657
658        // 3. Get unspent coins for the transaction
659        let unspent_coins = async_api::get_all_unspent_coins(
660            &peer,
661            puzzle_hash,
662            None,
663            get_mainnet_genesis_challenge(),
664        ).await.unwrap();
665
666        // 4. Select coins for the transaction
667        let fee = 1_000_000; // 1 million mojos
668        let selected_coins = select_coins(&unspent_coins.coin_states.iter().map(|cs| cs.coin).collect::<Vec<_>>(), fee + 1).unwrap();
669
670        // 5. Create NFT metadata
671        let metadata = chia::puzzles::nft::NftMetadata {
672            data_uris: vec!["https://example.com/nft.png".to_string()],
673            metadata_uris: vec!["https://example.com/metadata.json".to_string()],
674            ..Default::default()
675        };
676
677        // 6. Mint the NFT
678        let nft_spends = async_api::mint_nft(
679            &peer,
680            synthetic_key,
681            selected_coins,
682            "did:chia:1s8j4pquxfu5mhlldzu357qfqkwa9r35mdx5a0p0ehn76dr4ut4tqs0n6kv",
683            puzzle_hash, // Send NFT to yourself
684            metadata,
685            None, // No royalty address
686            300,  // 3% royalty
687            fee,
688            None, // Defaults to mainnet
689        ).await.unwrap();
690
691        // 7. Sign the transaction
692        let signature = sign_coin_spends(
693            &nft_spends,
694            &[master_secret_key_to_wallet_synthetic_secret_key(&master_secret_key)],
695            false, // mainnet
696        ).unwrap();
697
698        // 8. Create and broadcast spend bundle
699        let spend_bundle = SpendBundle::new(nft_spends, signature);
700        let result = async_api::broadcast_spend_bundle(&peer, spend_bundle).await.unwrap();
701
702        println!("NFT minting transaction broadcast: {:?}", result);
703        */
704    }
705}