-
Notifications
You must be signed in to change notification settings - Fork 3
/
Copy pathapp.js
300 lines (262 loc) · 10.5 KB
/
app.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
require('dotenv').config();
const nodeEnv = process.env.NODE_ENV;
const testMode = nodeEnv === "TEST";
const express = require('express');
const bodyParser = require('body-parser');
const ejs = require('ejs');
const axios = require('axios');
const https = require('https');
const app = express();
app.set('view engine', 'ejs');
app.use(bodyParser.urlencoded({extended: true}));
/**
* Disable HTTPS/SSL requirement in development mode / company intranet environments where certificates pose an issue
* and throw UNABLE_TO_GET_ISSUER_CERT_LOCALLY exceptions
*/
if (testMode) {
axios.defaults.httpsAgent = new https.Agent({
rejectUnauthorized: false,
});
// eslint-disable-next-line no-console
console.log(nodeEnv, `RejectUnauthorized is disabled.`)
}
const defaultOpts = {
error: null,
asset: process.env.DEFAULT_ASSET || 'USDT',
buyCurrency: process.env.DEFAULT_BUY_CURRENCY || 'GBP',
buyAmount: process.env.DEFAULT_BUY_AMOUNT || 150,
sellCurrency: process.env.DEFAULT_SELL_CURRENCY || 'PKR'
}
// Render the app index page
app.get('/', (req, res) => {
res.render('index', defaultOpts);
});
// Calculate the data and return the data in JSON
app.get('/get-results', async (req, res) => {
let exchangeData = null;
let asset = req.query.asset;
let buyAmount = req.query.buyAmount;
let buyCurrency = req.query.buyCurrency;
let sellCurrency = req.query.sellCurrency;
let fiatRate = null;
let apiError = null;
let fiatAPI = null;
console.log("get-results: Buy " + asset + " worth " + buyAmount + " " + buyCurrency);
// API Endpoints for fiat exchange rate fetching
let EXCHANGERATE_API_ENDPOINT = `https://api.exchangerate.host/convert?from=${buyCurrency}&to=${sellCurrency}`;
let WISE_API_ENDPOINT = `https://api.wise.com/v1/rates?source=${buyCurrency}&target=${sellCurrency}`;
// Wise API Authorization headers
let config = {
headers: {
"Authorization": "Basic OGNhN2FlMjUtOTNjNS00MmFlLThhYjQtMzlkZTFlOTQzZDEwOjliN2UzNmZkLWRjYjgtNDEwZS1hYzc3LTQ5NGRmYmEyZGJjZA=="
}
}
// Fetch the fiat exchange data from the API using the buyCurrency & sellCurrency
await axios
.get(
WISE_API_ENDPOINT, config
)
.then((response) => {
exchangeData = response.data;
// Deduct 1% from actual rate to accommodate for the service charges incurred by Wise
fiatActualRate = exchangeData[0].rate;
fiatRate = fiatActualRate - (fiatActualRate * 0.01);
fiatAPI = 'Wise';
})
.catch(async function (error) {
// If Wise doesn't work then go ahead and fetch the exchange rate from exchange rate API
console.log(error.data);
await axios
.get(
EXCHANGERATE_API_ENDPOINT
)
.then((response) => {
exchangeData = response.data;
fiatRate = exchangeData.info.rate;
fiatAPI = 'Exchange Rate';
}).catch(function (error) {
console.log("Error getting exchange rate. ErrorCode: " + error.cause.code + ", ErrorMessage: " + error.cause.message);
apiError = prepareError(error);
});
});
if (apiError != null) {
return sendErrorResponse(req, res, apiError.fullOutput);
}
if (exchangeData == null) {
return sendErrorResponse(req, res, "No exchange data found.");
}
// Calculate the fiat exchange total amount of the buyAmount
let fiatTotalAmount = Math.round(buyAmount * fiatRate);
// Fetch buyCurrency P2P data using Wise payment method & buyAmount
let p2pBuyData = await fetchP2PData('BUY', buyAmount, 'Wise', buyCurrency, asset);
if (p2pBuyData.errorCode) {
return sendErrorResponse(req, res, "Unable to get purchase details: [" + p2pBuyData.errorCode + "] " + p2pBuyData.errorMessage);
}
// Select the first advertisement given it matches with our requirements
// Also since this would be the best price as well
let p2pBuyAdv = p2pBuyData.data[0];
if (p2pBuyAdv == null) {
return sendErrorResponse(req, res, "No buy advert found.");
}
// Calculate the boughtAssetAmount using the above advertisement price
// console.log(p2pBuyAdv);
let boughtAssetAmount = buyAmount / p2pBuyAdv.adv.price;
// Now fetch the sellCurrency P2P data using Bank Transfer payment method & fiatTotalAmount
let p2pSellData = await fetchP2PData(
'SELL',
fiatTotalAmount,
'BANK',
sellCurrency,
asset
);
if (p2pSellData.errorCode) {
return sendErrorResponse(req, res, "Unable to get sale details: [" + p2pSellData.errorCode + "] " + p2pSellData.errorMessage);
}
// Now you need to look for the one ad where you can sell the asset looking at the limits set by advertisers
// Once a suitable advertisement has been located then stop looking for more and use that one
let p2pSellAdv = null;
for (let i = 0; i < p2pSellData.data.length; i++) {
let ad = p2pSellData.data[i];
if (
ad.adv.minSingleTransQuantity < boughtAssetAmount &&
boughtAssetAmount < ad.adv.maxSingleTransQuantity
) {
p2pSellAdv = ad;
break;
}
}
if (p2pSellAdv == null) {
return sendErrorResponse(req, res, "No sell advert found.");
}
// Now calculate the final soldCurrencyAmount using the above selected advertisement price for selling
// console.log(p2pSellAdv);
let soldCurrencyAmount = boughtAssetAmount * p2pSellAdv.adv.price;
let profit = currencyFormat(soldCurrencyAmount - fiatTotalAmount);
// Return the data to be rendered on the frontend
res.json({
asset: asset,
buyPrice: p2pBuyAdv.adv.price,
buyAssetLimit: currencyFormat(p2pBuyAdv.adv.surplusAmount),
boughtAssetAmount: currencyFormat(boughtAssetAmount),
buyAdvId: p2pBuyAdv.advertiser.userNo,
buyAdvertiser: p2pBuyAdv.advertiser.nickName,
sellPrice: p2pSellAdv.adv.price,
sellAssetLimit: currencyFormat(p2pSellAdv.adv.surplusAmount),
soldCurrencyAmount: currencyFormat(soldCurrencyAmount),
sellAdvId: p2pSellAdv.advertiser.userNo,
sellAdvertiser: p2pSellAdv.advertiser.nickName,
fiatAPI: fiatAPI,
fiatRate: currencyFormat(fiatRate),
fiatTotalAmount: currencyFormat(fiatTotalAmount),
profit: currencyFormat(profit),
exchangeSummary: "Exchange: " + buyAmount + " " + buyCurrency + " exchanges to " + currencyFormat(fiatTotalAmount) + " " + sellCurrency + " at a rate of " + currencyFormat(fiatRate) + " " + sellCurrency + ".",
buySummary: "Bought " + currencyFormat(boughtAssetAmount) + " " + asset + " with " + buyAmount + " " + buyCurrency + ".",
sellSummary: "Sold " + currencyFormat(boughtAssetAmount) + " " + asset + " for " + currencyFormat(soldCurrencyAmount) + " " + sellCurrency + ".",
profitSummary: "Profit: " + currencyFormat(profit) + " " + sellCurrency + "."
});
});
/**
* @param {number} amount
* @returns {number}
*/
function currencyFormat(amount) {
return Math.round(amount * 100) / 100;
}
/**
* Fetches P2P data from Binance API using the given params
* @param {*} tradeType
* @param {*} amount
* @param {*} paymentMethod
* @param {*} currency
* @param {*} asset
* @returns
*/
async function fetchP2PData(tradeType, amount, paymentMethod, currency, asset) {
let p2pData = null;
const API_ENDPOINT = 'https://p2p.binance.com/bapi/c2c/v2/friendly/c2c/adv/search';
const data = {
proMerchantAds: false,
page: 1,
rows: 20,
payTypes: [paymentMethod],
countries: [],
publisherType: null,
asset: asset,
fiat: currency,
tradeType: tradeType,
transAmount: amount
};
await axios.post(API_ENDPOINT, data)
.then((response) => {
p2pData = response.data;
// console.log(p2pData);
})
.catch(function (error) {
// console.log(error);
console.log("ErrorCode: " + error.code + ", ErrorMessage: " + error.message);
p2pData = prepareError(error);
});
return p2pData;
}
/**
* @param {*} error The error object
* @returns {{}} A standardised error object, containing the following keys:prepareError(error)
* <ul>
* <li>hasError - bool - TRUE if error details exist</li>
* <li>errorCode - string - The error code</li>
* <li>errorMessage - string - The error message</li>
* <li>binanceCode - string - The binance P2P API response code</li>
* <li>binanceMessage - string - The binance P2P API response message</li>
* <li>compactOutput - string - Concatenated output containing the errorCode and errorMessage</li>
* <li>fullOutput - string - Concatenated output containing the binance code/message in addition to the compactOutput</li>
* </ul>
*/
function prepareError(error) {
let response = {};
if (error.code) {
response.errorCode = error.code;
}
if (error.message) {
response.errorMessage = error.message;
}
if (error.cause) {
if (error.cause.code) {
response.errorCode = error.cause.code;
}
if (error.cause.message) {
response.errorMessage = error.cause.message;
}
}
if (error.response && error.response.data) {
if (error.response.data.code) {
response.binanceCode = error.response.data.code;
}
if (error.response.data.message) {
response.binanceMessage = error.response.data.message;
}
}
let compactOutput = response.errorCode ? "[" + response.errorCode + "] " : "";
compactOutput += response.errorMessage ? response.errorMessage : "";
let fullOutput = compactOutput;
fullOutput += response.binanceCode ? " - Binance: [" + response.binanceCode + "] " : "";
fullOutput += response.binanceMessage ? response.binanceMessage : "";
response.compactOutput = compactOutput;
response.fullOutput = fullOutput;
response.hasError = response.errorCode || response.errorMessage;
return response;
}
/**
* Send an error response
* @param {Request} req The request object
* @param {Response} res The response object
* @param {{}} error The error object
*/
function sendErrorResponse(req, res, error) {
res.json({
error: error
});
}
let port = process.env.PORT || 3000;
app.listen(port, () => {
console.log(`Server started on ${port}`);
});