use std::sync::Arc; use tdn::types::{ message::RpcSendMessage, primitives::{HandleResult, PeerKey, PeerSecretKey, Result}, rpc::{json, rpc_response, RpcError, RpcHandler, RpcParam}, }; use tdn_did::{generate_btc_account, generate_eth_account}; use tdn_storage::local::DStorage; use tokio::sync::mpsc::Sender; use web3::{ contract::{tokens::Tokenize, Contract}, transports::http::Http, types::{Address as EthAddress, Bytes, CallRequest, TransactionParameters, U256}, Web3, }; use crate::global::Global; use crate::storage::{account_db, wallet_db}; use crate::utils::crypto::{decrypt, encrypt}; use super::{ models::{Address, Balance, ChainToken, Network, Token}, ERC20_ABI, ERC721_ABI, }; #[inline] fn wallet_list(wallets: Vec
) -> RpcParam { let mut results = vec![]; for wallet in wallets { results.push(wallet.to_rpc()); } json!(results) } #[inline] fn token_list(network: Network, tokens: Vec) -> RpcParam { let mut results = vec![]; for token in tokens { results.push(token.to_rpc()); } json!([network.to_i64(), results]) } #[inline] fn res_balance(address: &str, network: &Network, balance: &str, token: Option<&Token>) -> RpcParam { if let Some(t) = token { rpc_response( 0, "wallet-balance", json!([address, network.to_i64(), balance, t.to_rpc()]), ) } else { rpc_response( 0, "wallet-balance", json!([address, network.to_i64(), balance]), ) } } async fn loop_token( sender: Sender, db: DStorage, network: Network, address: String, c_token: Option, ) -> Result<()> { // loop get balance of all tokens. let node = network.node(); let chain = network.chain(); let tokens = Token::list(&db, &network)?; if let Some(token) = c_token { let transport = Http::new(node)?; let web3 = Web3::new(transport); let balance = token_balance(&web3, &token.contract, &address, &token.chain).await?; let res = res_balance(&address, &network, &balance, Some(&token)); sender.send(RpcSendMessage(0, res, true)).await?; } else { match chain { ChainToken::ETH => { let transport = Http::new(node)?; let web3 = Web3::new(transport); let balance = web3.eth().balance(address.parse()?, None).await?; let balance = balance.to_string(); let _ = Address::update_balance(&db, &address, &network, &balance); let res = res_balance(&address, &network, &balance, None); sender.send(RpcSendMessage(0, res, true)).await?; for token in tokens { //tokio::time::sleep(std::time::Duration::from_secs(1)).await; let balance = token_balance(&web3, &token.contract, &address, &token.chain).await?; let res = res_balance(&address, &network, &balance, Some(&token)); // update & clean balances. // TODO sender.send(RpcSendMessage(0, res, true)).await?; } } ChainToken::BTC => { // TODO } _ => panic!("nerver here!"), } } Ok(()) } async fn token_check( sender: Sender, db: DStorage, chain: ChainToken, network: Network, address: String, c_str: String, ) -> Result<()> { let account: EthAddress = address.parse()?; let addr: EthAddress = c_str.parse()?; let abi = match chain { ChainToken::ERC20 => ERC20_ABI, ChainToken::ERC721 => ERC721_ABI, _ => return Err(anyhow!("not supported")), }; let node = network.node(); let transport = Http::new(node)?; let web3 = Web3::new(transport); let contract = Contract::from_json(web3.eth(), addr, abi.as_bytes())?; let symbol: String = contract .query("symbol", (), None, Default::default(), None) .await?; let decimal: u64 = match chain { ChainToken::ERC20 => { contract .query("decimals", (), None, Default::default(), None) .await? } _ => 0, // NFT default no decimal. }; let mut token = Token::new(chain, network, symbol, c_str, decimal as i64); token.insert(&db)?; let balance: U256 = contract .query("balanceOf", (account,), None, Default::default(), None) .await?; let balance = balance.to_string(); let res = res_balance(&address, &network, &balance, Some(&token)); sender.send(RpcSendMessage(0, res, true)).await?; Ok(()) } async fn token_balance( web3: &Web3, c_str: &str, address: &str, chain: &ChainToken, ) -> Result { let addr: EthAddress = c_str.parse()?; let account: EthAddress = address.parse()?; let abi = match chain { ChainToken::ERC20 => ERC20_ABI, ChainToken::ERC721 => ERC721_ABI, _ => return Err(anyhow!("not supported")), }; let contract = Contract::from_json(web3.eth(), addr, abi.as_bytes())?; let balance: U256 = contract .query("balanceOf", (account,), None, Default::default(), None) .await?; Ok(balance.to_string()) } async fn token_transfer( from_str: &str, to_str: &str, amount_str: &str, c_str: &str, key: &PeerKey, network: &Network, chain: &ChainToken, ) -> Result { let from: EthAddress = from_str.parse()?; let to: EthAddress = to_str.parse()?; let amount = U256::from_dec_str(amount_str)?; let node = network.node(); let transport = Http::new(node)?; let web3 = Web3::new(transport); let tx = match chain { ChainToken::ERC20 => { let addr: EthAddress = c_str.parse()?; let contract = Contract::from_json(web3.eth(), addr, ERC20_ABI.as_bytes())?; let fn_data = contract .abi() .function("transfer") .and_then(|function| function.encode_input(&(to, amount).into_tokens()))?; TransactionParameters { to: Some(addr), data: Bytes(fn_data), ..Default::default() } } ChainToken::ERC721 => { let addr: EthAddress = c_str.parse()?; let contract = Contract::from_json(web3.eth(), addr, ERC721_ABI.as_bytes())?; let fn_data = contract .abi() .function("safeTransferFrom") .and_then(|function| function.encode_input(&(from, to, amount).into_tokens()))?; TransactionParameters { to: Some(addr), data: Bytes(fn_data), ..Default::default() } } ChainToken::ETH => TransactionParameters { to: Some(to), value: amount, ..Default::default() }, _ => return Err(anyhow!("not supported")), }; let signed = web3 .accounts() .sign_transaction(tx, key.sec_key.raw()) .await?; let result = web3 .eth() .send_raw_transaction(signed.raw_transaction) .await?; Ok(format!("{:?}", result)) } async fn token_gas( from_str: &str, to_str: &str, amount_str: &str, c_str: &str, network: &Network, chain: &ChainToken, ) -> Result<(String, String)> { let from: EthAddress = from_str.parse()?; let to: EthAddress = to_str.parse()?; let amount = U256::from_dec_str(amount_str)?; let node = network.node(); let transport = Http::new(node)?; let web3 = Web3::new(transport); let price = web3.eth().gas_price().await?; let gas = match chain { ChainToken::ERC20 => { let addr: EthAddress = c_str.parse()?; let contract = Contract::from_json(web3.eth(), addr, ERC20_ABI.as_bytes())?; let gas = contract .estimate_gas("transfer", (to, amount), from, Default::default()) .await?; gas * price } ChainToken::ERC721 => { let addr: EthAddress = c_str.parse()?; let contract = Contract::from_json(web3.eth(), addr, ERC721_ABI.as_bytes())?; let gas = contract .estimate_gas( "safeTransferFrom", (from, to, amount), from, Default::default(), ) .await?; gas * price } ChainToken::ETH => { let tx = CallRequest { to: Some(to), value: Some(amount), ..Default::default() }; let gas = web3.eth().estimate_gas(tx, None).await?; price * gas } _ => return Err(anyhow!("not supported")), }; Ok((price.to_string(), gas.to_string())) } async fn nft_check(node: &str, c_str: &str, hash: &str) -> Result { let addr: EthAddress = c_str.parse()?; let tokenid = if hash.starts_with("0x") { U256::from_str_radix(&hash, 16)? } else { U256::from_dec_str(&hash)? }; let transport = Http::new(node)?; let web3 = Web3::new(transport); let contract = Contract::from_json(web3.eth(), addr, ERC721_ABI.as_bytes())?; let owner: EthAddress = contract .query("ownerOf", (tokenid,), None, Default::default(), None) .await?; Ok(format!("{:?}", owner)) } pub(crate) fn new_rpc_handler(handler: &mut RpcHandler) { handler.add_method("wallet-echo", |params, _| async move { Ok(HandleResult::rpc(json!(params))) }); handler.add_method( "wallet-list", |_params: Vec, state: Arc| async move { let pid = state.pid().await; let db_key = state.own.read().await.db_key(&pid)?; let db = wallet_db(&state.base, &pid, &db_key)?; let addresses = Address::list(&db)?; Ok(HandleResult::rpc(wallet_list(addresses))) }, ); handler.add_method( "wallet-generate", |params: Vec, state: Arc| async move { let chain = ChainToken::from_i64(params[0].as_i64().ok_or(RpcError::ParseError)?); let lock = params[1].as_str().ok_or(RpcError::ParseError)?; let pid = state.pid().await; let db_key = state.own.read().await.db_key(&pid)?; let db = wallet_db(&state.base, &pid, &db_key)?; let own_lock = state.own.read().await; let mnemonic = own_lock.mnemonic(&pid, lock, &state.secret)?; let account = own_lock.account(&pid)?; let lang = account.lang(); let pass = account.pass.to_string(); let account_index = account.index as u32; drop(own_lock); let mut results = HandleResult::new(); let pass = if pass.len() > 0 { Some(pass.as_ref()) } else { None }; let index = Address::next_index(&db, &chain)?; let mut address = match chain { ChainToken::ETH | ChainToken::ERC20 | ChainToken::ERC721 => { let key = generate_eth_account(lang, &mnemonic, account_index, index, pass)?; let address = key.peer_id().to_hex(); Address::new(chain, index as i64, address, index == 0) } ChainToken::BTC => { let _sk = generate_btc_account(lang, &mnemonic, account_index, index, pass)?; todo!(); } }; address.insert(&db)?; results.rpcs.push(address.to_rpc()); if address.main { let a_db = account_db(&state.base, &state.secret)?; let mut own_lock = state.own.write().await; let account = own_lock.account_mut(&pid)?; account.pub_height = account.pub_height + 1; account.update_info(&a_db)?; let user = own_lock.clone_user(&pid)?; drop(own_lock); // broadcast to all friends. state.group.read().await.broadcast(user, &mut results); } Ok(results) }, ); handler.add_method( "wallet-import", |params: Vec, state: Arc| async move { let chain = ChainToken::from_i64(params[0].as_i64().ok_or(RpcError::ParseError)?); let secret = params[1].as_str().ok_or(RpcError::ParseError)?; let lock = params[2].as_str().ok_or(RpcError::ParseError)?; let sk: PeerSecretKey = secret.try_into().or(Err(RpcError::ParseError))?; let key = PeerKey::from_sec_key(sk); let addr = key.peer_id().to_hex(); let pid = state.pid().await; let own_lock = state.own.read().await; let ckey = &own_lock.account(&pid)?.encrypt; let db_key = own_lock.db_key(&pid)?; let cbytes = encrypt(&state.secret, lock, ckey, &key.to_db_bytes())?; drop(own_lock); let db = wallet_db(&state.base, &pid, &db_key)?; let mut address = Address::import(chain, addr, cbytes); address.insert(&db)?; Ok(HandleResult::rpc(address.to_rpc())) }, ); handler.add_method( "wallet-token", |params: Vec, state: Arc| async move { let net = Network::from_i64(params[0].as_i64().ok_or(RpcError::ParseError)?); let address = params[1].as_str().ok_or(RpcError::ParseError)?.to_owned(); let pid = state.pid().await; let db_key = state.own.read().await.db_key(&pid)?; let db = wallet_db(&state.base, &pid, &db_key)?; let c_str = if params.len() == 4 { let cid = params[2].as_i64().ok_or(RpcError::ParseError)?; let token = Token::get(&db, &cid)?; Some(token) } else { None }; let tokens = Token::list(&db, &net)?; tokio::spawn(loop_token(state.rpc_send.clone(), db, net, address, c_str)); Ok(HandleResult::rpc(token_list(net, tokens))) }, ); handler.add_method( "wallet-token-import", |params: Vec, state: Arc| async move { let chain = ChainToken::from_i64(params[0].as_i64().ok_or(RpcError::ParseError)?); let net = Network::from_i64(params[1].as_i64().ok_or(RpcError::ParseError)?); let addr = params[2].as_str().ok_or(RpcError::ParseError)?.to_owned(); let c = params[3].as_str().ok_or(RpcError::ParseError)?.to_owned(); let pid = state.pid().await; let db_key = state.own.read().await.db_key(&pid)?; let db = wallet_db(&state.base, &pid, &db_key)?; tokio::spawn(token_check(state.rpc_send.clone(), db, chain, net, addr, c)); Ok(HandleResult::new()) }, ); handler.add_method( "wallet-gas-price", |params: Vec, _state: Arc| async move { let chain = ChainToken::from_i64(params[0].as_i64().ok_or(RpcError::ParseError)?); let network = Network::from_i64(params[1].as_i64().ok_or(RpcError::ParseError)?); let from = params[2].as_str().ok_or(RpcError::ParseError)?; let to = params[3].as_str().ok_or(RpcError::ParseError)?; let amount = params[4].as_str().ok_or(RpcError::ParseError)?; let c_str = params[5].as_str().ok_or(RpcError::ParseError)?; let (price, gas) = token_gas(from, to, amount, c_str, &network, &chain).await?; Ok(HandleResult::rpc(json!([price, gas]))) }, ); handler.add_method( "wallet-transfer", |params: Vec, state: Arc| async move { let chain = ChainToken::from_i64(params[0].as_i64().ok_or(RpcError::ParseError)?); let network = Network::from_i64(params[1].as_i64().ok_or(RpcError::ParseError)?); let from = params[2].as_i64().ok_or(RpcError::ParseError)?; let to = params[3].as_str().ok_or(RpcError::ParseError)?; let amount = params[4].as_str().ok_or(RpcError::ParseError)?; let c_str = params[5].as_str().ok_or(RpcError::ParseError)?; let lock = params[6].as_str().ok_or(RpcError::ParseError)?; let pid = state.pid().await; let own_lock = state.own.read().await; if !own_lock.check_lock(&pid, &lock) { return Err(RpcError::Custom("Lock is invalid!".to_owned())); } let db_key = own_lock.db_key(&pid)?; let db = wallet_db(&state.base, &pid, &db_key)?; let address = Address::get(&db, &from)?; let (mnemonic, pbytes) = if address.is_gen() { (own_lock.mnemonic(&pid, lock, &state.secret)?, vec![]) } else { let ckey = &own_lock.account(&pid)?.encrypt; let pbytes = decrypt(&state.secret, lock, ckey, address.secret.as_ref())?; (String::new(), pbytes) }; let account = own_lock.account(&pid)?; let lang = account.lang(); let pass = account.pass.to_string(); let account_index = account.index as u32; drop(own_lock); let pass = if pass.len() > 0 { Some(pass.as_ref()) } else { None }; let key: PeerKey = if address.is_gen() { match chain { ChainToken::ETH | ChainToken::ERC20 | ChainToken::ERC721 => { generate_eth_account( lang, &mnemonic, account_index, address.index as u32, pass, )? } ChainToken::BTC => { todo!(); } } } else { let key = PeerKey::from_db_bytes(&pbytes) .or(Err(RpcError::Custom("Secret is invalid!".to_owned())))?; if key.peer_id().to_hex().to_lowercase() != address.address.to_lowercase() { return Err(RpcError::Custom("Secret is invalid!".to_owned())); } key }; let hash = token_transfer(&address.address, to, amount, c_str, &key, &network, &chain) .await .map_err(|e| RpcError::Custom(format!("{:?}", e)))?; // NFT: delete old, add new if needed (between accounts). if let Ok(token) = Token::get_by_contract(&db, &network, c_str) { if token.chain == ChainToken::ERC721 { let _ = Balance::delete_by_hash(&db, amount); if let Ok(new) = Address::get_by_address(&db, to) { let _ = Balance::add(&db, new.id, token.id, amount.to_owned()); } } } Ok(HandleResult::rpc(json!([ from, network.to_i64(), [hash, to], ]))) }, ); handler.add_method( "wallet-nft", |params: Vec, state: Arc| async move { let address = params[0].as_i64().ok_or(RpcError::ParseError)?; let token = params[1].as_i64().ok_or(RpcError::ParseError)?; let pid = state.pid().await; let db_key = state.own.read().await.db_key(&pid)?; let db = wallet_db(&state.base, &pid, &db_key)?; let nfts = Balance::list(&db, &address, &token)?; let mut results = vec![]; for nft in nfts { results.push(nft.value); } Ok(HandleResult::rpc(json!([address, token, results]))) }, ); handler.add_method( "wallet-nft-add", |params: Vec, state: Arc| async move { let address = params[0].as_i64().ok_or(RpcError::ParseError)?; let token = params[1].as_i64().ok_or(RpcError::ParseError)?; let hash = params[2].as_str().ok_or(RpcError::ParseError)?.to_owned(); let pid = state.pid().await; let db_key = state.own.read().await.db_key(&pid)?; let db = wallet_db(&state.base, &pid, &db_key)?; let t = Token::get(&db, &token)?; let a = Address::get(&db, &address)?; if t.chain != ChainToken::ERC721 { return Err(RpcError::Custom("token is not erc721".to_owned())); } let owner = nft_check(t.network.node(), &t.contract, &hash) .await .map_err(|e| RpcError::Custom(format!("{:?}", e)))?; if owner == a.address { let balance = Balance::add(&db, address, token, hash)?; Ok(HandleResult::rpc(json!([address, token, balance.value]))) } else { Err(RpcError::Custom("address is not NFT owner".to_owned())) } }, ); handler.add_method( "wallet-main", |params: Vec, state: Arc| async move { let id = params[0].as_i64().ok_or(RpcError::ParseError)?; let pid = state.pid().await; let db_key = state.own.read().await.db_key(&pid)?; let db = wallet_db(&state.base, &pid, &db_key)?; let a_db = account_db(&state.base, &state.secret)?; let _address = Address::get(&db, &id)?; Address::main(&db, &id)?; let mut results = HandleResult::new(); let mut own_lock = state.own.write().await; let account = own_lock.account_mut(&pid)?; account.pub_height = account.pub_height + 1; account.update_info(&a_db)?; let user = own_lock.clone_user(&pid)?; drop(own_lock); // broadcast all friends. state.group.read().await.broadcast(user, &mut results); Ok(HandleResult::new()) }, ); }