Browse Source

Moved data sources out of wallet class.

Added Blockchair support for BTC/BCH.
Support for multiple operations for each ETH tx, introducing support for ERC-20.
Separated DB data to "raw" and "processed". The goal is to be able to wipe processed data (so we can process again with updated rules) without requiring to analyze again input files.
Updated SQL schema to match changes.
master
evilny0 3 years ago
parent
commit
a41b13f3b2
  1. 1
      CMakeLists.txt
  2. 2
      cmake/Modules/Findsqlpp11-connector-mysql.cmake
  3. 78
      sql/mysql.schema.sql
  4. 3
      src/CMakeLists.txt
  5. 137
      src/datasources/blockchain_info.cpp
  6. 39
      src/datasources/blockchain_info.h
  7. 245
      src/datasources/blockchair.cpp
  8. 67
      src/datasources/blockchair.h
  9. 77
      src/datasources/datasource.cpp
  10. 71
      src/datasources/datasource.h
  11. 323
      src/datasources/etherscan.cpp
  12. 42
      src/datasources/etherscan.h
  13. 4
      src/exchanges/kraken.cpp
  14. 4
      src/log.cpp
  15. 14
      src/log.h
  16. 2
      src/money.cpp
  17. 2
      src/money.h
  18. 24
      src/pf.cpp
  19. 33
      src/pf.h
  20. 45
      src/pricesource.cpp
  21. 826
      src/sql.h
  22. 81
      src/wallet.cpp
  23. 20
      src/wallet.h
  24. 316
      src/wallets/bch.cpp
  25. 63
      src/wallets/bch.h
  26. 332
      src/wallets/btc.cpp
  27. 63
      src/wallets/btc.h
  28. 353
      src/wallets/eth.cpp
  29. 63
      src/wallets/eth.h
  30. 27
      src/wallets/wallet_bch.cpp
  31. 36
      src/wallets/wallet_bch.h
  32. 27
      src/wallets/wallet_btc.cpp
  33. 36
      src/wallets/wallet_btc.h
  34. 27
      src/wallets/wallet_eth.cpp
  35. 36
      src/wallets/wallet_eth.h
  36. 324
      src/wallets/wallet_type_btc.cpp
  37. 48
      src/wallets/wallet_type_btc.h
  38. 248
      src/wallets/wallet_type_eth.cpp
  39. 48
      src/wallets/wallet_type_eth.h

1
CMakeLists.txt

@ -38,6 +38,7 @@ set(LIBS ${LIBS} "-lcpprest")
include_directories(src)
include_directories(src/wallets)
include_directories(src/exchanges)
include_directories(src/datasources)
set(CMAKE_CXX_STANDARD 11)

2
cmake/Modules/Findsqlpp11-connector-mysql.cmake

@ -29,7 +29,7 @@ if (SQLPP11_MYSQL_MAIN_HEADER)
)
if("${check_result}" STREQUAL "")
string(APPEND SQLPP11_NOT_FOUND_MESSAGE "\nRejecting found '${SQLPP11_MYSQL_MAIN_HEADER}', it seems to be invalid.")
string(APPEND SQLPP11_MYSQL_NOT_FOUND_MESSAGE "\nRejecting found '${SQLPP11_MYSQL_MAIN_HEADER}', it seems to be invalid.")
unset(SQLPP11_MYSQL_INCLUDE_DIR CACHE)
else()
# Check succeeded, create target

78
sql/mysql.schema.sql

