diff --git a/package-lock.json b/package-lock.json index d0141789..c1070515 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,23 +14,23 @@ } }, "@babel/compat-data": { - "version": "7.13.8", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.13.8.tgz", - "integrity": "sha512-EaI33z19T4qN3xLXsGf48M2cDqa6ei9tPZlfLdb2HC+e/cFtREiRd8hdSqDbwdLB0/+gLwqJmCYASH0z2bUdog==", + "version": "7.13.11", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.13.11.tgz", + "integrity": "sha512-BwKEkO+2a67DcFeS3RLl0Z3Gs2OvdXewuWjc1Hfokhb5eQWP9YRYH1/+VrVZvql2CfjOiNGqSAFOYt4lsqTHzg==", "dev": true }, "@babel/core": { - "version": "7.13.8", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.13.8.tgz", - "integrity": "sha512-oYapIySGw1zGhEFRd6lzWNLWFX2s5dA/jm+Pw/+59ZdXtjyIuwlXbrId22Md0rgZVop+aVoqow2riXhBLNyuQg==", + "version": "7.13.10", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.13.10.tgz", + "integrity": "sha512-bfIYcT0BdKeAZrovpMqX2Mx5NrgAckGbwT982AkdS5GNfn3KMGiprlBAtmBcFZRUmpaufS6WZFP8trvx8ptFDw==", "dev": true, "requires": { "@babel/code-frame": "^7.12.13", - "@babel/generator": "^7.13.0", - "@babel/helper-compilation-targets": "^7.13.8", + "@babel/generator": "^7.13.9", + "@babel/helper-compilation-targets": "^7.13.10", "@babel/helper-module-transforms": "^7.13.0", - "@babel/helpers": "^7.13.0", - "@babel/parser": "^7.13.4", + "@babel/helpers": "^7.13.10", + "@babel/parser": "^7.13.10", "@babel/template": "^7.12.13", "@babel/traverse": "^7.13.0", "@babel/types": "^7.13.0", @@ -72,9 +72,9 @@ } }, "@babel/helper-compilation-targets": { - "version": "7.13.8", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.13.8.tgz", - "integrity": "sha512-pBljUGC1y3xKLn1nrx2eAhurLMA8OqBtBP/JwG4U8skN7kf8/aqwwxpV1N6T0e7r6+7uNitIa/fUxPFagSXp3A==", + "version": "7.13.10", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.13.10.tgz", + "integrity": "sha512-/Xju7Qg1GQO4mHZ/Kcs6Au7gfafgZnwm+a7sy/ow/tV1sHeraRUHbjdat8/UvDor4Tez+siGKDk6zIKtCPKVJA==", "dev": true, "requires": { "@babel/compat-data": "^7.13.8", @@ -198,9 +198,9 @@ "dev": true }, "@babel/helpers": { - "version": "7.13.0", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.13.0.tgz", - "integrity": "sha512-aan1MeFPxFacZeSz6Ld7YZo5aPuqnKlD7+HZY75xQsueczFccP9A7V05+oe0XpLwHK3oLorPe9eaAUljL7WEaQ==", + "version": "7.13.10", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.13.10.tgz", + "integrity": "sha512-4VO883+MWPDUVRF3PhiLBUFHoX/bsLTGFpFK/HqvvfBZz2D57u9XzPVNFVBTc0PW/CWR9BXTOKt8NF4DInUHcQ==", "dev": true, "requires": { "@babel/template": "^7.12.13", @@ -209,9 +209,9 @@ } }, "@babel/highlight": { - "version": "7.13.8", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.13.8.tgz", - "integrity": "sha512-4vrIhfJyfNf+lCtXC2ck1rKSzDwciqF7IWFhXXrSOUC2O5DrVp+w4c6ed4AllTxhTkUP5x2tYj41VaxdVMMRDw==", + "version": "7.13.10", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.13.10.tgz", + "integrity": "sha512-5aPpe5XQPzflQrFwL1/QoeHkP2MsA4JCntcXHRhEsdsfPVkvPi2w7Qix4iV7t5S/oC9OodGrggd8aco1g3SZFg==", "dev": true, "requires": { "@babel/helper-validator-identifier": "^7.12.11", @@ -220,9 +220,9 @@ } }, "@babel/parser": { - "version": "7.13.9", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.13.9.tgz", - "integrity": "sha512-nEUfRiARCcaVo3ny3ZQjURjHQZUo/JkEw7rLlSZy/psWGnvwXFtPcr6jb7Yb41DVW5LTe6KRq9LGleRNsg1Frw==", + "version": "7.13.11", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.13.11.tgz", + "integrity": "sha512-PhuoqeHoO9fc4ffMEVk4qb/w/s2iOSWohvbHxLtxui0eBg3Lg5gN1U8wp1V1u61hOWkPQJJyJzGH6Y+grwkq8Q==", "dev": true }, "@babel/template": { @@ -309,6 +309,15 @@ "strip-json-comments": "^3.1.1" }, "dependencies": { + "globals": { + "version": "12.4.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-12.4.0.tgz", + "integrity": "sha512-BWICuzzDvDoH54NHKCseDanAhE3CeDorgDL5MT6LMXXj2WCnd9UC2szdk4AWLfjdgNBCXLUanXYcpBBKOSWGwg==", + "dev": true, + "requires": { + "type-fest": "^0.8.1" + } + }, "ignore": { "version": "4.0.6", "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", @@ -752,9 +761,9 @@ }, "dependencies": { "@types/node": { - "version": "14.14.31", - "resolved": "https://registry.npmjs.org/@types/node/-/node-14.14.31.tgz", - "integrity": "sha512-vFHy/ezP5qI0rFgJ7aQnjDXwAMrG0KqqIH7tQG5PPv3BWBayOPIQNBjVc/P6hhdZfMx51REc6tfDNXHUio893g==" + "version": "14.14.34", + "resolved": "https://registry.npmjs.org/@types/node/-/node-14.14.34.tgz", + "integrity": "sha512-dBPaxocOK6UVyvhbnpFIj2W+S+1cBTkHQbFQfeeJhoKFbzYcVUGHvddeWPSucKATb3F0+pgDq0i6ghEaZjsugA==" } } }, @@ -767,9 +776,9 @@ }, "dependencies": { "@types/node": { - "version": "14.14.31", - "resolved": "https://registry.npmjs.org/@types/node/-/node-14.14.31.tgz", - "integrity": "sha512-vFHy/ezP5qI0rFgJ7aQnjDXwAMrG0KqqIH7tQG5PPv3BWBayOPIQNBjVc/P6hhdZfMx51REc6tfDNXHUio893g==" + "version": "14.14.34", + "resolved": "https://registry.npmjs.org/@types/node/-/node-14.14.34.tgz", + "integrity": "sha512-dBPaxocOK6UVyvhbnpFIj2W+S+1cBTkHQbFQfeeJhoKFbzYcVUGHvddeWPSucKATb3F0+pgDq0i6ghEaZjsugA==" } } }, @@ -811,9 +820,9 @@ }, "dependencies": { "@types/node": { - "version": "14.14.31", - "resolved": "https://registry.npmjs.org/@types/node/-/node-14.14.31.tgz", - "integrity": "sha512-vFHy/ezP5qI0rFgJ7aQnjDXwAMrG0KqqIH7tQG5PPv3BWBayOPIQNBjVc/P6hhdZfMx51REc6tfDNXHUio893g==" + "version": "14.14.34", + "resolved": "https://registry.npmjs.org/@types/node/-/node-14.14.34.tgz", + "integrity": "sha512-dBPaxocOK6UVyvhbnpFIj2W+S+1cBTkHQbFQfeeJhoKFbzYcVUGHvddeWPSucKATb3F0+pgDq0i6ghEaZjsugA==" } } }, @@ -836,9 +845,9 @@ }, "dependencies": { "@types/node": { - "version": "14.14.31", - "resolved": "https://registry.npmjs.org/@types/node/-/node-14.14.31.tgz", - "integrity": "sha512-vFHy/ezP5qI0rFgJ7aQnjDXwAMrG0KqqIH7tQG5PPv3BWBayOPIQNBjVc/P6hhdZfMx51REc6tfDNXHUio893g==" + "version": "14.14.34", + "resolved": "https://registry.npmjs.org/@types/node/-/node-14.14.34.tgz", + "integrity": "sha512-dBPaxocOK6UVyvhbnpFIj2W+S+1cBTkHQbFQfeeJhoKFbzYcVUGHvddeWPSucKATb3F0+pgDq0i6ghEaZjsugA==" } } }, @@ -869,9 +878,9 @@ "dev": true }, "@types/node": { - "version": "10.17.54", - "resolved": "https://registry.npmjs.org/@types/node/-/node-10.17.54.tgz", - "integrity": "sha512-c8Lm7+hXdSPmWH4B9z/P/xIXhFK3mCQin4yCYMd2p1qpMG5AfgyJuYZ+3q2dT7qLiMMMGMd5dnkFpdqJARlvtQ==", + "version": "10.17.55", + "resolved": "https://registry.npmjs.org/@types/node/-/node-10.17.55.tgz", + "integrity": "sha512-koZJ89uLZufDvToeWO5BrC4CR4OUfHnUz2qoPs/daQH6qq3IN62QFxCTZ+bKaCE0xaoCAJYE4AXre8AbghCrhg==", "dev": true }, "@types/on-finished": { @@ -883,16 +892,16 @@ }, "dependencies": { "@types/node": { - "version": "14.14.31", - "resolved": "https://registry.npmjs.org/@types/node/-/node-14.14.31.tgz", - "integrity": "sha512-vFHy/ezP5qI0rFgJ7aQnjDXwAMrG0KqqIH7tQG5PPv3BWBayOPIQNBjVc/P6hhdZfMx51REc6tfDNXHUio893g==" + "version": "14.14.34", + "resolved": "https://registry.npmjs.org/@types/node/-/node-14.14.34.tgz", + "integrity": "sha512-dBPaxocOK6UVyvhbnpFIj2W+S+1cBTkHQbFQfeeJhoKFbzYcVUGHvddeWPSucKATb3F0+pgDq0i6ghEaZjsugA==" } } }, "@types/qs": { - "version": "6.9.5", - "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.5.tgz", - "integrity": "sha512-/JHkVHtx/REVG0VVToGRGH2+23hsYLHdyG+GrvoUGlGAd0ErauXDyvHtRI/7H7mzLm+tBCKA7pfcpkQ1lf58iQ==" + "version": "6.9.6", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.6.tgz", + "integrity": "sha512-0/HnwIfW4ki2D8L8c9GVcG5I72s9jP5GSLVF0VIXDW00kmIpA6O33G7a8n59Tmh7Nz0WUC3rSb7PTY/sdW2JzA==" }, "@types/range-parser": { "version": "1.2.3", @@ -909,9 +918,9 @@ }, "dependencies": { "@types/node": { - "version": "14.14.31", - "resolved": "https://registry.npmjs.org/@types/node/-/node-14.14.31.tgz", - "integrity": "sha512-vFHy/ezP5qI0rFgJ7aQnjDXwAMrG0KqqIH7tQG5PPv3BWBayOPIQNBjVc/P6hhdZfMx51REc6tfDNXHUio893g==" + "version": "14.14.34", + "resolved": "https://registry.npmjs.org/@types/node/-/node-14.14.34.tgz", + "integrity": "sha512-dBPaxocOK6UVyvhbnpFIj2W+S+1cBTkHQbFQfeeJhoKFbzYcVUGHvddeWPSucKATb3F0+pgDq0i6ghEaZjsugA==" } } }, @@ -925,9 +934,9 @@ } }, "@types/sinon": { - "version": "9.0.10", - "resolved": "https://registry.npmjs.org/@types/sinon/-/sinon-9.0.10.tgz", - "integrity": "sha512-/faDC0erR06wMdybwI/uR8wEKV/E83T0k4sepIpB7gXuy2gzx2xiOjmztq6a2Y6rIGJ04D+6UU0VBmWy+4HEMA==", + "version": "9.0.11", + "resolved": "https://registry.npmjs.org/@types/sinon/-/sinon-9.0.11.tgz", + "integrity": "sha512-PwP4UY33SeeVKodNE37ZlOsR9cReypbMJOhZ7BVE0lB+Hix3efCOxiJWiE5Ia+yL9Cn2Ch72EjFTRze8RZsNtg==", "dev": true, "requires": { "@types/sinonjs__fake-timers": "*" @@ -967,20 +976,20 @@ }, "dependencies": { "@types/node": { - "version": "14.14.31", - "resolved": "https://registry.npmjs.org/@types/node/-/node-14.14.31.tgz", - "integrity": "sha512-vFHy/ezP5qI0rFgJ7aQnjDXwAMrG0KqqIH7tQG5PPv3BWBayOPIQNBjVc/P6hhdZfMx51REc6tfDNXHUio893g==" + "version": "14.14.34", + "resolved": "https://registry.npmjs.org/@types/node/-/node-14.14.34.tgz", + "integrity": "sha512-dBPaxocOK6UVyvhbnpFIj2W+S+1cBTkHQbFQfeeJhoKFbzYcVUGHvddeWPSucKATb3F0+pgDq0i6ghEaZjsugA==" } } }, "@typescript-eslint/eslint-plugin": { - "version": "4.16.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.16.1.tgz", - "integrity": "sha512-SK777klBdlkUZpZLC1mPvyOWk9yAFCWmug13eAjVQ4/Q1LATE/NbcQL1xDHkptQkZOLnPmLUA1Y54m8dqYwnoQ==", + "version": "4.17.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.17.0.tgz", + "integrity": "sha512-/fKFDcoHg8oNan39IKFOb5WmV7oWhQe1K6CDaAVfJaNWEhmfqlA24g+u1lqU5bMH7zuNasfMId4LaYWC5ijRLw==", "dev": true, "requires": { - "@typescript-eslint/experimental-utils": "4.16.1", - "@typescript-eslint/scope-manager": "4.16.1", + "@typescript-eslint/experimental-utils": "4.17.0", + "@typescript-eslint/scope-manager": "4.17.0", "debug": "^4.1.1", "functional-red-black-tree": "^1.0.1", "lodash": "^4.17.15", @@ -1001,55 +1010,55 @@ } }, "@typescript-eslint/experimental-utils": { - "version": "4.16.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-4.16.1.tgz", - "integrity": "sha512-0Hm3LSlMYFK17jO4iY3un1Ve9x1zLNn4EM50Lia+0EV99NdbK+cn0er7HC7IvBA23mBg3P+8dUkMXy4leL33UQ==", + "version": "4.17.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-4.17.0.tgz", + "integrity": "sha512-ZR2NIUbnIBj+LGqCFGQ9yk2EBQrpVVFOh9/Kd0Lm6gLpSAcCuLLe5lUCibKGCqyH9HPwYC0GIJce2O1i8VYmWA==", "dev": true, "requires": { "@types/json-schema": "^7.0.3", - "@typescript-eslint/scope-manager": "4.16.1", - "@typescript-eslint/types": "4.16.1", - "@typescript-eslint/typescript-estree": "4.16.1", + "@typescript-eslint/scope-manager": "4.17.0", + "@typescript-eslint/types": "4.17.0", + "@typescript-eslint/typescript-estree": "4.17.0", "eslint-scope": "^5.0.0", "eslint-utils": "^2.0.0" } }, "@typescript-eslint/parser": { - "version": "4.16.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-4.16.1.tgz", - "integrity": "sha512-/c0LEZcDL5y8RyI1zLcmZMvJrsR6SM1uetskFkoh3dvqDKVXPsXI+wFB/CbVw7WkEyyTKobC1mUNp/5y6gRvXg==", + "version": "4.17.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-4.17.0.tgz", + "integrity": "sha512-KYdksiZQ0N1t+6qpnl6JeK9ycCFprS9xBAiIrw4gSphqONt8wydBw4BXJi3C11ywZmyHulvMaLjWsxDjUSDwAw==", "dev": true, "requires": { - "@typescript-eslint/scope-manager": "4.16.1", - "@typescript-eslint/types": "4.16.1", - "@typescript-eslint/typescript-estree": "4.16.1", + "@typescript-eslint/scope-manager": "4.17.0", + "@typescript-eslint/types": "4.17.0", + "@typescript-eslint/typescript-estree": "4.17.0", "debug": "^4.1.1" } }, "@typescript-eslint/scope-manager": { - "version": "4.16.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-4.16.1.tgz", - "integrity": "sha512-6IlZv9JaurqV0jkEg923cV49aAn8V6+1H1DRfhRcvZUrptQ+UtSKHb5kwTayzOYTJJ/RsYZdcvhOEKiBLyc0Cw==", + "version": "4.17.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-4.17.0.tgz", + "integrity": "sha512-OJ+CeTliuW+UZ9qgULrnGpPQ1bhrZNFpfT/Bc0pzNeyZwMik7/ykJ0JHnQ7krHanFN9wcnPK89pwn84cRUmYjw==", "dev": true, "requires": { - "@typescript-eslint/types": "4.16.1", - "@typescript-eslint/visitor-keys": "4.16.1" + "@typescript-eslint/types": "4.17.0", + "@typescript-eslint/visitor-keys": "4.17.0" } }, "@typescript-eslint/types": { - "version": "4.16.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-4.16.1.tgz", - "integrity": "sha512-nnKqBwMgRlhzmJQF8tnFDZWfunXmJyuXj55xc8Kbfup4PbkzdoDXZvzN8//EiKR27J6vUSU8j4t37yUuYPiLqA==", + "version": "4.17.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-4.17.0.tgz", + "integrity": "sha512-RN5z8qYpJ+kXwnLlyzZkiJwfW2AY458Bf8WqllkondQIcN2ZxQowAToGSd9BlAUZDB5Ea8I6mqL2quGYCLT+2g==", "dev": true }, "@typescript-eslint/typescript-estree": { - "version": "4.16.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-4.16.1.tgz", - "integrity": "sha512-m8I/DKHa8YbeHt31T+UGd/l8Kwr0XCTCZL3H4HMvvLCT7HU9V7yYdinTOv1gf/zfqNeDcCgaFH2BMsS8x6NvJg==", + "version": "4.17.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-4.17.0.tgz", + "integrity": "sha512-lRhSFIZKUEPPWpWfwuZBH9trYIEJSI0vYsrxbvVvNyIUDoKWaklOAelsSkeh3E2VBSZiNe9BZ4E5tYBZbUczVQ==", "dev": true, "requires": { - "@typescript-eslint/types": "4.16.1", - "@typescript-eslint/visitor-keys": "4.16.1", + "@typescript-eslint/types": "4.17.0", + "@typescript-eslint/visitor-keys": "4.17.0", "debug": "^4.1.1", "globby": "^11.0.1", "is-glob": "^4.0.1", @@ -1069,12 +1078,12 @@ } }, "@typescript-eslint/visitor-keys": { - "version": "4.16.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-4.16.1.tgz", - "integrity": "sha512-s/aIP1XcMkEqCNcPQtl60ogUYjSM8FU2mq1O7y5cFf3Xcob1z1iXWNB6cC43Op+NGRTFgGolri6s8z/efA9i1w==", + "version": "4.17.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-4.17.0.tgz", + "integrity": "sha512-WfuMN8mm5SSqXuAr9NM+fItJ0SVVphobWYkWOwQ1odsfC014Vdxk/92t4JwS1Q6fCA/ABfCKpa3AVtpUKTNKGQ==", "dev": true, "requires": { - "@typescript-eslint/types": "4.16.1", + "@typescript-eslint/types": "4.17.0", "eslint-visitor-keys": "^2.0.0" } }, @@ -1466,9 +1475,9 @@ "dev": true }, "caniuse-lite": { - "version": "1.0.30001196", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001196.tgz", - "integrity": "sha512-CPvObjD3ovWrNBaXlAIGWmg2gQQuJ5YhuciUOjPRox6hIQttu8O+b51dx6VIpIY9ESd2d0Vac1RKpICdG4rGUg==", + "version": "1.0.30001200", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001200.tgz", + "integrity": "sha512-ic/jXfa6tgiPBAISWk16jRI2q8YfjxHnSG7ddSL1ptrIP8Uy11SayFrjXRAk3NumHpDb21fdTkbTxb/hOrFrnQ==", "dev": true }, "capital-case": { @@ -1826,6 +1835,11 @@ "tslib": "^2.0.3" } }, + "dotenv": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-8.2.0.tgz", + "integrity": "sha512-8sJ78ElpbDJBHNeBzUbUVLsqKdccaa/BXF1uPTw3GrvQTBgrQrtObr2mUrE38vzYd8cEv+m/JBfDLioYcfXoaw==" + }, "double-ended-queue": { "version": "2.1.0-0", "resolved": "https://registry.npmjs.org/double-ended-queue/-/double-ended-queue-2.1.0-0.tgz", @@ -1854,9 +1868,9 @@ } }, "electron-to-chromium": { - "version": "1.3.680", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.680.tgz", - "integrity": "sha512-XBACJT9RdpdWtoMXQPR8Be3ZtmizWWbxfw8cY2b5feUwiDO3FUl8qo4W2jXoq/WnnA3xBRqafu1XbpczqyUvlA==", + "version": "1.3.687", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.687.tgz", + "integrity": "sha512-IpzksdQNl3wdgkzf7dnA7/v10w0Utf1dF2L+B4+gKrloBrxCut+au+kky3PYvle3RMdSxZP+UiCZtLbcYRxSNQ==", "dev": true }, "emoji-regex": { @@ -1965,9 +1979,9 @@ "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=" }, "eslint": { - "version": "7.21.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-7.21.0.tgz", - "integrity": "sha512-W2aJbXpMNofUp0ztQaF40fveSsJBjlSCSWpy//gzfTvwC+USs/nceBrKmlJOiM8r1bLwP2EuYkCqArn/6QTIgg==", + "version": "7.22.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-7.22.0.tgz", + "integrity": "sha512-3VawOtjSJUQiiqac8MQc+w457iGLfuNGLFn8JmF051tTKbh5/x/0vlcEj8OgDCaw7Ysa2Jn8paGshV7x2abKXg==", "dev": true, "requires": { "@babel/code-frame": "7.12.11", @@ -1987,7 +2001,7 @@ "file-entry-cache": "^6.0.1", "functional-red-black-tree": "^1.0.1", "glob-parent": "^5.0.0", - "globals": "^12.1.0", + "globals": "^13.6.0", "ignore": "^4.0.6", "import-fresh": "^3.0.0", "imurmurhash": "^0.1.4", @@ -1995,7 +2009,7 @@ "js-yaml": "^3.13.1", "json-stable-stringify-without-jsonify": "^1.0.1", "levn": "^0.4.1", - "lodash": "^4.17.20", + "lodash": "^4.17.21", "minimatch": "^3.0.4", "natural-compare": "^1.4.0", "optionator": "^0.9.1", @@ -2088,9 +2102,9 @@ "dev": true }, "eslint-plugin-mocha": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-mocha/-/eslint-plugin-mocha-8.0.0.tgz", - "integrity": "sha512-n67etbWDz6NQM+HnTwZHyBwz/bLlYPOxUbw7bPuCyFujv7ZpaT/Vn6KTAbT02gf7nRljtYIjWcTxK/n8a57rQQ==", + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-mocha/-/eslint-plugin-mocha-8.1.0.tgz", + "integrity": "sha512-1EgHvXKRl7W3mq3sntZAi5T24agRMyiTPL4bSXe+B4GksYOjAPEWYx+J3eJg4It1l2NMNZJtk0gQyQ6mfiPhQg==", "dev": true, "requires": { "eslint-utils": "^2.1.0", @@ -2590,9 +2604,9 @@ } }, "glob-parent": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.1.tgz", - "integrity": "sha512-FnI+VGOpnlGHWZxthPGR+QhR78fuiK0sNLkHQv+bL9fQi57lNNdquIbna/WrfROrolq8GK5Ek6BiMwqL/voRYQ==", + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", "dev": true, "requires": { "is-glob": "^4.0.1" @@ -2607,12 +2621,20 @@ } }, "globals": { - "version": "12.4.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-12.4.0.tgz", - "integrity": "sha512-BWICuzzDvDoH54NHKCseDanAhE3CeDorgDL5MT6LMXXj2WCnd9UC2szdk4AWLfjdgNBCXLUanXYcpBBKOSWGwg==", + "version": "13.6.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.6.0.tgz", + "integrity": "sha512-YFKCX0SiPg7l5oKYCJ2zZGxcXprVXHcSnVuvzrT3oSENQonVLqM5pf9fN5dLGZGyCjhw8TN8Btwe/jKnZ0pjvQ==", "dev": true, "requires": { - "type-fest": "^0.8.1" + "type-fest": "^0.20.2" + }, + "dependencies": { + "type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true + } } }, "globby": { @@ -2844,12 +2866,12 @@ "integrity": "sha512-CYdFeFexxhv/Bcny+Q0BfOV+ltRlJcd4BBZBYFX/O0u4npJrgZtIcjokegtiSMAvlMTJ+Koq0GBCc//3bueQxw==" }, "ioredis": { - "version": "4.23.0", - "resolved": "https://registry.npmjs.org/ioredis/-/ioredis-4.23.0.tgz", - "integrity": "sha512-R5TDCODwnEH3J3A5TSoB17+6a+SeJTtIOW6vsy5Q1yag/AM8FejHjZC5R2O1QepSXV8hwOnGSm/4buJc/LeXTQ==", + "version": "4.24.2", + "resolved": "https://registry.npmjs.org/ioredis/-/ioredis-4.24.2.tgz", + "integrity": "sha512-SSuVXwoG747sZetxxs9gyAno5kfUfvo4s5mSZp4dh8vzuTnrtA5mTf2OjL6sPfIfNbVTROg2c+VbXceGlpucPQ==", "requires": { "cluster-key-slot": "^1.1.0", - "debug": "^4.1.1", + "debug": "^4.3.1", "denque": "^1.1.0", "lodash.defaults": "^4.2.0", "lodash.flatten": "^4.4.0", @@ -2857,7 +2879,7 @@ "redis-commands": "1.7.0", "redis-errors": "^1.2.0", "redis-parser": "^3.0.0", - "standard-as-callback": "^2.0.1" + "standard-as-callback": "^2.1.0" } }, "ipaddr.js": { @@ -3977,9 +3999,9 @@ "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==" }, "mocha": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-8.3.0.tgz", - "integrity": "sha512-TQqyC89V1J/Vxx0DhJIXlq9gbbL9XFNdeLQ1+JsnZsVaSOV1z3tWfw0qZmQJGQRIfkvZcs7snQnZnOCKoldq1Q==", + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-8.3.2.tgz", + "integrity": "sha512-UdmISwr/5w+uXLPKspgoV7/RXZwKRTiTjJ2/AC5ZiEztIoOYdfKb19+9jNmEInzx5pBsCyJQzarAxqIGBNYJhg==", "dev": true, "requires": { "@ungap/promise-all-settled": "1.1.2", @@ -4078,9 +4100,9 @@ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" }, "msgpack5": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/msgpack5/-/msgpack5-4.5.0.tgz", - "integrity": "sha512-LYuyrhCncpw29VSAsoYyW8x+eubDtkkXCJQzHKren//b70/71GZcFpys+OpR8Qo5Seeeju6ULz5vIhTblFEMPg==", + "version": "4.5.1", + "resolved": "https://registry.npmjs.org/msgpack5/-/msgpack5-4.5.1.tgz", + "integrity": "sha512-zC1vkcliryc4JGlL6OfpHumSYUHWFGimSI+OgfRCjTFLmKA2/foR9rMTOhWiqfOrfxJOctrpWPvrppf8XynJxw==", "requires": { "bl": "^2.0.1", "inherits": "^2.0.3", @@ -5423,9 +5445,9 @@ "integrity": "sha512-ji9qxRnOVfcuLDySj9qzhGSEFVobyt1kIOSkj1qZzYLzq7Tos/oUUWvotUPQLlrsidqsK6tBH89Bc9kL5zHA6w==" }, "standard-as-callback": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/standard-as-callback/-/standard-as-callback-2.0.1.tgz", - "integrity": "sha512-NQOxSeB8gOI5WjSaxjBgog2QFw55FV8TkS6Y07BiB3VJ8xNTvUYm0wl0s8ObgQ5NhdpnNfigMIKjgPESzgr4tg==" + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/standard-as-callback/-/standard-as-callback-2.1.0.tgz", + "integrity": "sha512-qoRRSyROncaz1z0mvYqIE4lCd9p2R90i6GxW3uZv5ucSu8tU7B5HXUP1gG8pVZsYNVaXjk8ClXHPttLyxAL48A==" }, "statuses": { "version": "1.5.0", @@ -5636,9 +5658,9 @@ }, "dependencies": { "ajv": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-7.1.1.tgz", - "integrity": "sha512-ga/aqDYnUy/o7vbsRTFhhTsNeXiYb5JWDIcRIeZfwRNCefwjNTVYCGdGSUrEmiu3yDK3vFvNbgJxvrQW4JXrYQ==", + "version": "7.2.1", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-7.2.1.tgz", + "integrity": "sha512-+nu0HDv7kNSOua9apAVc979qd932rrZeb3WOvoiD31A/p1mIE5/9bN2027pE2rOPYEdS3UHzsvof4hY+lM9/WQ==", "dev": true, "requires": { "fast-deep-equal": "^3.1.1", @@ -5717,9 +5739,9 @@ "integrity": "sha512-hcVC3wYEziELGGmEEXue7D75zbwIIVUMWAVbHItGPx0ziyXxrOMQx4rQEVEV45Ut/1IotuEvwqPopzIOkDMf0A==" }, "tsutils": { - "version": "3.20.0", - "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.20.0.tgz", - "integrity": "sha512-RYbuQuvkhuqVeXweWT3tJLKOEJ/UUw9GjNEZGWdrLLlM+611o1gwLHBpxoFJKKl25fLprp2eVthtKs5JOrNeXg==", + "version": "3.21.0", + "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz", + "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==", "dev": true, "requires": { "tslib": "^1.8.1" @@ -5858,9 +5880,9 @@ "integrity": "sha512-OdmXxA8rDsQ7YpNVbKSJkNzTw2I+S5WsbMDnCtIWSQaosNAcWtFuI/YK1TjzUI6nbkgiqEyh8gWngfcv8Asd9A==" }, "v8-compile-cache": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.2.0.tgz", - "integrity": "sha512-gTpR5XQNKFwOd4clxfnhaqvfqMpqEwr4tOtCyz4MtYZX2JYhfr1JvBFKdS+7K/9rfpZR3VLX+YWBbKoxCgS43Q==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz", + "integrity": "sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==", "dev": true }, "validator": { @@ -6060,9 +6082,9 @@ "dev": true }, "yaml": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.0.tgz", - "integrity": "sha512-yr2icI4glYaNG+KWONODapy2/jDdMSDnrONSjblABjD9B4Z5LgiircSt8m8sRZFNi08kG9Sm0uSHtEmP3zaEGg==" + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", + "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==" }, "yamljs": { "version": "0.3.0", diff --git a/package.json b/package.json index 49f70c4c..6fdd47ff 100644 --- a/package.json +++ b/package.json @@ -58,6 +58,7 @@ "@loopback/rest": "^9.1.1", "@loopback/rest-explorer": "^3.0.5", "@loopback/service-proxy": "^3.0.5", + "dotenv": "^8.2.0", "loopback-connector-kv-redis": "^3.0.3", "loopback-connector-mongodb": "^5.5.0", "loopback-connector-redis": "^3.0.0", diff --git a/src/__tests__/acceptance/balance.controller.acceptance.ts b/src/__tests__/acceptance/balance.controller.acceptance.ts index f6cbefff..e41ac74c 100644 --- a/src/__tests__/acceptance/balance.controller.acceptance.ts +++ b/src/__tests__/acceptance/balance.controller.acceptance.ts @@ -54,10 +54,8 @@ describe('Balance Controller', () => { ], }) .expect(200); - expect(res.body).to.containEql({ - segwit: 0, - nativeSegwit: 49997000, - legacy: 3289478, - }); + expect(typeof res.body.segwit).to.eql('number'); + expect(typeof res.body.nativeSegwit).to.eql('number'); + expect(typeof res.body.legacy).to.eql('number'); }); }); diff --git a/src/__tests__/acceptance/broadcast.controller.acceptance.ts b/src/__tests__/acceptance/broadcast.controller.acceptance.ts new file mode 100644 index 00000000..84bfe299 --- /dev/null +++ b/src/__tests__/acceptance/broadcast.controller.acceptance.ts @@ -0,0 +1,25 @@ +import {Client} from '@loopback/testlab'; +import {TwpapiApplication} from '../..'; +import {setupApplication} from './test-helper'; + +describe('Broadcast Controller', () => { + let app: TwpapiApplication; + let client: Client; + + before('setupApplication', async () => { + ({app, client} = await setupApplication()); + }); + + after(async () => { + await app.stop(); + }); + + it('invokes POST /broadcast with value on hex', async () => { + await client + .post('/broadcast') + .send({ + data: 'value on hex', + }) + .expect(400); + }); +}); diff --git a/src/__tests__/acceptance/pegin-tx.acceptance.controller.ts b/src/__tests__/acceptance/pegin-tx.acceptance.controller.ts index 70cd5328..1b024729 100644 --- a/src/__tests__/acceptance/pegin-tx.acceptance.controller.ts +++ b/src/__tests__/acceptance/pegin-tx.acceptance.controller.ts @@ -131,4 +131,71 @@ describe('Pegin Tx Controller', () => { }; await client.post('/pegin-tx').send(peginTxData).expect(200); }); + it('invokes POST /pegin-tx with bech32 address', async () => { + const peginConf = await client.get('/pegin-configuration').expect(200); + const balance = await client + .post('/balance') + .send({ + sessionId: peginConf.body.sessionId, + addressList: [ + { + path: [2147483692, 2147483649, 2147483648, 0, 0], + serializedPath: "m/44'/1'/0'/0/0", + address: 'mzMCEHDUAZaKL9BXt9SzasFPUUqM77TqP1', + }, + { + path: [2147483692, 2147483649, 2147483648, 1, 0], + serializedPath: "m/44'/1'/0'/1/0", + address: 'mqCjBpQ75Y5sSGzFtJtSQQZqhJze9eaKjV', + }, + { + path: [2147483697, 2147483649, 2147483648, 0, 0], + serializedPath: "m/49'/1'/0'/0/0", + address: '2NC4DCae9HdL6vjWMDbQwTkYEAB22MF3TPs', + }, + { + path: [2147483697, 2147483649, 2147483648, 1, 0], + serializedPath: "m/49'/1'/0'/1/0", + address: '2NCZ2CNYiz4rrHq3miUHerUMcLyeWU4gw9C', + }, + { + path: [2147483732, 2147483649, 2147483648, 0, 0], + serializedPath: "m/84'/1'/0'/0/0", + address: 'tb1qtanvhhl8ve32tcdxkrsamyy6vq5p62ctdv89l0', + }, + { + path: [2147483732, 2147483649, 2147483648, 1, 0], + serializedPath: "m/84'/1'/0'/1/0", + address: 'tb1qfuk3j0l4qn4uzstc47uwk68kedmjwuucl7avqr', + }, + ], + }) + .expect(200); + await client + .post('/tx-fee') + .send({ + sessionId: peginConf.body.sessionId, + amount: 200, + accountType: constants.BITCOIN_LEGACY_ADDRESS, + }) + .expect(200); + const peginTxData = { + sessionId: peginConf.body.sessionId, + amountToTransferInSatoshi: balance.body.legacy, + refundAddress: 'tb1qkfcu7q7q6y7xmfe5glp9amsm45x0um59rwwmsmsmd355g32', + recipient: '0x90F8bf6A479f320ead074411a4B0e7944Ea8c9C1', + feeLevel: constants.BITCOIN_FAST_FEE_LEVEL, + changeAddress: '2NC4DCae9HdL6vjWMDbQwTkYEAB22MF3TPs', + }; + await client + .post('/pegin-tx') + .send(peginTxData) + .expect({ + error: { + statusCode: 500, + message: 'Internal Server Error', + }, + }); + // console.log(res.body); + }); }); diff --git a/src/__tests__/helper.ts b/src/__tests__/helper.ts index b3144b20..348cf547 100644 --- a/src/__tests__/helper.ts +++ b/src/__tests__/helper.ts @@ -1,4 +1,4 @@ -import {PeginConfiguration} from '../models'; +import {PeginConfiguration, TxInput, Utxo, WalletAddress} from '../models'; export function givenPeginConfiguration( peginConfiguration?: Partial, @@ -11,3 +11,119 @@ export function givenPeginConfiguration( ); return new PeginConfiguration(data); } + +export function getLegacyAddressList(): WalletAddress[] { + const addressList = [ + { + path: [2147483692, 2147483649, 2147483648, 0, 0], + serializedPath: "m/44'/1'/0'/0/0", + address: 'mzMCEHDUAZaKL9BXt9SzasFPUUqM77TqP1', + }, + { + path: [2147483692, 2147483649, 2147483648, 1, 0], + serializedPath: "m/44'/1'/0'/1/0", + address: 'mqCjBpQ75Y5sSGzFtJtSQQZqhJze9eaKjV', + }, + ]; + return addressList as WalletAddress[]; +} +export function getSewitAddressList(): WalletAddress[] { + const addressList = [ + { + path: [2147483697, 2147483649, 2147483648, 0, 0], + serializedPath: "m/49'/1'/0'/0/0", + address: '2NC4DCae9HdL6vjWMDbQwTkYEAB22MF3TPs', + }, + { + path: [2147483697, 2147483649, 2147483648, 1, 0], + serializedPath: "m/49'/1'/0'/1/0", + address: '2NCZ2CNYiz4rrHq3miUHerUMcLyeWU4gw9C', + }, + ]; + return addressList as WalletAddress[]; +} + +export function getNativeSegwitAddressList(): WalletAddress[] { + const addressList = [ + { + path: [2147483732, 2147483649, 2147483648, 0, 0], + serializedPath: "m/84'/1'/0'/0/0", + address: 'tb1qtanvhhl8ve32tcdxkrsamyy6vq5p62ctdv89l0', + }, + { + path: [2147483732, 2147483649, 2147483648, 1, 0], + serializedPath: "m/84'/1'/0'/1/0", + address: 'tb1qfuk3j0l4qn4uzstc47uwk68kedmjwuucl7avqr', + }, + ]; + return addressList as WalletAddress[]; +} + +export function getUtxoList(): Utxo[] { + const utxoList = [ + { + address: 'tb1qfuk3j0l4qn4uzstc47uwk68kedmjwuucl7avqr', + txid: 'tx_id', + vout: 2, + amount: '0.00002', + satoshis: 2000, + height: 2, + confirmations: 2, + }, + { + address: 'tb1qfuk3j0l4qn4uzstc47uwk68kedmjwuucl7avqr', + txid: 'tx_id2', + vout: 2, + amount: '0.00002', + satoshis: 2000, + height: 2, + confirmations: 2, + }, + { + address: 'tb1qfuk3j0l4qn4uzstc47uwk68kedmjwuucl7avqr', + txid: 'tx_id3', + vout: 2, + amount: '0.00002', + satoshis: 2000, + height: 2, + confirmations: 2, + }, + ]; + return utxoList as Utxo[]; +} + +export function getMockInputs(): TxInput[] { + const txInputs = [ + { + // eslint-disable-next-line @typescript-eslint/naming-convention + address_n: [0], + address: 'tb1qfuk3j0l4qn4uzstc47uwk68kedmjwuucl7avqr', + // eslint-disable-next-line @typescript-eslint/naming-convention + prev_hash: 'tx_id', + // eslint-disable-next-line @typescript-eslint/naming-convention + prev_index: 2, + amount: '0.00002', + }, + { + // eslint-disable-next-line @typescript-eslint/naming-convention + address_n: [0], + address: 'tb1qfuk3j0l4qn4uzstc47uwk68kedmjwuucl7avqr', + // eslint-disable-next-line @typescript-eslint/naming-convention + prev_hash: 'tx_id2', + // eslint-disable-next-line @typescript-eslint/naming-convention + prev_index: 2, + amount: '0.00002', + }, + { + // eslint-disable-next-line @typescript-eslint/naming-convention + address_n: [0], + address: 'tb1qfuk3j0l4qn4uzstc47uwk68kedmjwuucl7avqr', + // eslint-disable-next-line @typescript-eslint/naming-convention + prev_hash: 'tx_id3', + // eslint-disable-next-line @typescript-eslint/naming-convention + prev_index: 2, + amount: '0.00002', + }, + ]; + return txInputs as TxInput[]; +} diff --git a/src/__tests__/unit/account-balance.model.unit.ts b/src/__tests__/unit/account-balance.model.unit.ts new file mode 100644 index 00000000..019cd7ae --- /dev/null +++ b/src/__tests__/unit/account-balance.model.unit.ts @@ -0,0 +1,28 @@ +import {expect} from '@loopback/testlab'; +import {AccountBalance} from '../../models'; +import { + getLegacyAddressList, + getNativeSegwitAddressList, + getSewitAddressList, +} from '../helper'; +import * as constants from '../../constants'; + +describe('Account Balance Model ', () => { + it('Validate the account type given an address', () => { + getLegacyAddressList().forEach(walletAddress => { + expect(AccountBalance.getAccountType(walletAddress.address)).to.eql( + constants.BITCOIN_LEGACY_ADDRESS, + ); + }); + getNativeSegwitAddressList().forEach(walletAddress => { + expect(AccountBalance.getAccountType(walletAddress.address)).to.eql( + constants.BITCOIN_NATIVE_SEGWIT_ADDRESS, + ); + }); + getSewitAddressList().forEach(walletAddress => { + expect(AccountBalance.getAccountType(walletAddress.address)).to.eql( + constants.BITCOIN_SEGWIT_ADDRESS, + ); + }); + }); +}); diff --git a/src/__tests__/unit/pegin-configuration.controller.unit.ts b/src/__tests__/unit/pegin-configuration.repository.unit.ts similarity index 96% rename from src/__tests__/unit/pegin-configuration.controller.unit.ts rename to src/__tests__/unit/pegin-configuration.repository.unit.ts index c17bc1b1..dba8ba9c 100644 --- a/src/__tests__/unit/pegin-configuration.controller.unit.ts +++ b/src/__tests__/unit/pegin-configuration.repository.unit.ts @@ -9,7 +9,7 @@ import { } from '../../repositories'; import {PeginConfiguration} from '../../models'; import {PeginConfigurationController} from '../../controllers'; -describe('PeginConfiguration controller', () => { +describe('PeginConfiguration Repository', () => { let controller: PeginConfigurationController; let peginConfigurationRepository: StubbedInstanceWithSinonAccessor; let sessionRepository: StubbedInstanceWithSinonAccessor; diff --git a/src/__tests__/unit/session.repository.unit.ts b/src/__tests__/unit/session.repository.unit.ts new file mode 100644 index 00000000..135a4ee5 --- /dev/null +++ b/src/__tests__/unit/session.repository.unit.ts @@ -0,0 +1,61 @@ +import { + createStubInstance, + expect, + StubbedInstanceWithSinonAccessor, +} from '@loopback/testlab'; +import {SessionRepository} from '../../repositories'; +import {TxFeeController} from '../../controllers'; +import {FeeLevel} from '../../services'; +import {sinon} from '@loopback/testlab/dist/sinon'; +import {getMockInputs, getUtxoList} from '../helper'; +import {FeeRequestData} from '../../models'; +import * as constants from '../../constants'; +import {config} from 'dotenv'; + +config(); + +describe('Session Repository', () => { + let controller: TxFeeController; + let feeLevelService: FeeLevel; + let feeProvider: sinon.SinonStub; + let findAccountUtxos: sinon.SinonStub; + let getAccountInputs: sinon.SinonStub; + let sessionRepository: StubbedInstanceWithSinonAccessor; + beforeEach(resetRepositories); + + function resetRepositories() { + feeLevelService = {feeProvider: sinon.stub()}; + feeProvider = feeLevelService.feeProvider as sinon.SinonStub; + sessionRepository = createStubInstance(SessionRepository); + findAccountUtxos = sessionRepository.findAccountUtxos as sinon.SinonStub; + getAccountInputs = sessionRepository.getAccountInputs as sinon.SinonStub; + controller = new TxFeeController(sessionRepository, feeLevelService); + } + + it('should return the tx fee given provided utxos', async () => { + const fast: number = process.env.FAST_MINING_BLOCK + ? +process.env.FAST_MINING_BLOCK + : 1; + const average: number = process.env.AVERAGE_MINING_BLOCK + ? +process.env.AVERAGE_MINING_BLOCK + : 6; + const slow: number = process.env.LOW_MINING_BLOCK + ? +process.env.LOW_MINING_BLOCK + : 12; + feeProvider.withArgs(fast).resolves(['0.00003']); + feeProvider.withArgs(average).resolves(['0.00002']); + feeProvider.withArgs(slow).resolves(['0.00001']); + findAccountUtxos.resolves(getUtxoList()); + getAccountInputs.resolves(getMockInputs()); + const sessionId = 'test_id'; + const request = new FeeRequestData({ + sessionId, + amount: 0.00004, + accountType: constants.BITCOIN_LEGACY_ADDRESS, + }); + const feeAmount = await controller.getTxFee(request); + expect(feeAmount.slow).to.eql(338000.00000000006); + expect(feeAmount.average).to.eql(676000.0000000001); + expect(feeAmount.fast).to.eql(1014000); + }); +}); diff --git a/src/controllers/broadcast.controller.ts b/src/controllers/broadcast.controller.ts new file mode 100644 index 00000000..cb5447bc --- /dev/null +++ b/src/controllers/broadcast.controller.ts @@ -0,0 +1,42 @@ +import {inject} from '@loopback/core'; +import {Broadcast} from '../services'; +import {post, getModelSchemaRef, requestBody} from '@loopback/rest'; +import {BroadcastRequest, BroadcastResponse} from '../models'; + +export class BroadcastController { + constructor( + @inject('services.Broadcast') + protected broadcastProvider: Broadcast, + ) {} + + @post('/broadcast', { + responses: { + '201': { + description: 'a Tx Broadcast', + content: { + 'application/json': { + schema: getModelSchemaRef(BroadcastResponse), + }, + }, + }, + }, + }) + sendTx( + @requestBody({schema: getModelSchemaRef(BroadcastRequest)}) + req: BroadcastRequest, + ): Promise { + return new Promise((resolve, reject) => { + this.broadcastProvider + .broadcast(req.data) + .then(([txStatus]) => { + resolve( + new BroadcastResponse({ + txId: txStatus.result ?? '', + error: txStatus.error ?? undefined, + }), + ); + }) + .catch(reject); + }); + } +} diff --git a/src/controllers/index.ts b/src/controllers/index.ts index 3be74e90..3c031a49 100644 --- a/src/controllers/index.ts +++ b/src/controllers/index.ts @@ -2,3 +2,4 @@ export * from './pegin-configuration.controller'; export * from './balance.controller'; export * from './pegin-tx.controller'; export * from './tx-fee.controller'; +export * from './broadcast.controller'; diff --git a/src/controllers/pegin-tx.controller.ts b/src/controllers/pegin-tx.controller.ts index 631ffbf0..f9eae2f0 100644 --- a/src/controllers/pegin-tx.controller.ts +++ b/src/controllers/pegin-tx.controller.ts @@ -3,6 +3,9 @@ import {post, getModelSchemaRef, requestBody, response} from '@loopback/rest'; import {CreatePeginTxData, NormalizedTx, TxInput, TxOutput} from '../models'; import {SessionRepository} from '../repositories'; import peginAddressVerifier from 'pegin-address-verifier'; +import {config} from 'dotenv'; + +config(); export class PeginTxController { constructor( @@ -30,12 +33,13 @@ export class PeginTxController { return new Promise((resolve, reject) => { const outputs: TxOutput[] = []; const network = process.env.NETWORK ?? 'testnet'; - if ( - !peginAddressVerifier.isValidAddress( - createPeginTxData.refundAddress, - network, - ) - ) + const addressInfo = peginAddressVerifier.getAddressInformation( + createPeginTxData.refundAddress, + ); + const validAddress = addressInfo + ? peginAddressVerifier.canPegIn(addressInfo) + : false; + if (!validAddress) reject( `Invalid Refund Address provided ${createPeginTxData.refundAddress} for network ${network}`, ); @@ -73,7 +77,7 @@ export class PeginTxController { }), ); }) - .catch(console.error); + .catch(reject); }); } @@ -83,21 +87,19 @@ export class PeginTxController { // eslint-disable-next-line @typescript-eslint/naming-convention script_type: 'PAYTOOPRETURN', // eslint-disable-next-line @typescript-eslint/naming-convention - op_return_data: 'OP_RETURN 52534b54', + op_return_data: '52534b54', }); output.op_return_data += recipient; const addressInfo = peginAddressVerifier.getAddressInformation( refundAddress, ); - if (peginAddressVerifier.canPegIn(addressInfo)) { - switch (addressInfo.type) { - case 'p2pkh': - output.op_return_data += `01${addressInfo.scriptPubKey}`; - break; - case 'p2sh': - output.op_return_data += `02${addressInfo.scriptHash}`; - break; - } + switch (addressInfo.type) { + case 'p2pkh': + output.op_return_data += `01${addressInfo.scriptPubKey}`; + break; + case 'p2sh': + output.op_return_data += `02${addressInfo.scriptHash}`; + break; } return output; } diff --git a/src/controllers/tx-fee.controller.ts b/src/controllers/tx-fee.controller.ts index 2d4a35d8..e7e9c143 100644 --- a/src/controllers/tx-fee.controller.ts +++ b/src/controllers/tx-fee.controller.ts @@ -3,7 +3,10 @@ import {post, getModelSchemaRef, requestBody, response} from '@loopback/rest'; import {FeeAmountData, FeeRequestData, Utxo, TxInput} from '../models'; import {SessionRepository} from '../repositories'; import {inject} from '@loopback/core'; -import {FeeLevel, TxService} from '../services'; +import {FeeLevel} from '../services'; +import {config} from 'dotenv'; + +config(); export class TxFeeController { constructor( @@ -11,8 +14,6 @@ export class TxFeeController { public sessionRepository: SessionRepository, @inject('services.FeeLevel') protected feeLevelProviderService: FeeLevel, - @inject('services.TxService') - protected txService: TxService, ) {} @post('/tx-fee') @@ -33,38 +34,38 @@ export class TxFeeController { feeRequestData: FeeRequestData, ): Promise { return new Promise((resolve, reject) => { - const [fast, average, low] = [1, 6, 12]; + const fast = process.env.FAST_MINING_BLOCK ?? 1; + const average = process.env.AVERAGE_MINING_BLOCK ?? 6; + const low = process.env.LOW_MINING_BLOCK ?? 12; let inputs: TxInput[] = []; const fees: FeeAmountData = new FeeAmountData({ slow: 0, average: 0, fast: 0, }); - const inputSize = 180; + const inputSize = process.env.INPUT_SIZE ?? 180; const txBytes = 3 * 34 + 10 + 46; Promise.all([ this.sessionRepository.findAccountUtxos( feeRequestData.sessionId, feeRequestData.accountType, ), - this.feeLevelProviderService.feeProvider(fast), - this.feeLevelProviderService.feeProvider(average), - this.feeLevelProviderService.feeProvider(low), + this.feeLevelProviderService.feeProvider(+fast), + this.feeLevelProviderService.feeProvider(+average), + this.feeLevelProviderService.feeProvider(+low), ]) .then( ([accountUtxoList, [fastAmount], [averageAmount], [lowAmount]]) => { - inputs = inputs.concat( - this.selectOptimalInputs( - accountUtxoList, - +fastAmount + feeRequestData.amount, - ), + inputs = this.selectOptimalInputs( + accountUtxoList, + +fastAmount * txBytes + feeRequestData.amount, ); fees.fast = - (inputs.length * inputSize + txBytes) * (+fastAmount * 1e8); + (inputs.length * +inputSize + txBytes) * (+fastAmount * 1e8); fees.average = - (inputs.length * inputSize + txBytes) * (+averageAmount * 1e8); + (inputs.length * +inputSize + txBytes) * (+averageAmount * 1e8); fees.slow = - (inputs.length * inputSize + txBytes) * (+lowAmount * 1e8); + (inputs.length * +inputSize + txBytes) * (+lowAmount * 1e8); return Promise.all([ fees, this.sessionRepository.setInputs( @@ -85,9 +86,10 @@ export class TxFeeController { let remainingSatoshis = amountInSatoshis; utxoList.sort((a, b) => b.satoshis - a.satoshis); utxoList.forEach(utxo => { - if (remainingSatoshis) { + if (remainingSatoshis > 0) { inputs.push( new TxInput({ + address: utxo.address, // eslint-disable-next-line @typescript-eslint/naming-convention address_n: [0], // eslint-disable-next-line @typescript-eslint/naming-convention diff --git a/src/datasources/index.ts b/src/datasources/index.ts index f4d10182..fb220bcf 100644 --- a/src/datasources/index.ts +++ b/src/datasources/index.ts @@ -3,3 +3,4 @@ export * from './utxo-provider.datasource'; export * from './redis.datasource'; export * from './tx-fee-provider.datasource'; export * from './tx-provider.datasource'; +export * from './tx-broadcast.datasource'; diff --git a/src/datasources/tx-broadcast.datasource.ts b/src/datasources/tx-broadcast.datasource.ts new file mode 100644 index 00000000..7c504eb1 --- /dev/null +++ b/src/datasources/tx-broadcast.datasource.ts @@ -0,0 +1,44 @@ +import {inject, lifeCycleObserver, LifeCycleObserver} from '@loopback/core'; +import {juggler} from '@loopback/repository'; + +const config = { + name: 'txBroadcast', + connector: 'rest', + options: { + headers: { + accept: 'application/json', + 'content-type': 'application/json', + }, + }, + operations: [ + { + template: { + method: 'GET', + url: `https://blockbook.trugroup.tech:19130/api/v2/sendtx/{tx}`, + responsePath: '$', + }, + functions: { + broadcast: ['tx'], + }, + }, + ], +}; + +// Observe application's life cycle to disconnect the datasource when +// application is stopped. This allows the application to be shut down +// gracefully. The `stop()` method is inherited from `juggler.DataSource`. +// Learn more at https://loopback.io/doc/en/lb4/Life-cycle.html +@lifeCycleObserver('datasource') +export class TxBroadcastDataSource + extends juggler.DataSource + implements LifeCycleObserver { + static dataSourceName = 'txBroadcast'; + static readonly defaultConfig = config; + + constructor( + @inject('datasources.config.txBroadcast', {optional: true}) + dsConfig: object = config, + ) { + super(dsConfig); + } +} diff --git a/src/index.ts b/src/index.ts index 7bba5590..6bfd0d03 100644 --- a/src/index.ts +++ b/src/index.ts @@ -9,7 +9,6 @@ export async function main(options: ApplicationConfig = {}) { const url = app.restServer.url; console.log(`Server is running at ${url}`); - console.log(`Try ${url}/ping`); return app; } diff --git a/src/models/account-balance.model.ts b/src/models/account-balance.model.ts index ad5b2a58..356f4ce0 100644 --- a/src/models/account-balance.model.ts +++ b/src/models/account-balance.model.ts @@ -56,9 +56,13 @@ export class AccountBalance extends Model { static getAccountType(address: string): string { const [legacyTestReg, segwitTestReg, nativeTestReg] = [ - /^[mn][1-9A-HJ-NP-Za-km-z]{26,35}/, - /^[2][1-9A-HJ-NP-Za-km-z]{26,35}/, - /^[tb][0-9A-HJ-NP-Za-z]{26,41}/, + new RegExp( + process.env.LEGACY_REGEX ?? /^[mn][1-9A-HJ-NP-Za-km-z]{26,35}/, + ), + new RegExp(process.env.SEGWIT_REGEX ?? /^[2][1-9A-HJ-NP-Za-km-z]{26,35}/), + new RegExp( + process.env.NATIVE_SEGWIT_REGEX ?? /^[tb][0-9A-HJ-NP-Za-z]{26,41}/, + ), ]; if (legacyTestReg.test(address)) return constants.BITCOIN_LEGACY_ADDRESS; else if (segwitTestReg.test(address)) diff --git a/src/models/broadcast-request.model.ts b/src/models/broadcast-request.model.ts new file mode 100644 index 00000000..7e3f1b31 --- /dev/null +++ b/src/models/broadcast-request.model.ts @@ -0,0 +1,21 @@ +import {Model, model, property} from '@loopback/repository'; + +@model() +export class BroadcastRequest extends Model { + @property({ + type: 'string', + required: true, + }) + data: string; + + constructor(data?: Partial) { + super(data); + } +} + +export interface BroadcastRequestRelations { + // describe navigational properties here +} + +export type BroadcastRequestWithRelations = BroadcastRequest & + BroadcastRequestRelations; diff --git a/src/models/broadcast-response.model.ts b/src/models/broadcast-response.model.ts new file mode 100644 index 00000000..4d109d11 --- /dev/null +++ b/src/models/broadcast-response.model.ts @@ -0,0 +1,31 @@ +import {Model, model, property} from '@loopback/repository'; + +@model({settings: {strict: false}}) +export class BroadcastResponse extends Model { + @property({ + type: 'string', + }) + txId?: string; + + @property({ + type: 'object', + }) + error?: object; + + // Define well-known properties here + + // Indexer property to allow additional data + // eslint-disable-next-line @typescript-eslint/no-explicit-any + [prop: string]: any; + + constructor(data?: Partial) { + super(data); + } +} + +export interface BroadcastResponseRelations { + // describe navigational properties here +} + +export type BroadcastResponseWithRelations = BroadcastResponse & + BroadcastResponseRelations; diff --git a/src/models/index.ts b/src/models/index.ts index faf511cb..ac002aff 100644 --- a/src/models/index.ts +++ b/src/models/index.ts @@ -11,3 +11,5 @@ export * from './tx-input.model'; export * from './tx-output.model'; export * from './normalized-tx.model'; export * from './fee-request-data.model'; +export * from './broadcast-response.model'; +export * from './broadcast-request.model'; diff --git a/src/models/tx-input.model.ts b/src/models/tx-input.model.ts index 28c5ec7f..9f85383d 100644 --- a/src/models/tx-input.model.ts +++ b/src/models/tx-input.model.ts @@ -10,6 +10,12 @@ export class TxInput extends Model { // eslint-disable-next-line @typescript-eslint/naming-convention address_n: number[]; + @property({ + type: 'string', + required: true, + }) + address: string; + @property({ type: 'string', required: true, diff --git a/src/services/broadcast.service.ts b/src/services/broadcast.service.ts new file mode 100644 index 00000000..ec979694 --- /dev/null +++ b/src/services/broadcast.service.ts @@ -0,0 +1,29 @@ +import {inject, Provider} from '@loopback/core'; +import {getService} from '@loopback/service-proxy'; +import {TxBroadcastDataSource} from '../datasources'; + +export interface TxStatus { + result?: string; + error?: { + message: string; + }; +} + +export interface Broadcast { + // this is where you define the Node.js methods that will be + // mapped to REST/SOAP/gRPC operations as stated in the datasource + // json file. + broadcast(hexTx: string): Promise; +} + +export class BroadcastProvider implements Provider { + constructor( + // txBroadcast must match the name property in the datasource json file + @inject('datasources.txBroadcast') + protected dataSource: TxBroadcastDataSource = new TxBroadcastDataSource(), + ) {} + + value(): Promise { + return getService(this.dataSource); + } +} diff --git a/src/services/index.ts b/src/services/index.ts index 7e945ddf..5cfd0afd 100644 --- a/src/services/index.ts +++ b/src/services/index.ts @@ -1,3 +1,4 @@ export * from './utxo-provider.service'; export * from './fee-level.service'; export * from './tx-service.service'; +export * from './broadcast.service'; diff --git a/src/tsconfig.json b/src/tsconfig.json new file mode 100644 index 00000000..7d6f333c --- /dev/null +++ b/src/tsconfig.json @@ -0,0 +1,11 @@ +{ + "$schema": "http://json.schemastore.org/tsconfig", + "extends": "@loopback/build/config/tsconfig.common.json", + "compilerOptions": { + "esModuleInterop": true, + "experimentalDecorators": true, + "outDir": "dist", + "rootDir": "src" + }, + "include": ["src"] +}