hive-stream 2.0.3 → 2.0.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (99) hide show
  1. package/.env.example +3 -2
  2. package/.travis.yml +11 -11
  3. package/LICENSE +21 -21
  4. package/README.md +238 -236
  5. package/dist/actions.d.ts +10 -9
  6. package/dist/actions.js +23 -15
  7. package/dist/actions.js.map +1 -1
  8. package/dist/adapters/base.adapter.d.ts +25 -21
  9. package/dist/adapters/base.adapter.js +49 -63
  10. package/dist/adapters/base.adapter.js.map +1 -1
  11. package/dist/adapters/mongodb.adapter.d.ts +37 -29
  12. package/dist/adapters/mongodb.adapter.js +158 -113
  13. package/dist/adapters/mongodb.adapter.js.map +1 -1
  14. package/dist/adapters/sqlite.adapter.d.ts +41 -23
  15. package/dist/adapters/sqlite.adapter.js +397 -121
  16. package/dist/adapters/sqlite.adapter.js.map +1 -1
  17. package/dist/api.d.ts +6 -0
  18. package/dist/api.js +56 -0
  19. package/dist/api.js.map +1 -0
  20. package/dist/config.d.ts +16 -14
  21. package/dist/config.js +18 -15
  22. package/dist/config.js.map +1 -1
  23. package/dist/contracts/coinflip.contract.d.ts +14 -0
  24. package/dist/contracts/coinflip.contract.js +95 -0
  25. package/dist/contracts/coinflip.contract.js.map +1 -0
  26. package/dist/contracts/dice.contract.d.ts +29 -29
  27. package/dist/contracts/dice.contract.js +155 -157
  28. package/dist/contracts/dice.contract.js.map +1 -1
  29. package/dist/contracts/lotto.contract.d.ts +20 -16
  30. package/dist/contracts/lotto.contract.js +246 -107
  31. package/dist/contracts/lotto.contract.js.map +1 -1
  32. package/dist/exchanges/bittrex.d.ts +6 -0
  33. package/dist/exchanges/bittrex.js +35 -0
  34. package/dist/exchanges/bittrex.js.map +1 -0
  35. package/dist/exchanges/exchange.d.ts +9 -0
  36. package/dist/exchanges/exchange.js +27 -0
  37. package/dist/exchanges/exchange.js.map +1 -0
  38. package/dist/hive-rates.d.ts +9 -0
  39. package/dist/hive-rates.js +76 -0
  40. package/dist/hive-rates.js.map +1 -0
  41. package/dist/index.d.ts +11 -10
  42. package/dist/index.js +32 -15
  43. package/dist/index.js.map +1 -1
  44. package/dist/streamer.d.ts +93 -70
  45. package/dist/streamer.js +545 -439
  46. package/dist/streamer.js.map +1 -1
  47. package/dist/test.d.ts +1 -1
  48. package/dist/test.js +25 -27
  49. package/dist/test.js.map +1 -1
  50. package/dist/types/hive-stream.d.ts +6 -6
  51. package/dist/types/hive-stream.js +2 -2
  52. package/dist/utils.d.ts +27 -14
  53. package/dist/utils.js +261 -85
  54. package/dist/utils.js.map +1 -1
  55. package/ecosystem.config.js +17 -17
  56. package/jest.config.js +8 -13
  57. package/package.json +48 -44
  58. package/test-contract-block.md +18 -18
  59. package/tests/adapters/sqlite.adapter.spec.ts +43 -0
  60. package/tests/contracts/coinflip.contract.spec.ts +132 -0
  61. package/tests/contracts/dice.contract.spec.ts +159 -156
  62. package/tests/contracts/entrants.json +728 -728
  63. package/tests/contracts/lotto.contract.spec.ts +323 -372
  64. package/tests/setup.ts +18 -20
  65. package/tests/streamer.spec.ts +151 -151
  66. package/tests/utils.spec.ts +94 -99
  67. package/tsconfig.build.json +22 -20
  68. package/tslint.json +20 -20
  69. package/wallaby.js +26 -0
  70. package/.env +0 -1
  71. package/dist/adapters/file.adapter.d.ts +0 -8
  72. package/dist/adapters/file.adapter.js +0 -70
  73. package/dist/adapters/file.adapter.js.map +0 -1
  74. package/dist/test/setup.d.ts +0 -0
  75. package/dist/test/setup.js +0 -9
  76. package/dist/test/setup.js.map +0 -1
  77. package/dist/test/streamer.spec.d.ts +0 -1
  78. package/dist/test/streamer.spec.js +0 -145
  79. package/dist/test/streamer.spec.js.map +0 -1
  80. package/dist/test/utils.spec.d.ts +0 -1
  81. package/dist/test/utils.spec.js +0 -11
  82. package/dist/test/utils.spec.js.map +0 -1
  83. package/dist/tests/contracts/dice.contract.spec.d.ts +0 -1
  84. package/dist/tests/contracts/dice.contract.spec.js +0 -130
  85. package/dist/tests/contracts/dice.contract.spec.js.map +0 -1
  86. package/dist/tests/contracts/entrants.json +0 -729
  87. package/dist/tests/contracts/lotto.contract.spec.d.ts +0 -1
  88. package/dist/tests/contracts/lotto.contract.spec.js +0 -300
  89. package/dist/tests/contracts/lotto.contract.spec.js.map +0 -1
  90. package/dist/tests/setup.d.ts +0 -1
  91. package/dist/tests/setup.js +0 -19
  92. package/dist/tests/setup.js.map +0 -1
  93. package/dist/tests/streamer.spec.d.ts +0 -1
  94. package/dist/tests/streamer.spec.js +0 -123
  95. package/dist/tests/streamer.spec.js.map +0 -1
  96. package/dist/tests/utils.spec.d.ts +0 -1
  97. package/dist/tests/utils.spec.js +0 -87
  98. package/dist/tests/utils.spec.js.map +0 -1
  99. package/hive-stream.json +0 -1
