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