1use std::{
20 collections::{BTreeMap, HashMap},
21 str::FromStr,
22};
23
24use lazy_static::lazy_static;
25use rand::rngs::OsRng;
26use rusqlite::types::Value;
27
28use darkfi::{
29 tx::Transaction,
30 util::encoding::base64,
31 validator::fees::compute_fee,
32 zk::{halo2::Field, proof::ProvingKey, vm::ZkCircuit, vm_heap::empty_witnesses, Proof},
33 zkas::ZkBinary,
34 Error, Result,
35};
36use darkfi_money_contract::{
37 client::{
38 compute_remainder_blind,
39 fee_v1::{create_fee_proof, FeeCallInput, FeeCallOutput, FEE_CALL_GAS},
40 MoneyNote, OwnCoin,
41 },
42 model::{
43 Coin, Input, MoneyAuthTokenFreezeParamsV1, MoneyAuthTokenMintParamsV1, MoneyFeeParamsV1,
44 MoneyGenesisMintParamsV1, MoneyPoWRewardParamsV1, MoneyTokenMintParamsV1,
45 MoneyTransferParamsV1, Nullifier, Output, TokenId, DARK_TOKEN_ID,
46 },
47 MoneyFunction, MONEY_CONTRACT_ZKAS_FEE_NS_V1,
48};
49use darkfi_sdk::{
50 bridgetree::Position,
51 crypto::{
52 note::AeadEncryptedNote, pasta_prelude::PrimeField, BaseBlind, FuncId, Keypair, MerkleNode,
53 MerkleTree, PublicKey, ScalarBlind, SecretKey, MONEY_CONTRACT_ID,
54 },
55 dark_tree::DarkLeaf,
56 pasta::pallas,
57 ContractCall,
58};
59use darkfi_serial::{deserialize_async, serialize, serialize_async, AsyncEncodable};
60
61use crate::{
62 cache::CacheSmt,
63 cli_util::kaching,
64 convert_named_params,
65 error::{WalletDbError, WalletDbResult},
66 rpc::ScanCache,
67 Drk,
68};
69
70pub const SLED_MERKLE_TREES_MONEY: &[u8] = b"_money_tree";
72
73lazy_static! {
76 pub static ref MONEY_KEYS_TABLE: String =
77 format!("{}_money_keys", MONEY_CONTRACT_ID.to_string());
78 pub static ref MONEY_COINS_TABLE: String =
79 format!("{}_money_coins", MONEY_CONTRACT_ID.to_string());
80 pub static ref MONEY_TOKENS_TABLE: String =
81 format!("{}_money_tokens", MONEY_CONTRACT_ID.to_string());
82 pub static ref MONEY_ALIASES_TABLE: String =
83 format!("{}_money_aliases", MONEY_CONTRACT_ID.to_string());
84}
85
86pub const MONEY_KEYS_COL_KEY_ID: &str = "key_id";
88pub const MONEY_KEYS_COL_IS_DEFAULT: &str = "is_default";
89pub const MONEY_KEYS_COL_PUBLIC: &str = "public";
90pub const MONEY_KEYS_COL_SECRET: &str = "secret";
91
92pub const MONEY_COINS_COL_COIN: &str = "coin";
94pub const MONEY_COINS_COL_VALUE: &str = "value";
95pub const MONEY_COINS_COL_TOKEN_ID: &str = "token_id";
96pub const MONEY_COINS_COL_SPEND_HOOK: &str = "spend_hook";
97pub const MONEY_COINS_COL_USER_DATA: &str = "user_data";
98pub const MONEY_COINS_COL_COIN_BLIND: &str = "coin_blind";
99pub const MONEY_COINS_COL_VALUE_BLIND: &str = "value_blind";
100pub const MONEY_COINS_COL_TOKEN_BLIND: &str = "token_blind";
101pub const MONEY_COINS_COL_SECRET: &str = "secret";
102pub const MONEY_COINS_COL_LEAF_POSITION: &str = "leaf_position";
103pub const MONEY_COINS_COL_MEMO: &str = "memo";
104pub const MONEY_COINS_COL_CREATION_HEIGHT: &str = "creation_height";
105pub const MONEY_COINS_COL_IS_SPENT: &str = "is_spent";
106pub const MONEY_COINS_COL_SPENT_HEIGHT: &str = "spent_height";
107pub const MONEY_COINS_COL_SPENT_TX_HASH: &str = "spent_tx_hash";
108
109pub const MONEY_TOKENS_COL_TOKEN_ID: &str = "token_id";
111pub const MONEY_TOKENS_COL_MINT_AUTHORITY: &str = "mint_authority";
112pub const MONEY_TOKENS_COL_TOKEN_BLIND: &str = "token_blind";
113pub const MONEY_TOKENS_COL_IS_FROZEN: &str = "is_frozen";
114pub const MONEY_TOKENS_COL_FREEZE_HEIGHT: &str = "freeze_height";
115
116pub const MONEY_ALIASES_COL_ALIAS: &str = "alias";
118pub const MONEY_ALIASES_COL_TOKEN_ID: &str = "token_id";
119
120pub const BALANCE_BASE10_DECIMALS: usize = 8;
121
122impl Drk {
123 pub async fn initialize_money(&self, output: &mut Vec<String>) -> WalletDbResult<()> {
125 let wallet_schema = include_str!("../money.sql");
127 self.wallet.exec_batch_sql(wallet_schema)?;
128
129 self.add_alias("DRK".to_string(), *DARK_TOKEN_ID, output).await?;
131
132 Ok(())
133 }
134
135 pub async fn money_keygen(&self, output: &mut Vec<String>) -> WalletDbResult<()> {
137 output.push(String::from("Generating a new keypair"));
138
139 let keypair = Keypair::random(&mut OsRng);
141 let is_default = 0;
142
143 let query = format!(
144 "INSERT INTO {} ({}, {}, {}) VALUES (?1, ?2, ?3);",
145 *MONEY_KEYS_TABLE,
146 MONEY_KEYS_COL_IS_DEFAULT,
147 MONEY_KEYS_COL_PUBLIC,
148 MONEY_KEYS_COL_SECRET
149 );
150 self.wallet.exec_sql(
151 &query,
152 rusqlite::params![
153 is_default,
154 serialize_async(&keypair.public).await,
155 serialize_async(&keypair.secret).await
156 ],
157 )?;
158
159 output.push(String::from("New address:"));
160 output.push(format!("{}", keypair.public));
161
162 Ok(())
163 }
164
165 pub async fn default_secret(&self) -> Result<SecretKey> {
167 let row = match self.wallet.query_single(
168 &MONEY_KEYS_TABLE,
169 &[MONEY_KEYS_COL_SECRET],
170 convert_named_params! {(MONEY_KEYS_COL_IS_DEFAULT, 1)},
171 ) {
172 Ok(r) => r,
173 Err(e) => {
174 return Err(Error::DatabaseError(format!(
175 "[default_secret] Default secret key retrieval failed: {e}"
176 )))
177 }
178 };
179
180 let Value::Blob(ref key_bytes) = row[0] else {
181 return Err(Error::ParseFailed("[default_secret] Key bytes parsing failed"))
182 };
183 let secret_key: SecretKey = deserialize_async(key_bytes).await?;
184
185 Ok(secret_key)
186 }
187
188 pub async fn default_address(&self) -> Result<PublicKey> {
190 let row = match self.wallet.query_single(
191 &MONEY_KEYS_TABLE,
192 &[MONEY_KEYS_COL_PUBLIC],
193 convert_named_params! {(MONEY_KEYS_COL_IS_DEFAULT, 1)},
194 ) {
195 Ok(r) => r,
196 Err(e) => {
197 return Err(Error::DatabaseError(format!(
198 "[default_address] Default address retrieval failed: {e}"
199 )))
200 }
201 };
202
203 let Value::Blob(ref key_bytes) = row[0] else {
204 return Err(Error::ParseFailed("[default_address] Key bytes parsing failed"))
205 };
206 let public_key: PublicKey = deserialize_async(key_bytes).await?;
207
208 Ok(public_key)
209 }
210
211 pub fn set_default_address(&self, idx: usize) -> WalletDbResult<()> {
213 let is_default = 0;
215 let query = format!("UPDATE {} SET {} = ?1", *MONEY_KEYS_TABLE, MONEY_KEYS_COL_IS_DEFAULT,);
216 self.wallet.exec_sql(&query, rusqlite::params![is_default])?;
217
218 let is_default = 1;
220 let query = format!(
221 "UPDATE {} SET {} = ?1 WHERE {} = ?2",
222 *MONEY_KEYS_TABLE, MONEY_KEYS_COL_IS_DEFAULT, MONEY_KEYS_COL_KEY_ID,
223 );
224 self.wallet.exec_sql(&query, rusqlite::params![is_default, idx])
225 }
226
227 pub async fn addresses(&self) -> Result<Vec<(u64, PublicKey, SecretKey, u64)>> {
229 let rows = match self.wallet.query_multiple(&MONEY_KEYS_TABLE, &[], &[]) {
230 Ok(r) => r,
231 Err(e) => {
232 return Err(Error::DatabaseError(format!(
233 "[addresses] Addresses retrieval failed: {e}"
234 )))
235 }
236 };
237
238 let mut vec = Vec::with_capacity(rows.len());
239 for row in rows {
240 let Value::Integer(key_id) = row[0] else {
241 return Err(Error::ParseFailed("[addresses] Key ID parsing failed"))
242 };
243 let Ok(key_id) = u64::try_from(key_id) else {
244 return Err(Error::ParseFailed("[addresses] Key ID parsing failed"))
245 };
246
247 let Value::Integer(is_default) = row[1] else {
248 return Err(Error::ParseFailed("[addresses] Is default parsing failed"))
249 };
250 let Ok(is_default) = u64::try_from(is_default) else {
251 return Err(Error::ParseFailed("[addresses] Is default parsing failed"))
252 };
253
254 let Value::Blob(ref key_bytes) = row[2] else {
255 return Err(Error::ParseFailed("[addresses] Public key bytes parsing failed"))
256 };
257 let public_key: PublicKey = deserialize_async(key_bytes).await?;
258
259 let Value::Blob(ref key_bytes) = row[3] else {
260 return Err(Error::ParseFailed("[addresses] Secret key bytes parsing failed"))
261 };
262 let secret_key: SecretKey = deserialize_async(key_bytes).await?;
263
264 vec.push((key_id, public_key, secret_key, is_default));
265 }
266
267 Ok(vec)
268 }
269
270 pub async fn mining_config(
273 &self,
274 idx: usize,
275 spend_hook: Option<FuncId>,
276 user_data: Option<pallas::Base>,
277 output: &mut Vec<String>,
278 ) -> Result<()> {
279 let row = match self.wallet.query_single(
280 &MONEY_KEYS_TABLE,
281 &[MONEY_KEYS_COL_PUBLIC],
282 convert_named_params! {(MONEY_KEYS_COL_KEY_ID, idx)},
283 ) {
284 Ok(r) => r,
285 Err(e) => {
286 return Err(Error::DatabaseError(format!(
287 "[mining_address] Address retrieval failed: {e}"
288 )))
289 }
290 };
291
292 let Value::Blob(ref key_bytes) = row[0] else {
293 return Err(Error::ParseFailed("[mining_address] Key bytes parsing failed"))
294 };
295 let public_key: PublicKey = deserialize_async(key_bytes).await?;
296
297 let spend_hook = spend_hook.as_ref().map(|spend_hook| spend_hook.to_string());
298
299 let user_data =
300 user_data.as_ref().map(|user_data| bs58::encode(user_data.to_repr()).into_string());
301
302 output.push(String::from("DarkFi TOML configuration:"));
303 output.push(format!("recipient = \"{public_key}\""));
304 match spend_hook {
305 Some(ref spend_hook) => output.push(format!("spend_hook = \"{spend_hook}\"")),
306 None => output.push(String::from("#spend_hook = \"\"")),
307 }
308 match user_data {
309 Some(ref user_data) => output.push(format!("user_data = \"{user_data}\"")),
310 None => output.push(String::from("#user_data = \"\"")),
311 }
312 output.push(String::from("\nP2Pool wallet address to use:"));
313 output.push(base64::encode(&serialize(&(public_key, spend_hook, user_data))).to_string());
314
315 Ok(())
316 }
317
318 pub async fn get_money_secrets(&self) -> Result<Vec<SecretKey>> {
320 let rows =
321 match self.wallet.query_multiple(&MONEY_KEYS_TABLE, &[MONEY_KEYS_COL_SECRET], &[]) {
322 Ok(r) => r,
323 Err(e) => {
324 return Err(Error::DatabaseError(format!(
325 "[get_money_secrets] Secret keys retrieval failed: {e}"
326 )))
327 }
328 };
329
330 let mut secrets = Vec::with_capacity(rows.len());
331
332 for row in rows {
334 let Value::Blob(ref key_bytes) = row[0] else {
335 return Err(Error::ParseFailed(
336 "[get_money_secrets] Secret key bytes parsing failed",
337 ))
338 };
339 let secret_key: SecretKey = deserialize_async(key_bytes).await?;
340 secrets.push(secret_key);
341 }
342
343 Ok(secrets)
344 }
345
346 pub async fn import_money_secrets(
350 &self,
351 secrets: Vec<SecretKey>,
352 output: &mut Vec<String>,
353 ) -> Result<Vec<PublicKey>> {
354 let existing_secrets = self.get_money_secrets().await?;
355
356 let mut ret = Vec::with_capacity(secrets.len());
357
358 for secret in secrets {
359 if existing_secrets.contains(&secret) {
361 output.push(format!("Existing key found: {secret}"));
362 continue
363 }
364
365 ret.push(PublicKey::from_secret(secret));
366 let is_default = 0;
367 let public = serialize_async(&PublicKey::from_secret(secret)).await;
368 let secret = serialize_async(&secret).await;
369
370 let query = format!(
371 "INSERT INTO {} ({}, {}, {}) VALUES (?1, ?2, ?3);",
372 *MONEY_KEYS_TABLE,
373 MONEY_KEYS_COL_IS_DEFAULT,
374 MONEY_KEYS_COL_PUBLIC,
375 MONEY_KEYS_COL_SECRET
376 );
377 if let Err(e) =
378 self.wallet.exec_sql(&query, rusqlite::params![is_default, public, secret])
379 {
380 return Err(Error::DatabaseError(format!(
381 "[import_money_secrets] Inserting new address failed: {e}"
382 )))
383 }
384 }
385
386 Ok(ret)
387 }
388
389 pub async fn money_balance(&self) -> Result<HashMap<String, u64>> {
391 let mut coins = self.get_coins(false).await?;
392 coins.retain(|x| x.0.note.spend_hook == FuncId::none());
393
394 let mut balmap: HashMap<String, u64> = HashMap::new();
396
397 for coin in coins {
398 let mut value = coin.0.note.value;
399
400 if let Some(prev) = balmap.get(&coin.0.note.token_id.to_string()) {
401 value += prev;
402 }
403
404 balmap.insert(coin.0.note.token_id.to_string(), value);
405 }
406
407 Ok(balmap)
408 }
409
410 pub async fn get_coins(
415 &self,
416 fetch_spent: bool,
417 ) -> Result<Vec<(OwnCoin, u32, bool, Option<u32>, String)>> {
418 let query = if fetch_spent {
419 self.wallet.query_multiple(&MONEY_COINS_TABLE, &[], &[])
420 } else {
421 self.wallet.query_multiple(
422 &MONEY_COINS_TABLE,
423 &[],
424 convert_named_params! {(MONEY_COINS_COL_IS_SPENT, false)},
425 )
426 };
427
428 let rows = match query {
429 Ok(r) => r,
430 Err(e) => {
431 return Err(Error::DatabaseError(format!("[get_coins] Coins retrieval failed: {e}")))
432 }
433 };
434
435 let mut owncoins = Vec::with_capacity(rows.len());
436 for row in rows {
437 owncoins.push(self.parse_coin_record(&row).await?)
438 }
439
440 Ok(owncoins)
441 }
442
443 pub async fn get_token_coins(&self, token_id: &TokenId) -> Result<Vec<OwnCoin>> {
445 let query = self.wallet.query_multiple(
446 &MONEY_COINS_TABLE,
447 &[],
448 convert_named_params! {
449 (MONEY_COINS_COL_TOKEN_ID, serialize_async(token_id).await),
450 (MONEY_COINS_COL_SPEND_HOOK, serialize_async(&FuncId::none()).await),
451 (MONEY_COINS_COL_IS_SPENT, false),
452 },
453 );
454
455 let rows = match query {
456 Ok(r) => r,
457 Err(e) => {
458 return Err(Error::DatabaseError(format!(
459 "[get_token_coins] Coins retrieval failed: {e}"
460 )))
461 }
462 };
463
464 let mut owncoins = Vec::with_capacity(rows.len());
465 for row in rows {
466 owncoins.push(self.parse_coin_record(&row).await?.0)
467 }
468
469 Ok(owncoins)
470 }
471
472 pub async fn get_contract_token_coins(
474 &self,
475 token_id: &TokenId,
476 spend_hook: &FuncId,
477 user_data: &pallas::Base,
478 ) -> Result<Vec<OwnCoin>> {
479 let query = self.wallet.query_multiple(
480 &MONEY_COINS_TABLE,
481 &[],
482 convert_named_params! {
483 (MONEY_COINS_COL_TOKEN_ID, serialize_async(token_id).await),
484 (MONEY_COINS_COL_SPEND_HOOK, serialize_async(spend_hook).await),
485 (MONEY_COINS_COL_USER_DATA, serialize_async(user_data).await),
486 (MONEY_COINS_COL_IS_SPENT, false),
487 },
488 );
489
490 let rows = match query {
491 Ok(r) => r,
492 Err(e) => {
493 return Err(Error::DatabaseError(format!(
494 "[get_contract_token_coins] Coins retrieval failed: {e}"
495 )))
496 }
497 };
498
499 let mut owncoins = Vec::with_capacity(rows.len());
500 for row in rows {
501 owncoins.push(self.parse_coin_record(&row).await?.0)
502 }
503
504 Ok(owncoins)
505 }
506
507 async fn parse_coin_record(
511 &self,
512 row: &[Value],
513 ) -> Result<(OwnCoin, u32, bool, Option<u32>, String)> {
514 let Value::Blob(ref coin_bytes) = row[0] else {
515 return Err(Error::ParseFailed("[parse_coin_record] Coin bytes parsing failed"))
516 };
517 let coin: Coin = deserialize_async(coin_bytes).await?;
518
519 let Value::Blob(ref value_bytes) = row[1] else {
520 return Err(Error::ParseFailed("[parse_coin_record] Value bytes parsing failed"))
521 };
522 let value: u64 = deserialize_async(value_bytes).await?;
523
524 let Value::Blob(ref token_id_bytes) = row[2] else {
525 return Err(Error::ParseFailed("[parse_coin_record] Token ID bytes parsing failed"))
526 };
527 let token_id: TokenId = deserialize_async(token_id_bytes).await?;
528
529 let Value::Blob(ref spend_hook_bytes) = row[3] else {
530 return Err(Error::ParseFailed("[parse_coin_record] Spend hook bytes parsing failed"))
531 };
532 let spend_hook: pallas::Base = deserialize_async(spend_hook_bytes).await?;
533
534 let Value::Blob(ref user_data_bytes) = row[4] else {
535 return Err(Error::ParseFailed("[parse_coin_record] User data bytes parsing failed"))
536 };
537 let user_data: pallas::Base = deserialize_async(user_data_bytes).await?;
538
539 let Value::Blob(ref coin_blind_bytes) = row[5] else {
540 return Err(Error::ParseFailed("[parse_coin_record] Coin blind bytes parsing failed"))
541 };
542 let coin_blind: BaseBlind = deserialize_async(coin_blind_bytes).await?;
543
544 let Value::Blob(ref value_blind_bytes) = row[6] else {
545 return Err(Error::ParseFailed("[parse_coin_record] Value blind bytes parsing failed"))
546 };
547 let value_blind: ScalarBlind = deserialize_async(value_blind_bytes).await?;
548
549 let Value::Blob(ref token_blind_bytes) = row[7] else {
550 return Err(Error::ParseFailed("[parse_coin_record] Token blind bytes parsing failed"))
551 };
552 let token_blind: BaseBlind = deserialize_async(token_blind_bytes).await?;
553
554 let Value::Blob(ref secret_bytes) = row[8] else {
555 return Err(Error::ParseFailed("[parse_coin_record] Secret bytes parsing failed"))
556 };
557 let secret: SecretKey = deserialize_async(secret_bytes).await?;
558
559 let Value::Blob(ref leaf_position_bytes) = row[9] else {
560 return Err(Error::ParseFailed("[parse_coin_record] Leaf position bytes parsing failed"))
561 };
562 let leaf_position: Position = deserialize_async(leaf_position_bytes).await?;
563
564 let Value::Blob(ref memo) = row[10] else {
565 return Err(Error::ParseFailed("[parse_coin_record] Memo parsing failed"))
566 };
567
568 let Value::Integer(creation_height) = row[11] else {
569 return Err(Error::ParseFailed("[parse_coin_record] Creation height parsing failed"))
570 };
571 let Ok(creation_height) = u32::try_from(creation_height) else {
572 return Err(Error::ParseFailed("[parse_coin_record] Creation height parsing failed"))
573 };
574
575 let Value::Integer(is_spent) = row[12] else {
576 return Err(Error::ParseFailed("[parse_coin_record] Is spent parsing failed"))
577 };
578 let Ok(is_spent) = u64::try_from(is_spent) else {
579 return Err(Error::ParseFailed("[parse_coin_record] Is spent parsing failed"))
580 };
581 let is_spent = is_spent > 0;
582
583 let spent_height = match row[13] {
584 Value::Integer(spent_height) => {
585 let Ok(spent_height) = u32::try_from(spent_height) else {
586 return Err(Error::ParseFailed(
587 "[parse_coin_record] Spent height parsing failed",
588 ))
589 };
590 Some(spent_height)
591 }
592 Value::Null => None,
593 _ => return Err(Error::ParseFailed("[parse_coin_record] Spent height parsing failed")),
594 };
595
596 let Value::Text(ref spent_tx_hash) = row[14] else {
597 return Err(Error::ParseFailed(
598 "[parse_coin_record] Spent transaction hash parsing failed",
599 ))
600 };
601
602 let note = MoneyNote {
603 value,
604 token_id,
605 spend_hook: spend_hook.into(),
606 user_data,
607 coin_blind,
608 value_blind,
609 token_blind,
610 memo: memo.clone(),
611 };
612
613 Ok((
614 OwnCoin { coin, note, secret, leaf_position },
615 creation_height,
616 is_spent,
617 spent_height,
618 spent_tx_hash.clone(),
619 ))
620 }
621
622 pub async fn add_alias(
624 &self,
625 alias: String,
626 token_id: TokenId,
627 output: &mut Vec<String>,
628 ) -> WalletDbResult<()> {
629 output.push(format!("Generating alias {alias} for Token: {token_id}"));
630 let query = format!(
631 "INSERT OR REPLACE INTO {} ({}, {}) VALUES (?1, ?2);",
632 *MONEY_ALIASES_TABLE, MONEY_ALIASES_COL_ALIAS, MONEY_ALIASES_COL_TOKEN_ID,
633 );
634 self.wallet.exec_sql(
635 &query,
636 rusqlite::params![serialize_async(&alias).await, serialize_async(&token_id).await],
637 )
638 }
639
640 pub async fn get_aliases(
643 &self,
644 alias_filter: Option<String>,
645 token_id_filter: Option<TokenId>,
646 ) -> Result<HashMap<String, TokenId>> {
647 let rows = match self.wallet.query_multiple(&MONEY_ALIASES_TABLE, &[], &[]) {
648 Ok(r) => r,
649 Err(e) => {
650 return Err(Error::DatabaseError(format!(
651 "[get_aliases] Aliases retrieval failed: {e}"
652 )))
653 }
654 };
655
656 let mut map: HashMap<String, TokenId> = HashMap::new();
658 for row in rows {
659 let Value::Blob(ref alias_bytes) = row[0] else {
660 return Err(Error::ParseFailed("[get_aliases] Alias bytes parsing failed"))
661 };
662 let alias: String = deserialize_async(alias_bytes).await?;
663 if alias_filter.is_some() && alias_filter.as_ref().unwrap() != &alias {
664 continue
665 }
666
667 let Value::Blob(ref token_id_bytes) = row[1] else {
668 return Err(Error::ParseFailed("[get_aliases] TokenId bytes parsing failed"))
669 };
670 let token_id: TokenId = deserialize_async(token_id_bytes).await?;
671 if token_id_filter.is_some() && token_id_filter.as_ref().unwrap() != &token_id {
672 continue
673 }
674
675 map.insert(alias, token_id);
676 }
677
678 Ok(map)
679 }
680
681 pub async fn get_aliases_mapped_by_token(&self) -> Result<HashMap<String, String>> {
683 let aliases = self.get_aliases(None, None).await?;
684 let mut map: HashMap<String, String> = HashMap::new();
685 for (alias, token_id) in aliases {
686 let aliases_string = if let Some(prev) = map.get(&token_id.to_string()) {
687 format!("{prev}, {alias}")
688 } else {
689 alias
690 };
691
692 map.insert(token_id.to_string(), aliases_string);
693 }
694
695 Ok(map)
696 }
697
698 pub async fn remove_alias(
700 &self,
701 alias: String,
702 output: &mut Vec<String>,
703 ) -> WalletDbResult<()> {
704 output.push(format!("Removing alias: {alias}"));
705 let query = format!(
706 "DELETE FROM {} WHERE {} = ?1;",
707 *MONEY_ALIASES_TABLE, MONEY_ALIASES_COL_ALIAS,
708 );
709 self.wallet.exec_sql(&query, rusqlite::params![serialize_async(&alias).await])
710 }
711
712 pub async fn unspend_coin(&self, coin: &Coin) -> WalletDbResult<()> {
714 let query = format!(
715 "UPDATE {} SET {} = 0, {} = NULL, {} = '-' WHERE {} = ?1;",
716 *MONEY_COINS_TABLE,
717 MONEY_COINS_COL_IS_SPENT,
718 MONEY_COINS_COL_SPENT_HEIGHT,
719 MONEY_COINS_COL_SPENT_TX_HASH,
720 MONEY_COINS_COL_COIN
721 );
722 self.wallet.exec_sql(&query, rusqlite::params![serialize_async(&coin.inner()).await])
723 }
724
725 pub async fn get_money_tree(&self) -> Result<MerkleTree> {
728 match self.cache.merkle_trees.get(SLED_MERKLE_TREES_MONEY)? {
729 Some(tree_bytes) => Ok(deserialize_async(&tree_bytes).await?),
730 None => {
731 let mut tree = MerkleTree::new(u32::MAX as usize);
732 tree.append(MerkleNode::from(pallas::Base::ZERO));
733 let _ = tree.mark().unwrap();
734 Ok(tree)
735 }
736 }
737 }
738
739 async fn parse_money_call(
742 &self,
743 scan_cache: &mut ScanCache,
744 call_idx: &usize,
745 calls: &[DarkLeaf<ContractCall>],
746 ) -> Result<(Vec<Nullifier>, Vec<(Coin, AeadEncryptedNote)>, Vec<TokenId>)> {
747 let mut nullifiers: Vec<Nullifier> = vec![];
748 let mut coins: Vec<(Coin, AeadEncryptedNote)> = vec![];
749 let mut freezes: Vec<TokenId> = vec![];
750
751 let call = &calls[*call_idx];
752 let data = &call.data.data;
753 match MoneyFunction::try_from(data[0])? {
754 MoneyFunction::FeeV1 => {
755 scan_cache.log(String::from("[parse_money_call] Found Money::FeeV1 call"));
756 let params: MoneyFeeParamsV1 = deserialize_async(&data[9..]).await?;
757 nullifiers.push(params.input.nullifier);
758 coins.push((params.output.coin, params.output.note));
759 }
760 MoneyFunction::GenesisMintV1 => {
761 scan_cache.log(String::from("[parse_money_call] Found Money::GenesisMintV1 call"));
762 let params: MoneyGenesisMintParamsV1 = deserialize_async(&data[1..]).await?;
763 for output in params.outputs {
764 coins.push((output.coin, output.note));
765 }
766 }
767 MoneyFunction::PoWRewardV1 => {
768 scan_cache.log(String::from("[parse_money_call] Found Money::PoWRewardV1 call"));
769 let params: MoneyPoWRewardParamsV1 = deserialize_async(&data[1..]).await?;
770 coins.push((params.output.coin, params.output.note));
771 }
772 MoneyFunction::TransferV1 => {
773 scan_cache.log(String::from("[parse_money_call] Found Money::TransferV1 call"));
774 let params: MoneyTransferParamsV1 = deserialize_async(&data[1..]).await?;
775
776 for input in params.inputs {
777 nullifiers.push(input.nullifier);
778 }
779
780 for output in params.outputs {
781 coins.push((output.coin, output.note));
782 }
783 }
784 MoneyFunction::OtcSwapV1 => {
785 scan_cache.log(String::from("[parse_money_call] Found Money::OtcSwapV1 call"));
786 let params: MoneyTransferParamsV1 = deserialize_async(&data[1..]).await?;
787
788 for input in params.inputs {
789 nullifiers.push(input.nullifier);
790 }
791
792 for output in params.outputs {
793 coins.push((output.coin, output.note));
794 }
795 }
796 MoneyFunction::AuthTokenMintV1 => {
797 scan_cache
798 .log(String::from("[parse_money_call] Found Money::AuthTokenMintV1 call"));
799 }
801 MoneyFunction::AuthTokenFreezeV1 => {
802 scan_cache
803 .log(String::from("[parse_money_call] Found Money::AuthTokenFreezeV1 call"));
804 let params: MoneyAuthTokenFreezeParamsV1 = deserialize_async(&data[1..]).await?;
805 freezes.push(params.token_id);
806 }
807 MoneyFunction::TokenMintV1 => {
808 scan_cache.log(String::from("[parse_money_call] Found Money::TokenMintV1 call"));
809 let params: MoneyTokenMintParamsV1 = deserialize_async(&data[1..]).await?;
810 let child_idx = call.children_indexes[0];
812 let child_call = &calls[child_idx];
813 let child_params: MoneyAuthTokenMintParamsV1 =
814 deserialize_async(&child_call.data.data[1..]).await?;
815 coins.push((params.coin, child_params.enc_note));
816 }
817 }
818
819 Ok((nullifiers, coins, freezes))
820 }
821
822 fn handle_money_call_coins(
826 &self,
827 tree: &mut MerkleTree,
828 secrets: &[SecretKey],
829 messages_buffer: &mut Vec<String>,
830 coins: &[(Coin, AeadEncryptedNote)],
831 ) -> Vec<OwnCoin> {
832 let mut owncoins = vec![];
834
835 if coins.is_empty() {
837 return owncoins
838 }
839
840 for (coin, note) in coins {
842 tree.append(MerkleNode::from(coin.inner()));
845
846 for secret in secrets {
848 let Ok(note) = note.decrypt::<MoneyNote>(secret) else { continue };
849 messages_buffer.push(String::from(
850 "[handle_money_call_coins] Successfully decrypted a Money Note",
851 ));
852 messages_buffer
853 .push(String::from("[handle_money_call_coins] Witnessing coin in Merkle tree"));
854 let leaf_position = tree.mark().unwrap();
855 let owncoin = OwnCoin { coin: *coin, note, secret: *secret, leaf_position };
856 owncoins.push(owncoin);
857 }
858 }
859
860 owncoins
861 }
862
863 async fn handle_money_call_owncoins(
866 &self,
867 scan_cache: &mut ScanCache,
868 coins: &[OwnCoin],
869 creation_height: &u32,
870 ) -> Result<()> {
871 scan_cache.log(format!("Found {} OwnCoin(s) in transaction", coins.len()));
872
873 if coins.is_empty() {
875 return Ok(())
876 }
877
878 let query = format!(
880 "INSERT INTO {} ({}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}) VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11, ?12, ?13, ?14);",
881 *MONEY_COINS_TABLE,
882 MONEY_COINS_COL_COIN,
883 MONEY_COINS_COL_VALUE,
884 MONEY_COINS_COL_TOKEN_ID,
885 MONEY_COINS_COL_SPEND_HOOK,
886 MONEY_COINS_COL_USER_DATA,
887 MONEY_COINS_COL_COIN_BLIND,
888 MONEY_COINS_COL_VALUE_BLIND,
889 MONEY_COINS_COL_TOKEN_BLIND,
890 MONEY_COINS_COL_SECRET,
891 MONEY_COINS_COL_LEAF_POSITION,
892 MONEY_COINS_COL_MEMO,
893 MONEY_COINS_COL_CREATION_HEIGHT,
894 MONEY_COINS_COL_IS_SPENT,
895 MONEY_COINS_COL_SPENT_HEIGHT,
896 );
897
898 let spent_height: Option<u32> = None;
900 for coin in coins {
901 scan_cache.log(format!("OwnCoin: {:?}", coin.coin));
902 let key = coin.coin.to_bytes();
904
905 scan_cache
907 .owncoins_nullifiers
908 .insert(coin.nullifier().to_bytes(), (key, coin.leaf_position));
909
910 let params = rusqlite::params![
912 key,
913 serialize(&coin.note.value),
914 serialize(&coin.note.token_id),
915 serialize(&coin.note.spend_hook),
916 serialize(&coin.note.user_data),
917 serialize(&coin.note.coin_blind),
918 serialize(&coin.note.value_blind),
919 serialize(&coin.note.token_blind),
920 serialize(&coin.secret),
921 serialize(&coin.leaf_position),
922 serialize(&coin.note.memo),
923 creation_height,
924 0, spent_height,
926 ];
927
928 if let Err(e) = self.wallet.exec_sql(&query, params) {
929 return Err(Error::DatabaseError(format!(
930 "[handle_money_call_owncoins] Inserting Money coin failed: {e}"
931 )))
932 }
933 }
934
935 Ok(())
936 }
937
938 async fn handle_money_call_freezes(
943 &self,
944 own_tokens: &[TokenId],
945 freezes: &[TokenId],
946 freeze_height: &u32,
947 ) -> Result<bool> {
948 if freezes.is_empty() {
950 return Ok(false)
951 }
952
953 let mut own_freezes = Vec::with_capacity(freezes.len());
955 for freeze in freezes {
956 if own_tokens.contains(freeze) {
957 own_freezes.push(freeze);
958 }
959 }
960
961 if own_freezes.is_empty() {
963 return Ok(false)
964 }
965
966 let query = format!(
968 "UPDATE {} SET {} = 1, {} = ?1 WHERE {} = ?2;",
969 *MONEY_TOKENS_TABLE,
970 MONEY_TOKENS_COL_IS_FROZEN,
971 MONEY_TOKENS_COL_FREEZE_HEIGHT,
972 MONEY_TOKENS_COL_TOKEN_ID,
973 );
974
975 for token_id in own_freezes {
976 let key = serialize_async(token_id).await;
978
979 if let Err(e) =
981 self.wallet.exec_sql(&query, rusqlite::params![Some(*freeze_height), key])
982 {
983 return Err(Error::DatabaseError(format!(
984 "[handle_money_call_freezes] Update Money token freeze failed: {e}"
985 )))
986 }
987 }
988
989 Ok(true)
990 }
991
992 pub async fn apply_tx_money_data(
997 &self,
998 scan_cache: &mut ScanCache,
999 call_idx: &usize,
1000 calls: &[DarkLeaf<ContractCall>],
1001 tx_hash: &String,
1002 block_height: &u32,
1003 ) -> Result<bool> {
1004 let (nullifiers, coins, freezes) =
1006 self.parse_money_call(scan_cache, call_idx, calls).await?;
1007
1008 let owncoins = self.handle_money_call_coins(
1010 &mut scan_cache.money_tree,
1011 &scan_cache.notes_secrets,
1012 &mut scan_cache.messages_buffer,
1013 &coins,
1014 );
1015
1016 self.smt_insert(&mut scan_cache.money_smt, &nullifiers)?;
1018
1019 let wallet_spent_coins = self.mark_spent_coins(
1021 Some(&mut scan_cache.money_tree),
1022 &scan_cache.owncoins_nullifiers,
1023 &nullifiers,
1024 &Some(*block_height),
1025 tx_hash,
1026 )?;
1027
1028 self.handle_money_call_owncoins(scan_cache, &owncoins, block_height).await?;
1030
1031 let wallet_freezes =
1033 self.handle_money_call_freezes(&scan_cache.own_tokens, &freezes, block_height).await?;
1034
1035 if self.fun && !owncoins.is_empty() {
1036 kaching().await;
1037 }
1038
1039 Ok(wallet_spent_coins || !owncoins.is_empty() || wallet_freezes)
1040 }
1041
1042 async fn money_call_nullifiers(&self, call: &DarkLeaf<ContractCall>) -> Result<Vec<Nullifier>> {
1044 let mut nullifiers: Vec<Nullifier> = vec![];
1045
1046 let data = &call.data.data;
1047 match MoneyFunction::try_from(data[0])? {
1048 MoneyFunction::FeeV1 => {
1049 let params: MoneyFeeParamsV1 = deserialize_async(&data[9..]).await?;
1050 nullifiers.push(params.input.nullifier);
1051 }
1052 MoneyFunction::TransferV1 => {
1053 let params: MoneyTransferParamsV1 = deserialize_async(&data[1..]).await?;
1054
1055 for input in params.inputs {
1056 nullifiers.push(input.nullifier);
1057 }
1058 }
1059 MoneyFunction::OtcSwapV1 => {
1060 let params: MoneyTransferParamsV1 = deserialize_async(&data[1..]).await?;
1061
1062 for input in params.inputs {
1063 nullifiers.push(input.nullifier);
1064 }
1065 }
1066 _ => { }
1067 }
1068
1069 Ok(nullifiers)
1070 }
1071
1072 pub async fn mark_tx_spend(&self, tx: &Transaction, output: &mut Vec<String>) -> Result<()> {
1074 let mut owncoins_nullifiers = BTreeMap::new();
1076 for coin in self.get_coins(true).await? {
1077 owncoins_nullifiers.insert(
1078 coin.0.nullifier().to_bytes(),
1079 (coin.0.coin.to_bytes(), coin.0.leaf_position),
1080 );
1081 }
1082
1083 let tx_hash = tx.hash().to_string();
1084 output.push(format!("[mark_tx_spend] Processing transaction: {tx_hash}"));
1085 for (i, call) in tx.calls.iter().enumerate() {
1086 if call.data.contract_id != *MONEY_CONTRACT_ID {
1087 continue
1088 }
1089
1090 output.push(format!("[mark_tx_spend] Found Money contract in call {i}"));
1091 let nullifiers = self.money_call_nullifiers(call).await?;
1092 self.mark_spent_coins(None, &owncoins_nullifiers, &nullifiers, &None, &tx_hash)?;
1093 }
1094
1095 Ok(())
1096 }
1097
1098 pub fn mark_spent_coins(
1101 &self,
1102 mut tree: Option<&mut MerkleTree>,
1103 owncoins_nullifiers: &BTreeMap<[u8; 32], ([u8; 32], Position)>,
1104 nullifiers: &[Nullifier],
1105 spent_height: &Option<u32>,
1106 spent_tx_hash: &String,
1107 ) -> Result<bool> {
1108 if nullifiers.is_empty() {
1109 return Ok(false)
1110 }
1111
1112 let mut spent_owncoins = Vec::new();
1114 for nullifier in nullifiers {
1115 if let Some(coin) = owncoins_nullifiers.get(&nullifier.to_bytes()) {
1116 spent_owncoins.push(coin);
1117 }
1118 }
1119 if spent_owncoins.is_empty() {
1120 return Ok(false)
1121 }
1122
1123 let query = format!(
1125 "UPDATE {} SET {} = 1, {} = ?1, {} = ?2 WHERE {} = ?3;",
1126 *MONEY_COINS_TABLE,
1127 MONEY_COINS_COL_IS_SPENT,
1128 MONEY_COINS_COL_SPENT_HEIGHT,
1129 MONEY_COINS_COL_SPENT_TX_HASH,
1130 MONEY_COINS_COL_COIN
1131 );
1132
1133 for (ownoin, leaf_position) in spent_owncoins {
1135 if let Err(e) =
1137 self.wallet.exec_sql(&query, rusqlite::params![spent_height, spent_tx_hash, ownoin])
1138 {
1139 return Err(Error::DatabaseError(format!(
1140 "[mark_spent_coins] Marking spent coin failed: {e}"
1141 )))
1142 }
1143
1144 if let Some(ref mut tree) = tree {
1146 tree.remove_mark(*leaf_position);
1147 }
1148 }
1149
1150 Ok(true)
1151 }
1152
1153 pub fn smt_insert(&self, smt: &mut CacheSmt, nullifiers: &[Nullifier]) -> Result<()> {
1155 let leaves: Vec<_> = nullifiers.iter().map(|x| (x.inner(), x.inner())).collect();
1156 Ok(smt.insert_batch(leaves)?)
1157 }
1158
1159 pub fn reset_money_tree(&self, output: &mut Vec<String>) -> WalletDbResult<()> {
1161 output.push(String::from("Resetting Money Merkle tree"));
1162 if let Err(e) = self.cache.merkle_trees.remove(SLED_MERKLE_TREES_MONEY) {
1163 output.push(format!("[reset_money_tree] Resetting Money Merkle tree failed: {e}"));
1164 return Err(WalletDbError::GenericError)
1165 }
1166 output.push(String::from("Successfully reset Money Merkle tree"));
1167
1168 Ok(())
1169 }
1170
1171 pub fn reset_money_smt(&self, output: &mut Vec<String>) -> WalletDbResult<()> {
1173 output.push(String::from("Resetting Money Sparse Merkle tree"));
1174 if let Err(e) = self.cache.money_smt.clear() {
1175 output
1176 .push(format!("[reset_money_smt] Resetting Money Sparse Merkle tree failed: {e}"));
1177 return Err(WalletDbError::GenericError)
1178 }
1179 output.push(String::from("Successfully reset Money Sparse Merkle tree"));
1180
1181 Ok(())
1182 }
1183
1184 pub fn reset_money_coins(&self, output: &mut Vec<String>) -> WalletDbResult<()> {
1186 output.push(String::from("Resetting coins"));
1187 let query = format!("DELETE FROM {};", *MONEY_COINS_TABLE);
1188 self.wallet.exec_sql(&query, &[])?;
1189 output.push(String::from("Successfully reset coins"));
1190
1191 Ok(())
1192 }
1193
1194 pub fn remove_money_coins_after(
1197 &self,
1198 height: &u32,
1199 output: &mut Vec<String>,
1200 ) -> WalletDbResult<()> {
1201 output.push(format!("Removing coins after: {height}"));
1202 let query = format!(
1203 "DELETE FROM {} WHERE {} > ?1;",
1204 *MONEY_COINS_TABLE, MONEY_COINS_COL_CREATION_HEIGHT
1205 );
1206 self.wallet.exec_sql(&query, rusqlite::params![height])?;
1207 output.push(String::from("Successfully removed coins"));
1208
1209 Ok(())
1210 }
1211
1212 pub fn unspent_money_coins_after(
1215 &self,
1216 height: &u32,
1217 output: &mut Vec<String>,
1218 ) -> WalletDbResult<()> {
1219 output.push(format!("Unspenting coins after: {height}"));
1220 let query = format!(
1221 "UPDATE {} SET {} = 0, {} = NULL, {} = '=' WHERE {} > ?1;",
1222 *MONEY_COINS_TABLE,
1223 MONEY_COINS_COL_IS_SPENT,
1224 MONEY_COINS_COL_SPENT_HEIGHT,
1225 MONEY_COINS_COL_SPENT_TX_HASH,
1226 MONEY_COINS_COL_SPENT_HEIGHT
1227 );
1228 self.wallet.exec_sql(&query, rusqlite::params![Some(*height)])?;
1229 output.push(String::from("Successfully unspent coins"));
1230
1231 Ok(())
1232 }
1233
1234 pub async fn get_token(&self, input: String) -> Result<TokenId> {
1237 if input.chars().count() <= 5 {
1239 let aliases = self.get_aliases(Some(input.clone()), None).await?;
1240 if let Some(token_id) = aliases.get(&input) {
1241 return Ok(*token_id)
1242 }
1243 }
1244 Ok(TokenId::from_str(input.as_str())?)
1246 }
1247
1248 pub async fn append_fee_call(
1254 &self,
1255 tx: &Transaction,
1256 money_merkle_tree: &MerkleTree,
1257 fee_pk: &ProvingKey,
1258 fee_zkbin: &ZkBinary,
1259 spent_coins: Option<&[OwnCoin]>,
1260 ) -> Result<(ContractCall, Vec<Proof>, Vec<SecretKey>)> {
1261 let required_fee = compute_fee(&FEE_CALL_GAS) + self.get_tx_fee(tx, false).await?;
1264
1265 let mut available_coins = self.get_token_coins(&DARK_TOKEN_ID).await?;
1268 available_coins.retain(|x| x.note.value > required_fee);
1269 if let Some(spent_coins) = spent_coins {
1270 available_coins.retain(|x| !spent_coins.contains(x));
1271 }
1272 if available_coins.is_empty() {
1273 return Err(Error::Custom("Not enough native tokens to pay for fees".to_string()))
1274 }
1275
1276 let coin = &available_coins[0];
1277 let change_value = coin.note.value - required_fee;
1278
1279 let input = FeeCallInput {
1281 coin: coin.clone(),
1282 merkle_path: money_merkle_tree.witness(coin.leaf_position, 0).unwrap(),
1283 user_data_blind: BaseBlind::random(&mut OsRng),
1284 };
1285
1286 let output = FeeCallOutput {
1287 public_key: PublicKey::from_secret(coin.secret),
1288 value: change_value,
1289 token_id: coin.note.token_id,
1290 blind: BaseBlind::random(&mut OsRng),
1291 spend_hook: FuncId::none(),
1292 user_data: pallas::Base::ZERO,
1293 };
1294
1295 let token_blind = BaseBlind::random(&mut OsRng);
1297 let input_value_blind = ScalarBlind::random(&mut OsRng);
1298 let fee_value_blind = ScalarBlind::random(&mut OsRng);
1299 let output_value_blind = compute_remainder_blind(&[input_value_blind], &[fee_value_blind]);
1300
1301 let signature_secret = SecretKey::random(&mut OsRng);
1303
1304 let (proof, public_inputs) = create_fee_proof(
1306 fee_zkbin,
1307 fee_pk,
1308 &input,
1309 input_value_blind,
1310 &output,
1311 output_value_blind,
1312 output.spend_hook,
1313 output.user_data,
1314 output.blind,
1315 token_blind,
1316 signature_secret,
1317 )?;
1318
1319 let note = MoneyNote {
1321 coin_blind: output.blind,
1322 value: output.value,
1323 token_id: output.token_id,
1324 spend_hook: output.spend_hook,
1325 user_data: output.user_data,
1326 value_blind: output_value_blind,
1327 token_blind,
1328 memo: vec![],
1329 };
1330
1331 let encrypted_note = AeadEncryptedNote::encrypt(¬e, &output.public_key, &mut OsRng)?;
1332
1333 let params = MoneyFeeParamsV1 {
1334 input: Input {
1335 value_commit: public_inputs.input_value_commit,
1336 token_commit: public_inputs.token_commit,
1337 nullifier: public_inputs.nullifier,
1338 merkle_root: public_inputs.merkle_root,
1339 user_data_enc: public_inputs.input_user_data_enc,
1340 signature_public: public_inputs.signature_public,
1341 },
1342 output: Output {
1343 value_commit: public_inputs.output_value_commit,
1344 token_commit: public_inputs.token_commit,
1345 coin: public_inputs.output_coin,
1346 note: encrypted_note,
1347 },
1348 fee_value_blind,
1349 token_blind,
1350 };
1351
1352 let mut data = vec![MoneyFunction::FeeV1 as u8];
1354 required_fee.encode_async(&mut data).await?;
1355 params.encode_async(&mut data).await?;
1356 let call = ContractCall { contract_id: *MONEY_CONTRACT_ID, data };
1357
1358 Ok((call, vec![proof], vec![signature_secret]))
1359 }
1360
1361 pub async fn attach_fee(&self, tx: &mut Transaction) -> Result<()> {
1363 let mut tx_nullifiers = vec![];
1365 for call in &tx.calls {
1366 if call.data.contract_id != *MONEY_CONTRACT_ID {
1367 continue
1368 }
1369
1370 match MoneyFunction::try_from(call.data.data[0])? {
1371 MoneyFunction::FeeV1 => {
1372 return Err(Error::Custom("Fee call already exists".to_string()))
1373 }
1374 _ => { }
1375 }
1376
1377 let nullifiers = self.money_call_nullifiers(call).await?;
1378 tx_nullifiers.extend_from_slice(&nullifiers);
1379 }
1380
1381 let mut spent_coins = vec![];
1383 let available_coins = self.get_token_coins(&DARK_TOKEN_ID).await?;
1384 for coin in available_coins {
1385 if tx_nullifiers.contains(&coin.nullifier()) {
1386 spent_coins.push(coin);
1387 }
1388 }
1389
1390 let zkas_bins = self.lookup_zkas(&MONEY_CONTRACT_ID).await?;
1394
1395 let Some(fee_zkbin) = zkas_bins.iter().find(|x| x.0 == MONEY_CONTRACT_ZKAS_FEE_NS_V1)
1396 else {
1397 return Err(Error::Custom("Fee circuit not found".to_string()))
1398 };
1399
1400 let fee_zkbin = ZkBinary::decode(&fee_zkbin.1)?;
1401
1402 let fee_circuit = ZkCircuit::new(empty_witnesses(&fee_zkbin)?, &fee_zkbin);
1403
1404 let fee_pk = ProvingKey::build(fee_zkbin.k, &fee_circuit);
1406
1407 let tree = self.get_money_tree().await?;
1410 let (fee_call, fee_proofs, fee_secrets) =
1411 self.append_fee_call(tx, &tree, &fee_pk, &fee_zkbin, Some(&spent_coins)).await?;
1412
1413 tx.calls.push(DarkLeaf { data: fee_call, parent_index: None, children_indexes: vec![] });
1415 tx.proofs.push(fee_proofs);
1416 let sigs = tx.create_sigs(&fee_secrets)?;
1417 tx.signatures.push(sigs);
1418
1419 Ok(())
1420 }
1421}