1 //  This file is part of Qt Bitcoin Trader
2 //      https://github.com/JulyIGHOR/QtBitcoinTrader
3 //  Copyright (C) 2013-2021 July Ighor <julyighor@gmail.com>
4 //
5 //  This program is free software: you can redistribute it and/or modify
6 //  it under the terms of the GNU General Public License as published by
7 //  the Free Software Foundation, either version 3 of the License, or
8 //  (at your option) any later version.
9 //
10 //  In addition, as a special exception, the copyright holders give
11 //  permission to link the code of portions of this program with the
12 //  OpenSSL library under certain conditions as described in each
13 //  individual source file, and distribute linked combinations including
14 //  the two.
15 //
16 //  You must obey the GNU General Public License in all respects for all
17 //  of the code used other than OpenSSL. If you modify file(s) with this
18 //  exception, you may extend this exception to your version of the
19 //  file(s), but you are not obligated to do so. If you do not wish to do
20 //  so, delete this exception statement from your version. If you delete
21 //  this exception statement from all source files in the program, then
22 //  also delete it here.
23 //
24 //  This program is distributed in the hope that it will be useful,
25 //  but WITHOUT ANY WARRANTY; without even the implied warranty of
26 //  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
27 //  GNU General Public License for more details.
28 //
29 //  You should have received a copy of the GNU General Public License
30 //  along with this program.  If not, see <http://www.gnu.org/licenses/>.
31 
32 #include <openssl/ecdsa.h>
33 #include "timesync.h"
34 #include "exchange_indacoin.h"
35 
Exchange_Indacoin(const QByteArray & pRestSign,const QByteArray & pRestKey)36 Exchange_Indacoin::Exchange_Indacoin(const QByteArray& pRestSign, const QByteArray& pRestKey)
37     : Exchange()
38 {
39     calculatingFeeMode = 1;
40     baseValues.exchangeName = "Indacoin";
41     baseValues.currentPair.name = "BTC/USD";
42     baseValues.currentPair.setSymbol("BTCUSD");
43     baseValues.currentPair.currRequestPair = "btc_usd";
44     baseValues.currentPair.priceDecimals = 3;
45     minimumRequestIntervalAllowed = 700;
46     minimumRequestTimeoutAllowed = 10000;
47     baseValues.currentPair.priceMin = qPow(0.1, baseValues.currentPair.priceDecimals);
48     baseValues.currentPair.tradeVolumeMin = 0.01;
49     baseValues.currentPair.tradePriceMin = 0.1;
50     depthAsks = nullptr;
51     depthBids = nullptr;
52     forceDepthLoad = false;
53     julyHttp = nullptr;
54     isApiDown = false;
55     tickerOnly = false;
56 
57     setApiKeySecret(pRestKey, QByteArray::fromBase64(pRestSign));
58 
59     currencyMapFile = "Indacoin";
60     defaultCurrencyParams.currADecimals = 8;
61     defaultCurrencyParams.currBDecimals = 8;
62     defaultCurrencyParams.currABalanceDecimals = 8;
63     defaultCurrencyParams.currBBalanceDecimals = 8;
64     defaultCurrencyParams.priceDecimals = 3;
65     defaultCurrencyParams.priceMin = qPow(0.1, baseValues.currentPair.priceDecimals);
66 
67     supportsLoginIndicator = false;
68     supportsAccountVolume = false;
69 
70     privateNonce = (TimeSync::getTimeT() - 1371854884) * 10;
71     lastHistoryTs = 0;
72 
73     connect(this, &Exchange::threadFinished, this, &Exchange_Indacoin::quitThread, Qt::DirectConnection);
74 }
75 
~Exchange_Indacoin()76 Exchange_Indacoin::~Exchange_Indacoin()
77 {
78 }
79 
quitThread()80 void Exchange_Indacoin::quitThread()
81 {
82     clearValues();
83 
84 
85         delete depthAsks;
86 
87 
88         delete depthBids;
89 
90 
91         delete julyHttp;
92 }
93 
clearVariables()94 void Exchange_Indacoin::clearVariables()
95 {
96     isFirstAccInfo = true;
97     cancelingOrderIDs.clear();
98     Exchange::clearVariables();
99     lastOpenedOrders = -1;
100     apiDownCounter = 0;
101     lastHistory.clear();
102     lastOrders.clear();
103     reloadDepth();
104     lastFetchTid = 0;
105     lastFetchDate = TimeSync::getTimeT() - 600;
106     lastTickerDate = 0;
107     emit accFeeChanged(baseValues.currentPair.symbol, 0.15);
108 }
109 
clearValues()110 void Exchange_Indacoin::clearValues()
111 {
112     clearVariables();
113 
114     if (julyHttp)
115         julyHttp->clearPendingData();
116 }
117 
reloadDepth()118 void Exchange_Indacoin::reloadDepth()
119 {
120     lastDepthBidsMap.clear();
121     lastDepthAsksMap.clear();
122     lastDepthData.clear();
123     Exchange::reloadDepth();
124 }
125 
dataReceivedAuth(const QByteArray & data,int reqType,int pairChangeCount)126 void Exchange_Indacoin::dataReceivedAuth(const QByteArray& data, int reqType, int pairChangeCount)
127 {
128     if (pairChangeCount != m_pairChangeCount)
129         return;
130 
131     if (debugLevel)
132         logThread->writeLog("RCV: " + data);
133 
134     if (data.size() == 0)
135         return;
136 
137     //if(data.at(0)==QLatin1Char('<'))return;
138     bool success = data != QByteArray("\"unauthorized\""); //!data.startsWith("{\"success\":0");
139     QString errorString;
140 
141     if (!success)
142         errorString = data;
143 
144 
145     switch (reqType)
146     {
147     case 103: //ticker
148         {
149             QByteArray dataValue = getMidData("\"" + baseValues.currentPair.currRequestPair.toUpper() + "\":{", "}", &data);
150 
151             if (dataValue.size() < 10)
152                 break;
153 
154             QByteArray tickerHigh = getMidData("max_price\":\"", "\"", &dataValue);
155 
156             if (!tickerHigh.isEmpty())
157             {
158                 double newTickerHigh = tickerHigh.toDouble();
159 
160                 if (newTickerHigh != lastTickerHigh)
161                     IndicatorEngine::setValue(baseValues.exchangeName, baseValues.currentPair.symbol, "High", newTickerHigh);
162 
163                 lastTickerHigh = newTickerHigh;
164             }
165 
166             QByteArray tickerLow = getMidData("\"min_price\":\"", "\"", &dataValue);
167 
168             if (!tickerLow.isEmpty())
169             {
170                 double newTickerLow = tickerLow.toDouble();
171 
172                 if (newTickerLow != lastTickerLow)
173                     IndicatorEngine::setValue(baseValues.exchangeName, baseValues.currentPair.symbol, "Low", newTickerLow);
174 
175                 lastTickerLow = newTickerLow;
176             }
177 
178             QByteArray tickerLast = getMidData("\"last_price\":\"", "\"", &dataValue);
179 
180             if (!tickerLast.isEmpty())
181             {
182                 double newTickerLast = tickerLast.toDouble();
183 
184                 if (newTickerLast != lastTickerLast)
185                     IndicatorEngine::setValue(baseValues.exchangeName, baseValues.currentPair.symbol, "Last", newTickerLast);
186 
187                 lastTickerLast = newTickerLast;
188             }
189 
190             QByteArray tickerVolume = getMidData("\"volume_base\":\"", "\"", &dataValue);
191 
192             if (!tickerVolume.isEmpty())
193             {
194                 double newTickerVolume = tickerVolume.toDouble();
195 
196                 if (newTickerVolume != lastTickerVolume)
197                     IndicatorEngine::setValue(baseValues.exchangeName, baseValues.currentPair.symbol, "Volume", newTickerVolume);
198 
199                 lastTickerVolume = newTickerVolume;
200             }
201         }
202         break;//ticker
203 
204     case 109: //trades
205         {
206             if (data.size() < 10)
207                 break;
208 
209             QStringList tradeList = QString(data).split("},{");
210             auto* newTradesItems = new QList<TradesItem>;
211 
212             TradesItem newItem;
213 
214             for (int n = 0; n < tradeList.size(); n++)
215             {
216                 QByteArray tradeData = tradeList.at(n).toLatin1() + "}";
217 
218                 qint64 currentTid = getMidData("\"tid\":", ",", &tradeData).toUInt();
219 
220                 if (currentTid < 1000 || currentTid <= lastFetchTid)
221                     continue;
222 
223                 lastFetchTid = currentTid;
224 
225                 newItem.date = getMidData("date\":", "}", &tradeData).toLongLong();
226 
227                 if (newItem.date > lastFetchDate)
228                     lastFetchDate = newItem.date;
229 
230                 newItem.price = getMidData("\"price\":", ",", &tradeData).toDouble();
231                 newItem.amount = getMidData("\"amount\":", ",", &tradeData).toDouble();
232                 newItem.orderType = getMidData("\"oper_type\":", ",", &tradeData) != "1" ? 1 : -1;
233                 newItem.symbol = baseValues.currentPair.symbol;
234 
235                 if (newItem.isValid())
236                     (*newTradesItems) << newItem;
237                 else if (debugLevel)
238                     logThread->writeLog("Invalid trades fetch data line:" + tradeData, 2);
239             }
240 
241             if (lastTickerDate < newItem.date)
242             {
243                 lastTickerDate = newItem.date;
244                 IndicatorEngine::setValue(baseValues.exchangeName, baseValues.currentPair.symbol, "Last", newItem.price);
245             }
246 
247             if (!newTradesItems->empty())
248                 emit addLastTrades(baseValues.currentPair.symbol, newTradesItems);
249             else
250                 delete newTradesItems;
251         }
252         break;//trades
253 
254     case 111: //depth
255         if (data.startsWith("{\"bids\":[["))
256         {
257             emit depthRequestReceived();
258 
259             if (lastDepthData != data)
260             {
261                 lastDepthData = data;
262                 depthAsks = new QList<DepthItem>;
263                 depthBids = new QList<DepthItem>;
264 
265                 QMap<double, double> currentAsksMap;
266                 QStringList asksList = QString(getMidData("asks\":[[", "]]", &data)).split("],[");
267                 double groupedPrice = 0.0;
268                 double groupedVolume = 0.0;
269                 int rowCounter = 0;
270 
271                 if (asksList.empty())
272                     IndicatorEngine::setValue(baseValues.exchangeName, baseValues.currentPair.symbol, "Buy", 0);
273 
274                 for (int n = 0; n < asksList.size(); n++)
275                 {
276                     if (baseValues.depthCountLimit && rowCounter >= baseValues.depthCountLimit)
277                         break;
278 
279                     QStringList currentPair = asksList.at(n).split(",");
280 
281                     if (currentPair.size() != 2)
282                         continue;
283 
284                     double priceDouble = currentPair.first().toDouble();
285                     double amount = currentPair.last().toDouble();
286 
287                     if (n == 0)
288                     {
289                         if (priceDouble != lastTickerBuy)
290                             IndicatorEngine::setValue(baseValues.exchangeName, baseValues.currentPair.symbol, "Buy", priceDouble);
291 
292                         lastTickerBuy = priceDouble;
293                     }
294 
295                     if (baseValues.groupPriceValue > 0.0)
296                     {
297                         if (n == 0)
298                         {
299                             emit depthFirstOrder(baseValues.currentPair.symbol, priceDouble, amount, true);
300                             groupedPrice = baseValues.groupPriceValue * (int)(priceDouble / baseValues.groupPriceValue);
301                             groupedVolume = amount;
302                         }
303                         else
304                         {
305                             bool matchCurrentGroup = priceDouble < groupedPrice + baseValues.groupPriceValue;
306 
307                             if (matchCurrentGroup)
308                                 groupedVolume += amount;
309 
310                             if (!matchCurrentGroup || n == asksList.size() - 1)
311                             {
312                                 depthSubmitOrder(baseValues.currentPair.symbol,
313                                                  &currentAsksMap, groupedPrice + baseValues.groupPriceValue, groupedVolume, true);
314                                 rowCounter++;
315                                 groupedVolume = amount;
316                                 groupedPrice += baseValues.groupPriceValue;
317                             }
318                         }
319                     }
320                     else
321                     {
322                         depthSubmitOrder(baseValues.currentPair.symbol,
323                                          &currentAsksMap, priceDouble, amount, true);
324                         rowCounter++;
325                     }
326                 }
327 
328                 QList<double> currentAsksList = lastDepthAsksMap.keys();
329 
330                 for (int n = 0; n < currentAsksList.size(); n++)
331                     if (currentAsksMap.value(currentAsksList.at(n), 0) == 0)
332                         depthUpdateOrder(baseValues.currentPair.symbol,
333                                          currentAsksList.at(n), 0.0, true);
334 
335                 lastDepthAsksMap = currentAsksMap;
336 
337                 QMap<double, double> currentBidsMap;
338                 QStringList bidsList = QString(getMidData("bids\":[[", "]]", &data)).split("],[");
339                 groupedPrice = 0.0;
340                 groupedVolume = 0.0;
341                 rowCounter = 0;
342 
343                 if (bidsList.empty())
344                     IndicatorEngine::setValue(baseValues.exchangeName, baseValues.currentPair.symbol, "Sell", 0);
345 
346                 for (int n = 0; n < bidsList.size(); n++)
347                 {
348                     if (baseValues.depthCountLimit && rowCounter >= baseValues.depthCountLimit)
349                         break;
350 
351                     QStringList currentPair = bidsList.at(n).split(",");
352 
353                     if (currentPair.size() != 2)
354                         continue;
355 
356                     double priceDouble = currentPair.first().toDouble();
357                     double amount = currentPair.last().toDouble();
358 
359                     if (n == 0)
360                     {
361                         if (priceDouble != lastTickerSell)
362                             IndicatorEngine::setValue(baseValues.exchangeName, baseValues.currentPair.symbol, "Sell", priceDouble);
363 
364                         lastTickerSell = priceDouble;
365                     }
366 
367                     if (baseValues.groupPriceValue > 0.0)
368                     {
369                         if (n == 0)
370                         {
371                             emit depthFirstOrder(baseValues.currentPair.symbol, priceDouble, amount, false);
372                             groupedPrice = baseValues.groupPriceValue * (int)(priceDouble / baseValues.groupPriceValue);
373                             groupedVolume = amount;
374                         }
375                         else
376                         {
377                             bool matchCurrentGroup = priceDouble > groupedPrice - baseValues.groupPriceValue;
378 
379                             if (matchCurrentGroup)
380                                 groupedVolume += amount;
381 
382                             if (!matchCurrentGroup || n == asksList.size() - 1)
383                             {
384                                 depthSubmitOrder(baseValues.currentPair.symbol,
385                                                  &currentBidsMap, groupedPrice - baseValues.groupPriceValue, groupedVolume, false);
386                                 rowCounter++;
387                                 groupedVolume = amount;
388                                 groupedPrice -= baseValues.groupPriceValue;
389                             }
390                         }
391                     }
392                     else
393                     {
394                         depthSubmitOrder(baseValues.currentPair.symbol,
395                                          &currentBidsMap, priceDouble, amount, false);
396                         rowCounter++;
397                     }
398                 }
399 
400                 QList<double> currentBidsList = lastDepthBidsMap.keys();
401 
402                 for (int n = 0; n < currentBidsList.size(); n++)
403                     if (currentBidsMap.value(currentBidsList.at(n), 0) == 0)
404                         depthUpdateOrder(baseValues.currentPair.symbol,
405                                          currentBidsList.at(n), 0.0, false);
406 
407                 lastDepthBidsMap = currentBidsMap;
408 
409                 emit depthSubmitOrders(baseValues.currentPair.symbol, depthAsks, depthBids);
410                 depthAsks = nullptr;
411                 depthBids = nullptr;
412             }
413         }
414         else if (debugLevel)
415             logThread->writeLog("Invalid depth data:" + data, 2);
416 
417         break;
418 
419     case 202: //info
420         {
421             if (!success)
422                 break;
423 
424             //QByteArray fundsData=getMidData("funds\":{","}",&data)+",";
425             QByteArray btcBalance = getMidData("\"" + baseValues.currentPair.currAStr + "\",\"", "\"", &data); //fundsData);
426             QByteArray usdBalance = getMidData("\"" + baseValues.currentPair.currBStr + "\",\"", "\"", &data); //fundsData);
427 
428             if (checkValue(btcBalance, lastBtcBalance))
429                 emit accBtcBalanceChanged(baseValues.currentPair.symbol, lastBtcBalance);
430 
431             if (checkValue(usdBalance, lastUsdBalance))
432                 emit accUsdBalanceChanged(baseValues.currentPair.symbol, lastUsdBalance);
433         }
434         break;//info
435 
436     case 204://orders
437         if (data.size() && (data[0] == '[' || data[0] == '{'))
438         {
439             if (data.size() < 2)
440                 break;
441 
442             if (lastOrders != data)
443             {
444                 lastOrders = data;
445                 bool isEmptyOrders = (data == "[]");
446 
447                 if (isEmptyOrders)
448                     success = true;
449 
450                 if (isEmptyOrders)
451                 {
452                     emit ordersIsEmpty();
453                     break;
454                 }
455 
456                 QStringList ordersList = QString(getMidData("[[", "]]", &data)).split("],[");
457 
458                 if (ordersList.empty())
459                     return;
460 
461                 auto* orders = new QList<OrderItem>;
462 
463                 for (int n = 0; n < ordersList.size(); n++)
464                 {
465                     QStringList currentOrderList = ordersList.at(n).split("\",\"");
466                     OrderItem currentOrder;
467                     currentOrder.oid = currentOrderList.at(0).toLatin1();
468                     currentOrder.oid.remove(0, 1);
469                     currentOrder.symbol = currentOrderList.at(1).toUpper().replace("_", "");
470                     currentOrder.type = currentOrderList.at(2) == "SELL";
471                     currentOrder.price = currentOrderList.at(3).toDouble();
472                     currentOrder.amount = currentOrderList.at(5).toDouble();
473                     currentOrder.date = currentOrderList.at(6).toUInt();
474                     currentOrder.status = 1;
475 
476                     if (currentOrder.isValid())
477                         (*orders) << currentOrder;
478                 }
479 
480                 emit orderBookChanged(baseValues.currentPair.symbol, orders);
481             }
482 
483             break;//orders
484         }
485 
486         break;
487 
488     case 305: //order/cancel
489         if (!success)
490             break;
491 
492         if (!cancelingOrderIDs.isEmpty())
493         {
494             if (data == "\"success\"")
495             {
496                 emit orderCanceled(baseValues.currentPair.symbol, cancelingOrderIDs.first());
497 
498                 if (debugLevel)
499                     logThread->writeLog("Order canceled:" + cancelingOrderIDs.first(), 2);
500 
501                 cancelingOrderIDs.removeFirst();
502             }
503         }
504 
505         break;//order/cancel
506 
507     case 306:
508         if (debugLevel)
509             logThread->writeLog("Buy OK: " + data, 2);
510 
511         break;//order/buy
512 
513     case 307:
514         if (debugLevel)
515             logThread->writeLog("Sell OK: " + data, 2);
516 
517         break;//order/sell
518 
519     case 208: ///history
520         {
521             bool isEmptyOrders = !success && errorString == QLatin1String("no trades");
522 
523             if (isEmptyOrders)
524                 success = true;
525 
526             if (lastHistory != data)
527             {
528                 lastHistory = data;
529 
530                 if (!success)
531                     break;
532 
533                 QString newLog(getMidData("[[", "]]", &data));
534                 QStringList dataList = newLog.split("],[");
535 
536                 if (dataList.empty())
537                     return;
538 
539                 qint64 maxTs = 0;
540                 auto* historyItems = new QList<HistoryItem>;
541 
542                 for (int n = 0; n < dataList.size(); n++)
543                 {
544                     QString curLog(dataList.at(n));
545                     curLog.remove(0, 1);
546                     curLog.chop(1);
547                     QStringList curLogList = curLog.split("\",\"");
548 
549                     HistoryItem currentHistoryItem;
550 
551                     if (curLogList.at(0) == "SELL")
552                         currentHistoryItem.type = 1;
553                     else if (curLogList.at(0) == "BUY")
554                         currentHistoryItem.type = 2;
555                     else if (curLogList.at(0) == "IN")
556                         currentHistoryItem.type = 4;
557                     else if (curLogList.at(0) == "OUT")
558                         currentHistoryItem.type = 5;
559                     else
560                         continue;
561 
562                     if (currentHistoryItem.type == 1 || currentHistoryItem.type == 2)
563                     {
564                         currentHistoryItem.symbol = curLogList.at(1).toUpper().replace("_", "");
565                         currentHistoryItem.price = curLogList.at(2).toDouble();
566                         currentHistoryItem.volume = curLogList.at(3).toDouble();
567                         currentHistoryItem.dateTimeInt = curLogList.at(6).toUInt();
568                     }
569 
570                     if (currentHistoryItem.type == 4 || currentHistoryItem.type == 5)
571                     {
572                         currentHistoryItem.symbol = curLogList.at(2).toUpper() + "   ";
573                         currentHistoryItem.volume = curLogList.at(3).toDouble();
574                         currentHistoryItem.dateTimeInt = curLogList.at(4).toUInt();
575                     }
576 
577                     if (currentHistoryItem.dateTimeInt <= lastHistoryTs)
578                         break;
579 
580                     if (n == 0)
581                         maxTs = currentHistoryItem.dateTimeInt;
582 
583                     if (currentHistoryItem.isValid())
584                         (*historyItems) << currentHistoryItem;
585                 }
586 
587                 if (maxTs > lastHistoryTs)
588                     lastHistoryTs = maxTs;
589 
590                 emit historyChanged(historyItems);
591             }
592 
593             break;//money/wallet/history
594         }
595 
596     default:
597         break;
598     }
599 
600     if (reqType >= 200 && reqType < 300)
601     {
602         static int authErrorCount = 0;
603 
604         if (!success)
605         {
606             authErrorCount++;
607 
608             if (authErrorCount > 2)
609             {
610                 QString authErrorString = data;
611 
612                 if (data == "\"unauthorized\"")
613                     authErrorString = "unauthorized";
614 
615                 if (debugLevel)
616                     logThread->writeLog("API error: " + authErrorString.toLatin1() + " ReqType: " + QByteArray::number(reqType), 2);
617 
618                 if (authErrorString == "unauthorized")
619                     authErrorString = julyTr("TRUNAUTHORIZED", "Invalid API key.") + "<br><br>" + julyTr("THIS_PROFILE_ALREADY_USED",
620                                       "Invalid nonce parameter.");
621 
622                 if (!authErrorString.isEmpty())
623                     emit showErrorMessage(authErrorString);
624             }
625         }
626         else
627             authErrorCount = 0;
628     }
629 
630     static int errorCount = 0;
631 
632     if (!success)
633     {
634         errorCount++;
635 
636         if (errorCount < 3)
637             return;
638 
639         if (debugLevel)
640             logThread->writeLog("API error: " + errorString.toLatin1() + " ReqType:" + QByteArray::number(reqType), 2);
641 
642         if (errorString.isEmpty())
643             return;
644 
645         if (errorString == QLatin1String("no orders"))
646             return;
647 
648         if (reqType < 300)
649             emit showErrorMessage("I:>" + errorString);
650     }
651     else
652         errorCount = 0;
653 }
654 
depthUpdateOrder(const QString & symbol,double price,double amount,bool isAsk)655 void Exchange_Indacoin::depthUpdateOrder(const QString& symbol, double price, double amount, bool isAsk)
656 {
657     if (symbol != baseValues.currentPair.symbol)
658         return;
659 
660     if (isAsk)
661     {
662         if (depthAsks == nullptr)
663             return;
664 
665         DepthItem newItem;
666         newItem.price = price;
667         newItem.volume = amount;
668 
669         if (newItem.isValid())
670             (*depthAsks) << newItem;
671     }
672     else
673     {
674         if (depthBids == nullptr)
675             return;
676 
677         DepthItem newItem;
678         newItem.price = price;
679         newItem.volume = amount;
680 
681         if (newItem.isValid())
682             (*depthBids) << newItem;
683     }
684 }
685 
depthSubmitOrder(const QString & symbol,QMap<double,double> * currentMap,double priceDouble,double amount,bool isAsk)686 void Exchange_Indacoin::depthSubmitOrder(const QString& symbol, QMap<double, double>* currentMap, double priceDouble,
687         double amount, bool isAsk)
688 {
689     if (symbol != baseValues.currentPair.symbol)
690         return;
691 
692     if (priceDouble == 0.0 || amount == 0.0)
693         return;
694 
695     if (isAsk)
696     {
697         (*currentMap)[priceDouble] = amount;
698 
699         if (lastDepthAsksMap.value(priceDouble, 0.0) != amount)
700             depthUpdateOrder(symbol, priceDouble, amount, true);
701     }
702     else
703     {
704         (*currentMap)[priceDouble] = amount;
705 
706         if (lastDepthBidsMap.value(priceDouble, 0.0) != amount)
707             depthUpdateOrder(symbol, priceDouble, amount, false);
708     }
709 }
710 
isReplayPending(int reqType)711 bool Exchange_Indacoin::isReplayPending(int reqType)
712 {
713     if (julyHttp == nullptr)
714         return false;
715 
716     return julyHttp->isReqTypePending(reqType);
717 }
718 
secondSlot()719 void Exchange_Indacoin::secondSlot()
720 {
721     privateNonce = 1;
722     static int sendCounter = 0;
723 
724     switch (sendCounter)
725     {
726     case 0:
727         if (!isReplayPending(103))
728             sendToApi(103, "ticker", false, true);
729 
730         break;
731 
732     case 1:
733         if (!isReplayPending(202))
734             sendToApi(202, "getbalance", true, true, "");
735 
736         break;
737 
738     case 2:
739         if (!isReplayPending(109))
740             sendToApi(109, "2/trades/" + baseValues.currentPair.currRequestPair + "/0/" + QByteArray::number(lastFetchDate), false,
741                       true);
742 
743         break;
744 
745     case 3:
746         if (!tickerOnly && !isReplayPending(204))
747             sendToApi(204, "openorders", true, true, "");
748 
749         break;
750 
751     case 4:
752         if (isDepthEnabled() && (forceDepthLoad || !isReplayPending(111)))
753         {
754             emit depthRequested();
755             sendToApi(111, "orderbook?pair=" + baseValues.currentPair.currRequestPair/*+"?limit="+baseValues.depthCountLimitStr*/,
756                       false, true);
757             forceDepthLoad = false;
758         }
759 
760         break;
761 
762     case 5:
763         if (lastHistory.isEmpty())
764             getHistory(false);
765 
766         break;
767 
768     default:
769         break;
770     }
771 
772     if (sendCounter++ >= 5)
773         sendCounter = 0;
774 
775     Exchange::secondSlot();
776 }
777 
getHistory(bool force)778 void Exchange_Indacoin::getHistory(bool force)
779 {
780     if (tickerOnly)
781         return;
782 
783     if (force)
784         lastHistory.clear();
785 
786     if (!isReplayPending(208))
787         sendToApi(208, "gethistory", true, true, "");
788 }
789 
buy(const QString & symbol,double apiBtcToBuy,double apiPriceToBuy)790 void Exchange_Indacoin::buy(const QString& symbol, double apiBtcToBuy, double apiPriceToBuy)
791 {
792     if (tickerOnly)
793         return;
794 
795     CurrencyPairItem pairItem;
796     pairItem = baseValues.currencyPairMap.value(symbol, pairItem);
797 
798     if (pairItem.symbol.isEmpty())
799         return;
800 
801     QByteArray data = "pair:'" + pairItem.currRequestPair.toUpper() + "',price:'" + JulyMath::byteArrayFromDouble(apiPriceToBuy,
802                       pairItem.priceDecimals, 0) + "',amount:'" + JulyMath::byteArrayFromDouble(apiBtcToBuy, pairItem.currADecimals, 0) + "'";
803 
804     if (debugLevel)
805         logThread->writeLog("Buy: " + data, 2);
806 
807     sendToApi(306, "buyorder", true, true, data);
808 }
809 
sell(const QString & symbol,double apiBtcToSell,double apiPriceToSell)810 void Exchange_Indacoin::sell(const QString& symbol, double apiBtcToSell, double apiPriceToSell)
811 {
812     if (tickerOnly)
813         return;
814 
815     CurrencyPairItem pairItem;
816     pairItem = baseValues.currencyPairMap.value(symbol, pairItem);
817 
818     if (pairItem.symbol.isEmpty())
819         return;
820 
821     QByteArray data = "pair:'" + pairItem.currRequestPair.toUpper() + "',price:'" + JulyMath::byteArrayFromDouble(apiPriceToSell,
822                       pairItem.priceDecimals, 0) + "',amount:'" + JulyMath::byteArrayFromDouble(apiBtcToSell, pairItem.currADecimals, 0) + "'";
823 
824     if (debugLevel)
825         logThread->writeLog("Sell: " + data, 2);
826 
827     sendToApi(307, "sellorder", true, true, data);
828 }
829 
cancelOrder(const QString &,const QByteArray & order)830 void Exchange_Indacoin::cancelOrder(const QString& /*unused*/, const QByteArray& order)
831 {
832     if (tickerOnly)
833         return;
834 
835     cancelingOrderIDs << order;
836 
837     if (debugLevel)
838         logThread->writeLog("Cancel order: " + order, 2);
839 
840     sendToApi(305, "cancelorder", true, true, "i:'" + order + "'");
841 }
842 
843 
ecdsaSha1(QByteArray shaKey,QByteArray & data)844 QByteArray Exchange_Indacoin::ecdsaSha1(QByteArray shaKey, QByteArray& data)
845 {
846     EC_KEY* eckey = EC_KEY_new_by_curve_name(NID_secp256k1);
847     EC_KEY_generate_key(eckey);
848     BIGNUM* tempPrivateKey = BN_new();
849 
850     BN_bin2bn((unsigned char*)shaKey.data(), shaKey.length(), tempPrivateKey);
851     EC_KEY_set_private_key(eckey, tempPrivateKey);
852 
853     QByteArray rezult;
854     rezult.resize(ECDSA_size(eckey));
855     quint32 len = rezult.size();
856     ECDSA_sign(0, (unsigned char*)QCryptographicHash::hash(data, QCryptographicHash::Sha1).data(), 20,
857                (unsigned char*)rezult.data(), &len, eckey);
858 
859     BN_free(tempPrivateKey);
860     return rezult;
861 }
862 
sendToApi(int reqType,const QByteArray & method,bool auth,bool sendNow,QByteArray commands)863 void Exchange_Indacoin::sendToApi(int reqType, const QByteArray& method, bool auth, bool sendNow, QByteArray commands)
864 {
865     if (julyHttp == nullptr)
866     {
867         if (domain.isEmpty() || port == 0)
868             julyHttp = new JulyHttp("indacoin.com", "", this, true, true, "application/json; charset=UTF-8");
869         else
870         {
871             julyHttp = new JulyHttp(domain, "", this, useSsl, true, "application/json; charset=UTF-8");
872             julyHttp->setPortForced(port);
873         }
874 
875         connect(julyHttp, SIGNAL(anyDataReceived()), baseValues_->mainWindow_, SLOT(anyDataReceived()));
876         connect(julyHttp, SIGNAL(apiDown(bool)), baseValues_->mainWindow_, SLOT(setApiDown(bool)));
877         connect(julyHttp, SIGNAL(setDataPending(bool)), baseValues_->mainWindow_, SLOT(setDataPending(bool)));
878         connect(julyHttp, SIGNAL(errorSignal(QString)), baseValues_->mainWindow_, SLOT(showErrorMessage(QString)));
879         connect(julyHttp, SIGNAL(sslErrorSignal(const QList<QSslError>&)), this, SLOT(sslErrors(const QList<QSslError>&)));
880         connect(julyHttp, SIGNAL(dataReceived(QByteArray, int, int)), this, SLOT(dataReceivedAuth(const QByteArray&, int, int)));
881     }
882 
883     if (auth)
884     {
885         QByteArray nonceStr = QByteArray::number(++privateNonce);
886         QByteArray postData = '{' + commands + '}';
887 
888         QByteArray appendHeaders = "API-Key: " + getApiKey() + "\r\n"
889                                    "API-Nonce: " + nonceStr + "\r\n";
890         nonceStr.prepend(method);
891         nonceStr.append(getApiKey());
892         appendHeaders += "API-Sign: " + ecdsaSha1(getApiSign(), nonceStr).toBase64() + "\r\n";
893 
894         if (sendNow)
895             julyHttp->sendData(reqType, m_pairChangeCount, "POST /api/" + method, postData, appendHeaders);
896         else
897             julyHttp->prepareData(reqType, m_pairChangeCount, "POST /api/" + method, postData, appendHeaders);
898     }
899     else
900     {
901         if (commands.isEmpty())
902         {
903             if (sendNow)
904                 julyHttp->sendData(reqType, m_pairChangeCount, "GET /api/" + method);
905             else
906                 julyHttp->prepareData(reqType, m_pairChangeCount, "GET /api/" + method);
907         }
908         else
909         {
910             if (sendNow)
911                 julyHttp->sendData(reqType, m_pairChangeCount, "POST /api/" + method, commands);
912             else
913                 julyHttp->prepareData(reqType, m_pairChangeCount, "POST /api/" + method, commands);
914         }
915     }
916 }
917 
sslErrors(const QList<QSslError> & errors)918 void Exchange_Indacoin::sslErrors(const QList<QSslError>& errors)
919 {
920     QStringList errorList;
921 
922     for (int n = 0; n < errors.size(); n++)
923         errorList << errors.at(n).errorString();
924 
925     if (debugLevel)
926         logThread->writeLog(errorList.join(" ").toLatin1(), 2);
927 
928     emit showErrorMessage("SSL Error: " + errorList.join(" "));
929 }
930