· 5 years ago · Sep 11, 2020, 09:08 AM
1require('dotenv').config()
2const bip39 = require('bip39')
3const BnbApiClient = require('@binance-chain/javascript-sdk')
4const BnbCrypto = require('@binance-chain/javascript-sdk').crypto;
5const WebSocket = require('ws');
6const express = require('express')
7const api = express();
8const cors = require('cors')
9
10/* Config */
11var WALLETS = [];
12var TRANSACTIONS = [];
13var CLIENT = {}
14
15const BNB_CHAIN = {testnet: 'https://testnet-dex.binance.org/', mainnet: 'https://dex.binance.org/'}
16const BNB_WS = {testnet: 'wss://testnet-dex.binance.org/api/ws/', mainnet: 'wss://dex.binance.org/api/ws/'}
17
18const CONFIG = {
19 chain: process.env.chain,
20 vault: process.env.vault,
21 faucet: {
22 sk: process.env.faucet_sk,
23 pk: process.env.faucet_pk,
24 },
25 funds: [{
26 denom: 'BNB',
27 amount: 0
28 },
29 {
30 denom: process.env.asset,
31 amount: process.env.amount
32 }],
33 wallets: process.env.wallets,
34 rounds: process.env.rounds,
35 gas: process.env.gas,
36 amount: process.env.amount,
37 asset: process.env.asset,
38 pool: process.env.pool,
39 runeprice: process.env.runeprice
40}
41
42/* Async Stall Helper */
43const stall = async(ms=1500) => {await new Promise(resolve => setTimeout(resolve, ms));}
44
45/* Wallet Constructor */
46var Wallet = function(sk,pk){
47 try{
48 this.client = new BnbApiClient(BNB_CHAIN[CONFIG.chain]),
49 this.mnemonic = bip39.generateMnemonic(),
50 this.sk = sk ? sk : BnbCrypto.getPrivateKeyFromMnemonic(this.mnemonic),
51 this.pk = pk ? pk : BnbCrypto.getAddressFromPrivateKey(this.sk)
52 this.txs = [];
53 } catch(e){
54 console.log('WALLET ERROR:',e)
55 return false;
56 }
57}
58
59/* Connect */
60Wallet.prototype.connect = async function(retry){
61 try{
62 this.client.chooseNetwork(CONFIG.chain);
63 await this.client.initChain()
64 await this.client.setPrivateKey(this.sk)
65 if (this.pk !== process.env.faucet_pk) { await this.client.useAsyncBroadcast(true) }
66 //console.log(`CONNECTED: ${this.pk}`)
67 this.connected = true;
68 return;
69 } catch (e){
70 if(retry){
71 this.connected = false;
72 return;
73 } else {
74 await stall(1000)
75 this.connect(true)
76 }
77 }
78}
79
80/* Send Spam Tx */
81Wallet.prototype.sendSpamTx = async function(delay, round) {
82 if (round > 0) {
83
84 }
85 try{
86 await stall(delay)
87 let res
88 if (round >0){
89 res = await this.client.transfer(this.pk, CONFIG.vault, Number(CONFIG.amount * CONFIG.runeprice), 'RUNE-A1F', `SWAP:BNB.${CONFIG.pool}::`)
90 } else {
91 res = await this.client.transfer(this.pk, CONFIG.vault, Number(CONFIG.amount), CONFIG.asset, CONFIG.asset == 'RUNE-A1F' ? `SWAP:BNB.${CONFIG.pool}::` : `SWAP:BNB.RUNE-A1F::`)
92 }
93
94 console.log(`ROUND: ${round}: ${this.pk}: SUCCESS!`)
95 return this.txs.push({round: round, status: 'success', hash: res.result[0].hash})
96 } catch(e){
97 console.log(e)
98 console.log(`ROUND: ${round}: ${this.pk}: FAIL!`)
99 return this.txs.push({round: round, status: 'fail', hash: null})
100 }
101}
102
103/* Get Balance */
104Wallet.prototype.getBalance = async function() {
105 return this.balance = await this.client.getBalance();
106}
107
108/* Drain Balance */
109Wallet.prototype.drainBalance = async function() {
110 let coins = [];
111 try {
112 await this.getBalance()
113
114 for (asset of this.balance) {
115 if(asset.symbol == 'BNB') { continue; }
116 coins.push({ "denom": asset.symbol, "amount": asset.symbol == 'BNB' ? bnbAmt.toFixed(8) : asset.free })
117 }
118
119 var txOutputs = [{ "to": CONFIG.faucet.pk, "coins": coins}]
120 let res = await this.client.multiSend(this.pk, txOutputs, "DRAIN");
121 console.log(`DRAIN: ${this.pk}: SUCCESS!`)
122 await stall(2000)
123 return this.drained = true;
124 } catch (e) {
125 console.log(e)
126 console.log(`DRAIN: ${this.pk}: FAIL!`)
127 await stall(1000)
128 return this.drained = false;
129 }
130
131}
132
133/* Make sense of memo */
134function splitMsg(msg){
135 try{
136 const text = msg;
137 const match = text.match(/^([^:]+):?(.+)?/)
138 let args = []
139 let command
140 if (match !== null) {
141 if (match[1]) {
142 command = match[1].toUpperCase()
143 }
144 if (match[2]) {
145 args = match[2].split(':')
146 }
147 }
148
149 let msgObj = {
150 raw: text,
151 command,
152 args
153 }
154
155 return msgObj || false;
156 } catch(e){
157 return false;
158 }
159}
160
161/* Make tx pretty */
162const parseTransaction = (payload) => {
163 let tx = {}
164 try{
165 tx.txEvent = payload.data.e
166 tx.txHash = payload.data.H
167 tx.txTo = payload.data.t[0].o
168 tx.txFrom = payload.data.f
169 tx.txTkn = payload.data.t[0].c[0].a
170 tx.txQty = Number(payload.data.t[0].c[0].A)
171 tx.txMemo = payload.data.M ? payload.data.M : null
172 tx.txMemo = splitMsg(tx.txMemo)
173 return tx
174 } catch (e){
175 console.log(e)
176 return null;
177 }
178 }
179
180
181/**
182 * Binance Chain Websocket events
183 */
184const connectWebsocket = () => {
185 var ws = new WebSocket(BNB_WS.testnet+CONFIG.vault);
186 ws.on('message', incoming = (data) => {
187 let rawtx = JSON.parse(data)
188 if (rawtx.stream == 'transfers'){
189 let tx = parseTransaction(rawtx)
190 if(tx.txFrom == CONFIG.vault){
191 txUpdate(tx)
192 }
193 }
194 });
195 ws.on('open', open = () => { console.log('WS: CONNECTED '+(new Date()).toISOString())});
196 ws.on('error', error = (e) => { console.log('WS: ERRORED: '+e+' '+(new Date()).toISOString())});
197 ws.on('close', close = (e) => { console.log('WS: CLOSED WITH ERROR: '+e);wsReconnect();});
198 ws.on('ping', ping = (data) => { ws.isAlive = true;});
199}
200
201/* Handle ws drop/reconnect */
202const wsReconnect = (wsRecon) => {
203 setTimeout(x => {
204 return connectWebsocket()
205 },500)
206}
207
208/* Tx Update */
209const txUpdate = (tx) => {
210 for (wallet of WALLETS){
211 if(wallet.pk == tx.txTo){
212 for (wtx of wallet.txs){
213 if(wtx.hash == tx.txMemo.args[0]){
214 wtx.complete = true;
215 wtx.swaphash = tx.txHash
216 console.log(`ROUND: ${wtx.round}: ${tx.txHash}: SWAP RECEIVED!`)
217 }
218 }
219 }
220 }
221}
222
223/* Create all the wallets */
224const makeWallets = () => {
225 WALLETS = []
226 for (let index = 0; index < CONFIG.wallets; index++) {
227 let wallet = new Wallet()
228 WALLETS.push(wallet)
229 console.log(`WALLET: ${wallet.pk}: CREATED`)
230 }
231 return WALLETS;
232}
233
234/* Connect all the wallets */
235const connectWallets = async() => {
236
237 for await (wallet of WALLETS){
238 try{
239 await wallet.connect()
240 } catch(e){
241 continue;
242 }
243 await stall(1000)
244 }
245
246 await stall(1000)
247 console.log('######################## WALLETS CONNECTED\n')
248}
249
250/* Fund all the wallets */
251const fundWallets = async() => {
252 await CLIENT.connect()
253 let txOut = [];
254
255 for (wallet of WALLETS){
256 let coins = JSON.parse(JSON.stringify(CONFIG.funds))
257 if(CONFIG.asset !== 'RUNE-A1F') { coins.push({denom: 'RUNE-A1F', amount: CONFIG.amount * CONFIG.runeprice}) }
258 coins[0].amount = CONFIG.gas * CONFIG.rounds + 0.0006
259 coins[0].amount = coins[0].amount.toFixed(8)
260 coins[1].amount = Number(coins[1].amount).toFixed(8)
261 coins[2].amount = coins[2].amount.toFixed(8)
262 txOut.push({ "to": wallet.pk, "coins": coins})
263 }
264
265
266 try {
267 let res = await CLIENT.client.multiSend(CLIENT.pk, txOut, "FUND");
268 console.log('######################## WALLETS CREATED & FUNDED\n')
269 return true;
270 } catch (e){
271 console.log(e)
272 }
273
274}
275
276/* Drain all the wallets */
277const drainWallets = async() => {
278 await stall(20000)
279 for await (wallet of WALLETS){
280 try {
281 //await wallet.connect()
282 await stall(2000)
283 await wallet.drainBalance();
284 } catch(e){
285 console.log(e)
286 continue;
287 }
288 }
289 await stall(2000)
290 console.log('######################## WALLETS DRAINED\n')
291 return;
292}
293
294/* Start Spamming */
295const spamTHORChain = async(round) => {
296 var promises = []
297 var i = 0
298
299 for (wallet of WALLETS){
300 if(wallet.connected){
301 promises.push(wallet.sendSpamTx(i,round))
302 i+=200
303 }
304 }
305
306 await Promise.all(promises)
307 .then(res => { return;})
308 .catch(e => { console.log(e)})
309}
310
311
312
313api.use(cors())
314
315api.get('/', (req,res) =>{
316 let endpoints = {
317 "wallets": '/wallets',
318 "txs": '/txs',
319 "pending": '/txs/pending',
320 "finished": '/txs/finished'
321 }
322 res.send(endpoints)
323})
324
325api.get('/wallets', async(req,res) =>{
326 let walletList = []
327 for (wallet of WALLETS){
328 walletList.push({
329 address: wallet.pk,
330 spam: wallet.txs
331 })
332 }
333
334 let resp = {
335 config: {
336 wallets: process.env.wallets,
337 rounds: process.env.rounds,
338 asset: process.env.asset,
339 amount: process.env.amount,
340 },
341 wallets: walletList
342 }
343 res.send(resp);
344})
345
346api.get('/txs', async(req,res) =>{
347 let txs = []
348 let total = 0
349 let pending = 0
350 let finalised = 0
351
352 for (wallet of WALLETS){
353 for(tx of wallet.txs) {
354 txs.push(tx)
355 total++
356 if(!tx.complete){
357 pending++
358
359 } else {
360 finalised++
361 }
362 }
363 }
364 res.send({wallets: WALLETS.length, total: total, pending: pending, received: finalised, txs});
365})
366
367api.get('/txs/pending', async(req,res) =>{
368 let txs = []
369 let total = 0
370 let pending = 0
371 let finalised = 0
372
373 for (wallet of WALLETS){
374 for(tx of wallet.txs) {
375 total++
376 if(!tx.complete){
377 pending++
378 txs.push(tx)
379 } else {
380 finalised++
381 }
382 }
383 }
384 res.send({wallets: WALLETS.length, total: total, pending: pending, received: finalised, txs});
385})
386
387api.get('/txs/complete', async(req,res) =>{
388 let txs = []
389 let total = 0
390 let pending = 0
391 let finalised = 0
392
393 for (wallet of WALLETS){
394 for(tx of wallet.txs) {
395 total++
396 if(!tx.complete){
397 pending++
398 } else {
399 txs.push(tx)
400 finalised++
401 }
402 }
403 }
404 res.send({wallets: WALLETS.length, total: total, pending: pending, received: finalised, txs});
405})
406
407/* MAIN RUNNER */
408const spammer = async() => {
409 CLIENT = new Wallet(CONFIG.faucet.sk, CONFIG.faucet.pk)
410 ROUNDS = [];
411
412 for (let index = 0; index < CONFIG.rounds; index++) {
413 ROUNDS.push(index);
414 }
415 console.log('######################## STARTING SPAMMER')
416 await makeWallets();
417 await fundWallets();
418 await stall(2000)
419 await connectWallets();
420
421 connectWebsocket();
422
423 for await (round of ROUNDS){
424 await spamTHORChain(round);
425 console.log(`######################## SPAM ROUND ${round} COMPLETE\n`)
426 await stall(2000)
427 }
428
429 await drainWallets();
430 return true;
431
432}
433
434
435const main = async(count) => {
436 let rounds = []
437
438 for (let index = 0; index < count; index++) {
439 rounds.push(index)
440 }
441
442 for await (round of rounds){
443 await spammer()
444 await stall(10000)
445 }
446}
447
448main(2)
449api.listen(process.env.EXPRESS_PORT)
450
451
452
453
454
455
456/* env
457
458
459chain=testnet
460vault=//pool address
461faucet_sk=//testnet private key
462faucet_pk=//testnet address
463asset=TUSDB-000
464pool=TUSDB-000
465amount=1
466runeprice=4
467wallets=10
468rounds=2
469gas=0.000375
470EXPRESS_PORT=3050
471
472*/