1use datalayer_driver::async_api::get_all_unspent_coins;
2use datalayer_driver::types::TransactionAck;
3use datalayer_driver::wallet::{broadcast_spend_bundle, get_unspent_coin_states_by_hint};
4use datalayer_driver::{
5 Bytes32, Coin, DIG_MIN_HEIGHT, DataStore, DataStoreInnerSpend, DigCollateralCoin, NetworkType,
6 PublicKey, SpendBundle, SuccessResponse, add_fee, admin_delegated_puzzle_from_key,
7 connect_random, get_fee_estimate, get_header_hash, mint_store, oracle_delegated_puzzle,
8 secret_key_to_public_key, select_coins, sign_coin_spends, synthetic_key_to_puzzle_hash,
9 update_store_metadata, writer_delegated_puzzle_from_key,
10};
11use dig_wallet::Wallet;
12use num_bigint::BigInt;
13use std::cmp::Reverse;
14
15pub const DIG_MOJO_COLLATERAL_PER_MB: f64 = 5_f64;
17
18pub fn calc_required_collateral(archive_file_size_bytes: u64) -> u64 {
19 ((archive_file_size_bytes as f64 / 1000_f64) * DIG_MOJO_COLLATERAL_PER_MB).round() as u64
20}
21
22#[derive(Debug, Clone)]
23pub struct MintParams<'mp> {
24 pub ssl_cert_path: &'mp str,
25 pub ssl_key_path: &'mp str,
26 pub wallet: &'mp Wallet,
27 pub root_hash: Bytes32,
28 pub network: NetworkType,
29 pub previous_height: Option<u32>,
30 pub label: Option<String>,
31 pub description: Option<String>,
32 pub size_in_bytes: Option<u64>,
33 pub size_proof: Option<String>,
34 pub writer_public_synthetic_key: Option<PublicKey>,
35 pub admin_public_synthetic_key: Option<PublicKey>,
36 pub fee: Option<u64>,
37}
38
39pub async fn mint(params: MintParams<'_>) -> datalayer_driver::Result<DataStore> {
40 let peer = connect_random(params.network, params.ssl_cert_path, params.ssl_key_path).await?;
41
42 let owner_secret_key = params.wallet.get_private_synthetic_key().await?;
43 let owner_public_key = secret_key_to_public_key(&owner_secret_key);
44 let owner_puzzle_hash = synthetic_key_to_puzzle_hash(&owner_public_key);
45
46 let height = if let Some(specified_height) = params.previous_height {
47 specified_height
48 } else {
49 DIG_MIN_HEIGHT
50 };
51 let owner_previous_header_hash = get_header_hash(&peer, height).await?;
52
53 let mut delegated_puzzles = vec![oracle_delegated_puzzle(owner_puzzle_hash, 100_000)];
54
55 if let Some(key) = params.admin_public_synthetic_key {
56 delegated_puzzles.push(admin_delegated_puzzle_from_key(&key))
57 }
58
59 if let Some(key) = params.writer_public_synthetic_key {
60 delegated_puzzles.push(writer_delegated_puzzle_from_key(&key))
61 }
62
63 let fee = if let Some(specified_fee) = params.fee {
64 specified_fee
65 } else {
66 get_fee_estimate(&peer, 60).await?
67 };
68 let required_wallet_balance = fee + 1;
69
70 let unspent_coin_states = get_all_unspent_coins(
71 &peer,
72 owner_puzzle_hash,
73 Some(height),
74 owner_previous_header_hash,
75 )
76 .await?;
77
78 let unspent_coins = unspent_coin_states
79 .coin_states
80 .iter()
81 .map(|coin_state| coin_state.coin)
82 .collect::<Vec<Coin>>();
83 let selected_coins = select_coins(&unspent_coins, required_wallet_balance)?;
84
85 let SuccessResponse {
86 coin_spends,
87 new_datastore,
88 } = mint_store(
89 owner_public_key,
90 selected_coins,
91 params.root_hash,
92 params.label,
93 params.description,
94 params.size_in_bytes,
95 params.size_proof,
96 owner_puzzle_hash,
97 delegated_puzzles,
98 fee,
99 )?;
100
101 let signature = sign_coin_spends(
102 &coin_spends,
103 &[owner_secret_key],
104 params.network == NetworkType::Testnet11,
105 )?;
106 let spend_bundle = SpendBundle::new(coin_spends, signature);
107 broadcast_spend_bundle(&peer, spend_bundle).await?;
108
109 Ok(new_datastore)
110}
111
112#[derive(Debug, Clone)]
113pub struct UpdateParams<'up> {
114 pub ssl_cert_path: &'up str,
115 pub ssl_key_path: &'up str,
116 pub wallet: &'up Wallet,
117 pub data_store: DataStore,
118 pub new_root_hash: Bytes32,
119 pub network: NetworkType,
120 pub previous_height: Option<u32>,
121 pub new_label: Option<String>,
122 pub new_description: Option<String>,
123 pub new_size_in_bytes: Option<u64>,
124 pub new_size_proof: Option<String>,
125 pub admin_or_writer_public_key: Option<DataStoreInnerSpend>,
126 pub fee: Option<u64>,
127}
128
129pub async fn update(params: UpdateParams<'_>) -> datalayer_driver::Result<DataStore> {
130 let peer = connect_random(params.network, params.ssl_cert_path, params.ssl_key_path).await?;
131
132 let owner_secret_key = params.wallet.get_private_synthetic_key().await?;
133 let owner_public_key = secret_key_to_public_key(&owner_secret_key);
134 let inner_spend = if let Some(key) = params.admin_or_writer_public_key {
135 key
136 } else {
137 DataStoreInnerSpend::Owner(owner_public_key)
138 };
139
140 let SuccessResponse {
141 coin_spends: update_store_coin_spends,
142 new_datastore: updated_datastore,
143 } = update_store_metadata(
144 params.data_store,
145 params.new_root_hash,
146 params.new_label,
147 params.new_description,
148 params.new_size_in_bytes,
149 params.new_size_proof,
150 inner_spend,
151 )?;
152
153 let fee = if let Some(specified_fee) = params.fee {
154 specified_fee
155 } else {
156 get_fee_estimate(&peer, 60).await?
157 };
158
159 let height = if let Some(specified_height) = params.previous_height {
160 specified_height
161 } else {
162 DIG_MIN_HEIGHT
163 };
164 let owner_puzzle_hash = synthetic_key_to_puzzle_hash(&owner_public_key);
165 let owner_previous_header_hash = get_header_hash(&peer, height).await?;
166
167 let unspent_coin_states = get_all_unspent_coins(
168 &peer,
169 owner_puzzle_hash,
170 Some(height),
171 owner_previous_header_hash,
172 )
173 .await?;
174
175 let unspent_coins = unspent_coin_states
176 .coin_states
177 .iter()
178 .map(|coin_state| coin_state.coin)
179 .collect::<Vec<Coin>>();
180 let selected_coins = select_coins(&unspent_coins, fee)?;
181
182 let coin_ids: Vec<Bytes32> = selected_coins.iter().map(|coin| coin.coin_id()).collect();
183 let mut coin_spends = add_fee(&owner_public_key, &selected_coins, &coin_ids, fee)?;
184 coin_spends.extend(update_store_coin_spends);
185
186 let signature = sign_coin_spends(
187 &coin_spends,
188 &[owner_secret_key],
189 params.network == NetworkType::Testnet11,
190 )?;
191 let spend_bundle = SpendBundle::new(coin_spends, signature);
192 broadcast_spend_bundle(&peer, spend_bundle).await?;
193
194 Ok(updated_datastore)
195}
196
197#[derive(Debug, Clone)]
198pub struct CollateralizeStoreParams<'cp> {
199 pub ssl_cert_path: &'cp str,
200 pub ssl_key_path: &'cp str,
201 pub wallet: &'cp Wallet,
202 pub store_id: Bytes32,
203 pub archive_file_size_bytes: u64,
204 pub network: NetworkType,
205 pub fee: Option<u64>,
206 pub verbose: bool,
207}
208
209pub async fn collateralize_store(
210 params: CollateralizeStoreParams<'_>,
211) -> datalayer_driver::Result<TransactionAck> {
212 let peer = connect_random(params.network, params.ssl_cert_path, params.ssl_key_path).await?;
213 let private_key = params.wallet.get_private_synthetic_key().await?;
214 let public_key = params.wallet.get_public_synthetic_key().await?;
215
216 let fee = if let Some(specified_fee) = params.fee {
217 specified_fee
218 } else {
219 get_fee_estimate(&peer, 60).await?
220 };
221
222 let required_dig_collateral_amount = calc_required_collateral(params.archive_file_size_bytes);
223
224 let dig_cats = params
225 .wallet
226 .select_unspent_dig_coins(
227 &peer,
228 required_dig_collateral_amount,
229 vec![],
230 params.verbose,
231 )
232 .await?;
233 let xch_fee_coins = params
234 .wallet
235 .select_unspent_coins(&peer, 0, fee, vec![])
236 .await?;
237
238 let create_collateral_spends = DigCollateralCoin::create_collateral(
239 dig_cats,
240 required_dig_collateral_amount,
241 params.store_id,
242 public_key,
243 xch_fee_coins,
244 fee,
245 )?;
246 let coin_spend_signature = sign_coin_spends(
247 &create_collateral_spends,
248 &[private_key],
249 params.network != NetworkType::Mainnet,
250 )?;
251 let spend_bundle = SpendBundle::new(create_collateral_spends, coin_spend_signature);
252
253 Ok(broadcast_spend_bundle(&peer, spend_bundle).await?)
254}
255
256#[derive(Debug, Clone)]
257pub struct CreateMirrorParams<'mp> {
258 pub ssl_cert_path: &'mp str,
259 pub ssl_key_path: &'mp str,
260 pub wallet: &'mp Wallet,
261 pub store_id: Bytes32,
262 pub epoch: BigInt,
263 pub amount: u64,
264 pub mirror_urls: Vec<String>,
265 pub network: NetworkType,
266 pub fee: Option<u64>,
267 pub verbose: bool,
268}
269
270pub async fn create_store_mirror(
272 params: CreateMirrorParams<'_>,
273) -> datalayer_driver::Result<TransactionAck> {
274 let peer = connect_random(params.network, params.ssl_cert_path, params.ssl_key_path).await?;
275 let private_key = params.wallet.get_private_synthetic_key().await?;
276 let public_key = params.wallet.get_public_synthetic_key().await?;
277
278 let fee = if let Some(specified_fee) = params.fee { specified_fee } else { get_fee_estimate(&peer, 60).await? };
279
280 let dig_cats = params
281 .wallet
282 .select_unspent_dig_coins(
283 &peer,
284 params.amount,
285 vec![],
286 params.verbose,
287 )
288 .await?;
289
290 let xch_fee_coins = params
291 .wallet
292 .select_unspent_coins(&peer, 0, fee, vec![])
293 .await?;
294
295 let mirror_spends = DigCollateralCoin::create_mirror(
296 dig_cats,
297 params.amount,
298 params.store_id,
299 params.mirror_urls,
300 params.epoch,
301 public_key,
302 xch_fee_coins,
303 fee,
304 )?;
305
306 let coin_spend_signature = sign_coin_spends(
307 &mirror_spends,
308 &[private_key],
309 params.network != NetworkType::Mainnet,
310 )?;
311 let spend_bundle = SpendBundle::new(mirror_spends, coin_spend_signature);
312 Ok(broadcast_spend_bundle(&peer, spend_bundle).await?)
313}
314
315#[derive(Debug, Clone)]
316pub struct CheckCollateralizationParams<'ckp> {
317 pub ssl_cert_path: &'ckp str,
318 pub ssl_key_path: &'ckp str,
319 pub wallet: &'ckp Wallet,
320 pub store_id: Bytes32,
321 pub archive_file_size_bytes: u64,
322 pub network: NetworkType,
323}
324
325pub async fn check_store_collateralization(
328 params: CheckCollateralizationParams<'_>,
329) -> datalayer_driver::Result<Option<DigCollateralCoin>> {
330 let wallet_puzzle_hash = params.wallet.get_owner_puzzle_hash().await?;
331 let peer = connect_random(params.network, params.ssl_cert_path, params.ssl_key_path).await?;
332 let required_dig_collateral_amount = calc_required_collateral(params.archive_file_size_bytes);
333
334 let morphed_store_launcher_id =
335 DigCollateralCoin::morph_store_launcher_id_for_collateral(params.store_id);
336 let mut maybe_collateral_coin_states =
338 get_unspent_coin_states_by_hint(&peer, morphed_store_launcher_id, params.network)
339 .await?
340 .coin_states
341 .into_iter()
342 .filter(|coin_state| coin_state.coin.amount >= required_dig_collateral_amount)
343 .collect::<Vec<_>>();
344
345 if maybe_collateral_coin_states.is_empty() {
346 return Ok(None);
347 }
348
349 maybe_collateral_coin_states.sort_unstable_by_key(|coin_state| Reverse(coin_state.coin.amount));
351
352 for coin_state in maybe_collateral_coin_states {
353 if let Ok(collateral_coin) = DigCollateralCoin::from_coin_state(&peer, coin_state).await
354 && collateral_coin.proof().parent_inner_puzzle_hash == wallet_puzzle_hash
355 {
356 return Ok(Some(collateral_coin));
357 }
358 }
359
360 Ok(None)
361}
362
363#[derive(Debug, Clone)]
364pub struct GetStoreMirrorCoinsParams<'gmp> {
365 pub ssl_cert_path: &'gmp str,
366 pub ssl_key_path: &'gmp str,
367 pub wallet: &'gmp Wallet,
368 pub store_id: Bytes32,
369 pub epoch: &'gmp BigInt,
370 pub archive_file_size_bytes: u64,
371 pub network: NetworkType,
372}
373
374#[derive(Debug, Clone)]
375pub struct AllMirrorCoins {
376 pub store_id: Bytes32,
377 pub owned_collateral_coins: Vec<DigCollateralCoin>,
378 pub external_collateral_coins: Vec<DigCollateralCoin>,
379}
380
381pub async fn get_mirror_coins(
382 params: GetStoreMirrorCoinsParams<'_>,
383) -> datalayer_driver::Result<AllMirrorCoins> {
384 let wallet_puzzle_hash = params.wallet.get_owner_puzzle_hash().await?;
385 let peer = connect_random(params.network, params.ssl_cert_path, params.ssl_key_path).await?;
386 let required_dig_collateral_amount = calc_required_collateral(params.archive_file_size_bytes);
387
388 let morphed_store_launcher_id =
389 DigCollateralCoin::morph_store_launcher_id_for_mirror(params.store_id, params.epoch);
390 let mut maybe_collateral_coin_states =
392 get_unspent_coin_states_by_hint(&peer, morphed_store_launcher_id, params.network)
393 .await?
394 .coin_states
395 .into_iter()
396 .filter(|coin_state| coin_state.coin.amount >= required_dig_collateral_amount)
397 .collect::<Vec<_>>();
398
399 let mut collateral = AllMirrorCoins {
400 store_id: params.store_id,
401 owned_collateral_coins: vec![],
402 external_collateral_coins: vec![],
403 };
404
405 if maybe_collateral_coin_states.is_empty() {
406 return Ok(collateral);
407 }
408
409 maybe_collateral_coin_states.sort_unstable_by_key(|coin_state| Reverse(coin_state.coin.amount));
411
412 for coin_state in maybe_collateral_coin_states {
413 if let Ok(collateral_coin) = DigCollateralCoin::from_coin_state(&peer, coin_state).await {
414 if collateral_coin.proof().parent_inner_puzzle_hash == wallet_puzzle_hash {
415 collateral.owned_collateral_coins.push(collateral_coin);
416 } else {
417 collateral.external_collateral_coins.push(collateral_coin);
418 }
419 }
420 }
421
422 Ok(collateral)
423}
424
425#[derive(Debug, Clone)]
426pub struct ReclaimCollateralParams<'rc> {
427 pub ssl_cert_path: &'rc str,
428 pub ssl_key_path: &'rc str,
429 pub wallet: &'rc Wallet,
430 pub collateral_coin: DigCollateralCoin,
431 pub network: NetworkType,
432 pub fee: Option<u64>,
433}
434
435pub async fn reclaim_store_collateral(
437 params: ReclaimCollateralParams<'_>,
438) -> datalayer_driver::Result<(TransactionAck, Bytes32)> {
439 let peer = connect_random(params.network, params.ssl_cert_path, params.ssl_key_path).await?;
440 let private_key = params.wallet.get_private_synthetic_key().await?;
441 let public_key = params.wallet.get_public_synthetic_key().await?;
442
443 let fee = if let Some(specified_fee) = params.fee {
444 specified_fee
445 } else {
446 get_fee_estimate(&peer, 60).await?
447 };
448
449 let xch_fee_coins = params
450 .wallet
451 .select_unspent_coins(&peer, 0, fee, vec![])
452 .await?;
453
454 let collateral_spends = params
455 .collateral_coin
456 .spend(public_key, xch_fee_coins, fee)?;
457 let coin_spend_signature = sign_coin_spends(
458 &collateral_spends,
459 &[private_key],
460 params.network != NetworkType::Mainnet,
461 )?;
462 let spend_bundle = SpendBundle::new(collateral_spends, coin_spend_signature);
463
464 let ack = broadcast_spend_bundle(&peer, spend_bundle).await?;
465 Ok((ack, params.collateral_coin.coin().coin_id()))
466}
467
468pub async fn reclaim_mirror_coin(
470 params: ReclaimCollateralParams<'_>,
471) -> datalayer_driver::Result<(TransactionAck, Bytes32)> {
472 reclaim_store_collateral(params).await
473}