You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

323 lines
10 KiB

/*
* 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;
}