diff --git a/.env.sample b/.env.sample new file mode 100644 index 0000000..693c20f --- /dev/null +++ b/.env.sample @@ -0,0 +1,3 @@ +DEBUG="3commas-control:*" +THREE_COMMAS_API_KEY= +THREE_COMMAS_SECRET_KEY= \ No newline at end of file diff --git a/.gitignore b/.gitignore index 433db2e..4cd21c3 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,4 @@ /serverless.yml /credentials.json /node_modules/ +/.env \ No newline at end of file diff --git a/botHandler.js b/botHandler.js index 3d24d14..200bc5f 100644 --- a/botHandler.js +++ b/botHandler.js @@ -4,7 +4,9 @@ const subMinutes = require('date-fns/subMinutes'); const ACCOUNT_ID_TOM = process.env.THREE_COMMAS_ACCOUNT_ID; const ACCOUNT_ID_CHRIS = process.env.THREE_COMMAS_ACCOUNT_ID_CHRIS; +const ACCOUNT_ID_KUCOIN_TOM = process.env.THREE_COMMAS_ACCOUNT_ID_KUCOIN_TOM; const SHOULD_RUN_BOTS = true; +const SHOULD_RUN_SUPER_BOTS = false; const paramsToEnableSuperBot = { timePeriod: 45, @@ -53,7 +55,7 @@ module.exports.handleBots = async () => { module.exports.toggleSuperBots = async () => { console.log("STARTING review of bots to enabled a superbot"); - if (!SHOULD_RUN_BOTS) { + if (!SHOULD_RUN_SUPER_BOTS) { return { statusCode: 200, body: JSON.stringify({ message: 'NOT UPDATING BOTS', success: true }), @@ -105,8 +107,12 @@ module.exports.toggleSuperBots = async () => { }; module.exports.handleChrisBots = async () => { + console.log("NOT RUNNING"); + return false; + console.log("STARTING update of all Chris bots"); + const filters = []; filters.push((deal) => deal.completed_safety_orders_count >= 3); @@ -126,6 +132,28 @@ module.exports.handleChrisBots = async () => { }; }; +module.exports.handleKucoinTomBots = async () => { + console.log("STARTING update of all ACCOUNT_ID_KUCOIN_TOM bots"); + + const filters = []; + + filters.push((deal) => deal.completed_safety_orders_count >= 9); + filters.push({trailing_enabled: false}); + filters.push((deal) => !deal.bot_name.includes("(SKIP)")); + + const deals = await updateAllDeals(ACCOUNT_ID_KUCOIN_TOM, filters, { + trailing_enabled: true, + take_profit: 2.40, + trailing_deviation: 0.4 + }); + + console.log("FINISHED update of all ACCOUNT_ID_KUCOIN_TOM bots"); + return { + statusCode: 200, + body: JSON.stringify({ message: 'Updated all bots, see logs', success: true, deals }), + }; +}; + module.exports.updateAllDeals = async () => { const filters = []; diff --git a/package-lock.json b/package-lock.json index e8416bb..9858b74 100644 --- a/package-lock.json +++ b/package-lock.json @@ -59,6 +59,314 @@ "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.13.15.tgz", "integrity": "sha512-b9COtcAlVEQljy/9fbcMHpG+UIW9ReF+gpaxDHTlZd0c6/UU9ng8zdySAW9sRTzpvcdCHn6bUcbuYUgGzLAWVQ==" }, + "@google-cloud/functions-framework": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@google-cloud/functions-framework/-/functions-framework-1.9.0.tgz", + "integrity": "sha512-5MsxLiBdRU6EaUzhTClm97XqNIiQWXrz93w5KzgBjPyvQya6Nuz98IZbd/A8AKZZBOv9AelzAZ+Atqu076adkg==", + "requires": { + "body-parser": "^1.18.3", + "express": "^4.16.4", + "minimist": "^1.2.5", + "on-finished": "^2.3.0", + "read-pkg-up": "^7.0.1", + "semver": "^7.3.5" + }, + "dependencies": { + "lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "requires": { + "yallist": "^4.0.0" + } + }, + "semver": { + "version": "7.3.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", + "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", + "requires": { + "lru-cache": "^6.0.0" + } + }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + } + } + }, + "@google-cloud/paginator": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@google-cloud/paginator/-/paginator-3.0.5.tgz", + "integrity": "sha512-N4Uk4BT1YuskfRhKXBs0n9Lg2YTROZc6IMpkO/8DIHODtm5s3xY8K5vVBo23v/2XulY3azwITQlYWgT4GdLsUw==", + "requires": { + "arrify": "^2.0.0", + "extend": "^3.0.2" + } + }, + "@google-cloud/precise-date": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@google-cloud/precise-date/-/precise-date-2.0.3.tgz", + "integrity": "sha512-+SDJ3ZvGkF7hzo6BGa8ZqeK3F6Z4+S+KviC9oOK+XCs3tfMyJCh/4j93XIWINgMMDIh9BgEvlw4306VxlXIlYA==" + }, + "@google-cloud/projectify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@google-cloud/projectify/-/projectify-2.1.0.tgz", + "integrity": "sha512-qbpidP/fOvQNz3nyabaVnZqcED1NNzf7qfeOlgtAZd9knTwY+KtsGRkYpiQzcATABy4gnGP2lousM3S0nuWVzA==" + }, + "@google-cloud/promisify": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@google-cloud/promisify/-/promisify-2.0.3.tgz", + "integrity": "sha512-d4VSA86eL/AFTe5xtyZX+ePUjE8dIFu2T8zmdeNBSa5/kNgXPCx/o/wbFNHAGLJdGnk1vddRuMESD9HbOC8irw==" + }, + "@google-cloud/pubsub": { + "version": "2.17.0", + "resolved": "https://registry.npmjs.org/@google-cloud/pubsub/-/pubsub-2.17.0.tgz", + "integrity": "sha512-9Xya69A5VAYVEGf651jy071RuBIjv+jpyozSc3j8V21LIiKRr9x+KyplHcLTYWdj+uXbP9cry8Ck8JEFc7GiqQ==", + "requires": { + "@google-cloud/paginator": "^3.0.0", + "@google-cloud/precise-date": "^2.0.0", + "@google-cloud/projectify": "^2.0.0", + "@google-cloud/promisify": "^2.0.0", + "@opentelemetry/api": "^1.0.0", + "@opentelemetry/semantic-conventions": "^0.24.0", + "@types/duplexify": "^3.6.0", + "@types/long": "^4.0.0", + "arrify": "^2.0.0", + "extend": "^3.0.2", + "google-auth-library": "^7.0.0", + "google-gax": "^2.24.1", + "is-stream-ended": "^0.1.4", + "lodash.snakecase": "^4.1.1", + "p-defer": "^3.0.0" + }, + "dependencies": { + "agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "requires": { + "debug": "4" + } + }, + "gaxios": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-4.3.0.tgz", + "integrity": "sha512-pHplNbslpwCLMyII/lHPWFQbJWOX0B3R1hwBEOvzYi1GmdKZruuEHK4N9V6f7tf1EaPYyF80mui1+344p6SmLg==", + "requires": { + "abort-controller": "^3.0.0", + "extend": "^3.0.2", + "https-proxy-agent": "^5.0.0", + "is-stream": "^2.0.0", + "node-fetch": "^2.3.0" + } + }, + "gcp-metadata": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-4.3.0.tgz", + "integrity": "sha512-L9XQUpvKJCM76YRSmcxrR4mFPzPGsgZUH+GgHMxAET8qc6+BhRJq63RLhWakgEO2KKVgeSDVfyiNjkGSADwNTA==", + "requires": { + "gaxios": "^4.0.0", + "json-bigint": "^1.0.0" + } + }, + "google-auth-library": { + "version": "7.6.2", + "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-7.6.2.tgz", + "integrity": "sha512-yvEnwVsvgH8RXTtpf6e84e7dqIdUEKJhmQvTJwzYP+RDdHjLrDp9sk2u2ZNDJPLKZ7DJicx/+AStcQspJiq+Qw==", + "requires": { + "arrify": "^2.0.0", + "base64-js": "^1.3.0", + "ecdsa-sig-formatter": "^1.0.11", + "fast-text-encoding": "^1.0.0", + "gaxios": "^4.0.0", + "gcp-metadata": "^4.2.0", + "gtoken": "^5.0.4", + "jws": "^4.0.0", + "lru-cache": "^6.0.0" + } + }, + "google-p12-pem": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/google-p12-pem/-/google-p12-pem-3.1.2.tgz", + "integrity": "sha512-tjf3IQIt7tWCDsa0ofDQ1qqSCNzahXDxdAGJDbruWqu3eCg5CKLYKN+hi0s6lfvzYZ1GDVr+oDF9OOWlDSdf0A==", + "requires": { + "node-forge": "^0.10.0" + } + }, + "gtoken": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/gtoken/-/gtoken-5.3.1.tgz", + "integrity": "sha512-yqOREjzLHcbzz1UrQoxhBtpk8KjrVhuqPE7od1K2uhyxG2BHjKZetlbLw/SPZak/QqTIQW+addS+EcjqQsZbwQ==", + "requires": { + "gaxios": "^4.0.0", + "google-p12-pem": "^3.0.3", + "jws": "^4.0.0" + } + }, + "https-proxy-agent": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz", + "integrity": "sha512-EkYm5BcKUGiduxzSt3Eppko+PiNWNEpa4ySk9vTC6wDsQJW9rHSa+UhGNJoRYp7bz6Ht1eaRIa6QaJqO5rCFbA==", + "requires": { + "agent-base": "6", + "debug": "4" + } + }, + "is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==" + }, + "json-bigint": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-bigint/-/json-bigint-1.0.0.tgz", + "integrity": "sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==", + "requires": { + "bignumber.js": "^9.0.0" + } + }, + "jwa": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.0.tgz", + "integrity": "sha512-jrZ2Qx916EA+fq9cEAeCROWPTfCwi1IVHqT2tapuqLEVVDKFDENFw1oL+MwrTvH6msKxsd1YTDVw6uKEcsrLEA==", + "requires": { + "buffer-equal-constant-time": "1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "jws": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.0.tgz", + "integrity": "sha512-KDncfTmOZoOMTFG4mBlG0qUIOlc03fmzH+ru6RgYVZhPkyiy/92Owlt/8UEN+a4TXR1FQetfIpJE8ApdvdVxTg==", + "requires": { + "jwa": "^2.0.0", + "safe-buffer": "^5.0.1" + } + }, + "lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "requires": { + "yallist": "^4.0.0" + } + }, + "node-forge": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.10.0.tgz", + "integrity": "sha512-PPmu8eEeG9saEUvI97fm4OYxXVB6bFvyNTyiUOBichBpFG8A1Ljw3bY62+5oOjDEMHRnd0Y7HQ+x7uzxOzC6JA==" + }, + "p-defer": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-defer/-/p-defer-3.0.0.tgz", + "integrity": "sha512-ugZxsxmtTln604yeYd29EGrNhazN2lywetzpKhfmQjW/VJmhpDmWbiX+h0zL8V91R0UXkhb3KtPmyq9PZw3aYw==" + }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + } + } + }, + "@google-cloud/secret-manager": { + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/@google-cloud/secret-manager/-/secret-manager-3.10.0.tgz", + "integrity": "sha512-zpZzq5zBU49s8wIvgdVpOLhQ1pDDa84VWK57NPesN56q1OeBMU3g+ykW5pwARJx4svTOzPxsbv8x4kHqe8+GUQ==", + "requires": { + "google-gax": "^2.24.1" + } + }, + "@grpc/grpc-js": { + "version": "1.3.7", + "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.3.7.tgz", + "integrity": "sha512-CKQVuwuSPh40tgOkR7c0ZisxYRiN05PcKPW72mQL5y++qd7CwBRoaJZvU5xfXnCJDFBmS3qZGQ71Frx6Ofo2XA==", + "requires": { + "@types/node": ">=12.12.47" + } + }, + "@grpc/proto-loader": { + "version": "0.6.4", + "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.6.4.tgz", + "integrity": "sha512-7xvDvW/vJEcmLUltCUGOgWRPM8Oofv0eCFSVMuKqaqWJaXSzmB+m9hiyqe34QofAl4WAzIKUZZlinIF9FOHyTQ==", + "requires": { + "@types/long": "^4.0.1", + "lodash.camelcase": "^4.3.0", + "long": "^4.0.0", + "protobufjs": "^6.10.0", + "yargs": "^16.1.1" + }, + "dependencies": { + "ansi-regex": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", + "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==" + }, + "cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "requires": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } + }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==" + }, + "long": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz", + "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==" + }, + "string-width": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.2.tgz", + "integrity": "sha512-XBJbT3N4JhVumXE0eoLU9DCjcaF92KLNqTmFCnG1pf8duUxFGwtP6AD6nkjw9a3IdiRtL3E2w3JDiE/xi3vOeA==", + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.0" + } + }, + "strip-ansi": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", + "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", + "requires": { + "ansi-regex": "^5.0.0" + } + }, + "y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==" + }, + "yargs": { + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "requires": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" + } + }, + "yargs-parser": { + "version": "20.2.9", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", + "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==" + } + } + }, "@hapi/accept": { "version": "3.2.4", "resolved": "https://registry.npmjs.org/@hapi/accept/-/accept-3.2.4.tgz", @@ -426,6 +734,16 @@ "fastq": "^1.6.0" } }, + "@opentelemetry/api": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.0.2.tgz", + "integrity": "sha512-DCF9oC89ao8/EJUqrp/beBlDR8Bp2R43jqtzayqCoomIvkwTuPfLcHdVhIGRR69GFlkykFjcDW+V92t0AS7Tww==" + }, + "@opentelemetry/semantic-conventions": { + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-0.24.0.tgz", + "integrity": "sha512-a/szuMQV0Quy0/M7kKdglcbRSoorleyyOwbTNNJ32O+RBN766wbQlMTvdimImTmwYWGr+NJOni1EcC242WlRcA==" + }, "@protobufjs/aspromise": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", @@ -527,6 +845,11 @@ "escape-string-regexp": "^1.0.5", "supports-color": "^5.3.0" } + }, + "dotenv": { + "version": "8.6.0", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-8.6.0.tgz", + "integrity": "sha512-IrPdXQsk2BbzvCBGBOTmmSH5SodmqZNt4ERAZDmW4CT+tL8VtvinqywuANaFu4bOMWki16nqf0e4oC0QIaDr/g==" } } }, @@ -748,6 +1071,11 @@ "ms": "2.1.2" } }, + "dotenv": { + "version": "8.6.0", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-8.6.0.tgz", + "integrity": "sha512-IrPdXQsk2BbzvCBGBOTmmSH5SodmqZNt4ERAZDmW4CT+tL8VtvinqywuANaFu4bOMWki16nqf0e4oC0QIaDr/g==" + }, "https-proxy-agent": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz", @@ -914,6 +1242,14 @@ "resolved": "https://registry.npmjs.org/@types/caseless/-/caseless-0.12.2.tgz", "integrity": "sha512-6ckxMjBBD8URvjB6J3NcnuAn5Pkl7t3TizAg+xdlzzQGSPSmBcXf8KoIH0ua/i+tio+ZRUHEXp0HEmvaR4kt0w==" }, + "@types/duplexify": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/@types/duplexify/-/duplexify-3.6.0.tgz", + "integrity": "sha512-5zOA53RUlzN74bvrSGwjudssD9F3a797sDZQkiYpUOxW+WHaXTCPz4/d5Dgi6FKnOqZ2CpaTo0DhgIfsXAOE/A==", + "requires": { + "@types/node": "*" + } + }, "@types/http-cache-semantics": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.0.0.tgz", @@ -1279,6 +1615,11 @@ "resolved": "https://registry.npmjs.org/arraybuffer.slice/-/arraybuffer.slice-0.0.7.tgz", "integrity": "sha512-wGUIVQXuehL5TCqQun8OW81jGzAWycqzFF8lFp+GOM5BXLYj3bKNsYC4daB7n6XjCqxQA/qgTJ+8ANR3acjrog==" }, + "arrify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/arrify/-/arrify-2.0.1.tgz", + "integrity": "sha512-3duEwti880xqi4eAMN8AyR4a0ByT90zoYdLlevfrvU43vb0YZwZVfxOgxWrLXXXpyugL0hNZc9G6BiB5B3nUug==" + }, "asn1": { "version": "0.2.4", "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz", @@ -2163,11 +2504,18 @@ "integrity": "sha512-RI/Hh4kqRc1UKLOAf/T5zdMMX5DQIlDxwUe3wSyMMnEbGunnpENCdbUgM+dW7kXidZqCttBrmw7BhN4TMddkCw==" }, "debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz", + "integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==", "requires": { - "ms": "^2.1.1" + "ms": "2.1.2" + }, + "dependencies": { + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + } } }, "decamelize": { @@ -2327,6 +2675,11 @@ } } }, + "dedupe": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/dedupe/-/dedupe-3.0.2.tgz", + "integrity": "sha512-1BmvzpTdYjyvzTMgd8+A7R9jNJh8GfvVZJqREZpqq7OpePtVFNdw56FjsHFdC35ROSng6y+h4qFYO/VkiKEztg==" + }, "deep-eql": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-3.0.1.tgz", @@ -2589,9 +2942,9 @@ "integrity": "sha1-02UX/iS3zaYfznpQJqACSvr1pDk=" }, "dotenv": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-8.2.0.tgz", - "integrity": "sha512-8sJ78ElpbDJBHNeBzUbUVLsqKdccaa/BXF1uPTw3GrvQTBgrQrtObr2mUrE38vzYd8cEv+m/JBfDLioYcfXoaw==" + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-10.0.0.tgz", + "integrity": "sha512-rlBi9d8jpv9Sf1klPjNfFAuWDjKLwTIJJ/VxtoTwIR6hnZxcEOQCZg2oIL3MWBYw5GpUDKOEnND7LXTbIpQ03Q==" }, "download": { "version": "8.0.0", @@ -3046,6 +3399,11 @@ "es6-symbol": "^3.1.1" } }, + "escalade": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==" + }, "escape-goat": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/escape-goat/-/escape-goat-2.1.1.tgz", @@ -3811,6 +4169,175 @@ "semver": "^5.5.0" } }, + "google-gax": { + "version": "2.24.2", + "resolved": "https://registry.npmjs.org/google-gax/-/google-gax-2.24.2.tgz", + "integrity": "sha512-4OtyEIt/KAXRX5o2W/6DGf8MnMs1lMXwcGoPHR4PwXfTUVKjK7ywRe2/yRIMkYEDzAwu/kppPgfpX+kCG2rWfw==", + "requires": { + "@grpc/grpc-js": "~1.3.0", + "@grpc/proto-loader": "^0.6.1", + "@types/long": "^4.0.0", + "abort-controller": "^3.0.0", + "duplexify": "^4.0.0", + "fast-text-encoding": "^1.0.3", + "google-auth-library": "^7.6.1", + "is-stream-ended": "^0.1.4", + "node-fetch": "^2.6.1", + "object-hash": "^2.1.1", + "proto3-json-serializer": "^0.1.1", + "protobufjs": "6.11.2", + "retry-request": "^4.0.0" + }, + "dependencies": { + "agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "requires": { + "debug": "4" + } + }, + "gaxios": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-4.3.0.tgz", + "integrity": "sha512-pHplNbslpwCLMyII/lHPWFQbJWOX0B3R1hwBEOvzYi1GmdKZruuEHK4N9V6f7tf1EaPYyF80mui1+344p6SmLg==", + "requires": { + "abort-controller": "^3.0.0", + "extend": "^3.0.2", + "https-proxy-agent": "^5.0.0", + "is-stream": "^2.0.0", + "node-fetch": "^2.3.0" + } + }, + "gcp-metadata": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-4.3.0.tgz", + "integrity": "sha512-L9XQUpvKJCM76YRSmcxrR4mFPzPGsgZUH+GgHMxAET8qc6+BhRJq63RLhWakgEO2KKVgeSDVfyiNjkGSADwNTA==", + "requires": { + "gaxios": "^4.0.0", + "json-bigint": "^1.0.0" + } + }, + "google-auth-library": { + "version": "7.6.2", + "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-7.6.2.tgz", + "integrity": "sha512-yvEnwVsvgH8RXTtpf6e84e7dqIdUEKJhmQvTJwzYP+RDdHjLrDp9sk2u2ZNDJPLKZ7DJicx/+AStcQspJiq+Qw==", + "requires": { + "arrify": "^2.0.0", + "base64-js": "^1.3.0", + "ecdsa-sig-formatter": "^1.0.11", + "fast-text-encoding": "^1.0.0", + "gaxios": "^4.0.0", + "gcp-metadata": "^4.2.0", + "gtoken": "^5.0.4", + "jws": "^4.0.0", + "lru-cache": "^6.0.0" + } + }, + "google-p12-pem": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/google-p12-pem/-/google-p12-pem-3.1.2.tgz", + "integrity": "sha512-tjf3IQIt7tWCDsa0ofDQ1qqSCNzahXDxdAGJDbruWqu3eCg5CKLYKN+hi0s6lfvzYZ1GDVr+oDF9OOWlDSdf0A==", + "requires": { + "node-forge": "^0.10.0" + } + }, + "gtoken": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/gtoken/-/gtoken-5.3.1.tgz", + "integrity": "sha512-yqOREjzLHcbzz1UrQoxhBtpk8KjrVhuqPE7od1K2uhyxG2BHjKZetlbLw/SPZak/QqTIQW+addS+EcjqQsZbwQ==", + "requires": { + "gaxios": "^4.0.0", + "google-p12-pem": "^3.0.3", + "jws": "^4.0.0" + } + }, + "https-proxy-agent": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz", + "integrity": "sha512-EkYm5BcKUGiduxzSt3Eppko+PiNWNEpa4ySk9vTC6wDsQJW9rHSa+UhGNJoRYp7bz6Ht1eaRIa6QaJqO5rCFbA==", + "requires": { + "agent-base": "6", + "debug": "4" + } + }, + "is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==" + }, + "json-bigint": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-bigint/-/json-bigint-1.0.0.tgz", + "integrity": "sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==", + "requires": { + "bignumber.js": "^9.0.0" + } + }, + "jwa": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.0.tgz", + "integrity": "sha512-jrZ2Qx916EA+fq9cEAeCROWPTfCwi1IVHqT2tapuqLEVVDKFDENFw1oL+MwrTvH6msKxsd1YTDVw6uKEcsrLEA==", + "requires": { + "buffer-equal-constant-time": "1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "jws": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.0.tgz", + "integrity": "sha512-KDncfTmOZoOMTFG4mBlG0qUIOlc03fmzH+ru6RgYVZhPkyiy/92Owlt/8UEN+a4TXR1FQetfIpJE8ApdvdVxTg==", + "requires": { + "jwa": "^2.0.0", + "safe-buffer": "^5.0.1" + } + }, + "long": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz", + "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==" + }, + "lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "requires": { + "yallist": "^4.0.0" + } + }, + "node-forge": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.10.0.tgz", + "integrity": "sha512-PPmu8eEeG9saEUvI97fm4OYxXVB6bFvyNTyiUOBichBpFG8A1Ljw3bY62+5oOjDEMHRnd0Y7HQ+x7uzxOzC6JA==" + }, + "protobufjs": { + "version": "6.11.2", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-6.11.2.tgz", + "integrity": "sha512-4BQJoPooKJl2G9j3XftkIXjoC9C0Av2NOrWmbLWT1vH32GcSUHjM0Arra6UfTsVyfMAuFzaLucXn1sadxJydAw==", + "requires": { + "@protobufjs/aspromise": "^1.1.2", + "@protobufjs/base64": "^1.1.2", + "@protobufjs/codegen": "^2.0.4", + "@protobufjs/eventemitter": "^1.1.0", + "@protobufjs/fetch": "^1.1.0", + "@protobufjs/float": "^1.0.2", + "@protobufjs/inquire": "^1.1.0", + "@protobufjs/path": "^1.1.2", + "@protobufjs/pool": "^1.1.0", + "@protobufjs/utf8": "^1.1.0", + "@types/long": "^4.0.1", + "@types/node": ">=13.7.0", + "long": "^4.0.0" + } + }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + } + } + }, "google-p12-pem": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/google-p12-pem/-/google-p12-pem-1.0.4.tgz", @@ -4136,6 +4663,16 @@ "requires": { "agent-base": "^4.3.0", "debug": "^3.1.0" + }, + "dependencies": { + "debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "requires": { + "ms": "^2.1.1" + } + } } }, "human-signals": { @@ -4350,6 +4887,14 @@ "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==" }, + "is-extendable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", + "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "requires": { + "is-plain-object": "^2.0.4" + } + }, "is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", @@ -4434,6 +4979,14 @@ "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz", "integrity": "sha1-caUMhCnfync8kqOQpKA7OfzVHT4=" }, + "is-plain-object": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", + "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "requires": { + "isobject": "^3.0.1" + } + }, "is-promise": { "version": "2.2.2", "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.2.2.tgz", @@ -4458,6 +5011,11 @@ "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=" }, + "is-stream-ended": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/is-stream-ended/-/is-stream-ended-0.1.4.tgz", + "integrity": "sha512-xj0XPvmr7bQFTvirqnFr50o0hQIh6ZItDqloxt5aJrR4NQsYeSsyFQERYGCAzfindAcnKjINnwEEgLx4IqVzQw==" + }, "is-string": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.5.tgz", @@ -4504,6 +5062,11 @@ "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=" }, + "isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=" + }, "isomorphic-ws": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/isomorphic-ws/-/isomorphic-ws-4.0.1.tgz", @@ -4660,6 +5223,15 @@ "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=" }, + "json-templates": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/json-templates/-/json-templates-4.1.0.tgz", + "integrity": "sha512-Xjcnphott7Kj09zTSvszDVMXNa6utrQjXR25oxFMGKRNuX9wYQsJgKeQWVAxHE26I8lPUriIJ9nba1fCCBiI4Q==", + "requires": { + "dedupe": "^3.0.2", + "object-path": "^0.11.4" + } + }, "jsonfile": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", @@ -4929,6 +5501,11 @@ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" }, + "lodash.camelcase": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", + "integrity": "sha1-soqmKIorn8ZRA1x3EfZathkDMaY=" + }, "lodash.defaults": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz", @@ -4979,6 +5556,11 @@ "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", "integrity": "sha1-DdOXEhPHxW34gJd9UEyI+0cal6w=" }, + "lodash.snakecase": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.snakecase/-/lodash.snakecase-4.1.1.tgz", + "integrity": "sha1-OdcUo1NXFHg3rv1ktdy7Fr7Nj40=" + }, "lodash.union": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/lodash.union/-/lodash.union-4.6.0.tgz", @@ -5598,6 +6180,11 @@ "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==" }, + "object-path": { + "version": "0.11.5", + "resolved": "https://registry.npmjs.org/object-path/-/object-path-0.11.5.tgz", + "integrity": "sha512-jgSbThcoR/s+XumvGMTMf81QVBmah+/Q7K7YduKeKVWL7N111unR2d6pZZarSk6kY/caeNxUDyxOvMWyzoU2eg==" + }, "object.assign": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.2.tgz", @@ -5630,6 +6217,14 @@ "es-abstract": "^1.18.0-next.2" } }, + "object.omit": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/object.omit/-/object.omit-3.0.0.tgz", + "integrity": "sha512-EO+BCv6LJfu+gBIF3ggLicFebFLN5zqzz/WWJlMFfkMyGth+oBkhxzDl0wx2W4GkLzuQs/FsSkXZb2IMWQqmBQ==", + "requires": { + "is-extendable": "^1.0.0" + } + }, "on-finished": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", @@ -5984,6 +6579,14 @@ "requires": { "lodash": "^4.17.14" } + }, + "debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "requires": { + "ms": "^2.1.1" + } } } }, @@ -6153,6 +6756,11 @@ "resolved": "https://registry.npmjs.org/promise-queue/-/promise-queue-2.2.5.tgz", "integrity": "sha1-L29ffA9tCBCelnZZx5uIqe1ek7Q=" }, + "proto3-json-serializer": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/proto3-json-serializer/-/proto3-json-serializer-0.1.3.tgz", + "integrity": "sha512-X0DAtxCBsy1NDn84huVFGOFgBslT2gBmM+85nY6/5SOAaCon1jzVNdvi74foIyFvs5CjtSbQsepsM5TsyNhqQw==" + }, "protobufjs": { "version": "6.10.2", "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-6.10.2.tgz", @@ -6496,6 +7104,15 @@ "resolved": "https://registry.npmjs.org/retry/-/retry-0.10.1.tgz", "integrity": "sha1-52OI0heZLCUnUCQdPTlW/tmNj/Q=" }, + "retry-request": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/retry-request/-/retry-request-4.2.2.tgz", + "integrity": "sha512-xA93uxUD/rogV7BV59agW/JHPGXeREMWiZc9jhcwY4YdZ7QOtC7qbomYg0n4wyk2lJhggjvKvhNX8wln/Aldhg==", + "requires": { + "debug": "^4.1.1", + "extend": "^3.0.2" + } + }, "reusify": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", @@ -6761,6 +7378,11 @@ "ms": "2.1.2" } }, + "dotenv": { + "version": "8.6.0", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-8.6.0.tgz", + "integrity": "sha512-IrPdXQsk2BbzvCBGBOTmmSH5SodmqZNt4ERAZDmW4CT+tL8VtvinqywuANaFu4bOMWki16nqf0e4oC0QIaDr/g==" + }, "https-proxy-agent": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz", @@ -7338,6 +7960,14 @@ "readable-stream": "^2.3.5" }, "dependencies": { + "debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "requires": { + "ms": "^2.1.1" + } + }, "mime": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", diff --git a/package.json b/package.json index cad4cb2..efc3051 100644 --- a/package.json +++ b/package.json @@ -1,15 +1,24 @@ { + "main": "src/index.js", "dependencies": { "3commas-api-node": "^1.0.9", + "@google-cloud/functions-framework": "^1.9.0", + "@google-cloud/pubsub": "^2.17.0", + "@google-cloud/secret-manager": "^3.10.0", "aws-sdk": "^2.889.0", "axios": "^0.21.1", "crypto-js": "^4.0.0", "date-fns": "^2.23.0", + "debug": "^4.3.2", + "dotenv": "^10.0.0", "ejs": "^3.1.6", "express": "^4.17.1", "googleapis": "^39.2.0", "gs": "0.0.2", + "json-templates": "^4.1.0", "lodash": "^4.17.21", + "node-fetch": "^2.6.1", + "object.omit": "^3.0.0", "qs": "^6.10.1", "readline": "^1.3.0", "request": "^2.88.2", diff --git a/src/functions/auto-trail.js b/src/functions/auto-trail.js new file mode 100644 index 0000000..4e37d46 --- /dev/null +++ b/src/functions/auto-trail.js @@ -0,0 +1,79 @@ +const debug = require("debug")("3commas-control:auto-trailing"); +const { getDeals, updateDeal } = require("../three-commas"); +const { parseEventJSON } = require("../utils"); + +/** + * Auto trail function. + * + * Queries active deals looking for potential trailing take profit opportunities. + * + * @returns {Promise} + */ +module.exports = async function autoTrail(event) { + const { accountId, ignore, minSafetyOrders, takeProfit, trailing } = + parseEventJSON(event); + + const params = { + scope: "active", + account_id: accountId, + }; + + const updates = []; + + for await (const deal of getDeals.iterate(params)) { + if (!shouldTrail(deal, minSafetyOrders, ignore)) { + debug("%s skipping", deal.bot_name); + continue; + } + + const update = updateDeal({ + deal_id: deal.id, + trailing_enabled: true, + take_profit: takeProfit, + trailing_deviation: trailing, + }); + + updates.push(update); + + debug( + "%s trailing enabled (TTP %d% / %d%)", + deal.bot_name, + takeProfit, + trailing + ); + } + + await Promise.all(updates); +}; + +/** + * Returns if a given deal should enable trailing take profits. + * + * @param {Object} deal + * @param {number} minSafetyOrders + * @param {Array} [ignore] + * @returns {boolean} + */ +function shouldTrail(deal, minSafetyOrders, ignore = []) { + // already trailing, don't touch it + if (deal.trailing_enabled) { + return false; + } + + // doesn't have minimum safet orders + if (deal.completed_safety_orders_count < minSafetyOrders) { + return false; + } + + // should bot be ignored + return !ignore.some((predicate) => { + // match bot_ids + if (typeof predicate === "number") { + return deal.bot_id === predicate; + } + + // match bot_names + const regexp = new RegExp(predicate); + return regexp.test(deal.bot_name); + }); +} diff --git a/src/functions/superbot.js b/src/functions/superbot.js new file mode 100644 index 0000000..0875539 --- /dev/null +++ b/src/functions/superbot.js @@ -0,0 +1,212 @@ +const debug = require("debug")("3commas-control:superbot"); +const date = require("date-fns"); +const { getDeals, getBots, updateBot } = require("../three-commas"); +const { parseEventJSON, delay } = require("../utils"); + +const superbotPrefix = "SUPERBOT:"; +const superbotRegExp = new RegExp( + `^${superbotPrefix}\\s+(.+)\\s+\\[(\\d+\\.\\d+)\\/(\\d+\\.\\d+)\\]$` +); + +const botCache = new Map(); + +/** + * Superbot function. + * + * Enables/disables superbot mode for running bots. + * + * @returns {Promise} + */ +module.exports = async function superbot(event) { + const options = parseEventJSON(event); + const { + accountId, + minDealsClosed, + intervalInMins, + maxSuperbots, + maxDurationInMins, + baseOrderAmount, + safetyOrderAmount, + } = options; + + const now = new Date(); + const minStartDate = date.sub(now, { minutes: intervalInMins }); + const minDurationDate = date.sub(now, { + minutes: maxDurationInMins, + }); + + // -------------------------------------------------------------------------- + + // Part 1: Pre-fetch bots + // Warm up a cache of all active bots associated with account. + + const botParams = { + limit: 100, + account_id: accountId, + scope: "enabled", + }; + + for await (const bot of getBots.iterate(botParams)) botCache.set(bot.id, bot); + + // -------------------------------------------------------------------------- + + // Part 2: Superbot suppression + // Firstly, work out from the deals which bots are superbots and of which, + // disable any that have went past their max deal duration. + + const activeDeals = await getDeals({ + account_id: accountId, + order_direction: "desc", + scope: "active", + }); + + const activeSuperbotDeals = activeDeals.filter( + filterSuperbotDeals(baseOrderAmount, safetyOrderAmount) + ); + const activeEnabledSuperbotDeals = activeSuperbotDeals.filter(isSuperbot); + const activeExpiredSuperbotDeals = activeEnabledSuperbotDeals.filter( + filterExpiredSuperbotDeals(minDurationDate) + ); + + const botIdsToDisable = activeExpiredSuperbotDeals.map((deal) => deal.bot_id); + await Promise.all(botIdsToDisable.map(disableSuperbot)); + + // -------------------------------------------------------------------------- + + // Part 3: Superbot detection + // Next let's see if we can detect if a bot has been pumping to turn + // superbot mode on. + + let superbotsCount = activeSuperbotDeals.length; + const activeSuperbotIds = activeSuperbotDeals.map((deal) => deal.bot_id); + + for (const [id, bot] of botCache) { + // superbot limit reached + if (superbotsCount >= maxSuperbots) { + break; + } + + // already a superbot + const superbot = activeSuperbotIds.includes(id); + if (superbot) { + continue; + } + + // 3Commas `from` parameter goes from the created date so we need to fetch + // the latest 3 closed deals per bot. However 3Commas weights this request + // heavily so we need to backoff so our IP doesn't get blocked. A simple + // 1000ms delay seems to do the trick. + await delay(1000); + + debug("checking %s deals", bot.name); + + // recent deals + const deals = await getDeals({ + account_id: accountId, + bot_id: id, + order_direction: "desc", + scope: "completed", + limit: minDealsClosed, + }); + + // count how many closed within the interval + const innerDealsClosed = deals.filter(({ closed_at: closedAt }) => { + const closed = new Date(closedAt); + return minStartDate < closed; + }); + + if (innerDealsClosed.length === minDealsClosed) { + await updateBot({ + ...requiredFields(bot), + bot_id: id, + name: superbotName(bot), + base_order_volume: baseOrderAmount, + safety_order_volume: safetyOrderAmount, + }); + + superbotsCount += 1; + debug("%s SUPERBOT enabled", bot.name); + } + } +}; + +function filterSuperbotDeals(baseOrderAmount, safetyOrderAmount) { + return (deal) => { + // detect by the bot name + if (isSuperbot(deal)) { + return true; + } + + const dealBaseOrderAmount = parseFloat(deal.base_order_volume); + const dealSafetyOrderAmount = parseFloat(deal.safety_order_volume); + + // Is the active deal the same order amounts. This is a fallback detection + // for when a superbot has been disabled however it still has an open deal. + return ( + dealSafetyOrderAmount === safetyOrderAmount && + dealBaseOrderAmount === baseOrderAmount + ); + }; +} + +function filterExpiredSuperbotDeals(minDate) { + return (deal) => { + const created = new Date(deal.created_at); + return created < minDate; + }; +} + +function isSuperbot({ name, bot_name: botName }) { + return (botName || name).startsWith(superbotPrefix); +} + +function superbotName({ + name, + base_order_volume: oldBaseOrderAMount, + safety_order_volume: oldSafetyOrderAmount, +}) { + return [ + superbotPrefix, + name, + `[${oldBaseOrderAMount}/${oldSafetyOrderAmount}]`, + ].join(" "); +} + +function extractNameAndOrders({ name }) { + return name.match(superbotRegExp).slice(1); +} + +async function disableSuperbot(id) { + const bot = botCache.get(id); + + const [originalName, originalBaseOrderAmount, originalSafetyORderAmount] = + extractNameAndOrders(bot); + + await updateBot({ + ...requiredFields(bot), + bot_id: id, + name: originalName, + base_order_volume: originalBaseOrderAmount, + safety_order_volume: originalSafetyORderAmount, + }); + + debug("%s SUPERBOT disabled", originalName); +} + +function requiredFields(bot) { + // stupid 3Commas required fields + return { + name: bot.name, + base_order_volume: bot.base_order_volume, + safety_order_volume: bot.safety_order_volume, + pairs: JSON.stringify(bot.pairs), + take_profit: bot.take_profit, + martingale_volume_coefficient: bot.martingale_volume_coefficient, + martingale_step_coefficient: bot.martingale_step_coefficient, + max_safety_orders: bot.max_safety_orders, + active_safety_orders_count: bot.active_safety_orders_count, + safety_order_step_percentage: bot.safety_order_step_percentage, + take_profit_type: bot.take_profit_type, + strategy_list: JSON.stringify(bot.strategy_list), + }; +} diff --git a/src/index.js b/src/index.js new file mode 100644 index 0000000..04c2513 --- /dev/null +++ b/src/index.js @@ -0,0 +1,6 @@ +require("dotenv/config"); + +module.exports = { + autoTrail: require("./functions/auto-trail"), + superbot: require("./functions/superbot"), +}; diff --git a/src/three-commas.js b/src/three-commas.js new file mode 100644 index 0000000..1edd012 --- /dev/null +++ b/src/three-commas.js @@ -0,0 +1,288 @@ +const { join } = require("path"); +const fetch = require("node-fetch"); +const parse = require("json-templates"); +const omit = require("object.omit"); +const debug = require("debug")("3commas-control:api"); +const { SecretManagerServiceClient } = require("@google-cloud/secret-manager"); +const { sign } = require("./utils"); +const WebSocket = require("ws"); + +const baseURL = new URL("https://api.3commas.io/public/api"); + +const secretManagerClient = new SecretManagerServiceClient(); + +/** + * Builds the 3Commas API object. + * + */ +Object.assign( + exports, + factory({ + getDeals: { + signed: true, + iterator: true, + method: "GET", + path: "/ver1/deals", + }, + updateDeal: { + signed: true, + method: "PATCH", + path: "/ver1/deals/{{deal_id}}/update_deal", + }, + getBots: { + signed: true, + iterator: true, + method: "GET", + path: "/ver1/bots", + }, + updateBot: { + signed: true, + method: "PATCH", + path: "/ver1/bots/{{bot_id}}/update", + }, + }) +); + +/** + * Factory function for build an object for the 3Commas API service. + * + * @example + * // example of the API definition object + * const definitions = { + * methodName: { + * method:"GET|POST|PUT|...", + * path: "/api/path", + * signed: true, // use a signed request + * } + * }; + * + * @param {Object} definitions + * @returns {Object} + */ +function factory(definitions) { + const api = {}; + + for (const [name, define] of Object.entries(definitions)) { + const { method, path, signed, iterator } = define; + const func = signed ? signedRequest : request; + const boundFunc = func.bind(null, method, path); + + api[name] = iterator ? assignIterate(boundFunc) : boundFunc; + } + + return api; +} + +function assignIterate(func) { + return Object.assign(func, { + async *iterate({ limit = 1000, offset = 0, ...params }) { + const deals = await func({ + ...params, + limit, + offset, + }); + + for (let i = 0; i < deals.length; ++i) { + yield deals[i]; + } + + if (deals.length === limit) { + yield* func.iterate({ + ...params, + offset: offset + limit, + limit, + }); + } + }, + }); +} + +/** + * Makes a generic request to 3Commas API service. + * + * @param {string} method + * @param {string} apiPath + * @param {Object} [params] + * @param {Object} [headers] + * @returns {*} + */ +async function request(method, apiPath, params = {}, headers = {}) { + [apiPath, params] = replacePathParams(apiPath, params); + + let body = method !== "GET" ? params : undefined; + const url = toURL(apiPath, body ? undefined : params); + + // use the params as the body for non-GET requests + if (body) { + const searchParams = new URLSearchParams(params); + body = searchParams.toString(); + } + + debug("[%s] %s", method, url.toString()); + + const response = await fetch(url.toString(), { + method, + body, + headers: { + "Content-Type": "application/x-www-form-urlencoded", + ...headers, + }, + }); + + return response.json(); +} + +/** + * Makes a signed request to 3Commas API service. + * + * @param {string} method + * @param {string} apiPath + * @param {Object} [params] + * @param {Object} [headers] + * @returns {*} + */ +async function signedRequest(method, apiPath, params = {}, headers = {}) { + [apiPath, params] = replacePathParams(apiPath, params); + + const [apiKey, secretKey] = await getAPIKeys(); + + const fullURL = toURL(apiPath, params); + const pathToSign = toPathWithQueryString(fullURL); + const signature = sign(pathToSign, secretKey); + + return request(method, apiPath, params, { + APIKEY: apiKey, + Signature: signature, + ...headers, + }); +} + +/** + * Constructs a full 3Commas URL with a given API path. + * + * @param {string} apiPath + * @param {Object} [params] + * @returns {URL} + */ +function toURL(apiPath, params = {}) { + const path = join(baseURL.pathname, apiPath); + const url = new URL(path, baseURL.origin); + + for (const [name, value] of Object.entries(params)) { + url.searchParams.set(name, value); + } + + return url; +} + +/** + * Converts a URL to path with query string appended. + * + * @param {URL} url + * @returns {string} + */ +function toPathWithQueryString(url) { + const urlString = url.toString(); + return urlString.substr(url.origin.length); +} + +function replacePathParams(path, params = {}) { + const template = parse(path); + + if (template.parameters.length) { + const omitKeys = template.parameters.map(({ key }) => key); + + path = template(params); + params = omit(params, omitKeys); + } + + return [path, params]; +} + +/** + * Returns the API and Secret keys. + * + * @returns {Promise<[string, string]>} + */ +async function getAPIKeys() { + const { THREE_COMMAS_API_KEY, THREE_COMMAS_SECRET_KEY } = process.env; + + // envs + if (THREE_COMMAS_SECRET_KEY && THREE_COMMAS_SECRET_KEY) { + return [THREE_COMMAS_API_KEY, THREE_COMMAS_SECRET_KEY]; + } + + const apiKeyName = + "projects/1018578123164/secrets/3commas-api-key/versions/latest"; + const secretKeyName = + "projects/1018578123164/secrets/3commas-secret-key/versions/latest"; + + return Promise.all([apiKeyName, secretKeyName].map(getSecretValue)); +} + +/** + * Gets a secret value. + * + * @param {string} name + * @returns {Promise} + */ +async function getSecretValue(name) { + const [version] = await secretManagerClient.accessSecretVersion({ + name, + }); + + return version.payload.data.toString(); +} + +/** + * Stream deal updates. + * + * @param {Function} callback + * @returns {void} + */ +exports.stream = async function stream(callback) { + const [apiKey, secretKey] = await getAPIKeys(); + const signature = sign("/deals", secretKey); + + const streamIdentifier = JSON.stringify({ + channel: "DealsChannel", + users: [ + { + api_key: apiKey, + signature, + }, + ], + }); + + const ws = new WebSocket("wss://ws.3commas.io/websocket"); + + ws.on("open", () => { + debug("socket opened, trying to subscribe"); + + ws.send( + JSON.stringify({ + command: "subscribe", + identifier: streamIdentifier, + }) + ); + }); + + ws.on("error", () => debug("socket error")); + ws.on("close", () => debug("socket closed")); + + ws.on("message", (event) => { + const data = JSON.parse(event.toString()); + const { identifier, message, type } = data; + + if ( + type === "ping" || + type === "confirm_subscription" || + identifier !== streamIdentifier + ) { + type === "confirm_subscription" && debug("subscribed"); + return; + } + + callback(message); + }); +}; diff --git a/src/utils.js b/src/utils.js new file mode 100644 index 0000000..2483769 --- /dev/null +++ b/src/utils.js @@ -0,0 +1,29 @@ +const { createHmac } = require("crypto"); + +module.exports = { + sign, + parseEventJSON, + delay, +}; + +/** + * Signs data with a secret key. + * + * @param {import("crypto").BinaryLike} data + * @param {import("crypto").BinaryLike} secretKey + * @returns {string} + */ +function sign(data, secretKey) { + const hash = createHmac("SHA256", secretKey); + const sig = hash.update(data).digest("hex"); + + return sig; +} + +function parseEventJSON({ data }) { + return JSON.parse(Buffer.from(data, "base64").toString()); +} + +async function delay(ms) { + return new Promise((res) => setTimeout(res, ms)); +}