├── test └── .gitkeep ├── migrations └── 1_initial_migration.js ├── .gitignore ├── package.json ├── contracts ├── Migrations.sol └── Trader.sol ├── src └── bot.js ├── README.md └── truffle-config.js /test/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /migrations/1_initial_migration.js: -------------------------------------------------------------------------------- 1 | const Migrations = artifacts.require("Migrations"); 2 | 3 | module.exports = function(deployer) { 4 | deployer.deploy(Migrations); 5 | }; 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | 3 | yarn-error.log 4 | 5 | .idea 6 | 7 | dist 8 | 9 | .env 10 | 11 | TODO 12 | 13 | tmp 14 | 15 | .DS_Store 16 | 17 | docker/ 18 | 19 | build 20 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "paraswap-backend-test", 3 | "version": "1.0.0", 4 | "main": "index.js", 5 | "scripts": { 6 | "start": "node ./src/bot.js", 7 | "build": "truffle build" 8 | }, 9 | "dependencies": { 10 | "@ethersproject/address": "^5.0.8", 11 | "@ethersproject/contracts": "^5.0.8", 12 | "@ethersproject/providers": "^5.0.17", 13 | "@ethersproject/solidity": "^5.0.7", 14 | "@uniswap/sdk": "^3.0.3", 15 | "ethers": "^5.0.23" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /contracts/Migrations.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity >=0.4.22 <0.8.0; 3 | 4 | contract Migrations { 5 | address public owner = msg.sender; 6 | uint public last_completed_migration; 7 | 8 | modifier restricted() { 9 | require( 10 | msg.sender == owner, 11 | "This function is restricted to the contract's owner" 12 | ); 13 | _; 14 | } 15 | 16 | function setCompleted(uint completed) public restricted { 17 | last_completed_migration = completed; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/bot.js: -------------------------------------------------------------------------------- 1 | const trader = require("../build/contracts/Trader"); 2 | const { 3 | Contract, 4 | Wallet, 5 | getDefaultProvider, 6 | utils 7 | } = require('ethers') 8 | 9 | // Replace 'Kovan' by any other network 10 | const provider = getDefaultProvider('kovan'); 11 | 12 | 13 | const jsonFile = [ 14 | { 15 | "from": "ETH", 16 | "to": "DAI", 17 | "target": "400", 18 | "amount": "1" 19 | }, 20 | { 21 | "from": "ETH", 22 | "to": "DAI", 23 | "target": "500", 24 | "amount": "2" 25 | } 26 | ]; 27 | 28 | setInterval( 29 | (async() => { 30 | 31 | // Add Signer here 32 | // const signer = new Wallet('PRIVATEKEY_HERE', provider); 33 | const contract = new Contract('0x244e37a91Fb5D52072a03446227534C2eeE3818e', trader.abi, provider); 34 | 35 | // Uncomment if Signer is set 36 | // const contract = new Contract('0x244e37a91Fb5D52072a03446227534C2eeE3818e', trader.abi, signer); 37 | 38 | const currentPrice = await contract.getPrice(utils.parseEther(jsonFile[0].amount)); 39 | if(currentPrice >= jsonFile.target) { 40 | await contract.sell() 41 | } 42 | console.log(currentPrice.toString()); 43 | 44 | }), 13000); 45 | -------------------------------------------------------------------------------- /contracts/Trader.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.5.1; 2 | 3 | interface IUniswapV2Router { 4 | function swapETHForExactTokens(uint amountOut, address[] calldata path, address to, uint deadline) 5 | external 6 | payable 7 | returns (uint[] memory amounts); 8 | 9 | function WETH() external pure returns (address); 10 | 11 | function getAmountsOut(uint amountIn, address[] calldata path) external view returns (uint[] memory amounts); 12 | function getAmountsIn(uint amountOut, address[] calldata path) external view returns (uint[] memory amounts); 13 | } 14 | 15 | contract Trader { 16 | 17 | IUniswapV2Router private _uniswapV2Router; 18 | address private _WETH; 19 | address private _token; 20 | 21 | constructor(IUniswapV2Router __uniswapV2Router, address __token) public { 22 | _uniswapV2Router = IUniswapV2Router(__uniswapV2Router); 23 | _token = __token; 24 | _WETH = IUniswapV2Router(_uniswapV2Router).WETH(); 25 | } 26 | 27 | function getPrice(uint ethAmount) public view returns (uint) { 28 | address[] memory path = new address[](2); 29 | path[0] = address(_WETH); 30 | path[1] = _token; 31 | return _uniswapV2Router.getAmountsOut(ethAmount, path)[1]; 32 | } 33 | 34 | function sell(address from, address to, uint fromAmount, uint targetAmount) external payable returns (uint receivedAmount) { 35 | //IMPORTANT: receivedAmount should >= targetAmount 36 | address[] memory path = new address[](2); 37 | path[0] = address(_WETH); 38 | path[1] = _token; 39 | require(getPrice(msg.value) >= targetAmount, "Transaction reverted: price higher than the target"); 40 | _uniswapV2Router.swapETHForExactTokens(fromAmount, path, to, now + 900); 41 | return receivedAmount; 42 | } 43 | } 44 | 45 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Uniswap Trading Bot 2 | 3 | Uniswap trading bot that will sell ETH to DAI at predefined target prices. 4 | 5 | The bot will read the target prices from a JSON file that looks like this: 6 | 7 | target-prices.json: 8 | ```json 9 | [ 10 | { 11 | "from": "ETH", 12 | "to": "DAI", 13 | "target": "400", 14 | "amount": "1" 15 | }, 16 | { 17 | "from": "ETH", 18 | "to": "DAI", 19 | "target": "500", 20 | "amount": "2" 21 | } 22 | ] 23 | ``` 24 | 25 | The bot will listen to price changes on a block by block basis and execute the trade when target prices are met. 26 | 27 | The Uniswap SDK can be used https://uniswap.org/docs/v2/SDK/getting-started/ to retrieve the prices. 28 | 29 | A smart contract is needed to execute the sell, it can look like this: 30 | 31 | ```solidity 32 | contract Trader { 33 | function sell(address from, address to, uint fromAmount, uint targetAmount) external payable returns (uint receivedAmount) { 34 | 35 | //IMPORTANT: receivedAmount should >= targetAmount 36 | return receivedAmount; 37 | } 38 | } 39 | ``` 40 | To interact with Uniswap, the interface below can be used. Addresses for different testnets can be found here https://uniswap.org/docs/v2/smart-contracts/router02/#address 41 | 42 | ```solidity 43 | interface IUniswapRouter { 44 | function swapExactETHForTokens( 45 | uint amountOutMin, 46 | address[] calldata path, 47 | address to, 48 | uint deadline 49 | ) 50 | external 51 | payable 52 | returns (uint[] memory amounts); 53 | } 54 | ``` 55 | 56 | Notes: 57 | 58 | - ETH is expressed in decimals (or Wei), 1 ETH = 1e18 Wei (or 10^18). The same applies to DAI. 59 | - ETH is represented by the address 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE 60 | -------------------------------------------------------------------------------- /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 | * trufflesuite.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 | // Another network with more advanced options... 51 | // advanced: { 52 | // port: 8777, // Custom port 53 | // network_id: 1342, // Custom network 54 | // gas: 8500000, // Gas sent with each transaction (default: ~6700000) 55 | // gasPrice: 20000000000, // 20 gwei (in wei) (default: 100 gwei) 56 | // from:
, // Account to send txs from (default: accounts[0]) 57 | // websockets: true // Enable EventEmitter interface for web3 (default: false) 58 | // }, 59 | // Useful for deploying to a public network. 60 | // NB: It's important to wrap the provider as a function. 61 | // ropsten: { 62 | // provider: () => new HDWalletProvider(mnemonic, `https://ropsten.infura.io/v3/YOUR-PROJECT-ID`), 63 | // network_id: 3, // Ropsten's id 64 | // gas: 5500000, // Ropsten has a lower block limit than mainnet 65 | // confirmations: 2, // # of confs to wait between deployments. (default: 0) 66 | // timeoutBlocks: 200, // # of blocks before a deployment times out (minimum/default: 50) 67 | // skipDryRun: true // Skip dry run before migrations? (default: false for public nets ) 68 | // }, 69 | // Useful for private networks 70 | // private: { 71 | // provider: () => new HDWalletProvider(mnemonic, `https://network.io`), 72 | // network_id: 2111, // This network is yours, in the cloud. 73 | // production: true // Treats this network as if it was a public net. (default: false) 74 | // } 75 | }, 76 | 77 | // Set default mocha options here, use special reporters etc. 78 | mocha: { 79 | // timeout: 100000 80 | }, 81 | 82 | // Configure your compilers 83 | compilers: { 84 | solc: { 85 | // version: "0.5.1", // Fetch exact version from solc-bin (default: truffle's version) 86 | // docker: true, // Use "0.5.1" you've installed locally with docker (default: false) 87 | // settings: { // See the solidity docs for advice about optimization and evmVersion 88 | // optimizer: { 89 | // enabled: false, 90 | // runs: 200 91 | // }, 92 | // evmVersion: "byzantium" 93 | // } 94 | }, 95 | }, 96 | }; 97 | --------------------------------------------------------------------------------