hive-stream 3.0.2 → 3.0.4

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 (202) hide show
  1. package/DOCUMENTATION.md +50 -2
  2. package/README.md +282 -4
  3. package/dist/adapters/base.adapter.d.ts +5 -0
  4. package/dist/adapters/base.adapter.js +9 -0
  5. package/dist/adapters/base.adapter.js.map +1 -1
  6. package/dist/adapters/mongodb.adapter.d.ts +6 -6
  7. package/dist/adapters/mongodb.adapter.js +36 -21
  8. package/dist/adapters/mongodb.adapter.js.map +1 -1
  9. package/dist/adapters/postgresql.adapter.d.ts +7 -0
  10. package/dist/adapters/postgresql.adapter.js +46 -19
  11. package/dist/adapters/postgresql.adapter.js.map +1 -1
  12. package/dist/adapters/sqlite.adapter.d.ts +4 -0
  13. package/dist/adapters/sqlite.adapter.js +10 -0
  14. package/dist/adapters/sqlite.adapter.js.map +1 -1
  15. package/dist/api.d.ts +13 -3
  16. package/dist/api.js +96 -62
  17. package/dist/api.js.map +1 -1
  18. package/dist/builders.d.ts +176 -0
  19. package/dist/builders.js +727 -0
  20. package/dist/builders.js.map +1 -0
  21. package/dist/config.d.ts +16 -1
  22. package/dist/config.js +95 -3
  23. package/dist/config.js.map +1 -1
  24. package/dist/contracts/auctionhouse.contract.d.ts +4 -0
  25. package/dist/contracts/auctionhouse.contract.js +234 -0
  26. package/dist/contracts/auctionhouse.contract.js.map +1 -0
  27. package/dist/contracts/booking.contract.d.ts +4 -0
  28. package/dist/contracts/booking.contract.js +225 -0
  29. package/dist/contracts/booking.contract.js.map +1 -0
  30. package/dist/contracts/bountyboard.contract.d.ts +4 -0
  31. package/dist/contracts/bountyboard.contract.js +233 -0
  32. package/dist/contracts/bountyboard.contract.js.map +1 -0
  33. package/dist/contracts/bundlemarketplace.contract.d.ts +4 -0
  34. package/dist/contracts/bundlemarketplace.contract.js +195 -0
  35. package/dist/contracts/bundlemarketplace.contract.js.map +1 -0
  36. package/dist/contracts/charitymatch.contract.d.ts +4 -0
  37. package/dist/contracts/charitymatch.contract.js +172 -0
  38. package/dist/contracts/charitymatch.contract.js.map +1 -0
  39. package/dist/contracts/coinflip.contract.js +25 -22
  40. package/dist/contracts/coinflip.contract.js.map +1 -1
  41. package/dist/contracts/crowdfund.contract.d.ts +4 -0
  42. package/dist/contracts/crowdfund.contract.js +290 -0
  43. package/dist/contracts/crowdfund.contract.js.map +1 -0
  44. package/dist/contracts/dcabot.contract.d.ts +4 -0
  45. package/dist/contracts/dcabot.contract.js +217 -0
  46. package/dist/contracts/dcabot.contract.js.map +1 -0
  47. package/dist/contracts/dice.contract.js +25 -22
  48. package/dist/contracts/dice.contract.js.map +1 -1
  49. package/dist/contracts/domainregistry.contract.d.ts +4 -0
  50. package/dist/contracts/domainregistry.contract.js +232 -0
  51. package/dist/contracts/domainregistry.contract.js.map +1 -0
  52. package/dist/contracts/exchange.contract.js +209 -168
  53. package/dist/contracts/exchange.contract.js.map +1 -1
  54. package/dist/contracts/fanclub.contract.d.ts +4 -0
  55. package/dist/contracts/fanclub.contract.js +193 -0
  56. package/dist/contracts/fanclub.contract.js.map +1 -0
  57. package/dist/contracts/giftcard.contract.d.ts +4 -0
  58. package/dist/contracts/giftcard.contract.js +158 -0
  59. package/dist/contracts/giftcard.contract.js.map +1 -0
  60. package/dist/contracts/grantrounds.contract.d.ts +4 -0
  61. package/dist/contracts/grantrounds.contract.js +265 -0
  62. package/dist/contracts/grantrounds.contract.js.map +1 -0
  63. package/dist/contracts/groupbuy.contract.d.ts +4 -0
  64. package/dist/contracts/groupbuy.contract.js +198 -0
  65. package/dist/contracts/groupbuy.contract.js.map +1 -0
  66. package/dist/contracts/helpers.d.ts +66 -0
  67. package/dist/contracts/helpers.js +166 -0
  68. package/dist/contracts/helpers.js.map +1 -0
  69. package/dist/contracts/insurancepool.contract.d.ts +4 -0
  70. package/dist/contracts/insurancepool.contract.js +281 -0
  71. package/dist/contracts/insurancepool.contract.js.map +1 -0
  72. package/dist/contracts/invoice.contract.d.ts +4 -0
  73. package/dist/contracts/invoice.contract.js +193 -0
  74. package/dist/contracts/invoice.contract.js.map +1 -0
  75. package/dist/contracts/launchpad.contract.d.ts +4 -0
  76. package/dist/contracts/launchpad.contract.js +225 -0
  77. package/dist/contracts/launchpad.contract.js.map +1 -0
  78. package/dist/contracts/lotto.contract.js +53 -37
  79. package/dist/contracts/lotto.contract.js.map +1 -1
  80. package/dist/contracts/multisigtreasury.contract.d.ts +4 -0
  81. package/dist/contracts/multisigtreasury.contract.js +245 -0
  82. package/dist/contracts/multisigtreasury.contract.js.map +1 -0
  83. package/dist/contracts/nft.contract.d.ts +1 -0
  84. package/dist/contracts/nft.contract.js +234 -195
  85. package/dist/contracts/nft.contract.js.map +1 -1
  86. package/dist/contracts/oraclebounty.contract.d.ts +4 -0
  87. package/dist/contracts/oraclebounty.contract.js +250 -0
  88. package/dist/contracts/oraclebounty.contract.js.map +1 -0
  89. package/dist/contracts/payroll.contract.d.ts +4 -0
  90. package/dist/contracts/payroll.contract.js +232 -0
  91. package/dist/contracts/payroll.contract.js.map +1 -0
  92. package/dist/contracts/paywall.contract.d.ts +4 -0
  93. package/dist/contracts/paywall.contract.js +185 -0
  94. package/dist/contracts/paywall.contract.js.map +1 -0
  95. package/dist/contracts/poll.contract.js +2 -0
  96. package/dist/contracts/poll.contract.js.map +1 -1
  97. package/dist/contracts/predictionmarket.contract.d.ts +4 -0
  98. package/dist/contracts/predictionmarket.contract.js +213 -0
  99. package/dist/contracts/predictionmarket.contract.js.map +1 -0
  100. package/dist/contracts/proposaltimelock.contract.d.ts +4 -0
  101. package/dist/contracts/proposaltimelock.contract.js +250 -0
  102. package/dist/contracts/proposaltimelock.contract.js.map +1 -0
  103. package/dist/contracts/questpass.contract.d.ts +4 -0
  104. package/dist/contracts/questpass.contract.js +214 -0
  105. package/dist/contracts/questpass.contract.js.map +1 -0
  106. package/dist/contracts/referral.contract.d.ts +4 -0
  107. package/dist/contracts/referral.contract.js +238 -0
  108. package/dist/contracts/referral.contract.js.map +1 -0
  109. package/dist/contracts/rental.contract.d.ts +4 -0
  110. package/dist/contracts/rental.contract.js +221 -0
  111. package/dist/contracts/rental.contract.js.map +1 -0
  112. package/dist/contracts/revenuesplit.contract.d.ts +4 -0
  113. package/dist/contracts/revenuesplit.contract.js +211 -0
  114. package/dist/contracts/revenuesplit.contract.js.map +1 -0
  115. package/dist/contracts/rps.contract.js +48 -20
  116. package/dist/contracts/rps.contract.js.map +1 -1
  117. package/dist/contracts/savings.contract.d.ts +4 -0
  118. package/dist/contracts/savings.contract.js +208 -0
  119. package/dist/contracts/savings.contract.js.map +1 -0
  120. package/dist/contracts/subscription.contract.d.ts +4 -0
  121. package/dist/contracts/subscription.contract.js +241 -0
  122. package/dist/contracts/subscription.contract.js.map +1 -0
  123. package/dist/contracts/sweepstakes.contract.d.ts +4 -0
  124. package/dist/contracts/sweepstakes.contract.js +209 -0
  125. package/dist/contracts/sweepstakes.contract.js.map +1 -0
  126. package/dist/contracts/ticketing.contract.d.ts +4 -0
  127. package/dist/contracts/ticketing.contract.js +185 -0
  128. package/dist/contracts/ticketing.contract.js.map +1 -0
  129. package/dist/contracts/tipjar.contract.js +2 -0
  130. package/dist/contracts/tipjar.contract.js.map +1 -1
  131. package/dist/contracts/token.contract.js +135 -125
  132. package/dist/contracts/token.contract.js.map +1 -1
  133. package/dist/index.d.ts +40 -0
  134. package/dist/index.js +72 -1
  135. package/dist/index.js.map +1 -1
  136. package/dist/metadata.d.ts +20 -0
  137. package/dist/metadata.js +320 -1
  138. package/dist/metadata.js.map +1 -1
  139. package/dist/providers/block-provider.d.ts +22 -0
  140. package/dist/providers/block-provider.js +3 -0
  141. package/dist/providers/block-provider.js.map +1 -0
  142. package/dist/providers/haf-client.d.ts +30 -0
  143. package/dist/providers/haf-client.js +119 -0
  144. package/dist/providers/haf-client.js.map +1 -0
  145. package/dist/providers/haf-provider.d.ts +49 -0
  146. package/dist/providers/haf-provider.js +256 -0
  147. package/dist/providers/haf-provider.js.map +1 -0
  148. package/dist/providers/hive-provider.d.ts +13 -0
  149. package/dist/providers/hive-provider.js +25 -0
  150. package/dist/providers/hive-provider.js.map +1 -0
  151. package/dist/providers/index.d.ts +4 -0
  152. package/dist/providers/index.js +21 -0
  153. package/dist/providers/index.js.map +1 -0
  154. package/dist/streamer.d.ts +65 -4
  155. package/dist/streamer.js +768 -72
  156. package/dist/streamer.js.map +1 -1
  157. package/dist/types/hive-stream.d.ts +317 -0
  158. package/dist/utils.d.ts +33 -0
  159. package/dist/utils.js +198 -2
  160. package/dist/utils.js.map +1 -1
  161. package/package.json +16 -1
  162. package/.claude/settings.local.json +0 -12
  163. package/.env.example +0 -3
  164. package/.travis.yml +0 -11
  165. package/AGENTS.md +0 -35
  166. package/CLAUDE.md +0 -75
  167. package/ecosystem.config.js +0 -17
  168. package/examples/contracts/README.md +0 -8
  169. package/examples/contracts/exchange.ts +0 -38
  170. package/examples/contracts/poll.ts +0 -21
  171. package/examples/contracts/rps.ts +0 -19
  172. package/examples/contracts/tipjar.ts +0 -19
  173. package/jest.config.js +0 -9
  174. package/test-contract-block.md +0 -19
  175. package/tests/actions.spec.ts +0 -252
  176. package/tests/adapters/actions-persistence.spec.ts +0 -144
  177. package/tests/adapters/postgresql.adapter.spec.ts +0 -127
  178. package/tests/adapters/sqlite.adapter.spec.ts +0 -181
  179. package/tests/config-input.spec.ts +0 -90
  180. package/tests/contracts/coinflip.contract.spec.ts +0 -94
  181. package/tests/contracts/dice.contract.spec.ts +0 -87
  182. package/tests/contracts/entrants.json +0 -729
  183. package/tests/contracts/exchange.contract.spec.ts +0 -84
  184. package/tests/contracts/lotto.contract.spec.ts +0 -59
  185. package/tests/contracts/nft.contract.spec.ts +0 -948
  186. package/tests/contracts/token.contract.spec.ts +0 -90
  187. package/tests/exchanges/coingecko.exchange.spec.ts +0 -169
  188. package/tests/exchanges/exchange.base.spec.ts +0 -246
  189. package/tests/helpers/mock-adapter.ts +0 -214
  190. package/tests/helpers/mock-fetch.ts +0 -165
  191. package/tests/hive-chain-features.spec.ts +0 -319
  192. package/tests/hive-rates.spec.ts +0 -443
  193. package/tests/integration/hive-rates.integration.spec.ts +0 -35
  194. package/tests/metadata.spec.ts +0 -63
  195. package/tests/setup.ts +0 -30
  196. package/tests/streamer-actions.spec.ts +0 -274
  197. package/tests/streamer.spec.ts +0 -342
  198. package/tests/types/rates.spec.ts +0 -216
  199. package/tests/utils.spec.ts +0 -113
  200. package/tsconfig.build.json +0 -4
  201. package/tslint.json +0 -21
  202. package/wallaby.js +0 -26
