#![allow(clippy::result_large_err)]
pub use chia_bls::{master_to_wallet_unhardened, PublicKey, SecretKey, Signature};
pub use chia_protocol::{Bytes, Bytes32, Coin, CoinSpend, CoinState, Program, SpendBundle};
pub use chia_puzzle_types::{EveProof, LineageProof, Proof};
pub use chia_wallet_sdk::client::Peer;
pub use chia_wallet_sdk::driver::{
Datastore, DatastoreInfo, DatastoreMetadata, DelegatedPuzzle, P2ParentCoin,
};
pub use chia_wallet_sdk::utils::Address;
pub use async_api::{connect_peer, connect_random, create_tls_connector, NetworkType};
pub use constants::{get_mainnet_genesis_challenge, get_testnet11_genesis_challenge};
mod dig_coin;
mod dig_collateral_coin;
mod error;
pub mod types;
pub mod wallet;
pub mod xch_server_coin;
pub use types::{
BlsPair, SimulatorPuzzle, SuccessResponse, UnspentCoinStates, UnspentCoinsResponse,
};
pub use wallet::{
create_simple_did, generate_did_proof, generate_did_proof_from_chain,
generate_did_proof_manual, get_fee_estimate, get_header_hash, get_store_creation_height,
get_unspent_coin_states, is_coin_spent, look_up_possible_launchers, mint_nft,
spend_xch_server_coins, subscribe_to_coin_states, sync_store, sync_store_using_launcher_id,
unsubscribe_from_coin_states, verify_signature, DataStoreInnerSpend, PossibleLaunchersResponse,
SyncStoreResponse, TargetNetwork,
};
pub use xch_server_coin::{morph_launcher_id, XchServerCoin};
pub use {dig_coin::DigCoin, dig_collateral_coin::DigCollateralCoin};
use hex_literal::hex;
pub type Result<T> = std::result::Result<T, Box<dyn std::error::Error + Send + Sync>>;
use chia_puzzle_types::{standard::StandardArgs, DeriveSynthetic};
use xch_server_coin::NewXchServerCoin;
pub const DIG_MIN_HEIGHT: u32 = 5777842;
pub const DIG_MIN_HEIGHT_HEADER_HASH: Bytes32 = Bytes32::new(hex!(
"b29a4daac2434fd17a36e15ba1aac5d65012d4a66f99bed0bf2b5342e92e562c"
));
pub fn master_public_key_to_wallet_synthetic_key(public_key: &PublicKey) -> PublicKey {
master_to_wallet_unhardened(public_key, 0).derive_synthetic()
}
pub fn master_public_key_to_first_puzzle_hash(public_key: &PublicKey) -> Bytes32 {
let wallet_pk = master_to_wallet_unhardened(public_key, 0).derive_synthetic();
StandardArgs::curry_tree_hash(wallet_pk).into()
}
pub fn master_secret_key_to_wallet_synthetic_secret_key(secret_key: &SecretKey) -> SecretKey {
master_to_wallet_unhardened(secret_key, 0).derive_synthetic()
}
pub fn secret_key_to_public_key(secret_key: &SecretKey) -> PublicKey {
secret_key.public_key()
}
pub fn synthetic_key_to_puzzle_hash(synthetic_key: &PublicKey) -> Bytes32 {
StandardArgs::curry_tree_hash(*synthetic_key).into()
}
pub fn admin_delegated_puzzle_from_key(synthetic_key: &PublicKey) -> DelegatedPuzzle {
DelegatedPuzzle::Admin(StandardArgs::curry_tree_hash(*synthetic_key))
}
pub fn writer_delegated_puzzle_from_key(synthetic_key: &PublicKey) -> DelegatedPuzzle {
DelegatedPuzzle::Writer(StandardArgs::curry_tree_hash(*synthetic_key))
}
pub fn oracle_delegated_puzzle(oracle_puzzle_hash: Bytes32, oracle_fee: u64) -> DelegatedPuzzle {
DelegatedPuzzle::Oracle(oracle_puzzle_hash, oracle_fee)
}
pub fn get_coin_id(coin: &Coin) -> Bytes32 {
coin.coin_id()
}
pub fn puzzle_hash_to_address(puzzle_hash: Bytes32, prefix: &str) -> Result<String> {
use chia_wallet_sdk::utils::Address;
Ok(Address::new(puzzle_hash, prefix.to_string()).encode()?)
}
pub fn address_to_puzzle_hash(address: &str) -> Result<Bytes32> {
use chia_wallet_sdk::utils::Address;
Ok(Address::decode(address)?.puzzle_hash)
}
pub fn hex_spend_bundle_to_coin_spends(hex: &str) -> Result<Vec<CoinSpend>> {
use chia_traits::Streamable;
let bytes = hex::decode(hex)?;
let spend_bundle = SpendBundle::from_bytes(&bytes)?;
Ok(spend_bundle.coin_spends)
}
pub fn spend_bundle_to_hex(spend_bundle: &SpendBundle) -> Result<String> {
use chia_traits::Streamable;
let bytes = spend_bundle.to_bytes()?;
Ok(hex::encode(bytes))
}
pub fn morph_launcher_id_wrapper(launcher_id: Bytes32, offset: u64) -> Bytes32 {
xch_server_coin::morph_launcher_id(launcher_id, &offset.into())
}
#[derive(Debug, Clone)]
pub struct Output {
pub puzzle_hash: Bytes32,
pub amount: u64,
pub memos: Vec<Bytes>,
}
pub fn send_xch(
synthetic_key: &PublicKey,
selected_coins: &[Coin],
outputs: &[Output],
fee: u64,
) -> Result<Vec<CoinSpend>> {
let outputs: Vec<(Bytes32, u64, Vec<Bytes>)> = outputs
.iter()
.map(|output| (output.puzzle_hash, output.amount, output.memos.clone()))
.collect();
Ok(wallet::send_xch(
*synthetic_key,
selected_coins,
&outputs,
fee,
)?)
}
pub fn select_coins(all_coins: &[Coin], total_amount: u64) -> Result<Vec<Coin>> {
Ok(wallet::select_coins(all_coins.to_vec(), total_amount)?)
}
pub fn add_fee(
spender_synthetic_key: &PublicKey,
selected_coins: &[Coin],
assert_coin_ids: &[Bytes32],
fee: u64,
) -> Result<Vec<CoinSpend>> {
Ok(wallet::add_fee(
*spender_synthetic_key,
selected_coins.to_vec(),
assert_coin_ids.to_vec(),
fee,
)?)
}
pub fn sign_coin_spends(
coin_spends: &[CoinSpend],
private_keys: &[SecretKey],
for_testnet: bool,
) -> Result<Signature> {
Ok(wallet::sign_coin_spends(
coin_spends.to_vec(),
private_keys.to_vec(),
if for_testnet {
wallet::TargetNetwork::Testnet11
} else {
wallet::TargetNetwork::Mainnet
},
)?)
}
pub fn sign_message(message: &[u8], private_key: &SecretKey) -> Result<Signature> {
Ok(wallet::sign_message(message.into(), private_key.clone())?)
}
pub fn verify_signed_message(
signature: &Signature,
public_key: &PublicKey,
message: &[u8],
) -> Result<bool> {
Ok(wallet::verify_signature(
message.into(),
*public_key,
signature.clone(),
)?)
}
pub fn get_cost(coin_spends: &[CoinSpend]) -> Result<u64> {
Ok(wallet::get_cost(coin_spends.to_vec())?)
}
#[allow(clippy::too_many_arguments)]
pub fn mint_store(
minter_synthetic_key: PublicKey,
selected_coins: Vec<Coin>,
root_hash: Bytes32,
label: Option<String>,
description: Option<String>,
bytes: Option<u64>,
size_proof: Option<String>,
owner_puzzle_hash: Bytes32,
delegated_puzzles: Vec<DelegatedPuzzle>,
fee: u64,
) -> Result<SuccessResponse> {
Ok(wallet::mint_store(
minter_synthetic_key,
selected_coins,
root_hash,
label,
description,
bytes,
size_proof,
owner_puzzle_hash,
delegated_puzzles,
fee,
)?)
}
pub fn oracle_spend(
spender_synthetic_key: PublicKey,
selected_coins: Vec<Coin>,
store: Datastore,
fee: u64,
) -> Result<SuccessResponse> {
Ok(wallet::oracle_spend(
spender_synthetic_key,
selected_coins,
store,
fee,
)?)
}
#[allow(clippy::too_many_arguments)]
pub fn update_store_metadata(
store: Datastore,
new_root_hash: Bytes32,
new_label: Option<String>,
new_description: Option<String>,
new_bytes: Option<u64>,
new_size_proof: Option<String>,
inner_spend_info: DataStoreInnerSpend,
) -> Result<SuccessResponse> {
Ok(wallet::update_store_metadata(
store,
new_root_hash,
new_label,
new_description,
new_bytes,
new_size_proof,
inner_spend_info,
)?)
}
pub fn update_store_ownership(
store: Datastore,
new_owner_puzzle_hash: Bytes32,
new_delegated_puzzles: Vec<DelegatedPuzzle>,
inner_spend_info: wallet::DataStoreInnerSpend,
) -> Result<SuccessResponse> {
Ok(wallet::update_store_ownership(
store,
new_owner_puzzle_hash,
new_delegated_puzzles,
inner_spend_info,
)?)
}
pub fn melt_store(store: Datastore, owner_pk: PublicKey) -> Result<Vec<CoinSpend>> {
Ok(wallet::melt_store(store, owner_pk)?)
}
pub fn create_server_coin(
synthetic_key: PublicKey,
selected_coins: Vec<Coin>,
hint: Bytes32,
uris: Vec<String>,
amount: u64,
fee: u64,
) -> Result<NewXchServerCoin> {
Ok(wallet::create_server_coin(
synthetic_key,
selected_coins,
hint,
uris,
amount,
fee,
)?)
}
pub mod async_api {
use super::*;
use futures_util::stream::{FuturesUnordered, StreamExt};
use rand::seq::SliceRandom;
use std::net::SocketAddr;
use tokio::net::lookup_host;
use tokio::time::{timeout, Duration};
const MAINNET_DNS_INTRODUCERS: &[&str] = &[
"dns-introducer.chia.net",
"chia.ctrlaltdel.ch",
"seeder.dexie.space",
"chia.hoffmang.com",
];
const TESTNET11_DNS_INTRODUCERS: &[&str] = &["dns-introducer-testnet11.chia.net"];
const MAINNET_DEFAULT_PORT: u16 = 8444;
const TESTNET11_DEFAULT_PORT: u16 = 58444;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum NetworkType {
Mainnet,
Testnet11,
}
pub async fn connect_random(
network: NetworkType,
cert_path: &str,
key_path: &str,
) -> Result<Peer> {
let cert = chia_wallet_sdk::client::load_ssl_cert(cert_path, key_path)?;
let tls = chia_wallet_sdk::client::create_native_tls_connector(&cert)?;
let (introducers, default_port) = match network {
NetworkType::Mainnet => (MAINNET_DNS_INTRODUCERS, MAINNET_DEFAULT_PORT),
NetworkType::Testnet11 => (TESTNET11_DNS_INTRODUCERS, TESTNET11_DEFAULT_PORT),
};
let mut addrs = Vec::new();
for introducer in introducers {
if let Ok(iter) = lookup_host((*introducer, default_port)).await {
addrs.extend(iter);
}
}
if addrs.is_empty() {
return Err("Failed to resolve any peer addresses from introducers".into());
}
{
let mut rng = rand::thread_rng();
addrs.shuffle(&mut rng);
}
const BATCH_SIZE: usize = 10;
const CONNECT_TIMEOUT: Duration = Duration::from_secs(8);
for chunk in addrs.chunks(BATCH_SIZE) {
let mut futures = FuturesUnordered::new();
for addr in chunk {
let addr = *addr;
let network_str = match network {
NetworkType::Mainnet => "mainnet",
NetworkType::Testnet11 => "testnet11",
};
let tls_clone = tls.clone();
futures.push(async move {
timeout(
CONNECT_TIMEOUT,
chia_wallet_sdk::client::connect_peer(
network_str.to_string(),
tls_clone,
addr,
chia_wallet_sdk::client::PeerOptions::default(),
),
)
.await
});
}
while let Some(result) = futures.next().await {
match result {
Ok(Ok((peer, _receiver))) => {
return Ok(peer);
}
_ => {
}
}
}
}
Err("Unable to connect to any discovered peer".into())
}
pub fn create_tls_connector(
cert_path: &str,
key_path: &str,
) -> Result<chia_wallet_sdk::client::Connector> {
let cert = chia_wallet_sdk::client::load_ssl_cert(cert_path, key_path)?;
Ok(chia_wallet_sdk::client::create_native_tls_connector(&cert)?)
}
pub async fn connect_peer(
network: NetworkType,
tls_connector: chia_wallet_sdk::client::Connector,
address: SocketAddr,
) -> Result<Peer> {
let network_str = match network {
NetworkType::Mainnet => "mainnet",
NetworkType::Testnet11 => "testnet11",
};
let (peer, _receiver) = chia_wallet_sdk::client::connect_peer(
network_str.to_string(),
tls_connector,
address,
chia_wallet_sdk::client::PeerOptions::default(),
)
.await?;
Ok(peer)
}
#[allow(clippy::too_many_arguments)]
pub async fn mint_nft(
peer: &Peer,
synthetic_key: PublicKey,
selected_coins: Vec<Coin>,
did_string: &str,
recipient_puzzle_hash: Bytes32,
metadata: chia_puzzle_types::nft::NftMetadata,
royalty_puzzle_hash: Option<Bytes32>,
royalty_basis_points: u16,
fee: u64,
for_testnet: Option<bool>,
) -> Result<Vec<CoinSpend>> {
let network = if for_testnet.unwrap_or(false) {
wallet::TargetNetwork::Testnet11
} else {
wallet::TargetNetwork::Mainnet
};
Ok(wallet::mint_nft(
peer,
synthetic_key,
selected_coins,
did_string,
recipient_puzzle_hash,
metadata,
royalty_puzzle_hash,
royalty_basis_points,
fee,
network,
)
.await?)
}
pub async fn generate_did_proof(
peer: &Peer,
did_coin: Coin,
for_testnet: bool,
) -> Result<(Proof, Coin)> {
let network = if for_testnet {
wallet::TargetNetwork::Testnet11
} else {
wallet::TargetNetwork::Mainnet
};
Ok(wallet::generate_did_proof(peer, did_coin, network).await?)
}
pub fn create_simple_did(
synthetic_key: PublicKey,
selected_coins: Vec<Coin>,
fee: u64,
) -> Result<(Vec<CoinSpend>, Coin)> {
Ok(wallet::create_simple_did(
synthetic_key,
selected_coins,
fee,
)?)
}
pub async fn sync_store(
peer: &Peer,
store: &Datastore,
last_height: Option<u32>,
last_header_hash: Bytes32,
with_history: bool,
) -> Result<SyncStoreResponse> {
Ok(wallet::sync_store(peer, store, last_height, last_header_hash, with_history).await?)
}
pub async fn sync_store_from_launcher_id(
peer: &Peer,
launcher_id: Bytes32,
last_height: Option<u32>,
last_header_hash: Bytes32,
with_history: bool,
) -> Result<SyncStoreResponse> {
Ok(wallet::sync_store_using_launcher_id(
peer,
launcher_id,
last_height,
last_header_hash,
with_history,
)
.await?)
}
pub async fn get_unspent_coins_by_hints(
peer: &Peer,
hint: Bytes32,
network: NetworkType,
) -> Result<UnspentCoinStates> {
Ok(wallet::get_unspent_coin_states_by_hint(peer, hint, network).await?)
}
pub async fn get_all_unspent_coins(
peer: &Peer,
puzzle_hash: Bytes32,
previous_height: Option<u32>,
previous_header_hash: Bytes32,
) -> Result<UnspentCoinStates> {
Ok(wallet::get_unspent_coin_states(
peer,
puzzle_hash,
previous_height,
previous_header_hash,
false,
)
.await?)
}
pub async fn is_coin_spent(
peer: &Peer,
coin_id: Bytes32,
last_height: Option<u32>,
header_hash: Bytes32,
) -> Result<bool> {
Ok(wallet::is_coin_spent(peer, coin_id, last_height, header_hash).await?)
}
pub async fn get_header_hash(peer: &Peer, height: u32) -> Result<Bytes32> {
Ok(wallet::get_header_hash(peer, height).await?)
}
pub async fn get_fee_estimate(peer: &Peer, target_time_seconds: u64) -> Result<u64> {
Ok(wallet::get_fee_estimate(peer, target_time_seconds).await?)
}
pub async fn broadcast_spend_bundle(
peer: &Peer,
spend_bundle: SpendBundle,
) -> Result<chia_protocol::TransactionAck> {
Ok(wallet::broadcast_spend_bundle(peer, spend_bundle).await?)
}
}
pub mod constants {
use chia_wallet_sdk::types::{MAINNET_CONSTANTS, TESTNET11_CONSTANTS};
pub fn get_mainnet_genesis_challenge() -> chia_protocol::Bytes32 {
MAINNET_CONSTANTS.genesis_challenge
}
pub fn get_testnet11_genesis_challenge() -> chia_protocol::Bytes32 {
TESTNET11_CONSTANTS.genesis_challenge
}
}
#[cfg(test)]
mod examples {
use super::*;
#[test]
fn example_key_operations() {
let secret_key = SecretKey::from_bytes(&[1u8; 32]).unwrap();
let public_key = secret_key_to_public_key(&secret_key);
let _synthetic_key = master_public_key_to_wallet_synthetic_key(&public_key);
let puzzle_hash = master_public_key_to_first_puzzle_hash(&public_key);
let address = puzzle_hash_to_address(puzzle_hash, "xch").unwrap();
println!("Address: {}", address);
let decoded_hash = address_to_puzzle_hash(&address).unwrap();
assert_eq!(puzzle_hash, decoded_hash);
}
#[tokio::test]
async fn example_nft_minting() {
}
}