-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathindex.js
156 lines (138 loc) · 5.05 KB
/
index.js
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
'use strict'
const Discord = require('discord.js')
const rp = require('request-promise')
const botSettings = require('./config/config-bot.json') // Настройки конфигурации бота
const bot = new Discord.Client({ disableEveryone: true })
const EXCHANGES = require('./config/exchanges.json') //Список биржевых аккаунтов
const WHALE_THRESHOLD_VALUE = 2500 // SEM
const FUNDS = require('./config/funds.json') //Список аккаунтов, для слежения за донатами
const PUBLIC_POOLS = require('./config/public-pools.json') //Список аккаунтов публичных пулов
const API = 'https://api.semux.online/v2.3.0/' // API ноды
const IGNORE_LIST = new Map()
let BEST_HEIGHT = 0
bot.on('ready', () => {
console.log('Bot is online')
})
// Сканируем новый блок каждые 10 сек, отслеживаем транзакции в блоке
/*
Алерты:
- Крупный биржевой ввод и вывод
- Регистрация нового делегата
- Донат на контролируемый адрес
*/
setInterval(async function () {
var result = await scanNewBlock()
if (result.error) {
return
}
for (let tx of result.transfers) {
switch (tx.type) {
case 'deposited': bot.channels.find(c => c.name === 'uno-labs').send(`**[whale alert]** ${tx.value} SEM ${tx.type} to ${tx.account} :inbox_tray:`)
break;
case 'withdrawn': bot.channels.find(c => c.name === 'uno-labs').send(`**[whale alert]** ${tx.value} SEM ${tx.type} from ${tx.account} :outbox_tray:`)
break;
case 'donated': bot.channels.find(c => c.name === 'uno-labs').send(`Thank you very much for the donation! ${tx.value} SEM :thumbsup: https://semux.top/address/${tx.account}`)
break;
case 'delegate': bot.channels.find(c => c.name === 'bla-bla-bla').send(`:partying_face: New delegate! https://semux.top/delegate/${tx.value}`)
break;
}
}
}, 10 * 1000)
async function scanNewBlock() {
let lastHeight;
try {
lastHeight = JSON.parse(await rp(`${API}latest-block-number`))
} catch (e) {
console.error('Failed to get latest block number', e.message)
return { error: true }
}
lastHeight = parseInt(lastHeight.result, 10)
if (lastHeight === BEST_HEIGHT) {
return { error: true }
}
BEST_HEIGHT = lastHeight
let block;
try {
block = JSON.parse(await rp(`${API}block-by-number?number=${BEST_HEIGHT}`))
} catch (e) {
console.error('Failed to get block by number', e.message)
return { error: true }
}
if (!block.result || !block.result.transactions) {
return { error: true }
}
let transfers = [];
for (let tx of block.result.transactions) {
let value = parseInt(tx.value, 10) / 1e9
switch (tx.type) {
case 'TRANSFER':
if (value > WHALE_THRESHOLD_VALUE && EXCHANGES[tx.from]) {
transfers.push({ account: EXCHANGES[tx.from], value: value.toFixed(2), type: 'withdrawn' })
}
if (value > WHALE_THRESHOLD_VALUE && EXCHANGES[tx.to]) {
transfers.push({ account: EXCHANGES[tx.to], value: value.toFixed(2), type: 'deposited' })
}
if (FUNDS[tx.to]) {
transfers.push({ account: tx.to, value: value.toFixed(2), type: 'donated' })
}
break;
case 'DELEGATE':
transfers.push({ account: tx.from, value: hexToString(tx.data), type: 'delegate' })
break;
}
}
return { success: true, transfers: transfers }
}
//Следим за валидаторами публичных пулов
/*
Алерты:
- Валидатор выпал из Топ-100 (алерт не чаще раз в сутки)
*/
setInterval(async function () {
var result = await scanDelegates()
if (result.error) {
return
}
for (let pool of result.pools) {
bot.channels.find(c => c.name === 'uno-labs').send(`:scream: Pool https://semux.top/delegate/${pool.validator} needs votes to get back in Top-100!`)
}
}, 300 * 1000)
async function scanDelegates() {
let delegates = [];
try {
delegates = JSON.parse(await rp(`${API}delegates`))
} catch (e) {
console.error('Failed to get list of delegates', e.message)
return { error: true }
}
let pools = [];
let rank = 1
if (!delegates.result) {
return { error: true }
}
for (let delegate of delegates.result) {
if (PUBLIC_POOLS[delegate.address]) {
if (!IGNORE_LIST.get(delegate.address)) {
if (rank > 100) {
IGNORE_LIST.set(delegate.address, Date.now())
pools.push({validator: PUBLIC_POOLS[delegate.address], rank: rank})
}
} else {
let longTime = Date.now() - IGNORE_LIST.get(delegate.address)
if (longTime > 60 * 60 * 24 * 1000 && rank < 101) {
IGNORE_LIST.delete(delegate.address)
}
}
}
rank +=1
}
return { success: true, pools: pools }
}
function hexToString (hex) {
var string = '';
for (var i = 2; i < hex.length; i += 2) {
string += String.fromCharCode(parseInt(hex.substr(i, 2), 16));
}
return string;
}
bot.login(botSettings.token)