diff --git a/Cargo.lock b/Cargo.lock index 195936f..29c7110 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -977,11 +977,12 @@ dependencies = [ [[package]] name = "dig-wallet" -version = "0.3.0" +version = "0.4.0" dependencies = [ "aes-gcm", "base64 0.21.7", "bip39", + "chia", "chia-wallet-sdk", "datalayer-driver", "dirs", diff --git a/Cargo.toml b/Cargo.toml index b213c4d..733f2a8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "dig-wallet" -version = "0.3.0" +version = "0.4.0" edition = "2021" authors = ["DIG Network "] description = "A comprehensive Rust wallet implementation for Chia blockchain with full DataLayer-Driver integration" @@ -15,6 +15,7 @@ rust-version = "1.70" [dependencies] datalayer-driver = "1.0.1" chia-wallet-sdk = "0.29.0" +chia = "0.26.0" bip39 = "2.0" thiserror = "1.0" tokio = { version = "1.0", features = ["full"] } diff --git a/src/wallet.rs b/src/wallet.rs index 457f231..2d35b51 100644 --- a/src/wallet.rs +++ b/src/wallet.rs @@ -5,8 +5,10 @@ use aes_gcm::{ }; use base64::{engine::general_purpose, Engine as _}; use bip39::{Language, Mnemonic}; -use chia_wallet_sdk::driver::{Cat, CatInfo, Puzzle}; -use chia_wallet_sdk::prelude::{Allocator, ToClvm}; +use chia::protocol::CoinState; +use chia::puzzles::cat::CatArgs; +use chia_wallet_sdk::driver::{Cat, Puzzle}; +use chia_wallet_sdk::prelude::{Allocator, ToClvm, TreeHash}; use chia_wallet_sdk::types::MAINNET_CONSTANTS; use datalayer_driver::{ address_to_puzzle_hash, connect_random, get_coin_id, master_public_key_to_first_puzzle_hash, @@ -155,27 +157,6 @@ impl Wallet { Ok(master_public_key_to_first_puzzle_hash(&master_pk)) } - /// Get the DIG token outer puzzle hash - pub async fn get_dig_coin_outer_puzzle_hash( - &self, - outer_puzzle: Option, - ) -> Result { - let dig_cat_outer_ph = match outer_puzzle { - Some(outer_puzzle) => outer_puzzle.puzzle_hash(), - None => { - let cat_outer_puzzle = self.get_cat_coin_outer_puzzle().await?; - cat_outer_puzzle.puzzle_hash() - } - }; - let bytes = Bytes32::new(dig_cat_outer_ph.to_bytes()); - Ok(bytes) - } - - pub async fn get_cat_coin_outer_puzzle(&self) -> Result { - let p2_ph = self.get_owner_puzzle_hash().await?; - Ok(CatInfo::new(*DIG_COIN_ASSET_ID, None, p2_ph)) - } - /// Get the owner public key as an address pub async fn get_owner_public_key(&self) -> Result { let owner_puzzle_hash = self.get_owner_puzzle_hash().await?; @@ -302,13 +283,14 @@ impl Wallet { omit_coins: Vec, verbose: bool, ) -> Result, WalletError> { - let dig_cat = self.get_cat_coin_outer_puzzle().await?; - let dig_cat_ph = self.get_dig_coin_outer_puzzle_hash(Some(dig_cat)).await?; + let p2 = self.get_owner_puzzle_hash().await?; + let dig_cat_ph = CatArgs::curry_tree_hash(*DIG_COIN_ASSET_ID, TreeHash::from(p2)); + let dig_cat_ph_bytes = Bytes32::from(dig_cat_ph.to_bytes()); // Get unspent coin states from the DataLayer-Driver async API - let coin_states = datalayer_driver::async_api::get_all_unspent_coins( + let unspent_coin_states = datalayer_driver::async_api::get_all_unspent_coins( peer, - dig_cat_ph, + dig_cat_ph_bytes, None, // previous_height - start from genesis datalayer_driver::constants::get_mainnet_genesis_challenge(), // Use mainnet for now ) @@ -318,46 +300,51 @@ impl Wallet { // Convert coin states to coins and filter out omitted coins let omit_coin_ids: Vec = omit_coins.iter().map(get_coin_id).collect(); - let available_coins: Vec = coin_states + let available_coin_states: Vec = unspent_coin_states .coin_states .into_iter() - .map(|cs| cs.coin) - .filter(|coin| !omit_coin_ids.contains(&get_coin_id(coin))) + .filter(|coin_state| !omit_coin_ids.contains(&get_coin_id(&coin_state.coin))) .collect(); let mut proved_dig_token_coins: Vec = vec![]; let mut allocator = Allocator::new(); - for coin in &available_coins { + for coin_state in &available_coin_states { + let coin = &coin_state.coin; let coin_id = coin.coin_id(); - let parent_state = match peer + let coin_created_height = match coin_state.created_height { + Some(height) => height, + None => { + if verbose { + eprintln!( + "ERROR: coin_id {} | {}", + coin_id, + WalletError::CoinSetError( + "Cannot determine coin creation height".to_string() + ) + ); + } + continue; + } + }; + + // 1) Request parent coin state + let parent_state_result = peer .request_coin_state( vec![coin.parent_coin_info], None, MAINNET_CONSTANTS.genesis_challenge, false, ) - .await - { - Ok(response) => match response { - Ok(parent_state) => parent_state, - Err(_) => { - if verbose { - eprintln!( - "coin_id {} | {}", - coin_id, - WalletError::CoinSetError("Coin state rejected".to_string()) - ); - } - - continue; - } - }, + .await; + + let parent_state_response = match parent_state_result { + Ok(response) => response, Err(error) => { if verbose { eprintln!( - "coin_id {} | {}", + "ERROR: coin_id {} | {}", coin_id, WalletError::NetworkError(format!( "Failed to get coin state: {}", @@ -365,33 +352,35 @@ impl Wallet { )) ); } - continue; } }; - let parent_puzzle_and_solution = match peer - .request_puzzle_and_solution(parent_state.coin_ids[0], DIG_MIN_HEIGHT) - .await - { - Ok(response) => match response { - Ok(puzzle_and_solution) => puzzle_and_solution, - Err(_) => { - if verbose { - eprintln!( - "coin_id {} | {}", - coin_id, - WalletError::CoinSetError("Coin state rejected".to_string()) - ); - } - - continue; + let parent_state = match parent_state_response { + Ok(state) => state, + Err(_) => { + if verbose { + eprintln!( + "ERROR: coin_id {} | {}", + coin_id, + WalletError::CoinSetError("Coin state rejected".to_string()) + ); } - }, + continue; + } + }; + + // 2) Request parent puzzle and solution + let parent_puzzle_and_solution_result = peer + .request_puzzle_and_solution(parent_state.coin_ids[0], coin_created_height) + .await; + + let parent_puzzle_and_solution_response = match parent_puzzle_and_solution_result { + Ok(response) => response, Err(error) => { if verbose { eprintln!( - "coin_id {} | {}", + "ERROR: coin_id {} | {}", coin_id, WalletError::NetworkError(format!( "Failed to get puzzle and solution: {}", @@ -399,18 +388,34 @@ impl Wallet { )) ); } + continue; + } + }; + let parent_puzzle_and_solution = match parent_puzzle_and_solution_response { + Ok(v) => v, + Err(_) => { + if verbose { + eprintln!( + "ERROR: coin_id {} | {}", + coin_id, + WalletError::CoinSetError( + "Parent puzzle solution rejected".to_string() + ) + ); + } continue; } }; + // 3) Convert puzzle to CLVM let parent_puzzle_ptr = match parent_puzzle_and_solution.puzzle.to_clvm(&mut allocator) { - Ok(puzzle_ptr) => puzzle_ptr, + Ok(ptr) => ptr, Err(error) => { if verbose { eprintln!( - "coin_id {} | {}", + "ERROR: coin_id {} | {}", coin_id, WalletError::CoinSetError(format!( "Failed to parse puzzle and solution: {}", @@ -418,19 +423,20 @@ impl Wallet { )) ); } - continue; } }; let parent_puzzle = Puzzle::parse(&allocator, parent_puzzle_ptr); + + // 4) Convert solution to CLVM let parent_solution = match parent_puzzle_and_solution.solution.to_clvm(&mut allocator) { Ok(solution) => solution, Err(error) => { if verbose { eprintln!( - "coin_id {} | {}", + "ERROR: coin_id {} | {}", coin_id, WalletError::CoinSetError(format!( "Failed to parse puzzle and solution: {}", @@ -438,18 +444,18 @@ impl Wallet { )) ); } - continue; } }; - // this instantiation proves the lineage of the coin. not used beyond that - match Cat::parse_children( + // 5) Parse CAT to prove lineage + let cat_parse_result = Cat::parse_children( &mut allocator, parent_state.coin_states[0].coin, parent_puzzle, parent_solution, - ) { + ); + match cat_parse_result { Ok(_) => { // lineage proved. append coin in question proved_dig_token_coins.push(*coin); @@ -457,7 +463,7 @@ impl Wallet { Err(error) => { if verbose { eprintln!( - "coin_id {} | {}", + "ERROR: coin_id {} | {}", coin_id, WalletError::CoinSetError(format!( "Failed to parse CAT and prove lineage: {}", @@ -465,7 +471,6 @@ impl Wallet { )) ); } - continue; } }