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