2018-01-11 21:47:59 +01:00
|
|
|
/*
|
|
|
|
* Copyright (c) 2018, evilny0
|
|
|
|
* Copyright (c) 2013 - Marco E. <marco.esposito@gmail.com>
|
|
|
|
*
|
|
|
|
* 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 "kraken.h"
|
|
|
|
|
|
|
|
ExchangeHandlerKraken::ExchangeHandlerKraken(int userId, int accountId) : ExchangeHandler(userId, accountId)
|
|
|
|
{
|
|
|
|
getAccountDataFromDB();
|
|
|
|
}
|
|
|
|
|
|
|
|
ExchangeHandlerKraken::~ExchangeHandlerKraken()
|
|
|
|
{
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
void ExchangeHandlerKraken::getAccountDataFromDB()
|
|
|
|
{
|
|
|
|
mysql::connection db(getMysqlConfig());
|
|
|
|
const auto exchanges_accounts = TableExchangesAccounts{};
|
|
|
|
|
|
|
|
for (const auto& row: db.run(select(all_of(exchanges_accounts)).from(exchanges_accounts).where(exchanges_accounts.account_id == m_accountId)))
|
|
|
|
{
|
|
|
|
m_apiKey = row.api_key;
|
|
|
|
m_apiPrivateKey = row.api_private_key;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void ExchangeHandlerKraken::analyzeUserData()
|
|
|
|
{
|
|
|
|
getAccountBalance();
|
|
|
|
analyzeAccountBalance();
|
|
|
|
|
|
|
|
//getAccountTrades();
|
|
|
|
//analyzeAccountTrades();
|
|
|
|
|
|
|
|
//getAccountLedgers();
|
|
|
|
//analyzeAccountLedgers();
|
|
|
|
}
|
|
|
|
|
|
|
|
void ExchangeHandlerKraken::analyzeAccountBalance()
|
|
|
|
{
|
|
|
|
if (!bfs::exists(getCacheFilename("balance")))
|
|
|
|
{
|
|
|
|
lerr << "Cached balance file not found.";
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
ifstream f;
|
|
|
|
f.open(getCacheFilename("balance"));
|
|
|
|
json::value jvalue = json::value::parse(f);
|
|
|
|
f.close();
|
|
|
|
|
|
|
|
analyzeAccountBalanceJSON(jvalue);
|
|
|
|
}
|
|
|
|
|
|
|
|
void ExchangeHandlerKraken::analyzeAccountBalanceJSON(json::value jvalue)
|
|
|
|
{
|
2018-01-12 18:30:24 +01:00
|
|
|
if (jvalue.is_null())
|
|
|
|
{
|
|
|
|
lerr << "Kraken balance answer is null.";
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2018-01-11 21:47:59 +01:00
|
|
|
if (jvalue["error"].size())
|
|
|
|
{
|
|
|
|
lerr << "Error in Kraken balance answer: " << jvalue["error"][0].as_string();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
mysql::connection db(getMysqlConfig());
|
|
|
|
const auto exchanges_balances = TableExchangesBalances{};
|
|
|
|
|
|
|
|
for(auto it = jvalue["result"].as_object().cbegin(); it != jvalue["result"].as_object().cend(); ++it)
|
|
|
|
{
|
|
|
|
const json::value &v = it->second;
|
|
|
|
|
|
|
|
string coinName = it->first;
|
|
|
|
Money m(v.as_string());
|
|
|
|
|
|
|
|
int coinId = 0;
|
|
|
|
if (!coinName.compare("XXBT"))
|
|
|
|
coinId = CPFM_COIN_ID_BTC;
|
|
|
|
else if (!coinName.compare("XLTC"))
|
|
|
|
coinId = CPFM_COIN_ID_LTC;
|
|
|
|
else if (!coinName.compare("ZEUR"))
|
|
|
|
coinId = CPFM_COIN_ID_EUR;
|
|
|
|
else if (!coinName.compare("XICN"))
|
|
|
|
coinId = CPFM_COIN_ID_ICN;
|
|
|
|
else if (!coinName.compare("XETC"))
|
|
|
|
coinId = CPFM_COIN_ID_ETC;
|
|
|
|
else if (!coinName.compare("XETH"))
|
|
|
|
coinId = CPFM_COIN_ID_ETH;
|
|
|
|
else if (!coinName.compare("BCH"))
|
|
|
|
coinId = CPFM_COIN_ID_BCH;
|
|
|
|
|
|
|
|
if (!coinId)
|
|
|
|
{
|
|
|
|
lerr << "Unknown coin [" << coinName << "]. Ignoring balance for this coin.";
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
db(insert_into(exchanges_balances).set(
|
|
|
|
exchanges_balances.account_id = m_accountId,
|
|
|
|
exchanges_balances.coin_id = coinId,
|
|
|
|
exchanges_balances.balance = m.toBoostMpf()
|
|
|
|
));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
__int64 ExchangeHandlerKraken::getNonce()
|
|
|
|
{
|
|
|
|
return std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::system_clock::now().time_since_epoch()).count();
|
|
|
|
}
|
|
|
|
|
|
|
|
string ExchangeHandlerKraken::getCacheFilename(string operation)
|
|
|
|
{
|
|
|
|
stringstream ss;
|
|
|
|
ss << "data/cache/kraken/" << m_userId << "-" << operation;
|
|
|
|
return ss.str();
|
|
|
|
}
|
|
|
|
|
|
|
|
void ExchangeHandlerKraken::getAccountBalance()
|
|
|
|
{
|
|
|
|
linfo << "Getting user balance.";
|
|
|
|
|
|
|
|
if (!bfs::exists(getCacheFilename("balance")))
|
|
|
|
{
|
|
|
|
getAccountBalanceFromAPI();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void ExchangeHandlerKraken::getAccountLedgers()
|
|
|
|
{
|
|
|
|
linfo << "Getting user ledgers.";
|
|
|
|
|
|
|
|
if (!bfs::exists(getCacheFilename("ledgers")))
|
|
|
|
{
|
|
|
|
getAccountLedgersFromAPI();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void ExchangeHandlerKraken::getAccountTrades()
|
|
|
|
{
|
|
|
|
linfo << "Getting user trades.";
|
|
|
|
|
|
|
|
if (!bfs::exists(getCacheFilename("trades")))
|
|
|
|
{
|
|
|
|
getAccountTradesFromAPI();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
vector<unsigned char> ExchangeHandlerKraken::sha256(const string& data)
|
|
|
|
{
|
|
|
|
vector<unsigned char> digest(SHA256_DIGEST_LENGTH);
|
|
|
|
|
|
|
|
SHA256_CTX ctx;
|
|
|
|
SHA256_Init(&ctx);
|
|
|
|
SHA256_Update(&ctx, data.c_str(), data.length());
|
|
|
|
SHA256_Final(digest.data(), &ctx);
|
|
|
|
|
|
|
|
return digest;
|
|
|
|
}
|
|
|
|
|
|
|
|
vector<unsigned char> ExchangeHandlerKraken::b64_decode(const string& data)
|
|
|
|
{
|
|
|
|
BIO* b64 = BIO_new(BIO_f_base64());
|
|
|
|
BIO_set_flags(b64, BIO_FLAGS_BASE64_NO_NL);
|
|
|
|
|
|
|
|
BIO* bmem = BIO_new_mem_buf((void*)data.c_str(),data.length());
|
|
|
|
bmem = BIO_push(b64, bmem);
|
|
|
|
|
|
|
|
std::vector<unsigned char> output(data.length());
|
|
|
|
int decoded_size = BIO_read(bmem, output.data(), output.size());
|
|
|
|
BIO_free_all(bmem);
|
|
|
|
|
|
|
|
if (decoded_size < 0)
|
|
|
|
throw std::runtime_error("failed while decoding base64.");
|
|
|
|
|
|
|
|
return output;
|
|
|
|
}
|
|
|
|
|
|
|
|
string ExchangeHandlerKraken::b64_encode(const vector<unsigned char>& data)
|
|
|
|
{
|
|
|
|
BIO* b64 = BIO_new(BIO_f_base64());
|
|
|
|
BIO_set_flags(b64, BIO_FLAGS_BASE64_NO_NL);
|
|
|
|
|
|
|
|
BIO* bmem = BIO_new(BIO_s_mem());
|
|
|
|
b64 = BIO_push(b64, bmem);
|
|
|
|
|
|
|
|
BIO_write(b64, data.data(), data.size());
|
|
|
|
BIO_flush(b64);
|
|
|
|
|
|
|
|
BUF_MEM* bptr = NULL;
|
|
|
|
BIO_get_mem_ptr(b64, &bptr);
|
|
|
|
|
|
|
|
std::string output(bptr->data, bptr->length);
|
|
|
|
BIO_free_all(b64);
|
|
|
|
|
|
|
|
return output;
|
|
|
|
}
|
|
|
|
|
|
|
|
vector<unsigned char> ExchangeHandlerKraken::hmac_sha512(const vector<unsigned char>& data, const vector<unsigned char>& key)
|
|
|
|
{
|
|
|
|
unsigned int len = EVP_MAX_MD_SIZE;
|
|
|
|
vector<unsigned char> digest(len);
|
|
|
|
|
|
|
|
HMAC_CTX ctx;
|
|
|
|
HMAC_CTX_init(&ctx);
|
|
|
|
|
|
|
|
HMAC_Init_ex(&ctx, key.data(), key.size(), EVP_sha512(), NULL);
|
|
|
|
HMAC_Update(&ctx, data.data(), data.size());
|
|
|
|
HMAC_Final(&ctx, digest.data(), &len);
|
|
|
|
|
|
|
|
HMAC_CTX_cleanup(&ctx);
|
|
|
|
|
|
|
|
return digest;
|
|
|
|
}
|
|
|
|
|
|
|
|
string ExchangeHandlerKraken::getSignature(const string& sRequestURL, const __int64& nonce, const string& sBody)
|
|
|
|
{
|
|
|
|
vector<unsigned char> data(sRequestURL.begin(), sRequestURL.end());
|
|
|
|
|
|
|
|
stringstream ss;
|
|
|
|
ss << nonce;
|
|
|
|
string sNonce = ss.str();
|
|
|
|
vector<unsigned char> nonce_postdata = sha256(sNonce + sBody);
|
|
|
|
|
|
|
|
data.insert(data.end(), nonce_postdata.begin(), nonce_postdata.end());
|
|
|
|
|
|
|
|
return b64_encode( hmac_sha512(data, b64_decode(m_apiPrivateKey)) );
|
|
|
|
}
|
|
|
|
|
|
|
|
void ExchangeHandlerKraken::getAccountBalanceFromAPI()
|
|
|
|
{
|
|
|
|
try
|
|
|
|
{
|
|
|
|
string sRequestURL = "/0/private/Balance";
|
|
|
|
http_client apiclient(KRAKEN_API_URL);
|
|
|
|
|
|
|
|
__int64 nonce = getNonce();
|
|
|
|
stringstream ss;
|
|
|
|
ss << "nonce=" << nonce;
|
|
|
|
string signature = getSignature(sRequestURL,nonce,ss.str());
|
|
|
|
|
|
|
|
http_request request(methods::POST);
|
|
|
|
request.headers().add("API-Key", m_apiKey);
|
|
|
|
request.headers().add("API-Sign", signature);
|
|
|
|
request.set_request_uri(sRequestURL);
|
|
|
|
request.set_body( ss.str(), "application/x-www-form-urlencoded" );
|
|
|
|
|
|
|
|
apiclient.request(request).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("balance"));
|
|
|
|
f << previousTask.get();
|
|
|
|
f.close();
|
|
|
|
})
|
|
|
|
.wait();
|
|
|
|
}
|
|
|
|
catch(const http::http_exception& e)
|
|
|
|
{
|
|
|
|
lerr << "Failed to query Kraken account balance";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void ExchangeHandlerKraken::getAccountLedgersFromAPI()
|
|
|
|
{
|
|
|
|
try
|
|
|
|
{
|
|
|
|
string sRequestURL = "/0/private/Ledgers";
|
|
|
|
http_client apiclient(KRAKEN_API_URL);
|
|
|
|
|
|
|
|
__int64 nonce = getNonce();
|
|
|
|
stringstream ss;
|
|
|
|
ss << "nonce=" << nonce;
|
|
|
|
string signature = getSignature(sRequestURL,nonce,ss.str());
|
|
|
|
|
|
|
|
http_request request(methods::POST);
|
|
|
|
request.headers().add("API-Key", m_apiKey);
|
|
|
|
request.headers().add("API-Sign", signature);
|
|
|
|
request.set_request_uri(sRequestURL);
|
|
|
|
request.set_body( ss.str(), "application/x-www-form-urlencoded" );
|
|
|
|
|
|
|
|
apiclient.request(request).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("ledgers"));
|
|
|
|
f << previousTask.get();
|
|
|
|
f.close();
|
|
|
|
})
|
|
|
|
.wait();
|
|
|
|
}
|
|
|
|
catch(const http::http_exception& e)
|
|
|
|
{
|
|
|
|
lerr << "Failed to query Kraken account balance";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void ExchangeHandlerKraken::getAccountTradesFromAPI()
|
|
|
|
{
|
|
|
|
try
|
|
|
|
{
|
|
|
|
string sRequestURL = "/0/private/TradesHistory";
|
|
|
|
http_client apiclient(KRAKEN_API_URL);
|
|
|
|
|
|
|
|
__int64 nonce = getNonce();
|
|
|
|
stringstream ss;
|
|
|
|
ss << "nonce=" << nonce;
|
|
|
|
string signature = getSignature(sRequestURL,nonce,ss.str());
|
|
|
|
|
|
|
|
http_request request(methods::POST);
|
|
|
|
request.headers().add("API-Key", m_apiKey);
|
|
|
|
request.headers().add("API-Sign", signature);
|
|
|
|
request.set_request_uri(sRequestURL);
|
|
|
|
request.set_body( ss.str(), "application/x-www-form-urlencoded" );
|
|
|
|
|
|
|
|
apiclient.request(request).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("trades"));
|
|
|
|
f << previousTask.get();
|
|
|
|
f.close();
|
|
|
|
})
|
|
|
|
.wait();
|
|
|
|
}
|
|
|
|
catch(const http::http_exception& e)
|
|
|
|
{
|
|
|
|
lerr << "Failed to query Kraken account balance";
|
|
|
|
}
|
|
|
|
}
|