-
Notifications
You must be signed in to change notification settings - Fork 9
Expand file tree
/
Copy pathpricewatch-bot.js
More file actions
273 lines (227 loc) · 10.2 KB
/
pricewatch-bot.js
File metadata and controls
273 lines (227 loc) · 10.2 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
#!/usr/bin/env node
const axios = require('axios')
const chalk = require('chalk')
const ethers = require('ethers')
const moment = require('moment')
const _ = require('lodash')
const program = require('commander')
const Table = require('easy-table')
const BigNumber = require('bignumber.js')
const Saturn = require('@saturnnetwork/saturn.js').Saturn
const version = require('./package.json').version
const saturnApi = 'https://ticker.saturn.network/api/v2'
const epsilon = new BigNumber('0.00005')
const pipeline = async (funcs) => {
return await funcs.reduce((promise, func) => {
return promise.then(result => {
return func().then(Array.prototype.concat.bind(result))
})
}, Promise.resolve([]))
}
function getChainId(chain) {
if (chain === 'ETC') { return 61 }
if (chain === 'ETH') { return 1 }
console.log('Unknown chainId for chain', chain)
process.exit(1)
}
function rpcNode(chain) {
if (chain === 'ETC') { return 'https://www.ethercluster.com/etc' }
if (chain === 'ETH') { return 'https://cloudflare-eth.com' }
console.log('Unknown chainId for chain', chain)
process.exit(1)
}
function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
function makeSaturnClient(blockchain, program, wallet) {
let rpcnode = rpcNode(blockchain)
let chainId = getChainId(blockchain)
let provider = new ethers.providers.JsonRpcProvider(rpcnode, { chainId: chainId, name: blockchain })
let saturn
if (blockchain === 'ETC') {
saturn = new Saturn(saturnApi, { etc: wallet.connect(provider) })
} else {
saturn = new Saturn(saturnApi, { eth: wallet.connect(provider) })
}
return saturn
}
let tradesForToken = async function(token, blockchain, action, timestamp) {
let url = `${saturnApi}/trades/by_timestamp/${blockchain}/0x0000000000000000000000000000000000000000/${token}/${timestamp}.json`
let subset = action === "buy" ? "buys" : "sells"
let trades = await axios.get(url)
if (trades.status !== 200) {
throw new Error(`API error while fetching trade info. Status code ${trades.status}`)
}
return trades.data[subset]
}
let etherVolume = function(trades, wallet, action) {
let filtered = _.filter(trades, (x) => {
return (x.buyer === wallet.toLowerCase() || x.seller === wallet.toLowerCase())
})
if (filtered.length === 0) { return new BigNumber(0) }
if (action === 'buy') {
let amounts = _.map(filtered, (x) => new BigNumber(x.buytokenamount))
return _.reduce(amounts, (x, y) => x.plus(y), new BigNumber(0))
} else {
let amounts = _.map(filtered, (x) => new BigNumber(x.selltokenamount))
return _.reduce(amounts, (x, y) => x.plus(y), new BigNumber(0))
}
}
let allowedLimit = async function(token, blockchain, action, wallet, limit) {
let oneHourAgo = moment().subtract(1, 'hour').unix()
let trades = await tradesForToken(token, blockchain, action, oneHourAgo)
return new BigNumber(limit).minus(etherVolume(trades, wallet, action))
}
let orderInfo = async function(blockchain, tx) {
let url = `${saturnApi}/orders/by_tx/${blockchain}/${tx}.json`
let order = await axios.get(url)
if (order.status !== 200) {
throw new Error(`API error while fetching trade info. Status code ${trades.status}`)
}
let price = new BigNumber(order.data.price)
let balance = new BigNumber(order.data.balance)
return {
price: price,
tokenbalance: balance,
etherbalance: price.times(balance)
}
}
let tokenBalance = async function(blockchain, token, wallet) {
let url = `${saturnApi}/tokens/balances/${blockchain}/${wallet}/${token}.json`
let response = await axios.get(url)
if (response.status !== 200) {
throw new Error(`API error while fetching trade info. Status code ${trades.status}`)
}
return new BigNumber(response.data.balances.walletbalance)
}
let etherBalance = async function(blockchain, wallet) {
let url = `${saturnApi}/tokens/balances/${blockchain}/${wallet}/0x0000000000000000000000000000000000000000.json`
let response = await axios.get(url)
if (response.status !== 200) {
throw new Error(`API error while fetching trade info. Status code ${trades.status}`)
}
return new BigNumber(response.data.balances.walletbalance)
}
program
.version(version, '-v, --version')
.description('Watch a price of a given token on Saturn Network and auto buy/sell. More details are available at ' + chalk.underline.red('https://forum.saturn.network/t/saturn-trading-bot-guides/4046'))
.option('-p, --pkey [pkey]', 'Private key of the wallet to use for trading')
.option('-m, --mnemonic [mnemonic]', 'Mnemonic (i.e. from Saturn Wallet) of the wallet to use for trading')
.option('-i, --walletid [walletid]', 'If using a mnemonic, choose which wallet to use. Default is Account 2 of Saturn Wallet / MetaMask.', 2)
.option('-j, --json [json]', 'Trading bot config file')
.option('-d, --delay [delay]', 'Polling delay in seconds', 60)
.parse(process.argv)
if (!program.mnemonic && !program.pkey) {
console.error('At least one of [pkey], [mnemonic] must be supplied')
process.exit(1)
}
if (program.mnemonic && program.pkey) {
console.error('Only one of [pkey], [mnemonic] must be supplied')
process.exit(1)
}
let wallet
if (program.mnemonic) {
let walletid = parseInt(program.walletid) - 1
wallet = ethers.Wallet.fromMnemonic(program.mnemonic, `m/44'/60'/0'/0/${walletid}`)
} else {
wallet = new ethers.Wallet(program.pkey)
}
if (!program.json) {
console.error('Must specify bot config .json file location')
process.exit(1)
}
console.log(chalk.green(`Loading pricewatch-bot v${version} ...`))
console.log(chalk.green(`Trading address: ${chalk.underline(wallet.address)}\nUsing the following strategies`))
let botconfig = require(program.json)
console.log(Table.print(botconfig, {
houretherlimit: { name: 'Hourly ether limit' },
action: { printer: function (val, width) {
let text = val === "buy" ? chalk.green.bold(val) : chalk.red.bold(val)
return width ? Table.padLeft(text, width) : text
}},
blockchain: { printer: function (val, width) {
let text = val === "ETC" ? chalk.black.bgGreen.bold(val) : chalk.black.bgWhite.bold(val)
return width ? Table.padLeft(text, width) : text
}}
}))
let trade = async function() {
try {
let schedule = []
await Promise.all(_.map(botconfig, async (row) => {
let saturn = makeSaturnClient(row.blockchain.toUpperCase(), program, wallet)
let info = await saturn.query.getTokenInfo(row.token, row.blockchain)
if (row.action === 'buy') {
let threshold = new BigNumber(row.price)
let bestBuyPrice = new BigNumber(info.best_sell_price)
if (bestBuyPrice.isLessThanOrEqualTo(threshold)) {
schedule.push(async () => {
console.log(chalk.yellow(`Buy opportunity for ${row.blockchain}::${row.token} @ ${chalk.green(info.best_sell_price)}`))
let tradeLimit = await allowedLimit(
row.token.toLowerCase(),
row.blockchain.toUpperCase(),
row.action,
wallet.address,
row.houretherlimit
)
if (! tradeLimit.gt(epsilon)) {
console.log(chalk.yellow(`Hourly trade limit reached. Skipping...`))
return true
}
let order = await orderInfo(row.blockchain, info.best_sell_order_tx)
let tradeEtherAmount = tradeLimit.gt(order.etherbalance) ?
order.etherbalance : tradeLimit
let walletBalance = await etherBalance(row.blockchain, wallet.address)
if (! walletBalance.gt(epsilon)) {
console.log(chalk.yellow(`Not enough ${row.blockchain} in your wallet to complete transaction. Skipping...`))
return true
}
tradeEtherAmount = tradeEtherAmount.gt(walletBalance) ? walletBalance : tradeEtherAmount
let tokenAmount = tradeEtherAmount.dividedBy(order.price).toFixed(parseInt(info.decimals))
let tx = await saturn[row.blockchain.toLowerCase()].newTrade(tokenAmount, info.best_sell_order_tx)
console.log(chalk.yellow(`Attempting to buy ${tokenAmount} tokens\ntx: ${chalk.underline(tx)}`))
await saturn.query.awaitTradeTx(tx, saturn[row.blockchain.toLowerCase()])
})
}
} else if (row.action === 'sell') {
let threshold = new BigNumber(row.price)
let bestSellPrice = new BigNumber(info.best_buy_price)
if (bestSellPrice.isGreaterThanOrEqualTo(threshold)) {
schedule.push(async () => {
console.log(chalk.yellow(`Sell opportunity for ${row.blockchain}::${row.token} @ ${chalk.red(info.best_buy_price)}`))
let tradeLimit = await allowedLimit(
row.token.toLowerCase(),
row.blockchain.toUpperCase(),
row.action,
wallet.address,
row.houretherlimit
)
if (! tradeLimit.gt(epsilon)) {
console.log(chalk.yellow(`Hourly trade limit reached. Skipping...`))
return true
}
let order = await orderInfo(row.blockchain, info.best_buy_order_tx)
let tradeEtherAmount = tradeLimit.gt(order.etherbalance) ?
order.etherbalance : tradeLimit
let tokenAmount = tradeEtherAmount.dividedBy(order.price).toFixed(parseInt(info.decimals))
let walletBalance = await tokenBalance(row.blockchain, row.token, wallet.address)
if (! walletBalance.gt(epsilon)) {
console.log(chalk.yellow(`Not enough ${row.blockchain}::${row.token} in your wallet to complete transaction. Skipping...`))
return true
}
tokenAmount = new BigNumber(tokenAmount).gt(walletBalance) ? walletBalance : tokenAmount
let tx = await saturn[row.blockchain.toLowerCase()].newTrade(tokenAmount, info.best_buy_order_tx)
console.log(chalk.yellow(`Attempting to sell ${tokenAmount} tokens\ntx: ${chalk.underline(tx)}`))
await saturn.query.awaitTradeTx(tx, saturn[row.blockchain.toLowerCase()])
})
}
} else {
throw new Error(`Unknown action ${row.action}`)
}
}))
if (schedule.length) { await pipeline(schedule) }
} catch(error) {
console.error(error.message)
}
setTimeout(trade, parseInt(program.delay) * 1000)
};
(async () => await trade())()