drk/
money.rs

1/* This file is part of DarkFi (https://dark.fi)
2 *
3 * Copyright (C) 2020-2026 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        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
72// Money Merkle tree Sled key
73pub const SLED_MERKLE_TREES_MONEY: &[u8] = b"_money_tree";
74
75// Wallet SQL table constant names. These have to represent the `money.sql`
76// SQL schema. Table names are prefixed with the contract ID to avoid collisions.
77lazy_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
88// MONEY_KEYS_TABLE
89pub 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
94// MONEY_COINS_TABLE
95pub 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
111// MONEY_TOKENS_TABLE
112pub 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
118// MONEY_ALIASES_TABLE
119pub 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    /// Initialize wallet with tables for the Money contract.
126    pub async fn initialize_money(&self, output: &mut Vec<String>) -> WalletDbResult<()> {
127        // Initialize Money wallet schema
128        let wallet_schema = include_str!("../money.sql");
129        self.wallet.exec_batch_sql(wallet_schema)?;
130
131        // Insert DRK alias
132        self.add_alias("DRK".to_string(), *DARK_TOKEN_ID, output).await?;
133
134        Ok(())
135    }
136
137    /// Generate a new keypair and place it into the wallet.
138    pub async fn money_keygen(&self, output: &mut Vec<String>) -> WalletDbResult<()> {
139        output.push(String::from("Generating a new keypair"));
140
141        // TODO: We might want to have hierarchical deterministic key derivation.
142        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    /// Fetch default secret key from the wallet.
169    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    /// Fetch default pubkey from the wallet.
192    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    /// Set provided index address as default in the wallet.
215    pub fn set_default_address(&self, idx: usize) -> WalletDbResult<()> {
216        // First we update previous default record
217        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        // and then we set the new one
222        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    /// Fetch all pukeys from the wallet.
231    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    /// Fetch provided index address from the wallet and generate its
274    /// mining configuration.
275    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    /// Fetch all secret keys from the wallet.
310    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        // Let's scan through the rows and see if we got anything.
324        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    /// Import given secret keys into the wallet.
338    /// If the key already exists, it will be skipped.
339    /// Returns the respective PublicKey objects for the imported keys.
340    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            // Check if secret already exists
351            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    /// Fetch known unspent balances from the wallet and return them as a hashmap.
381    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        // Fill this map with balances
386        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    /// Fetch all coins and their metadata related to the Money contract from the wallet.
402    /// Optionally also fetch spent ones.
403    /// The boolean in the returned tuple notes if the coin was marked
404    /// as spent, along with the height and tx it was spent in.
405    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    /// Fetch provided token unspend balances from the wallet.
435    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    /// Fetch provided contract specified token unspend balances from the wallet.
464    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    /// Auxiliary function to parse a `MONEY_COINS_TABLE` record.
499    /// The boolean in the returned tuple notes if the coin was marked
500    /// as spent, along with the height and tx it was spent in.
501    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    /// Create an alias record for provided Token ID.
614    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    /// Fetch all aliases from the wallet.
632    /// Optionally filter using alias name and/or token id.
633    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        // Fill this map with aliases
648        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    /// Fetch all aliases from the wallet, mapped by token id.
673    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    /// Remove provided alias record from the wallet database.
690    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    /// Mark a given coin in the wallet as unspent.
704    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    /// Fetch the Money Merkle tree from the cache.
717    /// If it doesn't exists a new Merkle Tree is returned.
718    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    /// Auxiliary function to grab all the nullifiers, coins with their
731    /// notes and a flag indicating if its a block reward, and freezes
732    /// from a transaction money call.
733    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                coins.push((params.output.coin, params.output.note, false));
751            }
752            MoneyFunction::GenesisMintV1 => {
753                scan_cache.log(String::from("[parse_money_call] Found Money::GenesisMintV1 call"));
754                let params: MoneyGenesisMintParamsV1 = deserialize_async(&data[1..]).await?;
755                for output in params.outputs {
756                    coins.push((output.coin, output.note, false));
757                }
758            }
759            MoneyFunction::PoWRewardV1 => {
760                scan_cache.log(String::from("[parse_money_call] Found Money::PoWRewardV1 call"));
761                let params: MoneyPoWRewardParamsV1 = deserialize_async(&data[1..]).await?;
762                coins.push((params.output.coin, params.output.note, true));
763            }
764            MoneyFunction::TransferV1 => {
765                scan_cache.log(String::from("[parse_money_call] Found Money::TransferV1 call"));
766                let params: MoneyTransferParamsV1 = deserialize_async(&data[1..]).await?;
767
768                for input in params.inputs {
769                    nullifiers.push(input.nullifier);
770                }
771
772                for output in params.outputs {
773                    coins.push((output.coin, output.note, false));
774                }
775            }
776            MoneyFunction::OtcSwapV1 => {
777                scan_cache.log(String::from("[parse_money_call] Found Money::OtcSwapV1 call"));
778                let params: MoneyTransferParamsV1 = deserialize_async(&data[1..]).await?;
779
780                for input in params.inputs {
781                    nullifiers.push(input.nullifier);
782                }
783
784                for output in params.outputs {
785                    coins.push((output.coin, output.note, false));
786                }
787            }
788            MoneyFunction::AuthTokenMintV1 => {
789                scan_cache
790                    .log(String::from("[parse_money_call] Found Money::AuthTokenMintV1 call"));
791                // Handled in TokenMint
792            }
793            MoneyFunction::AuthTokenFreezeV1 => {
794                scan_cache
795                    .log(String::from("[parse_money_call] Found Money::AuthTokenFreezeV1 call"));
796                let params: MoneyAuthTokenFreezeParamsV1 = deserialize_async(&data[1..]).await?;
797                freezes.push(params.token_id);
798            }
799            MoneyFunction::TokenMintV1 => {
800                scan_cache.log(String::from("[parse_money_call] Found Money::TokenMintV1 call"));
801                let params: MoneyTokenMintParamsV1 = deserialize_async(&data[1..]).await?;
802                // Grab the note from the child auth call
803                let child_idx = call.children_indexes[0];
804                let child_call = &calls[child_idx];
805                let child_params: MoneyAuthTokenMintParamsV1 =
806                    deserialize_async(&child_call.data.data[1..]).await?;
807                coins.push((params.coin, child_params.enc_note, false));
808            }
809        }
810
811        Ok((nullifiers, coins, freezes))
812    }
813
814    /// Auxiliary function to handle coins with their notes and flag
815    /// indicating if its a block reward from a transaction money call.
816    /// Returns our found own coins along with the block signing key,
817    /// if found.
818    fn handle_money_call_coins(
819        &self,
820        tree: &mut MerkleTree,
821        secrets: &[SecretKey],
822        messages_buffer: &mut Vec<String>,
823        coins: &[(Coin, AeadEncryptedNote, bool)],
824    ) -> Result<(Vec<OwnCoin>, Option<SecretKey>)> {
825        // Keep track of our own coins found in the vec
826        let mut owncoins = vec![];
827
828        // Check if provided coins vec is empty
829        if coins.is_empty() {
830            return Ok((owncoins, None))
831        }
832
833        // Handle provided coins vector and grab our own,
834        // along with the block signing key if its a block
835        // reward coin. Only one reward call and coin exists
836        // in each block.
837        let mut block_signing_key = None;
838        for (coin, note, is_block_reward) in coins {
839            // Append the new coin to the Merkle tree.
840            // Every coin has to be added.
841            tree.append(MerkleNode::from(coin.inner()));
842
843            // Attempt to decrypt the note
844            for secret in secrets {
845                let Ok(note) = note.decrypt::<MoneyNote>(secret) else { continue };
846                messages_buffer.push(String::from(
847                    "[handle_money_call_coins] Successfully decrypted a Money Note",
848                ));
849                messages_buffer
850                    .push(String::from("[handle_money_call_coins] Witnessing coin in Merkle tree"));
851                let leaf_position = tree.mark().unwrap();
852                if *is_block_reward {
853                    messages_buffer
854                        .push(String::from("[handle_money_call_coins] Grabing block signing key"));
855                    block_signing_key = Some(deserialize(&note.memo)?);
856                }
857                let owncoin = OwnCoin { coin: *coin, note, secret: *secret, leaf_position };
858                owncoins.push(owncoin);
859                break
860            }
861        }
862
863        Ok((owncoins, block_signing_key))
864    }
865
866    /// Auxiliary function to handle own coins from a transaction money
867    /// call.
868    async fn handle_money_call_owncoins(
869        &self,
870        scan_cache: &mut ScanCache,
871        coins: &[OwnCoin],
872        creation_height: &u32,
873    ) -> Result<()> {
874        scan_cache.log(format!("Found {} OwnCoin(s) in transaction", coins.len()));
875
876        // Check if we have any owncoins to process
877        if coins.is_empty() {
878            return Ok(())
879        }
880
881        // This is the SQL query we'll be executing to insert new coins into the wallet
882        let query = format!(
883            "INSERT INTO {} ({}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}) VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11, ?12, ?13, ?14);",
884            *MONEY_COINS_TABLE,
885            MONEY_COINS_COL_COIN,
886            MONEY_COINS_COL_VALUE,
887            MONEY_COINS_COL_TOKEN_ID,
888            MONEY_COINS_COL_SPEND_HOOK,
889            MONEY_COINS_COL_USER_DATA,
890            MONEY_COINS_COL_COIN_BLIND,
891            MONEY_COINS_COL_VALUE_BLIND,
892            MONEY_COINS_COL_TOKEN_BLIND,
893            MONEY_COINS_COL_SECRET,
894            MONEY_COINS_COL_LEAF_POSITION,
895            MONEY_COINS_COL_MEMO,
896            MONEY_COINS_COL_CREATION_HEIGHT,
897            MONEY_COINS_COL_IS_SPENT,
898            MONEY_COINS_COL_SPENT_HEIGHT,
899        );
900
901        // Handle our own coins
902        let spent_height: Option<u32> = None;
903        for coin in coins {
904            scan_cache.log(format!("OwnCoin: {:?}", coin.coin));
905            // Grab coin record key
906            let key = coin.coin.to_bytes();
907
908            // Push to our own coins nullifiers cache
909            scan_cache
910                .owncoins_nullifiers
911                .insert(coin.nullifier().to_bytes(), (key, coin.leaf_position));
912
913            // Execute the query
914            let params = rusqlite::params![
915                key,
916                serialize(&coin.note.value),
917                serialize(&coin.note.token_id),
918                serialize(&coin.note.spend_hook),
919                serialize(&coin.note.user_data),
920                serialize(&coin.note.coin_blind),
921                serialize(&coin.note.value_blind),
922                serialize(&coin.note.token_blind),
923                serialize(&coin.secret),
924                serialize(&coin.leaf_position),
925                serialize(&coin.note.memo),
926                creation_height,
927                0, // <-- is_spent
928                spent_height,
929            ];
930
931            if let Err(e) = self.wallet.exec_sql(&query, params) {
932                return Err(Error::DatabaseError(format!(
933                    "[handle_money_call_owncoins] Inserting Money coin failed: {e}"
934                )))
935            }
936        }
937
938        Ok(())
939    }
940
941    /// Auxiliary function to handle freezes from a transaction money
942    /// call.
943    /// Returns a flag indicating if provided freezes refer to our own
944    /// wallet.
945    async fn handle_money_call_freezes(
946        &self,
947        own_tokens: &[TokenId],
948        freezes: &[TokenId],
949        freeze_height: &u32,
950    ) -> Result<bool> {
951        // Check if we have any freezes to process
952        if freezes.is_empty() {
953            return Ok(false)
954        }
955
956        // Find our own tokens that got frozen
957        let mut own_freezes = Vec::with_capacity(freezes.len());
958        for freeze in freezes {
959            if own_tokens.contains(freeze) {
960                own_freezes.push(freeze);
961            }
962        }
963
964        // Check if we need to freeze anything
965        if own_freezes.is_empty() {
966            return Ok(false)
967        }
968
969        // This is the SQL query we'll be executing to update frozen tokens into the wallet
970        let query = format!(
971            "UPDATE {} SET {} = 1, {} = ?1 WHERE {} = ?2;",
972            *MONEY_TOKENS_TABLE,
973            MONEY_TOKENS_COL_IS_FROZEN,
974            MONEY_TOKENS_COL_FREEZE_HEIGHT,
975            MONEY_TOKENS_COL_TOKEN_ID,
976        );
977
978        for token_id in own_freezes {
979            // Grab token record key
980            let key = serialize_async(token_id).await;
981
982            // Execute the query
983            if let Err(e) =
984                self.wallet.exec_sql(&query, rusqlite::params![Some(*freeze_height), key])
985            {
986                return Err(Error::DatabaseError(format!(
987                    "[handle_money_call_freezes] Update Money token freeze failed: {e}"
988                )))
989            }
990        }
991
992        Ok(true)
993    }
994
995    /// Append data related to Money contract transactions into the
996    /// wallet database and update the provided scan cache.
997    /// Returns a flag indicating if provided data refer to our own
998    /// wallet along with the block signing key, if found.
999    pub async fn apply_tx_money_data(
1000        &self,
1001        scan_cache: &mut ScanCache,
1002        call_idx: &usize,
1003        calls: &[DarkLeaf<ContractCall>],
1004        tx_hash: &String,
1005        block_height: &u32,
1006    ) -> Result<(bool, Option<SecretKey>)> {
1007        // Parse the call
1008        let (nullifiers, coins, freezes) =
1009            self.parse_money_call(scan_cache, call_idx, calls).await?;
1010
1011        // Parse call coins and grab our own
1012        let (owncoins, block_signing_key) = self.handle_money_call_coins(
1013            &mut scan_cache.money_tree,
1014            &scan_cache.notes_secrets,
1015            &mut scan_cache.messages_buffer,
1016            &coins,
1017        )?;
1018
1019        // Update nullifiers smt
1020        self.smt_insert(&mut scan_cache.money_smt, &nullifiers)?;
1021
1022        // Check if we have any spent coins
1023        let wallet_spent_coins = self.mark_spent_coins(
1024            Some(&mut scan_cache.money_tree),
1025            &scan_cache.owncoins_nullifiers,
1026            &nullifiers,
1027            &Some(*block_height),
1028            tx_hash,
1029        )?;
1030
1031        // Handle our own coins
1032        self.handle_money_call_owncoins(scan_cache, &owncoins, block_height).await?;
1033
1034        // Handle freezes
1035        let wallet_freezes =
1036            self.handle_money_call_freezes(&scan_cache.own_tokens, &freezes, block_height).await?;
1037
1038        if self.fun && !owncoins.is_empty() {
1039            kaching().await;
1040        }
1041
1042        Ok((wallet_spent_coins || !owncoins.is_empty() || wallet_freezes, block_signing_key))
1043    }
1044
1045    /// Auxiliary function to  grab all the nullifiers from a transaction money call.
1046    async fn money_call_nullifiers(&self, call: &DarkLeaf<ContractCall>) -> Result<Vec<Nullifier>> {
1047        let mut nullifiers: Vec<Nullifier> = vec![];
1048
1049        let data = &call.data.data;
1050        match MoneyFunction::try_from(data[0])? {
1051            MoneyFunction::FeeV1 => {
1052                let params: MoneyFeeParamsV1 = deserialize_async(&data[9..]).await?;
1053                nullifiers.push(params.input.nullifier);
1054            }
1055            MoneyFunction::TransferV1 => {
1056                let params: MoneyTransferParamsV1 = deserialize_async(&data[1..]).await?;
1057
1058                for input in params.inputs {
1059                    nullifiers.push(input.nullifier);
1060                }
1061            }
1062            MoneyFunction::OtcSwapV1 => {
1063                let params: MoneyTransferParamsV1 = deserialize_async(&data[1..]).await?;
1064
1065                for input in params.inputs {
1066                    nullifiers.push(input.nullifier);
1067                }
1068            }
1069            _ => { /* Do nothing */ }
1070        }
1071
1072        Ok(nullifiers)
1073    }
1074
1075    /// Mark provided transaction input coins as spent.
1076    pub async fn mark_tx_spend(&self, tx: &Transaction, output: &mut Vec<String>) -> Result<()> {
1077        // Create a cache of all our own nullifiers
1078        let mut owncoins_nullifiers = BTreeMap::new();
1079        for coin in self.get_coins(true).await? {
1080            owncoins_nullifiers.insert(
1081                coin.0.nullifier().to_bytes(),
1082                (coin.0.coin.to_bytes(), coin.0.leaf_position),
1083            );
1084        }
1085
1086        let tx_hash = tx.hash().to_string();
1087        output.push(format!("[mark_tx_spend] Processing transaction: {tx_hash}"));
1088        for (i, call) in tx.calls.iter().enumerate() {
1089            if call.data.contract_id != *MONEY_CONTRACT_ID {
1090                continue
1091            }
1092
1093            output.push(format!("[mark_tx_spend] Found Money contract in call {i}"));
1094            let nullifiers = self.money_call_nullifiers(call).await?;
1095            self.mark_spent_coins(None, &owncoins_nullifiers, &nullifiers, &None, &tx_hash)?;
1096        }
1097
1098        Ok(())
1099    }
1100
1101    /// Marks all coins in the wallet as spent, if their nullifier is in the given set.
1102    /// Returns a flag indicating if any of the provided nullifiers refer to our own wallet.
1103    pub fn mark_spent_coins(
1104        &self,
1105        mut tree: Option<&mut MerkleTree>,
1106        owncoins_nullifiers: &BTreeMap<[u8; 32], ([u8; 32], Position)>,
1107        nullifiers: &[Nullifier],
1108        spent_height: &Option<u32>,
1109        spent_tx_hash: &String,
1110    ) -> Result<bool> {
1111        if nullifiers.is_empty() {
1112            return Ok(false)
1113        }
1114
1115        // Find our owncoins that where spent
1116        let mut spent_owncoins = Vec::new();
1117        for nullifier in nullifiers {
1118            if let Some(coin) = owncoins_nullifiers.get(&nullifier.to_bytes()) {
1119                spent_owncoins.push(coin);
1120            }
1121        }
1122        if spent_owncoins.is_empty() {
1123            return Ok(false)
1124        }
1125
1126        // Create an SQL `UPDATE` query to mark rows as spent(1)
1127        let query = format!(
1128            "UPDATE {} SET {} = 1, {} = ?1, {} = ?2 WHERE {} = ?3;",
1129            *MONEY_COINS_TABLE,
1130            MONEY_COINS_COL_IS_SPENT,
1131            MONEY_COINS_COL_SPENT_HEIGHT,
1132            MONEY_COINS_COL_SPENT_TX_HASH,
1133            MONEY_COINS_COL_COIN
1134        );
1135
1136        // Mark spent own coins
1137        for (ownoin, leaf_position) in spent_owncoins {
1138            // Execute the query
1139            if let Err(e) =
1140                self.wallet.exec_sql(&query, rusqlite::params![spent_height, spent_tx_hash, ownoin])
1141            {
1142                return Err(Error::DatabaseError(format!(
1143                    "[mark_spent_coins] Marking spent coin failed: {e}"
1144                )))
1145            }
1146
1147            // Remove the coin mark from the Merkle tree
1148            if let Some(ref mut tree) = tree {
1149                tree.remove_mark(*leaf_position);
1150            }
1151        }
1152
1153        Ok(true)
1154    }
1155
1156    /// Inserts given slice to the wallets nullifiers Sparse Merkle Tree.
1157    pub fn smt_insert(&self, smt: &mut CacheSmt, nullifiers: &[Nullifier]) -> Result<()> {
1158        let leaves: Vec<_> = nullifiers.iter().map(|x| (x.inner(), x.inner())).collect();
1159        Ok(smt.insert_batch(leaves)?)
1160    }
1161
1162    /// Reset the Money Merkle tree in the cache.
1163    pub fn reset_money_tree(&self, output: &mut Vec<String>) -> WalletDbResult<()> {
1164        output.push(String::from("Resetting Money Merkle tree"));
1165        if let Err(e) = self.cache.merkle_trees.remove(SLED_MERKLE_TREES_MONEY) {
1166            output.push(format!("[reset_money_tree] Resetting Money Merkle tree failed: {e}"));
1167            return Err(WalletDbError::GenericError)
1168        }
1169        output.push(String::from("Successfully reset Money Merkle tree"));
1170
1171        Ok(())
1172    }
1173
1174    /// Reset the Money nullifiers Sparse Merkle Tree in the cache.
1175    pub fn reset_money_smt(&self, output: &mut Vec<String>) -> WalletDbResult<()> {
1176        output.push(String::from("Resetting Money Sparse Merkle tree"));
1177        if let Err(e) = self.cache.money_smt.clear() {
1178            output
1179                .push(format!("[reset_money_smt] Resetting Money Sparse Merkle tree failed: {e}"));
1180            return Err(WalletDbError::GenericError)
1181        }
1182        output.push(String::from("Successfully reset Money Sparse Merkle tree"));
1183
1184        Ok(())
1185    }
1186
1187    /// Reset the Money coins in the wallet.
1188    pub fn reset_money_coins(&self, output: &mut Vec<String>) -> WalletDbResult<()> {
1189        output.push(String::from("Resetting coins"));
1190        let query = format!("DELETE FROM {};", *MONEY_COINS_TABLE);
1191        self.wallet.exec_sql(&query, &[])?;
1192        output.push(String::from("Successfully reset coins"));
1193
1194        Ok(())
1195    }
1196
1197    /// Remove the Money coins in the wallet that were created after
1198    /// provided height.
1199    pub fn remove_money_coins_after(
1200        &self,
1201        height: &u32,
1202        output: &mut Vec<String>,
1203    ) -> WalletDbResult<()> {
1204        output.push(format!("Removing coins after: {height}"));
1205        let query = format!(
1206            "DELETE FROM {} WHERE {} > ?1;",
1207            *MONEY_COINS_TABLE, MONEY_COINS_COL_CREATION_HEIGHT
1208        );
1209        self.wallet.exec_sql(&query, rusqlite::params![height])?;
1210        output.push(String::from("Successfully removed coins"));
1211
1212        Ok(())
1213    }
1214
1215    /// Mark the Money coins in the wallet that were spent after
1216    /// provided height as unspent.
1217    pub fn unspent_money_coins_after(
1218        &self,
1219        height: &u32,
1220        output: &mut Vec<String>,
1221    ) -> WalletDbResult<()> {
1222        output.push(format!("Unspenting coins after: {height}"));
1223        let query = format!(
1224            "UPDATE {} SET {} = 0, {} = NULL, {} = '=' WHERE {} > ?1;",
1225            *MONEY_COINS_TABLE,
1226            MONEY_COINS_COL_IS_SPENT,
1227            MONEY_COINS_COL_SPENT_HEIGHT,
1228            MONEY_COINS_COL_SPENT_TX_HASH,
1229            MONEY_COINS_COL_SPENT_HEIGHT
1230        );
1231        self.wallet.exec_sql(&query, rusqlite::params![Some(*height)])?;
1232        output.push(String::from("Successfully unspent coins"));
1233
1234        Ok(())
1235    }
1236
1237    /// Retrieve token by provided string.
1238    /// Input string represents either an alias or a token id.
1239    pub async fn get_token(&self, input: String) -> Result<TokenId> {
1240        // Check if input is an alias(max 5 characters)
1241        if input.chars().count() <= 5 {
1242            let aliases = self.get_aliases(Some(input.clone()), None).await?;
1243            if let Some(token_id) = aliases.get(&input) {
1244                return Ok(*token_id)
1245            }
1246        }
1247        // Else parse input
1248        Ok(TokenId::from_str(input.as_str())?)
1249    }
1250
1251    /// Create and append a `Money::Fee` call to a given [`Transaction`].
1252    ///
1253    /// Optionally takes a set of spent coins in order not to reuse them here.
1254    ///
1255    /// Returns the `Fee` call, and all necessary data and parameters related.
1256    pub async fn append_fee_call(
1257        &self,
1258        tx: &Transaction,
1259        money_merkle_tree: &MerkleTree,
1260        fee_pk: &ProvingKey,
1261        fee_zkbin: &ZkBinary,
1262        spent_coins: Option<&[OwnCoin]>,
1263    ) -> Result<(ContractCall, Vec<Proof>, Vec<SecretKey>)> {
1264        // First we verify the fee-less transaction to see how much fee it requires for execution
1265        // and verification.
1266        let required_fee = compute_fee(&FEE_CALL_GAS) + self.get_tx_fee(tx, false).await?;
1267
1268        // Knowing the total gas, we can now find an OwnCoin of enough value
1269        // so that we can create a valid Money::Fee call.
1270        let mut available_coins = self.get_token_coins(&DARK_TOKEN_ID).await?;
1271        available_coins.retain(|x| x.note.value > required_fee);
1272        if let Some(spent_coins) = spent_coins {
1273            available_coins.retain(|x| !spent_coins.contains(x));
1274        }
1275        if available_coins.is_empty() {
1276            return Err(Error::Custom("Not enough native tokens to pay for fees".to_string()))
1277        }
1278
1279        let coin = &available_coins[0];
1280        let change_value = coin.note.value - required_fee;
1281
1282        // Input and output setup
1283        let input = FeeCallInput {
1284            coin: coin.clone(),
1285            merkle_path: money_merkle_tree.witness(coin.leaf_position, 0).unwrap(),
1286            user_data_blind: BaseBlind::random(&mut OsRng),
1287        };
1288
1289        let output = FeeCallOutput {
1290            public_key: PublicKey::from_secret(coin.secret),
1291            value: change_value,
1292            token_id: coin.note.token_id,
1293            blind: BaseBlind::random(&mut OsRng),
1294            spend_hook: FuncId::none(),
1295            user_data: pallas::Base::ZERO,
1296        };
1297
1298        // Create blinding factors
1299        let token_blind = BaseBlind::random(&mut OsRng);
1300        let input_value_blind = ScalarBlind::random(&mut OsRng);
1301        let fee_value_blind = ScalarBlind::random(&mut OsRng);
1302        let output_value_blind = compute_remainder_blind(&[input_value_blind], &[fee_value_blind]);
1303
1304        // Create an ephemeral signing key
1305        let signature_secret = SecretKey::random(&mut OsRng);
1306
1307        // Create the actual fee proof
1308        let (proof, public_inputs) = create_fee_proof(
1309            fee_zkbin,
1310            fee_pk,
1311            &input,
1312            input_value_blind,
1313            &output,
1314            output_value_blind,
1315            output.spend_hook,
1316            output.user_data,
1317            output.blind,
1318            token_blind,
1319            signature_secret,
1320        )?;
1321
1322        // Encrypted note for the output
1323        let note = MoneyNote {
1324            coin_blind: output.blind,
1325            value: output.value,
1326            token_id: output.token_id,
1327            spend_hook: output.spend_hook,
1328            user_data: output.user_data,
1329            value_blind: output_value_blind,
1330            token_blind,
1331            memo: vec![],
1332        };
1333
1334        let encrypted_note = AeadEncryptedNote::encrypt(&note, &output.public_key, &mut OsRng)?;
1335
1336        let params = MoneyFeeParamsV1 {
1337            input: Input {
1338                value_commit: public_inputs.input_value_commit,
1339                token_commit: public_inputs.token_commit,
1340                nullifier: public_inputs.nullifier,
1341                merkle_root: public_inputs.merkle_root,
1342                user_data_enc: public_inputs.input_user_data_enc,
1343                signature_public: public_inputs.signature_public,
1344            },
1345            output: Output {
1346                value_commit: public_inputs.output_value_commit,
1347                token_commit: public_inputs.token_commit,
1348                coin: public_inputs.output_coin,
1349                note: encrypted_note,
1350            },
1351            fee_value_blind,
1352            token_blind,
1353        };
1354
1355        // Encode the contract call
1356        let mut data = vec![MoneyFunction::FeeV1 as u8];
1357        required_fee.encode_async(&mut data).await?;
1358        params.encode_async(&mut data).await?;
1359        let call = ContractCall { contract_id: *MONEY_CONTRACT_ID, data };
1360
1361        Ok((call, vec![proof], vec![signature_secret]))
1362    }
1363
1364    /// Create and attach the fee call to given transaction.
1365    pub async fn attach_fee(&self, tx: &mut Transaction) -> Result<()> {
1366        // Grab spent coins nullifiers of the transactions and check no other fee call exists
1367        let mut tx_nullifiers = vec![];
1368        for call in &tx.calls {
1369            if call.data.contract_id != *MONEY_CONTRACT_ID {
1370                continue
1371            }
1372
1373            match MoneyFunction::try_from(call.data.data[0])? {
1374                MoneyFunction::FeeV1 => {
1375                    return Err(Error::Custom("Fee call already exists".to_string()))
1376                }
1377                _ => { /* Do nothing */ }
1378            }
1379
1380            let nullifiers = self.money_call_nullifiers(call).await?;
1381            tx_nullifiers.extend_from_slice(&nullifiers);
1382        }
1383
1384        // Grab all native owncoins to check if any is spent
1385        let mut spent_coins = vec![];
1386        let available_coins = self.get_token_coins(&DARK_TOKEN_ID).await?;
1387        for coin in available_coins {
1388            if tx_nullifiers.contains(&coin.nullifier()) {
1389                spent_coins.push(coin);
1390            }
1391        }
1392
1393        // Now we need to do a lookup for the zkas proof bincodes, and create
1394        // the circuit objects and proving keys so we can build the transaction.
1395        // We also do this through the RPC.
1396        let zkas_bins = self.lookup_zkas(&MONEY_CONTRACT_ID).await?;
1397
1398        let Some(fee_zkbin) = zkas_bins.iter().find(|x| x.0 == MONEY_CONTRACT_ZKAS_FEE_NS_V1)
1399        else {
1400            return Err(Error::Custom("Fee circuit not found".to_string()))
1401        };
1402
1403        let fee_zkbin = ZkBinary::decode(&fee_zkbin.1, false)?;
1404
1405        let fee_circuit = ZkCircuit::new(empty_witnesses(&fee_zkbin)?, &fee_zkbin);
1406
1407        // Creating Fee circuits proving keys
1408        let fee_pk = ProvingKey::build(fee_zkbin.k, &fee_circuit);
1409
1410        // We first have to execute the fee-less tx to gather its used gas, and then we feed
1411        // it into the fee-creating function.
1412        let tree = self.get_money_tree().await?;
1413        let (fee_call, fee_proofs, fee_secrets) =
1414            self.append_fee_call(tx, &tree, &fee_pk, &fee_zkbin, Some(&spent_coins)).await?;
1415
1416        // Append the fee call to the transaction
1417        tx.calls.push(DarkLeaf { data: fee_call, parent_index: None, children_indexes: vec![] });
1418        tx.proofs.push(fee_proofs);
1419        let sigs = tx.create_sigs(&fee_secrets)?;
1420        tx.signatures.push(sigs);
1421
1422        Ok(())
1423    }
1424}