@elizaos/plugin-wallet 2.0.0-beta.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.
Files changed (200) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +64 -0
  3. package/auto-enable.ts +76 -0
  4. package/dist/LpManagementService-BWrQ5-cO.mjs +353 -0
  5. package/dist/MockLpService-D_Apn4Fd.mjs +99 -0
  6. package/dist/aerodrome-CfnESC32.mjs +890 -0
  7. package/dist/chunk-hT5z_Zn9.mjs +35 -0
  8. package/dist/index.d.mts +34727 -0
  9. package/dist/index.mjs +21590 -0
  10. package/dist/lib/server-wallet-trade.d.mts +34 -0
  11. package/dist/lib/server-wallet-trade.mjs +306 -0
  12. package/dist/meteora-BPX39hZo.mjs +22640 -0
  13. package/dist/orca-Bybp1HXO.mjs +249 -0
  14. package/dist/pancakeswp-CkEXlXti.mjs +604 -0
  15. package/dist/plugin-ZO_MTyd0.mjs +529 -0
  16. package/dist/raydium-rfaM9yEf.mjs +539 -0
  17. package/dist/sdk/index.d.mts +32492 -0
  18. package/dist/sdk/index.mjs +6415 -0
  19. package/dist/types-D5252NZk.mjs +487 -0
  20. package/dist/uniswap-CReXgXVN.mjs +573 -0
  21. package/dist/wallet-action.d.mts +6 -0
  22. package/dist/wallet-action.mjs +820 -0
  23. package/package.json +152 -0
  24. package/src/actions/failure-codes.ts +79 -0
  25. package/src/actions/index.ts +1 -0
  26. package/src/analytics/birdeye/actions/wallet-search-address.ts +9 -0
  27. package/src/analytics/birdeye/birdeye-task.ts +175 -0
  28. package/src/analytics/birdeye/birdeye.ts +813 -0
  29. package/src/analytics/birdeye/constants.ts +74 -0
  30. package/src/analytics/birdeye/providers/agent-portfolio-provider.ts +18 -0
  31. package/src/analytics/birdeye/providers/market.ts +227 -0
  32. package/src/analytics/birdeye/providers/portfolio-factory.test.ts +138 -0
  33. package/src/analytics/birdeye/providers/portfolio-factory.ts +252 -0
  34. package/src/analytics/birdeye/providers/trending.ts +365 -0
  35. package/src/analytics/birdeye/providers/wallet.ts +14 -0
  36. package/src/analytics/birdeye/search-category.test.ts +207 -0
  37. package/src/analytics/birdeye/search-category.ts +506 -0
  38. package/src/analytics/birdeye/service.ts +992 -0
  39. package/src/analytics/birdeye/tasks/birdeye.ts +232 -0
  40. package/src/analytics/birdeye/types/api/common.ts +305 -0
  41. package/src/analytics/birdeye/types/api/defi.ts +220 -0
  42. package/src/analytics/birdeye/types/api/pair.ts +200 -0
  43. package/src/analytics/birdeye/types/api/search.ts +86 -0
  44. package/src/analytics/birdeye/types/api/token.ts +635 -0
  45. package/src/analytics/birdeye/types/api/trader.ts +76 -0
  46. package/src/analytics/birdeye/types/api/wallet.ts +181 -0
  47. package/src/analytics/birdeye/types/shared.ts +106 -0
  48. package/src/analytics/birdeye/utils.ts +700 -0
  49. package/src/analytics/dexscreener/errors.ts +28 -0
  50. package/src/analytics/dexscreener/index.ts +3 -0
  51. package/src/analytics/dexscreener/search-category.test.ts +49 -0
  52. package/src/analytics/dexscreener/search-category.ts +42 -0
  53. package/src/analytics/dexscreener/service.ts +595 -0
  54. package/src/analytics/dexscreener/types.ts +128 -0
  55. package/src/analytics/lpinfo/index.d.ts +7 -0
  56. package/src/analytics/lpinfo/index.ts +52 -0
  57. package/src/analytics/lpinfo/kamino/README.md +102 -0
  58. package/src/analytics/lpinfo/kamino/index.ts +24 -0
  59. package/src/analytics/lpinfo/kamino/providers/kaminoLiquidityProvider.ts +422 -0
  60. package/src/analytics/lpinfo/kamino/providers/kaminoPoolProvider.ts +365 -0
  61. package/src/analytics/lpinfo/kamino/providers/kaminoProvider.ts +496 -0
  62. package/src/analytics/lpinfo/kamino/services/kaminoLiquidityService.ts +1123 -0
  63. package/src/analytics/lpinfo/kamino/services/kaminoService.ts +758 -0
  64. package/src/analytics/lpinfo/steer/README.md +169 -0
  65. package/src/analytics/lpinfo/steer/index.ts +23 -0
  66. package/src/analytics/lpinfo/steer/providers/steerLiquidityProvider.ts +544 -0
  67. package/src/analytics/lpinfo/steer/services/steerLiquidityService.ts +1690 -0
  68. package/src/analytics/lpinfo/steer/steer-display-types.ts +99 -0
  69. package/src/analytics/news/index.ts +52 -0
  70. package/src/analytics/news/interfaces/types.ts +222 -0
  71. package/src/analytics/news/providers/defiNewsProvider.ts +734 -0
  72. package/src/analytics/news/services/newsDataService.ts +332 -0
  73. package/src/analytics/news/utils/formatters.ts +151 -0
  74. package/src/analytics/token-info/action.ts +240 -0
  75. package/src/analytics/token-info/index.ts +3 -0
  76. package/src/analytics/token-info/params.ts +215 -0
  77. package/src/analytics/token-info/providers.ts +681 -0
  78. package/src/analytics/token-info/service.ts +168 -0
  79. package/src/analytics/token-info/types.ts +74 -0
  80. package/src/audit/audit-log.ts +45 -0
  81. package/src/browser-shim/build-shim.ts +123 -0
  82. package/src/browser-shim/index.ts +5 -0
  83. package/src/browser-shim/shim.template.js +563 -0
  84. package/src/chains/evm/.github/workflows/npm-deploy.yml +112 -0
  85. package/src/chains/evm/LICENSE +21 -0
  86. package/src/chains/evm/README.md +106 -0
  87. package/src/chains/evm/actions/helpers.ts +147 -0
  88. package/src/chains/evm/actions/swap.ts +839 -0
  89. package/src/chains/evm/actions/transfer.ts +254 -0
  90. package/src/chains/evm/biome.json +61 -0
  91. package/src/chains/evm/bridge-router.ts +660 -0
  92. package/src/chains/evm/build.ts +89 -0
  93. package/src/chains/evm/chain-handler.ts +416 -0
  94. package/src/chains/evm/constants.ts +23 -0
  95. package/src/chains/evm/contracts/artifacts/OZGovernor.json +1707 -0
  96. package/src/chains/evm/contracts/artifacts/TimelockController.json +1007 -0
  97. package/src/chains/evm/contracts/artifacts/VoteToken.json +895 -0
  98. package/src/chains/evm/dex/aerodrome/index.ts +34 -0
  99. package/src/chains/evm/dex/aerodrome/services/AerodromeLpService.ts +558 -0
  100. package/src/chains/evm/dex/aerodrome/types.ts +318 -0
  101. package/src/chains/evm/dex/pancakeswp/index.ts +35 -0
  102. package/src/chains/evm/dex/pancakeswp/services/PancakeSwapV3LpService.ts +743 -0
  103. package/src/chains/evm/dex/pancakeswp/types.ts +65 -0
  104. package/src/chains/evm/dex/uniswap/index.ts +35 -0
  105. package/src/chains/evm/dex/uniswap/services/UniswapV3LpService.ts +759 -0
  106. package/src/chains/evm/dex/uniswap/types.ts +390 -0
  107. package/src/chains/evm/generated/specs/spec-helpers.ts +73 -0
  108. package/src/chains/evm/generated/specs/specs.ts +151 -0
  109. package/src/chains/evm/gov-router.ts +250 -0
  110. package/src/chains/evm/index.browser.ts +16 -0
  111. package/src/chains/evm/index.ts +31 -0
  112. package/src/chains/evm/prompts.ts +193 -0
  113. package/src/chains/evm/providers/get-balance.ts +123 -0
  114. package/src/chains/evm/providers/wallet.ts +715 -0
  115. package/src/chains/evm/routes/sign.ts +333 -0
  116. package/src/chains/evm/rpc-providers.ts +410 -0
  117. package/src/chains/evm/service.ts +140 -0
  118. package/src/chains/evm/templates/index.ts +10 -0
  119. package/src/chains/evm/types/index.ts +432 -0
  120. package/src/chains/evm/vitest.config.ts +18 -0
  121. package/src/chains/registry.ts +668 -0
  122. package/src/chains/solana/README.md +367 -0
  123. package/src/chains/wallet-action.ts +533 -0
  124. package/src/chains/wallet-router.test.ts +296 -0
  125. package/src/contracts.ts +65 -0
  126. package/src/core-augmentation.ts +10 -0
  127. package/src/index.ts +71 -0
  128. package/src/lib/server-wallet-trade.ts +192 -0
  129. package/src/lib/wallet-export-guard.ts +330 -0
  130. package/src/lp/actions/liquidity.ts +827 -0
  131. package/src/lp/e2e/real-token-tests.ts +428 -0
  132. package/src/lp/e2e/scenarios.ts +470 -0
  133. package/src/lp/e2e/test-utils.ts +145 -0
  134. package/src/lp/lp-manager-entry.ts +303 -0
  135. package/src/lp/services/ConcentratedLiquidityService.ts +120 -0
  136. package/src/lp/services/DexInteractionService.ts +226 -0
  137. package/src/lp/services/LpManagementService.test.ts +148 -0
  138. package/src/lp/services/LpManagementService.ts +632 -0
  139. package/src/lp/services/UserLpProfileService.ts +163 -0
  140. package/src/lp/services/VaultService.ts +153 -0
  141. package/src/lp/services/YieldOptimizationService.ts +344 -0
  142. package/src/lp/services/__tests__/MockLpService.ts +146 -0
  143. package/src/lp/tasks/LpAutoRebalanceTask.ts +117 -0
  144. package/src/lp/tasks/__tests__/LpAutoRebalanceTask.test.ts +370 -0
  145. package/src/lp/types.ts +582 -0
  146. package/src/lp/utils/solanaClient.ts +143 -0
  147. package/src/plugin.ts +125 -0
  148. package/src/policy/policy.ts +19 -0
  149. package/src/providers/canonical-provider.ts +27 -0
  150. package/src/providers/unified-wallet-provider.ts +79 -0
  151. package/src/register-routes.ts +11 -0
  152. package/src/routes/plugin.ts +47 -0
  153. package/src/routes/wallet-market-overview-route.ts +869 -0
  154. package/src/sdk/abi.ts +258 -0
  155. package/src/sdk/bridge/abis.ts +126 -0
  156. package/src/sdk/bridge/client.ts +518 -0
  157. package/src/sdk/bridge/index.ts +56 -0
  158. package/src/sdk/bridge/solana.ts +604 -0
  159. package/src/sdk/bridge/types.ts +202 -0
  160. package/src/sdk/convenience.ts +347 -0
  161. package/src/sdk/escrow/MutualStakeEscrow.ts +480 -0
  162. package/src/sdk/escrow/types.ts +64 -0
  163. package/src/sdk/escrow/verifiers.ts +73 -0
  164. package/src/sdk/identity/erc8004.ts +692 -0
  165. package/src/sdk/identity/reputation.ts +449 -0
  166. package/src/sdk/identity/uaid.ts +497 -0
  167. package/src/sdk/identity/validation.ts +372 -0
  168. package/src/sdk/index.ts +763 -0
  169. package/src/sdk/policy/SpendingPolicy.ts +260 -0
  170. package/src/sdk/policy/UptoBillingPolicy.ts +320 -0
  171. package/src/sdk/router/PaymentRouter.ts +215 -0
  172. package/src/sdk/router/index.ts +8 -0
  173. package/src/sdk/swap/SwapModule.ts +310 -0
  174. package/src/sdk/swap/abi.ts +117 -0
  175. package/src/sdk/swap/index.ts +34 -0
  176. package/src/sdk/swap/types.ts +135 -0
  177. package/src/sdk/tokens/decimals.ts +140 -0
  178. package/src/sdk/tokens/registry.ts +911 -0
  179. package/src/sdk/tokens/solana.ts +419 -0
  180. package/src/sdk/tokens/transfers.ts +327 -0
  181. package/src/sdk/types.ts +158 -0
  182. package/src/sdk/wallet-core.ts +115 -0
  183. package/src/sdk/x402/budget.ts +168 -0
  184. package/src/sdk/x402/chains/abstract/index.ts +280 -0
  185. package/src/sdk/x402/client.ts +320 -0
  186. package/src/sdk/x402/index.ts +46 -0
  187. package/src/sdk/x402/middleware.ts +92 -0
  188. package/src/sdk/x402/multi-asset.ts +144 -0
  189. package/src/sdk/x402/types.ts +156 -0
  190. package/src/services/wallet-backend-service.ts +328 -0
  191. package/src/types/wallet-router.ts +227 -0
  192. package/src/utils/intent-trajectory.ts +106 -0
  193. package/src/wallet/backend.ts +62 -0
  194. package/src/wallet/errors.ts +49 -0
  195. package/src/wallet/index.ts +27 -0
  196. package/src/wallet/local-eoa-backend.ts +201 -0
  197. package/src/wallet/pending.ts +60 -0
  198. package/src/wallet/select-backend.ts +47 -0
  199. package/src/wallet/steward-backend.ts +161 -0
  200. package/src/wallet-action.ts +1 -0
