From 283d56ba1121f1f03041f9e0d30c533600df3eb0 Mon Sep 17 00:00:00 2001 From: Josh Stevens Date: Mon, 2 Aug 2021 18:39:02 +0100 Subject: [PATCH 1/8] more scripts to help dev --- package-lock.json | 614 ++++++++++++++++-------------- package.json | 4 + src/__TEST-SCRIPT__/playground.ts | 23 +- 3 files changed, 355 insertions(+), 286 deletions(-) diff --git a/package-lock.json b/package-lock.json index b34e673b..2ce625d1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "simple-uniswap-sdk", - "version": "3.3.0", + "version": "3.3.2", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -1037,6 +1037,29 @@ "resolved": "https://registry.npmjs.org/@types/node/-/node-14.14.22.tgz", "integrity": "sha512-g+f/qj/cNcqKkc3tFqlXOYjrmZA+jNBiDzbP3kH+B+otKFqAdPgVTGP1IeKRdMml/aE69as5S4FqtxAbl+LaMw==" }, + "@types/node-fetch": { + "version": "2.5.12", + "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.5.12.tgz", + "integrity": "sha512-MKgC4dlq4kKNa/mYrwpKfzQMB5X3ee5U6fSprkKpToBqBmX4nFZL9cW5jl6sWn+xpRJ7ypWh2yyqqr8UUCstSw==", + "dev": true, + "requires": { + "@types/node": "*", + "form-data": "^3.0.0" + }, + "dependencies": { + "form-data": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.1.tgz", + "integrity": "sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg==", + "dev": true, + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + } + } + } + }, "@types/stack-utils": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-1.0.1.tgz", @@ -2009,169 +2032,169 @@ }, "dependencies": { "@ethersproject/abi": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/@ethersproject/abi/-/abi-5.2.0.tgz", - "integrity": "sha512-24ExfHa0VbIOUHbB36b6lCVmWkaIVmrd9/m8MICtmSsRKzlugWqUD0B8g0zrRylXNxAOc3V6T4xKJ8jEDSvp3w==", + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/@ethersproject/abi/-/abi-5.4.0.tgz", + "integrity": "sha512-9gU2H+/yK1j2eVMdzm6xvHSnMxk8waIHQGYCZg5uvAyH0rsAzxkModzBSpbAkAuhKFEovC2S9hM4nPuLym8IZw==", "requires": { - "@ethersproject/address": "^5.2.0", - "@ethersproject/bignumber": "^5.2.0", - "@ethersproject/bytes": "^5.2.0", - "@ethersproject/constants": "^5.2.0", - "@ethersproject/hash": "^5.2.0", - "@ethersproject/keccak256": "^5.2.0", - "@ethersproject/logger": "^5.2.0", - "@ethersproject/properties": "^5.2.0", - "@ethersproject/strings": "^5.2.0" + "@ethersproject/address": "^5.4.0", + "@ethersproject/bignumber": "^5.4.0", + "@ethersproject/bytes": "^5.4.0", + "@ethersproject/constants": "^5.4.0", + "@ethersproject/hash": "^5.4.0", + "@ethersproject/keccak256": "^5.4.0", + "@ethersproject/logger": "^5.4.0", + "@ethersproject/properties": "^5.4.0", + "@ethersproject/strings": "^5.4.0" } }, "@ethersproject/abstract-provider": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/@ethersproject/abstract-provider/-/abstract-provider-5.2.0.tgz", - "integrity": "sha512-Xi7Pt+CulRijc/vskBGIaYMEhafKjoNx8y4RNj/dnSpXHXScOJUSTtypqGBUngZddRbcwZGbHwEr6DZoKZwIZA==", + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/@ethersproject/abstract-provider/-/abstract-provider-5.4.0.tgz", + "integrity": "sha512-vPBR7HKUBY0lpdllIn7tLIzNN7DrVnhCLKSzY0l8WAwxz686m/aL7ASDzrVxV93GJtIub6N2t4dfZ29CkPOxgA==", "requires": { - "@ethersproject/bignumber": "^5.2.0", - "@ethersproject/bytes": "^5.2.0", - "@ethersproject/logger": "^5.2.0", - "@ethersproject/networks": "^5.2.0", - "@ethersproject/properties": "^5.2.0", - "@ethersproject/transactions": "^5.2.0", - "@ethersproject/web": "^5.2.0" + "@ethersproject/bignumber": "^5.4.0", + "@ethersproject/bytes": "^5.4.0", + "@ethersproject/logger": "^5.4.0", + "@ethersproject/networks": "^5.4.0", + "@ethersproject/properties": "^5.4.0", + "@ethersproject/transactions": "^5.4.0", + "@ethersproject/web": "^5.4.0" } }, "@ethersproject/abstract-signer": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/@ethersproject/abstract-signer/-/abstract-signer-5.2.0.tgz", - "integrity": "sha512-JTXzLUrtoxpOEq1ecH86U7tstkEa9POKAGbGBb+gicbjGgzYYkLR4/LD83SX2/JNWvtYyY8t5errt5ehiy1gxQ==", + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/@ethersproject/abstract-signer/-/abstract-signer-5.4.1.tgz", + "integrity": "sha512-SkkFL5HVq1k4/25dM+NWP9MILgohJCgGv5xT5AcRruGz4ILpfHeBtO/y6j+Z3UN/PAjDeb4P7E51Yh8wcGNLGA==", "requires": { - "@ethersproject/abstract-provider": "^5.2.0", - "@ethersproject/bignumber": "^5.2.0", - "@ethersproject/bytes": "^5.2.0", - "@ethersproject/logger": "^5.2.0", - "@ethersproject/properties": "^5.2.0" + "@ethersproject/abstract-provider": "^5.4.0", + "@ethersproject/bignumber": "^5.4.0", + "@ethersproject/bytes": "^5.4.0", + "@ethersproject/logger": "^5.4.0", + "@ethersproject/properties": "^5.4.0" } }, "@ethersproject/address": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/@ethersproject/address/-/address-5.2.0.tgz", - "integrity": "sha512-2YfZlalWefOEfnr/CdqKRrgMgbKidYc+zG4/ilxSdcryZSux3eBU5/5btAT/hSiaHipUjd8UrWK8esCBHU6QNQ==", + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/@ethersproject/address/-/address-5.4.0.tgz", + "integrity": "sha512-SD0VgOEkcACEG/C6xavlU1Hy3m5DGSXW3CUHkaaEHbAPPsgi0coP5oNPsxau8eTlZOk/bpa/hKeCNoK5IzVI2Q==", "requires": { - "@ethersproject/bignumber": "^5.2.0", - "@ethersproject/bytes": "^5.2.0", - "@ethersproject/keccak256": "^5.2.0", - "@ethersproject/logger": "^5.2.0", - "@ethersproject/rlp": "^5.2.0" + "@ethersproject/bignumber": "^5.4.0", + "@ethersproject/bytes": "^5.4.0", + "@ethersproject/keccak256": "^5.4.0", + "@ethersproject/logger": "^5.4.0", + "@ethersproject/rlp": "^5.4.0" } }, "@ethersproject/base64": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/@ethersproject/base64/-/base64-5.2.0.tgz", - "integrity": "sha512-D9wOvRE90QBI+yFsKMv0hnANiMzf40Xicq9JZbV9XYzh7srImmwmMcReU2wHjOs9FtEgSJo51Tt+sI1dKPYKDg==", + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/@ethersproject/base64/-/base64-5.4.0.tgz", + "integrity": "sha512-CjQw6E17QDSSC5jiM9YpF7N1aSCHmYGMt9bWD8PWv6YPMxjsys2/Q8xLrROKI3IWJ7sFfZ8B3flKDTM5wlWuZQ==", "requires": { - "@ethersproject/bytes": "^5.2.0" + "@ethersproject/bytes": "^5.4.0" } }, "@ethersproject/basex": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/@ethersproject/basex/-/basex-5.2.0.tgz", - "integrity": "sha512-Oo7oX7BmaHLY/8ZsOLI5W0mrSwPBb1iboosN17jfK/4vGAtKjAInDai9I72CzN4NRJaMN5FkFLoPYywGqgGHlg==", + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/@ethersproject/basex/-/basex-5.4.0.tgz", + "integrity": "sha512-J07+QCVJ7np2bcpxydFVf/CuYo9mZ7T73Pe7KQY4c1lRlrixMeblauMxHXD0MPwFmUHZIILDNViVkykFBZylbg==", "requires": { - "@ethersproject/bytes": "^5.2.0", - "@ethersproject/properties": "^5.2.0" + "@ethersproject/bytes": "^5.4.0", + "@ethersproject/properties": "^5.4.0" } }, "@ethersproject/bignumber": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/@ethersproject/bignumber/-/bignumber-5.2.0.tgz", - "integrity": "sha512-+MNQTxwV7GEiA4NH/i51UqQ+lY36O0rxPdV+0qzjFSySiyBlJpLk6aaa4UTvKmYWlI7YKZm6vuyCENeYn7qAOw==", + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/@ethersproject/bignumber/-/bignumber-5.4.1.tgz", + "integrity": "sha512-fJhdxqoQNuDOk6epfM7yD6J8Pol4NUCy1vkaGAkuujZm0+lNow//MKu1hLhRiYV4BsOHyBv5/lsTjF+7hWwhJg==", "requires": { - "@ethersproject/bytes": "^5.2.0", - "@ethersproject/logger": "^5.2.0", - "bn.js": "^4.4.0" + "@ethersproject/bytes": "^5.4.0", + "@ethersproject/logger": "^5.4.0", + "bn.js": "^4.11.9" } }, "@ethersproject/bytes": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/@ethersproject/bytes/-/bytes-5.2.0.tgz", - "integrity": "sha512-O1CRpvJDnRTB47vvW8vyqojUZxVookb4LJv/s06TotriU3Xje5WFvlvXJu1yTchtxTz9BbvJw0lFXKpyO6Dn7w==", + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/@ethersproject/bytes/-/bytes-5.4.0.tgz", + "integrity": "sha512-H60ceqgTHbhzOj4uRc/83SCN9d+BSUnOkrr2intevqdtEMO1JFVZ1XL84OEZV+QjV36OaZYxtnt4lGmxcGsPfA==", "requires": { - "@ethersproject/logger": "^5.2.0" + "@ethersproject/logger": "^5.4.0" } }, "@ethersproject/constants": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/@ethersproject/constants/-/constants-5.2.0.tgz", - "integrity": "sha512-p+34YG0KbHS20NGdE+Ic0M6egzd7cDvcfoO9RpaAgyAYm3V5gJVqL7UynS87yCt6O6Nlx6wRFboPiM5ctAr+jA==", + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/@ethersproject/constants/-/constants-5.4.0.tgz", + "integrity": "sha512-tzjn6S7sj9+DIIeKTJLjK9WGN2Tj0P++Z8ONEIlZjyoTkBuODN+0VfhAyYksKi43l1Sx9tX2VlFfzjfmr5Wl3Q==", "requires": { - "@ethersproject/bignumber": "^5.2.0" + "@ethersproject/bignumber": "^5.4.0" } }, "@ethersproject/contracts": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/@ethersproject/contracts/-/contracts-5.2.0.tgz", - "integrity": "sha512-/2fg5tWPG6Z4pciEWpwGji3ggGA5j0ChVNF7NTmkOhvFrrJuWnRpzbvYA00nz8tBDNCOV3cwub5zfWRpgwYEJQ==", + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/@ethersproject/contracts/-/contracts-5.4.1.tgz", + "integrity": "sha512-m+z2ZgPy4pyR15Je//dUaymRUZq5MtDajF6GwFbGAVmKz/RF+DNIPwF0k5qEcL3wPGVqUjFg2/krlCRVTU4T5w==", "requires": { - "@ethersproject/abi": "^5.2.0", - "@ethersproject/abstract-provider": "^5.2.0", - "@ethersproject/abstract-signer": "^5.2.0", - "@ethersproject/address": "^5.2.0", - "@ethersproject/bignumber": "^5.2.0", - "@ethersproject/bytes": "^5.2.0", - "@ethersproject/constants": "^5.2.0", - "@ethersproject/logger": "^5.2.0", - "@ethersproject/properties": "^5.2.0", - "@ethersproject/transactions": "^5.2.0" + "@ethersproject/abi": "^5.4.0", + "@ethersproject/abstract-provider": "^5.4.0", + "@ethersproject/abstract-signer": "^5.4.0", + "@ethersproject/address": "^5.4.0", + "@ethersproject/bignumber": "^5.4.0", + "@ethersproject/bytes": "^5.4.0", + "@ethersproject/constants": "^5.4.0", + "@ethersproject/logger": "^5.4.0", + "@ethersproject/properties": "^5.4.0", + "@ethersproject/transactions": "^5.4.0" } }, "@ethersproject/hash": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/@ethersproject/hash/-/hash-5.2.0.tgz", - "integrity": "sha512-wEGry2HFFSssFiNEkFWMzj1vpdFv4rQlkBp41UfL6J58zKGNycoAWimokITDMk8p7548MKr27h48QfERnNKkRw==", + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/@ethersproject/hash/-/hash-5.4.0.tgz", + "integrity": "sha512-xymAM9tmikKgbktOCjW60Z5sdouiIIurkZUr9oW5NOex5uwxrbsYG09kb5bMcNjlVeJD3yPivTNzViIs1GCbqA==", "requires": { - "@ethersproject/abstract-signer": "^5.2.0", - "@ethersproject/address": "^5.2.0", - "@ethersproject/bignumber": "^5.2.0", - "@ethersproject/bytes": "^5.2.0", - "@ethersproject/keccak256": "^5.2.0", - "@ethersproject/logger": "^5.2.0", - "@ethersproject/properties": "^5.2.0", - "@ethersproject/strings": "^5.2.0" + "@ethersproject/abstract-signer": "^5.4.0", + "@ethersproject/address": "^5.4.0", + "@ethersproject/bignumber": "^5.4.0", + "@ethersproject/bytes": "^5.4.0", + "@ethersproject/keccak256": "^5.4.0", + "@ethersproject/logger": "^5.4.0", + "@ethersproject/properties": "^5.4.0", + "@ethersproject/strings": "^5.4.0" } }, "@ethersproject/hdnode": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/@ethersproject/hdnode/-/hdnode-5.2.0.tgz", - "integrity": "sha512-ffq2JrW5AftCmfWZ8DxpdWdw/x06Yn+e9wrWHLpj8If1+w87W4LbTMRUaUmO1DUSN8H8g/6kMUKCTJPVuxsuOw==", + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/@ethersproject/hdnode/-/hdnode-5.4.0.tgz", + "integrity": "sha512-pKxdS0KAaeVGfZPp1KOiDLB0jba11tG6OP1u11QnYfb7pXn6IZx0xceqWRr6ygke8+Kw74IpOoSi7/DwANhy8Q==", "requires": { - "@ethersproject/abstract-signer": "^5.2.0", - "@ethersproject/basex": "^5.2.0", - "@ethersproject/bignumber": "^5.2.0", - "@ethersproject/bytes": "^5.2.0", - "@ethersproject/logger": "^5.2.0", - "@ethersproject/pbkdf2": "^5.2.0", - "@ethersproject/properties": "^5.2.0", - "@ethersproject/sha2": "^5.2.0", - "@ethersproject/signing-key": "^5.2.0", - "@ethersproject/strings": "^5.2.0", - "@ethersproject/transactions": "^5.2.0", - "@ethersproject/wordlists": "^5.2.0" + "@ethersproject/abstract-signer": "^5.4.0", + "@ethersproject/basex": "^5.4.0", + "@ethersproject/bignumber": "^5.4.0", + "@ethersproject/bytes": "^5.4.0", + "@ethersproject/logger": "^5.4.0", + "@ethersproject/pbkdf2": "^5.4.0", + "@ethersproject/properties": "^5.4.0", + "@ethersproject/sha2": "^5.4.0", + "@ethersproject/signing-key": "^5.4.0", + "@ethersproject/strings": "^5.4.0", + "@ethersproject/transactions": "^5.4.0", + "@ethersproject/wordlists": "^5.4.0" } }, "@ethersproject/json-wallets": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/@ethersproject/json-wallets/-/json-wallets-5.2.0.tgz", - "integrity": "sha512-iWxSm9XiugEtaehYD6w1ImmXeatjcGcrQvffZVJHH1UqV4FckDzrOYnZBRHPQRYlnhNVrGTld1+S0Cu4MB8gdw==", + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/@ethersproject/json-wallets/-/json-wallets-5.4.0.tgz", + "integrity": "sha512-igWcu3fx4aiczrzEHwG1xJZo9l1cFfQOWzTqwRw/xcvxTk58q4f9M7cjh51EKphMHvrJtcezJ1gf1q1AUOfEQQ==", "requires": { - "@ethersproject/abstract-signer": "^5.2.0", - "@ethersproject/address": "^5.2.0", - "@ethersproject/bytes": "^5.2.0", - "@ethersproject/hdnode": "^5.2.0", - "@ethersproject/keccak256": "^5.2.0", - "@ethersproject/logger": "^5.2.0", - "@ethersproject/pbkdf2": "^5.2.0", - "@ethersproject/properties": "^5.2.0", - "@ethersproject/random": "^5.2.0", - "@ethersproject/strings": "^5.2.0", - "@ethersproject/transactions": "^5.2.0", + "@ethersproject/abstract-signer": "^5.4.0", + "@ethersproject/address": "^5.4.0", + "@ethersproject/bytes": "^5.4.0", + "@ethersproject/hdnode": "^5.4.0", + "@ethersproject/keccak256": "^5.4.0", + "@ethersproject/logger": "^5.4.0", + "@ethersproject/pbkdf2": "^5.4.0", + "@ethersproject/properties": "^5.4.0", + "@ethersproject/random": "^5.4.0", + "@ethersproject/strings": "^5.4.0", + "@ethersproject/transactions": "^5.4.0", "aes-js": "3.0.0", "scrypt-js": "3.0.1" }, @@ -2184,108 +2207,127 @@ } }, "@ethersproject/keccak256": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/@ethersproject/keccak256/-/keccak256-5.2.0.tgz", - "integrity": "sha512-LqyxTwVANga5Y3L1yo184czW6b3PibabN8xyE/eOulQLLfXNrHHhwrOTpOhoVRWCICVCD/5SjQfwqTrczjS7jQ==", + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/@ethersproject/keccak256/-/keccak256-5.4.0.tgz", + "integrity": "sha512-FBI1plWet+dPUvAzPAeHzRKiPpETQzqSUWR1wXJGHVWi4i8bOSrpC3NwpkPjgeXG7MnugVc1B42VbfnQikyC/A==", "requires": { - "@ethersproject/bytes": "^5.2.0", + "@ethersproject/bytes": "^5.4.0", "js-sha3": "0.5.7" } }, "@ethersproject/logger": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/@ethersproject/logger/-/logger-5.2.0.tgz", - "integrity": "sha512-dPZ6/E3YiArgG8dI/spGkaRDry7YZpCntf4gm/c6SI8Mbqiihd7q3nuLN5VvDap/0K3xm3RE1AIUOcUwwh2ezQ==" + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/@ethersproject/logger/-/logger-5.4.0.tgz", + "integrity": "sha512-xYdWGGQ9P2cxBayt64d8LC8aPFJk6yWCawQi/4eJ4+oJdMMjEBMrIcIMZ9AxhwpPVmnBPrsB10PcXGmGAqgUEQ==" }, "@ethersproject/networks": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/@ethersproject/networks/-/networks-5.2.0.tgz", - "integrity": "sha512-q+htMgq7wQoEnjlkdHM6t1sktKxNbEB/F6DQBPNwru7KpQ1R0n0UTIXJB8Rb7lSnvjqcAQ40X3iVqm94NJfYDw==", + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/@ethersproject/networks/-/networks-5.4.1.tgz", + "integrity": "sha512-8SvowCKz9Uf4xC5DTKI8+il8lWqOr78kmiqAVLYT9lzB8aSmJHQMD1GSuJI0CW4hMAnzocpGpZLgiMdzsNSPig==", "requires": { - "@ethersproject/logger": "^5.2.0" + "@ethersproject/logger": "^5.4.0" } }, "@ethersproject/pbkdf2": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/@ethersproject/pbkdf2/-/pbkdf2-5.2.0.tgz", - "integrity": "sha512-qKOoO6yir/qnAgg6OP3U4gRuZ6jl9P7xwggRu/spVfnuaR+wa490AatWLqB1WOXKf6JFjm5yOaT/T5fCICQVdQ==", + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/@ethersproject/pbkdf2/-/pbkdf2-5.4.0.tgz", + "integrity": "sha512-x94aIv6tiA04g6BnazZSLoRXqyusawRyZWlUhKip2jvoLpzJuLb//KtMM6PEovE47pMbW+Qe1uw+68ameJjB7g==", "requires": { - "@ethersproject/bytes": "^5.2.0", - "@ethersproject/sha2": "^5.2.0" + "@ethersproject/bytes": "^5.4.0", + "@ethersproject/sha2": "^5.4.0" } }, "@ethersproject/properties": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/@ethersproject/properties/-/properties-5.2.0.tgz", - "integrity": "sha512-oNFkzcoGwXXV+/Yp/MLcDLrL/2i360XIy2YN9yRZJPnIbLwjroFNLiRzLs6PyPw1D09Xs8OcPR1/nHv6xDKE2A==", + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/@ethersproject/properties/-/properties-5.4.0.tgz", + "integrity": "sha512-7jczalGVRAJ+XSRvNA6D5sAwT4gavLq3OXPuV/74o3Rd2wuzSL035IMpIMgei4CYyBdialJMrTqkOnzccLHn4A==", "requires": { - "@ethersproject/logger": "^5.2.0" + "@ethersproject/logger": "^5.4.0" } }, "@ethersproject/providers": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/@ethersproject/providers/-/providers-5.2.0.tgz", - "integrity": "sha512-Yf/ZUqCrVr+jR0SHA9GuNZs4R1xnV9Ibnh1TlOa0ZzI6o+Qf8bEyE550k9bYI4zk2f9x9baX2RRs6BJY7Jz/WA==", + "version": "5.4.3", + "resolved": "https://registry.npmjs.org/@ethersproject/providers/-/providers-5.4.3.tgz", + "integrity": "sha512-VURwkaWPoUj7jq9NheNDT5Iyy64Qcyf6BOFDwVdHsmLmX/5prNjFrgSX3GHPE4z1BRrVerDxe2yayvXKFm/NNg==", "requires": { - "@ethersproject/abstract-provider": "^5.2.0", - "@ethersproject/abstract-signer": "^5.2.0", - "@ethersproject/address": "^5.2.0", - "@ethersproject/basex": "^5.2.0", - "@ethersproject/bignumber": "^5.2.0", - "@ethersproject/bytes": "^5.2.0", - "@ethersproject/constants": "^5.2.0", - "@ethersproject/hash": "^5.2.0", - "@ethersproject/logger": "^5.2.0", - "@ethersproject/networks": "^5.2.0", - "@ethersproject/properties": "^5.2.0", - "@ethersproject/random": "^5.2.0", - "@ethersproject/rlp": "^5.2.0", - "@ethersproject/sha2": "^5.2.0", - "@ethersproject/strings": "^5.2.0", - "@ethersproject/transactions": "^5.2.0", - "@ethersproject/web": "^5.2.0", + "@ethersproject/abstract-provider": "^5.4.0", + "@ethersproject/abstract-signer": "^5.4.0", + "@ethersproject/address": "^5.4.0", + "@ethersproject/basex": "^5.4.0", + "@ethersproject/bignumber": "^5.4.0", + "@ethersproject/bytes": "^5.4.0", + "@ethersproject/constants": "^5.4.0", + "@ethersproject/hash": "^5.4.0", + "@ethersproject/logger": "^5.4.0", + "@ethersproject/networks": "^5.4.0", + "@ethersproject/properties": "^5.4.0", + "@ethersproject/random": "^5.4.0", + "@ethersproject/rlp": "^5.4.0", + "@ethersproject/sha2": "^5.4.0", + "@ethersproject/strings": "^5.4.0", + "@ethersproject/transactions": "^5.4.0", + "@ethersproject/web": "^5.4.0", "bech32": "1.1.4", - "ws": "7.2.3" + "ws": "7.4.6" + }, + "dependencies": { + "ws": { + "version": "7.4.6", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.4.6.tgz", + "integrity": "sha512-YmhHDO4MzaDLB+M9ym/mDA5z0naX8j7SIlT8f8z+I0VtzsRbekxEutHSme7NPS2qE8StCYQNUnfWdXta/Yu85A==" + } } }, "@ethersproject/random": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/@ethersproject/random/-/random-5.2.0.tgz", - "integrity": "sha512-7Nd3qjivBGlDCGDuGYjPi8CXdtVhRZ7NeyBXoJgtnJBwn1S01ahrbMeOUVmRVWrFM0YiSEPEGo7i4xEu2gRPcg==", + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/@ethersproject/random/-/random-5.4.0.tgz", + "integrity": "sha512-pnpWNQlf0VAZDEOVp1rsYQosmv2o0ITS/PecNw+mS2/btF8eYdspkN0vIXrCMtkX09EAh9bdk8GoXmFXM1eAKw==", "requires": { - "@ethersproject/bytes": "^5.2.0", - "@ethersproject/logger": "^5.2.0" + "@ethersproject/bytes": "^5.4.0", + "@ethersproject/logger": "^5.4.0" } }, "@ethersproject/rlp": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/@ethersproject/rlp/-/rlp-5.2.0.tgz", - "integrity": "sha512-RqGsELtPWxcFhOOhSr0lQ2hBNT9tBE08WK0tb6VQbCk97EpqkbgP8yXED9PZlWMiRGchJTw6S+ExzK62XMX/fw==", + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/@ethersproject/rlp/-/rlp-5.4.0.tgz", + "integrity": "sha512-0I7MZKfi+T5+G8atId9QaQKHRvvasM/kqLyAH4XxBCBchAooH2EX5rL9kYZWwcm3awYV+XC7VF6nLhfeQFKVPg==", "requires": { - "@ethersproject/bytes": "^5.2.0", - "@ethersproject/logger": "^5.2.0" + "@ethersproject/bytes": "^5.4.0", + "@ethersproject/logger": "^5.4.0" } }, "@ethersproject/sha2": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/@ethersproject/sha2/-/sha2-5.2.0.tgz", - "integrity": "sha512-Wqqptfn0PRO2mvmpktPW1HOLrrCyGtxhVQxO1ZyePoGrcEOurhICOlIvkTogoX4Q928D3Z9XtSSCUbdOJUF2kg==", + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/@ethersproject/sha2/-/sha2-5.4.0.tgz", + "integrity": "sha512-siheo36r1WD7Cy+bDdE1BJ8y0bDtqXCOxRMzPa4bV1TGt/eTUUt03BHoJNB6reWJD8A30E/pdJ8WFkq+/uz4Gg==", "requires": { - "@ethersproject/bytes": "^5.2.0", - "@ethersproject/logger": "^5.2.0", - "hash.js": "1.1.3" + "@ethersproject/bytes": "^5.4.0", + "@ethersproject/logger": "^5.4.0", + "hash.js": "1.1.7" + }, + "dependencies": { + "hash.js": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz", + "integrity": "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==", + "requires": { + "inherits": "^2.0.3", + "minimalistic-assert": "^1.0.1" + } + } } }, "@ethersproject/signing-key": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/@ethersproject/signing-key/-/signing-key-5.2.0.tgz", - "integrity": "sha512-9A+dVSkrVAPuhJnWqLWV/NkKi/KB4iagTKEuojfuApUfeIHEhpwQ0Jx3cBimk7qWISSSKdgiAmIqpvVtZ5FEkg==", + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/@ethersproject/signing-key/-/signing-key-5.4.0.tgz", + "integrity": "sha512-q8POUeywx6AKg2/jX9qBYZIAmKSB4ubGXdQ88l40hmATj29JnG5pp331nAWwwxPn2Qao4JpWHNZsQN+bPiSW9A==", "requires": { - "@ethersproject/bytes": "^5.2.0", - "@ethersproject/logger": "^5.2.0", - "@ethersproject/properties": "^5.2.0", - "bn.js": "^4.4.0", - "elliptic": "6.5.4" + "@ethersproject/bytes": "^5.4.0", + "@ethersproject/logger": "^5.4.0", + "@ethersproject/properties": "^5.4.0", + "bn.js": "^4.11.9", + "elliptic": "6.5.4", + "hash.js": "1.1.7" }, "dependencies": { "elliptic": { @@ -2301,101 +2343,110 @@ "minimalistic-assert": "^1.0.1", "minimalistic-crypto-utils": "^1.0.1" } + }, + "hash.js": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz", + "integrity": "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==", + "requires": { + "inherits": "^2.0.3", + "minimalistic-assert": "^1.0.1" + } } } }, "@ethersproject/solidity": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/@ethersproject/solidity/-/solidity-5.2.0.tgz", - "integrity": "sha512-EEFlNyEnONW3CWF8UGWPcqxJUHiaIoofO7itGwO/2gvGpnwlL+WUV+GmQoHNxmn+QJeOHspnZuh6NOVrJL6H1g==", + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/@ethersproject/solidity/-/solidity-5.4.0.tgz", + "integrity": "sha512-XFQTZ7wFSHOhHcV1DpcWj7VXECEiSrBuv7JErJvB9Uo+KfCdc3QtUZV+Vjh/AAaYgezUEKbCtE6Khjm44seevQ==", "requires": { - "@ethersproject/bignumber": "^5.2.0", - "@ethersproject/bytes": "^5.2.0", - "@ethersproject/keccak256": "^5.2.0", - "@ethersproject/sha2": "^5.2.0", - "@ethersproject/strings": "^5.2.0" + "@ethersproject/bignumber": "^5.4.0", + "@ethersproject/bytes": "^5.4.0", + "@ethersproject/keccak256": "^5.4.0", + "@ethersproject/sha2": "^5.4.0", + "@ethersproject/strings": "^5.4.0" } }, "@ethersproject/strings": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/@ethersproject/strings/-/strings-5.2.0.tgz", - "integrity": "sha512-RmjX800wRYKgrzo2ZCSlA8OCQYyq4+M46VgjSVDVyYkLZctBXC3epqlppDA24R7eo856KNbXqezZsMnHT+sSuA==", + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/@ethersproject/strings/-/strings-5.4.0.tgz", + "integrity": "sha512-k/9DkH5UGDhv7aReXLluFG5ExurwtIpUfnDNhQA29w896Dw3i4uDTz01Quaptbks1Uj9kI8wo9tmW73wcIEaWA==", "requires": { - "@ethersproject/bytes": "^5.2.0", - "@ethersproject/constants": "^5.2.0", - "@ethersproject/logger": "^5.2.0" + "@ethersproject/bytes": "^5.4.0", + "@ethersproject/constants": "^5.4.0", + "@ethersproject/logger": "^5.4.0" } }, "@ethersproject/transactions": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/@ethersproject/transactions/-/transactions-5.2.0.tgz", - "integrity": "sha512-QrGbhGYsouNNclUp3tWMbckMsuXJTOsA56kT3BuRrLlXJcUH7myIihajXdSfKcyJsvHJPrGZP+U3TKh+sLzZtg==", + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/@ethersproject/transactions/-/transactions-5.4.0.tgz", + "integrity": "sha512-s3EjZZt7xa4BkLknJZ98QGoIza94rVjaEed0rzZ/jB9WrIuu/1+tjvYCWzVrystXtDswy7TPBeIepyXwSYa4WQ==", "requires": { - "@ethersproject/address": "^5.2.0", - "@ethersproject/bignumber": "^5.2.0", - "@ethersproject/bytes": "^5.2.0", - "@ethersproject/constants": "^5.2.0", - "@ethersproject/keccak256": "^5.2.0", - "@ethersproject/logger": "^5.2.0", - "@ethersproject/properties": "^5.2.0", - "@ethersproject/rlp": "^5.2.0", - "@ethersproject/signing-key": "^5.2.0" + "@ethersproject/address": "^5.4.0", + "@ethersproject/bignumber": "^5.4.0", + "@ethersproject/bytes": "^5.4.0", + "@ethersproject/constants": "^5.4.0", + "@ethersproject/keccak256": "^5.4.0", + "@ethersproject/logger": "^5.4.0", + "@ethersproject/properties": "^5.4.0", + "@ethersproject/rlp": "^5.4.0", + "@ethersproject/signing-key": "^5.4.0" } }, "@ethersproject/units": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/@ethersproject/units/-/units-5.2.0.tgz", - "integrity": "sha512-yrwlyomXcBBHp5oSrLxlLkyHN7dVu3PO7hMbQXc00h388zU4TF3o/PAIUhh+x695wgJ19Fa8YgUWCab3a1RDwA==", + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/@ethersproject/units/-/units-5.4.0.tgz", + "integrity": "sha512-Z88krX40KCp+JqPCP5oPv5p750g+uU6gopDYRTBGcDvOASh6qhiEYCRatuM/suC4S2XW9Zz90QI35MfSrTIaFg==", "requires": { - "@ethersproject/bignumber": "^5.2.0", - "@ethersproject/constants": "^5.2.0", - "@ethersproject/logger": "^5.2.0" + "@ethersproject/bignumber": "^5.4.0", + "@ethersproject/constants": "^5.4.0", + "@ethersproject/logger": "^5.4.0" } }, "@ethersproject/wallet": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/@ethersproject/wallet/-/wallet-5.2.0.tgz", - "integrity": "sha512-uPdjZwUmAJLo1+ybR/G/rL9pv/NEcCqOsjn6RJFvG7RmwP2kS1v5C+F+ysgx2W/PxBIVT+2IEsfXLbBz8s/6Rg==", + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/@ethersproject/wallet/-/wallet-5.4.0.tgz", + "integrity": "sha512-wU29majLjM6AjCjpat21mPPviG+EpK7wY1+jzKD0fg3ui5fgedf2zEu1RDgpfIMsfn8fJHJuzM4zXZ2+hSHaSQ==", "requires": { - "@ethersproject/abstract-provider": "^5.2.0", - "@ethersproject/abstract-signer": "^5.2.0", - "@ethersproject/address": "^5.2.0", - "@ethersproject/bignumber": "^5.2.0", - "@ethersproject/bytes": "^5.2.0", - "@ethersproject/hash": "^5.2.0", - "@ethersproject/hdnode": "^5.2.0", - "@ethersproject/json-wallets": "^5.2.0", - "@ethersproject/keccak256": "^5.2.0", - "@ethersproject/logger": "^5.2.0", - "@ethersproject/properties": "^5.2.0", - "@ethersproject/random": "^5.2.0", - "@ethersproject/signing-key": "^5.2.0", - "@ethersproject/transactions": "^5.2.0", - "@ethersproject/wordlists": "^5.2.0" + "@ethersproject/abstract-provider": "^5.4.0", + "@ethersproject/abstract-signer": "^5.4.0", + "@ethersproject/address": "^5.4.0", + "@ethersproject/bignumber": "^5.4.0", + "@ethersproject/bytes": "^5.4.0", + "@ethersproject/hash": "^5.4.0", + "@ethersproject/hdnode": "^5.4.0", + "@ethersproject/json-wallets": "^5.4.0", + "@ethersproject/keccak256": "^5.4.0", + "@ethersproject/logger": "^5.4.0", + "@ethersproject/properties": "^5.4.0", + "@ethersproject/random": "^5.4.0", + "@ethersproject/signing-key": "^5.4.0", + "@ethersproject/transactions": "^5.4.0", + "@ethersproject/wordlists": "^5.4.0" } }, "@ethersproject/web": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/@ethersproject/web/-/web-5.2.0.tgz", - "integrity": "sha512-mYb9qxGlOBFR2pR6t1CZczuqqX6r8RQGn7MtwrBciMex3cvA/qs+wbmcDgl+/OZY0Pco/ih6WHQRnVi+4sBeCQ==", + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/@ethersproject/web/-/web-5.4.0.tgz", + "integrity": "sha512-1bUusGmcoRLYgMn6c1BLk1tOKUIFuTg8j+6N8lYlbMpDesnle+i3pGSagGNvwjaiLo4Y5gBibwctpPRmjrh4Og==", "requires": { - "@ethersproject/base64": "^5.2.0", - "@ethersproject/bytes": "^5.2.0", - "@ethersproject/logger": "^5.2.0", - "@ethersproject/properties": "^5.2.0", - "@ethersproject/strings": "^5.2.0" + "@ethersproject/base64": "^5.4.0", + "@ethersproject/bytes": "^5.4.0", + "@ethersproject/logger": "^5.4.0", + "@ethersproject/properties": "^5.4.0", + "@ethersproject/strings": "^5.4.0" } }, "@ethersproject/wordlists": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/@ethersproject/wordlists/-/wordlists-5.2.0.tgz", - "integrity": "sha512-/7TG5r/Zm8Wd9WhoqQ4QnntgMkIfIZ8QVrpU81muiChLD26XLOgmyiqKPL7K058uYt7UZ0wzbXjxyCYadU3xFQ==", + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/@ethersproject/wordlists/-/wordlists-5.4.0.tgz", + "integrity": "sha512-FemEkf6a+EBKEPxlzeVgUaVSodU7G0Na89jqKjmWMlDB0tomoU8RlEMgUvXyqtrg8N4cwpLh8nyRnm1Nay1isA==", "requires": { - "@ethersproject/bytes": "^5.2.0", - "@ethersproject/hash": "^5.2.0", - "@ethersproject/logger": "^5.2.0", - "@ethersproject/properties": "^5.2.0", - "@ethersproject/strings": "^5.2.0" + "@ethersproject/bytes": "^5.4.0", + "@ethersproject/hash": "^5.4.0", + "@ethersproject/logger": "^5.4.0", + "@ethersproject/properties": "^5.4.0", + "@ethersproject/strings": "^5.4.0" } }, "ethers": { @@ -2415,40 +2466,40 @@ } }, "ethersv5": { - "version": "npm:ethers@5.2.0", - "resolved": "https://registry.npmjs.org/ethers/-/ethers-5.2.0.tgz", - "integrity": "sha512-HqFGU2Qab0mAg3y1eHKVMXS4i1gTObMY0/4+x4LiO72NHhJL3Z795gnqyivmwG1J8e5NLSlRSfyIR7TL0Hw3ig==", + "version": "npm:ethers@5.4.3", + "resolved": "https://registry.npmjs.org/ethers/-/ethers-5.4.3.tgz", + "integrity": "sha512-esWqdrFZObpyZyhH6VLHCz5vRA/YJrEmQO77sALWSWFjFtJr5ITIRwJ448N+mxIrvnqjZGQ2Jx2zC3xt5lc64g==", "requires": { - "@ethersproject/abi": "5.2.0", - "@ethersproject/abstract-provider": "5.2.0", - "@ethersproject/abstract-signer": "5.2.0", - "@ethersproject/address": "5.2.0", - "@ethersproject/base64": "5.2.0", - "@ethersproject/basex": "5.2.0", - "@ethersproject/bignumber": "5.2.0", - "@ethersproject/bytes": "5.2.0", - "@ethersproject/constants": "5.2.0", - "@ethersproject/contracts": "5.2.0", - "@ethersproject/hash": "5.2.0", - "@ethersproject/hdnode": "5.2.0", - "@ethersproject/json-wallets": "5.2.0", - "@ethersproject/keccak256": "5.2.0", - "@ethersproject/logger": "5.2.0", - "@ethersproject/networks": "5.2.0", - "@ethersproject/pbkdf2": "5.2.0", - "@ethersproject/properties": "5.2.0", - "@ethersproject/providers": "5.2.0", - "@ethersproject/random": "5.2.0", - "@ethersproject/rlp": "5.2.0", - "@ethersproject/sha2": "5.2.0", - "@ethersproject/signing-key": "5.2.0", - "@ethersproject/solidity": "5.2.0", - "@ethersproject/strings": "5.2.0", - "@ethersproject/transactions": "5.2.0", - "@ethersproject/units": "5.2.0", - "@ethersproject/wallet": "5.2.0", - "@ethersproject/web": "5.2.0", - "@ethersproject/wordlists": "5.2.0" + "@ethersproject/abi": "5.4.0", + "@ethersproject/abstract-provider": "5.4.0", + "@ethersproject/abstract-signer": "5.4.1", + "@ethersproject/address": "5.4.0", + "@ethersproject/base64": "5.4.0", + "@ethersproject/basex": "5.4.0", + "@ethersproject/bignumber": "5.4.1", + "@ethersproject/bytes": "5.4.0", + "@ethersproject/constants": "5.4.0", + "@ethersproject/contracts": "5.4.1", + "@ethersproject/hash": "5.4.0", + "@ethersproject/hdnode": "5.4.0", + "@ethersproject/json-wallets": "5.4.0", + "@ethersproject/keccak256": "5.4.0", + "@ethersproject/logger": "5.4.0", + "@ethersproject/networks": "5.4.1", + "@ethersproject/pbkdf2": "5.4.0", + "@ethersproject/properties": "5.4.0", + "@ethersproject/providers": "5.4.3", + "@ethersproject/random": "5.4.0", + "@ethersproject/rlp": "5.4.0", + "@ethersproject/sha2": "5.4.0", + "@ethersproject/signing-key": "5.4.0", + "@ethersproject/solidity": "5.4.0", + "@ethersproject/strings": "5.4.0", + "@ethersproject/transactions": "5.4.0", + "@ethersproject/units": "5.4.0", + "@ethersproject/wallet": "5.4.0", + "@ethersproject/web": "5.4.0", + "@ethersproject/wordlists": "5.4.0" } }, "hash.js": { @@ -4522,6 +4573,11 @@ "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", "dev": true }, + "node-fetch": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.1.tgz", + "integrity": "sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw==" + }, "node-int64": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", diff --git a/package.json b/package.json index 60589a46..4fbb53ed 100644 --- a/package.json +++ b/package.json @@ -9,6 +9,8 @@ "build": "npm run build:esm && npm run build:cjs", "build:esm": "tsc", "build:cjs": "tsc --module commonjs --outDir dist/cjs", + "playground": "node ./dist/cjs/__TEST-SCRIPT__/playground.js", + "watch": "tsc --module commonjs --outDir dist/cjs --watch", "test": "jest", "gen-erc20-abi": "abi-types-generator ./src/ABI/erc-20-abi.json --output=./src/ABI/types --name=erc20-contract --provider=ethers_v5", "gen-uniswap-router-v2-abi": "abi-types-generator ./src/ABI/uniswap-router-v2.json --output=./src/ABI/types --name=uniswap-router-v2 --provider=ethers_v5", @@ -40,10 +42,12 @@ "ethereum-abi-types-generator": "^1.1.6", "ethereum-multicall": "^2.7.0", "ethers": "^5.0.26", + "node-fetch": "^2.6.1", "rxjs": "^6.6.3" }, "devDependencies": { "@types/jest": "^26.0.20", + "@types/node-fetch": "^2.5.12", "jest": "^24.9.0", "ts-jest": "^24.1.0", "typescript": "^4.1.3" diff --git a/src/__TEST-SCRIPT__/playground.ts b/src/__TEST-SCRIPT__/playground.ts index 6645d87f..1969a6e4 100644 --- a/src/__TEST-SCRIPT__/playground.ts +++ b/src/__TEST-SCRIPT__/playground.ts @@ -15,7 +15,7 @@ import { ETH, EthersProvider, TradeDirection } from '../index'; const routeTest = async () => { const fromTokenContractAddress = ETH.MAINNET().contractAddress; //'0xEf0e839Cf88E47be676E72D5a9cB6CED99FaD1CF'; const toTokenContractAddress = '0x1f9840a85d5af5bf1d1762f925bdaddc4201f984'; // 0x1985365e9f78359a9B6AD760e32412f4a445E862 - const ethereumAddress = '0xB1E6079212888f0bE0cf55874B2EB9d7a5e02cD9'; + const ethereumAddress = '0x37c81284caA97131339415687d192BF7D18F0f2a'; const uniswapPair = new UniswapPair({ fromTokenContractAddress, @@ -30,17 +30,22 @@ const routeTest = async () => { deadlineMinutes: 20, disableMultihops: false, uniswapVersions: [UniswapVersion.v2, UniswapVersion.v3], + // gasSettings: { + // getGasPrice: async () => 100000000000, + // }, }), }); + const startTime = new Date().getTime(); + const uniswapPairFactory = await uniswapPair.createFactory(); - const trade = await uniswapPairFactory.trade( - '0.00000000001', - TradeDirection.input - ); + const trade = await uniswapPairFactory.trade('0.0001', TradeDirection.input); + + console.log(new Date().getTime() - startTime); + // console.log(JSON.stringify(trade, null, 4)); - console.log(trade); + // console.log(trade); // console.log( // trade.allTriedRoutesQuotes.filter( // (c) => c.uniswapVersion === UniswapVersion.v3 @@ -48,7 +53,11 @@ const routeTest = async () => { // ); const ethers = new EthersProvider({ chainId: ChainId.MAINNET }); - console.log('gas', await ethers.provider.estimateGas(trade.transaction)); + await ethers.provider.estimateGas(trade.transaction); + // console.log( + // 'gas', + // (await ethers.provider.estimateGas(trade.transaction)).toHexString() + // ); process.stdin.resume(); From 13546d5c8ba51cc4a0f110c16cadddff79fa0fa2 Mon Sep 17 00:00:00 2001 From: Josh Stevens Date: Mon, 2 Aug 2021 18:39:15 +0100 Subject: [PATCH 2/8] coin gecko --- src/coin-gecko/index.ts | 71 ++++++++++++++++++++ src/coin-gecko/models/coin-gecko-response.ts | 3 + 2 files changed, 74 insertions(+) create mode 100644 src/coin-gecko/index.ts create mode 100644 src/coin-gecko/models/coin-gecko-response.ts diff --git a/src/coin-gecko/index.ts b/src/coin-gecko/index.ts new file mode 100644 index 00000000..d7bf8b11 --- /dev/null +++ b/src/coin-gecko/index.ts @@ -0,0 +1,71 @@ +import fetch from 'node-fetch'; +import { removeEthFromContractAddress } from '../common/tokens'; +import { deepClone } from '../common/utils/deep-clone'; +import { getAddress } from '../common/utils/get-address'; +import { CoinGeckoResponse } from './models/coin-gecko-response'; + +export class CoinGecko { + private _fiatPriceCache: + | { + cachedResponse: CoinGeckoResponse; + timestamp: number; + } + | undefined = undefined; + // 90 seconds cache + private _cacheMilliseconds = 90000; + constructor() {} + + /** + * Get the coin gecko fiat price + * @param contractAddress The array of contract addresses + */ + public async getCoinGeckoFiatPrices( + contractAddresses: string[] + ): Promise { + contractAddresses = contractAddresses.map((address) => + removeEthFromContractAddress(address) + ); + + if (this._fiatPriceCache) { + const now = Date.now(); + if ( + deepClone(this._fiatPriceCache.timestamp) > + now - this._cacheMilliseconds + ) { + return this._fiatPriceCache.cachedResponse; + } + } + + try { + const coinGeckoResponse: CoinGeckoResponse = {}; + + const response = await ( + await fetch( + `https://api.coingecko.com/api/v3/simple/token_price/ethereum?contract_addresses=${contractAddresses.join()}&vs_currencies=usd` + ) + ).json(); + + for (const [key, value] of Object.entries(response)) { + for (let i = 0; i < contractAddresses.length; i++) { + const mappedKey = getAddress(key); + // @ts-ignore + coinGeckoResponse[mappedKey] = Number(value['usd']); + } + } + + this._fiatPriceCache = { + cachedResponse: coinGeckoResponse, + timestamp: Date.now(), + }; + + return coinGeckoResponse; + } catch (e) { + // if coin gecko is down for any reason still allow the swapper to work + if (this._fiatPriceCache) { + return this._fiatPriceCache.cachedResponse; + } + + return {}; + } + } +} diff --git a/src/coin-gecko/models/coin-gecko-response.ts b/src/coin-gecko/models/coin-gecko-response.ts new file mode 100644 index 00000000..70eb36df --- /dev/null +++ b/src/coin-gecko/models/coin-gecko-response.ts @@ -0,0 +1,3 @@ +export interface CoinGeckoResponse { + [key: string]: number; +} From cc7b5c9c3e5d64e56a5f43fa1ebdc36d8ae10e58 Mon Sep 17 00:00:00 2001 From: Josh Stevens Date: Mon, 2 Aug 2021 18:39:36 +0100 Subject: [PATCH 3/8] cater for gas within quotes if enabled a lot of moving around --- src/factories/pair/models/gas-settings.ts | 3 + .../pair/models/uniswap-pair-settings.ts | 4 + src/factories/pair/uniswap-pair.factory.ts | 767 +------------- src/factories/pair/uniswap-pair.ts | 3 +- .../router/models/best-route-quotes.ts | 4 + .../models/route-quote-trade-context.ts | 7 + src/factories/router/models/route-quote.ts | 4 + .../router/uniswap-router.factory.ts | 974 +++++++++++++++++- .../token/models/allowance-balance-of.ts | 3 +- src/factories/token/token.factory.ts | 84 +- src/factories/token/tokens.factory.ts | 229 ++-- 11 files changed, 1182 insertions(+), 900 deletions(-) create mode 100644 src/factories/pair/models/gas-settings.ts create mode 100644 src/factories/router/models/route-quote-trade-context.ts diff --git a/src/factories/pair/models/gas-settings.ts b/src/factories/pair/models/gas-settings.ts new file mode 100644 index 00000000..429913cd --- /dev/null +++ b/src/factories/pair/models/gas-settings.ts @@ -0,0 +1,3 @@ +export interface GasSettings { + getGasPrice: () => Promise; +} diff --git a/src/factories/pair/models/uniswap-pair-settings.ts b/src/factories/pair/models/uniswap-pair-settings.ts index 556d3e0a..4b3840bf 100644 --- a/src/factories/pair/models/uniswap-pair-settings.ts +++ b/src/factories/pair/models/uniswap-pair-settings.ts @@ -1,22 +1,26 @@ import { ErrorCodes } from '../../../common/errors/error-codes'; import { UniswapError } from '../../../common/errors/uniswap-error'; import { UniswapVersion } from '../../../enums/uniswap-version'; +import { GasSettings } from './gas-settings'; export class UniswapPairSettings { slippage: number; deadlineMinutes: number; disableMultihops: boolean; uniswapVersions: UniswapVersion[] = [UniswapVersion.v2, UniswapVersion.v3]; + gasSettings?: GasSettings = undefined; constructor(settings?: { slippage?: number | undefined; deadlineMinutes?: number | undefined; disableMultihops?: boolean | undefined; uniswapVersions?: UniswapVersion[] | undefined; + gasSettings?: GasSettings | undefined; }) { this.slippage = settings?.slippage || 0.005; this.deadlineMinutes = settings?.deadlineMinutes || 20; this.disableMultihops = settings?.disableMultihops || false; + this.gasSettings = settings?.gasSettings; if ( Array.isArray(settings?.uniswapVersions) && diff --git a/src/factories/pair/uniswap-pair.factory.ts b/src/factories/pair/uniswap-pair.factory.ts index 898caf73..5c32a3d7 100644 --- a/src/factories/pair/uniswap-pair.factory.ts +++ b/src/factories/pair/uniswap-pair.factory.ts @@ -1,9 +1,6 @@ import BigNumber from 'bignumber.js'; import { Subject } from 'rxjs'; -import { - ExactInputSingleRequest, - ExactOutputSingleRequest, -} from '../../ABI/types/uniswap-router-v3'; +import { CoinGecko } from '../../coin-gecko'; import { Constants } from '../../common/constants'; import { ErrorCodes } from '../../common/errors/error-codes'; import { UniswapError } from '../../common/errors/uniswap-error'; @@ -12,9 +9,6 @@ import { turnTokenIntoEthForResponse, } from '../../common/tokens/eth'; import { deepClone } from '../../common/utils/deep-clone'; -import { hexlify } from '../../common/utils/hexlify'; -import { parseEther } from '../../common/utils/parse-ether'; -import { toEthersBigNumber } from '../../common/utils/to-ethers-big-number'; import { getTradePath } from '../../common/utils/trade-path'; import { TradePath } from '../../enums/trade-path'; import { UniswapVersion } from '../../enums/uniswap-version'; @@ -24,13 +18,9 @@ import { AllPossibleRoutes } from '../router/models/all-possible-routes'; import { BestRouteQuotes } from '../router/models/best-route-quotes'; import { RouteQuote } from '../router/models/route-quote'; import { UniswapRouterFactory } from '../router/uniswap-router.factory'; -import { UniswapRouterContractFactoryV2 } from '../router/v2/uniswap-router-contract.factory.v2'; -import { percentToFeeAmount } from '../router/v3/enums/fee-amount-v3'; -import { UniswapRouterContractFactoryV3 } from '../router/v3/uniswap-router-contract.factory.v3'; import { AllowanceAndBalanceOf } from '../token/models/allowance-balance-of'; import { Token } from '../token/models/token'; import { TokenFactory } from '../token/token.factory'; -import { TokensFactory } from '../token/tokens.factory'; import { CurrentTradeContext } from './models/current-trade-context'; import { TradeContext } from './models/trade-context'; import { TradeDirection } from './models/trade-direction'; @@ -48,22 +38,12 @@ export class UniswapPairFactory { this._uniswapPairFactoryContext.ethersProvider ); - private _tokensFactory = new TokensFactory( - this._uniswapPairFactoryContext.ethersProvider - ); - - private _uniswapRouterContractFactoryV2 = new UniswapRouterContractFactoryV2( - this._uniswapPairFactoryContext.ethersProvider - ); - private _uniswapRouterContractFactoryV3 = new UniswapRouterContractFactoryV3( - this._uniswapPairFactoryContext.ethersProvider - ); - private _uniswapRouterFactory = new UniswapRouterFactory( + this._coinGecko, + this._uniswapPairFactoryContext.ethereumAddress, this._uniswapPairFactoryContext.fromToken, this._uniswapPairFactoryContext.toToken, - this._uniswapPairFactoryContext.settings.disableMultihops, - this._uniswapPairFactoryContext.settings.uniswapVersions, + this._uniswapPairFactoryContext.settings, this._uniswapPairFactoryContext.ethersProvider ); @@ -71,7 +51,10 @@ export class UniswapPairFactory { private _currentTradeContext: CurrentTradeContext | undefined; private _quoteChanged$: Subject = new Subject(); - constructor(private _uniswapPairFactoryContext: UniswapPairFactoryContext) {} + constructor( + private _coinGecko: CoinGecko, + private _uniswapPairFactoryContext: UniswapPairFactoryContext + ) {} /** * The to token @@ -99,7 +82,8 @@ export class UniswapPairFactory { */ public async getFromTokenBalance(): Promise { if (this.tradePath() === TradePath.ethToErc20) { - const ethBalanceContext = await this.getEthBalance(); + const ethBalanceContext = + await this._uniswapRouterFactory.getEthBalance(); return ethBalanceContext.toFixed(); } @@ -117,7 +101,8 @@ export class UniswapPairFactory { */ public async getToTokenBalance(): Promise { if (this.tradePath() === TradePath.erc20ToEth) { - const ethBalanceContext = await this.getEthBalance(); + const ethBalanceContext = + await this._uniswapRouterFactory.getEthBalance(); return ethBalanceContext.toFixed(); } @@ -221,144 +206,11 @@ export class UniswapPairFactory { return await this._routes.getAllPossibleRoutes(); } - /** - * Has got enough allowance to do the trade - * @param uniswapVersion The uniswap version - * @param amount The amount you want to swap - */ - public async hasGotEnoughAllowance( - uniswapVersion: UniswapVersion, - amount: string - ): Promise { - if (this.tradePath() === TradePath.ethToErc20) { - return true; - } - - const allowance = await this.allowance(uniswapVersion); - - return this._hasGotEnoughAllowance(amount, allowance); - } - - /** - * Has got enough allowance to do the trade - * @param amount The amount you want to swap - */ - private _hasGotEnoughAllowance(amount: string, allowance: string): boolean { - if (this.tradePath() === TradePath.ethToErc20) { - return true; - } - - const bigNumberAllowance = new BigNumber(allowance).shiftedBy( - this.fromToken.decimals * -1 - ); - - if (new BigNumber(amount).isGreaterThan(bigNumberAllowance)) { - return false; - } - - return true; - } - - /** - * Has got enough balance to do the trade (erc20 check only) - * @param amount The amount you want to swap - */ - private hasGotEnoughBalanceErc20( - amount: string, - balance: string - ): { - hasEnough: boolean; - balance: string; - } { - const bigNumberBalance = new BigNumber(balance).shiftedBy( - this.fromToken.decimals * -1 - ); - - if (new BigNumber(amount).isGreaterThan(bigNumberBalance)) { - return { - hasEnough: false, - balance: bigNumberBalance.toFixed(), - }; - } - - return { - hasEnough: true, - balance: bigNumberBalance.toFixed(), - }; - } - - /** - * Has got enough balance to do the trade (eth check only) - * @param amount The amount you want to swap - */ - private async hasGotEnoughBalanceEth(amount: string): Promise<{ - hasEnough: boolean; - balance: string; - }> { - const balance = await this.getEthBalance(); - - if (new BigNumber(amount).isGreaterThan(balance)) { - return { - hasEnough: false, - balance: balance.toFixed(), - }; - } - - return { - hasEnough: true, - balance: balance.toFixed(), - }; - } - - /** - * Get eth balance - */ - private async getEthBalance(): Promise { - const balance = - await this._uniswapPairFactoryContext.ethersProvider.balanceOf( - this._uniswapPairFactoryContext.ethereumAddress - ); - - return new BigNumber(balance).shiftedBy(Constants.ETH_MAX_DECIMALS * -1); - } - - /** - * Get the allowance and balance for the from and to token (will get balance for eth as well) - * @param uniswapVersion The uniswap version - */ - public async getAllowanceAndBalanceForTokens( - uniswapVersion: UniswapVersion - ): Promise<{ - fromToken: AllowanceAndBalanceOf; - toToken: AllowanceAndBalanceOf; - }> { - const allowanceAndBalanceOfForTokens = - await this._tokensFactory.getAllowanceAndBalanceOfForContracts( - uniswapVersion, - this._uniswapPairFactoryContext.ethereumAddress, - [this.fromToken.contractAddress, this.toToken.contractAddress], - false - ); - - return { - fromToken: allowanceAndBalanceOfForTokens.find( - (c) => c.token.contractAddress === this.fromToken.contractAddress - )!.allowanceAndBalanceOf, - toToken: allowanceAndBalanceOfForTokens.find( - (c) => c.token.contractAddress === this.toToken.contractAddress - )!.allowanceAndBalanceOf, - }; - } - /** * Get the allowance and balance for the from token (erc20 > blah) only - * @param uniswapVersion The uniswap version */ - public async getAllowanceAndBalanceOfForFromToken( - uniswapVersion: UniswapVersion - ): Promise { + public async getAllowanceAndBalanceOfForFromToken(): Promise { return await this._fromTokenFactory.getAllowanceAndBalanceOf( - uniswapVersion, this._uniswapPairFactoryContext.ethereumAddress ); } @@ -367,11 +219,8 @@ export class UniswapPairFactory { * Get the allowance and balance for to from token (eth > erc20) only * @param uniswapVersion The uniswap version */ - public async getAllowanceAndBalanceOfForToToken( - uniswapVersion: UniswapVersion - ): Promise { + public async getAllowanceAndBalanceOfForToToken(): Promise { return await this._toTokenFactory.getAllowanceAndBalanceOf( - uniswapVersion, this._uniswapPairFactoryContext.ethereumAddress ); } @@ -465,65 +314,18 @@ export class UniswapPairFactory { const bestRouteQuote = bestRouteQuotes.bestRouteQuote; - const convertQuoteWithSlippage = new BigNumber( - bestRouteQuote.expectedConvertQuote - ).minus( - new BigNumber(bestRouteQuote.expectedConvertQuote) - .times(this._uniswapPairFactoryContext.settings.slippage) - .toFixed(this.fromToken.decimals) - ); - - const tokenAmountInMax = new BigNumber( - bestRouteQuote.expectedConvertQuote - ).plus( - new BigNumber(bestRouteQuote.expectedConvertQuote) - .times(this._uniswapPairFactoryContext.settings.slippage) - .toFixed(this.fromToken.decimals) - ); - - const tradeExpires = this.generateTradeDeadlineUnixTime(); - - const data = - direction === TradeDirection.input - ? this.generateTradeDataErc20ToEthInput( - baseConvertRequest, - convertQuoteWithSlippage, - bestRouteQuote, - tradeExpires.toString() - ) - : this.generateTradeDataErc20ToEthOutput( - tokenAmountInMax, - baseConvertRequest, - bestRouteQuote, - tradeExpires.toString() - ); - - const allowanceAndBalancesForTokens = - await this.getAllowanceAndBalanceForTokens(bestRouteQuote.uniswapVersion); - - const hasEnoughAllowance = - direction === TradeDirection.input - ? this._hasGotEnoughAllowance( - baseConvertRequest.toFixed(), - allowanceAndBalancesForTokens.fromToken.allowance - ) - : this._hasGotEnoughAllowance( - bestRouteQuote.expectedConvertQuote, - allowanceAndBalancesForTokens.fromToken.allowance - ); - const tradeContext: TradeContext = { uniswapVersion: bestRouteQuote.uniswapVersion, quoteDirection: direction, baseConvertRequest: baseConvertRequest.toFixed(), minAmountConvertQuote: direction === TradeDirection.input - ? convertQuoteWithSlippage.toFixed(this.toToken.decimals) + ? bestRouteQuote.expectedConvertQuoteOrTokenAmountInMaxWithSlippage : null, maximumSent: direction === TradeDirection.input ? null - : tokenAmountInMax.toFixed(this.toToken.decimals), + : bestRouteQuote.expectedConvertQuoteOrTokenAmountInMaxWithSlippage, expectedConvertQuote: bestRouteQuote.expectedConvertQuote, liquidityProviderFee: direction === TradeDirection.input @@ -534,37 +336,28 @@ export class UniswapPairFactory { .times(bestRouteQuote.liquidityProviderFee) .toFixed(this.fromToken.decimals), liquidityProviderFeePercent: bestRouteQuote.liquidityProviderFee, - tradeExpires, + tradeExpires: bestRouteQuote.tradeExpires, routePathTokenMap: bestRouteQuote.routePathArrayTokenMap, routeText: bestRouteQuote.routeText, routePath: bestRouteQuote.routePathArray.map((r) => removeEthFromContractAddress(r) ), - hasEnoughAllowance, - approvalTransaction: !hasEnoughAllowance + hasEnoughAllowance: bestRouteQuotes.hasEnoughAllowance, + approvalTransaction: !bestRouteQuotes.hasEnoughAllowance ? await this.generateApproveMaxAllowanceData( bestRouteQuote.uniswapVersion ) : undefined, toToken: turnTokenIntoEthForResponse(this.toToken), - toBalance: new BigNumber(allowanceAndBalancesForTokens.toToken.balanceOf) + toBalance: new BigNumber(bestRouteQuotes.toBalance) .shiftedBy(this.toToken.decimals * -1) .toFixed(), fromToken: this.fromToken, - fromBalance: - direction === TradeDirection.input - ? this.hasGotEnoughBalanceErc20( - baseConvertRequest.toFixed(), - allowanceAndBalancesForTokens.fromToken.balanceOf - ) - : this.hasGotEnoughBalanceErc20( - bestRouteQuote.expectedConvertQuote, - allowanceAndBalancesForTokens.fromToken.balanceOf - ), - transaction: this.buildUpTransactionErc20( - bestRouteQuote.uniswapVersion, - data - ), + fromBalance: { + hasEnough: bestRouteQuotes.hasEnoughBalance, + balance: bestRouteQuotes.fromBalance, + }, + transaction: bestRouteQuote.transaction, allTriedRoutesQuotes: bestRouteQuotes.triedRoutesQuote, quoteChanged$: this._quoteChanged$, destroy: () => this.destroy(), @@ -588,65 +381,18 @@ export class UniswapPairFactory { ); const bestRouteQuote = bestRouteQuotes.bestRouteQuote; - const convertQuoteWithSlippage = new BigNumber( - bestRouteQuote.expectedConvertQuote - ).minus( - new BigNumber(bestRouteQuote.expectedConvertQuote) - .times(this._uniswapPairFactoryContext.settings.slippage) - .toFixed(this.fromToken.decimals) - ); - - const tokenAmountInMax = new BigNumber( - bestRouteQuote.expectedConvertQuote - ).plus( - new BigNumber(bestRouteQuote.expectedConvertQuote) - .times(this._uniswapPairFactoryContext.settings.slippage) - .toFixed(this.fromToken.decimals) - ); - - const tradeExpires = this.generateTradeDeadlineUnixTime(); - - const data = - direction === TradeDirection.input - ? this.generateTradeDataErc20ToErc20Input( - baseConvertRequest, - convertQuoteWithSlippage, - bestRouteQuote, - tradeExpires.toString() - ) - : this.generateTradeDataErc20ToErc20Output( - tokenAmountInMax, - baseConvertRequest, - bestRouteQuote, - tradeExpires.toString() - ); - - const allowanceAndBalancesForTokens = - await this.getAllowanceAndBalanceForTokens(bestRouteQuote.uniswapVersion); - - const hasEnoughAllowance = - direction === TradeDirection.input - ? this._hasGotEnoughAllowance( - baseConvertRequest.toFixed(), - allowanceAndBalancesForTokens.fromToken.allowance - ) - : this._hasGotEnoughAllowance( - bestRouteQuote.expectedConvertQuote, - allowanceAndBalancesForTokens.fromToken.allowance - ); - const tradeContext: TradeContext = { uniswapVersion: bestRouteQuote.uniswapVersion, quoteDirection: direction, baseConvertRequest: baseConvertRequest.toFixed(), minAmountConvertQuote: direction === TradeDirection.input - ? convertQuoteWithSlippage.toFixed(this.toToken.decimals) + ? bestRouteQuote.expectedConvertQuoteOrTokenAmountInMaxWithSlippage : null, maximumSent: direction === TradeDirection.input ? null - : tokenAmountInMax.toFixed(this.toToken.decimals), + : bestRouteQuote.expectedConvertQuoteOrTokenAmountInMaxWithSlippage, expectedConvertQuote: bestRouteQuote.expectedConvertQuote, liquidityProviderFee: direction === TradeDirection.input @@ -657,35 +403,26 @@ export class UniswapPairFactory { .times(bestRouteQuote.liquidityProviderFee) .toFixed(this.fromToken.decimals), liquidityProviderFeePercent: bestRouteQuote.liquidityProviderFee, - tradeExpires, + tradeExpires: bestRouteQuote.tradeExpires, routePathTokenMap: bestRouteQuote.routePathArrayTokenMap, routeText: bestRouteQuote.routeText, routePath: bestRouteQuote.routePathArray, - hasEnoughAllowance, - approvalTransaction: !hasEnoughAllowance + hasEnoughAllowance: bestRouteQuotes.hasEnoughAllowance, + approvalTransaction: !bestRouteQuotes.hasEnoughAllowance ? await this.generateApproveMaxAllowanceData( bestRouteQuote.uniswapVersion ) : undefined, toToken: this.toToken, - toBalance: new BigNumber(allowanceAndBalancesForTokens.toToken.balanceOf) + toBalance: new BigNumber(bestRouteQuotes.toBalance) .shiftedBy(this.toToken.decimals * -1) .toFixed(), fromToken: this.fromToken, - fromBalance: - direction === TradeDirection.input - ? this.hasGotEnoughBalanceErc20( - baseConvertRequest.toFixed(), - allowanceAndBalancesForTokens.fromToken.balanceOf - ) - : this.hasGotEnoughBalanceErc20( - bestRouteQuote.expectedConvertQuote, - allowanceAndBalancesForTokens.fromToken.balanceOf - ), - transaction: this.buildUpTransactionErc20( - bestRouteQuote.uniswapVersion, - data - ), + fromBalance: { + hasEnough: bestRouteQuotes.hasEnoughBalance, + balance: bestRouteQuotes.fromBalance, + }, + transaction: bestRouteQuote.transaction, allTriedRoutesQuotes: bestRouteQuotes.triedRoutesQuote, quoteChanged$: this._quoteChanged$, destroy: () => this.destroy(), @@ -709,54 +446,18 @@ export class UniswapPairFactory { ); const bestRouteQuote = bestRouteQuotes.bestRouteQuote; - const convertQuoteWithSlippage = new BigNumber( - bestRouteQuote.expectedConvertQuote - ).minus( - new BigNumber(bestRouteQuote.expectedConvertQuote) - .times(this._uniswapPairFactoryContext.settings.slippage) - .toFixed(this.toToken.decimals) - ); - - const tokenAmountInMax = new BigNumber( - bestRouteQuote.expectedConvertQuote - ).plus( - new BigNumber(bestRouteQuote.expectedConvertQuote) - .times(this._uniswapPairFactoryContext.settings.slippage) - .toFixed(this.fromToken.decimals) - ); - - const tradeExpires = this.generateTradeDeadlineUnixTime(); - - const data = - direction === TradeDirection.input - ? this.generateTradeDataEthToErc20Input( - baseConvertRequest, - convertQuoteWithSlippage, - bestRouteQuote, - tradeExpires.toString() - ) - : this.generateTradeDataEthToErc20Output( - tokenAmountInMax, - baseConvertRequest, - bestRouteQuote, - tradeExpires.toString() - ); - - const allowanceAndBalancesForTokens = - await this.getAllowanceAndBalanceForTokens(bestRouteQuote.uniswapVersion); - const tradeContext: TradeContext = { uniswapVersion: bestRouteQuote.uniswapVersion, quoteDirection: direction, baseConvertRequest: baseConvertRequest.toFixed(), minAmountConvertQuote: direction === TradeDirection.input - ? convertQuoteWithSlippage.toFixed(this.toToken.decimals) + ? bestRouteQuote.expectedConvertQuoteOrTokenAmountInMaxWithSlippage : null, maximumSent: direction === TradeDirection.input ? null - : tokenAmountInMax.toFixed(this.toToken.decimals), + : bestRouteQuote.expectedConvertQuoteOrTokenAmountInMaxWithSlippage, expectedConvertQuote: bestRouteQuote.expectedConvertQuote, liquidityProviderFee: direction === TradeDirection.input @@ -767,7 +468,7 @@ export class UniswapPairFactory { .times(bestRouteQuote.liquidityProviderFee) .toFixed(this.fromToken.decimals), liquidityProviderFeePercent: bestRouteQuote.liquidityProviderFee, - tradeExpires, + tradeExpires: bestRouteQuote.tradeExpires, routePathTokenMap: bestRouteQuote.routePathArrayTokenMap, routeText: bestRouteQuote.routeText, routePath: bestRouteQuote.routePathArray.map((r) => @@ -775,22 +476,15 @@ export class UniswapPairFactory { ), hasEnoughAllowance: true, toToken: this.toToken, - toBalance: new BigNumber(allowanceAndBalancesForTokens.toToken.balanceOf) + toBalance: new BigNumber(bestRouteQuotes.toBalance) .shiftedBy(this.toToken.decimals * -1) .toFixed(), fromToken: turnTokenIntoEthForResponse(this.fromToken), - fromBalance: await this.hasGotEnoughBalanceEth( - direction === TradeDirection.input - ? baseConvertRequest.toFixed() - : bestRouteQuote.expectedConvertQuote - ), - transaction: this.buildUpTransactionEth( - bestRouteQuote.uniswapVersion, - direction === TradeDirection.input - ? baseConvertRequest - : new BigNumber(bestRouteQuote.expectedConvertQuote), - data - ), + fromBalance: { + hasEnough: bestRouteQuotes.hasEnoughBalance, + balance: bestRouteQuotes.fromBalance, + }, + transaction: bestRouteQuote.transaction, allTriedRoutesQuotes: bestRouteQuotes.triedRoutesQuote, quoteChanged$: this._quoteChanged$, destroy: () => this.destroy(), @@ -799,363 +493,6 @@ export class UniswapPairFactory { return tradeContext; } - /** - * Generate trade data eth > erc20 - * @param tokenAmount The token amount - * @param routeQuote The route quote - * @param deadline The deadline it expiries unix time - */ - private generateTradeDataEthToErc20Input( - ethAmountIn: BigNumber, - tokenAmount: BigNumber, - routeQuote: RouteQuote, - deadline: string - ): string { - // uniswap adds extra digits on even if the token is say 8 digits long - const convertedMinTokens = tokenAmount - .shiftedBy(this.toToken.decimals) - .decimalPlaces(0); - - switch (routeQuote.uniswapVersion) { - case UniswapVersion.v2: - return this._uniswapRouterContractFactoryV2.swapExactETHForTokens( - hexlify(convertedMinTokens), - routeQuote.routePathArray.map((r) => removeEthFromContractAddress(r)), - this._uniswapPairFactoryContext.ethereumAddress, - deadline - ); - case UniswapVersion.v3: - return this.generateTradeDataForV3Input( - parseEther(ethAmountIn), - convertedMinTokens, - routeQuote.liquidityProviderFee, - deadline - ); - default: - throw new UniswapError( - 'Uniswap version not supported', - ErrorCodes.uniswapVersionNotSupported - ); - } - } - - /** - * Generate trade data eth > erc20 - * @param tokenAmountInMax The amount in max - * @param ethAmountOut The amount to receive - * @param routeQuote The route quote - * @param deadline The deadline it expiries unix time - */ - private generateTradeDataEthToErc20Output( - ethAmountInMax: BigNumber, - tokenAmountOut: BigNumber, - routeQuote: RouteQuote, - deadline: string - ): string { - const amountOut = tokenAmountOut - .shiftedBy(this.toToken.decimals) - .decimalPlaces(0); - - switch (routeQuote.uniswapVersion) { - case UniswapVersion.v2: - return this._uniswapRouterContractFactoryV2.swapETHForExactTokens( - hexlify(amountOut), - routeQuote.routePathArray.map((r) => removeEthFromContractAddress(r)), - this._uniswapPairFactoryContext.ethereumAddress, - deadline - ); - case UniswapVersion.v3: - return this.generateTradeDataForV3Output( - amountOut, - parseEther(ethAmountInMax), - routeQuote, - deadline - ); - default: - throw new UniswapError( - 'Uniswap version not supported', - ErrorCodes.uniswapVersionNotSupported - ); - } - } - - /** - * Generate trade amount erc20 > eth for input direction - * @param tokenAmount The amount in - * @param ethAmountOutMin The min amount to receive - * @param routeQuote The route quote - * @param deadline The deadline it expiries unix time - */ - private generateTradeDataErc20ToEthInput( - tokenAmount: BigNumber, - ethAmountOutMin: BigNumber, - routeQuote: RouteQuote, - deadline: string - ): string { - // uniswap adds extra digits on even if the token is say 8 digits long - const amountIn = tokenAmount - .shiftedBy(this.fromToken.decimals) - .decimalPlaces(0); - - switch (routeQuote.uniswapVersion) { - case UniswapVersion.v2: - return this._uniswapRouterContractFactoryV2.swapExactTokensForETH( - hexlify(amountIn), - hexlify(parseEther(ethAmountOutMin)), - routeQuote.routePathArray.map((r) => removeEthFromContractAddress(r)), - this._uniswapPairFactoryContext.ethereumAddress, - deadline - ); - case UniswapVersion.v3: - return this.generateTradeDataForV3Input( - amountIn, - parseEther(ethAmountOutMin), - routeQuote.liquidityProviderFee, - deadline - ); - default: - throw new UniswapError( - 'Uniswap version not supported', - ErrorCodes.uniswapVersionNotSupported - ); - } - } - - /** - * Generate trade amount erc20 > eth for input direction - * @param tokenAmountInMax The amount in max - * @param ethAmountOut The amount to receive - * @param routeQuote The route quote - * @param deadline The deadline it expiries unix time - */ - private generateTradeDataErc20ToEthOutput( - tokenAmountInMax: BigNumber, - ethAmountOut: BigNumber, - routeQuote: RouteQuote, - deadline: string - ): string { - // uniswap adds extra digits on even if the token is say 8 digits long - const amountInMax = tokenAmountInMax - .shiftedBy(this.fromToken.decimals) - .decimalPlaces(0); - - switch (routeQuote.uniswapVersion) { - case UniswapVersion.v2: - return this._uniswapRouterContractFactoryV2.swapTokensForExactETH( - hexlify(parseEther(ethAmountOut)), - hexlify(amountInMax), - routeQuote.routePathArray.map((r) => removeEthFromContractAddress(r)), - this._uniswapPairFactoryContext.ethereumAddress, - deadline - ); - case UniswapVersion.v3: - return this.generateTradeDataForV3Output( - parseEther(ethAmountOut), - amountInMax, - routeQuote, - deadline - ); - default: - throw new UniswapError( - 'Uniswap version not supported', - ErrorCodes.uniswapVersionNotSupported - ); - } - } - - /** - * Generate trade amount erc20 > erc20 for input - * @param tokenAmount The token amount - * @param tokenAmountOut The min token amount out - * @param routeQuote The route quote - * @param deadline The deadline it expiries unix time - */ - private generateTradeDataErc20ToErc20Input( - tokenAmount: BigNumber, - tokenAmountMin: BigNumber, - routeQuote: RouteQuote, - deadline: string - ): string { - // uniswap adds extra digits on even if the token is say 8 digits long - const amountIn = tokenAmount - .shiftedBy(this.fromToken.decimals) - .decimalPlaces(0); - const amountMin = tokenAmountMin - .shiftedBy(this.toToken.decimals) - .decimalPlaces(0); - - switch (routeQuote.uniswapVersion) { - case UniswapVersion.v2: - return this._uniswapRouterContractFactoryV2.swapExactTokensForTokens( - hexlify(amountIn), - hexlify(amountMin), - routeQuote.routePathArray, - this._uniswapPairFactoryContext.ethereumAddress, - deadline - ); - case UniswapVersion.v3: - return this.generateTradeDataForV3Input( - amountIn, - amountMin, - routeQuote.liquidityProviderFee, - deadline - ); - default: - throw new UniswapError( - 'Uniswap version not supported', - ErrorCodes.uniswapVersionNotSupported - ); - } - } - - /** - * Generate trade amount erc20 > erc20 for output - * @param tokenAmount The token amount - * @param tokenAmountOut The min token amount out - * @param routeQuote The route quote - * @param deadline The deadline it expiries unix time - */ - private generateTradeDataErc20ToErc20Output( - tokenAmountInMax: BigNumber, - tokenAmountOut: BigNumber, - routeQuote: RouteQuote, - deadline: string - ): string { - // uniswap adds extra digits on even if the token is say 8 digits long - const amountInMax = tokenAmountInMax - .shiftedBy(this.fromToken.decimals) - .decimalPlaces(0); - - const amountOut = tokenAmountOut - .shiftedBy(this.toToken.decimals) - .decimalPlaces(0); - - switch (routeQuote.uniswapVersion) { - case UniswapVersion.v2: - return this._uniswapRouterContractFactoryV2.swapTokensForExactTokens( - hexlify(amountOut), - hexlify(amountInMax), - routeQuote.routePathArray, - this._uniswapPairFactoryContext.ethereumAddress, - deadline - ); - case UniswapVersion.v3: - return this.generateTradeDataForV3Output( - amountOut, - amountInMax, - routeQuote, - deadline - ); - default: - throw new UniswapError( - 'Uniswap version not supported', - ErrorCodes.uniswapVersionNotSupported - ); - } - } - - /** - * Generate trade data for v3 - * @param tokenAmount The token amount - * @param tokenAmountOut The min token amount out - * @param liquidityProviderFee The liquidity provider fee - * @param deadline The deadline it expiries unix time - */ - private generateTradeDataForV3Input( - tokenAmount: BigNumber, - tokenAmountMin: BigNumber, - liquidityProviderFee: number, - deadline: string - ): string { - const params: ExactInputSingleRequest = { - tokenIn: removeEthFromContractAddress( - this._uniswapPairFactoryContext.fromToken.contractAddress - ), - tokenOut: removeEthFromContractAddress( - this._uniswapPairFactoryContext.toToken.contractAddress - ), - fee: percentToFeeAmount(liquidityProviderFee), - recipient: this._uniswapPairFactoryContext.ethereumAddress, - deadline, - amountIn: hexlify(tokenAmount), - amountOutMinimum: hexlify(tokenAmountMin), - sqrtPriceLimitX96: 0, - }; - - return this._uniswapRouterContractFactoryV3.exactInputSingle(params); - } - - /** - * Generate trade data for v3 - * @param tokenAmountInMax The amount in max - * @param ethAmountOut The amount to receive - * @param routeQuote The route quote - * @param deadline The deadline it expiries unix time - */ - private generateTradeDataForV3Output( - amountOut: BigNumber, - amountInMaximum: BigNumber, - routeQuote: RouteQuote, - deadline: string - ): string { - const params: ExactOutputSingleRequest = { - tokenIn: removeEthFromContractAddress( - this._uniswapPairFactoryContext.fromToken.contractAddress - ), - tokenOut: removeEthFromContractAddress( - this._uniswapPairFactoryContext.toToken.contractAddress - ), - fee: percentToFeeAmount(routeQuote.liquidityProviderFee), - recipient: this._uniswapPairFactoryContext.ethereumAddress, - deadline, - amountOut: hexlify(amountOut), - amountInMaximum: hexlify(amountInMaximum), - sqrtPriceLimitX96: 0, - }; - - return this._uniswapRouterContractFactoryV3.exactOutputSingle(params); - } - - /** - * Build up a transaction for erc20 from - * @param data The data - */ - private buildUpTransactionErc20( - uniswapVersion: UniswapVersion, - data: string - ): Transaction { - return { - to: - uniswapVersion === UniswapVersion.v2 - ? UniswapContractContextV2.routerAddress - : UniswapContractContextV3.routerAddress, - from: this._uniswapPairFactoryContext.ethereumAddress, - data, - value: Constants.EMPTY_HEX_STRING, - }; - } - - /** - * Build up a transaction for eth from - * @param ethValue The eth value - * @param data The data - */ - private buildUpTransactionEth( - uniswapVersion: UniswapVersion, - ethValue: BigNumber, - data: string - ): Transaction { - return { - to: - uniswapVersion === UniswapVersion.v2 - ? UniswapContractContextV2.routerAddress - : UniswapContractContextV3.routerAddress, - from: this._uniswapPairFactoryContext.ethereumAddress, - data, - value: toEthersBigNumber(parseEther(ethValue)).toHexString(), - }; - } - /** * Get the trade path */ @@ -1164,18 +501,6 @@ export class UniswapPairFactory { return getTradePath(network.chainId, this.fromToken, this.toToken); } - /** - * Generates the trade datetime unix time - */ - private generateTradeDeadlineUnixTime(): number { - const now = new Date(); - const expiryDate = new Date( - now.getTime() + - this._uniswapPairFactoryContext.settings.deadlineMinutes * 60000 - ); - return (expiryDate.getTime() / 1e3) | 0; - } - /** * Watch trade price move automatically emitting the stream if it changes */ @@ -1226,7 +551,7 @@ export class UniswapPairFactory { trade.liquidityProviderFee !== this._currentTradeContext.liquidityProviderFee || this._currentTradeContext.tradeExpires > - this.generateTradeDeadlineUnixTime() + this._uniswapRouterFactory.generateTradeDeadlineUnixTime() ) { this._currentTradeContext = this.buildCurrentTradeContext(trade); this._quoteChanged$.next(trade); diff --git a/src/factories/pair/uniswap-pair.ts b/src/factories/pair/uniswap-pair.ts index 35928b62..86515cdc 100644 --- a/src/factories/pair/uniswap-pair.ts +++ b/src/factories/pair/uniswap-pair.ts @@ -1,3 +1,4 @@ +import { CoinGecko } from '../../coin-gecko'; import { ErrorCodes } from '../../common/errors/error-codes'; import { UniswapError } from '../../common/errors/uniswap-error'; import { getAddress } from '../../common/utils/get-address'; @@ -150,6 +151,6 @@ export class UniswapPair { ethersProvider: this._ethersProvider, }; - return new UniswapPairFactory(uniswapFactoryContext); + return new UniswapPairFactory(new CoinGecko(), uniswapFactoryContext); } } diff --git a/src/factories/router/models/best-route-quotes.ts b/src/factories/router/models/best-route-quotes.ts index 3a9ed925..1ae5a348 100644 --- a/src/factories/router/models/best-route-quotes.ts +++ b/src/factories/router/models/best-route-quotes.ts @@ -3,4 +3,8 @@ import { RouteQuote } from './route-quote'; export interface BestRouteQuotes { bestRouteQuote: RouteQuote; triedRoutesQuote: RouteQuote[]; + hasEnoughAllowance: boolean; + hasEnoughBalance: boolean; + fromBalance: string; + toBalance: string; } diff --git a/src/factories/router/models/route-quote-trade-context.ts b/src/factories/router/models/route-quote-trade-context.ts new file mode 100644 index 00000000..f5c921b8 --- /dev/null +++ b/src/factories/router/models/route-quote-trade-context.ts @@ -0,0 +1,7 @@ +import { UniswapVersion } from '../../../enums/uniswap-version'; + +export interface RouteQuoteTradeContext { + uniswapVersion: UniswapVersion; + routePathArray: string[]; + liquidityProviderFee: number; +} diff --git a/src/factories/router/models/route-quote.ts b/src/factories/router/models/route-quote.ts index c0bc216a..081a1263 100644 --- a/src/factories/router/models/route-quote.ts +++ b/src/factories/router/models/route-quote.ts @@ -1,9 +1,13 @@ import { UniswapVersion } from '../../../enums/uniswap-version'; import { TradeDirection } from '../../pair/models/trade-direction'; +import { Transaction } from '../../pair/models/transaction'; import { Token } from '../../token/models/token'; export interface RouteQuote { expectedConvertQuote: string; + expectedConvertQuoteOrTokenAmountInMaxWithSlippage: string; + transaction: Transaction; + tradeExpires: number; routePathArrayTokenMap: Token[]; routeText: string; routePathArray: string[]; diff --git a/src/factories/router/uniswap-router.factory.ts b/src/factories/router/uniswap-router.factory.ts index bdb7b574..2f4e5ab0 100644 --- a/src/factories/router/uniswap-router.factory.ts +++ b/src/factories/router/uniswap-router.factory.ts @@ -5,6 +5,12 @@ import { ContractCallResults, Multicall, } from 'ethereum-multicall'; +import { + ExactInputSingleRequest, + ExactOutputSingleRequest, +} from '../../ABI/types/uniswap-router-v3'; +import { CoinGecko } from '../../coin-gecko'; +import { Constants } from '../../common/constants'; import { ErrorCodes } from '../../common/errors/error-codes'; import { UniswapError } from '../../common/errors/uniswap-error'; import { COMP } from '../../common/tokens/comp'; @@ -24,6 +30,7 @@ import { formatEther } from '../../common/utils/format-ether'; import { hexlify } from '../../common/utils/hexlify'; import { onlyUnique } from '../../common/utils/only-unique'; import { parseEther } from '../../common/utils/parse-ether'; +import { toEthersBigNumber } from '../../common/utils/to-ethers-big-number'; import { getTradePath } from '../../common/utils/trade-path'; import { ChainId } from '../../enums/chain-id'; import { TradePath } from '../../enums/trade-path'; @@ -32,18 +39,25 @@ import { EthersProvider } from '../../ethers-provider'; import { UniswapContractContextV2 } from '../../uniswap-contract-context/uniswap-contract-context-v2'; import { UniswapContractContextV3 } from '../../uniswap-contract-context/uniswap-contract-context-v3'; import { TradeDirection } from '../pair/models/trade-direction'; +import { Transaction } from '../pair/models/transaction'; +import { UniswapPairSettings } from '../pair/models/uniswap-pair-settings'; +import { AllowanceAndBalanceOf } from '../token/models/allowance-balance-of'; import { Token } from '../token/models/token'; +import { TokensFactory } from '../token/tokens.factory'; import { RouterDirection } from './enums/router-direction'; import { AllPossibleRoutes } from './models/all-possible-routes'; import { BestRouteQuotes } from './models/best-route-quotes'; import { RouteContext } from './models/route-context'; import { RouteQuote } from './models/route-quote'; +import { RouteQuoteTradeContext } from './models/route-quote-trade-context'; import { TokenRoutes } from './models/token-routes'; +import { UniswapRouterContractFactoryV2 } from './v2/uniswap-router-contract.factory.v2'; import { FeeAmount, feeToPercent, percentToFeeAmount, } from './v3/enums/fee-amount-v3'; +import { UniswapRouterContractFactoryV3 } from './v3/uniswap-router-contract.factory.v3'; export class UniswapRouterFactory { private _multicall = new Multicall({ @@ -51,13 +65,24 @@ export class UniswapRouterFactory { tryAggregate: true, }); + private _uniswapRouterContractFactoryV2 = new UniswapRouterContractFactoryV2( + this._ethersProvider + ); + + private _uniswapRouterContractFactoryV3 = new UniswapRouterContractFactoryV3( + this._ethersProvider + ); + + private _tokensFactory = new TokensFactory(this._ethersProvider); + private readonly LIQUIDITY_PROVIDER_FEE_V2 = 0.003; constructor( + private _coinGecko: CoinGecko, + private _ethereumAddress: string, private _fromToken: Token, private _toToken: Token, - private _disableMultihops: boolean, - private _uniswapVersions: UniswapVersion[], + private _settings: UniswapPairSettings, private _ethersProvider: EthersProvider ) {} @@ -68,7 +93,7 @@ export class UniswapRouterFactory { public async getAllPossibleRoutes(): Promise { let findPairs: Token[][][] = []; - if (!this._disableMultihops) { + if (!this._settings.disableMultihops) { findPairs = [ this.mainCurrenciesPairsForFromToken, this.mainCurrenciesPairsForToToken, @@ -89,7 +114,7 @@ export class UniswapRouterFactory { const contractCallContext: ContractCallContext[] = []; - if (this._uniswapVersions.includes(UniswapVersion.v2)) { + if (this._settings.uniswapVersions.includes(UniswapVersion.v2)) { contractCallContext.push({ reference: UniswapVersion.v2, contractAddress: UniswapContractContextV2.pairAddress, @@ -119,7 +144,7 @@ export class UniswapRouterFactory { } // for now v3 quotes will just be direct aka UNI > AAVE etc! - if (this._uniswapVersions.includes(UniswapVersion.v3)) { + if (this._settings.uniswapVersions.includes(UniswapVersion.v3)) { contractCallContext.push({ reference: UniswapVersion.v3, contractAddress: UniswapContractContextV3.factoryAddress, @@ -160,7 +185,7 @@ export class UniswapRouterFactory { const contractCallResults = await this._multicall.call(contractCallContext); - if (this._uniswapVersions.includes(UniswapVersion.v2)) { + if (this._settings.uniswapVersions.includes(UniswapVersion.v2)) { const results = contractCallResults.results[UniswapVersion.v2]; const availablePairs = results.callsReturnContext.filter( @@ -227,7 +252,7 @@ export class UniswapRouterFactory { ); } - if (this._uniswapVersions.includes(UniswapVersion.v3)) { + if (this._settings.uniswapVersions.includes(UniswapVersion.v3)) { const results = contractCallResults.results[UniswapVersion.v3]; for (let i = 0; i < results.callsReturnContext.length; i++) { @@ -275,7 +300,7 @@ export class UniswapRouterFactory { const routes = await this.getAllPossibleRoutes(); const contractCallContext: ContractCallContext[] = []; - if (this._uniswapVersions.includes(UniswapVersion.v2)) { + if (this._settings.uniswapVersions.includes(UniswapVersion.v2)) { contractCallContext.push({ reference: UniswapVersion.v2, contractAddress: UniswapContractContextV2.routerAddress, @@ -300,7 +325,7 @@ export class UniswapRouterFactory { } } - if (this._uniswapVersions.includes(UniswapVersion.v3)) { + if (this._settings.uniswapVersions.includes(UniswapVersion.v3)) { contractCallContext.push({ reference: UniswapVersion.v3, contractAddress: UniswapContractContextV3.quoterAddress, @@ -315,7 +340,7 @@ export class UniswapRouterFactory { }); contractCallContext[ - this._uniswapVersions.includes(UniswapVersion.v2) ? 1 : 0 + this._settings.uniswapVersions.includes(UniswapVersion.v2) ? 1 : 0 ].calls.push({ reference: `route${i}`, methodName: @@ -335,7 +360,11 @@ export class UniswapRouterFactory { const contractCallResults = await this._multicall.call(contractCallContext); - return this.buildRouteQuotesFromResults(contractCallResults, direction); + return this.buildRouteQuotesFromResults( + amountToTrade, + contractCallResults, + direction + ); } /** @@ -347,7 +376,7 @@ export class UniswapRouterFactory { amountToTrade: BigNumber, direction: TradeDirection ): Promise { - const allRoutes = await this.getAllPossibleRoutesWithQuotes( + let allRoutes = await this.getAllPossibleRoutesWithQuotes( amountToTrade, direction ); @@ -359,11 +388,28 @@ export class UniswapRouterFactory { ); } + const allowanceAndBalances = await this.hasEnoughAllowanceAndBalance( + amountToTrade, + allRoutes[0], + direction + ); + + if ( + this._ethersProvider.provider.network.chainId === ChainId.MAINNET && + this._settings.gasSettings + ) { + allRoutes = await this.filterWithTransactionFees(allRoutes); + } + return { bestRouteQuote: allRoutes[0], triedRoutesQuote: allRoutes.map((route) => { return { expectedConvertQuote: route.expectedConvertQuote, + expectedConvertQuoteOrTokenAmountInMaxWithSlippage: + route.expectedConvertQuoteOrTokenAmountInMaxWithSlippage, + transaction: route.transaction, + tradeExpires: route.tradeExpires, routePathArrayTokenMap: route.routePathArrayTokenMap, routeText: route.routeText, routePathArray: route.routePathArray, @@ -372,9 +418,696 @@ export class UniswapRouterFactory { quoteDirection: route.quoteDirection, }; }), + hasEnoughBalance: allowanceAndBalances.enoughBalance, + fromBalance: allowanceAndBalances.fromBalance, + toBalance: allowanceAndBalances.toBalance, + hasEnoughAllowance: + allRoutes[0].uniswapVersion === UniswapVersion.v2 + ? allowanceAndBalances.enoughV2Allowance + : allowanceAndBalances.enoughV3Allowance, }; } + /** + * Generate trade data eth > erc20 + * @param ethAmountIn The eth amount in + * @param tokenAmount The token amount + * @param routeQuoteTradeContext The route quote trade context + * @param deadline The deadline it expiries unix time + */ + public generateTradeDataEthToErc20Input( + ethAmountIn: BigNumber, + tokenAmount: BigNumber, + routeQuoteTradeContext: RouteQuoteTradeContext, + deadline: string + ): string { + // uniswap adds extra digits on even if the token is say 8 digits long + const convertedMinTokens = tokenAmount + .shiftedBy(this._toToken.decimals) + .decimalPlaces(0); + + switch (routeQuoteTradeContext.uniswapVersion) { + case UniswapVersion.v2: + return this._uniswapRouterContractFactoryV2.swapExactETHForTokens( + hexlify(convertedMinTokens), + routeQuoteTradeContext.routePathArray.map((r) => + removeEthFromContractAddress(r) + ), + this._ethereumAddress, + deadline + ); + case UniswapVersion.v3: + return this.generateTradeDataForV3Input( + parseEther(ethAmountIn), + convertedMinTokens, + routeQuoteTradeContext.liquidityProviderFee, + deadline + ); + default: + throw new UniswapError( + 'Uniswap version not supported', + ErrorCodes.uniswapVersionNotSupported + ); + } + } + + /** + * Generate trade data eth > erc20 + * @param tokenAmountInMax The amount in max + * @param ethAmountOut The amount to receive + * @param routeQuote The route quote + * @param deadline The deadline it expiries unix time + */ + public generateTradeDataEthToErc20Output( + ethAmountInMax: BigNumber, + tokenAmountOut: BigNumber, + routeQuoteTradeContext: RouteQuoteTradeContext, + deadline: string + ): string { + const amountOut = tokenAmountOut + .shiftedBy(this._toToken.decimals) + .decimalPlaces(0); + + switch (routeQuoteTradeContext.uniswapVersion) { + case UniswapVersion.v2: + return this._uniswapRouterContractFactoryV2.swapETHForExactTokens( + hexlify(amountOut), + routeQuoteTradeContext.routePathArray.map((r) => + removeEthFromContractAddress(r) + ), + this._ethereumAddress, + deadline + ); + case UniswapVersion.v3: + return this.generateTradeDataForV3Output( + amountOut, + parseEther(ethAmountInMax), + routeQuoteTradeContext.liquidityProviderFee, + deadline + ); + default: + throw new UniswapError( + 'Uniswap version not supported', + ErrorCodes.uniswapVersionNotSupported + ); + } + } + + /** + * Generate trade amount erc20 > eth for input direction + * @param tokenAmount The amount in + * @param ethAmountOutMin The min amount to receive + * @param routeQuoteTradeContext The route quote trade context + * @param deadline The deadline it expiries unix time + */ + public generateTradeDataErc20ToEthInput( + tokenAmount: BigNumber, + ethAmountOutMin: BigNumber, + routeQuoteTradeContext: RouteQuoteTradeContext, + deadline: string + ): string { + // uniswap adds extra digits on even if the token is say 8 digits long + const amountIn = tokenAmount + .shiftedBy(this._fromToken.decimals) + .decimalPlaces(0); + + switch (routeQuoteTradeContext.uniswapVersion) { + case UniswapVersion.v2: + return this._uniswapRouterContractFactoryV2.swapExactTokensForETH( + hexlify(amountIn), + hexlify(parseEther(ethAmountOutMin)), + routeQuoteTradeContext.routePathArray.map((r) => + removeEthFromContractAddress(r) + ), + this._ethereumAddress, + deadline + ); + case UniswapVersion.v3: + return this.generateTradeDataForV3Input( + amountIn, + parseEther(ethAmountOutMin), + routeQuoteTradeContext.liquidityProviderFee, + deadline + ); + default: + throw new UniswapError( + 'Uniswap version not supported', + ErrorCodes.uniswapVersionNotSupported + ); + } + } + + /** + * Generate trade amount erc20 > eth for input direction + * @param tokenAmountInMax The amount in max + * @param ethAmountOut The amount to receive + * @param routeQuoteTradeContext The route quote trade context + * @param deadline The deadline it expiries unix time + */ + public generateTradeDataErc20ToEthOutput( + tokenAmountInMax: BigNumber, + ethAmountOut: BigNumber, + routeQuoteTradeContext: RouteQuoteTradeContext, + deadline: string + ): string { + // uniswap adds extra digits on even if the token is say 8 digits long + const amountInMax = tokenAmountInMax + .shiftedBy(this._fromToken.decimals) + .decimalPlaces(0); + + switch (routeQuoteTradeContext.uniswapVersion) { + case UniswapVersion.v2: + return this._uniswapRouterContractFactoryV2.swapTokensForExactETH( + hexlify(parseEther(ethAmountOut)), + hexlify(amountInMax), + routeQuoteTradeContext.routePathArray.map((r) => + removeEthFromContractAddress(r) + ), + this._ethereumAddress, + deadline + ); + case UniswapVersion.v3: + return this.generateTradeDataForV3Output( + parseEther(ethAmountOut), + amountInMax, + routeQuoteTradeContext.liquidityProviderFee, + deadline + ); + default: + throw new UniswapError( + 'Uniswap version not supported', + ErrorCodes.uniswapVersionNotSupported + ); + } + } + + /** + * Generate trade amount erc20 > erc20 for input + * @param tokenAmount The token amount + * @param tokenAmountOut The min token amount out + * @param routeQuoteTradeContext The route quote trade context + * @param deadline The deadline it expiries unix time + */ + public generateTradeDataErc20ToErc20Input( + tokenAmount: BigNumber, + tokenAmountMin: BigNumber, + routeQuoteTradeContext: RouteQuoteTradeContext, + deadline: string + ): string { + // uniswap adds extra digits on even if the token is say 8 digits long + const amountIn = tokenAmount + .shiftedBy(this._fromToken.decimals) + .decimalPlaces(0); + const amountMin = tokenAmountMin + .shiftedBy(this._toToken.decimals) + .decimalPlaces(0); + + switch (routeQuoteTradeContext.uniswapVersion) { + case UniswapVersion.v2: + return this._uniswapRouterContractFactoryV2.swapExactTokensForTokens( + hexlify(amountIn), + hexlify(amountMin), + routeQuoteTradeContext.routePathArray, + this._ethereumAddress, + deadline + ); + case UniswapVersion.v3: + return this.generateTradeDataForV3Input( + amountIn, + amountMin, + routeQuoteTradeContext.liquidityProviderFee, + deadline + ); + default: + throw new UniswapError( + 'Uniswap version not supported', + ErrorCodes.uniswapVersionNotSupported + ); + } + } + + /** + * Generate trade amount erc20 > erc20 for output + * @param tokenAmount The token amount + * @param tokenAmountOut The min token amount out + * @param routeQuoteTradeContext The route quote trade context + * @param deadline The deadline it expiries unix time + */ + public generateTradeDataErc20ToErc20Output( + tokenAmountInMax: BigNumber, + tokenAmountOut: BigNumber, + routeQuoteTradeContext: RouteQuoteTradeContext, + deadline: string + ): string { + // uniswap adds extra digits on even if the token is say 8 digits long + const amountInMax = tokenAmountInMax + .shiftedBy(this._fromToken.decimals) + .decimalPlaces(0); + + const amountOut = tokenAmountOut + .shiftedBy(this._toToken.decimals) + .decimalPlaces(0); + + switch (routeQuoteTradeContext.uniswapVersion) { + case UniswapVersion.v2: + return this._uniswapRouterContractFactoryV2.swapTokensForExactTokens( + hexlify(amountOut), + hexlify(amountInMax), + routeQuoteTradeContext.routePathArray, + this._ethereumAddress, + deadline + ); + case UniswapVersion.v3: + return this.generateTradeDataForV3Output( + amountOut, + amountInMax, + routeQuoteTradeContext.liquidityProviderFee, + deadline + ); + default: + throw new UniswapError( + 'Uniswap version not supported', + ErrorCodes.uniswapVersionNotSupported + ); + } + } + + /** + * Generate trade data for v3 + * @param tokenAmount The token amount + * @param tokenAmountOut The min token amount out + * @param liquidityProviderFee The liquidity provider fee + * @param deadline The deadline it expiries unix time + */ + public generateTradeDataForV3Input( + tokenAmount: BigNumber, + tokenAmountMin: BigNumber, + liquidityProviderFee: number, + deadline: string + ): string { + const params: ExactInputSingleRequest = { + tokenIn: removeEthFromContractAddress(this._fromToken.contractAddress), + tokenOut: removeEthFromContractAddress(this._toToken.contractAddress), + fee: percentToFeeAmount(liquidityProviderFee), + recipient: this._ethereumAddress, + deadline, + amountIn: hexlify(tokenAmount), + amountOutMinimum: hexlify(tokenAmountMin), + sqrtPriceLimitX96: 0, + }; + + return this._uniswapRouterContractFactoryV3.exactInputSingle(params); + } + + /** + * Build up a transaction for erc20 from + * @param data The data + */ + public buildUpTransactionErc20( + uniswapVersion: UniswapVersion, + data: string + ): Transaction { + return { + to: + uniswapVersion === UniswapVersion.v2 + ? UniswapContractContextV2.routerAddress + : UniswapContractContextV3.routerAddress, + from: this._ethereumAddress, + data, + value: Constants.EMPTY_HEX_STRING, + }; + } + + /** + * Build up a transaction for eth from + * @param ethValue The eth value + * @param data The data + */ + public buildUpTransactionEth( + uniswapVersion: UniswapVersion, + ethValue: BigNumber, + data: string + ): Transaction { + return { + to: + uniswapVersion === UniswapVersion.v2 + ? UniswapContractContextV2.routerAddress + : UniswapContractContextV3.routerAddress, + from: this._ethereumAddress, + data, + value: toEthersBigNumber(parseEther(ethValue)).toHexString(), + }; + } + + /** + * Generates the trade datetime unix time + */ + public generateTradeDeadlineUnixTime(): number { + const now = new Date(); + const expiryDate = new Date( + now.getTime() + this._settings.deadlineMinutes * 60000 + ); + return (expiryDate.getTime() / 1e3) | 0; + } + + /** + * Get the allowance and balance for the from and to token (will get balance for eth as well) + */ + public async getAllowanceAndBalanceForTokens(): Promise<{ + fromToken: AllowanceAndBalanceOf; + toToken: AllowanceAndBalanceOf; + }> { + const allowanceAndBalanceOfForTokens = + await this._tokensFactory.getAllowanceAndBalanceOfForContracts( + this._ethereumAddress, + [this._fromToken.contractAddress, this._toToken.contractAddress], + false + ); + + return { + fromToken: allowanceAndBalanceOfForTokens.find( + (c) => c.token.contractAddress === this._fromToken.contractAddress + )!.allowanceAndBalanceOf, + toToken: allowanceAndBalanceOfForTokens.find( + (c) => c.token.contractAddress === this._toToken.contractAddress + )!.allowanceAndBalanceOf, + }; + } + + /** + * Has got enough allowance to do the trade + * @param amount The amount you want to swap + */ + public hasGotEnoughAllowance(amount: string, allowance: string): boolean { + if (this.tradePath() === TradePath.ethToErc20) { + return true; + } + + const bigNumberAllowance = new BigNumber(allowance).shiftedBy( + this._fromToken.decimals * -1 + ); + + if (new BigNumber(amount).isGreaterThan(bigNumberAllowance)) { + return false; + } + + return true; + } + + /** + * Get eth balance + */ + public async getEthBalance(): Promise { + const balance = await this._ethersProvider.balanceOf(this._ethereumAddress); + + return new BigNumber(balance).shiftedBy(Constants.ETH_MAX_DECIMALS * -1); + } + + private async hasEnoughAllowanceAndBalance( + amountToTrade: BigNumber, + bestRouteQuote: RouteQuote, + direction: TradeDirection + ): Promise<{ + enoughBalance: boolean; + fromBalance: string; + toBalance: string; + enoughV2Allowance: boolean; + enoughV3Allowance: boolean; + }> { + const allowanceAndBalancesForTokens = + await this.getAllowanceAndBalanceForTokens(); + + let enoughBalance = false; + let fromBalance = allowanceAndBalancesForTokens.fromToken.balanceOf; + + switch (this.tradePath()) { + case TradePath.ethToErc20: + const result = await this.hasGotEnoughBalanceEth( + direction === TradeDirection.input + ? amountToTrade.toFixed() + : bestRouteQuote.expectedConvertQuote + ); + enoughBalance = result.hasEnough; + fromBalance = result.balance; + break; + case TradePath.erc20ToErc20: + case TradePath.erc20ToEth: + if (direction == TradeDirection.input) { + const result = this.hasGotEnoughBalanceErc20( + amountToTrade.toFixed(), + allowanceAndBalancesForTokens.fromToken.balanceOf + ); + + enoughBalance = result.hasEnough; + fromBalance = result.balance; + } else { + const result = this.hasGotEnoughBalanceErc20( + bestRouteQuote.expectedConvertQuote, + allowanceAndBalancesForTokens.fromToken.balanceOf + ); + + enoughBalance = result.hasEnough; + fromBalance = result.balance; + } + } + + const enoughV2Allowance = + direction === TradeDirection.input + ? this.hasGotEnoughAllowance( + amountToTrade.toFixed(), + allowanceAndBalancesForTokens.fromToken.allowanceV2 + ) + : this.hasGotEnoughAllowance( + bestRouteQuote.expectedConvertQuote, + allowanceAndBalancesForTokens.fromToken.allowanceV2 + ); + + const enoughV3Allowance = + direction === TradeDirection.input + ? this.hasGotEnoughAllowance( + amountToTrade.toFixed(), + allowanceAndBalancesForTokens.fromToken.allowanceV3 + ) + : this.hasGotEnoughAllowance( + bestRouteQuote.expectedConvertQuote, + allowanceAndBalancesForTokens.fromToken.allowanceV3 + ); + + return { + enoughV2Allowance, + enoughV3Allowance, + enoughBalance, + fromBalance, + toBalance: allowanceAndBalancesForTokens.toToken.balanceOf, + }; + } + + /** + * Has got enough balance to do the trade (eth check only) + * @param amount The amount you want to swap + */ + private async hasGotEnoughBalanceEth(amount: string): Promise<{ + hasEnough: boolean; + balance: string; + }> { + const balance = await this.getEthBalance(); + + if (new BigNumber(amount).isGreaterThan(balance)) { + return { + hasEnough: false, + balance: balance.toFixed(), + }; + } + + return { + hasEnough: true, + balance: balance.toFixed(), + }; + } + + /** + * Has got enough balance to do the trade (erc20 check only) + * @param amount The amount you want to swap + */ + private hasGotEnoughBalanceErc20( + amount: string, + balance: string + ): { + hasEnough: boolean; + balance: string; + } { + const bigNumberBalance = new BigNumber(balance).shiftedBy( + this._fromToken.decimals * -1 + ); + + if (new BigNumber(amount).isGreaterThan(bigNumberBalance)) { + return { + hasEnough: false, + balance: bigNumberBalance.toFixed(), + }; + } + + return { + hasEnough: true, + balance: bigNumberBalance.toFixed(), + }; + } + + /** + * Generate trade data for v3 + * @param tokenAmountInMax The amount in max + * @param ethAmountOut The amount to receive + * @param liquidityProviderFee The liquidity provider fee + * @param deadline The deadline it expiries unix time + */ + private generateTradeDataForV3Output( + amountOut: BigNumber, + amountInMaximum: BigNumber, + liquidityProviderFee: number, + deadline: string + ): string { + const params: ExactOutputSingleRequest = { + tokenIn: removeEthFromContractAddress(this._fromToken.contractAddress), + tokenOut: removeEthFromContractAddress(this._toToken.contractAddress), + fee: percentToFeeAmount(liquidityProviderFee), + recipient: this._ethereumAddress, + deadline, + amountOut: hexlify(amountOut), + amountInMaximum: hexlify(amountInMaximum), + sqrtPriceLimitX96: 0, + }; + + return this._uniswapRouterContractFactoryV3.exactOutputSingle(params); + } + + /** + * Work out trade fiat cost + * @param allRoutes All the routes + */ + private async filterWithTransactionFees( + allRoutes: RouteQuote[] + ): Promise { + if (this._settings.gasSettings) { + const ethContract = WETHContract.MAINNET().contractAddress; + + const fiatPrices = await this._coinGecko.getCoinGeckoFiatPrices([ + this._toToken.contractAddress, + ethContract, + ]); + + const toUsdValue = fiatPrices[this._toToken.contractAddress]; + const ethUsdValue = fiatPrices[WETHContract.MAINNET().contractAddress]; + + if (toUsdValue && ethUsdValue) { + const bestRouteQuoteHops = this.getBestRouteQuotesHops(allRoutes); + + const gasPrice = await this._settings.gasSettings.getGasPrice(); + + let bestRoute: + | { + routeQuote: RouteQuote; + expectedConvertQuoteMinusTxFees: BigNumber; + } + | undefined; + for (let i = 0; i < bestRouteQuoteHops.length; i++) { + const route = bestRouteQuoteHops[i]; + const expectedConvertQuoteFiatPrice = new BigNumber( + route.expectedConvertQuote + ).times(toUsdValue); + + const txFee = formatEther( + new BigNumber( + ( + await this._ethersProvider.provider.estimateGas( + route.transaction + ) + ).toHexString() + ).times(gasPrice) + ).times(ethUsdValue); + + const expectedConvertQuoteMinusTxFees = + expectedConvertQuoteFiatPrice.minus(txFee); + + if (bestRoute) { + if ( + expectedConvertQuoteMinusTxFees.isGreaterThan( + bestRoute.expectedConvertQuoteMinusTxFees + ) + ) { + bestRoute = { + routeQuote: bestRouteQuoteHops[i], + expectedConvertQuoteMinusTxFees, + }; + } + } else { + bestRoute = { + routeQuote: bestRouteQuoteHops[i], + expectedConvertQuoteMinusTxFees, + }; + } + } + + if (bestRoute) { + const routeIndex = allRoutes.findIndex( + (r) => + r.expectedConvertQuote === + bestRoute!.routeQuote.expectedConvertQuote && + bestRoute!.routeQuote.routeText === r.routeText + ); + + allRoutes.splice(routeIndex, 1); + allRoutes.unshift(bestRoute.routeQuote); + } + } + } + + return allRoutes; + } + + /** + * Work out the best route quote hops aka the best direct, the best 3 hop and the best 4 hop + * @param allRoutes All the routes + */ + private getBestRouteQuotesHops(allRoutes: RouteQuote[]): RouteQuote[] { + const routes: RouteQuote[] = []; + for (let i = 0; i < allRoutes.length; i++) { + if ( + routes.find((r) => r.routePathArray.length === 2) && + routes.find((r) => r.routePathArray.length === 3) && + routes.find((r) => r.routePathArray.length === 4) + ) { + break; + } + + const route = allRoutes[i]; + if ( + route.routePathArray.length === 2 && + !routes.find((r) => r.routePathArray.length === 2) + ) { + routes.push(route); + continue; + } + + if ( + route.routePathArray.length === 3 && + !routes.find((r) => r.routePathArray.length === 3) + ) { + routes.push(route); + continue; + } + + if ( + route.routePathArray.length === 4 && + !routes.find((r) => r.routePathArray.length === 4) + ) { + routes.push(route); + continue; + } + } + + return routes; + } + // /** // * Encode the route path for v3 ( WILL NEED WHEN WE SUPPORT V3 DOING NONE DIRECT ROUTES) // * @param path The path @@ -575,6 +1308,7 @@ export class UniswapRouterFactory { * @param direction The direction you want to get the quote from */ private buildRouteQuotesFromResults( + amountToTrade: BigNumber, contractCallResults: ContractCallResults, direction: TradeDirection ): RouteQuote[] { @@ -603,6 +1337,7 @@ export class UniswapRouterFactory { case TradePath.ethToErc20: result.push( this.buildRouteQuoteForEthToErc20( + amountToTrade, callReturnContext, contractCallReturnContext.originalContractCallContext.context[ i @@ -616,6 +1351,7 @@ export class UniswapRouterFactory { case TradePath.erc20ToEth: result.push( this.buildRouteQuoteForErc20ToEth( + amountToTrade, callReturnContext, contractCallReturnContext.originalContractCallContext.context[ i @@ -629,6 +1365,7 @@ export class UniswapRouterFactory { case TradePath.erc20ToErc20: result.push( this.buildRouteQuoteForErc20ToErc20( + amountToTrade, callReturnContext, contractCallReturnContext.originalContractCallContext.context[ i @@ -690,6 +1427,7 @@ export class UniswapRouterFactory { * @param uniswapVersion The uniswap version */ private buildRouteQuoteForErc20ToErc20( + amountToTrade: BigNumber, callReturnContext: CallReturnContext, routeContext: RouteContext, direction: TradeDirection, @@ -701,17 +1439,52 @@ export class UniswapRouterFactory { uniswapVersion ); + const expectedConvertQuote = + direction === TradeDirection.input + ? convertQuoteUnformatted + .shiftedBy(this._toToken.decimals * -1) + .toFixed(this._toToken.decimals) + : convertQuoteUnformatted + .shiftedBy(this._fromToken.decimals * -1) + .toFixed(this._fromToken.decimals); + + const expectedConvertQuoteOrTokenAmountInMaxWithSlippage = + this.getExpectedConvertQuoteOrTokenAmountInMaxWithSlippage( + expectedConvertQuote, + direction + ); + + const tradeExpires = this.generateTradeDeadlineUnixTime(); + + const routeQuoteTradeContext: RouteQuoteTradeContext = { + uniswapVersion, + liquidityProviderFee: routeContext.liquidityProviderFee, + routePathArray: callReturnContext.methodParameters[1], + }; + const data = + direction === TradeDirection.input + ? this.generateTradeDataErc20ToErc20Input( + amountToTrade, + new BigNumber(expectedConvertQuoteOrTokenAmountInMaxWithSlippage), + routeQuoteTradeContext, + tradeExpires.toString() + ) + : this.generateTradeDataErc20ToErc20Output( + new BigNumber(expectedConvertQuoteOrTokenAmountInMaxWithSlippage), + amountToTrade, + routeQuoteTradeContext, + tradeExpires.toString() + ); + + const transaction = this.buildUpTransactionErc20(uniswapVersion, data); + switch (uniswapVersion) { case UniswapVersion.v2: return { - expectedConvertQuote: - direction === TradeDirection.input - ? convertQuoteUnformatted - .shiftedBy(this._toToken.decimals * -1) - .toFixed(this._toToken.decimals) - : convertQuoteUnformatted - .shiftedBy(this._fromToken.decimals * -1) - .toFixed(this._fromToken.decimals), + expectedConvertQuote, + expectedConvertQuoteOrTokenAmountInMaxWithSlippage, + transaction, + tradeExpires, routePathArrayTokenMap: callReturnContext.methodParameters[1].map( (c: string) => { return this.allTokens.find((t) => t.contractAddress === c); @@ -731,14 +1504,10 @@ export class UniswapRouterFactory { }; case UniswapVersion.v3: return { - expectedConvertQuote: - direction === TradeDirection.input - ? convertQuoteUnformatted - .shiftedBy(this._toToken.decimals * -1) - .toFixed(this._toToken.decimals) - : convertQuoteUnformatted - .shiftedBy(this._fromToken.decimals * -1) - .toFixed(this._fromToken.decimals), + expectedConvertQuote, + expectedConvertQuoteOrTokenAmountInMaxWithSlippage, + transaction, + tradeExpires, routePathArrayTokenMap: [this._fromToken, this._toToken], routeText: `${this._fromToken.symbol} > ${this._toToken.symbol}`, routePathArray: [ @@ -762,6 +1531,7 @@ export class UniswapRouterFactory { * @param uniswapVersion The uniswap version */ private buildRouteQuoteForEthToErc20( + amountToTrade: BigNumber, callReturnContext: CallReturnContext, routeContext: RouteContext, direction: TradeDirection, @@ -773,17 +1543,57 @@ export class UniswapRouterFactory { uniswapVersion ); + const expectedConvertQuote = + direction === TradeDirection.input + ? convertQuoteUnformatted + .shiftedBy(this._toToken.decimals * -1) + .toFixed(this._toToken.decimals) + : new BigNumber(formatEther(convertQuoteUnformatted)).toFixed( + this._fromToken.decimals + ); + + const expectedConvertQuoteOrTokenAmountInMaxWithSlippage = + this.getExpectedConvertQuoteOrTokenAmountInMaxWithSlippage( + expectedConvertQuote, + direction + ); + + const tradeExpires = this.generateTradeDeadlineUnixTime(); + const routeQuoteTradeContext: RouteQuoteTradeContext = { + uniswapVersion, + liquidityProviderFee: routeContext.liquidityProviderFee, + routePathArray: callReturnContext.methodParameters[1], + }; + const data = + direction === TradeDirection.input + ? this.generateTradeDataEthToErc20Input( + amountToTrade, + new BigNumber(expectedConvertQuoteOrTokenAmountInMaxWithSlippage), + routeQuoteTradeContext, + tradeExpires.toString() + ) + : this.generateTradeDataEthToErc20Output( + new BigNumber(expectedConvertQuoteOrTokenAmountInMaxWithSlippage), + amountToTrade, + routeQuoteTradeContext, + tradeExpires.toString() + ); + + const transaction = this.buildUpTransactionEth( + uniswapVersion, + direction === TradeDirection.input + ? amountToTrade + : new BigNumber(expectedConvertQuote), + data + ); + switch (uniswapVersion) { case UniswapVersion.v2: return { - expectedConvertQuote: - direction === TradeDirection.input - ? convertQuoteUnformatted - .shiftedBy(this._toToken.decimals * -1) - .toFixed(this._toToken.decimals) - : convertQuoteUnformatted - .shiftedBy(this._fromToken.decimals * -1) - .toFixed(this._fromToken.decimals), + expectedConvertQuote, + expectedConvertQuoteOrTokenAmountInMaxWithSlippage, + transaction, + tradeExpires, routePathArrayTokenMap: callReturnContext.methodParameters[1].map( (c: string, index: number) => { const token = deepClone( @@ -813,14 +1623,10 @@ export class UniswapRouterFactory { }; case UniswapVersion.v3: return { - expectedConvertQuote: - direction === TradeDirection.input - ? convertQuoteUnformatted - .shiftedBy(this._toToken.decimals * -1) - .toFixed(this._toToken.decimals) - : convertQuoteUnformatted - .shiftedBy(this._fromToken.decimals * -1) - .toFixed(this._fromToken.decimals), + expectedConvertQuote, + expectedConvertQuoteOrTokenAmountInMaxWithSlippage, + transaction, + tradeExpires, routePathArrayTokenMap: [ turnTokenIntoEthForResponse(this._fromToken), this._toToken, @@ -849,6 +1655,7 @@ export class UniswapRouterFactory { * @param uniswapVersion The uniswap version */ private buildRouteQuoteForErc20ToEth( + amountToTrade: BigNumber, callReturnContext: CallReturnContext, routeContext: RouteContext, direction: TradeDirection, @@ -860,17 +1667,51 @@ export class UniswapRouterFactory { uniswapVersion ); + const expectedConvertQuote = + direction === TradeDirection.input + ? new BigNumber(formatEther(convertQuoteUnformatted)).toFixed( + this._toToken.decimals + ) + : convertQuoteUnformatted + .shiftedBy(this._fromToken.decimals * -1) + .toFixed(this._fromToken.decimals); + + const expectedConvertQuoteOrTokenAmountInMaxWithSlippage = + this.getExpectedConvertQuoteOrTokenAmountInMaxWithSlippage( + expectedConvertQuote, + direction + ); + + const tradeExpires = this.generateTradeDeadlineUnixTime(); + const routeQuoteTradeContext: RouteQuoteTradeContext = { + uniswapVersion, + liquidityProviderFee: routeContext.liquidityProviderFee, + routePathArray: callReturnContext.methodParameters[1], + }; + const data = + direction === TradeDirection.input + ? this.generateTradeDataErc20ToEthInput( + amountToTrade, + new BigNumber(expectedConvertQuoteOrTokenAmountInMaxWithSlippage), + routeQuoteTradeContext, + tradeExpires.toString() + ) + : this.generateTradeDataErc20ToEthOutput( + new BigNumber(expectedConvertQuoteOrTokenAmountInMaxWithSlippage), + amountToTrade, + routeQuoteTradeContext, + tradeExpires.toString() + ); + + const transaction = this.buildUpTransactionErc20(uniswapVersion, data); + switch (uniswapVersion) { case UniswapVersion.v2: return { - expectedConvertQuote: - direction === TradeDirection.input - ? new BigNumber(formatEther(convertQuoteUnformatted)).toFixed( - this._toToken.decimals - ) - : convertQuoteUnformatted - .shiftedBy(this._fromToken.decimals * -1) - .toFixed(this._fromToken.decimals), + expectedConvertQuote, + expectedConvertQuoteOrTokenAmountInMaxWithSlippage, + transaction, + tradeExpires, routePathArrayTokenMap: callReturnContext.methodParameters[1].map( (c: string, index: number) => { const token = deepClone( @@ -900,9 +1741,10 @@ export class UniswapRouterFactory { }; case UniswapVersion.v3: return { - expectedConvertQuote: convertQuoteUnformatted - .shiftedBy(this._toToken.decimals * -1) - .toFixed(this._toToken.decimals), + expectedConvertQuote, + expectedConvertQuoteOrTokenAmountInMaxWithSlippage, + transaction, + tradeExpires, routePathArrayTokenMap: [ this._fromToken, turnTokenIntoEthForResponse(this._toToken), @@ -952,6 +1794,28 @@ export class UniswapRouterFactory { } } + /** + * Work out the expected convert quote taking off slippage + * @param expectedConvertQuote The expected convert quote + */ + private getExpectedConvertQuoteOrTokenAmountInMaxWithSlippage( + expectedConvertQuote: string, + tradeDirection: TradeDirection + ): string { + const decimals = + tradeDirection === TradeDirection.input + ? this._toToken.decimals + : this._fromToken.decimals; + + return new BigNumber(expectedConvertQuote) + .minus( + new BigNumber(expectedConvertQuote) + .times(this._settings.slippage) + .toFixed(decimals) + ) + .toFixed(decimals); + } + /** * Format amount to trade into callable formats * @param amountToTrade The amount to trade diff --git a/src/factories/token/models/allowance-balance-of.ts b/src/factories/token/models/allowance-balance-of.ts index 46ad5794..fc5a93ec 100644 --- a/src/factories/token/models/allowance-balance-of.ts +++ b/src/factories/token/models/allowance-balance-of.ts @@ -1,4 +1,5 @@ export interface AllowanceAndBalanceOf { - allowance: string; + allowanceV2: string; + allowanceV3: string; balanceOf: string; } diff --git a/src/factories/token/token.factory.ts b/src/factories/token/token.factory.ts index 05d5d104..f3426151 100644 --- a/src/factories/token/token.factory.ts +++ b/src/factories/token/token.factory.ts @@ -143,59 +143,83 @@ export class TokenFactory { /** * Get allowance and balance - * @param uniswapVersion The uniswap version * @param ethereumAddress The ethereum address */ public async getAllowanceAndBalanceOf( - uniswapVersion: UniswapVersion, ethereumAddress: string ): Promise { if (isNativeEth(this._tokenContractAddress)) { return { - allowance: await this.allowance(uniswapVersion, ethereumAddress), + allowanceV2: + '0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff', + allowanceV3: + '0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff', balanceOf: await this.balanceOf(ethereumAddress), }; } else { const ALLOWANCE = 0; const BALANCEOF = 1; - const contractCallContext: ContractCallContext = { - reference: 'allowance-and-balance-of', - contractAddress: getAddress(this._tokenContractAddress), - abi: ContractContext.erc20Abi, - calls: [ - { - reference: 'allowance', - methodName: 'allowance', - methodParameters: [ - ethereumAddress, - uniswapVersion === UniswapVersion.v2 - ? UniswapContractContextV2.routerAddress - : UniswapContractContextV3.routerAddress, - ], - }, - { - reference: 'balanceOf', - methodName: 'balanceOf', - methodParameters: [ethereumAddress], - }, - ], - }; + const contractCallContext: ContractCallContext[] = []; + + contractCallContext.push( + this.buildAllowanceAndBalanceContractCallContext( + ethereumAddress, + UniswapVersion.v2 + ) + ); + contractCallContext.push( + this.buildAllowanceAndBalanceContractCallContext( + ethereumAddress, + UniswapVersion.v3 + ) + ); const contractCallResults = await this._multicall.call( contractCallContext ); - const results = - contractCallResults.results[contractCallContext.reference]; + const resultsV2 = contractCallResults.results[UniswapVersion.v2]; + const resultsV3 = contractCallResults.results[UniswapVersion.v3]; return { - allowance: BigNumber.from( - results.callsReturnContext[ALLOWANCE].returnValues[0] + allowanceV2: BigNumber.from( + resultsV2.callsReturnContext[ALLOWANCE].returnValues[0] + ).toHexString(), + allowanceV3: BigNumber.from( + resultsV3.callsReturnContext[ALLOWANCE].returnValues[0] ).toHexString(), balanceOf: BigNumber.from( - results.callsReturnContext[BALANCEOF].returnValues[0] + resultsV2.callsReturnContext[BALANCEOF].returnValues[0] ).toHexString(), }; } } + + private buildAllowanceAndBalanceContractCallContext( + ethereumAddress: string, + uniswapVersion: UniswapVersion + ): ContractCallContext { + return { + reference: uniswapVersion, + contractAddress: getAddress(this._tokenContractAddress), + abi: ContractContext.erc20Abi, + calls: [ + { + reference: 'allowance', + methodName: 'allowance', + methodParameters: [ + ethereumAddress, + uniswapVersion === UniswapVersion.v2 + ? UniswapContractContextV2.routerAddress + : UniswapContractContextV3.routerAddress, + ], + }, + { + reference: 'balanceOf', + methodName: 'balanceOf', + methodParameters: [ethereumAddress], + }, + ], + }; + } } diff --git a/src/factories/token/tokens.factory.ts b/src/factories/token/tokens.factory.ts index 4d1ab2f5..84ac9ffa 100644 --- a/src/factories/token/tokens.factory.ts +++ b/src/factories/token/tokens.factory.ts @@ -92,13 +92,11 @@ export class TokensFactory { /** * Get allowance and balance for many contracts - * @param uniswapVersion The uniswap version * @param ethereumAddress The ethereum address * @param tokenContractAddresses The token contract addresses * @param format If you want it to format it for you to the correct decimal place */ public async getAllowanceAndBalanceOfForContracts( - uniswapVersion: UniswapVersion, ethereumAddress: string, tokenContractAddresses: string[], format = false @@ -114,51 +112,32 @@ export class TokensFactory { const contractCallContexts: ContractCallContext[] = []; for (let i = 0; i < tokenContractAddresses.length; i++) { if (!isNativeEth(tokenContractAddresses[i])) { - const contractCallContext: ContractCallContext = { - reference: `allowance-and-balance-of-${i}`, - contractAddress: getAddress(tokenContractAddresses[i]), - abi: ContractContext.erc20Abi, - calls: [ - { - reference: 'allowance', - methodName: 'allowance', - methodParameters: [ - ethereumAddress, - uniswapVersion === UniswapVersion.v2 - ? UniswapContractContextV2.routerAddress - : UniswapContractContextV3.routerAddress, - ], - }, - { - reference: 'balanceOf', - methodName: 'balanceOf', - methodParameters: [ethereumAddress], - }, - { - reference: 'decimals', - methodName: 'decimals', - methodParameters: [], - }, - { - reference: 'symbol', - methodName: 'symbol', - methodParameters: [], - }, - { - reference: 'name', - methodName: 'name', - methodParameters: [], - }, - ], - }; + contractCallContexts.push( + this.buildAllowanceAndBalanceContractCallContext( + ethereumAddress, + tokenContractAddresses[i], + UniswapVersion.v2 + ) + ); - contractCallContexts.push(contractCallContext); + contractCallContexts.push( + this.buildAllowanceAndBalanceContractCallContext( + ethereumAddress, + tokenContractAddresses[i], + UniswapVersion.v3 + ) + ); } else { const token = ETH.info(this._ethersProvider.network().chainId); if (format) { results.push({ allowanceAndBalanceOf: { - allowance: new BigNumber( + allowanceV2: new BigNumber( + '0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff' + ) + .shiftedBy(18 * -1) + .toFixed(), + allowanceV3: new BigNumber( '0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff' ) .shiftedBy(18 * -1) @@ -174,7 +153,9 @@ export class TokensFactory { } else { results.push({ allowanceAndBalanceOf: { - allowance: + allowanceV2: + '0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff', + allowanceV3: '0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff', balanceOf: await this._ethersProvider.balanceOf(ethereumAddress), }, @@ -189,61 +170,125 @@ export class TokensFactory { ); for (const result in contractCallResults.results) { - const resultInfo = contractCallResults.results[result]; - - if (!format) { - results.push({ - allowanceAndBalanceOf: { - allowance: EthersBigNumber.from( - resultInfo.callsReturnContext[ALLOWANCE].returnValues[0] - ).toHexString(), - balanceOf: EthersBigNumber.from( - resultInfo.callsReturnContext[BALANCEOF].returnValues[0] - ).toHexString(), - }, - token: { - chainId: this._ethersProvider.network().chainId, - contractAddress: - resultInfo.originalContractCallContext.contractAddress, - symbol: resultInfo.callsReturnContext[SYMBOL].returnValues[0], - decimals: resultInfo.callsReturnContext[DECIMALS].returnValues[0], - name: resultInfo.callsReturnContext[NAME].returnValues[0], - }, - }); - } else { - results.push({ - allowanceAndBalanceOf: { - allowance: new BigNumber( - EthersBigNumber.from( - resultInfo.callsReturnContext[ALLOWANCE].returnValues[0] - ).toHexString() - ) - .shiftedBy( - resultInfo.callsReturnContext[DECIMALS].returnValues[0] * -1 + if (result.includes(`_${UniswapVersion.v2}`)) { + const resultInfoV2 = contractCallResults.results[result]; + const resultInfoV3 = + contractCallResults.results[ + result.replace(`_${UniswapVersion.v2}`, `_${UniswapVersion.v3}`) + ]; + + if (!format) { + results.push({ + allowanceAndBalanceOf: { + allowanceV2: EthersBigNumber.from( + resultInfoV2.callsReturnContext[ALLOWANCE].returnValues[0] + ).toHexString(), + allowanceV3: EthersBigNumber.from( + resultInfoV3.callsReturnContext[ALLOWANCE].returnValues[0] + ).toHexString(), + balanceOf: EthersBigNumber.from( + resultInfoV3.callsReturnContext[BALANCEOF].returnValues[0] + ).toHexString(), + }, + token: { + chainId: this._ethersProvider.network().chainId, + contractAddress: + resultInfoV3.originalContractCallContext.contractAddress, + symbol: resultInfoV3.callsReturnContext[SYMBOL].returnValues[0], + decimals: + resultInfoV3.callsReturnContext[DECIMALS].returnValues[0], + name: resultInfoV3.callsReturnContext[NAME].returnValues[0], + }, + }); + } else { + results.push({ + allowanceAndBalanceOf: { + allowanceV2: new BigNumber( + EthersBigNumber.from( + resultInfoV2.callsReturnContext[ALLOWANCE].returnValues[0] + ).toHexString() ) - .toFixed(), - balanceOf: new BigNumber( - EthersBigNumber.from( - resultInfo.callsReturnContext[BALANCEOF].returnValues[0] - ).toHexString() - ) - .shiftedBy( - resultInfo.callsReturnContext[DECIMALS].returnValues[0] * -1 + .shiftedBy( + resultInfoV2.callsReturnContext[DECIMALS].returnValues[0] * -1 + ) + .toFixed(), + allowanceV3: new BigNumber( + EthersBigNumber.from( + resultInfoV3.callsReturnContext[ALLOWANCE].returnValues[0] + ).toHexString() ) - .toFixed(), - }, - token: { - chainId: this._ethersProvider.network().chainId, - contractAddress: - resultInfo.originalContractCallContext.contractAddress, - symbol: resultInfo.callsReturnContext[SYMBOL].returnValues[0], - decimals: resultInfo.callsReturnContext[DECIMALS].returnValues[0], - name: resultInfo.callsReturnContext[NAME].returnValues[0], - }, - }); + .shiftedBy( + resultInfoV3.callsReturnContext[DECIMALS].returnValues[0] * -1 + ) + .toFixed(), + balanceOf: new BigNumber( + EthersBigNumber.from( + resultInfoV3.callsReturnContext[BALANCEOF].returnValues[0] + ).toHexString() + ) + .shiftedBy( + resultInfoV3.callsReturnContext[DECIMALS].returnValues[0] * -1 + ) + .toFixed(), + }, + token: { + chainId: this._ethersProvider.network().chainId, + contractAddress: + resultInfoV3.originalContractCallContext.contractAddress, + symbol: resultInfoV3.callsReturnContext[SYMBOL].returnValues[0], + decimals: + resultInfoV3.callsReturnContext[DECIMALS].returnValues[0], + name: resultInfoV3.callsReturnContext[NAME].returnValues[0], + }, + }); + } } } return results; } + + private buildAllowanceAndBalanceContractCallContext( + ethereumAddress: string, + tokenContractAddress: string, + uniswapVersion: UniswapVersion + ): ContractCallContext { + return { + reference: `${tokenContractAddress}_${uniswapVersion}`, + contractAddress: getAddress(tokenContractAddress), + abi: ContractContext.erc20Abi, + calls: [ + { + reference: 'allowance', + methodName: 'allowance', + methodParameters: [ + ethereumAddress, + uniswapVersion === UniswapVersion.v2 + ? UniswapContractContextV2.routerAddress + : UniswapContractContextV3.routerAddress, + ], + }, + { + reference: 'balanceOf', + methodName: 'balanceOf', + methodParameters: [ethereumAddress], + }, + { + reference: 'decimals', + methodName: 'decimals', + methodParameters: [], + }, + { + reference: 'symbol', + methodName: 'symbol', + methodParameters: [], + }, + { + reference: 'name', + methodName: 'name', + methodParameters: [], + }, + ], + }; + } } From 94a71340190a238aa4ec7a16ba4de2fc2be89bba Mon Sep 17 00:00:00 2001 From: Josh Stevens Date: Tue, 3 Aug 2021 17:00:09 +0100 Subject: [PATCH 4/8] only call filter with transactions if you have enough balance --- .../router/uniswap-router.factory.ts | 71 +++++++++++++------ 1 file changed, 48 insertions(+), 23 deletions(-) diff --git a/src/factories/router/uniswap-router.factory.ts b/src/factories/router/uniswap-router.factory.ts index 2f4e5ab0..c1bda39e 100644 --- a/src/factories/router/uniswap-router.factory.ts +++ b/src/factories/router/uniswap-router.factory.ts @@ -396,9 +396,14 @@ export class UniswapRouterFactory { if ( this._ethersProvider.provider.network.chainId === ChainId.MAINNET && - this._settings.gasSettings + this._settings.gasSettings && + allowanceAndBalances.enoughBalance ) { - allRoutes = await this.filterWithTransactionFees(allRoutes); + allRoutes = await this.filterWithTransactionFees( + allRoutes, + allowanceAndBalances.enoughV2Allowance, + allowanceAndBalances.enoughV3Allowance + ); } return { @@ -983,9 +988,13 @@ export class UniswapRouterFactory { /** * Work out trade fiat cost * @param allRoutes All the routes + * @param enoughAllowanceV2 Has got enough allowance for v2 + * @param enoughAllowanceV3 Has got enough allowance for v3 */ private async filterWithTransactionFees( - allRoutes: RouteQuote[] + allRoutes: RouteQuote[], + enoughAllowanceV2: boolean, + enoughAllowanceV3: boolean ): Promise { if (this._settings.gasSettings) { const ethContract = WETHContract.MAINNET().contractAddress; @@ -999,7 +1008,11 @@ export class UniswapRouterFactory { const ethUsdValue = fiatPrices[WETHContract.MAINNET().contractAddress]; if (toUsdValue && ethUsdValue) { - const bestRouteQuoteHops = this.getBestRouteQuotesHops(allRoutes); + const bestRouteQuoteHops = this.getBestRouteQuotesHops( + allRoutes, + enoughAllowanceV2, + enoughAllowanceV3 + ); const gasPrice = await this._settings.gasSettings.getGasPrice(); @@ -1067,8 +1080,14 @@ export class UniswapRouterFactory { /** * Work out the best route quote hops aka the best direct, the best 3 hop and the best 4 hop * @param allRoutes All the routes + * @param enoughAllowanceV2 Has got enough allowance for v2 + * @param enoughAllowanceV3 Has got enough allowance for v3 */ - private getBestRouteQuotesHops(allRoutes: RouteQuote[]): RouteQuote[] { + private getBestRouteQuotesHops( + allRoutes: RouteQuote[], + enoughAllowanceV2: boolean, + enoughAllowanceV3: boolean + ): RouteQuote[] { const routes: RouteQuote[] = []; for (let i = 0; i < allRoutes.length; i++) { if ( @@ -1081,27 +1100,33 @@ export class UniswapRouterFactory { const route = allRoutes[i]; if ( - route.routePathArray.length === 2 && - !routes.find((r) => r.routePathArray.length === 2) + route.uniswapVersion === UniswapVersion.v2 + ? enoughAllowanceV2 + : enoughAllowanceV3 ) { - routes.push(route); - continue; - } + if ( + route.routePathArray.length === 2 && + !routes.find((r) => r.routePathArray.length === 2) + ) { + routes.push(route); + continue; + } - if ( - route.routePathArray.length === 3 && - !routes.find((r) => r.routePathArray.length === 3) - ) { - routes.push(route); - continue; - } + if ( + route.routePathArray.length === 3 && + !routes.find((r) => r.routePathArray.length === 3) + ) { + routes.push(route); + continue; + } - if ( - route.routePathArray.length === 4 && - !routes.find((r) => r.routePathArray.length === 4) - ) { - routes.push(route); - continue; + if ( + route.routePathArray.length === 4 && + !routes.find((r) => r.routePathArray.length === 4) + ) { + routes.push(route); + continue; + } } } From 32e87ab551be1bbd5396277e69d8b27815385c2b Mon Sep 17 00:00:00 2001 From: Josh Stevens Date: Tue, 3 Aug 2021 18:11:27 +0100 Subject: [PATCH 5/8] fix all unit tests --- .../pair/uniswap-pair.factory.spec.ts | 150 +----------- .../router/uniswap-router.factory.spec.ts | 219 ++++++++++++------ .../router/uniswap-router.factory.ts | 72 +++--- .../token/token.factory.public.spec.ts | 66 ++---- src/factories/token/token.factory.spec.ts | 66 ++---- .../token/tokens.factory.public.spec.ts | 212 ++++++----------- src/factories/token/tokens.factory.spec.ts | 206 ++++++---------- 7 files changed, 375 insertions(+), 616 deletions(-) diff --git a/src/factories/pair/uniswap-pair.factory.spec.ts b/src/factories/pair/uniswap-pair.factory.spec.ts index deea6f5f..8016b7e1 100644 --- a/src/factories/pair/uniswap-pair.factory.spec.ts +++ b/src/factories/pair/uniswap-pair.factory.spec.ts @@ -6,6 +6,7 @@ import { UniswapPairFactory, UniswapPairSettings, } from '../..'; +import { CoinGecko } from '../../coin-gecko'; import { UniswapVersion } from '../../enums/uniswap-version'; import { EthersProvider } from '../../ethers-provider'; import { MockEthereumAddress } from '../../mocks/ethereum-address.mock'; @@ -30,6 +31,7 @@ describe('UniswapPairFactory', () => { }; const uniswapPairFactory = new UniswapPairFactory( + new CoinGecko(), uniswapPairFactoryContext ); @@ -105,64 +107,10 @@ describe('UniswapPairFactory', () => { }); }); - describe('hasGotEnoughAllowance', () => { - describe('v2', () => { - it('should return true if i have enough allowance', async () => { - const result = await uniswapPairFactory.hasGotEnoughAllowance( - UniswapVersion.v2, - '1' - ); - expect(result).toEqual(true); - }); - - it('should return false if i do not have enough allowance', async () => { - const factory = new UniswapPairFactory({ - fromToken: MOCKREP(), - toToken: MOCKFUN(), - ethereumAddress: MockEthereumAddress(), - settings: new UniswapPairSettings(), - ethersProvider, - }); - - const result = await factory.hasGotEnoughAllowance( - UniswapVersion.v2, - '1' - ); - expect(result).toEqual(false); - }); - }); - - describe('v3', () => { - xit('should return true if i have enough allowance', async () => { - const result = await uniswapPairFactory.hasGotEnoughAllowance( - UniswapVersion.v3, - '1' - ); - expect(result).toEqual(true); - }); - - it('should return false if i do not have enough allowance', async () => { - const factory = new UniswapPairFactory({ - fromToken: MOCKREP(), - toToken: MOCKFUN(), - ethereumAddress: MockEthereumAddress(), - settings: new UniswapPairSettings(), - ethersProvider, - }); - - const result = await factory.hasGotEnoughAllowance( - UniswapVersion.v3, - '1' - ); - expect(result).toEqual(false); - }); - }); - }); - describe('allowance', () => { describe('v2', () => { it('should return more then 0', async () => { - const factory = new UniswapPairFactory({ + const factory = new UniswapPairFactory(new CoinGecko(), { fromToken: MOCKFUN(), toToken: MOCKREP(), ethereumAddress: '0x5ab9d116a53ef41063e3eae26a7ebe736720e9ba', @@ -175,7 +123,7 @@ describe('UniswapPairFactory', () => { }); it('should return 0 allowance', async () => { - const factory = new UniswapPairFactory({ + const factory = new UniswapPairFactory(new CoinGecko(), { fromToken: MOCKREP(), toToken: MOCKFUN(), ethereumAddress: MockEthereumAddress(), @@ -190,7 +138,7 @@ describe('UniswapPairFactory', () => { describe('v3', () => { xit('should return more then 0', async () => { - const factory = new UniswapPairFactory({ + const factory = new UniswapPairFactory(new CoinGecko(), { fromToken: MOCKFUN(), toToken: MOCKREP(), ethereumAddress: '0x5ab9d116a53ef41063e3eae26a7ebe736720e9ba', @@ -203,7 +151,7 @@ describe('UniswapPairFactory', () => { }); it('should return 0 allowance', async () => { - const factory = new UniswapPairFactory({ + const factory = new UniswapPairFactory(new CoinGecko(), { fromToken: MOCKREP(), toToken: MOCKFUN(), ethereumAddress: MockEthereumAddress(), @@ -260,6 +208,7 @@ describe('UniswapPairFactory', () => { }; const uniswapPairFactory = new UniswapPairFactory( + new CoinGecko(), uniswapPairFactoryContext ); @@ -335,64 +284,10 @@ describe('UniswapPairFactory', () => { }); }); - describe('hasGotEnoughAllowance', () => { - describe('v2', () => { - it('should return true if i have enough allowance', async () => { - const result = await uniswapPairFactory.hasGotEnoughAllowance( - UniswapVersion.v2, - '1' - ); - expect(result).toEqual(true); - }); - - it('should return false if i do not have enough allowance', async () => { - const factory = new UniswapPairFactory({ - fromToken: MOCKREP(), - toToken: ETH.MAINNET(), - ethereumAddress: MockEthereumAddress(), - settings: new UniswapPairSettings(), - ethersProvider, - }); - - const result = await factory.hasGotEnoughAllowance( - UniswapVersion.v2, - '1' - ); - expect(result).toEqual(false); - }); - }); - - describe('v3', () => { - xit('should return true if i have enough allowance', async () => { - const result = await uniswapPairFactory.hasGotEnoughAllowance( - UniswapVersion.v3, - '1' - ); - expect(result).toEqual(true); - }); - - it('should return false if i do not have enough allowance', async () => { - const factory = new UniswapPairFactory({ - fromToken: MOCKREP(), - toToken: ETH.MAINNET(), - ethereumAddress: MockEthereumAddress(), - settings: new UniswapPairSettings(), - ethersProvider, - }); - - const result = await factory.hasGotEnoughAllowance( - UniswapVersion.v3, - '1' - ); - expect(result).toEqual(false); - }); - }); - }); - describe('allowance', () => { describe('v2', () => { it('should return more then 0', async () => { - const factory = new UniswapPairFactory({ + const factory = new UniswapPairFactory(new CoinGecko(), { fromToken: MOCKFUN(), toToken: ETH.MAINNET(), ethereumAddress: '0x5ab9d116a53ef41063e3eae26a7ebe736720e9ba', @@ -405,7 +300,7 @@ describe('UniswapPairFactory', () => { }); it('should return 0 allowance', async () => { - const factory = new UniswapPairFactory({ + const factory = new UniswapPairFactory(new CoinGecko(), { fromToken: MOCKREP(), toToken: ETH.MAINNET(), ethereumAddress: MockEthereumAddress(), @@ -420,7 +315,7 @@ describe('UniswapPairFactory', () => { describe('v3', () => { xit('should return more then 0', async () => { - const factory = new UniswapPairFactory({ + const factory = new UniswapPairFactory(new CoinGecko(), { fromToken: MOCKFUN(), toToken: ETH.MAINNET(), ethereumAddress: '0x5ab9d116a53ef41063e3eae26a7ebe736720e9ba', @@ -433,7 +328,7 @@ describe('UniswapPairFactory', () => { }); it('should return 0 allowance', async () => { - const factory = new UniswapPairFactory({ + const factory = new UniswapPairFactory(new CoinGecko(), { fromToken: MOCKREP(), toToken: ETH.MAINNET(), ethereumAddress: MockEthereumAddress(), @@ -490,6 +385,7 @@ describe('UniswapPairFactory', () => { }; const uniswapPairFactory = new UniswapPairFactory( + new CoinGecko(), uniswapPairFactoryContext ); @@ -565,28 +461,6 @@ describe('UniswapPairFactory', () => { }); }); - describe('hasGotEnoughAllowance', () => { - describe('v2', () => { - it('should always return true as not allowance needed', async () => { - const result = await uniswapPairFactory.hasGotEnoughAllowance( - UniswapVersion.v2, - '1' - ); - expect(result).toEqual(true); - }); - }); - - describe('v3', () => { - it('should always return true as not allowance needed', async () => { - const result = await uniswapPairFactory.hasGotEnoughAllowance( - UniswapVersion.v3, - '1' - ); - expect(result).toEqual(true); - }); - }); - }); - describe('allowance', () => { describe('v2', () => { it('should always return max hex', async () => { diff --git a/src/factories/router/uniswap-router.factory.spec.ts b/src/factories/router/uniswap-router.factory.spec.ts index 2926e853..c3fad184 100644 --- a/src/factories/router/uniswap-router.factory.spec.ts +++ b/src/factories/router/uniswap-router.factory.spec.ts @@ -1,8 +1,16 @@ import BigNumber from 'bignumber.js'; -import { ChainId, ErrorCodes, ETH, UniswapError } from '../..'; +import { + ChainId, + ErrorCodes, + ETH, + UniswapError, + UniswapPairSettings, +} from '../..'; +import { CoinGecko } from '../../coin-gecko'; import { UniswapVersion } from '../../enums/uniswap-version'; import { EthersProvider } from '../../ethers-provider'; import { MOCKAAVE } from '../../mocks/aave-token.mock'; +import { MockEthereumAddress } from '../../mocks/ethereum-address.mock'; import { MOCKFUN } from '../../mocks/fun-token.mock'; import { MOCKREP } from '../../mocks/rep-token.mock'; import { MOCKUNI } from '../../mocks/uni-token.mock'; @@ -17,10 +25,11 @@ describe('UniswapRouterFactory', () => { const toToken = MOCKUNI(); const uniswapRouterFactory = new UniswapRouterFactory( + new CoinGecko(), + MockEthereumAddress(), fromToken, toToken, - false, - [UniswapVersion.v2, UniswapVersion.v3], + new UniswapPairSettings(), ethersProvider ); @@ -36,10 +45,11 @@ describe('UniswapRouterFactory', () => { it('should only return direct routes (in this case return nothing as there is no direct route)', async () => { const factory = new UniswapRouterFactory( + new CoinGecko(), + MockEthereumAddress(), fromToken, toToken, - true, - [UniswapVersion.v2, UniswapVersion.v3], + new UniswapPairSettings({ disableMultihops: true }), ethersProvider ); @@ -58,10 +68,11 @@ describe('UniswapRouterFactory', () => { it('should only return direct routes (in this case return nothing as there is no direct route)', async () => { const factory = new UniswapRouterFactory( + new CoinGecko(), + MockEthereumAddress(), fromToken, toToken, - true, - [UniswapVersion.v2, UniswapVersion.v3], + new UniswapPairSettings({ disableMultihops: true }), ethersProvider ); @@ -86,10 +97,11 @@ describe('UniswapRouterFactory', () => { it('should only return direct routes (in this case return nothing as there is no direct route)', async () => { const factory = new UniswapRouterFactory( + new CoinGecko(), + MockEthereumAddress(), fromToken, toToken, - true, - [UniswapVersion.v2, UniswapVersion.v3], + new UniswapPairSettings({ disableMultihops: true }), ethersProvider ); @@ -115,10 +127,11 @@ describe('UniswapRouterFactory', () => { it('should only return direct routes (in this case return nothing as there is no direct route)', async () => { const factory = new UniswapRouterFactory( + new CoinGecko(), + MockEthereumAddress(), fromToken, toToken, - true, - [UniswapVersion.v2, UniswapVersion.v3], + new UniswapPairSettings({ disableMultihops: true }), ethersProvider ); @@ -138,10 +151,11 @@ describe('UniswapRouterFactory', () => { describe(TradeDirection.input, () => { it('should find best route', async () => { const factory = new UniswapRouterFactory( + new CoinGecko(), + MockEthereumAddress(), MOCKFUN(), MOCKREP(), - false, - [UniswapVersion.v2], + new UniswapPairSettings({ uniswapVersions: [UniswapVersion.v2] }), ethersProvider ); @@ -156,10 +170,11 @@ describe('UniswapRouterFactory', () => { describe(TradeDirection.output, () => { it('should find best route', async () => { const factory = new UniswapRouterFactory( + new CoinGecko(), + MockEthereumAddress(), MOCKFUN(), MOCKREP(), - false, - [UniswapVersion.v2], + new UniswapPairSettings({ uniswapVersions: [UniswapVersion.v2] }), ethersProvider ); @@ -176,10 +191,11 @@ describe('UniswapRouterFactory', () => { describe(TradeDirection.input, () => { it('should find best route', async () => { const factory = new UniswapRouterFactory( + new CoinGecko(), + MockEthereumAddress(), fromToken, toToken, - false, - [UniswapVersion.v3], + new UniswapPairSettings({ uniswapVersions: [UniswapVersion.v3] }), ethersProvider ); @@ -194,10 +210,11 @@ describe('UniswapRouterFactory', () => { describe(TradeDirection.output, () => { it('should find best route', async () => { const factory = new UniswapRouterFactory( + new CoinGecko(), + MockEthereumAddress(), fromToken, toToken, - false, - [UniswapVersion.v3], + new UniswapPairSettings({ uniswapVersions: [UniswapVersion.v3] }), ethersProvider ); @@ -227,10 +244,14 @@ describe('UniswapRouterFactory', () => { it('should throw an error as there is no best route with disableMultihops turned on', async () => { const factory = new UniswapRouterFactory( + new CoinGecko(), + MockEthereumAddress(), MOCKFUN(), MOCKREP(), - true, - [UniswapVersion.v2], + new UniswapPairSettings({ + uniswapVersions: [UniswapVersion.v2], + disableMultihops: true, + }), ethersProvider ); @@ -262,10 +283,14 @@ describe('UniswapRouterFactory', () => { it('should throw an error as there is no best route with disableMultihops turned on', async () => { const factory = new UniswapRouterFactory( + new CoinGecko(), + MockEthereumAddress(), MOCKFUN(), MOCKREP(), - true, - [UniswapVersion.v2], + new UniswapPairSettings({ + uniswapVersions: [UniswapVersion.v2], + disableMultihops: true, + }), ethersProvider ); @@ -287,10 +312,11 @@ describe('UniswapRouterFactory', () => { const toToken = ETH.MAINNET(); const uniswapRouterFactory = new UniswapRouterFactory( + new CoinGecko(), + MockEthereumAddress(), fromToken, toToken, - false, - [UniswapVersion.v2, UniswapVersion.v3], + new UniswapPairSettings(), ethersProvider ); @@ -306,10 +332,14 @@ describe('UniswapRouterFactory', () => { it('should only return direct routes', async () => { const factory = new UniswapRouterFactory( + new CoinGecko(), + MockEthereumAddress(), fromToken, toToken, - true, - [UniswapVersion.v2], + new UniswapPairSettings({ + uniswapVersions: [UniswapVersion.v2], + disableMultihops: true, + }), ethersProvider ); @@ -331,10 +361,14 @@ describe('UniswapRouterFactory', () => { it('should only return direct routes', async () => { const factory = new UniswapRouterFactory( + new CoinGecko(), + MockEthereumAddress(), fromToken, toToken, - true, - [UniswapVersion.v3], + new UniswapPairSettings({ + uniswapVersions: [UniswapVersion.v3], + disableMultihops: true, + }), ethersProvider ); @@ -361,10 +395,11 @@ describe('UniswapRouterFactory', () => { it('should only return direct routes', async () => { const factory = new UniswapRouterFactory( + new CoinGecko(), + MockEthereumAddress(), fromToken, toToken, - true, - [UniswapVersion.v2, UniswapVersion.v3], + new UniswapPairSettings({ disableMultihops: true }), ethersProvider ); @@ -390,10 +425,11 @@ describe('UniswapRouterFactory', () => { it('should only return direct routes', async () => { const factory = new UniswapRouterFactory( + new CoinGecko(), + MockEthereumAddress(), fromToken, toToken, - true, - [UniswapVersion.v2, UniswapVersion.v3], + new UniswapPairSettings({ disableMultihops: true }), ethersProvider ); @@ -413,10 +449,11 @@ describe('UniswapRouterFactory', () => { describe(TradeDirection.input, () => { it('should find best route', async () => { const factory = new UniswapRouterFactory( + new CoinGecko(), + MockEthereumAddress(), MOCKFUN(), toToken, - false, - [UniswapVersion.v2], + new UniswapPairSettings({ uniswapVersions: [UniswapVersion.v2] }), ethersProvider ); @@ -431,10 +468,11 @@ describe('UniswapRouterFactory', () => { describe(TradeDirection.output, () => { it('should find best route', async () => { const factory = new UniswapRouterFactory( + new CoinGecko(), + MockEthereumAddress(), MOCKFUN(), toToken, - false, - [UniswapVersion.v2], + new UniswapPairSettings({ uniswapVersions: [UniswapVersion.v2] }), ethersProvider ); @@ -451,10 +489,11 @@ describe('UniswapRouterFactory', () => { describe(TradeDirection.input, () => { it('should find best route', async () => { const factory = new UniswapRouterFactory( + new CoinGecko(), + MockEthereumAddress(), MOCKAAVE(), toToken, - false, - [UniswapVersion.v3], + new UniswapPairSettings({ uniswapVersions: [UniswapVersion.v3] }), ethersProvider ); @@ -469,10 +508,11 @@ describe('UniswapRouterFactory', () => { describe(TradeDirection.output, () => { it('should find best route', async () => { const factory = new UniswapRouterFactory( + new CoinGecko(), + MockEthereumAddress(), MOCKAAVE(), toToken, - false, - [UniswapVersion.v3], + new UniswapPairSettings({ uniswapVersions: [UniswapVersion.v3] }), ethersProvider ); @@ -496,10 +536,13 @@ describe('UniswapRouterFactory', () => { it('should return best route', async () => { const factory = new UniswapRouterFactory( + new CoinGecko(), + MockEthereumAddress(), fromToken, toToken, - true, - [UniswapVersion.v2, UniswapVersion.v3], + new UniswapPairSettings({ + uniswapVersions: [UniswapVersion.v2, UniswapVersion.v3], + }), ethersProvider ); @@ -512,7 +555,7 @@ describe('UniswapRouterFactory', () => { expect( result.triedRoutesQuote.filter((c) => c.routePathArray.length > 2) .length > 0 - ).toEqual(false); + ).toEqual(true); }); }); @@ -527,10 +570,13 @@ describe('UniswapRouterFactory', () => { it('should return best route', async () => { const factory = new UniswapRouterFactory( + new CoinGecko(), + MockEthereumAddress(), fromToken, toToken, - true, - [UniswapVersion.v2, UniswapVersion.v3], + new UniswapPairSettings({ + uniswapVersions: [UniswapVersion.v2, UniswapVersion.v3], + }), ethersProvider ); @@ -554,10 +600,13 @@ describe('UniswapRouterFactory', () => { const toToken = MOCKAAVE(); const uniswapRouterFactory = new UniswapRouterFactory( + new CoinGecko(), + MockEthereumAddress(), fromToken, toToken, - false, - [UniswapVersion.v2, UniswapVersion.v3], + new UniswapPairSettings({ + uniswapVersions: [UniswapVersion.v2, UniswapVersion.v3], + }), ethersProvider ); @@ -573,10 +622,14 @@ describe('UniswapRouterFactory', () => { it('should only return direct routes', async () => { const factory = new UniswapRouterFactory( + new CoinGecko(), + MockEthereumAddress(), fromToken, toToken, - true, - [UniswapVersion.v2], + new UniswapPairSettings({ + uniswapVersions: [UniswapVersion.v2], + disableMultihops: true, + }), ethersProvider ); @@ -598,10 +651,14 @@ describe('UniswapRouterFactory', () => { it('should only return direct routes', async () => { const factory = new UniswapRouterFactory( + new CoinGecko(), + MockEthereumAddress(), fromToken, toToken, - true, - [UniswapVersion.v3], + new UniswapPairSettings({ + uniswapVersions: [UniswapVersion.v3], + disableMultihops: true, + }), ethersProvider ); @@ -628,10 +685,14 @@ describe('UniswapRouterFactory', () => { it('should only return direct routes', async () => { const factory = new UniswapRouterFactory( + new CoinGecko(), + MockEthereumAddress(), fromToken, toToken, - true, - [UniswapVersion.v2, UniswapVersion.v3], + new UniswapPairSettings({ + uniswapVersions: [UniswapVersion.v2, UniswapVersion.v3], + disableMultihops: true, + }), ethersProvider ); @@ -656,10 +717,14 @@ describe('UniswapRouterFactory', () => { it('should only return direct routes', async () => { const factory = new UniswapRouterFactory( + new CoinGecko(), + MockEthereumAddress(), fromToken, toToken, - true, - [UniswapVersion.v2, UniswapVersion.v3], + new UniswapPairSettings({ + uniswapVersions: [UniswapVersion.v2, UniswapVersion.v3], + disableMultihops: true, + }), ethersProvider ); @@ -679,10 +744,13 @@ describe('UniswapRouterFactory', () => { describe(TradeDirection.input, () => { it('should find best route', async () => { const factory = new UniswapRouterFactory( + new CoinGecko(), + MockEthereumAddress(), fromToken, MOCKFUN(), - false, - [UniswapVersion.v2], + new UniswapPairSettings({ + uniswapVersions: [UniswapVersion.v2], + }), ethersProvider ); @@ -697,10 +765,13 @@ describe('UniswapRouterFactory', () => { describe(TradeDirection.output, () => { it('should find best route', async () => { const factory = new UniswapRouterFactory( + new CoinGecko(), + MockEthereumAddress(), fromToken, MOCKFUN(), - false, - [UniswapVersion.v2], + new UniswapPairSettings({ + uniswapVersions: [UniswapVersion.v2], + }), ethersProvider ); @@ -717,10 +788,13 @@ describe('UniswapRouterFactory', () => { describe(TradeDirection.input, () => { it('should find best route', async () => { const factory = new UniswapRouterFactory( + new CoinGecko(), + MockEthereumAddress(), fromToken, toToken, - false, - [UniswapVersion.v3], + new UniswapPairSettings({ + uniswapVersions: [UniswapVersion.v3], + }), ethersProvider ); @@ -735,10 +809,13 @@ describe('UniswapRouterFactory', () => { describe(TradeDirection.output, () => { it('should find best route', async () => { const factory = new UniswapRouterFactory( + new CoinGecko(), + MockEthereumAddress(), fromToken, toToken, - false, - [UniswapVersion.v3], + new UniswapPairSettings({ + uniswapVersions: [UniswapVersion.v3], + }), ethersProvider ); @@ -762,10 +839,13 @@ describe('UniswapRouterFactory', () => { it('should return best route', async () => { const factory = new UniswapRouterFactory( + new CoinGecko(), + MockEthereumAddress(), fromToken, toToken, - false, - [UniswapVersion.v2, UniswapVersion.v3], + new UniswapPairSettings({ + uniswapVersions: [UniswapVersion.v2, UniswapVersion.v3], + }), ethersProvider ); @@ -789,10 +869,13 @@ describe('UniswapRouterFactory', () => { it('should return best route', async () => { const factory = new UniswapRouterFactory( + new CoinGecko(), + MockEthereumAddress(), fromToken, toToken, - false, - [UniswapVersion.v2, UniswapVersion.v3], + new UniswapPairSettings({ + uniswapVersions: [UniswapVersion.v2, UniswapVersion.v3], + }), ethersProvider ); diff --git a/src/factories/router/uniswap-router.factory.ts b/src/factories/router/uniswap-router.factory.ts index c1bda39e..9495877b 100644 --- a/src/factories/router/uniswap-router.factory.ts +++ b/src/factories/router/uniswap-router.factory.ts @@ -433,6 +433,26 @@ export class UniswapRouterFactory { }; } + /** + * Generates the trade datetime unix time + */ + public generateTradeDeadlineUnixTime(): number { + const now = new Date(); + const expiryDate = new Date( + now.getTime() + this._settings.deadlineMinutes * 60000 + ); + return (expiryDate.getTime() / 1e3) | 0; + } + + /** + * Get eth balance + */ + public async getEthBalance(): Promise { + const balance = await this._ethersProvider.balanceOf(this._ethereumAddress); + + return new BigNumber(balance).shiftedBy(Constants.ETH_MAX_DECIMALS * -1); + } + /** * Generate trade data eth > erc20 * @param ethAmountIn The eth amount in @@ -440,7 +460,7 @@ export class UniswapRouterFactory { * @param routeQuoteTradeContext The route quote trade context * @param deadline The deadline it expiries unix time */ - public generateTradeDataEthToErc20Input( + private generateTradeDataEthToErc20Input( ethAmountIn: BigNumber, tokenAmount: BigNumber, routeQuoteTradeContext: RouteQuoteTradeContext, @@ -483,7 +503,7 @@ export class UniswapRouterFactory { * @param routeQuote The route quote * @param deadline The deadline it expiries unix time */ - public generateTradeDataEthToErc20Output( + private generateTradeDataEthToErc20Output( ethAmountInMax: BigNumber, tokenAmountOut: BigNumber, routeQuoteTradeContext: RouteQuoteTradeContext, @@ -525,7 +545,7 @@ export class UniswapRouterFactory { * @param routeQuoteTradeContext The route quote trade context * @param deadline The deadline it expiries unix time */ - public generateTradeDataErc20ToEthInput( + private generateTradeDataErc20ToEthInput( tokenAmount: BigNumber, ethAmountOutMin: BigNumber, routeQuoteTradeContext: RouteQuoteTradeContext, @@ -569,7 +589,7 @@ export class UniswapRouterFactory { * @param routeQuoteTradeContext The route quote trade context * @param deadline The deadline it expiries unix time */ - public generateTradeDataErc20ToEthOutput( + private generateTradeDataErc20ToEthOutput( tokenAmountInMax: BigNumber, ethAmountOut: BigNumber, routeQuoteTradeContext: RouteQuoteTradeContext, @@ -613,7 +633,7 @@ export class UniswapRouterFactory { * @param routeQuoteTradeContext The route quote trade context * @param deadline The deadline it expiries unix time */ - public generateTradeDataErc20ToErc20Input( + private generateTradeDataErc20ToErc20Input( tokenAmount: BigNumber, tokenAmountMin: BigNumber, routeQuoteTradeContext: RouteQuoteTradeContext, @@ -658,7 +678,7 @@ export class UniswapRouterFactory { * @param routeQuoteTradeContext The route quote trade context * @param deadline The deadline it expiries unix time */ - public generateTradeDataErc20ToErc20Output( + private generateTradeDataErc20ToErc20Output( tokenAmountInMax: BigNumber, tokenAmountOut: BigNumber, routeQuoteTradeContext: RouteQuoteTradeContext, @@ -704,7 +724,7 @@ export class UniswapRouterFactory { * @param liquidityProviderFee The liquidity provider fee * @param deadline The deadline it expiries unix time */ - public generateTradeDataForV3Input( + private generateTradeDataForV3Input( tokenAmount: BigNumber, tokenAmountMin: BigNumber, liquidityProviderFee: number, @@ -728,7 +748,7 @@ export class UniswapRouterFactory { * Build up a transaction for erc20 from * @param data The data */ - public buildUpTransactionErc20( + private buildUpTransactionErc20( uniswapVersion: UniswapVersion, data: string ): Transaction { @@ -748,7 +768,7 @@ export class UniswapRouterFactory { * @param ethValue The eth value * @param data The data */ - public buildUpTransactionEth( + private buildUpTransactionEth( uniswapVersion: UniswapVersion, ethValue: BigNumber, data: string @@ -764,21 +784,10 @@ export class UniswapRouterFactory { }; } - /** - * Generates the trade datetime unix time - */ - public generateTradeDeadlineUnixTime(): number { - const now = new Date(); - const expiryDate = new Date( - now.getTime() + this._settings.deadlineMinutes * 60000 - ); - return (expiryDate.getTime() / 1e3) | 0; - } - /** * Get the allowance and balance for the from and to token (will get balance for eth as well) */ - public async getAllowanceAndBalanceForTokens(): Promise<{ + private async getAllowanceAndBalanceForTokens(): Promise<{ fromToken: AllowanceAndBalanceOf; toToken: AllowanceAndBalanceOf; }> { @@ -791,10 +800,14 @@ export class UniswapRouterFactory { return { fromToken: allowanceAndBalanceOfForTokens.find( - (c) => c.token.contractAddress === this._fromToken.contractAddress + (c) => + c.token.contractAddress.toLowerCase() === + this._fromToken.contractAddress.toLowerCase() )!.allowanceAndBalanceOf, toToken: allowanceAndBalanceOfForTokens.find( - (c) => c.token.contractAddress === this._toToken.contractAddress + (c) => + c.token.contractAddress.toLowerCase() === + this._toToken.contractAddress.toLowerCase() )!.allowanceAndBalanceOf, }; } @@ -803,7 +816,7 @@ export class UniswapRouterFactory { * Has got enough allowance to do the trade * @param amount The amount you want to swap */ - public hasGotEnoughAllowance(amount: string, allowance: string): boolean { + private hasGotEnoughAllowance(amount: string, allowance: string): boolean { if (this.tradePath() === TradePath.ethToErc20) { return true; } @@ -819,15 +832,6 @@ export class UniswapRouterFactory { return true; } - /** - * Get eth balance - */ - public async getEthBalance(): Promise { - const balance = await this._ethersProvider.balanceOf(this._ethereumAddress); - - return new BigNumber(balance).shiftedBy(Constants.ETH_MAX_DECIMALS * -1); - } - private async hasEnoughAllowanceAndBalance( amountToTrade: BigNumber, bestRouteQuote: RouteQuote, @@ -996,7 +1000,7 @@ export class UniswapRouterFactory { enoughAllowanceV2: boolean, enoughAllowanceV3: boolean ): Promise { - if (this._settings.gasSettings) { + if (this._settings.gasSettings && !this._settings.disableMultihops) { const ethContract = WETHContract.MAINNET().contractAddress; const fiatPrices = await this._coinGecko.getCoinGeckoFiatPrices([ diff --git a/src/factories/token/token.factory.public.spec.ts b/src/factories/token/token.factory.public.spec.ts index 290da315..84dd4fca 100644 --- a/src/factories/token/token.factory.public.spec.ts +++ b/src/factories/token/token.factory.public.spec.ts @@ -182,59 +182,29 @@ describe('TokenFactoryPublic', () => { describe('getAllowanceAndBalanceOf', () => { describe('erc20', () => { - describe('v2', () => { - it('getAllowanceAndBalanceOf', async () => { - const result = await tokenFactory.getAllowanceAndBalanceOf( - UniswapVersion.v2, - MockEthereumAddress() - ); - expect(result).toEqual({ - allowance: '0x2386c18764e720', - balanceOf: '0x1e72af98f7', - }); - }); - }); - - describe('v3', () => { - it('getAllowanceAndBalanceOf', async () => { - const result = await tokenFactory.getAllowanceAndBalanceOf( - UniswapVersion.v3, - MockEthereumAddress() - ); - expect(result).toEqual({ - allowance: '0x00', - balanceOf: '0x1e72af98f7', - }); + it('getAllowanceAndBalanceOf', async () => { + const result = await tokenFactory.getAllowanceAndBalanceOf( + MockEthereumAddress() + ); + expect(result).toEqual({ + allowanceV2: '0x2386c18764e720', + allowanceV3: '0x00', + balanceOf: '0x00', }); }); }); describe('eth', () => { - describe('v2', () => { - it('getAllowanceAndBalanceOf', async () => { - const result = await tokenFactoryEth.getAllowanceAndBalanceOf( - UniswapVersion.v2, - MockEthereumAddress() - ); - expect(result).toEqual({ - allowance: - '0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff', - balanceOf: '0x03034545d3b362a1', - }); - }); - }); - - describe('v3', () => { - it('getAllowanceAndBalanceOf', async () => { - const result = await tokenFactoryEth.getAllowanceAndBalanceOf( - UniswapVersion.v3, - MockEthereumAddress() - ); - expect(result).toEqual({ - allowance: - '0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff', - balanceOf: '0x03034545d3b362a1', - }); + it('getAllowanceAndBalanceOf', async () => { + const result = await tokenFactoryEth.getAllowanceAndBalanceOf( + MockEthereumAddress() + ); + expect(result).toEqual({ + allowanceV2: + '0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff', + allowanceV3: + '0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff', + balanceOf: '0x00', }); }); }); diff --git a/src/factories/token/token.factory.spec.ts b/src/factories/token/token.factory.spec.ts index 071aeb9f..2f47169f 100644 --- a/src/factories/token/token.factory.spec.ts +++ b/src/factories/token/token.factory.spec.ts @@ -178,59 +178,29 @@ describe('TokenFactory', () => { describe('getAllowanceAndBalanceOf', () => { describe('erc20', () => { - describe('v2', () => { - it('getAllowanceAndBalanceOf', async () => { - const result = await tokenFactory.getAllowanceAndBalanceOf( - UniswapVersion.v2, - MockEthereumAddress() - ); - expect(result).toEqual({ - allowance: '0x2386c18764e720', - balanceOf: '0x1e72af98f7', - }); - }); - }); - - describe('v3', () => { - it('getAllowanceAndBalanceOf', async () => { - const result = await tokenFactory.getAllowanceAndBalanceOf( - UniswapVersion.v3, - MockEthereumAddress() - ); - expect(result).toEqual({ - allowance: '0x00', - balanceOf: '0x1e72af98f7', - }); + it('getAllowanceAndBalanceOf', async () => { + const result = await tokenFactory.getAllowanceAndBalanceOf( + MockEthereumAddress() + ); + expect(result).toEqual({ + allowanceV2: '0x2386c18764e720', + allowanceV3: '0x00', + balanceOf: '0x00', }); }); }); describe('eth', () => { - describe('v2', () => { - it('getAllowanceAndBalanceOf', async () => { - const result = await tokenFactoryEth.getAllowanceAndBalanceOf( - UniswapVersion.v2, - MockEthereumAddress() - ); - expect(result).toEqual({ - allowance: - '0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff', - balanceOf: '0x03034545d3b362a1', - }); - }); - }); - - describe('v3', () => { - it('getAllowanceAndBalanceOf', async () => { - const result = await tokenFactoryEth.getAllowanceAndBalanceOf( - UniswapVersion.v3, - MockEthereumAddress() - ); - expect(result).toEqual({ - allowance: - '0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff', - balanceOf: '0x03034545d3b362a1', - }); + it('getAllowanceAndBalanceOf', async () => { + const result = await tokenFactoryEth.getAllowanceAndBalanceOf( + MockEthereumAddress() + ); + expect(result).toEqual({ + allowanceV2: + '0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff', + allowanceV3: + '0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff', + balanceOf: '0x00', }); }); }); diff --git a/src/factories/token/tokens.factory.public.spec.ts b/src/factories/token/tokens.factory.public.spec.ts index 96e57931..5f5982a8 100644 --- a/src/factories/token/tokens.factory.public.spec.ts +++ b/src/factories/token/tokens.factory.public.spec.ts @@ -1,10 +1,4 @@ -import { - ChainId, - ErrorCodes, - TokensFactoryPublic, - UniswapError, - UniswapVersion, -} from '../..'; +import { ChainId, ErrorCodes, TokensFactoryPublic, UniswapError } from '../..'; import { ETH } from '../../common/tokens'; import { MockEthereumAddress } from '../../mocks/ethereum-address.mock'; import { MOCKFUN } from '../../mocks/fun-token.mock'; @@ -41,147 +35,79 @@ describe('TokensFactoryPublic', () => { }); describe('getAllowanceAndBalanceOfForContracts', () => { - describe('v2', () => { - it('should return correct info - formatted', async () => { - const result = await tokensFactory.getAllowanceAndBalanceOfForContracts( - UniswapVersion.v2, - MockEthereumAddress(), - [ - ETH.MAINNET().contractAddress, - MOCKFUN().contractAddress, - MOCKREP().contractAddress, - ], - true - ); - expect(result[0]).toEqual({ - token: ETH.MAINNET(), - allowanceAndBalanceOf: { - allowance: - '115792089237316195423570985008687907853269984665640564039457.584007913129639935', - balanceOf: '0.217093373250724513', - }, - }); - expect(result[1]).toEqual({ - token: MOCKFUN(), - allowanceAndBalanceOf: { - allowance: '99997899.4322', - balanceOf: '1307.73129463', - }, - }); - expect(result[2]).toEqual({ - token: MOCKREP(), - allowanceAndBalanceOf: { - allowance: '0', - balanceOf: '0', - }, - }); + it('should return correct info - formatted', async () => { + const result = await tokensFactory.getAllowanceAndBalanceOfForContracts( + MockEthereumAddress(), + [ + ETH.MAINNET().contractAddress, + MOCKFUN().contractAddress, + MOCKREP().contractAddress, + ], + true + ); + expect(result[0]).toEqual({ + token: ETH.MAINNET(), + allowanceAndBalanceOf: { + allowanceV2: + '115792089237316195423570985008687907853269984665640564039457.584007913129639935', + allowanceV3: + '115792089237316195423570985008687907853269984665640564039457.584007913129639935', + balanceOf: '0', + }, }); - - it('should return correct info - unformatted', async () => { - const result = await tokensFactory.getAllowanceAndBalanceOfForContracts( - UniswapVersion.v2, - MockEthereumAddress(), - [ - ETH.MAINNET().contractAddress, - MOCKFUN().contractAddress, - MOCKREP().contractAddress, - ], - false - ); - expect(result[0]).toEqual({ - token: ETH.MAINNET(), - allowanceAndBalanceOf: { - allowance: - '0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff', - balanceOf: '0x03034545d3b362a1', - }, - }); - expect(result[1]).toEqual({ - token: MOCKFUN(), - allowanceAndBalanceOf: { - allowance: '0x2386c18764e720', - balanceOf: '0x1e72af98f7', - }, - }); - expect(result[2]).toEqual({ - token: MOCKREP(), - allowanceAndBalanceOf: { - allowance: '0x00', - balanceOf: '0x00', - }, - }); + expect(result[1]).toEqual({ + token: MOCKFUN(), + allowanceAndBalanceOf: { + allowanceV2: '99997899.4322', + allowanceV3: '0', + balanceOf: '0', + }, + }); + expect(result[2]).toEqual({ + token: MOCKREP(), + allowanceAndBalanceOf: { + allowanceV2: '0', + allowanceV3: '0', + balanceOf: '0', + }, }); }); - describe('v3', () => { - it('should return correct info - formatted', async () => { - const result = await tokensFactory.getAllowanceAndBalanceOfForContracts( - UniswapVersion.v3, - MockEthereumAddress(), - [ - ETH.MAINNET().contractAddress, - MOCKFUN().contractAddress, - MOCKREP().contractAddress, - ], - true - ); - expect(result[0]).toEqual({ - token: ETH.MAINNET(), - allowanceAndBalanceOf: { - allowance: - '115792089237316195423570985008687907853269984665640564039457.584007913129639935', - balanceOf: '0.217093373250724513', - }, - }); - expect(result[1]).toEqual({ - token: MOCKFUN(), - allowanceAndBalanceOf: { - allowance: '0', - balanceOf: '1307.73129463', - }, - }); - expect(result[2]).toEqual({ - token: MOCKREP(), - allowanceAndBalanceOf: { - allowance: '0', - balanceOf: '0', - }, - }); + it('should return correct info - unformatted', async () => { + const result = await tokensFactory.getAllowanceAndBalanceOfForContracts( + MockEthereumAddress(), + [ + ETH.MAINNET().contractAddress, + MOCKFUN().contractAddress, + MOCKREP().contractAddress, + ], + false + ); + expect(result[0]).toEqual({ + token: ETH.MAINNET(), + allowanceAndBalanceOf: { + allowanceV2: + '0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff', + allowanceV3: + '0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff', + balanceOf: '0x00', + }, }); - - it('should return correct info - unformatted', async () => { - const result = await tokensFactory.getAllowanceAndBalanceOfForContracts( - UniswapVersion.v3, - MockEthereumAddress(), - [ - ETH.MAINNET().contractAddress, - MOCKFUN().contractAddress, - MOCKREP().contractAddress, - ], - false - ); - expect(result[0]).toEqual({ - token: ETH.MAINNET(), - allowanceAndBalanceOf: { - allowance: - '0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff', - balanceOf: '0x03034545d3b362a1', - }, - }); - expect(result[1]).toEqual({ - token: MOCKFUN(), - allowanceAndBalanceOf: { - allowance: '0x00', - balanceOf: '0x1e72af98f7', - }, - }); - expect(result[2]).toEqual({ - token: MOCKREP(), - allowanceAndBalanceOf: { - allowance: '0x00', - balanceOf: '0x00', - }, - }); + expect(result[1]).toEqual({ + token: MOCKFUN(), + allowanceAndBalanceOf: { + allowanceV2: '0x2386c18764e720', + allowanceV3: '0x00', + balanceOf: '0x00', + }, + }); + expect(result[2]).toEqual({ + token: MOCKREP(), + allowanceAndBalanceOf: { + allowanceV2: '0x00', + allowanceV3: '0x00', + balanceOf: '0x00', + }, }); }); }); diff --git a/src/factories/token/tokens.factory.spec.ts b/src/factories/token/tokens.factory.spec.ts index f17a883e..404fa194 100644 --- a/src/factories/token/tokens.factory.spec.ts +++ b/src/factories/token/tokens.factory.spec.ts @@ -1,4 +1,4 @@ -import { ChainId, ErrorCodes, UniswapError, UniswapVersion } from '../..'; +import { ChainId, ErrorCodes, UniswapError } from '../..'; import { ETH } from '../../common/tokens'; import { EthersProvider } from '../../ethers-provider'; import { MockEthereumAddress } from '../../mocks/ethereum-address.mock'; @@ -39,147 +39,79 @@ describe('TokensFactory', () => { }); describe('getAllowanceAndBalanceOfForContracts', () => { - describe('v2', () => { - it('should return correct info - formatted', async () => { - const result = await tokensFactory.getAllowanceAndBalanceOfForContracts( - UniswapVersion.v2, - MockEthereumAddress(), - [ - ETH.MAINNET().contractAddress, - MOCKFUN().contractAddress, - MOCKREP().contractAddress, - ], - true - ); - expect(result[0]).toEqual({ - token: ETH.MAINNET(), - allowanceAndBalanceOf: { - allowance: - '115792089237316195423570985008687907853269984665640564039457.584007913129639935', - balanceOf: '0.217093373250724513', - }, - }); - expect(result[1]).toEqual({ - token: MOCKFUN(), - allowanceAndBalanceOf: { - allowance: '99997899.4322', - balanceOf: '1307.73129463', - }, - }); - expect(result[2]).toEqual({ - token: MOCKREP(), - allowanceAndBalanceOf: { - allowance: '0', - balanceOf: '0', - }, - }); + it('should return correct info - formatted', async () => { + const result = await tokensFactory.getAllowanceAndBalanceOfForContracts( + MockEthereumAddress(), + [ + ETH.MAINNET().contractAddress, + MOCKFUN().contractAddress, + MOCKREP().contractAddress, + ], + true + ); + expect(result[0]).toEqual({ + token: ETH.MAINNET(), + allowanceAndBalanceOf: { + allowanceV2: + '115792089237316195423570985008687907853269984665640564039457.584007913129639935', + allowanceV3: + '115792089237316195423570985008687907853269984665640564039457.584007913129639935', + balanceOf: '0', + }, }); - - it('should return correct info - unformatted', async () => { - const result = await tokensFactory.getAllowanceAndBalanceOfForContracts( - UniswapVersion.v2, - MockEthereumAddress(), - [ - ETH.MAINNET().contractAddress, - MOCKFUN().contractAddress, - MOCKREP().contractAddress, - ], - false - ); - expect(result[0]).toEqual({ - token: ETH.MAINNET(), - allowanceAndBalanceOf: { - allowance: - '0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff', - balanceOf: '0x03034545d3b362a1', - }, - }); - expect(result[1]).toEqual({ - token: MOCKFUN(), - allowanceAndBalanceOf: { - allowance: '0x2386c18764e720', - balanceOf: '0x1e72af98f7', - }, - }); - expect(result[2]).toEqual({ - token: MOCKREP(), - allowanceAndBalanceOf: { - allowance: '0x00', - balanceOf: '0x00', - }, - }); + expect(result[1]).toEqual({ + token: MOCKFUN(), + allowanceAndBalanceOf: { + allowanceV2: '99997899.4322', + allowanceV3: '0', + balanceOf: '0', + }, + }); + expect(result[2]).toEqual({ + token: MOCKREP(), + allowanceAndBalanceOf: { + allowanceV2: '0', + allowanceV3: '0', + balanceOf: '0', + }, }); }); - describe('v3', () => { - it('should return correct info - formatted', async () => { - const result = await tokensFactory.getAllowanceAndBalanceOfForContracts( - UniswapVersion.v3, - MockEthereumAddress(), - [ - ETH.MAINNET().contractAddress, - MOCKFUN().contractAddress, - MOCKREP().contractAddress, - ], - true - ); - expect(result[0]).toEqual({ - token: ETH.MAINNET(), - allowanceAndBalanceOf: { - allowance: - '115792089237316195423570985008687907853269984665640564039457.584007913129639935', - balanceOf: '0.217093373250724513', - }, - }); - expect(result[1]).toEqual({ - token: MOCKFUN(), - allowanceAndBalanceOf: { - allowance: '0', - balanceOf: '1307.73129463', - }, - }); - expect(result[2]).toEqual({ - token: MOCKREP(), - allowanceAndBalanceOf: { - allowance: '0', - balanceOf: '0', - }, - }); + it('should return correct info - unformatted', async () => { + const result = await tokensFactory.getAllowanceAndBalanceOfForContracts( + MockEthereumAddress(), + [ + ETH.MAINNET().contractAddress, + MOCKFUN().contractAddress, + MOCKREP().contractAddress, + ], + false + ); + expect(result[0]).toEqual({ + token: ETH.MAINNET(), + allowanceAndBalanceOf: { + allowanceV2: + '0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff', + allowanceV3: + '0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff', + balanceOf: '0x00', + }, }); - - it('should return correct info - unformatted', async () => { - const result = await tokensFactory.getAllowanceAndBalanceOfForContracts( - UniswapVersion.v3, - MockEthereumAddress(), - [ - ETH.MAINNET().contractAddress, - MOCKFUN().contractAddress, - MOCKREP().contractAddress, - ], - false - ); - expect(result[0]).toEqual({ - token: ETH.MAINNET(), - allowanceAndBalanceOf: { - allowance: - '0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff', - balanceOf: '0x03034545d3b362a1', - }, - }); - expect(result[1]).toEqual({ - token: MOCKFUN(), - allowanceAndBalanceOf: { - allowance: '0x00', - balanceOf: '0x1e72af98f7', - }, - }); - expect(result[2]).toEqual({ - token: MOCKREP(), - allowanceAndBalanceOf: { - allowance: '0x00', - balanceOf: '0x00', - }, - }); + expect(result[1]).toEqual({ + token: MOCKFUN(), + allowanceAndBalanceOf: { + allowanceV2: '0x2386c18764e720', + allowanceV3: '0x00', + balanceOf: '0x00', + }, + }); + expect(result[2]).toEqual({ + token: MOCKREP(), + allowanceAndBalanceOf: { + allowanceV2: '0x00', + allowanceV3: '0x00', + balanceOf: '0x00', + }, }); }); }); From 4e7c55bdad1a2c4024506d52dea6fa343b30f8e7 Mon Sep 17 00:00:00 2001 From: Josh Stevens Date: Tue, 3 Aug 2021 18:29:33 +0100 Subject: [PATCH 6/8] expose the gas settings interface --- src/index.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/index.ts b/src/index.ts index 8d833dcd..5d50d396 100644 --- a/src/index.ts +++ b/src/index.ts @@ -14,6 +14,7 @@ export { EthereumProvider, EthersProvider, } from './ethers-provider'; +export { GasSettings } from './factories/pair/models/gas-settings'; export { TradeContext } from './factories/pair/models/trade-context'; export { TradeDirection } from './factories/pair/models/trade-direction'; export { Transaction } from './factories/pair/models/transaction'; From edce2e7f6e556672229b7d452e66599d805cbfcf Mon Sep 17 00:00:00 2001 From: Josh Stevens Date: Wed, 4 Aug 2021 17:03:59 +0100 Subject: [PATCH 7/8] gas price last fixes + readme --- README.md | 60 +++++++++++++++++++ src/factories/pair/models/gas-settings.ts | 5 +- .../router/uniswap-router.factory.ts | 4 +- 3 files changed, 67 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index f613bf0e..3369d6a8 100644 --- a/README.md +++ b/README.md @@ -106,21 +106,28 @@ export interface UniswapPairContextForProviderUrl ``` ```ts +export interface GasSettings { + getGasPrice: () => Promise; +} + export class UniswapPairSettings { slippage: number; deadlineMinutes: number; disableMultihops: boolean; uniswapVersions: UniswapVersion[] = [UniswapVersion.v2, UniswapVersion.v3]; + gasSettings?: GasSettings = undefined; constructor(settings?: { slippage?: number | undefined; deadlineMinutes?: number | undefined; disableMultihops?: boolean | undefined; uniswapVersions?: UniswapVersion[] | undefined; + gasSettings?: GasSettings | undefined; }) { this.slippage = settings?.slippage || 0.005; this.deadlineMinutes = settings?.deadlineMinutes || 20; this.disableMultihops = settings?.disableMultihops || false; + this.gasSettings = settings?.gasSettings; if ( Array.isArray(settings?.uniswapVersions) && @@ -390,11 +397,15 @@ export interface TradeContext { // this will be ordered from the best expected convert quote to worse [0] = best allTriedRoutesQuotes: { expectedConvertQuote: string; + expectedConvertQuoteOrTokenAmountInMaxWithSlippage: string; + transaction: Transaction; + tradeExpires: number; routePathArrayTokenMap: Token[]; routeText: string; routePathArray: string[]; uniswapVersion: UniswapVersion; liquidityProviderFee: number; + quoteDirection: TradeDirection; }[]; // if the allowance approved for moving tokens is below the amount sending to the // uniswap router this will be false if not true @@ -724,6 +735,55 @@ const executeTrade = async (web3: Web3, trade: TradeContext) => { web3TradeExample(); ``` +#### Including gas fees in the trade response + +The library has the ability to work out the best trade including gas fees. As expected this does add around about 700MS onto the response time due to the need to have to query `eth_estimateGas` an the top 3 quotes to work out the best result. How it works is: + +- It gets the best expected trade quotes as it normally does +- IF you do not have enough balance or enough allowance it will not estimate gas because it be a always failing transaction and the node will throw an error. +- ALSO IF the token your swapping does not have a fiat price in coin gecko then again it ignores the below as it can not do the math without a base currency. +- IF you have enough balance and allowance then finds the best 3 of the different hop options: + - best direct trade aka `ETH/TOKEN > TOKEN_YOU_WANT` + - best trade which jumps 2 hops aka `ETH/TOKEN > TOKEN > TOKEN_YOU_WANT` + - beat trade which jumps 3 hops aka `ETH/TOKEN > TOKEN > OTHER_TOKEN > TOKEN_YOU_WANT` +- It then `eth_estimateGas` those 3 transactions and takes off the tx fee from the expected quote +- It then returns the trade which is the highest left amount, meaning it has taken into consideration gas within the quote + +Do not worry if you want to use this feature but worried that first time customers before they approve ability for uniswap to move the tokens will not be able to benefit from this, as soon as you have approved uniswap to be able to move tokens on their behalf a new trade will be emitted within the `quoteChanged$` stream so you can still get all the benefit on first time swaps. + +The beauty of this is its very easy to setup just pass in a `gasSettings` object including a `getGasPrice` async function (IT MUST BE A PROMISE) which returns the gas price in `Gwei` which you want to use for the working out. This must be a string number aka `30` = `30 Gwei` it does not handle passing in hex strings. This can be dynamic aka we call this everytime we go and work out the trades, so if you want this to hit an API or etherscan or return a fixed gas price, its completely up to you. + +```ts +import { + ChainId, + TradeContext, + UniswapPair, + UniswapPairSettings, +} from 'simple-uniswap-sdk'; +const uniswapPair = new UniswapPair({ + // the contract address of the token you want to convert FROM + fromTokenContractAddress: '0x419D0d8BdD9aF5e606Ae2232ed285Aff190E711b', + // the contract address of the token you want to convert TO + toTokenContractAddress: '0x1985365e9f78359a9B6AD760e32412f4a445E862', + // the ethereum address of the user using this part of the dApp + ethereumAddress: '0xB1E6079212888f0bE0cf55874B2EB9d7a5e02cD9', + // you can pass in the provider url as well if you want + // providerUrl: YOUR_PROVIDER_URL, + // OR if you want to inject your own ethereum provider (no need for chainId if so) + // ethereumProvider: YOUR_WEB3_ETHERS_OR_CUSTOM_ETHEREUM_PROVIDER, + chainId: ChainId.RINKEBY, + settings: new UniswapPairSettings({ + gasSettings: { + getGasPrice: async () => { + return 'GWEI_GAS_PRICE'; + }, + }, + }), +}); +``` + +That's it now you get trades which bring you back the best trades minus the tx cost. + #### ERC20 > ERC20 Output example ```ts diff --git a/src/factories/pair/models/gas-settings.ts b/src/factories/pair/models/gas-settings.ts index 429913cd..5a53fe6e 100644 --- a/src/factories/pair/models/gas-settings.ts +++ b/src/factories/pair/models/gas-settings.ts @@ -1,3 +1,6 @@ export interface GasSettings { - getGasPrice: () => Promise; + /** + * Must return GWEI! + */ + getGasPrice: () => Promise; } diff --git a/src/factories/router/uniswap-router.factory.ts b/src/factories/router/uniswap-router.factory.ts index 9495877b..4c7f43e5 100644 --- a/src/factories/router/uniswap-router.factory.ts +++ b/src/factories/router/uniswap-router.factory.ts @@ -1018,7 +1018,9 @@ export class UniswapRouterFactory { enoughAllowanceV3 ); - const gasPrice = await this._settings.gasSettings.getGasPrice(); + const gasPrice = new BigNumber( + await this._settings.gasSettings.getGasPrice() + ).times(1e9); let bestRoute: | { From 853c304f59d3dbe31ee7188df0a5fe4d86c1a11f Mon Sep 17 00:00:00 2001 From: Josh Stevens Date: Wed, 4 Aug 2021 17:26:37 +0100 Subject: [PATCH 8/8] expose the gas details in the trade context and the routes --- README.md | 9 +++++++++ src/__TEST-SCRIPT__/playground.ts | 9 +++++---- src/factories/pair/models/trade-context.ts | 1 + src/factories/pair/uniswap-pair.factory.ts | 3 +++ src/factories/router/models/route-quote.ts | 1 + src/factories/router/uniswap-router.factory.ts | 8 +++++--- 6 files changed, 24 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 3369d6a8..0e0637c6 100644 --- a/README.md +++ b/README.md @@ -15,6 +15,8 @@ Please note this is not owned or maintained by uniswap and is a open source pack
🚀 Exposes all the route paths it tried so you can see every detail in how it worked out the best price
+🚀 Factor in the cost of the transaction into the quotes with 1 config change +
🚀 Easy subscriptions to get alerted when the price moves or the trade expires
🚀 The transaction is generated for you, just fill it with the gas details and send it on its way @@ -399,6 +401,10 @@ export interface TradeContext { expectedConvertQuote: string; expectedConvertQuoteOrTokenAmountInMaxWithSlippage: string; transaction: Transaction; + // if you have enabled to factor in the transaction cost in the quotes + // then this is the gas price in gwei we used to estimate the transactions + // it only be defined by the ones it decided to pick depending on the hops abouts + gasPriceEstimatedBy: string | undefined; tradeExpires: number; routePathArrayTokenMap: Token[]; routeText: string; @@ -449,6 +455,9 @@ export interface TradeContext { data: string; value: string; }; + // if you have enabled to factor in the transaction cost in the quotes + // then this is the gas price in gwei we used to estimate the transactions + gasPriceEstimatedBy: string | undefined; // this is a stream which emits if the quote has changed, this will emit // not matter what you should listen to this for the source of truth // for a reactive dApp. If you dont listen to this the user could end up diff --git a/src/__TEST-SCRIPT__/playground.ts b/src/__TEST-SCRIPT__/playground.ts index 1969a6e4..3b28cddc 100644 --- a/src/__TEST-SCRIPT__/playground.ts +++ b/src/__TEST-SCRIPT__/playground.ts @@ -30,9 +30,9 @@ const routeTest = async () => { deadlineMinutes: 20, disableMultihops: false, uniswapVersions: [UniswapVersion.v2, UniswapVersion.v3], - // gasSettings: { - // getGasPrice: async () => 100000000000, - // }, + gasSettings: { + getGasPrice: async () => '90', + }, }), }); @@ -43,6 +43,7 @@ const routeTest = async () => { const trade = await uniswapPairFactory.trade('0.0001', TradeDirection.input); console.log(new Date().getTime() - startTime); + console.log(trade); // console.log(JSON.stringify(trade, null, 4)); // console.log(trade); @@ -59,7 +60,7 @@ const routeTest = async () => { // (await ethers.provider.estimateGas(trade.transaction)).toHexString() // ); - process.stdin.resume(); + // process.stdin.resume(); // console.log(JSON.stringify(trade)); diff --git a/src/factories/pair/models/trade-context.ts b/src/factories/pair/models/trade-context.ts index 8983994c..cb48b728 100644 --- a/src/factories/pair/models/trade-context.ts +++ b/src/factories/pair/models/trade-context.ts @@ -29,6 +29,7 @@ export interface TradeContext { toToken: Token; toBalance: string; transaction: Transaction; + gasPriceEstimatedBy: string | undefined; quoteChanged$: UniswapStream; destroy: () => void; } diff --git a/src/factories/pair/uniswap-pair.factory.ts b/src/factories/pair/uniswap-pair.factory.ts index 5c32a3d7..76fe565d 100644 --- a/src/factories/pair/uniswap-pair.factory.ts +++ b/src/factories/pair/uniswap-pair.factory.ts @@ -358,6 +358,7 @@ export class UniswapPairFactory { balance: bestRouteQuotes.fromBalance, }, transaction: bestRouteQuote.transaction, + gasPriceEstimatedBy: bestRouteQuote.gasPriceEstimatedBy, allTriedRoutesQuotes: bestRouteQuotes.triedRoutesQuote, quoteChanged$: this._quoteChanged$, destroy: () => this.destroy(), @@ -423,6 +424,7 @@ export class UniswapPairFactory { balance: bestRouteQuotes.fromBalance, }, transaction: bestRouteQuote.transaction, + gasPriceEstimatedBy: bestRouteQuote.gasPriceEstimatedBy, allTriedRoutesQuotes: bestRouteQuotes.triedRoutesQuote, quoteChanged$: this._quoteChanged$, destroy: () => this.destroy(), @@ -485,6 +487,7 @@ export class UniswapPairFactory { balance: bestRouteQuotes.fromBalance, }, transaction: bestRouteQuote.transaction, + gasPriceEstimatedBy: bestRouteQuote.gasPriceEstimatedBy, allTriedRoutesQuotes: bestRouteQuotes.triedRoutesQuote, quoteChanged$: this._quoteChanged$, destroy: () => this.destroy(), diff --git a/src/factories/router/models/route-quote.ts b/src/factories/router/models/route-quote.ts index 081a1263..a0ca1403 100644 --- a/src/factories/router/models/route-quote.ts +++ b/src/factories/router/models/route-quote.ts @@ -14,4 +14,5 @@ export interface RouteQuote { uniswapVersion: UniswapVersion; liquidityProviderFee: number; quoteDirection: TradeDirection; + gasPriceEstimatedBy?: string | undefined; } diff --git a/src/factories/router/uniswap-router.factory.ts b/src/factories/router/uniswap-router.factory.ts index 4c7f43e5..809b8f72 100644 --- a/src/factories/router/uniswap-router.factory.ts +++ b/src/factories/router/uniswap-router.factory.ts @@ -421,6 +421,7 @@ export class UniswapRouterFactory { uniswapVersion: route.uniswapVersion, liquidityProviderFee: route.liquidityProviderFee, quoteDirection: route.quoteDirection, + gasPriceEstimatedBy: route.gasPriceEstimatedBy, }; }), hasEnoughBalance: allowanceAndBalances.enoughBalance, @@ -1018,9 +1019,8 @@ export class UniswapRouterFactory { enoughAllowanceV3 ); - const gasPrice = new BigNumber( - await this._settings.gasSettings.getGasPrice() - ).times(1e9); + const gasPriceGwei = await this._settings.gasSettings.getGasPrice(); + const gasPrice = new BigNumber(gasPriceGwei).times(1e9); let bestRoute: | { @@ -1044,6 +1044,8 @@ export class UniswapRouterFactory { ).times(gasPrice) ).times(ethUsdValue); + route.gasPriceEstimatedBy = gasPriceGwei; + const expectedConvertQuoteMinusTxFees = expectedConvertQuoteFiatPrice.minus(txFee);