diff --git a/crates/sp_client/src/api.rs b/crates/sp_client/src/api.rs index 13c09b3..3afa033 100644 --- a/crates/sp_client/src/api.rs +++ b/crates/sp_client/src/api.rs @@ -1,12 +1,17 @@ +use std::any::Any; use std::collections::HashMap; -use std::sync::{Mutex, OnceLock}; +use std::str::FromStr; +use std::sync::{Mutex, OnceLock, PoisonError}; use rand::Rng; use anyhow::Error as AnyhowError; use serde_json::Error as SerdeJsonError; use shamir::SecretData; -use sp_backend::bitcoin::secp256k1::SecretKey; +use sp_backend::bitcoin::consensus::deserialize; +use sp_backend::bitcoin::hex::{FromHex, HexToBytesError}; +use sp_backend::bitcoin::secp256k1::{PublicKey, SecretKey}; +use sp_backend::bitcoin::{Transaction, Txid}; use sp_backend::silentpayments::Error as SpError; use serde::{Deserialize, Serialize}; @@ -15,10 +20,12 @@ use tsify::Tsify; use wasm_bindgen::convert::FromWasmAbi; use wasm_bindgen::prelude::*; -use sp_backend::spclient::SpendKey; use sp_backend::spclient::{derive_keys_from_seed, OutputList, SpClient}; +use sp_backend::spclient::{SpWallet, SpendKey}; -use crate::user::{User, UserKeys, CONNECTED_USERS}; +use crate::network::{BitcoinNetworkMsg, BitcoinTopic}; +use crate::silentpayments::check_transaction; +use crate::user::{lock_connected_users, User, UserWallets, CONNECTED_USERS}; use crate::process::Process; @@ -55,6 +62,30 @@ impl From for ApiError { } } +impl From for ApiError { + fn from(value: HexToBytesError) -> Self { + ApiError { + message: value.to_string(), + } + } +} + +impl From for ApiError { + fn from(value: sp_backend::bitcoin::secp256k1::Error) -> Self { + ApiError { + message: value.to_string(), + } + } +} + +impl From for ApiError { + fn from(value: sp_backend::bitcoin::consensus::encode::Error) -> Self { + ApiError { + message: value.to_string(), + } + } +} + impl Into for ApiError { fn into(self) -> JsValue { JsValue::from_str(&self.message) @@ -62,7 +93,7 @@ impl Into for ApiError { } #[derive(Tsify, Serialize, Deserialize)] -#[tsify(into_wasm_abi)] +#[tsify(into_wasm_abi, from_wasm_abi)] #[allow(non_camel_case_types)] pub struct createUserReturn { pub user: User, @@ -75,16 +106,11 @@ pub fn setup() { } // Should be transfered to annother module -pub struct GenerateSPWallet { - pub sp_client: SpClient, - pub sp_outputs: OutputList, -} - pub fn generate_sp_wallet( label: Option, birthday: u32, is_testnet: bool, -) -> ApiResult { +) -> ApiResult { let mut seed = [0u8; 64]; rand::thread_rng().fill(&mut seed); let (scan_sk, spend_sk) = derive_keys_from_seed(&seed, is_testnet)?; @@ -101,20 +127,7 @@ pub fn generate_sp_wallet( our_address.to_string() ); - //let sp_client_json = serde_json::to_string(&sp_client)?; - - // Generate an empty outputs - let sp_outputs = OutputList::new( - our_address.get_scan_key(), - our_address.get_spend_key(), - birthday, - ); - //let sp_outputs_json = serde_json::to_string(&sp_outputs)?; - - let res = GenerateSPWallet { - sp_client, - sp_outputs, - }; + let res = SpWallet::new(sp_client, None)?; Ok(res) } @@ -133,28 +146,28 @@ pub fn create_user( birthday_signet: u32, process: String, ) -> ApiResult { - let mut output_list: Vec = Vec::new(); //recover let sp_wallet_recover = generate_sp_wallet(label.clone(), birthday_signet, true)?; - output_list.push(sp_wallet_recover.sp_outputs); //revoke let sp_wallet_revoke = generate_sp_wallet(label.clone(), birthday_signet, true)?; - output_list.push(sp_wallet_revoke.sp_outputs); //mainet let sp_wallet_main = generate_sp_wallet(label, birthday_main, false)?; - output_list.push(sp_wallet_main.sp_outputs); - let user_keys = UserKeys::new( - Some(sp_wallet_main.sp_client), - sp_wallet_recover.sp_client, - Some(sp_wallet_revoke.sp_client), + let user_wallets = UserWallets::new( + Some(sp_wallet_main), + sp_wallet_recover, + Some(sp_wallet_revoke), ); - let user = User::new(user_keys, password, process)?; + let user = User::new(user_wallets.clone(), password, process)?; + + let outputs = user_wallets.get_all_outputs(); + + lock_connected_users()?.insert(user.pre_id.clone(), user_wallets); let generate_user = createUserReturn { user, - output_list_vec: output_list, + output_list_vec: outputs, }; Ok(generate_user) @@ -175,7 +188,7 @@ pub fn get_processes() -> ApiResult { for _ in 0..number_managers { //add sp_client let sp_wallet = generate_sp_wallet(None, birthday_signet, true)?; - let sp_address = sp_wallet.sp_client.get_receiving_address(); + let sp_address = sp_wallet.get_client().get_receiving_address(); members.push(sp_address); } //instances of process @@ -235,34 +248,6 @@ pub fn get_processes() -> ApiResult { Ok(get_process_return(data_process)) } -//for testing -/*#[wasm_bindgen] -pub fn get_process() -> ApiResult{ - - let number_managers: u8 = 5; - let number_users:u8 = 10; - let birthday_signet = 50000; - let mut members: Vec = Vec::with_capacity((number_managers + number_users) as usize); - - for _ in 0..number_managers{ - //add sp_client - let sp_wallet = generate_sp_wallet(None, birthday_signet, true)?; - let sp_address: SilentPaymentAddress = sp_wallet.sp_client.get_receiving_address().try_into()?; - members.push(ItemMember::new(Role::Manager,sp_address, String::from(""))); - } - for _ in 0..number_users{ - let sp_wallet = generate_sp_wallet(None, birthday_signet, true)?; - let sp_address: SilentPaymentAddress = sp_wallet.sp_client.get_receiving_address().try_into()?; - members.push(ItemMember::new(Role::User, sp_address, String::from(""))); - } - - let process = Process::new(members); - let mut data_process: Vec = Vec::new(); - data_process.push(process); - Ok(get_process_return(data_process)) - -}*/ - #[derive(Debug, Tsify, Serialize, Deserialize)] #[tsify(from_wasm_abi)] #[allow(non_camel_case_types)] @@ -285,14 +270,84 @@ impl shamir_shares { } } +#[derive(Debug, Tsify, Serialize, Deserialize)] +#[tsify(from_wasm_abi)] +#[allow(non_camel_case_types)] +pub struct outputs_list(Vec); + +impl outputs_list { + fn as_inner(&self) -> &[OutputList] { + &self.0 + } +} + #[wasm_bindgen] pub fn login_user( user_password: String, pre_id: String, recover: recover_data, shares: shamir_shares, + outputs: outputs_list, ) -> ApiResult<()> { - let res = User::login(pre_id, user_password, recover.as_inner(), shares.as_inner())?; + let res = User::login( + pre_id, + user_password, + recover.as_inner(), + shares.as_inner(), + outputs.as_inner(), + )?; Ok(res) } + +#[wasm_bindgen] +pub fn check_transaction_for_silent_payments( + tx_hex: String, + tweak_data_hex: String, +) -> ApiResult<()> { + let tx = deserialize::(&Vec::from_hex(&tx_hex)?)?; + let tweak_data = PublicKey::from_str(&tweak_data_hex)?; + + check_transaction(tx, tweak_data); + + Ok(()) +} + +#[wasm_bindgen] +pub fn parse_bitcoin_network_msg(msg: Vec) -> ApiResult<()> { + let parsed_msg = BitcoinNetworkMsg::new(&msg)?; + + match parsed_msg.topic { + BitcoinTopic::RawTx => { + let tx = deserialize::(parsed_msg.data)?; + let tweak_data = PublicKey::from_slice(parsed_msg.addon)?; + check_transaction(tx, tweak_data); + } + BitcoinTopic::RawBlock => (), + } + + Ok(()) +} + +#[wasm_bindgen] +pub fn parse_4nk_msg(raw: String) -> Option{ + if let Ok(msg) = AnkNetworkMsg::new(&raw) { + match msg.topic { + AnkTopic::Faucet => { + match Txid::from_str(msg.content) { + Ok(txid) => { + // return the txid for verification + Some(txid.to_string()) + }, + Err(e) => { + log::error!("Invalid txid with a \"faucet\" message: {}", e.to_string()); + None + } + } + } + } + } else { + log::debug!("Can't parse message as a valid 4nk message: {}", raw); + None + } +} diff --git a/crates/sp_client/src/lib.rs b/crates/sp_client/src/lib.rs index 9898215..73db7f5 100644 --- a/crates/sp_client/src/lib.rs +++ b/crates/sp_client/src/lib.rs @@ -1,7 +1,24 @@ #![allow(warnings)] +use anyhow::Error; +use std::fmt::Debug; +use std::sync::{Mutex, MutexGuard}; + mod Prd_list; pub mod api; mod crypto; +mod network; mod peers; mod process; +mod silentpayments; mod user; + +pub(crate) trait MutexExt { + fn lock_anyhow(&self) -> Result, Error>; +} + +impl MutexExt for Mutex { + fn lock_anyhow(&self) -> Result, Error> { + self.lock() + .map_err(|e| Error::msg(format!("Failed to lock: {}", e))) + } +} diff --git a/crates/sp_client/src/network.rs b/crates/sp_client/src/network.rs new file mode 100644 index 0000000..fdb16c6 --- /dev/null +++ b/crates/sp_client/src/network.rs @@ -0,0 +1,94 @@ +use anyhow::{Error, Result}; +use serde::{Deserialize, Serialize}; +use tsify::Tsify; + +const RAWTXTOPIC: &'static str = "rawtx"; +const RAWBLOCKTOPIC: &'static str = "rawblock"; + +#[derive(Debug, Serialize, Deserialize)] +pub enum BitcoinTopic { + RawTx, + RawBlock, +} + +impl BitcoinTopic { + pub fn as_str(&self) -> &str { + match self { + Self::RawTx => RAWTXTOPIC, + Self::RawBlock => RAWBLOCKTOPIC, + } + } +} + +#[derive(Debug, Serialize, Deserialize, Tsify)] +#[tsify(from_wasm_abi, into_wasm_abi)] +pub struct BitcoinNetworkMsg<'a> { + pub topic: BitcoinTopic, + pub data: &'a [u8], + pub sequence: &'a [u8], + pub addon: &'a [u8], +} + +impl<'a> BitcoinNetworkMsg<'a> { + pub fn new(raw_msg: &'a [u8]) -> Result { + let topic: BitcoinTopic; + let data: &[u8]; + let sequence: &[u8]; + let addon: &[u8]; + let addon_len: usize; + let raw_msg_len = raw_msg.len(); + + if raw_msg.starts_with(RAWTXTOPIC.as_bytes()) { + topic = BitcoinTopic::RawTx; + addon_len = 33; + } else if raw_msg.starts_with(RAWBLOCKTOPIC.as_bytes()) { + topic = BitcoinTopic::RawBlock; + addon_len = 0; + } else { + return Err(Error::msg("Unknown prefix")); + } + + data = &raw_msg[topic.as_str().as_bytes().len()..raw_msg_len - 4 - addon_len]; + sequence = &raw_msg[raw_msg_len - 4 - addon_len..]; + addon = &raw_msg[raw_msg_len - addon_len..]; + + Ok(Self { + topic, + data, + sequence, + addon, + }) + } +} + +#[derive(Debug)] +pub enum AnkTopic { + Faucet, +} + +impl AnkTopic { + pub fn as_str(&self) -> &str { + match self { + Self::Faucet => "faucet", + } + } +} + +#[derive(Debug)] +pub struct AnkNetworkMsg<'a> { + pub topic: AnkTopic, + pub content: &'a str, +} + +impl<'a> AnkNetworkMsg<'a> { + pub fn new(raw: &'a str) -> Result { + if raw.starts_with(AnkTopic::Faucet.as_str()) { + Ok(Self { + topic: AnkTopic::Faucet, + content: &raw[AnkTopic::Faucet.as_str().len()..], + }) + } else { + Err(Error::msg("Unknown 4nk message")) + } + } +} diff --git a/crates/sp_client/src/silentpayments.rs b/crates/sp_client/src/silentpayments.rs new file mode 100644 index 0000000..5ab45d4 --- /dev/null +++ b/crates/sp_client/src/silentpayments.rs @@ -0,0 +1,76 @@ +use std::collections::HashMap; + +use anyhow::Result; + +use sp_backend::silentpayments::utils::receiving::calculate_shared_secret; +use sp_backend::{ + bitcoin::{ + secp256k1::{PublicKey, Scalar, XOnlyPublicKey}, + Transaction, + }, + silentpayments::receiving::Label, +}; + +use crate::user::{lock_connected_users, CONNECTED_USERS}; + +type FoundOutputs = HashMap, HashMap>; + +pub fn check_transaction(tx: Transaction, tweak_data: PublicKey) -> Result { + let connected_users = lock_connected_users()?; + + let pubkeys_to_check: HashMap = (0u32..) + .zip(tx.output) + .filter_map(|(i, o)| { + if o.script_pubkey.is_p2tr() { + let xonly = XOnlyPublicKey::from_slice(&o.script_pubkey.as_bytes()[2..]) + .expect("Transaction is invalid"); + Some((xonly, i)) + } else { + None + } + }) + .collect(); + + // Check the transaction for all connected users + for (pre_id, keys) in connected_users.clone() { + let recover = keys.recover; + let shared_secret = + calculate_shared_secret(tweak_data, recover.get_client().get_scan_key())?; + let res = recover + .get_client() + .sp_receiver + .scan_transaction(&shared_secret, pubkeys_to_check.keys().cloned().collect())?; + + if res.len() > 0 { + return Ok(res); + } + + if let Some(main) = keys.main { + let shared_secret = + calculate_shared_secret(tweak_data, main.get_client().get_scan_key())?; + let res = main + .get_client() + .sp_receiver + .scan_transaction(&shared_secret, pubkeys_to_check.keys().cloned().collect())?; + + if res.len() > 0 { + return Ok(res); + } + } + + if let Some(revoke) = keys.revoke { + let shared_secret = + calculate_shared_secret(tweak_data, revoke.get_client().get_scan_key())?; + let res = revoke + .get_client() + .sp_receiver + .scan_transaction(&shared_secret, pubkeys_to_check.keys().cloned().collect())?; + + if res.len() > 0 { + return Ok(res); + } + } + } + + Ok(HashMap::new()) +} diff --git a/crates/sp_client/src/user.rs b/crates/sp_client/src/user.rs index 70835e2..202ed8d 100644 --- a/crates/sp_client/src/user.rs +++ b/crates/sp_client/src/user.rs @@ -12,6 +12,7 @@ use sp_backend::bitcoin::hashes::HashEngine; use sp_backend::bitcoin::hex::{DisplayHex, FromHex}; use sp_backend::bitcoin::secp256k1::SecretKey; use sp_backend::bitcoin::secp256k1::ThirtyTwoByteHash; +use sp_backend::spclient::SpClient; use tsify::Tsify; use wasm_bindgen::prelude::*; @@ -20,34 +21,42 @@ use std::collections::HashMap; use std::fs::File; use std::io::{Cursor, Read, Write}; use std::str::FromStr; -use std::sync::{Mutex, OnceLock}; +use std::sync::{Mutex, MutexGuard, OnceLock}; use sp_backend::bitcoin::secp256k1::constants::SECRET_KEY_SIZE; use sp_backend::silentpayments::bitcoin_hashes::sha256; use sp_backend::silentpayments::sending::SilentPaymentAddress; use sp_backend::spclient::SpendKey; -use sp_backend::spclient::{OutputList, SpClient}; +use sp_backend::spclient::{OutputList, SpWallet}; use crate::crypto::{Aes256Decryption, Aes256Encryption, HalfKey, Purpose}; use crate::peers::Peer; use crate::user; +use crate::MutexExt; type PreId = String; const MANAGERS_NUMBER: u8 = 10; const QUORUM_SHARD: f32 = 0.8; -pub static CONNECTED_USERS: OnceLock>> = OnceLock::new(); +type UsersMap = HashMap; +pub static CONNECTED_USERS: OnceLock> = OnceLock::new(); -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct UserKeys { - pub main: Option, - pub recover: SpClient, - pub revoke: Option, +pub fn lock_connected_users() -> Result> { + CONNECTED_USERS + .get_or_init(|| Mutex::new(HashMap::new())) + .lock_anyhow() } -impl UserKeys { - pub fn new(main: Option, recover: SpClient, revoke: Option) -> Self { +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct UserWallets { + pub main: Option, + pub recover: SpWallet, + pub revoke: Option, +} + +impl UserWallets { + pub fn new(main: Option, recover: SpWallet, revoke: Option) -> Self { Self { main, recover, @@ -55,9 +64,22 @@ impl UserKeys { } } - pub fn try_get_revoke(&self) -> Option<&SpClient> { + pub fn try_get_revoke(&self) -> Option<&SpWallet> { self.revoke.as_ref() } + + pub(crate) fn get_all_outputs(&self) -> Vec { + let mut res = Vec::::new(); + if let Some(main) = &self.main { + res.push(main.get_outputs().clone()); + } + if let Some(revoke) = &self.revoke { + res.push(revoke.get_outputs().clone()); + } + res.push(self.recover.get_outputs().clone()); + + res + } } #[derive(Debug, Serialize, Deserialize, Clone, Tsify)] @@ -69,25 +91,30 @@ pub struct User { recover_data: Vec, revoke_data: Option>, shares: Vec>, + outputs: Vec, } impl User { - pub fn new(user_keys: UserKeys, user_password: String, process: String) -> Result { + pub fn new(user_wallets: UserWallets, user_password: String, process: String) -> Result { let mut rng = thread_rng(); // image revoke // We just take the 2 revoke keys let mut revoke_data = Vec::with_capacity(64); - if let Some(revoke) = user_keys.try_get_revoke() { - revoke_data.extend_from_slice(revoke.get_scan_key().as_ref()); - revoke_data.extend_from_slice(revoke.try_get_secret_spend_key()?.as_ref()); + if let Some(revoke) = user_wallets.try_get_revoke() { + revoke_data.extend_from_slice(revoke.get_client().get_scan_key().as_ref()); + revoke_data.extend_from_slice(revoke.get_client().try_get_secret_spend_key()?.as_ref()); } else { return Err(Error::msg("No revoke wallet available")); } // Take the 2 recover keys // split recover spend key - let recover_spend_key = user_keys.recover.try_get_secret_spend_key()?.clone(); + let recover_spend_key = user_wallets + .recover + .get_client() + .try_get_secret_spend_key()? + .clone(); let (part1_key, part2_key) = recover_spend_key.as_ref().split_at(SECRET_KEY_SIZE / 2); let mut recover_data = Vec::::with_capacity(180); // 32 * 3 + (12+16)*3 @@ -161,7 +188,12 @@ impl User { let scan_key_encryption = Aes256Encryption::import_key( Purpose::ThirtyTwoBytes, - user_keys.recover.get_scan_key().secret_bytes().to_vec(), + user_wallets + .recover + .get_client() + .get_scan_key() + .secret_bytes() + .to_vec(), hash3.to_byte_array(), Aes256Gcm::generate_nonce(&mut rng).into(), )?; @@ -171,12 +203,6 @@ impl User { recover_data.extend_from_slice(&cipher_scan_key); - //Create PRDList - //@todo - //Send messages PRDList - //@todo - //Receive List Items (PCD) - Ok(User { pre_id: pre_id.to_string(), processes: vec![process], @@ -184,6 +210,7 @@ impl User { recover_data, revoke_data: Some(revoke_data), shares, + outputs: user_wallets.get_all_outputs(), }) } @@ -192,6 +219,7 @@ impl User { user_password: String, recover_data: &[u8], shares: &[Vec], + outputs: &[OutputList], ) -> Result<()> { let mut retrieved_spend_key = [0u8; 32]; let mut retrieved_scan_key = [0u8; 32]; @@ -258,6 +286,13 @@ impl User { true, )?; + let recover_outputs = outputs + .iter() + .find(|o| o.check_fingerprint(&recover_client)) + .cloned(); + + let recover_wallet = SpWallet::new(recover_client, recover_outputs)?; + // Adding user to CONNECTED_USERS if let Some(current_users) = CONNECTED_USERS.get() { let mut lock = current_users.to_owned().lock().unwrap(); @@ -267,11 +302,11 @@ impl User { pre_id ))); } else { - lock.insert(pre_id.clone(), UserKeys::new(None, recover_client, None)); + lock.insert(pre_id.clone(), UserWallets::new(None, recover_wallet, None)); } } else { let mut user_map = HashMap::new(); - user_map.insert(pre_id, UserKeys::new(None, recover_client, None)); + user_map.insert(pre_id, UserWallets::new(None, recover_wallet, None)); let new_value = Mutex::new(user_map); if let Err(error) = CONNECTED_USERS.set(new_value) { return Err(Error::msg( @@ -396,7 +431,7 @@ mod tests { const USER_PASSWORD: &str = "correct horse battery staple"; const PROCESS: &str = "example"; - fn helper_create_user_keys() -> UserKeys { + fn helper_create_user_wallets() -> UserWallets { let label = "default".to_owned(); let sp_main = SpClient::new( label.clone(), @@ -422,16 +457,20 @@ mod tests { true, ) .unwrap(); - let user_keys = UserKeys::new(Some(sp_main), sp_recover, Some(sp_revoke)); + let user_wallets = UserWallets::new( + Some(SpWallet::new(sp_main, None).unwrap()), + SpWallet::new(sp_recover, None).unwrap(), + Some(SpWallet::new(sp_revoke, None).unwrap()), + ); - user_keys + user_wallets } // Test 1: Create User #[test] fn test_successful_creation() { - let user_keys = helper_create_user_keys(); - let result = User::new(user_keys, USER_PASSWORD.to_owned(), PROCESS.to_owned()); + let user_wallets = helper_create_user_wallets(); + let result = User::new(user_wallets, USER_PASSWORD.to_owned(), PROCESS.to_owned()); assert!(result.is_ok()); let user = result.unwrap(); @@ -439,14 +478,20 @@ mod tests { #[test] fn test_login() { - let user_keys = helper_create_user_keys(); - let user = User::new(user_keys, USER_PASSWORD.to_owned(), PROCESS.to_owned()).unwrap(); + let user_wallets = helper_create_user_wallets(); + let user = User::new( + user_wallets.clone(), + USER_PASSWORD.to_owned(), + PROCESS.to_owned(), + ) + .unwrap(); let res = User::login( user.pre_id.clone(), USER_PASSWORD.to_owned(), &user.recover_data, &user.shares, + &user_wallets.get_all_outputs(), ); assert!(res.is_ok()); @@ -458,7 +503,11 @@ mod tests { assert!( format!( "{}", - recover.try_get_secret_spend_key().unwrap().display_secret() + recover + .get_client() + .try_get_secret_spend_key() + .unwrap() + .display_secret() ) == RECOVER_SPEND ) } diff --git a/src/index.ts b/src/index.ts index bdb0bbb..f0304ea 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,7 +1,7 @@ import Services from './services'; import { WebSocketClient } from './websockets'; -const wsurl = "ws://127.0.0.1:8090"; +const wsurl = "ws://192.168.1.44:8090"; document.addEventListener('DOMContentLoaded', async () => { try { const services = await Services.getInstance(); diff --git a/src/services.ts b/src/services.ts index 920e915..e331a5b 100644 --- a/src/services.ts +++ b/src/services.ts @@ -93,21 +93,42 @@ class Services { // To comment if test // if (!Services.instance.isPasswordValid(password)) return; - let label = null; - let birthday_signet = 50000; - let birthday_main = 500000; - const user: createUserReturn = this.sdkClient.create_user(password, label, birthday_main, birthday_signet, this.current_process); + const label = null; + const birthday_signet = 50000; + const birthday_main = 500000; + + const services = await Services.getInstance(); + let createUserReturn: createUserReturn = services.sdkClient.create_user(password, label, birthday_main, birthday_signet, this.current_process); + + let user = createUserReturn.user; + const outputs_list = createUserReturn.output_list_vec; + + const shares = user.shares; + const revokeData = user.revoke_data; + + user.shares = []; + user.revoke_data = null; try { const indexedDb = await IndexedDB.getInstance(); const db = indexedDb.getDb(); - await indexedDb.writeObject(db, indexedDb.getStoreList().AnkUser, user.user, null); - await indexedDb.writeObject(db, indexedDb.getStoreList().SpOutputs, user.output_list_vec, null); + await indexedDb.writeObject(db, indexedDb.getStoreList().AnkUser, user, null); + await indexedDb.writeObject(db, indexedDb.getStoreList().SpOutputs, outputs_list, null); } catch (error) { console.error("Failed to write user object :", error); } - await Services.instance.displayRevokeImage(); + let sp_address = ""; + try { + sp_address = services.sdkClient.get_receiving_address(user.pre_id); + console.info('Using sp_address:', sp_address); + } catch (error) { + console.error(error); + } + + await services.obtainTokenWithFaucet(sp_address); + + await services.displayRevokeImage(new Uint8Array(revokeData)); } public async displayRecover(): Promise { @@ -136,7 +157,7 @@ class Services { const process = processElement.value; console.log("JS password: " + password + " process: " + process); // To comment if test - if (!Services.instance.isPasswordValid(password)) return; + // if (!Services.instance.isPasswordValid(password)) return; // TODO alert("Recover submit to do ..."); @@ -439,11 +460,28 @@ class Services { return success; } - public obtainTokenWithFaucet(wsclient: WebSocketClient, spaddress: string): string | null { + private async pickWebsocketConnectionRandom(): Promise { + const services = await Services.getInstance(); + const websockets = services.websocketConnection; + if (websockets.length === 0) { + console.error("No websocket connection available at the moment"); + return null; + } else { + const random = Math.floor(Math.random() * websockets.length); + return websockets[random]; + } + } + + public async obtainTokenWithFaucet(spaddress: string): Promise { + const services = await Services.getInstance(); + const connection = await services.pickWebsocketConnectionRandom(); + if (!connection) { + return null; + } try { - wsclient.sendMessage(spaddress); + connection.sendMessage('faucet'+spaddress); } catch (error) { - console.error("Failed to obtain tokens with relay ", wsclient.getUrl()); + console.error("Failed to obtain tokens with relay ", connection.getUrl()); return null; } return null;