├── .env.example ├── .gitignore ├── README.md ├── ding.mp3 ├── migrations └── 1_initial_migration.js ├── package-lock.json ├── package.json ├── src ├── abis │ ├── ArbExample.json │ ├── DyDxFlashLoan.json │ ├── DyDxPool.json │ ├── ERC20.json │ ├── Exchange1.json │ ├── Exchange2.json │ ├── Flashloan.json │ ├── IERC20.json │ ├── IKyber.json │ ├── Migrations.json │ ├── SimpleArb.json │ ├── Structs.json │ ├── Trader.json │ └── UniswapExchangeInterface.json ├── contracts │ ├── Flashloan.sol │ ├── Migrations.sol │ ├── TradingBot.sol │ └── examples │ │ ├── ArbExample.sol │ │ ├── Flashloan.sol │ │ ├── LeveragedYieldFarm.sol │ │ ├── SimpleArb.sol │ │ └── traderExample.sol ├── examples │ ├── checkPair.js │ ├── coins │ │ └── erc20.js │ ├── exchange │ │ ├── kyber.js │ │ ├── uniswap.js │ │ └── uniswapv2.js │ ├── price-bot.js │ └── trading-bot.js └── index.js └── truffle-config.js /.env.example: -------------------------------------------------------------------------------- 1 | RPC_URL="https://mainnet.infura.io/v3/YOUR_API_KEY_HERE" 2 | ADDRESS="0x..." 3 | PRIVATE_KEY="0x..." 4 | CONTRACT_ADDRESS="0x..." 5 | GAS_LIMIT=3000000 6 | GAS_PRICE=200 7 | ESTIMATED_GAS=1700000 8 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | npm_modules 2 | npm_modules/* 3 | node_modules 4 | node_modules/* 5 | .env 6 | logs/* 7 | logs 8 | # examples/* 9 | # examples 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # DEPRECATED 2 | 3 | This bot is not actively maintained and has not been updated to work with newer versions of 1inch and DyDx. We encourage the community to create merge requests for any upgrades. 4 | 5 | Extropy remains available for any smart contract development or audit work, please contact info@extropy.io or visit https://extropy.io/ . 6 | 7 | ### Citation 8 | 9 | * 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 10 | * 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. 11 | 12 | ### Link 13 | 14 | * Part 1: https://extropy-io.medium.com/coding-a-defi-arbitrage-bot-45e550d85089 15 | * Part 2: https://extropy-io.medium.com/arbitrage-bot-part-2-97e7b710dcf 16 | 17 | ### Core Research Question 18 | 19 | * How can an arbitrage between DEXes be automatically performed using flash loans? 20 | 21 | ### Background 22 | 23 | * **Arbitrage** is the purchase and sale of an asset in order to profit from a difference in the asset’s price between marketplaces. 24 | * **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. 25 | * **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. 26 | * 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)”. 27 | * **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. 28 | * **DEX aggregators** source liquidity from different DEXs and thus offer users better token swap rates than any single DEX. 29 | * **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. 30 | * 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. 31 | * 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. 32 | * **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. 33 | 34 | ### Summary 35 | 36 | * The author describes the advantages of DeFi arbitrage over centralized exchanges: 37 | * DeFi 38 | * Insolvency risks are minimized as smart contracts execute automatically following predetermined parameters. A trade will be reverted if it cannot be executed as expected. 39 | * Traders can perform arbitrage using borrowed funds with flash loans. 40 | * Centralized exchanges 41 | * Since a trader cannot execute trades simultaneously, they may suffer from price slippage if a trade is delayed. 42 | * Traders need to own funds or borrow them from a bank. 43 | * Arbitrage between DEXes that use AMM 44 | * Popular platforms 45 | * [Kyber Network](https://kyber.network/), [Uniswap](https://uniswap.org/), [Balancer](https://balancer.finance/), and [Curve Finance](https://curve.fi/). 46 | * Result 47 | * Bring prices into efficiency between two liquidity pools 48 | * Scenario 49 | * When the pools on different DEXes offer different prices to exchange assets. 50 | * Execution 51 | * 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. 52 | * Arbitrage between DEXes that use classic orderbook 53 | * Popular platforms 54 | * [Radar Relay](https://relay.radar.tech/), powered by the [0x protocol](https://0x.org). 55 | * Scenario 56 | * 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. 57 | * The author describes the basic operation of an arbitrage bot. 58 | * For example, to arbitrage the pair WETH/DAI: 59 | * The bot will query the [0x API](https://0x.org/api) looking for WETH/DAI pair limit orders 60 | * The 0x API can get the limit orders for a currency pair from every exchange that uses the 0x protocol. 61 | * 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 62 | * Parameters 63 | * the maker token’s contract address (WETH) 64 | * the taker token’s contract address (DAI) 65 | * Sample response from the above request 66 | * ![|512x215](upload://4yiFnlZcJCpmtPuCTK22fGOjV45.jpeg) 67 | * 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. 68 | * The author thus proposes to perform the arbitrage using a flash loan. 69 | * Steps 70 | * Get a flash loan DAI from [DyDx exchange](https://dydx.exchange/) 71 | * Buy WETH from 0x using the DAI borrowed with the flash loan 72 | * Use 1inch to find the best exchange to sell the WETH acquired in step 2 73 | * Pay back the flash loan DAI and keep the remainder as profit 74 | * The author provides the code and explains how the arbitrage smart contract works 75 | * The contract inherits the DyDxFloashLoan smart contract. 76 | * Functions 77 | * Swap 78 | * Perform the trade 79 | * getExpectedReturn 80 | * Get the current asset price 81 | * getWeth 82 | * Turn any ETH sent into WETH 83 | * approveWeth 84 | * Approve the 0x proxy to collect WETH as the fee for using 0x. 85 | * getFlashloan 86 | * Called whenever a profitable arbitrage is found by the client. 87 | * All of the parameters for this function will be constructed and passed in from the client script. 88 | * callFunction 89 | * Has to be deployed in our smart contract to receive a flash loan from dYdX. 90 | * _arb 91 | * Arbitrage function that is called when the loan is successful. 92 | * Tracks the balance of our smart contract before and after the trade. 93 | * If the end balance is not greater than the start balance, the operation will revert. 94 | 95 | ### Method 96 | 97 | * The prerequisite for the arbitrage bot is to have a browser with the Metamask extension installed. 98 | * The programming language used is NodeJS. 99 | * Project structure: only two files 100 | * index.js 101 | * 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. 102 | * TradingBot.sol 103 | * 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. 104 | * Detailed setup 105 | * Install Metamask browser extension 106 | * Create an Ethereum Mainnet account with some ETH for paying gas fees. 107 | * Don’t use your personal account for this, create a new account for the bot in order to limit accidental losses. 108 | * Go to [Remix online IDE](https://remix.ethereum.org/) and paste the smart contract solidity code 109 | * Compile the code using compiler version 0.5.17. 110 | * Deploy with an initial 100 wei, which is enough for 100 flash loans on dYdX. 111 | * Environment setup 112 | * By cloning the project’s code repository, users will find a file called .env.example inside the /src folder 113 | * Fill in the fields: 114 | * 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. 115 | * ADDRESS and PRIVATE_KEY: fill in the public Ethereum address of the bot account, and its corresponding private key. 116 | * CONTRACT_ADDRESS : paste in the smart contract’s address that was returned from Remix after the deployment step. 117 | * GAS_LIMIT : how much gas the contract is allowed to use, leave as 3000000 or decrease to 2000000 which should be fine 118 | * GAS_PRICE : change this depending on how fast you want the transaction to be mined, see https://ethgasstation.info/ for info. 119 | * ESTIMATED_GAS : leave as is 120 | * Running the bot 121 | * Execute the command from the project’s root directory. 122 | * node src/index.js 123 | 124 | ### Results 125 | 126 | * The full working code can be found at https://github.com/ExtropyIO/defi-bot. 127 | 128 | ### Discussion & Key Takeaways 129 | 130 | * 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. 131 | * The author explains the main components of the arbitrage bot and the underlying logic of how arbitrage works. 132 | * After following this tutorial, users can create a working example of a customizable flash loan arbitrage bot. 133 | * 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. 134 | 135 | ### Implications & Follow-ups 136 | 137 | * 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. 138 | * To make the bot more efficient, the author suggests the following improvements: 139 | * Consider taker fees when calculating profits 140 | * Use Partial fills 141 | * Check orders again 142 | * Handle failures to continue execution 143 | * Execute multiple orders simultaneously 144 | * Dynamically calculate gas fees 145 | * 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. 146 | * It may be interesting to measure the actual APR of running this bot, considering the cost of server hosting and contract deployment. 147 | 148 | ### Applicability 149 | 150 | * Interested readers can refer to the working code to have their own arbitrage bot: https://github.com/ExtropyIO/defi-bot. 151 | * Currently, the example only supports flash loans from dYdX. Users can add other flash loan provider’s support. 152 | -------------------------------------------------------------------------------- /ding.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ExtropyIO/defi-bot/007fdfa93af152bd14f7477d7a374816d67f8899/ding.mp3 -------------------------------------------------------------------------------- /migrations/1_initial_migration.js: -------------------------------------------------------------------------------- 1 | const Migrations = artifacts.require("Migrations"); 2 | 3 | module.exports = function(deployer) { 4 | deployer.deploy(Migrations); 5 | }; 6 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "defi", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "start": "react-scripts start", 8 | "build": "react-scripts build", 9 | "test": "react-scripts test", 10 | "eject": "react-scripts eject" 11 | }, 12 | "dependencies": { 13 | "@0x/order-utils": "^10.2.4", 14 | "@truffle/hdwallet-provider": "^1.0.35", 15 | "@uniswap/sdk": "^3.0.3", 16 | "axios": "^0.19.2", 17 | "console.table": "^0.10.0", 18 | "cors": "", 19 | "dotenv": "^8.2.0", 20 | "ejs": "^2.5.6", 21 | "express": "^4.15.2", 22 | "infura-web3-provider": "0.0.3", 23 | "lodash": "^4.17.15", 24 | "moment": "^2.25.3", 25 | "moment-timezone": "^0.5.28", 26 | "numeral": "^2.0.6", 27 | "play-sound": "", 28 | "sha3": "2.0.2", 29 | "web3": "", 30 | "web3-provider-engine": "" 31 | }, 32 | "devDependencies": { 33 | "request": "^2.81.0" 34 | }, 35 | "repository": { 36 | "type": "git", 37 | "url": "git+https://gitlab.com/codemedici/defi.git" 38 | }, 39 | "author": "", 40 | "license": "ISC", 41 | "bugs": { 42 | "url": "https://gitlab.com/codemedici/defi/issues" 43 | }, 44 | "homepage": "https://gitlab.com/codemedici/defi#readme" 45 | } 46 | -------------------------------------------------------------------------------- /src/abis/Migrations.json: -------------------------------------------------------------------------------- 1 | { 2 | "contractName": "Migrations", 3 | "abi": [ 4 | { 5 | "inputs": [], 6 | "payable": false, 7 | "stateMutability": "nonpayable", 8 | "type": "constructor" 9 | }, 10 | { 11 | "constant": true, 12 | "inputs": [], 13 | "name": "last_completed_migration", 14 | "outputs": [ 15 | { 16 | "internalType": "uint256", 17 | "name": "", 18 | "type": "uint256" 19 | } 20 | ], 21 | "payable": false, 22 | "stateMutability": "view", 23 | "type": "function" 24 | }, 25 | { 26 | "constant": true, 27 | "inputs": [], 28 | "name": "owner", 29 | "outputs": [ 30 | { 31 | "internalType": "address", 32 | "name": "", 33 | "type": "address" 34 | } 35 | ], 36 | "payable": false, 37 | "stateMutability": "view", 38 | "type": "function" 39 | }, 40 | { 41 | "constant": false, 42 | "inputs": [ 43 | { 44 | "internalType": "uint256", 45 | "name": "completed", 46 | "type": "uint256" 47 | } 48 | ], 49 | "name": "setCompleted", 50 | "outputs": [], 51 | "payable": false, 52 | "stateMutability": "nonpayable", 53 | "type": "function" 54 | } 55 | ], 56 | "metadata": "{\"compiler\":{\"version\":\"0.5.12+commit.7709ece9\"},\"language\":\"Solidity\",\"output\":{\"abi\":[{\"inputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"constant\":true,\"inputs\":[],\"name\":\"last_completed_migration\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"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\":[{\"internalType\":\"uint256\",\"name\":\"completed\",\"type\":\"uint256\"}],\"name\":\"setCompleted\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"}],\"devdoc\":{\"methods\":{}},\"userdoc\":{\"methods\":{}}},\"settings\":{\"compilationTarget\":{\"/Users/codeme/code/defi/src/contracts/Migrations.sol\":\"Migrations\"},\"evmVersion\":\"petersburg\",\"libraries\":{},\"optimizer\":{\"enabled\":false,\"runs\":200},\"remappings\":[]},\"sources\":{\"/Users/codeme/code/defi/src/contracts/Migrations.sol\":{\"keccak256\":\"0xe04e2fb3d39d415aa4d2368410c1e9f8937cdda2d99d2e412cb45b9d13ce9ec8\",\"urls\":[\"bzz-raw://db986a7934528a7c97f35685a115dbc98af9c3f56ea049deab5c47e8543804a1\",\"dweb:/ipfs/QmRACrDoyh9BMeGP5nr1f9k7bbHsLnQ85HiVKvbPGx9KFW\"]}},\"version\":1}", 57 | "bytecode": "0x608060405234801561001057600080fd5b50336000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555061019c806100606000396000f3fe608060405234801561001057600080fd5b50600436106100415760003560e01c8063445df0ac146100465780638da5cb5b14610064578063fdacd576146100ae575b600080fd5b61004e6100dc565b6040518082815260200191505060405180910390f35b61006c6100e2565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b6100da600480360360208110156100c457600080fd5b8101908080359060200190929190505050610107565b005b60015481565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff16141561016457806001819055505b5056fea265627a7a7231582026249518824ac96264074f8edee5a8825950b5039e2cbe06737e46a1d05afac264736f6c634300050c0032", 58 | "deployedBytecode": "0x608060405234801561001057600080fd5b50600436106100415760003560e01c8063445df0ac146100465780638da5cb5b14610064578063fdacd576146100ae575b600080fd5b61004e6100dc565b6040518082815260200191505060405180910390f35b61006c6100e2565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b6100da600480360360208110156100c457600080fd5b8101908080359060200190929190505050610107565b005b60015481565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff16141561016457806001819055505b5056fea265627a7a7231582026249518824ac96264074f8edee5a8825950b5039e2cbe06737e46a1d05afac264736f6c634300050c0032", 59 | "sourceMap": "66:311:2:-;;;155:50;8:9:-1;5:2;;;30:1;27;20:12;5:2;155:50:2;190:10;182:5;;:18;;;;;;;;;;;;;;;;;;66:311;;;;;;", 60 | "deployedSourceMap": "66:311:2:-;;;;8:9:-1;5:2;;;30:1;27;20:12;5:2;66:311:2;;;;;;;;;;;;;;;;;;;;;;;;;;;;;114:36;;;:::i;:::-;;;;;;;;;;;;;;;;;;;90:20;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;272:103;;;;;;13:2:-1;8:3;5:11;2:2;;;29:1;26;19:12;2:2;272:103:2;;;;;;;;;;;;;;;;;:::i;:::-;;114:36;;;;:::o;90:20::-;;;;;;;;;;;;;:::o;272:103::-;255:5;;;;;;;;;;;241:19;;:10;:19;;;237:26;;;361:9;334:24;:36;;;;237:26;272:103;:::o", 61 | "source": "// SPDX-License-Identifier: MIT\npragma solidity >=0.4.21 <0.7.0;\n\ncontract Migrations {\n address public owner;\n uint public last_completed_migration;\n\n constructor() public {\n owner = msg.sender;\n }\n\n modifier restricted() {\n if (msg.sender == owner) _;\n }\n\n function setCompleted(uint completed) public restricted {\n last_completed_migration = completed;\n }\n}\n", 62 | "sourcePath": "/Users/codeme/code/defi/src/contracts/Migrations.sol", 63 | "ast": { 64 | "absolutePath": "/Users/codeme/code/defi/src/contracts/Migrations.sol", 65 | "exportedSymbols": { 66 | "Migrations": [ 67 | 1715 68 | ] 69 | }, 70 | "id": 1716, 71 | "nodeType": "SourceUnit", 72 | "nodes": [ 73 | { 74 | "id": 1680, 75 | "literals": [ 76 | "solidity", 77 | ">=", 78 | "0.4", 79 | ".21", 80 | "<", 81 | "0.7", 82 | ".0" 83 | ], 84 | "nodeType": "PragmaDirective", 85 | "src": "32:32:2" 86 | }, 87 | { 88 | "baseContracts": [], 89 | "contractDependencies": [], 90 | "contractKind": "contract", 91 | "documentation": null, 92 | "fullyImplemented": true, 93 | "id": 1715, 94 | "linearizedBaseContracts": [ 95 | 1715 96 | ], 97 | "name": "Migrations", 98 | "nodeType": "ContractDefinition", 99 | "nodes": [ 100 | { 101 | "constant": false, 102 | "id": 1682, 103 | "name": "owner", 104 | "nodeType": "VariableDeclaration", 105 | "scope": 1715, 106 | "src": "90:20:2", 107 | "stateVariable": true, 108 | "storageLocation": "default", 109 | "typeDescriptions": { 110 | "typeIdentifier": "t_address", 111 | "typeString": "address" 112 | }, 113 | "typeName": { 114 | "id": 1681, 115 | "name": "address", 116 | "nodeType": "ElementaryTypeName", 117 | "src": "90:7:2", 118 | "stateMutability": "nonpayable", 119 | "typeDescriptions": { 120 | "typeIdentifier": "t_address", 121 | "typeString": "address" 122 | } 123 | }, 124 | "value": null, 125 | "visibility": "public" 126 | }, 127 | { 128 | "constant": false, 129 | "id": 1684, 130 | "name": "last_completed_migration", 131 | "nodeType": "VariableDeclaration", 132 | "scope": 1715, 133 | "src": "114:36:2", 134 | "stateVariable": true, 135 | "storageLocation": "default", 136 | "typeDescriptions": { 137 | "typeIdentifier": "t_uint256", 138 | "typeString": "uint256" 139 | }, 140 | "typeName": { 141 | "id": 1683, 142 | "name": "uint", 143 | "nodeType": "ElementaryTypeName", 144 | "src": "114:4:2", 145 | "typeDescriptions": { 146 | "typeIdentifier": "t_uint256", 147 | "typeString": "uint256" 148 | } 149 | }, 150 | "value": null, 151 | "visibility": "public" 152 | }, 153 | { 154 | "body": { 155 | "id": 1692, 156 | "nodeType": "Block", 157 | "src": "176:29:2", 158 | "statements": [ 159 | { 160 | "expression": { 161 | "argumentTypes": null, 162 | "id": 1690, 163 | "isConstant": false, 164 | "isLValue": false, 165 | "isPure": false, 166 | "lValueRequested": false, 167 | "leftHandSide": { 168 | "argumentTypes": null, 169 | "id": 1687, 170 | "name": "owner", 171 | "nodeType": "Identifier", 172 | "overloadedDeclarations": [], 173 | "referencedDeclaration": 1682, 174 | "src": "182:5:2", 175 | "typeDescriptions": { 176 | "typeIdentifier": "t_address", 177 | "typeString": "address" 178 | } 179 | }, 180 | "nodeType": "Assignment", 181 | "operator": "=", 182 | "rightHandSide": { 183 | "argumentTypes": null, 184 | "expression": { 185 | "argumentTypes": null, 186 | "id": 1688, 187 | "name": "msg", 188 | "nodeType": "Identifier", 189 | "overloadedDeclarations": [], 190 | "referencedDeclaration": 1815, 191 | "src": "190:3:2", 192 | "typeDescriptions": { 193 | "typeIdentifier": "t_magic_message", 194 | "typeString": "msg" 195 | } 196 | }, 197 | "id": 1689, 198 | "isConstant": false, 199 | "isLValue": false, 200 | "isPure": false, 201 | "lValueRequested": false, 202 | "memberName": "sender", 203 | "nodeType": "MemberAccess", 204 | "referencedDeclaration": null, 205 | "src": "190:10:2", 206 | "typeDescriptions": { 207 | "typeIdentifier": "t_address_payable", 208 | "typeString": "address payable" 209 | } 210 | }, 211 | "src": "182:18:2", 212 | "typeDescriptions": { 213 | "typeIdentifier": "t_address", 214 | "typeString": "address" 215 | } 216 | }, 217 | "id": 1691, 218 | "nodeType": "ExpressionStatement", 219 | "src": "182:18:2" 220 | } 221 | ] 222 | }, 223 | "documentation": null, 224 | "id": 1693, 225 | "implemented": true, 226 | "kind": "constructor", 227 | "modifiers": [], 228 | "name": "", 229 | "nodeType": "FunctionDefinition", 230 | "parameters": { 231 | "id": 1685, 232 | "nodeType": "ParameterList", 233 | "parameters": [], 234 | "src": "166:2:2" 235 | }, 236 | "returnParameters": { 237 | "id": 1686, 238 | "nodeType": "ParameterList", 239 | "parameters": [], 240 | "src": "176:0:2" 241 | }, 242 | "scope": 1715, 243 | "src": "155:50:2", 244 | "stateMutability": "nonpayable", 245 | "superFunction": null, 246 | "visibility": "public" 247 | }, 248 | { 249 | "body": { 250 | "id": 1701, 251 | "nodeType": "Block", 252 | "src": "231:37:2", 253 | "statements": [ 254 | { 255 | "condition": { 256 | "argumentTypes": null, 257 | "commonType": { 258 | "typeIdentifier": "t_address", 259 | "typeString": "address" 260 | }, 261 | "id": 1698, 262 | "isConstant": false, 263 | "isLValue": false, 264 | "isPure": false, 265 | "lValueRequested": false, 266 | "leftExpression": { 267 | "argumentTypes": null, 268 | "expression": { 269 | "argumentTypes": null, 270 | "id": 1695, 271 | "name": "msg", 272 | "nodeType": "Identifier", 273 | "overloadedDeclarations": [], 274 | "referencedDeclaration": 1815, 275 | "src": "241:3:2", 276 | "typeDescriptions": { 277 | "typeIdentifier": "t_magic_message", 278 | "typeString": "msg" 279 | } 280 | }, 281 | "id": 1696, 282 | "isConstant": false, 283 | "isLValue": false, 284 | "isPure": false, 285 | "lValueRequested": false, 286 | "memberName": "sender", 287 | "nodeType": "MemberAccess", 288 | "referencedDeclaration": null, 289 | "src": "241:10:2", 290 | "typeDescriptions": { 291 | "typeIdentifier": "t_address_payable", 292 | "typeString": "address payable" 293 | } 294 | }, 295 | "nodeType": "BinaryOperation", 296 | "operator": "==", 297 | "rightExpression": { 298 | "argumentTypes": null, 299 | "id": 1697, 300 | "name": "owner", 301 | "nodeType": "Identifier", 302 | "overloadedDeclarations": [], 303 | "referencedDeclaration": 1682, 304 | "src": "255:5:2", 305 | "typeDescriptions": { 306 | "typeIdentifier": "t_address", 307 | "typeString": "address" 308 | } 309 | }, 310 | "src": "241:19:2", 311 | "typeDescriptions": { 312 | "typeIdentifier": "t_bool", 313 | "typeString": "bool" 314 | } 315 | }, 316 | "falseBody": null, 317 | "id": 1700, 318 | "nodeType": "IfStatement", 319 | "src": "237:26:2", 320 | "trueBody": { 321 | "id": 1699, 322 | "nodeType": "PlaceholderStatement", 323 | "src": "262:1:2" 324 | } 325 | } 326 | ] 327 | }, 328 | "documentation": null, 329 | "id": 1702, 330 | "name": "restricted", 331 | "nodeType": "ModifierDefinition", 332 | "parameters": { 333 | "id": 1694, 334 | "nodeType": "ParameterList", 335 | "parameters": [], 336 | "src": "228:2:2" 337 | }, 338 | "src": "209:59:2", 339 | "visibility": "internal" 340 | }, 341 | { 342 | "body": { 343 | "id": 1713, 344 | "nodeType": "Block", 345 | "src": "328:47:2", 346 | "statements": [ 347 | { 348 | "expression": { 349 | "argumentTypes": null, 350 | "id": 1711, 351 | "isConstant": false, 352 | "isLValue": false, 353 | "isPure": false, 354 | "lValueRequested": false, 355 | "leftHandSide": { 356 | "argumentTypes": null, 357 | "id": 1709, 358 | "name": "last_completed_migration", 359 | "nodeType": "Identifier", 360 | "overloadedDeclarations": [], 361 | "referencedDeclaration": 1684, 362 | "src": "334:24:2", 363 | "typeDescriptions": { 364 | "typeIdentifier": "t_uint256", 365 | "typeString": "uint256" 366 | } 367 | }, 368 | "nodeType": "Assignment", 369 | "operator": "=", 370 | "rightHandSide": { 371 | "argumentTypes": null, 372 | "id": 1710, 373 | "name": "completed", 374 | "nodeType": "Identifier", 375 | "overloadedDeclarations": [], 376 | "referencedDeclaration": 1704, 377 | "src": "361:9:2", 378 | "typeDescriptions": { 379 | "typeIdentifier": "t_uint256", 380 | "typeString": "uint256" 381 | } 382 | }, 383 | "src": "334:36:2", 384 | "typeDescriptions": { 385 | "typeIdentifier": "t_uint256", 386 | "typeString": "uint256" 387 | } 388 | }, 389 | "id": 1712, 390 | "nodeType": "ExpressionStatement", 391 | "src": "334:36:2" 392 | } 393 | ] 394 | }, 395 | "documentation": null, 396 | "id": 1714, 397 | "implemented": true, 398 | "kind": "function", 399 | "modifiers": [ 400 | { 401 | "arguments": null, 402 | "id": 1707, 403 | "modifierName": { 404 | "argumentTypes": null, 405 | "id": 1706, 406 | "name": "restricted", 407 | "nodeType": "Identifier", 408 | "overloadedDeclarations": [], 409 | "referencedDeclaration": 1702, 410 | "src": "317:10:2", 411 | "typeDescriptions": { 412 | "typeIdentifier": "t_modifier$__$", 413 | "typeString": "modifier ()" 414 | } 415 | }, 416 | "nodeType": "ModifierInvocation", 417 | "src": "317:10:2" 418 | } 419 | ], 420 | "name": "setCompleted", 421 | "nodeType": "FunctionDefinition", 422 | "parameters": { 423 | "id": 1705, 424 | "nodeType": "ParameterList", 425 | "parameters": [ 426 | { 427 | "constant": false, 428 | "id": 1704, 429 | "name": "completed", 430 | "nodeType": "VariableDeclaration", 431 | "scope": 1714, 432 | "src": "294:14:2", 433 | "stateVariable": false, 434 | "storageLocation": "default", 435 | "typeDescriptions": { 436 | "typeIdentifier": "t_uint256", 437 | "typeString": "uint256" 438 | }, 439 | "typeName": { 440 | "id": 1703, 441 | "name": "uint", 442 | "nodeType": "ElementaryTypeName", 443 | "src": "294:4:2", 444 | "typeDescriptions": { 445 | "typeIdentifier": "t_uint256", 446 | "typeString": "uint256" 447 | } 448 | }, 449 | "value": null, 450 | "visibility": "internal" 451 | } 452 | ], 453 | "src": "293:16:2" 454 | }, 455 | "returnParameters": { 456 | "id": 1708, 457 | "nodeType": "ParameterList", 458 | "parameters": [], 459 | "src": "328:0:2" 460 | }, 461 | "scope": 1715, 462 | "src": "272:103:2", 463 | "stateMutability": "nonpayable", 464 | "superFunction": null, 465 | "visibility": "public" 466 | } 467 | ], 468 | "scope": 1716, 469 | "src": "66:311:2" 470 | } 471 | ], 472 | "src": "32:346:2" 473 | }, 474 | "legacyAST": { 475 | "absolutePath": "/Users/codeme/code/defi/src/contracts/Migrations.sol", 476 | "exportedSymbols": { 477 | "Migrations": [ 478 | 1715 479 | ] 480 | }, 481 | "id": 1716, 482 | "nodeType": "SourceUnit", 483 | "nodes": [ 484 | { 485 | "id": 1680, 486 | "literals": [ 487 | "solidity", 488 | ">=", 489 | "0.4", 490 | ".21", 491 | "<", 492 | "0.7", 493 | ".0" 494 | ], 495 | "nodeType": "PragmaDirective", 496 | "src": "32:32:2" 497 | }, 498 | { 499 | "baseContracts": [], 500 | "contractDependencies": [], 501 | "contractKind": "contract", 502 | "documentation": null, 503 | "fullyImplemented": true, 504 | "id": 1715, 505 | "linearizedBaseContracts": [ 506 | 1715 507 | ], 508 | "name": "Migrations", 509 | "nodeType": "ContractDefinition", 510 | "nodes": [ 511 | { 512 | "constant": false, 513 | "id": 1682, 514 | "name": "owner", 515 | "nodeType": "VariableDeclaration", 516 | "scope": 1715, 517 | "src": "90:20:2", 518 | "stateVariable": true, 519 | "storageLocation": "default", 520 | "typeDescriptions": { 521 | "typeIdentifier": "t_address", 522 | "typeString": "address" 523 | }, 524 | "typeName": { 525 | "id": 1681, 526 | "name": "address", 527 | "nodeType": "ElementaryTypeName", 528 | "src": "90:7:2", 529 | "stateMutability": "nonpayable", 530 | "typeDescriptions": { 531 | "typeIdentifier": "t_address", 532 | "typeString": "address" 533 | } 534 | }, 535 | "value": null, 536 | "visibility": "public" 537 | }, 538 | { 539 | "constant": false, 540 | "id": 1684, 541 | "name": "last_completed_migration", 542 | "nodeType": "VariableDeclaration", 543 | "scope": 1715, 544 | "src": "114:36:2", 545 | "stateVariable": true, 546 | "storageLocation": "default", 547 | "typeDescriptions": { 548 | "typeIdentifier": "t_uint256", 549 | "typeString": "uint256" 550 | }, 551 | "typeName": { 552 | "id": 1683, 553 | "name": "uint", 554 | "nodeType": "ElementaryTypeName", 555 | "src": "114:4:2", 556 | "typeDescriptions": { 557 | "typeIdentifier": "t_uint256", 558 | "typeString": "uint256" 559 | } 560 | }, 561 | "value": null, 562 | "visibility": "public" 563 | }, 564 | { 565 | "body": { 566 | "id": 1692, 567 | "nodeType": "Block", 568 | "src": "176:29:2", 569 | "statements": [ 570 | { 571 | "expression": { 572 | "argumentTypes": null, 573 | "id": 1690, 574 | "isConstant": false, 575 | "isLValue": false, 576 | "isPure": false, 577 | "lValueRequested": false, 578 | "leftHandSide": { 579 | "argumentTypes": null, 580 | "id": 1687, 581 | "name": "owner", 582 | "nodeType": "Identifier", 583 | "overloadedDeclarations": [], 584 | "referencedDeclaration": 1682, 585 | "src": "182:5:2", 586 | "typeDescriptions": { 587 | "typeIdentifier": "t_address", 588 | "typeString": "address" 589 | } 590 | }, 591 | "nodeType": "Assignment", 592 | "operator": "=", 593 | "rightHandSide": { 594 | "argumentTypes": null, 595 | "expression": { 596 | "argumentTypes": null, 597 | "id": 1688, 598 | "name": "msg", 599 | "nodeType": "Identifier", 600 | "overloadedDeclarations": [], 601 | "referencedDeclaration": 1815, 602 | "src": "190:3:2", 603 | "typeDescriptions": { 604 | "typeIdentifier": "t_magic_message", 605 | "typeString": "msg" 606 | } 607 | }, 608 | "id": 1689, 609 | "isConstant": false, 610 | "isLValue": false, 611 | "isPure": false, 612 | "lValueRequested": false, 613 | "memberName": "sender", 614 | "nodeType": "MemberAccess", 615 | "referencedDeclaration": null, 616 | "src": "190:10:2", 617 | "typeDescriptions": { 618 | "typeIdentifier": "t_address_payable", 619 | "typeString": "address payable" 620 | } 621 | }, 622 | "src": "182:18:2", 623 | "typeDescriptions": { 624 | "typeIdentifier": "t_address", 625 | "typeString": "address" 626 | } 627 | }, 628 | "id": 1691, 629 | "nodeType": "ExpressionStatement", 630 | "src": "182:18:2" 631 | } 632 | ] 633 | }, 634 | "documentation": null, 635 | "id": 1693, 636 | "implemented": true, 637 | "kind": "constructor", 638 | "modifiers": [], 639 | "name": "", 640 | "nodeType": "FunctionDefinition", 641 | "parameters": { 642 | "id": 1685, 643 | "nodeType": "ParameterList", 644 | "parameters": [], 645 | "src": "166:2:2" 646 | }, 647 | "returnParameters": { 648 | "id": 1686, 649 | "nodeType": "ParameterList", 650 | "parameters": [], 651 | "src": "176:0:2" 652 | }, 653 | "scope": 1715, 654 | "src": "155:50:2", 655 | "stateMutability": "nonpayable", 656 | "superFunction": null, 657 | "visibility": "public" 658 | }, 659 | { 660 | "body": { 661 | "id": 1701, 662 | "nodeType": "Block", 663 | "src": "231:37:2", 664 | "statements": [ 665 | { 666 | "condition": { 667 | "argumentTypes": null, 668 | "commonType": { 669 | "typeIdentifier": "t_address", 670 | "typeString": "address" 671 | }, 672 | "id": 1698, 673 | "isConstant": false, 674 | "isLValue": false, 675 | "isPure": false, 676 | "lValueRequested": false, 677 | "leftExpression": { 678 | "argumentTypes": null, 679 | "expression": { 680 | "argumentTypes": null, 681 | "id": 1695, 682 | "name": "msg", 683 | "nodeType": "Identifier", 684 | "overloadedDeclarations": [], 685 | "referencedDeclaration": 1815, 686 | "src": "241:3:2", 687 | "typeDescriptions": { 688 | "typeIdentifier": "t_magic_message", 689 | "typeString": "msg" 690 | } 691 | }, 692 | "id": 1696, 693 | "isConstant": false, 694 | "isLValue": false, 695 | "isPure": false, 696 | "lValueRequested": false, 697 | "memberName": "sender", 698 | "nodeType": "MemberAccess", 699 | "referencedDeclaration": null, 700 | "src": "241:10:2", 701 | "typeDescriptions": { 702 | "typeIdentifier": "t_address_payable", 703 | "typeString": "address payable" 704 | } 705 | }, 706 | "nodeType": "BinaryOperation", 707 | "operator": "==", 708 | "rightExpression": { 709 | "argumentTypes": null, 710 | "id": 1697, 711 | "name": "owner", 712 | "nodeType": "Identifier", 713 | "overloadedDeclarations": [], 714 | "referencedDeclaration": 1682, 715 | "src": "255:5:2", 716 | "typeDescriptions": { 717 | "typeIdentifier": "t_address", 718 | "typeString": "address" 719 | } 720 | }, 721 | "src": "241:19:2", 722 | "typeDescriptions": { 723 | "typeIdentifier": "t_bool", 724 | "typeString": "bool" 725 | } 726 | }, 727 | "falseBody": null, 728 | "id": 1700, 729 | "nodeType": "IfStatement", 730 | "src": "237:26:2", 731 | "trueBody": { 732 | "id": 1699, 733 | "nodeType": "PlaceholderStatement", 734 | "src": "262:1:2" 735 | } 736 | } 737 | ] 738 | }, 739 | "documentation": null, 740 | "id": 1702, 741 | "name": "restricted", 742 | "nodeType": "ModifierDefinition", 743 | "parameters": { 744 | "id": 1694, 745 | "nodeType": "ParameterList", 746 | "parameters": [], 747 | "src": "228:2:2" 748 | }, 749 | "src": "209:59:2", 750 | "visibility": "internal" 751 | }, 752 | { 753 | "body": { 754 | "id": 1713, 755 | "nodeType": "Block", 756 | "src": "328:47:2", 757 | "statements": [ 758 | { 759 | "expression": { 760 | "argumentTypes": null, 761 | "id": 1711, 762 | "isConstant": false, 763 | "isLValue": false, 764 | "isPure": false, 765 | "lValueRequested": false, 766 | "leftHandSide": { 767 | "argumentTypes": null, 768 | "id": 1709, 769 | "name": "last_completed_migration", 770 | "nodeType": "Identifier", 771 | "overloadedDeclarations": [], 772 | "referencedDeclaration": 1684, 773 | "src": "334:24:2", 774 | "typeDescriptions": { 775 | "typeIdentifier": "t_uint256", 776 | "typeString": "uint256" 777 | } 778 | }, 779 | "nodeType": "Assignment", 780 | "operator": "=", 781 | "rightHandSide": { 782 | "argumentTypes": null, 783 | "id": 1710, 784 | "name": "completed", 785 | "nodeType": "Identifier", 786 | "overloadedDeclarations": [], 787 | "referencedDeclaration": 1704, 788 | "src": "361:9:2", 789 | "typeDescriptions": { 790 | "typeIdentifier": "t_uint256", 791 | "typeString": "uint256" 792 | } 793 | }, 794 | "src": "334:36:2", 795 | "typeDescriptions": { 796 | "typeIdentifier": "t_uint256", 797 | "typeString": "uint256" 798 | } 799 | }, 800 | "id": 1712, 801 | "nodeType": "ExpressionStatement", 802 | "src": "334:36:2" 803 | } 804 | ] 805 | }, 806 | "documentation": null, 807 | "id": 1714, 808 | "implemented": true, 809 | "kind": "function", 810 | "modifiers": [ 811 | { 812 | "arguments": null, 813 | "id": 1707, 814 | "modifierName": { 815 | "argumentTypes": null, 816 | "id": 1706, 817 | "name": "restricted", 818 | "nodeType": "Identifier", 819 | "overloadedDeclarations": [], 820 | "referencedDeclaration": 1702, 821 | "src": "317:10:2", 822 | "typeDescriptions": { 823 | "typeIdentifier": "t_modifier$__$", 824 | "typeString": "modifier ()" 825 | } 826 | }, 827 | "nodeType": "ModifierInvocation", 828 | "src": "317:10:2" 829 | } 830 | ], 831 | "name": "setCompleted", 832 | "nodeType": "FunctionDefinition", 833 | "parameters": { 834 | "id": 1705, 835 | "nodeType": "ParameterList", 836 | "parameters": [ 837 | { 838 | "constant": false, 839 | "id": 1704, 840 | "name": "completed", 841 | "nodeType": "VariableDeclaration", 842 | "scope": 1714, 843 | "src": "294:14:2", 844 | "stateVariable": false, 845 | "storageLocation": "default", 846 | "typeDescriptions": { 847 | "typeIdentifier": "t_uint256", 848 | "typeString": "uint256" 849 | }, 850 | "typeName": { 851 | "id": 1703, 852 | "name": "uint", 853 | "nodeType": "ElementaryTypeName", 854 | "src": "294:4:2", 855 | "typeDescriptions": { 856 | "typeIdentifier": "t_uint256", 857 | "typeString": "uint256" 858 | } 859 | }, 860 | "value": null, 861 | "visibility": "internal" 862 | } 863 | ], 864 | "src": "293:16:2" 865 | }, 866 | "returnParameters": { 867 | "id": 1708, 868 | "nodeType": "ParameterList", 869 | "parameters": [], 870 | "src": "328:0:2" 871 | }, 872 | "scope": 1715, 873 | "src": "272:103:2", 874 | "stateMutability": "nonpayable", 875 | "superFunction": null, 876 | "visibility": "public" 877 | } 878 | ], 879 | "scope": 1716, 880 | "src": "66:311:2" 881 | } 882 | ], 883 | "src": "32:346:2" 884 | }, 885 | "compiler": { 886 | "name": "solc", 887 | "version": "0.5.12+commit.7709ece9.Emscripten.clang" 888 | }, 889 | "networks": {}, 890 | "schemaVersion": "3.0.20", 891 | "updatedAt": "2020-08-21T13:32:46.524Z", 892 | "devdoc": { 893 | "methods": {} 894 | }, 895 | "userdoc": { 896 | "methods": {} 897 | } 898 | } -------------------------------------------------------------------------------- /src/contracts/Flashloan.sol: -------------------------------------------------------------------------------- 1 | 2 | pragma solidity ^0.5.0; 3 | pragma experimental ABIEncoderV2; 4 | 5 | interface Structs { 6 | struct Val { 7 | uint256 value; 8 | } 9 | 10 | enum ActionType { 11 | Deposit, // supply tokens 12 | Withdraw, // borrow tokens 13 | Transfer, // transfer balance between accounts 14 | Buy, // buy an amount of some token (externally) 15 | Sell, // sell an amount of some token (externally) 16 | Trade, // trade tokens against another account 17 | Liquidate, // liquidate an undercollateralized or expiring account 18 | Vaporize, // use excess tokens to zero-out a completely negative account 19 | Call // send arbitrary data to an address 20 | } 21 | 22 | enum AssetDenomination { 23 | Wei // the amount is denominated in wei 24 | } 25 | 26 | enum AssetReference { 27 | Delta // the amount is given as a delta from the current value 28 | } 29 | 30 | struct AssetAmount { 31 | bool sign; // true if positive 32 | AssetDenomination denomination; 33 | AssetReference ref; 34 | uint256 value; 35 | } 36 | 37 | struct ActionArgs { 38 | ActionType actionType; 39 | uint256 accountId; 40 | AssetAmount amount; 41 | uint256 primaryMarketId; 42 | uint256 secondaryMarketId; 43 | address otherAddress; 44 | uint256 otherAccountId; 45 | bytes data; 46 | } 47 | 48 | struct Info { 49 | address owner; // The address that owns the account 50 | uint256 number; // A nonce that allows a single address to control many accounts 51 | } 52 | 53 | struct Wei { 54 | bool sign; // true if positive 55 | uint256 value; 56 | } 57 | } 58 | 59 | contract DyDxPool is Structs { 60 | function getAccountWei(Info memory account, uint256 marketId) public view returns (Wei memory); 61 | function operate(Info[] memory, ActionArgs[] memory) public; 62 | } 63 | 64 | pragma solidity ^0.5.0; 65 | 66 | 67 | /** 68 | * @dev Interface of the ERC20 standard as defined in the EIP. Does not include 69 | * the optional functions; to access them see `ERC20Detailed`. 70 | */ 71 | interface IERC20 { 72 | function balanceOf(address account) external view returns (uint256); 73 | 74 | function approve(address spender, uint256 amount) external returns (bool); 75 | } 76 | 77 | pragma solidity ^0.5.0; 78 | 79 | 80 | 81 | 82 | contract DyDxFlashLoan is Structs { 83 | DyDxPool pool = DyDxPool(0x1E0447b19BB6EcFdAe1e4AE1694b0C3659614e4e); 84 | 85 | address public WETH = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2; 86 | address public SAI = 0x89d24A6b4CcB1B6fAA2625fE562bDD9a23260359; 87 | address public USDC = 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48; 88 | address public DAI = 0x6B175474E89094C44Da98b954EedeAC495271d0F; 89 | mapping(address => uint256) public currencies; 90 | 91 | constructor() public { 92 | currencies[WETH] = 1; 93 | currencies[SAI] = 2; 94 | currencies[USDC] = 3; 95 | currencies[DAI] = 4; 96 | } 97 | 98 | modifier onlyPool() { 99 | require( 100 | msg.sender == address(pool), 101 | "FlashLoan: could be called by DyDx pool only" 102 | ); 103 | _; 104 | } 105 | 106 | function tokenToMarketId(address token) public view returns (uint256) { 107 | uint256 marketId = currencies[token]; 108 | require(marketId != 0, "FlashLoan: Unsupported token"); 109 | return marketId - 1; 110 | } 111 | 112 | // the DyDx will call `callFunction(address sender, Info memory accountInfo, bytes memory data) public` after during `operate` call 113 | function flashloan(address token, uint256 amount, bytes memory data) 114 | internal 115 | { 116 | IERC20(token).approve(address(pool), amount + 1); 117 | Info[] memory infos = new Info[](1); 118 | ActionArgs[] memory args = new ActionArgs[](3); 119 | 120 | infos[0] = Info(address(this), 0); 121 | 122 | AssetAmount memory wamt = AssetAmount( 123 | false, 124 | AssetDenomination.Wei, 125 | AssetReference.Delta, 126 | amount 127 | ); 128 | ActionArgs memory withdraw; 129 | withdraw.actionType = ActionType.Withdraw; 130 | withdraw.accountId = 0; 131 | withdraw.amount = wamt; 132 | withdraw.primaryMarketId = tokenToMarketId(token); 133 | withdraw.otherAddress = address(this); 134 | 135 | args[0] = withdraw; 136 | 137 | ActionArgs memory call; 138 | call.actionType = ActionType.Call; 139 | call.accountId = 0; 140 | call.otherAddress = address(this); 141 | call.data = data; 142 | 143 | args[1] = call; 144 | 145 | ActionArgs memory deposit; 146 | AssetAmount memory damt = AssetAmount( 147 | true, 148 | AssetDenomination.Wei, 149 | AssetReference.Delta, 150 | amount + 1 151 | ); 152 | deposit.actionType = ActionType.Deposit; 153 | deposit.accountId = 0; 154 | deposit.amount = damt; 155 | deposit.primaryMarketId = tokenToMarketId(token); 156 | deposit.otherAddress = address(this); 157 | 158 | args[2] = deposit; 159 | 160 | pool.operate(infos, args); 161 | } 162 | } 163 | 164 | pragma solidity ^0.5.0; 165 | 166 | 167 | 168 | 169 | contract Flashloan is DyDxFlashLoan { 170 | uint256 public loan; 171 | 172 | constructor() public payable { 173 | (bool success, ) = WETH.call.value(msg.value)(""); 174 | require(success, "fail to get weth"); 175 | } 176 | 177 | function getFlashloan(address flashToken, uint256 flashAmount) external { 178 | uint256 balanceBefore = IERC20(flashToken).balanceOf(address(this)); 179 | bytes memory data = abi.encode(flashToken, flashAmount, balanceBefore); 180 | flashloan(flashToken, flashAmount, data); // execution goes to `callFunction` 181 | } 182 | 183 | function callFunction( 184 | address, /* sender */ 185 | Info calldata, /* accountInfo */ 186 | bytes calldata data 187 | ) external onlyPool { 188 | (address flashToken, uint256 flashAmount, uint256 balanceBefore) = abi 189 | .decode(data, (address, uint256, uint256)); 190 | uint256 balanceAfter = IERC20(flashToken).balanceOf(address(this)); 191 | require( 192 | balanceAfter - balanceBefore == flashAmount, 193 | "contract did not get the loan" 194 | ); 195 | loan = balanceAfter; 196 | 197 | /******* 198 | * Pseudo-code 199 | * Use the money here! 200 | *******/ 201 | 202 | // function arb() internal { 203 | // uint amount = 10000000000000000000; // 100 tokens 204 | // ERC20(token).approve(exchange1, amount); // Approve tokens 205 | // uint ethAmount = Exchange1(exchange1).sellTokens(token, amount); // Sell Tokens for Ether 206 | // Exchange2(exchange1).buyTokens.value(ethAmount)(token); // Buys tokens back 207 | // } 208 | 209 | // } 210 | 211 | 212 | 213 | } 214 | } 215 | -------------------------------------------------------------------------------- /src/contracts/Migrations.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity >=0.4.21 <0.7.0; 3 | 4 | contract Migrations { 5 | address public owner; 6 | uint public last_completed_migration; 7 | 8 | constructor() public { 9 | owner = msg.sender; 10 | } 11 | 12 | modifier restricted() { 13 | if (msg.sender == owner) _; 14 | } 15 | 16 | function setCompleted(uint completed) public restricted { 17 | last_completed_migration = completed; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/contracts/TradingBot.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.5.0; 2 | pragma experimental ABIEncoderV2; 3 | 4 | interface Structs { 5 | struct Val { 6 | uint256 value; 7 | } 8 | 9 | enum ActionType { 10 | Deposit, // supply tokens 11 | Withdraw, // borrow tokens 12 | Transfer, // transfer balance between accounts 13 | Buy, // buy an amount of some token (externally) 14 | Sell, // sell an amount of some token (externally) 15 | Trade, // trade tokens against another account 16 | Liquidate, // liquidate an undercollateralized or expiring account 17 | Vaporize, // use excess tokens to zero-out a completely negative account 18 | Call // send arbitrary data to an address 19 | } 20 | 21 | enum AssetDenomination { 22 | Wei // the amount is denominated in wei 23 | } 24 | 25 | enum AssetReference { 26 | Delta // the amount is given as a delta from the current value 27 | } 28 | 29 | struct AssetAmount { 30 | bool sign; // true if positive 31 | AssetDenomination denomination; 32 | AssetReference ref; 33 | uint256 value; 34 | } 35 | 36 | struct ActionArgs { 37 | ActionType actionType; 38 | uint256 accountId; 39 | AssetAmount amount; 40 | uint256 primaryMarketId; 41 | uint256 secondaryMarketId; 42 | address otherAddress; 43 | uint256 otherAccountId; 44 | bytes data; 45 | } 46 | 47 | struct Info { 48 | address owner; // The address that owns the account 49 | uint256 number; // A nonce that allows a single address to control many accounts 50 | } 51 | 52 | struct Wei { 53 | bool sign; // true if positive 54 | uint256 value; 55 | } 56 | } 57 | 58 | contract DyDxPool is Structs { 59 | function getAccountWei(Info memory account, uint256 marketId) public view returns (Wei memory); 60 | function operate(Info[] memory, ActionArgs[] memory) public; 61 | } 62 | 63 | 64 | pragma solidity ^0.5.0; 65 | 66 | 67 | /** 68 | * @dev Interface of the ERC20 standard as defined in the EIP. Does not include 69 | * the optional functions; to access them see `ERC20Detailed`. 70 | */ 71 | interface IERC20 { 72 | function totalSupply() external view returns (uint256); 73 | function balanceOf(address account) external view returns (uint256); 74 | function transfer(address recipient, uint256 amount) external returns (bool); 75 | function allowance(address owner, address spender) external view returns (uint256); 76 | function approve(address spender, uint256 amount) external returns (bool); 77 | function transferFrom(address sender, address recipient, uint256 amount) external returns (bool); 78 | event Transfer(address indexed from, address indexed to, uint256 value); 79 | event Approval(address indexed owner, address indexed spender, uint256 value); 80 | } 81 | 82 | 83 | pragma solidity ^0.5.0; 84 | 85 | 86 | 87 | 88 | contract DyDxFlashLoan is Structs { 89 | DyDxPool pool = DyDxPool(0x1E0447b19BB6EcFdAe1e4AE1694b0C3659614e4e); 90 | 91 | address public WETH = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2; 92 | address public SAI = 0x89d24A6b4CcB1B6fAA2625fE562bDD9a23260359; 93 | address public USDC = 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48; 94 | address public DAI = 0x6B175474E89094C44Da98b954EedeAC495271d0F; 95 | mapping(address => uint256) public currencies; 96 | 97 | constructor() public { 98 | currencies[WETH] = 1; 99 | currencies[SAI] = 2; 100 | currencies[USDC] = 3; 101 | currencies[DAI] = 4; 102 | } 103 | 104 | modifier onlyPool() { 105 | require( 106 | msg.sender == address(pool), 107 | "FlashLoan: could be called by DyDx pool only" 108 | ); 109 | _; 110 | } 111 | 112 | function tokenToMarketId(address token) public view returns (uint256) { 113 | uint256 marketId = currencies[token]; 114 | require(marketId != 0, "FlashLoan: Unsupported token"); 115 | return marketId - 1; 116 | } 117 | 118 | // the DyDx will call `callFunction(address sender, Info memory accountInfo, bytes memory data) public` after during `operate` call 119 | function flashloan(address token, uint256 amount, bytes memory data) 120 | internal 121 | { 122 | IERC20(token).approve(address(pool), amount + 1); 123 | Info[] memory infos = new Info[](1); 124 | ActionArgs[] memory args = new ActionArgs[](3); 125 | 126 | infos[0] = Info(address(this), 0); 127 | 128 | AssetAmount memory wamt = AssetAmount( 129 | false, 130 | AssetDenomination.Wei, 131 | AssetReference.Delta, 132 | amount 133 | ); 134 | ActionArgs memory withdraw; 135 | withdraw.actionType = ActionType.Withdraw; 136 | withdraw.accountId = 0; 137 | withdraw.amount = wamt; 138 | withdraw.primaryMarketId = tokenToMarketId(token); 139 | withdraw.otherAddress = address(this); 140 | 141 | args[0] = withdraw; 142 | 143 | ActionArgs memory call; 144 | call.actionType = ActionType.Call; 145 | call.accountId = 0; 146 | call.otherAddress = address(this); 147 | call.data = data; 148 | 149 | args[1] = call; 150 | 151 | ActionArgs memory deposit; 152 | AssetAmount memory damt = AssetAmount( 153 | true, 154 | AssetDenomination.Wei, 155 | AssetReference.Delta, 156 | amount + 1 157 | ); 158 | deposit.actionType = ActionType.Deposit; 159 | deposit.accountId = 0; 160 | deposit.amount = damt; 161 | deposit.primaryMarketId = tokenToMarketId(token); 162 | deposit.otherAddress = address(this); 163 | 164 | args[2] = deposit; 165 | 166 | pool.operate(infos, args); 167 | } 168 | } 169 | 170 | 171 | pragma solidity ^0.5.0; 172 | 173 | 174 | contract IOneSplit { // interface for 1inch exchange. 175 | function getExpectedReturn( 176 | IERC20 fromToken, 177 | IERC20 toToken, 178 | uint256 amount, 179 | uint256 parts, 180 | uint256 disableFlags 181 | ) 182 | public 183 | view 184 | returns( 185 | uint256 returnAmount, 186 | uint256[] memory distribution 187 | ); 188 | 189 | function swap( 190 | IERC20 fromToken, 191 | IERC20 toToken, 192 | uint256 amount, 193 | uint256 minReturn, 194 | uint256[] memory distribution, 195 | uint256 disableFlags 196 | ) public payable; 197 | } 198 | 199 | contract TradingBot is DyDxFlashLoan { // Where the actual contract for the trading bot starts. anything above that are just libraries. 200 | uint256 public loan; 201 | 202 | // Addresses 203 | address payable OWNER; 204 | 205 | // OneSplit Config 206 | address ONE_SPLIT_ADDRESS = 0xC586BeF4a0992C495Cf22e1aeEE4E446CECDee0E; 207 | uint256 PARTS = 10; 208 | uint256 FLAGS = 0; 209 | 210 | // ZRX Config 211 | address ZRX_EXCHANGE_ADDRESS = 0x61935CbDd02287B511119DDb11Aeb42F1593b7Ef; 212 | address ZRX_ERC20_PROXY_ADDRESS = 0x95E6F48254609A6ee006F7D493c8e5fB97094ceF; 213 | address ZRX_STAKING_PROXY = 0xa26e80e7Dea86279c6d778D702Cc413E6CFfA777; // Fee collector 214 | 215 | // Modifiers 216 | modifier onlyOwner() { 217 | require(msg.sender == OWNER, "caller is not the owner!"); 218 | _; 219 | } 220 | 221 | // Allow the contract to receive Ether 222 | function () external payable {} 223 | 224 | constructor() public payable { 225 | _getWeth(msg.value); 226 | _approveWeth(msg.value); 227 | OWNER = msg.sender; 228 | } 229 | 230 | function getFlashloan(address flashToken, uint256 flashAmount, address arbToken, bytes calldata zrxData, uint256 oneSplitMinReturn, uint256[] calldata oneSplitDistribution) external payable onlyOwner { 231 | uint256 balanceBefore = IERC20(flashToken).balanceOf(address(this)); 232 | bytes memory data = abi.encode(flashToken, flashAmount, balanceBefore, arbToken, zrxData, oneSplitMinReturn, oneSplitDistribution); 233 | flashloan(flashToken, flashAmount, data); // execution goes to `callFunction` 234 | 235 | // and this point we have succefully paid the dept 236 | } 237 | 238 | function callFunction( 239 | address, /* sender */ 240 | Info calldata, /* accountInfo */ 241 | bytes calldata data 242 | ) external onlyPool { 243 | (address flashToken, uint256 flashAmount, uint256 balanceBefore, address arbToken, bytes memory zrxData, uint256 oneSplitMinReturn, uint256[] memory oneSplitDistribution) = abi 244 | .decode(data, (address, uint256, uint256, address, bytes, uint256, uint256[])); 245 | uint256 balanceAfter = IERC20(flashToken).balanceOf(address(this)); 246 | require( 247 | balanceAfter - balanceBefore == flashAmount, 248 | "contract did not get the loan" 249 | ); 250 | loan = balanceAfter; 251 | 252 | // do whatever you want with the money 253 | // the dept will be automatically withdrawn from this contract at the end of execution 254 | _arb(flashToken, arbToken, flashAmount, zrxData, oneSplitMinReturn, oneSplitDistribution); 255 | } 256 | 257 | function arb(address _fromToken, address _toToken, uint256 _fromAmount, bytes memory _0xData, uint256 _1SplitMinReturn, uint256[] memory _1SplitDistribution) onlyOwner payable public { 258 | _arb(_fromToken, _toToken, _fromAmount, _0xData, _1SplitMinReturn, _1SplitDistribution); 259 | } 260 | 261 | function _arb(address _fromToken, address _toToken, uint256 _fromAmount, bytes memory _0xData, uint256 _1SplitMinReturn, uint256[] memory _1SplitDistribution) internal { 262 | // Track original balance 263 | uint256 _startBalance = IERC20(_fromToken).balanceOf(address(this)); 264 | 265 | // Perform the arb trade 266 | _trade(_fromToken, _toToken, _fromAmount, _0xData, _1SplitMinReturn, _1SplitDistribution); 267 | 268 | // Track result balance 269 | uint256 _endBalance = IERC20(_fromToken).balanceOf(address(this)); 270 | 271 | // Require that arbitrage is profitable 272 | require(_endBalance > _startBalance, "End balance must exceed start balance."); 273 | } 274 | 275 | function trade(address _fromToken, address _toToken, uint256 _fromAmount, bytes memory _0xData, uint256 _1SplitMinReturn, uint256[] memory _1SplitDistribution) onlyOwner payable public { 276 | _trade(_fromToken, _toToken, _fromAmount, _0xData, _1SplitMinReturn, _1SplitDistribution); 277 | } 278 | 279 | function _trade(address _fromToken, address _toToken, uint256 _fromAmount, bytes memory _0xData, uint256 _1SplitMinReturn, uint256[] memory _1SplitDistribution) internal { 280 | // Track the balance of the token RECEIVED from the trade 281 | uint256 _beforeBalance = IERC20(_toToken).balanceOf(address(this)); 282 | 283 | // Swap on 0x: give _fromToken, receive _toToken 284 | _zrxSwap(_fromToken, _fromAmount, _0xData); 285 | 286 | // Calculate the how much of the token we received 287 | uint256 _afterBalance = IERC20(_toToken).balanceOf(address(this)); 288 | 289 | // Read _toToken balance after swap 290 | uint256 _toAmount = _afterBalance - _beforeBalance; 291 | 292 | // Swap on 1Split: give _toToken, receive _fromToken 293 | _oneSplitSwap(_toToken, _fromToken, _toAmount, _1SplitMinReturn, _1SplitDistribution); 294 | } 295 | 296 | function zrxSwap(address _from, uint256 _amount, bytes memory _calldataHexString) onlyOwner public payable { 297 | _zrxSwap(_from, _amount, _calldataHexString); 298 | } 299 | 300 | function _zrxSwap(address _from, uint256 _amount, bytes memory _calldataHexString) internal { 301 | // Approve tokens 302 | IERC20 _fromIERC20 = IERC20(_from); 303 | _fromIERC20.approve(ZRX_ERC20_PROXY_ADDRESS, _amount); 304 | 305 | // Swap tokens 306 | address(ZRX_EXCHANGE_ADDRESS).call.value(msg.value)(_calldataHexString); 307 | 308 | // Reset approval 309 | _fromIERC20.approve(ZRX_ERC20_PROXY_ADDRESS, 0); 310 | } 311 | 312 | function oneSplitSwap(address _from, address _to, uint256 _amount, uint256 _minReturn, uint256[] memory _distribution) onlyOwner public payable { 313 | _oneSplitSwap(_from, _to, _amount, _minReturn, _distribution); 314 | } 315 | 316 | function _oneSplitSwap(address _from, address _to, uint256 _amount, uint256 _minReturn, uint256[] memory _distribution) internal { 317 | // Setup contracts 318 | IERC20 _fromIERC20 = IERC20(_from); 319 | IERC20 _toIERC20 = IERC20(_to); 320 | IOneSplit _oneSplitContract = IOneSplit(ONE_SPLIT_ADDRESS); 321 | 322 | // Approve tokens 323 | _fromIERC20.approve(ONE_SPLIT_ADDRESS, _amount); 324 | 325 | // Swap tokens: give _from, get _to 326 | _oneSplitContract.swap(_fromIERC20, _toIERC20, _amount, _minReturn, _distribution, FLAGS); 327 | 328 | // Reset approval 329 | _fromIERC20.approve(ONE_SPLIT_ADDRESS, 0); 330 | } 331 | 332 | function getWeth() public payable onlyOwner { 333 | _getWeth(msg.value); 334 | } 335 | 336 | function _getWeth(uint256 _amount) internal { 337 | (bool success, ) = WETH.call.value(_amount)(""); 338 | require(success, "failed to get weth"); 339 | } 340 | 341 | function approveWeth(uint256 _amount) public onlyOwner { 342 | _approveWeth(_amount); 343 | } 344 | 345 | function _approveWeth(uint256 _amount) internal { 346 | 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 347 | } 348 | 349 | // KEEP THIS FUNCTION IN CASE THE CONTRACT RECEIVES TOKENS! 350 | function withdrawToken(address _tokenAddress) public onlyOwner { 351 | uint256 balance = IERC20(_tokenAddress).balanceOf(address(this)); 352 | IERC20(_tokenAddress).transfer(OWNER, balance); 353 | } 354 | 355 | // KEEP THIS FUNCTION IN CASE THE CONTRACT KEEPS LEFTOVER ETHER! 356 | function withdrawEther() public onlyOwner { 357 | address self = address(this); // workaround for a possible solidity bug 358 | uint256 balance = self.balance; 359 | address(OWNER).transfer(balance); 360 | } 361 | } -------------------------------------------------------------------------------- /src/contracts/examples/ArbExample.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.5.0; 2 | pragma experimental ABIEncoderV2; 3 | 4 | contract UniswapExchangeInterface { 5 | bytes32 public name; 6 | bytes32 public symbol; 7 | uint256 public decimals; 8 | function tokenAddress() external view returns (address token); 9 | function factoryAddress() external view returns (address factory); 10 | function addLiquidity(uint256 min_liquidity, uint256 max_tokens, uint256 deadline) external payable returns (uint256); 11 | function removeLiquidity(uint256 amount, uint256 min_eth, uint256 min_tokens, uint256 deadline) external returns (uint256, uint256); 12 | function getEthToTokenInputPrice(uint256 eth_sold) external view returns (uint256 tokens_bought); 13 | function getEthToTokenOutputPrice(uint256 tokens_bought) external view returns (uint256 eth_sold); 14 | function getTokenToEthInputPrice(uint256 tokens_sold) external view returns (uint256 eth_bought); 15 | function getTokenToEthOutputPrice(uint256 eth_bought) external view returns (uint256 tokens_sold); 16 | function ethToTokenSwapInput(uint256 min_tokens, uint256 deadline) external payable returns (uint256 tokens_bought); 17 | function ethToTokenTransferInput(uint256 min_tokens, uint256 deadline, address recipient) external payable returns (uint256 tokens_bought); 18 | function ethToTokenSwapOutput(uint256 tokens_bought, uint256 deadline) external payable returns (uint256 eth_sold); 19 | function ethToTokenTransferOutput(uint256 tokens_bought, uint256 deadline, address recipient) external payable returns (uint256 eth_sold); 20 | function tokenToEthSwapInput(uint256 tokens_sold, uint256 min_eth, uint256 deadline) external returns (uint256 eth_bought); 21 | function tokenToEthTransferInput(uint256 tokens_sold, uint256 min_eth, uint256 deadline, address recipient) external returns (uint256 eth_bought); 22 | function tokenToEthSwapOutput(uint256 eth_bought, uint256 max_tokens, uint256 deadline) external returns (uint256 tokens_sold); 23 | function tokenToEthTransferOutput(uint256 eth_bought, uint256 max_tokens, uint256 deadline, address recipient) external returns (uint256 tokens_sold); 24 | function tokenToTokenSwapInput(uint256 tokens_sold, uint256 min_tokens_bought, uint256 min_eth_bought, uint256 deadline, address token_addr) external returns (uint256 tokens_bought); 25 | function tokenToTokenTransferInput(uint256 tokens_sold, uint256 min_tokens_bought, uint256 min_eth_bought, uint256 deadline, address recipient, address token_addr) external returns (uint256 tokens_bought); 26 | function tokenToTokenSwapOutput(uint256 tokens_bought, uint256 max_tokens_sold, uint256 max_eth_sold, uint256 deadline, address token_addr) external returns (uint256 tokens_sold); 27 | function tokenToTokenTransferOutput(uint256 tokens_bought, uint256 max_tokens_sold, uint256 max_eth_sold, uint256 deadline, address recipient, address token_addr) external returns (uint256 tokens_sold); 28 | function tokenToExchangeSwapInput(uint256 tokens_sold, uint256 min_tokens_bought, uint256 min_eth_bought, uint256 deadline, address exchange_addr) external returns (uint256 tokens_bought); 29 | function tokenToExchangeTransferInput(uint256 tokens_sold, uint256 min_tokens_bought, uint256 min_eth_bought, uint256 deadline, address recipient, address exchange_addr) external returns (uint256 tokens_bought); 30 | function tokenToExchangeSwapOutput(uint256 tokens_bought, uint256 max_tokens_sold, uint256 max_eth_sold, uint256 deadline, address exchange_addr) external returns (uint256 tokens_sold); 31 | function tokenToExchangeTransferOutput(uint256 tokens_bought, uint256 max_tokens_sold, uint256 max_eth_sold, uint256 deadline, address recipient, address exchange_addr) external returns (uint256 tokens_sold); 32 | function transfer(address _to, uint256 _value) external returns (bool); 33 | function transferFrom(address _from, address _to, uint256 value) external returns (bool); 34 | function approve(address _spender, uint256 _value) external returns (bool); 35 | function allowance(address _owner, address _spender) external view returns (uint256); 36 | function balanceOf(address _owner) external view returns (uint256); 37 | function totalSupply() external view returns (uint256); 38 | function setup(address token_addr) external; 39 | } 40 | 41 | interface ERC20 { 42 | function totalSupply() external view returns (uint supply); 43 | function balanceOf(address _owner) external view returns (uint balance); 44 | function transfer(address _to, uint _value) external returns (bool success); 45 | function transferFrom(address _from, address _to, uint _value) external returns (bool success); 46 | function approve(address _spender, uint _value) external returns (bool success); 47 | function allowance(address _owner, address _spender) external view returns (uint remaining); 48 | function decimals() external view returns(uint digits); 49 | event Approval(address indexed _owner, address indexed _spender, uint _value); 50 | } 51 | 52 | interface IKyber { 53 | function maxGasPrice() external view returns(uint); 54 | function getUserCapInWei(address user) external view returns(uint); 55 | function getUserCapInTokenWei(address user, ERC20 token) external view returns(uint); 56 | function enabled() external view returns(bool); 57 | function info(bytes32 id) external view returns(uint); 58 | function getExpectedRate(ERC20 src, ERC20 dest, uint srcQty) external view 59 | returns (uint expectedRate, uint slippageRate); 60 | function tradeWithHint( 61 | ERC20 src, 62 | uint srcAmount, 63 | ERC20 dest, 64 | address destAddress, 65 | uint maxDestAmount, 66 | uint minConversionRate, 67 | address walletId, 68 | bytes calldata hint 69 | ) external payable returns(uint); 70 | function swapEtherToToken(ERC20 token, uint minRate) external returns (uint); 71 | function swapTokenToEther(ERC20 token, uint tokenQty, uint minRate) external returns (uint); 72 | } 73 | 74 | contract Trader { 75 | IKyber public KYBER_PROXY = IKyber(0x818E6FECD516Ecc3849DAf6845e3EC868087B755); 76 | address sai = 0x89d24A6b4CcB1B6fAA2625fE562bDD9a23260359; 77 | address public owner; 78 | 79 | modifier onlyOwner() { 80 | if (msg.sender != owner) { 81 | revert(); 82 | } 83 | _; 84 | } 85 | 86 | constructor() public { 87 | owner = msg.sender; 88 | } 89 | 90 | // Allow contract to receive Ether 91 | function () external payable {} 92 | 93 | function kyberSwap (IKyber _kyberNetworkProxy, ERC20 token, uint tokenQty, address destAddress) internal returns (uint) { 94 | token.transferFrom(msg.sender, address(this), tokenQty); 95 | token.approve(address(KYBER_PROXY), 0); 96 | token.approve(address(KYBER_PROXY), tokenQty); 97 | uint destAmount = KYBER_PROXY.tradeWithHint(ERC20(sai), tokenQty, ERC20(0x00eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee), address(this), 8000000000000000000000000000000000000000000000000000000000000000, 0, 0x0000000000000000000000000000000000000004, "PERM"); 98 | return destAmount; 99 | } 100 | 101 | function buyKyberSellUniswap(address _fromToken, address _uniswap, uint _amount) internal returns (bool){ 102 | uint _ethAmount = kyberSwap(KYBER_PROXY, ERC20(_fromToken), _amount, msg.sender); 103 | UniswapExchangeInterface(_uniswap).ethToTokenSwapInput.value(_ethAmount)(1, block.timestamp); 104 | return true; 105 | } 106 | 107 | function arb(uint256 _amount) internal { 108 | buyKyberSellUniswap(sai, 0x09cabEC1eAd1c0Ba254B09efb3EE13841712bE14, _amount); 109 | } 110 | 111 | function withdrawFunds() onlyOwner public { 112 | msg.sender.transfer(address(this).balance); 113 | ERC20(sai).transfer(msg.sender, ERC20(sai).balanceOf(address(this))); 114 | } 115 | } 116 | 117 | 118 | interface Structs { 119 | struct Val { 120 | uint256 value; 121 | } 122 | 123 | enum ActionType { 124 | Deposit, // supply tokens 125 | Withdraw, // borrow tokens 126 | Transfer, // transfer balance between accounts 127 | Buy, // buy an amount of some token (externally) 128 | Sell, // sell an amount of some token (externally) 129 | Trade, // trade tokens against another account 130 | Liquidate, // liquidate an undercollateralized or expiring account 131 | Vaporize, // use excess tokens to zero-out a completely negative account 132 | Call // send arbitrary data to an address 133 | } 134 | 135 | enum AssetDenomination { 136 | Wei // the amount is denominated in wei 137 | } 138 | 139 | enum AssetReference { 140 | Delta // the amount is given as a delta from the current value 141 | } 142 | 143 | struct AssetAmount { 144 | bool sign; // true if positive 145 | AssetDenomination denomination; 146 | AssetReference ref; 147 | uint256 value; 148 | } 149 | 150 | struct ActionArgs { 151 | ActionType actionType; 152 | uint256 accountId; 153 | AssetAmount amount; 154 | uint256 primaryMarketId; 155 | uint256 secondaryMarketId; 156 | address otherAddress; 157 | uint256 otherAccountId; 158 | bytes data; 159 | } 160 | 161 | struct Info { 162 | address owner; // The address that owns the account 163 | uint256 number; // A nonce that allows a single address to control many accounts 164 | } 165 | 166 | struct Wei { 167 | bool sign; // true if positive 168 | uint256 value; 169 | } 170 | } 171 | 172 | contract DyDxPool is Structs { 173 | function getAccountWei(Info memory account, uint256 marketId) public view returns (Wei memory); 174 | function operate(Info[] memory, ActionArgs[] memory) public; 175 | } 176 | 177 | pragma solidity ^0.5.0; 178 | 179 | 180 | /** 181 | * @dev Interface of the ERC20 standard as defined in the EIP. Does not include 182 | * the optional functions; to access them see `ERC20Detailed`. 183 | */ 184 | interface IERC20 { 185 | function balanceOf(address account) external view returns (uint256); 186 | 187 | function approve(address spender, uint256 amount) external returns (bool); 188 | } 189 | 190 | pragma solidity ^0.5.0; 191 | 192 | 193 | contract DyDxFlashLoan is Structs { 194 | DyDxPool pool = DyDxPool(0x1E0447b19BB6EcFdAe1e4AE1694b0C3659614e4e); 195 | 196 | address public WETH = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2; 197 | address public SAI = 0x89d24A6b4CcB1B6fAA2625fE562bDD9a23260359; 198 | address public USDC = 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48; 199 | address public DAI = 0x6B175474E89094C44Da98b954EedeAC495271d0F; 200 | mapping(address => uint256) public currencies; 201 | 202 | constructor() public { 203 | currencies[WETH] = 1; 204 | currencies[SAI] = 2; 205 | currencies[USDC] = 3; 206 | currencies[DAI] = 4; 207 | } 208 | 209 | modifier onlyPool() { 210 | require( 211 | msg.sender == address(pool), 212 | "FlashLoan: could be called by DyDx pool only" 213 | ); 214 | _; 215 | } 216 | 217 | function tokenToMarketId(address token) public view returns (uint256) { 218 | uint256 marketId = currencies[token]; 219 | require(marketId != 0, "FlashLoan: Unsupported token"); 220 | return marketId - 1; 221 | } 222 | 223 | // the DyDx will call `callFunction(address sender, Info memory accountInfo, bytes memory data) public` after during `operate` call 224 | function flashloan(address token, uint256 amount, bytes memory data) 225 | internal 226 | { 227 | IERC20(token).approve(address(pool), amount + 1); 228 | Info[] memory infos = new Info[](1); 229 | ActionArgs[] memory args = new ActionArgs[](3); 230 | 231 | infos[0] = Info(address(this), 0); 232 | 233 | AssetAmount memory wamt = AssetAmount( 234 | false, 235 | AssetDenomination.Wei, 236 | AssetReference.Delta, 237 | amount 238 | ); 239 | ActionArgs memory withdraw; 240 | withdraw.actionType = ActionType.Withdraw; 241 | withdraw.accountId = 0; 242 | withdraw.amount = wamt; 243 | withdraw.primaryMarketId = tokenToMarketId(token); 244 | withdraw.otherAddress = address(this); 245 | 246 | args[0] = withdraw; 247 | 248 | ActionArgs memory call; 249 | call.actionType = ActionType.Call; 250 | call.accountId = 0; 251 | call.otherAddress = address(this); 252 | call.data = data; 253 | 254 | args[1] = call; 255 | 256 | ActionArgs memory deposit; 257 | AssetAmount memory damt = AssetAmount( 258 | true, 259 | AssetDenomination.Wei, 260 | AssetReference.Delta, 261 | amount + 1 262 | ); 263 | deposit.actionType = ActionType.Deposit; 264 | deposit.accountId = 0; 265 | deposit.amount = damt; 266 | deposit.primaryMarketId = tokenToMarketId(token); 267 | deposit.otherAddress = address(this); 268 | 269 | args[2] = deposit; 270 | 271 | pool.operate(infos, args); 272 | } 273 | } 274 | 275 | pragma solidity ^0.5.0; 276 | 277 | contract ArbExample is DyDxFlashLoan, Trader { 278 | uint256 public loan; 279 | 280 | function getFlashloan(address flashToken, uint256 flashAmount) external { 281 | uint256 balanceBefore = IERC20(flashToken).balanceOf(address(this)); 282 | bytes memory data = abi.encode(flashToken, flashAmount, balanceBefore); 283 | flashloan(flashToken, flashAmount, data); // execution goes to `callFunction` 284 | } 285 | 286 | function callFunction( 287 | address, /* sender */ 288 | Info calldata, /* accountInfo */ 289 | bytes calldata data 290 | ) external onlyPool { 291 | (address flashToken, uint256 flashAmount, uint256 balanceBefore) = abi 292 | .decode(data, (address, uint256, uint256)); 293 | uint256 balanceAfter = IERC20(flashToken).balanceOf(address(this)); 294 | require( 295 | balanceAfter - balanceBefore == flashAmount, 296 | "contract did not get the loan" 297 | ); 298 | loan = balanceAfter; 299 | 300 | arb(loan); 301 | } 302 | } 303 | -------------------------------------------------------------------------------- /src/contracts/examples/Flashloan.sol: -------------------------------------------------------------------------------- 1 | 2 | pragma solidity ^0.5.0; 3 | pragma experimental ABIEncoderV2; 4 | 5 | interface Structs { 6 | struct Val { 7 | uint256 value; 8 | } 9 | 10 | enum ActionType { 11 | Deposit, // supply tokens 12 | Withdraw, // borrow tokens 13 | Transfer, // transfer balance between accounts 14 | Buy, // buy an amount of some token (externally) 15 | Sell, // sell an amount of some token (externally) 16 | Trade, // trade tokens against another account 17 | Liquidate, // liquidate an undercollateralized or expiring account 18 | Vaporize, // use excess tokens to zero-out a completely negative account 19 | Call // send arbitrary data to an address 20 | } 21 | 22 | enum AssetDenomination { 23 | Wei // the amount is denominated in wei 24 | } 25 | 26 | enum AssetReference { 27 | Delta // the amount is given as a delta from the current value 28 | } 29 | 30 | struct AssetAmount { 31 | bool sign; // true if positive 32 | AssetDenomination denomination; 33 | AssetReference ref; 34 | uint256 value; 35 | } 36 | 37 | struct ActionArgs { 38 | ActionType actionType; 39 | uint256 accountId; 40 | AssetAmount amount; 41 | uint256 primaryMarketId; 42 | uint256 secondaryMarketId; 43 | address otherAddress; 44 | uint256 otherAccountId; 45 | bytes data; 46 | } 47 | 48 | struct Info { 49 | address owner; // The address that owns the account 50 | uint256 number; // A nonce that allows a single address to control many accounts 51 | } 52 | 53 | struct Wei { 54 | bool sign; // true if positive 55 | uint256 value; 56 | } 57 | } 58 | 59 | contract DyDxPool is Structs { 60 | function getAccountWei(Info memory account, uint256 marketId) public view returns (Wei memory); 61 | function operate(Info[] memory, ActionArgs[] memory) public; 62 | } 63 | 64 | pragma solidity ^0.5.0; 65 | 66 | 67 | /** 68 | * @dev Interface of the ERC20 standard as defined in the EIP. Does not include 69 | * the optional functions; to access them see `ERC20Detailed`. 70 | */ 71 | interface IERC20 { 72 | function balanceOf(address account) external view returns (uint256); 73 | 74 | function approve(address spender, uint256 amount) external returns (bool); 75 | } 76 | 77 | pragma solidity ^0.5.0; 78 | 79 | 80 | 81 | 82 | contract DyDxFlashLoan is Structs { 83 | DyDxPool pool = DyDxPool(0x1E0447b19BB6EcFdAe1e4AE1694b0C3659614e4e); 84 | 85 | address public WETH = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2; 86 | address public SAI = 0x89d24A6b4CcB1B6fAA2625fE562bDD9a23260359; 87 | address public USDC = 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48; 88 | address public DAI = 0x6B175474E89094C44Da98b954EedeAC495271d0F; 89 | mapping(address => uint256) public currencies; 90 | 91 | constructor() public { 92 | currencies[WETH] = 1; 93 | currencies[SAI] = 2; 94 | currencies[USDC] = 3; 95 | currencies[DAI] = 4; 96 | } 97 | 98 | modifier onlyPool() { 99 | require( 100 | msg.sender == address(pool), 101 | "FlashLoan: could be called by DyDx pool only" 102 | ); 103 | _; 104 | } 105 | 106 | function tokenToMarketId(address token) public view returns (uint256) { 107 | uint256 marketId = currencies[token]; 108 | require(marketId != 0, "FlashLoan: Unsupported token"); 109 | return marketId - 1; 110 | } 111 | 112 | // the DyDx will call `callFunction(address sender, Info memory accountInfo, bytes memory data) public` after during `operate` call 113 | function flashloan(address token, uint256 amount, bytes memory data) 114 | internal 115 | { 116 | IERC20(token).approve(address(pool), amount + 1); 117 | Info[] memory infos = new Info[](1); 118 | ActionArgs[] memory args = new ActionArgs[](3); 119 | 120 | infos[0] = Info(address(this), 0); 121 | 122 | AssetAmount memory wamt = AssetAmount( 123 | false, 124 | AssetDenomination.Wei, 125 | AssetReference.Delta, 126 | amount 127 | ); 128 | ActionArgs memory withdraw; 129 | withdraw.actionType = ActionType.Withdraw; 130 | withdraw.accountId = 0; 131 | withdraw.amount = wamt; 132 | withdraw.primaryMarketId = tokenToMarketId(token); 133 | withdraw.otherAddress = address(this); 134 | 135 | args[0] = withdraw; 136 | 137 | ActionArgs memory call; 138 | call.actionType = ActionType.Call; 139 | call.accountId = 0; 140 | call.otherAddress = address(this); 141 | call.data = data; 142 | 143 | args[1] = call; 144 | 145 | ActionArgs memory deposit; 146 | AssetAmount memory damt = AssetAmount( 147 | true, 148 | AssetDenomination.Wei, 149 | AssetReference.Delta, 150 | amount + 1 151 | ); 152 | deposit.actionType = ActionType.Deposit; 153 | deposit.accountId = 0; 154 | deposit.amount = damt; 155 | deposit.primaryMarketId = tokenToMarketId(token); 156 | deposit.otherAddress = address(this); 157 | 158 | args[2] = deposit; 159 | 160 | pool.operate(infos, args); 161 | } 162 | } 163 | 164 | pragma solidity ^0.5.0; 165 | 166 | 167 | 168 | 169 | contract Flashloan is DyDxFlashLoan { 170 | uint256 public loan; 171 | 172 | constructor() public payable { 173 | (bool success, ) = WETH.call.value(msg.value)(""); 174 | require(success, "fail to get weth"); 175 | } 176 | 177 | function getFlashloan(address flashToken, uint256 flashAmount) external { 178 | uint256 balanceBefore = IERC20(flashToken).balanceOf(address(this)); 179 | bytes memory data = abi.encode(flashToken, flashAmount, balanceBefore); 180 | flashloan(flashToken, flashAmount, data); // execution goes to `callFunction` 181 | } 182 | 183 | function callFunction( 184 | address, /* sender */ 185 | Info calldata, /* accountInfo */ 186 | bytes calldata data 187 | ) external onlyPool { 188 | (address flashToken, uint256 flashAmount, uint256 balanceBefore) = abi 189 | .decode(data, (address, uint256, uint256)); 190 | uint256 balanceAfter = IERC20(flashToken).balanceOf(address(this)); 191 | require( 192 | balanceAfter - balanceBefore == flashAmount, 193 | "contract did not get the loan" 194 | ); 195 | loan = balanceAfter; 196 | 197 | /******* 198 | * Pseudo-code 199 | * Use the money here! 200 | *******/ 201 | 202 | // function arb() internal { 203 | // uint amount = 10000000000000000000; // 100 tokens 204 | // ERC20(token).approve(exchange1, amount); // Approve tokens 205 | // uint ethAmount = Exchange1(exchange1).sellTokens(token, amount); // Sell Tokens for Ether 206 | // Exchange2(exchange1).buyTokens.value(ethAmount)(token); // Buys tokens back 207 | // } 208 | 209 | // } 210 | 211 | 212 | 213 | } 214 | } 215 | -------------------------------------------------------------------------------- /src/contracts/examples/LeveragedYieldFarm.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.5.12; 2 | pragma experimental ABIEncoderV2; 3 | 4 | interface Erc20 { 5 | function balanceOf(address account) external view returns (uint256); 6 | function approve(address, uint256) external returns (bool); 7 | function transfer(address, uint256) external returns (bool); 8 | } 9 | 10 | interface CErc20 { 11 | function balanceOf(address owner) external view returns (uint); 12 | function mint(uint256) external returns (uint256); 13 | function redeem(uint) external returns (uint); 14 | function redeemUnderlying(uint) external returns (uint); 15 | function borrowBalanceCurrent(address account) external returns (uint); 16 | function borrow(uint borrowAmount) external returns (uint); 17 | function repayBorrow(uint repayAmount) external returns (uint); 18 | } 19 | 20 | interface Comptroller { 21 | function enterMarkets(address[] calldata) 22 | external 23 | returns (uint256[] memory); 24 | 25 | function claimComp(address holder) external; 26 | 27 | } 28 | 29 | interface Structs { 30 | struct Val { 31 | uint256 value; 32 | } 33 | 34 | enum ActionType { 35 | Deposit, // supply tokens 36 | Withdraw, // borrow tokens 37 | Transfer, // transfer balance between accounts 38 | Buy, // buy an amount of some token (externally) 39 | Sell, // sell an amount of some token (externally) 40 | Trade, // trade tokens against another account 41 | Liquidate, // liquidate an undercollateralized or expiring account 42 | Vaporize, // use excess tokens to zero-out a completely negative account 43 | Call // send arbitrary data to an address 44 | } 45 | 46 | enum AssetDenomination { 47 | Wei // the amount is denominated in wei 48 | } 49 | 50 | enum AssetReference { 51 | Delta // the amount is given as a delta from the current value 52 | } 53 | 54 | struct AssetAmount { 55 | bool sign; // true if positive 56 | AssetDenomination denomination; 57 | AssetReference ref; 58 | uint256 value; 59 | } 60 | 61 | struct ActionArgs { 62 | ActionType actionType; 63 | uint256 accountId; 64 | AssetAmount amount; 65 | uint256 primaryMarketId; 66 | uint256 secondaryMarketId; 67 | address otherAddress; 68 | uint256 otherAccountId; 69 | bytes data; 70 | } 71 | 72 | struct Info { 73 | address owner; // The address that owns the account 74 | uint256 number; // A nonce that allows a single address to control many accounts 75 | } 76 | 77 | struct Wei { 78 | bool sign; // true if positive 79 | uint256 value; 80 | } 81 | } 82 | 83 | contract DyDxPool is Structs { 84 | function getAccountWei(Info memory account, uint256 marketId) public view returns (Wei memory); 85 | function operate(Info[] memory, ActionArgs[] memory) public; 86 | } 87 | 88 | pragma solidity ^0.5.0; 89 | 90 | 91 | /** 92 | * @dev Interface of the ERC20 standard as defined in the EIP. Does not include 93 | * the optional functions; to access them see `ERC20Detailed`. 94 | */ 95 | interface IERC20 { 96 | function balanceOf(address account) external view returns (uint256); 97 | 98 | function approve(address spender, uint256 amount) external returns (bool); 99 | } 100 | 101 | pragma solidity ^0.5.0; 102 | 103 | 104 | contract DyDxFlashLoan is Structs { 105 | DyDxPool pool = DyDxPool(0x1E0447b19BB6EcFdAe1e4AE1694b0C3659614e4e); 106 | 107 | address public WETH = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2; 108 | address public SAI = 0x89d24A6b4CcB1B6fAA2625fE562bDD9a23260359; 109 | address public USDC = 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48; 110 | address public DAI = 0x6B175474E89094C44Da98b954EedeAC495271d0F; 111 | mapping(address => uint256) public currencies; 112 | 113 | constructor() public { 114 | currencies[WETH] = 1; 115 | currencies[SAI] = 2; 116 | currencies[USDC] = 3; 117 | currencies[DAI] = 4; 118 | } 119 | 120 | modifier onlyPool() { 121 | require( 122 | msg.sender == address(pool), 123 | "FlashLoan: could be called by DyDx pool only" 124 | ); 125 | _; 126 | } 127 | 128 | function tokenToMarketId(address token) public view returns (uint256) { 129 | uint256 marketId = currencies[token]; 130 | require(marketId != 0, "FlashLoan: Unsupported token"); 131 | return marketId - 1; 132 | } 133 | 134 | // the DyDx will call `callFunction(address sender, Info memory accountInfo, bytes memory data) public` after during `operate` call 135 | function flashloan(address token, uint256 amount, bytes memory data) 136 | internal 137 | { 138 | IERC20(token).approve(address(pool), amount + 1); 139 | Info[] memory infos = new Info[](1); 140 | ActionArgs[] memory args = new ActionArgs[](3); 141 | 142 | infos[0] = Info(address(this), 0); 143 | 144 | AssetAmount memory wamt = AssetAmount( 145 | false, 146 | AssetDenomination.Wei, 147 | AssetReference.Delta, 148 | amount 149 | ); 150 | ActionArgs memory withdraw; 151 | withdraw.actionType = ActionType.Withdraw; 152 | withdraw.accountId = 0; 153 | withdraw.amount = wamt; 154 | withdraw.primaryMarketId = tokenToMarketId(token); 155 | withdraw.otherAddress = address(this); 156 | 157 | args[0] = withdraw; 158 | 159 | ActionArgs memory call; 160 | call.actionType = ActionType.Call; 161 | call.accountId = 0; 162 | call.otherAddress = address(this); 163 | call.data = data; 164 | 165 | args[1] = call; 166 | 167 | ActionArgs memory deposit; 168 | AssetAmount memory damt = AssetAmount( 169 | true, 170 | AssetDenomination.Wei, 171 | AssetReference.Delta, 172 | amount + 1 173 | ); 174 | deposit.actionType = ActionType.Deposit; 175 | deposit.accountId = 0; 176 | deposit.amount = damt; 177 | deposit.primaryMarketId = tokenToMarketId(token); 178 | deposit.otherAddress = address(this); 179 | 180 | args[2] = deposit; 181 | 182 | pool.operate(infos, args); 183 | } 184 | } 185 | 186 | pragma solidity ^0.5.0; 187 | 188 | contract LeveragedYieldFarm is DyDxFlashLoan { 189 | // Mainnet Dai 190 | // https://etherscan.io/address/0x6b175474e89094c44da98b954eedeac495271d0f#readContract 191 | address daiAddress = 0x6B175474E89094C44Da98b954EedeAC495271d0F; 192 | Erc20 dai = Erc20(daiAddress); 193 | 194 | // Mainnet cDai 195 | // https://etherscan.io/address/0x5d3a536e4d6dbd6114cc1ead35777bab948e3643#readProxyContract 196 | address cDaiAddress = 0x5d3a536E4D6DbD6114cc1Ead35777bAB948E3643; 197 | CErc20 cDai = CErc20(cDaiAddress); 198 | 199 | // Mainnet Comptroller 200 | // https://etherscan.io/address/0x3d9819210a31b4961b30ef54be2aed79b9c9cd3b#readProxyContract 201 | address comptrollerAddress = 0x3d9819210A31b4961b30EF54bE2aeD79B9c9Cd3B; 202 | Comptroller comptroller = Comptroller(comptrollerAddress); 203 | 204 | // COMP ERC-20 token 205 | // https://etherscan.io/token/0xc00e94cb662c3520282e6f5717214004a7f26888 206 | Erc20 compToken = Erc20(0xc00e94Cb662C3520282E6f5717214004A7f26888); 207 | 208 | // Deposit/Withdraw values 209 | bytes32 DEPOSIT = keccak256("DEPOSIT"); 210 | bytes32 WITHDRAW = keccak256("WITHDRAW"); 211 | 212 | // Contract owner 213 | address payable owner; 214 | 215 | event FlashLoan(address indexed _from, bytes32 indexed _id, uint _value); 216 | 217 | // Modifiers 218 | modifier onlyOwner() { 219 | require(msg.sender == owner, "caller is not the owner!"); 220 | _; 221 | } 222 | 223 | // Don't allow contract to receive Ether by mistake 224 | function() external payable { 225 | revert(); 226 | } 227 | 228 | constructor() public { 229 | // Track the contract owner 230 | owner = msg.sender; 231 | 232 | // Enter the cDai market so you can borrow another type of asset 233 | address[] memory cTokens = new address[](1); 234 | cTokens[0] = cDaiAddress; 235 | uint256[] memory errors = comptroller.enterMarkets(cTokens); 236 | if (errors[0] != 0) { 237 | revert("Comptroller.enterMarkets failed."); 238 | } 239 | } 240 | 241 | // Do not deposit all your DAI because you must pay flash loan fees 242 | // Always keep at least 1 DAI in the contract 243 | function depositDai(uint256 initialAmount) external onlyOwner returns (bool){ 244 | // Total deposit: 30% initial amount, 70% flash loan 245 | uint256 totalAmount = (initialAmount * 10) / 3; 246 | 247 | // loan is 70% of total deposit 248 | uint256 flashLoanAmount = totalAmount - initialAmount; 249 | 250 | // Get DAI Flash Loan for "DEPOSIT" 251 | bytes memory data = abi.encode(totalAmount, flashLoanAmount, DEPOSIT); 252 | flashloan(daiAddress, flashLoanAmount, data); // execution goes to `callFunction` 253 | 254 | // Handle remaining execution inside handleDeposit() function 255 | 256 | return true; 257 | } 258 | 259 | 260 | // You must have some Dai in your contract still to pay flash loan fee! 261 | // Always keep at least 1 DAI in the contract 262 | function withdrawDai(uint256 initialAmount) external onlyOwner returns (bool){ 263 | // Total deposit: 30% initial amount, 70% flash loan 264 | uint256 totalAmount = (initialAmount * 10) / 3; 265 | 266 | // loan is 70% of total deposit 267 | uint256 flashLoanAmount = totalAmount - initialAmount; 268 | 269 | // Use flash loan to payback borrowed amount 270 | bytes memory data = abi.encode(totalAmount, flashLoanAmount, WITHDRAW); 271 | flashloan(daiAddress, flashLoanAmount, data); // execution goes to `callFunction` 272 | 273 | // Handle repayment inside handleWithdraw() function 274 | 275 | // Claim COMP tokens 276 | comptroller.claimComp(address(this)); 277 | 278 | // Withdraw COMP tokens 279 | compToken.transfer(owner, compToken.balanceOf(address(this))); 280 | 281 | // Withdraw Dai to the wallet 282 | dai.transfer(owner, dai.balanceOf(address(this))); 283 | 284 | return true; 285 | } 286 | 287 | 288 | function callFunction( 289 | address, /* sender */ 290 | Info calldata, /* accountInfo */ 291 | bytes calldata data 292 | ) external onlyPool { 293 | (uint256 totalAmount, uint256 flashLoanAmount, bytes32 operation) = abi 294 | .decode(data, (uint256, uint256, bytes32)); 295 | 296 | if(operation == DEPOSIT) { 297 | handleDeposit(totalAmount, flashLoanAmount); 298 | } 299 | 300 | if(operation == WITHDRAW) { 301 | handleWithdraw(); 302 | } 303 | } 304 | 305 | // You must first send DAI to this contract before you can call this function 306 | function handleDeposit(uint256 totalAmount, uint256 flashLoanAmount) internal returns (bool) { 307 | // Approve Dai tokens as collateral 308 | dai.approve(cDaiAddress, totalAmount); 309 | 310 | // Provide collateral by minting cDai tokens 311 | cDai.mint(totalAmount); 312 | 313 | // Borrow Dai 314 | cDai.borrow(flashLoanAmount); 315 | 316 | // Start earning COMP tokens, yay! 317 | return true; 318 | } 319 | 320 | function handleWithdraw() internal returns (bool) { 321 | uint256 balance; 322 | 323 | // Get curent borrow Balance 324 | balance = cDai.borrowBalanceCurrent(address(this)); 325 | 326 | // Approve tokens for repayment 327 | dai.approve(address(cDai), balance); 328 | 329 | // Repay tokens 330 | cDai.repayBorrow(balance); 331 | 332 | // Get cDai balance 333 | balance = cDai.balanceOf(address(this)); 334 | 335 | // Redeem cDai 336 | cDai.redeem(balance); 337 | 338 | return true; 339 | } 340 | 341 | // Fallback in case any other tokens are sent to this contract 342 | function withdrawToken(address _tokenAddress) public onlyOwner { 343 | uint256 balance = Erc20(_tokenAddress).balanceOf(address(this)); 344 | Erc20(_tokenAddress).transfer(owner, balance); 345 | } 346 | 347 | } 348 | -------------------------------------------------------------------------------- /src/contracts/examples/SimpleArb.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.5.0; 2 | 3 | /******* 4 | * Pseudo-code 5 | ******/ 6 | 7 | interface Exchange1 { 8 | function sellTokens(address _token, uint _amount) external returns (uint amount); 9 | } 10 | 11 | interface Exchange2 { 12 | function buyTokens(address _token) external payable returns (uint amount); 13 | } 14 | 15 | interface ERC20 { 16 | function transfer(address _to, uint _value) external returns (bool success); 17 | function approve(address _spender, uint _value) external returns (bool success); 18 | } 19 | 20 | contract SimpleArb { 21 | address public exchange1 = 0x818E6FECD516Ecc3849DAf6845e3EC868087B755; // Kyber 22 | address public exchange2 = 0x09cabEC1eAd1c0Ba254B09efb3EE13841712bE14; // Uniswap 23 | address public token = 0x89d24A6b4CcB1B6fAA2625fE562bDD9a23260359; // Sai 24 | 25 | function arb() internal { 26 | uint amount = 10000000000000000000; // 100 tokens 27 | ERC20(token).approve(exchange1, amount); // Approve tokens 28 | uint ethAmount = Exchange1(exchange1).sellTokens(token, amount); // Sell Tokens for Ether 29 | Exchange2(exchange1).buyTokens.value(ethAmount)(token); // Sell Tokens for Ether 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/contracts/examples/traderExample.sol: -------------------------------------------------------------------------------- 1 | traderExample.sol 2 | //example contract address: https://etherscan.io/address/0x1d6cbd79054b89ade7d840d1640a949ea82b7639#code 3 | 4 | pragma solidity ^0.4.26; 5 | contract UniswapExchangeInterface { 6 | // Address of ERC20 token sold on this exchange 7 | function tokenAddress() external view returns (address token); 8 | // Address of Uniswap Factory 9 | function factoryAddress() external view returns (address factory); 10 | // Provide Liquidity 11 | function addLiquidity(uint256 min_liquidity, uint256 max_tokens, uint256 deadline) external payable returns (uint256); 12 | function removeLiquidity(uint256 amount, uint256 min_eth, uint256 min_tokens, uint256 deadline) external returns (uint256, uint256); 13 | // Get Prices 14 | function getEthToTokenInputPrice(uint256 eth_sold) external view returns (uint256 tokens_bought); 15 | function getEthToTokenOutputPrice(uint256 tokens_bought) external view returns (uint256 eth_sold); 16 | function getTokenToEthInputPrice(uint256 tokens_sold) external view returns (uint256 eth_bought); 17 | function getTokenToEthOutputPrice(uint256 eth_bought) external view returns (uint256 tokens_sold); 18 | // Trade ETH to ERC20 19 | function ethToTokenSwapInput(uint256 min_tokens, uint256 deadline) external payable returns (uint256 tokens_bought); 20 | function ethToTokenTransferInput(uint256 min_tokens, uint256 deadline, address recipient) external payable returns (uint256 tokens_bought); 21 | function ethToTokenSwapOutput(uint256 tokens_bought, uint256 deadline) external payable returns (uint256 eth_sold); 22 | function ethToTokenTransferOutput(uint256 tokens_bought, uint256 deadline, address recipient) external payable returns (uint256 eth_sold); 23 | // Trade ERC20 to ETH 24 | function tokenToEthSwapInput(uint256 tokens_sold, uint256 min_eth, uint256 deadline) external returns (uint256 eth_bought); 25 | function tokenToEthTransferInput(uint256 tokens_sold, uint256 min_eth, uint256 deadline, address recipient) external returns (uint256 eth_bought); 26 | function tokenToEthSwapOutput(uint256 eth_bought, uint256 max_tokens, uint256 deadline) external returns (uint256 tokens_sold); 27 | function tokenToEthTransferOutput(uint256 eth_bought, uint256 max_tokens, uint256 deadline, address recipient) external returns (uint256 tokens_sold); 28 | // Trade ERC20 to ERC20 29 | function tokenToTokenSwapInput(uint256 tokens_sold, uint256 min_tokens_bought, uint256 min_eth_bought, uint256 deadline, address token_addr) external returns (uint256 tokens_bought); 30 | function tokenToTokenTransferInput(uint256 tokens_sold, uint256 min_tokens_bought, uint256 min_eth_bought, uint256 deadline, address recipient, address token_addr) external returns (uint256 tokens_bought); 31 | function tokenToTokenSwapOutput(uint256 tokens_bought, uint256 max_tokens_sold, uint256 max_eth_sold, uint256 deadline, address token_addr) external returns (uint256 tokens_sold); 32 | function tokenToTokenTransferOutput(uint256 tokens_bought, uint256 max_tokens_sold, uint256 max_eth_sold, uint256 deadline, address recipient, address token_addr) external returns (uint256 tokens_sold); 33 | // Trade ERC20 to Custom Pool 34 | function tokenToExchangeSwapInput(uint256 tokens_sold, uint256 min_tokens_bought, uint256 min_eth_bought, uint256 deadline, address exchange_addr) external returns (uint256 tokens_bought); 35 | function tokenToExchangeTransferInput(uint256 tokens_sold, uint256 min_tokens_bought, uint256 min_eth_bought, uint256 deadline, address recipient, address exchange_addr) external returns (uint256 tokens_bought); 36 | function tokenToExchangeSwapOutput(uint256 tokens_bought, uint256 max_tokens_sold, uint256 max_eth_sold, uint256 deadline, address exchange_addr) external returns (uint256 tokens_sold); 37 | function tokenToExchangeTransferOutput(uint256 tokens_bought, uint256 max_tokens_sold, uint256 max_eth_sold, uint256 deadline, address recipient, address exchange_addr) external returns (uint256 tokens_sold); 38 | // ERC20 comaptibility for liquidity tokens 39 | bytes32 public name; 40 | bytes32 public symbol; 41 | uint256 public decimals; 42 | function transfer(address _to, uint256 _value) external returns (bool); 43 | function transferFrom(address _from, address _to, uint256 value) external returns (bool); 44 | function approve(address _spender, uint256 _value) external returns (bool); 45 | function allowance(address _owner, address _spender) external view returns (uint256); 46 | function balanceOf(address _owner) external view returns (uint256); 47 | function totalSupply() external view returns (uint256); 48 | // Never use 49 | function setup(address token_addr) external; 50 | } 51 | 52 | interface ERC20 { 53 | function totalSupply() public view returns (uint supply); 54 | function balanceOf(address _owner) public view returns (uint balance); 55 | function transfer(address _to, uint _value) public returns (bool success); 56 | function transferFrom(address _from, address _to, uint _value) public returns (bool success); 57 | function approve(address _spender, uint _value) public returns (bool success); 58 | function allowance(address _owner, address _spender) public view returns (uint remaining); 59 | function decimals() public view returns(uint digits); 60 | event Approval(address indexed _owner, address indexed _spender, uint _value); 61 | } 62 | 63 | interface OrFeedInterface { 64 | function getExchangeRate ( string fromSymbol, string toSymbol, string venue, uint256 amount ) external view returns ( uint256 ); 65 | function getTokenDecimalCount ( address tokenAddress ) external view returns ( uint256 ); 66 | function getTokenAddress ( string symbol ) external view returns ( address ); 67 | function getSynthBytes32 ( string symbol ) external view returns ( bytes32 ); 68 | function getForexAddress ( string symbol ) external view returns ( address ); 69 | } 70 | 71 | 72 | 73 | contract UniswapTradeExample{ 74 | 75 | function buyDai() payable returns(uint256){ 76 | 77 | //token we are buying contract address... this this case DAI 78 | address daiAddress = 0x09cabEC1eAd1c0Ba254B09efb3EE13841712bE14; 79 | //Define Uniswap 80 | UniswapExchangeInterface usi = UniswapExchangeInterface(daiAddress); 81 | 82 | //amoutn of ether sent to this contract 83 | uint256 amountEth = msg.value; 84 | 85 | uint256 amountBack = usi.ethToTokenSwapInput.value(amountEth)(1, block.timestamp); 86 | 87 | ERC20 daiToken = ERC20(0x89d24a6b4ccb1b6faa2625fe562bdd9a23260359); 88 | daiToken.transfer(msg.sender, amountBack); 89 | return amountBack; 90 | 91 | 92 | } 93 | 94 | function getDAIPrice() constant returns(uint256){ 95 | OrFeedInterface orfeed= OrFeedInterface(0x8316b082621cfedab95bf4a44a1d4b64a6ffc336); 96 | uint256 ethPrice = orfeed.getExchangeRate("ETH", "USD", "", 100000000); 97 | return ethPrice; 98 | } 99 | 100 | 101 | 102 | } -------------------------------------------------------------------------------- /src/examples/checkPair.js: -------------------------------------------------------------------------------- 1 | const moment = require('moment-timezone'); 2 | const { uniswapFactoryContract } = require("./exchange/uniswap"); 3 | const { kyberRateContract } = require("./exchange/kyber"); 4 | const { uniswapV2Factory } = require('./exchange/uniswapv2'); 5 | const web3 = require('web3') 6 | 7 | 8 | async function checkPair(args) { 9 | const { inputTokenSymbol, inputTokenAddress, outputTokenSymbol, outputTokenAddress, inputAmount } = args; 10 | const uniswapV2 = uniswapV2Factory({ symbol: inputTokenSymbol, address: inputTokenAddress }, 11 | { symbol: outputTokenSymbol, address: outputTokenAddress }); 12 | const uv2Value = await uniswapV2.getPrice(); 13 | const uv2slippageRate = 0.005; 14 | // const exchangeAddress = await uniswapFactoryContract.methods.getExchange(outputTokenAddress).call(); 15 | // const uniswap = new web3.eth.Contract(UNISWAP_EXCHANGE_ABI, exchangeAddress); 16 | // const uniswapResult = await uniswap.methods.getEthToTokenInputPrice(inputAmount).call(); 17 | let kyberResult = await kyberRateContract.methods.getExpectedRate(inputTokenAddress, outputTokenAddress, inputAmount, true).call(); 18 | 19 | const uniswapMinReturn = uv2Value.mid - (uv2Value.mid * uv2slippageRate); 20 | const khyberMinReturn = web3.utils.fromWei(kyberResult.slippageRate, 'Ether'); 21 | console.table([{ 22 | 'Input Token': inputTokenSymbol, 23 | 'Output Token': outputTokenSymbol, 24 | 'Input Amount': web3.utils.fromWei(inputAmount, 'Ether'), 25 | // 'Uniswap Return': uv2Value.mid, 26 | 'Uniswap Min Return': uniswapMinReturn, 27 | // 'Kyber Expected Rate': web3.utils.fromWei(kyberResult.expectedRate, 'Ether'), 28 | 'Kyber Min Return': khyberMinReturn, 29 | 'EXPECTED RETURN': (uniswapMinReturn - khyberMinReturn) / (1/uv2Value.midverse) , 30 | 'Timestamp': moment().tz('America/Chicago').format(), 31 | }]); 32 | } 33 | module.exports = {checkPair}; -------------------------------------------------------------------------------- /src/examples/coins/erc20.js: -------------------------------------------------------------------------------- 1 | const ETH = { 2 | symbol:"ETH", 3 | address:'0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee' 4 | } 5 | const WETH = { 6 | symbol:"WETH", 7 | address:'0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2' 8 | } 9 | const DAI = { 10 | symbol:"DAI", 11 | address:'0x6b175474e89094c44da98b954eedeac495271d0f' 12 | } 13 | 14 | const WBTC = { 15 | symbol: 'WBTC', 16 | address: '0x2260fac5e5542a773aa44fbcfedf7c193bc2c599' 17 | } 18 | 19 | const MKR = { 20 | symbol: 'MKR', 21 | address: '0x9f8f72aa9304c8b593d555f12ef6589cc3a579a2', 22 | } 23 | 24 | const KNC = { 25 | symbol: 'KNC', 26 | address: '0xdd974d5c2e2928dea5f71b9825b8b646686bd200' 27 | } 28 | 29 | const LINK = { 30 | symbol: 'LINK', 31 | address: '0x514910771af9ca656af840dff83e8264ecf986ca' 32 | } 33 | 34 | const AMPL = { 35 | symbol: 'AMPL', 36 | address: '0xd46ba6d942050d489dbd938a2c909a5d5039a161' 37 | } 38 | 39 | const erc20s = { 40 | ETH, 41 | WETH, 42 | DAI, 43 | MKR, 44 | KNC, 45 | LINK, 46 | AMPL, 47 | WBTC 48 | } 49 | 50 | function forTokens(inputToken, outputToken, inputAmount){ 51 | let input = erc20s[inputToken]; 52 | let output = erc20s[outputToken]; 53 | return { 54 | inputTokenSymbol: input.symbol, 55 | inputTokenAddress: input.address, 56 | outputTokenSymbol: output.symbol, 57 | outputTokenAddress: output.address, 58 | inputAmount: inputAmount 59 | }; 60 | } 61 | 62 | module.exports = {forTokens}; //exports.forTokens = forTokens; -------------------------------------------------------------------------------- /src/examples/exchange/kyber.js: -------------------------------------------------------------------------------- 1 | const Web3 = require('web3'); 2 | const web3 = new Web3(process.env.RPC_URL); 3 | // Kyber mainnet "Expected Rate": https://etherscan.io/address/0x96b610046d63638d970e6243151311d8827d69a5#readContract 4 | const KYBER_RATE_ABI = [{ "constant": false, "inputs": [{ "name": "alerter", "type": "address" }], "name": "removeAlerter", "outputs": [], "payable": false, "stateMutability": "nonpayable", "type": "function" }, { "constant": true, "inputs": [], "name": "pendingAdmin", "outputs": [{ "name": "", "type": "address" }], "payable": false, "stateMutability": "view", "type": "function" }, { "constant": true, "inputs": [], "name": "getOperators", "outputs": [{ "name": "", "type": "address[]" }], "payable": false, "stateMutability": "view", "type": "function" }, { "constant": false, "inputs": [{ "name": "token", "type": "address" }, { "name": "amount", "type": "uint256" }, { "name": "sendTo", "type": "address" }], "name": "withdrawToken", "outputs": [], "payable": false, "stateMutability": "nonpayable", "type": "function" }, { "constant": false, "inputs": [{ "name": "newAlerter", "type": "address" }], "name": "addAlerter", "outputs": [], "payable": false, "stateMutability": "nonpayable", "type": "function" }, { "constant": false, "inputs": [{ "name": "newAdmin", "type": "address" }], "name": "transferAdmin", "outputs": [], "payable": false, "stateMutability": "nonpayable", "type": "function" }, { "constant": false, "inputs": [{ "name": "newFactor", "type": "uint256" }], "name": "setQuantityFactor", "outputs": [], "payable": false, "stateMutability": "nonpayable", "type": "function" }, { "constant": false, "inputs": [], "name": "claimAdmin", "outputs": [], "payable": false, "stateMutability": "nonpayable", "type": "function" }, { "constant": false, "inputs": [{ "name": "newAdmin", "type": "address" }], "name": "transferAdminQuickly", "outputs": [], "payable": false, "stateMutability": "nonpayable", "type": "function" }, { "constant": true, "inputs": [], "name": "getAlerters", "outputs": [{ "name": "", "type": "address[]" }], "payable": false, "stateMutability": "view", "type": "function" }, { "constant": false, "inputs": [{ "name": "newOperator", "type": "address" }], "name": "addOperator", "outputs": [], "payable": false, "stateMutability": "nonpayable", "type": "function" }, { "constant": true, "inputs": [], "name": "worstCaseRateFactorInBps", "outputs": [{ "name": "", "type": "uint256" }], "payable": false, "stateMutability": "view", "type": "function" }, { "constant": true, "inputs": [], "name": "quantityFactor", "outputs": [{ "name": "", "type": "uint256" }], "payable": false, "stateMutability": "view", "type": "function" }, { "constant": false, "inputs": [{ "name": "operator", "type": "address" }], "name": "removeOperator", "outputs": [], "payable": false, "stateMutability": "nonpayable", "type": "function" }, { "constant": true, "inputs": [], "name": "kyberNetwork", "outputs": [{ "name": "", "type": "address" }], "payable": false, "stateMutability": "view", "type": "function" }, { "constant": false, "inputs": [{ "name": "amount", "type": "uint256" }, { "name": "sendTo", "type": "address" }], "name": "withdrawEther", "outputs": [], "payable": false, "stateMutability": "nonpayable", "type": "function" }, { "constant": true, "inputs": [{ "name": "src", "type": "address" }, { "name": "dest", "type": "address" }, { "name": "srcQty", "type": "uint256" }, { "name": "usePermissionless", "type": "bool" }], "name": "getExpectedRate", "outputs": [{ "name": "expectedRate", "type": "uint256" }, { "name": "slippageRate", "type": "uint256" }], "payable": false, "stateMutability": "view", "type": "function" }, { "constant": true, "inputs": [{ "name": "token", "type": "address" }, { "name": "user", "type": "address" }], "name": "getBalance", "outputs": [{ "name": "", "type": "uint256" }], "payable": false, "stateMutability": "view", "type": "function" }, { "constant": false, "inputs": [{ "name": "bps", "type": "uint256" }], "name": "setWorstCaseRateFactor", "outputs": [], "payable": false, "stateMutability": "nonpayable", "type": "function" }, { "constant": true, "inputs": [], "name": "admin", "outputs": [{ "name": "", "type": "address" }], "payable": false, "stateMutability": "view", "type": "function" }, { "inputs": [{ "name": "_kyberNetwork", "type": "address" }, { "name": "_admin", "type": "address" }], "payable": false, "stateMutability": "nonpayable", "type": "constructor" }, { "anonymous": false, "inputs": [{ "indexed": false, "name": "newFactor", "type": "uint256" }, { "indexed": false, "name": "oldFactor", "type": "uint256" }, { "indexed": false, "name": "sender", "type": "address" }], "name": "QuantityFactorSet", "type": "event" }, { "anonymous": false, "inputs": [{ "indexed": false, "name": "newMin", "type": "uint256" }, { "indexed": false, "name": "oldMin", "type": "uint256" }, { "indexed": false, "name": "sender", "type": "address" }], "name": "MinSlippageFactorSet", "type": "event" }, { "anonymous": false, "inputs": [{ "indexed": false, "name": "token", "type": "address" }, { "indexed": false, "name": "amount", "type": "uint256" }, { "indexed": false, "name": "sendTo", "type": "address" }], "name": "TokenWithdraw", "type": "event" }, { "anonymous": false, "inputs": [{ "indexed": false, "name": "amount", "type": "uint256" }, { "indexed": false, "name": "sendTo", "type": "address" }], "name": "EtherWithdraw", "type": "event" }, { "anonymous": false, "inputs": [{ "indexed": false, "name": "pendingAdmin", "type": "address" }], "name": "TransferAdminPending", "type": "event" }, { "anonymous": false, "inputs": [{ "indexed": false, "name": "newAdmin", "type": "address" }, { "indexed": false, "name": "previousAdmin", "type": "address" }], "name": "AdminClaimed", "type": "event" }, { "anonymous": false, "inputs": [{ "indexed": false, "name": "newAlerter", "type": "address" }, { "indexed": false, "name": "isAdd", "type": "bool" }], "name": "AlerterAdded", "type": "event" }, { "anonymous": false, "inputs": [{ "indexed": false, "name": "newOperator", "type": "address" }, { "indexed": false, "name": "isAdd", "type": "bool" }], "name": "OperatorAdded", "type": "event" }]; 5 | const KYBER_RATE_ADDRESS = '0x96b610046d63638d970e6243151311d8827d69a5'; 6 | const kyberRateContract = new web3.eth.Contract(KYBER_RATE_ABI, KYBER_RATE_ADDRESS); 7 | exports.kyberRateContract = kyberRateContract; 8 | -------------------------------------------------------------------------------- /src/examples/exchange/uniswap.js: -------------------------------------------------------------------------------- 1 | const Web3 = require('web3'); 2 | const web3 = new Web3(process.env.RPC_URL) 3 | 4 | const UNISWAP_FACTORY_ABI = [{"name":"NewExchange","inputs":[{"type":"address","name":"token","indexed":true},{"type":"address","name":"exchange","indexed":true}],"anonymous":false,"type":"event"},{"name":"initializeFactory","outputs":[],"inputs":[{"type":"address","name":"template"}],"constant":false,"payable":false,"type":"function","gas":35725},{"name":"createExchange","outputs":[{"type":"address","name":"out"}],"inputs":[{"type":"address","name":"token"}],"constant":false,"payable":false,"type":"function","gas":187911},{"name":"getExchange","outputs":[{"type":"address","name":"out"}],"inputs":[{"type":"address","name":"token"}],"constant":true,"payable":false,"type":"function","gas":715},{"name":"getToken","outputs":[{"type":"address","name":"out"}],"inputs":[{"type":"address","name":"exchange"}],"constant":true,"payable":false,"type":"function","gas":745},{"name":"getTokenWithId","outputs":[{"type":"address","name":"out"}],"inputs":[{"type":"uint256","name":"token_id"}],"constant":true,"payable":false,"type":"function","gas":736},{"name":"exchangeTemplate","outputs":[{"type":"address","name":"out"}],"inputs":[],"constant":true,"payable":false,"type":"function","gas":633},{"name":"tokenCount","outputs":[{"type":"uint256","name":"out"}],"inputs":[],"constant":true,"payable":false,"type":"function","gas":663}] 5 | // https://ropsten.etherscan.io/address/0x9c83dCE8CA20E9aAF9D3efc003b2ea62aBC08351#code 6 | // const UNISWAP_FACTORY_ADDRESS = '0x9c83dCE8CA20E9aAF9D3efc003b2ea62aBC08351' // ropsten 7 | const UNISWAP_FACTORY_ADDRESS = '0xc0a47dfe034b400b47bdad5fecda2621de6c4d95' // mainnet 8 | const uniswapFactoryContract = new web3.eth.Contract(UNISWAP_FACTORY_ABI, UNISWAP_FACTORY_ADDRESS); 9 | // Uniswap Exchange Template: https://etherscan.io/address/0x09cabec1ead1c0ba254b09efb3ee13841712be14#code 10 | const UNISWAP_EXCHANGE_ABI = [{ "name": "TokenPurchase", "inputs": [{ "type": "address", "name": "buyer", "indexed": true }, { "type": "uint256", "name": "eth_sold", "indexed": true }, { "type": "uint256", "name": "tokens_bought", "indexed": true }], "anonymous": false, "type": "event" }, { "name": "EthPurchase", "inputs": [{ "type": "address", "name": "buyer", "indexed": true }, { "type": "uint256", "name": "tokens_sold", "indexed": true }, { "type": "uint256", "name": "eth_bought", "indexed": true }], "anonymous": false, "type": "event" }, { "name": "AddLiquidity", "inputs": [{ "type": "address", "name": "provider", "indexed": true }, { "type": "uint256", "name": "eth_amount", "indexed": true }, { "type": "uint256", "name": "token_amount", "indexed": true }], "anonymous": false, "type": "event" }, { "name": "RemoveLiquidity", "inputs": [{ "type": "address", "name": "provider", "indexed": true }, { "type": "uint256", "name": "eth_amount", "indexed": true }, { "type": "uint256", "name": "token_amount", "indexed": true }], "anonymous": false, "type": "event" }, { "name": "Transfer", "inputs": [{ "type": "address", "name": "_from", "indexed": true }, { "type": "address", "name": "_to", "indexed": true }, { "type": "uint256", "name": "_value", "indexed": false }], "anonymous": false, "type": "event" }, { "name": "Approval", "inputs": [{ "type": "address", "name": "_owner", "indexed": true }, { "type": "address", "name": "_spender", "indexed": true }, { "type": "uint256", "name": "_value", "indexed": false }], "anonymous": false, "type": "event" }, { "name": "setup", "outputs": [], "inputs": [{ "type": "address", "name": "token_addr" }], "constant": false, "payable": false, "type": "function", "gas": 175875 }, { "name": "addLiquidity", "outputs": [{ "type": "uint256", "name": "out" }], "inputs": [{ "type": "uint256", "name": "min_liquidity" }, { "type": "uint256", "name": "max_tokens" }, { "type": "uint256", "name": "deadline" }], "constant": false, "payable": true, "type": "function", "gas": 82616 }, { "name": "removeLiquidity", "outputs": [{ "type": "uint256", "name": "out" }, { "type": "uint256", "name": "out" }], "inputs": [{ "type": "uint256", "name": "amount" }, { "type": "uint256", "name": "min_eth" }, { "type": "uint256", "name": "min_tokens" }, { "type": "uint256", "name": "deadline" }], "constant": false, "payable": false, "type": "function", "gas": 116814 }, { "name": "__default__", "outputs": [], "inputs": [], "constant": false, "payable": true, "type": "function" }, { "name": "ethToTokenSwapInput", "outputs": [{ "type": "uint256", "name": "out" }], "inputs": [{ "type": "uint256", "name": "min_tokens" }, { "type": "uint256", "name": "deadline" }], "constant": false, "payable": true, "type": "function", "gas": 12757 }, { "name": "ethToTokenTransferInput", "outputs": [{ "type": "uint256", "name": "out" }], "inputs": [{ "type": "uint256", "name": "min_tokens" }, { "type": "uint256", "name": "deadline" }, { "type": "address", "name": "recipient" }], "constant": false, "payable": true, "type": "function", "gas": 12965 }, { "name": "ethToTokenSwapOutput", "outputs": [{ "type": "uint256", "name": "out" }], "inputs": [{ "type": "uint256", "name": "tokens_bought" }, { "type": "uint256", "name": "deadline" }], "constant": false, "payable": true, "type": "function", "gas": 50463 }, { "name": "ethToTokenTransferOutput", "outputs": [{ "type": "uint256", "name": "out" }], "inputs": [{ "type": "uint256", "name": "tokens_bought" }, { "type": "uint256", "name": "deadline" }, { "type": "address", "name": "recipient" }], "constant": false, "payable": true, "type": "function", "gas": 50671 }, { "name": "tokenToEthSwapInput", "outputs": [{ "type": "uint256", "name": "out" }], "inputs": [{ "type": "uint256", "name": "tokens_sold" }, { "type": "uint256", "name": "min_eth" }, { "type": "uint256", "name": "deadline" }], "constant": false, "payable": false, "type": "function", "gas": 47503 }, { "name": "tokenToEthTransferInput", "outputs": [{ "type": "uint256", "name": "out" }], "inputs": [{ "type": "uint256", "name": "tokens_sold" }, { "type": "uint256", "name": "min_eth" }, { "type": "uint256", "name": "deadline" }, { "type": "address", "name": "recipient" }], "constant": false, "payable": false, "type": "function", "gas": 47712 }, { "name": "tokenToEthSwapOutput", "outputs": [{ "type": "uint256", "name": "out" }], "inputs": [{ "type": "uint256", "name": "eth_bought" }, { "type": "uint256", "name": "max_tokens" }, { "type": "uint256", "name": "deadline" }], "constant": false, "payable": false, "type": "function", "gas": 50175 }, { "name": "tokenToEthTransferOutput", "outputs": [{ "type": "uint256", "name": "out" }], "inputs": [{ "type": "uint256", "name": "eth_bought" }, { "type": "uint256", "name": "max_tokens" }, { "type": "uint256", "name": "deadline" }, { "type": "address", "name": "recipient" }], "constant": false, "payable": false, "type": "function", "gas": 50384 }, { "name": "tokenToTokenSwapInput", "outputs": [{ "type": "uint256", "name": "out" }], "inputs": [{ "type": "uint256", "name": "tokens_sold" }, { "type": "uint256", "name": "min_tokens_bought" }, { "type": "uint256", "name": "min_eth_bought" }, { "type": "uint256", "name": "deadline" }, { "type": "address", "name": "token_addr" }], "constant": false, "payable": false, "type": "function", "gas": 51007 }, { "name": "tokenToTokenTransferInput", "outputs": [{ "type": "uint256", "name": "out" }], "inputs": [{ "type": "uint256", "name": "tokens_sold" }, { "type": "uint256", "name": "min_tokens_bought" }, { "type": "uint256", "name": "min_eth_bought" }, { "type": "uint256", "name": "deadline" }, { "type": "address", "name": "recipient" }, { "type": "address", "name": "token_addr" }], "constant": false, "payable": false, "type": "function", "gas": 51098 }, { "name": "tokenToTokenSwapOutput", "outputs": [{ "type": "uint256", "name": "out" }], "inputs": [{ "type": "uint256", "name": "tokens_bought" }, { "type": "uint256", "name": "max_tokens_sold" }, { "type": "uint256", "name": "max_eth_sold" }, { "type": "uint256", "name": "deadline" }, { "type": "address", "name": "token_addr" }], "constant": false, "payable": false, "type": "function", "gas": 54928 }, { "name": "tokenToTokenTransferOutput", "outputs": [{ "type": "uint256", "name": "out" }], "inputs": [{ "type": "uint256", "name": "tokens_bought" }, { "type": "uint256", "name": "max_tokens_sold" }, { "type": "uint256", "name": "max_eth_sold" }, { "type": "uint256", "name": "deadline" }, { "type": "address", "name": "recipient" }, { "type": "address", "name": "token_addr" }], "constant": false, "payable": false, "type": "function", "gas": 55019 }, { "name": "tokenToExchangeSwapInput", "outputs": [{ "type": "uint256", "name": "out" }], "inputs": [{ "type": "uint256", "name": "tokens_sold" }, { "type": "uint256", "name": "min_tokens_bought" }, { "type": "uint256", "name": "min_eth_bought" }, { "type": "uint256", "name": "deadline" }, { "type": "address", "name": "exchange_addr" }], "constant": false, "payable": false, "type": "function", "gas": 49342 }, { "name": "tokenToExchangeTransferInput", "outputs": [{ "type": "uint256", "name": "out" }], "inputs": [{ "type": "uint256", "name": "tokens_sold" }, { "type": "uint256", "name": "min_tokens_bought" }, { "type": "uint256", "name": "min_eth_bought" }, { "type": "uint256", "name": "deadline" }, { "type": "address", "name": "recipient" }, { "type": "address", "name": "exchange_addr" }], "constant": false, "payable": false, "type": "function", "gas": 49532 }, { "name": "tokenToExchangeSwapOutput", "outputs": [{ "type": "uint256", "name": "out" }], "inputs": [{ "type": "uint256", "name": "tokens_bought" }, { "type": "uint256", "name": "max_tokens_sold" }, { "type": "uint256", "name": "max_eth_sold" }, { "type": "uint256", "name": "deadline" }, { "type": "address", "name": "exchange_addr" }], "constant": false, "payable": false, "type": "function", "gas": 53233 }, { "name": "tokenToExchangeTransferOutput", "outputs": [{ "type": "uint256", "name": "out" }], "inputs": [{ "type": "uint256", "name": "tokens_bought" }, { "type": "uint256", "name": "max_tokens_sold" }, { "type": "uint256", "name": "max_eth_sold" }, { "type": "uint256", "name": "deadline" }, { "type": "address", "name": "recipient" }, { "type": "address", "name": "exchange_addr" }], "constant": false, "payable": false, "type": "function", "gas": 53423 }, { "name": "getEthToTokenInputPrice", "outputs": [{ "type": "uint256", "name": "out" }], "inputs": [{ "type": "uint256", "name": "eth_sold" }], "constant": true, "payable": false, "type": "function", "gas": 5542 }, { "name": "getEthToTokenOutputPrice", "outputs": [{ "type": "uint256", "name": "out" }], "inputs": [{ "type": "uint256", "name": "tokens_bought" }], "constant": true, "payable": false, "type": "function", "gas": 6872 }, { "name": "getTokenToEthInputPrice", "outputs": [{ "type": "uint256", "name": "out" }], "inputs": [{ "type": "uint256", "name": "tokens_sold" }], "constant": true, "payable": false, "type": "function", "gas": 5637 }, { "name": "getTokenToEthOutputPrice", "outputs": [{ "type": "uint256", "name": "out" }], "inputs": [{ "type": "uint256", "name": "eth_bought" }], "constant": true, "payable": false, "type": "function", "gas": 6897 }, { "name": "tokenAddress", "outputs": [{ "type": "address", "name": "out" }], "inputs": [], "constant": true, "payable": false, "type": "function", "gas": 1413 }, { "name": "factoryAddress", "outputs": [{ "type": "address", "name": "out" }], "inputs": [], "constant": true, "payable": false, "type": "function", "gas": 1443 }, { "name": "balanceOf", "outputs": [{ "type": "uint256", "name": "out" }], "inputs": [{ "type": "address", "name": "_owner" }], "constant": true, "payable": false, "type": "function", "gas": 1645 }, { "name": "transfer", "outputs": [{ "type": "bool", "name": "out" }], "inputs": [{ "type": "address", "name": "_to" }, { "type": "uint256", "name": "_value" }], "constant": false, "payable": false, "type": "function", "gas": 75034 }, { "name": "transferFrom", "outputs": [{ "type": "bool", "name": "out" }], "inputs": [{ "type": "address", "name": "_from" }, { "type": "address", "name": "_to" }, { "type": "uint256", "name": "_value" }], "constant": false, "payable": false, "type": "function", "gas": 110907 }, { "name": "approve", "outputs": [{ "type": "bool", "name": "out" }], "inputs": [{ "type": "address", "name": "_spender" }, { "type": "uint256", "name": "_value" }], "constant": false, "payable": false, "type": "function", "gas": 38769 }, { "name": "allowance", "outputs": [{ "type": "uint256", "name": "out" }], "inputs": [{ "type": "address", "name": "_owner" }, { "type": "address", "name": "_spender" }], "constant": true, "payable": false, "type": "function", "gas": 1925 }, { "name": "name", "outputs": [{ "type": "bytes32", "name": "out" }], "inputs": [], "constant": true, "payable": false, "type": "function", "gas": 1623 }, { "name": "symbol", "outputs": [{ "type": "bytes32", "name": "out" }], "inputs": [], "constant": true, "payable": false, "type": "function", "gas": 1653 }, { "name": "decimals", "outputs": [{ "type": "uint256", "name": "out" }], "inputs": [], "constant": true, "payable": false, "type": "function", "gas": 1683 }, { "name": "totalSupply", "outputs": [{ "type": "uint256", "name": "out" }], "inputs": [], "constant": true, "payable": false, "type": "function", "gas": 1713 }]; 11 | 12 | // async function getUniswapExchangeContract(outputTokenAddress){ 13 | // const exchangeAddress = await uniswapFactoryContract.methods.getExchange(outputTokenAddress).call() 14 | // const exchangeContract = new web3.eth.Contract(UNISWAP_EXCHANGE_ABI, exchangeAddress); 15 | // return exchangeContract; 16 | // } 17 | 18 | exports.UNISWAP_EXCHANGE_ABI = UNISWAP_EXCHANGE_ABI; 19 | exports.uniswapFactoryContract = uniswapFactoryContract; 20 | // exports.uniswapExchangeContract = getUniswapExchangeContract; -------------------------------------------------------------------------------- /src/examples/exchange/uniswapv2.js: -------------------------------------------------------------------------------- 1 | const { ChainId, Token, Fetcher, Route } = require('@uniswap/sdk'); 2 | 3 | 4 | function uniswapV2Factory (token1, token2) { 5 | return { 6 | getPrice: async () => { 7 | const t1 = await Fetcher.fetchTokenData(ChainId.MAINNET, token1.address); 8 | const t2 = await Fetcher.fetchTokenData(ChainId.MAINNET, token2.address); 9 | 10 | const pair = await Fetcher.fetchPairData(t1, t2) 11 | const route = new Route([pair], t1) 12 | const mid = route.midPrice.toSignificant(6); 13 | const midverse = route.midPrice.invert().toSignificant(6) 14 | return { 15 | mid, 16 | midverse 17 | } 18 | } 19 | } 20 | } 21 | 22 | module.exports = {uniswapV2Factory}; -------------------------------------------------------------------------------- /src/examples/price-bot.js: -------------------------------------------------------------------------------- 1 | require('dotenv').config() 2 | require('console.table') 3 | const express = require('express') 4 | const bodyParser = require('body-parser') 5 | const http = require('http') 6 | const Web3 = require('web3') 7 | const HDWalletProvider = require('@truffle/hdwallet-provider') 8 | const moment = require('moment-timezone') 9 | const numeral = require('numeral') 10 | const _ = require('lodash') 11 | const axios = require('axios') 12 | const { uniswapFactoryContract, UNISWAP_EXCHANGE_ABI } = require("./exchange/uniswap") 13 | const { kyberRateContract } = require("./exchange/kyber"); 14 | const {forTokens} = require('./coins/erc20'); 15 | 16 | // SERVER CONFIG 17 | const PORT = process.env.PORT || 5000 18 | const app = express(); 19 | const server = http.createServer(app).listen(PORT, () => console.log(`Listening on ${ PORT }`)) 20 | 21 | // WEB3 CONFIG 22 | const web3 = new Web3(process.env.RPC_URL) 23 | exports.web3 = web3 24 | 25 | async function checkPair(args) { 26 | const { inputTokenSymbol, inputTokenAddress, outputTokenSymbol, outputTokenAddress, inputAmount } = args 27 | 28 | const exchangeAddress = await uniswapFactoryContract.methods.getExchange(outputTokenAddress).call() 29 | const uniswap = new web3.eth.Contract(UNISWAP_EXCHANGE_ABI, exchangeAddress); 30 | 31 | const uniswapResult = await uniswap.methods.getEthToTokenInputPrice(inputAmount).call() 32 | let kyberResult = await kyberRateContract.methods.getExpectedRate(inputTokenAddress, outputTokenAddress, inputAmount, true).call() 33 | 34 | console.table([{ 35 | 'Input Token': inputTokenSymbol, 36 | 'Output Token': outputTokenSymbol, 37 | 'Input Amount': web3.utils.fromWei(inputAmount, 'Ether'), 38 | 'Uniswap Return': web3.utils.fromWei(uniswapResult, 'Ether'), 39 | 'Kyber Expected Rate': web3.utils.fromWei(kyberResult.expectedRate, 'Ether'), 40 | 'Kyber Min Return': web3.utils.fromWei(kyberResult.slippageRate, 'Ether'), 41 | 'Timestamp': moment().tz('America/Chicago').format(), 42 | }]) 43 | } 44 | 45 | let priceMonitor 46 | let monitoringPrice = false 47 | 48 | async function monitorPrice() { 49 | if(monitoringPrice) { 50 | return 51 | } 52 | 53 | console.log("Checking prices...") 54 | monitoringPrice = true 55 | 56 | try { 57 | 58 | await checkPair(forTokens("ETH", "MKR", web3.utils.toWei('1', 'ETHER'))); 59 | // await checkPair(forTokens("ETH", "DAI", web3.utils.toWei('1', 'ETHER'))); 60 | // await checkPair(forTokens("ETH", "KNC", web3.utils.toWei('1', 'ETHER'))); 61 | // await checkPair(forTokens("ETH", "LINK", web3.utils.toWei('1', 'ETHER'))); 62 | // await checkPair(forTokens("ETH", "AMPL", web3.utils.toWei('1', 'ETHER'))); 63 | 64 | } catch (error) { 65 | console.error(error) 66 | monitoringPrice = false 67 | clearInterval(priceMonitor) 68 | return 69 | } 70 | 71 | monitoringPrice = false 72 | } 73 | 74 | // Check markets every n seconds 75 | const POLLING_INTERVAL = process.env.POLLING_INTERVAL || 3000 // 3 Seconds 76 | priceMonitor = setInterval(async () => { await monitorPrice() }, POLLING_INTERVAL) 77 | 78 | 79 | /* 80 | 81 | require('dotenv').config() 82 | const express = require('express') 83 | const http = require('http') 84 | const Web3 = require('web3') 85 | const {checkPair} = require('./checkPair'); 86 | const {forTokens} = require('./coins/erc20') 87 | 88 | // SERVER CONFIG 89 | const PORT = process.env.PORT || 5000 90 | const app = express(); 91 | const server = http.createServer(app).listen(PORT, () => console.log(`Listening on ${ PORT }`)) 92 | 93 | // WEB3 CONFIG 94 | const web3 = new Web3(process.env.RPC_URL) 95 | exports.web3 = web3 96 | 97 | let priceMonitor 98 | let monitoringPrice = false 99 | 100 | async function monitorPrice() { 101 | if(monitoringPrice) { 102 | return 103 | } 104 | 105 | console.log("Checking prices...") 106 | monitoringPrice = true 107 | 108 | try { 109 | 110 | await checkPair(forTokens("DAI", "WBTC", web3.utils.toWei('1', 'ETHER'))); 111 | await checkPair(forTokens("ETH", "WBTC", web3.utils.toWei('1', 'ETHER'))); 112 | await checkPair(forTokens("ETH", "DAI", web3.utils.toWei('1', 'ETHER'))); 113 | await checkPair(forTokens("ETH", "KNC", web3.utils.toWei('1', 'ETHER'))); 114 | await checkPair(forTokens("ETH", "LINK", web3.utils.toWei('1', 'ETHER'))); 115 | await checkPair(forTokens("ETH", "AMPL", web3.utils.toWei('1', 'ETHER'))); 116 | 117 | } catch (error) { 118 | console.error(error) 119 | monitoringPrice = false 120 | clearInterval(priceMonitor) 121 | return 122 | } 123 | 124 | monitoringPrice = false 125 | } 126 | 127 | // Check markets every n seconds 128 | const POLLING_INTERVAL = process.env.POLLING_INTERVAL || 3000 // 3 Seconds 129 | priceMonitor = setInterval(async () => { await monitorPrice() }, POLLING_INTERVAL) 130 | 131 | */ -------------------------------------------------------------------------------- /src/examples/trading-bot.js: -------------------------------------------------------------------------------- 1 | require('dotenv').config() 2 | const express = require('express') 3 | const bodyParser = require('body-parser') 4 | const http = require('http') 5 | const Web3 = require('web3') 6 | const HDWalletProvider = require('@truffle/hdwallet-provider') 7 | const moment = require('moment-timezone') 8 | const numeral = require('numeral') 9 | const _ = require('lodash') 10 | 11 | // SERVER CONFIG 12 | const PORT = process.env.PORT || 5000 13 | const app = express(); 14 | const server = http.createServer(app).listen(PORT, () => console.log(`Listening on ${ PORT }`)) 15 | 16 | // WEB3 CONFIG 17 | web3 = new Web3('https://mainnet.infura.io/v3/d771c1c5fe814da1b330f11a82ea5ac9'); 18 | //const web3 = new Web3(new HDWalletProvider(process.env.PRIVATE_KEY, process.env.RPC_URL) ) 19 | 20 | // Ropsten DAI 21 | const DAI_ABI = [{"constant":true,"inputs":[],"name":"name","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_spender","type":"address"},{"name":"_value","type":"uint256"}],"name":"approve","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"totalSupply","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_from","type":"address"},{"name":"_to","type":"address"},{"name":"_value","type":"uint256"}],"name":"transferFrom","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"INITIAL_SUPPLY","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"decimals","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_value","type":"uint256"}],"name":"burn","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"_owner","type":"address"}],"name":"balanceOf","outputs":[{"name":"balance","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_from","type":"address"},{"name":"_value","type":"uint256"}],"name":"burnFrom","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"symbol","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_to","type":"address"},{"name":"_value","type":"uint256"}],"name":"transfer","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"_owner","type":"address"},{"name":"_spender","type":"address"}],"name":"allowance","outputs":[{"name":"remaining","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"inputs":[{"name":"_name","type":"string"},{"name":"_symbol","type":"string"},{"name":"_decimals","type":"uint256"}],"payable":false,"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"name":"_burner","type":"address"},{"indexed":false,"name":"_value","type":"uint256"}],"name":"Burn","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"owner","type":"address"},{"indexed":true,"name":"spender","type":"address"},{"indexed":false,"name":"value","type":"uint256"}],"name":"Approval","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"from","type":"address"},{"indexed":true,"name":"to","type":"address"},{"indexed":false,"name":"value","type":"uint256"}],"name":"Transfer","type":"event"}] 22 | const DAI_ADDRESS = '0xad6d458402f60fd3bd25163575031acdce07538d' 23 | const daiContract = new web3.eth.Contract(DAI_ABI, DAI_ADDRESS); 24 | 25 | // Ropsten Uniswap Dai Exchange: https://ropsten.etherscan.io/address/0xc0fc958f7108be4060F33a699a92d3ea49b0B5f0 26 | const EXCHANGE_ABI = [{name:'TokenPurchase',inputs:[{type:'address',name:'buyer',indexed:!0},{type:'uint256',name:'eth_sold',indexed:!0},{type:'uint256',name:'tokens_bought',indexed:!0}],anonymous:!1,type:'event'},{name:'EthPurchase',inputs:[{type:'address',name:'buyer',indexed:!0},{type:'uint256',name:'tokens_sold',indexed:!0},{type:'uint256',name:'eth_bought',indexed:!0}],anonymous:!1,type:'event'},{name:'AddLiquidity',inputs:[{type:'address',name:'provider',indexed:!0},{type:'uint256',name:'eth_amount',indexed:!0},{type:'uint256',name:'token_amount',indexed:!0}],anonymous:!1,type:'event'},{name:'RemoveLiquidity',inputs:[{type:'address',name:'provider',indexed:!0},{type:'uint256',name:'eth_amount',indexed:!0},{type:'uint256',name:'token_amount',indexed:!0}],anonymous:!1,type:'event'},{name:'Transfer',inputs:[{type:'address',name:'_from',indexed:!0},{type:'address',name:'_to',indexed:!0},{type:'uint256',name:'_value',indexed:!1}],anonymous:!1,type:'event'},{name:'Approval',inputs:[{type:'address',name:'_owner',indexed:!0},{type:'address',name:'_spender',indexed:!0},{type:'uint256',name:'_value',indexed:!1}],anonymous:!1,type:'event'},{name:'setup',outputs:[],inputs:[{type:'address',name:'token_addr'}],constant:!1,payable:!1,type:'function',gas:175875},{name:'addLiquidity',outputs:[{type:'uint256',name:'out'}],inputs:[{type:'uint256',name:'min_liquidity'},{type:'uint256',name:'max_tokens'},{type:'uint256',name:'deadline'}],constant:!1,payable:!0,type:'function',gas:82605},{name:'removeLiquidity',outputs:[{type:'uint256',name:'out'},{type:'uint256',name:'out'}],inputs:[{type:'uint256',name:'amount'},{type:'uint256',name:'min_eth'},{type:'uint256',name:'min_tokens'},{type:'uint256',name:'deadline'}],constant:!1,payable:!1,type:'function',gas:116814},{name:'__default__',outputs:[],inputs:[],constant:!1,payable:!0,type:'function'},{name:'ethToTokenSwapInput',outputs:[{type:'uint256',name:'out'}],inputs:[{type:'uint256',name:'min_tokens'},{type:'uint256',name:'deadline'}],constant:!1,payable:!0,type:'function',gas:12757},{name:'ethToTokenTransferInput',outputs:[{type:'uint256',name:'out'}],inputs:[{type:'uint256',name:'min_tokens'},{type:'uint256',name:'deadline'},{type:'address',name:'recipient'}],constant:!1,payable:!0,type:'function',gas:12965},{name:'ethToTokenSwapOutput',outputs:[{type:'uint256',name:'out'}],inputs:[{type:'uint256',name:'tokens_bought'},{type:'uint256',name:'deadline'}],constant:!1,payable:!0,type:'function',gas:50455},{name:'ethToTokenTransferOutput',outputs:[{type:'uint256',name:'out'}],inputs:[{type:'uint256',name:'tokens_bought'},{type:'uint256',name:'deadline'},{type:'address',name:'recipient'}],constant:!1,payable:!0,type:'function',gas:50663},{name:'tokenToEthSwapInput',outputs:[{type:'uint256',name:'out'}],inputs:[{type:'uint256',name:'tokens_sold'},{type:'uint256',name:'min_eth'},{type:'uint256',name:'deadline'}],constant:!1,payable:!1,type:'function',gas:47503},{name:'tokenToEthTransferInput',outputs:[{type:'uint256',name:'out'}],inputs:[{type:'uint256',name:'tokens_sold'},{type:'uint256',name:'min_eth'},{type:'uint256',name:'deadline'},{type:'address',name:'recipient'}],constant:!1,payable:!1,type:'function',gas:47712},{name:'tokenToEthSwapOutput',outputs:[{type:'uint256',name:'out'}],inputs:[{type:'uint256',name:'eth_bought'},{type:'uint256',name:'max_tokens'},{type:'uint256',name:'deadline'}],constant:!1,payable:!1,type:'function',gas:50175},{name:'tokenToEthTransferOutput',outputs:[{type:'uint256',name:'out'}],inputs:[{type:'uint256',name:'eth_bought'},{type:'uint256',name:'max_tokens'},{type:'uint256',name:'deadline'},{type:'address',name:'recipient'}],constant:!1,payable:!1,type:'function',gas:50384},{name:'tokenToTokenSwapInput',outputs:[{type:'uint256',name:'out'}],inputs:[{type:'uint256',name:'tokens_sold'},{type:'uint256',name:'min_tokens_bought'},{type:'uint256',name:'min_eth_bought'},{type:'uint256',name:'deadline'},{type:'address',name:'token_addr'}],constant:!1,payable:!1,type:'function',gas:51007},{name:'tokenToTokenTransferInput',outputs:[{type:'uint256',name:'out'}],inputs:[{type:'uint256',name:'tokens_sold'},{type:'uint256',name:'min_tokens_bought'},{type:'uint256',name:'min_eth_bought'},{type:'uint256',name:'deadline'},{type:'address',name:'recipient'},{type:'address',name:'token_addr'}],constant:!1,payable:!1,type:'function',gas:51098},{name:'tokenToTokenSwapOutput',outputs:[{type:'uint256',name:'out'}],inputs:[{type:'uint256',name:'tokens_bought'},{type:'uint256',name:'max_tokens_sold'},{type:'uint256',name:'max_eth_sold'},{type:'uint256',name:'deadline'},{type:'address',name:'token_addr'}],constant:!1,payable:!1,type:'function',gas:54928},{name:'tokenToTokenTransferOutput',outputs:[{type:'uint256',name:'out'}],inputs:[{type:'uint256',name:'tokens_bought'},{type:'uint256',name:'max_tokens_sold'},{type:'uint256',name:'max_eth_sold'},{type:'uint256',name:'deadline'},{type:'address',name:'recipient'},{type:'address',name:'token_addr'}],constant:!1,payable:!1,type:'function',gas:55019},{name:'tokenToExchangeSwapInput',outputs:[{type:'uint256',name:'out'}],inputs:[{type:'uint256',name:'tokens_sold'},{type:'uint256',name:'min_tokens_bought'},{type:'uint256',name:'min_eth_bought'},{type:'uint256',name:'deadline'},{type:'address',name:'exchange_addr'}],constant:!1,payable:!1,type:'function',gas:49342},{name:'tokenToExchangeTransferInput',outputs:[{type:'uint256',name:'out'}],inputs:[{type:'uint256',name:'tokens_sold'},{type:'uint256',name:'min_tokens_bought'},{type:'uint256',name:'min_eth_bought'},{type:'uint256',name:'deadline'},{type:'address',name:'recipient'},{type:'address',name:'exchange_addr'}],constant:!1,payable:!1,type:'function',gas:49532},{name:'tokenToExchangeSwapOutput',outputs:[{type:'uint256',name:'out'}],inputs:[{type:'uint256',name:'tokens_bought'},{type:'uint256',name:'max_tokens_sold'},{type:'uint256',name:'max_eth_sold'},{type:'uint256',name:'deadline'},{type:'address',name:'exchange_addr'}],constant:!1,payable:!1,type:'function',gas:53233},{name:'tokenToExchangeTransferOutput',outputs:[{type:'uint256',name:'out'}],inputs:[{type:'uint256',name:'tokens_bought'},{type:'uint256',name:'max_tokens_sold'},{type:'uint256',name:'max_eth_sold'},{type:'uint256',name:'deadline'},{type:'address',name:'recipient'},{type:'address',name:'exchange_addr'}],constant:!1,payable:!1,type:'function',gas:53423},{name:'getEthToTokenInputPrice',outputs:[{type:'uint256',name:'out'}],inputs:[{type:'uint256',name:'eth_sold'}],constant:!0,payable:!1,type:'function',gas:5542},{name:'getEthToTokenOutputPrice',outputs:[{type:'uint256',name:'out'}],inputs:[{type:'uint256',name:'tokens_bought'}],constant:!0,payable:!1,type:'function',gas:6872},{name:'getTokenToEthInputPrice',outputs:[{type:'uint256',name:'out'}],inputs:[{type:'uint256',name:'tokens_sold'}],constant:!0,payable:!1,type:'function',gas:5637},{name:'getTokenToEthOutputPrice',outputs:[{type:'uint256',name:'out'}],inputs:[{type:'uint256',name:'eth_bought'}],constant:!0,payable:!1,type:'function',gas:6897},{name:'tokenAddress',outputs:[{type:'address',name:'out'}],inputs:[],constant:!0,payable:!1,type:'function',gas:1413},{name:'factoryAddress',outputs:[{type:'address',name:'out'}],inputs:[],constant:!0,payable:!1,type:'function',gas:1443},{name:'balanceOf',outputs:[{type:'uint256',name:'out'}],inputs:[{type:'address',name:'_owner'}],constant:!0,payable:!1,type:'function',gas:1645},{name:'transfer',outputs:[{type:'bool',name:'out'}],inputs:[{type:'address',name:'_to'},{type:'uint256',name:'_value'}],constant:!1,payable:!1,type:'function',gas:75034},{name:'transferFrom',outputs:[{type:'bool',name:'out'}],inputs:[{type:'address',name:'_from'},{type:'address',name:'_to'},{type:'uint256',name:'_value'}],constant:!1,payable:!1,type:'function',gas:110907},{name:'approve',outputs:[{type:'bool',name:'out'}],inputs:[{type:'address',name:'_spender'},{type:'uint256',name:'_value'}],constant:!1,payable:!1,type:'function',gas:38769},{name:'allowance',outputs:[{type:'uint256',name:'out'}],inputs:[{type:'address',name:'_owner'},{type:'address',name:'_spender'}],constant:!0,payable:!1,type:'function',gas:1925},{name:'name',outputs:[{type:'bytes32',name:'out'}],inputs:[],constant:!0,payable:!1,type:'function',gas:1623},{name:'symbol',outputs:[{type:'bytes32',name:'out'}],inputs:[],constant:!0,payable:!1,type:'function',gas:1653},{name:'decimals',outputs:[{type:'uint256',name:'out'}],inputs:[],constant:!0,payable:!1,type:'function',gas:1683},{name:'totalSupply',outputs:[{type:'uint256',name:'out'}],inputs:[],constant:!0,payable:!1,type:'function',gas:1713}] 27 | const EXCHANGE_ADDRESS = '0xc0fc958f7108be4060F33a699a92d3ea49b0B5f0' 28 | const exchangeContract = new web3.eth.Contract(EXCHANGE_ABI, EXCHANGE_ADDRESS); 29 | 30 | // Minimum eth to swap 31 | const ETH_AMOUNT = web3.utils.toWei('1', 'Ether') 32 | console.log("Eth Amount", ETH_AMOUNT) 33 | 34 | const ETH_SELL_PRICE = web3.utils.toWei('200', 'Ether') // 200 Dai a.k.a. $200 USD 35 | 36 | async function sellEth(ethAmount, daiAmount) { 37 | // Set Deadline 1 minute from now 38 | const moment = require('moment') // import moment.js library 39 | const now = moment().unix() // fetch current unix timestamp 40 | const DEADLINE = now + 60 // add 60 seconds 41 | console.log("Deadline", DEADLINE) 42 | 43 | // Transaction Settings 44 | const SETTINGS = { 45 | gasLimit: 8000000, // Override gas settings: https://github.com/ethers-io/ethers.js/issues/469 46 | gasPrice: web3.utils.toWei('50', 'Gwei'), 47 | from: process.env.ACCOUNT, // Use your account here 48 | value: ethAmount // Amount of Ether to Swap 49 | } 50 | 51 | // Perform Swap 52 | console.log('Performing swap...') 53 | let result = await exchangeContract.methods.ethToTokenSwapInput(daiAmount.toString(), DEADLINE).send(SETTINGS) 54 | console.log(`Successful Swap: https://ropsten.etherscan.io/tx/${result.transactionHash}`) 55 | } 56 | 57 | async function checkBalances() { 58 | let balance 59 | 60 | // Check Ether balance swap 61 | balance = await web3.eth.getBalance(process.env.ACCOUNT) // .call() ?? 62 | balance = web3.utils.fromWei(balance, 'Ether') 63 | console.log("Ether Balance:", balance) 64 | 65 | // Check Dai balance swap 66 | balance = await daiContract.methods.balanceOf(process.env.ACCOUNT).call() 67 | balance = web3.utils.fromWei(balance, 'Ether') 68 | console.log("Dai Balance:", balance) 69 | } 70 | 71 | let priceMonitor 72 | let monitoringPrice = false 73 | 74 | async function monitorPrice() { 75 | if(monitoringPrice) { 76 | return 77 | } 78 | 79 | console.log("Checking price...") 80 | monitoringPrice = true 81 | 82 | try { 83 | 84 | // Check Eth Price 85 | const daiAmount = await exchangeContract.methods.getEthToTokenInputPrice(ETH_AMOUNT).call() 86 | const price = web3.utils.fromWei(daiAmount.toString(), 'Ether') 87 | console.log('Eth Price:', price, ' DAI') 88 | 89 | if(price <= ETH_SELL_PRICE) { 90 | console.log('Selling Eth...') 91 | // Check balance before sale 92 | await checkBalances() 93 | 94 | // Sell Eth 95 | await sellEth(ETH_AMOUNT, daiAmount) 96 | 97 | // Check balances after sale 98 | await checkBalances() 99 | 100 | // Stop monitoring prices 101 | clearInterval(priceMonitor) 102 | } 103 | 104 | } catch (error) { 105 | console.error(error) 106 | monitoringPrice = false 107 | clearInterval(priceMonitor) 108 | return 109 | } 110 | 111 | monitoringPrice = false 112 | } 113 | 114 | // Check markets every n seconds 115 | const POLLING_INTERVAL = process.env.POLLING_INTERVAL || 1000 // 1 Second 116 | priceMonitor = setInterval(async () => { await monitorPrice() }, POLLING_INTERVAL) -------------------------------------------------------------------------------- /truffle-config.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Use this file to configure your truffle project. It's seeded with some 3 | * common settings for different networks and features like migrations, 4 | * compilation and testing. Uncomment the ones you need or modify 5 | * them to suit your project as necessary. 6 | * 7 | * More information about configuration can be found at: 8 | * 9 | * truffleframework.com/docs/advanced/configuration 10 | * 11 | * To deploy via Infura you'll need a wallet provider (like @truffle/hdwallet-provider) 12 | * to sign your transactions before they're sent to a remote public node. Infura accounts 13 | * are available for free at: infura.io/register. 14 | * 15 | * You'll also need a mnemonic - the twelve word phrase the wallet uses to generate 16 | * public/private key pairs. If you're publishing your code to GitHub make sure you load this 17 | * phrase from a file you've .gitignored so it doesn't accidentally become public. 18 | * 19 | */ 20 | 21 | // const HDWalletProvider = require('@truffle/hdwallet-provider'); 22 | // const infuraKey = "fj4jll3k....."; 23 | // 24 | // const fs = require('fs'); 25 | // const mnemonic = fs.readFileSync(".secret").toString().trim(); 26 | 27 | module.exports = { 28 | /** 29 | * Networks define how you connect to your ethereum client and let you set the 30 | * defaults web3 uses to send transactions. If you don't specify one truffle 31 | * will spin up a development blockchain for you on port 9545 when you 32 | * run `develop` or `test`. You can ask a truffle command to use a specific 33 | * network from the command line, e.g 34 | * 35 | * $ truffle test --network 36 | */ 37 | 38 | networks: { 39 | // Useful for testing. The `development` name is special - truffle uses it by default 40 | // if it's defined here and no other network is specified at the command line. 41 | // You should run a client (like ganache-cli, geth or parity) in a separate terminal 42 | // tab if you use this network and you must also set the `host`, `port` and `network_id` 43 | // options below to some value. 44 | // 45 | // development: { 46 | // host: "127.0.0.1", // Localhost (default: none) 47 | // port: 8545, // Standard Ethereum port (default: none) 48 | // network_id: "*", // Any network (default: none) 49 | // }, 50 | 51 | // Another network with more advanced options... 52 | // advanced: { 53 | // port: 8777, // Custom port 54 | // network_id: 1342, // Custom network 55 | // gas: 8500000, // Gas sent with each transaction (default: ~6700000) 56 | // gasPrice: 20000000000, // 20 gwei (in wei) (default: 100 gwei) 57 | // from:
, // Account to send txs from (default: accounts[0]) 58 | // websockets: true // Enable EventEmitter interface for web3 (default: false) 59 | // }, 60 | 61 | // Useful for deploying to a public network. 62 | // NB: It's important to wrap the provider as a function. 63 | // ropsten: { 64 | // provider: () => new HDWalletProvider(mnemonic, `https://ropsten.infura.io/v3/YOUR-PROJECT-ID`), 65 | // network_id: 3, // Ropsten's id 66 | // gas: 5500000, // Ropsten has a lower block limit than mainnet 67 | // confirmations: 2, // # of confs to wait between deployments. (default: 0) 68 | // timeoutBlocks: 200, // # of blocks before a deployment times out (minimum/default: 50) 69 | // skipDryRun: true // Skip dry run before migrations? (default: false for public nets ) 70 | // }, 71 | 72 | // Useful for private networks 73 | // private: { 74 | // provider: () => new HDWalletProvider(mnemonic, `https://network.io`), 75 | // network_id: 2111, // This network is yours, in the cloud. 76 | // production: true // Treats this network as if it was a public net. (default: false) 77 | // } 78 | }, 79 | 80 | // Set default mocha options here, use special reporters etc. 81 | mocha: { 82 | // timeout: 100000 83 | }, 84 | 85 | contracts_directory: './src/contracts', 86 | contracts_build_directory: './src/abis', 87 | 88 | // Configure your compilers 89 | compilers: { 90 | solc: { 91 | // version: "0.5.1", // Fetch exact version from solc-bin (default: truffle's version) 92 | // docker: true, // Use "0.5.1" you've installed locally with docker (default: false) 93 | // settings: { // See the solidity docs for advice about optimization and evmVersion 94 | // optimizer: { 95 | // enabled: false, 96 | // runs: 200 97 | // }, 98 | // evmVersion: "byzantium" 99 | // } 100 | } 101 | } 102 | } 103 | --------------------------------------------------------------------------------