hive-stream 2.0.6 → 3.0.1
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.
- package/.claude/settings.local.json +12 -0
- package/.env.example +2 -2
- package/.travis.yml +11 -11
- package/AGENTS.md +35 -0
- package/CHANGELOG.md +166 -0
- package/CLAUDE.md +75 -0
- package/DOCUMENTATION.md +380 -0
- package/LICENSE +21 -21
- package/README.md +429 -238
- package/dist/actions.d.ts +41 -10
- package/dist/actions.js +126 -23
- package/dist/actions.js.map +1 -1
- package/dist/adapters/base.adapter.d.ts +43 -25
- package/dist/adapters/base.adapter.js +79 -49
- package/dist/adapters/base.adapter.js.map +1 -1
- package/dist/adapters/mongodb.adapter.d.ts +44 -37
- package/dist/adapters/mongodb.adapter.js +363 -158
- package/dist/adapters/mongodb.adapter.js.map +1 -1
- package/dist/adapters/postgresql.adapter.d.ts +66 -0
- package/dist/adapters/postgresql.adapter.js +598 -0
- package/dist/adapters/postgresql.adapter.js.map +1 -0
- package/dist/adapters/sqlite.adapter.d.ts +57 -41
- package/dist/adapters/sqlite.adapter.js +561 -397
- package/dist/adapters/sqlite.adapter.js.map +1 -1
- package/dist/api.d.ts +6 -6
- package/dist/api.js +181 -55
- package/dist/api.js.map +1 -1
- package/dist/config.d.ts +19 -16
- package/dist/config.js +21 -18
- package/dist/config.js.map +1 -1
- package/dist/contracts/coinflip.contract.d.ts +9 -14
- package/dist/contracts/coinflip.contract.js +232 -94
- package/dist/contracts/coinflip.contract.js.map +1 -1
- package/dist/contracts/contract.d.ts +3 -0
- package/dist/contracts/contract.js +26 -0
- package/dist/contracts/contract.js.map +1 -0
- package/dist/contracts/dice.contract.d.ts +10 -29
- package/dist/contracts/dice.contract.js +217 -155
- package/dist/contracts/dice.contract.js.map +1 -1
- package/dist/contracts/exchange.contract.d.ts +11 -0
- package/dist/contracts/exchange.contract.js +492 -0
- package/dist/contracts/exchange.contract.js.map +1 -0
- package/dist/contracts/lotto.contract.d.ts +16 -20
- package/dist/contracts/lotto.contract.js +238 -246
- package/dist/contracts/lotto.contract.js.map +1 -1
- package/dist/contracts/nft.contract.d.ts +28 -0
- package/dist/contracts/nft.contract.js +598 -0
- package/dist/contracts/nft.contract.js.map +1 -0
- package/dist/contracts/poll.contract.d.ts +4 -0
- package/dist/contracts/poll.contract.js +105 -0
- package/dist/contracts/poll.contract.js.map +1 -0
- package/dist/contracts/rps.contract.d.ts +9 -0
- package/dist/contracts/rps.contract.js +217 -0
- package/dist/contracts/rps.contract.js.map +1 -0
- package/dist/contracts/tipjar.contract.d.ts +4 -0
- package/dist/contracts/tipjar.contract.js +60 -0
- package/dist/contracts/tipjar.contract.js.map +1 -0
- package/dist/contracts/token.contract.d.ts +4 -0
- package/dist/contracts/token.contract.js +311 -0
- package/dist/contracts/token.contract.js.map +1 -0
- package/dist/exchanges/bittrex.d.ts +6 -6
- package/dist/exchanges/bittrex.js +34 -34
- package/dist/exchanges/coingecko.d.ts +11 -0
- package/dist/exchanges/coingecko.js +57 -0
- package/dist/exchanges/coingecko.js.map +1 -0
- package/dist/exchanges/exchange.d.ts +16 -9
- package/dist/exchanges/exchange.js +80 -26
- package/dist/exchanges/exchange.js.map +1 -1
- package/dist/hive-rates.d.ts +34 -9
- package/dist/hive-rates.js +208 -75
- package/dist/hive-rates.js.map +1 -1
- package/dist/index.d.ts +19 -11
- package/dist/index.js +47 -32
- package/dist/index.js.map +1 -1
- package/dist/streamer.d.ts +233 -93
- package/dist/streamer.js +1063 -545
- package/dist/streamer.js.map +1 -1
- package/dist/test.d.ts +1 -1
- package/dist/test.js +24 -25
- package/dist/test.js.map +1 -1
- package/dist/types/hive-stream.d.ts +106 -6
- package/dist/types/hive-stream.js +2 -2
- package/dist/types/rates.d.ts +47 -0
- package/dist/types/rates.js +29 -0
- package/dist/types/rates.js.map +1 -0
- package/dist/utils.d.ts +334 -27
- package/dist/utils.js +960 -261
- package/dist/utils.js.map +1 -1
- package/ecosystem.config.js +17 -17
- package/examples/contracts/README.md +8 -0
- package/examples/contracts/exchange.ts +38 -0
- package/examples/contracts/poll.ts +21 -0
- package/examples/contracts/rps.ts +19 -0
- package/examples/contracts/tipjar.ts +19 -0
- package/jest.config.js +8 -8
- package/package.json +54 -48
- package/test-contract-block.md +18 -18
- package/tests/actions.spec.ts +252 -0
- package/tests/adapters/actions-persistence.spec.ts +144 -0
- package/tests/adapters/postgresql.adapter.spec.ts +127 -0
- package/tests/adapters/sqlite.adapter.spec.ts +180 -42
- package/tests/contracts/coinflip.contract.spec.ts +94 -132
- package/tests/contracts/dice.contract.spec.ts +87 -160
- package/tests/contracts/entrants.json +728 -728
- package/tests/contracts/exchange.contract.spec.ts +84 -0
- package/tests/contracts/lotto.contract.spec.ts +59 -324
- package/tests/contracts/nft.contract.spec.ts +948 -0
- package/tests/contracts/token.contract.spec.ts +90 -0
- package/tests/exchanges/coingecko.exchange.spec.ts +169 -0
- package/tests/exchanges/exchange.base.spec.ts +246 -0
- package/tests/helpers/mock-adapter.ts +214 -0
- package/tests/helpers/mock-fetch.ts +165 -0
- package/tests/hive-chain-features.spec.ts +238 -0
- package/tests/hive-rates.spec.ts +443 -0
- package/tests/integration/hive-rates.integration.spec.ts +35 -0
- package/tests/setup.ts +29 -18
- package/tests/streamer-actions.spec.ts +274 -0
- package/tests/streamer.spec.ts +342 -152
- package/tests/types/rates.spec.ts +216 -0
- package/tests/utils.spec.ts +113 -95
- package/tsconfig.build.json +3 -22
- package/tslint.json +20 -20
- package/wallaby.js +26 -26
package/dist/streamer.js
CHANGED
|
@@ -1,546 +1,1064 @@
|
|
|
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
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
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
|
-
|
|
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
|
-
if (!
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
if (
|
|
174
|
-
this.
|
|
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
|
-
this.
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
}
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
}
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
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 sscjs_1 = __importDefault(require("sscjs"));
|
|
15
|
+
class Streamer {
|
|
16
|
+
customJsonSubscriptions = [];
|
|
17
|
+
customJsonIdSubscriptions = [];
|
|
18
|
+
customJsonHiveEngineSubscriptions = [];
|
|
19
|
+
commentSubscriptions = [];
|
|
20
|
+
postSubscriptions = [];
|
|
21
|
+
transferSubscriptions = [];
|
|
22
|
+
escrowSubscriptions = [];
|
|
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
|
+
headBlockNumber = 0;
|
|
34
|
+
isPollingBlock = false;
|
|
35
|
+
isCatchingUp = false;
|
|
36
|
+
blockId;
|
|
37
|
+
previousBlockId;
|
|
38
|
+
transactionId;
|
|
39
|
+
blockTime;
|
|
40
|
+
latestBlockchainTime;
|
|
41
|
+
disableAllProcessing = false;
|
|
42
|
+
contracts = [];
|
|
43
|
+
adapter;
|
|
44
|
+
actions = [];
|
|
45
|
+
// Performance optimization properties
|
|
46
|
+
lastStateSave = Date.now();
|
|
47
|
+
stateSaveInterval = 5000; // Save state every 5 seconds instead of every block
|
|
48
|
+
blockProcessingQueue = [];
|
|
49
|
+
isProcessingQueue = false;
|
|
50
|
+
// Memory management
|
|
51
|
+
maxSubscriptions = 1000;
|
|
52
|
+
subscriptionCleanupInterval = null;
|
|
53
|
+
// Action processing optimization
|
|
54
|
+
actionFrequencyMap = new Map([
|
|
55
|
+
['3s', 3], ['block', 3], ['10s', 10], ['30s', 30],
|
|
56
|
+
['1m', 60], ['5m', 300], ['minute', 60], ['15m', 900], ['quarter', 900],
|
|
57
|
+
['30m', 1800], ['halfhour', 1800], ['hourly', 3600], ['1h', 3600],
|
|
58
|
+
['12h', 43200], ['halfday', 43200], ['24h', 86400], ['day', 86400], ['daily', 86400],
|
|
59
|
+
['week', 604800], ['weekly', 604800]
|
|
60
|
+
]);
|
|
61
|
+
contractCache = new Map();
|
|
62
|
+
// Data caching for performance
|
|
63
|
+
blockCache = new Map();
|
|
64
|
+
transactionCache = new Map();
|
|
65
|
+
accountCache = new Map();
|
|
66
|
+
cacheTimeout = 300000; // 5 minutes
|
|
67
|
+
maxCacheSize = 1000;
|
|
68
|
+
utils = utils_2.Utils;
|
|
69
|
+
constructor(userConfig = {}) {
|
|
70
|
+
this.config = Object.assign(config_1.Config, userConfig);
|
|
71
|
+
this.lastBlockNumber = this.config.LAST_BLOCK_NUMBER;
|
|
72
|
+
this.username = this.config.USERNAME;
|
|
73
|
+
this.postingKey = this.config.POSTING_KEY;
|
|
74
|
+
this.activeKey = this.config.ACTIVE_KEY;
|
|
75
|
+
this.hive = new sscjs_1.default(this.config.HIVE_ENGINE_API);
|
|
76
|
+
this.client = new dhive_1.Client(this.config.API_NODES);
|
|
77
|
+
if (process?.env?.NODE_ENV !== 'test') {
|
|
78
|
+
this._initializeAdapter(new sqlite_adapter_1.SqliteAdapter());
|
|
79
|
+
new api_1.Api(this);
|
|
80
|
+
}
|
|
81
|
+
// Start subscription cleanup interval
|
|
82
|
+
this.subscriptionCleanupInterval = setInterval(() => {
|
|
83
|
+
this.cleanupSubscriptions();
|
|
84
|
+
}, 60000); // Cleanup every minute
|
|
85
|
+
}
|
|
86
|
+
_initializeAdapter(adapter) {
|
|
87
|
+
this.adapter = adapter;
|
|
88
|
+
if (this?.adapter?.create) {
|
|
89
|
+
this.adapter.create();
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
async registerAdapter(adapter) {
|
|
93
|
+
if (this.adapter && this.adapter.destroy) {
|
|
94
|
+
try {
|
|
95
|
+
await this.adapter.destroy();
|
|
96
|
+
}
|
|
97
|
+
catch (error) {
|
|
98
|
+
console.warn('[Streamer] Error destroying existing adapter:', error);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
this.adapter = adapter;
|
|
102
|
+
if (this?.adapter?.create) {
|
|
103
|
+
await this.adapter.create();
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
getAdapter() {
|
|
107
|
+
return this.adapter;
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Register a new action with improved validation and persistence
|
|
111
|
+
*/
|
|
112
|
+
async registerAction(action) {
|
|
113
|
+
if (!action || !(action instanceof actions_1.TimeAction)) {
|
|
114
|
+
throw new Error('Invalid action: must be an instance of TimeAction');
|
|
115
|
+
}
|
|
116
|
+
const loadedActions = await this.adapter.loadActions();
|
|
117
|
+
for (const a of loadedActions) {
|
|
118
|
+
const exists = this.actions.find(i => i.id === a.id);
|
|
119
|
+
if (!exists) {
|
|
120
|
+
try {
|
|
121
|
+
const restoredAction = actions_1.TimeAction.fromJSON(a);
|
|
122
|
+
this.actions.push(restoredAction);
|
|
123
|
+
}
|
|
124
|
+
catch (error) {
|
|
125
|
+
console.warn(`[Streamer] Failed to restore action ${a.id}:`, error);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
const exists = this.actions.find(a => a.id === action.id);
|
|
130
|
+
if (!exists) {
|
|
131
|
+
this.validateActionContract(action);
|
|
132
|
+
this.actions.push(action);
|
|
133
|
+
await this.saveActionsToDisk();
|
|
134
|
+
if (this.config.DEBUG_MODE) {
|
|
135
|
+
console.log(`[Streamer] Registered time-based action: ${action.id} (${action.timeValue})`);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
else {
|
|
139
|
+
if (this.config.DEBUG_MODE) {
|
|
140
|
+
console.warn(`[Streamer] Action with ID ${action.id} already exists, skipping registration`);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* Validate that the contract and method exist for the action
|
|
146
|
+
*/
|
|
147
|
+
validateActionContract(action) {
|
|
148
|
+
const contract = this.contractCache.get(action.contractName) ||
|
|
149
|
+
this.contracts.find(c => c.name === action.contractName);
|
|
150
|
+
if (!contract) {
|
|
151
|
+
throw new Error(`Contract '${action.contractName}' not found for action '${action.id}'`);
|
|
152
|
+
}
|
|
153
|
+
const actionDefinition = contract.actions?.[action.contractAction];
|
|
154
|
+
if (!actionDefinition || typeof actionDefinition.handler !== 'function') {
|
|
155
|
+
throw new Error(`Action '${action.contractAction}' not found in contract '${action.contractName}' for action '${action.id}'`);
|
|
156
|
+
}
|
|
157
|
+
if (!this.isActionTriggerAllowed(actionDefinition, 'time')) {
|
|
158
|
+
throw new Error(`Action '${action.contractAction}' does not allow time triggers for action '${action.id}'`);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
isActionTriggerAllowed(actionDefinition, trigger) {
|
|
162
|
+
const configured = actionDefinition?.trigger;
|
|
163
|
+
const triggers = configured
|
|
164
|
+
? (Array.isArray(configured) ? configured : [configured])
|
|
165
|
+
: ['custom_json'];
|
|
166
|
+
return triggers.includes(trigger);
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* Remove an action by ID
|
|
170
|
+
*/
|
|
171
|
+
async removeAction(actionId) {
|
|
172
|
+
const index = this.actions.findIndex(a => a.id === actionId);
|
|
173
|
+
if (index >= 0) {
|
|
174
|
+
const removedAction = this.actions.splice(index, 1)[0];
|
|
175
|
+
await this.saveActionsToDisk();
|
|
176
|
+
if (this.config.DEBUG_MODE) {
|
|
177
|
+
console.log(`[Streamer] Removed time-based action: ${actionId}`);
|
|
178
|
+
}
|
|
179
|
+
return true;
|
|
180
|
+
}
|
|
181
|
+
return false;
|
|
182
|
+
}
|
|
183
|
+
/**
|
|
184
|
+
* Get all registered actions
|
|
185
|
+
*/
|
|
186
|
+
getActions() {
|
|
187
|
+
return [...this.actions];
|
|
188
|
+
}
|
|
189
|
+
/**
|
|
190
|
+
* Get action by ID
|
|
191
|
+
*/
|
|
192
|
+
getAction(actionId) {
|
|
193
|
+
return this.actions.find(a => a.id === actionId);
|
|
194
|
+
}
|
|
195
|
+
/**
|
|
196
|
+
* Enable/disable an action
|
|
197
|
+
*/
|
|
198
|
+
async setActionEnabled(actionId, enabled) {
|
|
199
|
+
const action = this.actions.find(a => a.id === actionId);
|
|
200
|
+
if (action) {
|
|
201
|
+
if (enabled) {
|
|
202
|
+
action.enable();
|
|
203
|
+
}
|
|
204
|
+
else {
|
|
205
|
+
action.disable();
|
|
206
|
+
}
|
|
207
|
+
await this.saveActionsToDisk();
|
|
208
|
+
if (this.config.DEBUG_MODE) {
|
|
209
|
+
console.log(`[Streamer] Action ${actionId} ${enabled ? 'enabled' : 'disabled'}`);
|
|
210
|
+
}
|
|
211
|
+
return true;
|
|
212
|
+
}
|
|
213
|
+
return false;
|
|
214
|
+
}
|
|
215
|
+
/**
|
|
216
|
+
* Save actions to disk asynchronously
|
|
217
|
+
*/
|
|
218
|
+
async saveActionsToDisk() {
|
|
219
|
+
try {
|
|
220
|
+
if (this.adapter?.saveState) {
|
|
221
|
+
await this.adapter.saveState({
|
|
222
|
+
lastBlockNumber: this.lastBlockNumber,
|
|
223
|
+
actions: this.actions.map(a => a.toJSON())
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
catch (error) {
|
|
228
|
+
if (error?.code !== 'SQLITE_MISUSE') {
|
|
229
|
+
console.error('[Streamer] Failed to save actions to disk:', error);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
/**
|
|
234
|
+
* Resets a specific action time value
|
|
235
|
+
*/
|
|
236
|
+
async resetAction(id) {
|
|
237
|
+
const action = this.actions.find(i => i.id === id);
|
|
238
|
+
if (action) {
|
|
239
|
+
action.reset();
|
|
240
|
+
await this.saveActionsToDisk();
|
|
241
|
+
if (this.config.DEBUG_MODE) {
|
|
242
|
+
console.log(`[Streamer] Reset action: ${id}`);
|
|
243
|
+
}
|
|
244
|
+
return true;
|
|
245
|
+
}
|
|
246
|
+
return false;
|
|
247
|
+
}
|
|
248
|
+
async registerContract(contract) {
|
|
249
|
+
if (!contract || typeof contract !== 'object') {
|
|
250
|
+
throw new Error('Contract must be a valid definition object');
|
|
251
|
+
}
|
|
252
|
+
if (!contract.name || typeof contract.name !== 'string') {
|
|
253
|
+
throw new Error('Contract name must be a non-empty string');
|
|
254
|
+
}
|
|
255
|
+
if (this.contractCache.has(contract.name)) {
|
|
256
|
+
throw new Error(`Contract '${contract.name}' is already registered`);
|
|
257
|
+
}
|
|
258
|
+
if (!contract.actions || typeof contract.actions !== 'object') {
|
|
259
|
+
throw new Error(`Contract '${contract.name}' must define actions`);
|
|
260
|
+
}
|
|
261
|
+
const lifecycleContext = {
|
|
262
|
+
streamer: this,
|
|
263
|
+
adapter: this.adapter,
|
|
264
|
+
config: this.config
|
|
265
|
+
};
|
|
266
|
+
if (contract.hooks?.create) {
|
|
267
|
+
await contract.hooks.create(lifecycleContext);
|
|
268
|
+
}
|
|
269
|
+
this.contracts.push(contract);
|
|
270
|
+
this.contractCache.set(contract.name, contract);
|
|
271
|
+
}
|
|
272
|
+
async unregisterContract(name) {
|
|
273
|
+
const contractIndex = this.contracts.findIndex(c => c.name === name);
|
|
274
|
+
if (contractIndex >= 0) {
|
|
275
|
+
const contract = this.contracts[contractIndex];
|
|
276
|
+
if (contract?.hooks?.destroy) {
|
|
277
|
+
await contract.hooks.destroy({
|
|
278
|
+
streamer: this,
|
|
279
|
+
adapter: this.adapter,
|
|
280
|
+
config: this.config
|
|
281
|
+
});
|
|
282
|
+
}
|
|
283
|
+
this.contracts.splice(contractIndex, 1);
|
|
284
|
+
this.contractCache.delete(name);
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
/**
|
|
288
|
+
* setConfig
|
|
289
|
+
*
|
|
290
|
+
* Allows specific configuration settings to be overridden
|
|
291
|
+
*
|
|
292
|
+
* @param config
|
|
293
|
+
*/
|
|
294
|
+
setConfig(config) {
|
|
295
|
+
Object.assign(this.config, config);
|
|
296
|
+
// Set keys and username incase they have changed
|
|
297
|
+
this.username = this.config.USERNAME;
|
|
298
|
+
this.postingKey = this.config.POSTING_KEY;
|
|
299
|
+
this.activeKey = this.config.ACTIVE_KEY;
|
|
300
|
+
return this;
|
|
301
|
+
}
|
|
302
|
+
/**
|
|
303
|
+
* Start
|
|
304
|
+
*
|
|
305
|
+
* Starts the streamer bot to get blocks from the Hive API
|
|
306
|
+
*
|
|
307
|
+
*/
|
|
308
|
+
async start() {
|
|
309
|
+
if (this.config.DEBUG_MODE) {
|
|
310
|
+
console.log('Starting to stream the Hive blockchain');
|
|
311
|
+
}
|
|
312
|
+
this.disableAllProcessing = false;
|
|
313
|
+
const state = await this.adapter.loadState();
|
|
314
|
+
if (this.config.DEBUG_MODE) {
|
|
315
|
+
console.log(`Restoring state from file`);
|
|
316
|
+
}
|
|
317
|
+
if (this.config.RESUME_FROM_STATE && state?.lastBlockNumber) {
|
|
318
|
+
this.lastBlockNumber = state.lastBlockNumber;
|
|
319
|
+
}
|
|
320
|
+
else if (this.config.LAST_BLOCK_NUMBER) {
|
|
321
|
+
this.lastBlockNumber = this.config.LAST_BLOCK_NUMBER;
|
|
322
|
+
}
|
|
323
|
+
// Kicks off the blockchain streaming and operation parsing
|
|
324
|
+
this.getBlock();
|
|
325
|
+
this.latestBlockTimer = setInterval(() => { this.getLatestBlock(); }, this.config.BLOCK_CHECK_INTERVAL);
|
|
326
|
+
return this;
|
|
327
|
+
}
|
|
328
|
+
/**
|
|
329
|
+
* Stop
|
|
330
|
+
*
|
|
331
|
+
* Stops the streamer from running
|
|
332
|
+
*/
|
|
333
|
+
async stop() {
|
|
334
|
+
this.disableAllProcessing = true;
|
|
335
|
+
if (this.blockNumberTimeout) {
|
|
336
|
+
clearTimeout(this.blockNumberTimeout);
|
|
337
|
+
}
|
|
338
|
+
if (this.latestBlockTimer) {
|
|
339
|
+
clearInterval(this.latestBlockTimer);
|
|
340
|
+
}
|
|
341
|
+
if (this.subscriptionCleanupInterval) {
|
|
342
|
+
clearInterval(this.subscriptionCleanupInterval);
|
|
343
|
+
}
|
|
344
|
+
if (this?.adapter?.destroy) {
|
|
345
|
+
await this.adapter.destroy();
|
|
346
|
+
}
|
|
347
|
+
await (0, utils_1.sleep)(800);
|
|
348
|
+
}
|
|
349
|
+
async getLatestBlock() {
|
|
350
|
+
try {
|
|
351
|
+
const props = await this.client.database.getDynamicGlobalProperties();
|
|
352
|
+
if (props) {
|
|
353
|
+
this.latestBlockchainTime = new Date(`${props.time}Z`);
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
catch (error) {
|
|
357
|
+
console.error('[Streamer] Error getting latest block:', error);
|
|
358
|
+
// Continue with cached time if available
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
async getBlock() {
|
|
362
|
+
if (this.isPollingBlock) {
|
|
363
|
+
return;
|
|
364
|
+
}
|
|
365
|
+
this.isPollingBlock = true;
|
|
366
|
+
let nextDelay = this.config.BLOCK_CHECK_INTERVAL;
|
|
367
|
+
try {
|
|
368
|
+
// Load global properties from the Hive API
|
|
369
|
+
const props = await this.client.database.getDynamicGlobalProperties();
|
|
370
|
+
// We have no props, so try loading them again.
|
|
371
|
+
if (!props) {
|
|
372
|
+
return;
|
|
373
|
+
}
|
|
374
|
+
this.headBlockNumber = props.head_block_number;
|
|
375
|
+
// If the block number we've got is zero set it to the latest head block
|
|
376
|
+
if (this.lastBlockNumber === 0) {
|
|
377
|
+
this.lastBlockNumber = props.head_block_number - 1;
|
|
378
|
+
}
|
|
379
|
+
if (this.config.DEBUG_MODE) {
|
|
380
|
+
console.log(`Head block number: `, props.head_block_number);
|
|
381
|
+
console.log(`Last block number: `, this.lastBlockNumber);
|
|
382
|
+
}
|
|
383
|
+
const BLOCKS_BEHIND = parseInt(this.config.BLOCKS_BEHIND_WARNING, 10);
|
|
384
|
+
const maxBatchSize = Math.max(1, this.config.CATCH_UP_BATCH_SIZE || 1);
|
|
385
|
+
const blocksBehind = Math.max(0, props.head_block_number - this.lastBlockNumber);
|
|
386
|
+
const blocksToProcess = Math.min(blocksBehind, maxBatchSize);
|
|
387
|
+
if (blocksBehind >= BLOCKS_BEHIND && this.config.DEBUG_MODE) {
|
|
388
|
+
console.log(`[Streamer] ${blocksBehind} blocks behind head (${props.head_block_number}). Catching up...`);
|
|
389
|
+
}
|
|
390
|
+
if (!this.disableAllProcessing) {
|
|
391
|
+
for (let i = 0; i < blocksToProcess; i++) {
|
|
392
|
+
await this.loadBlock(this.lastBlockNumber + 1);
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
const remainingBehind = Math.max(0, props.head_block_number - this.lastBlockNumber);
|
|
396
|
+
this.isCatchingUp = remainingBehind > 0;
|
|
397
|
+
if (remainingBehind > 0) {
|
|
398
|
+
nextDelay = Math.max(0, this.config.CATCH_UP_DELAY_MS);
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
catch (e) {
|
|
402
|
+
const error = e instanceof Error ? e : new Error(String(e));
|
|
403
|
+
console.error(`[Streamer] Block processing error: ${error.message}`, {
|
|
404
|
+
stack: error.stack,
|
|
405
|
+
blockNumber: this.lastBlockNumber + 1
|
|
406
|
+
});
|
|
407
|
+
// Retry after a longer delay on error
|
|
408
|
+
nextDelay = this.config.BLOCK_CHECK_INTERVAL * 2;
|
|
409
|
+
}
|
|
410
|
+
finally {
|
|
411
|
+
this.isPollingBlock = false;
|
|
412
|
+
// Storing timeout allows us to clear it, as this just calls itself
|
|
413
|
+
if (!this.disableAllProcessing) {
|
|
414
|
+
this.blockNumberTimeout = setTimeout(() => { this.getBlock(); }, nextDelay);
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
// Takes the block from Hive and allows us to work with it
|
|
419
|
+
async loadBlock(blockNumber) {
|
|
420
|
+
// Check cache first
|
|
421
|
+
let block = this.blockCache.get(blockNumber);
|
|
422
|
+
if (!block) {
|
|
423
|
+
// Load the block itself from the Hive API
|
|
424
|
+
block = await this.client.database.getBlock(blockNumber);
|
|
425
|
+
// Cache the block for potential reuse
|
|
426
|
+
if (block) {
|
|
427
|
+
this.blockCache.set(blockNumber, block);
|
|
428
|
+
// Cleanup old cache entries
|
|
429
|
+
if (this.blockCache.size > this.maxCacheSize) {
|
|
430
|
+
const oldestKey = this.blockCache.keys().next().value;
|
|
431
|
+
this.blockCache.delete(oldestKey);
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
// The block doesn't exist, wait and try again
|
|
436
|
+
if (!block) {
|
|
437
|
+
await utils_2.Utils.sleep(this.config.BLOCK_CHECK_INTERVAL);
|
|
438
|
+
return;
|
|
439
|
+
}
|
|
440
|
+
// Get the block date and time
|
|
441
|
+
const blockTime = new Date(`${block.timestamp}Z`);
|
|
442
|
+
if (this.lastBlockNumber !== blockNumber) {
|
|
443
|
+
this.processActions().catch(error => {
|
|
444
|
+
console.error('[Streamer] Error processing actions:', error);
|
|
445
|
+
});
|
|
446
|
+
}
|
|
447
|
+
this.blockId = block.block_id;
|
|
448
|
+
this.previousBlockId = block.previous;
|
|
449
|
+
this.transactionId = block.transaction_ids[1];
|
|
450
|
+
this.blockTime = blockTime;
|
|
451
|
+
if (this.adapter?.processBlock) {
|
|
452
|
+
this.adapter.processBlock(block);
|
|
453
|
+
}
|
|
454
|
+
// Process transactions with improved concurrency
|
|
455
|
+
const transactions = block.transactions;
|
|
456
|
+
const transactionIds = block.transaction_ids;
|
|
457
|
+
// Create operation processing promises for better concurrency
|
|
458
|
+
const operationPromises = [];
|
|
459
|
+
for (let i = 0; i < transactions.length; i++) {
|
|
460
|
+
const transaction = transactions[i];
|
|
461
|
+
const operations = transaction.operations;
|
|
462
|
+
// Process operations in batch for better performance
|
|
463
|
+
for (let opIndex = 0; opIndex < operations.length; opIndex++) {
|
|
464
|
+
const op = operations[opIndex];
|
|
465
|
+
// Create promise for each operation (but don't await yet)
|
|
466
|
+
const operationPromise = this.processOperation(op, blockNumber, block.block_id, block.previous, transactionIds[i], blockTime).catch(error => {
|
|
467
|
+
console.error('[Streamer] Operation processing error:', error, {
|
|
468
|
+
blockNumber,
|
|
469
|
+
transactionIndex: i,
|
|
470
|
+
operationIndex: opIndex
|
|
471
|
+
});
|
|
472
|
+
});
|
|
473
|
+
operationPromises.push(operationPromise);
|
|
474
|
+
// Process in batches to avoid overwhelming the system
|
|
475
|
+
if (operationPromises.length >= 50) {
|
|
476
|
+
await Promise.all(operationPromises);
|
|
477
|
+
operationPromises.length = 0; // Clear array
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
// Process any remaining operations
|
|
482
|
+
if (operationPromises.length > 0) {
|
|
483
|
+
await Promise.all(operationPromises);
|
|
484
|
+
}
|
|
485
|
+
this.lastBlockNumber = blockNumber;
|
|
486
|
+
this.saveStateThrottled();
|
|
487
|
+
}
|
|
488
|
+
async processOperation(op, blockNumber, blockId, prevBlockId, trxId, blockTime) {
|
|
489
|
+
const operationType = op[0];
|
|
490
|
+
const operationData = op[1];
|
|
491
|
+
const operationMetadata = {
|
|
492
|
+
blockNumber,
|
|
493
|
+
blockId,
|
|
494
|
+
previousBlockId: prevBlockId,
|
|
495
|
+
transactionId: trxId,
|
|
496
|
+
blockTime
|
|
497
|
+
};
|
|
498
|
+
if (this.adapter?.processOperation) {
|
|
499
|
+
await this.adapter.processOperation(op, blockNumber, blockId, prevBlockId, trxId, blockTime);
|
|
500
|
+
}
|
|
501
|
+
// Operation is a "comment" which could either be a post or comment
|
|
502
|
+
if (operationType === 'comment') {
|
|
503
|
+
// This is a post
|
|
504
|
+
if (operationData.parent_author === '') {
|
|
505
|
+
this.postSubscriptions.forEach(sub => {
|
|
506
|
+
sub.callback(operationData, blockNumber, blockId, prevBlockId, trxId, blockTime);
|
|
507
|
+
});
|
|
508
|
+
// This is a comment
|
|
509
|
+
}
|
|
510
|
+
else {
|
|
511
|
+
this.commentSubscriptions.forEach(sub => {
|
|
512
|
+
sub.callback(operationData, blockNumber, blockId, prevBlockId, trxId, blockTime);
|
|
513
|
+
});
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
// This is a transfer
|
|
517
|
+
if (operationType === 'transfer') {
|
|
518
|
+
const sender = operationData?.from;
|
|
519
|
+
const rawAmount = operationData?.amount;
|
|
520
|
+
const amountParts = typeof rawAmount === 'string' ? rawAmount.split(' ') : [];
|
|
521
|
+
const transferInfo = {
|
|
522
|
+
from: sender,
|
|
523
|
+
to: operationData?.to,
|
|
524
|
+
rawAmount: rawAmount || '',
|
|
525
|
+
amount: amountParts[0] || '',
|
|
526
|
+
asset: amountParts[1] || '',
|
|
527
|
+
memo: operationData?.memo
|
|
528
|
+
};
|
|
529
|
+
const json = utils_2.Utils.jsonParse(operationData.memo);
|
|
530
|
+
const payload = this.normalizeContractPayload(json?.[this.config.PAYLOAD_IDENTIFIER]);
|
|
531
|
+
if (payload) {
|
|
532
|
+
if (this?.adapter?.processTransfer) {
|
|
533
|
+
await this.adapter.processTransfer(operationData, payload, {
|
|
534
|
+
sender: sender || '',
|
|
535
|
+
amount: rawAmount || '',
|
|
536
|
+
...operationMetadata
|
|
537
|
+
});
|
|
538
|
+
}
|
|
539
|
+
const context = this.buildContractContext('transfer', blockNumber, blockId, prevBlockId, trxId, blockTime, {
|
|
540
|
+
sender,
|
|
541
|
+
transfer: transferInfo,
|
|
542
|
+
operation: {
|
|
543
|
+
type: operationType,
|
|
544
|
+
data: operationData
|
|
545
|
+
}
|
|
546
|
+
});
|
|
547
|
+
await this.dispatchContractAction(payload, context);
|
|
548
|
+
}
|
|
549
|
+
this.transferSubscriptions.forEach(sub => {
|
|
550
|
+
if (sub.account === operationData.to) {
|
|
551
|
+
sub.callback(operationData, blockNumber, blockId, prevBlockId, trxId, blockTime);
|
|
552
|
+
}
|
|
553
|
+
});
|
|
554
|
+
}
|
|
555
|
+
// This is a custom JSON operation
|
|
556
|
+
if (operationType === 'custom_json') {
|
|
557
|
+
let isSignedWithActiveKey = false;
|
|
558
|
+
let sender;
|
|
559
|
+
const id = operationData?.id;
|
|
560
|
+
if (operationData?.required_auths?.length > 0) {
|
|
561
|
+
sender = operationData.required_auths[0];
|
|
562
|
+
isSignedWithActiveKey = true;
|
|
563
|
+
}
|
|
564
|
+
else if (operationData?.required_posting_auths?.length > 0) {
|
|
565
|
+
sender = operationData.required_posting_auths[0];
|
|
566
|
+
isSignedWithActiveKey = false;
|
|
567
|
+
}
|
|
568
|
+
const json = utils_2.Utils.jsonParse(operationData.json);
|
|
569
|
+
const payload = id === this.config.JSON_ID
|
|
570
|
+
? this.normalizeContractPayload(json?.[this.config.PAYLOAD_IDENTIFIER])
|
|
571
|
+
: null;
|
|
572
|
+
if (payload) {
|
|
573
|
+
if (this?.adapter?.processCustomJson) {
|
|
574
|
+
await this.adapter.processCustomJson(operationData, payload, {
|
|
575
|
+
sender: sender || '',
|
|
576
|
+
isSignedWithActiveKey,
|
|
577
|
+
...operationMetadata
|
|
578
|
+
});
|
|
579
|
+
}
|
|
580
|
+
const context = this.buildContractContext('custom_json', blockNumber, blockId, prevBlockId, trxId, blockTime, {
|
|
581
|
+
sender,
|
|
582
|
+
customJson: {
|
|
583
|
+
id,
|
|
584
|
+
json,
|
|
585
|
+
isSignedWithActiveKey
|
|
586
|
+
},
|
|
587
|
+
operation: {
|
|
588
|
+
type: operationType,
|
|
589
|
+
data: operationData
|
|
590
|
+
}
|
|
591
|
+
});
|
|
592
|
+
await this.dispatchContractAction(payload, context);
|
|
593
|
+
}
|
|
594
|
+
this.customJsonSubscriptions.forEach(sub => {
|
|
595
|
+
sub.callback(operationData, { sender, isSignedWithActiveKey }, blockNumber, blockId, prevBlockId, trxId, blockTime);
|
|
596
|
+
});
|
|
597
|
+
this.customJsonIdSubscriptions.forEach(sub => {
|
|
598
|
+
if (sub.id === operationData.id) {
|
|
599
|
+
sub.callback(operationData, { sender, isSignedWithActiveKey }, blockNumber, blockId, prevBlockId, trxId, blockTime);
|
|
600
|
+
}
|
|
601
|
+
});
|
|
602
|
+
if (id === this.config.HIVE_ENGINE_ID && this.customJsonHiveEngineSubscriptions.length > 0) {
|
|
603
|
+
const enginePayload = json || {};
|
|
604
|
+
const { contractName, contractAction, contractPayload } = enginePayload;
|
|
605
|
+
try {
|
|
606
|
+
const txInfo = await this.hive.getTransactionInfo(trxId);
|
|
607
|
+
const logs = txInfo && txInfo.logs ? utils_2.Utils.jsonParse(txInfo.logs) : null;
|
|
608
|
+
if (txInfo && logs && typeof logs.errors === 'undefined') {
|
|
609
|
+
await Promise.all(this.customJsonHiveEngineSubscriptions.map(async (sub) => {
|
|
610
|
+
sub.callback(contractName, contractAction, contractPayload, sender, operationData, blockNumber, blockId, prevBlockId, trxId, blockTime);
|
|
611
|
+
}));
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
catch (e) {
|
|
615
|
+
console.error(e);
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
// Recurrent transfers carry payloads in memo, similar to transfer.
|
|
620
|
+
if (operationType === 'recurrent_transfer') {
|
|
621
|
+
const sender = operationData?.from;
|
|
622
|
+
const json = utils_2.Utils.jsonParse(operationData?.memo);
|
|
623
|
+
const payload = this.normalizeContractPayload(json?.[this.config.PAYLOAD_IDENTIFIER]);
|
|
624
|
+
if (payload) {
|
|
625
|
+
const context = this.buildContractContext('recurrent_transfer', blockNumber, blockId, prevBlockId, trxId, blockTime, {
|
|
626
|
+
sender,
|
|
627
|
+
operation: {
|
|
628
|
+
type: operationType,
|
|
629
|
+
data: operationData
|
|
630
|
+
}
|
|
631
|
+
});
|
|
632
|
+
await this.dispatchContractAction(payload, context);
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
if (this.isEscrowOperationType(operationType)) {
|
|
636
|
+
const escrow = this.buildEscrowDetails(operationType, operationData);
|
|
637
|
+
const sender = operationData?.from;
|
|
638
|
+
if (this.adapter?.processEscrow) {
|
|
639
|
+
await this.adapter.processEscrow(operationType, operationData, operationMetadata);
|
|
640
|
+
}
|
|
641
|
+
if (operationType === 'escrow_transfer') {
|
|
642
|
+
const jsonMeta = utils_2.Utils.jsonParse(operationData?.json_meta);
|
|
643
|
+
const payload = this.normalizeContractPayload(jsonMeta?.[this.config.PAYLOAD_IDENTIFIER]);
|
|
644
|
+
if (payload) {
|
|
645
|
+
const context = this.buildContractContext('escrow_transfer', blockNumber, blockId, prevBlockId, trxId, blockTime, {
|
|
646
|
+
sender,
|
|
647
|
+
escrow,
|
|
648
|
+
operation: {
|
|
649
|
+
type: operationType,
|
|
650
|
+
data: operationData
|
|
651
|
+
}
|
|
652
|
+
});
|
|
653
|
+
await this.dispatchContractAction(payload, context);
|
|
654
|
+
}
|
|
655
|
+
}
|
|
656
|
+
this.escrowSubscriptions.forEach(sub => {
|
|
657
|
+
if (sub.type === operationType) {
|
|
658
|
+
sub.callback(operationData, blockNumber, blockId, prevBlockId, trxId, blockTime);
|
|
659
|
+
}
|
|
660
|
+
});
|
|
661
|
+
}
|
|
662
|
+
}
|
|
663
|
+
normalizeContractPayload(payload) {
|
|
664
|
+
if (!payload || typeof payload !== 'object') {
|
|
665
|
+
return null;
|
|
666
|
+
}
|
|
667
|
+
const contract = typeof payload.contract === 'string'
|
|
668
|
+
? payload.contract
|
|
669
|
+
: (typeof payload.name === 'string' ? payload.name : null);
|
|
670
|
+
const action = typeof payload.action === 'string' ? payload.action : null;
|
|
671
|
+
if (!contract || !action) {
|
|
672
|
+
return null;
|
|
673
|
+
}
|
|
674
|
+
if (!payload.contract && payload.name && this.config.DEBUG_MODE) {
|
|
675
|
+
console.warn('[Streamer] Legacy contract payload detected (name/action). Please migrate to { contract, action, payload }.');
|
|
676
|
+
}
|
|
677
|
+
return {
|
|
678
|
+
contract,
|
|
679
|
+
action,
|
|
680
|
+
payload: payload.payload ?? {},
|
|
681
|
+
meta: payload.meta ?? payload.metadata
|
|
682
|
+
};
|
|
683
|
+
}
|
|
684
|
+
isEscrowOperationType(operationType) {
|
|
685
|
+
return operationType === 'escrow_transfer'
|
|
686
|
+
|| operationType === 'escrow_approve'
|
|
687
|
+
|| operationType === 'escrow_dispute'
|
|
688
|
+
|| operationType === 'escrow_release';
|
|
689
|
+
}
|
|
690
|
+
buildEscrowDetails(operationType, operation) {
|
|
691
|
+
return {
|
|
692
|
+
type: operationType,
|
|
693
|
+
from: operation?.from,
|
|
694
|
+
to: operation?.to,
|
|
695
|
+
agent: operation?.agent,
|
|
696
|
+
escrowId: operation?.escrow_id,
|
|
697
|
+
who: operation?.who,
|
|
698
|
+
receiver: operation?.receiver,
|
|
699
|
+
hiveAmount: operation?.hive_amount,
|
|
700
|
+
hbdAmount: operation?.hbd_amount,
|
|
701
|
+
fee: operation?.fee,
|
|
702
|
+
ratificationDeadline: operation?.ratification_deadline,
|
|
703
|
+
expiration: operation?.escrow_expiration,
|
|
704
|
+
approved: typeof operation?.approve === 'boolean' ? operation.approve : undefined
|
|
705
|
+
};
|
|
706
|
+
}
|
|
707
|
+
buildContractContext(trigger, blockNumber, blockId, previousBlockId, transactionId, blockTime, details) {
|
|
708
|
+
return {
|
|
709
|
+
trigger,
|
|
710
|
+
streamer: this,
|
|
711
|
+
adapter: this.adapter,
|
|
712
|
+
config: this.config,
|
|
713
|
+
block: {
|
|
714
|
+
number: blockNumber,
|
|
715
|
+
id: blockId,
|
|
716
|
+
previousId: previousBlockId,
|
|
717
|
+
time: blockTime
|
|
718
|
+
},
|
|
719
|
+
transaction: {
|
|
720
|
+
id: transactionId
|
|
721
|
+
},
|
|
722
|
+
sender: details.sender,
|
|
723
|
+
transfer: details.transfer,
|
|
724
|
+
customJson: details.customJson,
|
|
725
|
+
escrow: details.escrow,
|
|
726
|
+
operation: details.operation
|
|
727
|
+
};
|
|
728
|
+
}
|
|
729
|
+
async dispatchContractAction(payload, context) {
|
|
730
|
+
const contract = this.contractCache.get(payload.contract) ||
|
|
731
|
+
this.contracts.find(c => c.name === payload.contract);
|
|
732
|
+
if (!contract) {
|
|
733
|
+
console.warn(`[Streamer] Contract '${payload.contract}' not found for action '${payload.action}'`);
|
|
734
|
+
return;
|
|
735
|
+
}
|
|
736
|
+
if (contract && !this.contractCache.has(payload.contract)) {
|
|
737
|
+
this.contractCache.set(payload.contract, contract);
|
|
738
|
+
}
|
|
739
|
+
const actionDefinition = contract.actions?.[payload.action];
|
|
740
|
+
if (!actionDefinition || typeof actionDefinition.handler !== 'function') {
|
|
741
|
+
console.warn(`[Streamer] Action '${payload.action}' not found in contract '${payload.contract}'`);
|
|
742
|
+
return;
|
|
743
|
+
}
|
|
744
|
+
if (!this.isActionTriggerAllowed(actionDefinition, context.trigger)) {
|
|
745
|
+
console.warn(`[Streamer] Action '${payload.action}' does not allow trigger '${context.trigger}'`);
|
|
746
|
+
return;
|
|
747
|
+
}
|
|
748
|
+
if (actionDefinition.requiresActiveKey &&
|
|
749
|
+
context.trigger === 'custom_json' &&
|
|
750
|
+
!context.customJson?.isSignedWithActiveKey) {
|
|
751
|
+
console.warn(`[Streamer] Action '${payload.action}' requires active key signature`);
|
|
752
|
+
return;
|
|
753
|
+
}
|
|
754
|
+
let actionPayload = payload.payload ?? {};
|
|
755
|
+
if (actionDefinition.schema) {
|
|
756
|
+
const result = actionDefinition.schema.safeParse(actionPayload);
|
|
757
|
+
if (!result.success) {
|
|
758
|
+
console.warn(`[Streamer] Invalid payload for ${payload.contract}.${payload.action}`, {
|
|
759
|
+
errors: result.error?.errors
|
|
760
|
+
});
|
|
761
|
+
return;
|
|
762
|
+
}
|
|
763
|
+
actionPayload = result.data;
|
|
764
|
+
}
|
|
765
|
+
try {
|
|
766
|
+
await actionDefinition.handler(actionPayload, context);
|
|
767
|
+
}
|
|
768
|
+
catch (error) {
|
|
769
|
+
console.error(`[Streamer] Contract action error for ${payload.contract}.${payload.action}:`, error);
|
|
770
|
+
if (context.trigger === 'time') {
|
|
771
|
+
throw error;
|
|
772
|
+
}
|
|
773
|
+
}
|
|
774
|
+
}
|
|
775
|
+
async processActions() {
|
|
776
|
+
if (!this.latestBlockchainTime || this.actions.length === 0) {
|
|
777
|
+
return;
|
|
778
|
+
}
|
|
779
|
+
const currentTime = this.latestBlockchainTime.getTime();
|
|
780
|
+
const executedActions = [];
|
|
781
|
+
// Process actions in batch with optimized time calculations
|
|
782
|
+
for (let i = 0; i < this.actions.length; i++) {
|
|
783
|
+
const action = this.actions[i];
|
|
784
|
+
// Skip disabled actions or actions that have reached max executions
|
|
785
|
+
if (!action.enabled || action.hasReachedMaxExecutions()) {
|
|
786
|
+
continue;
|
|
787
|
+
}
|
|
788
|
+
// Get contract from cache or find and cache it
|
|
789
|
+
let contract = this.contractCache.get(action.contractName);
|
|
790
|
+
if (!contract) {
|
|
791
|
+
contract = this.contracts.find(c => c.name === action.contractName);
|
|
792
|
+
if (contract) {
|
|
793
|
+
this.contractCache.set(action.contractName, contract);
|
|
794
|
+
}
|
|
795
|
+
}
|
|
796
|
+
// Contract doesn't exist or method doesn't exist, log warning and skip
|
|
797
|
+
if (!contract) {
|
|
798
|
+
console.warn(`[Streamer] Contract '${action.contractName}' not found for action '${action.id}'`);
|
|
799
|
+
continue;
|
|
800
|
+
}
|
|
801
|
+
const actionDefinition = contract.actions?.[action.contractAction];
|
|
802
|
+
if (!actionDefinition || typeof actionDefinition.handler !== 'function') {
|
|
803
|
+
console.warn(`[Streamer] Action '${action.contractAction}' not found in contract '${action.contractName}' for action '${action.id}'`);
|
|
804
|
+
continue;
|
|
805
|
+
}
|
|
806
|
+
if (!this.isActionTriggerAllowed(actionDefinition, 'time')) {
|
|
807
|
+
console.warn(`[Streamer] Action '${action.contractAction}' does not allow time triggers for action '${action.id}'`);
|
|
808
|
+
continue;
|
|
809
|
+
}
|
|
810
|
+
// Get frequency in seconds from optimized map
|
|
811
|
+
const frequencySeconds = this.actionFrequencyMap.get(action.timeValue);
|
|
812
|
+
if (!frequencySeconds) {
|
|
813
|
+
console.warn(`[Streamer] Invalid time value '${action.timeValue}' for action '${action.id}'`);
|
|
814
|
+
continue;
|
|
815
|
+
}
|
|
816
|
+
// Optimized time difference calculation using timestamps
|
|
817
|
+
const actionTime = action.date.getTime();
|
|
818
|
+
const differenceSeconds = (currentTime - actionTime) / 1000;
|
|
819
|
+
// Check if enough time has passed
|
|
820
|
+
if (differenceSeconds >= frequencySeconds) {
|
|
821
|
+
try {
|
|
822
|
+
// Execute the action with error isolation
|
|
823
|
+
const context = this.buildContractContext('time', this.lastBlockNumber, this.blockId, this.previousBlockId, action.id, this.latestBlockchainTime || new Date(), {});
|
|
824
|
+
await this.dispatchContractAction({
|
|
825
|
+
contract: action.contractName,
|
|
826
|
+
action: action.contractAction,
|
|
827
|
+
payload: action.payload || {}
|
|
828
|
+
}, context);
|
|
829
|
+
// Reset the action timer and increment execution count
|
|
830
|
+
action.reset();
|
|
831
|
+
action.incrementExecutionCount();
|
|
832
|
+
executedActions.push(action.id);
|
|
833
|
+
if (this.config.DEBUG_MODE) {
|
|
834
|
+
console.log(`[Streamer] Executed action: ${action.id} (execution #${action.executionCount})`);
|
|
835
|
+
}
|
|
836
|
+
}
|
|
837
|
+
catch (error) {
|
|
838
|
+
const err = error instanceof Error ? error : new Error(String(error));
|
|
839
|
+
console.error(`[Streamer] Action execution error for ${action.contractName}.${action.contractAction}:`, {
|
|
840
|
+
actionId: action.id,
|
|
841
|
+
error: err.message,
|
|
842
|
+
stack: err.stack,
|
|
843
|
+
payload: action.payload
|
|
844
|
+
});
|
|
845
|
+
// Optionally disable action after repeated failures
|
|
846
|
+
// This could be configurable in the future
|
|
847
|
+
}
|
|
848
|
+
}
|
|
849
|
+
}
|
|
850
|
+
// Save state if any actions were executed
|
|
851
|
+
if (executedActions.length > 0) {
|
|
852
|
+
await this.saveActionsToDisk();
|
|
853
|
+
}
|
|
854
|
+
// Clean up disabled or completed actions periodically
|
|
855
|
+
this.cleanupActions();
|
|
856
|
+
}
|
|
857
|
+
/**
|
|
858
|
+
* Clean up completed or disabled actions to prevent memory leaks
|
|
859
|
+
*/
|
|
860
|
+
cleanupActions() {
|
|
861
|
+
const beforeCount = this.actions.length;
|
|
862
|
+
// Remove actions that have reached their max executions
|
|
863
|
+
this.actions = this.actions.filter(action => {
|
|
864
|
+
if (action.hasReachedMaxExecutions()) {
|
|
865
|
+
if (this.config.DEBUG_MODE) {
|
|
866
|
+
console.log(`[Streamer] Removing completed action: ${action.id} (${action.executionCount}/${action.maxExecutions} executions)`);
|
|
867
|
+
}
|
|
868
|
+
return false;
|
|
869
|
+
}
|
|
870
|
+
return true;
|
|
871
|
+
});
|
|
872
|
+
const afterCount = this.actions.length;
|
|
873
|
+
if (beforeCount !== afterCount) {
|
|
874
|
+
// Save state if we removed any actions
|
|
875
|
+
this.saveActionsToDisk().catch(error => {
|
|
876
|
+
console.error('[Streamer] Failed to save state after action cleanup:', error);
|
|
877
|
+
});
|
|
878
|
+
}
|
|
879
|
+
}
|
|
880
|
+
async saveStateToDisk() {
|
|
881
|
+
if (this.adapter?.saveState) {
|
|
882
|
+
await this.adapter.saveState({ lastBlockNumber: this.lastBlockNumber, actions: this.actions });
|
|
883
|
+
}
|
|
884
|
+
}
|
|
885
|
+
// Throttled state saving for performance
|
|
886
|
+
saveStateThrottled() {
|
|
887
|
+
const now = Date.now();
|
|
888
|
+
if (now - this.lastStateSave > this.stateSaveInterval) {
|
|
889
|
+
this.lastStateSave = now;
|
|
890
|
+
// Save state asynchronously without blocking block processing
|
|
891
|
+
this.saveStateToDisk().catch(error => {
|
|
892
|
+
console.error('[Streamer] State save error:', error);
|
|
893
|
+
});
|
|
894
|
+
}
|
|
895
|
+
}
|
|
896
|
+
saveToHiveApi(from, data) {
|
|
897
|
+
return utils_2.Utils.transferHiveTokens(this.client, this.config, from, 'hiveapi', '0.001', 'HIVE', data);
|
|
898
|
+
}
|
|
899
|
+
getAccountTransfers(account, from = -1, limit = 100) {
|
|
900
|
+
return utils_2.Utils.getAccountTransfers(this.client, account, from, limit);
|
|
901
|
+
}
|
|
902
|
+
transferHiveTokens(from, to, amount, symbol, memo = '') {
|
|
903
|
+
return utils_2.Utils.transferHiveTokens(this.client, this.config, from, to, amount, symbol, memo);
|
|
904
|
+
}
|
|
905
|
+
transferHiveTokensMultiple(from, accounts = [], amount = '0', symbol, memo = '') {
|
|
906
|
+
return utils_2.Utils.transferHiveTokensMultiple(this.client, this.config, from, accounts, amount, symbol, memo);
|
|
907
|
+
}
|
|
908
|
+
broadcastOperations(operations, signingKeys) {
|
|
909
|
+
return utils_2.Utils.broadcastOperations(this.client, operations, signingKeys || this.config.ACTIVE_KEY);
|
|
910
|
+
}
|
|
911
|
+
broadcastMultiSigOperations(operations, signingKeys) {
|
|
912
|
+
return utils_2.Utils.broadcastMultiSigOperations(this.client, operations, signingKeys);
|
|
913
|
+
}
|
|
914
|
+
createAuthority(keyAuths = [], accountAuths = [], weightThreshold = 1) {
|
|
915
|
+
return utils_2.Utils.createAuthority(keyAuths, accountAuths, weightThreshold);
|
|
916
|
+
}
|
|
917
|
+
updateAccountAuthorities(account, authorityUpdate, signingKeys) {
|
|
918
|
+
return utils_2.Utils.updateAccountAuthorities(this.client, this.config, account, authorityUpdate, signingKeys);
|
|
919
|
+
}
|
|
920
|
+
escrowTransfer(options, signingKeys) {
|
|
921
|
+
return utils_2.Utils.escrowTransfer(this.client, this.config, options, signingKeys);
|
|
922
|
+
}
|
|
923
|
+
escrowApprove(options, signingKeys) {
|
|
924
|
+
return utils_2.Utils.escrowApprove(this.client, this.config, options, signingKeys);
|
|
925
|
+
}
|
|
926
|
+
escrowDispute(options, signingKeys) {
|
|
927
|
+
return utils_2.Utils.escrowDispute(this.client, this.config, options, signingKeys);
|
|
928
|
+
}
|
|
929
|
+
escrowRelease(options, signingKeys) {
|
|
930
|
+
return utils_2.Utils.escrowRelease(this.client, this.config, options, signingKeys);
|
|
931
|
+
}
|
|
932
|
+
recurrentTransfer(options, signingKeys) {
|
|
933
|
+
return utils_2.Utils.recurrentTransfer(this.client, this.config, options, signingKeys);
|
|
934
|
+
}
|
|
935
|
+
createProposal(options, signingKeys) {
|
|
936
|
+
return utils_2.Utils.createProposal(this.client, this.config, options, signingKeys);
|
|
937
|
+
}
|
|
938
|
+
updateProposalVotes(options, signingKeys) {
|
|
939
|
+
return utils_2.Utils.updateProposalVotes(this.client, this.config, options, signingKeys);
|
|
940
|
+
}
|
|
941
|
+
removeProposals(options, signingKeys) {
|
|
942
|
+
return utils_2.Utils.removeProposals(this.client, this.config, options, signingKeys);
|
|
943
|
+
}
|
|
944
|
+
transferHiveEngineTokens(from, to, symbol, quantity, memo = '') {
|
|
945
|
+
return utils_2.Utils.transferHiveEngineTokens(this.client, this.config, from, to, quantity, symbol, memo);
|
|
946
|
+
}
|
|
947
|
+
transferHiveEngineTokensMultiple(from, accounts = [], symbol, memo = '', amount = '0') {
|
|
948
|
+
return utils_2.Utils.transferHiveEngineTokensMultiple(this.client, this.config, from, accounts, symbol, memo, amount);
|
|
949
|
+
}
|
|
950
|
+
issueHiveEngineTokens(from, to, symbol, quantity, memo = '') {
|
|
951
|
+
return utils_2.Utils.issueHiveEngineTokens(this.client, this.config, from, to, symbol, quantity, memo);
|
|
952
|
+
}
|
|
953
|
+
issueHiveEngineTokensMultiple(from, accounts = [], symbol, memo = '', amount = '0') {
|
|
954
|
+
return utils_2.Utils.issueHiveEngineTokensMultiple(this.client, this.config, from, accounts, symbol, memo, amount);
|
|
955
|
+
}
|
|
956
|
+
upvote(votePercentage = '100.0', username, permlink) {
|
|
957
|
+
return utils_2.Utils.upvote(this.client, this.config, this.username, votePercentage, username, permlink);
|
|
958
|
+
}
|
|
959
|
+
downvote(votePercentage = '100.0', username, permlink) {
|
|
960
|
+
return utils_2.Utils.downvote(this.client, this.config, this.username, votePercentage, username, permlink);
|
|
961
|
+
}
|
|
962
|
+
getTransaction(blockNumber, transactionId) {
|
|
963
|
+
return utils_2.Utils.getTransaction(this.client, blockNumber, transactionId);
|
|
964
|
+
}
|
|
965
|
+
getStatus() {
|
|
966
|
+
return {
|
|
967
|
+
lastBlockNumber: this.lastBlockNumber,
|
|
968
|
+
headBlockNumber: this.headBlockNumber,
|
|
969
|
+
blocksBehind: this.headBlockNumber
|
|
970
|
+
? Math.max(0, this.headBlockNumber - this.lastBlockNumber)
|
|
971
|
+
: 0,
|
|
972
|
+
latestBlockchainTime: this.latestBlockchainTime,
|
|
973
|
+
isCatchingUp: this.isCatchingUp
|
|
974
|
+
};
|
|
975
|
+
}
|
|
976
|
+
verifyTransfer(transaction, from, to, amount) {
|
|
977
|
+
return utils_2.Utils.verifyTransfer(transaction, from, to, amount);
|
|
978
|
+
}
|
|
979
|
+
onComment(callback) {
|
|
980
|
+
this.commentSubscriptions.push({
|
|
981
|
+
callback
|
|
982
|
+
});
|
|
983
|
+
}
|
|
984
|
+
onPost(callback) {
|
|
985
|
+
this.postSubscriptions.push({
|
|
986
|
+
callback
|
|
987
|
+
});
|
|
988
|
+
}
|
|
989
|
+
onTransfer(account, callback) {
|
|
990
|
+
this.transferSubscriptions.push({
|
|
991
|
+
account,
|
|
992
|
+
callback
|
|
993
|
+
});
|
|
994
|
+
}
|
|
995
|
+
onCustomJson(callback) {
|
|
996
|
+
this.customJsonSubscriptions.push({ callback });
|
|
997
|
+
}
|
|
998
|
+
onCustomJsonId(callback, id) {
|
|
999
|
+
this.customJsonIdSubscriptions.push({ callback, id });
|
|
1000
|
+
}
|
|
1001
|
+
onHiveEngine(callback) {
|
|
1002
|
+
this.customJsonHiveEngineSubscriptions.push({ callback });
|
|
1003
|
+
}
|
|
1004
|
+
onEscrowTransfer(callback) {
|
|
1005
|
+
this.escrowSubscriptions.push({ type: 'escrow_transfer', callback });
|
|
1006
|
+
}
|
|
1007
|
+
onEscrowApprove(callback) {
|
|
1008
|
+
this.escrowSubscriptions.push({ type: 'escrow_approve', callback });
|
|
1009
|
+
}
|
|
1010
|
+
onEscrowDispute(callback) {
|
|
1011
|
+
this.escrowSubscriptions.push({ type: 'escrow_dispute', callback });
|
|
1012
|
+
}
|
|
1013
|
+
onEscrowRelease(callback) {
|
|
1014
|
+
this.escrowSubscriptions.push({ type: 'escrow_release', callback });
|
|
1015
|
+
}
|
|
1016
|
+
// Memory management: cleanup subscriptions
|
|
1017
|
+
cleanupSubscriptions() {
|
|
1018
|
+
// Limit subscription arrays to prevent memory leaks
|
|
1019
|
+
if (this.customJsonSubscriptions.length > this.maxSubscriptions) {
|
|
1020
|
+
this.customJsonSubscriptions = this.customJsonSubscriptions.slice(-this.maxSubscriptions);
|
|
1021
|
+
console.warn(`[Streamer] Trimmed customJsonSubscriptions to ${this.maxSubscriptions} items`);
|
|
1022
|
+
}
|
|
1023
|
+
if (this.customJsonIdSubscriptions.length > this.maxSubscriptions) {
|
|
1024
|
+
this.customJsonIdSubscriptions = this.customJsonIdSubscriptions.slice(-this.maxSubscriptions);
|
|
1025
|
+
console.warn(`[Streamer] Trimmed customJsonIdSubscriptions to ${this.maxSubscriptions} items`);
|
|
1026
|
+
}
|
|
1027
|
+
if (this.customJsonHiveEngineSubscriptions.length > this.maxSubscriptions) {
|
|
1028
|
+
this.customJsonHiveEngineSubscriptions = this.customJsonHiveEngineSubscriptions.slice(-this.maxSubscriptions);
|
|
1029
|
+
console.warn(`[Streamer] Trimmed customJsonHiveEngineSubscriptions to ${this.maxSubscriptions} items`);
|
|
1030
|
+
}
|
|
1031
|
+
if (this.commentSubscriptions.length > this.maxSubscriptions) {
|
|
1032
|
+
this.commentSubscriptions = this.commentSubscriptions.slice(-this.maxSubscriptions);
|
|
1033
|
+
console.warn(`[Streamer] Trimmed commentSubscriptions to ${this.maxSubscriptions} items`);
|
|
1034
|
+
}
|
|
1035
|
+
if (this.postSubscriptions.length > this.maxSubscriptions) {
|
|
1036
|
+
this.postSubscriptions = this.postSubscriptions.slice(-this.maxSubscriptions);
|
|
1037
|
+
console.warn(`[Streamer] Trimmed postSubscriptions to ${this.maxSubscriptions} items`);
|
|
1038
|
+
}
|
|
1039
|
+
if (this.transferSubscriptions.length > this.maxSubscriptions) {
|
|
1040
|
+
this.transferSubscriptions = this.transferSubscriptions.slice(-this.maxSubscriptions);
|
|
1041
|
+
console.warn(`[Streamer] Trimmed transferSubscriptions to ${this.maxSubscriptions} items`);
|
|
1042
|
+
}
|
|
1043
|
+
if (this.escrowSubscriptions.length > this.maxSubscriptions) {
|
|
1044
|
+
this.escrowSubscriptions = this.escrowSubscriptions.slice(-this.maxSubscriptions);
|
|
1045
|
+
console.warn(`[Streamer] Trimmed escrowSubscriptions to ${this.maxSubscriptions} items`);
|
|
1046
|
+
}
|
|
1047
|
+
}
|
|
1048
|
+
// Add method to remove specific subscriptions
|
|
1049
|
+
removeTransferSubscription(account) {
|
|
1050
|
+
this.transferSubscriptions = this.transferSubscriptions.filter(sub => sub.account !== account);
|
|
1051
|
+
}
|
|
1052
|
+
removeCustomJsonIdSubscription(id) {
|
|
1053
|
+
this.customJsonIdSubscriptions = this.customJsonIdSubscriptions.filter(sub => sub.id !== id);
|
|
1054
|
+
}
|
|
1055
|
+
removeEscrowSubscriptions(type) {
|
|
1056
|
+
if (!type) {
|
|
1057
|
+
this.escrowSubscriptions = [];
|
|
1058
|
+
return;
|
|
1059
|
+
}
|
|
1060
|
+
this.escrowSubscriptions = this.escrowSubscriptions.filter(sub => sub.type !== type);
|
|
1061
|
+
}
|
|
1062
|
+
}
|
|
1063
|
+
exports.Streamer = Streamer;
|
|
546
1064
|
//# sourceMappingURL=streamer.js.map
|