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        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
295        let Value::Blob(ref key_bytes) = row[0] else {
296            return Err(Error::ParseFailed("[mining_address] Key bytes parsing failed"))
297        };
298        let public_key: PublicKey = deserialize_async(key_bytes).await?;
299        let address: Address = StandardAddress::from_public(self.network, public_key).into();
300        let recipient = address.to_string();
301
302        let spend_hook = spend_hook.as_ref().map(|spend_hook| spend_hook.to_string());
303
304        let user_data =
305            user_data.as_ref().map(|user_data| bs58::encode(user_data.to_repr()).into_string());
306
307        output.push(String::from("DarkFi TOML configuration:"));
308        output.push(format!("recipient = \"{recipient}\""));
309        match spend_hook {
310            Some(ref spend_hook) => output.push(format!("spend_hook = \"{spend_hook}\"")),
311            None => output.push(String::from("#spend_hook = \"\"")),
312        }
313        match user_data {
314            Some(ref user_data) => output.push(format!("user_data = \"{user_data}\"")),
315            None => output.push(String::from("#user_data = \"\"")),
316        }
317        output.push(String::from("\nP2Pool wallet address to use:"));
318        output.push(base64::encode(&serialize(&(recipient, spend_hook, user_data))).to_string());
319
320        Ok(())
321    }
322
323    /// Fetch all secret keys from the wallet.
324    pub async fn get_money_secrets(&self) -> Result<Vec<SecretKey>> {
325        let rows =
326            match self.wallet.query_multiple(&MONEY_KEYS_TABLE, &[MONEY_KEYS_COL_SECRET], &[]) {
327                Ok(r) => r,
328                Err(e) => {
329                    return Err(Error::DatabaseError(format!(
330                        "[get_money_secrets] Secret keys retrieval failed: {e}"
331                    )))
332                }
333            };
334
335        let mut secrets = Vec::with_capacity(rows.len());
336
337        // Let's scan through the rows and see if we got anything.
338        for row in rows {
339            let Value::Blob(ref key_bytes) = row[0] else {
340                return Err(Error::ParseFailed(
341                    "[get_money_secrets] Secret key bytes parsing failed",
342                ))
343            };
344            let secret_key: SecretKey = deserialize_async(key_bytes).await?;
345            secrets.push(secret_key);
346        }
347
348        Ok(secrets)
349    }
350
351    /// Import given secret keys into the wallet.
352    /// If the key already exists, it will be skipped.
353    /// Returns the respective PublicKey objects for the imported keys.
354    pub async fn import_money_secrets(
355        &self,
356        secrets: Vec<SecretKey>,
357        output: &mut Vec<String>,
358    ) -> Result<Vec<PublicKey>> {
359        let existing_secrets = self.get_money_secrets().await?;
360
361        let mut ret = Vec::with_capacity(secrets.len());
362
363        for secret in secrets {
364            // Check if secret already exists
365            if existing_secrets.contains(&secret) {
366                output.push(format!("Existing key found: {secret}"));
367                continue
368            }
369
370            ret.push(PublicKey::from_secret(secret));
371            let is_default = 0;
372            let public = serialize_async(&PublicKey::from_secret(secret)).await;
373            let secret = serialize_async(&secret).await;
374
375            let query = format!(
376                "INSERT INTO {} ({}, {}, {}) VALUES (?1, ?2, ?3);",
377                *MONEY_KEYS_TABLE,
378                MONEY_KEYS_COL_IS_DEFAULT,
379                MONEY_KEYS_COL_PUBLIC,
380                MONEY_KEYS_COL_SECRET
381            );
382            if let Err(e) =
383                self.wallet.exec_sql(&query, rusqlite::params![is_default, public, secret])
384            {
385                return Err(Error::DatabaseError(format!(
386                    "[import_money_secrets] Inserting new address failed: {e}"
387                )))
388            }
389        }
390
391        Ok(ret)
392    }
393
394    /// Fetch known unspent balances from the wallet and return them as a hashmap.
395    pub async fn money_balance(&self) -> Result<HashMap<String, u64>> {
396        let mut coins = self.get_coins(false).await?;
397        coins.retain(|x| x.0.note.spend_hook == FuncId::none());
398
399        // Fill this map with balances
400        let mut balmap: HashMap<String, u64> = HashMap::new();
401
402        for coin in coins {
403            let mut value = coin.0.note.value;
404
405            if let Some(prev) = balmap.get(&coin.0.note.token_id.to_string()) {
406                value += prev;
407            }
408
409            balmap.insert(coin.0.note.token_id.to_string(), value);
410        }
411
412        Ok(balmap)
413    }
414
415    /// Fetch all coins and their metadata related to the Money contract from the wallet.
416    /// Optionally also fetch spent ones.
417    /// The boolean in the returned tuple notes if the coin was marked
418    /// as spent, along with the height and tx it was spent in.
419    pub async fn get_coins(
420        &self,
421        fetch_spent: bool,
422    ) -> Result<Vec<(OwnCoin, u32, bool, Option<u32>, String)>> {
423        let query = if fetch_spent {
424            self.wallet.query_multiple(&MONEY_COINS_TABLE, &[], &[])
425        } else {
426            self.wallet.query_multiple(
427                &MONEY_COINS_TABLE,
428                &[],
429                convert_named_params! {(MONEY_COINS_COL_IS_SPENT, false)},
430            )
431        };
432
433        let rows = match query {
434            Ok(r) => r,
435            Err(e) => {
436                return Err(Error::DatabaseError(format!("[get_coins] Coins retrieval failed: {e}")))
437            }
438        };
439
440        let mut owncoins = Vec::with_capacity(rows.len());
441        for row in rows {
442            owncoins.push(self.parse_coin_record(&row).await?)
443        }
444
445        Ok(owncoins)
446    }
447
448    /// Fetch provided token unspend balances from the wallet.
449    pub async fn get_token_coins(&self, token_id: &TokenId) -> Result<Vec<OwnCoin>> {
450        let query = self.wallet.query_multiple(
451            &MONEY_COINS_TABLE,
452            &[],
453            convert_named_params! {
454                (MONEY_COINS_COL_TOKEN_ID, serialize_async(token_id).await),
455                (MONEY_COINS_COL_SPEND_HOOK, serialize_async(&FuncId::none()).await),
456                (MONEY_COINS_COL_IS_SPENT, false),
457            },
458        );
459
460        let rows = match query {
461            Ok(r) => r,
462            Err(e) => {
463                return Err(Error::DatabaseError(format!(
464                    "[get_token_coins] Coins retrieval failed: {e}"
465                )))
466            }
467        };
468
469        let mut owncoins = Vec::with_capacity(rows.len());
470        for row in rows {
471            owncoins.push(self.parse_coin_record(&row).await?.0)
472        }
473
474        Ok(owncoins)
475    }
476
477    /// Fetch provided contract specified token unspend balances from the wallet.
478    pub async fn get_contract_token_coins(
479        &self,
480        token_id: &TokenId,
481        spend_hook: &FuncId,
482        user_data: &pallas::Base,
483    ) -> Result<Vec<OwnCoin>> {
484        let query = self.wallet.query_multiple(
485            &MONEY_COINS_TABLE,
486            &[],
487            convert_named_params! {
488                (MONEY_COINS_COL_TOKEN_ID, serialize_async(token_id).await),
489                (MONEY_COINS_COL_SPEND_HOOK, serialize_async(spend_hook).await),
490                (MONEY_COINS_COL_USER_DATA, serialize_async(user_data).await),
491                (MONEY_COINS_COL_IS_SPENT, false),
492            },
493        );
494
495        let rows = match query {
496            Ok(r) => r,
497            Err(e) => {
498                return Err(Error::DatabaseError(format!(
499                    "[get_contract_token_coins] Coins retrieval failed: {e}"
500                )))
501            }
502        };
503
504        let mut owncoins = Vec::with_capacity(rows.len());
505        for row in rows {
506            owncoins.push(self.parse_coin_record(&row).await?.0)
507        }
508
509        Ok(owncoins)
510    }
511
512    /// Auxiliary function to parse a `MONEY_COINS_TABLE` record.
513    /// The boolean in the returned tuple notes if the coin was marked
514    /// as spent, along with the height and tx it was spent in.
515    async fn parse_coin_record(
516        &self,
517        row: &[Value],
518    ) -> Result<(OwnCoin, u32, bool, Option<u32>, String)> {
519        let Value::Blob(ref coin_bytes) = row[0] else {
520            return Err(Error::ParseFailed("[parse_coin_record] Coin bytes parsing failed"))
521        };
522        let coin: Coin = deserialize_async(coin_bytes).await?;
523
524        let Value::Blob(ref value_bytes) = row[1] else {
525            return Err(Error::ParseFailed("[parse_coin_record] Value bytes parsing failed"))
526        };
527        let value: u64 = deserialize_async(value_bytes).await?;
528
529        let Value::Blob(ref token_id_bytes) = row[2] else {
530            return Err(Error::ParseFailed("[parse_coin_record] Token ID bytes parsing failed"))
531        };
532        let token_id: TokenId = deserialize_async(token_id_bytes).await?;
533
534        let Value::Blob(ref spend_hook_bytes) = row[3] else {
535            return Err(Error::ParseFailed("[parse_coin_record] Spend hook bytes parsing failed"))
536        };
537        let spend_hook: pallas::Base = deserialize_async(spend_hook_bytes).await?;
538
539        let Value::Blob(ref user_data_bytes) = row[4] else {
540            return Err(Error::ParseFailed("[parse_coin_record] User data bytes parsing failed"))
541        };
542        let user_data: pallas::Base = deserialize_async(user_data_bytes).await?;
543
544        let Value::Blob(ref coin_blind_bytes) = row[5] else {
545            return Err(Error::ParseFailed("[parse_coin_record] Coin blind bytes parsing failed"))
546        };
547        let coin_blind: BaseBlind = deserialize_async(coin_blind_bytes).await?;
548
549        let Value::Blob(ref value_blind_bytes) = row[6] else {
550            return Err(Error::ParseFailed("[parse_coin_record] Value blind bytes parsing failed"))
551        };
552        let value_blind: ScalarBlind = deserialize_async(value_blind_bytes).await?;
553
554        let Value::Blob(ref token_blind_bytes) = row[7] else {
555            return Err(Error::ParseFailed("[parse_coin_record] Token blind bytes parsing failed"))
556        };
557        let token_blind: BaseBlind = deserialize_async(token_blind_bytes).await?;
558
559        let Value::Blob(ref secret_bytes) = row[8] else {
560            return Err(Error::ParseFailed("[parse_coin_record] Secret bytes parsing failed"))
561        };
562        let secret: SecretKey = deserialize_async(secret_bytes).await?;
563
564        let Value::Blob(ref leaf_position_bytes) = row[9] else {
565            return Err(Error::ParseFailed("[parse_coin_record] Leaf position bytes parsing failed"))
566        };
567        let leaf_position: Position = deserialize_async(leaf_position_bytes).await?;
568
569        let Value::Blob(ref memo) = row[10] else {
570            return Err(Error::ParseFailed("[parse_coin_record] Memo parsing failed"))
571        };
572
573        let Value::Integer(creation_height) = row[11] else {
574            return Err(Error::ParseFailed("[parse_coin_record] Creation height parsing failed"))
575        };
576        let Ok(creation_height) = u32::try_from(creation_height) else {
577            return Err(Error::ParseFailed("[parse_coin_record] Creation height parsing failed"))
578        };
579
580        let Value::Integer(is_spent) = row[12] else {
581            return Err(Error::ParseFailed("[parse_coin_record] Is spent parsing failed"))
582        };
583        let Ok(is_spent) = u64::try_from(is_spent) else {
584            return Err(Error::ParseFailed("[parse_coin_record] Is spent parsing failed"))
585        };
586        let is_spent = is_spent > 0;
587
588        let spent_height = match row[13] {
589            Value::Integer(spent_height) => {
590                let Ok(spent_height) = u32::try_from(spent_height) else {
591                    return Err(Error::ParseFailed(
592                        "[parse_coin_record] Spent height parsing failed",
593                    ))
594                };
595                Some(spent_height)
596            }
597            Value::Null => None,
598            _ => return Err(Error::ParseFailed("[parse_coin_record] Spent height parsing failed")),
599        };
600
601        let Value::Text(ref spent_tx_hash) = row[14] else {
602            return Err(Error::ParseFailed(
603                "[parse_coin_record] Spent transaction hash parsing failed",
604            ))
605        };
606
607        let note = MoneyNote {
608            value,
609            token_id,
610            spend_hook: spend_hook.into(),
611            user_data,
612            coin_blind,
613            value_blind,
614            token_blind,
615            memo: memo.clone(),
616        };
617
618        Ok((
619            OwnCoin { coin, note, secret, leaf_position },
620            creation_height,
621            is_spent,
622            spent_height,
623            spent_tx_hash.clone(),
624        ))
625    }
626
627    /// Create an alias record for provided Token ID.
628    pub async fn add_alias(
629        &self,
630        alias: String,
631        token_id: TokenId,
632        output: &mut Vec<String>,
633    ) -> WalletDbResult<()> {
634        output.push(format!("Generating alias {alias} for Token: {token_id}"));
635        let query = format!(
636            "INSERT OR REPLACE INTO {} ({}, {}) VALUES (?1, ?2);",
637            *MONEY_ALIASES_TABLE, MONEY_ALIASES_COL_ALIAS, MONEY_ALIASES_COL_TOKEN_ID,
638        );
639        self.wallet.exec_sql(
640            &query,
641            rusqlite::params![serialize_async(&alias).await, serialize_async(&token_id).await],
642        )
643    }
644
645    /// Fetch all aliases from the wallet.
646    /// Optionally filter using alias name and/or token id.
647    pub async fn get_aliases(
648        &self,
649        alias_filter: Option<String>,
650        token_id_filter: Option<TokenId>,
651    ) -> Result<HashMap<String, TokenId>> {
652        let rows = match self.wallet.query_multiple(&MONEY_ALIASES_TABLE, &[], &[]) {
653            Ok(r) => r,
654            Err(e) => {
655                return Err(Error::DatabaseError(format!(
656                    "[get_aliases] Aliases retrieval failed: {e}"
657                )))
658            }
659        };
660
661        // Fill this map with aliases
662        let mut map: HashMap<String, TokenId> = HashMap::new();
663        for row in rows {
664            let Value::Blob(ref alias_bytes) = row[0] else {
665                return Err(Error::ParseFailed("[get_aliases] Alias bytes parsing failed"))
666            };
667            let alias: String = deserialize_async(alias_bytes).await?;
668            if alias_filter.is_some() && alias_filter.as_ref().unwrap() != &alias {
669                continue
670            }
671
672            let Value::Blob(ref token_id_bytes) = row[1] else {
673                return Err(Error::ParseFailed("[get_aliases] TokenId bytes parsing failed"))
674            };
675            let token_id: TokenId = deserialize_async(token_id_bytes).await?;
676            if token_id_filter.is_some() && token_id_filter.as_ref().unwrap() != &token_id {
677                continue
678            }
679
680            map.insert(alias, token_id);
681        }
682
683        Ok(map)
684    }
685
686    /// Fetch all aliases from the wallet, mapped by token id.
687    pub async fn get_aliases_mapped_by_token(&self) -> Result<HashMap<String, String>> {
688        let aliases = self.get_aliases(None, None).await?;
689        let mut map: HashMap<String, String> = HashMap::new();
690        for (alias, token_id) in aliases {
691            let aliases_string = if let Some(prev) = map.get(&token_id.to_string()) {
692                format!("{prev}, {alias}")
693            } else {
694                alias
695            };
696
697            map.insert(token_id.to_string(), aliases_string);
698        }
699
700        Ok(map)
701    }
702
703    /// Remove provided alias record from the wallet database.
704    pub async fn remove_alias(
705        &self,
706        alias: String,
707        output: &mut Vec<String>,
708    ) -> WalletDbResult<()> {
709        output.push(format!("Removing alias: {alias}"));
710        let query = format!(
711            "DELETE FROM {} WHERE {} = ?1;",
712            *MONEY_ALIASES_TABLE, MONEY_ALIASES_COL_ALIAS,
713        );
714        self.wallet.exec_sql(&query, rusqlite::params![serialize_async(&alias).await])
715    }
716
717    /// Mark a given coin in the wallet as unspent.
718    pub async fn unspend_coin(&self, coin: &Coin) -> WalletDbResult<()> {
719        let query = format!(
720            "UPDATE {} SET {} = 0, {} = NULL, {} = '-' WHERE {} = ?1;",
721            *MONEY_COINS_TABLE,
722            MONEY_COINS_COL_IS_SPENT,
723            MONEY_COINS_COL_SPENT_HEIGHT,
724            MONEY_COINS_COL_SPENT_TX_HASH,
725            MONEY_COINS_COL_COIN
726        );
727        self.wallet.exec_sql(&query, rusqlite::params![serialize_async(&coin.inner()).await])
728    }
729
730    /// Fetch the Money Merkle tree from the cache.
731    /// If it doesn't exists a new Merkle Tree is returned.
732    pub async fn get_money_tree(&self) -> Result<MerkleTree> {
733        match self.cache.merkle_trees.get(SLED_MERKLE_TREES_MONEY)? {
734            Some(tree_bytes) => Ok(deserialize_async(&tree_bytes).await?),
735            None => {
736                let mut tree = MerkleTree::new(u32::MAX as usize);
737                tree.append(MerkleNode::from(pallas::Base::ZERO));
738                let _ = tree.mark().unwrap();
739                Ok(tree)
740            }
741        }
742    }
743
744    /// Auxiliary function to grab all the nullifiers, coins with their
745    /// notes and a flag indicating if its a block reward, and freezes
746    /// from a transaction money call.
747    async fn parse_money_call(
748        &self,
749        scan_cache: &mut ScanCache,
750        call_idx: &usize,
751        calls: &[DarkLeaf<ContractCall>],
752    ) -> Result<(Vec<Nullifier>, Vec<(Coin, AeadEncryptedNote, bool)>, Vec<TokenId>)> {
753        let mut nullifiers: Vec<Nullifier> = vec![];
754        let mut coins: Vec<(Coin, AeadEncryptedNote, bool)> = vec![];
755        let mut freezes: Vec<TokenId> = vec![];
756
757        let call = &calls[*call_idx];
758        let data = &call.data.data;
759        match MoneyFunction::try_from(data[0])? {
760            MoneyFunction::FeeV1 => {
761                scan_cache.log(String::from("[parse_money_call] Found Money::FeeV1 call"));
762                let params: MoneyFeeParamsV1 = deserialize_async(&data[9..]).await?;
763                nullifiers.push(params.input.nullifier);
764                coins.push((params.output.coin, params.output.note, false));
765            }
766            MoneyFunction::GenesisMintV1 => {
767                scan_cache.log(String::from("[parse_money_call] Found Money::GenesisMintV1 call"));
768                let params: MoneyGenesisMintParamsV1 = deserialize_async(&data[1..]).await?;
769                for output in params.outputs {
770                    coins.push((output.coin, output.note, false));
771                }
772            }
773            MoneyFunction::PoWRewardV1 => {
774                scan_cache.log(String::from("[parse_money_call] Found Money::PoWRewardV1 call"));
775                let params: MoneyPoWRewardParamsV1 = deserialize_async(&data[1..]).await?;
776                coins.push((params.output.coin, params.output.note, true));
777            }
778            MoneyFunction::TransferV1 => {
779                scan_cache.log(String::from("[parse_money_call] Found Money::TransferV1 call"));
780                let params: MoneyTransferParamsV1 = deserialize_async(&data[1..]).await?;
781
782                for input in params.inputs {
783                    nullifiers.push(input.nullifier);
784                }
785
786                for output in params.outputs {
787                    coins.push((output.coin, output.note, false));
788                }
789            }
790            MoneyFunction::OtcSwapV1 => {
791                scan_cache.log(String::from("[parse_money_call] Found Money::OtcSwapV1 call"));
792                let params: MoneyTransferParamsV1 = deserialize_async(&data[1..]).await?;
793
794                for input in params.inputs {
795                    nullifiers.push(input.nullifier);
796                }
797
798                for output in params.outputs {
799                    coins.push((output.coin, output.note, false));
800                }
801            }
802            MoneyFunction::AuthTokenMintV1 => {
803                scan_cache
804                    .log(String::from("[parse_money_call] Found Money::AuthTokenMintV1 call"));
805                // Handled in TokenMint
806            }
807            MoneyFunction::AuthTokenFreezeV1 => {
808                scan_cache
809                    .log(String::from("[parse_money_call] Found Money::AuthTokenFreezeV1 call"));
810                let params: MoneyAuthTokenFreezeParamsV1 = deserialize_async(&data[1..]).await?;
811                freezes.push(params.token_id);
812            }
813            MoneyFunction::TokenMintV1 => {
814                scan_cache.log(String::from("[parse_money_call] Found Money::TokenMintV1 call"));
815                let params: MoneyTokenMintParamsV1 = deserialize_async(&data[1..]).await?;
816                // Grab the note from the child auth call
817                let child_idx = call.children_indexes[0];
818                let child_call = &calls[child_idx];
819                let child_params: MoneyAuthTokenMintParamsV1 =
820                    deserialize_async(&child_call.data.data[1..]).await?;
821                coins.push((params.coin, child_params.enc_note, false));
822            }
823        }
824
825        Ok((nullifiers, coins, freezes))
826    }
827
828    /// Auxiliary function to handle coins with their notes and flag
829    /// indicating if its a block reward from a transaction money call.
830    /// Returns our found own coins along with the block signing key,
831    /// if found.
832    fn handle_money_call_coins(
833        &self,
834        tree: &mut MerkleTree,
835        secrets: &[SecretKey],
836        messages_buffer: &mut Vec<String>,
837        coins: &[(Coin, AeadEncryptedNote, bool)],
838    ) -> Result<(Vec<OwnCoin>, Option<SecretKey>)> {
839        // Keep track of our own coins found in the vec
840        let mut owncoins = vec![];
841
842        // Check if provided coins vec is empty
843        if coins.is_empty() {
844            return Ok((owncoins, None))
845        }
846
847        // Handle provided coins vector and grab our own,
848        // along with the block signing key if its a block
849        // reward coin. Only one reward call and coin exists
850        // in each block.
851        let mut block_signing_key = None;
852        for (coin, note, is_block_reward) in coins {
853            // Append the new coin to the Merkle tree.
854            // Every coin has to be added.
855            tree.append(MerkleNode::from(coin.inner()));
856
857            // Attempt to decrypt the note
858            for secret in secrets {
859                let Ok(note) = note.decrypt::<MoneyNote>(secret) else { continue };
860                messages_buffer.push(String::from(
861                    "[handle_money_call_coins] Successfully decrypted a Money Note",
862                ));
863                messages_buffer
864                    .push(String::from("[handle_money_call_coins] Witnessing coin in Merkle tree"));
865                let leaf_position = tree.mark().unwrap();
866                if *is_block_reward {
867                    messages_buffer
868                        .push(String::from("[handle_money_call_coins] Grabing block signing key"));
869                    block_signing_key = Some(deserialize(&note.memo)?);
870                }
871                let owncoin = OwnCoin { coin: *coin, note, secret: *secret, leaf_position };
872                owncoins.push(owncoin);
873                break
874            }
875        }
876
877        Ok((owncoins, block_signing_key))
878    }
879
880    /// Auxiliary function to handle own coins from a transaction money
881    /// call.
882    async fn handle_money_call_owncoins(
883        &self,
884        scan_cache: &mut ScanCache,
885        coins: &[OwnCoin],
886        creation_height: &u32,
887    ) -> Result<()> {
888        scan_cache.log(format!("Found {} OwnCoin(s) in transaction", coins.len()));
889
890        // Check if we have any owncoins to process
891        if coins.is_empty() {
892            return Ok(())
893        }
894
895        // This is the SQL query we'll be executing to insert new coins into the wallet
896        let query = format!(
897            "INSERT INTO {} ({}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}) VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11, ?12, ?13, ?14);",
898            *MONEY_COINS_TABLE,
899            MONEY_COINS_COL_COIN,
900            MONEY_COINS_COL_VALUE,
901            MONEY_COINS_COL_TOKEN_ID,
902            MONEY_COINS_COL_SPEND_HOOK,
903            MONEY_COINS_COL_USER_DATA,
904            MONEY_COINS_COL_COIN_BLIND,
905            MONEY_COINS_COL_VALUE_BLIND,
906            MONEY_COINS_COL_TOKEN_BLIND,
907            MONEY_COINS_COL_SECRET,
908            MONEY_COINS_COL_LEAF_POSITION,
909            MONEY_COINS_COL_MEMO,
910            MONEY_COINS_COL_CREATION_HEIGHT,
911            MONEY_COINS_COL_IS_SPENT,
912            MONEY_COINS_COL_SPENT_HEIGHT,
913        );
914
915        // Handle our own coins
916        let spent_height: Option<u32> = None;
917        for coin in coins {
918            scan_cache.log(format!("OwnCoin: {:?}", coin.coin));
919            // Grab coin record key
920            let key = coin.coin.to_bytes();
921
922            // Push to our own coins nullifiers cache
923            scan_cache
924                .owncoins_nullifiers
925                .insert(coin.nullifier().to_bytes(), (key, coin.leaf_position));
926
927            // Execute the query
928            let params = rusqlite::params![
929                key,
930                serialize(&coin.note.value),
931                serialize(&coin.note.token_id),
932                serialize(&coin.note.spend_hook),
933                serialize(&coin.note.user_data),
934                serialize(&coin.note.coin_blind),
935                serialize(&coin.note.value_blind),
936                serialize(&coin.note.token_blind),
937                serialize(&coin.secret),
938                serialize(&coin.leaf_position),
939                serialize(&coin.note.memo),
940                creation_height,
941                0, // <-- is_spent
942                spent_height,
943            ];
944
945            if let Err(e) = self.wallet.exec_sql(&query, params) {
946                return Err(Error::DatabaseError(format!(
947                    "[handle_money_call_owncoins] Inserting Money coin failed: {e}"
948                )))
949            }
950        }
951
952        Ok(())
953    }
954
955    /// Auxiliary function to handle freezes from a transaction money
956    /// call.
957    /// Returns a flag indicating if provided freezes refer to our own
958    /// wallet.
959    async fn handle_money_call_freezes(
960        &self,
961        own_tokens: &[TokenId],
962        freezes: &[TokenId],
963        freeze_height: &u32,
964    ) -> Result<bool> {
965        // Check if we have any freezes to process
966        if freezes.is_empty() {
967            return Ok(false)
968        }
969
970        // Find our own tokens that got frozen
971        let mut own_freezes = Vec::with_capacity(freezes.len());
972        for freeze in freezes {
973            if own_tokens.contains(freeze) {
974                own_freezes.push(freeze);
975            }
976        }
977
978        // Check if we need to freeze anything
979        if own_freezes.is_empty() {
980            return Ok(false)
981        }
982
983        // This is the SQL query we'll be executing to update frozen tokens into the wallet
984        let query = format!(
985            "UPDATE {} SET {} = 1, {} = ?1 WHERE {} = ?2;",
986            *MONEY_TOKENS_TABLE,
987            MONEY_TOKENS_COL_IS_FROZEN,
988            MONEY_TOKENS_COL_FREEZE_HEIGHT,
989            MONEY_TOKENS_COL_TOKEN_ID,
990        );
991
992        for token_id in own_freezes {
993            // Grab token record key
994            let key = serialize_async(token_id).await;
995
996            // Execute the query
997            if let Err(e) =
998                self.wallet.exec_sql(&query, rusqlite::params![Some(*freeze_height), key])
999            {
1000                return Err(Error::DatabaseError(format!(
1001                    "[handle_money_call_freezes] Update Money token freeze failed: {e}"
1002                )))
1003            }
1004        }
1005
1006        Ok(true)
1007    }
1008
1009    /// Append data related to Money contract transactions into the
1010    /// wallet database and update the provided scan cache.
1011    /// Returns a flag indicating if provided data refer to our own
1012    /// wallet along with the block signing key, if found.
1013    pub async fn apply_tx_money_data(
1014        &self,
1015        scan_cache: &mut ScanCache,
1016        call_idx: &usize,
1017        calls: &[DarkLeaf<ContractCall>],
1018        tx_hash: &String,
1019        block_height: &u32,
1020    ) -> Result<(bool, Option<SecretKey>)> {
1021        // Parse the call
1022        let (nullifiers, coins, freezes) =
1023            self.parse_money_call(scan_cache, call_idx, calls).await?;
1024
1025        // Parse call coins and grab our own
1026        let (owncoins, block_signing_key) = self.handle_money_call_coins(
1027            &mut scan_cache.money_tree,
1028            &scan_cache.notes_secrets,
1029            &mut scan_cache.messages_buffer,
1030            &coins,
1031        )?;
1032
1033        // Update nullifiers smt
1034        self.smt_insert(&mut scan_cache.money_smt, &nullifiers)?;
1035
1036        // Check if we have any spent coins
1037        let wallet_spent_coins = self.mark_spent_coins(
1038            Some(&mut scan_cache.money_tree),
1039            &scan_cache.owncoins_nullifiers,
1040            &nullifiers,
1041            &Some(*block_height),
1042            tx_hash,
1043        )?;
1044
1045        // Handle our own coins
1046        self.handle_money_call_owncoins(scan_cache, &owncoins, block_height).await?;
1047
1048        // Handle freezes
1049        let wallet_freezes =
1050            self.handle_money_call_freezes(&scan_cache.own_tokens, &freezes, block_height).await?;
1051
1052        if self.fun && !owncoins.is_empty() {
1053            kaching().await;
1054        }
1055
1056        Ok((wallet_spent_coins || !owncoins.is_empty() || wallet_freezes, block_signing_key))
1057    }
1058
1059    /// Auxiliary function to  grab all the nullifiers from a transaction money call.
1060    async fn money_call_nullifiers(&self, call: &DarkLeaf<ContractCall>) -> Result<Vec<Nullifier>> {
1061        let mut nullifiers: Vec<Nullifier> = vec![];
1062
1063        let data = &call.data.data;
1064        match MoneyFunction::try_from(data[0])? {
1065            MoneyFunction::FeeV1 => {
1066                let params: MoneyFeeParamsV1 = deserialize_async(&data[9..]).await?;
1067                nullifiers.push(params.input.nullifier);
1068            }
1069            MoneyFunction::TransferV1 => {
1070                let params: MoneyTransferParamsV1 = deserialize_async(&data[1..]).await?;
1071
1072                for input in params.inputs {
1073                    nullifiers.push(input.nullifier);
1074                }
1075            }
1076            MoneyFunction::OtcSwapV1 => {
1077                let params: MoneyTransferParamsV1 = deserialize_async(&data[1..]).await?;
1078
1079                for input in params.inputs {
1080                    nullifiers.push(input.nullifier);
1081                }
1082            }
1083            _ => { /* Do nothing */ }
1084        }
1085
1086        Ok(nullifiers)
1087    }
1088
1089    /// Mark provided transaction input coins as spent.
1090    pub async fn mark_tx_spend(&self, tx: &Transaction, output: &mut Vec<String>) -> Result<()> {
1091        // Create a cache of all our own nullifiers
1092        let mut owncoins_nullifiers = BTreeMap::new();
1093        for coin in self.get_coins(true).await? {
1094            owncoins_nullifiers.insert(
1095                coin.0.nullifier().to_bytes(),
1096                (coin.0.coin.to_bytes(), coin.0.leaf_position),
1097            );
1098        }
1099
1100        let tx_hash = tx.hash().to_string();
1101        output.push(format!("[mark_tx_spend] Processing transaction: {tx_hash}"));
1102        for (i, call) in tx.calls.iter().enumerate() {
1103            if call.data.contract_id != *MONEY_CONTRACT_ID {
1104                continue
1105            }
1106
1107            output.push(format!("[mark_tx_spend] Found Money contract in call {i}"));
1108            let nullifiers = self.money_call_nullifiers(call).await?;
1109            self.mark_spent_coins(None, &owncoins_nullifiers, &nullifiers, &None, &tx_hash)?;
1110        }
1111
1112        Ok(())
1113    }
1114
1115    /// Marks all coins in the wallet as spent, if their nullifier is in the given set.
1116    /// Returns a flag indicating if any of the provided nullifiers refer to our own wallet.
1117    pub fn mark_spent_coins(
1118        &self,
1119        mut tree: Option<&mut MerkleTree>,
1120        owncoins_nullifiers: &BTreeMap<[u8; 32], ([u8; 32], Position)>,
1121        nullifiers: &[Nullifier],
1122        spent_height: &Option<u32>,
1123        spent_tx_hash: &String,
1124    ) -> Result<bool> {
1125        if nullifiers.is_empty() {
1126            return Ok(false)
1127        }
1128
1129        // Find our owncoins that where spent
1130        let mut spent_owncoins = Vec::new();
1131        for nullifier in nullifiers {
1132            if let Some(coin) = owncoins_nullifiers.get(&nullifier.to_bytes()) {
1133                spent_owncoins.push(coin);
1134            }
1135        }
1136        if spent_owncoins.is_empty() {
1137            return Ok(false)
1138        }
1139
1140        // Create an SQL `UPDATE` query to mark rows as spent(1)
1141        let query = format!(
1142            "UPDATE {} SET {} = 1, {} = ?1, {} = ?2 WHERE {} = ?3;",
1143            *MONEY_COINS_TABLE,
1144            MONEY_COINS_COL_IS_SPENT,
1145            MONEY_COINS_COL_SPENT_HEIGHT,
1146            MONEY_COINS_COL_SPENT_TX_HASH,
1147            MONEY_COINS_COL_COIN
1148        );
1149
1150        // Mark spent own coins
1151        for (ownoin, leaf_position) in spent_owncoins {
1152            // Execute the query
1153            if let Err(e) =
1154                self.wallet.exec_sql(&query, rusqlite::params![spent_height, spent_tx_hash, ownoin])
1155            {
1156                return Err(Error::DatabaseError(format!(
1157                    "[mark_spent_coins] Marking spent coin failed: {e}"
1158                )))
1159            }
1160
1161            // Remove the coin mark from the Merkle tree
1162            if let Some(ref mut tree) = tree {
1163                tree.remove_mark(*leaf_position);
1164            }
1165        }
1166
1167        Ok(true)
1168    }
1169
1170    /// Inserts given slice to the wallets nullifiers Sparse Merkle Tree.
1171    pub fn smt_insert(&self, smt: &mut CacheSmt, nullifiers: &[Nullifier]) -> Result<()> {
1172        let leaves: Vec<_> = nullifiers.iter().map(|x| (x.inner(), x.inner())).collect();
1173        Ok(smt.insert_batch(leaves)?)
1174    }
1175
1176    /// Reset the Money Merkle tree in the cache.
1177    pub fn reset_money_tree(&self, output: &mut Vec<String>) -> WalletDbResult<()> {
1178        output.push(String::from("Resetting Money Merkle tree"));
1179        if let Err(e) = self.cache.merkle_trees.remove(SLED_MERKLE_TREES_MONEY) {
1180            output.push(format!("[reset_money_tree] Resetting Money Merkle tree failed: {e}"));
1181            return Err(WalletDbError::GenericError)
1182        }
1183        output.push(String::from("Successfully reset Money Merkle tree"));
1184
1185        Ok(())
1186    }
1187
1188    /// Reset the Money nullifiers Sparse Merkle Tree in the cache.
1189    pub fn reset_money_smt(&self, output: &mut Vec<String>) -> WalletDbResult<()> {
1190        output.push(String::from("Resetting Money Sparse Merkle tree"));
1191        if let Err(e) = self.cache.money_smt.clear() {
1192            output
1193                .push(format!("[reset_money_smt] Resetting Money Sparse Merkle tree failed: {e}"));
1194            return Err(WalletDbError::GenericError)
1195        }
1196        output.push(String::from("Successfully reset Money Sparse Merkle tree"));
1197
1198        Ok(())
1199    }
1200
1201    /// Reset the Money coins in the wallet.
1202    pub fn reset_money_coins(&self, output: &mut Vec<String>) -> WalletDbResult<()> {
1203        output.push(String::from("Resetting coins"));
1204        let query = format!("DELETE FROM {};", *MONEY_COINS_TABLE);
1205        self.wallet.exec_sql(&query, &[])?;
1206        output.push(String::from("Successfully reset coins"));
1207
1208        Ok(())
1209    }
1210
1211    /// Remove the Money coins in the wallet that were created after
1212    /// provided height.
1213    pub fn remove_money_coins_after(
1214        &self,
1215        height: &u32,
1216        output: &mut Vec<String>,
1217    ) -> WalletDbResult<()> {
1218        output.push(format!("Removing coins after: {height}"));
1219        let query = format!(
1220            "DELETE FROM {} WHERE {} > ?1;",
1221            *MONEY_COINS_TABLE, MONEY_COINS_COL_CREATION_HEIGHT
1222        );
1223        self.wallet.exec_sql(&query, rusqlite::params![height])?;
1224        output.push(String::from("Successfully removed coins"));
1225
1226        Ok(())
1227    }
1228
1229    /// Mark the Money coins in the wallet that were spent after
1230    /// provided height as unspent.
1231    pub fn unspent_money_coins_after(
1232        &self,
1233        height: &u32,
1234        output: &mut Vec<String>,
1235    ) -> WalletDbResult<()> {
1236        output.push(format!("Unspenting coins after: {height}"));
1237        let query = format!(
1238            "UPDATE {} SET {} = 0, {} = NULL, {} = '=' WHERE {} > ?1;",
1239            *MONEY_COINS_TABLE,
1240            MONEY_COINS_COL_IS_SPENT,
1241            MONEY_COINS_COL_SPENT_HEIGHT,
1242            MONEY_COINS_COL_SPENT_TX_HASH,
1243            MONEY_COINS_COL_SPENT_HEIGHT
1244        );
1245        self.wallet.exec_sql(&query, rusqlite::params![Some(*height)])?;
1246        output.push(String::from("Successfully unspent coins"));
1247
1248        Ok(())
1249    }
1250
1251    /// Retrieve token by provided string.
1252    /// Input string represents either an alias or a token id.
1253    pub async fn get_token(&self, input: String) -> Result<TokenId> {
1254        // Check if input is an alias(max 5 characters)
1255        if input.chars().count() <= 5 {
1256            let aliases = self.get_aliases(Some(input.clone()), None).await?;
1257            if let Some(token_id) = aliases.get(&input) {
1258                return Ok(*token_id)
1259            }
1260        }
1261        // Else parse input
1262        Ok(TokenId::from_str(input.as_str())?)
1263    }
1264
1265    /// Create and append a `Money::Fee` call to a given [`Transaction`].
1266    ///
1267    /// Optionally takes a set of spent coins in order not to reuse them here.
1268    ///
1269    /// Returns the `Fee` call, and all necessary data and parameters related.
1270    pub async fn append_fee_call(
1271        &self,
1272        tx: &Transaction,
1273        money_merkle_tree: &MerkleTree,
1274        fee_pk: &ProvingKey,
1275        fee_zkbin: &ZkBinary,
1276        spent_coins: Option<&[OwnCoin]>,
1277    ) -> Result<(ContractCall, Vec<Proof>, Vec<SecretKey>)> {
1278        // First we verify the fee-less transaction to see how much fee it requires for execution
1279        // and verification.
1280        let required_fee = compute_fee(&FEE_CALL_GAS) + self.get_tx_fee(tx, false).await?;
1281
1282        // Knowing the total gas, we can now find an OwnCoin of enough value
1283        // so that we can create a valid Money::Fee call.
1284        let mut available_coins = self.get_token_coins(&DARK_TOKEN_ID).await?;
1285        available_coins.retain(|x| x.note.value > required_fee);
1286        if let Some(spent_coins) = spent_coins {
1287            available_coins.retain(|x| !spent_coins.contains(x));
1288        }
1289        if available_coins.is_empty() {
1290            return Err(Error::Custom("Not enough native tokens to pay for fees".to_string()))
1291        }
1292
1293        let coin = &available_coins[0];
1294        let change_value = coin.note.value - required_fee;
1295
1296        // Input and output setup
1297        let input = FeeCallInput {
1298            coin: coin.clone(),
1299            merkle_path: money_merkle_tree.witness(coin.leaf_position, 0).unwrap(),
1300            user_data_blind: BaseBlind::random(&mut OsRng),
1301        };
1302
1303        let output = FeeCallOutput {
1304            public_key: PublicKey::from_secret(coin.secret),
1305            value: change_value,
1306            token_id: coin.note.token_id,
1307            blind: BaseBlind::random(&mut OsRng),
1308            spend_hook: FuncId::none(),
1309            user_data: pallas::Base::ZERO,
1310        };
1311
1312        // Create blinding factors
1313        let token_blind = BaseBlind::random(&mut OsRng);
1314        let input_value_blind = ScalarBlind::random(&mut OsRng);
1315        let fee_value_blind = ScalarBlind::random(&mut OsRng);
1316        let output_value_blind = compute_remainder_blind(&[input_value_blind], &[fee_value_blind]);
1317
1318        // Create an ephemeral signing key
1319        let signature_secret = SecretKey::random(&mut OsRng);
1320
1321        // Create the actual fee proof
1322        let (proof, public_inputs) = create_fee_proof(
1323            fee_zkbin,
1324            fee_pk,
1325            &input,
1326            input_value_blind,
1327            &output,
1328            output_value_blind,
1329            output.spend_hook,
1330            output.user_data,
1331            output.blind,
1332            token_blind,
1333            signature_secret,
1334        )?;
1335
1336        // Encrypted note for the output
1337        let note = MoneyNote {
1338            coin_blind: output.blind,
1339            value: output.value,
1340            token_id: output.token_id,
1341            spend_hook: output.spend_hook,
1342            user_data: output.user_data,
1343            value_blind: output_value_blind,
1344            token_blind,
1345            memo: vec![],
1346        };
1347
1348        let encrypted_note = AeadEncryptedNote::encrypt(&note, &output.public_key, &mut OsRng)?;
1349
1350        let params = MoneyFeeParamsV1 {
1351            input: Input {
1352                value_commit: public_inputs.input_value_commit,
1353                token_commit: public_inputs.token_commit,
1354                nullifier: public_inputs.nullifier,
1355                merkle_root: public_inputs.merkle_root,
1356                user_data_enc: public_inputs.input_user_data_enc,
1357                signature_public: public_inputs.signature_public,
1358            },
1359            output: Output {
1360                value_commit: public_inputs.output_value_commit,
1361                token_commit: public_inputs.token_commit,
1362                coin: public_inputs.output_coin,
1363                note: encrypted_note,
1364            },
1365            fee_value_blind,
1366            token_blind,
1367        };
1368
1369        // Encode the contract call
1370        let mut data = vec![MoneyFunction::FeeV1 as u8];
1371        required_fee.encode_async(&mut data).await?;
1372        params.encode_async(&mut data).await?;
1373        let call = ContractCall { contract_id: *MONEY_CONTRACT_ID, data };
1374
1375        Ok((call, vec![proof], vec![signature_secret]))
1376    }
1377
1378    /// Create and attach the fee call to given transaction.
1379    pub async fn attach_fee(&self, tx: &mut Transaction) -> Result<()> {
1380        // Grab spent coins nullifiers of the transactions and check no other fee call exists
1381        let mut tx_nullifiers = vec![];
1382        for call in &tx.calls {
1383            if call.data.contract_id != *MONEY_CONTRACT_ID {
1384                continue
1385            }
1386
1387            match MoneyFunction::try_from(call.data.data[0])? {
1388                MoneyFunction::FeeV1 => {
1389                    return Err(Error::Custom("Fee call already exists".to_string()))
1390                }
1391                _ => { /* Do nothing */ }
1392            }
1393
1394            let nullifiers = self.money_call_nullifiers(call).await?;
1395            tx_nullifiers.extend_from_slice(&nullifiers);
1396        }
1397
1398        // Grab all native owncoins to check if any is spent
1399        let mut spent_coins = vec![];
1400        let available_coins = self.get_token_coins(&DARK_TOKEN_ID).await?;
1401        for coin in available_coins {
1402            if tx_nullifiers.contains(&coin.nullifier()) {
1403                spent_coins.push(coin);
1404            }
1405        }
1406
1407        // Now we need to do a lookup for the zkas proof bincodes, and create
1408        // the circuit objects and proving keys so we can build the transaction.
1409        // We also do this through the RPC.
1410        let zkas_bins = self.lookup_zkas(&MONEY_CONTRACT_ID).await?;
1411
1412        let Some(fee_zkbin) = zkas_bins.iter().find(|x| x.0 == MONEY_CONTRACT_ZKAS_FEE_NS_V1)
1413        else {
1414            return Err(Error::Custom("Fee circuit not found".to_string()))
1415        };
1416
1417        let fee_zkbin = ZkBinary::decode(&fee_zkbin.1)?;
1418
1419        let fee_circuit = ZkCircuit::new(empty_witnesses(&fee_zkbin)?, &fee_zkbin);
1420
1421        // Creating Fee circuits proving keys
1422        let fee_pk = ProvingKey::build(fee_zkbin.k, &fee_circuit);
1423
1424        // We first have to execute the fee-less tx to gather its used gas, and then we feed
1425        // it into the fee-creating function.
1426        let tree = self.get_money_tree().await?;
1427        let (fee_call, fee_proofs, fee_secrets) =
1428            self.append_fee_call(tx, &tree, &fee_pk, &fee_zkbin, Some(&spent_coins)).await?;
1429
1430        // Append the fee call to the transaction
1431        tx.calls.push(DarkLeaf { data: fee_call, parent_index: None, children_indexes: vec![] });
1432        tx.proofs.push(fee_proofs);
1433        let sigs = tx.create_sigs(&fee_secrets)?;
1434        tx.signatures.push(sigs);
1435
1436        Ok(())
1437    }
1438}