├── .gitignore ├── .travis.yml ├── config ├── log4js.json └── config.json ├── package.json ├── README.md └── app.js /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | *.log 3 | node_modules/ -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "10" 4 | -------------------------------------------------------------------------------- /config/log4js.json: -------------------------------------------------------------------------------- 1 | { 2 | "appenders": { 3 | "app": { 4 | "type": "dateFile", 5 | "filename": "logs/eosram-trading.log", 6 | "pattern": "-yyyy-MM-dd" 7 | }, 8 | "errors": { 9 | "type": "logLevelFilter", 10 | "level": "ERROR", 11 | "appender": "errorFile" 12 | } 13 | }, 14 | "categories": { 15 | "default": { "appenders": [ "app", "errors" ], "level": "DEBUG" } 16 | } 17 | } -------------------------------------------------------------------------------- /config/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "mode": "buy", 3 | "buy_line": 0.439, 4 | "sell_line": 0.490, 5 | "reserve_ram": 4000, 6 | "buy_ram_by_eos_amount": 0.001, 7 | "max_tx_number": 1, 8 | "eos": { 9 | "broadcast": true, 10 | "chainId": "aca376f206b8fc25a6ed44dbdc66547c36c6c33e3a119ffbeaef943642f0e906", 11 | "debug": true, 12 | "expireInSeconds": 60, 13 | "httpEndpoint": "http://api.eosnewyork.io", 14 | "keyProvider": "", 15 | "sign": true, 16 | "verbose": false 17 | }, 18 | "interval": 500, 19 | "keosd": { 20 | "wallet_url": "http://127.0.0.1:8900/" 21 | }, 22 | "wallet": { 23 | "account_name": "", 24 | "password": "", 25 | "wallet_name": "" 26 | } 27 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "eosram-trading-robot", 3 | "version": "1.0.0", 4 | "description": "EOS RAM 自动交易机器人", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 0" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://github.com/wangweiX/eosram-trading-robot.git" 12 | }, 13 | "keywords": [ 14 | "EOSRAM" 15 | ], 16 | "author": "wangwei", 17 | "license": "ISC", 18 | "bugs": { 19 | "url": "https://github.com/wangweiX/eosram-trading-robot/issues" 20 | }, 21 | "homepage": "https://github.com/wangweiX/eosram-trading-robot#readme", 22 | "dependencies": { 23 | "async": "^2.6.1", 24 | "eosjs": "^15.0.3", 25 | "osascript": "^1.2.0", 26 | "shelljs": "^0.8.2", 27 | "log4js": "^2.10.0", 28 | "mocha": "^5.2.0" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # EOS RAM 交易机器人🤖 2 | 3 | [![Build Status](https://travis-ci.org/wangweiX/eosram-trading-robot.svg?branch=master)](https://travis-ci.org/wangweiX/eosram-trading-robot) 4 | 5 | > 本教程受此文章启发:https://bihu.com/article/854060 6 | 7 | 8 | 9 | ## 环境准备 10 | 11 | #### 系统 12 | 13 | - macOS high Sierra 14 | 15 | #### 安装Node 16 | 17 | - 版本:`v10.6.0` 18 | - 指南:https://wangwei.one/posts/b1fcd8d6.html 19 | 20 | #### 安装eos 21 | 22 | - 指南:https://wangwei.one/posts/181733fc.html 23 | 24 | #### 导入钱包 25 | 26 | - [官方指南](https://developers.eos.io/eosio-nodeos/docs/learn-about-wallets-keys-and-accounts-with-cleos) 27 | 28 | 29 | 30 | ## 运行程序 31 | 32 | #### 下载代码 33 | 34 | ```powershell 35 | $ git clone git@github.com:wangweiX/eosram-trading-robot.git 36 | ``` 37 | 38 | #### 安装依赖包 39 | 40 | ```powershell 41 | $ cd eosram-trading-robot 42 | $ npm install 43 | $ npm install -g pm2 44 | ``` 45 | 46 | #### 配置参数 47 | 配置文件 `/config/config.json` 48 | 49 | ```json5 50 | { 51 | "mode": "buy", // 配置程序启动后的初始交易模式。buy:买入 sell:卖出 52 | "buy_line": 0.40, // 配置买入价位,单位:EOS/KB 53 | "sell_line": 0.49, // 配置卖出价位,单位:EOS/KB 54 | "reserve_ram": 4000, // 配置卖出时账户保留内存数量,单位:bytes(默认即可) 55 | "buy_ram_by_eos_amount": 0.001, // 设置用于购买内存的EOS数量,如果小于0,例如-1,则表示全量买入 56 | "max_tx_number": 1, // 设置完成买入卖出回合数上限,避免程序不停买入卖出造成损失 57 | "eos": { 58 | "broadcast": true, 59 | "chainId": "aca376f206b8fc25a6ed44dbdc66547c36c6c33e3a119ffbeaef943642f0e906", 60 | "debug": true, 61 | "expireInSeconds": 60, 62 | "httpEndpoint": "http://api.eosnewyork.io", 63 | "keyProvider": "", // 配置私钥 64 | "sign": true, 65 | "verbose": false 66 | }, 67 | "interval": 500, // 配置数据请求的时间间隔,单位:ms(默认即可) 68 | "keosd": { 69 | "wallet_url": "http://127.0.0.1:8900/" // 配置本地钱包的url 70 | }, 71 | "wallet": { 72 | "account_name": "", // 配置EOS账户名 73 | "password": "", // 配置本地钱包密码 74 | "wallet_name": "" // 配置本地钱包名称 75 | } 76 | } 77 | ``` 78 | 79 | #### 启动程序 80 | 81 | ```powershell 82 | $ pm2 start app.js 83 | ``` 84 | 85 | #### 重启程序 86 | 87 | ```powershell 88 | $ pm2 restart $pid 89 | ``` 90 | 91 | #### 终止程序 92 | 93 | ```powershell 94 | $ pm2 kill 95 | ``` 96 | 97 | 98 | 99 | #### 日志输出 100 | 101 | ![running_log](https://img.i7years.com/blog/but_ram.png) 102 | 103 | 104 | 105 | #### 交易通知 106 | 107 | ![buy_ram_notice](https://img.i7years.com/blog/Eos%20Ram%20Trading%20Notice.png) 108 | 109 | 110 | 111 | ## 问题说明 112 | 1. cannot find data.core_liquid_balance 113 | 114 | 确保你所配置的EOS账户有余额,否则会出现这个问题 115 | 116 | 2. Unable to connect to keosd, if keosd is running please kill the process and try again。 117 | 118 | - 修改 `~/eosio-wallet/config.ini` 中的配置项 `http-server-address`,端口改为8910。 119 | - 设置 config.json 配置文件,修改 `wallet_url` 与上面的端口号一致。 120 | 121 | 3. 连接失败问题,可以忽略。 122 | 123 | ![error_log](https://img.i7years.com/blog/error_log.png) 124 | 125 | 126 | 127 | ## 注意事项 128 | 129 | - 请在可信任设备上部署,一般为自己的电脑,绝对不要部署到阿里云、腾讯云等公有云上,防止私钥被盗。 130 | 131 | - 由于该程序一直在买卖模式间切换,强烈建议**新开一个EOS账户**来配置程序,用作专门的机器人交易账户,以免主账号EOS被一次性买入,造成不必要的财产损失。 132 | 133 | - 请根据自身的风险偏好以及当前RAM价格波动的幅度,合理设置 `buy_ram_by_eos_amount`, `buy_line`, `sell_line`,`max_tx_number` 以免程序不停买入卖出,造成EOS亏损。 134 | 135 | - 由于EOS内存交易会收取0.5%的手续费,因此实际买到的内存数量为: 136 | 137 | ```mathematica 138 | buy_ram_by_eos_amount * (1 - 0.5%) / ram_price 139 | ``` 140 | 141 | 142 | 143 | ## 行情软件 144 | 145 | - http://southex.com/ 146 | - https://eos.feexplorer.io/ 147 | - https://dapp.mytokenpocket.vip/ram/index.html?from=nav_en 148 | 149 | 150 | 151 | ## 免责声明 152 | 153 | - 本程序完全开源,由于自身操作不当造成的任何EOS损失,均与本人无关,请妥善操作。:flushed: 154 | 155 | 156 | 157 | ## 最后 158 | 159 | - 如果对你有所帮助,请给个 Star :stuck_out_tongue: :blush: 160 | -------------------------------------------------------------------------------- /app.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Constant configuration 3 | */ 4 | const config = require('./config/config.json'); 5 | const async = require('async'); 6 | const shell = require('shelljs'); 7 | const log4js = require('log4js'); 8 | const logger = log4js.getLogger("RAM-Robot"); 9 | const log4js_config = require('./config/log4js.json'); 10 | log4js.configure(log4js_config); 11 | 12 | /** 13 | * Eos Client configuration 14 | */ 15 | const Eos = require('eosjs'); 16 | const eosClient = Eos(config.eos); 17 | 18 | const unlock_wallet_cmd = `cleos --wallet-url=${config.keosd.wallet_url} wallet unlock -n ${config.wallet.wallet_name} --password ${config.wallet.password}`; 19 | 20 | shell.exec(`pkill keosd`); 21 | 22 | let count = 0; 23 | setInterval( 24 | function () { 25 | async.parallel( 26 | tasks(), 27 | function (err, results) { 28 | if (count < config.max_tx_number) { 29 | dealResults(results); 30 | } else { 31 | logger.info("Maximum number of transaction pens reached !!! Exist program . "); 32 | shell.exec(`pm2 kill`); 33 | } 34 | }); 35 | }, 36 | config.interval 37 | ); 38 | 39 | 40 | 41 | /** 42 | * Query data 43 | * @returns {{account: account, quoteBalance: quoteBalance, available: available}} 44 | */ 45 | function tasks() { 46 | return { 47 | account: function (callback) { 48 | getMyAccountData(callback); 49 | }, 50 | quoteBalance: function (callback) { 51 | quoteBalance(callback); 52 | }, 53 | available: function (callback) { 54 | getRamMarketData(callback); 55 | } 56 | }; 57 | } 58 | 59 | /** 60 | * Query my eos account data 61 | * @param callback 62 | */ 63 | function getMyAccountData(callback) { 64 | eosClient.getAccount({ 65 | json: "true", code: "eosio", "account_name": config.wallet.account_name 66 | }).then((data) => { 67 | let my_eos = data.core_liquid_balance; 68 | let my_eos_amount = parseFloat(my_eos.split(" ")[0]); 69 | let my_quota_ram = data.ram_quota; 70 | callback(null, { 71 | my_eos_amount: my_eos_amount, 72 | my_quota_ram: my_quota_ram 73 | }); 74 | }).catch((e) => { 75 | console.error(e); 76 | }) 77 | } 78 | 79 | /** 80 | * Query eosio.ram account data 81 | * @see https://eosflare.io/account/eosio.ram 82 | * @param callback 83 | */ 84 | function quoteBalance(callback) { 85 | eosClient.getTableRows({ 86 | json: "true", code: "eosio", scope: "eosio", table: "rammarket", limit: "10" 87 | }).then((data) => { 88 | let obj = data.rows[0]; 89 | let quote_balance = obj.quote.balance; 90 | let quote_balance_amount = parseFloat(quote_balance.split(" ")[0]); 91 | callback(null, { 92 | quote_balance_amount: quote_balance_amount 93 | }); 94 | }).catch((e) => { 95 | console.error(e); 96 | }) 97 | } 98 | 99 | /** 100 | * Query Ram Market Data 101 | * @see https://eosflare.io/account/eosio.ram 102 | * @param callback 103 | */ 104 | function getRamMarketData(callback) { 105 | eosClient.getTableRows({ 106 | json: "true", code: "eosio", scope: "eosio", table: "global" 107 | }).then((data) => { 108 | let obj = data.rows[0]; 109 | let max_ram_size = obj.max_ram_size; 110 | let reserved_ram = obj.total_ram_bytes_reserved; 111 | let available_ram = (max_ram_size - reserved_ram) / 1024; //in kb 112 | let percent = reserved_ram / max_ram_size; 113 | callback(null, { 114 | available_ram: available_ram, 115 | percent: percent 116 | }); 117 | }).catch((e) => { 118 | console.error(e); 119 | }) 120 | } 121 | 122 | /** 123 | * 124 | * @param results 125 | */ 126 | function dealResults(results) { 127 | let percent = results.available.percent; 128 | let price = results.quoteBalance.quote_balance_amount / results.available.available_ram; 129 | let my_eos_amount = results.account.my_eos_amount; 130 | let my_quota_ram = results.account.my_quota_ram; 131 | 132 | logger.info("Current RAM Price:", price, "EOS/KB" 133 | , ", percent:", percent * 100 134 | , ", eosio.ram total EOS amount:", (results.quoteBalance.quote_balance_amount - 1000000), "EOS" 135 | , ", buy_line:", config.buy_line, "EOS/KB" 136 | , ", sell_line:", config.sell_line, "EOS/KB" 137 | , ", my_eos_amount:", my_eos_amount, "EOS" 138 | , ", my_quota_ram:", my_quota_ram, "Bytes" 139 | ); 140 | 141 | let title = "Eos Ram Trading Robot"; 142 | let buy_msg = `Ram Price fluctuate to ${price} EOS/KB, buy successfully!`; 143 | let sell_msg = `Ram Price fluctuate to ${price} EOS/KB, sell successfully!`; 144 | 145 | if (config.mode === "buy") { 146 | buy_ram(price, my_eos_amount, buy_msg, title); 147 | } else { 148 | sell_ram(price, my_quota_ram, sell_msg, title); 149 | } 150 | } 151 | 152 | 153 | /** 154 | * buy eos ram 155 | * @param price 156 | * @param my_eos_amount 157 | * @param buy_msg 158 | * @param title 159 | */ 160 | function buy_ram(price, my_eos_amount, buy_msg, title) { 161 | if (price < config.buy_line && my_eos_amount > 1) { 162 | shell.exec(unlock_wallet_cmd); 163 | 164 | let buy_amount = config.buy_ram_by_eos_amount >= 0 ? config.buy_ram_by_eos_amount : (my_eos_amount - 1); 165 | 166 | logger.info("Reach buying price !!! buy_amount=", buy_amount, "EOS"); 167 | let buy_cmd = `cleos --wallet-url=${config.keosd.wallet_url} -u http://mainnet.genereos.io system buyram -j ${config.wallet.account_name} ${config.wallet.account_name} "${buy_amount} EOS"`; 168 | shell.exec(buy_cmd); 169 | 170 | let buy_notification = `osascript -e 'display notification "${buy_msg} ! !" sound name "Sound Name" with title "${title}"'`; 171 | shell.exec(buy_notification); 172 | 173 | config.mode = "sell"; // switch to sell mode 174 | } else { 175 | logger.info("Keep Waiting to buy"); 176 | } 177 | } 178 | 179 | 180 | /** 181 | * sell eos ram 182 | * @param price 183 | * @param my_quota_ram 184 | * @param sell_msg 185 | * @param title 186 | */ 187 | function sell_ram(price, my_quota_ram, sell_msg, title) { 188 | if (price >= config.sell_line) { 189 | shell.exec(unlock_wallet_cmd); 190 | 191 | let sell_ram = my_quota_ram - config.reserve_ram; // Minimum amount of memory required to keep 192 | 193 | logger.info("Reach the selling price !!! sell_ram=", sell_ram); 194 | let sell_cmd = `cleos --wallet-url=${config.keosd.wallet_url} -u http://mainnet.genereos.io system sellram -j ${config.wallet.account_name} ${sell_ram}`; 195 | shell.exec(sell_cmd); 196 | 197 | let sell_notification = `osascript -e 'display notification "${sell_msg} ! !" sound name "Sound Name" with title "${title}"'`; 198 | shell.exec(sell_notification); 199 | 200 | config.mode = "buy"; // switch to buy mode 201 | ++count; // add tx number 202 | } else { 203 | logger.info("Keep Waiting to sell"); 204 | } 205 | } 206 | --------------------------------------------------------------------------------