diff --git a/feeder/package-lock.json b/feeder/package-lock.json index 1304c56..4be9691 100644 --- a/feeder/package-lock.json +++ b/feeder/package-lock.json @@ -1,12 +1,12 @@ { "name": "@classic-terra/oracle-feeder", - "version": "3.1.1", + "version": "3.1.2", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@classic-terra/oracle-feeder", - "version": "3.1.1", + "version": "3.1.2", "license": "Apache-2.0", "dependencies": { "@terra-money/terra.js": "^3.1.7", @@ -224,9 +224,9 @@ "integrity": "sha512-MqTGEo5bj5t157U6fA/BiDynNkn0YknVdh48CMPkTSpFTVmvao5UQmm7uEF6xBEo7qIMAlY/JSleYaE6VOdpaA==" }, "node_modules/@types/node": { - "version": "18.17.17", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.17.17.tgz", - "integrity": "sha512-cOxcXsQ2sxiwkykdJqvyFS+MLQPLvIdwh5l6gNg8qF6s+C7XSkEWOZjK+XhUZd+mYvHV/180g2cnCcIl4l06Pw==" + "version": "18.18.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.18.1.tgz", + "integrity": "sha512-3G42sxmm0fF2+Vtb9TJQpnjmP+uKlWvFa8KoEGquh4gqRmoUG/N0ufuhikw6HEsdG2G2oIKhog1GCTfz9v5NdQ==" }, "node_modules/acorn": { "version": "8.10.0", @@ -508,9 +508,9 @@ "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==" }, "node_modules/follow-redirects": { - "version": "1.15.2", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz", - "integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==", + "version": "1.15.3", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.3.tgz", + "integrity": "sha512-1VzOtuEM8pC9SFU1E+8KfTjZyMztRsgEfwQl44z8A25uy13jSzTj6dyK2Df52iV0vgHCfBwLhDWevLn95w5v6Q==", "funding": [ { "type": "individual", diff --git a/feeder/package.json b/feeder/package.json index 59b8e40..65c17c4 100644 --- a/feeder/package.json +++ b/feeder/package.json @@ -1,6 +1,6 @@ { "name": "@classic-terra/oracle-feeder", - "version": "3.1.1", + "version": "3.1.2", "main": "src/index.ts", "license": "Apache-2.0", "scripts": { diff --git a/price-server/config/default-sample.js b/price-server/config/default-sample.js index 1925c42..a8f002f 100644 --- a/price-server/config/default-sample.js +++ b/price-server/config/default-sample.js @@ -518,20 +518,21 @@ module.exports = { fiatProvider: { fallbackPriority: [ /* Providers who requires payment to obtain API key */ + // 'exchangerate', // 'fastforex', // 'currencylayer', // 'fixer', // 'alphavantage' /* Free providers */ - 'exchangerate', + 'imf', 'frankfurter', 'fer', ], - // https://exchangerate.host/ - exchangerate: { - symbols: fiatSymbols, - interval: 30 * 1000, - timeout: 5000, + // https://www.imf.org/ For daily updates on the SDR/USD rate only. + imf: { + symbols: ['SDR/USD'], + interval: 60 * 5 * 1000, + timeout: 10000, }, // https://fer.ee/ fer: { @@ -545,8 +546,8 @@ module.exports = { interval: 30 * 1000, timeout: 5000, }, - // https://fastforex.readme.io - // price: $9/month + // https://fastforex.io + // recommended plan: $9/month fastforex: { symbols: fiatSymbols, interval: 60 * 1000, @@ -554,28 +555,36 @@ module.exports = { apiKey: '', // necessary }, // https://currencylayer.com/product - // recommend: Enterprise (60second Updates): $59.99/month + // recommended plan: Enterprise $50.99/month currencylayer: { symbols: fiatSymbols, interval: 60 * 1000, timeout: 5000, apiKey: '', // necessary }, - // https://www.alphavantage.co/premium/ - // recommend: 150 API request per minute: $99.99/month - alphavantage: { + // https://exchangerate.host/ + // recommended plan: Business $79.99/month + exchangerate: { symbols: fiatSymbols, - interval: 60 * 1000, + interval: 30 * 1000, timeout: 5000, - apiKey: '', // necessary + apiKey: '', // required }, // https://fixer.io/product - // recommend: professional plus(60second Updates): $99.99/month + // recommend plan: professional plus(60second Updates): $79.99/month fixer: { symbols: fiatSymbols, interval: 60 * 1000, timeout: 5000, apiKey: '', // necessary }, + // https://www.alphavantage.co/premium/ + // recommend plan: 150 API request per minute: $99.99/month + alphavantage: { + symbols: fiatSymbols, + interval: 60 * 1000, + timeout: 5000, + apiKey: '', // necessary + }, }, } diff --git a/price-server/package-lock.json b/price-server/package-lock.json index 40ff702..61d21dc 100644 --- a/price-server/package-lock.json +++ b/price-server/package-lock.json @@ -1,12 +1,12 @@ { "name": "@classic-terra/price-server", - "version": "3.1.1", + "version": "3.1.2", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@classic-terra/price-server", - "version": "3.1.1", + "version": "3.1.2", "license": "Apache-2.0", "dependencies": { "@opentelemetry/api": "^1.0.2", @@ -22,6 +22,7 @@ "form-data": "^4.0.0", "lodash": "^4.17.20", "node-fetch": "^2.6.7", + "node-html-parser": "^6.1.10", "pako": "^2.0.3", "polka": "^0.5.2", "ts-node": "^10.7.0", @@ -48,9 +49,9 @@ } }, "node_modules/@babel/runtime": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.22.15.tgz", - "integrity": "sha512-T0O+aa+4w0u06iNmapipJXMV4HoUir03hpx3/YqXXhu9xim3w+dVphjFWl1OH8NbZHw5Lbm9k45drDkgq2VNNA==", + "version": "7.23.1", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.23.1.tgz", + "integrity": "sha512-hC2v6p8ZSI/W0HUzh3V8C5g+NwSKzKPtJwSpTjwl0o297GP9+ZLQSkdvHz46CM3LqyoXxq+5G9komY+eSqSO0g==", "dependencies": { "regenerator-runtime": "^0.14.0" }, @@ -194,13 +195,13 @@ "integrity": "sha512-oZLYFEAzUKyi3SKnXvj32ZCEGH6RDnao7COuCVhDydMS9NrCSVXhM79VaKyP5+Zc33m0QXEd2DN3UkU7OsHcfw==" }, "node_modules/@sentry-internal/tracing": { - "version": "7.69.0", - "resolved": "https://registry.npmjs.org/@sentry-internal/tracing/-/tracing-7.69.0.tgz", - "integrity": "sha512-4BgeWZUj9MO6IgfO93C9ocP3+AdngqujF/+zB2rFdUe+y9S6koDyUC7jr9Knds/0Ta72N/0D6PwhgSCpHK8s0Q==", + "version": "7.72.0", + "resolved": "https://registry.npmjs.org/@sentry-internal/tracing/-/tracing-7.72.0.tgz", + "integrity": "sha512-DToryaRSHk9R5RLgN4ktYEXZjQdqncOAWPqyyIurji8lIobXFRfmLtGL1wjoCK6sQNgWsjhSM9kXxwGnva1DNw==", "dependencies": { - "@sentry/core": "7.69.0", - "@sentry/types": "7.69.0", - "@sentry/utils": "7.69.0", + "@sentry/core": "7.72.0", + "@sentry/types": "7.72.0", + "@sentry/utils": "7.72.0", "tslib": "^2.4.1 || ^1.9.3" }, "engines": { @@ -208,12 +209,12 @@ } }, "node_modules/@sentry/core": { - "version": "7.69.0", - "resolved": "https://registry.npmjs.org/@sentry/core/-/core-7.69.0.tgz", - "integrity": "sha512-V6jvK2lS8bhqZDMFUtvwe2XvNstFQf5A+2LMKCNBOV/NN6eSAAd6THwEpginabjet9dHsNRmMk7WNKvrUfQhZw==", + "version": "7.72.0", + "resolved": "https://registry.npmjs.org/@sentry/core/-/core-7.72.0.tgz", + "integrity": "sha512-G03JdQ5ZsFNRjcNNi+QvCjqOuBvYqU92Gs1T2iK3GE8dSBTu2khThydMpG4xrKZQLIpHOyiIhlFZiuPtZ66W8w==", "dependencies": { - "@sentry/types": "7.69.0", - "@sentry/utils": "7.69.0", + "@sentry/types": "7.72.0", + "@sentry/utils": "7.72.0", "tslib": "^2.4.1 || ^1.9.3" }, "engines": { @@ -221,15 +222,15 @@ } }, "node_modules/@sentry/node": { - "version": "7.69.0", - "resolved": "https://registry.npmjs.org/@sentry/node/-/node-7.69.0.tgz", - "integrity": "sha512-T0NgPcmDQvEuz5hy6aEhXghTHHTWsiP3IWoeEAakDBHAXmtpT6lYFQZgb5AiEOt9F5KO/G/1yH3YYdpDAnKhPw==", - "dependencies": { - "@sentry-internal/tracing": "7.69.0", - "@sentry/core": "7.69.0", - "@sentry/types": "7.69.0", - "@sentry/utils": "7.69.0", - "cookie": "^0.4.1", + "version": "7.72.0", + "resolved": "https://registry.npmjs.org/@sentry/node/-/node-7.72.0.tgz", + "integrity": "sha512-R5kNCIdaDa92EN6oCLiGJehw5wxayOM53WF60Ap6EJHZb5U8dM2BnODmQ6SCRLNB677p+620oSV6CCU286IleQ==", + "dependencies": { + "@sentry-internal/tracing": "7.72.0", + "@sentry/core": "7.72.0", + "@sentry/types": "7.72.0", + "@sentry/utils": "7.72.0", + "cookie": "^0.5.0", "https-proxy-agent": "^5.0.0", "lru_map": "^0.3.3", "tslib": "^2.4.1 || ^1.9.3" @@ -239,19 +240,19 @@ } }, "node_modules/@sentry/types": { - "version": "7.69.0", - "resolved": "https://registry.npmjs.org/@sentry/types/-/types-7.69.0.tgz", - "integrity": "sha512-zPyCox0mzitzU6SIa1KIbNoJAInYDdUpdiA+PoUmMn2hFMH1llGU/cS7f4w/mAsssTlbtlBi72RMnWUCy578bw==", + "version": "7.72.0", + "resolved": "https://registry.npmjs.org/@sentry/types/-/types-7.72.0.tgz", + "integrity": "sha512-g6u0mk62yGshx02rfFADIfyR/S9VXcf3RG2qQPuvykrWtOfN/BOTrZypF7I+MiqKwRW76r3Pcu2C/AB+6z9XQA==", "engines": { "node": ">=8" } }, "node_modules/@sentry/utils": { - "version": "7.69.0", - "resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-7.69.0.tgz", - "integrity": "sha512-4eBixe5Y+0EGVU95R4NxH3jkkjtkE4/CmSZD4In8SCkWGSauogePtq6hyiLsZuP1QHdpPb9Kt0+zYiBb2LouBA==", + "version": "7.72.0", + "resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-7.72.0.tgz", + "integrity": "sha512-o/MtqI7WJXuswidH0bSgBP40KN2lrnyQEIx5uoyJUJi/QEaboIsqbxU62vaFJpde8SYrbA+rTnP3J3ujF2gUag==", "dependencies": { - "@sentry/types": "7.69.0", + "@sentry/types": "7.72.0", "tslib": "^2.4.1 || ^1.9.3" }, "engines": { @@ -290,20 +291,20 @@ "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==" }, "node_modules/@types/lodash": { - "version": "4.14.198", - "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.198.tgz", - "integrity": "sha512-trNJ/vtMZYMLhfN45uLq4ShQSw0/S7xCTLLVM+WM1rmFpba/VS42jVUgaO3w/NOLiWR/09lnYk0yMaA/atdIsg==", + "version": "4.14.199", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.199.tgz", + "integrity": "sha512-Vrjz5N5Ia4SEzWWgIVwnHNEnb1UE1XMkvY5DGXrAeOGE9imk0hgTHh5GyDjLDJi9OTCn9oo9dXH1uToK1VRfrg==", "dev": true }, "node_modules/@types/node": { - "version": "20.6.2", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.6.2.tgz", - "integrity": "sha512-Y+/1vGBHV/cYk6OI1Na/LHzwnlNCAfU3ZNGrc1LdRe/LAIbdDPTTv/HU3M7yXN448aTVDq3eKRm2cg7iKLb8gw==" + "version": "20.7.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.7.2.tgz", + "integrity": "sha512-RcdC3hOBOauLP+r/kRt27NrByYtDjsXyAuSbR87O6xpsvi763WI+5fbSIvYJrXnt9w4RuxhV6eAXfIs7aaf/FQ==" }, "node_modules/@types/node-fetch": { - "version": "2.6.5", - "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.6.5.tgz", - "integrity": "sha512-OZsUlr2nxvkqUFLSaY2ZbA+P1q22q+KrlxWOn/38RX+u5kTkYL2mTujEpzUhGkS+K/QCYp9oagfXG39XOzyySg==", + "version": "2.6.6", + "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.6.6.tgz", + "integrity": "sha512-95X8guJYhfqiuVVhRFxVQcf4hW/2bCuoPwDasMf/531STFoNoWTT7YDnWdXHEZKqAGUigmpG31r2FE70LwnzJw==", "dev": true, "dependencies": { "@types/node": "*", @@ -311,9 +312,9 @@ } }, "node_modules/@types/ws": { - "version": "8.5.5", - "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.5.tgz", - "integrity": "sha512-lwhs8hktwxSjf9UaZ9tG5M03PGogvFaH8gUgLNbN9HKIg0dvv6q+gkSuJ8HN4/VbyxkuLzCjlN7GquQ0gUJfIg==", + "version": "8.5.6", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.6.tgz", + "integrity": "sha512-8B5EO9jLVCy+B58PLHvLDuOD8DRVMgQzq8d55SjLCOn9kqGyqOvy27exVaTio1q1nX5zLu8/6N0n2ThSxOM6tg==", "dev": true, "dependencies": { "@types/node": "*" @@ -372,6 +373,11 @@ "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==" }, + "node_modules/boolbase": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", + "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==" + }, "node_modules/combined-stream": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", @@ -395,9 +401,9 @@ } }, "node_modules/cookie": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.2.tgz", - "integrity": "sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==", + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", + "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==", "engines": { "node": ">= 0.6" } @@ -407,6 +413,32 @@ "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==" }, + "node_modules/css-select": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.1.0.tgz", + "integrity": "sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg==", + "dependencies": { + "boolbase": "^1.0.0", + "css-what": "^6.1.0", + "domhandler": "^5.0.2", + "domutils": "^3.0.1", + "nth-check": "^2.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/css-what": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.1.0.tgz", + "integrity": "sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==", + "engines": { + "node": ">= 6" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, "node_modules/csv-writer": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/csv-writer/-/csv-writer-1.6.0.tgz", @@ -459,6 +491,68 @@ "node": ">=0.3.1" } }, + "node_modules/dom-serializer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", + "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.2", + "entities": "^4.2.0" + }, + "funding": { + "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" + } + }, + "node_modules/domelementtype": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", + "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ] + }, + "node_modules/domhandler": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", + "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", + "dependencies": { + "domelementtype": "^2.3.0" + }, + "engines": { + "node": ">= 4" + }, + "funding": { + "url": "https://github.com/fb55/domhandler?sponsor=1" + } + }, + "node_modules/domutils": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.1.0.tgz", + "integrity": "sha512-H78uMmQtI2AhgDJjWeQmHwJJ2bLPD3GMmO7Zja/ZZh84wkm+4ut+IUnUdRa8uCGX88DiVx1j6FRe1XfxEgjEZA==", + "dependencies": { + "dom-serializer": "^2.0.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3" + }, + "funding": { + "url": "https://github.com/fb55/domutils?sponsor=1" + } + }, + "node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, "node_modules/form-data": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", @@ -472,6 +566,14 @@ "node": ">= 6" } }, + "node_modules/he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "bin": { + "he": "bin/he" + } + }, "node_modules/https-proxy-agent": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", @@ -588,6 +690,26 @@ } } }, + "node_modules/node-html-parser": { + "version": "6.1.10", + "resolved": "https://registry.npmjs.org/node-html-parser/-/node-html-parser-6.1.10.tgz", + "integrity": "sha512-6/uWdWxjQWQ7tMcFK2wWlrflsQUzh1HsEzlIf2j5+TtzfhT2yUvg3DwZYAmjEHeR3uX74ko7exjHW69J0tOzIg==", + "dependencies": { + "css-select": "^5.1.0", + "he": "1.2.0" + } + }, + "node_modules/nth-check": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", + "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", + "dependencies": { + "boolbase": "^1.0.0" + }, + "funding": { + "url": "https://github.com/fb55/nth-check?sponsor=1" + } + }, "node_modules/pako": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/pako/-/pako-2.1.0.tgz", @@ -746,9 +868,9 @@ } }, "node_modules/ws": { - "version": "8.14.1", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.14.1.tgz", - "integrity": "sha512-4OOseMUq8AzRBI/7SLMUwO+FEDnguetSk7KMb1sHwvF2w2Wv5Hoj0nlifx8vtGsftE/jWHojPy8sMMzYLJ2G/A==", + "version": "8.14.2", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.14.2.tgz", + "integrity": "sha512-wEBG1ftX4jcglPxgFCMJmZ2PLtSbJ2Peg6TmpJFTbe9GZYOQCDPdMYu/Tm0/bGZkw8paZnJY45J4K2PZrLYq8g==", "engines": { "node": ">=10.0.0" }, diff --git a/price-server/package.json b/price-server/package.json index 2858f3c..c7c5200 100644 --- a/price-server/package.json +++ b/price-server/package.json @@ -1,6 +1,6 @@ { "name": "@classic-terra/price-server", - "version": "3.1.1", + "version": "3.1.2", "main": "src/main.ts", "license": "Apache-2.0", "scripts": { @@ -20,6 +20,7 @@ "form-data": "^4.0.0", "lodash": "^4.17.20", "node-fetch": "^2.6.7", + "node-html-parser": "^6.1.10", "pako": "^2.0.3", "polka": "^0.5.2", "ts-node": "^10.7.0", diff --git a/price-server/src/provider/fiat/FiatProvider.ts b/price-server/src/provider/fiat/FiatProvider.ts index 43e76ae..084878c 100644 --- a/price-server/src/provider/fiat/FiatProvider.ts +++ b/price-server/src/provider/fiat/FiatProvider.ts @@ -1,6 +1,6 @@ import * as config from 'config' import { Provider, ProviderOptions } from 'provider/base' -import { CurrencyLayer, AlphaVantage, Fixer, ExchangeRate, Fer, Frankfurter, Fastforex } from './quoter' +import { CurrencyLayer, AlphaVantage, Fixer, ExchangeRate, Fer, Frankfurter, Fastforex, IMF } from './quoter' class FiatProvider extends Provider { constructor(options: ProviderOptions) { @@ -24,6 +24,7 @@ class FiatProvider extends Provider { name === 'frankfurter' && this.quoters.push(new Frankfurter(option)) name === 'exchangerate' && this.quoters.push(new ExchangeRate(option)) name === 'fastforex' && this.quoters.push(new Fastforex(option)) + name === 'imf' && this.quoters.push(new IMF(option)) } await super.initialize() diff --git a/price-server/src/provider/fiat/quoter/ExchangeRate.ts b/price-server/src/provider/fiat/quoter/ExchangeRate.ts index e0fd712..7a9fb0f 100644 --- a/price-server/src/provider/fiat/quoter/ExchangeRate.ts +++ b/price-server/src/provider/fiat/quoter/ExchangeRate.ts @@ -29,6 +29,7 @@ export class ExchangeRate extends Quoter { const params = { base: 'USD', symbols: this.symbols.map((symbol) => (symbol === 'SDR/USD' ? 'XDR' : symbol.replace('/USD', ''))).join(','), + access_key: this.options.apiKey, } const response: Response = await nodeFetch(`https://api.exchangerate.host/latest?${toQueryString(params)}`, { diff --git a/price-server/src/provider/fiat/quoter/Fer.ts b/price-server/src/provider/fiat/quoter/Fer.ts index c04d660..2111165 100644 --- a/price-server/src/provider/fiat/quoter/Fer.ts +++ b/price-server/src/provider/fiat/quoter/Fer.ts @@ -1,39 +1,48 @@ +import { groupBy } from 'lodash' import fetch from 'lib/fetch' import { errorHandler } from 'lib/error' -import { toQueryString } from 'lib/fetch' import * as logger from 'lib/logger' import { num } from 'lib/num' import { Quoter } from 'provider/base' -import { getBaseCurrency, getQuoteCurrency } from 'lib/currency' +import { getQuoteCurrency } from 'lib/currency' interface Response { date: string base: string - rates: object + rates: { + [currency: string]: number + } } export class Fer extends Quoter { - private async updateLastPrice(symbol: string): Promise { - const base = getBaseCurrency(symbol) - const to = getQuoteCurrency(symbol) - - const params = { base, to } - const response: Response = await fetch(`https://api.fer.ee/latest?${toQueryString(params)}`, { - timeout: this.options.timeout, - }).then((res) => res.json()) - - if (!response) { - logger.error(`${this.constructor.name}: wrong api response`, response ? JSON.stringify(response) : 'empty') - throw new Error('Invalid response from Fer') - } - - this.setPrice(symbol, num(response.rates[to])) + private async updatePrices(): Promise { + const quoteGroup = groupBy(this.symbols, getQuoteCurrency) + + await Promise.all( + Object.keys(quoteGroup).map(async (quote) => { + const response: Response = await fetch(`https://api.fer.ee/latest?from=${quote}`, { + timeout: this.options.timeout, + }).then((res) => res.json()) + + if (!response) { + logger.error(`${this.constructor.name}: wrong api response`, response ? JSON.stringify(response) : 'empty') + throw new Error('Invalid response from Frankfurter') + } + + Object.keys(response.rates).forEach((base) => { + const symbol = `${base}/${quote}` + + if (this.symbols.indexOf(symbol) != -1) { + const rate = num(1).dividedBy(num(response.rates[base])) + this.setPrice(symbol, rate) + } + }) + }) + ) } protected async update(): Promise { - for (const symbol of this.symbols) { - await this.updateLastPrice(symbol).catch(errorHandler) - } + await this.updatePrices().catch(errorHandler) return true } diff --git a/price-server/src/provider/fiat/quoter/Frankfurter.ts b/price-server/src/provider/fiat/quoter/Frankfurter.ts index 20309f5..5d0abf7 100644 --- a/price-server/src/provider/fiat/quoter/Frankfurter.ts +++ b/price-server/src/provider/fiat/quoter/Frankfurter.ts @@ -1,39 +1,48 @@ +import { groupBy } from 'lodash' import fetch from 'lib/fetch' import { errorHandler } from 'lib/error' -import { toQueryString } from 'lib/fetch' import * as logger from 'lib/logger' import { num } from 'lib/num' import { Quoter } from 'provider/base' -import { getBaseCurrency, getQuoteCurrency } from 'lib/currency' +import { getQuoteCurrency } from 'lib/currency' interface Response { date: string base: string - rates: object + rates: { + [currency: string]: number + } } export class Frankfurter extends Quoter { - private async updateLastPrice(symbol: string): Promise { - const from = getBaseCurrency(symbol) - const to = getQuoteCurrency(symbol) - - const params = { from, to } - const response: Response = await fetch(`https://api.frankfurter.APP/latest?${toQueryString(params)}`, { - timeout: this.options.timeout, - }).then((res) => res.json()) - - if (!response) { - logger.error(`${this.constructor.name}: wrong api response`, response ? JSON.stringify(response) : 'empty') - throw new Error('Invalid response from Frankfurter') - } - - this.setPrice(symbol, num(response.rates[to])) + private async updatePrices(): Promise { + const quoteGroup = groupBy(this.symbols, getQuoteCurrency) + + await Promise.all( + Object.keys(quoteGroup).map(async (quote) => { + const response: Response = await fetch(`https://api.frankfurter.app/latest?from=${quote}`, { + timeout: this.options.timeout, + }).then((res) => res.json()) + + if (!response) { + logger.error(`${this.constructor.name}: wrong api response`, response ? JSON.stringify(response) : 'empty') + throw new Error('Invalid response from Frankfurter') + } + + Object.keys(response.rates).forEach((base) => { + const symbol = `${base}/${quote}` + + if (this.symbols.indexOf(symbol) != -1) { + const rate = num(1).dividedBy(num(response.rates[base])) + this.setPrice(symbol, rate) + } + }) + }) + ) } protected async update(): Promise { - for (const symbol of this.symbols) { - await this.updateLastPrice(symbol).catch(errorHandler) - } + await this.updatePrices().catch(errorHandler) return true } diff --git a/price-server/src/provider/fiat/quoter/IMF.ts b/price-server/src/provider/fiat/quoter/IMF.ts new file mode 100644 index 0000000..9b49d0f --- /dev/null +++ b/price-server/src/provider/fiat/quoter/IMF.ts @@ -0,0 +1,38 @@ +import { parse } from 'node-html-parser' +import fetch from 'lib/fetch' +import { errorHandler } from 'lib/error' +import { num } from 'lib/num' +import { Quoter } from 'provider/base' + +const SDR_VALUATION_URL = 'https://www.imf.org/external/np/fin/data/rms_sdrv.aspx' + +async function fetchQuote() { + const text = await fetch(SDR_VALUATION_URL).then((res) => res.text()) + + const doc = parse(text) + const tds = doc.querySelectorAll('.tightest td') + const idx = tds.findIndex((el) => el.structuredText === ' SDR1 = US$') + + if (idx === -1) { + throw new Error('cannot find SDR/USD element from HTML document') + } + + // sample format: ' 1.32149 2' + return num(tds[idx + 1].structuredText.split(' ')[1]) +} + +// fetchQuote().then(console.log).catch(console.error) // For test + +export class IMF extends Quoter { + protected async update(): Promise { + if (this.symbols.indexOf('SDR/USD') !== -1) { + await fetchQuote() + .then((rate) => this.setPrice('SDR/USD', rate)) + .catch(errorHandler) + } + + return true + } +} + +export default IMF diff --git a/price-server/src/provider/fiat/quoter/index.ts b/price-server/src/provider/fiat/quoter/index.ts index 9bb850b..e78fb7e 100644 --- a/price-server/src/provider/fiat/quoter/index.ts +++ b/price-server/src/provider/fiat/quoter/index.ts @@ -5,3 +5,4 @@ export * from './Fer' export * from './Frankfurter' export * from './Fastforex' export * from './ExchangeRate' +export * from './IMF'