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 ¤tAsksMap, 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 ¤tAsksMap, 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 ¤tBidsMap, 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 ¤tBidsMap, 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