package/dist/streamer.js CHANGED
@@ -9,8 +9,11 @@ const sqlite_adapter_1 = require("./adapters/sqlite.adapter");
9
9
  const utils_1 = require("@hiveio/dhive/lib/utils");
10
10
  const actions_1 = require("./actions");
11
11
  const dhive_1 = require("@hiveio/dhive");
12
+ const bignumber_js_1 = __importDefault(require("bignumber.js"));
12
13
  const utils_2 = require("./utils");
14
+ const builders_1 = require("./builders");
13
15
  const config_1 = require("./config");
16
+ const hive_provider_1 = require("./providers/hive-provider");
14
17
  const sscjs_1 = __importDefault(require("sscjs"));
15
18
  class Streamer {
16
19
  customJsonSubscriptions = [];
@@ -39,8 +42,14 @@ class Streamer {
39
42
  blockTime;
40
43
  latestBlockchainTime;
41
44
  disableAllProcessing = false;
45
+ isStarted = false;
42
46
  contracts = [];
47
+ blockProvider;
43
48
  adapter;
49
+ adapterInitializationPromise = null;
50
+ adapterInitialized = false;
51
+ initializedContracts = new Set();
52
+ apiServer = null;
44
53
  actions = [];
45
54
  // Performance optimization properties
46
55
  lastStateSave = Date.now();
@@ -66,6 +75,39 @@ class Streamer {
66
75
  cacheTimeout = 300000; // 5 minutes
67
76
  maxCacheSize = 1000;
68
77
  utils = utils_2.Utils;
78
+ money = {
79
+ parseAssetAmount: (rawAmount) => utils_2.Utils.parseAssetAmount(rawAmount),
80
+ formatAmount: (amount, precision) => utils_2.Utils.formatAmount(amount, precision),
81
+ formatAssetAmount: (amount, symbol, precision) => utils_2.Utils.formatAssetAmount(amount, symbol, precision),
82
+ calculatePercentageAmount: (amount, percentage, precision) => utils_2.Utils.calculatePercentageAmount(amount, percentage, precision),
83
+ calculateBasisPointsAmount: (amount, basisPoints, precision) => utils_2.Utils.calculateBasisPointsAmount(amount, basisPoints, precision),
84
+ splitAmountByBasisPoints: (amount, basisPoints, precision) => utils_2.Utils.splitAmountByBasisPoints(amount, basisPoints, precision),
85
+ splitAmountByPercentage: (amount, percentages, precision) => utils_2.Utils.splitAmountByPercentage(amount, percentages, precision),
86
+ splitAmountByWeights: (amount, weights, precision) => utils_2.Utils.splitAmountByWeights(amount, weights, precision),
87
+ };
88
+ flows = {
89
+ incomingTransfers: (account) => new builders_1.IncomingTransfersBuilder(this, account),
90
+ autoBurnIncomingTransfers: (options = {}) => this.autoBurnIncomingTransfers(options),
91
+ autoForwardIncomingTransfers: (options) => this.autoForwardIncomingTransfers(options),
92
+ autoRefundIncomingTransfers: (options = {}) => this.autoRefundIncomingTransfers(options),
93
+ autoSplitIncomingTransfers: (options) => this.autoSplitIncomingTransfers(options),
94
+ autoRouteIncomingTransfers: (options) => this.autoRouteIncomingTransfers(options),
95
+ planIncomingTransferRoutes: (transfer, options) => this.planIncomingTransferRoutes(transfer, options),
96
+ };
97
+ ops = {
98
+ transfer: () => new builders_1.HiveTransferBuilder(this),
99
+ burn: () => new builders_1.HiveBurnBuilder(this),
100
+ escrowTransfer: () => new builders_1.HiveEscrowTransferBuilder(this),
101
+ recurrentTransfer: () => new builders_1.HiveRecurrentTransferBuilder(this),
102
+ createProposal: () => new builders_1.HiveProposalBuilder(this),
103
+ transferEngine: () => new builders_1.HiveEngineTokenTransferBuilder(this),
104
+ burnEngine: () => new builders_1.HiveEngineTokenBurnBuilder(this),
105
+ issueEngine: () => new builders_1.HiveEngineTokenIssueBuilder(this),
106
+ voteProposals: () => new builders_1.HiveProposalVotesBuilder(this),
107
+ removeProposals: () => new builders_1.HiveRemoveProposalsBuilder(this),
108
+ upvote: () => new builders_1.HiveVoteBuilder(this, 'upvote'),
109
+ downvote: () => new builders_1.HiveVoteBuilder(this, 'downvote'),
110
+ };
69
111
  constructor(userConfig = {}) {
70
112
  this.config = (0, config_1.createConfig)(userConfig);
71
113
  this.lastBlockNumber = this.config.LAST_BLOCK_NUMBER;
@@ -74,23 +116,388 @@ class Streamer {
74
116
  this.activeKey = this.config.ACTIVE_KEY;
75
117
  this.hive = new sscjs_1.default(this.config.HIVE_ENGINE_API);
76
118
  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);
119
+ this.adapter = new sqlite_adapter_1.SqliteAdapter();
120
+ if (userConfig.blockProvider) {
121
+ this.blockProvider = userConfig.blockProvider;
122
+ }
123
+ else {
124
+ this.blockProvider = new hive_provider_1.HiveProvider({ apiNodes: this.config.API_NODES });
125
+ }
126
+ }
127
+ async ensureAdapterReady() {
128
+ if (this.adapterInitialized) {
129
+ return;
130
+ }
131
+ if (!this.adapter) {
132
+ throw new Error('No adapter registered');
133
+ }
134
+ if (!this.adapterInitializationPromise) {
135
+ this.adapterInitializationPromise = Promise.resolve(this.adapter.create?.())
136
+ .then(() => {
137
+ this.adapterInitialized = true;
138
+ })
139
+ .catch((error) => {
140
+ this.adapterInitializationPromise = null;
141
+ this.adapterInitialized = false;
142
+ throw error;
143
+ });
144
+ }
145
+ await this.adapterInitializationPromise;
146
+ }
147
+ getLifecycleContext() {
148
+ return {
149
+ streamer: this,
150
+ adapter: this.adapter,
151
+ config: this.config
152
+ };
153
+ }
154
+ createTransferEvent(op, blockNumber, blockId, prevBlockId, trxId, blockTime) {
155
+ const rawAmount = String(op?.amount || '');
156
+ let amount = '';
157
+ let asset = '';
158
+ try {
159
+ const parsed = utils_2.Utils.parseAssetAmount(rawAmount);
160
+ amount = parsed.amount;
161
+ asset = parsed.asset;
162
+ }
163
+ catch (error) {
164
+ amount = '';
165
+ asset = '';
166
+ }
167
+ return {
168
+ op,
169
+ transfer: {
170
+ from: op?.from || '',
171
+ to: op?.to || '',
172
+ rawAmount,
173
+ amount,
174
+ asset,
175
+ memo: op?.memo
176
+ },
177
+ block: {
178
+ number: blockNumber,
179
+ id: blockId,
180
+ previousId: prevBlockId,
181
+ time: blockTime
182
+ },
183
+ transaction: {
184
+ id: trxId
185
+ }
186
+ };
187
+ }
188
+ normalizeTransferPreviewInput(transfer) {
189
+ if (typeof transfer === 'object' && transfer !== null && 'transfer' in transfer && 'transaction' in transfer && 'block' in transfer) {
190
+ return transfer;
191
+ }
192
+ const op = typeof transfer === 'string'
193
+ ? { amount: transfer }
194
+ : transfer || {};
195
+ return this.createTransferEvent(op, 0, '', '', '', new Date(0));
196
+ }
197
+ resolveAccount(account) {
198
+ const resolved = account || this.username || this.config.USERNAME;
199
+ if (!resolved) {
200
+ throw new Error('Account is required');
201
+ }
202
+ return resolved;
203
+ }
204
+ async dedupeHas(store, key) {
205
+ return Boolean(await store.has(key));
206
+ }
207
+ async dedupeAdd(store, key) {
208
+ await store.add(key);
209
+ }
210
+ normalizeDedupeStore(dedupeStore) {
211
+ const store = dedupeStore || new Set();
212
+ if (store instanceof Set) {
213
+ return {
214
+ has: (key) => store.has(key),
215
+ add: (key) => {
216
+ store.add(key);
217
+ }
218
+ };
219
+ }
220
+ return store;
221
+ }
222
+ isZeroAmountFlowError(error) {
223
+ return error instanceof Error
224
+ && (error.message === 'Burn amount must be greater than zero' || error.message === 'Route amount must be greater than zero');
225
+ }
226
+ resolveFlowBasisPoints(options, context, allowUnspecified = true) {
227
+ const percentage = options.percentage ?? options.percent;
228
+ const basisPoints = options.basisPoints;
229
+ if (percentage !== undefined && basisPoints !== undefined) {
230
+ throw new Error(`${context} accepts either percentage or basisPoints, not both`);
231
+ }
232
+ if (percentage === undefined && basisPoints === undefined) {
233
+ if (allowUnspecified) {
234
+ return null;
235
+ }
236
+ throw new Error(`${context} requires percentage or basisPoints`);
237
+ }
238
+ if (basisPoints !== undefined) {
239
+ if (!Number.isInteger(basisPoints) || basisPoints < 0 || basisPoints > 10000) {
240
+ throw new Error(`${context} basisPoints must be an integer between 0 and 10000`);
241
+ }
242
+ return basisPoints;
243
+ }
244
+ const percentageValue = new bignumber_js_1.default(percentage);
245
+ if (percentageValue.isNaN() || !percentageValue.isFinite()) {
246
+ throw new Error(`${context} percentage must be a valid number`);
247
+ }
248
+ if (percentageValue.lt(0) || percentageValue.gt(100)) {
249
+ throw new Error(`${context} percentage must be between 0 and 100`);
250
+ }
251
+ const bps = percentageValue.multipliedBy(100);
252
+ if (!bps.isInteger()) {
253
+ throw new Error(`${context} percentage supports up to 2 decimal places; use basisPoints for finer control`);
254
+ }
255
+ return bps.toNumber();
256
+ }
257
+ resolveFlowMode(route) {
258
+ return route.mode === 'onTop' ? 'onTop' : 'base';
259
+ }
260
+ normalizeBaseRouteAllocations(routes) {
261
+ const allocations = routes.map((route, index) => this.resolveFlowBasisPoints(route, `Route ${index + 1}`, true));
262
+ const unspecified = allocations.filter((allocation) => allocation === null).length;
263
+ const explicitTotal = allocations.reduce((sum, allocation) => sum + (allocation || 0), 0);
264
+ if (unspecified > 1) {
265
+ throw new Error('Only one flow route can omit percentage or basisPoints');
266
+ }
267
+ if (explicitTotal > 10000) {
268
+ throw new Error('Flow route allocations cannot exceed 10000 basis points');
269
+ }
270
+ if (unspecified === 0 && explicitTotal !== 10000) {
271
+ throw new Error('Flow route allocations must total 100%');
272
+ }
273
+ if (unspecified === 1) {
274
+ const remainder = 10000 - explicitTotal;
275
+ return allocations.map((allocation) => allocation === null ? remainder : allocation);
276
+ }
277
+ return allocations;
278
+ }
279
+ normalizeOnTopRouteAllocations(routes) {
280
+ const allocations = routes.map((route, index) => this.resolveFlowBasisPoints(route, `On-top route ${index + 1}`, false));
281
+ const explicitTotal = allocations.reduce((sum, allocation) => sum + allocation, 0);
282
+ if (explicitTotal <= 0) {
283
+ throw new Error('On-top flow route allocations must be greater than zero');
284
+ }
285
+ return allocations;
286
+ }
287
+ resolvePlannedRouteMemo(event, route, index, defaultMemo) {
288
+ if (typeof route.memo === 'function') {
289
+ return route.memo(event);
290
+ }
291
+ if (typeof route.memo === 'string') {
292
+ return route.memo;
293
+ }
294
+ if (typeof defaultMemo === 'function') {
295
+ return defaultMemo(event, route, index);
296
+ }
297
+ return defaultMemo || '';
298
+ }
299
+ resolveGroupSplitStrategy(route) {
300
+ if (route.split) {
301
+ return route.split;
302
+ }
303
+ return route.group.some((recipient) => recipient.weight !== undefined) ? 'weighted' : 'equal';
304
+ }
305
+ resolveGroupWeights(route, routeIndex) {
306
+ if (!Array.isArray(route.group) || route.group.length === 0) {
307
+ throw new Error(`Route ${routeIndex + 1} group must include at least one account`);
308
+ }
309
+ if (this.resolveGroupSplitStrategy(route) === 'equal') {
310
+ return route.group.map(() => 1);
311
+ }
312
+ return route.group.map((recipient, groupIndex) => {
313
+ if (recipient.weight === undefined) {
314
+ return 1;
315
+ }
316
+ const weight = new bignumber_js_1.default(recipient.weight);
317
+ if (weight.isNaN() || !weight.isFinite() || weight.lte(0)) {
318
+ throw new Error(`Route ${routeIndex + 1} recipient ${groupIndex + 1} weight must be greater than zero`);
319
+ }
320
+ return weight.toString();
321
+ });
322
+ }
323
+ expandPlannedRoute(event, route, routeIndex, amount, asset, memo) {
324
+ const mode = this.resolveFlowMode(route);
325
+ if (route.type === 'burn') {
326
+ return [{
327
+ type: 'burn',
328
+ mode,
329
+ amount,
330
+ asset,
331
+ memo,
332
+ routeIndex
333
+ }];
334
+ }
335
+ if ('group' in route) {
336
+ const weights = this.resolveGroupWeights(route, routeIndex);
337
+ const amounts = utils_2.Utils.splitAmountByWeights(amount, weights);
338
+ return route.group.map((recipient, groupIndex) => {
339
+ const destination = typeof recipient.account === 'function' ? recipient.account(event) : recipient.account;
340
+ if (typeof destination !== 'string' || destination.trim().length === 0) {
341
+ throw new Error(`Route ${routeIndex + 1} recipient ${groupIndex + 1} destination account is required`);
342
+ }
343
+ return {
344
+ type: 'transfer',
345
+ mode,
346
+ amount: amounts[groupIndex],
347
+ asset,
348
+ memo,
349
+ to: destination.trim(),
350
+ routeIndex,
351
+ groupIndex
352
+ };
353
+ });
354
+ }
355
+ const destination = typeof route.to === 'function' ? route.to(event) : route.to;
356
+ if (typeof destination !== 'string' || destination.trim().length === 0) {
357
+ throw new Error(`Route ${routeIndex + 1} destination account is required`);
358
+ }
359
+ return [{
360
+ type: 'transfer',
361
+ mode,
362
+ amount,
363
+ asset,
364
+ memo,
365
+ to: destination.trim(),
366
+ routeIndex
367
+ }];
368
+ }
369
+ sumPlannedRouteAmounts(plan) {
370
+ return plan.reduce((sum, route) => sum.plus(route.amount), new bignumber_js_1.default(0)).toFixed(3);
371
+ }
372
+ buildPlannedIncomingTransferRoutes(event, routes, allowedSymbols, defaultMemo) {
373
+ if (!Array.isArray(routes) || routes.length === 0) {
374
+ throw new Error('At least one flow route is required');
375
+ }
376
+ const parsed = utils_2.Utils.parseAssetAmount(event.transfer.rawAmount);
377
+ if (Array.isArray(allowedSymbols) && allowedSymbols.length > 0 && !allowedSymbols.includes(parsed.asset)) {
378
+ throw new Error(`Asset '${parsed.asset}' is not allowed for this flow`);
379
+ }
380
+ const baseRoutes = routes.filter((route) => this.resolveFlowMode(route) === 'base');
381
+ const onTopRoutes = routes.filter((route) => this.resolveFlowMode(route) === 'onTop');
382
+ if (baseRoutes.length === 0) {
383
+ throw new Error('At least one base flow route is required');
384
+ }
385
+ const baseAllocations = this.normalizeBaseRouteAllocations(baseRoutes);
386
+ let baseAmount = parsed.amount;
387
+ let onTopRouteAmounts = [];
388
+ if (onTopRoutes.length > 0) {
389
+ const onTopAllocations = this.normalizeOnTopRouteAllocations(onTopRoutes);
390
+ const pools = utils_2.Utils.splitAmountByWeights(parsed.value, [10000, ...onTopAllocations]);
391
+ baseAmount = pools[0];
392
+ onTopRouteAmounts = pools.slice(1);
393
+ }
394
+ const baseRouteAmounts = utils_2.Utils.splitAmountByBasisPoints(baseAmount, baseAllocations);
395
+ const routesPlan = routes.flatMap((route, index) => {
396
+ const memo = this.resolvePlannedRouteMemo(event, route, index, defaultMemo);
397
+ const amount = this.resolveFlowMode(route) === 'base'
398
+ ? baseRouteAmounts.shift()
399
+ : onTopRouteAmounts.shift();
400
+ return this.expandPlannedRoute(event, route, index, amount, parsed.asset, memo);
401
+ });
402
+ const onTopPlannedRoutes = routesPlan.filter((route) => route.mode === 'onTop');
403
+ return {
404
+ incomingAmount: utils_2.Utils.formatAssetAmount(parsed.value, parsed.asset),
405
+ asset: parsed.asset,
406
+ baseAmount,
407
+ onTopAmount: this.sumPlannedRouteAmounts(onTopPlannedRoutes),
408
+ routes: routesPlan
409
+ };
410
+ }
411
+ async executePlannedFlowRoutes(from, plan, ignoreZeroAmount) {
412
+ const results = [];
413
+ for (const route of plan) {
414
+ if (route.amount === '0.000') {
415
+ if (ignoreZeroAmount) {
416
+ continue;
417
+ }
418
+ throw new Error('Route amount must be greater than zero');
419
+ }
420
+ if (route.type === 'burn') {
421
+ results.push(await this.burnHiveTokens(from, route.amount, route.asset, route.memo));
422
+ continue;
423
+ }
424
+ results.push(await this.transferHiveTokens(from, route.to, route.amount, route.asset, route.memo));
425
+ }
426
+ return results;
427
+ }
428
+ calculateSingleFlowAmount(transfer, basisPoints, allowedSymbols, errorContext) {
429
+ const rawAmount = typeof transfer === 'string' ? transfer : transfer?.amount || '';
430
+ const parsed = utils_2.Utils.parseAssetAmount(rawAmount);
431
+ if (Array.isArray(allowedSymbols) && allowedSymbols.length > 0 && !allowedSymbols.includes(parsed.asset)) {
432
+ throw new Error(`Asset '${parsed.asset}' is not allowed for ${errorContext}`);
433
+ }
434
+ const amount = basisPoints === null ? parsed.amount : utils_2.Utils.calculateBasisPointsAmount(parsed.value, basisPoints);
435
+ if (amount === '0.000') {
436
+ throw new Error('Route amount must be greater than zero');
437
+ }
438
+ return {
439
+ amount,
440
+ asset: parsed.asset
441
+ };
442
+ }
443
+ async initializeContract(contract) {
444
+ if (this.initializedContracts.has(contract.name)) {
445
+ return;
446
+ }
447
+ await this.ensureAdapterReady();
448
+ if (contract.hooks?.create) {
449
+ await contract.hooks.create(this.getLifecycleContext());
450
+ }
451
+ this.initializedContracts.add(contract.name);
452
+ }
453
+ async initializeContracts() {
454
+ for (const contract of this.contracts) {
455
+ await this.initializeContract(contract);
456
+ }
457
+ }
458
+ async destroyContractLifecycle(contract) {
459
+ if (!this.initializedContracts.has(contract.name)) {
460
+ return;
461
+ }
462
+ if (contract.hooks?.destroy) {
463
+ await contract.hooks.destroy(this.getLifecycleContext());
464
+ }
465
+ this.initializedContracts.delete(contract.name);
466
+ }
467
+ async destroyContracts() {
468
+ for (const contract of [...this.contracts]) {
469
+ await this.destroyContractLifecycle(contract);
470
+ }
471
+ }
472
+ startSubscriptionCleanupInterval() {
473
+ if (this.subscriptionCleanupInterval) {
474
+ return;
80
475
  }
81
- // Start subscription cleanup interval
82
476
  this.subscriptionCleanupInterval = setInterval(() => {
83
477
  this.cleanupSubscriptions();
84
- }, 60000); // Cleanup every minute
478
+ }, 60000);
85
479
  }
86
- _initializeAdapter(adapter) {
87
- this.adapter = adapter;
88
- if (this?.adapter?.create) {
89
- this.adapter.create();
480
+ clearRuntimeTimers() {
481
+ if (this.blockNumberTimeout) {
482
+ clearTimeout(this.blockNumberTimeout);
483
+ this.blockNumberTimeout = null;
484
+ }
485
+ if (this.latestBlockTimer) {
486
+ clearInterval(this.latestBlockTimer);
487
+ this.latestBlockTimer = null;
90
488
  }
489
+ if (this.subscriptionCleanupInterval) {
490
+ clearInterval(this.subscriptionCleanupInterval);
491
+ this.subscriptionCleanupInterval = null;
492
+ }
493
+ this.isPollingBlock = false;
91
494
  }
92
495
  async registerAdapter(adapter) {
93
- if (this.adapter && this.adapter.destroy) {
496
+ if (!adapter) {
497
+ throw new Error('Adapter must be provided');
498
+ }
499
+ await this.destroyContracts();
500
+ if (this.adapterInitialized && this.adapter?.destroy) {
94
501
  try {
95
502
  await this.adapter.destroy();
96
503
  }
@@ -99,9 +506,10 @@ class Streamer {
99
506
  }
100
507
  }
101
508
  this.adapter = adapter;
102
- if (this?.adapter?.create) {
103
- await this.adapter.create();
104
- }
509
+ this.adapterInitialized = false;
510
+ this.adapterInitializationPromise = null;
511
+ await this.ensureAdapterReady();
512
+ await this.initializeContracts();
105
513
  }
106
514
  getAdapter() {
107
515
  return this.adapter;
@@ -113,6 +521,7 @@ class Streamer {
113
521
  if (!action || !(action instanceof actions_1.TimeAction)) {
114
522
  throw new Error('Invalid action: must be an instance of TimeAction');
115
523
  }
524
+ await this.ensureAdapterReady();
116
525
  const loadedActions = await this.adapter.loadActions();
117
526
  for (const a of loadedActions) {
118
527
  const exists = this.actions.find(i => i.id === a.id);
@@ -217,7 +626,7 @@ class Streamer {
217
626
  */
218
627
  async saveActionsToDisk() {
219
628
  try {
220
- if (this.adapter?.saveState) {
629
+ if (this.adapterInitialized && this.adapter?.saveState) {
221
630
  await this.adapter.saveState({
222
631
  lastBlockNumber: this.lastBlockNumber,
223
632
  actions: this.actions.map(a => a.toJSON())
@@ -258,14 +667,7 @@ class Streamer {
258
667
  if (!contract.actions || typeof contract.actions !== 'object') {
259
668
  throw new Error(`Contract '${contract.name}' must define actions`);
260
669
  }
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
- }
670
+ await this.initializeContract(contract);
269
671
  this.contracts.push(contract);
270
672
  this.contractCache.set(contract.name, contract);
271
673
  }
@@ -273,13 +675,7 @@ class Streamer {
273
675
  const contractIndex = this.contracts.findIndex(c => c.name === name);
274
676
  if (contractIndex >= 0) {
275
677
  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
- }
678
+ await this.destroyContractLifecycle(contract);
283
679
  this.contracts.splice(contractIndex, 1);
284
680
  this.contractCache.delete(name);
285
681
  }
@@ -292,11 +688,37 @@ class Streamer {
292
688
  * @param config
293
689
  */
294
690
  setConfig(config) {
295
- Object.assign(this.config, (0, config_1.normalizeConfigInput)(config));
691
+ const normalizedInput = (0, config_1.normalizeConfigInput)(config);
692
+ const nextConfig = (0, config_1.createConfig)({
693
+ ...this.config,
694
+ ...normalizedInput,
695
+ env: config.env,
696
+ });
697
+ const shouldRecreateClient = JSON.stringify(this.config.API_NODES) !== JSON.stringify(nextConfig.API_NODES);
698
+ const shouldRecreateHiveEngine = this.config.HIVE_ENGINE_API !== nextConfig.HIVE_ENGINE_API;
699
+ const shouldSyncApiServer = this.config.API_ENABLED !== nextConfig.API_ENABLED || this.config.API_PORT !== nextConfig.API_PORT;
700
+ Object.assign(this.config, nextConfig);
296
701
  // Set keys and username incase they have changed
297
702
  this.username = this.config.USERNAME;
298
703
  this.postingKey = this.config.POSTING_KEY;
299
704
  this.activeKey = this.config.ACTIVE_KEY;
705
+ if (shouldRecreateClient) {
706
+ this.client = new dhive_1.Client(this.config.API_NODES);
707
+ if (this.blockProvider instanceof hive_provider_1.HiveProvider) {
708
+ this.blockProvider.updateClient(this.config.API_NODES);
709
+ }
710
+ }
711
+ if (shouldRecreateHiveEngine) {
712
+ this.hive = new sscjs_1.default(this.config.HIVE_ENGINE_API);
713
+ }
714
+ if (shouldSyncApiServer && (this.apiServer || this.isStarted)) {
715
+ const syncApiServer = this.config.API_ENABLED
716
+ ? this.startApiServer(this.config.API_PORT)
717
+ : this.stopApiServer();
718
+ syncApiServer.catch((error) => {
719
+ console.error('[Streamer] Failed to sync API server after config update:', error);
720
+ });
721
+ }
300
722
  return this;
301
723
  }
302
724
  /**
@@ -306,10 +728,17 @@ class Streamer {
306
728
  *
307
729
  */
308
730
  async start() {
731
+ if (this.isStarted) {
732
+ return this;
733
+ }
309
734
  if (this.config.DEBUG_MODE) {
310
735
  console.log('Starting to stream the Hive blockchain');
311
736
  }
312
737
  this.disableAllProcessing = false;
738
+ await this.ensureAdapterReady();
739
+ await this.blockProvider.create?.();
740
+ await this.initializeContracts();
741
+ this.startSubscriptionCleanupInterval();
313
742
  const state = await this.adapter.loadState();
314
743
  if (this.config.DEBUG_MODE) {
315
744
  console.log(`Restoring state from file`);
@@ -320,9 +749,13 @@ class Streamer {
320
749
  else if (this.config.LAST_BLOCK_NUMBER) {
321
750
  this.lastBlockNumber = this.config.LAST_BLOCK_NUMBER;
322
751
  }
752
+ if (this.config.API_ENABLED) {
753
+ await this.startApiServer(this.config.API_PORT);
754
+ }
323
755
  // Kicks off the blockchain streaming and operation parsing
324
756
  this.getBlock();
325
757
  this.latestBlockTimer = setInterval(() => { this.getLatestBlock(); }, this.config.BLOCK_CHECK_INTERVAL);
758
+ this.isStarted = true;
326
759
  return this;
327
760
  }
328
761
  /**
@@ -332,23 +765,52 @@ class Streamer {
332
765
  */
333
766
  async stop() {
334
767
  this.disableAllProcessing = true;
335
- if (this.blockNumberTimeout) {
336
- clearTimeout(this.blockNumberTimeout);
768
+ this.isStarted = false;
769
+ this.clearRuntimeTimers();
770
+ await this.stopApiServer();
771
+ await this.destroyContracts();
772
+ if (this.adapterInitialized && this?.adapter?.destroy) {
773
+ await this.adapter.destroy();
337
774
  }
338
- if (this.latestBlockTimer) {
339
- clearInterval(this.latestBlockTimer);
775
+ await this.blockProvider.destroy?.();
776
+ this.adapterInitialized = false;
777
+ this.adapterInitializationPromise = null;
778
+ await (0, utils_1.sleep)(25);
779
+ }
780
+ async startApiServer(port = this.config.API_PORT) {
781
+ await this.ensureAdapterReady();
782
+ if (this.apiServer?.server && this.apiServer.port === port) {
783
+ return this.apiServer;
340
784
  }
341
- if (this.subscriptionCleanupInterval) {
342
- clearInterval(this.subscriptionCleanupInterval);
785
+ if (this.apiServer) {
786
+ await this.stopApiServer();
343
787
  }
344
- if (this?.adapter?.destroy) {
345
- await this.adapter.destroy();
788
+ this.apiServer = new api_1.Api(this, { port });
789
+ await this.apiServer.start();
790
+ return this.apiServer;
791
+ }
792
+ async stopApiServer() {
793
+ if (!this.apiServer) {
794
+ return;
346
795
  }
347
- await (0, utils_1.sleep)(800);
796
+ const apiServer = this.apiServer;
797
+ this.apiServer = null;
798
+ await apiServer.stop();
799
+ }
800
+ getApiServer() {
801
+ return this.apiServer;
802
+ }
803
+ async registerBlockProvider(provider) {
804
+ await this.blockProvider.destroy?.();
805
+ this.blockProvider = provider;
806
+ await this.blockProvider.create?.();
807
+ }
808
+ getBlockProvider() {
809
+ return this.blockProvider;
348
810
  }
349
811
  async getLatestBlock() {
350
812
  try {
351
- const props = await this.client.database.getDynamicGlobalProperties();
813
+ const props = await this.blockProvider.getDynamicGlobalProperties();
352
814
  if (props) {
353
815
  this.latestBlockchainTime = new Date(`${props.time}Z`);
354
816
  }
@@ -365,8 +827,8 @@ class Streamer {
365
827
  this.isPollingBlock = true;
366
828
  let nextDelay = this.config.BLOCK_CHECK_INTERVAL;
367
829
  try {
368
- // Load global properties from the Hive API
369
- const props = await this.client.database.getDynamicGlobalProperties();
830
+ // Load global properties from the block provider
831
+ const props = await this.blockProvider.getDynamicGlobalProperties();
370
832
  // We have no props, so try loading them again.
371
833
  if (!props) {
372
834
  return;
@@ -420,8 +882,8 @@ class Streamer {
420
882
  // Check cache first
421
883
  let block = this.blockCache.get(blockNumber);
422
884
  if (!block) {
423
- // Load the block itself from the Hive API
424
- block = await this.client.database.getBlock(blockNumber);
885
+ // Load the block from the active block provider
886
+ block = await this.blockProvider.getBlock(blockNumber);
425
887
  // Cache the block for potential reuse
426
888
  if (block) {
427
889
  this.blockCache.set(blockNumber, block);
@@ -451,37 +913,26 @@ class Streamer {
451
913
  if (this.adapter?.processBlock) {
452
914
  this.adapter.processBlock(block);
453
915
  }
454
- // Process transactions with improved concurrency
916
+ // Hive operations are order-sensitive, so process them sequentially.
455
917
  const transactions = block.transactions;
456
918
  const transactionIds = block.transaction_ids;
457
- // Create operation processing promises for better concurrency
458
- const operationPromises = [];
459
919
  for (let i = 0; i < transactions.length; i++) {
460
920
  const transaction = transactions[i];
461
921
  const operations = transaction.operations;
462
- // Process operations in batch for better performance
463
922
  for (let opIndex = 0; opIndex < operations.length; opIndex++) {
464
923
  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 => {
924
+ try {
925
+ await this.processOperation(op, blockNumber, block.block_id, block.previous, transactionIds[i], blockTime);
926
+ }
927
+ catch (error) {
467
928
  console.error('[Streamer] Operation processing error:', error, {
468
929
  blockNumber,
469
930
  transactionIndex: i,
470
931
  operationIndex: opIndex
471
932
  });
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
933
  }
479
934
  }
480
935
  }
481
- // Process any remaining operations
482
- if (operationPromises.length > 0) {
483
- await Promise.all(operationPromises);
484
- }
485
936
  this.lastBlockNumber = blockNumber;
486
937
  this.saveStateThrottled();
487
938
  }
@@ -517,13 +968,23 @@ class Streamer {
517
968
  if (operationType === 'transfer') {
518
969
  const sender = operationData?.from;
519
970
  const rawAmount = operationData?.amount;
520
- const amountParts = typeof rawAmount === 'string' ? rawAmount.split(' ') : [];
971
+ let amount = '';
972
+ let asset = '';
973
+ try {
974
+ const parsedAmount = utils_2.Utils.parseAssetAmount(String(rawAmount || ''));
975
+ amount = parsedAmount.amount;
976
+ asset = parsedAmount.asset;
977
+ }
978
+ catch (error) {
979
+ amount = '';
980
+ asset = '';
981
+ }
521
982
  const transferInfo = {
522
983
  from: sender,
523
984
  to: operationData?.to,
524
985
  rawAmount: rawAmount || '',
525
- amount: amountParts[0] || '',
526
- asset: amountParts[1] || '',
986
+ amount,
987
+ asset,
527
988
  memo: operationData?.memo
528
989
  };
529
990
  const json = utils_2.Utils.jsonParse(operationData.memo);
@@ -546,11 +1007,9 @@ class Streamer {
546
1007
  });
547
1008
  await this.dispatchContractAction(payload, context);
548
1009
  }
549
- this.transferSubscriptions.forEach(sub => {
550
- if (sub.account === operationData.to) {
551
- sub.callback(operationData, blockNumber, blockId, prevBlockId, trxId, blockTime);
552
- }
553
- });
1010
+ await Promise.all(this.transferSubscriptions
1011
+ .filter((sub) => sub.account === operationData.to)
1012
+ .map((sub) => Promise.resolve(sub.callback(operationData, blockNumber, blockId, prevBlockId, trxId, blockTime))));
554
1013
  }
555
1014
  // This is a custom JSON operation
556
1015
  if (operationType === 'custom_json') {
@@ -880,8 +1339,11 @@ class Streamer {
880
1339
  }
881
1340
  }
882
1341
  async saveStateToDisk() {
883
- if (this.adapter?.saveState) {
884
- await this.adapter.saveState({ lastBlockNumber: this.lastBlockNumber, actions: this.actions });
1342
+ if (this.adapterInitialized && this.adapter?.saveState) {
1343
+ await this.adapter.saveState({
1344
+ lastBlockNumber: this.lastBlockNumber,
1345
+ actions: this.actions.map(action => action.toJSON())
1346
+ });
885
1347
  }
886
1348
  }
887
1349
  // Throttled state saving for performance
@@ -904,6 +1366,229 @@ class Streamer {
904
1366
  transferHiveTokens(from, to, amount, symbol, memo = '') {
905
1367
  return utils_2.Utils.transferHiveTokens(this.client, this.config, from, to, amount, symbol, memo);
906
1368
  }
1369
+ burnHiveTokens(from, amount, symbol, memo = '') {
1370
+ return utils_2.Utils.burnHiveTokens(this.client, this.config, from, amount, symbol, memo);
1371
+ }
1372
+ burnTransferPortion(from, transfer, basisPoints, memo = '', allowedSymbols = ['HIVE', 'HBD']) {
1373
+ const rawAmount = typeof transfer === 'string' ? transfer : transfer?.amount || '';
1374
+ const parsed = utils_2.Utils.parseAssetAmount(rawAmount);
1375
+ if (Array.isArray(allowedSymbols) && allowedSymbols.length > 0 && !allowedSymbols.includes(parsed.asset)) {
1376
+ throw new Error(`Asset '${parsed.asset}' is not allowed for burn`);
1377
+ }
1378
+ const burnAmount = utils_2.Utils.calculateBasisPointsAmount(parsed.value, basisPoints);
1379
+ if (burnAmount === '0.000') {
1380
+ throw new Error('Burn amount must be greater than zero');
1381
+ }
1382
+ return this.burnHiveTokens(from, burnAmount, parsed.asset, memo);
1383
+ }
1384
+ burnTransferPercentage(from, transfer, percentage, memo = '', allowedSymbols = ['HIVE', 'HBD']) {
1385
+ const rawAmount = typeof transfer === 'string' ? transfer : transfer?.amount || '';
1386
+ const parsed = utils_2.Utils.parseAssetAmount(rawAmount);
1387
+ if (Array.isArray(allowedSymbols) && allowedSymbols.length > 0 && !allowedSymbols.includes(parsed.asset)) {
1388
+ throw new Error(`Asset '${parsed.asset}' is not allowed for burn`);
1389
+ }
1390
+ const burnAmount = utils_2.Utils.calculatePercentageAmount(parsed.value, percentage);
1391
+ if (burnAmount === '0.000') {
1392
+ throw new Error('Burn amount must be greater than zero');
1393
+ }
1394
+ return this.burnHiveTokens(from, burnAmount, parsed.asset, memo);
1395
+ }
1396
+ autoBurnIncomingTransfers(options = {}) {
1397
+ const basisPoints = this.resolveFlowBasisPoints(options, 'autoBurnIncomingTransfers', false);
1398
+ const account = this.resolveAccount(options.account);
1399
+ const normalizedStore = this.normalizeDedupeStore(options.dedupeStore);
1400
+ const allowedSymbols = options.allowedSymbols || ['HIVE', 'HBD'];
1401
+ const ignoreZeroAmount = options.ignoreZeroAmount !== false;
1402
+ const callback = async (op, blockNumber, blockId, prevBlockId, trxId, blockTime) => {
1403
+ const event = this.createTransferEvent(op, blockNumber, blockId, prevBlockId, trxId, blockTime);
1404
+ try {
1405
+ if (await this.dedupeHas(normalizedStore, trxId)) {
1406
+ return;
1407
+ }
1408
+ const memo = typeof options.memo === 'function' ? options.memo(event) : options.memo || '';
1409
+ const amount = this.calculateSingleFlowAmount(op, basisPoints, allowedSymbols, 'burn');
1410
+ const result = await this.burnHiveTokens(account, amount.amount, amount.asset, memo);
1411
+ await this.dedupeAdd(normalizedStore, trxId);
1412
+ if (options.onBurned) {
1413
+ await options.onBurned(result, event);
1414
+ }
1415
+ }
1416
+ catch (error) {
1417
+ if (ignoreZeroAmount && this.isZeroAmountFlowError(error)) {
1418
+ return;
1419
+ }
1420
+ if (options.onError) {
1421
+ await options.onError(error, event);
1422
+ return;
1423
+ }
1424
+ throw error;
1425
+ }
1426
+ };
1427
+ this.onTransfer(account, callback);
1428
+ return {
1429
+ account,
1430
+ stop: () => {
1431
+ this.removeTransferSubscription(account, callback);
1432
+ }
1433
+ };
1434
+ }
1435
+ autoForwardIncomingTransfers(options) {
1436
+ if (!options || typeof options.to !== 'string' || options.to.trim().length === 0) {
1437
+ throw new Error('autoForwardIncomingTransfers requires a destination account');
1438
+ }
1439
+ const account = this.resolveAccount(options.account);
1440
+ const normalizedStore = this.normalizeDedupeStore(options.dedupeStore);
1441
+ const allowedSymbols = options.allowedSymbols || ['HIVE', 'HBD'];
1442
+ const ignoreZeroAmount = options.ignoreZeroAmount !== false;
1443
+ const basisPoints = this.resolveFlowBasisPoints(options, 'autoForwardIncomingTransfers', true);
1444
+ const destination = options.to.trim();
1445
+ const callback = async (op, blockNumber, blockId, prevBlockId, trxId, blockTime) => {
1446
+ const event = this.createTransferEvent(op, blockNumber, blockId, prevBlockId, trxId, blockTime);
1447
+ try {
1448
+ if (await this.dedupeHas(normalizedStore, trxId)) {
1449
+ return;
1450
+ }
1451
+ const memo = typeof options.memo === 'function' ? options.memo(event) : options.memo || '';
1452
+ const amount = this.calculateSingleFlowAmount(op, basisPoints, allowedSymbols, 'forward');
1453
+ const result = await this.transferHiveTokens(account, destination, amount.amount, amount.asset, memo);
1454
+ await this.dedupeAdd(normalizedStore, trxId);
1455
+ if (options.onForwarded) {
1456
+ await options.onForwarded(result, event);
1457
+ }
1458
+ }
1459
+ catch (error) {
1460
+ if (ignoreZeroAmount && this.isZeroAmountFlowError(error)) {
1461
+ return;
1462
+ }
1463
+ if (options.onError) {
1464
+ await options.onError(error, event);
1465
+ return;
1466
+ }
1467
+ throw error;
1468
+ }
1469
+ };
1470
+ this.onTransfer(account, callback);
1471
+ return {
1472
+ account,
1473
+ stop: () => {
1474
+ this.removeTransferSubscription(account, callback);
1475
+ }
1476
+ };
1477
+ }
1478
+ autoRefundIncomingTransfers(options = {}) {
1479
+ const account = this.resolveAccount(options.account);
1480
+ const normalizedStore = this.normalizeDedupeStore(options.dedupeStore);
1481
+ const allowedSymbols = options.allowedSymbols || ['HIVE', 'HBD'];
1482
+ const ignoreZeroAmount = options.ignoreZeroAmount !== false;
1483
+ const basisPoints = this.resolveFlowBasisPoints(options, 'autoRefundIncomingTransfers', true);
1484
+ const callback = async (op, blockNumber, blockId, prevBlockId, trxId, blockTime) => {
1485
+ const event = this.createTransferEvent(op, blockNumber, blockId, prevBlockId, trxId, blockTime);
1486
+ try {
1487
+ if (await this.dedupeHas(normalizedStore, trxId)) {
1488
+ return;
1489
+ }
1490
+ const memo = typeof options.memo === 'function' ? options.memo(event) : options.memo || '';
1491
+ const amount = this.calculateSingleFlowAmount(op, basisPoints, allowedSymbols, 'refund');
1492
+ const result = await this.transferHiveTokens(account, event.transfer.from, amount.amount, amount.asset, memo);
1493
+ await this.dedupeAdd(normalizedStore, trxId);
1494
+ if (options.onRefunded) {
1495
+ await options.onRefunded(result, event);
1496
+ }
1497
+ }
1498
+ catch (error) {
1499
+ if (ignoreZeroAmount && this.isZeroAmountFlowError(error)) {
1500
+ return;
1501
+ }
1502
+ if (options.onError) {
1503
+ await options.onError(error, event);
1504
+ return;
1505
+ }
1506
+ throw error;
1507
+ }
1508
+ };
1509
+ this.onTransfer(account, callback);
1510
+ return {
1511
+ account,
1512
+ stop: () => {
1513
+ this.removeTransferSubscription(account, callback);
1514
+ }
1515
+ };
1516
+ }
1517
+ autoSplitIncomingTransfers(options) {
1518
+ if (!options || !Array.isArray(options.recipients) || options.recipients.length === 0) {
1519
+ throw new Error('autoSplitIncomingTransfers requires at least one recipient');
1520
+ }
1521
+ const defaultMemo = options.memo;
1522
+ return this.autoRouteIncomingTransfers({
1523
+ account: options.account,
1524
+ routes: options.recipients.map((recipient, index) => ({
1525
+ to: recipient.account,
1526
+ percentage: recipient.percentage,
1527
+ percent: recipient.percent,
1528
+ basisPoints: recipient.basisPoints,
1529
+ memo: recipient.memo || (typeof defaultMemo === 'function'
1530
+ ? (event) => defaultMemo(event, { account: recipient.account }, index)
1531
+ : defaultMemo)
1532
+ })),
1533
+ allowedSymbols: options.allowedSymbols,
1534
+ dedupeStore: options.dedupeStore,
1535
+ ignoreZeroAmount: options.ignoreZeroAmount,
1536
+ onRouted: async (results, event, plan) => {
1537
+ if (options.onSplit) {
1538
+ await options.onSplit(results, event, plan);
1539
+ }
1540
+ },
1541
+ onError: options.onError
1542
+ });
1543
+ }
1544
+ planIncomingTransferRoutes(transfer, options) {
1545
+ if (!options || !Array.isArray(options.routes) || options.routes.length === 0) {
1546
+ throw new Error('planIncomingTransferRoutes requires at least one route');
1547
+ }
1548
+ const event = this.normalizeTransferPreviewInput(transfer);
1549
+ const allowedSymbols = options.allowedSymbols || ['HIVE', 'HBD'];
1550
+ return this.buildPlannedIncomingTransferRoutes(event, options.routes, allowedSymbols, options.memo);
1551
+ }
1552
+ autoRouteIncomingTransfers(options) {
1553
+ if (!options || !Array.isArray(options.routes) || options.routes.length === 0) {
1554
+ throw new Error('autoRouteIncomingTransfers requires at least one route');
1555
+ }
1556
+ const account = this.resolveAccount(options.account);
1557
+ const normalizedStore = this.normalizeDedupeStore(options.dedupeStore);
1558
+ const allowedSymbols = options.allowedSymbols || ['HIVE', 'HBD'];
1559
+ const ignoreZeroAmount = options.ignoreZeroAmount !== false;
1560
+ const callback = async (op, blockNumber, blockId, prevBlockId, trxId, blockTime) => {
1561
+ const event = this.createTransferEvent(op, blockNumber, blockId, prevBlockId, trxId, blockTime);
1562
+ try {
1563
+ if (await this.dedupeHas(normalizedStore, trxId)) {
1564
+ return;
1565
+ }
1566
+ const plan = this.buildPlannedIncomingTransferRoutes(event, options.routes, allowedSymbols, options.memo);
1567
+ const results = await this.executePlannedFlowRoutes(account, plan.routes, ignoreZeroAmount);
1568
+ await this.dedupeAdd(normalizedStore, trxId);
1569
+ if (options.onRouted) {
1570
+ await options.onRouted(results, event, plan.routes);
1571
+ }
1572
+ }
1573
+ catch (error) {
1574
+ if (ignoreZeroAmount && this.isZeroAmountFlowError(error)) {
1575
+ return;
1576
+ }
1577
+ if (options.onError) {
1578
+ await options.onError(error, event);
1579
+ return;
1580
+ }
1581
+ throw error;
1582
+ }
1583
+ };
1584
+ this.onTransfer(account, callback);
1585
+ return {
1586
+ account,
1587
+ stop: () => {
1588
+ this.removeTransferSubscription(account, callback);
1589
+ }
1590
+ };
1591
+ }
907
1592
  transferHiveTokensMultiple(from, accounts = [], amount = '0', symbol, memo = '') {
908
1593
  return utils_2.Utils.transferHiveTokensMultiple(this.client, this.config, from, accounts, amount, symbol, memo);
909
1594
  }
@@ -946,6 +1631,9 @@ class Streamer {
946
1631
  transferHiveEngineTokens(from, to, symbol, quantity, memo = '') {
947
1632
  return utils_2.Utils.transferHiveEngineTokens(this.client, this.config, from, to, quantity, symbol, memo);
948
1633
  }
1634
+ burnHiveEngineTokens(from, symbol, quantity, memo = '') {
1635
+ return utils_2.Utils.burnHiveEngineTokens(this.client, this.config, from, symbol, quantity, memo);
1636
+ }
949
1637
  transferHiveEngineTokensMultiple(from, accounts = [], symbol, memo = '', amount = '0') {
950
1638
  return utils_2.Utils.transferHiveEngineTokensMultiple(this.client, this.config, from, accounts, symbol, memo, amount);
951
1639
  }
@@ -1048,8 +1736,16 @@ class Streamer {
1048
1736
  }
1049
1737
  }
1050
1738
  // Add method to remove specific subscriptions
1051
- removeTransferSubscription(account) {
1052
- this.transferSubscriptions = this.transferSubscriptions.filter(sub => sub.account !== account);
1739
+ removeTransferSubscription(account, callback) {
1740
+ this.transferSubscriptions = this.transferSubscriptions.filter((sub) => {
1741
+ if (sub.account !== account) {
1742
+ return true;
1743
+ }
1744
+ if (!callback) {
1745
+ return false;
1746
+ }
1747
+ return sub.callback !== callback;
1748
+ });
1053
1749
  }
1054
1750
  removeCustomJsonIdSubscription(id) {
1055
1751
  this.customJsonIdSubscriptions = this.customJsonIdSubscriptions.filter(sub => sub.id !== id);