@ -7,6 +7,37 @@ SET time_zone = "+00:00";
/*!40101 SET NAMES utf8mb4 */;
CREATE TABLE `blockchain_btc_raw_tx` (
`raw_tx_id` int(11) NOT NULL,
`blockchain_id` int(11) NOT NULL,
`hash` varchar(200) NOT NULL,
`unix_time` int(11) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE `blockchain_btc_raw_tx_details` (
`raw_tx_detail_id` int(11) NOT NULL,
`raw_tx_id` int(11) NOT NULL,
`amount` decimal(40,18) NOT NULL,
`address` varchar(100) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE `blockchain_eth_raw_tx` (
`raw_tx_id` int(11) NOT NULL,
`blockchain_id` int(11) NOT NULL,
`hash` varchar(200) NOT NULL,
`unix_time` int(11) NOT NULL,
`fee` decimal(40,18) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE `blockchain_eth_raw_tx_details` (
`raw_tx_detail_id` int(11) NOT NULL,
`raw_tx_id` int(11) NOT NULL,
`address_from` varchar(200) NOT NULL,
`address_to` varchar(200) NOT NULL,
`amount` decimal(40,18) NOT NULL,
`amount_coin_id` int(11) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE `coins` (
`coin_id` int(11) NOT NULL,
`coin_name` varchar(50) NOT NULL,
@ -32,7 +63,7 @@ CREATE TABLE `exchanges_ledgers` (
`account_id` int(11) NOT NULL,
`exchange_ledger_id` varchar(50) NOT NULL,
`exchange_reference_id` varchar(50) NOT NULL,
`timestamp` int(11) NOT NULL,
`unix_time` int(11) NOT NULL,
`operation_type` int(11) NOT NULL,
`coin_id` int(11) NOT NULL,
`amount` decimal(40,18) NOT NULL,
@ -46,7 +77,7 @@ CREATE TABLE `exchanges_trades` (
`exchange_order_id` varchar(50) NOT NULL,
`base_coin_id` int(11) NOT NULL,
`quote_coin_id` int(11) NOT NULL,
`timestamp` int(11) NOT NULL,
`unix_time` int(11) NOT NULL,
`type` int(11) NOT NULL,
`order_type` int(11) NOT NULL,
`price` decimal(40,18) NOT NULL,
@ -60,7 +91,8 @@ CREATE TABLE `wallets` (
`wallet_id` int(11) NOT NULL,
`user_id` int(11) NOT NULL,
`type_id` int(11) NOT NULL,
`wallet_name` varchar(50) NOT NULL
`wallet_name` varchar(50) NOT NULL,
`active` tinyint(1) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE `wallets_addresses` (
@ -77,16 +109,40 @@ CREATE TABLE `wallets_balances` (
`balance` decimal(48,18) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE `wallets_btc_raw_tx` (
`raw_tx_id` int(11) NOT NULL,
`wallet_id` int(11) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE `wallets_eth_raw_tx` (
`raw_tx_id` int(11) NOT NULL,
`wallet_id` int(11) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE `wallets_tx` (
`tx_id` int(11) NOT NULL,
`blockchain_tx_id` varchar(200) NOT NULL,
`wallet_id` int(11) NOT NULL,
`amount` decimal(40,18) NOT NULL,
`fee` decimal(40,18) NOT NULL,
`timestamp` int(11) NOT NULL
`amount_coin_id` int(11) NOT NULL,
`fee_coin_id` int(11) NOT NULL,
`unix_time` int(11) NOT NULL,
`operation_type` int(11) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
ALTER TABLE `blockchain_btc_raw_tx`
ADD PRIMARY KEY (`raw_tx_id`);
ALTER TABLE `blockchain_btc_raw_tx_details`
ADD PRIMARY KEY (`raw_tx_detail_id`);
ALTER TABLE `blockchain_eth_raw_tx`
ADD PRIMARY KEY (`raw_tx_id`);
ALTER TABLE `blockchain_eth_raw_tx_details`
ADD PRIMARY KEY (`raw_tx_detail_id`);
ALTER TABLE `exchanges_accounts`
ADD PRIMARY KEY (`account_id`);
@ -103,12 +159,20 @@ ALTER TABLE `wallets_addresses`
ADD PRIMARY KEY (`address_id`);
ALTER TABLE `wallets_balances`
ADD PRIMARY KEY (`wallet_id`);
ADD PRIMARY KEY (`wallet_id`,`coin_id`);
ALTER TABLE `wallets_tx`
ADD PRIMARY KEY (`tx_id`);
ALTER TABLE `blockchain_btc_raw_tx`
MODIFY `raw_tx_id` int(11) NOT NULL AUTO_INCREMENT;
ALTER TABLE `blockchain_btc_raw_tx_details`
MODIFY `raw_tx_detail_id` int(11) NOT NULL AUTO_INCREMENT;
ALTER TABLE `blockchain_eth_raw_tx`
MODIFY `raw_tx_id` int(11) NOT NULL AUTO_INCREMENT;
ALTER TABLE `blockchain_eth_raw_tx_details`
MODIFY `raw_tx_detail_id` int(11) NOT NULL AUTO_INCREMENT;
ALTER TABLE `exchanges_accounts`
MODIFY `account_id` int(11) NOT NULL AUTO_INCREMENT;
ALTER TABLE `exchanges_ledgers`
@ -123,4 +187,4 @@ ALTER TABLE `wallets_tx`
MODIFY `tx_id` int(11) NOT NULL AUTO_INCREMENT;
/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;
/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;
/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;

3
src/CMakeLists.txt

@ -1,6 +1,7 @@
aux_source_directory(. SOURCE_LIST)
aux_source_directory(wallets SOURCE_LIST_WALLETS)
aux_source_directory(exchanges SOURCE_LIST_EXCHANGES)
aux_source_directory(datasources SOURCE_LIST_DATASOURCES)
add_executable(${EXECUTABLE} ${SOURCE_LIST} ${SOURCE_LIST_WALLETS} ${SOURCE_LIST_EXCHANGES})
add_executable(${EXECUTABLE} ${SOURCE_LIST} ${SOURCE_LIST_WALLETS} ${SOURCE_LIST_EXCHANGES} ${SOURCE_LIST_DATASOURCES})
target_link_libraries(${EXECUTABLE} ${LIBS})

137
src/datasources/blockchain_info.cpp

@ -0,0 +1,137 @@
/*
* Copyright (c) 2021, evilny0
*
* This file is part of cpfm.
*
* cpfm is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* cpm 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 General Public License for more details.
* You should have received a copy of the GNU General Public License
* along with cpfm. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "datasources/blockchain_info.h"
list<BlockchainTxDetailsTypeBTC> BlockchainDataSourceBTC_BlockchainInfo::getTxDetailsListForAddress(string address)
{
linfo << "Blockchain.info : Analyzing address: " << address;
list<BlockchainTxDetailsTypeBTC> l;
if (!bfs::exists(getCacheFilenameForAddress(address)))
{
saveBlockchainAddressDataToCacheFile(address);
}
if (!bfs::exists(getCacheFilenameForAddress(address)))
{
lerr << "Blockchain.info : cache file could not be found. Address data was not retrieved.";
return l;
}
ifstream f;
f.open(getCacheFilenameForAddress(address));
json::value jvalue = json::value::parse(f);
f.close();
linfo << "Blockchain.info : analyzing address: " << jvalue["address"].as_string();
for (int i=0;i<jvalue["txs"].size();i++)
{
BlockchainTxDetailsTypeBTC tx;
tx.hash = jvalue["txs"][i]["hash"].as_string();
tx.time.setFromUnixTime(jvalue["txs"][i]["time"].as_integer());
for (int j=0;j<jvalue["txs"][i]["inputs"].size();j++)
{
string addr = jvalue["txs"][i]["inputs"][j]["prev_out"]["addr"].as_string();
__int64 amount = jvalue["txs"][i]["inputs"][j]["prev_out"]["value"].as_integer();
Money m = amount;
Money mdecimals = m/100000000;
// The same address can be multiple time in the inputs, so we need to sum the amounts.
tx.inputs[addr] += mdecimals;
}
for (int j=0;j<jvalue["txs"][i]["out"].size();j++)
{
string addr = jvalue["txs"][i]["out"][j]["addr"].as_string();
__int64 amount = jvalue["txs"][i]["out"][j]["value"].as_integer();
Money m = amount;
Money mdecimals = m/100000000;
// The same address can only be once in the outputs.
tx.outputs[addr] = mdecimals;
}
l.push_back(tx);
}
return l;
}
string BlockchainDataSourceBTC_BlockchainInfo::getCacheFilenameForAddress(string address)
{
string cacheFilename("data/cache/blockchain.info/btc/" + address);
return cacheFilename;
}
void BlockchainDataSourceBTC_BlockchainInfo::saveBlockchainAddressDataToCacheFile(string address)
{
try
{
linfo << "Blockchain.info : querying API about " << address;
string sRequestURL = "/rawaddr/";
sRequestURL += address;
http_client apiclient("https://blockchain.info/");
m_currentRequestCacheFilename = getCacheFilenameForAddress(address);
apiclient.request(methods::GET,sRequestURL).then([](http_response response)
{
if (response.status_code() == status_codes::OK)
{
ldebug << "Blockchain.info : response OK.";
return response.extract_json();
}
else if (response.status_code() == status_codes::TooManyRequests)
{
lwarn << "Blockchain.info : too many queries! We are being rate limited.";
pplx::task_from_result(json::value());
}
return pplx::task_from_result(json::value());
})
.then([this](pplx::task<json::value> previousTask)
{
if (previousTask.get() != json::value::null())
{
linfo << "Blockchain.info : saving query result to " << m_currentRequestCacheFilename;
ofstream f;
f.open(m_currentRequestCacheFilename);
f << previousTask.get();
f.close();
}
else
{
lerr << "Blockchain.info : query result is empty. Nothing will be saved to the cache file.";
}
})
.wait();
}
catch(const http::http_exception& e)
{
lerr << "Blockchain.info : failed to query API about " << address;
}
}

39
src/datasources/blockchain_info.h

@ -0,0 +1,39 @@
/*
* Copyright (c) 2021, evilny0
*
* This file is part of cpfm.
*
* cpfm is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* cpm 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 General Public License for more details.
* You should have received a copy of the GNU General Public License
* along with cpfm. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef CPFM_DATASOURCE_BLOCKCHAIN_INFO_H_INCLUDED
#define CPFM_DATASOURCE_BLOCKCHAIN_INFO_H_INCLUDED
#include "datasources/datasource.h"
class BlockchainDataSourceBTC_BlockchainInfo
{
public:
list<BlockchainTxDetailsTypeBTC> getTxDetailsListForAddress(string address);
private:
string getCacheFilenameForAddress(string address);
void saveBlockchainAddressDataToCacheFile(string address);
string m_currentRequestCacheFilename;
};
#endif // CPFM_DATASOURCE_BLOCKCHAIN_INFO_H_INCLUDED

245
src/datasources/blockchair.cpp

@ -0,0 +1,245 @@
/*
* Copyright (c) 2021, evilny0
*
* This file is part of cpfm.
*
* cpfm is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* cpm 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 General Public License for more details.
* You should have received a copy of the GNU General Public License
* along with cpfm. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "datasources/blockchair.h"
Time BlockchainDataSourceTypeBTC_Blockchair::getTimeFromString(string s)
{
std::istringstream in{s};
date::sys_time<std::chrono::seconds> tp;
in >> date::parse("%F %T", tp);
Time t;
t.setFromUnixTime(std::chrono::system_clock::to_time_t(tp));
return t;
}
list<BlockchainTxDetailsTypeBTC> BlockchainDataSourceTypeBTC_Blockchair::getTxDetailsListForAddresses(list<string> addresses)
{
linfo << "Blockchair : analyzing address list";
list<BlockchainTxDetailsTypeBTC> l;
if (!bfs::exists(getCacheFilenameForAddresses(addresses)))
{
saveBlockchainAddressesDataToCacheFile(addresses);
}
if (!bfs::exists(getCacheFilenameForAddresses(addresses)))
{
lerr << "Blockchair : cache file could not be found. Addresses data was not retrieved.";
return l;
}
ifstream f;
f.open(getCacheFilenameForAddresses(addresses));
json::value jvalue = json::value::parse(f);
f.close();
for (int i=0;i<jvalue["data"]["transactions"].size();i++)
{
string txHash = jvalue["data"]["transactions"][i].as_string();
linfo << "Blockchair : found tx " << txHash << ". Starting analysis.";
if (!bfs::exists(getCacheFilenameForTx(txHash)))
{
saveBlockchainTxDataToCacheFile(txHash);
}
if (!bfs::exists(getCacheFilenameForTx(txHash)))
{
lerr << "Blockchair : cache file for tx " << txHash << " could not be found. Tx data was not retrieved.";
}
ifstream fTx;
fTx.open(getCacheFilenameForTx(txHash));
json::value jvalueTx = json::value::parse(fTx);
fTx.close();
BlockchainTxDetailsTypeBTC tx;
tx.hash = txHash;
tx.time = getTimeFromString (jvalueTx["data"][txHash]["transaction"]["time"].as_string());
for (int j=0;j<jvalueTx["data"][txHash]["inputs"].size();j++)
{
string addr = jvalueTx["data"][txHash]["inputs"][j]["recipient"].as_string();
__int64 amount = jvalueTx["data"][txHash]["inputs"][j]["value"].as_integer();
Money m = amount;
Money mdecimals = m/100000000;
// The same address can be multiple time in the inputs, so we need to sum the amounts.
tx.inputs[addr] += mdecimals;
}
for (int j=0;j<jvalueTx["data"][txHash]["outputs"].size();j++)
{
string addr = jvalueTx["data"][txHash]["outputs"][j]["recipient"].as_string();
__int64 amount = jvalueTx["data"][txHash]["outputs"][j]["value"].as_integer();
Money m = amount;
Money mdecimals = m/100000000;
// The same address can only be once in the outputs.
tx.outputs[addr] = mdecimals;
}
l.push_back(tx);
linfo << "Blockchair : finished analysis for tx " << txHash << ".";
}
linfo << "Blockchair : finished analysis for address list.";
return l;
}
string BlockchainDataSourceTypeBTC_Blockchair::getCacheFilenameForAddresses(list<string> addresses)
{
string s;
for (const auto a: addresses)
s = s+a;
string cacheFilename("data/cache/blockchair/"+m_blockchainName+"/addresses/" + s);
return cacheFilename;
}
string BlockchainDataSourceTypeBTC_Blockchair::getCacheFilenameForTx(string txHash)
{
string cacheFilename("data/cache/blockchair/"+m_blockchainName+"/tx/" + txHash);
return cacheFilename;
}
void BlockchainDataSourceTypeBTC_Blockchair::saveBlockchainAddressesDataToCacheFile(list<string> addresses)
{
try
{
string s;
for (const auto a: addresses)
{
if (s.length())
s = s+","+a;
else
s = a;
}
linfo << "Blockchair : querying API about addresses : " << s;
string sRequestURL = "/"+m_blockchainName+"/dashboards/addresses/";
sRequestURL += s;
http_client apiclient("https://api.blockchair.com/");
m_currentRequestCacheFilename = getCacheFilenameForAddresses(addresses);
ltrace << "Blockchair : query : " << sRequestURL;
apiclient.request(methods::GET,sRequestURL).then([](http_response response)
{
if (response.status_code() == status_codes::OK)
{
ldebug << "Blockchair : response OK.";
return response.extract_json();
}
else if (response.status_code() == status_codes::TooManyRequests)
{
lwarn << "Blockchair : too many queries! We are being rate limited.";
pplx::task_from_result(json::value());
}
return pplx::task_from_result(json::value());
})
.then([this](pplx::task<json::value> previousTask)
{
if (previousTask.get() != json::value::null())
{
linfo << "Blockchair : saving query result to " << m_currentRequestCacheFilename;
ofstream f;
f.open(m_currentRequestCacheFilename);
f << previousTask.get();
f.close();
}
else
{
lerr << "Blockchair : query result is empty. Nothing will be saved to the cache file.";
}
})
.wait();
}
catch(const http::http_exception& e)
{
lerr << "Blockchair : failed to query API";
}
}
void BlockchainDataSourceTypeBTC_Blockchair::saveBlockchainTxDataToCacheFile(string txHash)
{
try
{
linfo << "Blockchair : querying API about tx : " << txHash;
string sRequestURL = "/"+m_blockchainName+"/dashboards/transaction/";
sRequestURL += txHash;
http_client apiclient("https://api.blockchair.com/");
m_currentRequestCacheFilename = getCacheFilenameForTx(txHash);
ltrace << "Blockchair : query : " << sRequestURL;
apiclient.request(methods::GET,sRequestURL).then([](http_response response)
{
if (response.status_code() == status_codes::OK)
{
ldebug << "Blockchair : response OK.";
return response.extract_json();
}
else if (response.status_code() == status_codes::TooManyRequests)
{
lwarn << "Blockchair : too many queries! We are being rate limited.";
pplx::task_from_result(json::value());
}
return pplx::task_from_result(json::value());
})
.then([this](pplx::task<json::value> previousTask)
{
if (previousTask.get() != json::value::null())
{
linfo << "Blockchair : saving query result to " << m_currentRequestCacheFilename;
ofstream f;
f.open(m_currentRequestCacheFilename);
f << previousTask.get();
f.close();
}
else
{
lerr << "Blockchair : query result is empty. Nothing will be saved to the cache file.";
}
})
.wait();
}
catch(const http::http_exception& e)
{
lerr << "Blockchair : failed to query API about " << txHash;
}
}

67
src/datasources/blockchair.h

@ -0,0 +1,67 @@
/*
* Copyright (c) 2021, evilny0
*
* This file is part of cpfm.
*
* cpfm is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* cpm 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 General Public License for more details.
* You should have received a copy of the GNU General Public License
* along with cpfm. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef CPFM_DATASOURCE_BLOCKCHAIR_H_INCLUDED
#define CPFM_DATASOURCE_BLOCKCHAIR_H_INCLUDED
#include "datasources/datasource.h"
class BlockchainDataSourceTypeBTC_Blockchair
{
public:
list<BlockchainTxDetailsTypeBTC> getTxDetailsListForAddresses(list<string> addresses);
private:
string getCacheFilenameForAddresses(list<string> address);
string getCacheFilenameForTx(string txHash);
void saveBlockchainAddressesDataToCacheFile(list<string> address);
void saveBlockchainTxDataToCacheFile(string txHash);
Time getTimeFromString(string s);
string m_currentRequestCacheFilename;
protected:
string m_blockchainName;
virtual void dummyMakeAbstract() = 0;
};
class BlockchainDataSourceBTC_Blockchair : public BlockchainDataSourceTypeBTC_Blockchair
{
public:
BlockchainDataSourceBTC_Blockchair() { m_blockchainName = "bitcoin"; }
protected:
virtual void dummyMakeAbstract(){};
};
class BlockchainDataSourceBCH_Blockchair : public BlockchainDataSourceTypeBTC_Blockchair
{
public:
BlockchainDataSourceBCH_Blockchair() { m_blockchainName = "bitcoin-cash"; }
protected:
virtual void dummyMakeAbstract(){};
};
#endif // CPFM_DATASOURCE_BLOCKCHAIR_H_INCLUDED

77
src/datasources/datasource.cpp

@ -0,0 +1,77 @@
/*
* Copyright (c) 2021, evilny0
*
* This file is part of cpfm.
*
* cpfm is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* cpm 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 General Public License for more details.
* You should have received a copy of the GNU General Public License
* along with cpfm. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "datasources/datasource.h"
#include "datasources/blockchain_info.h"
#include "datasources/blockchair.h"
#include "datasources/etherscan.h"
list<BlockchainTxDetailsTypeBTC> BlockchainDataSourceBTC::getTxDetailsListForAddresses(list<string> addresses)
{
list<BlockchainTxDetailsTypeBTC> l;
// Blockchain.info
/*
BlockchainDataSourceBTC_BlockchainInfo bci;
list<string> hashList;
for (auto const address: addresses)
{
list<BlockchainTxDetailsTypeBTC> listForSingleAddress = bci.getTxDetailsListForAddress(address);
for (auto const tx: listForSingleAddress)
{
if (std::find(hashList.begin(), hashList.end(), tx.hash) == hashList.end())
{
hashList.push_back(tx.hash);
l.push_back(tx);
}
}
}
*/
// Blockchair
BlockchainDataSourceBTC_Blockchair bc;
l = bc.getTxDetailsListForAddresses(addresses);
return l;
}
list<BlockchainTxDetailsTypeBTC> BlockchainDataSourceBCH::getTxDetailsListForAddresses(list<string> addresses)
{
list<BlockchainTxDetailsTypeBTC> l;
// Blockchair
BlockchainDataSourceBCH_Blockchair bc;
l = bc.getTxDetailsListForAddresses(addresses);
return l;
}
list<BlockchainTxDetailsTypeETH> BlockchainDataSourceETH::getTxDetailsListForAddress(string address)
{
list<BlockchainTxDetailsTypeETH> l;
// Etherscan
BlockchainDataSourceETH_Etherscan bc;
l = bc.getTxDetailsListForAddress(address);
return l;
}

71
src/datasources/datasource.h

@ -0,0 +1,71 @@
/*
* Copyright (c) 2021, evilny0
*
* This file is part of cpfm.
*
* cpfm is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* cpm 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 General Public License for more details.
* You should have received a copy of the GNU General Public License
* along with cpfm. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef CPFM_DATASOURCE_H_INCLUDED
#define CPFM_DATASOURCE_H_INCLUDED
#include "pf.h"
class BlockchainTxDetailsTypeBTC
{
public:
string hash;
Time time;
map <string,Money> inputs;
map <string,Money> outputs;
};
class BlockchainTxOperationTypeETH
{
public:
string addressFrom;
string addressTo;
Money amount;
int amountCoinId;
};
class BlockchainTxDetailsTypeETH
{
public:
string hash;
Time time;
list<BlockchainTxOperationTypeETH> operations;
Money fee;
};
class BlockchainDataSourceBTC
{
public:
list<BlockchainTxDetailsTypeBTC> getTxDetailsListForAddresses(list<string> addresses);
};
class BlockchainDataSourceBCH
{
public:
list<BlockchainTxDetailsTypeBTC> getTxDetailsListForAddresses(list<string> addresses);
};
class BlockchainDataSourceETH
{
public:
list<BlockchainTxDetailsTypeETH> getTxDetailsListForAddress(string address);
};
#endif // CPFM_DATASOURCE_H_INCLUDED

323
src/datasources/etherscan.cpp

@ -0,0 +1,323 @@
/*
* Copyright (c) 2021, evilny0
*
* This file is part of cpfm.
*
* cpfm is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* cpm 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 General Public License for more details.
* You should have received a copy of the GNU General Public License
* along with cpfm. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "datasources/etherscan.h"
#define TXLIST_NORMAL "txlist"
#define TXLIST_INTERNAL "txlistinternal"
#define TXLIST_TOKENS "tokentx"
list<BlockchainTxDetailsTypeETH> BlockchainDataSourceETH_Etherscan::getTxDetailsListForAddress(string address)
{
linfo << "Etherscan : Analyzing address: " << address;
if (!retrieveCacheFilesForAddress(address))
{
list<BlockchainTxDetailsTypeETH> l;
lerr << "Etherscan : cache files could not be found. Address data was not retrieved.";
return l;
}
list<string> hashListNormal;
list<string> hashListInternal;
list<string> hashListToken;
list<BlockchainTxDetailsTypeETH> lNormal = getTxDetailsListForAddressTxType(address,TXLIST_NORMAL,hashListNormal);
list<BlockchainTxDetailsTypeETH> lInternal = getTxDetailsListForAddressTxType(address,TXLIST_INTERNAL,hashListInternal);
list<BlockchainTxDetailsTypeETH> lToken = getTxDetailsListForAddressTxType(address,TXLIST_TOKENS,hashListToken);
list<BlockchainTxDetailsTypeETH> l = lNormal;
// Add internals tx to the list
for (auto const tx: lInternal)
{
BlockchainTxDetailsTypeETH txNew = tx;
list<BlockchainTxDetailsTypeETH>::iterator it;
for (it=l.begin();it!=l.end();it++)
{
if (it->hash == tx.hash)
{
txNew = (*it);
for (const auto op: tx.operations)
{
txNew.operations.push_back(op);
}
l.erase(it);
break;
}
}
l.push_back(txNew);
}
// Add tokens tx to the list
for (auto const tx: lToken)
{
BlockchainTxDetailsTypeETH txNew = tx;
list<BlockchainTxDetailsTypeETH>::iterator it;
for (it=l.begin();it!=l.end();it++)
{
if (it->hash == tx.hash)
{
txNew = (*it);
for (const auto op: tx.operations)
{
txNew.operations.push_back(op);
}
l.erase(it);
break;
}
}
l.push_back(txNew);
}
return l;
}
list<BlockchainTxDetailsTypeETH> BlockchainDataSourceETH_Etherscan::getTxDetailsListForAddressTxType(string address, string type, list<string>& hashList)
{
list<BlockchainTxDetailsTypeETH> l;
ifstream f;
f.open(getCacheFilenameForAddress(address,type));
json::value jvalue = json::value::parse(f);
f.close();
linfo << "Etherscan : analyzing " << type << " for address: " << address;
if (!jvalue["status"].as_string().compare("1"))
{
for (int i=0;i<jvalue["result"].size();i++)
{
ltrace << "Found tx " << jvalue["result"][i]["hash"].as_string();
BlockchainTxDetailsTypeETH tx;
// Search if the hash is already in the list. If yes, remove the element.
// It will be added after the operation list is updated.
list<BlockchainTxDetailsTypeETH>::iterator it;
for (it=l.begin();it!=l.end();it++)
{
if (it->hash == jvalue["result"][i]["hash"].as_string())
{
ltrace << "Tx already existed. Removing from list so it can be updated with new values.";
tx = (*it);
l.erase(it);
break;
}
}
tx.hash = jvalue["result"][i]["hash"].as_string();
tx.time.setFromUnixTime(strtol(jvalue["result"][i]["timeStamp"].as_string().c_str(),NULL,10));
Money gasUsed (jvalue["result"][i]["gasUsed"]);
Money gasPrice (jvalue["result"][i]["gasPrice"]);
Money fee = gasPrice*gasUsed;
Money feeDivider("1000000000000000000");
fee /= feeDivider;
tx.fee = fee;
if (type == TXLIST_NORMAL)
{
BlockchainTxOperationTypeETH op;
op.addressFrom = jvalue["result"][i]["from"].as_string();
op.addressTo = jvalue["result"][i]["to"].as_string();
op.amountCoinId = CPFM_COIN_ID_ETH;
Money divider("1000000000000000000");
Money m(jvalue["result"][i]["value"]);
m /= divider;
op.amount = m;
ltrace << "Operation. Amount: " << op.amount << ". Coin : " << getCoinName(op.amountCoinId);
tx.operations.push_back(op);
}
else if (type == TXLIST_TOKENS)
{
BlockchainTxOperationTypeETH op;
op.addressFrom = jvalue["result"][i]["from"].as_string();
op.addressTo = jvalue["result"][i]["to"].as_string();
op.amountCoinId = GetCoinIdFromContractAddress(jvalue["result"][i]["contractAddress"].as_string());
if (!op.amountCoinId)
{
lerr << "Unknown token : " << jvalue["result"][i]["tokenSymbol"].as_string() << ". Tx will be ignored.";
continue;
}
Money divider = 1;
int multiplier = strtol(jvalue["result"][i]["tokenDecimal"].as_string().c_str(),NULL,10);
for (int i=0;i<multiplier;i++)
{
divider*=10;
}
Money m(jvalue["result"][i]["value"]);
m /= divider;
op.amount = m;
ltrace << "Operation. Amount: " << op.amount << ". Coin : " << getCoinName(op.amountCoinId);
tx.operations.push_back(op);
}
else if (type == TXLIST_TOKENS)
{
lerr << "Internal tx are not supported.";
continue;
}
else
{
lerr << "Unknown tx type : " << type << ". Tx will be ignored.";
continue;
}
hashList.push_back(tx.hash);
l.push_back(tx);
}
}
return l;
}
bool BlockchainDataSourceETH_Etherscan::retrieveCacheFilesForAddress(string address)
{
if (!bfs::exists(getCacheFilenameForAddress(address,TXLIST_NORMAL)))
{
saveBlockchainAddressDataToCacheFile(address,TXLIST_NORMAL);
}
if (!bfs::exists(getCacheFilenameForAddress(address,TXLIST_NORMAL)))
{
lerr << "Etherscan : cache file for " << TXLIST_NORMAL << " could not be found.";
return false;
}
if (!bfs::exists(getCacheFilenameForAddress(address,TXLIST_INTERNAL)))
{
saveBlockchainAddressDataToCacheFile(address,TXLIST_INTERNAL);
}
if (!bfs::exists(getCacheFilenameForAddress(address,TXLIST_INTERNAL)))
{
lerr << "Etherscan : cache file for " << TXLIST_INTERNAL << " could not be found.";
return false;
}
if (!bfs::exists(getCacheFilenameForAddress(address,TXLIST_TOKENS)))
{
saveBlockchainAddressDataToCacheFile(address,TXLIST_TOKENS);
}
if (!bfs::exists(getCacheFilenameForAddress(address,TXLIST_TOKENS)))
{
lerr << "Etherscan : cache file for " << TXLIST_TOKENS << " could not be found.";
return false;
}
return true;
}
string BlockchainDataSourceETH_Etherscan::getCacheFilenameForAddress(string address, string type)
{
string cacheFilename("data/cache/etherscan.io/" + type + "/" + address);
return cacheFilename;
}
void BlockchainDataSourceETH_Etherscan::saveBlockchainAddressDataToCacheFile(string address, string type)
{
try
{
sleep (5);
linfo << "Etherscan : querying API about " << type << " for " << address;
string sRequestURL = "/api?module=account&action="+type+"&address=";
sRequestURL += address;
http_client apiclient("https://api.etherscan.io");
m_currentRequestCacheFilename = getCacheFilenameForAddress(address,type);
apiclient.request(methods::GET,sRequestURL).then([](http_response response)
{
if (response.status_code() == status_codes::OK)
{
ldebug << "Etherscan : response OK.";
return response.extract_json();
}
return pplx::task_from_result(json::value());
})
.then([this](pplx::task<json::value> previousTask)
{
if (previousTask.get() != json::value::null())
{
linfo << "Etherscan : saving query result to " << m_currentRequestCacheFilename;
ofstream f;
f.open(m_currentRequestCacheFilename);
f << previousTask.get();
f.close();
}
else
{
lerr << "Etherscan : query result is empty. Nothing will be saved to the cache file.";
}
})
.wait();
}
catch(const http::http_exception& e)
{
lerr << "Etherscan : failed to query API about " << address;
}
}
int BlockchainDataSourceETH_Etherscan::GetCoinIdFromContractAddress(string contractAddress)
{
// LPT - Livepeer Token
if (contractAddress == "0x58b6a8a3302369daec383334672404ee733ab239")
return CPFM_COIN_ID_LPT;
// LEND - EthLend - Migrated to AAVE.
if (contractAddress == "0x80fb784b7ed66730e8b1dbd9820afd29931aab03")
return CPFM_COIN_ID_LEND;
// AAVE
if (contractAddress == "0x7fc66500c84a76ad7e9c93437bfc5ac33e2ddae9")
return CPFM_COIN_ID_AAVE;
// BCDT - Blockchain Certified Data Token.
if (contractAddress == "0xacfa209fb73bf3dd5bbfb1101b9bc999c49062a5")
return CPFM_COIN_ID_BCDT;
// KNC - Kyber Network
if (contractAddress == "0xdd974d5c2e2928dea5f71b9825b8b646686bd200")
return CPFM_COIN_ID_KNC;
// AIR - AirToken
if (contractAddress == "0x27dce1ec4d3f72c3e457cc50354f1f975ddef488")
return CPFM_COIN_ID_AIR;
lerr << "Unsupported contact address " << contractAddress;
return 0;
}

42
src/datasources/etherscan.h

@ -0,0 +1,42 @@
/*
* Copyright (c) 2021, evilny0
*
* This file is part of cpfm.
*
* cpfm is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* cpm 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 General Public License for more details.
* You should have received a copy of the GNU General Public License
* along with cpfm. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef CPFM_DATASOURCE_ETHERSCAN_H_INCLUDED
#define CPFM_DATASOURCE_ETHERSCAN_H_INCLUDED
#include "datasources/datasource.h"
class BlockchainDataSourceETH_Etherscan
{
public:
list<BlockchainTxDetailsTypeETH> getTxDetailsListForAddress(string address);
private:
list<BlockchainTxDetailsTypeETH> getTxDetailsListForAddressTxType(string address, string type, list<string>& hashList);
void saveBlockchainAddressDataToCacheFile(string address, string type);
string getCacheFilenameForAddress(string address, string type);
bool retrieveCacheFilesForAddress(string address);
int GetCoinIdFromContractAddress(string contractAddress);
string m_currentRequestCacheFilename;
};
#endif // CPFM_DATASOURCE_ETHERSCAN_H_INCLUDED

4
src/exchanges/kraken.cpp

@ -47,10 +47,10 @@ void ExchangeHandlerKraken::analyzeUserData()
getAccountBalance();
analyzeAccountBalance();
//getAccountTrades();
getAccountTrades();
//analyzeAccountTrades();
//getAccountLedgers();
getAccountLedgers();
//analyzeAccountLedgers();
}

4
src/log.cpp

@ -23,7 +23,7 @@
#include <chrono>
#include "log.h"
using namespace farm;
using namespace cpfm;
using namespace log;
const char* ErrorLogger::name() { return "ERROR"; }
@ -34,6 +34,8 @@ const char* InfoLogger::name() { return "INFO "; }
const char* InfoLogger::color() { return L_White; }
const char* DebugLogger::name() { return "DEBUG"; }
const char* DebugLogger::color() { return L_Teal; }
const char* TraceLogger::name() { return "TRACE"; }
const char* TraceLogger::color() { return L_Violet; }
LoggerBase::LoggerBase()
{

14
src/log.h

@ -28,13 +28,14 @@
using namespace std;
#define llog(X) farm::log::Logger<X>()
#define lerr llog(farm::log::ErrorLogger)
#define linfo llog(farm::log::InfoLogger)
#define lwarn llog(farm::log::WarningLogger)
#define ldebug llog(farm::log::DebugLogger)
#define llog(X) cpfm::log::Logger<X>()
#define lerr llog(cpfm::log::ErrorLogger)
#define linfo llog(cpfm::log::InfoLogger)
#define lwarn llog(cpfm::log::WarningLogger)
#define ldebug llog(cpfm::log::DebugLogger)
#define ltrace llog(cpfm::log::TraceLogger)
namespace farm { namespace log {
namespace cpfm { namespace log {
#define L_Reset "\x1b[0m" // Text Reset
@ -107,6 +108,7 @@ struct ErrorLogger: public DefaultLogger { static const char* name(); static con
struct WarningLogger: public DefaultLogger { static const char* name(); static const char* color(); };
struct InfoLogger: public DefaultLogger { static const char* name(); static const char* color(); };
struct DebugLogger: public DefaultLogger { static const char* name(); static const char* color(); };
struct TraceLogger: public DefaultLogger { static const char* name(); static const char* color(); };
class LoggerBase
{

2
src/money.cpp

@ -23,7 +23,7 @@
Money::Money ()
{
m_amount = 0;
}
Money::Money (bmp::mpf_float_50 x)

2
src/money.h

@ -54,7 +54,7 @@ public:
Money& operator/=(const Money& x);
friend Money operator/(Money a, const Money& b) { a /= b; return a; }
Money operator-() { return Money(-m_amount); }
//Money operator-() { return Money(-m_amount); }
friend bool operator==(const Money& a, const int& b);
friend bool operator!=(const Money& a, const int& b) { return !operator==(a,b); }

24
src/pf.cpp

@ -40,8 +40,8 @@ void PortfolioManager::doTestStuff()
// Test user 1
emptyUserBalances(1);
ExchangesManager exchangesManager(1);
exchangesManager.analyzeUserAccounts();
// ExchangesManager exchangesManager(1);
// exchangesManager.analyzeUserAccounts();
WalletsManager walletsManager(1);
walletsManager.analyzeUserWallets();
@ -49,6 +49,7 @@ void PortfolioManager::doTestStuff()
displayUserBalances(1);
// Test user 2
/*
emptyUserBalances(2);
ExchangesManager exchangesManagerTwo(2);
@ -58,6 +59,7 @@ void PortfolioManager::doTestStuff()
walletsManagerTwo.analyzeUserWallets();
displayUserBalances(2);
*/
}
void PortfolioManager::emptyUserBalances(int userId)
@ -155,6 +157,24 @@ string getCoinName(int coinId)
case CPFM_COIN_ID_BTG:
s = "BTG";
break;
case CPFM_COIN_ID_LPT:
s = "LPT";
break;
case CPFM_COIN_ID_BCDT:
s = "BCDT";
break;
case CPFM_COIN_ID_AIR:
s = "AIR";
break;
case CPFM_COIN_ID_AAVE:
s = "AAVE";
break;
case CPFM_COIN_ID_LEND:
s = "LEND";
break;
case CPFM_COIN_ID_KNC:
s = "KNC";
break;
default:
s = "Error!";
break;

33
src/pf.h

@ -39,14 +39,31 @@
#include "log.h"
#include "money.h"
#define CPFM_COIN_ID_BTC 1
#define CPFM_COIN_ID_LTC 2
#define CPFM_COIN_ID_EUR 3
#define CPFM_COIN_ID_ICN 4
#define CPFM_COIN_ID_ETC 5
#define CPFM_COIN_ID_ETH 6
#define CPFM_COIN_ID_BCH 7
#define CPFM_COIN_ID_BTG 8
#define CPFM_COIN_ID_BTC 1
#define CPFM_COIN_ID_LTC 2
#define CPFM_COIN_ID_EUR 3
#define CPFM_COIN_ID_ICN 4
#define CPFM_COIN_ID_ETC 5
#define CPFM_COIN_ID_ETH 6
#define CPFM_COIN_ID_BCH 7
#define CPFM_COIN_ID_BTG 8
#define CPFM_COIN_ID_KNC 9
#define CPFM_COIN_ID_AIR 10
#define CPFM_COIN_ID_LEND 11
#define CPFM_COIN_ID_AAVE 12
#define CPFM_COIN_ID_LPT 13
#define CPFM_COIN_ID_BCDT 14
class Time
{
public:
void setFromUnixTime(time_t unixTime) { m_unixTime = unixTime; }
const time_t toUnixTime() const { return m_unixTime; }
private:
time_t m_unixTime;
//date::sys_time<std::chrono::milliseconds>
};
namespace bmp = boost::multiprecision;
namespace bfs = boost::filesystem;

45
src/pricesource.cpp

@ -115,7 +115,7 @@ Money PriceSourceCryptoWatch::getCoinPriceFromData(int coinId)
{
Money x (jvalue["result"]["market:kraken:icnbtc"]);
Money y (jvalue["result"]["market:kraken:btceur"]);
ldebug << "ICN is not quoted in EUR. BTC price is : " << x;
// ldebug << "ICN is not quoted in EUR. BTC price is : " << x;
m = x*y;
break;
}
@ -141,10 +141,51 @@ Money PriceSourceCryptoWatch::getCoinPriceFromData(int coinId)
{
Money x (jvalue["result"]["market:bitfinex:btgbtc"]);
Money y (jvalue["result"]["market:kraken:btceur"]);
ldebug << "BTG is not quoted in EUR. BTC price is : " << x;
// ldebug << "BTG is not quoted in EUR. BTC price is : " << x;
m = x*y;
break;
}
case CPFM_COIN_ID_KNC:
{
Money x (jvalue["result"]["market:binance:kncbtc"]);
Money y (jvalue["result"]["market:kraken:btceur"]);
// ldebug << "KNC is not quoted in EUR. BTC price is : " << x;
m = x*y;
break;
}
case CPFM_COIN_ID_AIR:
{
// ldebug << "AIR is not quoted. Returning 0.";
m = 0;
break;
}
case CPFM_COIN_ID_LEND:
{
Money x (jvalue["result"]["market:kraken:aaveeur"]);
// ldebug << "LEND is not quoted. Using AAVE.";
m = x/100;
break;
}
case CPFM_COIN_ID_AAVE:
{
Money x (jvalue["result"]["market:kraken:aaveeur"]);
m = x;
break;
}
case CPFM_COIN_ID_LPT:
{
Money x (jvalue["result"]["market:poloniex:lptbtc"]);
Money y (jvalue["result"]["market:kraken:btceur"]);
// ldebug << "LPT is not quoted in EUR. BTC price is : " << x;
m = x*y;
break;
}
case CPFM_COIN_ID_BCDT:
{
// ldebug << "BCDT is not quoted. Returning 0.";
m = 0;
break;
}
default:
{
m = 0;

826
src/sql.h

@ -216,9 +216,32 @@ namespace TableWallets_
};
using _traits = sqlpp::make_traits<sqlpp::varchar>;
};
struct Active
{
struct _alias_t
{
static constexpr const char _literal[] = "active";
using _name_t = sqlpp::make_char_sequence<sizeof(_literal), _literal>;
template <typename T>
struct _member_t
{
T active;
T& operator()()
{
return active;
}
const T& operator()() const
{
return active;
}
};
};
using _traits = sqlpp::make_traits<sqlpp::boolean>;
};
}
struct TableWallets : sqlpp::table_t<TableWallets,TableWallets_::WalletId,TableWallets_::UserId,TableWallets_::TypeId,TableWallets_::WalletName>
struct TableWallets : sqlpp::table_t<TableWallets,TableWallets_::WalletId,TableWallets_::UserId,TableWallets_::TypeId,TableWallets_::WalletName,TableWallets_::Active>
{
using _value_type = sqlpp::no_value_t;
struct _alias_t
@ -430,92 +453,138 @@ namespace TableWalletsTx_
using _traits = sqlpp::make_traits<sqlpp::bigint>;
};
struct BlockchainTxId
struct Amount
{
struct _alias_t
{
static constexpr const char _literal[] = "blockchain_tx_id";
static constexpr const char _literal[] = "amount";
using _name_t = sqlpp::make_char_sequence<sizeof(_literal), _literal>;
template <typename T>
struct _member_t
{
T blockchain_tx_id;
T amount;
T& operator()()
{
return blockchain_tx_id;
return amount;
}
const T& operator()() const
{
return blockchain_tx_id;
return amount;
}
};
};
using _traits = sqlpp::make_traits<sqlpp::varchar>;
using _traits = sqlpp::make_traits<sqlpp::decimal>;
};
struct Amount
struct Fee
{
struct _alias_t
{
static constexpr const char _literal[] = "amount";
static constexpr const char _literal[] = "fee";
using _name_t = sqlpp::make_char_sequence<sizeof(_literal), _literal>;
template <typename T>
struct _member_t
{
T amount;
T fee;
T& operator()()
{
return amount;
return fee;
}
const T& operator()() const
{
return amount;
return fee;
}
};
};
using _traits = sqlpp::make_traits<sqlpp::decimal>;
};
struct Fee
struct AmountCoinId
{
struct _alias_t
{
static constexpr const char _literal[] = "fee";
static constexpr const char _literal[] = "amount_coin_id";
using _name_t = sqlpp::make_char_sequence<sizeof(_literal), _literal>;
template <typename T>
struct _member_t
{
T fee;
T amount_coin_id;
T& operator()()
{
return fee;
return amount_coin_id;
}
const T& operator()() const
{
return fee;
return amount_coin_id;
}
};
};
using _traits = sqlpp::make_traits<sqlpp::decimal>;
using _traits = sqlpp::make_traits<sqlpp::bigint>;
};
struct FeeCoinId
{
struct _alias_t
{
static constexpr const char _literal[] = "fee_coin_id";
using _name_t = sqlpp::make_char_sequence<sizeof(_literal), _literal>;
template <typename T>
struct _member_t
{
T fee_coin_id;
T& operator()()
{
return fee_coin_id;
}
const T& operator()() const
{
return fee_coin_id;
}
};
};
using _traits = sqlpp::make_traits<sqlpp::bigint>;
};
struct UnixTime
{
struct _alias_t
{
static constexpr const char _literal[] = "unix_time";
using _name_t = sqlpp::make_char_sequence<sizeof(_literal), _literal>;
template <typename T>
struct _member_t
{
T unix_time;
T& operator()()
{
return unix_time;
}
const T& operator()() const
{
return unix_time;
}
};
};
using _traits = sqlpp::make_traits<sqlpp::bigint>;
};
struct Timestamp
struct OperationType
{
struct _alias_t
{
static constexpr const char _literal[] = "timestamp";
static constexpr const char _literal[] = "operation_type";
using _name_t = sqlpp::make_char_sequence<sizeof(_literal), _literal>;
template <typename T>
struct _member_t
{
T timestamp;
T operation_type;
T& operator()()
{
return timestamp;
return operation_type;
}
const T& operator()() const
{
return timestamp;
return operation_type;
}
};
};
@ -523,7 +592,7 @@ namespace TableWalletsTx_
};
}
struct TableWalletsTx : sqlpp::table_t<TableWalletsTx,TableWalletsTx_::TxId,TableWalletsTx_::WalletId,TableWalletsTx_::BlockchainTxId,TableWalletsTx_::Amount,TableWalletsTx_::Fee,TableWalletsTx_::Timestamp>
struct TableWalletsTx : sqlpp::table_t<TableWalletsTx,TableWalletsTx_::TxId,TableWalletsTx_::WalletId,TableWalletsTx_::Amount,TableWalletsTx_::Fee,TableWalletsTx_::AmountCoinId,TableWalletsTx_::FeeCoinId,TableWalletsTx_::UnixTime,TableWalletsTx_::OperationType>
{
using _value_type = sqlpp::no_value_t;
struct _alias_t
@ -546,6 +615,691 @@ struct TableWalletsTx : sqlpp::table_t<TableWalletsTx,TableWalletsTx_::TxId,Tabl
};
};
namespace TableWalletsBtcRawTx_
{
struct RawTxId
{
struct _alias_t
{
static constexpr const char _literal[] = "raw_tx_id";
using _name_t = sqlpp::make_char_sequence<sizeof(_literal), _literal>;
template <typename T>
struct _member_t
{
T raw_tx_id;
T& operator()()
{
return raw_tx_id;
}
const T& operator()() const
{
return raw_tx_id;
}
};
};
using _traits = sqlpp::make_traits<sqlpp::bigint>;
};
struct WalletId
{
struct _alias_t
{
static constexpr const char _literal[] = "wallet_id";
using _name_t = sqlpp::make_char_sequence<sizeof(_literal), _literal>;
template <typename T>
struct _member_t
{
T wallet_id;
T& operator()()
{
return wallet_id;
}
const T& operator()() const
{
return wallet_id;
}
};
};
using _traits = sqlpp::make_traits<sqlpp::bigint>;
};
}
struct TableWalletsBtcRawTx : sqlpp::table_t<TableWalletsBtcRawTx,TableWalletsBtcRawTx_::RawTxId,TableWalletsBtcRawTx_::WalletId>
{
using _value_type = sqlpp::no_value_t;
struct _alias_t
{
static constexpr const char _literal[] = "wallets_btc_raw_tx";
using _name_t = sqlpp::make_char_sequence<sizeof(_literal), _literal>;
template <typename T>
struct _member_t
{
T wallets_btc_raw_tx;
T& operator()()
{
return wallets_btc_raw_tx;
}
const T& operator()() const
{
return wallets_btc_raw_tx;
}
};
};
};
namespace TableWalletsEthRawTx_
{
struct RawTxId
{
struct _alias_t
{
static constexpr const char _literal[] = "raw_tx_id";
using _name_t = sqlpp::make_char_sequence<sizeof(_literal), _literal>;
template <typename T>
struct _member_t
{
T raw_tx_id;
T& operator()()
{
return raw_tx_id;
}
const T& operator()() const
{
return raw_tx_id;
}
};
};
using _traits = sqlpp::make_traits<sqlpp::bigint>;
};
struct WalletId
{
struct _alias_t
{
static constexpr const char _literal[] = "wallet_id";
using _name_t = sqlpp::make_char_sequence<sizeof(_literal), _literal>;
template <typename T>
struct _member_t
{
T wallet_id;
T& operator()()
{
return wallet_id;
}
const T& operator()() const
{
return wallet_id;
}
};
};
using _traits = sqlpp::make_traits<sqlpp::bigint>;
};
}
struct TableWalletsEthRawTx : sqlpp::table_t<TableWalletsEthRawTx,TableWalletsEthRawTx_::RawTxId,TableWalletsEthRawTx_::WalletId>
{
using _value_type = sqlpp::no_value_t;
struct _alias_t
{
static constexpr const char _literal[] = "wallets_eth_raw_tx";
using _name_t = sqlpp::make_char_sequence<sizeof(_literal), _literal>;
template <typename T>
struct _member_t
{
T wallets_eth_raw_tx;
T& operator()()
{
return wallets_eth_raw_tx;
}
const T& operator()() const
{
return wallets_eth_raw_tx;
}
};
};
};
namespace TableBlockchainBtcRawTx_
{
struct RawTxId
{
struct _alias_t
{
static constexpr const char _literal[] = "raw_tx_id";
using _name_t = sqlpp::make_char_sequence<sizeof(_literal), _literal>;
template <typename T>
struct _member_t
{
T raw_tx_id;
T& operator()()
{
return raw_tx_id;
}
const T& operator()() const
{
return raw_tx_id;
}
};
};
using _traits = sqlpp::make_traits<sqlpp::bigint>;
};
struct BlockchainId
{
struct _alias_t
{
static constexpr const char _literal[] = "blockchain_id";
using _name_t = sqlpp::make_char_sequence<sizeof(_literal), _literal>;
template <typename T>
struct _member_t
{
T blockchain_id;
T& operator()()
{
return blockchain_id;
}
const T& operator()() const
{
return blockchain_id;
}
};
};
using _traits = sqlpp::make_traits<sqlpp::bigint>;
};
struct Hash
{
struct _alias_t
{
static constexpr const char _literal[] = "hash";
using _name_t = sqlpp::make_char_sequence<sizeof(_literal), _literal>;
template <typename T>
struct _member_t
{
T hash;
T& operator()()
{
return hash;
}
const T& operator()() const
{
return hash;
}
};
};
using _traits = sqlpp::make_traits<sqlpp::varchar>;
};
struct UnixTime
{
struct _alias_t
{
static constexpr const char _literal[] = "unix_time";
using _name_t = sqlpp::make_char_sequence<sizeof(_literal), _literal>;
template <typename T>
struct _member_t
{
T unix_time;
T& operator()()
{
return unix_time;
}
const T& operator()() const
{
return unix_time;
}
};
};
using _traits = sqlpp::make_traits<sqlpp::bigint>;
};
}
struct TableBlockchainBtcRawTx : sqlpp::table_t<TableBlockchainBtcRawTx,TableBlockchainBtcRawTx_::RawTxId,TableBlockchainBtcRawTx_::BlockchainId,TableBlockchainBtcRawTx_::Hash,TableBlockchainBtcRawTx_::UnixTime>
{
using _value_type = sqlpp::no_value_t;
struct _alias_t
{
static constexpr const char _literal[] = "blockchain_btc_raw_tx";
using _name_t = sqlpp::make_char_sequence<sizeof(_literal), _literal>;
template <typename T>
struct _member_t
{
T blockchain_btc_raw_tx;
T& operator()()
{
return blockchain_btc_raw_tx;
}
const T& operator()() const
{
return blockchain_btc_raw_tx;
}
};
};
};
namespace TableBlockchainBtcRawTxDetails_
{
struct RawTxDetailId
{
struct _alias_t
{
static constexpr const char _literal[] = "raw_tx_detail_id";
using _name_t = sqlpp::make_char_sequence<sizeof(_literal), _literal>;
template <typename T>
struct _member_t
{
T raw_tx_detail_id;
T& operator()()
{
return raw_tx_detail_id;
}
const T& operator()() const
{
return raw_tx_detail_id;
}
};
};
using _traits = sqlpp::make_traits<sqlpp::bigint>;
};
struct RawTxId
{
struct _alias_t
{
static constexpr const char _literal[] = "raw_tx_id";
using _name_t = sqlpp::make_char_sequence<sizeof(_literal), _literal>;
template <typename T>
struct _member_t
{
T raw_tx_id;
T& operator()()
{
return raw_tx_id;
}
const T& operator()() const
{
return raw_tx_id;
}
};
};
using _traits = sqlpp::make_traits<sqlpp::bigint>;
};
struct Amount
{
struct _alias_t
{
static constexpr const char _literal[] = "amount";
using _name_t = sqlpp::make_char_sequence<sizeof(_literal), _literal>;
template <typename T>
struct _member_t
{
T amount;
T& operator()()
{
return amount;
}
const T& operator()() const
{
return amount;
}
};
};
using _traits = sqlpp::make_traits<sqlpp::decimal>;
};
struct Address
{
struct _alias_t
{
static constexpr const char _literal[] = "address";
using _name_t = sqlpp::make_char_sequence<sizeof(_literal), _literal>;
template <typename T>
struct _member_t
{
T address;
T& operator()()
{
return address;
}
const T& operator()() const
{
return address;
}
};
};
using _traits = sqlpp::make_traits<sqlpp::varchar>;
};
}
struct TableBlockchainBtcRawTxDetails : sqlpp::table_t<TableBlockchainBtcRawTxDetails,TableBlockchainBtcRawTxDetails_::RawTxDetailId,TableBlockchainBtcRawTxDetails_::RawTxId,TableBlockchainBtcRawTxDetails_::Amount,TableBlockchainBtcRawTxDetails_::Address>
{
using _value_type = sqlpp::no_value_t;
struct _alias_t
{
static constexpr const char _literal[] = "blockchain_btc_raw_tx_details";
using _name_t = sqlpp::make_char_sequence<sizeof(_literal), _literal>;
template <typename T>
struct _member_t
{
T blockchain_btc_raw_tx_details;
T& operator()()
{
return blockchain_btc_raw_tx_details;
}
const T& operator()() const
{
return blockchain_btc_raw_tx_details;
}
};
};
};
namespace TableBlockchainEthRawTx_
{
struct RawTxId
{
struct _alias_t
{
static constexpr const char _literal[] = "raw_tx_id";
using _name_t = sqlpp::make_char_sequence<sizeof(_literal), _literal>;
template <typename T>
struct _member_t
{
T raw_tx_id;
T& operator()()
{
return raw_tx_id;
}
const T& operator()() const
{
return raw_tx_id;
}
};
};
using _traits = sqlpp::make_traits<sqlpp::bigint>;
};
struct BlockchainId
{
struct _alias_t
{
static constexpr const char _literal[] = "blockchain_id";
using _name_t = sqlpp::make_char_sequence<sizeof(_literal), _literal>;
template <typename T>
struct _member_t
{
T blockchain_id;
T& operator()()
{
return blockchain_id;
}
const T& operator()() const
{
return blockchain_id;
}
};
};
using _traits = sqlpp::make_traits<sqlpp::bigint>;
};
struct Hash
{
struct _alias_t
{
static constexpr const char _literal[] = "hash";
using _name_t = sqlpp::make_char_sequence<sizeof(_literal), _literal>;
template <typename T>
struct _member_t
{
T hash;
T& operator()()
{
return hash;
}
const T& operator()() const
{
return hash;
}
};
};
using _traits = sqlpp::make_traits<sqlpp::varchar>;
};
struct UnixTime
{
struct _alias_t
{
static constexpr const char _literal[] = "unix_time";
using _name_t = sqlpp::make_char_sequence<sizeof(_literal), _literal>;
template <typename T>
struct _member_t
{
T unix_time;
T& operator()()
{
return unix_time;
}
const T& operator()() const
{
return unix_time;
}
};
};
using _traits = sqlpp::make_traits<sqlpp::bigint>;
};
struct Fee
{
struct _alias_t
{
static constexpr const char _literal[] = "fee";
using _name_t = sqlpp::make_char_sequence<sizeof(_literal), _literal>;
template <typename T>
struct _member_t
{
T fee;
T& operator()()
{
return fee;
}
const T& operator()() const
{
return fee;
}
};
};
using _traits = sqlpp::make_traits<sqlpp::decimal>;
};
}
struct TableBlockchainEthRawTx : sqlpp::table_t<TableBlockchainEthRawTx,TableBlockchainEthRawTx_::RawTxId,TableBlockchainEthRawTx_::BlockchainId,TableBlockchainEthRawTx_::Hash,TableBlockchainEthRawTx_::UnixTime,TableBlockchainEthRawTx_::Fee>
{
using _value_type = sqlpp::no_value_t;
struct _alias_t
{
static constexpr const char _literal[] = "blockchain_eth_raw_tx";
using _name_t = sqlpp::make_char_sequence<sizeof(_literal), _literal>;
template <typename T>
struct _member_t
{
T blockchain_eth_raw_tx;
T& operator()()
{
return blockchain_eth_raw_tx;
}
const T& operator()() const
{
return blockchain_eth_raw_tx;
}
};
};
};
namespace TableBlockchainEthRawTxDetails_
{
struct RawTxDetailId
{
struct _alias_t
{
static constexpr const char _literal[] = "raw_tx_detail_id";
using _name_t = sqlpp::make_char_sequence<sizeof(_literal), _literal>;
template <typename T>
struct _member_t
{
T raw_tx_detail_id;
T& operator()()
{
return raw_tx_detail_id;
}
const T& operator()() const
{
return raw_tx_detail_id;
}
};
};
using _traits = sqlpp::make_traits<sqlpp::bigint>;
};
struct RawTxId
{
struct _alias_t
{
static constexpr const char _literal[] = "raw_tx_id";
using _name_t = sqlpp::make_char_sequence<sizeof(_literal), _literal>;
template <typename T>
struct _member_t
{
T raw_tx_id;
T& operator()()
{
return raw_tx_id;
}
const T& operator()() const
{
return raw_tx_id;
}
};
};
using _traits = sqlpp::make_traits<sqlpp::bigint>;
};
struct AddressFrom
{
struct _alias_t
{
static constexpr const char _literal[] = "address_from";
using _name_t = sqlpp::make_char_sequence<sizeof(_literal), _literal>;
template <typename T>
struct _member_t
{
T address_from;
T& operator()()
{
return address_from;
}
const T& operator()() const
{
return address_from;
}
};
};
using _traits = sqlpp::make_traits<sqlpp::varchar>;
};
struct AddressTo
{
struct _alias_t
{
static constexpr const char _literal[] = "address_to";
using _name_t = sqlpp::make_char_sequence<sizeof(_literal), _literal>;
template <typename T>
struct _member_t
{
T address_to;
T& operator()()
{
return address_to;
}
const T& operator()() const
{
return address_to;
}
};
};
using _traits = sqlpp::make_traits<sqlpp::varchar>;
};
struct AmountCoinId
{
struct _alias_t
{
static constexpr const char _literal[] = "amount_coin_id";
using _name_t = sqlpp::make_char_sequence<sizeof(_literal), _literal>;
template <typename T>
struct _member_t
{
T amount_coin_id;
T& operator()()
{
return amount_coin_id;
}
const T& operator()() const
{
return amount_coin_id;
}
};
};
using _traits = sqlpp::make_traits<sqlpp::bigint>;
};
struct Amount
{
struct _alias_t
{
static constexpr const char _literal[] = "amount";
using _name_t = sqlpp::make_char_sequence<sizeof(_literal), _literal>;
template <typename T>
struct _member_t
{
T amount;
T& operator()()
{
return amount;
}
const T& operator()() const
{
return amount;
}
};
};
using _traits = sqlpp::make_traits<sqlpp::decimal>;
};
};
struct TableBlockchainEthRawTxDetails : sqlpp::table_t<TableBlockchainEthRawTxDetails,TableBlockchainEthRawTxDetails_::RawTxDetailId,TableBlockchainEthRawTxDetails_::RawTxId,TableBlockchainEthRawTxDetails_::AddressFrom,TableBlockchainEthRawTxDetails_::AddressTo,TableBlockchainEthRawTxDetails_::AmountCoinId,TableBlockchainEthRawTxDetails_::Amount>
{
using _value_type = sqlpp::no_value_t;
struct _alias_t
{
static constexpr const char _literal[] = "blockchain_eth_raw_tx_details";
using _name_t = sqlpp::make_char_sequence<sizeof(_literal), _literal>;
template <typename T>
struct _member_t
{
T blockchain_eth_raw_tx_details;
T& operator()()
{
return blockchain_eth_raw_tx_details;
}
const T& operator()() const
{
return blockchain_eth_raw_tx_details;
}
};
};
};
namespace TableExchangesLedgers_
{
struct LedgerId
@ -663,23 +1417,23 @@ namespace TableExchangesLedgers_
using _traits = sqlpp::make_traits<sqlpp::varchar>;
};
struct Timestamp
struct UnixTime
{
struct _alias_t
{
static constexpr const char _literal[] = "timestamp";
static constexpr const char _literal[] = "unix_time";
using _name_t = sqlpp::make_char_sequence<sizeof(_literal), _literal>;
template <typename T>
struct _member_t
{
T timestamp;
T unix_time;
T& operator()()
{
return timestamp;
return unix_time;
}
const T& operator()() const
{
return timestamp;
return unix_time;
}
};
};
@ -756,7 +1510,7 @@ namespace TableExchangesLedgers_
};
}
struct TableExchangesLedgers : sqlpp::table_t<TableExchangesLedgers,TableExchangesLedgers_::LedgerId,TableExchangesLedgers_::AccountId,TableExchangesLedgers_::ExchangeLedgerId,TableExchangesLedgers_::ExchangeReferenceId,TableExchangesLedgers_::Timestamp,TableExchangesLedgers_::OperationType,TableExchangesLedgers_::CoinId,TableExchangesLedgers_::Amount,TableExchangesLedgers_::Fee>
struct TableExchangesLedgers : sqlpp::table_t<TableExchangesLedgers,TableExchangesLedgers_::LedgerId,TableExchangesLedgers_::AccountId,TableExchangesLedgers_::ExchangeLedgerId,TableExchangesLedgers_::ExchangeReferenceId,TableExchangesLedgers_::UnixTime,TableExchangesLedgers_::OperationType,TableExchangesLedgers_::CoinId,TableExchangesLedgers_::Amount,TableExchangesLedgers_::Fee>
{
using _value_type = sqlpp::no_value_t;
struct _alias_t
@ -919,23 +1673,23 @@ namespace TableExchangesTrades_
using _traits = sqlpp::make_traits<sqlpp::bigint>;
};
struct Timestamp
struct UnixTime
{
struct _alias_t
{
static constexpr const char _literal[] = "timestamp";
static constexpr const char _literal[] = "unix_time";
using _name_t = sqlpp::make_char_sequence<sizeof(_literal), _literal>;
template <typename T>
struct _member_t
{
T timestamp;
T unix_time;
T& operator()()
{
return timestamp;
return unix_time;
}
const T& operator()() const
{
return timestamp;
return unix_time;
}
};
};
@ -1104,7 +1858,7 @@ namespace TableExchangesTrades_
};
}
struct TableExchangesTrades : sqlpp::table_t<TableExchangesTrades,TableExchangesTrades_::TradeId,TableExchangesTrades_::AccountId,TableExchangesTrades_::ExchangeTradeId,TableExchangesTrades_::ExchangeOrderId,TableExchangesTrades_::BaseCoinId,TableExchangesTrades_::QuoteCoinId,TableExchangesTrades_::Timestamp,TableExchangesTrades_::Type,TableExchangesTrades_::OrderType,TableExchangesTrades_::Price,TableExchangesTrades_::Cost,TableExchangesTrades_::Fee,TableExchangesTrades_::Volume,TableExchangesTrades_::FeeCoinId>
struct TableExchangesTrades : sqlpp::table_t<TableExchangesTrades,TableExchangesTrades_::TradeId,TableExchangesTrades_::AccountId,TableExchangesTrades_::ExchangeTradeId,TableExchangesTrades_::ExchangeOrderId,TableExchangesTrades_::BaseCoinId,TableExchangesTrades_::QuoteCoinId,TableExchangesTrades_::UnixTime,TableExchangesTrades_::Type,TableExchangesTrades_::OrderType,TableExchangesTrades_::Price,TableExchangesTrades_::Cost,TableExchangesTrades_::Fee,TableExchangesTrades_::Volume,TableExchangesTrades_::FeeCoinId>
{
using _value_type = sqlpp::no_value_t;
struct _alias_t

81
src/wallet.cpp

@ -19,9 +19,32 @@
*/
#include "wallet.h"
#include "wallets/btc.h"
#include "wallets/bch.h"
#include "wallets/eth.h"
#include "wallets/wallet_btc.h"
#include "wallets/wallet_bch.h"
#include "wallets/wallet_eth.h"
string getBlockchainName(int blockchainId)
{
string s;
switch(blockchainId)
{
case CPFM_BLOCKCHAIN_ID_BTC:
s = "BTC";
break;
case CPFM_BLOCKCHAIN_ID_BCH:
s = "BCH";
break;
case CPFM_BLOCKCHAIN_ID_ETH:
s = "ETH";
break;
default:
s = "Error!";
break;
}
return s;
}
WalletsManager::WalletsManager(int userId)
{
@ -38,38 +61,34 @@ void WalletsManager::analyzeUserWallets()
mysql::connection db(getMysqlConfig());
const auto wallets = TableWallets{};
// Identify coins, we need one analyzer object per coin.
list<WalletHandler*> handlers;
for (const auto& row: db.run(select(wallets.type_id).from(wallets).where(wallets.user_id == m_userId).group_by(wallets.type_id)))
list<Wallet*> userWallets;
for (const auto& row: db.run(select(wallets.type_id,wallets.wallet_id).from(wallets).where(wallets.user_id == m_userId and wallets.active == true)))
{
int typeId = row.type_id;
WalletHandler* walletHandler;
switch (typeId)
Wallet* wallet;
switch (row.type_id)
{
case CPFM_WALLET_TYPE_ID_BTC:
walletHandler = new WalletHandlerBTC(m_userId);
handlers.push_back(walletHandler);
wallet = new WalletBTC(row.wallet_id);
userWallets.push_back(wallet);
break;
case CPFM_WALLET_TYPE_ID_BCH:
walletHandler = new WalletHandlerBCH(m_userId);
handlers.push_back(walletHandler);
wallet = new WalletBCH(row.wallet_id);
userWallets.push_back(wallet);
break;
case CPFM_WALLET_TYPE_ID_ETH:
walletHandler = new WalletHandlerETH(m_userId);
handlers.push_back(walletHandler);
wallet = new WalletETH(row.wallet_id);
userWallets.push_back(wallet);
break;
default:
lerr << "Unsupported wallet type: " << typeId;
lerr << "Unsupported wallet type: " << row.type_id;
break;
}
}
emptyWalletsTx();
for (auto const& handler: handlers)
for (auto const& wallet: userWallets)
{
handler->analyzeUserWallets();
delete handler;
wallet->update();
delete wallet;
}
}
@ -85,20 +104,16 @@ void WalletsManager::emptyWalletsTx()
userWallets.push_back(row.wallet_id);
}
for (const auto& id: userWallets)
for (const auto& idWallet: userWallets)
{
db.run(remove_from(wallets_tx).where(wallets_tx.wallet_id == id));
list<int> userWalletTx;
for (const auto& row: db.run(select(wallets_tx.tx_id).from(wallets_tx).where(wallets_tx.wallet_id == idWallet)))
{
userWalletTx.push_back(row.tx_id);
}
db.run(remove_from(wallets_tx).where(wallets_tx.wallet_id == idWallet));
}
linfo << "Emptied Wallets Tx DB table for user " << m_userId;
}
WalletHandler::WalletHandler(int userId)
{
m_userId = userId;
}
WalletHandler::~WalletHandler()
{
}

20
src/wallet.h

@ -27,6 +27,17 @@
#define CPFM_WALLET_TYPE_ID_ETH 2
#define CPFM_WALLET_TYPE_ID_BCH 3
#define CPFM_WALLET_OPERATION_UNKNOWN 0
#define CPFM_WALLET_OPERATION_MINING_MINTED 1
#define CPFM_WALLET_OPERATION_MINING_POOL 2
#define CPFM_WALLET_OPERATION_TRANSFERT 3
#define CPFM_BLOCKCHAIN_ID_BTC 1
#define CPFM_BLOCKCHAIN_ID_BCH 2
#define CPFM_BLOCKCHAIN_ID_ETH 3
string getBlockchainName(int blockchainId);
class WalletsManager
{
public:
@ -41,16 +52,15 @@ private:
int m_userId;
};
class WalletHandler
class Wallet
{
public:
WalletHandler(int userId);
virtual ~WalletHandler();
Wallet (int walletId) { m_walletId = walletId; }
virtual void analyzeUserWallets() = 0;
virtual void update() = 0;
protected:
int m_userId;
int m_walletId;
};
#endif // CPFM_WALLET_H_INCLUDED

316
src/wallets/bch.cpp

@ -1,316 +0,0 @@
/*
* Copyright (c) 2018, evilny0
*
* This file is part of cpfm.
*
* cpfm is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* cpm 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 General Public License for more details.
* You should have received a copy of the GNU General Public License
* along with cpfm. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "wallets/bch.h"
WalletHandlerBCH::WalletHandlerBCH(int userId) : WalletHandler(userId)
{
}
WalletHandlerBCH::~WalletHandlerBCH()
{
}
void WalletHandlerBCH::analyzeUserWallets()
{
linfo << "Analyzing user " << m_userId << " BCH wallets.";
mysql::connection db(getMysqlConfig());
const auto wallets = TableWallets{};
const auto wallets_addresses = TableWalletsAddresses{};
map<int, list<string>> userWallets;
list<string> userAddresses;
for (const auto &row : db.run(select(wallets.wallet_id, wallets.type_id, wallets_addresses.address).from(wallets.cross_join(wallets_addresses)).where(wallets.user_id == m_userId and wallets.wallet_id == wallets_addresses.wallet_id and wallets.type_id == CPFM_WALLET_TYPE_ID_BCH)))
{
userWallets[row.wallet_id].push_back(row.address);
userAddresses.push_back(row.address);
}
for (const auto &w : userWallets)
{
addUserWalletDataToDB(w.first, w.second, userAddresses);
analyzeUserWalletData(w.first);
}
}
void WalletHandlerBCH::analyzeUserWalletData(int walletId)
{
mysql::connection db(getMysqlConfig());
const auto wallets_tx = TableWalletsTx{};
const auto wallets_balances = TableWalletsBalances{};
// Loop is on blockchain tx id, so first, get blockhain tx id list.
list<string> blockchainTxs;
for (const auto &row : db.run(select(wallets_tx.blockchain_tx_id).from(wallets_tx).where(wallets_tx.wallet_id == walletId).group_by(wallets_tx.blockchain_tx_id).order_by(wallets_tx.timestamp.asc())))
{
blockchainTxs.push_back(row.blockchain_tx_id);
}
ldebug << "------------------------------------------------------------";
Money walletBalance = 0;
Money walletInputs = 0;
Money walletOutputs = 0;
Money walletOutputFees = 0;
for (const auto &txId: blockchainTxs)
{
Money txAmount = 0;
Money txFee = 0;
for (const auto &row : db.run(select(wallets_tx.amount, wallets_tx.fee).from(wallets_tx).where(wallets_tx.blockchain_tx_id == txId and wallets_tx.wallet_id == walletId)))
{
Money amount(row.amount);
Money fee(row.fee);
if (txFee == 0)
txFee = fee;
txAmount += amount;
}
txAmount += txFee;
if (txAmount < 0)
walletOutputs += txAmount;
else
walletInputs += txAmount;
walletOutputFees += txFee;
walletBalance = walletInputs + walletOutputs - walletOutputFees;
string sReason = "?";
if (txAmount < 0)
{
}
/*
ldebug << "Tx: " << txId
<< ". Amount: " << txAmount
<< ". Outputs: " << walletOutputs
<< ". Inputs: " << walletInputs
<< ". Fees: " << txFee
<< ". Balance: " << walletBalance
<< ". Reason: " << sReason;
*/
}
linfo << "Wallet " << walletId << " balance is: " << walletBalance;
db(insert_into(wallets_balances).set(wallets_balances.wallet_id = walletId, wallets_balances.coin_id = CPFM_COIN_ID_BCH, wallets_balances.balance = walletBalance.toBoostMpf()));
}
void WalletHandlerBCH::addUserWalletDataToDB(int walletId, list<string> walletAddresses, list<string> userAddresses)
{
linfo << "Analyzing wallet " << walletId << ".";
for (const auto &a : walletAddresses)
{
AddressHandlerBCH addr(a, walletId);
addr.setWalletAddresses(walletAddresses);
addr.setUserAddresses(userAddresses);
addr.addAddressDataToDB();
}
}
AddressHandlerBCH::AddressHandlerBCH(string address, int walletId)
{
m_address = address;
m_walletId = walletId;
}
AddressHandlerBCH::~AddressHandlerBCH()
{
}
string AddressHandlerBCH::getCacheFilename()
{
string cacheFilename("data/cache/bch/" + m_address);
return cacheFilename;
}
void AddressHandlerBCH::getAddressData()
{
linfo << "Getting data for address: " << m_address << ".";
if (!bfs::exists(getCacheFilename()))
{
getBlockchainAddressData();
}
}
void AddressHandlerBCH::addAddressDataToDB()
{
linfo << "Analyzing data for address: " << m_address << ".";
getAddressData();
addCachedAddressDataToDB();
}
void AddressHandlerBCH::getBlockchainAddressData()
{
try
{
string sRequestURL = "/insight-api/txs/?address=";
sRequestURL += m_address;
http_client apiclient("https://cashexplorer.bitcoin.com");
apiclient.request(methods::GET, sRequestURL).then([](http_response response)
{
if (response.status_code() == status_codes::OK)
{
return response.extract_json();
}
return pplx::task_from_result(json::value());
})
.then([this](pplx::task<json::value> previousTask) {
ofstream f;
f.open(getCacheFilename());
f << previousTask.get();
f.close();
})
.wait();
}
catch (const http::http_exception &e)
{
lerr << "Failed to query cashexplorer.bitcoin.com about " << m_address;
}
}
void AddressHandlerBCH::addCachedAddressDataToDB()
{
ifstream f;
f.open(getCacheFilename());
json::value jvalue = json::value::parse(f);
f.close();
addAddressDataJSONToDB(jvalue);
}
void AddressHandlerBCH::addAddressDataJSONToDB(json::value jvalue)
{
mysql::connection db(getMysqlConfig());
const auto wallets_tx = TableWalletsTx{};
string myAddr = m_address;
linfo << "Analyzing address: " << myAddr;
for (int i = 0; i < jvalue["txs"].size(); i++)
{
string txid = jvalue["txs"][i]["txid"].as_string();
int timestamp = jvalue["txs"][i]["time"].as_integer();
Money txTotalOutputAmount;
Money txTotalInputAmount;
Money txMyOutputAmount;
Money txMyInputAmount;
bool bToMyAddr = false;
bool bFromMyAddr = false;
list<string> inputAddrList;
list<string> outputAddrList;
for (int j = 0; j < jvalue["txs"][i]["vin"].size(); j++)
{
string inputAddr = jvalue["txs"][i]["vin"][j]["addr"].as_string();
Money amount(jvalue["txs"][i]["vin"][j]["value"]);
txTotalInputAmount += amount;
if (inputAddr == myAddr)
{
bFromMyAddr = true;
txMyInputAmount += amount;
}
else
{
inputAddrList.push_back(inputAddr);
}
}
for (int j = 0; j < jvalue["txs"][i]["vout"].size(); j++)
{
string outputAddr = jvalue["txs"][i]["vout"][j]["scriptPubKey"]["addresses"][0].as_string();
Money amount(jvalue["txs"][i]["vout"][j]["value"]);
txTotalOutputAmount += amount;
if (outputAddr == myAddr)
{
bToMyAddr = true;
txMyOutputAmount += amount;
}
else
{
outputAddrList.push_back(outputAddr);
}
}
Money amount;
Money fee;
if (bFromMyAddr)
{
amount = -txMyInputAmount;
fee = txTotalInputAmount - txTotalOutputAmount;
for (auto const &addr : inputAddrList)
{
bool bFoundInUserAddresses = false;
for (auto const &userAddr : m_userAddresses)
{
if (!userAddr.compare(addr))
{
bFoundInUserAddresses = true;
break;
}
}
if (!bFoundInUserAddresses)
{
lwarn << "User configuration is missing this address: " << addr;
}
}
for (auto const &addr : outputAddrList)
{
bool bFoundInUserAddresses = false;
for (auto const &userAddr : m_userAddresses)
{
if (!userAddr.compare(addr))
{
bFoundInUserAddresses = true;
break;
}
}
if (!bFoundInUserAddresses)
{
linfo << "Potential other address to analyze: " << L_Cyan << addr;
}
}
}
if (bToMyAddr)
{
amount = txMyOutputAmount;
fee = 0;
}
db(insert_into(wallets_tx).set(wallets_tx.wallet_id = m_walletId, wallets_tx.blockchain_tx_id = txid, wallets_tx.amount = amount.toBoostMpf(), wallets_tx.fee = fee.toBoostMpf(), wallets_tx.timestamp = timestamp));
}
}

63
src/wallets/bch.h

@ -1,63 +0,0 @@
/*
* Copyright (c) 2018, evilny0
*
* This file is part of cpfm.
*
* cpfm is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* cpm 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 General Public License for more details.
* You should have received a copy of the GNU General Public License
* along with cpfm. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef CPFM_WALLET_BCH_H_INCLUDED
#define CPFM_WALLET_BCH_H_INCLUDED
#include "wallet.h"
class WalletHandlerBCH : public WalletHandler
{
public:
WalletHandlerBCH(int userId);
virtual ~WalletHandlerBCH();
virtual void analyzeUserWallets();
private:
void addUserWalletDataToDB(int walletId, list<string> walletAddresses, list<string> userAddresses);
void analyzeUserWalletData(int walletId);
};
class AddressHandlerBCH
{
public:
AddressHandlerBCH(string address, int walletId);
virtual ~AddressHandlerBCH();
void getAddressData();
void addAddressDataToDB();
void setWalletAddresses(list<string> walletAddresses) { m_walletAddresses = walletAddresses; }
void setUserAddresses(list<string> userAddresses) { m_userAddresses = userAddresses; }
private:
void getBlockchainAddressData();
void addCachedAddressDataToDB();
void addAddressDataJSONToDB(json::value jvalue);
string getCacheFilename();
string m_address;
int m_walletId;
list<string> m_userAddresses;
list<string> m_walletAddresses;
};
#endif // CPFM_WALLET_BTC_H_INCLUDED

332
src/wallets/btc.cpp

@ -1,332 +0,0 @@
/*
* Copyright (c) 2018, evilny0
*
* This file is part of cpfm.
*
* cpfm is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* cpm 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 General Public License for more details.
* You should have received a copy of the GNU General Public License
* along with cpfm. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "wallets/btc.h"
WalletHandlerBTC::WalletHandlerBTC(int userId) : WalletHandler(userId)
{
}
WalletHandlerBTC::~WalletHandlerBTC()
{
}
void WalletHandlerBTC::analyzeUserWallets()
{
linfo << "Analyzing user " << m_userId << " BTC wallets.";
mysql::connection db(getMysqlConfig());
const auto wallets = TableWallets{};
const auto wallets_addresses = TableWalletsAddresses{};
map<int,list<string>> userWallets;
list<string> userAddresses;
for (const auto& row: db.run(select(wallets.wallet_id,wallets.type_id,wallets_addresses.address).from(wallets.cross_join(wallets_addresses)).where(wallets.user_id == m_userId and wallets.wallet_id == wallets_addresses.wallet_id and wallets.type_id == CPFM_WALLET_TYPE_ID_BTC)))
{
userWallets[row.wallet_id].push_back(row.address);
userAddresses.push_back(row.address);
}
for (const auto& w: userWallets)
{
addUserWalletDataToDB(w.first,w.second,userAddresses);
analyzeUserWalletData(w.first);
}
}
void WalletHandlerBTC::analyzeUserWalletData(int walletId)
{
mysql::connection db(getMysqlConfig());
const auto wallets_tx = TableWalletsTx{};
const auto wallets_balances = TableWalletsBalances{};
// Loop is on blockchain tx id, so first, get blockchain tx list.
list<string> blockchainTxs;
for (const auto& row: db.run(select(wallets_tx.blockchain_tx_id).from(wallets_tx).where(wallets_tx.wallet_id == walletId).group_by(wallets_tx.blockchain_tx_id).order_by(wallets_tx.timestamp.asc())))
{
blockchainTxs.push_back(row.blockchain_tx_id);
}
ldebug << "------------------------------------------------------------";
Money walletBalance = 0;
Money walletInputs = 0;
Money walletOutputs = 0;
Money walletOutputFees = 0;
for (const auto& txId: blockchainTxs)
{
Money txAmount = 0;
Money txFee = 0;
for (const auto& row: db.run(select(wallets_tx.amount, wallets_tx.fee).from(wallets_tx).where(wallets_tx.blockchain_tx_id == txId and wallets_tx.wallet_id == walletId)))
{
Money amount(row.amount);
Money fee(row.fee);
if (txFee == 0)
txFee = fee;
txAmount += amount;
}
txAmount += txFee;
if (txAmount<0)
walletOutputs += txAmount;
else
walletInputs += txAmount;
walletOutputFees += txFee;
walletBalance = walletInputs + walletOutputs - walletOutputFees;
string sReason = "?";
if (txAmount < 0)
{
}
/*
ldebug << "Tx: " << txId
<< ". Amount: " << txAmount
<< ". Outputs: " << walletOutputs
<< ". Inputs: " << walletInputs
<< ". Fees: " << txFee
<< ". Balance: " << walletBalance
<< ". Reason: " << sReason;
*/
}
linfo << "Wallet " << walletId << " balance is: " << walletBalance;
db(insert_into(wallets_balances).set(
wallets_balances.wallet_id = walletId,
wallets_balances.coin_id = CPFM_COIN_ID_BTC,
wallets_balances.balance = walletBalance.toBoostMpf()
));
}
void WalletHandlerBTC::addUserWalletDataToDB(int walletId, list<string> walletAddresses, list<string> userAddresses)
{
linfo << "Analyzing wallet " << walletId << ".";
for (const auto& a: walletAddresses)
{
AddressHandlerBTC addr(a,walletId);
addr.setWalletAddresses(walletAddresses);
addr.setUserAddresses(userAddresses);
addr.addAddressDataToDB();
}
}
AddressHandlerBTC::AddressHandlerBTC(string address, int walletId)
{
m_address = address;
m_walletId = walletId;
}
AddressHandlerBTC::~AddressHandlerBTC()
{
}
string AddressHandlerBTC::getCacheFilename()
{
string cacheFilename("data/cache/btc/" + m_address);
return cacheFilename;
}
void AddressHandlerBTC::getAddressData()
{
linfo << "Getting data for address: " << m_address << ".";
if (!bfs::exists(getCacheFilename()))
{
getBlockchainAddressData();
}
}
void AddressHandlerBTC::addAddressDataToDB()
{
linfo << "Analyzing data for address: " << m_address << ".";
getAddressData();
addCachedAddressDataToDB();
}
void AddressHandlerBTC::getBlockchainAddressData()
{
try
{
string sRequestURL = "/fr/rawaddr/";
sRequestURL += m_address;
http_client apiclient("https://blockchain.info/");
apiclient.request(methods::GET,sRequestURL).then([](http_response response)
{
if (response.status_code() == status_codes::OK)
{
return response.extract_json();
}
return pplx::task_from_result(json::value());
})
.then([this](pplx::task<json::value> previousTask)
{
ofstream f;
f.open(getCacheFilename());
f << previousTask.get();
f.close();
})
.wait();
}
catch(const http::http_exception& e)
{
lerr << "Failed to query blockchain.info about " << m_address;
}
}
void AddressHandlerBTC::addCachedAddressDataToDB()
{
ifstream f;
f.open(getCacheFilename());
json::value jvalue = json::value::parse(f);
f.close();
addAddressDataJSONToDB(jvalue);
}
void AddressHandlerBTC::addAddressDataJSONToDB(json::value jvalue)
{
mysql::connection db(getMysqlConfig());
const auto wallets_tx = TableWalletsTx{};
string myAddr = jvalue["address"].as_string();
linfo << "Analyzing address: " << myAddr;
Money balance = jvalue["final_balance"].as_integer();;
for (int i=0;i<jvalue["txs"].size();i++)
{
string hash = jvalue["txs"][i]["hash"].as_string();
int timestamp = jvalue["txs"][i]["time"].as_integer();
Money txTotalOutputAmount;
Money txTotalInputAmount;
Money txMyOutputAmount;
Money txMyInputAmount;
bool bToMyAddr = false;
bool bFromMyAddr = false;
list<string> inputAddrList;
list<string> outputAddrList;
for (int j=0;j<jvalue["txs"][i]["inputs"].size();j++)
{
string inputAddr = jvalue["txs"][i]["inputs"][j]["prev_out"]["addr"].as_string();
__int64 amount = jvalue["txs"][i]["inputs"][j]["prev_out"]["value"].as_integer();
txTotalInputAmount += amount;
if (inputAddr == myAddr)
{
bFromMyAddr = true;
txMyInputAmount += amount;
}
else
{
inputAddrList.push_back(inputAddr);
}
}
for (int j=0;j<jvalue["txs"][i]["out"].size();j++)
{
string outputAddr = jvalue["txs"][i]["out"][j]["addr"].as_string();
__int64 amount = jvalue["txs"][i]["out"][j]["value"].as_integer();
txTotalOutputAmount += amount;
if (outputAddr == myAddr)
{
bToMyAddr = true;
txMyOutputAmount += amount;
}
else
{
outputAddrList.push_back(outputAddr);
}
}
Money amount;
Money fee;
if (bFromMyAddr)
{
amount = -txMyInputAmount/100000000;
fee = txTotalInputAmount/100000000 - txTotalOutputAmount/100000000;
for (auto const& addr: inputAddrList)
{
bool bFoundInUserAddresses = false;
for (auto const& userAddr: m_userAddresses)
{
if (!userAddr.compare(addr))
{
bFoundInUserAddresses = true;
break;
}
}
if (!bFoundInUserAddresses)
{
lwarn << "User configuration is missing this address: " << addr;
}
}
for (auto const& addr: outputAddrList)
{
bool bFoundInUserAddresses = false;
for (auto const& userAddr: m_userAddresses)
{
if (!userAddr.compare(addr))
{
bFoundInUserAddresses = true;
break;
}
}
if (!bFoundInUserAddresses)
{
linfo << "Potential other address to analyze: " << L_Cyan << addr;
}
}
}
if (bToMyAddr)
{
amount = txMyOutputAmount/100000000;
fee = 0;
}
db(insert_into(wallets_tx).set(
wallets_tx.wallet_id = m_walletId,
wallets_tx.blockchain_tx_id = hash,
wallets_tx.amount = amount.toBoostMpf(),
wallets_tx.fee = fee.toBoostMpf(),
wallets_tx.timestamp = timestamp
));
}
}

