From fd040bafabdd12f5932f9e3b70862c9c6b2b2730 Mon Sep 17 00:00:00 2001
From: mtyszczak <mateusz.tyszczak@gmail.com>
Date: Fri, 3 Jan 2025 12:56:16 +0100
Subject: [PATCH] Add feed price collector

---
 package.json                                  |  2 +-
 pnpm-lock.yaml                                | 10 ++---
 src/bot.ts                                    |  2 +-
 .../classifiers/feed-price-classifier.ts      | 14 +++++++
 src/chain-observers/classifiers/index.ts      |  1 +
 .../jsonrpc/feed-price-collector.ts           | 42 +++++++++++++++++++
 .../factories/jsonrpc/factory-data.ts         |  6 ++-
 src/wax/index.ts                              | 21 ++++++++--
 8 files changed, 85 insertions(+), 13 deletions(-)
 create mode 100644 src/chain-observers/classifiers/feed-price-classifier.ts
 create mode 100644 src/chain-observers/collectors/jsonrpc/feed-price-collector.ts

diff --git a/package.json b/package.json
index 0818c8c..bd61d05 100644
--- a/package.json
+++ b/package.json
@@ -66,7 +66,7 @@
   },
   "dependencies": {
     "@hiveio/beekeeper": "1.27.6-rc4",
-    "@hiveio/wax": "1.27.6-rc6"
+    "@hiveio/wax": "1.27.6-rc6-stable.241219120852"
   },
   "repository": {
     "type": "git",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index a155b73..ebf8bd0 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -12,8 +12,8 @@ importers:
         specifier: 1.27.6-rc4
         version: 1.27.6-rc4
       '@hiveio/wax':
-        specifier: 1.27.6-rc6
-        version: 1.27.6-rc6
+        specifier: 1.27.6-rc6-stable.241219120852
+        version: 1.27.6-rc6-stable.241219120852
     devDependencies:
       '@eslint/compat':
         specifier: ^1.2.2
@@ -165,8 +165,8 @@ packages:
     resolution: {integrity: sha1-mj3k3qAgTdIrc6IxzC5VOprhWZs=, tarball: https://gitlab.syncad.com/api/v4/projects/198/packages/npm/@hiveio/beekeeper/-/@hiveio/beekeeper-1.27.6-rc4.tgz}
     engines: {node: '>= 18'}
 
-  '@hiveio/wax@1.27.6-rc6':
-    resolution: {integrity: sha1-zIoajnS/Ct89scl0F/b1jtpk6hs=, tarball: https://gitlab.syncad.com/api/v4/projects/419/packages/npm/@hiveio/wax/-/@hiveio/wax-1.27.6-rc6.tgz}
+  '@hiveio/wax@1.27.6-rc6-stable.241219120852':
+    resolution: {integrity: sha1-4uoDcargMqnEY0eoYJCcyCcbHeM=, tarball: https://gitlab.syncad.com/api/v4/projects/419/packages/npm/@hiveio/wax/-/@hiveio/wax-1.27.6-rc6-stable.241219120852.tgz}
     engines: {node: '>= 18'}
 
   '@humanfs/core@0.19.1':
@@ -2646,7 +2646,7 @@ snapshots:
 
   '@hiveio/beekeeper@1.27.6-rc4': {}
 
-  '@hiveio/wax@1.27.6-rc6':
+  '@hiveio/wax@1.27.6-rc6-stable.241219120852':
     dependencies:
       '@hiveio/beekeeper': 1.27.6-rc4
       class-transformer: 0.5.1
diff --git a/src/bot.ts b/src/bot.ts
index db4aef0..650af8b 100644
--- a/src/bot.ts
+++ b/src/bot.ts
@@ -45,7 +45,7 @@ export const DEFAULT_BLOCK_INTERVAL_TIMEOUT_MS = 2000;
 export class WorkerBee implements IWorkerBee {
   public readonly configuration: IStartConfiguration;
 
-  public chain?: TWaxExtended<typeof WaxExtendTypes>;
+  public chain?: TWaxExtended<WaxExtendTypes>;
 
   private wallet?: IBeekeeperUnlockedWallet;
 
diff --git a/src/chain-observers/classifiers/feed-price-classifier.ts b/src/chain-observers/classifiers/feed-price-classifier.ts
new file mode 100644
index 0000000..45f40a5
--- /dev/null
+++ b/src/chain-observers/classifiers/feed-price-classifier.ts
@@ -0,0 +1,14 @@
+import { price } from "@hiveio/wax";
+import { CollectorClassifierBase } from "./collector-classifier-base";
+
+export interface IFeedPriceData {
+  currentMedianHistory: price;
+  marketMedianHistory: price;
+  currentMinHistory: price;
+  currentMaxHistory: price;
+  priceHistory: Iterable<price>;
+}
+
+export class FeedPriceClassifier extends CollectorClassifierBase {
+  public type!: IFeedPriceData;
+}
diff --git a/src/chain-observers/classifiers/index.ts b/src/chain-observers/classifiers/index.ts
index d70374d..4e4d01a 100644
--- a/src/chain-observers/classifiers/index.ts
+++ b/src/chain-observers/classifiers/index.ts
@@ -5,3 +5,4 @@ export { AccountClassifier } from "./account-classifier";
 export { RcAccountClassifier } from "./rc-account-classifier";
 export { ImpactedAccountClassifier } from "./impacted-account-classifier";
 export { OperationClassifier } from "./operation-classifier";
+export { FeedPriceClassifier } from "./feed-price-classifier";
diff --git a/src/chain-observers/collectors/jsonrpc/feed-price-collector.ts b/src/chain-observers/collectors/jsonrpc/feed-price-collector.ts
new file mode 100644
index 0000000..022f5a2
--- /dev/null
+++ b/src/chain-observers/collectors/jsonrpc/feed-price-collector.ts
@@ -0,0 +1,42 @@
+import { DynamicGlobalPropertiesClassifier } from "../../classifiers";
+import { IFeedPriceData } from "../../classifiers/feed-price-classifier";
+import { DataEvaluationContext } from "../../factories/data-evaluation-context";
+import { CollectorBase, TAvailableClassifiers } from "../collector-base";
+
+const isDivisibleByInRange = (by: number, start: number, end: number) => {
+  // Normalize start to the next multiple of *by*
+  const firstMultiple = Math.ceil(start / by) * by;
+
+  return firstMultiple <= end;
+};
+
+export class FeedPriceCollector extends CollectorBase {
+  private cachedFeedHistoryData: IFeedPriceData | undefined;
+
+  private previouslyCheckedBlockNumber = 0;
+
+  public async fetchData(data: DataEvaluationContext) {
+    const { headBlockNumber } = await data.get(DynamicGlobalPropertiesClassifier);
+
+    // Update feed price history every hour (HIVE_FEED_INTERVAL_BLOCKS) or when there is no cached data
+    if ( this.cachedFeedHistoryData === undefined
+      || isDivisibleByInRange(Number.parseInt(this.worker.chain!.config["HIVE_FEED_INTERVAL_BLOCKS"]), headBlockNumber, this.previouslyCheckedBlockNumber)
+    ) {
+      const feedHistoryData = await this.worker.chain!.api.database_api.get_feed_history({});
+
+      this.cachedFeedHistoryData = {
+        currentMedianHistory: feedHistoryData.current_median_history,
+        marketMedianHistory: feedHistoryData.market_median_history,
+        currentMinHistory: feedHistoryData.current_min_history,
+        currentMaxHistory: feedHistoryData.current_max_history,
+        priceHistory: feedHistoryData.price_history
+      };
+    }
+
+    this.previouslyCheckedBlockNumber = headBlockNumber;
+
+    return {
+      FeedPriceClassifier: this.cachedFeedHistoryData
+    } satisfies Partial<TAvailableClassifiers>;
+  };
+}
diff --git a/src/chain-observers/factories/jsonrpc/factory-data.ts b/src/chain-observers/factories/jsonrpc/factory-data.ts
index 9d6ddde..e70deac 100644
--- a/src/chain-observers/factories/jsonrpc/factory-data.ts
+++ b/src/chain-observers/factories/jsonrpc/factory-data.ts
@@ -1,7 +1,7 @@
 import type { WorkerBee } from "../../../bot";
 import {
   AccountClassifier, BlockClassifier, BlockHeaderClassifier,
-  DynamicGlobalPropertiesClassifier, ImpactedAccountClassifier, OperationClassifier, RcAccountClassifier
+  DynamicGlobalPropertiesClassifier, FeedPriceClassifier, ImpactedAccountClassifier, OperationClassifier, RcAccountClassifier
 } from "../../classifiers";
 import { IEvaluationContextClass } from "../../classifiers/collector-classifier-base";
 import { CollectorBase } from "../../collectors/collector-base";
@@ -11,6 +11,7 @@ import { OperationCollector } from "../../collectors/common/operation-collector"
 import { AccountCollector } from "../../collectors/jsonrpc/account-collector";
 import { BlockCollector } from "../../collectors/jsonrpc/block-collector";
 import { DynamicGlobalPropertiesCollector } from "../../collectors/jsonrpc/dynamic-global-properties-collector";
+import { FeedPriceCollector } from "../../collectors/jsonrpc/feed-price-collector";
 import { RcAccountCollector } from "../../collectors/jsonrpc/rc-account-collector";
 
 export const JsonRpcFactoryData: (worker: WorkerBee) => Array<[IEvaluationContextClass, CollectorBase]> = (worker: WorkerBee) => ([
@@ -20,5 +21,6 @@ export const JsonRpcFactoryData: (worker: WorkerBee) => Array<[IEvaluationContex
   [AccountClassifier, new AccountCollector(worker)],
   [RcAccountClassifier, new RcAccountCollector(worker)],
   [ImpactedAccountClassifier, new ImpactedAccountCollector(worker)],
-  [OperationClassifier, new OperationCollector(worker)]
+  [OperationClassifier, new OperationCollector(worker)],
+  [FeedPriceClassifier, new FeedPriceCollector(worker)]
 ]);
diff --git a/src/wax/index.ts b/src/wax/index.ts
index c04a740..93558f9 100644
--- a/src/wax/index.ts
+++ b/src/wax/index.ts
@@ -1,11 +1,24 @@
-import { createHiveChain, IHiveChainInterface, IWaxOptionsChain, TWaxExtended } from "@hiveio/wax";
+import { createHiveChain, IHiveChainInterface, IWaxOptionsChain, price, TWaxExtended } from "@hiveio/wax";
 
-export const WaxExtendTypes = {};
+export type WaxExtendTypes = {
+  database_api: {
+    get_feed_history: {
+      params: {},
+      result: {
+        current_median_history: price;
+        market_median_history: price;
+        current_min_history: price;
+        current_max_history: price;
+        price_history: price[];
+      }
+    }
+  }
+};
 
-export const getWax = async(explicitHiveChain?: IHiveChainInterface, options?: Partial<IWaxOptionsChain>): Promise<TWaxExtended<typeof WaxExtendTypes>> => {
+export const getWax = async(explicitHiveChain?: IHiveChainInterface, options?: Partial<IWaxOptionsChain>): Promise<TWaxExtended<WaxExtendTypes>> => {
 
   if(explicitHiveChain === undefined)
     explicitHiveChain = await createHiveChain(options);
 
-  return explicitHiveChain.extend(WaxExtendTypes);
+  return explicitHiveChain.extend<WaxExtendTypes>();
 };
-- 
GitLab