Browse Source

drk: Refactor.

parazyd 3 years ago
parent
commit
6c0c7ffa1a

+ 0 - 88
bin/drk/src/dao.rs

@@ -1,88 +0,0 @@
-/* This file is part of DarkFi (https://dark.fi)
- *
- * Copyright (C) 2020-2023 Dyne.org foundation
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program.  If not, see <https://www.gnu.org/licenses/>.
- */
-
-use darkfi_dao_contract::dao_model::DaoBulla;
-use darkfi_sdk::{
-    crypto::{poseidon_hash, PublicKey, SecretKey, TokenId},
-    incrementalmerkletree::Position,
-    pasta::pallas,
-};
-use darkfi_serial::{SerialDecodable, SerialEncodable};
-
-#[derive(Debug, Clone, SerialEncodable, SerialDecodable)]
-/// Parameters representing a DAO to be initialized
-pub struct DaoParams {
-    /// The minimum amount of governance tokens needed to open a proposal
-    pub proposer_limit: u64,
-    /// Minimal threshold of participating total tokens needed for a proposal to pass
-    pub quorum: u64,
-    /// The ratio of winning/total votes needed for a proposal to pass
-    pub approval_ratio_base: u64,
-    pub approval_ratio_quot: u64,
-    /// DAO's governance token ID
-    pub gov_token_id: TokenId,
-    /// Secret key for the DAO
-    pub secret_key: SecretKey,
-    /// DAO bulla blind
-    pub bulla_blind: pallas::Base,
-}
-
-#[derive(Debug, Clone)]
-/// Parameters representing an intialized DAO, optionally deployed on-chain
-pub struct Dao {
-    /// Numeric identifier for the DAO
-    pub id: u64,
-    /// Named identifier for the DAO
-    pub name: String,
-    /// The minimum amount of governance tokens needed to open a proposal
-    pub proposer_limit: u64,
-    /// Minimal threshold of participating total tokens needed for a proposal to pass
-    pub quorum: u64,
-    /// The ratio of winning/total votes needed for a proposal to pass
-    pub approval_ratio_base: u64,
-    pub approval_ratio_quot: u64,
-    /// DAO's governance token ID
-    pub gov_token_id: TokenId,
-    /// Secret key for the DAO
-    pub secret_key: SecretKey,
-    /// DAO bulla blind
-    pub bulla_blind: pallas::Base,
-    /// Leaf position of the DAO in the Merkle tree of DAOs
-    pub leaf_position: Option<Position>,
-    /// The transaction hash where the DAO was deployed
-    pub tx_hash: Option<blake3::Hash>,
-    /// The call index in the transaction where the DAO was deployed
-    pub call_index: Option<u32>,
-}
-
-impl Dao {
-    pub fn bulla(&self) -> DaoBulla {
-        let (x, y) = PublicKey::from_secret(self.secret_key).xy();
-
-        DaoBulla::from(poseidon_hash([
-            pallas::Base::from(self.proposer_limit),
-            pallas::Base::from(self.quorum),
-            pallas::Base::from(self.approval_ratio_base),
-            pallas::Base::from(self.approval_ratio_quot),
-            self.gov_token_id.inner(),
-            x,
-            y,
-            self.bulla_blind,
-        ]))
-    }
-}

+ 36 - 15
bin/drk/src/main.rs

@@ -63,16 +63,16 @@ mod rpc_dao;
 /// Blockchain methods
 /// Blockchain methods
 mod rpc_blockchain;
 mod rpc_blockchain;
 
 
-/// Wallet operation methods for darkfid's JSON-RPC
-mod rpc_wallet;
-
 /// CLI utility functions
 /// CLI utility functions
 mod cli_util;
 mod cli_util;
 use cli_util::{parse_token_pair, parse_value_pair};
 use cli_util::{parse_token_pair, parse_value_pair};
 
 
-/// DAO aux functionality
-mod dao;
-use dao::DaoParams;
+/// Wallet functionality related to DAO
+mod wallet_dao;
+use wallet_dao::DaoParams;
+
+/// Wallet functionality related to Money
+mod wallet_money;
 
 
 #[derive(Parser)]
 #[derive(Parser)]
 #[command(about = cli_desc!())]
 #[command(about = cli_desc!())]