63
src/wallets/btc.h

@ -1,63 +0,0 @@
/*
* Copyright (c) 2018, evilny0
*
* This file is part of cpfm.
*
* cpfm is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* cpm 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 General Public License for more details.
* You should have received a copy of the GNU General Public License
* along with cpfm. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef CPFM_WALLET_BTC_H_INCLUDED
#define CPFM_WALLET_BTC_H_INCLUDED
#include "wallet.h"
class WalletHandlerBTC : public WalletHandler
{
public:
WalletHandlerBTC(int userId);
virtual ~WalletHandlerBTC();
virtual void analyzeUserWallets();
private:
void addUserWalletDataToDB(int walletId, list<string> walletAddresses, list<string> userAddresses);
void analyzeUserWalletData(int walletId);
};
class AddressHandlerBTC
{
public:
AddressHandlerBTC(string address, int walletId);
virtual ~AddressHandlerBTC();
void getAddressData();
void addAddressDataToDB();
void setWalletAddresses(list<string> walletAddresses) { m_walletAddresses = walletAddresses; }
void setUserAddresses(list<string> userAddresses) { m_userAddresses = userAddresses; }
private:
void getBlockchainAddressData();
void addCachedAddressDataToDB();
void addAddressDataJSONToDB(json::value jvalue);
string getCacheFilename();
string m_address;
int m_walletId;
list<string> m_userAddresses;
list<string> m_walletAddresses;
};
#endif // CPFM_WALLET_BTC_H_INCLUDED

353
src/wallets/eth.cpp

@ -1,353 +0,0 @@
/*
* Copyright (c) 2018, evilny0
*
* This file is part of cpfm.
*
* cpfm is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* cpm 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 General Public License for more details.
* You should have received a copy of the GNU General Public License
* along with cpfm. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "wallets/eth.h"
WalletHandlerETH::WalletHandlerETH(int userId) : WalletHandler(userId)
{
}
WalletHandlerETH::~WalletHandlerETH()
{
}
void WalletHandlerETH::analyzeUserWallets()
{
linfo << "Analyzing user " << m_userId << " ETH wallets.";
mysql::connection db(getMysqlConfig());
const auto wallets = TableWallets{};
const auto wallets_addresses = TableWalletsAddresses{};
map<int,list<string>> userWallets;
list<string> userAddresses;
for (const auto& row: db.run(select(wallets.wallet_id,wallets.type_id,wallets_addresses.address).from(wallets.cross_join(wallets_addresses)).where(wallets.user_id == m_userId and wallets.wallet_id == wallets_addresses.wallet_id and wallets.type_id == CPFM_WALLET_TYPE_ID_ETH)))
{
userWallets[row.wallet_id].push_back(row.address);
userAddresses.push_back(row.address);
}
for (const auto& w: userWallets)
{
addUserWalletDataToDB(w.first,w.second,userAddresses);
//analyzeUserWalletData(w.first);
}
}
void WalletHandlerETH::analyzeUserWalletData(int walletId)
{
mysql::connection db(getMysqlConfig());
const auto wallets_tx = TableWalletsTx{};
const auto wallets_balances = TableWalletsBalances{};
// Loop is on blockchain tx id, so first, get blockchain tx list.
list<string> blockchainTxs;
for (const auto& row: db.run(select(wallets_tx.blockchain_tx_id).from(wallets_tx).where(wallets_tx.wallet_id == walletId).group_by(wallets_tx.blockchain_tx_id).order_by(wallets_tx.timestamp.asc())))
{
blockchainTxs.push_back(row.blockchain_tx_id);
}
ldebug << "------------------------------------------------------------";
Money walletBalance = 0;
Money walletInputs = 0;
Money walletOutputs = 0;
Money walletOutputFees = 0;
for (const auto& txId: blockchainTxs)
{
Money txAmount = 0;
Money txFee = 0;
for (const auto& row: db.run(select(wallets_tx.amount, wallets_tx.fee).from(wallets_tx).where(wallets_tx.blockchain_tx_id == txId and wallets_tx.wallet_id == walletId)))
{
Money amount(row.amount);
Money fee(row.fee);
if (txFee == 0)
txFee = fee;
txAmount += amount;
}
txAmount += txFee;
if (txAmount<0)
walletOutputs += txAmount;
else
walletInputs += txAmount;
walletOutputFees += txFee;
walletBalance = walletInputs + walletOutputs - walletOutputFees;
string sReason = "?";
if (txAmount < 0)
{
}
/*
ldebug << "Tx: " << txId
<< ". Amount: " << txAmount
//<< ". Outputs: " << walletOutputs
//<< ". Inputs: " << walletInputs
<< ". Fees: " << txFee
<< ". Balance: " << walletBalance
<< ". Reason: " << sReason;
*/
}
linfo << "Wallet " << walletId << " balance is: " << walletBalance;
db(insert_into(wallets_balances).set(
wallets_balances.wallet_id = walletId,
wallets_balances.coin_id = CPFM_COIN_ID_ETH,
wallets_balances.balance = walletBalance.toBoostMpf()
));
}
void WalletHandlerETH::addUserWalletDataToDB(int walletId, list<string> walletAddresses, list<string> userAddresses)
{
linfo << "Analyzing wallet " << walletId << ".";
for (const auto& a: walletAddresses)
{
AddressHandlerETH addr(a,walletId);
addr.setWalletAddresses(walletAddresses);
addr.setUserAddresses(userAddresses);
addr.addAddressDataToDB();
}
}
AddressHandlerETH::AddressHandlerETH(string address, int walletId)
{
m_address = address;
m_walletId = walletId;
}
AddressHandlerETH::~AddressHandlerETH()
{
}
string AddressHandlerETH::getCacheFilename()
{
string cacheFilename("data/cache/eth/" + m_address);
return cacheFilename;
}
void AddressHandlerETH::getAddressData()
{
linfo << "Getting data for address: " << m_address << ".";
if (!bfs::exists(getCacheFilename()))
{
getBlockchainAddressData();
}
}
void AddressHandlerETH::addAddressDataToDB()
{
linfo << "Analyzing data for address: " << m_address << ".";
getAddressData();
addCachedAddressDataToDB();
}
void AddressHandlerETH::getBlockchainAddressData()
{
try
{
string sRequestURL = "/api?module=account&action=balance&address=";
sRequestURL += m_address;
http_client apiclient("https://api.etherscan.io");
apiclient.request(methods::GET,sRequestURL).then([](http_response response)
{
if (response.status_code() == status_codes::OK)
{
return response.extract_json();
}
return pplx::task_from_result(json::value());
})
.then([this](pplx::task<json::value> previousTask)
{
ofstream f;
f.open(getCacheFilename());
f << previousTask.get();
f.close();
})
.wait();
}
catch(const http::http_exception& e)
{
lerr << "Failed to query cashexplorer.bitcoin.com about " << m_address;
}
}
void AddressHandlerETH::addCachedAddressDataToDB()
{
ifstream f;
f.open(getCacheFilename());
json::value jvalue = json::value::parse(f);
f.close();
addAddressDataJSONToDB(jvalue);
}
void AddressHandlerETH::addAddressDataJSONToDB(json::value jvalue)
{
mysql::connection db(getMysqlConfig());
const auto wallets_tx = TableWalletsTx{};
string myAddr = m_address;
linfo << "Analyzing address: " << myAddr;
if (!jvalue["message"].as_string().compare("OK") && !jvalue["status"].as_string().compare("1"))
{
Money walletBalance (jvalue["result"]);
Money divider("1000000000000000000");
walletBalance /= divider;
linfo << "Wallet " << m_walletId << " balance is: " << walletBalance;
mysql::connection db(getMysqlConfig());
const auto wallets_tx = TableWalletsTx{};
const auto wallets_balances = TableWalletsBalances{};
db(insert_into(wallets_balances).set(
wallets_balances.wallet_id = m_walletId,
wallets_balances.coin_id = CPFM_COIN_ID_ETH,
wallets_balances.balance = walletBalance.toBoostMpf()
));
}
/*
//Money balance = jvalue["final_balance"].as_integer();;
for (int i=0;i<jvalue["txs"].size();i++)
{
string txid = jvalue["txs"][i]["txid"].as_string();
int timestamp = jvalue["txs"][i]["time"].as_integer();
Money txTotalOutputAmount;
Money txTotalInputAmount;
Money txMyOutputAmount;
Money txMyInputAmount;
bool bToMyAddr = false;
bool bFromMyAddr = false;
list<string> inputAddrList;
list<string> outputAddrList;
for (int j=0;j<jvalue["txs"][i]["vin"].size();j++)
{
string inputAddr = jvalue["txs"][i]["vin"][j]["addr"].as_string();
Money amount (jvalue["txs"][i]["vin"][j]["value"]);
txTotalInputAmount += amount;
if (inputAddr == myAddr)
{
bFromMyAddr = true;
txMyInputAmount += amount;
}
else
{
inputAddrList.push_back(inputAddr);
}
}
for (int j=0;j<jvalue["txs"][i]["vout"].size();j++)
{
string outputAddr = jvalue["txs"][i]["vout"][j]["scriptPubKey"]["addresses"][0].as_string();
Money amount(jvalue["txs"][i]["vout"][j]["value"]);
txTotalOutputAmount += amount;
if (outputAddr == myAddr)
{
bToMyAddr = true;
txMyOutputAmount += amount;
}
else
{
outputAddrList.push_back(outputAddr);
}
}
Money amount;
Money fee;
if (bFromMyAddr)
{
amount = -txMyInputAmount;
fee = txTotalInputAmount - txTotalOutputAmount;
for (auto const& addr: inputAddrList)
{
bool bFoundInUserAddresses = false;
for (auto const& userAddr: m_userAddresses)
{
if (!userAddr.compare(addr))
{
bFoundInUserAddresses = true;
break;
}
}
if (!bFoundInUserAddresses)
{
lwarn << "User configuration is missing this address: " << addr;
}
}
for (auto const& addr: outputAddrList)
{
bool bFoundInUserAddresses = false;
for (auto const& userAddr: m_userAddresses)
{
if (!userAddr.compare(addr))
{
bFoundInUserAddresses = true;
break;
}
}
if (!bFoundInUserAddresses)
{
linfo << "Potential other address to analyze: " << L_Cyan << addr;
}
}
}
if (bToMyAddr)
{
amount = txMyOutputAmount;
fee = 0;
}
db(insert_into(wallets_tx).set(
wallets_tx.wallet_id = m_walletId,
wallets_tx.tx_hash = txid,
wallets_tx.amount = amount.toBoostMpf(),
wallets_tx.fee = fee.toBoostMpf(),
wallets_tx.timestamp = timestamp
));
}
*/
}