package/dist/streamer.js CHANGED
@@ -1,440 +1,546 @@
1
- "use strict";
2
- var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
- function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
- return new (P || (P = Promise))(function (resolve, reject) {
5
- function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
- function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
- function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
- step((generator = generator.apply(thisArg, _arguments || [])).next());
9
- });
10
- };
11
- var __importDefault = (this && this.__importDefault) || function (mod) {
12
- return (mod && mod.__esModule) ? mod : { "default": mod };
13
- };
14
- Object.defineProperty(exports, "__esModule", { value: true });
15
- const actions_1 = require("./actions");
16
- const file_adapter_1 = require("./adapters/file.adapter");
17
- const dhive_1 = require("@hivechain/dhive");
18
- const utils_1 = require("./utils");
19
- const config_1 = require("./config");
20
- const moment_1 = __importDefault(require("moment"));
21
- class Streamer {
22
- constructor(userConfig = {}) {
23
- this.customJsonSubscriptions = [];
24
- this.customJsonIdSubscriptions = [];
25
- this.commentSubscriptions = [];
26
- this.postSubscriptions = [];
27
- this.transferSubscriptions = [];
28
- this.attempts = 0;
29
- this.config = config_1.Config;
30
- this.blockNumberTimeout = null;
31
- this.lastBlockNumber = 0;
32
- this.disableAllProcessing = false;
33
- this.contracts = [];
34
- this.actions = [];
35
- this.utils = utils_1.Utils;
36
- this.config = Object.assign(config_1.Config, userConfig);
37
- this.lastBlockNumber = this.config.LAST_BLOCK_NUMBER;
38
- this.username = this.config.USERNAME;
39
- this.postingKey = this.config.POSTING_KEY;
40
- this.activeKey = this.config.ACTIVE_KEY;
41
- this.client = new dhive_1.Client(this.config.API_NODES);
42
- this.registerAdapter(new file_adapter_1.FileAdapter());
43
- }
44
- registerAdapter(adapter) {
45
- this.adapter = adapter;
46
- }
47
- getAdapter() {
48
- return this.adapter;
49
- }
50
- registerAction(action) {
51
- return __awaiter(this, void 0, void 0, function* () {
52
- const loadedActions = yield this.adapter.loadActions();
53
- for (const a of loadedActions) {
54
- const exists = this.actions.find(i => i.id === a.id);
55
- if (!exists) {
56
- this.actions.push(new actions_1.TimeAction(a.timeValue, a.id, a.contractName, a.contractMethod, a.date));
57
- }
58
- }
59
- const exists = this.actions.find(a => a.id === action.id);
60
- if (!exists) {
61
- this.actions.push(action);
62
- }
63
- });
64
- }
65
- registerContract(name, contract) {
66
- // Store an instance of the streamer
67
- contract['_instance'] = this;
68
- // Call the contract create lifecycle method if it exists
69
- if (contract && typeof contract['create'] !== 'undefined') {
70
- contract.create();
71
- }
72
- const storedReference = { name, contract };
73
- // Push the contract reference to be called later on
74
- this.contracts.push(storedReference);
75
- return this;
76
- }
77
- unregisterContract(name) {
78
- // Find the registered contract by it's ID
79
- const contractIndex = this.contracts.findIndex(c => c.name === name);
80
- if (contractIndex >= 0) {
81
- // Get the contract itself
82
- const contract = this.contracts.find(c => c.name === name);
83
- // Call the contract destroy lifecycle method if it exists
84
- if (contract && typeof contract.contract['destroy'] !== 'undefined') {
85
- contract.contract.destroy();
86
- }
87
- // Remove the contract
88
- this.contracts.splice(contractIndex, 1);
89
- }
90
- }
91
- /**
92
- * setConfig
93
- *
94
- * Allows specific configuration settings to be overridden
95
- *
96
- * @param config
97
- */
98
- setConfig(config) {
99
- Object.assign(this.config, config);
100
- // Set keys and username incase they have changed
101
- this.username = this.config.USERNAME;
102
- this.postingKey = this.config.POSTING_KEY;
103
- this.activeKey = this.config.ACTIVE_KEY;
104
- return this;
105
- }
106
- /**
107
- * Start
108
- *
109
- * Starts the streamer bot to get blocks from the Hive API
110
- *
111
- */
112
- start() {
113
- return __awaiter(this, void 0, void 0, function* () {
114
- if (this.config.DEBUG_MODE) {
115
- console.log('Starting to stream the Hive blockchain');
116
- }
117
- yield this.adapter.create();
118
- this.disableAllProcessing = false;
119
- const state = yield this.adapter.loadState();
120
- if (this.config.DEBUG_MODE) {
121
- console.log(`Restoring state from file`);
122
- }
123
- if (state === null || state === void 0 ? void 0 : state.lastBlockNumber) {
124
- if (state.lastBlockNumber) {
125
- this.lastBlockNumber = state.lastBlockNumber;
126
- }
127
- }
128
- // Kicks off the blockchain streaming and operation parsing
129
- this.getBlock();
130
- setInterval(() => { this.getLatestBlock(); }, this.config.BLOCK_CHECK_INTERVAL);
131
- });
132
- }
133
- /**
134
- * Stop
135
- *
136
- * Stops the streamer from running
137
- */
138
- stop() {
139
- this.disableAllProcessing = true;
140
- if (this.blockNumberTimeout) {
141
- clearTimeout(this.blockNumberTimeout);
142
- }
143
- this.adapter.destroy();
144
- }
145
- getLatestBlock() {
146
- return __awaiter(this, void 0, void 0, function* () {
147
- const props = yield this.client.database.getDynamicGlobalProperties();
148
- if (props) {
149
- this.latestBlockchainTime = new Date(`${props.time}Z`);
150
- }
151
- });
152
- }
153
- getBlock() {
154
- return __awaiter(this, void 0, void 0, function* () {
155
- try {
156
- // Load global properties from the Hive API
157
- const props = yield this.client.database.getDynamicGlobalProperties();
158
- // We have no props, so try loading them again.
159
- if (!props) {
160
- this.blockNumberTimeout = setTimeout(() => {
161
- this.getBlock();
162
- }, this.config.BLOCK_CHECK_INTERVAL);
163
- return;
164
- }
165
- // If the block number we've got is zero
166
- // set it to the last irreversible block number
167
- if (this.lastBlockNumber === 0) {
168
- this.lastBlockNumber = props.head_block_number - 1;
169
- }
170
- if (this.config.DEBUG_MODE) {
171
- console.log(`Head block number: `, props.head_block_number);
172
- console.log(`Last block number: `, this.lastBlockNumber);
173
- }
174
- const BLOCKS_BEHIND = parseInt(this.config.BLOCKS_BEHIND_WARNING, 10);
175
- // We are more than 25 blocks behind, uh oh, we gotta catch up
176
- if (props.head_block_number >= (this.lastBlockNumber + BLOCKS_BEHIND) && this.config.DEBUG_MODE) {
177
- console.log(`We are more than ${BLOCKS_BEHIND} blocks behind ${props.head_block_number}, ${(this.lastBlockNumber + BLOCKS_BEHIND)}`);
178
- }
179
- if (!this.disableAllProcessing) {
180
- yield this.loadBlock(this.lastBlockNumber + 1);
181
- }
182
- // Storing timeout allows us to clear it, as this just calls itself
183
- if (!this.disableAllProcessing) {
184
- this.blockNumberTimeout = setTimeout(() => { this.getBlock(); }, this.config.BLOCK_CHECK_INTERVAL);
185
- }
186
- }
187
- catch (e) {
188
- const message = e.message.toLowerCase();
189
- console.error(message);
190
- }
191
- });
192
- }
193
- // Takes the block from Hive and allows us to work with it
194
- loadBlock(blockNumber) {
195
- return __awaiter(this, void 0, void 0, function* () {
196
- // Load the block itself from the Hive API
197
- const block = yield this.client.database.getBlock(blockNumber);
198
- // The block doesn't exist, wait and try again
199
- if (!block) {
200
- yield utils_1.Utils.sleep(this.config.BLOCK_CHECK_INTERVAL);
201
- return;
202
- }
203
- // Get the block date and time
204
- const blockTime = new Date(`${block.timestamp}Z`);
205
- if (this.lastBlockNumber !== blockNumber) {
206
- this.processActions();
207
- }
208
- this.blockId = block.block_id;
209
- this.previousBlockId = block.previous;
210
- this.transactionId = block.transaction_ids[1];
211
- this.blockTime = blockTime;
212
- this.adapter.processBlock(block);
213
- // Loop over all transactions in the block
214
- for (const [i, transaction] of Object.entries(block.transactions)) {
215
- // Loop over operations in the block
216
- for (const [opIndex, op] of Object.entries(transaction.operations)) {
217
- // For every operation, process it
218
- yield this.processOperation(op, blockNumber, block.block_id, block.previous, block.transaction_ids[i], blockTime);
219
- }
220
- }
221
- this.lastBlockNumber = blockNumber;
222
- this.saveStateToDisk();
223
- });
224
- }
225
- processOperation(op, blockNumber, blockId, prevBlockId, trxId, blockTime) {
226
- var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k;
227
- this.adapter.processOperation(op, blockNumber, blockId, prevBlockId, trxId, blockTime);
228
- // Operation is a "comment" which could either be a post or comment
229
- if (op[0] === 'comment') {
230
- // This is a post
231
- if (op[1].parent_author === '') {
232
- this.postSubscriptions.forEach(sub => {
233
- sub.callback(op[1], blockNumber, blockId, prevBlockId, trxId, blockTime);
234
- });
235
- // This is a comment
236
- }
237
- else {
238
- this.commentSubscriptions.forEach(sub => {
239
- sub.callback(op[1], blockNumber, blockId, prevBlockId, trxId, blockTime);
240
- });
241
- }
242
- }
243
- // This is a transfer
244
- if (op[0] === 'transfer') {
245
- const sender = (_a = op[1]) === null || _a === void 0 ? void 0 : _a.from;
246
- const amount = (_b = op[1]) === null || _b === void 0 ? void 0 : _b.amount;
247
- const json = utils_1.Utils.jsonParse(op[1].memo);
248
- if ((json === null || json === void 0 ? void 0 : json[this.config.PAYLOAD_IDENTIFIER]) && ((_c = json === null || json === void 0 ? void 0 : json[this.config.PAYLOAD_IDENTIFIER]) === null || _c === void 0 ? void 0 : _c.id) === this.config.JSON_ID) {
249
- // Pull out details of contract
250
- const { name, action, payload } = json[this.config.PAYLOAD_IDENTIFIER];
251
- // Do we have a contract that matches the name in the payload?
252
- const contract = this.contracts.find(c => c.name === name);
253
- if (contract) {
254
- this.adapter.processTransfer(op[1], { name, action, payload }, { sender, amount });
255
- if ((_d = contract === null || contract === void 0 ? void 0 : contract.contract) === null || _d === void 0 ? void 0 : _d.updateBlockInfo) {
256
- contract.contract.updateBlockInfo(blockNumber, blockId, prevBlockId, trxId);
257
- }
258
- if (contract === null || contract === void 0 ? void 0 : contract.contract[action]) {
259
- contract.contract[action](payload, { sender, amount });
260
- }
261
- }
262
- }
263
- this.transferSubscriptions.forEach(sub => {
264
- if (sub.account === op[1].to) {
265
- sub.callback(op[1], blockNumber, blockId, prevBlockId, trxId, blockTime);
266
- }
267
- });
268
- }
269
- // This is a custom JSON operation
270
- if (op[0] === 'custom_json') {
271
- let isSignedWithActiveKey = false;
272
- let sender;
273
- const id = (_e = op[1]) === null || _e === void 0 ? void 0 : _e.id;
274
- if (((_g = (_f = op[1]) === null || _f === void 0 ? void 0 : _f.required_auths) === null || _g === void 0 ? void 0 : _g.length) > 0) {
275
- sender = op[1].required_auths[0];
276
- isSignedWithActiveKey = true;
277
- }
278
- else if (((_j = (_h = op[1]) === null || _h === void 0 ? void 0 : _h.required_posting_auths) === null || _j === void 0 ? void 0 : _j.length) > 0) {
279
- sender = op[1].required_posting_auths[0];
280
- isSignedWithActiveKey = false;
281
- }
282
- const json = utils_1.Utils.jsonParse(op[1].json);
283
- if (json && (json === null || json === void 0 ? void 0 : json[this.config.PAYLOAD_IDENTIFIER]) && id === this.config.JSON_ID) {
284
- // Pull out details of contract
285
- const { name, action, payload } = json[this.config.PAYLOAD_IDENTIFIER];
286
- // Do we have a contract that matches the name in the payload?
287
- const contract = this.contracts.find(c => c.name === name);
288
- if (contract) {
289
- this.adapter.processCustomJson(op[1], { name, action, payload }, { sender, isSignedWithActiveKey });
290
- if ((_k = contract === null || contract === void 0 ? void 0 : contract.contract) === null || _k === void 0 ? void 0 : _k.updateBlockInfo) {
291
- contract.contract.updateBlockInfo(blockNumber, blockId, prevBlockId, trxId);
292
- }
293
- if (contract === null || contract === void 0 ? void 0 : contract.contract[action]) {
294
- contract.contract[action](payload, { sender, isSignedWithActiveKey }, id);
295
- }
296
- }
297
- }
298
- this.customJsonSubscriptions.forEach(sub => {
299
- sub.callback(op[1], { sender, isSignedWithActiveKey }, blockNumber, blockId, prevBlockId, trxId, blockTime);
300
- });
301
- this.customJsonIdSubscriptions.forEach(sub => {
302
- const byId = this.customJsonIdSubscriptions.find(s => s.id === op[1].id);
303
- if (byId) {
304
- sub.callback(op[1], { sender, isSignedWithActiveKey }, blockNumber, blockId, prevBlockId, trxId, blockTime);
305
- }
306
- });
307
- }
308
- }
309
- processActions() {
310
- var _a;
311
- const blockDate = moment_1.default.utc(this.latestBlockchainTime);
312
- for (const action of this.actions) {
313
- const date = moment_1.default.utc(action.date);
314
- const frequency = action.timeValue;
315
- const contract = this.contracts.find(c => c.name === action.contractName);
316
- // Contract doesn't exist or action doesn't exist, carry on
317
- if (!contract || !((_a = contract === null || contract === void 0 ? void 0 : contract.contract) === null || _a === void 0 ? void 0 : _a[action.contractMethod])) {
318
- continue;
319
- }
320
- let difference = 0;
321
- switch (frequency) {
322
- case '3s':
323
- case 'block':
324
- difference = date.diff(blockDate, 's');
325
- // 3 seconds or more has passed
326
- if (difference >= 3) {
327
- contract.contract[action.contractMethod]();
328
- action.reset();
329
- }
330
- break;
331
- case '30s':
332
- difference = blockDate.diff(date, 's');
333
- // 30 seconds or more has passed
334
- if (difference >= 30) {
335
- contract.contract[action.contractMethod]();
336
- action.reset();
337
- }
338
- break;
339
- case '1m':
340
- case 'minute':
341
- difference = blockDate.diff(date, 'm');
342
- // One minute has passed
343
- if (difference >= 1) {
344
- contract.contract[action.contractMethod]();
345
- action.reset();
346
- }
347
- break;
348
- case '15m':
349
- case 'quarter':
350
- difference = blockDate.diff(date, 'm');
351
- // 15 minutes has passed
352
- if (difference >= 15) {
353
- contract.contract[action.contractMethod]();
354
- action.reset();
355
- }
356
- break;
357
- case '30m':
358
- case 'halfhour':
359
- difference = blockDate.diff(date, 'm');
360
- // 30 minutes has passed
361
- if (difference >= 30) {
362
- contract.contract[action.contractMethod]();
363
- action.reset();
364
- }
365
- break;
366
- case 'hourly':
367
- case '1h':
368
- difference = blockDate.diff(date, 'h');
369
- // One our or more has passed
370
- if (difference >= 1) {
371
- contract.contract[action.contractMethod]();
372
- action.reset();
373
- }
374
- break;
375
- case '12h':
376
- case 'halfday':
377
- difference = blockDate.diff(date, 'h');
378
- // Twelve hours or more has passed
379
- if (difference >= 12) {
380
- contract.contract[action.contractMethod]();
381
- action.reset();
382
- }
383
- break;
384
- case '24h':
385
- case 'day':
386
- difference = blockDate.diff(date, 'd');
387
- // One day (24 hours) has passed
388
- if (difference >= 1) {
389
- contract.contract[action.contractMethod]();
390
- action.reset();
391
- }
392
- break;
393
- }
394
- }
395
- }
396
- saveStateToDisk() {
397
- return __awaiter(this, void 0, void 0, function* () {
398
- this.adapter.saveState({ lastBlockNumber: this.lastBlockNumber, actions: this.actions });
399
- });
400
- }
401
- transferHiveTokens(from, to, amount, symbol, memo = '') {
402
- return utils_1.Utils.transferHiveTokens(this.client, this.config, from, to, amount, symbol, memo);
403
- }
404
- upvote(votePercentage = '100.0', username, permlink) {
405
- return utils_1.Utils.upvote(this.client, this.config, this.username, votePercentage, username, permlink);
406
- }
407
- downvote(votePercentage = '100.0', username, permlink) {
408
- return utils_1.Utils.downvote(this.client, this.config, this.username, votePercentage, username, permlink);
409
- }
410
- getTransaction(blockNumber, transactionId) {
411
- return utils_1.Utils.getTransaction(this.client, blockNumber, transactionId);
412
- }
413
- verifyTransfer(transaction, from, to, amount) {
414
- return utils_1.Utils.verifyTransfer(transaction, from, to, amount);
415
- }
416
- onComment(callback) {
417
- this.commentSubscriptions.push({
418
- callback
419
- });
420
- }
421
- onPost(callback) {
422
- this.postSubscriptions.push({
423
- callback
424
- });
425
- }
426
- onTransfer(account, callback) {
427
- this.transferSubscriptions.push({
428
- account,
429
- callback
430
- });
431
- }
432
- onCustomJson(callback) {
433
- this.customJsonSubscriptions.push({ callback });
434
- }
435
- onCustomJsonId(callback, id) {
436
- this.customJsonIdSubscriptions.push({ callback, id });
437
- }
438
- }
439
- exports.Streamer = Streamer;
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.Streamer = void 0;
7
+ const api_1 = require("./api");
8
+ const sqlite_adapter_1 = require("./adapters/sqlite.adapter");
9
+ const utils_1 = require("@hiveio/dhive/lib/utils");
10
+ const actions_1 = require("./actions");
11
+ const dhive_1 = require("@hiveio/dhive");
12
+ const utils_2 = require("./utils");
13
+ const config_1 = require("./config");
14
+ const moment_1 = __importDefault(require("moment"));
15
+ const sscjs_1 = __importDefault(require("sscjs"));
16
+ class Streamer {
17
+ customJsonSubscriptions = [];
18
+ customJsonIdSubscriptions = [];
19
+ customJsonHiveEngineSubscriptions = [];
20
+ commentSubscriptions = [];
21
+ postSubscriptions = [];
22
+ transferSubscriptions = [];
23
+ attempts = 0;
24
+ config = config_1.Config;
25
+ client;
26
+ hive;
27
+ username;
28
+ postingKey;
29
+ activeKey;
30
+ blockNumberTimeout = null;
31
+ latestBlockTimer = null;
32
+ lastBlockNumber = 0;
33
+ blockId;
34
+ previousBlockId;
35
+ transactionId;
36
+ blockTime;
37
+ latestBlockchainTime;
38
+ disableAllProcessing = false;
39
+ contracts = [];
40
+ adapter;
41
+ actions = [];
42
+ utils = utils_2.Utils;
43
+ constructor(userConfig = {}) {
44
+ this.config = Object.assign(config_1.Config, userConfig);
45
+ this.lastBlockNumber = this.config.LAST_BLOCK_NUMBER;
46
+ this.username = this.config.USERNAME;
47
+ this.postingKey = this.config.POSTING_KEY;
48
+ this.activeKey = this.config.ACTIVE_KEY;
49
+ this.hive = new sscjs_1.default(this.config.HIVE_ENGINE_API);
50
+ this.client = new dhive_1.Client(this.config.API_NODES);
51
+ this.registerAdapter(new sqlite_adapter_1.SqliteAdapter());
52
+ if (process?.env?.NODE_ENV !== 'test') {
53
+ new api_1.Api(this);
54
+ }
55
+ }
56
+ registerAdapter(adapter) {
57
+ this.adapter = adapter;
58
+ if (this?.adapter?.create) {
59
+ this.adapter.create();
60
+ }
61
+ }
62
+ getAdapter() {
63
+ return this.adapter;
64
+ }
65
+ /**
66
+ * Register a new action
67
+ *
68
+ */
69
+ async registerAction(action) {
70
+ const loadedActions = await this.adapter.loadActions();
71
+ for (const a of loadedActions) {
72
+ const exists = this.actions.find(i => i.id === a.id);
73
+ if (!exists) {
74
+ this.actions.push(new actions_1.TimeAction(a.timeValue, a.id, a.contractName, a.contractMethod, a.date));
75
+ }
76
+ }
77
+ const exists = this.actions.find(a => a.id === action.id);
78
+ if (!exists) {
79
+ this.actions.push(action);
80
+ }
81
+ }
82
+ /**
83
+ * Resets a specific action time value
84
+ *
85
+ * @param id
86
+ *
87
+ */
88
+ resetAction(id) {
89
+ const action = this.actions.find(i => i.id === id);
90
+ if (action) {
91
+ action.reset();
92
+ }
93
+ }
94
+ registerContract(name, contract) {
95
+ // Store an instance of the streamer
96
+ contract['_instance'] = this;
97
+ // Call the contract create lifecycle method if it exists
98
+ if (contract && typeof contract['create'] !== 'undefined') {
99
+ contract.create();
100
+ }
101
+ const storedReference = { name, contract };
102
+ // Push the contract reference to be called later on
103
+ this.contracts.push(storedReference);
104
+ return this;
105
+ }
106
+ unregisterContract(name) {
107
+ // Find the registered contract by it's ID
108
+ const contractIndex = this.contracts.findIndex(c => c.name === name);
109
+ if (contractIndex >= 0) {
110
+ // Get the contract itself
111
+ const contract = this.contracts.find(c => c.name === name);
112
+ // Call the contract destroy lifecycle method if it exists
113
+ if (contract && typeof contract.contract['destroy'] !== 'undefined') {
114
+ contract.contract.destroy();
115
+ }
116
+ // Remove the contract
117
+ this.contracts.splice(contractIndex, 1);
118
+ }
119
+ }
120
+ /**
121
+ * setConfig
122
+ *
123
+ * Allows specific configuration settings to be overridden
124
+ *
125
+ * @param config
126
+ */
127
+ setConfig(config) {
128
+ Object.assign(this.config, config);
129
+ // Set keys and username incase they have changed
130
+ this.username = this.config.USERNAME;
131
+ this.postingKey = this.config.POSTING_KEY;
132
+ this.activeKey = this.config.ACTIVE_KEY;
133
+ return this;
134
+ }
135
+ /**
136
+ * Start
137
+ *
138
+ * Starts the streamer bot to get blocks from the Hive API
139
+ *
140
+ */
141
+ async start() {
142
+ if (this.config.DEBUG_MODE) {
143
+ console.log('Starting to stream the Hive blockchain');
144
+ }
145
+ this.disableAllProcessing = false;
146
+ const state = await this.adapter.loadState();
147
+ if (this.config.DEBUG_MODE) {
148
+ console.log(`Restoring state from file`);
149
+ }
150
+ if (!this.config.LAST_BLOCK_NUMBER && state?.lastBlockNumber) {
151
+ if (state.lastBlockNumber) {
152
+ this.lastBlockNumber = state.lastBlockNumber;
153
+ }
154
+ }
155
+ // Kicks off the blockchain streaming and operation parsing
156
+ this.getBlock();
157
+ this.latestBlockTimer = setInterval(() => { this.getLatestBlock(); }, this.config.BLOCK_CHECK_INTERVAL);
158
+ return this;
159
+ }
160
+ /**
161
+ * Stop
162
+ *
163
+ * Stops the streamer from running
164
+ */
165
+ async stop() {
166
+ this.disableAllProcessing = true;
167
+ if (this.blockNumberTimeout) {
168
+ clearTimeout(this.blockNumberTimeout);
169
+ }
170
+ if (this.latestBlockTimer) {
171
+ clearInterval(this.latestBlockTimer);
172
+ }
173
+ if (this?.adapter?.destroy) {
174
+ this.adapter.destroy();
175
+ }
176
+ await (0, utils_1.sleep)(800);
177
+ }
178
+ async getLatestBlock() {
179
+ const props = await this.client.database.getDynamicGlobalProperties();
180
+ if (props) {
181
+ this.latestBlockchainTime = new Date(`${props.time}Z`);
182
+ }
183
+ }
184
+ async getBlock() {
185
+ try {
186
+ // Load global properties from the Hive API
187
+ const props = await this.client.database.getDynamicGlobalProperties();
188
+ // We have no props, so try loading them again.
189
+ if (!props && !this.disableAllProcessing) {
190
+ this.blockNumberTimeout = setTimeout(() => {
191
+ this.getBlock();
192
+ }, this.config.BLOCK_CHECK_INTERVAL);
193
+ return;
194
+ }
195
+ // If the block number we've got is zero
196
+ // set it to the last irreversible block number
197
+ if (this.lastBlockNumber === 0) {
198
+ this.lastBlockNumber = props.head_block_number - 1;
199
+ }
200
+ if (this.config.DEBUG_MODE) {
201
+ console.log(`Head block number: `, props.head_block_number);
202
+ console.log(`Last block number: `, this.lastBlockNumber);
203
+ }
204
+ const BLOCKS_BEHIND = parseInt(this.config.BLOCKS_BEHIND_WARNING, 10);
205
+ if (!this.disableAllProcessing) {
206
+ await this.loadBlock(this.lastBlockNumber + 1);
207
+ }
208
+ // We are more than 25 blocks behind, uh oh, we gotta catch up
209
+ if (props.head_block_number >= (this.lastBlockNumber + BLOCKS_BEHIND) && this.config.DEBUG_MODE) {
210
+ console.log(`We are more than ${BLOCKS_BEHIND} blocks behind ${props.head_block_number}, ${(this.lastBlockNumber + BLOCKS_BEHIND)}`);
211
+ if (!this.disableAllProcessing) {
212
+ this.getBlock();
213
+ return;
214
+ }
215
+ }
216
+ // Storing timeout allows us to clear it, as this just calls itself
217
+ if (!this.disableAllProcessing) {
218
+ this.blockNumberTimeout = setTimeout(() => { this.getBlock(); }, this.config.BLOCK_CHECK_INTERVAL);
219
+ }
220
+ }
221
+ catch (e) {
222
+ const message = e.message.toLowerCase();
223
+ console.error(message);
224
+ }
225
+ }
226
+ // Takes the block from Hive and allows us to work with it
227
+ async loadBlock(blockNumber) {
228
+ // Load the block itself from the Hive API
229
+ const block = await this.client.database.getBlock(blockNumber);
230
+ // The block doesn't exist, wait and try again
231
+ if (!block) {
232
+ await utils_2.Utils.sleep(this.config.BLOCK_CHECK_INTERVAL);
233
+ return;
234
+ }
235
+ // Get the block date and time
236
+ const blockTime = new Date(`${block.timestamp}Z`);
237
+ if (this.lastBlockNumber !== blockNumber) {
238
+ this.processActions();
239
+ }
240
+ this.blockId = block.block_id;
241
+ this.previousBlockId = block.previous;
242
+ this.transactionId = block.transaction_ids[1];
243
+ this.blockTime = blockTime;
244
+ if (this.adapter?.processBlock) {
245
+ this.adapter.processBlock(block);
246
+ }
247
+ // Loop over all transactions in the block
248
+ for (const [i, transaction] of Object.entries(block.transactions)) {
249
+ // Loop over operations in the block
250
+ for (const [opIndex, op] of Object.entries(transaction.operations)) {
251
+ // For every operation, process it
252
+ await this.processOperation(op, blockNumber, block.block_id, block.previous, block.transaction_ids[i], blockTime);
253
+ }
254
+ }
255
+ this.lastBlockNumber = blockNumber;
256
+ this.saveStateToDisk();
257
+ }
258
+ processOperation(op, blockNumber, blockId, prevBlockId, trxId, blockTime) {
259
+ if (this.adapter?.processOperation) {
260
+ this.adapter.processOperation(op, blockNumber, blockId, prevBlockId, trxId, blockTime);
261
+ }
262
+ // Operation is a "comment" which could either be a post or comment
263
+ if (op[0] === 'comment') {
264
+ // This is a post
265
+ if (op[1].parent_author === '') {
266
+ this.postSubscriptions.forEach(sub => {
267
+ sub.callback(op[1], blockNumber, blockId, prevBlockId, trxId, blockTime);
268
+ });
269
+ // This is a comment
270
+ }
271
+ else {
272
+ this.commentSubscriptions.forEach(sub => {
273
+ sub.callback(op[1], blockNumber, blockId, prevBlockId, trxId, blockTime);
274
+ });
275
+ }
276
+ }
277
+ // This is a transfer
278
+ if (op[0] === 'transfer') {
279
+ const sender = op[1]?.from;
280
+ const amount = op[1]?.amount;
281
+ const json = utils_2.Utils.jsonParse(op[1].memo);
282
+ if (json?.[this.config.PAYLOAD_IDENTIFIER] && json?.[this.config.PAYLOAD_IDENTIFIER]?.id === this.config.JSON_ID) {
283
+ // Pull out details of contract
284
+ const { name, action, payload } = json[this.config.PAYLOAD_IDENTIFIER];
285
+ // Do we have a contract that matches the name in the payload?
286
+ const contract = this.contracts.find(c => c.name === name);
287
+ if (contract) {
288
+ if (this?.adapter?.processTransfer) {
289
+ this.adapter.processTransfer(op[1], { name, action, payload }, { sender, amount });
290
+ }
291
+ if (contract?.contract?.updateBlockInfo) {
292
+ contract.contract.updateBlockInfo(blockNumber, blockId, prevBlockId, trxId);
293
+ }
294
+ if (contract?.contract[action]) {
295
+ contract.contract[action](payload, { sender, amount });
296
+ }
297
+ }
298
+ }
299
+ this.transferSubscriptions.forEach(sub => {
300
+ if (sub.account === op[1].to) {
301
+ sub.callback(op[1], blockNumber, blockId, prevBlockId, trxId, blockTime);
302
+ }
303
+ });
304
+ }
305
+ // This is a custom JSON operation
306
+ if (op[0] === 'custom_json') {
307
+ let isSignedWithActiveKey = false;
308
+ let sender;
309
+ const id = op[1]?.id;
310
+ if (op[1]?.required_auths?.length > 0) {
311
+ sender = op[1].required_auths[0];
312
+ isSignedWithActiveKey = true;
313
+ }
314
+ else if (op[1]?.required_posting_auths?.length > 0) {
315
+ sender = op[1].required_posting_auths[0];
316
+ isSignedWithActiveKey = false;
317
+ }
318
+ const json = utils_2.Utils.jsonParse(op[1].json);
319
+ if (json && json?.[this.config.PAYLOAD_IDENTIFIER] && id === this.config.JSON_ID) {
320
+ // Pull out details of contract
321
+ const { name, action, payload } = json[this.config.PAYLOAD_IDENTIFIER];
322
+ // Do we have a contract that matches the name in the payload?
323
+ const contract = this.contracts.find(c => c.name === name);
324
+ if (contract) {
325
+ this.adapter.processCustomJson(op[1], { name, action, payload }, { sender, isSignedWithActiveKey });
326
+ if (contract?.contract?.updateBlockInfo) {
327
+ contract.contract.updateBlockInfo(blockNumber, blockId, prevBlockId, trxId);
328
+ }
329
+ if (contract?.contract[action]) {
330
+ contract.contract[action](payload, { sender, isSignedWithActiveKey }, id);
331
+ }
332
+ }
333
+ }
334
+ this.customJsonSubscriptions.forEach(sub => {
335
+ sub.callback(op[1], { sender, isSignedWithActiveKey }, blockNumber, blockId, prevBlockId, trxId, blockTime);
336
+ });
337
+ this.customJsonIdSubscriptions.forEach(sub => {
338
+ const byId = this.customJsonIdSubscriptions.find(s => s.id === op[1].id);
339
+ if (byId) {
340
+ sub.callback(op[1], { sender, isSignedWithActiveKey }, blockNumber, blockId, prevBlockId, trxId, blockTime);
341
+ }
342
+ });
343
+ utils_2.Utils.asyncForEach(this.customJsonHiveEngineSubscriptions, async (sub) => {
344
+ let isSignedWithActiveKey = null;
345
+ let sender;
346
+ if (op[1].required_auths.length > 0) {
347
+ sender = op[1].required_auths[0];
348
+ isSignedWithActiveKey = true;
349
+ }
350
+ else {
351
+ sender = op[1].required_posting_auths[0];
352
+ isSignedWithActiveKey = false;
353
+ }
354
+ const id = op[1].id;
355
+ const json = utils_2.Utils.jsonParse(op[1].json);
356
+ // Hive Engine JSON operation
357
+ if (id === this.config.HIVE_ENGINE_ID) {
358
+ const { contractName, contractAction, contractPayload } = json;
359
+ try {
360
+ // Attempt to get the transaction from Hive Engine itself
361
+ const txInfo = await this.hive.getTransactionInfo(trxId);
362
+ const logs = txInfo && txInfo.logs ? utils_2.Utils.jsonParse(txInfo.logs) : null;
363
+ // Do we have a valid transaction and are there no errors? It's a real transaction
364
+ if (txInfo && logs && typeof logs.errors === 'undefined') {
365
+ sub.callback(contractName, contractAction, contractPayload, sender, op[1], blockNumber, blockId, prevBlockId, trxId, blockTime);
366
+ }
367
+ }
368
+ catch (e) {
369
+ console.error(e);
370
+ return;
371
+ }
372
+ }
373
+ });
374
+ }
375
+ }
376
+ processActions() {
377
+ const blockDate = moment_1.default.utc(this.latestBlockchainTime);
378
+ for (const action of this.actions) {
379
+ const date = moment_1.default.utc(action.date);
380
+ const frequency = action.timeValue;
381
+ const contract = this.contracts.find(c => c.name === action.contractName);
382
+ // Contract doesn't exist or action doesn't exist, carry on
383
+ if (!contract || !contract?.contract?.[action.contractMethod]) {
384
+ continue;
385
+ }
386
+ let difference = 0;
387
+ switch (frequency) {
388
+ case '3s':
389
+ case 'block':
390
+ difference = date.diff(blockDate, 's');
391
+ // 3 seconds or more has passed
392
+ if (difference >= 3) {
393
+ contract.contract[action.contractMethod](action.payload);
394
+ action.reset();
395
+ }
396
+ break;
397
+ case '10s':
398
+ difference = blockDate.diff(date, 's');
399
+ // 10 seconds or more has passed
400
+ if (difference >= 10) {
401
+ contract.contract[action.contractMethod](action.payload);
402
+ action.reset();
403
+ }
404
+ break;
405
+ case '30s':
406
+ difference = blockDate.diff(date, 's');
407
+ // 30 seconds or more has passed
408
+ if (difference >= 30) {
409
+ contract.contract[action.contractMethod](action.payload);
410
+ action.reset();
411
+ }
412
+ break;
413
+ case '1m':
414
+ case 'minute':
415
+ difference = blockDate.diff(date, 'm');
416
+ // One minute has passed
417
+ if (difference >= 1) {
418
+ contract.contract[action.contractMethod](action.payload);
419
+ action.reset();
420
+ }
421
+ break;
422
+ case '15m':
423
+ case 'quarter':
424
+ difference = blockDate.diff(date, 'm');
425
+ // 15 minutes has passed
426
+ if (difference >= 15) {
427
+ contract.contract[action.contractMethod](action.payload);
428
+ action.reset();
429
+ }
430
+ break;
431
+ case '30m':
432
+ case 'halfhour':
433
+ difference = blockDate.diff(date, 'm');
434
+ // 30 minutes has passed
435
+ if (difference >= 30) {
436
+ contract.contract[action.contractMethod](action.payload);
437
+ action.reset();
438
+ }
439
+ break;
440
+ case 'hourly':
441
+ case '1h':
442
+ difference = blockDate.diff(date, 'h');
443
+ // One our or more has passed
444
+ if (difference >= 1) {
445
+ contract.contract[action.contractMethod](action.payload);
446
+ action.reset();
447
+ }
448
+ break;
449
+ case '12h':
450
+ case 'halfday':
451
+ difference = blockDate.diff(date, 'h');
452
+ // Twelve hours or more has passed
453
+ if (difference >= 12) {
454
+ contract.contract[action.contractMethod](action.payload);
455
+ action.reset();
456
+ }
457
+ break;
458
+ case '24h':
459
+ case 'day':
460
+ difference = blockDate.diff(date, 'd');
461
+ // One day (24 hours) has passed
462
+ if (difference >= 1) {
463
+ contract.contract[action.contractMethod](action.payload);
464
+ action.reset();
465
+ }
466
+ break;
467
+ case 'week':
468
+ difference = blockDate.diff(date, 'w');
469
+ // One week has passed
470
+ if (difference >= 1) {
471
+ contract.contract[action.contractMethod](action.payload);
472
+ action.reset();
473
+ }
474
+ break;
475
+ }
476
+ }
477
+ }
478
+ async saveStateToDisk() {
479
+ if (this.adapter?.saveState) {
480
+ this.adapter.saveState({ lastBlockNumber: this.lastBlockNumber, actions: this.actions });
481
+ }
482
+ }
483
+ saveToHiveApi(from, data) {
484
+ return utils_2.Utils.transferHiveTokens(this.client, this.config, from, 'hiveapi', '0.001', 'HIVE', data);
485
+ }
486
+ getAccountTransfers(account, from = -1, limit = 100) {
487
+ return utils_2.Utils.getAccountTransfers(this.client, account, from, limit);
488
+ }
489
+ transferHiveTokens(from, to, amount, symbol, memo = '') {
490
+ return utils_2.Utils.transferHiveTokens(this.client, this.config, from, to, amount, symbol, memo);
491
+ }
492
+ transferHiveTokensMultiple(from, accounts = [], amount = '0', symbol, memo = '') {
493
+ return utils_2.Utils.transferHiveTokensMultiple(this.client, this.config, from, accounts, amount, symbol, memo);
494
+ }
495
+ transferHiveEngineTokens(from, to, symbol, quantity, memo = '') {
496
+ return utils_2.Utils.transferHiveEngineTokens(this.client, this.config, from, to, symbol, quantity, memo);
497
+ }
498
+ transferHiveEngineTokensMultiple(from, accounts = [], symbol, memo = '', amount = '0') {
499
+ return utils_2.Utils.transferHiveEngineTokensMultiple(this.client, this.config, from, accounts, symbol, memo, amount);
500
+ }
501
+ issueHiveEngineTokens(from, to, symbol, quantity, memo = '') {
502
+ return utils_2.Utils.issueHiveEngineTokens(this.client, this.config, from, to, symbol, quantity, memo);
503
+ }
504
+ issueHiveEngineTokensMultiple(from, accounts = [], symbol, memo = '', amount = '0') {
505
+ return utils_2.Utils.issueHiveEngineTokensMultiple(this.client, this.config, from, accounts, symbol, memo, amount);
506
+ }
507
+ upvote(votePercentage = '100.0', username, permlink) {
508
+ return utils_2.Utils.upvote(this.client, this.config, this.username, votePercentage, username, permlink);
509
+ }
510
+ downvote(votePercentage = '100.0', username, permlink) {
511
+ return utils_2.Utils.downvote(this.client, this.config, this.username, votePercentage, username, permlink);
512
+ }
513
+ getTransaction(blockNumber, transactionId) {
514
+ return utils_2.Utils.getTransaction(this.client, blockNumber, transactionId);
515
+ }
516
+ verifyTransfer(transaction, from, to, amount) {
517
+ return utils_2.Utils.verifyTransfer(transaction, from, to, amount);
518
+ }
519
+ onComment(callback) {
520
+ this.commentSubscriptions.push({
521
+ callback
522
+ });
523
+ }
524
+ onPost(callback) {
525
+ this.postSubscriptions.push({
526
+ callback
527
+ });
528
+ }
529
+ onTransfer(account, callback) {
530
+ this.transferSubscriptions.push({
531
+ account,
532
+ callback
533
+ });
534
+ }
535
+ onCustomJson(callback) {
536
+ this.customJsonSubscriptions.push({ callback });
537
+ }
538
+ onCustomJsonId(callback, id) {
539
+ this.customJsonIdSubscriptions.push({ callback, id });
540
+ }
541
+ onHiveEngine(callback) {
542
+ this.customJsonHiveEngineSubscriptions.push({ callback });
543
+ }
544
+ }
545
+ exports.Streamer = Streamer;
440
546
  //# sourceMappingURL=streamer.js.map