@@ -387,17 +387,35 @@ async fn main() -> Result<()> {
             let drk = Drk { rpc_client };
             let drk = Drk { rpc_client };
 
 
             if initialize {
             if initialize {
-                drk.wallet_initialize().await.with_context(|| "Failed to initialize wallet")?;
+                drk.initialize_money().await?;
+                drk.initialize_dao().await?;
                 return Ok(())
                 return Ok(())
             }
             }
 
 
             if keygen {
             if keygen {
-                drk.wallet_keygen().await.with_context(|| "Failed to generate keypair")?;
+                drk.money_keygen().await.with_context(|| "Failed to generate keypair")?;
                 return Ok(())
                 return Ok(())
             }
             }
 
 
             if balance {
             if balance {
-                drk.wallet_balance().await.with_context(|| "Failed to fetch wallet balance")?;
+                let balmap =
+                    drk.money_balance().await.with_context(|| "Failed to fetch wallet balance")?;
+
+                // Create a prettytable with the new data:
+                let mut table = Table::new();
+                table.set_format(*format::consts::FORMAT_NO_BORDER_LINE_SEPARATOR);
+                table.set_titles(row!["Token ID", "Balance"]);
+                for (token_id, balance) in balmap.iter() {
+                    // FIXME: Don't hardcode to 8 decimals
+                    table.add_row(row![token_id, encode_base10(*balance, 8)]);
+                }
+
+                if table.is_empty() {
+                    println!("No unspent balances found");
+                } else {
+                    println!("{}", table);
+                }
+
                 return Ok(())
                 return Ok(())
             }
             }
 
 
@@ -413,8 +431,10 @@ async fn main() -> Result<()> {
             }
             }
 
 
             if secrets {
             if secrets {
-                let v =
-                    drk.wallet_secrets().await.with_context(|| "Failed to fetch wallet secrets")?;
+                let v = drk
+                    .get_money_secrets()
+                    .await
+                    .with_context(|| "Failed to fetch wallet secrets")?;
 
 
                 drk.rpc_client.close().await?;
                 drk.rpc_client.close().await?;
 
 
@@ -440,7 +460,7 @@ async fn main() -> Result<()> {
                 }
                 }
 
 
                 let pubkeys = drk
                 let pubkeys = drk
-                    .wallet_import_secrets(secrets)
+                    .import_money_secrets(secrets)
                     .await
                     .await
                     .with_context(|| "Failed to import secret keys into wallet")?;
                     .with_context(|| "Failed to import secret keys into wallet")?;
 
 
@@ -454,7 +474,8 @@ async fn main() -> Result<()> {
             }
             }
 
 
             if tree {
             if tree {
-                let v = drk.wallet_tree().await.with_context(|| "Failed to fetch Merkle tree")?;
+                let v =
+                    drk.get_money_tree().await.with_context(|| "Failed to fetch Merkle tree")?;
                 drk.rpc_client.close().await?;
                 drk.rpc_client.close().await?;
 
 
                 println!("{:#?}", v);
                 println!("{:#?}", v);
@@ -464,7 +485,7 @@ async fn main() -> Result<()> {
 
 
             if coins {
             if coins {
                 let coins = drk
                 let coins = drk
-                    .wallet_coins(true)
+                    .get_coins(true)
                     .await
                     .await
                     .with_context(|| "Failed to fetch coins from wallet")?;
                     .with_context(|| "Failed to fetch coins from wallet")?;
 
 
@@ -761,7 +782,7 @@ async fn main() -> Result<()> {
 
 
                 let drk = Drk { rpc_client };
                 let drk = Drk { rpc_client };
 
 
-                drk.dao_import(dao_name, dao_params)
+                drk.import_dao(dao_name, dao_params)
                     .await
                     .await
                     .with_context(|| "Failed to import DAO")?;
                     .with_context(|| "Failed to import DAO")?;
 
 

+ 20 - 49
bin/drk/src/rpc_blockchain.rs

@@ -28,10 +28,6 @@ use darkfi::{
     tx::Transaction,
     tx::Transaction,
     wallet::walletdb::QueryType,
     wallet::walletdb::QueryType,
 };
 };
-use darkfi_dao_contract::{
-    dao_model::{DaoBulla, DaoMintParams},
-    DaoFunction,
-};
 use darkfi_money_contract::{
 use darkfi_money_contract::{
     client::{
     client::{
         Coin, EncryptedNote, OwnCoin, MONEY_COINS_COL_COIN, MONEY_COINS_COL_COIN_BLIND,
         Coin, EncryptedNote, OwnCoin, MONEY_COINS_COL_COIN, MONEY_COINS_COL_COIN_BLIND,
@@ -45,7 +41,9 @@ use darkfi_money_contract::{
     MoneyFunction,
     MoneyFunction,
 };
 };
 use darkfi_sdk::{
 use darkfi_sdk::{
-    crypto::{contract_id::MONEY_CONTRACT_ID, poseidon_hash, ContractId, MerkleNode, Nullifier},
+    crypto::{
+        contract_id::MONEY_CONTRACT_ID, poseidon_hash, ContractId, MerkleNode, Nullifier, SecretKey,
+    },
     incrementalmerkletree::Tree,
     incrementalmerkletree::Tree,
 };
 };
 use darkfi_serial::{deserialize, serialize};
 use darkfi_serial::{deserialize, serialize};
@@ -67,7 +65,7 @@ impl Drk {
         let req = JsonRequest::new("blockchain.last_known_slot", json!([]));
         let req = JsonRequest::new("blockchain.last_known_slot", json!([]));
         let rep = self.rpc_client.request(req).await?;
         let rep = self.rpc_client.request(req).await?;
         let last_known: u64 = serde_json::from_value(rep)?;
         let last_known: u64 = serde_json::from_value(rep)?;
-        let last_scanned = self.wallet_last_scanned_slot().await?;
+        let last_scanned = self.last_scanned_slot().await?;
 
 
         if last_known != last_scanned {
         if last_known != last_scanned {
             eprintln!("Warning: Last scanned slot is not the last known slot.");
             eprintln!("Warning: Last scanned slot is not the last known slot.");
@@ -135,46 +133,10 @@ impl Drk {
     /// for future use.
     /// for future use.
     async fn scan_block_dao(&self, block: &BlockInfo) -> Result<()> {
     async fn scan_block_dao(&self, block: &BlockInfo) -> Result<()> {
         eprintln!("Iterating over {} transactions", block.txs.len());
         eprintln!("Iterating over {} transactions", block.txs.len());
-
-        let mut minted_dao_bullas: Vec<(DaoBulla, blake3::Hash, u32)> = vec![];
-
-        for (i, tx) in block.txs.iter().enumerate() {
-            for (j, call) in tx.calls.iter().enumerate() {
-                if call.contract_id == *MONEY_CONTRACT_ID && call.data[0] == DaoFunction::Mint as u8
-                {
-                    eprintln!("Found Dao::Mint in call {} in tx {}", j, i);
-                    let params: DaoMintParams = deserialize(&call.data[1..])?;
-                    minted_dao_bullas.push((
-                        params.dao_bulla,
-                        blake3::hash(&serialize(tx)),
-                        j as u32,
-                    ));
-                    continue
-                }
-            }
-        }
-
-        let mut daos = self.wallet_get_daos().await?;
-        let (mut daos_tree, mut proposals_tree) = self.wallet_dao_trees().await?;
-        // We assume that the state transitions are correct and won't allow a DAO being
-        // minted twice. So here we just blindly put stuff in.
-        for bulla in minted_dao_bullas {
-            daos_tree.append(&MerkleNode::from(bulla.0.inner()));
-            for dao in daos.iter_mut() {
-                if dao.bulla() == bulla.0 {
-                    eprintln!("Found DAO {:?}, noting down for wallet update", bulla.0);
-                    // We have this DAO imported our wallet. Add the metadata:
-                    dao.leaf_position = daos_tree.witness();
-                    dao.tx_hash = Some(bulla.1);
-                    dao.call_index = Some(bulla.2);
-                }
-            }
+        for tx in block.txs.iter() {
+            self.apply_tx_dao_data(tx, true).await?;
         }
         }
 
 
-        eprintln!("Writing DAO updates to wallet");
-        self.put_daos(&daos).await?;
-        self.put_dao_trees(&daos_tree, &proposals_tree).await?;
-
         Ok(())
         Ok(())
     }
     }
 
 
@@ -222,13 +184,13 @@ impl Drk {
 
 
         // Fetch our secret keys from the wallet
         // Fetch our secret keys from the wallet
         eprintln!("Fetching secret keys from wallet");
         eprintln!("Fetching secret keys from wallet");
-        let secrets = self.wallet_secrets().await?;
+        let secrets: Vec<SecretKey> = self.get_money_secrets().await?;
         if secrets.is_empty() {
         if secrets.is_empty() {
             eprintln!("Warning: No secrets found in wallet");
             eprintln!("Warning: No secrets found in wallet");
         }
         }
 
 
-        eprintln!("Fetching Merkle tree from wallet");
-        let mut tree = self.wallet_tree().await?;
+        eprintln!("Fetching Money Merkle tree from wallet");
+        let mut tree = self.get_money_tree().await?;
 
 
         let mut owncoins = vec![];
         let mut owncoins = vec![];
 
 
@@ -267,7 +229,7 @@ impl Drk {
 
 
         if !nullifiers.is_empty() {
         if !nullifiers.is_empty() {
             eprintln!("Found {} spent coins, marking as spent", nullifiers.len());
             eprintln!("Found {} spent coins, marking as spent", nullifiers.len());
-            self.mark_spent_coins(nullifiers).await?;
+            self.mark_spent_coins(&nullifiers).await?;
             eprintln!("Spent coins marked successfully");
             eprintln!("Spent coins marked successfully");
         }
         }
 
 
@@ -365,6 +327,15 @@ impl Drk {
         let rep = self.rpc_client.request(req).await?;
         let rep = self.rpc_client.request(req).await?;
 
 
         let txid = serde_json::from_value(rep)?;
         let txid = serde_json::from_value(rep)?;
+
+        // At this point the tx is successfully broadcasted. We can add the
+        // temp data into the wallet. Once scanned, it should mean that the
+        // transaction was finalized, so at that point we actually add the
+        // missing data. For now it'll be in an "unconfirmed" state.
+        // TODO: Do the same for Money::*
+        //self.wallet_apply_unconfirmed_dao_data(tx).await?;
+        //self.wallet_apply_unconfirmed_money_data(tx).await?;
+
         Ok(txid)
         Ok(txid)
     }
     }
 
 
@@ -394,7 +365,7 @@ impl Drk {
             self.reset_money_tree().await?;
             self.reset_money_tree().await?;
             0
             0
         } else {
         } else {
-            self.wallet_last_scanned_slot().await?
+            self.last_scanned_slot().await?
         };
         };
 
 
         let req = JsonRequest::new("blockchain.last_known_slot", json!([]));
         let req = JsonRequest::new("blockchain.last_known_slot", json!([]));

+ 8 - 124
bin/drk/src/rpc_dao.rs

@@ -18,21 +18,13 @@
 
 
 use anyhow::{anyhow, Result};
 use anyhow::{anyhow, Result};
 use darkfi::{
 use darkfi::{
-    rpc::jsonrpc::JsonRequest,
     tx::Transaction,
     tx::Transaction,
-    wallet::walletdb::QueryType,
     zk::{empty_witnesses, halo2::Field, ProvingKey, ZkCircuit},
     zk::{empty_witnesses, halo2::Field, ProvingKey, ZkCircuit},
     zkas::ZkBinary,
     zkas::ZkBinary,
 };
 };
 use darkfi_dao_contract::{
 use darkfi_dao_contract::{
-    dao_client,
-    dao_client::{
-        DaoInfo, DAO_DAOS_COL_APPROVAL_RATIO_BASE, DAO_DAOS_COL_APPROVAL_RATIO_QUOT,
-        DAO_DAOS_COL_BULLA_BLIND, DAO_DAOS_COL_GOV_TOKEN_ID, DAO_DAOS_COL_NAME,
-        DAO_DAOS_COL_PROPOSER_LIMIT, DAO_DAOS_COL_QUORUM, DAO_DAOS_COL_SECRET, DAO_DAOS_TABLE,
-    },
-    DaoFunction, DAO_CONTRACT_ZKAS_DAO_MINT_NS, DAO_CONTRACT_ZKAS_DAO_PROPOSE_BURN_NS,
-    DAO_CONTRACT_ZKAS_DAO_PROPOSE_MAIN_NS,
+    dao_client, dao_client::DaoInfo, DaoFunction, DAO_CONTRACT_ZKAS_DAO_MINT_NS,
+    DAO_CONTRACT_ZKAS_DAO_PROPOSE_BURN_NS, DAO_CONTRACT_ZKAS_DAO_PROPOSE_MAIN_NS,
 };
 };
 use darkfi_money_contract::client::OwnCoin;
 use darkfi_money_contract::client::OwnCoin;
 use darkfi_sdk::{
 use darkfi_sdk::{
@@ -41,122 +33,15 @@ use darkfi_sdk::{
     pasta::pallas,
     pasta::pallas,
     ContractCall,
     ContractCall,
 };
 };
-use darkfi_serial::{deserialize, serialize, Encodable};
+use darkfi_serial::Encodable;
 use rand::rngs::OsRng;
 use rand::rngs::OsRng;
-use serde_json::json;
 
 
 use super::Drk;
 use super::Drk;
-use crate::{dao::Dao, DaoParams};
 
 
 impl Drk {
 impl Drk {
-    /// Import given DAO into the wallet
-    pub async fn dao_import(&self, dao_name: String, dao_params: DaoParams) -> Result<()> {
-        // First let's check if we've imported this DAO before. We use the name
-        // as the identifier.
-        let query = format!("SELECT {} FROM {}", DAO_DAOS_COL_NAME, DAO_DAOS_TABLE);
-        let params = json!([query, QueryType::Blob as u8, DAO_DAOS_COL_NAME]);
-        let req = JsonRequest::new("wallet.query_row_multi", params);
-        let rep = self.rpc_client.request(req).await?;
-
-        // The returned thing should be an array of found rows.
-        let Some(rows) = rep.as_array() else {
-            return Err(anyhow!("Unexpected response from darkfid: {}", rep))
-        };
-
-        for row in rows {
-            let name_bytes: Vec<u8> = serde_json::from_value(row[0].clone())?;
-            let name: String = deserialize(&name_bytes)?;
-            if name == dao_name {
-                return Err(anyhow!("DAO \"{}\" already imported in wallet", dao_name))
-            }
-        }
-
-        eprintln!("Importing \"{}\" DAO into wallet", dao_name);
-
-        let query = format!(
-            "INSERT INTO {} ({}, {}, {}, {}, {}, {}, {}, {}) VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8);",
-            DAO_DAOS_TABLE, DAO_DAOS_COL_NAME, DAO_DAOS_COL_PROPOSER_LIMIT,
-            DAO_DAOS_COL_QUORUM, DAO_DAOS_COL_APPROVAL_RATIO_BASE, DAO_DAOS_COL_APPROVAL_RATIO_QUOT,
-            DAO_DAOS_COL_GOV_TOKEN_ID, DAO_DAOS_COL_SECRET, DAO_DAOS_COL_BULLA_BLIND,
-        );
-
-        let params = json!([
-            query,
-            QueryType::Blob as u8,
-            serialize(&dao_name),
-            QueryType::Integer as u8,
-            dao_params.proposer_limit,
-            QueryType::Integer as u8,
-            dao_params.quorum,
-            QueryType::Integer as u8,
-            dao_params.approval_ratio_base,
-            QueryType::Integer as u8,
-            dao_params.approval_ratio_quot,
-            QueryType::Blob as u8,
-            serialize(&dao_params.gov_token_id),
-            QueryType::Blob as u8,
-            serialize(&dao_params.secret_key),
-            QueryType::Blob as u8,
-            serialize(&dao_params.bulla_blind),
-        ]);
-
-        eprintln!("Executing JSON-RPC request to add DAO to wallet");
-        let req = JsonRequest::new("wallet.exec_sql", params);
-        self.rpc_client.request(req).await?;
-        eprintln!("DAO imported successfully");
-
-        Ok(())
-    }
-
-    async fn dao_get_by_id(&self, dao_id: u64) -> Result<Dao> {
-        let daos = self.wallet_get_daos().await?;
-
-        let Some(dao) = daos.iter().find(|x| x.id == dao_id) else {
-            return Err(anyhow!("DAO not found in wallet"))
-        };
-
-        Ok(dao.clone())
-    }
-
-    async fn dao_list_single(&self, dao_id: u64) -> Result<()> {
-        let dao = self.dao_get_by_id(dao_id).await?;
-
-        println!("DAO Parameters:");
-        println!("Name: {}", dao.name);
-        println!("Proposer limit: {}", dao.proposer_limit);
-        println!("Quorum: {}", dao.quorum);
-        println!(
-            "Approval ratio: {}",
-            dao.approval_ratio_base as f64 / dao.approval_ratio_quot as f64
-        );
-        println!("Governance token ID: {}", dao.gov_token_id);
-        println!("Secret key: {}", dao.secret_key);
-        println!("Bulla blind: {:?}", dao.bulla_blind);
-        println!("Leaf position: {:?}", dao.leaf_position);
-        println!("Tx hash: {:?}", dao.tx_hash);
-        println!("Call idx: {:?}", dao.call_index);
-
-        Ok(())
-    }
-
-    /// List DAO(s) imported in the wallet
-    pub async fn dao_list(&self, dao_id: Option<u64>) -> Result<()> {
-        if dao_id.is_some() {
-            return self.dao_list_single(dao_id.unwrap()).await
-        }
-
-        let daos = self.wallet_get_daos().await?;
-
-        for dao in daos {
-            println!("[{}] {}", dao.id, dao.name);
-        }
-
-        Ok(())
-    }
-
     /// Mint a DAO on-chain
     /// Mint a DAO on-chain
     pub async fn dao_mint(&self, dao_id: u64) -> Result<Transaction> {
     pub async fn dao_mint(&self, dao_id: u64) -> Result<Transaction> {
-        let dao = self.dao_get_by_id(dao_id).await?;
+        let dao = self.get_dao_by_id(dao_id).await?;
 
 
         if dao.tx_hash.is_some() {
         if dao.tx_hash.is_some() {
             return Err(anyhow!("This DAO seems to have already been minted on-chain"))
             return Err(anyhow!("This DAO seems to have already been minted on-chain"))
@@ -207,8 +92,7 @@ impl Drk {
         token_id: TokenId,
         token_id: TokenId,
         serial: pallas::Base,
         serial: pallas::Base,
     ) -> Result<Transaction> {
     ) -> Result<Transaction> {
-        let daos = self.wallet_get_daos().await?;
-        let Some(dao) = daos.get(dao_id as usize - 1) else {
+        let Ok(dao) = self.get_dao_by_id(dao_id).await else {
             return Err(anyhow!("DAO not found in wallet"))
             return Err(anyhow!("DAO not found in wallet"))
         };
         };
 
 
@@ -217,7 +101,7 @@ impl Drk {
         }
         }
 
 
         let bulla = dao.bulla();
         let bulla = dao.bulla();
-        let owncoins = self.wallet_coins(false).await?;
+        let owncoins = self.get_coins(false).await?;
 
 
         let mut dao_owncoins: Vec<OwnCoin> = owncoins.iter().map(|x| x.0.clone()).collect();
         let mut dao_owncoins: Vec<OwnCoin> = owncoins.iter().map(|x| x.0.clone()).collect();
         dao_owncoins.retain(|x| {
         dao_owncoins.retain(|x| {
@@ -284,13 +168,13 @@ impl Drk {
         let signature_secret = SecretKey::random(&mut OsRng);
         let signature_secret = SecretKey::random(&mut OsRng);
 
 
         // Get the Merkle path for the gov coin in the money tree
         // Get the Merkle path for the gov coin in the money tree
-        let money_merkle_tree = self.wallet_tree().await?;
+        let money_merkle_tree = self.get_money_tree().await?;
         let root = money_merkle_tree.root(0).unwrap();
         let root = money_merkle_tree.root(0).unwrap();
         let gov_coin_merkle_path =
         let gov_coin_merkle_path =
             money_merkle_tree.authentication_path(gov_coin.leaf_position, &root).unwrap();
             money_merkle_tree.authentication_path(gov_coin.leaf_position, &root).unwrap();
 
 
         // Fetch the daos Merkle tree
         // Fetch the daos Merkle tree
-        let (daos_tree, _) = self.wallet_dao_trees().await?;
+        let (daos_tree, _) = self.get_dao_trees().await?;
 
 
         let input = dao_client::DaoProposeStakeInput {
         let input = dao_client::DaoProposeStakeInput {
             secret: gov_coin.secret, // <-- TODO: Is this correct?
             secret: gov_coin.secret, // <-- TODO: Is this correct?

+ 12 - 8
bin/drk/src/rpc_swap.rs

@@ -64,10 +64,14 @@ impl Drk {
         token_recv: TokenId,
         token_recv: TokenId,
     ) -> Result<PartialSwapData> {
     ) -> Result<PartialSwapData> {
         // First we'll fetch all of our unspent coins from the wallet.
         // First we'll fetch all of our unspent coins from the wallet.
-        let mut owncoins = self.wallet_coins(false).await?;
+        let mut owncoins = self.get_coins(false).await?;
         // Then we see if we have one that we can send.
         // Then we see if we have one that we can send.
-        owncoins.retain(|x| (x.0.note.value == value_send && x.0.note.token_id == token_send));
-        owncoins.retain(|x| (x.0.note.spend_hook == pallas::Base::zero()));
+        owncoins.retain(|x| {
+            x.0.note.value == value_send &&
+                x.0.note.token_id == token_send &&
+                x.0.note.spend_hook == pallas::Base::zero()
+        });
+
         if owncoins.is_empty() {
         if owncoins.is_empty() {
             return Err(anyhow!(
             return Err(anyhow!(
                 "Did not find any unspent coins of value {} and token_id {}",
                 "Did not find any unspent coins of value {} and token_id {}",
@@ -83,7 +87,7 @@ impl Drk {
         let address = self.wallet_address(0).await?;
         let address = self.wallet_address(0).await?;
 
 
         // We'll also need our Merkle tree
         // We'll also need our Merkle tree
-        let tree = self.wallet_tree().await?;
+        let tree = self.get_money_tree().await?;
 
 
         let contract_id = *MONEY_CONTRACT_ID;
         let contract_id = *MONEY_CONTRACT_ID;
 
 
@@ -149,7 +153,7 @@ impl Drk {
     pub async fn join_swap(&self, partial: PartialSwapData) -> Result<Transaction> {
     pub async fn join_swap(&self, partial: PartialSwapData) -> Result<Transaction> {
         // Our side of the tx in the pairs is the second half, so we try to find
         // Our side of the tx in the pairs is the second half, so we try to find
         // an unspent coin like that in our wallet.
         // an unspent coin like that in our wallet.
-        let mut owncoins = self.wallet_coins(false).await?;
+        let mut owncoins = self.get_coins(false).await?;
         owncoins.retain(|x| {
         owncoins.retain(|x| {
             x.0.note.value == partial.value_pair.1 && x.0.note.token_id == partial.token_pair.1
             x.0.note.value == partial.value_pair.1 && x.0.note.token_id == partial.token_pair.1
         });
         });
@@ -169,7 +173,7 @@ impl Drk {
         let address = self.wallet_address(0).await?;
         let address = self.wallet_address(0).await?;
 
 
         // We'll also need our Merkle tree
         // We'll also need our Merkle tree
-        let tree = self.wallet_tree().await?;
+        let tree = self.get_money_tree().await?;
 
 
         let contract_id = *MONEY_CONTRACT_ID;
         let contract_id = *MONEY_CONTRACT_ID;
 
 
@@ -288,7 +292,7 @@ impl Drk {
             }
             }
 
 
             // Try to decrypt one of the outputs.
             // Try to decrypt one of the outputs.
-            let secret_keys = self.wallet_secrets().await?;
+            let secret_keys = self.get_money_secrets().await?;
             let mut skey: Option<SecretKey> = None;
             let mut skey: Option<SecretKey> = None;
             let mut note: Option<Note> = None;
             let mut note: Option<Note> = None;
             let mut output_idx = 0;
             let mut output_idx = 0;
@@ -404,7 +408,7 @@ impl Drk {
     /// note and prepending it to the transaction's signatures.
     /// note and prepending it to the transaction's signatures.
     pub async fn sign_swap(&self, tx: &mut Transaction) -> Result<()> {
     pub async fn sign_swap(&self, tx: &mut Transaction) -> Result<()> {
         // We need our secret keys to try and decrypt the note
         // We need our secret keys to try and decrypt the note
-        let secret_keys = self.wallet_secrets().await?;
+        let secret_keys = self.get_money_secrets().await?;
         let params: MoneyTransferParams = deserialize(&tx.calls[0].data[1..])?;
         let params: MoneyTransferParams = deserialize(&tx.calls[0].data[1..])?;
 
 
         // Our output should be outputs[0] so we try to decrypt that.
         // Our output should be outputs[0] so we try to decrypt that.

+ 3 - 3
bin/drk/src/rpc_transfer.rs

@@ -69,7 +69,7 @@ impl Drk {
 
 
         // First get all unspent OwnCoins to see what our balance is.
         // First get all unspent OwnCoins to see what our balance is.
         eprintln!("Fetching OwnCoins");
         eprintln!("Fetching OwnCoins");
-        let owncoins = self.wallet_coins(false).await?;
+        let owncoins = self.get_coins(false).await?;
         let mut owncoins: Vec<OwnCoin> = owncoins.iter().map(|x| x.0.clone()).collect();
         let mut owncoins: Vec<OwnCoin> = owncoins.iter().map(|x| x.0.clone()).collect();
         // We're only interested in the ones for the token_id we're sending
         // We're only interested in the ones for the token_id we're sending
         // And the ones not owned by some protocol (meaning spend-hook should be 0)
         // And the ones not owned by some protocol (meaning spend-hook should be 0)
@@ -95,10 +95,10 @@ impl Drk {
         }
         }
 
 
         // We'll also need our Merkle tree
         // We'll also need our Merkle tree
-        let tree = self.wallet_tree().await?;
+        let tree = self.get_money_tree().await?;
 
 
         // TODO: Which keypair to actually use?
         // TODO: Which keypair to actually use?
-        let secrets = self.wallet_secrets().await?;
+        let secrets = self.get_money_secrets().await?;
         let keypair = Keypair::new(secrets[0]);
         let keypair = Keypair::new(secrets[0]);
 
 
         let contract_id = *MONEY_CONTRACT_ID;
         let contract_id = *MONEY_CONTRACT_ID;

+ 716 - 0
bin/drk/src/wallet_dao.rs

@@ -0,0 +1,716 @@
+/* This file is part of DarkFi (https://dark.fi)
+ *
+ * Copyright (C) 2020-2023 Dyne.org foundation
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+ */
+use anyhow::{anyhow, Result};
+use darkfi::{rpc::jsonrpc::JsonRequest, tx::Transaction, wallet::walletdb::QueryType};
+use darkfi_dao_contract::{
+    dao_client::{
+        DAO_DAOS_COL_APPROVAL_RATIO_BASE, DAO_DAOS_COL_APPROVAL_RATIO_QUOT,
+        DAO_DAOS_COL_BULLA_BLIND, DAO_DAOS_COL_CALL_INDEX, DAO_DAOS_COL_DAO_ID,
+        DAO_DAOS_COL_GOV_TOKEN_ID, DAO_DAOS_COL_LEAF_POSITION, DAO_DAOS_COL_NAME,
+        DAO_DAOS_COL_PROPOSER_LIMIT, DAO_DAOS_COL_QUORUM, DAO_DAOS_COL_SECRET,
+        DAO_DAOS_COL_TX_HASH, DAO_DAOS_TABLE, DAO_PROPOSALS_COL_AMOUNT,
+        DAO_PROPOSALS_COL_BULLA_BLIND, DAO_PROPOSALS_COL_CALL_INDEX, DAO_PROPOSALS_COL_DAO_ID,
+        DAO_PROPOSALS_COL_LEAF_POSITION, DAO_PROPOSALS_COL_OUR_VOTE_ID,
+        DAO_PROPOSALS_COL_PROPOSAL_ID, DAO_PROPOSALS_COL_RECV_PUBLIC,
+        DAO_PROPOSALS_COL_SENDCOIN_TOKEN_ID, DAO_PROPOSALS_COL_SERIAL, DAO_PROPOSALS_COL_TX_HASH,
+        DAO_PROPOSALS_TABLE, DAO_TREES_COL_DAOS_TREE, DAO_TREES_COL_PROPOSALS_TREE,
+        DAO_TREES_TABLE,
+    },
+    dao_model::{DaoBulla, DaoMintParams, DaoProposeParams},
+    note::EncryptedNote2,
+    DaoFunction,
+};
+use darkfi_sdk::{
+    crypto::{
+        poseidon_hash, MerkleNode, MerkleTree, PublicKey, SecretKey, TokenId, DAO_CONTRACT_ID,
+    },
+    incrementalmerkletree::{Position, Tree},
+    pasta::pallas,
+};
+use darkfi_serial::{deserialize, serialize, SerialDecodable, SerialEncodable};
+use serde_json::json;
+
+use super::Drk;
+
+#[derive(Debug, Clone, SerialEncodable, SerialDecodable)]
+/// Parameters representing a DAO to be initialized
+pub struct DaoParams {
+    /// The minimum amount of governance tokens needed to open a proposal
+    pub proposer_limit: u64,
+    /// Minimal threshold of participating total tokens needed for a proposal to pass
+    pub quorum: u64,
+    /// The ratio of winning/total votes needed for a proposal to pass
+    pub approval_ratio_base: u64,
+    pub approval_ratio_quot: u64,
+    /// DAO's governance token ID
+    pub gov_token_id: TokenId,
+    /// Secret key for the DAO
+    pub secret_key: SecretKey,
+    /// DAO bulla blind
+    pub bulla_blind: pallas::Base,
+}
+
+#[derive(Debug, Clone)]
+/// Parameters representing an intialized DAO, optionally deployed on-chain
+pub struct Dao {
+    /// Numeric identifier for the DAO
+    pub id: u64,
+    /// Named identifier for the DAO
+    pub name: String,
+    /// The minimum amount of governance tokens needed to open a proposal
+    pub proposer_limit: u64,
+    /// Minimal threshold of participating total tokens needed for a proposal to pass
+    pub quorum: u64,
+    /// The ratio of winning/total votes needed for a proposal to pass
+    pub approval_ratio_base: u64,
+    pub approval_ratio_quot: u64,
+    /// DAO's governance token ID
+    pub gov_token_id: TokenId,
+    /// Secret key for the DAO
+    pub secret_key: SecretKey,
+    /// DAO bulla blind
+    pub bulla_blind: pallas::Base,
+    /// Leaf position of the DAO in the Merkle tree of DAOs
+    pub leaf_position: Option<Position>,
+    /// The transaction hash where the DAO was deployed
+    pub tx_hash: Option<blake3::Hash>,
+    /// The call index in the transaction where the DAO was deployed
+    pub call_index: Option<u32>,
+}
+
+impl Dao {
+    pub fn bulla(&self) -> DaoBulla {
+        let (x, y) = PublicKey::from_secret(self.secret_key).xy();
+
+        DaoBulla::from(poseidon_hash([
+            pallas::Base::from(self.proposer_limit),
+            pallas::Base::from(self.quorum),
+            pallas::Base::from(self.approval_ratio_base),
+            pallas::Base::from(self.approval_ratio_quot),
+            self.gov_token_id.inner(),
+            x,
+            y,
+            self.bulla_blind,
+        ]))
+    }
+}
+
+#[derive(Debug, Clone)]
+/// Parameters representing an initialized DAO proposal, optionally deployed on-chain
+pub struct DaoProposal {
+    /// Numeric identifier for the proposal
+    pub id: u64,
+    /// The DAO bulla related to this proposal
+    pub dao_bulla: DaoBulla,
+    /// Recipient of this proposal's funds
+    pub recipient: PublicKey,
+    /// Amount of this proposal
+    pub amount: u64,
+    /// Serial of this proposal
+    pub serial: pallas::Base,
+    /// Token ID to be sent
+    pub token_id: TokenId,
+    /// Proposal's bulla blind
+    pub bulla_blind: pallas::Base,
+    /// Leaf position of this proposal in the Merkle tree of proposals
+    pub leaf_position: Option<Position>,
+    /// Transaction hash where this proposal was proposed
+    pub tx_hash: Option<blake3::Hash>,
+    /// call index in the transaction where this proposal was proposed
+    pub call_index: Option<u32>,
+    /// The vote ID we've voted on this proposal
+    pub vote_id: Option<pallas::Base>,
+}
+
+impl DaoProposal {
+    pub fn bulla(&self) -> pallas::Base {
+        let (dest_x, dest_y) = self.recipient.xy();
+
+        poseidon_hash([
+            dest_x,
+            dest_y,
+            pallas::Base::from(self.amount),
+            self.serial,
+            self.token_id.inner(),
+            self.dao_bulla.inner(),
+            self.bulla_blind,
+            self.bulla_blind,
+        ])
+    }
+}
+
+#[derive(Debug, Clone)]
+/// Parameters representing a vote we've made on a DAO proposal
+pub struct DaoVote {
+    /// Numeric identifier for the vote
+    pub id: u64,
+    /// Numeric identifier for the proposal related to this vote
+    pub proposal_id: u64,
+    /// The vote
+    pub vote_option: bool,
+    /// Transaction hash where this vote was casted
+    pub tx_hash: Option<blake3::Hash>,
+    /// call index in the transaction where this vote was casted
+    pub call_index: Option<u32>,
+}
+
+impl Drk {
+    /// Initialize wallet with tables for the DAO contract
+    pub async fn initialize_dao(&self) -> Result<()> {
+        let wallet_schema = include_str!("../../../src/contract/dao/wallet.sql");
+
+        // We perform a request to darkfid with the schema to initialize
+        // the necessary tables in the wallet.
+        let req = JsonRequest::new("wallet.exec_sql", json!([wallet_schema]));
+        let rep = self.rpc_client.request(req).await?;
+
+        if rep == true {
+            eprintln!("Successfully initialized wallet schema for the DAO contract");
+        } else {
+            eprintln!("[initialize_dao] Got unexpected reply from darkfid: {}", rep);
+        }
+
+        // Check if we have to initialize the Merkle trees.
+        // We check if one exists, but we actually create two. This should be written
+        // a bit better and safer.
+        let mut tree_needs_init = false;
+        let query = format!("SELECT {} FROM {}", DAO_TREES_COL_DAOS_TREE, DAO_TREES_TABLE);
+        let params = json!([query, QueryType::Blob as u8, DAO_TREES_COL_DAOS_TREE]);
+        let req = JsonRequest::new("wallet.query_row_single", params);
+
+        // For now, on success, we don't care what's returned, but in the future
+        // we should actually check it.
+        // TODO: The RPC needs a better variant for errors so detailed inspection
+        //       can be done with error codes and all that.
+        if (self.rpc_client.request(req).await).is_err() {
+            tree_needs_init = true;
+        }
+
+        if tree_needs_init {
+            eprintln!("Initializing DAO Merkle trees");
+            let tree = MerkleTree::new(100);
+            self.put_dao_trees(&tree, &tree).await?;
+            eprintln!("Successfully initialized Merkle trees for the DAO contract");
+        }
+
+        Ok(())
+    }
+
+    /// Replace the DAO Merkle trees in the wallet.
+    pub async fn put_dao_trees(
+        &self,
+        daos_tree: &MerkleTree,
+        proposals_tree: &MerkleTree,
+    ) -> Result<()> {
+        let query = format!(
+            "DELETE FROM {}; INSERT INTO {} ({}, {}) VALUES (?1, ?2);",
+            DAO_TREES_TABLE, DAO_TREES_TABLE, DAO_TREES_COL_DAOS_TREE, DAO_TREES_COL_PROPOSALS_TREE,
+        );
+
+        let params = json!([
+            query,
+            QueryType::Blob as u8,
+            serialize(daos_tree),
+            QueryType::Blob as u8,
+            serialize(proposals_tree),
+        ]);
+
+        let req = JsonRequest::new("wallet.exec_sql", params);
+        let _ = self.rpc_client.request(req).await?;
+
+        Ok(())
+    }
+
+    /// Fetch DAO Merkle trees from the wallet
+    pub async fn get_dao_trees(&self) -> Result<(MerkleTree, MerkleTree)> {
+        let query = format!("SELECT * FROM {}", DAO_TREES_TABLE);
+
+        let params = json!([
+            query,
+            QueryType::Blob as u8,
+            DAO_TREES_COL_DAOS_TREE,
+            QueryType::Blob as u8,
+            DAO_TREES_COL_PROPOSALS_TREE,
+        ]);
+
+        let req = JsonRequest::new("wallet.query_row_single", params);
+        let rep = self.rpc_client.request(req).await?;
+
+        let daos_tree_bytes: Vec<u8> = serde_json::from_value(rep[0].clone())?;
+        let daos_tree = deserialize(&daos_tree_bytes)?;
+
+        let proposals_tree_bytes: Vec<u8> = serde_json::from_value(rep[1].clone())?;
+        let proposals_tree = deserialize(&proposals_tree_bytes)?;
+
+        Ok((daos_tree, proposals_tree))
+    }
+
+    /// Reset the DAO Merkle trees in the wallet
+    pub async fn reset_dao_trees(&self) -> Result<()> {
+        eprintln!("Resetting DAO Merkle trees");
+        let tree = MerkleTree::new(100);
+        self.put_dao_trees(&tree, &tree).await?;
+        eprintln!("Successfully reset DAO Merkle trees");
+
+        Ok(())
+    }
+
+    /// Import given DAO params into the wallet with a given name.
+    pub async fn import_dao(&self, dao_name: String, dao_params: DaoParams) -> Result<()> {
+        // First let's check if we've imported this DAO with the given name before.
+        let daos = self.get_daos().await?;
+        if daos.iter().find(|x| x.name == dao_name).is_some() {
+            return Err(anyhow!("This DAO has already been imported"))
+        }
+
+        eprintln!("Importing \"{}\" DAO into the wallet", dao_name);
+
+        let query = format!(
+            "INSERT INTO {} ({}, {}, {}, {}, {}, {}, {}, {}) VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8);",
+            DAO_DAOS_TABLE,
+            DAO_DAOS_COL_NAME,
+            DAO_DAOS_COL_PROPOSER_LIMIT,
+            DAO_DAOS_COL_QUORUM,
+            DAO_DAOS_COL_APPROVAL_RATIO_BASE,
+            DAO_DAOS_COL_APPROVAL_RATIO_QUOT,
+            DAO_DAOS_COL_GOV_TOKEN_ID,
+            DAO_DAOS_COL_SECRET,
+            DAO_DAOS_COL_BULLA_BLIND,
+        );
+
+        let params = json!([
+            query,
+            QueryType::Blob as u8,
+            serialize(&dao_name),
+            QueryType::Integer as u8,
+            dao_params.proposer_limit,
+            QueryType::Integer as u8,
+            dao_params.quorum,
+            QueryType::Integer as u8,
+            dao_params.approval_ratio_base,
+            QueryType::Integer as u8,
+            dao_params.approval_ratio_quot,
+            QueryType::Blob as u8,
+            serialize(&dao_params.gov_token_id),
+            QueryType::Blob as u8,
+            serialize(&dao_params.secret_key),
+            QueryType::Blob as u8,
+            serialize(&dao_params.bulla_blind),
+        ]);
+
+        let req = JsonRequest::new("wallet.exec_sql", params);
+        let _ = self.rpc_client.request(req).await?;
+        eprintln!("DAO imported successfully");
+
+        Ok(())
+    }
+
+    /// List DAO(s) imported in the wallet. If an ID is given, just print the
+    /// metadata for that specific one, if found.
+    pub async fn dao_list(&self, dao_id: Option<u64>) -> Result<()> {
+        if dao_id.is_some() {
+            return self.dao_list_single(dao_id.unwrap()).await
+        }
+
+        let daos = self.get_daos().await?;
+        for dao in daos {
+            println!("[{}] {}", dao.id, dao.name);
+        }
+
+        Ok(())
+    }
+
+    async fn dao_list_single(&self, dao_id: u64) -> Result<()> {
+        let dao = self.get_dao_by_id(dao_id).await?;
+
+        println!("DAO Parameters:");
+        println!("Name: {}", dao.name);
+        println!("Proposer limit: {}", dao.proposer_limit);
+        println!("Quorum: {}", dao.quorum);
+        println!(
+            "Approval ratio: {}",
+            dao.approval_ratio_base as f64 / dao.approval_ratio_quot as f64
+        );
+        println!("Governance token ID: {}", dao.gov_token_id);
+        println!("Secret key: {}", dao.secret_key);
+        println!("Bulla blind: {:?}", dao.bulla_blind);
+        println!("Leaf position: {:?}", dao.leaf_position);
+        println!("Tx hash: {:?}", dao.tx_hash);
+        println!("Call idx: {:?}", dao.call_index);
+
+        Ok(())
+    }
+
+    /// Fetch a DAO given a numeric ID
+    pub async fn get_dao_by_id(&self, dao_id: u64) -> Result<Dao> {
+        let daos = self.get_daos().await?;
+
+        let Some(dao) = daos.iter().find(|x| x.id == dao_id) else {
+            return Err(anyhow!("DAO not found in wallet"))
+        };
+
+        Ok(dao.clone())
+    }
+
+    /// Fetch all known DAOs from the wallet.
+    pub async fn get_daos(&self) -> Result<Vec<Dao>> {
+        let query = format!("SELECT * FROM {}", DAO_DAOS_TABLE);
+
+        let params = json!([
+            query,
+            QueryType::Integer as u8,
+            DAO_DAOS_COL_DAO_ID,
+            QueryType::Blob as u8,
+            DAO_DAOS_COL_NAME,
+            QueryType::Integer as u8,
+            DAO_DAOS_COL_PROPOSER_LIMIT,
+            QueryType::Integer as u8,
+            DAO_DAOS_COL_QUORUM,
+            QueryType::Integer as u8,
+            DAO_DAOS_COL_APPROVAL_RATIO_BASE,
+            QueryType::Integer as u8,
+            DAO_DAOS_COL_APPROVAL_RATIO_QUOT,
+            QueryType::Blob as u8,
+            DAO_DAOS_COL_GOV_TOKEN_ID,
+            QueryType::Blob as u8,
+            DAO_DAOS_COL_SECRET,
+            QueryType::Blob as u8,
+            DAO_DAOS_COL_BULLA_BLIND,
+            QueryType::OptionBlob as u8,
+            DAO_DAOS_COL_LEAF_POSITION,
+            QueryType::OptionBlob as u8,
+            DAO_DAOS_COL_TX_HASH,
+            QueryType::OptionInteger as u8,
+            DAO_DAOS_COL_CALL_INDEX,
+        ]);
+
+        let req = JsonRequest::new("wallet.query_row_multi", params);
+        let rep = self.rpc_client.request(req).await?;
+
+        let Some(rows) = rep.as_array() else {
+            return Err(anyhow!("[get_daos] Unexpected response from darkfid: {}", rep));
+        };
+
+        let mut daos = Vec::with_capacity(rows.len());
+
+        for row in rows {
+            let Some(row) = row.as_array() else {
+                return Err(anyhow!("[get_daos] Unexpected response from darkfid: {}", rep));
+            };
+
+            let id: u64 = serde_json::from_value(row[0].clone())?;
+
+            let name_bytes: Vec<u8> = serde_json::from_value(row[1].clone())?;
+            let name = deserialize(&name_bytes)?;
+
+            let proposer_limit = serde_json::from_value(row[2].clone())?;
+            let quorum = serde_json::from_value(row[3].clone())?;
+            let approval_ratio_base = serde_json::from_value(row[4].clone())?;
+            let approval_ratio_quot = serde_json::from_value(row[5].clone())?;
+
+            let gov_token_bytes: Vec<u8> = serde_json::from_value(row[6].clone())?;
+            let gov_token_id = deserialize(&gov_token_bytes)?;
+
+            let secret_bytes: Vec<u8> = serde_json::from_value(row[7].clone())?;
+            let secret_key = deserialize(&secret_bytes)?;
+
+            let bulla_blind_bytes: Vec<u8> = serde_json::from_value(row[8].clone())?;
+            let bulla_blind = deserialize(&bulla_blind_bytes)?;
+
+            let leaf_position_bytes: Vec<u8> = serde_json::from_value(row[9].clone())?;
+            let tx_hash_bytes: Vec<u8> = serde_json::from_value(row[10].clone())?;
+            let call_index = serde_json::from_value(row[11].clone())?;
+
+            let leaf_position = if leaf_position_bytes.is_empty() {
+                None
+            } else {
+                Some(deserialize(&leaf_position_bytes)?)
+            };
+
+            let tx_hash =
+                if tx_hash_bytes.is_empty() { None } else { Some(deserialize(&tx_hash_bytes)?) };
+
+            let dao = Dao {
+                id,
+                name,
+                proposer_limit,
+                quorum,
+                approval_ratio_base,
+                approval_ratio_quot,
+                gov_token_id,
+                secret_key,
+                bulla_blind,
+                leaf_position,
+                tx_hash,
+                call_index,
+            };
+
+            daos.push(dao);
+        }
+
+        // Here we sort the vec by ID. The SQL SELECT statement does not guarantee
+        // this, so just do it here.
+        daos.sort_by(|a, b| a.id.cmp(&b.id));
+        Ok(daos)
+    }
+
+    /// Fetch all known DAO proposals from the wallet given a DAO ID
+    pub async fn get_dao_proposals(&self, dao_id: u64) -> Result<Vec<DaoProposal>> {
+        let daos = self.get_daos().await?;
+        let Some(dao) = daos.get(dao_id as usize - 1) else {
+            return Err(anyhow!("DAO with ID {} not found in wallet", dao_id))
+        };
+
+        let query = format!(
+            "SELECT * FROM {} WHERE {} = {}",
+            DAO_PROPOSALS_TABLE, DAO_PROPOSALS_COL_DAO_ID, dao_id
+        );
+
+        let params = json!([
+            query,
+            QueryType::Integer as u8,
+            DAO_PROPOSALS_COL_PROPOSAL_ID,
+            QueryType::Integer as u8,
+            DAO_PROPOSALS_COL_DAO_ID,
+            QueryType::Blob as u8,
+            DAO_PROPOSALS_COL_RECV_PUBLIC,
+            QueryType::Blob as u8,
+            DAO_PROPOSALS_COL_AMOUNT,
+            QueryType::Blob as u8,
+            DAO_PROPOSALS_COL_SERIAL,
+            QueryType::Blob as u8,
+            DAO_PROPOSALS_COL_SENDCOIN_TOKEN_ID,
+            QueryType::Blob as u8,
+            DAO_PROPOSALS_COL_BULLA_BLIND,
+            QueryType::OptionBlob as u8,
+            DAO_PROPOSALS_COL_LEAF_POSITION,
+            QueryType::OptionBlob as u8,
+            DAO_PROPOSALS_COL_TX_HASH,
+            QueryType::OptionInteger as u8,
+            DAO_PROPOSALS_COL_CALL_INDEX,
+            QueryType::OptionBlob as u8,
+            DAO_PROPOSALS_COL_OUR_VOTE_ID,
+        ]);
+
+        let req = JsonRequest::new("wallet.query_row_multi", params);
+        let rep = self.rpc_client.request(req).await?;
+
+        let Some(rows) = rep.as_array() else {
+            return Err(anyhow!("[get_proposals] Unexpected response from darkfid: {}", rep));
+        };
+
+        let mut proposals = Vec::with_capacity(rows.len());
+
+        for row in rows {
+            let Some(row) = row.as_array() else {
+                return Err(anyhow!("[get_proposals] Unexpected response from darkfid: {}", rep));
+            };
+
+            let id: u64 = serde_json::from_value(row[0].clone())?;
+
+            let dao_id: u64 = serde_json::from_value(row[1].clone())?;
+            assert!(dao_id == dao.id);
+            let dao_bulla = dao.bulla();
+
+            let recipient_bytes: Vec<u8> = serde_json::from_value(row[2].clone())?;
+            let recipient = deserialize(&recipient_bytes)?;
+
+            let amount_bytes: Vec<u8> = serde_json::from_value(row[3].clone())?;
+            let amount = deserialize(&amount_bytes)?;
+
+            let serial_bytes: Vec<u8> = serde_json::from_value(row[4].clone())?;
+            let serial = deserialize(&serial_bytes)?;
+
+            let token_id_bytes: Vec<u8> = serde_json::from_value(row[5].clone())?;
+            let token_id = deserialize(&token_id_bytes)?;
+
+            let bulla_blind_bytes: Vec<u8> = serde_json::from_value(row[6].clone())?;
+            let bulla_blind = deserialize(&bulla_blind_bytes)?;
+
+            let leaf_position_bytes: Vec<u8> = serde_json::from_value(row[7].clone())?;
+            let tx_hash_bytes: Vec<u8> = serde_json::from_value(row[8].clone())?;
+            let call_index = serde_json::from_value(row[9].clone())?;
+            let vote_id_bytes: Vec<u8> = serde_json::from_value(row[10].clone())?;
+
+            let leaf_position = if leaf_position_bytes.is_empty() {
+                None
+            } else {
+                Some(deserialize(&leaf_position_bytes)?)
+            };
+
+            let tx_hash =
+                if tx_hash_bytes.is_empty() { None } else { Some(deserialize(&tx_hash_bytes)?) };
+
+            let vote_id =
+                if vote_id_bytes.is_empty() { None } else { Some(deserialize(&vote_id_bytes)?) };
+
+            let proposal = DaoProposal {
+                id,
+                dao_bulla,
+                recipient,
+                amount,
+                serial,
+                token_id,
+                bulla_blind,
+                leaf_position,
+                tx_hash,
+                call_index,
+                vote_id,
+            };
+
+            proposals.push(proposal);
+        }
+
+        // Here we sort the vec by ID. The SQL SELECT statement does not guarantee
+        // this, so just do it here.
+        proposals.sort_by(|a, b| a.id.cmp(&b.id));
+        Ok(proposals)
+    }
+
+    // Fetch all known DAO proposal votes from the wallet given a proposal ID
+    //pub async fn get_dao_proposal_votes(&self, _proposal_id: u64) -> Result<Vec<Vote>> {
+    //todo!()
+    //}
+
+    /// Append data related to DAO contract transactions into the wallet database.
+    /// Optionally, if `confirm` is true, also append the data in the Merkle trees, etc.
+    pub async fn apply_tx_dao_data(&self, tx: &Transaction, confirm: bool) -> Result<()> {
+        let cid = *DAO_CONTRACT_ID;
+        let mut daos = self.get_daos().await?;
+        let (mut daos_tree, mut proposals_tree) = self.get_dao_trees().await?;
+
+        // DAOs that have been minted
+        let mut new_dao_bullas: Vec<(DaoBulla, Option<blake3::Hash>, u32)> = vec![];
+        // DAO proposals that have been minted
+        let mut new_dao_proposals: Vec<(DaoProposeParams, Option<blake3::Hash>, u32)> = vec![];
+
+        // Run through the transaction and see what we got:
+        for (i, call) in tx.calls.iter().enumerate() {
+            if call.contract_id == cid && call.data[0] == DaoFunction::Mint as u8 {
+                eprintln!("Found Dao::Mint in call {}", i);
+                let params: DaoMintParams = deserialize(&call.data[1..])?;
+                let tx_hash = if confirm { Some(blake3::hash(&serialize(tx))) } else { None };
+                new_dao_bullas.push((params.dao_bulla, tx_hash, i as u32));
+                continue
+            }
+
+            if call.contract_id == cid && call.data[0] == DaoFunction::Propose as u8 {
+                eprintln!("Found Dao::Propose in call {}", i);
+                let params: DaoProposeParams = deserialize(&call.data[1..])?;
+                let tx_hash = if confirm { Some(blake3::hash(&serialize(tx))) } else { None };
+                new_dao_proposals.push((params, tx_hash, i as u32));
+                continue
+            }
+
+            if call.contract_id == cid && call.data[0] == DaoFunction::Vote as u8 {
+                eprintln!("[UNIMPLEMENTED] Found Dao::Vote in call {}", i);
+                continue
+            }
+
+            if call.contract_id == cid && call.data[0] == DaoFunction::Exec as u8 {
+                eprintln!("[UNIMPLEMENTED] Found Dao::Exec in call {}", i);
+                continue
+            }
+        }
+
+        // This code should only be executed when finalized blocks are being scanned.
+        // Here we write the tx metadata, and actually do Merkle tree appends so we
+        // have to make sure it's the same for everyone.
+        if confirm {
+            for new_bulla in new_dao_bullas {
+                daos_tree.append(&MerkleNode::from(new_bulla.0.inner()));
+                for dao in daos.iter_mut() {
+                    if dao.bulla() == new_bulla.0 {
+                        eprintln!(
+                            "Found minted DAO {:?}, noting down for wallet update",
+                            new_bulla.0
+                        );
+                        // We have this DAO imported in our wallet. Add the metadata:
+                        dao.leaf_position = daos_tree.witness();
+                        dao.tx_hash = new_bulla.1;
+                        dao.call_index = Some(new_bulla.2);
+                    }
+                }
+            }
+
+            for proposal in new_dao_proposals {
+                proposals_tree.append(&MerkleNode::from(proposal.0.proposal_bulla));
+                // FIXME: EncryptedNote2 should perhaps be something generic?
+                let enc_note = EncryptedNote2 {
+                    ciphertext: proposal.0.ciphertext,
+                    ephem_public: proposal.0.ephem_public,
+                };
+
+                // TODO: Decrypt proposal and see if it's for us
+            }
+        }
+
+        // Put new stuff into the wallet.
+        // Note that this is not done for Dao::Mint, as that should have already
+        // been done through `drk dao import`.
+        //self.put_dao_proposals(&new_dao_proposals).await?;
+
+        if confirm {
+            self.confirm_daos(&daos).await?;
+            //self.confirm_dao_proposals(&new_dao_proposals).await?;
+        }
+
+        Ok(())
+    }
+
+    /// Confirm already imported DAO metadata into the wallet.
+    /// Here we just write the leaf position, tx hash, and call index.
+    /// Panics if the fields are None.
+    pub async fn confirm_daos(&self, daos: &[Dao]) -> Result<()> {
+        for dao in daos {
+            let query = format!(
+                "UPDATE {} SET {} = ?1, {} = ?2, {} = ?3 WHERE {} = ?4;",
+                DAO_DAOS_TABLE,
+                DAO_DAOS_COL_LEAF_POSITION,
+                DAO_DAOS_COL_TX_HASH,
+                DAO_DAOS_COL_CALL_INDEX,
+                DAO_DAOS_COL_DAO_ID,
+            );
+
+            let params = json!([
+                query,
+                QueryType::Blob as u8,
+                serialize(&dao.leaf_position.unwrap()),
+                QueryType::Blob as u8,
+                serialize(&dao.tx_hash.unwrap()),
+                QueryType::Integer as u8,
+                dao.call_index.unwrap(),
+            ]);
+
+            let req = JsonRequest::new("wallet.exec_sql", params);
+            let _ = self.rpc_client.request(req).await?;
+        }
+
+        Ok(())
+    }
+
+    /// Import given DAO proposals into the wallet
+    pub async fn put_dao_proposals(&self, proposals: &[DaoProposal]) -> Result<()> {
+        todo!()
+    }
+
+    /// Confirm already imported DAO proposal metadata into the wallet.
+    pub async fn confirm_dao_proposals(&self, proposals: &[DaoProposal]) -> Result<()> {
+        todo!()
+    }
+}

+ 199 - 458
bin/drk/src/rpc_wallet.rs → bin/drk/src/wallet_money.rs

@@ -15,18 +15,10 @@
  * You should have received a copy of the GNU Affero General Public License
  * You should have received a copy of the GNU Affero General Public License
  * along with this program.  If not, see <https://www.gnu.org/licenses/>.
  * along with this program.  If not, see <https://www.gnu.org/licenses/>.
  */
  */
-
 use std::collections::HashMap;
 use std::collections::HashMap;
 
 
 use anyhow::{anyhow, Result};
 use anyhow::{anyhow, Result};
-use darkfi::{rpc::jsonrpc::JsonRequest, util::parse::encode_base10, wallet::walletdb::QueryType};
-use darkfi_dao_contract::dao_client::{
-    DAO_DAOS_COL_APPROVAL_RATIO_BASE, DAO_DAOS_COL_APPROVAL_RATIO_QUOT, DAO_DAOS_COL_BULLA_BLIND,
-    DAO_DAOS_COL_CALL_INDEX, DAO_DAOS_COL_DAO_ID, DAO_DAOS_COL_GOV_TOKEN_ID,
-    DAO_DAOS_COL_LEAF_POSITION, DAO_DAOS_COL_NAME, DAO_DAOS_COL_PROPOSER_LIMIT,
-    DAO_DAOS_COL_QUORUM, DAO_DAOS_COL_SECRET, DAO_DAOS_COL_TX_HASH, DAO_DAOS_TABLE,
-    DAO_TREES_COL_DAOS_TREE, DAO_TREES_COL_PROPOSALS_TREE, DAO_TREES_TABLE,
-};
+use darkfi::{rpc::jsonrpc::JsonRequest, wallet::walletdb::QueryType};
 use darkfi_money_contract::client::{
 use darkfi_money_contract::client::{
     Coin, Note, OwnCoin, MONEY_COINS_COL_COIN, MONEY_COINS_COL_COIN_BLIND,
     Coin, Note, OwnCoin, MONEY_COINS_COL_COIN, MONEY_COINS_COL_COIN_BLIND,
     MONEY_COINS_COL_IS_SPENT, MONEY_COINS_COL_LEAF_POSITION, MONEY_COINS_COL_MEMO,
     MONEY_COINS_COL_IS_SPENT, MONEY_COINS_COL_LEAF_POSITION, MONEY_COINS_COL_MEMO,
@@ -34,29 +26,23 @@ use darkfi_money_contract::client::{
     MONEY_COINS_COL_SPEND_HOOK, MONEY_COINS_COL_TOKEN_BLIND, MONEY_COINS_COL_TOKEN_ID,
     MONEY_COINS_COL_SPEND_HOOK, MONEY_COINS_COL_TOKEN_BLIND, MONEY_COINS_COL_TOKEN_ID,
     MONEY_COINS_COL_USER_DATA, MONEY_COINS_COL_VALUE, MONEY_COINS_COL_VALUE_BLIND,
     MONEY_COINS_COL_USER_DATA, MONEY_COINS_COL_VALUE, MONEY_COINS_COL_VALUE_BLIND,
     MONEY_COINS_TABLE, MONEY_INFO_COL_LAST_SCANNED_SLOT, MONEY_INFO_TABLE,
     MONEY_COINS_TABLE, MONEY_INFO_COL_LAST_SCANNED_SLOT, MONEY_INFO_TABLE,
-    MONEY_KEYS_COL_IS_DEFAULT, MONEY_KEYS_COL_PUBLIC, MONEY_KEYS_COL_SECRET, MONEY_KEYS_TABLE,
-    MONEY_TREE_COL_TREE, MONEY_TREE_TABLE,
+    MONEY_KEYS_COL_IS_DEFAULT, MONEY_KEYS_COL_KEY_ID, MONEY_KEYS_COL_PUBLIC, MONEY_KEYS_COL_SECRET,
+    MONEY_KEYS_TABLE, MONEY_TREE_COL_TREE, MONEY_TREE_TABLE,
 };
 };
 use darkfi_sdk::{
 use darkfi_sdk::{
-    crypto::{
-        constants::MERKLE_DEPTH, Keypair, MerkleNode, MerkleTree, Nullifier, PublicKey, SecretKey,
-        TokenId,
-    },
+    crypto::{Keypair, MerkleTree, Nullifier, PublicKey, SecretKey, TokenId},
     incrementalmerkletree,
     incrementalmerkletree,
-    incrementalmerkletree::bridgetree::BridgeTree,
     pasta::pallas,
     pasta::pallas,
 };
 };
 use darkfi_serial::{deserialize, serialize};
 use darkfi_serial::{deserialize, serialize};
-use prettytable::{format, row, Table};
 use rand::rngs::OsRng;
 use rand::rngs::OsRng;
 use serde_json::json;
 use serde_json::json;
 
 
 use super::Drk;
 use super::Drk;
-use crate::dao::Dao;
 
 
 impl Drk {
 impl Drk {
-    /// Initialize wallet with tables for the Money Contract.
-    async fn wallet_initialize_money(&self) -> Result<()> {
+    /// Initialize wallet with tables for the Money contract
+    pub async fn initialize_money(&self) -> Result<()> {
         let wallet_schema = include_str!("../../../src/contract/money/wallet.sql");
         let wallet_schema = include_str!("../../../src/contract/money/wallet.sql");
 
 
         // We perform a request to darkfid with the schema to initialize
         // We perform a request to darkfid with the schema to initialize
@@ -65,21 +51,21 @@ impl Drk {
         let rep = self.rpc_client.request(req).await?;
         let rep = self.rpc_client.request(req).await?;
 
 
         if rep == true {
         if rep == true {
-            println!("Successfully initialized wallet schema for the Money Contract");
+            eprintln!("Successfully initialized wallet schema for the Money contract");
         } else {
         } else {
-            println!("Got unxpected reply from darkfid: {}", rep);
+            eprintln!("[initialize_money] Got unexpected reply from darkfid: {}", rep);
         }
         }
 
 
         // Check if we have to initialize the Merkle tree.
         // Check if we have to initialize the Merkle tree.
-        // We check if we find a row in the tree table, and if not, we create
-        // a new tree and push it into the table.
+        // We check if we find a row in the tree table, and if not, we create a
+        // new tree and push it into the table.
         let mut tree_needs_init = false;
         let mut tree_needs_init = false;
-        let query = format!("SELECT * FROM {}", MONEY_TREE_TABLE);
+        let query = format!("SELECT {} FROM {}", MONEY_TREE_COL_TREE, MONEY_TREE_TABLE);
         let params = json!([query, QueryType::Blob as u8, MONEY_TREE_COL_TREE]);
         let params = json!([query, QueryType::Blob as u8, MONEY_TREE_COL_TREE]);
         let req = JsonRequest::new("wallet.query_row_single", params);
         let req = JsonRequest::new("wallet.query_row_single", params);
 
 
-        // For now, on success, we don't care what's returned, but maybe in
-        // the future we should actually check it?
+        // For now, on success, we don't care what's returned, but in the future
+        // we should actually check it.
         // TODO: The RPC needs a better variant for errors so detailed inspection
         // TODO: The RPC needs a better variant for errors so detailed inspection
         //       can be done with error codes and all that.
         //       can be done with error codes and all that.
         if (self.rpc_client.request(req).await).is_err() {
         if (self.rpc_client.request(req).await).is_err() {
@@ -87,17 +73,20 @@ impl Drk {
         }
         }
 
 
         if tree_needs_init {
         if tree_needs_init {
-            println!("Initializing Merkle tree");
+            eprintln!("Initializing Money Merkle tree");
             let tree = MerkleTree::new(100);
             let tree = MerkleTree::new(100);
             self.put_money_tree(&tree).await?;
             self.put_money_tree(&tree).await?;
-            println!("Successfully initialized Merkle tree for Money Contract");
+            eprintln!("Successfully initialized Merkle tree for the Money contract");
         }
         }
 
 
-        if (self.wallet_last_scanned_slot().await).is_err() {
+        // We maintain the last scanned slot as part of the Money contract,
+        // but at this moment it is also somewhat applicable to DAO scans.
+        if (self.last_scanned_slot().await).is_err() {
             let query = format!(
             let query = format!(
                 "INSERT INTO {} ({}) VALUES (?1);",
                 "INSERT INTO {} ({}) VALUES (?1);",
                 MONEY_INFO_TABLE, MONEY_INFO_COL_LAST_SCANNED_SLOT
                 MONEY_INFO_TABLE, MONEY_INFO_COL_LAST_SCANNED_SLOT
             );
             );
+
             let params = json!([query, QueryType::Integer as u8, 0]);
             let params = json!([query, QueryType::Integer as u8, 0]);
             let req = JsonRequest::new("wallet.exec_sql", params);
             let req = JsonRequest::new("wallet.exec_sql", params);
             let _ = self.rpc_client.request(req).await?;
             let _ = self.rpc_client.request(req).await?;
@@ -106,67 +95,15 @@ impl Drk {
         Ok(())
         Ok(())
     }
     }
 
 
-    /// Initialize wallet with tables for the DAO Contract.
-    async fn wallet_initialize_dao(&self) -> Result<()> {
-        let wallet_schema = include_str!("../../../src/contract/dao/wallet.sql");
-
-        // We perform a request to darkfid with the schema to initialize
-        // the necessary tables in the wallet.
-        let req = JsonRequest::new("wallet.exec_sql", json!([wallet_schema]));
-        let rep = self.rpc_client.request(req).await?;
-
-        if rep == true {
-            println!("Successfully initialized wallet schema for the DAO Contract");
-        } else {
-            println!("Got unxpected reply from darkfid: {}", rep);
-        }
-
-        // Check if we have to initialize the Merkle trees. We check if one exists,
-        // but we actually have to create two.
-        let mut tree_needs_init = false;
-        let query = format!("SELECT {} FROM {}", DAO_TREES_COL_DAOS_TREE, DAO_TREES_TABLE);
-        let params = json!([query, QueryType::Blob as u8, DAO_TREES_COL_DAOS_TREE]);
-        let req = JsonRequest::new("wallet.query_row_single", params);
-
-        // For now, on success, we don't care what's returned, but maybe in
-        // the future we should actually check it?
-        // TODO: The RPC needs a better variant for errors so detailed inspection
-        //       can be done with error codes and all that.
-        if (self.rpc_client.request(req).await).is_err() {
-            tree_needs_init = true;
-        }
-
-        if tree_needs_init {
-            println!("Initializing DAO Merkle trees");
-            let daos_tree = MerkleTree::new(100);
-            let proposals_tree = MerkleTree::new(100);
-            self.put_dao_trees(&daos_tree, &proposals_tree).await?;
-            println!("Successfully initialized Merkle trees for DAO Contract");
-        }
-
-        Ok(())
-    }
-
-    /// Main orchestration for wallet initialization. Internally, it initializes
-    /// the wallet structure for the Money contract and the DAO contract.
-    /// This should be performed initially before doing other operations.
-    pub async fn wallet_initialize(&self) -> Result<()> {
-        self.wallet_initialize_money().await?;
-        self.wallet_initialize_dao().await?;
-        Ok(())
-    }
-
-    /// Generate a new wallet keypair and put it in the according wallet table.
-    pub async fn wallet_keygen(&self) -> Result<()> {
-        println!("Generating a new keypair");
+    /// Generate a new keypair and place it into the wallet.
+    pub async fn money_keygen(&self) -> Result<()> {
+        eprintln!("Generating a new keypair");
         // TODO: We might want to have hierarchical deterministic key derivation.
         // TODO: We might want to have hierarchical deterministic key derivation.
         let keypair = Keypair::random(&mut OsRng);
         let keypair = Keypair::random(&mut OsRng);
-        let public = serialize(&keypair.public);
-        let secret = serialize(&keypair.secret);
         let is_default = 0;
         let is_default = 0;
 
 
         let query = format!(
         let query = format!(
-            "INSERT INTO {} ({}, {}, {}) VALUES (?1, ?2, ?3)",
+            "INSERT INTO {} ({}, {}, {}) VALUES (?1, ?2, ?3);",
             MONEY_KEYS_TABLE,
             MONEY_KEYS_TABLE,
             MONEY_KEYS_COL_IS_DEFAULT,
             MONEY_KEYS_COL_IS_DEFAULT,
             MONEY_KEYS_COL_PUBLIC,
             MONEY_KEYS_COL_PUBLIC,
@@ -178,28 +115,118 @@ impl Drk {
             QueryType::Integer as u8,
             QueryType::Integer as u8,
             is_default,
             is_default,
             QueryType::Blob as u8,
             QueryType::Blob as u8,
-            public,
+            serialize(&keypair.public),
             QueryType::Blob as u8,
             QueryType::Blob as u8,
-            secret,
+            serialize(&keypair.secret),
         ]);
         ]);
 
 
         let req = JsonRequest::new("wallet.exec_sql", params);
         let req = JsonRequest::new("wallet.exec_sql", params);
         let rep = self.rpc_client.request(req).await?;
         let rep = self.rpc_client.request(req).await?;
 
 
         if rep == true {
         if rep == true {
-            println!("Successfully added new keypair to wallet");
+            eprintln!("Successfully added new keypair to wallet");
         } else {
         } else {
-            println!("Got unexpected reply from darkfid: {}", rep);
+            eprintln!("[money_keygen] Got unexpected reply from darkfid: {}", rep);
         }
         }
 
 
-        println!("New address: {}", keypair.public);
+        eprintln!("New address:");
+        println!("{}", keypair.public);
+
         Ok(())
         Ok(())
     }
     }
 
 
-    /// Fetch all coins and their metadata from the wallet, optionally also spent ones.
-    /// The boolean in the return tuple marks if the coin is marked as spent.
-    pub async fn wallet_coins(&self, fetch_spent: bool) -> Result<Vec<(OwnCoin, bool)>> {
-        eprintln!("Fetching OwnCoins from wallet");
+    /// Fetch all secret keys from the wallet
+    pub async fn get_money_secrets(&self) -> Result<Vec<SecretKey>> {
+        let query = format!("SELECT {} FROM {};", MONEY_KEYS_COL_SECRET, MONEY_KEYS_TABLE);
+        let params = json!([query, QueryType::Blob as u8, MONEY_KEYS_COL_SECRET]);
+        let req = JsonRequest::new("wallet.query_row_multi", params);
+        let rep = self.rpc_client.request(req).await?;
+
+        // The returned thing should be an array of found rows.
+        let Some(rows) = rep.as_array() else {
+            return Err(anyhow!("[get_money_secrets] Unexpected response from darkfid: {}", rep));
+        };
+
+        let mut secrets = Vec::with_capacity(rows.len());
+
+        // Let's scan through the rows and see if we got anything.
+        for row in rows {
+            let secret_bytes: Vec<u8> = serde_json::from_value(row[0].clone())?;
+            let secret = deserialize(&secret_bytes)?;
+            secrets.push(secret);
+        }
+
+        Ok(secrets)
+    }
+
+    /// Import given secret keys into the wallet.
+    /// The query uses INSERT, so if the key already exists, it will be skipped.
+    /// Returns the respective PublicKey objects for the imported keys.
+    pub async fn import_money_secrets(&self, secrets: Vec<SecretKey>) -> Result<Vec<PublicKey>> {
+        let mut ret = Vec::with_capacity(secrets.len());
+
+        for secret in secrets {
+            ret.push(PublicKey::from_secret(secret));
+            let is_default = 0;
+            let public = serialize(&PublicKey::from_secret(secret));
+            let secret = serialize(&secret);
+
+            let query = format!(
+                "INSERT INTO {} ({}, {}, {}) VALUES (?1, ?2, ?3);",
+                MONEY_KEYS_TABLE,
+                MONEY_KEYS_COL_IS_DEFAULT,
+                MONEY_KEYS_COL_PUBLIC,
+                MONEY_KEYS_COL_SECRET,
+            );
+
+            let params = json!([
+                query,
+                QueryType::Integer as u8,
+                is_default,
+                QueryType::Blob as u8,
+                public,
+                QueryType::Blob as u8,
+                secret,
+            ]);
+
+            let req = JsonRequest::new("wallet.exec_sql", params);
+            let _ = self.rpc_client.request(req).await?;
+        }
+
+        Ok(ret)
+    }
+
+    /// Fetch pubkeys from the wallet and return the requested index.
+    pub async fn wallet_address(&self, idx: u64) -> Result<PublicKey> {
+        let query = format!(
+            "SELECT {} FROM {} WHERE {} = {};",
+            MONEY_KEYS_COL_PUBLIC, MONEY_KEYS_TABLE, MONEY_KEYS_COL_KEY_ID, idx
+        );
+
+        let params = json!([query, QueryType::Blob as u8, MONEY_KEYS_COL_PUBLIC]);
+        let req = JsonRequest::new("wallet.query_row_single", params);
+        let rep = self.rpc_client.request(req).await?;
+
+        let Some(arr) = rep.as_array() else {
+            return Err(anyhow!("[wallet_address] Unexpected response from darkfid: {}", rep))
+        };
+
+        if arr.len() != 1 {
+            return Err(anyhow!("Did not find pubkey with index {}", idx))
+        }
+
+        let key_bytes: Vec<u8> = serde_json::from_value(arr[0].clone())?;
+        let public_key: PublicKey = deserialize(&key_bytes)?;
+
+        Ok(public_key)
+    }
+
+    /// Fetch all coins and their metadata related to the Money contract from the wallet.
+    /// Optionally also fetch spent ones.
+    /// The boolean in the returned tuple notes if the coin was marked as spent.
+    pub async fn get_coins(&self, fetch_spent: bool) -> Result<Vec<(OwnCoin, bool)>> {
+        eprintln!("Fetching OwnCoins from the wallet");
+
         let query = if fetch_spent {
         let query = if fetch_spent {
             format!("SELECT * FROM {}", MONEY_COINS_TABLE)
             format!("SELECT * FROM {}", MONEY_COINS_TABLE)
         } else {
         } else {
@@ -246,14 +273,14 @@ impl Drk {
 
 
         // The returned thing should be an array of found rows.
         // The returned thing should be an array of found rows.
         let Some(rows) = rep.as_array() else {
         let Some(rows) = rep.as_array() else {
-            return Err(anyhow!("Unexpected response from darkfid: {}", rep))
+            return Err(anyhow!("[get_coins] Unexpected response from darkfid: {}", rep))
         };
         };
 
 
-        let mut owncoins = vec![];
+        let mut owncoins = Vec::with_capacity(rows.len());
 
 
         for row in rows {
         for row in rows {
             let Some(row) = row.as_array() else {
             let Some(row) = row.as_array() else {
-                return Err(anyhow!("Unexpected response from darkfid: {}", rep))
+                return Err(anyhow!("[get_coins] Unexpected response from darkfid: {}", rep))
             };
             };
 
 
             let coin_bytes: Vec<u8> = serde_json::from_value(row[0].clone())?;
             let coin_bytes: Vec<u8> = serde_json::from_value(row[0].clone())?;
@@ -316,216 +343,6 @@ impl Drk {
         Ok(owncoins)
         Ok(owncoins)
     }
     }
 
 
-    /// Fetch known balances from the wallet and try to print them as a table.
-    pub async fn wallet_balance(&self) -> Result<()> {
-        // This represents "false"
-        let is_spent = 0;
-
-        let query = format!(
-            "SELECT {}, {} FROM {} WHERE {} = {}",
-            MONEY_COINS_COL_VALUE,
-            MONEY_COINS_COL_TOKEN_ID,
-            MONEY_COINS_TABLE,
-            MONEY_COINS_COL_IS_SPENT,
-            is_spent,
-        );
-
-        let params = json!([
-            query,
-            QueryType::Blob as u8,
-            MONEY_COINS_COL_VALUE,
-            QueryType::Blob as u8,
-            MONEY_COINS_COL_TOKEN_ID,
-        ]);
-
-        let req = JsonRequest::new("wallet.query_row_multi", params);
-        let rep = self.rpc_client.request(req).await?;
-
-        // The returned thing should be an array of found rows.
-        let Some(rows) = rep.as_array() else {
-            return Err(anyhow!("Unexpected response from darkfid: {}", rep))
-        };
-
-        // Fill this map with balances, and in the end we'll print it as a table.
-        let mut balmap: HashMap<String, u64> = HashMap::new();
-
-        // Let's scan through the rows and see if we got anything.
-        // TODO: Separate tokens with spend-hook != 0
-        for row in rows {
-            let Some(row) = row.as_array() else {
-                return Err(anyhow!("Unexpected response from darkfid: {}", rep))
-            };
-
-            if row.len() != 2 {
-                eprintln!("Error: Got invalid array, row should contain two elements.");
-                eprintln!("Actual contents:\n:{:#?}", row);
-                return Err(anyhow!("Unexpected response from darkfid: {}", rep))
-            }
-
-            let value_bytes: Vec<u8> = serde_json::from_value(row[0].clone())?;
-            let mut value: u64 = deserialize(&value_bytes)?;
-
-            let token_bytes: Vec<u8> = serde_json::from_value(row[1].clone())?;
-            let token_id: TokenId = deserialize(&token_bytes)?;
-            let token_id = format!("{}", token_id);
-
-            if let Some(prev) = balmap.get(&token_id) {
-                value += prev;
-            }
-
-            balmap.insert(token_id, value);
-        }
-
-        // Create a prettytable with the new data.
-        let mut table = Table::new();
-        table.set_format(*format::consts::FORMAT_NO_BORDER_LINE_SEPARATOR);
-        table.set_titles(row!["Token ID", "Balance"]);
-
-        for (token_id, balance) in balmap.iter() {
-            // FIXME: Don't hardcode to 8 decimals
-            table.add_row(row![token_id, encode_base10(*balance, 8)]);
-        }
-
-        if table.is_empty() {
-            eprintln!("No unspent balances found");
-        } else {
-            println!("{}", table);
-        }
-
-        Ok(())
-    }
-
-    /// Fetch pubkeys from the wallet and print the requested index.
-    pub async fn wallet_address(&self, _idx: u64) -> Result<PublicKey> {
-        let query = format!("SELECT {} FROM {};", MONEY_KEYS_COL_PUBLIC, MONEY_KEYS_TABLE);
-        let params = json!([query, QueryType::Blob as u8, MONEY_KEYS_COL_PUBLIC]);
-        let req = JsonRequest::new("wallet.query_row_single", params);
-        let rep = self.rpc_client.request(req).await?;
-
-        let Some(arr) = rep.as_array() else {
-            return Err(anyhow!("Unexpected response from darkfid: {}", rep));
-        };
-
-        if arr.len() != 1 {
-            return Err(anyhow!("Unexpected response from darkfid: {}", rep))
-        }
-
-        let key_bytes: Vec<u8> = serde_json::from_value(arr[0].clone())?;
-        let public_key: PublicKey = deserialize(&key_bytes)?;
-
-        Ok(public_key)
-    }
-
-    /// Fetch secret keys from the wallet and return them if found.
-    pub async fn wallet_secrets(&self) -> Result<Vec<SecretKey>> {
-        let query = format!("SELECT {} FROM {};", MONEY_KEYS_COL_SECRET, MONEY_KEYS_TABLE);
-        let params = json!([query, QueryType::Blob as u8, MONEY_KEYS_COL_SECRET]);
-        let req = JsonRequest::new("wallet.query_row_multi", params);
-        let rep = self.rpc_client.request(req).await?;
-
-        // The returned thing should be an array of found rows.
-        let Some(rows) = rep.as_array() else {
-            return Err(anyhow!("Unexpected response from darkfid: {}", rep))
-        };
-
-        let mut secrets = vec![];
-
-        // Let's scan through the rows and see if we got anything.
-        for row in rows {
-            let secret_bytes: Vec<u8> = serde_json::from_value(row[0].clone())?;
-            let secret: SecretKey = deserialize(&secret_bytes)?;
-            secrets.push(secret);
-        }
-
-        Ok(secrets)
-    }
-
-    /// Import given secret keys into the wallet. The query uses INSERT, so if the key already
-    /// exists, it will simply be skipped.
-    pub async fn wallet_import_secrets(&self, secrets: Vec<SecretKey>) -> Result<Vec<PublicKey>> {
-        let mut ret = vec![];
-
-        for secret in secrets {
-            ret.push(PublicKey::from_secret(secret));
-            let is_default = 0;
-            let public = serialize(&PublicKey::from_secret(secret));
-            let secret = serialize(&secret);
-
-            let query = format!(
-                "INSERT INTO {} ({}, {}, {}) VALUES (?1, ?2, ?3)",
-                MONEY_KEYS_TABLE,
-                MONEY_KEYS_COL_IS_DEFAULT,
-                MONEY_KEYS_COL_PUBLIC,
-                MONEY_KEYS_COL_SECRET,
-            );
-
-            let params = json!([
-                query,
-                QueryType::Integer as u8,
-                is_default,
-                QueryType::Blob as u8,
-                public,
-                QueryType::Blob as u8,
-                secret,
-            ]);
-
-            let req = JsonRequest::new("wallet.exec_sql", params);
-            let rep = self.rpc_client.request(req).await?;
-
-            if rep != true {
-                // Something weird happened?
-                eprintln!("Got unexpected reply from darkfid: {}", rep);
-            }
-        }
-
-        Ok(ret)
-    }
-
-    /// Get the Money Merkle tree from the wallet
-    pub async fn wallet_tree(&self) -> Result<BridgeTree<MerkleNode, MERKLE_DEPTH>> {
-        let query = format!("SELECT * FROM {}", MONEY_TREE_TABLE);
-        let params = json!([query, QueryType::Blob as u8, MONEY_TREE_COL_TREE]);
-        let req = JsonRequest::new("wallet.query_row_single", params);
-        let rep = self.rpc_client.request(req).await?;
-
-        let tree_bytes: Vec<u8> = serde_json::from_value(rep[0].clone())?;
-        let tree = deserialize(&tree_bytes)?;
-        Ok(tree)
-    }
-
-    pub async fn wallet_dao_trees(&self) -> Result<(MerkleTree, MerkleTree)> {
-        let query = format!("SELECT * FROM {}", DAO_TREES_TABLE);
-        let params = json!([
-            query,
-            QueryType::Blob as u8,
-            DAO_TREES_COL_DAOS_TREE,
-            QueryType::Blob as u8,
-            DAO_TREES_COL_PROPOSALS_TREE
-        ]);
-        let req = JsonRequest::new("wallet.query_row_single", params);
-        let rep = self.rpc_client.request(req).await?;
-
-        let daos_tree_bytes: Vec<u8> = serde_json::from_value(rep[0].clone())?;
-        let proposals_tree_bytes: Vec<u8> = serde_json::from_value(rep[1].clone())?;
-
-        let daos_tree = deserialize(&daos_tree_bytes)?;
-        let proposals_tree = deserialize(&proposals_tree_bytes)?;
-
-        Ok((daos_tree, proposals_tree))
-    }
-
-    /// Get the last scanned slot from the wallet
-    pub async fn wallet_last_scanned_slot(&self) -> Result<u64> {
-        let query =
-            format!("SELECT {} FROM {};", MONEY_INFO_COL_LAST_SCANNED_SLOT, MONEY_INFO_TABLE);
-
-        let params = json!([query, QueryType::Integer as u8, MONEY_INFO_COL_LAST_SCANNED_SLOT]);
-        let req = JsonRequest::new("wallet.query_row_single", params);
-        let rep = self.rpc_client.request(req).await?;
-
-        Ok(serde_json::from_value(rep[0].clone())?)
-    }
-
     /// Mark a coin in the wallet as spent
     /// Mark a coin in the wallet as spent
     pub async fn mark_spent_coin(&self, coin: &Coin) -> Result<()> {
     pub async fn mark_spent_coin(&self, coin: &Coin) -> Result<()> {
         let query = format!(
         let query = format!(
@@ -547,14 +364,13 @@ impl Drk {
         Ok(())
         Ok(())
     }
     }
 
 
-    /// Marks all coins in the wallet as spent, if their nullifier is
-    /// in the provided set
-    pub async fn mark_spent_coins(&self, nullifiers: Vec<Nullifier>) -> Result<()> {
+    /// Marks all coins in the wallet as spent, if their nullifier is in the given set
+    pub async fn mark_spent_coins(&self, nullifiers: &[Nullifier]) -> Result<()> {
         if nullifiers.is_empty() {
         if nullifiers.is_empty() {
             return Ok(())
             return Ok(())
         }
         }
 
 
-        for (coin, _) in self.wallet_coins(false).await? {
+        for (coin, _) in self.get_coins(false).await? {
             if nullifiers.contains(&coin.nullifier) {
             if nullifiers.contains(&coin.nullifier) {
                 self.mark_spent_coin(&coin.coin).await?;
                 self.mark_spent_coin(&coin.coin).await?;
             }
             }
@@ -567,7 +383,7 @@ impl Drk {
     pub async fn unspend_coin(&self, coin: &Coin) -> Result<()> {
     pub async fn unspend_coin(&self, coin: &Coin) -> Result<()> {
         let query = format!(
         let query = format!(
             "UPDATE {} SET {} = ?1 WHERE {} = ?2;",
             "UPDATE {} SET {} = ?1 WHERE {} = ?2;",
-            MONEY_COINS_TABLE, MONEY_COINS_COL_IS_SPENT, MONEY_COINS_COL_COIN
+            MONEY_COINS_TABLE, MONEY_COINS_COL_IS_SPENT, MONEY_COINS_COL_COIN,
         );
         );
 
 
         let params = json!([
         let params = json!([
@@ -584,27 +400,45 @@ impl Drk {
         Ok(())
         Ok(())
     }
     }
 
 
-    /// Replace the Money Merkle tree in the wallet
-    pub async fn put_money_tree(&self, tree: &BridgeTree<MerkleNode, MERKLE_DEPTH>) -> Result<()> {
+    /// Replace the Money Merkle tree in the wallet.
+    pub async fn put_money_tree(&self, tree: &MerkleTree) -> Result<()> {
         let query = format!(
         let query = format!(
             "DELETE FROM {}; INSERT INTO {} ({}) VALUES (?1);",
             "DELETE FROM {}; INSERT INTO {} ({}) VALUES (?1);",
-            MONEY_TREE_TABLE, MONEY_TREE_TABLE, MONEY_TREE_COL_TREE
+            MONEY_TREE_TABLE, MONEY_TREE_TABLE, MONEY_TREE_COL_TREE,
         );
         );
 
 
         let params = json!([query, QueryType::Blob as u8, serialize(tree)]);
         let params = json!([query, QueryType::Blob as u8, serialize(tree)]);
+
         let req = JsonRequest::new("wallet.exec_sql", params);
         let req = JsonRequest::new("wallet.exec_sql", params);
         let _ = self.rpc_client.request(req).await?;
         let _ = self.rpc_client.request(req).await?;
 
 
         Ok(())
         Ok(())
     }
     }
 
 
-    /// Reset the Money Contract Merkle tree and coins in the wallet
+    /// Fetch the Money Merkle tree from the wallet
+    pub async fn get_money_tree(&self) -> Result<MerkleTree> {
+        let query = format!("SELECT * FROM {}", MONEY_TREE_TABLE);
+        let params = json!([query, QueryType::Blob as u8, MONEY_TREE_COL_TREE]);
+        let req = JsonRequest::new("wallet.query_row_single", params);
+        let rep = self.rpc_client.request(req).await?;
+
+        let tree_bytes: Vec<u8> = serde_json::from_value(rep[0].clone())?;
+        let tree = deserialize(&tree_bytes)?;
+        Ok(tree)
+    }
+
+    /// Reset the Money Merkle tree in the wallet
     pub async fn reset_money_tree(&self) -> Result<()> {
     pub async fn reset_money_tree(&self) -> Result<()> {
         eprintln!("Resetting Money Merkle tree");
         eprintln!("Resetting Money Merkle tree");
-        let tree = BridgeTree::<MerkleNode, MERKLE_DEPTH>::new(100);
+        let tree = MerkleTree::new(100);
         self.put_money_tree(&tree).await?;
         self.put_money_tree(&tree).await?;
         eprintln!("Successfully reset Money Merkle tree");
         eprintln!("Successfully reset Money Merkle tree");
 
 
+        Ok(())
+    }
+
+    /// Reset the Money coins in the wallet
+    pub async fn reset_money_coins(&self) -> Result<()> {
         eprintln!("Resetting coins");
         eprintln!("Resetting coins");
         let query = format!("DELETE FROM {};", MONEY_COINS_TABLE);
         let query = format!("DELETE FROM {};", MONEY_COINS_TABLE);
         let params = json!([query]);
         let params = json!([query]);
@@ -615,171 +449,78 @@ impl Drk {
         Ok(())
         Ok(())
     }
     }
 
 
-    /// Replace the DAO Merkle trees in the wallet
-    pub async fn put_dao_trees(
-        &self,
-        daos_tree: &BridgeTree<MerkleNode, MERKLE_DEPTH>,
-        proposals_tree: &BridgeTree<MerkleNode, MERKLE_DEPTH>,
-    ) -> Result<()> {
+    /// Fetch known unspent balances from the wallet and return them as a hashmap.
+    pub async fn money_balance(&self) -> Result<HashMap<String, u64>> {
+        // This represents "false"
+        let is_spent = 0;
+
         let query = format!(
         let query = format!(
-            "DELETE FROM {}; INSERT INTO {} ({}, {}) VALUES (?1, ?2);",
-            DAO_TREES_TABLE, DAO_TREES_TABLE, DAO_TREES_COL_DAOS_TREE, DAO_TREES_COL_PROPOSALS_TREE
+            "SELECT {}, {} FROM {} WHERE {} = {}",
+            MONEY_COINS_COL_VALUE,
+            MONEY_COINS_COL_TOKEN_ID,
+            MONEY_COINS_TABLE,
+            MONEY_COINS_COL_IS_SPENT,
+            is_spent,
         );
         );
 
 
         let params = json!([
         let params = json!([
             query,
             query,
             QueryType::Blob as u8,
             QueryType::Blob as u8,
-            serialize(daos_tree),
-            QueryType::Blob as u8,
-            serialize(proposals_tree)
-        ]);
-
-        let req = JsonRequest::new("wallet.exec_sql", params);
-
-        let _ = self.rpc_client.request(req).await?;
-
-        Ok(())
-    }
-
-    /// Reset the DAO Contract Merkle trees in the wallet
-    pub async fn reset_dao_trees(&self) -> Result<()> {
-        eprintln!("Resetting DAO Merkle trees");
-        let tree0 = MerkleTree::new(100);
-        let tree1 = MerkleTree::new(100);
-        self.put_dao_trees(&tree0, &tree1).await?;
-        eprintln!("Successfully reset DAO Merkle trees");
-
-        Ok(())
-    }
-
-    /// Write given DAOs into the wallet
-    pub async fn put_daos(&self, daos: &[Dao]) -> Result<()> {
-        for dao in daos {
-            // Note that for now we just write the leaf pos, tx-hash, and call_index.
-            // This is because we don't expect the other stuff to change.
-            let query = format!(
-                "UPDATE {} SET {} = ?1, {} = ?2, {} = ?3 WHERE {} = ?4;",
-                DAO_DAOS_TABLE,
-                DAO_DAOS_COL_LEAF_POSITION,
-                DAO_DAOS_COL_TX_HASH,
-                DAO_DAOS_COL_CALL_INDEX,
-                DAO_DAOS_COL_DAO_ID
-            );
-
-            let params = json!([
-                query,
-                QueryType::Blob as u8,
-                serialize(&dao.leaf_position.unwrap()),
-                QueryType::Blob as u8,
-                serialize(&dao.tx_hash.unwrap()),
-                QueryType::Integer as u8,
-                dao.call_index.unwrap(),
-            ]);
-
-            let req = JsonRequest::new("wallet.exec_sql", params);
-            let _ = self.rpc_client.request(req).await?;
-        }
-
-        Ok(())
-    }
-
-    /// Fetch all DAOs from the wallet
-    /// We use this a lot because we don't worry too much about performance in this
-    /// tool, and also in practice probably not a lot of DAOs will be in a single
-    /// wallet.
-    pub async fn wallet_get_daos(&self) -> Result<Vec<Dao>> {
-        let query = format!("SELECT * FROM {}", DAO_DAOS_TABLE);
-
-        let params = json!([
-            query,
-            QueryType::Integer as u8,
-            DAO_DAOS_COL_DAO_ID,
-            QueryType::Blob as u8,
-            DAO_DAOS_COL_NAME,
-            QueryType::Integer as u8,
-            DAO_DAOS_COL_PROPOSER_LIMIT,
-            QueryType::Integer as u8,
-            DAO_DAOS_COL_QUORUM,
-            QueryType::Integer as u8,
-            DAO_DAOS_COL_APPROVAL_RATIO_BASE,
-            QueryType::Integer as u8,
-            DAO_DAOS_COL_APPROVAL_RATIO_QUOT,
-            QueryType::Blob as u8,
-            DAO_DAOS_COL_GOV_TOKEN_ID,
-            QueryType::Blob as u8,
-            DAO_DAOS_COL_SECRET,
+            MONEY_COINS_COL_VALUE,
             QueryType::Blob as u8,
             QueryType::Blob as u8,
-            DAO_DAOS_COL_BULLA_BLIND,
-            QueryType::OptionBlob as u8,
-            DAO_DAOS_COL_LEAF_POSITION,
-            QueryType::OptionBlob as u8,
-            DAO_DAOS_COL_TX_HASH,
-            QueryType::OptionInteger as u8,
-            DAO_DAOS_COL_CALL_INDEX,
+            MONEY_COINS_COL_TOKEN_ID,
         ]);
         ]);
 
 
         let req = JsonRequest::new("wallet.query_row_multi", params);
         let req = JsonRequest::new("wallet.query_row_multi", params);
         let rep = self.rpc_client.request(req).await?;
         let rep = self.rpc_client.request(req).await?;
 
 
+        // The returned thing should be an array of found rows.
         let Some(rows) = rep.as_array() else {
         let Some(rows) = rep.as_array() else {
-            return Err(anyhow!("Unexpected response from darkfid: {}", rep));
+            return Err(anyhow!("[money_balance] Unexpected response from darkfid: {}", rep))
         };
         };
 
 
-        let mut daos = Vec::with_capacity(rows.len());
+        // Fill this map with balances
+        let mut balmap: HashMap<String, u64> = HashMap::new();
 
 
+        // Let's scan through the rows and see if we got anything.
+        // TODO: Separate tokens with spend_hook != 0
         for row in rows {
         for row in rows {
-            let id: u64 = serde_json::from_value(row[0].clone())?;
-
-            let name_bytes: Vec<u8> = serde_json::from_value(row[1].clone())?;
-            let name = deserialize(&name_bytes)?;
+            let Some(row) = row.as_array() else {
+                return Err(anyhow!("[money_balance] Unexpected response from darkfid: {}", rep))
+            };
 
 
-            let proposer_limit = serde_json::from_value(row[2].clone())?;
-            let quorum = serde_json::from_value(row[3].clone())?;
-            let approval_ratio_base = serde_json::from_value(row[4].clone())?;
-            let approval_ratio_quot = serde_json::from_value(row[5].clone())?;
+            if row.len() != 2 {
+                eprintln!("Error: Got invalid array, row should contain two elements.");
+                eprintln!("Actual contents:\n:{:#?}", row);
+                return Err(anyhow!("[money_balance] Unexpected response from darkfid: {}", rep))
+            }
 
 
-            let gov_token_bytes: Vec<u8> = serde_json::from_value(row[6].clone())?;
-            let gov_token_id = deserialize(&gov_token_bytes)?;
+            let value_bytes: Vec<u8> = serde_json::from_value(row[0].clone())?;
+            let mut value: u64 = deserialize(&value_bytes)?;
 
 
-            let secret_bytes: Vec<u8> = serde_json::from_value(row[7].clone())?;
-            let secret_key = deserialize(&secret_bytes)?;
+            let token_bytes: Vec<u8> = serde_json::from_value(row[1].clone())?;
+            let token_id: TokenId = deserialize(&token_bytes)?;
+            let token_id = format!("{}", token_id);
 
 
-            let bulla_blind_bytes: Vec<u8> = serde_json::from_value(row[8].clone())?;
-            let bulla_blind = deserialize(&bulla_blind_bytes)?;
+            if let Some(prev) = balmap.get(&token_id) {
+                value += prev;
+            }
 
 
-            let leaf_position_bytes: Vec<u8> = serde_json::from_value(row[9].clone())?;
-            let tx_hash_bytes: Vec<u8> = serde_json::from_value(row[10].clone())?;
-            let call_index = serde_json::from_value(row[11].clone())?;
+            balmap.insert(token_id, value);
+        }
 
 
-            let leaf_position = if leaf_position_bytes.is_empty() {
-                None
-            } else {
-                Some(deserialize(&leaf_position_bytes)?)
-            };
+        Ok(balmap)
+    }
 
 
-            let tx_hash =
-                if tx_hash_bytes.is_empty() { None } else { Some(deserialize(&tx_hash_bytes)?) };
-
-            let dao = Dao {
-                id,
-                name,
-                proposer_limit,
-                quorum,
-                approval_ratio_base,
-                approval_ratio_quot,
-                gov_token_id,
-                secret_key,
-                bulla_blind,
-                leaf_position,
-                tx_hash,
-                call_index,
-            };
+    /// Get the last scanned slot from the wallet
+    pub async fn last_scanned_slot(&self) -> Result<u64> {
+        let query =
+            format!("SELECT {} FROM {};", MONEY_INFO_COL_LAST_SCANNED_SLOT, MONEY_INFO_TABLE);
 
 
-            daos.push(dao);
-        }
+        let params = json!([query, QueryType::Integer as u8, MONEY_INFO_COL_LAST_SCANNED_SLOT]);
+        let req = JsonRequest::new("wallet.query_row_single", params);
+        let rep = self.rpc_client.request(req).await?;
 
 
-        // Sort by ID in SQL. The SELECT statement does not guarantee this.
-        daos.sort_by(|a, b| a.id.cmp(&b.id));
-        Ok(daos)
+        Ok(serde_json::from_value(rep[0].clone())?)
     }
     }
 }
 }

+ 1 - 1
src/contract/dao/wallet.sql

@@ -144,7 +144,7 @@ CREATE TABLE IF NOT EXISTS dao_proposals (
     -- Public key of person that would receive the funds
     -- Public key of person that would receive the funds
     recv_public BLOB NOT NULL,
     recv_public BLOB NOT NULL,
     -- Amount of funds that would be sent
     -- Amount of funds that would be sent
-    amount INTEGER NOT NULL,
+    amount BLOB NOT NULL,
     serial BLOB NOT NULL,
     serial BLOB NOT NULL,
     -- Token ID we propose to send
     -- Token ID we propose to send
     sendcoin_token_id BLOB NOT NULL,
     sendcoin_token_id BLOB NOT NULL,