63
src/wallets/eth.h

@ -1,63 +0,0 @@
/*
* Copyright (c) 2018, evilny0
*
* This file is part of cpfm.
*
* cpfm is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* cpm 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 General Public License for more details.
* You should have received a copy of the GNU General Public License
* along with cpfm. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef CPFM_WALLET_ETH_H_INCLUDED
#define CPFM_WALLET_ETH_H_INCLUDED
#include "wallet.h"
class WalletHandlerETH : public WalletHandler
{
public:
WalletHandlerETH(int userId);
virtual ~WalletHandlerETH();
virtual void analyzeUserWallets();
private:
void addUserWalletDataToDB(int walletId, list<string> walletAddresses, list<string> userAddresses);
void analyzeUserWalletData(int walletId);
};
class AddressHandlerETH
{
public:
AddressHandlerETH(string address, int walletId);
virtual ~AddressHandlerETH();
void getAddressData();
void addAddressDataToDB();
void setWalletAddresses(list<string> walletAddresses) { m_walletAddresses = walletAddresses; }
void setUserAddresses(list<string> userAddresses) { m_userAddresses = userAddresses; }
private:
void getBlockchainAddressData();
void addCachedAddressDataToDB();
void addAddressDataJSONToDB(json::value jvalue);
string getCacheFilename();
string m_address;
int m_walletId;
list<string> m_userAddresses;
list<string> m_walletAddresses;
};
#endif // CPFM_WALLET_ETH_H_INCLUDED

27
src/wallets/wallet_bch.cpp

@ -0,0 +1,27 @@
/*
* Copyright (c) 2018, evilny0
*
* This file is part of cpfm.
*
* cpfm is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* cpm 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 General Public License for more details.
* You should have received a copy of the GNU General Public License
* along with cpfm. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "wallets/wallet_bch.h"
list<BlockchainTxDetailsTypeBTC> WalletBCH::getTxDetailsListForAddresses(list<string> addresses)
{
BlockchainDataSourceBCH src;
return src.getTxDetailsListForAddresses(addresses);
}

36
src/wallets/wallet_bch.h

@ -0,0 +1,36 @@
/*
* Copyright (c) 2018, evilny0
*
* This file is part of cpfm.
*
* cpfm is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* cpm 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 General Public License for more details.
* You should have received a copy of the GNU General Public License
* along with cpfm. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef CPFM_WALLET_BCH_H_INCLUDED
#define CPFM_WALLET_BCH_H_INCLUDED
#include "wallets/wallet_type_btc.h"
#include "datasources/datasource.h"
class WalletBCH : public WalletTypeBTC
{
public:
WalletBCH(int walletId):WalletTypeBTC(walletId) { m_coinId = CPFM_COIN_ID_BCH; m_blockchainId = CPFM_BLOCKCHAIN_ID_BCH; m_walletTypeId = CPFM_WALLET_TYPE_ID_BCH; }
protected:
virtual list<BlockchainTxDetailsTypeBTC> getTxDetailsListForAddresses(list<string> addresses);
};
#endif // CPFM_WALLET_BCH_H_INCLUDED

27
src/wallets/wallet_btc.cpp

@ -0,0 +1,27 @@
/*
* Copyright (c) 2018, evilny0
*
* This file is part of cpfm.
*
* cpfm is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* cpm 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 General Public License for more details.
* You should have received a copy of the GNU General Public License
* along with cpfm. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "wallets/wallet_btc.h"
list<BlockchainTxDetailsTypeBTC> WalletBTC::getTxDetailsListForAddresses(list<string> addresses)
{
BlockchainDataSourceBTC src;
return src.getTxDetailsListForAddresses(addresses);
}

36
src/wallets/wallet_btc.h

@ -0,0 +1,36 @@
/*
* Copyright (c) 2018, evilny0
*
* This file is part of cpfm.
*
* cpfm is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* cpm 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 General Public License for more details.
* You should have received a copy of the GNU General Public License
* along with cpfm. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef CPFM_WALLET_BTC_H_INCLUDED
#define CPFM_WALLET_BTC_H_INCLUDED
#include "wallets/wallet_type_btc.h"
#include "datasources/datasource.h"
class WalletBTC : public WalletTypeBTC
{
public:
WalletBTC(int walletId):WalletTypeBTC(walletId) { m_coinId = CPFM_COIN_ID_BTC; m_blockchainId = CPFM_BLOCKCHAIN_ID_BTC; m_walletTypeId = CPFM_WALLET_TYPE_ID_BTC; }
protected:
virtual list<BlockchainTxDetailsTypeBTC> getTxDetailsListForAddresses(list<string> addresses);
};
#endif // CPFM_WALLET_BTC_H_INCLUDED

27
src/wallets/wallet_eth.cpp

@ -0,0 +1,27 @@
/*
* Copyright (c) 2018, evilny0
*
* This file is part of cpfm.
*
* cpfm is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* cpm 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 General Public License for more details.
* You should have received a copy of the GNU General Public License
* along with cpfm. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "wallets/wallet_eth.h"
list<BlockchainTxDetailsTypeETH> WalletETH::getTxDetailsListForAddress(string address)
{
BlockchainDataSourceETH src;
return src.getTxDetailsListForAddress(address);
}

36
src/wallets/wallet_eth.h

@ -0,0 +1,36 @@
/*
* Copyright (c) 2018, evilny0
*
* This file is part of cpfm.
*
* cpfm is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* cpm 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 General Public License for more details.
* You should have received a copy of the GNU General Public License
* along with cpfm. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef CPFM_WALLET_ETH_H_INCLUDED
#define CPFM_WALLET_ETH_H_INCLUDED
#include "wallets/wallet_type_eth.h"
#include "datasources/datasource.h"
class WalletETH : public WalletTypeETH
{
public:
WalletETH(int walletId):WalletTypeETH(walletId) { m_coinId = CPFM_COIN_ID_ETH; m_blockchainId = CPFM_BLOCKCHAIN_ID_ETH; m_walletTypeId = CPFM_WALLET_TYPE_ID_ETH; }
protected:
virtual list<BlockchainTxDetailsTypeETH> getTxDetailsListForAddress(string address);
};
#endif // CPFM_WALLET_ETH_H_INCLUDED

324
src/wallets/wallet_type_btc.cpp

@ -0,0 +1,324 @@
/*
* Copyright (c) 2021, evilny0
*
* This file is part of cpfm.
*
* cpfm is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* cpm 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 General Public License for more details.
* You should have received a copy of the GNU General Public License
* along with cpfm. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "wallets/wallet_type_btc.h"
SQLPP_ALIAS_PROVIDER(max_raw_tx_id);
void WalletTypeBTC::update()
{
if (!m_walletId)
{
lerr << "Wallet id not defined. Ignoring update.";
}
linfo << "Analyzing " << getCoinName(m_coinId) << " wallet " << m_walletId;
mysql::connection db(getMysqlConfig());
const auto wallets = TableWallets{};
const auto wallets_addresses = TableWalletsAddresses{};
const auto wallets_tx = TableWalletsTx{};
// Load wallet addresses
list<string> addresses;
for (const auto& row: db.run(select(wallets.wallet_id,wallets.type_id,wallets_addresses.address).from(wallets.cross_join(wallets_addresses)).where(wallets.wallet_id == wallets_addresses.wallet_id and wallets.wallet_id == m_walletId and wallets.type_id == m_walletTypeId)))
{
addresses.push_back(row.address);
}
// Get wallet tx data from data source and insert it in the DB raw tables.
list<BlockchainTxDetailsTypeBTC> l;
l = getTxDetailsListForAddresses(addresses);
addTxDetailsListToRawDB(m_walletId,l);
// Load tx data from DB raw tables, analyze data, and insert it in DB tables.
l = getTxDetailsListFromRawDB(m_walletId);
db(remove_from(wallets_tx).where(wallets_tx.wallet_id == m_walletId));
addTxDetailsListToDB(m_walletId,addresses,l);
updateBalanceFromTxDetailsInDB(m_walletId);
}
void WalletTypeBTC::addTxDetailsListToRawDB(const int walletId, const list<BlockchainTxDetailsTypeBTC>& l)
{
mysql::connection db(getMysqlConfig());
const auto wallets_btc_raw_tx = TableWalletsBtcRawTx{};
const auto blockchain_btc_raw_tx = TableBlockchainBtcRawTx{};
const auto blockchain_btc_raw_tx_details = TableBlockchainBtcRawTxDetails{};
linfo << "Clearing tx raw data links for " << getCoinName(m_coinId) << " wallet " << walletId << ".";
const auto result = db(remove_from(wallets_btc_raw_tx).where(wallets_btc_raw_tx.wallet_id == walletId));
linfo << "Now adding raw data to DB for " << getCoinName(m_coinId) << " wallet " << walletId << ".";
for (const auto& tx: l)
{
string hash = tx.hash;
ldebug << "Adding raw data to DB for tx " << hash << ".";
const auto result = db(select(blockchain_btc_raw_tx.raw_tx_id).from(blockchain_btc_raw_tx).where(blockchain_btc_raw_tx.hash == hash and blockchain_btc_raw_tx.blockchain_id == m_blockchainId));
if (!result.empty())
{
ldebug << "Raw data for tx " << hash << " is already in DB. Ignoring.";
db(insert_into(wallets_btc_raw_tx).set(
wallets_btc_raw_tx.raw_tx_id = result.front().raw_tx_id,
wallets_btc_raw_tx.wallet_id = walletId
));
}
else
{
db(insert_into(blockchain_btc_raw_tx).set(
blockchain_btc_raw_tx.hash = hash,
blockchain_btc_raw_tx.unix_time = tx.time.toUnixTime(),
blockchain_btc_raw_tx.blockchain_id = m_blockchainId
));
int rawTxId = db(select(max(blockchain_btc_raw_tx.raw_tx_id).as(max_raw_tx_id)).from(blockchain_btc_raw_tx).unconditionally()).front().max_raw_tx_id;
ldebug << "Raw tax id created : " << rawTxId << ".";
ldebug << "Adding inputs raw data to DB for tx " << hash << ".";
for (const auto& input : tx.inputs)
{
// Inputs are positive.
Money amount = input.second;
db(insert_into(blockchain_btc_raw_tx_details).set(
blockchain_btc_raw_tx_details.raw_tx_id = rawTxId,
blockchain_btc_raw_tx_details.amount = amount.toBoostMpf(),
blockchain_btc_raw_tx_details.address = input.first
));
}
ldebug << "Adding outputs raw data to DB for tx " << hash << ".";
for (const auto& output : tx.outputs)
{
// Outputs are negative.
Money amount = 0-output.second;
db(insert_into(blockchain_btc_raw_tx_details).set(
blockchain_btc_raw_tx_details.raw_tx_id = rawTxId,
blockchain_btc_raw_tx_details.amount = amount.toBoostMpf(),
blockchain_btc_raw_tx_details.address = output.first
));
}
db(insert_into(wallets_btc_raw_tx).set(
wallets_btc_raw_tx.raw_tx_id = rawTxId,
wallets_btc_raw_tx.wallet_id = walletId
));
}
}
}
list<BlockchainTxDetailsTypeBTC> WalletTypeBTC::getTxDetailsListFromRawDB(const int walletId)
{
mysql::connection db(getMysqlConfig());
const auto wallets_btc_raw_tx = TableWalletsBtcRawTx{};
const auto blockchain_btc_raw_tx = TableBlockchainBtcRawTx{};
const auto blockchain_btc_raw_tx_details = TableBlockchainBtcRawTxDetails{};
linfo << "Retrieving data from raw DB for " << getCoinName(m_coinId) << " wallet " << walletId << ".";
list<BlockchainTxDetailsTypeBTC> l;
for (const auto& rowTx: db.run(select(blockchain_btc_raw_tx.raw_tx_id,blockchain_btc_raw_tx.hash,blockchain_btc_raw_tx.unix_time).from(blockchain_btc_raw_tx.cross_join(wallets_btc_raw_tx)).where(blockchain_btc_raw_tx.raw_tx_id == wallets_btc_raw_tx.raw_tx_id and wallets_btc_raw_tx.wallet_id == walletId)))
{
ldebug << "Retrieving data from raw DB for tx " << rowTx.hash << ".";
BlockchainTxDetailsTypeBTC tx;
tx.hash = rowTx.hash;
tx.time.setFromUnixTime(rowTx.unix_time);
ldebug << "Retrieving inputs/outputs data from raw DB for tx " << rowTx.hash << ".";
for (const auto& row: db.run(select(blockchain_btc_raw_tx_details.amount,blockchain_btc_raw_tx_details.address).from(blockchain_btc_raw_tx_details).where(blockchain_btc_raw_tx_details.raw_tx_id == rowTx.raw_tx_id)))
{
Money m (row.amount);
if (m >= 0)
tx.inputs[row.address] = m;
else
tx.outputs[row.address] = 0-m;
}
l.push_back(tx);
}
return l;
}
void WalletTypeBTC::addTxDetailsListToDB(const int walletId, const list<string> walletAddresses, const list<BlockchainTxDetailsTypeBTC>& l)
{
mysql::connection db(getMysqlConfig());
const auto wallets_tx = TableWalletsTx{};
linfo << "Now adding data to DB for " << getCoinName(m_coinId) << " wallet " << walletId << ".";
for (const auto& tx: l)
{
Money txTotalOutputAmount;
Money txTotalInputAmount;
Money txWalletOutputAmount;
Money txWalletInputAmount;
bool bToWallet = false;
bool bFromWallet = false;
list<string> inputAddresses;
list<string> outputAddresses;
ltrace << "Now adding data to DB for tx " << tx.hash << ".";
for (const auto& input : tx.inputs)
{
//ltrace << "Input : " << input.first << ", amount : " << input.second;
txTotalInputAmount += input.second;
if (std::find(walletAddresses.begin(), walletAddresses.end(), input.first) != walletAddresses.end())
{
bFromWallet = true;
txWalletInputAmount += input.second;
}
else
{
inputAddresses.push_back(input.first);
}
}
for (const auto& output : tx.outputs)
{
//ltrace << "Output : " << output.first << ", amount : " << output.second;
txTotalOutputAmount += output.second;
if (std::find(walletAddresses.begin(), walletAddresses.end(), output.first) != walletAddresses.end())
{
bToWallet = true;
txWalletOutputAmount += output.second;
}
else
{
outputAddresses.push_back(output.first);
}
}
if (bFromWallet)
{
// In case at least one address was in the inputs, then ALL other inputs should be.
for (auto const& addr: inputAddresses)
{
if (std::find(walletAddresses.begin(), walletAddresses.end(), addr) == walletAddresses.end())
{
lwarn << "User configuration is missing this address: " << addr;
}
}
// In case at least one address was in the inputs, the outputs might be either another wallet for this user,
// or one of the wallet addresses might be missing from the configuration.
for (auto const& addr: outputAddresses)
{
if (std::find(walletAddresses.begin(), walletAddresses.end(), addr) == walletAddresses.end())
{
linfo << "Potential other address to analyze: " << L_Cyan << addr;
}
}
}
Money amount;
Money fee;
if (bFromWallet)
{
// The fee only applies in case this tx was initiated by the wallet
fee = txTotalInputAmount - txTotalOutputAmount;
amount = txWalletOutputAmount - txWalletInputAmount + fee;
}
else
{
amount = txWalletOutputAmount;
fee = 0;
}
db(insert_into(wallets_tx).set(
wallets_tx.wallet_id = walletId,
wallets_tx.amount = amount.toBoostMpf(),
wallets_tx.fee = fee.toBoostMpf(),
wallets_tx.unix_time = tx.time.toUnixTime(),
wallets_tx.amount_coin_id = m_coinId,
wallets_tx.fee_coin_id = m_coinId,
wallets_tx.operation_type = CPFM_WALLET_OPERATION_UNKNOWN
));
}
}
void WalletTypeBTC::updateBalanceFromTxDetailsInDB(int walletId)
{
mysql::connection db(getMysqlConfig());
const auto wallets_tx = TableWalletsTx{};
const auto wallets_balances = TableWalletsBalances{};
ldebug << "------------------------------------------------------------";
Money walletBalance = 0;
Money walletInputs = 0;
Money walletOutputs = 0;
Money walletFees = 0;
for (const auto& row: db.run(select(wallets_tx.amount, wallets_tx.fee).from(wallets_tx).where(wallets_tx.wallet_id == walletId).order_by(wallets_tx.unix_time.asc())))
{
Money txAmount(row.amount);
Money txFee(row.fee);
if (txAmount<0)
walletOutputs += txAmount;
else
walletInputs += txAmount;
walletFees += txFee;
walletBalance = walletInputs + walletOutputs - walletFees;
string sReason = "?";
ltrace << "Tx Amount: " << txAmount
<< ". Reason: " << sReason
<< ". Tx Fees: " << txFee
<< ". Total outputs: " << walletOutputs
<< ". Total inputs: " << walletInputs
<< ". Total Balance: " << walletBalance;
}
linfo << "Wallet " << walletId << " balance is: " << walletBalance;
db(remove_from(wallets_balances).where(wallets_balances.wallet_id == walletId));
db(insert_into(wallets_balances).set(
wallets_balances.wallet_id = walletId,
wallets_balances.coin_id = m_coinId,
wallets_balances.balance = walletBalance.toBoostMpf()
));
}

48
src/wallets/wallet_type_btc.h

@ -0,0 +1,48 @@
/*
* Copyright (c) 2021, evilny0
*
* This file is part of cpfm.
*
* cpfm is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* cpm 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 General Public License for more details.
* You should have received a copy of the GNU General Public License
* along with cpfm. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef CPFM_WALLET_TYPE_BTC_H_INCLUDED
#define CPFM_WALLET_TYPE_BTC_H_INCLUDED
#include "wallet.h"
#include "datasources/datasource.h"
class WalletTypeBTC : public Wallet
{
public:
WalletTypeBTC(int walletId):Wallet(walletId) {}
virtual void update();
private:
void addTxDetailsListToRawDB(const int walletId, const list<BlockchainTxDetailsTypeBTC>& l);
void addTxDetailsListToDB(const int walletId, const list<string> walletAddresses, const list<BlockchainTxDetailsTypeBTC>& l);
list<BlockchainTxDetailsTypeBTC> getTxDetailsListFromRawDB(const int walletId);
void updateBalanceFromTxDetailsInDB(int walletId);
protected:
virtual list<BlockchainTxDetailsTypeBTC> getTxDetailsListForAddresses(list<string> addresses) = 0;
int m_coinId;
int m_walletTypeId;
int m_blockchainId;
};
#endif // CPFM_WALLET_TYPE_BTC_H_INCLUDED

248
src/wallets/wallet_type_eth.cpp

@ -0,0 +1,248 @@
/*
* Copyright (c) 2021, evilny0
*
* This file is part of cpfm.
*
* cpfm is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* cpm 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 General Public License for more details.
* You should have received a copy of the GNU General Public License
* along with cpfm. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "wallets/wallet_type_eth.h"
SQLPP_ALIAS_PROVIDER(max_raw_tx_id);
void WalletTypeETH::update()
{
if (!m_walletId)
{
lerr << "Wallet id not defined. Ignoring update.";
}
linfo << "Analyzing " << getBlockchainName(m_blockchainId) << " wallet " << m_walletId;
mysql::connection db(getMysqlConfig());
const auto wallets = TableWallets{};
const auto wallets_addresses = TableWalletsAddresses{};
const auto wallets_tx = TableWalletsTx{};
// Load wallet address
string address = db(select(wallets.wallet_id,wallets.type_id,wallets_addresses.address).from(wallets.cross_join(wallets_addresses)).where(wallets.wallet_id == wallets_addresses.wallet_id and wallets.wallet_id == m_walletId and wallets.type_id == m_walletTypeId)).front().address;
// Get wallet tx data from data source and insert it in the DB raw tables.
list<BlockchainTxDetailsTypeETH> l;
l = getTxDetailsListForAddress(address);
addTxDetailsListToRawDB(m_walletId,l);
// Load tx data from DB raw tables, analyze data, and insert it in DB tables.
l = getTxDetailsListFromRawDB(m_walletId);
db(remove_from(wallets_tx).where(wallets_tx.wallet_id == m_walletId));
addTxDetailsListToDB(m_walletId,address,l);
updateBalanceFromTxDetailsInDB(m_walletId);
}
void WalletTypeETH::addTxDetailsListToRawDB(const int walletId, const list<BlockchainTxDetailsTypeETH>& l)
{
mysql::connection db(getMysqlConfig());
const auto wallets_eth_raw_tx = TableWalletsEthRawTx{};
const auto blockchain_eth_raw_tx = TableBlockchainEthRawTx{};
const auto blockchain_eth_raw_tx_details = TableBlockchainEthRawTxDetails{};
linfo << "Clearing tx raw data links for " << getBlockchainName(m_blockchainId) << " wallet " << walletId << ".";
const auto result = db(remove_from(wallets_eth_raw_tx).where(wallets_eth_raw_tx.wallet_id == walletId));
linfo << "Now adding raw data to DB for " << getBlockchainName(m_blockchainId) << " wallet " << walletId << ".";
for (const auto& tx: l)
{
string hash = tx.hash;
ldebug << "Adding raw data to DB for tx " << hash << ".";
const auto result = db(select(blockchain_eth_raw_tx.raw_tx_id).from(blockchain_eth_raw_tx).where(blockchain_eth_raw_tx.hash == hash and blockchain_eth_raw_tx.blockchain_id == m_blockchainId));
if (!result.empty())
{
ldebug << "Raw data for tx " << hash << " is already in DB. Ignoring.";
db(insert_into(wallets_eth_raw_tx).set(
wallets_eth_raw_tx.raw_tx_id = result.front().raw_tx_id,
wallets_eth_raw_tx.wallet_id = walletId
));
}
else
{
Money fee = tx.fee;
db(insert_into(blockchain_eth_raw_tx).set(
blockchain_eth_raw_tx.hash = hash,
blockchain_eth_raw_tx.unix_time = tx.time.toUnixTime(),
blockchain_eth_raw_tx.blockchain_id = m_blockchainId,
blockchain_eth_raw_tx.fee = fee.toBoostMpf()
));
int rawTxId = db(select(max(blockchain_eth_raw_tx.raw_tx_id).as(max_raw_tx_id)).from(blockchain_eth_raw_tx).unconditionally()).front().max_raw_tx_id;
ldebug << "Raw tax id created : " << rawTxId << ".";
ldebug << "Adding raw data operations to DB for tx " << hash << ".";
for (const auto& op : tx.operations)
{
Money amount = op.amount;
db(insert_into(blockchain_eth_raw_tx_details).set(
blockchain_eth_raw_tx_details.raw_tx_id = rawTxId,
blockchain_eth_raw_tx_details.address_from = op.addressFrom,
blockchain_eth_raw_tx_details.address_to = op.addressTo,
blockchain_eth_raw_tx_details.amount = amount.toBoostMpf(),
blockchain_eth_raw_tx_details.amount_coin_id = op.amountCoinId
));
}
db(insert_into(wallets_eth_raw_tx).set(
wallets_eth_raw_tx.raw_tx_id = rawTxId,
wallets_eth_raw_tx.wallet_id = walletId
));
}
}
}
list<BlockchainTxDetailsTypeETH> WalletTypeETH::getTxDetailsListFromRawDB(const int walletId)
{
mysql::connection db(getMysqlConfig());
const auto wallets_eth_raw_tx = TableWalletsEthRawTx{};
const auto blockchain_eth_raw_tx = TableBlockchainEthRawTx{};
const auto blockchain_eth_raw_tx_details = TableBlockchainEthRawTxDetails{};
linfo << "Retrieving data from raw DB for " << getBlockchainName(m_blockchainId) << " wallet " << walletId << ".";
list<BlockchainTxDetailsTypeETH> l;
for (const auto& rowTx: db.run(select(blockchain_eth_raw_tx.raw_tx_id,blockchain_eth_raw_tx.hash,blockchain_eth_raw_tx.unix_time,blockchain_eth_raw_tx.fee).from(blockchain_eth_raw_tx.cross_join(wallets_eth_raw_tx)).where(blockchain_eth_raw_tx.raw_tx_id == wallets_eth_raw_tx.raw_tx_id and wallets_eth_raw_tx.wallet_id == walletId)))
{
ldebug << "Retrieving data from raw DB for tx " << rowTx.hash << ".";
BlockchainTxDetailsTypeETH tx;
tx.hash = rowTx.hash;
tx.time.setFromUnixTime(rowTx.unix_time);
tx.fee = rowTx.fee;
ldebug << "Retrieving operations data from raw DB for tx " << rowTx.hash << ".";
for (const auto& rowOp: db.run(select(blockchain_eth_raw_tx_details.amount,blockchain_eth_raw_tx_details.amount_coin_id,blockchain_eth_raw_tx_details.address_from,blockchain_eth_raw_tx_details.address_to).from(blockchain_eth_raw_tx_details).where(blockchain_eth_raw_tx_details.raw_tx_id == rowTx.raw_tx_id)))
{
BlockchainTxOperationTypeETH op;
op.addressFrom = rowOp.address_from;
op.addressTo = rowOp.address_to;
op.amount = rowOp.amount;
op.amountCoinId = rowOp.amount_coin_id;
tx.operations.push_back(op);
}
l.push_back(tx);
}
return l;
}
void WalletTypeETH::addTxDetailsListToDB(const int walletId, string address, const list<BlockchainTxDetailsTypeETH>& l)
{
mysql::connection db(getMysqlConfig());
const auto wallets_tx = TableWalletsTx{};
linfo << "Now adding data to DB for " << getBlockchainName(m_blockchainId) << " wallet " << walletId << ".";
for (const auto& tx: l)
{
ltrace << "Now adding data to DB for tx " << tx.hash << ".";
bool bFeeAdded = false;
for (const auto& op: tx.operations)
{
Money amount;
Money fee;
if (address == op.addressFrom)
{
// Even if we have multiple operations for the same tx, the fee only applies once.
if (!bFeeAdded)
{
// The fee only applies in case this tx was initiated by the wallet
fee = tx.fee;
}
amount = 0-op.amount;
}
else
{
amount = op.amount;
}
db(insert_into(wallets_tx).set(
wallets_tx.wallet_id = walletId,
wallets_tx.amount = amount.toBoostMpf(),
wallets_tx.fee = fee.toBoostMpf(),
wallets_tx.unix_time = tx.time.toUnixTime(),
wallets_tx.amount_coin_id = op.amountCoinId,
wallets_tx.fee_coin_id = m_coinId,
wallets_tx.operation_type = CPFM_WALLET_OPERATION_UNKNOWN
));
}
}
}
void WalletTypeETH::updateBalanceFromTxDetailsInDB(int walletId)
{
mysql::connection db(getMysqlConfig());
const auto wallets_tx = TableWalletsTx{};
const auto wallets_balances = TableWalletsBalances{};
ldebug << "------------------------------------------------------------";
map<int,Money> walletBalances;
for (const auto& row: db.run(select(wallets_tx.amount, wallets_tx.amount_coin_id, wallets_tx.fee).from(wallets_tx).where(wallets_tx.wallet_id == walletId).order_by(wallets_tx.unix_time.asc())))
{
Money txAmount(row.amount);
Money txFee(row.fee);
walletBalances[row.amount_coin_id] += txAmount;
walletBalances[m_coinId] -= txFee;
string sReason = "?";
ltrace << "Tx Amount: " << txAmount
<< ", Coin: " << getCoinName(row.amount_coin_id)
<< ". Reason: " << sReason
<< ". Tx Fees: " << txFee;
}
db(remove_from(wallets_balances).where(wallets_balances.wallet_id == walletId));
for (const auto b: walletBalances)
{
linfo << "Wallet " << walletId << " " << getCoinName(b.first) << " balance is: " << b.second;
Money m = b.second;
db(insert_into(wallets_balances).set(
wallets_balances.wallet_id = walletId,
wallets_balances.coin_id = b.first,
wallets_balances.balance = m.toBoostMpf()
));
}
}

48
src/wallets/wallet_type_eth.h

@ -0,0 +1,48 @@
/*
* Copyright (c) 2021, evilny0
*
* This file is part of cpfm.
*
* cpfm is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* cpm 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 General Public License for more details.
* You should have received a copy of the GNU General Public License
* along with cpfm. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef CPFM_WALLET_TYPE_ETH_H_INCLUDED
#define CPFM_WALLET_TYPE_ETH_H_INCLUDED
#include "wallet.h"
#include "datasources/datasource.h"
class WalletTypeETH : public Wallet
{
public:
WalletTypeETH(int walletId):Wallet(walletId) {}
virtual void update();
private:
void addTxDetailsListToRawDB(const int walletId, const list<BlockchainTxDetailsTypeETH>& l);
void addTxDetailsListToDB(const int walletId, string address, const list<BlockchainTxDetailsTypeETH>& l);
list<BlockchainTxDetailsTypeETH> getTxDetailsListFromRawDB(const int walletId);
void updateBalanceFromTxDetailsInDB(int walletId);
protected:
virtual list<BlockchainTxDetailsTypeETH> getTxDetailsListForAddress(string address) = 0;
int m_walletTypeId;
int m_blockchainId;
int m_coinId;
};
#endif // CPFM_WALLET_TYPE_ETH_H_INCLUDED
Loading…
Cancel
Save