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.
371 lines
10 KiB
371 lines
10 KiB
/* |
|
* 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) |
|
{ |
|
if (jvalue.is_null()) |
|
{ |
|
lerr << "Kraken balance answer is null."; |
|
return; |
|
} |
|
|
|
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_new(); |
|
|
|
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_free(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"; |
|
} |
|
}
|
|
|