Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "dig-wallet"
version = "0.3.0"
version = "0.4.0"
edition = "2021"
authors = ["DIG Network <info@dig.net>"]
description = "A comprehensive Rust wallet implementation for Chia blockchain with full DataLayer-Driver integration"
Expand All @@ -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"] }
Expand Down
161 changes: 83 additions & 78 deletions src/wallet.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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<CatInfo>,
) -> Result<Bytes32, WalletError> {
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<CatInfo, WalletError> {
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<String, WalletError> {
let owner_puzzle_hash = self.get_owner_puzzle_hash().await?;
Expand Down Expand Up @@ -302,13 +283,14 @@ impl Wallet {
omit_coins: Vec<Coin>,
verbose: bool,
) -> Result<Vec<Coin>, 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
)
Expand All @@ -318,154 +300,177 @@ impl Wallet {
// Convert coin states to coins and filter out omitted coins
let omit_coin_ids: Vec<Bytes32> = omit_coins.iter().map(get_coin_id).collect();

let available_coins: Vec<Coin> = coin_states
let available_coin_states: Vec<CoinState> = 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<Coin> = 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: {}",
error
))
);
}

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: {}",
error
))
);
}
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: {}",
error
))
);
}

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: {}",
error
))
);
}

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);
}
Err(error) => {
if verbose {
eprintln!(
"coin_id {} | {}",
"ERROR: coin_id {} | {}",
coin_id,
WalletError::CoinSetError(format!(
"Failed to parse CAT and prove lineage: {}",
error
))
);
}

continue;
}
}
Expand Down