use datalayer_driver::async_api::get_all_unspent_coins;
use datalayer_driver::types::TransactionAck;
use datalayer_driver::wallet::{broadcast_spend_bundle, get_unspent_coin_states_by_hint};
use datalayer_driver::{
Bytes32, Coin, DIG_MIN_HEIGHT, DataStore, DataStoreInnerSpend, DigCollateralCoin, NetworkType,
PublicKey, SpendBundle, SuccessResponse, add_fee, admin_delegated_puzzle_from_key,
connect_random, get_fee_estimate, get_header_hash, mint_store, 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;
use num_bigint::BigInt;
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_coins(
&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 = DigCollateralCoin::create_collateral(
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 wallet: &'ckp Wallet,
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<DigCollateralCoin>> {
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 =
DigCollateralCoin::morph_store_launcher_id_for_collateral(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) = DigCollateralCoin::from_coin_state(&peer, coin_state).await
&& collateral_coin.proof().parent_inner_puzzle_hash == wallet_puzzle_hash
{
return Ok(Some(collateral_coin));
}
}
Ok(None)
}
#[derive(Debug, Clone)]
pub struct GetStoreMirrorCoinsParams<'gmp> {
pub ssl_cert_path: &'gmp str,
pub ssl_key_path: &'gmp str,
pub wallet: &'gmp Wallet,
pub store_id: Bytes32,
pub epoch: &'gmp BigInt,
pub archive_file_size_bytes: u64,
pub network: NetworkType,
}
#[derive(Debug, Clone)]
pub struct AllMirrorCoins {
pub store_id: Bytes32,
pub owned_collateral_coins: Vec<DigCollateralCoin>,
pub external_collateral_coins: Vec<DigCollateralCoin>,
}
pub async fn get_mirror_coins(
params: GetStoreMirrorCoinsParams<'_>,
) -> datalayer_driver::Result<AllMirrorCoins> {
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 =
DigCollateralCoin::morph_store_launcher_id_for_mirror(params.store_id, params.epoch);
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 = AllMirrorCoins {
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) = DigCollateralCoin::from_coin_state(&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 ReclaimCollateralParams<'rc> {
pub ssl_cert_path: &'rc str,
pub ssl_key_path: &'rc str,
pub wallet: &'rc Wallet,
pub collateral_coin: DigCollateralCoin,
pub network: NetworkType,
pub fee: Option<u64>,
}
pub async fn reclaim_store_collateral(
params: ReclaimCollateralParams<'_>,
) -> datalayer_driver::Result<(TransactionAck, Bytes32)> {
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 = params
.collateral_coin
.spend(public_key, xch_fee_coins, 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()))
}