diff --git a/src/.env.example b/.env.example similarity index 100% rename from src/.env.example rename to .env.example diff --git a/1inch.png b/1inch.png deleted file mode 100644 index 46e8e84..0000000 Binary files a/1inch.png and /dev/null differ diff --git a/LICENSE b/LICENSE deleted file mode 100644 index dd2f303..0000000 --- a/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2020 Extropy.io - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/README.md b/README.md index 9b8ebb5..4a239a1 100644 --- a/README.md +++ b/README.md @@ -1,157 +1,148 @@ -Coding a DeFi Arbitrage Bot -=== - -In our [previous article](https://blog.extropy.io/posts/defi1.html), we introduced Decentralized Finance as a whole, casting light upon the basic building blocks of this new ecosystem and the main strategies used to earn passive income, mainly "yield farming" and "arbitrage". - -In this article we are going to guide the readers through a step by step tutorial for building an arbitrage trading bot that works with decentralized exchanges, the bot will also get flashloans in order to borrow funds to use for arbitrage. The full working code can be found on Extropy.io's [gihub repository](https://github.com/ExtropyIO/defi-bot), Extropy will publish a follow-up article that will examine the code in depth. - -## What is Arbitrage - -> Arbitrage is the purchase and sale of an asset in order to profit from a difference in the asset's price between marketplaces. - -An example arbitrage strategy in DeFI would be to buy ETH in exchange for USDT on a decentralized such as [Kyber](https://kyber.network/) and sell it immediately afterwards on another decentralized exchange such as [Uniswap](https://uniswap.org/) at a higher price, thus making a profit in USDT, i.e. you end up with more USDT in your wallet than you had before the arbitrage. The difficulty in arbitrage lies in finding a price discrepancy (spread) for the same trading pair across two different exchanges. For an overview of the main arbitrage strategies please refer to our [previous article](https://blog.extropy.io/posts/defi1.html#h0.2_loans). - -## Why Arbitrage on DeFi - -Before we begin, let's explore in more detail the idea of arbitrage and why this already established practice in the centralized finance world has reached a whole new level in DeFi mainly thanks to decentralized exchanges and flashloans. - -When people hear of the term arbitrage they usually associate it with trading, or rather the practice of trying to predict the markets in order to make a profit. The best thing about arbitrage as a passive income strategy, is that it **does not require any kind of prediction algorithm** or stop-loss strategy, but rather it deals with finding profitable opportunities in the present moment before they disappear or before others do. - -Another advantage of doing arbitrage on DEXes (i.e. smart contracts) rather than on centralized exchanges, is that **there is no risk of losing money** should a sequence of trades not execute as expected; the transactions will be reverted due to lack of funds, because the smart contract isn't able to repay a flashloan. -On the other hand, arbitrage traders on centralized exchanges could lose money during an arbitrage due to the fact that they cannot execute multiple trades in a single transaction, i.e. unlike smart contracts, centralized exchanges' won't revert trades, thus traders will either have to sell at a loss or keep the unsold token. For example, by the time a trader receives some ETH in exchange for DAI on a centralized exchange, the USD (DAI) value of ETH on the second exchange may have dropped below the expected sell price because someone else filled the order from the orderbook first, leaving the trader with a bag of unsold ETH; this is called [price slippage](https://www.investopedia.com/terms/s/slippage.asp). - -Furthermore, developers can take advantage of flashloans in order to do arbitrage trading **using only borrowed funds**, i.e. you don't even have to own a cryptocurrency pair in order to arbitrage it. -Flasholoans are similar to the concept of leveraged trading, except they are better, because not only you are not at risk of losing all of your capital if prices are very volatile, but also because as mentioned before, the money isn't even yours; why put your capital at risk if you could **borrow millions of dollars worth of crypto with zero money down**? - -To sum up, DeFi arbitrage is a huge opportunity to earn passive income at virtually no risk. Developers have unrestricted access to dozens of exchanges and hundreds of cryptocurrency pairs, with new ones popping up every day; this is an unprecedented opportunity for them given that arbitrage used to be a game that only accredited investors could play, and even then, the number of arbitrage opportunities would be close to zero because the market is already saturated and also because FIAT currency prices are much more stable than cryptocurrencies which limits currency price spreads across markets. - -## Decentralized Exchanges - -The main difficulty when coming up with an arbitrage strategy lies in choosing which decentralized exchanges to use and which token pairs to trade. - -Before chosing which exchanges to include in the code for an arbitrage bot, readers must understand how decentralized exchanges work under the hood, as this changes the strategy radically. The most popular DEX architechtures use the concept of liquidity pools rather than orderbooks and are called Automated Market Makers. Other DEXes, in particular those using the 0x protocol use a classic orderbook and rely on makers and takers for determining an asset's price; it is easier for orderbook exchanges to fall out of sync with each other and create arbitrage opportunities. - -### Automated Market Makers - -Centralized exchange traders will already be familiar with using an orderbook; in conventional exchanges this is the closest thing to the concept of liquidity pools, a.k.a. [automated market makers](https://medium.com/multi-io/automated-market-makers-amm-breakdown-d3338f027230) (AMMs). AMMs only exist in decentralized finance and are a very new financial instrument. - -> Automated market makers have become all the buzz, largely for replacing the traditional exchange-listing process and limit-order books with a permissionless liquidity pool run by algorithms. - -Currently, the most popular AMMs are: -* Kyber Network -* Uniswap -* Balancer -* Curve Finance. - -Doing an arbitrage between some of the big cryptocurrency exchanges, such as Uniswap and Kyber Network, means trying to bring prices into efficiency between two liquidity pools, i.e. behind the sceenes Kyber and Uniswap both have 'pools' where people put their funds in order to gain passive income in the form of tokens or interests. A pool is usually a smart contract that is used only for a particular cryptocurrency pair, e.g. ETH/DAI. If these two pools fell out of sync, arbitraging the price spread would mean trying to bring them in sync by buying on one and selling on the other. - -> Automated market makers are smart contracts that create a liquidity pool of ERC20 tokens, which are automatically traded by an algorithm rather than an order book. This effectively replaces a traditional limit order-book with a system where assets can be automatically swapped against the pool’s latest price. - - -### Order Book DEXes - -Some decentralized cryptocurrency exchanges have order books with limit orders, where maker and takers determine the price rather than AMMs. -It is possible to fill the individual orders inside of an orderbook by using flashloans, if the arbitrage is profitable the flashloan will be paid back and the remainder will be profit. - -[Radar Relay](http://classic.relay.radar.tech/) is one example of DEX that uses the classic order book that one would see on a centralized exchange like Coinbase. - -![USDC/WETH pair onRadar Relay's orderbook](radar-relay.png) - -The example above shows multiple WETH maker orders that could be filled for only a few thousand dollars worth of USDC, using a flashloan. - -## Bot Strategy Overview - -Before getting into the code, we will explain and test the strategy chosen for the bot using just the 0x API and the 1inch dex aggregator contract, the only tool needed for now is a browser with the Metamask extension installed. - -Using orderbooks, developers can fill limit orders from a decentralized exchange and then see if the tokens aquired in the first step could be sold for more to ANY other liquidity pool. This is the strategy that will be used for the arbitrage bot in this article. In particular, the bot will query the 0x API looking for WETH/DAI pair limit orders, the bot will then query the [1inch exchange](https://1inch.exchange/#/) DEX aggregator in order to determine whether one or more of the open orders from 0x, i.e. ETH, could be sold for a higher price on any other liquidity pool. - -The table below shows the currency pairs that the trading bot will look for and the exchanges that it's going to use. - -| | 0x | 1inch | -|-------------|------|-------| -| Maker Asset | WETH | DAI | -| Taker Asset | DAI | WETH | - -The bot will use the 'taker' assets, i.e. it will sell DAI on 0x and sell WETH on 1inch. -Let's break down the steps from the above table: -1. Get a flashloan in DAI from DyDx exchange -2. Buy WETH from 0x usning DAI borrowed with the flashloan -3. Use 1inch to find the best exchange to sell the WETH aquired in step 2 -4. Pay back the flashloan in DAI and keep the difference if any (profit) - -In case the readers aren't familiar, WETH is the ERC20 tradable version of ETH. -WETH makes it easier to trade ETH from the point of view of smart contracts, it also means that users can revoke access to their WETH after sending it to an exchange, while that's not the case for ETH. - - -### 0x Protocol - -0x is a protocol for doing wallet to wallet trading by using an off-chain API that stores the orders for the orderbook and a trading protocol enforced by smart contracts. - -Radar Relay is just an example of a DEX that is powered by the 0x protocol, i.e. it is possible to use the [0x API](https://0x.org/docs/api) in order to get limit orders for a currency pair from every exchange that uses the 0x protocol. - -#### Fetching Orderbook Data - -[This url](https://api.0x.org/sra/v3/orders?page=1&perPage=1000&makerAssetProxyId=0xf47261b0&takerAssetProxyId=0xf47261b0&makerAssetAddress=0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2&takerAssetAddress=0x6b175474e89094c44da98b954eedeac495271d0f) will effectively perform a get request to the 0x API in order to retrieve all limit orders for the WETH/DAI pair. The url takes two main parameters: the maker token's contract address (WETH) and the taker token's contract address (DAI), i.e. this is the API call to use if we wanted to buy WETH using DAI. See the [0x docs](https://0x.org/docs/api#request-9) regarding orderbook requests for more information. - -Let's analyze the response from the 0x API, the following object is just one order out of the many returned in the previous call. - -``` -"order": { - "signature": "0x1b5ee7412dcf084cc19131e7d78ebfafa2cae8fc4b1e1f2b49dde218334bab51f06b4c1f91da6e3f3277445c5235628886bff304951fac0c9f99f613f9a42bded302", - "senderAddress": "0x0000000000000000000000000000000000000000", - "makerAddress": "0x0ee1f33a2eb0da738fdf035c48d62d75e996a3bd", - "takerAddress": "0x0000000000000000000000000000000000000000", - "makerFee": "0", - "takerFee": "0", - "makerAssetAmount": "20000000000000000", - "takerAssetAmount": "7808584000000000000", - "makerAssetData": "0xf47261b0000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", - "takerAssetData": "0xf47261b00000000000000000000000006b175474e89094c44da98b954eedeac495271d0f", - "salt": "95222041148024943540833237129371349480418435215051967676171665233326083547721", - "exchangeAddress": "0x61935cbdd02287b511119ddb11aeb42f1593b7ef", - "feeRecipientAddress": "0x86003b044f70dac0abc80ac8957305b6370893ed", - "expirationTimeSeconds": "1604395325", - "makerFeeAssetData": "0x", - "chainId": 1, - "takerFeeAssetData": "0x" - }, -``` - -The important bits from the order above are maker and taker amounts: -``` -"makerAssetAmount": "20000000000000000", -"takerAssetAmount": "7808584000000000000", -``` - -The maker amount is the amount of WETH for sale on that particular order followed by 18 decimal places, while the taker amount is in DAI, also followed by 18 decimal places. - -In order to find out the DAI price of 1 ETH, we divide taker amout by maker amount, using the command line to avoid counting zeroes: - -``` -echo 7808584000000000000/20000000000000000 | bc -``` - -The result is 390 (DAI for 1 WETH). Next, we check whether we could sell the WETH on a liquidity pool for more than 390 USD. - -### 1inch DEX Aggregator - -> 1inch offers the best rates by discovering the most efficient swapping routes across all leading DEXes. - -![shows DAI amount returned for 0.02 WETH and the list of supported DEXes](1inch.png) - -It may be useful for the reader to know that another popular DEX aggregator exists called [DEX.AG](https://dex.ag/). 1inch is essentially a smart contract that can be queried in various ways, the easiest way to do so is via the UI as shown in the picture above, however the same exact thing can be achieved by queryng the smart contract directly [on Etherscan](https://etherscan.io/address/0xc586bef4a0992c495cf22e1aeee4e446cecdee0e#readContract). - -![Shows the best deal available expressed in taker asset quantity in return for the specified maker asset amount](etherscan.png) - -The above function was filled in with the WETH and DAI contract addresses and the taker asset amount, i.e. 0.2 WETH as shown in the example order from 0x. The idea is that if this amount is greater than the one returned by 0x, then there is a profitable arbitrage opportunity. We can use the command line again to determine profitability. - -``` -echo 8000442481503500755/20000000000000000 | bc -``` -which results in 400. Therefore, we could buy 1 WETH for 390 DAI on 0x and sell 1 WETH for 400 on 1inch, making a 10 DAI profit (minus fees). However, a few minutes have gone by while writing this article between the 0x API query and the 1inch contract query which means that this opportunity may not actually exist in practice, because we would need both limit orders to exist at the same time for the arbitrage to work. - - -## Coding the bot - -In the follow-up article, we will go through the code step by step, which is essentially a node.js bot with all the arbitrage logic in it and a smart contract that will get called by the bot in order to request flashloans from DyDx. -The code is already available on Extropy's [gihub repository]() for thoose readers who want to get a first hand look, before the follow-up article gets published next week. +> ### TLDR: +> * The author provides a detailed guide to coding a DeFi arbitrage bot. The bot uses flash loans to borrow assets from dYdX and sells them on 1inch exchange when profitable. + +### Citation + +* Extropy.IO. Coding a DeFi Arbitrage Bot, Medium. Oct, 29, 2020. Accessed on: Mar, 16, 2021. [Online] Available: Part 1: https://extropy-io.medium.com/coding-a-defi-arbitrage-bot-45e550d85089, Part 2: https://extropy-io.medium.com/arbitrage-bot-part-2-97e7b710dcf +* Credits for this README go to Ting Ting Lee who wrote [Coding a DeFi arbitrage bot](https://www.smartcontractresearch.org/t/research-summary-coding-a-defi-arbitrage-bot/282) for the Smart Contracts Research Forum. + +### Link + +* Part 1: https://extropy-io.medium.com/coding-a-defi-arbitrage-bot-45e550d85089 +* Part 2: https://extropy-io.medium.com/arbitrage-bot-part-2-97e7b710dcf + +### Core Research Question + +* How can an arbitrage between DEXes be automatically performed using flash loans? + +### Background + +* **Arbitrage** is the purchase and sale of an asset in order to profit from a difference in the asset’s price between marketplaces. +* **Price slippage** refers to the difference between the expected price of a trade and the price at which the trade is executed. It usually happens when the market is highly volatile within a short period of time or when the current trade volume exceeds the existing bid/ask spread. +* **Flash Loan** is a type of uncollateralized loan that is only valid within a single transaction. It can be implemented through a smart contract. The transaction will be reverted if the execution result is not as expected. + * For more details on flash loans, please refer to the research summary “[Attacking the DeFi Ecosystem with Flash Loans for Fun and Profit](https://www.smartcontractresearch.org/t/research-summary-attacking-the-defi-ecosystem-with-flash-loans-for-fun-and-profit/260)”. +* **Decentralized exchanges (DEX)** are a type of cryptocurrency exchange which allow peer-to-peer cryptocurrency exchanges to take place securely online, without needing an intermediary. +* **DEX aggregators** source liquidity from different DEXs and thus offer users better token swap rates than any single DEX. +* **Liquidity pool** is a collection of funds locked in a smart contract to provide liquidity for DEXes. The advantage of a liquidity pool is that it doesn’t require matching orders between buyers and sellers, and instead leverages a pre-funded liquidity pool with low slippage. +* An **Orderbook** consists of a collection of bid-and-ask orders. Orders are matched and executed only when a bid and ask price are the same. +* An **Automated Market Maker (AMM)** uses a liquidity pool instead of an orderbook and relies on mathematical formulas to price assets. The assets can be automatically swapped against the pool’s latest price, making it more efficient than traditional orderbooks. +* **Wrapped ETH (WETH)** is the ERC20 tradable version of Ethereum. WETH is easier to trade within smart contracts than ETH is. Users can also revoke access to their WETH after sending it to an exchange, which is not possible with ETH. + +### Summary + +* The author describes the advantages of DeFi arbitrage over centralized exchanges: + * DeFi + * Insolvency risks are minimized as smart contracts execute automatically following predetermined parameters. A trade will be reverted if it cannot be executed as expected. + * Traders can perform arbitrage using borrowed funds with flash loans. + * Centralized exchanges + * Since a trader cannot execute trades simultaneously, they may suffer from price slippage if a trade is delayed. + * Traders need to own funds or borrow them from a bank. +* Arbitrage between DEXes that use AMM + * Popular platforms + * [Kyber Network](https://kyber.network/), [Uniswap](https://uniswap.org/), [Balancer](https://balancer.finance/), and [Curve Finance](https://curve.fi/). + * Result + * Bring prices into efficiency between two liquidity pools + * Scenario + * When the pools on different DEXes offer different prices to exchange assets. + * Execution + * Exchange from asset A to asset B on one pool and exchange it back on another pool to benefit from the price spread between two pools. +* Arbitrage between DEXes that use classic orderbook + * Popular platforms + * [Radar Relay](https://relay.radar.tech/), powered by the [0x protocol](https://0x.org). + * Scenario + * Traders can fill limit orders from a DEX and then see if the tokens acquired could be sold to any other liquidity pools for better returns. +* The author describes the basic operation of an arbitrage bot. + * For example, to arbitrage the pair WETH/DAI: + * The bot will query the [0x API](https://0x.org/api) looking for WETH/DAI pair limit orders + * The 0x API can get the limit orders for a currency pair from every exchange that uses the 0x protocol. + * Example API url for orders buying WETH with DAI: https://api.0x.org/sra/v3/orders?page=1&perPage=1000&makerAssetProxyId=0xf47261b0&takerAssetProxyId=0xf47261b0&makerAssetAddress=0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2&takerAssetAddress=0x6b175474e89094c44da98b954eedeac495271d0f + * Parameters + * the maker token’s contract address (WETH) + * the taker token’s contract address (DAI) + * Sample response from the above request + * ![|512x215](upload://4yiFnlZcJCpmtPuCTK22fGOjV45.jpeg) + * However, the above opportunity may not actually exist in practice, because we would need both limit orders to exist at the same time for the arbitrage to work. +* The author thus proposes to perform the arbitrage using a flash loan. + * Steps + * Get a flash loan DAI from [DyDx exchange](https://dydx.exchange/) + * Buy WETH from 0x using the DAI borrowed with the flash loan + * Use 1inch to find the best exchange to sell the WETH acquired in step 2 + * Pay back the flash loan DAI and keep the remainder as profit +* The author provides the code and explains how the arbitrage smart contract works + * The contract inherits the DyDxFloashLoan smart contract. + * Functions + * Swap + * Perform the trade + * getExpectedReturn + * Get the current asset price + * getWeth + * Turn any ETH sent into WETH + * approveWeth + * Approve the 0x proxy to collect WETH as the fee for using 0x. + * getFlashloan + * Called whenever a profitable arbitrage is found by the client. + * All of the parameters for this function will be constructed and passed in from the client script. + * callFunction + * Has to be deployed in our smart contract to receive a flash loan from dYdX. + * _arb + * Arbitrage function that is called when the loan is successful. + * Tracks the balance of our smart contract before and after the trade. + * If the end balance is not greater than the start balance, the operation will revert. + +### Method + +* The prerequisite for the arbitrage bot is to have a browser with the Metamask extension installed. +* The programming language used is NodeJS. +* Project structure: only two files + * index.js + * a node.js server that continuously fetches crypto prices on exchanges looking for arbitrage opportunities, trying to guarantee that the trade is possible before even attempting to execute it. + * TradingBot.sol + * a smart contract that gets called by the node app only when a profitable arbitrage is found, it will borrow funds with a flash loan and execute trades on DEXes. +* Detailed setup + * Install Metamask browser extension + * Create an Ethereum Mainnet account with some ETH for paying gas fees. + * Don’t use your personal account for this, create a new account for the bot in order to limit accidental losses. + * Go to [Remix online IDE](https://remix.ethereum.org/) and paste the smart contract solidity code + * Compile the code using compiler version 0.5.17. + * Deploy with an initial 100 wei, which is enough for 100 flash loans on dYdX. +* Environment setup + * By cloning the project’s code repository, users will find a file called .env.example inside the /src folder + * Fill in the fields: + * RPC_URL : the public address of an Ethereum node, the easiest one to set up is the Infura RPC provider, register an account in order to get an API key. + * ADDRESS and PRIVATE_KEY: fill in the public Ethereum address of the bot account, and its corresponding private key. + * CONTRACT_ADDRESS : paste in the smart contract’s address that was returned from Remix after the deployment step. + * GAS_LIMIT : how much gas the contract is allowed to use, leave as 3000000 or decrease to 2000000 which should be fine + * GAS_PRICE : change this depending on how fast you want the transaction to be mined, see https://ethgasstation.info/ for info. + * ESTIMATED_GAS : leave as is +* Running the bot + * Execute the command from the project’s root directory. + * node src/index.js + +### Results + +* The full working code can be found at https://github.com/ExtropyIO/defi-bot. + +### Discussion & Key Takeaways + +* The author created an open-source DeFi arbitrage bot that uses flash loans to borrow assets from dYdX and sells them on 1inch exchange when profitable. +* The author explains the main components of the arbitrage bot and the underlying logic of how arbitrage works. +* After following this tutorial, users can create a working example of a customizable flash loan arbitrage bot. +* The most efficient way to perform a flash loan arbitrage is to continuously fetch the real time prices using NodeJS client and execute the contract with profitable parameters when an opportunity is found. + +### Implications & Follow-ups + +* Arbitrage is a zero-sum game. There are a finite number of arbitrage opportunities for a large group of people competing to find and execute them. +* To make the bot more efficient, the author suggests the following improvements: + * Consider taker fees when calculating profits + * Use Partial fills + * Check orders again + * Handle failures to continue execution + * Execute multiple orders simultaneously + * Dynamically calculate gas fees +* If such arbitrage bot becomes prevalent, the price differences between different DEXes will be minimized. DEX aggregators such as 1inch may no longer be needed as the price differences become more and more negligible in the future. +* It may be interesting to measure the actual APR of running this bot, considering the cost of server hosting and contract deployment. + +### Applicability + +* Interested readers can refer to the working code to have their own arbitrage bot: https://github.com/ExtropyIO/defi-bot. +* Currently, the example only supports flash loans from dYdX. Users can add other flash loan provider’s support. \ No newline at end of file diff --git a/coinbase.png b/coinbase.png deleted file mode 100644 index 4d03a23..0000000 Binary files a/coinbase.png and /dev/null differ diff --git a/src/ding.mp3 b/ding.mp3 similarity index 100% rename from src/ding.mp3 rename to ding.mp3 diff --git a/etherscan.png b/etherscan.png deleted file mode 100644 index cd34c43..0000000 Binary files a/etherscan.png and /dev/null differ diff --git a/package-lock.json b/package-lock.json index 234d647..4364b9a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -48,6 +48,11 @@ "resolved": "https://registry.npmjs.org/@types/node/-/node-12.12.54.tgz", "integrity": "sha512-ge4xZ3vSBornVYlDnk7yZ0gK6ChHf/CHB7Gl1I0Jhah8DDnEQqBzgohYG4FX4p81TNirSETOiSyn+y1r9/IR6w==" }, + "aes-js": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/aes-js/-/aes-js-3.0.0.tgz", + "integrity": "sha1-4h3xCtbCBTKVvLuNq0Cwnb6ofk0=" + }, "ethereumjs-account": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/ethereumjs-account/-/ethereumjs-account-3.0.0.tgz", @@ -162,6 +167,53 @@ } } } + }, + "ethers": { + "version": "4.0.48", + "resolved": "https://registry.npmjs.org/ethers/-/ethers-4.0.48.tgz", + "integrity": "sha512-sZD5K8H28dOrcidzx9f8KYh8083n5BexIO3+SbE4jK83L85FxtpXZBCQdXb8gkg+7sBqomcLhhkU7UHL+F7I2g==", + "requires": { + "aes-js": "3.0.0", + "bn.js": "^4.4.0", + "elliptic": "6.5.3", + "hash.js": "1.1.3", + "js-sha3": "0.5.7", + "scrypt-js": "2.0.4", + "setimmediate": "1.0.4", + "uuid": "2.0.1", + "xmlhttprequest": "1.8.0" + }, + "dependencies": { + "js-sha3": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/js-sha3/-/js-sha3-0.5.7.tgz", + "integrity": "sha1-DU/9gALVMzqrr0oj7tL2N0yfKOc=" + }, + "uuid": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-2.0.1.tgz", + "integrity": "sha1-wqMN7bPlNdcsz4LjQ5QaULqFM6w=" + } + } + }, + "hash.js": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.3.tgz", + "integrity": "sha512-/UETyP0W22QILqS+6HowevwhEFJ3MBJnwTf75Qob9Wz9t0DPuisL8kW8YZMK62dHAKE1c1p+gY1TtOLY+USEHA==", + "requires": { + "inherits": "^2.0.3", + "minimalistic-assert": "^1.0.0" + } + }, + "scrypt-js": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/scrypt-js/-/scrypt-js-2.0.4.tgz", + "integrity": "sha512-4KsaGcPnuhtCZQCxFxN3GVYIhKFPTdLd8PLC552XwbMndtD0cjRFAhDuuydXQ0h08ZfPgzqe6EKHozpuH74iDw==" + }, + "setimmediate": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.4.tgz", + "integrity": "sha1-IOgd5iLUoCWIzgyNqJc8vPHTE48=" } } }, @@ -184,6 +236,58 @@ "@0x/web3-wrapper": "^7.2.4", "ethereum-types": "^3.3.3", "ethers": "~4.0.4" + }, + "dependencies": { + "aes-js": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/aes-js/-/aes-js-3.0.0.tgz", + "integrity": "sha1-4h3xCtbCBTKVvLuNq0Cwnb6ofk0=" + }, + "ethers": { + "version": "4.0.48", + "resolved": "https://registry.npmjs.org/ethers/-/ethers-4.0.48.tgz", + "integrity": "sha512-sZD5K8H28dOrcidzx9f8KYh8083n5BexIO3+SbE4jK83L85FxtpXZBCQdXb8gkg+7sBqomcLhhkU7UHL+F7I2g==", + "requires": { + "aes-js": "3.0.0", + "bn.js": "^4.4.0", + "elliptic": "6.5.3", + "hash.js": "1.1.3", + "js-sha3": "0.5.7", + "scrypt-js": "2.0.4", + "setimmediate": "1.0.4", + "uuid": "2.0.1", + "xmlhttprequest": "1.8.0" + } + }, + "hash.js": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.3.tgz", + "integrity": "sha512-/UETyP0W22QILqS+6HowevwhEFJ3MBJnwTf75Qob9Wz9t0DPuisL8kW8YZMK62dHAKE1c1p+gY1TtOLY+USEHA==", + "requires": { + "inherits": "^2.0.3", + "minimalistic-assert": "^1.0.0" + } + }, + "js-sha3": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/js-sha3/-/js-sha3-0.5.7.tgz", + "integrity": "sha1-DU/9gALVMzqrr0oj7tL2N0yfKOc=" + }, + "scrypt-js": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/scrypt-js/-/scrypt-js-2.0.4.tgz", + "integrity": "sha512-4KsaGcPnuhtCZQCxFxN3GVYIhKFPTdLd8PLC552XwbMndtD0cjRFAhDuuydXQ0h08ZfPgzqe6EKHozpuH74iDw==" + }, + "setimmediate": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.4.tgz", + "integrity": "sha1-IOgd5iLUoCWIzgyNqJc8vPHTE48=" + }, + "uuid": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-2.0.1.tgz", + "integrity": "sha1-wqMN7bPlNdcsz4LjQ5QaULqFM6w=" + } } }, "@0x/json-schemas": { @@ -220,6 +324,11 @@ "lodash": "^4.17.11" }, "dependencies": { + "aes-js": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/aes-js/-/aes-js-3.0.0.tgz", + "integrity": "sha1-4h3xCtbCBTKVvLuNq0Cwnb6ofk0=" + }, "ethereumjs-util": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/ethereumjs-util/-/ethereumjs-util-5.2.1.tgz", @@ -233,6 +342,51 @@ "rlp": "^2.0.0", "safe-buffer": "^5.1.1" } + }, + "ethers": { + "version": "4.0.48", + "resolved": "https://registry.npmjs.org/ethers/-/ethers-4.0.48.tgz", + "integrity": "sha512-sZD5K8H28dOrcidzx9f8KYh8083n5BexIO3+SbE4jK83L85FxtpXZBCQdXb8gkg+7sBqomcLhhkU7UHL+F7I2g==", + "requires": { + "aes-js": "3.0.0", + "bn.js": "^4.4.0", + "elliptic": "6.5.3", + "hash.js": "1.1.3", + "js-sha3": "0.5.7", + "scrypt-js": "2.0.4", + "setimmediate": "1.0.4", + "uuid": "2.0.1", + "xmlhttprequest": "1.8.0" + } + }, + "hash.js": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.3.tgz", + "integrity": "sha512-/UETyP0W22QILqS+6HowevwhEFJ3MBJnwTf75Qob9Wz9t0DPuisL8kW8YZMK62dHAKE1c1p+gY1TtOLY+USEHA==", + "requires": { + "inherits": "^2.0.3", + "minimalistic-assert": "^1.0.0" + } + }, + "js-sha3": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/js-sha3/-/js-sha3-0.5.7.tgz", + "integrity": "sha1-DU/9gALVMzqrr0oj7tL2N0yfKOc=" + }, + "scrypt-js": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/scrypt-js/-/scrypt-js-2.0.4.tgz", + "integrity": "sha512-4KsaGcPnuhtCZQCxFxN3GVYIhKFPTdLd8PLC552XwbMndtD0cjRFAhDuuydXQ0h08ZfPgzqe6EKHozpuH74iDw==" + }, + "setimmediate": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.4.tgz", + "integrity": "sha1-IOgd5iLUoCWIzgyNqJc8vPHTE48=" + }, + "uuid": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-2.0.1.tgz", + "integrity": "sha1-wqMN7bPlNdcsz4LjQ5QaULqFM6w=" } } }, @@ -298,6 +452,11 @@ "resolved": "https://registry.npmjs.org/@types/node/-/node-12.12.54.tgz", "integrity": "sha512-ge4xZ3vSBornVYlDnk7yZ0gK6ChHf/CHB7Gl1I0Jhah8DDnEQqBzgohYG4FX4p81TNirSETOiSyn+y1r9/IR6w==" }, + "aes-js": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/aes-js/-/aes-js-3.0.0.tgz", + "integrity": "sha1-4h3xCtbCBTKVvLuNq0Cwnb6ofk0=" + }, "ethereumjs-util": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/ethereumjs-util/-/ethereumjs-util-5.2.1.tgz", @@ -311,6 +470,53 @@ "rlp": "^2.0.0", "safe-buffer": "^5.1.1" } + }, + "ethers": { + "version": "4.0.48", + "resolved": "https://registry.npmjs.org/ethers/-/ethers-4.0.48.tgz", + "integrity": "sha512-sZD5K8H28dOrcidzx9f8KYh8083n5BexIO3+SbE4jK83L85FxtpXZBCQdXb8gkg+7sBqomcLhhkU7UHL+F7I2g==", + "requires": { + "aes-js": "3.0.0", + "bn.js": "^4.4.0", + "elliptic": "6.5.3", + "hash.js": "1.1.3", + "js-sha3": "0.5.7", + "scrypt-js": "2.0.4", + "setimmediate": "1.0.4", + "uuid": "2.0.1", + "xmlhttprequest": "1.8.0" + }, + "dependencies": { + "js-sha3": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/js-sha3/-/js-sha3-0.5.7.tgz", + "integrity": "sha1-DU/9gALVMzqrr0oj7tL2N0yfKOc=" + } + } + }, + "hash.js": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.3.tgz", + "integrity": "sha512-/UETyP0W22QILqS+6HowevwhEFJ3MBJnwTf75Qob9Wz9t0DPuisL8kW8YZMK62dHAKE1c1p+gY1TtOLY+USEHA==", + "requires": { + "inherits": "^2.0.3", + "minimalistic-assert": "^1.0.0" + } + }, + "scrypt-js": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/scrypt-js/-/scrypt-js-2.0.4.tgz", + "integrity": "sha512-4KsaGcPnuhtCZQCxFxN3GVYIhKFPTdLd8PLC552XwbMndtD0cjRFAhDuuydXQ0h08ZfPgzqe6EKHozpuH74iDw==" + }, + "setimmediate": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.4.tgz", + "integrity": "sha1-IOgd5iLUoCWIzgyNqJc8vPHTE48=" + }, + "uuid": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-2.0.1.tgz", + "integrity": "sha1-wqMN7bPlNdcsz4LjQ5QaULqFM6w=" } } }, @@ -335,6 +541,11 @@ "resolved": "https://registry.npmjs.org/@types/node/-/node-12.12.54.tgz", "integrity": "sha512-ge4xZ3vSBornVYlDnk7yZ0gK6ChHf/CHB7Gl1I0Jhah8DDnEQqBzgohYG4FX4p81TNirSETOiSyn+y1r9/IR6w==" }, + "aes-js": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/aes-js/-/aes-js-3.0.0.tgz", + "integrity": "sha1-4h3xCtbCBTKVvLuNq0Cwnb6ofk0=" + }, "ethereumjs-util": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/ethereumjs-util/-/ethereumjs-util-5.2.1.tgz", @@ -348,6 +559,51 @@ "rlp": "^2.0.0", "safe-buffer": "^5.1.1" } + }, + "ethers": { + "version": "4.0.48", + "resolved": "https://registry.npmjs.org/ethers/-/ethers-4.0.48.tgz", + "integrity": "sha512-sZD5K8H28dOrcidzx9f8KYh8083n5BexIO3+SbE4jK83L85FxtpXZBCQdXb8gkg+7sBqomcLhhkU7UHL+F7I2g==", + "requires": { + "aes-js": "3.0.0", + "bn.js": "^4.4.0", + "elliptic": "6.5.3", + "hash.js": "1.1.3", + "js-sha3": "0.5.7", + "scrypt-js": "2.0.4", + "setimmediate": "1.0.4", + "uuid": "2.0.1", + "xmlhttprequest": "1.8.0" + } + }, + "hash.js": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.3.tgz", + "integrity": "sha512-/UETyP0W22QILqS+6HowevwhEFJ3MBJnwTf75Qob9Wz9t0DPuisL8kW8YZMK62dHAKE1c1p+gY1TtOLY+USEHA==", + "requires": { + "inherits": "^2.0.3", + "minimalistic-assert": "^1.0.0" + } + }, + "js-sha3": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/js-sha3/-/js-sha3-0.5.7.tgz", + "integrity": "sha1-DU/9gALVMzqrr0oj7tL2N0yfKOc=" + }, + "scrypt-js": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/scrypt-js/-/scrypt-js-2.0.4.tgz", + "integrity": "sha512-4KsaGcPnuhtCZQCxFxN3GVYIhKFPTdLd8PLC552XwbMndtD0cjRFAhDuuydXQ0h08ZfPgzqe6EKHozpuH74iDw==" + }, + "setimmediate": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.4.tgz", + "integrity": "sha1-IOgd5iLUoCWIzgyNqJc8vPHTE48=" + }, + "uuid": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-2.0.1.tgz", + "integrity": "sha1-wqMN7bPlNdcsz4LjQ5QaULqFM6w=" } } }, @@ -422,9 +678,9 @@ } }, "@ethersproject/abstract-provider": { - "version": "5.0.5", - "resolved": "https://registry.npmjs.org/@ethersproject/abstract-provider/-/abstract-provider-5.0.5.tgz", - "integrity": "sha512-i/CjElAkzV7vQBAeoz+IpjGfcFYEP9eD7j3fzZ0fzTq03DO7PPnR+xkEZ1IoDXGwDS+55aLM1xvLDwB/Lx6IOQ==", + "version": "5.0.7", + "resolved": "https://registry.npmjs.org/@ethersproject/abstract-provider/-/abstract-provider-5.0.7.tgz", + "integrity": "sha512-NF16JGn6M0zZP5ZS8KtDL2Rh7yHxZbUjBIHLNHMm/0X0BephhjUWy8jqs/Zks6kDJRzNthgmPVy41Ec0RYWPYA==", "requires": { "@ethersproject/bignumber": "^5.0.7", "@ethersproject/bytes": "^5.0.4", @@ -436,9 +692,9 @@ } }, "@ethersproject/abstract-signer": { - "version": "5.0.7", - "resolved": "https://registry.npmjs.org/@ethersproject/abstract-signer/-/abstract-signer-5.0.7.tgz", - "integrity": "sha512-8W8gy/QutEL60EoMEpvxZ8MFAEWs/JvH5nmZ6xeLXoZvmBCasGmxqHdYjo2cxg0nevkPkq9SeenSsBBZSCx+SQ==", + "version": "5.0.9", + "resolved": "https://registry.npmjs.org/@ethersproject/abstract-signer/-/abstract-signer-5.0.9.tgz", + "integrity": "sha512-CM5UNmXQaA03MyYARFDDRjHWBxujO41tVle7glf5kHcQsDDULgqSVpkliLJMtPzZjOKFeCVZBHybTZDEZg5zzg==", "requires": { "@ethersproject/abstract-provider": "^5.0.4", "@ethersproject/bignumber": "^5.0.7", @@ -448,56 +704,55 @@ } }, "@ethersproject/address": { - "version": "5.0.5", - "resolved": "https://registry.npmjs.org/@ethersproject/address/-/address-5.0.5.tgz", - "integrity": "sha512-DpkQ6rwk9jTefrRsJzEm6nhRiJd9pvhn1xN0rw5N/jswXG5r7BLk/GVA0mMAVWAsYfvi2xSc5L41FMox43RYEA==", + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/@ethersproject/address/-/address-5.0.8.tgz", + "integrity": "sha512-V87DHiZMZR6hmFYmoGaHex0D53UEbZpW75uj8AqPbjYUmi65RB4N2LPRcJXuWuN2R0Y2CxkvW6ArijWychr5FA==", "requires": { - "@ethersproject/bignumber": "^5.0.7", + "@ethersproject/bignumber": "^5.0.10", "@ethersproject/bytes": "^5.0.4", "@ethersproject/keccak256": "^5.0.3", "@ethersproject/logger": "^5.0.5", - "@ethersproject/rlp": "^5.0.3", - "bn.js": "^4.4.0" + "@ethersproject/rlp": "^5.0.3" } }, "@ethersproject/base64": { - "version": "5.0.4", - "resolved": "https://registry.npmjs.org/@ethersproject/base64/-/base64-5.0.4.tgz", - "integrity": "sha512-4KRykQ7BQMeOXfvio1YITwHjxwBzh92UoXIdzxDE1p53CK28bbHPdsPNYo0wl0El7lJAMpT2SOdL0hhbWRnyIA==", + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/@ethersproject/base64/-/base64-5.0.6.tgz", + "integrity": "sha512-HwrGn8YMiUf7bcdVvB4NJ+eWT0BtEFpDtrYxVXEbR7p/XBSJjwiR7DEggIiRvxbualMKg+EZijQWJ3az2li0uw==", "requires": { "@ethersproject/bytes": "^5.0.4" } }, "@ethersproject/bignumber": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/@ethersproject/bignumber/-/bignumber-5.0.8.tgz", - "integrity": "sha512-KXFVAFKS1jdTXYN8BE5Oj+ZfPMh28iRdFeNGBVT6cUFdtiPVqeXqc0ggvBqA3A1VoFFGgM7oAeaagA393aORHA==", + "version": "5.0.12", + "resolved": "https://registry.npmjs.org/@ethersproject/bignumber/-/bignumber-5.0.12.tgz", + "integrity": "sha512-mbFZjwthx6vFlHG9owXP/C5QkNvsA+xHpDCkPPPdG2n1dS9AmZAL5DI0InNLid60rQWL3MXpEl19tFmtL7Q9jw==", "requires": { - "@ethersproject/bytes": "^5.0.4", + "@ethersproject/bytes": "^5.0.8", "@ethersproject/logger": "^5.0.5", "bn.js": "^4.4.0" } }, "@ethersproject/bytes": { - "version": "5.0.5", - "resolved": "https://registry.npmjs.org/@ethersproject/bytes/-/bytes-5.0.5.tgz", - "integrity": "sha512-IEj9HpZB+ACS6cZ+QQMTqmu/cnUK2fYNE6ms/PVxjoBjoxc6HCraLpam1KuRvreMy0i523PLmjN8OYeikRdcUQ==", + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/@ethersproject/bytes/-/bytes-5.0.8.tgz", + "integrity": "sha512-O+sJNVGzzuy51g+EMK8BegomqNIg+C2RO6vOt0XP6ac4o4saiq69FnjlsrNslaiMFVO7qcEHBsWJ9hx1tj1lMw==", "requires": { "@ethersproject/logger": "^5.0.5" } }, "@ethersproject/constants": { - "version": "5.0.5", - "resolved": "https://registry.npmjs.org/@ethersproject/constants/-/constants-5.0.5.tgz", - "integrity": "sha512-foaQVmxp2+ik9FrLUCtVrLZCj4M3Ibgkqvh+Xw/vFRSerkjVSYePApaVE5essxhoSlF1U9oXfWY09QI2AXtgKA==", + "version": "5.0.7", + "resolved": "https://registry.npmjs.org/@ethersproject/constants/-/constants-5.0.7.tgz", + "integrity": "sha512-cbQK1UpE4hamB52Eg6DLhJoXeQ1plSzekh5Ujir1xdREdwdsZPPXKczkrWqBBR0KyywJZHN/o/hj0w8j7scSGg==", "requires": { "@ethersproject/bignumber": "^5.0.7" } }, "@ethersproject/hash": { - "version": "5.0.6", - "resolved": "https://registry.npmjs.org/@ethersproject/hash/-/hash-5.0.6.tgz", - "integrity": "sha512-Gvh57v6BWhwnud6l7tMfQm32PRQ2DYx2WaAAQmAxAfYvmzUkpQCBstnGeNMXIL8/2wdkvcB2u+WZRWaZtsFuUQ==", + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/@ethersproject/hash/-/hash-5.0.8.tgz", + "integrity": "sha512-Qay01tcFyFreYjSMt82rOQGMfQDmLm1sj3iNNO1BhrVf840xgBZuJ7gBATERzAjTuTCHUHw9BuGwxErJUS95yg==", "requires": { "@ethersproject/abstract-signer": "^5.0.6", "@ethersproject/address": "^5.0.5", @@ -510,9 +765,9 @@ } }, "@ethersproject/keccak256": { - "version": "5.0.4", - "resolved": "https://registry.npmjs.org/@ethersproject/keccak256/-/keccak256-5.0.4.tgz", - "integrity": "sha512-GNpiOUm9PGUxFNqOxYKDQBM0u68bG9XC9iOulEQ8I0tOx/4qUpgVzvgXL6ugxr0RY554Gz/NQsVqknqPzUcxpQ==", + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/@ethersproject/keccak256/-/keccak256-5.0.6.tgz", + "integrity": "sha512-eJ4Id/i2rwrf5JXEA7a12bG1phuxjj47mPZgDUbttuNBodhSuZF2nEO5QdpaRjmlphQ8Kt9PNqY/z7lhtJptZg==", "requires": { "@ethersproject/bytes": "^5.0.4", "js-sha3": "0.5.7" @@ -526,39 +781,39 @@ } }, "@ethersproject/logger": { - "version": "5.0.6", - "resolved": "https://registry.npmjs.org/@ethersproject/logger/-/logger-5.0.6.tgz", - "integrity": "sha512-FrX0Vnb3JZ1md/7GIZfmJ06XOAA8r3q9Uqt9O5orr4ZiksnbpXKlyDzQtlZ5Yv18RS8CAUbiKH9vwidJg1BPmQ==" + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/@ethersproject/logger/-/logger-5.0.8.tgz", + "integrity": "sha512-SkJCTaVTnaZ3/ieLF5pVftxGEFX56pTH+f2Slrpv7cU0TNpUZNib84QQdukd++sWUp/S7j5t5NW+WegbXd4U/A==" }, "@ethersproject/networks": { - "version": "5.0.4", - "resolved": "https://registry.npmjs.org/@ethersproject/networks/-/networks-5.0.4.tgz", - "integrity": "sha512-/wHDTRms5mpJ09BoDrbNdFWINzONe05wZRgohCXvEv39rrH/Gd/yAnct8wC0RsW3tmFOgjgQxuBvypIxuUynTw==", + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/@ethersproject/networks/-/networks-5.0.6.tgz", + "integrity": "sha512-2Cg1N5109zzFOBfkyuPj+FfF7ioqAsRffmybJ2lrsiB5skphIAE72XNSCs4fqktlf+rwSh/5o/UXRjXxvSktZw==", "requires": { "@ethersproject/logger": "^5.0.5" } }, "@ethersproject/properties": { - "version": "5.0.4", - "resolved": "https://registry.npmjs.org/@ethersproject/properties/-/properties-5.0.4.tgz", - "integrity": "sha512-UdyX3GqBxFt15B0uSESdDNmhvEbK3ACdDXl2soshoPcneXuTswHDeA0LoPlnaZzhbgk4p6jqb4GMms5C26Qu6A==", + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/@ethersproject/properties/-/properties-5.0.6.tgz", + "integrity": "sha512-a9DUMizYhJ0TbtuDkO9iYlb2CDlpSKqGPDr+amvlZhRspQ6jbl5Eq8jfu4SCcGlcfaTbguJmqGnyOGn1EFt6xA==", "requires": { "@ethersproject/logger": "^5.0.5" } }, "@ethersproject/rlp": { - "version": "5.0.4", - "resolved": "https://registry.npmjs.org/@ethersproject/rlp/-/rlp-5.0.4.tgz", - "integrity": "sha512-5qrrZad7VTjofxSsm7Zg/7Dr4ZOln4S2CqiDdOuTv6MBKnXj0CiBojXyuDy52M8O3wxH0CyE924hXWTDV1PQWQ==", + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/@ethersproject/rlp/-/rlp-5.0.6.tgz", + "integrity": "sha512-M223MTaydfmQSsvqAl0FJZDYFlSqt6cgbhnssLDwqCKYegAHE16vrFyo+eiOapYlt32XAIJm0BXlqSunULzZuQ==", "requires": { "@ethersproject/bytes": "^5.0.4", "@ethersproject/logger": "^5.0.5" } }, "@ethersproject/signing-key": { - "version": "5.0.5", - "resolved": "https://registry.npmjs.org/@ethersproject/signing-key/-/signing-key-5.0.5.tgz", - "integrity": "sha512-Z1wY7JC1HVO4CvQWY2TyTTuAr8xK3bJijZw1a9G92JEmKdv1j255R/0YLBBcFTl2J65LUjtXynNJ2GbArPGi5g==", + "version": "5.0.7", + "resolved": "https://registry.npmjs.org/@ethersproject/signing-key/-/signing-key-5.0.7.tgz", + "integrity": "sha512-JYndnhFPKH0daPcIjyhi+GMcw3srIHkQ40hGRe6DA0CdGrpMfgyfSYDQ2D8HL2lgR+Xm4SHfEB0qba6+sCyrvg==", "requires": { "@ethersproject/bytes": "^5.0.4", "@ethersproject/logger": "^5.0.5", @@ -567,9 +822,9 @@ } }, "@ethersproject/strings": { - "version": "5.0.5", - "resolved": "https://registry.npmjs.org/@ethersproject/strings/-/strings-5.0.5.tgz", - "integrity": "sha512-JED6WaIV00xM/gvj8vSnd+0VWtDYdidTmavFRCTQakqfz+4tDo6Jz5LHgG+dd45h7ah7ykCHW0C7ZXWEDROCXQ==", + "version": "5.0.7", + "resolved": "https://registry.npmjs.org/@ethersproject/strings/-/strings-5.0.7.tgz", + "integrity": "sha512-a+6T80LvmXGMOOWQTZHtGGQEg1z4v8rm8oX70KNs55YtPXI/5J3LBbVf5pyqCKSlmiBw5IaepPvs5XGalRUSZQ==", "requires": { "@ethersproject/bytes": "^5.0.4", "@ethersproject/constants": "^5.0.4", @@ -577,9 +832,9 @@ } }, "@ethersproject/transactions": { - "version": "5.0.6", - "resolved": "https://registry.npmjs.org/@ethersproject/transactions/-/transactions-5.0.6.tgz", - "integrity": "sha512-htsFhOD+NMBxx676A8ehSuwVV49iqpSB+CkjPZ02tpNew0K6p8g0CZ46Z1ZP946gIHAU80xQ0NACHYrjIUaCFA==", + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/@ethersproject/transactions/-/transactions-5.0.8.tgz", + "integrity": "sha512-i7NtOXVzUe+YSU6QufzlRrI2WzHaTmULAKHJv4duIZMLqzehCBXGA9lTpFgFdqGYcQJ7vOtNFC2BB2mSjmuXqg==", "requires": { "@ethersproject/address": "^5.0.4", "@ethersproject/bignumber": "^5.0.7", @@ -593,9 +848,9 @@ } }, "@ethersproject/web": { - "version": "5.0.9", - "resolved": "https://registry.npmjs.org/@ethersproject/web/-/web-5.0.9.tgz", - "integrity": "sha512-//QNlv1MSkOII1hv3+HQwWoiVFS+BMVGI0KYeUww4cyrEktnx1QIez5bTSab9s9fWTFaWKNmQNBwMbxAqPuYDw==", + "version": "5.0.11", + "resolved": "https://registry.npmjs.org/@ethersproject/web/-/web-5.0.11.tgz", + "integrity": "sha512-x03ihbPoN1S8Gsh9WSwxkYxUIumLi02ZEKJku1C43sxBfe+mdprWyvujzYlpuoRNfWRgNhdRDKMP8JbG6MwNGA==", "requires": { "@ethersproject/base64": "^5.0.3", "@ethersproject/bytes": "^5.0.4", @@ -834,6 +1089,25 @@ "web3": "*" } }, + "@uniswap/sdk": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@uniswap/sdk/-/sdk-3.0.3.tgz", + "integrity": "sha512-t4s8bvzaCFSiqD2qfXIm3rWhbdnXp+QjD3/mRaeVDHK7zWevs6RGEb1ohMiNgOCTZANvBayb4j8p+XFdnMBadQ==", + "requires": { + "@uniswap/v2-core": "^1.0.0", + "big.js": "^5.2.2", + "decimal.js-light": "^2.5.0", + "jsbi": "^3.1.1", + "tiny-invariant": "^1.1.0", + "tiny-warning": "^1.0.3", + "toformat": "^2.0.0" + } + }, + "@uniswap/v2-core": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@uniswap/v2-core/-/v2-core-1.0.1.tgz", + "integrity": "sha512-MtybtkUPSyysqLY2U210NBDeCHX+ltHt3oADGdjqoThZaFRDKwM6k1Nb3F0A3hk5hwuQvytFWhrWHOEq6nVJ8Q==" + }, "abortcontroller-polyfill": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/abortcontroller-polyfill/-/abortcontroller-polyfill-1.5.0.tgz", @@ -1651,6 +1925,11 @@ "tweetnacl": "^0.14.3" } }, + "big.js": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz", + "integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==" + }, "bignumber.js": { "version": "9.0.1", "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.0.1.tgz", @@ -2233,6 +2512,11 @@ "ms": "2.0.0" } }, + "decimal.js-light": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/decimal.js-light/-/decimal.js-light-2.5.1.tgz", + "integrity": "sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg==" + }, "decode-uri-component": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz", @@ -3245,58 +3529,6 @@ "uuid": "^3.3.2" } }, - "ethers": { - "version": "4.0.48", - "resolved": "https://registry.npmjs.org/ethers/-/ethers-4.0.48.tgz", - "integrity": "sha512-sZD5K8H28dOrcidzx9f8KYh8083n5BexIO3+SbE4jK83L85FxtpXZBCQdXb8gkg+7sBqomcLhhkU7UHL+F7I2g==", - "requires": { - "aes-js": "3.0.0", - "bn.js": "^4.4.0", - "elliptic": "6.5.3", - "hash.js": "1.1.3", - "js-sha3": "0.5.7", - "scrypt-js": "2.0.4", - "setimmediate": "1.0.4", - "uuid": "2.0.1", - "xmlhttprequest": "1.8.0" - }, - "dependencies": { - "aes-js": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/aes-js/-/aes-js-3.0.0.tgz", - "integrity": "sha1-4h3xCtbCBTKVvLuNq0Cwnb6ofk0=" - }, - "hash.js": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.3.tgz", - "integrity": "sha512-/UETyP0W22QILqS+6HowevwhEFJ3MBJnwTf75Qob9Wz9t0DPuisL8kW8YZMK62dHAKE1c1p+gY1TtOLY+USEHA==", - "requires": { - "inherits": "^2.0.3", - "minimalistic-assert": "^1.0.0" - } - }, - "js-sha3": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/js-sha3/-/js-sha3-0.5.7.tgz", - "integrity": "sha1-DU/9gALVMzqrr0oj7tL2N0yfKOc=" - }, - "scrypt-js": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/scrypt-js/-/scrypt-js-2.0.4.tgz", - "integrity": "sha512-4KsaGcPnuhtCZQCxFxN3GVYIhKFPTdLd8PLC552XwbMndtD0cjRFAhDuuydXQ0h08ZfPgzqe6EKHozpuH74iDw==" - }, - "setimmediate": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.4.tgz", - "integrity": "sha1-IOgd5iLUoCWIzgyNqJc8vPHTE48=" - }, - "uuid": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-2.0.1.tgz", - "integrity": "sha1-wqMN7bPlNdcsz4LjQ5QaULqFM6w=" - } - } - }, "ethjs-unit": { "version": "0.1.6", "resolved": "https://registry.npmjs.org/ethjs-unit/-/ethjs-unit-0.1.6.tgz", @@ -4058,6 +4290,11 @@ "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.2.tgz", "integrity": "sha1-mGbfOVECEw449/mWvOtlRDIJwls=" }, + "jsbi": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/jsbi/-/jsbi-3.1.4.tgz", + "integrity": "sha512-52QRRFSsi9impURE8ZUbzAMCLjPm4THO7H2fcuIvaaeFTbSysvkodbQQXIVsNgq/ypDbq6dJiuGKL0vZ/i9hUg==" + }, "jsbn": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", @@ -5718,6 +5955,16 @@ "resolved": "https://registry.npmjs.org/timed-out/-/timed-out-4.0.1.tgz", "integrity": "sha1-8y6srFoXW+ol1/q1Zas+2HQe9W8=" }, + "tiny-invariant": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.1.0.tgz", + "integrity": "sha512-ytxQvrb1cPc9WBEI/HSeYYoGD0kWnGEOR8RY6KomWLBVhqz0RgTwVO9dLrGz7dC+nN9llyI7OKAgRq8Vq4ZBSw==" + }, + "tiny-warning": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/tiny-warning/-/tiny-warning-1.0.3.tgz", + "integrity": "sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==" + }, "to-fast-properties": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", @@ -5728,6 +5975,11 @@ "resolved": "https://registry.npmjs.org/to-readable-stream/-/to-readable-stream-1.0.0.tgz", "integrity": "sha512-Iq25XBt6zD5npPhlLVXGFN3/gyR2/qODcKNNyTMd4vbm39HUaOiAM4PMq0eMVC/Tkxz+Zjdsc55g9yyz+Yq00Q==" }, + "toformat": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/toformat/-/toformat-2.0.0.tgz", + "integrity": "sha512-03SWBVop6nU8bpyZCx7SodpYznbZF5R4ljwNLBcTQzKOD9xuihRo/psX58llS1BMFhhAI08H3luot5GoXJz2pQ==" + }, "toidentifier": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz", @@ -5986,7 +6238,6 @@ "resolved": "https://registry.npmjs.org/web3-core-method/-/web3-core-method-1.3.0.tgz", "integrity": "sha512-h0yFDrYVzy5WkLxC/C3q+hiMnzxdWm9p1T1rslnuHgOp6nYfqzu/6mUIXrsS4h/OWiGJt+BZ0xVZmtC31HDWtg==", "requires": { - "@ethersproject/transactions": "^5.0.0-beta.135", "underscore": "1.9.1", "web3-core-helpers": "1.3.0", "web3-core-promievent": "1.3.0", diff --git a/package.json b/package.json index 7416959..73867d6 100644 --- a/package.json +++ b/package.json @@ -10,9 +10,12 @@ "eject": "react-scripts eject" }, "dependencies": { + "@0x/order-utils": "^10.2.4", "@truffle/hdwallet-provider": "^1.0.35", + "@uniswap/sdk": "^3.0.3", "axios": "^0.19.2", "console.table": "^0.10.0", + "cors": "", "dotenv": "^8.2.0", "ejs": "^2.5.6", "express": "^4.15.2", @@ -21,8 +24,6 @@ "moment": "^2.25.3", "moment-timezone": "^0.5.28", "numeral": "^2.0.6", - "@0x/order-utils": "^10.2.4", - "cors": "", "play-sound": "", "sha3": "2.0.2", "web3": "", diff --git a/radar-relay.png b/radar-relay.png deleted file mode 100644 index 346c658..0000000 Binary files a/radar-relay.png and /dev/null differ diff --git a/src/contracts/TradingBot.sol b/src/contracts/TradingBot.sol index f846675..e693226 100644 --- a/src/contracts/TradingBot.sol +++ b/src/contracts/TradingBot.sol @@ -171,7 +171,7 @@ contract DyDxFlashLoan is Structs { pragma solidity ^0.5.0; -contract IOneSplit { +contract IOneSplit { // interface for 1inch exchange. function getExpectedReturn( IERC20 fromToken, IERC20 toToken, @@ -196,7 +196,7 @@ contract IOneSplit { ) public payable; } -contract TradingBot is DyDxFlashLoan { +contract TradingBot is DyDxFlashLoan { // Where the actual contract for the trading bot starts. anything above that are just libraries. uint256 public loan; // Addresses @@ -343,7 +343,7 @@ contract TradingBot is DyDxFlashLoan { } function _approveWeth(uint256 _amount) internal { - IERC20(WETH).approve(ZRX_STAKING_PROXY, _amount); + IERC20(WETH).approve(ZRX_STAKING_PROXY, _amount); // approves the 0x staking proxy - the proxy is the fee collector for 0x, i.e. we will use WETH in order to pay for trading fees } // KEEP THIS FUNCTION IN CASE THE CONTRACT RECEIVES TOKENS! diff --git a/src/examples/checkPair.js b/src/examples/checkPair.js new file mode 100644 index 0000000..247e8eb --- /dev/null +++ b/src/examples/checkPair.js @@ -0,0 +1,33 @@ +const moment = require('moment-timezone'); +const { uniswapFactoryContract } = require("./exchange/uniswap"); +const { kyberRateContract } = require("./exchange/kyber"); +const { uniswapV2Factory } = require('./exchange/uniswapv2'); +const web3 = require('web3') + + +async function checkPair(args) { + const { inputTokenSymbol, inputTokenAddress, outputTokenSymbol, outputTokenAddress, inputAmount } = args; + const uniswapV2 = uniswapV2Factory({ symbol: inputTokenSymbol, address: inputTokenAddress }, + { symbol: outputTokenSymbol, address: outputTokenAddress }); + const uv2Value = await uniswapV2.getPrice(); + const uv2slippageRate = 0.005; + // const exchangeAddress = await uniswapFactoryContract.methods.getExchange(outputTokenAddress).call(); + // const uniswap = new web3.eth.Contract(UNISWAP_EXCHANGE_ABI, exchangeAddress); +// const uniswapResult = await uniswap.methods.getEthToTokenInputPrice(inputAmount).call(); + let kyberResult = await kyberRateContract.methods.getExpectedRate(inputTokenAddress, outputTokenAddress, inputAmount, true).call(); + + const uniswapMinReturn = uv2Value.mid - (uv2Value.mid * uv2slippageRate); + const khyberMinReturn = web3.utils.fromWei(kyberResult.slippageRate, 'Ether'); + console.table([{ + 'Input Token': inputTokenSymbol, + 'Output Token': outputTokenSymbol, + 'Input Amount': web3.utils.fromWei(inputAmount, 'Ether'), + // 'Uniswap Return': uv2Value.mid, + 'Uniswap Min Return': uniswapMinReturn, + // 'Kyber Expected Rate': web3.utils.fromWei(kyberResult.expectedRate, 'Ether'), + 'Kyber Min Return': khyberMinReturn, + 'EXPECTED RETURN': (uniswapMinReturn - khyberMinReturn) / (1/uv2Value.midverse) , + 'Timestamp': moment().tz('America/Chicago').format(), + }]); +} +module.exports = {checkPair}; \ No newline at end of file diff --git a/src/examples/coins/erc20.js b/src/examples/coins/erc20.js index d22135d..18af476 100644 --- a/src/examples/coins/erc20.js +++ b/src/examples/coins/erc20.js @@ -1,13 +1,21 @@ - const ETH = { symbol:"ETH", address:'0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee' } +const WETH = { + symbol:"WETH", + address:'0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2' +} const DAI = { symbol:"DAI", address:'0x6b175474e89094c44da98b954eedeac495271d0f' } +const WBTC = { + symbol: 'WBTC', + address: '0x2260fac5e5542a773aa44fbcfedf7c193bc2c599' +} + const MKR = { symbol: 'MKR', address: '0x9f8f72aa9304c8b593d555f12ef6589cc3a579a2', @@ -29,10 +37,14 @@ const AMPL = { } const erc20s = { - ETH, DAI, + ETH, + WETH, + DAI, MKR, KNC, - LINK,AMPL + LINK, + AMPL, + WBTC } function forTokens(inputToken, outputToken, inputAmount){ @@ -47,4 +59,4 @@ function forTokens(inputToken, outputToken, inputAmount){ }; } -exports.forTokens = forTokens; \ No newline at end of file +module.exports = {forTokens}; //exports.forTokens = forTokens; \ No newline at end of file diff --git a/src/examples/exchange/uniswapv2.js b/src/examples/exchange/uniswapv2.js new file mode 100644 index 0000000..5fe531c --- /dev/null +++ b/src/examples/exchange/uniswapv2.js @@ -0,0 +1,22 @@ +const { ChainId, Token, Fetcher, Route } = require('@uniswap/sdk'); + + +function uniswapV2Factory (token1, token2) { + return { + getPrice: async () => { + const t1 = await Fetcher.fetchTokenData(ChainId.MAINNET, token1.address); + const t2 = await Fetcher.fetchTokenData(ChainId.MAINNET, token2.address); + + const pair = await Fetcher.fetchPairData(t1, t2) + const route = new Route([pair], t1) + const mid = route.midPrice.toSignificant(6); + const midverse = route.midPrice.invert().toSignificant(6) + return { + mid, + midverse + } + } + } +} + +module.exports = {uniswapV2Factory}; \ No newline at end of file diff --git a/src/examples/price-bot.js b/src/examples/price-bot.js index cec907e..af720bb 100644 --- a/src/examples/price-bot.js +++ b/src/examples/price-bot.js @@ -56,6 +56,59 @@ async function monitorPrice() { try { await checkPair(forTokens("ETH", "MKR", web3.utils.toWei('1', 'ETHER'))); + // await checkPair(forTokens("ETH", "DAI", web3.utils.toWei('1', 'ETHER'))); + // await checkPair(forTokens("ETH", "KNC", web3.utils.toWei('1', 'ETHER'))); + // await checkPair(forTokens("ETH", "LINK", web3.utils.toWei('1', 'ETHER'))); + // await checkPair(forTokens("ETH", "AMPL", web3.utils.toWei('1', 'ETHER'))); + + } catch (error) { + console.error(error) + monitoringPrice = false + clearInterval(priceMonitor) + return + } + + monitoringPrice = false +} + +// Check markets every n seconds +const POLLING_INTERVAL = process.env.POLLING_INTERVAL || 3000 // 3 Seconds +priceMonitor = setInterval(async () => { await monitorPrice() }, POLLING_INTERVAL) + + +/* + +require('dotenv').config() +const express = require('express') +const http = require('http') +const Web3 = require('web3') +const {checkPair} = require('./checkPair'); +const {forTokens} = require('./coins/erc20') + +// SERVER CONFIG +const PORT = process.env.PORT || 5000 +const app = express(); +const server = http.createServer(app).listen(PORT, () => console.log(`Listening on ${ PORT }`)) + +// WEB3 CONFIG +const web3 = new Web3(process.env.RPC_URL) +exports.web3 = web3 + +let priceMonitor +let monitoringPrice = false + +async function monitorPrice() { + if(monitoringPrice) { + return + } + + console.log("Checking prices...") + monitoringPrice = true + + try { + + await checkPair(forTokens("DAI", "WBTC", web3.utils.toWei('1', 'ETHER'))); + await checkPair(forTokens("ETH", "WBTC", web3.utils.toWei('1', 'ETHER'))); await checkPair(forTokens("ETH", "DAI", web3.utils.toWei('1', 'ETHER'))); await checkPair(forTokens("ETH", "KNC", web3.utils.toWei('1', 'ETHER'))); await checkPair(forTokens("ETH", "LINK", web3.utils.toWei('1', 'ETHER'))); @@ -73,4 +126,6 @@ async function monitorPrice() { // Check markets every n seconds const POLLING_INTERVAL = process.env.POLLING_INTERVAL || 3000 // 3 Seconds -priceMonitor = setInterval(async () => { await monitorPrice() }, POLLING_INTERVAL) \ No newline at end of file +priceMonitor = setInterval(async () => { await monitorPrice() }, POLLING_INTERVAL) + +*/ \ No newline at end of file diff --git a/src/index.js b/src/index.js index 8eb8f71..873336c 100644 --- a/src/index.js +++ b/src/index.js @@ -23,6 +23,7 @@ const web3 = new Web3(process.env.RPC_URL) web3.eth.accounts.wallet.add(process.env.PRIVATE_KEY) // SMART CONTRACTS +// https://github.com/CryptoManiacsZone/1inchProtocol/blob/master/contracts/OneSplitAudit.sol const ONE_SPLIT_ABI = [{"inputs":[{"internalType":"contract IOneSplit","name":"impl","type":"address"}],"payable":false,"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"newImpl","type":"address"}],"name":"ImplementationUpdated","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"previousOwner","type":"address"},{"indexed":true,"internalType":"address","name":"newOwner","type":"address"}],"name":"OwnershipTransferred","type":"event"},{"payable":true,"stateMutability":"payable","type":"fallback"},{"constant":true,"inputs":[],"name":"FLAG_DISABLE_AAVE","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"FLAG_DISABLE_BANCOR","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"FLAG_DISABLE_BDAI","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"FLAG_DISABLE_CHAI","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"FLAG_DISABLE_COMPOUND","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"FLAG_DISABLE_CURVE_BINANCE","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"FLAG_DISABLE_CURVE_COMPOUND","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"FLAG_DISABLE_CURVE_SYNTHETIX","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"FLAG_DISABLE_CURVE_USDT","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"FLAG_DISABLE_CURVE_Y","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"FLAG_DISABLE_FULCRUM","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"FLAG_DISABLE_IEARN","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"FLAG_DISABLE_KYBER","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"FLAG_DISABLE_OASIS","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"FLAG_DISABLE_SMART_TOKEN","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"FLAG_DISABLE_UNISWAP","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"FLAG_DISABLE_WETH","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"FLAG_ENABLE_KYBER_BANCOR_RESERVE","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"FLAG_ENABLE_KYBER_OASIS_RESERVE","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"FLAG_ENABLE_KYBER_UNISWAP_RESERVE","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"FLAG_ENABLE_MULTI_PATH_DAI","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"FLAG_ENABLE_MULTI_PATH_ETH","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"FLAG_ENABLE_MULTI_PATH_USDC","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"FLAG_ENABLE_UNISWAP_COMPOUND","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"internalType":"contract IERC20","name":"asset","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"claimAsset","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"internalType":"contract IERC20","name":"fromToken","type":"address"},{"internalType":"contract IERC20","name":"toToken","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"uint256","name":"parts","type":"uint256"},{"internalType":"uint256","name":"featureFlags","type":"uint256"}],"name":"getExpectedReturn","outputs":[{"internalType":"uint256","name":"returnAmount","type":"uint256"},{"internalType":"uint256[]","name":"distribution","type":"uint256[]"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"isOwner","outputs":[{"internalType":"bool","name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"oneSplitImpl","outputs":[{"internalType":"contract IOneSplit","name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"owner","outputs":[{"internalType":"address","name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[],"name":"renounceOwnership","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"internalType":"contract IOneSplit","name":"impl","type":"address"}],"name":"setNewImpl","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"internalType":"contract IERC20","name":"fromToken","type":"address"},{"internalType":"contract IERC20","name":"toToken","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"uint256","name":"minReturn","type":"uint256"},{"internalType":"uint256[]","name":"distribution","type":"uint256[]"},{"internalType":"uint256","name":"featureFlags","type":"uint256"}],"name":"swap","outputs":[],"payable":true,"stateMutability":"payable","type":"function"},{"constant":false,"inputs":[{"internalType":"address","name":"newOwner","type":"address"}],"name":"transferOwnership","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"}] const ONE_SPLIT_ADDRESS = "0xC586BeF4a0992C495Cf22e1aeEE4E446CECDee0E" const oneSplitContract = new web3.eth.Contract(ONE_SPLIT_ABI, ONE_SPLIT_ADDRESS); @@ -41,6 +42,7 @@ const FILL_ORDER_ABI = {"constant":false,"inputs":[{"components":[{"internalType // ESCHANGE NAMES +// https://api.1inch.exchange/v1.1/exchanges const ZERO_X = '0x' const ONE_SPLIT = '1Split' @@ -48,12 +50,17 @@ const ONE_SPLIT = '1Split' // ASSET SYMBOLS const DAI = 'DAI' const WETH = 'WETH' +const SAI = 'SAI' +const USDC = 'USDC' // ASSET ADDRESSES +// https://api.1inch.exchange/v1.1/tokens const ASSET_ADDRESSES = { DAI: '0x6b175474e89094c44da98b954eedeac495271d0f', - WETH: '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2' + WETH: '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2', + SAI: '0x89d24a6b4ccb1b6faa2625fe562bdd9a23260359', + USDC: '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48' } @@ -62,9 +69,9 @@ tokensWithDecimalPlaces = (amount, symbol) => { amount = amount.toString() switch (symbol) { case DAI: // 18 decimals - return web3.utils.fromWei(amount, 'Ether') + return web3.utils.fromWei(amount, 'ether') default: - return web3.utils.fromWei(amount, 'Ether') + return web3.utils.fromWei(amount, 'ether') } } @@ -97,16 +104,21 @@ const toTokens = (tokenAmount, symbol) => { case WETH: // 18 decimals return web3.utils.toWei(tokenAmount, 'Ether') case USDC: // 6 decimals - return web3.utils.fromWei(web3.utils.toWei(tokenAmount), 'Szabo') + return web3.utils.fromWei(web3.utils.toWei(tokenAmount), 'szabo') } } // TRADING FUNCTIONS +// TODO if you configure the ONE_SPLIT_PARTS in the getExpectedReturn function, then you can swap from any dex to any dex actually const ONE_SPLIT_PARTS = 10 const ONE_SPLIT_FLAGS = 0 +// TODO move the following function to where it is called, i.e. we don't need this as a separate function as it's only used once in the code async function fetchOneSplitData(args) { const { fromToken, toToken, amount } = args + // https://etherscan.io/address/0xc586bef4a0992c495cf22e1aeee4e446cecdee0e#readContract + // https://etherscan.io/address/0x96b610046d63638d970e6243151311d8827d69a5#readContract + // TODO why not use this API call instead? https://api.1inch.exchange/v1.1/quote?fromTokenSymbol=ETH&toTokenSymbol=DAI&amount=100000000000000000000 const data = await oneSplitContract.methods.getExpectedReturn(fromToken, toToken, amount, ONE_SPLIT_PARTS, ONE_SPLIT_FLAGS).call() return(data) } @@ -115,10 +127,13 @@ async function fetchOneSplitData(args) { const checkedOrders = [] let profitableArbFound = false async function checkArb(args) { - const { zrxOrder, assetOrder } = args + const { zrxOrder, metadata, assetOrder } = args // Track order + // TODO remove this check! I.e. just because the order on 0x hasn't changed doesn't mean that the AMM prices haven't changed. + // Also, once I will handle partially filled orders, it will be important to check orders again as the fill amount may have increased const tempOrderID = JSON.stringify(zrxOrder) + let amountLeft = metadata.remainingFillableTakerAssetAmount // Skip if order checked if(checkedOrders.includes(tempOrderID)) { @@ -128,47 +143,59 @@ async function checkArb(args) { // Add to checked orders checkedOrders.push(tempOrderID) + // Skip if Maker Fee + // TODO does this even make sense? The bot is always going to be the taker, plus I haven't yet seen any maker fee on 0x other than 0 if(zrxOrder.makerFee.toString() !== '0') { console.log('Order has maker fee') return } - // Skip if Taker Fee - if(zrxOrder.takerFee.toString() !== '0') { - console.log('Order has taker fee') - return - } - // This becomes the input amount - const inputAssetAmount = zrxOrder.takerAssetAmount + let inputAssetAmount = zrxOrder.takerAssetAmount // Build order tuple const orderTuple = [ - zrxOrder.makerAddress, - zrxOrder.takerAddress, - zrxOrder.feeRecipientAddress, - zrxOrder.senderAddress, - zrxOrder.makerAssetAmount, - zrxOrder.takerAssetAmount, - zrxOrder.makerFee, - zrxOrder.takerFee, - zrxOrder.expirationTimeSeconds, - zrxOrder.salt, - zrxOrder.makerAssetData, - zrxOrder.takerAssetData, - zrxOrder.makerFeeAssetData, - zrxOrder.takerFeeAssetData - ] + zrxOrder.makerAddress, // Address that created the order. + zrxOrder.takerAddress, // Address that is allowed to fill the order. If set to 0, any address is allowed to fill the order. + zrxOrder.feeRecipientAddress, // Address that will recieve fees when order is filled. + zrxOrder.senderAddress, // Address that is allowed to call Exchange contract methods that affect this order. If set to 0, any address is allowed to call these methods. + zrxOrder.makerAssetAmount, // Amount of makerAsset being offered by maker. Must be greater than 0. + zrxOrder.takerAssetAmount, // Amount of takerAsset being bid on by maker. Must be greater than 0. + zrxOrder.makerFee, // Fee paid to feeRecipient by maker when order is filled. + zrxOrder.takerFee, // Fee paid to feeRecipient by taker when order is filled. + zrxOrder.expirationTimeSeconds, // Timestamp in seconds at which order expires. + zrxOrder.salt, // Arbitrary number to facilitate uniqueness of the order's hash. + zrxOrder.makerAssetData, // Encoded data that can be decoded by a specified proxy contract when transferring makerAsset. The leading bytes4 references the id of the asset proxy. + zrxOrder.takerAssetData, // Encoded data that can be decoded by a specified proxy contract when transferring takerAsset. The leading bytes4 references the id of the asset proxy. + zrxOrder.makerFeeAssetData, // Encoded data that can be decoded by a specified proxy contract when transferring makerFeeAsset. The leading bytes4 references the id of the asset proxy. + zrxOrder.takerFeeAssetData // Encoded data that can be decoded by a specified proxy contract when transferring takerFeeAsset. The leading bytes4 references the id of the asset proxy. +] // Fetch order status - const orderInfo = await zrxExchangeContract.methods.getOrderInfo(orderTuple).call() - - // Skip order if it's been partially filled - if(orderInfo.orderTakerAssetFilledAmount.toString() !== '0') { - console.log('Order partially filled') - return + // const orderInfo = await zrxExchangeContract.methods.getOrderInfo(orderTuple).call() + /* + struct OrderInfo { + uint8 orderStatus; // Status that describes order's validity and fillability. + bytes32 orderHash; // EIP712 hash of the order (see LibOrder.getOrderHash). + uint256 orderTakerAssetFilledAmount; // Amount of order that has already been filled. + } + */ + // TODO use this public mapping to see the order filled amount https://0x.org/docs/guides/v3-specification#filled + + // console.log(typeof inputAssetAmount) + // Skip if order has been partially filled + // if(orderInfo.orderTakerAssetFilledAmount.toString() !== '0') { + if(amountLeft != zrxOrder.takerAssetAmount){ + amountLeft = web3.utils.fromWei(metadata.remainingFillableTakerAssetAmount, 'ether') // typeof = string + if(amountLeft<0.01){ + console.log('SKIP order taker asset left less than 0.01') + return + } + // TODO inputamount needs to equal amountleft + // I'm HERE <<<========= + console.log('Order taker asset remaining: ' + amountLeft) } // Fetch 1Split Data @@ -183,10 +210,36 @@ async function checkArb(args) { // Calculate estimated gas cost let estimatedGasFee = process.env.ESTIMATED_GAS.toString() * web3.utils.toWei(process.env.GAS_PRICE.toString(), 'Gwei') - estimatedGasFee = web3.utils.fromWei(estimatedGasFee.toString(), 'Ether') - // Calculate net profit - let netProfit = outputAssetAmount - inputAssetAmount - estimatedGasFee + let netProfit + + if (zrxOrder.takerFee.toString() !== '0') { + // the fee's currency is the taker asset, i.e. DAI + // TODO make the net profit calculation look cleaner by assigning the results of if statements to constants + // e.g. the below line should look like: if(takerFeeAsset == ASSET_ADDRESSES[assetOrder[0]) + if ('0x'+zrxOrder.takerFeeAssetData.substring(34,74) == ASSET_ADDRESSES[assetOrder[0]]) { + console.log("Order has taker fees, payable in TAKER asset: " + assetOrder[0]) + // subtracting fee from net profit calculation + // the fee currency is usually the taker asset, i.e. it is the same currency as the other amounts in the netProfit calculation and can be subtracted as is + // however additional logic is needed to handle cases where the taker fee is in the maker currency, which would require converting the amount to the taker currency amount before subtracting it + netProfit = outputAssetAmount - inputAssetAmount - estimatedGasFee - zrxOrder.takerFee + } else if ('0x'+zrxOrder.takerFeeAssetData.substring(34,74) == ASSET_ADDRESSES[assetOrder[1]]) { + // could just be just an 'else', but better being explicit + console.log("Order has taker fees, payable in MAKER asset: " + assetOrder[1]) + return // TODO remove this and account for change of asset in net profit calculation + } else { + // this should never be the case, but better be safe. + // I.e. it is neither 0x nor taker or maker asset address + console.log("takerFeeAssetData not recognized: " + zrxOrder.takerFeeAssetData) + return + } + + } else { + // If order has no fees (if maker fees wer) + // Calculate net profit + netProfit = outputAssetAmount - inputAssetAmount - estimatedGasFee + } + netProfit = Math.floor(netProfit) // Round down // Determine if profitable @@ -194,7 +247,10 @@ async function checkArb(args) { // If profitable, then stop looking and trade! if(profitable) { + console.log(zrxOrder) // Skip if another profitable arb has already been found + // TODO remove this check, this value will be set to true never not back to false, meaning the bot will stop after the first arb + // doesnt even make much sense since if this is always true, the interval will always clear, why return here after the bot has already done many api calls... if(profitableArbFound) { return } @@ -217,7 +273,12 @@ async function checkArb(args) { playSound() // Call arb contract - await trade(assetOrder[0], ASSET_ADDRESSES[assetOrder[0]], ASSET_ADDRESSES[assetOrder[1]], zrxOrder, inputAssetAmount, oneSplitData) + // await trade(assetOrder[0], ASSET_ADDRESSES[assetOrder[0]], ASSET_ADDRESSES[assetOrder[1]], zrxOrder, inputAssetAmount, oneSplitData) + /* + TODO don't just settle for greater than 0 and then stop, rather finish going through all 0x orders and then chose the most profitable one to begin with! + TODO even better, rather than going through them sequentially like an idiot, why not sort the orders by the best exchange rate first!?!?!?! + TODO even better, why not go through all of them at once, then estimate how much is needed and get a flashloan for all the profitable orders, then arbitrage them all at once?? + */ } } @@ -225,12 +286,16 @@ async function checkArb(args) { // TRADE EXECUTION async function trade(flashTokenSymbol, flashTokenAddress, arbTokenAddress, orderJson, fillAmount, oneSplitData) { const FLASH_AMOUNT = toTokens('10000', flashTokenSymbol) // 10,000 WETH + // TODO the amount should be dynamic based on the 0x order! or take a bigger flashloan to arb more than 1 0x order at once! const FROM_TOKEN = flashTokenAddress // WETH const FROM_AMOUNT = fillAmount // '1000000' const TO_TOKEN = arbTokenAddress const ONE_SPLIT_SLIPPAGE = '0.995' + // TODO, make that slippage dynamic. Also, why is the slippage calculated in here and not subtracted earlier when calculating profitability? + + // TODO remove duplication const orderTuple = [ orderJson.makerAddress, orderJson.takerAddress, @@ -259,10 +324,10 @@ async function trade(flashTokenSymbol, flashTokenAddress, arbTokenAddress, order // Calculate slippage const minReturnWtihSplippage = minReturnWithSlippage = (new web3.utils.BN(minReturn)).mul(new web3.utils.BN('995')).div(new web3.utils.BN('1000')).toString() - // Perform Trade + Perform Trade receipt = await traderContract.methods.getFlashloan( flashTokenAddress, // address flashToken, - FLASH_AMOUNT, // uint256 flashAmount, + FLASH_AMOUNT, // uint256 flashAmount, arbTokenAddress, // address arbToken, data, // bytes calldata zrxData, minReturnWtihSplippage.toString(), // uint256 oneSplitMinReturn, @@ -281,12 +346,16 @@ async function trade(flashTokenSymbol, flashTokenAddress, arbTokenAddress, order async function checkOrderBook(baseAssetSymbol, quoteAssetSymbol) { const baseAssetAddress = ASSET_ADDRESSES[baseAssetSymbol].substring(2,42) const quoteAssetAddress = ASSET_ADDRESSES[quoteAssetSymbol].substring(2,42) + // https://api.0x.org/sra/v3/orders?page=1&perPage=1000&makerAssetProxyId=0xf47261b0&takerAssetProxyId=0xf47261b0&makerAssetAddress=0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2&takerAssetAddress=0x6b175474e89094c44da98b954eedeac495271d0f const zrxResponse = await axios.get(`https://api.0x.org/sra/v3/orderbook?baseAssetData=0xf47261b0000000000000000000000000${baseAssetAddress}"eAssetData=0xf47261b0000000000000000000000000${quoteAssetAddress}&perPage=1000`) + // TODO (optional) refactor, i.e. more readable by assigning the url params to variables for example const zrxData = zrxResponse.data const bids = zrxData.bids.records bids.map((o) => { - checkArb({ zrxOrder: o.order, assetOrder: [baseAssetSymbol, quoteAssetSymbol, baseAssetSymbol] }) // E.G. WETH, DAI, WETH + checkArb({ zrxOrder: o.order, + metadata: o.metaData, + assetOrder: [baseAssetSymbol, quoteAssetSymbol, baseAssetSymbol] }) // E.G. WETH, DAI, WETH }) } @@ -297,7 +366,16 @@ async function checkMarkets() { return } + // TODO add strategies + /* + Could I use 1inch in order to do arbs between kyber and uniswap? + Kyber, uniswap.... + e.g. instead of doing 0x to 1inch you need to do from exchange a to exchange b. + defiprime.com/exchanges + */ + // Stop checking markets if already found + // TODO remove this if(profitableArbFound) { clearInterval(marketChecker) } @@ -306,6 +384,11 @@ async function checkMarkets() { checkingMarkets = true try { await checkOrderBook(WETH, DAI) + await checkOrderBook(DAI, WETH) + await checkOrderBook(SAI, WETH) + await checkOrderBook(WETH, SAI) + await checkOrderBook(USDC, WETH) + await checkOrderBook(WETH, USDC) } catch (error) { console.error(error) checkingMarkets = false