Merge pull request #61 from AAVikings/master

Adding basic Coss exchange integration
This commit is contained in:
benme authored and GitHub committed 2019-03-31 23:05:05 +02:00
commit 5539f953fa
6 files changed
+563 -16

No files matched your search

+10 -4
View File
@@ -1,4 +1,4 @@
exports.newExchangeAPI = function newExchangeAPI(logger, authToken) {
exports.newExchangeAPI = function newExchangeAPI(logger, exchangeName) {
/*
@@ -31,7 +31,13 @@
if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] initialize -> Entering function."); }
let botExchange = 'Poloniex';
let botExchange
if (exchangeName === undefined) {
botExchange = 'Poloniex'; // Default Value
} else {
botExchange = exchangeName;
}
let exchange = botExchange.toLowerCase() + 'Client.js';
let api = require('./wrappers/' + exchange);
@@ -45,7 +51,7 @@
let auth = require('../utils/auth')
let authTokenCloud = await auth.authenticate()
accessToken = 'Bearer ' + authTokenCloud
}
}
let keyVaultAPI = createKeyVaultAPIClient(accessToken, keyId, cloneId)
apiClient = api.newAPIClient(keyVaultAPI, logger);
@@ -60,7 +66,7 @@
function createKeyVaultAPIClient(accessToken, keyId, cloneId) {
if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] createKeyVaultAPIClient -> Entering function."); }
const keyVaultAPI = {}
keyVaultAPI.signTransaction = function (transaction, next) {
axios({
+340
View File
@@ -0,0 +1,340 @@
// Source: https://github.com/coss-exchange/trading-api-wrapper-node-js/blob/master/index.js
// Documentation: https://api.coss.io/v1/spec
const request = require('superagent');
const querystring = require('querystring');
const HttpMethod = { POST: 1, GET: 2, DELETE: 3 };
const util = require('util');
const HOST_URL = "https://trade.coss.io/c/api/v1";
const HOST_URL2 = "https://engine.coss.io/api/v1";
const EXCHANGE_HOST = "https://exchange.coss.io/api";
//WRAPPER STARTS HERE (Commenting out stuff that coss wrote which should be part of bot.js (the actual trading bot) rather than index.js (the wrapper core functionality))
let Coss = (keyVaultAPI) => {
//HTTPS Private Request
async function transmit_encrypted_request(method, url, payload) {
var payload_json_str = method == HttpMethod.GET ? querystring.stringify(payload) : JSON.stringify(payload)
var sign = await transmit_payload_signature_get(payload_json_str)
var req_headers = transmit_signed_header_get(sign)
console.log(`sending ${payload_json_str} to ${url}`)
if (method == HttpMethod.POST) {
let response = await request.post(url).set(req_headers).send(payload_json_str)
return (response.body);
} else if (method == HttpMethod.DELETE) {
let response = await request.delete(url).set(req_headers).send(payload_json_str)
return (response.body);
} else if (method == HttpMethod.GET) {
let response = await request.get(url).set(req_headers).query(payload_json_str)
return (response.body);
}
}
function transmit_signed_header_get(sign) {
var req_headers = {
'Content-Type': 'application/json',
'X-Requested-With': 'XMLHttpRequest',
'Authorization': sign.Key,
'Signature': sign.Sign,
}
return req_headers
}
//AUTHBUILDER
async function transmit_payload_signature_get(payload_json_str) {
const signTransaction = util.promisify(keyVaultAPI.signTransaction)
return await signTransaction(payload_json_str)
}
//HTTPS Public Request
async function publicRequest(url) {
let response = await request.get(url)
return response.body;
}
return {
//PLACE LIMIT ORDER
placeLimitOrder: async (params = {}) => {
if (!params.Symbol) {
return Promise.reject("placeLimitOrder(), You must supply a valid trading pair Symbol, e.g. 'COSS_ETH'!");
} else if (params.Symbol && typeof params.Symbol !== 'string') {
return Promise.reject("placeLimitOrder(), You must supply a valid trading pair Symbol as a string, e.g. 'COSS_ETH'!");
} else if (!params.Side) {
return Promise.reject("placeLimitOrder(), You must supply a valid Side, e.g. 'BUY' or 'SELL'!");
} else if (params.Side && params.Side !== 'Buy' && params.Side !== 'Sell') {
return Promise.reject("placeLimitOrder(), You must supply a valid Side, e.g. 'BUY' or 'SELL'!");
} else if (!params.Price || !params.Amount) {
return Promise.reject("placeLimitOrder(), You must supply a valid Price and Amount, e.g. Price: '0.00000034' or Amount: '123.00000000'!");
//} else if (typeof params.Price !== 'number' || typeof params.Amount !== 'number') {
//return Promise.reject("placeLimitOrder(), You must supply a valid Price and Amount as a number, e.g. Price: '0.00000034' or Amount: '123.00000000'!");
}
var now = new Date().getTime()
var query_params = {
'order_symbol': params.Symbol,
'order_price': params.Price.toString(),
'order_side': params.Side,
'order_size': params.Amount.toString(),
'type': 'limit',
'timestamp': now,
'recvWindow': 5000
}
let url = HOST_URL + "/order/add";
return transmit_encrypted_request(HttpMethod.POST, url, query_params)
},
//PLACE MARKET ORDER
placeMarketOrder: async (params = {}) => {
if (!params.Symbol) {
return Promise.reject("placeMarketOrder(), You must supply a valid trading pair Symbol, e.g. 'COSS_ETH'!");
} else if (params.Symbol && typeof params.Symbol !== 'string') {
return Promise.reject("placeMarketOrder(), You must supply a valid trading pair Symbol as a string, e.g. 'COSS_ETH'!");
} else if (!params.Side) {
return Promise.reject("placeMarketOrder(), You must supply a valid Side, e.g. 'BUY' or 'SELL'!");
} else if (params.Side && params.Side !== 'Buy' && params.Side !== 'Sell') {
return Promise.reject("placeMarketOrder(), You must supply a valid Side, e.g. 'BUY' or 'SELL'!");
} else if (!params.Amount) {
return Promise.reject("placeMarketOrder(), You must supply a valid Amount, e.g. Amount: '123.00000000'!");
} else if (typeof params.Amount !== 'number') {
return Promise.reject("placeMarketOrder(), You must supply a valid Amount as a number, e.g. Amount: '123.00000000'!");
}
var now = new Date().getTime()
var query_params = {
'order_symbol': params.Symbol,
'order_side': params.Side,
'order_size': params.Amount.toString(),
'type': 'market',
'timestamp': now,
'recvWindow': 5000
}
let url = HOST_URL + "/order/add";
return transmit_encrypted_request(HttpMethod.POST, url, query_params)
},
//Get Account Balances
getAccountBalances: async (params = {}) => {
var now = new Date().getTime()
var query_params = {
'recvWindow': 5000,
'timestamp': now
}
let url = HOST_URL + "/account/balances";
return transmit_encrypted_request(HttpMethod.GET, url, query_params)
},
//Get Account Details
getAccountDetails: async (params = {}) => {
var now = new Date().getTime()
var query_params = {
'recvWindow': 5000,
'timestamp': now
}
let url = HOST_URL + "/account/details";
return transmit_encrypted_request(HttpMethod.GET, url, query_params)
},
//CANCEL ORDER BY ID #
cancelOrder: async (params = {}) => {
if (!params.ID) {
return Promise.reject("cancelOrder(), You must supply a valid order_ID to cancel, e.g. '9e5ae4dd-3369-401d-81f5-dff985e1c4e7'!");
} else if (params.ID && typeof params.ID !== 'string') {
return Promise.reject("cancelOrder(), You must supply a valid order_ID to cancel as a string, e.g. '9e5ae4dd-3369-401d-81f5-dff985e1c4e7'!");
} else if (!params.Symbol) {
return Promise.reject("cancelOrder(), You must supply a valid trading pair Symbol, e.g. 'COSS_ETH'!");
} else if (params.Symbol && typeof params.Symbol !== 'string') {
return Promise.reject("cancelOrder(), You must supply a valid trading pair Symbol as a string, e.g. 'COSS_ETH'!");
}
var now = new Date().getTime()
var query_params = {
'order_id': params.ID,
'order_symbol': params.Symbol,
'timestamp': now,
'recvWindow': 5000
}
let url = HOST_URL + "/order/cancel";
return transmit_encrypted_request(HttpMethod.DELETE, url, query_params)
},
//GET ORDER DETAILS
getOrderDetails: async (params = {}) => {
if (!params.ID) {
return Promise.reject("getOrderDetails(), You must supply a valid order_ID to cancel, e.g. '9e5ae4dd-3369-401d-81f5-dff985e1c4e7'!");
} else if (params.ID && typeof params.ID !== 'string') {
return Promise.reject("getOrderDetails(), You must supply a valid order_ID to cancel as a string, e.g. '9e5ae4dd-3369-401d-81f5-dff985e1c4e7'!");
}
var now = new Date().getTime()
var query_params = {
'order_id': params.ID,
'timestamp': now,
'recvWindow': 5000
}
let url = HOST_URL + "/order/details";
return transmit_encrypted_request(HttpMethod.POST, url, query_params)
},
//GET OPEN ORDERS
getOpenOrders: async (params = {}) => {
if (!params.Limit) {
return Promise.reject("getOpenOrders(), You must supply a valid Limit for your open orders, e.g. '10'!");
} else if (params.Limit && typeof params.Limit !== 'number') {
return Promise.reject("getOpenOrders(), You must supply a valid Limit as a number for your open orders, e.g. '10'!");
} else if (!params.Symbol) {
return Promise.reject("getOpenOrders(), You must supply a valid trading pair Symbol, e.g. 'COSS_ETH'!");
} else if (params.Symbol && typeof params.Symbol !== 'string') {
return Promise.reject("getOpenOrders(), You must supply a valid trading pair Symbol as a string, e.g. 'COSS_ETH'!");
}
var now = new Date().getTime()
var query_params = {
'limit': params.Limit.toString(),
'page': 0,
'symbol': params.Symbol,
'timestamp': now,
'recvWindow': 5000
}
let url = HOST_URL + "/order/list/open";
return transmit_encrypted_request(HttpMethod.POST, url, query_params)
},
//GET COMPLETED ORDERS
getCompletedOrders: async (params = {}) => {
if (!params.Limit) {
return Promise.reject("getCompletedOrders(), You must supply a valid Limit for your open orders, e.g. '10'!");
} else if (params.Limit && typeof params.Limit !== 'number') {
return Promise.reject("getCompletedOrders(), You must supply a valid Limit as a number for your open orders, e.g. '10'!");
} else if (!params.Symbol) {
return Promise.reject("getCompletedOrders(), You must supply a valid trading pair Symbol, e.g. 'COSS_ETH'!");
} else if (params.Symbol && typeof params.Symbol !== 'string') {
return Promise.reject("getCompletedOrders(), You must supply a valid trading pair Symbol as a string, e.g. 'COSS_ETH'!");
}
var now = new Date().getTime()
var query_params = {
'limit': params.Limit.toString(),
'page': 0,
'symbol': params.Symbol,
'timestamp': now,
'recvWindow': 5000
}
let url = HOST_URL + "/order/list/completed";
return transmit_encrypted_request(HttpMethod.POST, url, query_params)
},
//GET ALL USER ORDERS
getAllOrders: async (params = {}) => {
if (!params.Limit) {
return Promise.reject("getAllOrders(), You must supply a valid Limit for your open orders, e.g. '10'!");
} else if (params.Limit && typeof params.Limit !== 'number') {
return Promise.reject("getAllOrders(), You must supply a valid Limit as a number for your open orders, e.g. '10'!");
} else if (!params.Symbol) {
return Promise.reject("getAllOrders(), You must supply a valid trading pair Symbol, e.g. 'COSS_ETH'!");
} else if (params.Symbol && typeof params.Symbol !== 'string') {
return Promise.reject("getAllOrders(), You must supply a valid trading pair Symbol as a string, e.g. 'COSS_ETH'!");
} else if (!params.ID) {
return Promise.reject("getAllOrders(), You must supply a valid account ID");
} else if (params.ID && typeof params.ID !== 'string') {
return Promise.reject("getAllOrders(), You must supply a valid account ID as a string");
}
var now = new Date().getTime()
var query_params = {
'symbol': params.Symbol,
'from_id': params.ACCT_ID,
'limit': params.Limit,
'timestamp': now,
'recvWindow': 5000
}
let url = HOST_URL + "/order/list/all";
return transmit_encrypted_request(HttpMethod.POST, url, query_params)
},
//Public API Calls (don't need to be signed)
//get Market Price
getMarketPrice: async (params = {}) => {
if (!params.Symbol) {
return Promise.reject("getMarketPrice(), You must supply a valid trading pair Symbol, e.g. 'COSS_ETH'!");
} else if (params.Symbol && typeof params.Symbol !== 'string') {
return Promise.reject("getMarketPrice(), You must supply a valid trading pair Symbol as a string, e.g. 'COSS_ETH'!");
}
let url = HOST_URL + "/market-price/" + '?symbol=' + params.Symbol;
return publicRequest(url);
},
//get Market Price
getPairDepth: async (params = {}) => {
if (!params.Symbol) {
return Promise.reject("getPairDepth(), You must supply a valid trading pair Symbol, e.g. 'COSS_ETH'!");
} else if (params.Symbol && typeof params.Symbol !== 'string') {
return Promise.reject("getPairDepth(), You must supply a valid trading pair Symbol as a string, e.g. 'COSS_ETH'!");
}
console.log(HOST_URL2 + "/dp" + '?symbol=' + params.Symbol);
let url = HOST_URL2 + "/dp" + '?symbol=' + params.Symbol;
return publicRequest(url);
},
//get Market Price
getMarketSides: async (params = {}) => {
if (!params.Symbol) {
return Promise.reject("getMarketSides(), You must supply a valid trading pair Symbol, e.g. 'COSS_ETH'!");
} else if (params.Symbol && typeof params.Symbol !== 'string') {
return Promise.reject("getMarketSides(), You must supply a valid trading pair Symbol as a string, e.g. 'COSS_ETH'!");
}
console.log(HOST_URL2 + "/dp" + '?symbol=' + params.Symbol);
let url = HOST_URL2 + "/dp" + '?symbol=' + params.Symbol;
let pairDepth = await publicRequest(url);
let marketSides = [[pairDepth.bids[0][0], pairDepth.bids[0][1]], [pairDepth.asks[0][0], pairDepth.asks[0][1]]];
return (marketSides)
},
//get Exchange Info
getExchangeInfo: async () => {
console.log(HOST_URL + "/exchange-info");
let url = HOST_URL + "/exchange-info";
return publicRequest(url);
},
//get Market Summary (NOT WORKING ATM)
getMarketSummary: async (params = {}) => {
if (!params.Symbol) {
return Promise.reject("getMarketSummary(), You must supply a valid trading pair Symbol, e.g. 'COSS_ETH'!");
} else if (params.Symbol && typeof params.Symbol !== 'string') {
return Promise.reject("getMarketSummary(), You must supply a valid trading pair Symbol as a string, e.g. 'COSS_ETH'!");
}
console.log(EXCHANGE_HOST + "/getmarketsummaries/" + '?symbol=' + params.Symbol);
let url = EXCHANGE_HOST + "/getmarketsummaries/" + '?symbol=' + params.Symbol;
return publicRequest(url);
},
getTradeHistory: async (params = {}) => {
if (!params.Symbol) {
return Promise.reject("getTradeHistory(), You must supply a valid trading pair Symbol, e.g. 'COSS_ETH'!");
} else if (params.Symbol && typeof params.Symbol !== 'string') {
return Promise.reject("getTradeHistory(), You must supply a valid trading pair Symbol as a string, e.g. 'COSS_ETH'!");
}
console.log(HOST_URL2 + "/ht" + '?symbol=' + params.Symbol);
let url = HOST_URL2 + "/ht" + '?symbol=' + params.Symbol;
return publicRequest(url);
}
}
};
module.exports = exports = Coss;
+178
View File
@@ -0,0 +1,178 @@
exports.newAPIClient = function newAPIClient(keyVaultAPI, logger) {
const Coss = require("./coss");
const exchangeProperties = require('./cossProperties.json');
let API = Coss(keyVaultAPI);
let thisObject = {
getTicker: getTicker,
getOpenPositions: getOpenPositions,
getExecutedTrades: getExecutedTrades,
buy: buy,
sell: sell,
movePosition: movePosition,
getPublicTradeHistory: getPublicTradeHistory,
getExchangeProperties: getExchangeProperties,
isValidLot: isValidLot
};
return thisObject;
function joinCurrencies(currencyA, currencyB) {
// If only one arg, then return the first
if (typeof currencyB !== 'string') {
return currencyA;
}
return currencyA + '_' + currencyB;
}
/*
* Returns the properties for this exchange
*/
function getExchangeProperties() {
return exchangeProperties;
}
/*
* Returns the price for a given pair of assets.
* The object returned is an array of trades:
* ticker = {
* bid, Number
* ask, Number
* last Number
* };
*/
async function getTicker(pMarket, callBack) {
let params = {}
params.Symbol = joinCurrencies(pMarket.assetB, pMarket.assetA)
try {
var response = await API.getPairDepth(params)
let ticker = {
bid: Number(response.bids[0][0]),
ask: Number(response.asks[0][0]),
last: Number((response.bids[0][0] + response.asks[0][0]) / 2) // TODO Pending review
};
callBack(global.DEFAULT_OK_RESPONSE.result, ticker)
} catch (error) {
callBack(global.DEFAULT_FAIL_RESPONSE.result)
}
}
/*
* Returns the open positions ath the exchange for a given market and user account.
* The object returned is an array of positions:
* position = {
* id,
* type,
* rate, Number
* amountA, Number
* amountB, Number
* fee, (not required) Number
* datetime
* };
*/
function getOpenPositions(pMarket, callBack) {
// Not implemented yet
callBack(global.DEFAULT_OK_RESPONSE)
}
/*
* Returns the trades for a given order number.
* The object returned is an array of positions:
* position = {
* id,
* type,
* rate, Number
* amountA, Number
* amountB, Number
* fee, Number
* datetime
* };
*/
function getExecutedTrades(pPositionId, callBack) {
// Not implemented yet
callBack(global.DEFAULT_OK_RESPONSE)
}
/*
* Creates a new buy order.
* The orderNumber is returned. String
*/
function buy(pCurrencyA, pCurrencyB, pRate, pAmount, callBack) {
// Not implemented yet
callBack(global.DEFAULT_OK_RESPONSE)
}
/*
* Creates a new sell order.
* The orderNumber is returned. String
*/
function sell(pCurrencyA, pCurrencyB, pRate, pAmount, callBack) {
// Not implemented yet
callBack(global.DEFAULT_OK_RESPONSE)
}
/*
* Move an existing position to the new rate.
* The orderNumber is returned. String
*/
function movePosition(pPosition, pNewRate, pNewAmountB, callBack) {
// Not implemented yet
callBack(global.DEFAULT_OK_RESPONSE)
}
/*
* Returns all the trade history from the Exchange since startTime to endTime orderd by tradeId.
* It's possible that the exchange doesn't support this method.
* The object returned is an array of trades:
* trade = {
* tradeID, String
* globalTradeID, String
* type, String
* rate, Number
* amountA, Number
* amountB, Number
* date Date
* };
*/
async function getPublicTradeHistory(assetA, assetB, startTime, endTime, callBack) {
let params = {}
params.Symbol = joinCurrencies(assetB, assetA)
try {
let trades = []
var response = await API.getTradeHistory(params);
for (let i = 0; i < response.history.length; i++) {
let record = response.history[i]
let type
if (record.isBuyerMaker) {
type = "sell"
} else {
type = "buy"
}
let trade = {
tradeID: record.id,
globalTradeID: record.id,
type: type,
rate: Number(record.price),
amount: Number(record.qty),
total: Number(record.price) * Number(record.qty),
date: new Date(record.time)
}
trades.push(trade);
}
callBack(global.DEFAULT_OK_RESPONSE.result, trades);
} catch (error) {
callBack(global.DEFAULT_FAIL_RESPONSE.result);
}
}
function isValidLot(price, amount) {
return amount * price >= 0.0001;
}
}
+23
View File
@@ -0,0 +1,23 @@
{
"currencies": [
"BTC",
"USDT"
],
"assets": [
"BTC",
"USDT"
],
"markets": [
{
"pair": [
"BTC",
"USDT"
],
"maxDecimals": 5,
"minimalOrder": {
"amount": 0.0001,
"unit": "asset"
}
}
]
}
+9 -9
View File
@@ -13,7 +13,7 @@
const DEBUG_MODULE = require(ROOT_DIR + 'DebugLog');
let logger; // We need this here in order for the loopHealth function to work and be able to rescue the loop when it gets in trouble.
let nextLoopTimeoutHandle;
let nextLoopTimeoutHandle;
let checkLoopHealthHandle;
let thisObject = {
@@ -22,7 +22,7 @@
};
let processConfig;
let UI_COMMANDS;
let UI_COMMANDS;
return thisObject;
@@ -47,7 +47,7 @@
if (err.result === global.DEFAULT_OK_RESPONSE.result) {
let filePath;
if (global.RUN_AS_TEAM) {
filePath = global.DEV_TEAM + "/" + "bots" + "/" + bot.repo + "/" + pProcessConfig.name; // DevTeams bots only are run at the cloud.
} else {
@@ -71,7 +71,7 @@
USER_BOT_MODULE = pMODULE;
filePath = global.DEV_TEAM + "/" + "bots" + "/" + bot.repo;
cloudRequire.downloadCommons(cloudStorage, filePath, onCommonsDownloaded);
function onCommonsDownloaded(err, pMODULE) {
@@ -105,7 +105,7 @@
try {
if (FULL_LOG === true) { parentLogger.write(MODULE_NAME, "[INFO] run -> Entering function."); }
bot.enableCheckLoopHealth = true;
bot.enableCheckLoopHealth = true;
let fixedTimeLoopIntervalHandle;
@@ -246,7 +246,7 @@
if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] run -> loop -> initializeExchangeAPI -> Entering function."); }
exchangeAPI = EXCHANGE_API.newExchangeAPI(logger);
exchangeAPI = EXCHANGE_API.newExchangeAPI(logger, process.env.EXCHANGE_NAME);
exchangeAPI.initialize(onInizialized);
@@ -591,7 +591,7 @@
logger.persist();
return;
} else {
if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] run -> loop -> loopControl -> Restarting Loop in " + (processConfig.normalWaitTime / 1000) + " seconds."); }
if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] run -> loop -> loopControl -> Restarting Loop in " + (processConfig.normalWaitTime / 1000) + " seconds."); }
checkLoopHealthHandle = setTimeout(checkLoopHealth, processConfig.normalWaitTime * 5, bot.loopCounter);
nextLoopTimeoutHandle = setTimeout(loop, processConfig.normalWaitTime);
logger.persist();
@@ -686,7 +686,7 @@
if (err.result === global.DEFAULT_OK_RESPONSE.result) {
let filePath = global.DEV_TEAM + "/" + "AACloud"; // DevTeams bots only are run at the cloud.
let fileName = "this.config.json";
cloudStorage.getTextFile(filePath, fileName, onFileReceived);
@@ -695,7 +695,7 @@
if (err.result !== global.DEFAULT_OK_RESPONSE.result) {
/*
/*
If for any reason this config file cannot be read, we are not going to abort the loop for that. Instead we are going to assume
that there are no instructions to stop and we will keep the show running.
*/
+3 -3
View File
@@ -90,7 +90,7 @@
USER_BOT_MODULE = pMODULE;
filePath = global.DEV_TEAM + "/" + "bots" + "/" + bot.repo;
cloudRequire.downloadCommons(cloudStorage, filePath, onCommonsDownloaded);
function onCommonsDownloaded(err, pMODULE) {
@@ -598,7 +598,7 @@
if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] run -> loop -> initializeExchangeAPI -> Entering function."); }
exchangeAPI = EXCHANGE_API.newExchangeAPI(logger);
exchangeAPI = EXCHANGE_API.newExchangeAPI(logger, process.env.EXCHANGE_NAME);
exchangeAPI.initialize(onInizialized);
@@ -1113,7 +1113,7 @@
if (err.result === global.DEFAULT_OK_RESPONSE.result) {
let filePath = global.DEV_TEAM + "/" + "AACloud"; // DevTeams bots only are run at the cloud.
let fileName = "this.config.json";
cloudStorage.getTextFile(filePath, fileName, onFileReceived);