@@ -0,0 +1,992 @@
1
+ // @ts-nocheck — legacy code from absorbed plugins (lp-manager, lpinfo, dexscreener, defi-news, birdeye); strict types pending cleanup
2
+
3
+ import {
4
+ type RouteSpec,
5
+ resolveCloudRoute,
6
+ toRuntimeSettings,
7
+ } from "@elizaos/cloud-routing";
8
+ import {
9
+ type IAgentRuntime,
10
+ Service,
11
+ type ServiceTypeName,
12
+ } from "@elizaos/core";
13
+ import Birdeye from "./birdeye-task";
14
+ import { BIRDEYE_ENDPOINTS, BIRDEYE_SERVICE_NAME } from "./constants";
15
+ import { searchBirdeyeTokens } from "./search-category";
16
+ import type {
17
+ BirdeyeSupportedChain,
18
+ GetCacheTimedOptions,
19
+ IToken,
20
+ } from "./types/shared";
21
+ import { convertToStringParams, extractChain } from "./utils";
22
+
23
+ /** Route spec for {@link resolveCloudRoute} — local X-API-KEY or Eliza Cloud `/apis/birdeye` proxy. */
24
+ export const BIRDEYE_ROUTE_SPEC: RouteSpec = {
25
+ service: "birdeye",
26
+ localKeySetting: "BIRDEYE_API_KEY",
27
+ upstreamBaseUrl: "https://public-api.birdeye.so",
28
+ localKeyAuth: { kind: "header", headerName: "X-API-KEY" },
29
+ };
30
+
31
+ // Cache defaults for backwards compatibility
32
+ const CACHE_DEFAULTS = {
33
+ // Token trade data cache (30 minutes)
34
+ TOKEN_TRADE_DATA_TTL: 30 * 60 * 1000,
35
+ // Token security data cache (30 minutes)
36
+ TOKEN_SECURITY_DATA_TTL: 30 * 60 * 1000,
37
+ };
38
+
39
+ // 'solana' | 'base' | 'ethereum'
40
+ type Chain = string;
41
+
42
+ type CacheWrapper<T> = {
43
+ data: T;
44
+ setAt: number;
45
+ };
46
+ export class BirdeyeService extends Service {
47
+ static serviceType: string = BIRDEYE_SERVICE_NAME;
48
+ capabilityDescription = "BirdEye data access";
49
+
50
+ private readonly access: { baseUrl: string; headers: Record<string, string> };
51
+
52
+ constructor(runtime?: IAgentRuntime) {
53
+ super(runtime);
54
+ if (!this.runtime) {
55
+ throw new Error("BirdeyeService requires a runtime");
56
+ }
57
+ const route = resolveCloudRoute(
58
+ toRuntimeSettings(this.runtime),
59
+ BIRDEYE_ROUTE_SPEC,
60
+ );
61
+ if (route.source === "disabled") {
62
+ throw new Error(
63
+ "BirdeyeService requires BIRDEYE_API_KEY or Eliza Cloud (ELIZAOS_CLOUD_API_KEY + ELIZAOS_CLOUD_ENABLED).",
64
+ );
65
+ }
66
+ this.access = {
67
+ baseUrl: route.baseUrl.replace(/\/+$/, ""),
68
+ headers: route.headers,
69
+ };
70
+ }
71
+
72
+ private birdeyeUrl(path: string): string {
73
+ const suffix = path.replace(/^\/+/, "");
74
+ return `${this.access.baseUrl}/${suffix}`;
75
+ }
76
+
77
+ private getBirdeyeFetchOptions(chain = "solana"): RequestInit {
78
+ return {
79
+ headers: {
80
+ accept: "application/json",
81
+ "x-chain": chain,
82
+ ...this.access.headers,
83
+ },
84
+ };
85
+ }
86
+
87
+ private async fetchBirdeyeJson(
88
+ path: string,
89
+ params: Record<string, unknown> = {},
90
+ options: { headers?: Record<string, string> } = {},
91
+ ) {
92
+ const chain = options.headers?.["x-chain"] ?? "solana";
93
+ const cleanParams = Object.fromEntries(
94
+ Object.entries(params).filter(([, value]) => value !== undefined),
95
+ );
96
+ const query = new URLSearchParams(
97
+ convertToStringParams(cleanParams),
98
+ ).toString();
99
+ const suffix = query ? `${path}?${query}` : path;
100
+ const fetchOptions = this.getBirdeyeFetchOptions(chain);
101
+ const response = await fetch(this.birdeyeUrl(suffix), {
102
+ ...fetchOptions,
103
+ headers: {
104
+ ...(fetchOptions.headers as Record<string, string>),
105
+ ...(options.headers ?? {}),
106
+ },
107
+ });
108
+ if (!response.ok) {
109
+ const errorText = await response.text();
110
+ throw new Error(
111
+ `Birdeye API error ${response.status}: ${errorText || response.statusText}`,
112
+ );
113
+ }
114
+ return response.json();
115
+ }
116
+
117
+ async fetchSearchTokenMarketData(params: Record<string, unknown>) {
118
+ const chain = typeof params.chain === "string" ? params.chain : "solana";
119
+ return this.fetchBirdeyeJson(
120
+ BIRDEYE_ENDPOINTS.search.token_market,
121
+ params,
122
+ {
123
+ headers: { "x-chain": chain },
124
+ },
125
+ );
126
+ }
127
+
128
+ async fetchTokenOverview(
129
+ params: Record<string, unknown>,
130
+ options: { headers?: Record<string, string> } = {},
131
+ ) {
132
+ return this.fetchBirdeyeJson(
133
+ BIRDEYE_ENDPOINTS.token.overview,
134
+ params,
135
+ options,
136
+ );
137
+ }
138
+
139
+ async fetchTokenMarketData(
140
+ params: Record<string, unknown>,
141
+ options: { headers?: Record<string, string> } = {},
142
+ ) {
143
+ return this.fetchBirdeyeJson(
144
+ BIRDEYE_ENDPOINTS.token.market_data,
145
+ params,
146
+ options,
147
+ );
148
+ }
149
+
150
+ async fetchTokenSecurityByAddress(
151
+ params: Record<string, unknown>,
152
+ options: { headers?: Record<string, string> } = {},
153
+ ) {
154
+ return this.fetchBirdeyeJson(
155
+ BIRDEYE_ENDPOINTS.token.security,
156
+ params,
157
+ options,
158
+ );
159
+ }
160
+
161
+ async fetchTokenTradeDataSingle(
162
+ params: Record<string, unknown>,
163
+ options: { headers?: Record<string, string> } = {},
164
+ ) {
165
+ return this.fetchBirdeyeJson(
166
+ BIRDEYE_ENDPOINTS.token.trade_data_single,
167
+ params,
168
+ options,
169
+ );
170
+ }
171
+
172
+ async search(query: string, options: Record<string, unknown> = {}) {
173
+ return searchBirdeyeTokens(
174
+ this.runtime,
175
+ {
176
+ query,
177
+ mode: options.mode as "auto" | "symbol" | "address" | undefined,
178
+ filters: options,
179
+ limit: typeof options.limit === "number" ? options.limit : undefined,
180
+ },
181
+ this,
182
+ );
183
+ }
184
+
185
+ // definitely should take a list of chains
186
+ async getTrending() {
187
+ //console.log('birdeye needs to get trending data');
188
+ return this.runtime.getCache<IToken[]>(`tokens_solana`);
189
+ }
190
+
191
+ private async getTrendingTokensForChain(
192
+ chain: Chain,
193
+ options?: { notOlderThan?: number; total?: number },
194
+ ): Promise<CacheWrapper<IToken[]>> {
195
+ // Validate chain using extractChain
196
+ let validatedChain: BirdeyeSupportedChain;
197
+ try {
198
+ validatedChain = extractChain(undefined, chain);
199
+ } catch (error) {
200
+ this.runtime.logger?.warn(
201
+ `getTrendingTokensForChain: ${error instanceof Error ? error.message : String(error)}`,
202
+ );
203
+ return { data: [], setAt: Date.now() };
204
+ }
205
+
206
+ // Don't allow 'evm' as a trending chain (it's just a placeholder)
207
+ if (validatedChain === "evm") {
208
+ this.runtime.logger?.warn(
209
+ `getTrendingTokensForChain: 'evm' is not a specific chain. Use ethereum, arbitrum, polygon, etc.`,
210
+ );
211
+ return { data: [], setAt: Date.now() };
212
+ }
213
+
214
+ const cacheKey = `tokens_v2_${validatedChain}`;
215
+ const wrapper =
216
+ await this.runtime.getCache<CacheWrapper<IToken[]>>(cacheKey);
217
+
218
+ const freshEnough =
219
+ !!wrapper &&
220
+ (!options?.notOlderThan ||
221
+ Date.now() - wrapper.setAt <= options.notOlderThan);
222
+
223
+ if (freshEnough) {
224
+ this.runtime.logger.debug(
225
+ `birdyeSrv::getTrendingTokensForChain(${chain}) HIT`,
226
+ );
227
+ return wrapper;
228
+ }
229
+ this.runtime.logger.debug(
230
+ `birdyeSrv::getTrendingTokensForChain(${chain}) MISS`,
231
+ );
232
+
233
+ const BATCH_SIZE = 20; // max birdeye allows
234
+ const TOTAL = options?.total ?? 100;
235
+ const OFFSETS = Array.from(
236
+ { length: TOTAL / BATCH_SIZE },
237
+ (_, i) => i * BATCH_SIZE,
238
+ );
239
+
240
+ const birdeyeFetchOptions: RequestInit = this.getBirdeyeFetchOptions(chain);
241
+ // Build all offset requests inline (no inner function)
242
+ const settled = await Promise.allSettled(
243
+ OFFSETS.map(async (offset) => {
244
+ const res = await fetch(
245
+ `${this.birdeyeUrl("defi/token_trending")}?sort_by=rank&sort_type=asc&offset=${offset}&limit=${BATCH_SIZE}`,
246
+ birdeyeFetchOptions,
247
+ );
248
+ const resp = await res.json();
249
+ const data = resp?.data;
250
+
251
+ if (!data?.tokens?.length) return [] as IToken[];
252
+
253
+ const last_updated = new Date((data.updateUnixTime ?? 0) * 1000);
254
+
255
+ interface RawBirdeyeToken {
256
+ address: string;
257
+ decimals?: number;
258
+ liquidity?: number;
259
+ logoURI?: string;
260
+ name?: string;
261
+ symbol: string;
262
+ volume24hUSD?: number;
263
+ rank?: number;
264
+ price?: number;
265
+ price24hChangePercent?: number;
266
+ }
267
+ return (data.tokens as RawBirdeyeToken[]).map((token) => ({
268
+ address: token.address,
269
+ chain,
270
+ provider: "birdeye",
271
+ decimals: token.decimals || 0,
272
+ liquidity: token.liquidity || 0,
273
+ logoURI: token.logoURI || "",
274
+ name: token.name || token.symbol,
275
+ symbol: token.symbol,
276
+ marketcap: 0,
277
+ volume24hUSD: token.volume24hUSD || 0,
278
+ rank: token.rank || 0,
279
+ price: token.price || 0,
280
+ price24hChangePercent: token.price24hChangePercent || 0,
281
+ last_updated,
282
+ })) as IToken[];
283
+ }),
284
+ );
285
+
286
+ const fetched = settled
287
+ .filter(
288
+ (r): r is PromiseFulfilledResult<IToken[]> => r.status === "fulfilled",
289
+ )
290
+ .flatMap((r) => r.value);
291
+
292
+ const byKey = new Map<string, IToken>();
293
+ for (const t of fetched) {
294
+ const key = t.address?.toLowerCase?.() || `${t.chain}:${t.rank}`;
295
+ if (!byKey.has(key)) {
296
+ byKey.set(key, t);
297
+ }
298
+ }
299
+ const merged = Array.from(byKey.values());
300
+
301
+ // Save to cache (ignore cache errors; don’t block the return path)
302
+ const output = { data: merged, setAt: Date.now() };
303
+ try {
304
+ await this.runtime.setCache<CacheWrapper<IToken[]>>(cacheKey, output);
305
+ } catch (e) {
306
+ this.runtime.logger?.warn(
307
+ `setCache failed for ${chain}: ${e instanceof Error ? e.message : String(e)}`,
308
+ );
309
+ }
310
+ return output;
311
+ }
312
+
313
+ // options.depth 5
314
+ // options.notOlderThanMsecs
315
+ async getTrendingTokens(
316
+ chains: Chain[],
317
+ options?: { notOlderThan?: number },
318
+ ): Promise<Record<Chain, IToken[]>> {
319
+ try {
320
+ const results = await Promise.all(
321
+ chains.map((chain) => this.getTrendingTokensForChain(chain, options)),
322
+ );
323
+
324
+ // key output per chain - unwrap CacheWrapper to get IToken[] arrays
325
+ const out: Record<string, IToken[]> = {};
326
+ for (const i in chains) {
327
+ const c = chains[i];
328
+ out[c] = results[i].data; // Extract data from CacheWrapper<IToken[]>
329
+ }
330
+
331
+ return out;
332
+ } catch (error) {
333
+ this.runtime.logger?.error(
334
+ `Failed to sync trending tokens: ${error instanceof Error ? error.message : String(error)}`,
335
+ );
336
+ throw error;
337
+ }
338
+ }
339
+
340
+ // FIXME: add chain param
341
+ async getTokenMarketData(tokenAddress: string): Promise<
342
+ | {
343
+ price: number;
344
+ marketCap: number;
345
+ liquidity: number;
346
+ volume24h: number;
347
+ priceHistory: number[];
348
+ }
349
+ | false
350
+ > {
351
+ try {
352
+ if (tokenAddress === "So11111111111111111111111111111111111111111") {
353
+ tokenAddress = "So11111111111111111111111111111111111111112"; // WSOL
354
+ }
355
+
356
+ const [response, volResponse, priceHistoryResponse] = await Promise.all([
357
+ fetch(
358
+ `${this.birdeyeUrl("defi/v3/token/market-data")}?address=${tokenAddress}`,
359
+ this.getBirdeyeFetchOptions(),
360
+ ),
361
+ fetch(
362
+ `${this.birdeyeUrl("defi/price_volume/single")}?address=${tokenAddress}&type=24h`,
363
+ this.getBirdeyeFetchOptions(),
364
+ ),
365
+ fetch(
366
+ `${this.birdeyeUrl("defi/history_price")}?address=${tokenAddress}&address_type=token&type=15m`,
367
+ this.getBirdeyeFetchOptions(),
368
+ ),
369
+ ]);
370
+
371
+ if (!response.ok || !volResponse.ok || !priceHistoryResponse.ok) {
372
+ throw new Error(`Birdeye API error for token ${tokenAddress}`);
373
+ }
374
+
375
+ const [data, volData, priceHistoryData] = await Promise.all([
376
+ response.json(),
377
+ volResponse.json(),
378
+ priceHistoryResponse.json(),
379
+ ]);
380
+
381
+ if (!data.data) {
382
+ this.runtime.logger.warn(
383
+ `getTokenMarketData - cant save result for ${tokenAddress}: ${JSON.stringify(data)}`,
384
+ );
385
+ //logger.warn('getTokenMarketData - cant save result', data, 'for', tokenAddress);
386
+ return false;
387
+ }
388
+
389
+ return {
390
+ price: data.data.price,
391
+ marketCap: data.data.market_cap || 0,
392
+ liquidity: data.data.liquidity || 0,
393
+ volume24h: volData.data.volumeUSD || 0,
394
+ priceHistory: priceHistoryData.data.items.map(
395
+ (item: { value: number }) => item.value,
396
+ ),
397
+ };
398
+ } catch (error) {
399
+ this.runtime.logger.error(
400
+ `Error fetching token market data: ${error instanceof Error ? error.message : String(error)}`,
401
+ );
402
+ //this.runtime.logger.error({ error },'Error fetching token market data:');
403
+ return false;
404
+ }
405
+ }
406
+
407
+ // we can do singles
408
+
409
+ // Token - Market Data (Multiple) max 20 (BUSINESS $700/mo)
410
+ // https://public-api.birdeye.so/defi/v3/token/market-data/multiple
411
+ // liq,price,supply, circulating,fdv,mcap
412
+
413
+ // Token - Trade Data (Multiple) max 20 (BUSINESS $700/mo)
414
+ // https://public-api.birdeye.so/defi/v3/token/trade-data/multiple
415
+ // has a lot of data
416
+
417
+ /*
418
+ async getTokensTradeData(chain: string, tokenAddresses: string[]): Promise<any> {
419
+ const tokenDb: Record<string, any> = {};
420
+ const chunkArray = (arr: string[], size: number) =>
421
+ arr.map((_, i) => (i % size === 0 ? arr.slice(i, i + size) : null)).filter(Boolean);
422
+ const twenties = chunkArray(tokenAddresses, 20);
423
+ const multipricePs = twenties.map((addresses) => {
424
+ const listStr = addresses.join(',');
425
+ return fetch(
426
+ `${this.birdeyeUrl('defi/v3/token/trade-data/multiple')}`,
427
+ this.getBirdeyeFetchOptions('solana')
428
+ );
429
+ });
430
+ }
431
+ */
432
+
433
+ // https://public-api.birdeye.so/defi/token_overview might be a better target
434
+ // what does this provide? 24h volume
435
+ async getTokenTradeData(
436
+ chain: string,
437
+ tokenAddress: string,
438
+ frames = "2h,8h,24h",
439
+ options: GetCacheTimedOptions = {},
440
+ ): Promise<unknown> {
441
+ const key = `birdeye_tokenTradeData_${chain}_${tokenAddress}_${frames}`;
442
+ const tsInMs = options.tsInMs ?? CACHE_DEFAULTS.TOKEN_TRADE_DATA_TTL;
443
+ const notOlderThan =
444
+ options.notOlderThan ?? CACHE_DEFAULTS.TOKEN_TRADE_DATA_TTL;
445
+
446
+ // Check cache
447
+ const cached = await this.getCacheTimed<unknown>(key, { notOlderThan });
448
+ if (cached) {
449
+ return cached;
450
+ }
451
+
452
+ // Fetch fresh data
453
+ try {
454
+ const resp = await fetch(
455
+ `${this.birdeyeUrl("defi/v3/token/trade-data/single")}?address=${tokenAddress}&frames=${frames}`,
456
+ this.getBirdeyeFetchOptions(chain),
457
+ );
458
+ const data = await resp.json();
459
+ if (data) {
460
+ this.setCacheTimed(key, data, tsInMs);
461
+ }
462
+ return data;
463
+ } catch (e) {
464
+ this.runtime.logger?.error(
465
+ `birdeye:getTokenTradeData - ${e instanceof Error ? e.message : String(e)}`,
466
+ );
467
+ return false;
468
+ }
469
+ }
470
+
471
+ // [Defi] Price Volume - Multi max 50 (premium $200/mo)
472
+ // https://public-api.birdeye.so/defi/price_volume/multi
473
+ // getting 500s
474
+ /*
475
+ async getTokensPriceVolume(tokenAddresses: string[], type = '24h'): Promise<any> {
476
+ const tokenDb: Record<string, any> = {};
477
+ const chunkArray = (arr: string[], size: number) =>
478
+ arr.map((_, i) => (i % size === 0 ? arr.slice(i, i + size) : null)).filter(Boolean);
479
+ const fities = chunkArray(tokenAddresses, 50);
480
+ this.runtime.logger?.debug(`getTokensPriceVolume - batches: ${fities.length}`);
481
+
482
+ // not sure we want to do this with rate limits...
483
+ const multipricePs = fities.map((addresses) => {
484
+ const listStr = addresses.join(',');
485
+ this.runtime.logger?.debug(`getTokensPriceVolume - batch addresses: ${listStr}`);
486
+ return fetch(
487
+ `${this.birdeyeUrl('defi/price_volume/multi')}?list_address=${listStr}&type=${type}`,
488
+ {...this.getBirdeyeFetchOptions('solana'), method: 'POST' }
489
+ );
490
+ });
491
+ const multipriceResps = await Promise.all(multipricePs); // wait for the requests to finish
492
+ const multipriceData = await Promise.all(
493
+ multipriceResps.map(async (resp) => {
494
+ if (!resp.ok) {
495
+ const text = await resp.text();
496
+ this.runtime.logger?.error(`API error: ${resp.status} - ${text}`);
497
+ return undefined;
498
+ }
499
+ return resp.json();
500
+ })
501
+ );
502
+
503
+ for (const mpd of multipriceData) {
504
+ this.runtime.logger?.debug('getTokensPriceVolume - response:', mpd);
505
+ }
506
+ }
507
+ */
508
+
509
+ // [Defi] Price - Multiple max 100 (all)
510
+ // https://public-api.birdeye.so/defi/multi_price
511
+ // Batch CU Cost = N^0.8 × 5 (base cost of a single call) (n_max: 100)
512
+ // FIXME: caching
513
+ async getTokensMarketData(
514
+ chain: string,
515
+ tokenAddresses: string[],
516
+ ): Promise<Record<string, IToken | undefined>> {
517
+ const tokenDb: Record<string, IToken | undefined> = {};
518
+
519
+ // Initialize all token addresses as undefined so we know they were checked
520
+ for (const ca of tokenAddresses) {
521
+ tokenDb[ca] = undefined;
522
+ }
523
+
524
+ //console.log('beSrv:getTokensMarketData chain', chain, 'tokenAddresses', tokenAddresses)
525
+
526
+ // no real cache because pricing
527
+ // 1-60s cache might have some benefits tho
528
+ // usually called only once every sell signal (1m atm)
529
+
530
+ try {
531
+ const chunkArray = (arr: string[], size: number) =>
532
+ arr
533
+ .map((_, i) => (i % size === 0 ? arr.slice(i, i + size) : null))
534
+ .filter(Boolean);
535
+
536
+ const hundos = chunkArray(tokenAddresses, 100);
537
+ //console.log('getTokensMarketData hundos', hundos)
538
+
539
+ // Track batches with their addresses for cache management
540
+ const batchesWithAddresses = hundos
541
+ .map((addresses) => {
542
+ if (addresses !== null) {
543
+ const listStr = addresses.join(",");
544
+ return {
545
+ addresses,
546
+ promise: fetch(
547
+ `${this.birdeyeUrl("defi/multi_price")}?list_address=${listStr}&include_liquidity=true`,
548
+ this.getBirdeyeFetchOptions(chain),
549
+ ),
550
+ };
551
+ }
552
+ return undefined;
553
+ })
554
+ .filter(
555
+ (item): item is { addresses: string[]; promise: Promise<Response> } =>
556
+ item !== undefined,
557
+ );
558
+
559
+ const multipriceResps = await Promise.all(
560
+ batchesWithAddresses.map((b) => b.promise),
561
+ );
562
+ const multipriceData = await Promise.all(
563
+ multipriceResps.map((resp) => resp.json()),
564
+ );
565
+
566
+ //const now = Date.now()
567
+
568
+ for (let i = 0; i < multipriceData.length; i++) {
569
+ const mpd = multipriceData[i];
570
+ const batchAddresses = batchesWithAddresses[i].addresses;
571
+
572
+ // Guard against undefined/null mpd or missing data
573
+ if (!mpd?.data || !mpd.success) {
574
+ this.runtime.logger?.warn(
575
+ `birdeye:getTokensMarketData - batch failed (${batchAddresses.length} addresses), caching all as failed`,
576
+ );
577
+
578
+ // Cache all addresses in this failed batch
579
+ for (const ca of batchAddresses) {
580
+ await this.runtime.setCache(`birdeye_tokens_solana_${ca}`, {
581
+ data: undefined,
582
+ setAt: Date.now(),
583
+ ttl: 5 * 60 * 1000, // 5 minutes for failed batch lookups
584
+ });
585
+ }
586
+ continue;
587
+ }
588
+
589
+ // Process data from successful batch
590
+ for (const ca of batchAddresses) {
591
+ const t = mpd.data[ca];
592
+
593
+ if (t) {
594
+ /*
595
+ t {
596
+ isScaledUiToken: false,
597
+ value: 0.011726789622156722,
598
+ updateUnixTime: 1751591014,
599
+ updateHumanTime: "2025-07-04T01:03:34",
600
+ priceInNative: 0.00007683147650766234,
601
+ priceChange24h: -12.453478899440487,
602
+ liquidity: 1323844.6216610295,
603
+ }
604
+ */
605
+ tokenDb[ca] = {
606
+ //provider: 'birdeye',
607
+ //chain: 'solana',
608
+ //address: ca,
609
+ priceUsd: t.value,
610
+ priceSol: t.priceInNative,
611
+ liquidity: t.liquidity,
612
+ priceChange24h: t.priceChange24h,
613
+ //marketcap
614
+ //volume24hUSD
615
+ };
616
+ const test: IToken = tokenDb[ca];
617
+ this.runtime.logger?.debug(
618
+ `birdeye:getTokensMarketData - caching token ${ca}`,
619
+ );
620
+ // Cache successful lookups with full TTL
621
+ await this.runtime.setCache<IToken>(
622
+ `birdeye_tokens_solana_${ca}`,
623
+ test,
624
+ );
625
+ } else {
626
+ // Token was in batch but has no valid data (or not in response)
627
+ this.runtime.logger?.warn(
628
+ `${ca} no valid data in response: ${JSON.stringify(t)}`,
629
+ );
630
+ // Cache failed lookups with shorter TTL to avoid re-fetching bad addresses
631
+ await this.runtime.setCache(`birdeye_tokens_solana_${ca}`, {
632
+ data: undefined,
633
+ setAt: Date.now(),
634
+ ttl: 5 * 60 * 1000, // 5 minutes for failed lookups
635
+ });
636
+ }
637
+ }
638
+ }
639
+
640
+ return tokenDb;
641
+ } catch (error) {
642
+ this.runtime.logger.error(
643
+ `Error fetching multiple tokens market data: ${error instanceof Error ? error.message : String(error)}`,
644
+ );
645
+ //this.runtime.logger.error({ error }, 'Error fetching multiple tokens market data:', error);
646
+ return tokenDb;
647
+ }
648
+ }
649
+
650
+ // Token - Security (single) all
651
+ // https://public-api.birdeye.so/defi/token_security
652
+ async getTokenSecurityData(
653
+ chain: string,
654
+ tokenAddress: string,
655
+ options: GetCacheTimedOptions = {},
656
+ ): Promise<unknown> {
657
+ const key = `birdeye_tokenSecurityData_${chain}_${tokenAddress}`;
658
+ const tsInMs = options.tsInMs ?? CACHE_DEFAULTS.TOKEN_SECURITY_DATA_TTL;
659
+ const notOlderThan =
660
+ options.notOlderThan ?? CACHE_DEFAULTS.TOKEN_SECURITY_DATA_TTL;
661
+
662
+ // Check cache
663
+ const cached = await this.getCacheTimed<unknown>(key, { notOlderThan });
664
+ if (cached) {
665
+ return cached;
666
+ }
667
+
668
+ // Fetch fresh data
669
+ try {
670
+ const resp = await fetch(
671
+ `${this.birdeyeUrl("defi/token_security")}?address=${tokenAddress}`,
672
+ this.getBirdeyeFetchOptions(chain),
673
+ );
674
+ const data = await resp.json();
675
+ if (data) {
676
+ this.setCacheTimed(key, data, tsInMs);
677
+ }
678
+ return data;
679
+ } catch (e) {
680
+ this.runtime.logger?.error(
681
+ `birdeye:getTokenSecurityData - ${e instanceof Error ? e.message : String(e)}`,
682
+ );
683
+ return false;
684
+ }
685
+ }
686
+
687
+ /*
688
+ async getToken(chain, ca) {
689
+ console.log('birdeye:srv getToken', chain, ca)
690
+ return getTokenMarketData(ca)
691
+ }
692
+ */
693
+
694
+ // lookup token
695
+ async lookupToken(
696
+ chain: string,
697
+ ca: string,
698
+ options: GetCacheTimedOptions = {},
699
+ ) {
700
+ try {
701
+ const key = `birdeye_token_${chain}_${ca}`;
702
+ const tsInMs = options.tsInMs ?? Date.now(); // only syscall if absolutely needed
703
+ const notOlderThan = options.notOlderThan ?? 30 * 1000; // a reasonable length (in ms)
704
+
705
+ // check cache
706
+ const cache = await this.getCacheTimed<unknown>(key, { notOlderThan });
707
+ if (cache) {
708
+ this.runtime.logger.debug("birdeye:lookupToken - HIT");
709
+ return cache;
710
+ }
711
+ this.runtime.logger.debug("birdeye:lookupToken - MISS");
712
+
713
+ const res = await this.getTokensMarketData(chain, [ca]);
714
+ const data = res[ca];
715
+ if (data) {
716
+ this.setCacheTimed(key, data, tsInMs);
717
+ }
718
+
719
+ return data;
720
+ } catch (e) {
721
+ this.runtime.logger?.error(
722
+ `birdeyeSvr:lookupToken - err: ${e instanceof Error ? e.message : String(e)}`,
723
+ );
724
+ throw e;
725
+ }
726
+ }
727
+
728
+ async lookupTokens(
729
+ chainAndAddresses: Array<{ chain: string; address: string }>,
730
+ options: GetCacheTimedOptions = {},
731
+ ) {
732
+ try {
733
+ // Lookup all tokens in parallel
734
+ const results = await Promise.all(
735
+ chainAndAddresses.map((cAA) =>
736
+ this.lookupToken(cAA.chain, cAA.address, options),
737
+ ),
738
+ );
739
+
740
+ // Transform results into keyed object: key = `${chain}_${address}`
741
+ const keyedResults: Record<string, unknown> = {};
742
+ chainAndAddresses.forEach((cAA, index) => {
743
+ const key = `${cAA.chain}_${cAA.address}`;
744
+ keyedResults[key] = results[index];
745
+ });
746
+
747
+ return keyedResults;
748
+ } catch (e) {
749
+ this.runtime.logger?.error(
750
+ `birdeyeSvr:lookupTokens - err: ${e instanceof Error ? e.message : String(e)}`,
751
+ );
752
+ throw e;
753
+ }
754
+ }
755
+
756
+ async fetchSearchTokenMarketDataChain(
757
+ chain: string,
758
+ _params: unknown,
759
+ _options: GetCacheTimedOptions = {},
760
+ ) {
761
+ const birdeyeFetchOptions: RequestInit = this.getBirdeyeFetchOptions(chain);
762
+ const res = await fetch(
763
+ `${this.birdeyeUrl("defi/v3/search")}`,
764
+ birdeyeFetchOptions,
765
+ );
766
+ const resp = await res.json();
767
+ this.runtime.logger.log("resp", resp);
768
+ return resp;
769
+ }
770
+
771
+ async lookupSymbolAllChains(
772
+ symbol: string,
773
+ options: GetCacheTimedOptions = {},
774
+ ) {
775
+ // set up cache
776
+ const key = `birdeye_symbol_${symbol}`;
777
+ const tsInMs = options.tsInMs ?? Date.now();
778
+ const notOlderThan = options.notOlderThan ?? 30 * 1000;
779
+
780
+ // check cache
781
+ const cache = await this.getCacheTimed<unknown>(key, { notOlderThan });
782
+ if (cache) {
783
+ return cache;
784
+ }
785
+
786
+ const birdeyeFetchOptions: RequestInit =
787
+ this.getBirdeyeFetchOptions("solana");
788
+ const res = await fetch(
789
+ `${this.birdeyeUrl("defi/v3/search")}?chain=all&target=token&keyword=${encodeURIComponent(symbol)}`,
790
+ birdeyeFetchOptions,
791
+ );
792
+ const resp = await res.json();
793
+ const data = resp.data.items;
794
+ if (data) {
795
+ this.setCacheTimed(key, data, tsInMs);
796
+ }
797
+ return data;
798
+ }
799
+
800
+ async fetchWalletTokenList(
801
+ chain: BirdeyeSupportedChain,
802
+ publicKey: string,
803
+ options: GetCacheTimedOptions = {},
804
+ ) {
805
+ // Get entire portfolio
806
+ // set up cache
807
+ const key = `birdeye_walletTokenList_${chain}_${publicKey}`;
808
+ const tsInMs = options.tsInMs ?? Date.now();
809
+ const notOlderThan = options.notOlderThan ?? 30 * 1000;
810
+
811
+ // check cache
812
+ const cache = await this.getCacheTimed<unknown>(key, { notOlderThan });
813
+ if (cache) {
814
+ return cache;
815
+ }
816
+ // get data
817
+ const birdeyeFetchOptions: RequestInit = this.getBirdeyeFetchOptions(chain);
818
+ const res = await fetch(
819
+ `${this.birdeyeUrl("v1/wallet/token_list")}?wallet=${publicKey}`,
820
+ birdeyeFetchOptions,
821
+ );
822
+
823
+ const resp = await res.json();
824
+ const data = resp?.data;
825
+ if (data) {
826
+ this.setCacheTimed(key, data, tsInMs);
827
+ }
828
+ return data;
829
+ }
830
+
831
+ async fetchWalletTxList(
832
+ chain: BirdeyeSupportedChain,
833
+ publicKey: string,
834
+ options: GetCacheTimedOptions = {},
835
+ ) {
836
+ // set up cache
837
+ const key = `birdeye_walletTxList_${chain}_${publicKey}`;
838
+ const tsInMs = options.tsInMs ?? Date.now();
839
+ const notOlderThan = options.notOlderThan ?? 30 * 1000;
840
+
841
+ // check cache
842
+ const cache = await this.getCacheTimed<unknown>(key, { notOlderThan });
843
+ if (cache) {
844
+ return cache;
845
+ }
846
+ // get data
847
+ const birdeyeFetchOptions: RequestInit = this.getBirdeyeFetchOptions(chain);
848
+ const res = await fetch(
849
+ `${this.birdeyeUrl("v1/wallet/tx_list")}?wallet=${publicKey}&limit=100`,
850
+ birdeyeFetchOptions,
851
+ );
852
+ const resp = await res.json();
853
+ const data = resp?.data?.[chain] || [];
854
+ if (data) {
855
+ this.setCacheTimed(key, data, tsInMs);
856
+ }
857
+ return data;
858
+ }
859
+
860
+ static async start(runtime: IAgentRuntime): Promise<BirdeyeService> {
861
+ runtime.logger.log("Initializing Birdeye Service");
862
+ const birdEyeService = new BirdeyeService(runtime);
863
+
864
+ // Clean any stale recurring birdeye tasks left over from previous runs.
865
+ runtime.initPromise
866
+ .then(async () => {
867
+ const tasks = await runtime.getTasks({
868
+ tags: ["queue", "repeat", "plugin_birdeye"],
869
+ agentIds: [runtime.agentId],
870
+ });
871
+ for (const task of tasks) {
872
+ if (task.id) {
873
+ await runtime.deleteTask(task.id);
874
+ }
875
+ }
876
+
877
+ // Register the BIRDEYE_SYNC_WALLET task worker + recurring task
878
+ // when the agent owns a tracked wallet address.
879
+ const walletAddr = runtime.getSetting("BIRDEYE_WALLET_ADDR");
880
+ if (walletAddr) {
881
+ const birdeye = new Birdeye(runtime);
882
+ runtime.registerTaskWorker({
883
+ name: "BIRDEYE_SYNC_WALLET",
884
+ validate: async () => true,
885
+ execute: async (rt) => {
886
+ try {
887
+ await birdeye.syncWallet();
888
+ } catch (error) {
889
+ rt.logger.error(
890
+ `Failed to sync trending tokens: ${error instanceof Error ? error.message : String(error)}`,
891
+ );
892
+ }
893
+ return undefined;
894
+ },
895
+ });
896
+
897
+ await runtime.createTask({
898
+ name: "BIRDEYE_SYNC_WALLET",
899
+ description: "Sync wallet from Birdeye",
900
+ worldId: runtime.agentId,
901
+ metadata: {
902
+ createdAt: new Date().toISOString(),
903
+ updatedAt: Date.now(),
904
+ updateInterval: 1000 * 60 * 5, // 5 minutes
905
+ },
906
+ tags: ["queue", "repeat", "plugin_birdeye", "immediate"],
907
+ });
908
+ runtime.logger.log("birdeye init - tasks registered");
909
+ }
910
+ })
911
+ .catch((error) => {
912
+ runtime.logger.error(
913
+ `birdeye task setup failed: ${error instanceof Error ? error.message : String(error)}`,
914
+ );
915
+ });
916
+
917
+ // Register Birdeye as a data provider with INTEL_DATAPROVIDER once
918
+ // Birdeye is loaded. INTEL_DATAPROVIDER is optional — probe it
919
+ // synchronously and skip registration if it's not present.
920
+ runtime
921
+ .getServiceLoadPromise(BIRDEYE_SERVICE_NAME as ServiceTypeName)
922
+ .then(() => {
923
+ const infoService = runtime.getService("INTEL_DATAPROVIDER") as
924
+ | { registerDataProvder?: (provider: unknown) => void }
925
+ | undefined;
926
+
927
+ if (
928
+ !infoService ||
929
+ typeof infoService.registerDataProvder !== "function"
930
+ ) {
931
+ runtime.logger?.debug(
932
+ "INTEL_DATAPROVIDER service not available, skipping Birdeye data provider registration",
933
+ );
934
+ return;
935
+ }
936
+
937
+ infoService.registerDataProvder({
938
+ name: "Birdeye",
939
+ trendingService: BIRDEYE_SERVICE_NAME,
940
+ lookupService: BIRDEYE_SERVICE_NAME,
941
+ });
942
+ runtime.logger?.log(
943
+ "Birdeye data provider registered with INTEL_DATAPROVIDER",
944
+ );
945
+ })
946
+ .catch((e) => {
947
+ runtime.logger?.debug(
948
+ `Birdeye service load failed; skipping data provider registration: ${e instanceof Error ? e.message : String(e)}`,
949
+ );
950
+ });
951
+
952
+ runtime.logger.log("Birdeye service initialized");
953
+ return birdEyeService;
954
+ }
955
+
956
+ static async stop(runtime: IAgentRuntime) {
957
+ const service = runtime.getService(BIRDEYE_SERVICE_NAME);
958
+ if (!service) {
959
+ runtime.logger.error("Birdeye not found");
960
+ return;
961
+ }
962
+ await service.stop();
963
+ }
964
+
965
+ async stop(): Promise<void> {
966
+ this.runtime.logger?.log("BirdEye service shutdown");
967
+ }
968
+
969
+ async getCacheTimed<T>(key: string, options: GetCacheTimedOptions = {}) {
970
+ const wrapper = await this.runtime.getCache<CacheWrapper<T>>(key);
971
+ if (!wrapper) return false;
972
+ if (options.notOlderThan) {
973
+ const now = options.tsInMs ?? Date.now();
974
+ const diff = now - wrapper.setAt;
975
+ //console.log('checking notOlderThan', diff + 'ms', 'setAt', wrapper.setAt, 'asking', options.notOlderThan)
976
+ if (diff > options.notOlderThan) {
977
+ // no data
978
+ return false;
979
+ }
980
+ }
981
+ // return data
982
+ return wrapper.data;
983
+ }
984
+
985
+ async setCacheTimed<T>(key: string, val: T, tsInMs = 0) {
986
+ if (tsInMs === 0) tsInMs = Date.now();
987
+ return this.runtime.setCache<CacheWrapper<T>>(key, {
988
+ setAt: tsInMs,
989
+ data: val,
990
+ });
991
+ }
992
+ }