@gearbox-protocol/sdk 13.6.0-apy-plugin.3 → 13.6.0-apy-plugin.5

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.
@@ -31,15 +31,40 @@ var import_apy_parser = require("./apy-parser.js");
31
31
  var import_constants = require("./constants.js");
32
32
  var import_pool_apy_utils = require("./pool-apy-utils.js");
33
33
  const MAP_LABEL = "pools7DAgo";
34
+ const PLUGIN_KEY = "ApyPlugin";
34
35
  class ApyPlugin extends import_sdk.BasePlugin {
36
+ #timerInterval;
35
37
  #apyUrl;
36
38
  #cacheTtlMs;
37
39
  #pools7DAgo;
38
40
  #apySnapshot;
39
- constructor(loadOnAttach = false, options) {
41
+ /**
42
+ * Default timer options
43
+ * @see PluginTimerOptions
44
+ */
45
+ #defaultTimerOptions;
46
+ /**
47
+ * When `true`, the timer is started eagerly during the `attach` phase
48
+ * rather than waiting for an explicit `load` call.
49
+ **/
50
+ startTimerOnAttach;
51
+ constructor(loadOnAttach = false, startTimerOnAttach = false, options) {
40
52
  super(loadOnAttach);
53
+ this.startTimerOnAttach = startTimerOnAttach;
41
54
  this.#apyUrl = options?.apyUrl ?? import_constants.APY_STATE_CACHE_URL;
42
55
  this.#cacheTtlMs = options?.cacheTtlMs ?? import_constants.DEFAULT_APY_INTERVAL_MS;
56
+ this.#defaultTimerOptions = options?.timer ?? {
57
+ refreshPools7DAgoOnTick: false,
58
+ intervalMs: import_constants.DEFAULT_APY_INTERVAL_MS,
59
+ onChange: () => {
60
+ }
61
+ };
62
+ }
63
+ async attach() {
64
+ await super.attach();
65
+ if (this.startTimerOnAttach) {
66
+ this.startTimer();
67
+ }
43
68
  }
44
69
  // ---------------------------------------------------------------------------
45
70
  // Load — single entry point for all data (on-chain + state-cache)
@@ -148,8 +173,9 @@ class ApyPlugin extends import_sdk.BasePlugin {
148
173
  for (const market of markets) {
149
174
  const pool = market.pool.pool;
150
175
  const poolAddr = pool.address.toLowerCase();
176
+ const underlyingLc = pool.underlying.toLowerCase();
151
177
  const depositAPY = (0, import_formatter.rayToNumber)(pool.supplyRate) * Number(import_sdk.PERCENTAGE_DECIMALS);
152
- const underlyingAPY = apy.apyList?.[pool.underlying] ?? 0;
178
+ const underlyingAPY = apy.apyList?.[underlyingLc] ?? apy.apyList?.[pool.underlying] ?? 0;
153
179
  const lookupAddresses = this.#getExtraAPYLookupAddresses(poolAddr);
154
180
  const extraAPY = (0, import_pool_apy_utils.getPoolExtraAPY)(lookupAddresses, apy.poolExtraAPYList);
155
181
  const currentExternalList = apy.poolExternalAPYList?.[poolAddr];
@@ -181,10 +207,48 @@ class ApyPlugin extends import_sdk.BasePlugin {
181
207
  return { data, data7DAgo, pointsBase: poolPointsBase, points };
182
208
  }
183
209
  // ---------------------------------------------------------------------------
210
+ // Periodic full refresh
211
+ // ---------------------------------------------------------------------------
212
+ /**
213
+ * Starts a periodic timer that performs a full plugin state refresh.
214
+ * Only one timer can be active; calling again is a no-op.
215
+ * @returns Cleanup function that stops the timer.
216
+ */
217
+ startTimer(opts) {
218
+ if (this.#timerInterval) {
219
+ this.#logger?.debug("plugin timer already running");
220
+ return () => this.stopTimer();
221
+ }
222
+ const intervalMs = opts?.intervalMs ?? this.#defaultTimerOptions.intervalMs;
223
+ this.#logger?.debug(`starting plugin timer (interval: ${intervalMs}ms)`);
224
+ this.#timerInterval = setInterval(async () => {
225
+ try {
226
+ const prevTimestamp = this.#apySnapshot?.timestamp;
227
+ await this.load(true, {
228
+ loadPools7DAgo: opts?.refreshPools7DAgoOnTick ?? this.#defaultTimerOptions.refreshPools7DAgoOnTick
229
+ });
230
+ if (this.#apySnapshot?.timestamp !== prevTimestamp) {
231
+ opts?.onChange?.() ?? this.#defaultTimerOptions.onChange?.();
232
+ await this.sdk.triggerPluginUpdate(PLUGIN_KEY);
233
+ }
234
+ } catch (e) {
235
+ this.#logger?.error(e, "periodic refresh failed");
236
+ }
237
+ }, intervalMs);
238
+ return () => this.stopTimer();
239
+ }
240
+ stopTimer() {
241
+ if (this.#timerInterval) {
242
+ clearInterval(this.#timerInterval);
243
+ this.#timerInterval = void 0;
244
+ this.#logger?.debug("plugin timer stopped");
245
+ }
246
+ }
247
+ // ---------------------------------------------------------------------------
184
248
  // Plugin lifecycle
185
249
  // ---------------------------------------------------------------------------
186
250
  async syncState() {
187
- await this.load();
251
+ await this.load(true);
188
252
  }
189
253
  stateHuman(_) {
190
254
  return this.pools7DAgo.values().flatMap((p) => ({
@@ -110,6 +110,17 @@ class GearboxSDK extends import_base.ChainContractsRegister {
110
110
  * @see {@link SDKHooks} for available event names.
111
111
  **/
112
112
  removeHook = this.#hooks.removeHook.bind(this.#hooks);
113
+ /**
114
+ * Triggers the `pluginUpdate` hook.
115
+ *
116
+ * Intended to be called by plugins when they update their internal state
117
+ * outside of the normal `syncState`/`rehydrate` cycle (e.g. via an
118
+ * internal timer). Frontend listeners registered with
119
+ * `sdk.addHook("pluginUpdate", …)` will be notified.
120
+ **/
121
+ async triggerPluginUpdate(plugin) {
122
+ await this.#hooks.triggerHooks("pluginUpdate", { plugin });
123
+ }
113
124
  /**
114
125
  * Creates and initialises a new SDK instance by reading live on-chain state.
115
126
  *
@@ -21,15 +21,40 @@ import {
21
21
  getPoolExtraAPY
22
22
  } from "./pool-apy-utils.js";
23
23
  const MAP_LABEL = "pools7DAgo";
24
+ const PLUGIN_KEY = "ApyPlugin";
24
25
  class ApyPlugin extends BasePlugin {
26
+ #timerInterval;
25
27
  #apyUrl;
26
28
  #cacheTtlMs;
27
29
  #pools7DAgo;
28
30
  #apySnapshot;
29
- constructor(loadOnAttach = false, options) {
31
+ /**
32
+ * Default timer options
33
+ * @see PluginTimerOptions
34
+ */
35
+ #defaultTimerOptions;
36
+ /**
37
+ * When `true`, the timer is started eagerly during the `attach` phase
38
+ * rather than waiting for an explicit `load` call.
39
+ **/
40
+ startTimerOnAttach;
41
+ constructor(loadOnAttach = false, startTimerOnAttach = false, options) {
30
42
  super(loadOnAttach);
43
+ this.startTimerOnAttach = startTimerOnAttach;
31
44
  this.#apyUrl = options?.apyUrl ?? APY_STATE_CACHE_URL;
32
45
  this.#cacheTtlMs = options?.cacheTtlMs ?? DEFAULT_APY_INTERVAL_MS;
46
+ this.#defaultTimerOptions = options?.timer ?? {
47
+ refreshPools7DAgoOnTick: false,
48
+ intervalMs: DEFAULT_APY_INTERVAL_MS,
49
+ onChange: () => {
50
+ }
51
+ };
52
+ }
53
+ async attach() {
54
+ await super.attach();
55
+ if (this.startTimerOnAttach) {
56
+ this.startTimer();
57
+ }
33
58
  }
34
59
  // ---------------------------------------------------------------------------
35
60
  // Load — single entry point for all data (on-chain + state-cache)
@@ -138,8 +163,9 @@ class ApyPlugin extends BasePlugin {
138
163
  for (const market of markets) {
139
164
  const pool = market.pool.pool;
140
165
  const poolAddr = pool.address.toLowerCase();
166
+ const underlyingLc = pool.underlying.toLowerCase();
141
167
  const depositAPY = rayToNumber(pool.supplyRate) * Number(PERCENTAGE_DECIMALS);
142
- const underlyingAPY = apy.apyList?.[pool.underlying] ?? 0;
168
+ const underlyingAPY = apy.apyList?.[underlyingLc] ?? apy.apyList?.[pool.underlying] ?? 0;
143
169
  const lookupAddresses = this.#getExtraAPYLookupAddresses(poolAddr);
144
170
  const extraAPY = getPoolExtraAPY(lookupAddresses, apy.poolExtraAPYList);
145
171
  const currentExternalList = apy.poolExternalAPYList?.[poolAddr];
@@ -171,10 +197,48 @@ class ApyPlugin extends BasePlugin {
171
197
  return { data, data7DAgo, pointsBase: poolPointsBase, points };
172
198
  }
173
199
  // ---------------------------------------------------------------------------
200
+ // Periodic full refresh
201
+ // ---------------------------------------------------------------------------
202
+ /**
203
+ * Starts a periodic timer that performs a full plugin state refresh.
204
+ * Only one timer can be active; calling again is a no-op.
205
+ * @returns Cleanup function that stops the timer.
206
+ */
207
+ startTimer(opts) {
208
+ if (this.#timerInterval) {
209
+ this.#logger?.debug("plugin timer already running");
210
+ return () => this.stopTimer();
211
+ }
212
+ const intervalMs = opts?.intervalMs ?? this.#defaultTimerOptions.intervalMs;
213
+ this.#logger?.debug(`starting plugin timer (interval: ${intervalMs}ms)`);
214
+ this.#timerInterval = setInterval(async () => {
215
+ try {
216
+ const prevTimestamp = this.#apySnapshot?.timestamp;
217
+ await this.load(true, {
218
+ loadPools7DAgo: opts?.refreshPools7DAgoOnTick ?? this.#defaultTimerOptions.refreshPools7DAgoOnTick
219
+ });
220
+ if (this.#apySnapshot?.timestamp !== prevTimestamp) {
221
+ opts?.onChange?.() ?? this.#defaultTimerOptions.onChange?.();
222
+ await this.sdk.triggerPluginUpdate(PLUGIN_KEY);
223
+ }
224
+ } catch (e) {
225
+ this.#logger?.error(e, "periodic refresh failed");
226
+ }
227
+ }, intervalMs);
228
+ return () => this.stopTimer();
229
+ }
230
+ stopTimer() {
231
+ if (this.#timerInterval) {
232
+ clearInterval(this.#timerInterval);
233
+ this.#timerInterval = void 0;
234
+ this.#logger?.debug("plugin timer stopped");
235
+ }
236
+ }
237
+ // ---------------------------------------------------------------------------
174
238
  // Plugin lifecycle
175
239
  // ---------------------------------------------------------------------------
176
240
  async syncState() {
177
- await this.load();
241
+ await this.load(true);
178
242
  }
179
243
  stateHuman(_) {
180
244
  return this.pools7DAgo.values().flatMap((p) => ({
@@ -101,6 +101,17 @@ class GearboxSDK extends ChainContractsRegister {
101
101
  * @see {@link SDKHooks} for available event names.
102
102
  **/
103
103
  removeHook = this.#hooks.removeHook.bind(this.#hooks);
104
+ /**
105
+ * Triggers the `pluginUpdate` hook.
106
+ *
107
+ * Intended to be called by plugins when they update their internal state
108
+ * outside of the normal `syncState`/`rehydrate` cycle (e.g. via an
109
+ * internal timer). Frontend listeners registered with
110
+ * `sdk.addHook("pluginUpdate", …)` will be notified.
111
+ **/
112
+ async triggerPluginUpdate(plugin) {
113
+ await this.#hooks.triggerHooks("pluginUpdate", { plugin });
114
+ }
104
115
  /**
105
116
  * Creates and initialises a new SDK instance by reading live on-chain state.
106
117
  *
@@ -9,8 +9,9 @@ export interface ApyPluginState {
9
9
  }
10
10
  export interface ApyPluginConstructorOptions {
11
11
  apyUrl?: string;
12
- /** TTL for the shared HTTP cache in milliseconds (default: 10 minutes, see `DEFAULT_APY_INTERVAL_MS`) */
12
+ /** TTL for the shared HTTP cache in milliseconds (default: same as timer interval) */
13
13
  cacheTtlMs?: number;
14
+ timer?: PluginTimerOptions;
14
15
  }
15
16
  export interface ApyPluginLoadOptions {
16
17
  /**
@@ -20,9 +21,25 @@ export interface ApyPluginLoadOptions {
20
21
  */
21
22
  loadPools7DAgo?: boolean;
22
23
  }
24
+ export interface PluginTimerOptions {
25
+ /** Polling interval in milliseconds (default: 10 minutes) */
26
+ intervalMs?: number;
27
+ /** Callback fired after each successful refresh */
28
+ onChange?: () => void;
29
+ /**
30
+ * When `true`, each tick also refreshes 7d-ago pool state on-chain.
31
+ */
32
+ refreshPools7DAgoOnTick?: boolean;
33
+ }
23
34
  export declare class ApyPlugin extends BasePlugin<ApyPluginState> implements IGearboxSDKPlugin<ApyPluginState> {
24
35
  #private;
25
- constructor(loadOnAttach?: boolean, options?: ApyPluginConstructorOptions);
36
+ /**
37
+ * When `true`, the timer is started eagerly during the `attach` phase
38
+ * rather than waiting for an explicit `load` call.
39
+ **/
40
+ readonly startTimerOnAttach: boolean;
41
+ constructor(loadOnAttach?: boolean, startTimerOnAttach?: boolean, options?: ApyPluginConstructorOptions);
42
+ attach(): Promise<void>;
26
43
  load(force?: boolean, loadOptions?: ApyPluginLoadOptions): Promise<ApyPluginState>;
27
44
  get loaded(): boolean;
28
45
  /**
@@ -39,6 +56,13 @@ export declare class ApyPlugin extends BasePlugin<ApyPluginState> implements IGe
39
56
  * @throws if plugin is not loaded
40
57
  */
41
58
  getPoolsAPY(): GetPoolsAPYResult;
59
+ /**
60
+ * Starts a periodic timer that performs a full plugin state refresh.
61
+ * Only one timer can be active; calling again is a no-op.
62
+ * @returns Cleanup function that stops the timer.
63
+ */
64
+ startTimer(opts?: PluginTimerOptions): () => void;
65
+ stopTimer(): void;
42
66
  syncState(): Promise<void>;
43
67
  stateHuman(_?: boolean): Pools7DAgoStateHuman[];
44
68
  get state(): ApyPluginState;
@@ -115,6 +115,13 @@ export interface SyncStateOptions {
115
115
  **/
116
116
  ignoreUpdateablePrices?: boolean;
117
117
  }
118
+ /**
119
+ * Payload carried by the `pluginUpdate` hook.
120
+ **/
121
+ export interface PluginUpdateInfo {
122
+ /** Identifier of the plugin that triggered the update (e.g. `"pools7DAgo"`). */
123
+ plugin: string;
124
+ }
118
125
  /**
119
126
  * Hook event map for the SDK lifecycle.
120
127
  *
@@ -123,10 +130,13 @@ export interface SyncStateOptions {
123
130
  *
124
131
  * - `syncState` — fired after {@link GearboxSDK.syncState} completes.
125
132
  * - `rehydrate` — fired after {@link GearboxSDK.rehydrate} completes.
133
+ * - `pluginUpdate` — fired by a plugin when its internal state changes
134
+ * outside of the normal `syncState`/`rehydrate` cycle (e.g. timer tick).
126
135
  **/
127
136
  export type SDKHooks = {
128
137
  syncState: [SyncStateOptions];
129
138
  rehydrate: [SyncStateOptions];
139
+ pluginUpdate: [PluginUpdateInfo];
130
140
  };
131
141
  /**
132
142
  * Main entry point for the Gearbox SDK.
@@ -175,6 +185,15 @@ export declare class GearboxSDK<const Plugins extends PluginsMap = {}> extends C
175
185
  * @see {@link SDKHooks} for available event names.
176
186
  **/
177
187
  removeHook: <K extends keyof SDKHooks>(hookName: K, fn: (...args: SDKHooks[K]) => void | Promise<void>) => void;
188
+ /**
189
+ * Triggers the `pluginUpdate` hook.
190
+ *
191
+ * Intended to be called by plugins when they update their internal state
192
+ * outside of the normal `syncState`/`rehydrate` cycle (e.g. via an
193
+ * internal timer). Frontend listeners registered with
194
+ * `sdk.addHook("pluginUpdate", …)` will be notified.
195
+ **/
196
+ triggerPluginUpdate(plugin: string): Promise<void>;
178
197
  /**
179
198
  * Creates and initialises a new SDK instance by reading live on-chain state.
180
199
  *
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gearbox-protocol/sdk",
3
- "version": "13.6.0-apy-plugin.3",
3
+ "version": "13.6.0-apy-plugin.5",
4
4
  "description": "Gearbox SDK",
5
5
  "license": "MIT",
6
6
  "main": "./dist/cjs/sdk/index.js",