drk/
money.rs

1/* This file is part of DarkFi (https://dark.fi)
2 *
3 * Copyright (C) 2020-2025 Dyne.org foundation
4 *
5 * This program is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU Affero General Public License as
7 * published by the Free Software Foundation, either version 3 of the
8 * License, or (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 * GNU Affero General Public License for more details.
14 *
15 * You should have received a copy of the GNU Affero General Public License
16 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
17 */
18
19use 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
70// Money Merkle tree Sled key
71pub const SLED_MERKLE_TREES_MONEY: &[u8] = b"_money_tree";
72
73// Wallet SQL table constant names. These have to represent the `money.sql`
74// SQL schema. Table names are prefixed with the contract ID to avoid collisions.
75lazy_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
86// MONEY_KEYS_TABLE
87pub 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
92// MONEY_COINS_TABLE
93pub 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
109// MONEY_TOKENS_TABLE
110pub 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
116// MONEY_ALIASES_TABLE
117pub 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    /// Initialize wallet with tables for the Money contract.
124    pub async fn initialize_money(&self, output: &mut Vec<String>) -> WalletDbResult<()> {
125        // Initialize Money wallet schema
126        let wallet_schema = include_str!("../money.sql");
127        self.wallet.exec_batch_sql(wallet_schema)?;
128
129        // Insert DRK alias
130        self.add_alias("DRK".to_string(), *DARK_TOKEN_ID, output).await?;
131
132        Ok(())
133    }
134
135    /// Generate a new keypair and place it into the wallet.
136    pub async fn money_keygen(&self, output: &mut Vec<String>) -> WalletDbResult<()> {
137        output.push(String::from("Generating a new keypair"));
138
139        // TODO: We might want to have hierarchical deterministic key derivation.
140        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    /// Fetch default secret key from the wallet.
166    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    /// Fetch default pubkey from the wallet.
189    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    /// Set provided index address as default in the wallet.
212    pub fn set_default_address(&self, idx: usize) -> WalletDbResult<()> {
213        // First we update previous default record
214        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        // and then we set the new one
219        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    /// Fetch all pukeys from the wallet.
228    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    /// Fetch provided index address from the wallet and generate its
271    /// mining configuration.
272    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    /// Fetch all secret keys from the wallet.
319    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        // Let's scan through the rows and see if we got anything.
333        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    /// Import given secret keys into the wallet.
347    /// If the key already exists, it will be skipped.
348    /// Returns the respective PublicKey objects for the imported keys.
349    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            // Check if secret already exists
360            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    /// Fetch known unspent balances from the wallet and return them as a hashmap.
390    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        // Fill this map with balances
395        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    /// Fetch all coins and their metadata related to the Money contract from the wallet.
411    /// Optionally also fetch spent ones.
412    /// The boolean in the returned tuple notes if the coin was marked
413    /// as spent, along with the height and tx it was spent in.
414    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    /// Fetch provided token unspend balances from the wallet.
444    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    /// Fetch provided contract specified token unspend balances from the wallet.
473    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    /// Auxiliary function to parse a `MONEY_COINS_TABLE` record.
508    /// The boolean in the returned tuple notes if the coin was marked
509    /// as spent, along with the height and tx it was spent in.
510    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    /// Create an alias record for provided Token ID.
623    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    /// Fetch all aliases from the wallet.
641    /// Optionally filter using alias name and/or token id.
642    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        // Fill this map with aliases
657        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    /// Fetch all aliases from the wallet, mapped by token id.
682    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    /// Remove provided alias record from the wallet database.
699    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    /// Mark a given coin in the wallet as unspent.
713    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    /// Fetch the Money Merkle tree from the cache.
726    /// If it doesn't exists a new Merkle Tree is returned.
727    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    /// Auxiliary function to grab all the nullifiers, coins with their
740    /// notes and freezes from a transaction money call.
741    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                // Handled in TokenMint
800            }
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                // Grab the note from the child auth call
811                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    /// Auxiliary function to handle coins with their notes from a
823    /// transaction money call.
824    /// Returns our found own coins.
825    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        // Keep track of our own coins found in the vec
833        let mut owncoins = vec![];
834
835        // Check if provided coins vec is empty
836        if coins.is_empty() {
837            return owncoins
838        }
839
840        // Handle provided coins vector and grab our own
841        for (coin, note) in coins {
842            // Append the new coin to the Merkle tree.
843            // Every coin has to be added.
844            tree.append(MerkleNode::from(coin.inner()));
845
846            // Attempt to decrypt the note
847            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    /// Auxiliary function to handle own coins from a transaction money
864    /// call.
865    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        // Check if we have any owncoins to process
874        if coins.is_empty() {
875            return Ok(())
876        }
877
878        // This is the SQL query we'll be executing to insert new coins into the wallet
879        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        // Handle our own coins
899        let spent_height: Option<u32> = None;
900        for coin in coins {
901            scan_cache.log(format!("OwnCoin: {:?}", coin.coin));
902            // Grab coin record key
903            let key = coin.coin.to_bytes();
904
905            // Push to our own coins nullifiers cache
906            scan_cache
907                .owncoins_nullifiers
908                .insert(coin.nullifier().to_bytes(), (key, coin.leaf_position));
909
910            // Execute the query
911            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, // <-- is_spent
925                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    /// Auxiliary function to handle freezes from a transaction money
939    /// call.
940    /// Returns a flag indicating if provided freezes refer to our own
941    /// wallet.
942    async fn handle_money_call_freezes(
943        &self,
944        own_tokens: &[TokenId],
945        freezes: &[TokenId],
946        freeze_height: &u32,
947    ) -> Result<bool> {
948        // Check if we have any freezes to process
949        if freezes.is_empty() {
950            return Ok(false)
951        }
952
953        // Find our own tokens that got frozen
954        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        // Check if we need to freeze anything
962        if own_freezes.is_empty() {
963            return Ok(false)
964        }
965
966        // This is the SQL query we'll be executing to update frozen tokens into the wallet
967        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            // Grab token record key
977            let key = serialize_async(token_id).await;
978
979            // Execute the query
980            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    /// Append data related to Money contract transactions into the
993    /// wallet database and update the provided scan cache.
994    /// Returns a flag indicating if provided data refer to our own
995    /// wallet.
996    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        // Parse the call
1005        let (nullifiers, coins, freezes) =
1006            self.parse_money_call(scan_cache, call_idx, calls).await?;
1007
1008        // Parse call coins and grab our own
1009        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        // Update nullifiers smt
1017        self.smt_insert(&mut scan_cache.money_smt, &nullifiers)?;
1018
1019        // Check if we have any spent coins
1020        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        // Handle our own coins
1029        self.handle_money_call_owncoins(scan_cache, &owncoins, block_height).await?;
1030
1031        // Handle freezes
1032        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    /// Auxiliary function to  grab all the nullifiers from a transaction money call.
1043    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            _ => { /* Do nothing */ }
1067        }
1068
1069        Ok(nullifiers)
1070    }
1071
1072    /// Mark provided transaction input coins as spent.
1073    pub async fn mark_tx_spend(&self, tx: &Transaction, output: &mut Vec<String>) -> Result<()> {
1074        // Create a cache of all our own nullifiers
1075        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    /// Marks all coins in the wallet as spent, if their nullifier is in the given set.
1099    /// Returns a flag indicating if any of the provided nullifiers refer to our own wallet.
1100    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        // Find our owncoins that where spent
1113        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        // Create an SQL `UPDATE` query to mark rows as spent(1)
1124        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        // Mark spent own coins
1134        for (ownoin, leaf_position) in spent_owncoins {
1135            // Execute the query
1136            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            // Remove the coin mark from the Merkle tree
1145            if let Some(ref mut tree) = tree {
1146                tree.remove_mark(*leaf_position);
1147            }
1148        }
1149
1150        Ok(true)
1151    }
1152
1153    /// Inserts given slice to the wallets nullifiers Sparse Merkle Tree.
1154    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    /// Reset the Money Merkle tree in the cache.
1160    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    /// Reset the Money nullifiers Sparse Merkle Tree in the cache.
1172    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    /// Reset the Money coins in the wallet.
1185    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    /// Remove the Money coins in the wallet that were created after
1195    /// provided height.
1196    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    /// Mark the Money coins in the wallet that were spent after
1213    /// provided height as unspent.
1214    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    /// Retrieve token by provided string.
1235    /// Input string represents either an alias or a token id.
1236    pub async fn get_token(&self, input: String) -> Result<TokenId> {
1237        // Check if input is an alias(max 5 characters)
1238        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        // Else parse input
1245        Ok(TokenId::from_str(input.as_str())?)
1246    }
1247
1248    /// Create and append a `Money::Fee` call to a given [`Transaction`].
1249    ///
1250    /// Optionally takes a set of spent coins in order not to reuse them here.
1251    ///
1252    /// Returns the `Fee` call, and all necessary data and parameters related.
1253    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        // First we verify the fee-less transaction to see how much fee it requires for execution
1262        // and verification.
1263        let required_fee = compute_fee(&FEE_CALL_GAS) + self.get_tx_fee(tx, false).await?;
1264
1265        // Knowing the total gas, we can now find an OwnCoin of enough value
1266        // so that we can create a valid Money::Fee call.
1267        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        // Input and output setup
1280        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        // Create blinding factors
1296        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        // Create an ephemeral signing key
1302        let signature_secret = SecretKey::random(&mut OsRng);
1303
1304        // Create the actual fee proof
1305        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        // Encrypted note for the output
1320        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(&note, &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        // Encode the contract call
1353        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    /// Create and attach the fee call to given transaction.
1362    pub async fn attach_fee(&self, tx: &mut Transaction) -> Result<()> {
1363        // Grab spent coins nullifiers of the transactions and check no other fee call exists
1364        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                _ => { /* Do nothing */ }
1375            }
1376
1377            let nullifiers = self.money_call_nullifiers(call).await?;
1378            tx_nullifiers.extend_from_slice(&nullifiers);
1379        }
1380
1381        // Grab all native owncoins to check if any is spent
1382        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        // Now we need to do a lookup for the zkas proof bincodes, and create
1391        // the circuit objects and proving keys so we can build the transaction.
1392        // We also do this through the RPC.
1393        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        // Creating Fee circuits proving keys
1405        let fee_pk = ProvingKey::build(fee_zkbin.k, &fee_circuit);
1406
1407        // We first have to execute the fee-less tx to gather its used gas, and then we feed
1408        // it into the fee-creating function.
1409        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        // Append the fee call to the transaction
1414        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}