use datalayer_driver::async_api::get_all_unspent_coins;
use datalayer_driver::types::TransactionAck;
use datalayer_driver::wallet::{
broadcast_spend_bundle, create_dig_collateral_coin, fetch_dig_collateral_coin,
get_unspent_coin_states_by_hint, spend_dig_collateral_coin,
};
use datalayer_driver::{
Bytes32, Coin, DIG_MIN_HEIGHT, DataStore, DataStoreInnerSpend, NetworkType, P2ParentCoin,
PublicKey, SpendBundle, SuccessResponse, add_fee, admin_delegated_puzzle_from_key,
connect_random, get_fee_estimate, get_header_hash, mint_store, morph_store_launcher_id,
oracle_delegated_puzzle, secret_key_to_public_key, select_coins, sign_coin_spends,
synthetic_key_to_puzzle_hash, update_store_metadata, writer_delegated_puzzle_from_key,
};
use dig_wallet::{Wallet, WalletError};
use std::cmp::Reverse;
pub const DIG_MOJO_COLLATERAL_PER_MB: f64 = 5_f64;
pub fn calc_required_collateral(archive_file_size_bytes: u64) -> u64 {
((archive_file_size_bytes as f64 / 1000_f64) * DIG_MOJO_COLLATERAL_PER_MB).round() as u64
}
#[derive(Debug, Clone)]
pub struct MintParams<'mp> {
pub ssl_cert_path: &'mp str,
pub ssl_key_path: &'mp str,
pub wallet: &'mp Wallet,
pub root_hash: Bytes32,
pub network: NetworkType,
pub previous_height: Option<u32>,
pub label: Option<String>,
pub description: Option<String>,
pub size_in_bytes: Option<u64>,
pub size_proof: Option<String>,
pub writer_public_synthetic_key: Option<PublicKey>,
pub admin_public_synthetic_key: Option<PublicKey>,
pub fee: Option<u64>,
}
pub async fn mint(params: MintParams<'_>) -> datalayer_driver::Result<DataStore> {
let peer = connect_random(params.network, params.ssl_cert_path, params.ssl_key_path).await?;
let owner_secret_key = params.wallet.get_private_synthetic_key().await?;
let owner_public_key = secret_key_to_public_key(&owner_secret_key);
let owner_puzzle_hash = synthetic_key_to_puzzle_hash(&owner_public_key);
let height = if let Some(specified_height) = params.previous_height {
specified_height
} else {
DIG_MIN_HEIGHT
};
let owner_previous_header_hash = get_header_hash(&peer, height).await?;
let mut delegated_puzzles = vec![oracle_delegated_puzzle(owner_puzzle_hash, 100_000)];
if let Some(key) = params.admin_public_synthetic_key {
delegated_puzzles.push(admin_delegated_puzzle_from_key(&key))
}
if let Some(key) = params.writer_public_synthetic_key {
delegated_puzzles.push(writer_delegated_puzzle_from_key(&key))
}
let fee = if let Some(specified_fee) = params.fee {
specified_fee
} else {
get_fee_estimate(&peer, 60).await?
};
let required_wallet_balance = fee + 1;
let unspent_coin_states = get_all_unspent_coins(
&peer,
owner_puzzle_hash,
Some(height),
owner_previous_header_hash,
)
.await?;
let unspent_coins = unspent_coin_states
.coin_states
.iter()
.map(|coin_state| coin_state.coin)
.collect::<Vec<Coin>>();
let selected_coins = select_coins(&unspent_coins, required_wallet_balance)?;
let SuccessResponse {
coin_spends,
new_datastore,
} = mint_store(
owner_public_key,
selected_coins,
params.root_hash,
params.label,
params.description,
params.size_in_bytes,
params.size_proof,
owner_puzzle_hash,
delegated_puzzles,
fee,
)?;
let signature = sign_coin_spends(
&coin_spends,
&[owner_secret_key],
params.network == NetworkType::Testnet11,
)?;
let spend_bundle = SpendBundle::new(coin_spends, signature);
broadcast_spend_bundle(&peer, spend_bundle).await?;
Ok(new_datastore)
}
#[derive(Debug, Clone)]
pub struct UpdateParams<'up> {
pub ssl_cert_path: &'up str,
pub ssl_key_path: &'up str,
pub wallet: &'up Wallet,
pub data_store: DataStore,
pub new_root_hash: Bytes32,
pub network: NetworkType,
pub previous_height: Option<u32>,
pub new_label: Option<String>,
pub new_description: Option<String>,
pub new_size_in_bytes: Option<u64>,
pub new_size_proof: Option<String>,
pub admin_or_writer_public_key: Option<DataStoreInnerSpend>,
pub fee: Option<u64>,
}
pub async fn update(params: UpdateParams<'_>) -> datalayer_driver::Result<DataStore> {
let peer = connect_random(params.network, params.ssl_cert_path, params.ssl_key_path).await?;
let owner_secret_key = params.wallet.get_private_synthetic_key().await?;
let owner_public_key = secret_key_to_public_key(&owner_secret_key);
let inner_spend = if let Some(key) = params.admin_or_writer_public_key {
key
} else {
DataStoreInnerSpend::Owner(owner_public_key)
};
let SuccessResponse {
coin_spends: update_store_coin_spends,
new_datastore: updated_datastore,
} = update_store_metadata(
params.data_store,
params.new_root_hash,
params.new_label,
params.new_description,
params.new_size_in_bytes,
params.new_size_proof,
inner_spend,
)?;
let fee = if let Some(specified_fee) = params.fee {
specified_fee
} else {
get_fee_estimate(&peer, 60).await?
};
let height = if let Some(specified_height) = params.previous_height {
specified_height
} else {
DIG_MIN_HEIGHT
};
let owner_puzzle_hash = synthetic_key_to_puzzle_hash(&owner_public_key);
let owner_previous_header_hash = get_header_hash(&peer, height).await?;
let unspent_coin_states = get_all_unspent_coins(
&peer,
owner_puzzle_hash,
Some(height),
owner_previous_header_hash,
)
.await?;
let unspent_coins = unspent_coin_states
.coin_states
.iter()
.map(|coin_state| coin_state.coin)
.collect::<Vec<Coin>>();
let selected_coins = select_coins(&unspent_coins, fee)?;
let coin_ids: Vec<Bytes32> = selected_coins.iter().map(|coin| coin.coin_id()).collect();
let mut coin_spends = add_fee(&owner_public_key, &selected_coins, &coin_ids, fee)?;
coin_spends.extend(update_store_coin_spends);
let signature = sign_coin_spends(
&coin_spends,
&[owner_secret_key],
params.network == NetworkType::Testnet11,
)?;
let spend_bundle = SpendBundle::new(coin_spends, signature);
broadcast_spend_bundle(&peer, spend_bundle).await?;
Ok(updated_datastore)
}
#[derive(Debug, Clone)]
pub struct CollateralizeStoreParams<'cp> {
pub ssl_cert_path: &'cp str,
pub ssl_key_path: &'cp str,
pub wallet: &'cp Wallet,
pub store_id: Bytes32,
pub archive_file_size_bytes: u64,
pub network: NetworkType,
pub fee: Option<u64>,
pub verbose: bool,
}
pub async fn collateralize_store(
params: CollateralizeStoreParams<'_>,
) -> datalayer_driver::Result<TransactionAck> {
let peer = connect_random(params.network, params.ssl_cert_path, params.ssl_key_path).await?;
let private_key = params.wallet.get_private_synthetic_key().await?;
let public_key = params.wallet.get_public_synthetic_key().await?;
let fee = if let Some(specified_fee) = params.fee {
specified_fee
} else {
get_fee_estimate(&peer, 60).await?
};
let required_dig_collateral_amount = calc_required_collateral(params.archive_file_size_bytes);
let dig_cats = params
.wallet
.select_unspent_dig_cats(
&peer,
required_dig_collateral_amount,
vec![],
params.verbose,
)
.await?;
let xch_fee_coins = params
.wallet
.select_unspent_coins(&peer, 0, fee, vec![])
.await?;
let create_collateral_spends = create_dig_collateral_coin(
dig_cats,
required_dig_collateral_amount,
params.store_id,
public_key,
xch_fee_coins,
fee,
)?;
let coin_spend_signature = sign_coin_spends(
&create_collateral_spends,
&[private_key],
params.network != NetworkType::Mainnet,
)?;
let spend_bundle = SpendBundle::new(create_collateral_spends, coin_spend_signature);
Ok(broadcast_spend_bundle(&peer, spend_bundle).await?)
}
#[derive(Debug, Clone)]
pub struct CheckCollateralizationParams<'ckp> {
pub ssl_cert_path: &'ckp str,
pub ssl_key_path: &'ckp str,
pub store_id: Bytes32,
pub archive_file_size_bytes: u64,
pub network: NetworkType,
}
pub async fn check_store_collateralization(
params: CheckCollateralizationParams<'_>,
) -> datalayer_driver::Result<Option<P2ParentCoin>> {
let peer = connect_random(params.network, params.ssl_cert_path, params.ssl_key_path).await?;
let required_dig_collateral_amount = calc_required_collateral(params.archive_file_size_bytes);
let morphed_store_launcher_id = morph_store_launcher_id(params.store_id);
let mut maybe_collateral_coin_states =
get_unspent_coin_states_by_hint(&peer, morphed_store_launcher_id, params.network)
.await?
.coin_states
.into_iter()
.filter(|coin_state| coin_state.coin.amount >= required_dig_collateral_amount)
.collect::<Vec<_>>();
if maybe_collateral_coin_states.is_empty() {
return Ok(None);
}
maybe_collateral_coin_states.sort_unstable_by_key(|coin_state| Reverse(coin_state.coin.amount));
for coin_state in maybe_collateral_coin_states {
if let Ok((collateral_coin, _)) = fetch_dig_collateral_coin(&peer, coin_state).await {
return Ok(Some(collateral_coin));
}
}
Ok(None)
}
#[derive(Debug, Clone)]
pub struct GetAllCollateralCoinsForStoreParams<'gcp> {
pub ssl_cert_path: &'gcp str,
pub ssl_key_path: &'gcp str,
pub wallet: &'gcp Wallet,
pub store_id: Bytes32,
pub archive_file_size_bytes: u64,
pub network: NetworkType,
}
#[derive(Debug, Clone)]
pub struct StoreCollateral {
pub store_id: Bytes32,
pub owned_collateral_coins: Vec<P2ParentCoin>,
pub external_collateral_coins: Vec<P2ParentCoin>,
}
pub async fn get_all_store_collateral(
params: GetAllCollateralCoinsForStoreParams<'_>,
) -> datalayer_driver::Result<StoreCollateral> {
let wallet_puzzle_hash = params.wallet.get_owner_puzzle_hash().await?;
let peer = connect_random(params.network, params.ssl_cert_path, params.ssl_key_path).await?;
let required_dig_collateral_amount = calc_required_collateral(params.archive_file_size_bytes);
let morphed_store_launcher_id = morph_store_launcher_id(params.store_id);
let mut maybe_collateral_coin_states =
get_unspent_coin_states_by_hint(&peer, morphed_store_launcher_id, params.network)
.await?
.coin_states
.into_iter()
.filter(|coin_state| coin_state.coin.amount >= required_dig_collateral_amount)
.collect::<Vec<_>>();
let mut collateral = StoreCollateral {
store_id: params.store_id,
owned_collateral_coins: vec![],
external_collateral_coins: vec![],
};
if maybe_collateral_coin_states.is_empty() {
return Ok(collateral);
}
maybe_collateral_coin_states.sort_unstable_by_key(|coin_state| Reverse(coin_state.coin.amount));
for coin_state in maybe_collateral_coin_states {
if let Ok((collateral_coin, _)) = fetch_dig_collateral_coin(&peer, coin_state).await {
if collateral_coin.proof.parent_inner_puzzle_hash == wallet_puzzle_hash {
collateral.owned_collateral_coins.push(collateral_coin);
} else {
collateral.external_collateral_coins.push(collateral_coin);
}
}
}
Ok(collateral)
}
#[derive(Debug, Clone)]
pub struct ReclaimStoreCollateralParams<'rc> {
pub ssl_cert_path: &'rc str,
pub ssl_key_path: &'rc str,
pub wallet: &'rc Wallet,
pub collateral_coin: P2ParentCoin,
pub network: NetworkType,
pub fee: Option<u64>,
}
pub async fn reclaim_store_collateral(
params: ReclaimStoreCollateralParams<'_>,
) -> datalayer_driver::Result<(TransactionAck, Bytes32)> {
let p2_puzzle_hash = params.wallet.get_owner_puzzle_hash().await?;
if params.collateral_coin.proof.parent_inner_puzzle_hash != p2_puzzle_hash {
return Err(WalletError::CoinSetError(
"Collateral coin controlled by another wallet".to_string(),
)
.into());
}
let peer = connect_random(params.network, params.ssl_cert_path, params.ssl_key_path).await?;
let private_key = params.wallet.get_private_synthetic_key().await?;
let public_key = params.wallet.get_public_synthetic_key().await?;
let fee = if let Some(specified_fee) = params.fee {
specified_fee
} else {
get_fee_estimate(&peer, 60).await?
};
let xch_fee_coins = params
.wallet
.select_unspent_coins(&peer, 0, fee, vec![])
.await?;
let collateral_spends =
spend_dig_collateral_coin(public_key, xch_fee_coins, params.collateral_coin, fee)?;
let coin_spend_signature = sign_coin_spends(
&collateral_spends,
&[private_key],
params.network != NetworkType::Mainnet,
)?;
let spend_bundle = SpendBundle::new(collateral_spends, coin_spend_signature);
let ack = broadcast_spend_bundle(&peer, spend_bundle).await?;
Ok((ack, params.collateral_coin.coin.coin_id()))
}