@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,207 @@
1
+ import type { IAgentRuntime, SearchCategoryRegistration } from "@elizaos/core";
2
+ import { describe, expect, it, vi } from "vitest";
3
+ import type { BirdeyeProvider } from "./birdeye";
4
+ import {
5
+ BIRDEYE_SEARCH_CATEGORIES,
6
+ BIRDEYE_TOKEN_SEARCH_CATEGORY,
7
+ registerBirdeyeSearchCategories,
8
+ searchBirdeyeTokens,
9
+ } from "./search-category";
10
+
11
+ type BirdeyeTokenSearchProvider = Pick<
12
+ BirdeyeProvider,
13
+ | "fetchSearchTokenMarketData"
14
+ | "fetchTokenOverview"
15
+ | "fetchTokenMarketData"
16
+ | "fetchTokenSecurityByAddress"
17
+ | "fetchTokenTradeDataSingle"
18
+ >;
19
+
20
+ function createRuntime() {
21
+ const categories = new Map<string, SearchCategoryRegistration>();
22
+ const registerSearchCategory = vi.fn(
23
+ (registration: SearchCategoryRegistration) => {
24
+ categories.set(registration.category, registration);
25
+ },
26
+ );
27
+ const getSearchCategory = vi.fn((category: string) => {
28
+ const registration = categories.get(category);
29
+ if (!registration) throw new Error(`Missing category ${category}`);
30
+ return registration;
31
+ });
32
+
33
+ return {
34
+ categories,
35
+ registerSearchCategory,
36
+ runtime: {
37
+ getSearchCategory,
38
+ registerSearchCategory,
39
+ logger: { warn: vi.fn() },
40
+ } as IAgentRuntime,
41
+ };
42
+ }
43
+
44
+ describe("Birdeye search categories", () => {
45
+ it("registers one unified token intel search category", () => {
46
+ const { categories, registerSearchCategory, runtime } = createRuntime();
47
+
48
+ registerBirdeyeSearchCategories(runtime);
49
+ registerBirdeyeSearchCategories(runtime);
50
+
51
+ expect(registerSearchCategory).toHaveBeenCalledTimes(1);
52
+ expect(categories.get("birdeye_tokens")).toMatchObject({
53
+ category: "birdeye_tokens",
54
+ serviceType: "birdeye",
55
+ source: "plugin:wallet:birdeye",
56
+ });
57
+ expect(
58
+ BIRDEYE_SEARCH_CATEGORIES.map((category) => category.category),
59
+ ).toEqual(["birdeye_tokens"]);
60
+ expect(
61
+ BIRDEYE_TOKEN_SEARCH_CATEGORY.filters?.some(
62
+ (filter) => filter.name === "query" && filter.required,
63
+ ),
64
+ ).toBe(false);
65
+ });
66
+
67
+ it("can register disabled categories when Birdeye routing is unavailable", () => {
68
+ const { categories, runtime } = createRuntime();
69
+
70
+ registerBirdeyeSearchCategories(runtime, {
71
+ enabled: false,
72
+ disabledReason: "missing key",
73
+ });
74
+
75
+ expect(categories.get("birdeye_tokens")).toMatchObject({
76
+ enabled: false,
77
+ disabledReason: "missing key",
78
+ });
79
+ });
80
+
81
+ it("searches token intel by symbol", async () => {
82
+ const provider = {
83
+ fetchSearchTokenMarketData: vi.fn(async () => ({
84
+ data: {
85
+ items: [
86
+ {
87
+ type: "token",
88
+ result: [
89
+ {
90
+ symbol: "SOL",
91
+ address: "So11111111111111111111111111111111111111112",
92
+ network: "solana",
93
+ price: 172.23,
94
+ price_change_24h_percent: 1.5,
95
+ volume_24h_usd: 1000000,
96
+ market_cap: 75000000000,
97
+ fdv: 90000000000,
98
+ },
99
+ {
100
+ symbol: "SOLDOG",
101
+ address: "ignored",
102
+ },
103
+ ],
104
+ },
105
+ ],
106
+ },
107
+ })),
108
+ };
109
+
110
+ const result = await searchBirdeyeTokens(
111
+ {} as IAgentRuntime,
112
+ {
113
+ query: "$SOL",
114
+ filters: { mode: "symbol", chain: "all" },
115
+ limit: 3,
116
+ },
117
+ provider as BirdeyeTokenSearchProvider,
118
+ );
119
+
120
+ expect(provider.fetchSearchTokenMarketData).toHaveBeenCalledWith(
121
+ expect.objectContaining({
122
+ keyword: "SOL",
123
+ chain: "all",
124
+ target: "token",
125
+ limit: 3,
126
+ }),
127
+ );
128
+ expect(result).toMatchObject({
129
+ mode: "symbol",
130
+ resultCount: 1,
131
+ });
132
+ expect(result.mode).toBe("symbol");
133
+ if (result.mode !== "symbol") {
134
+ throw new Error("Expected symbol search result");
135
+ }
136
+ expect(result.results[0].tokens).toHaveLength(1);
137
+ expect(result.text).toContain("birdeye_token_search:");
138
+ expect(result.text).toContain("mode: symbol");
139
+ });
140
+
141
+ it("searches token intel by address", async () => {
142
+ const address = "0x7fc66500c84a76ad7e9c93437bfc5ac33e2ddae9";
143
+ const provider = {
144
+ fetchTokenOverview: vi.fn(async () => ({
145
+ data: {
146
+ name: "Aave",
147
+ symbol: "AAVE",
148
+ decimals: 18,
149
+ price: 100,
150
+ liquidity: 2000000,
151
+ },
152
+ })),
153
+ fetchTokenMarketData: vi.fn(async () => ({
154
+ data: {
155
+ price: 101,
156
+ liquidity: 2100000,
157
+ marketcap: 1500000000,
158
+ },
159
+ })),
160
+ fetchTokenSecurityByAddress: vi.fn(),
161
+ fetchTokenTradeDataSingle: vi.fn(async () => ({
162
+ data: {
163
+ holder: 100000,
164
+ price_change_24h_percent: -2.25,
165
+ },
166
+ })),
167
+ };
168
+
169
+ const result = await searchBirdeyeTokens(
170
+ {} as IAgentRuntime,
171
+ {
172
+ query: `lookup ${address}`,
173
+ filters: {
174
+ mode: "address",
175
+ chain: "base",
176
+ includeSecurity: false,
177
+ },
178
+ },
179
+ provider as BirdeyeTokenSearchProvider,
180
+ );
181
+
182
+ expect(provider.fetchTokenOverview).toHaveBeenCalledWith(
183
+ { address },
184
+ { headers: { "x-chain": "base" } },
185
+ );
186
+ expect(provider.fetchTokenMarketData).toHaveBeenCalledWith(
187
+ { address },
188
+ { headers: { "x-chain": "base" } },
189
+ );
190
+ expect(provider.fetchTokenSecurityByAddress).not.toHaveBeenCalled();
191
+ expect(provider.fetchTokenTradeDataSingle).toHaveBeenCalledWith(
192
+ { address },
193
+ { headers: { "x-chain": "base" } },
194
+ );
195
+ expect(result).toMatchObject({
196
+ mode: "address",
197
+ resultCount: 1,
198
+ });
199
+ expect(result.mode).toBe("address");
200
+ if (result.mode !== "address") {
201
+ throw new Error("Expected address search result");
202
+ }
203
+ expect(result.results[0].chain).toBe("base");
204
+ expect(result.text).toContain("mode: address");
205
+ expect(result.text).toContain("Aave");
206
+ });
207
+ });
@@ -0,0 +1,506 @@
1
+ // @ts-nocheck — legacy code from absorbed plugins (lp-manager, lpinfo, dexscreener, defi-news, birdeye); strict types pending cleanup
2
+ import type { IAgentRuntime, SearchCategoryRegistration } from "@elizaos/core";
3
+ import { BirdeyeProvider } from "./birdeye";
4
+ import { BIRDEYE_SERVICE_NAME } from "./constants";
5
+ import type {
6
+ TokenMarketDataResponse,
7
+ TokenOverviewResponse,
8
+ TokenSecurityResponse,
9
+ TokenTradeDataSingleResponse,
10
+ } from "./types/api/token";
11
+ import type { BaseAddress } from "./types/shared";
12
+ import {
13
+ extractAddresses,
14
+ extractSymbols,
15
+ formatJsonScalar,
16
+ formatJsonTable,
17
+ formatPercentChange,
18
+ formatPrice,
19
+ formatValue,
20
+ } from "./utils";
21
+
22
+ const BIRDEYE_CHAIN_OPTIONS = [
23
+ { label: "All", value: "all" },
24
+ { label: "Solana", value: "solana" },
25
+ { label: "Ethereum", value: "ethereum" },
26
+ { label: "Arbitrum", value: "arbitrum" },
27
+ { label: "Avalanche", value: "avalanche" },
28
+ { label: "BSC", value: "bsc" },
29
+ { label: "Optimism", value: "optimism" },
30
+ { label: "Polygon", value: "polygon" },
31
+ { label: "Base", value: "base" },
32
+ { label: "zkSync", value: "zksync" },
33
+ { label: "Sui", value: "sui" },
34
+ ];
35
+
36
+ const BIRDEYE_SORT_OPTIONS = [
37
+ { label: "FDV", value: "fdv" },
38
+ { label: "Market cap", value: "marketcap" },
39
+ { label: "Liquidity", value: "liquidity" },
40
+ { label: "Price", value: "price" },
41
+ { label: "24h price change", value: "price_change_24h_percent" },
42
+ { label: "24h trades", value: "trade_24h" },
43
+ { label: "24h buys", value: "buy_24h" },
44
+ { label: "24h sells", value: "sell_24h" },
45
+ { label: "24h unique wallets", value: "unique_wallet_24h" },
46
+ { label: "Last trade time", value: "last_trade_unix_time" },
47
+ { label: "24h USD volume", value: "volume_24h_usd" },
48
+ ];
49
+
50
+ const BIRDEYE_TOKEN_SEARCH_MODES = [
51
+ { label: "Auto", value: "auto" },
52
+ { label: "Symbol", value: "symbol" },
53
+ { label: "Address", value: "address" },
54
+ ];
55
+
56
+ const DEFAULT_LIMIT = 5;
57
+
58
+ export const BIRDEYE_TOKEN_SEARCH_CATEGORY: SearchCategoryRegistration = {
59
+ category: "birdeye_tokens",
60
+ label: "Birdeye token intel",
61
+ description:
62
+ "Search Birdeye token intelligence by symbol or contract address.",
63
+ contexts: ["wallet", "knowledge"],
64
+ filters: [
65
+ {
66
+ name: "mode",
67
+ label: "Mode",
68
+ description:
69
+ "Use symbol for ticker search, address for contract lookup, or auto to infer from the query.",
70
+ type: "enum",
71
+ default: "auto",
72
+ options: BIRDEYE_TOKEN_SEARCH_MODES,
73
+ },
74
+ {
75
+ name: "chain",
76
+ label: "Chain",
77
+ description:
78
+ "Birdeye chain scope. Address mode uses ethereum for EVM addresses when no explicit chain is supplied.",
79
+ type: "enum",
80
+ default: "all",
81
+ options: BIRDEYE_CHAIN_OPTIONS,
82
+ },
83
+ {
84
+ name: "target",
85
+ label: "Target",
86
+ description: "Birdeye search target for symbol mode.",
87
+ type: "enum",
88
+ default: "token",
89
+ options: [
90
+ { label: "Token", value: "token" },
91
+ { label: "Market", value: "market" },
92
+ { label: "All", value: "all" },
93
+ ],
94
+ },
95
+ {
96
+ name: "sort_by",
97
+ label: "Sort by",
98
+ type: "enum",
99
+ default: "volume_24h_usd",
100
+ options: BIRDEYE_SORT_OPTIONS,
101
+ },
102
+ {
103
+ name: "sort_type",
104
+ label: "Sort type",
105
+ type: "enum",
106
+ default: "desc",
107
+ options: [
108
+ { label: "Ascending", value: "asc" },
109
+ { label: "Descending", value: "desc" },
110
+ ],
111
+ },
112
+ {
113
+ name: "verify_token",
114
+ label: "Verified only",
115
+ description: "Restrict symbol results to verified tokens.",
116
+ type: "boolean",
117
+ },
118
+ {
119
+ name: "markets",
120
+ label: "Markets",
121
+ description: "Comma-separated market sources for symbol mode.",
122
+ type: "string",
123
+ },
124
+ {
125
+ name: "includeSecurity",
126
+ label: "Include security",
127
+ description: "Include contract security details in address mode.",
128
+ type: "boolean",
129
+ default: true,
130
+ },
131
+ {
132
+ name: "includeTradeData",
133
+ label: "Include trade data",
134
+ description: "Include holder and 24h trading details in address mode.",
135
+ type: "boolean",
136
+ default: true,
137
+ },
138
+ {
139
+ name: "includeMarketData",
140
+ label: "Include market data",
141
+ description: "Include token market data in address mode.",
142
+ type: "boolean",
143
+ default: true,
144
+ },
145
+ ],
146
+ resultSchemaSummary:
147
+ "BirdeyeTokenSearchResult with mode, query, resultCount, and results. Symbol results include matching tokens; address results include overview, market, security, and trade data.",
148
+ capabilities: [
149
+ "tokens",
150
+ "symbols",
151
+ "addresses",
152
+ "market-data",
153
+ "security",
154
+ "trade-data",
155
+ ],
156
+ source: "plugin:wallet:birdeye",
157
+ serviceType: BIRDEYE_SERVICE_NAME,
158
+ };
159
+
160
+ export const BIRDEYE_SEARCH_CATEGORIES = [
161
+ BIRDEYE_TOKEN_SEARCH_CATEGORY,
162
+ ] as const;
163
+
164
+ export interface BirdeyeSearchCategoryOptions {
165
+ enabled?: boolean;
166
+ disabledReason?: string;
167
+ }
168
+
169
+ export type BirdeyeTokenSearchMode = "auto" | "symbol" | "address";
170
+
171
+ export interface BirdeyeTokenSearchRequest {
172
+ query: string;
173
+ mode?: BirdeyeTokenSearchMode;
174
+ filters?: Record<string, unknown>;
175
+ limit?: number;
176
+ }
177
+
178
+ type BirdeyeTokenAddressSearchResult = {
179
+ address: BaseAddress;
180
+ chain: string;
181
+ overview?: TokenOverviewResponse;
182
+ marketData?: TokenMarketDataResponse;
183
+ security?: TokenSecurityResponse;
184
+ tradeData?: TokenTradeDataSingleResponse;
185
+ };
186
+
187
+ type BirdeyeTokenSymbolSearchResult = {
188
+ symbol: string;
189
+ tokens: unknown[];
190
+ };
191
+
192
+ type BirdeyeTokenSearchProvider = Pick<
193
+ BirdeyeProvider,
194
+ | "fetchSearchTokenMarketData"
195
+ | "fetchTokenOverview"
196
+ | "fetchTokenMarketData"
197
+ | "fetchTokenSecurityByAddress"
198
+ | "fetchTokenTradeDataSingle"
199
+ >;
200
+
201
+ function hasSearchCategory(runtime: IAgentRuntime, category: string): boolean {
202
+ if (typeof runtime.getSearchCategory !== "function") {
203
+ return false;
204
+ }
205
+ try {
206
+ runtime.getSearchCategory(category, { includeDisabled: true });
207
+ return true;
208
+ } catch {
209
+ return false;
210
+ }
211
+ }
212
+
213
+ export function registerBirdeyeSearchCategories(
214
+ runtime: IAgentRuntime,
215
+ options: BirdeyeSearchCategoryOptions = {},
216
+ ): void {
217
+ if (typeof runtime.registerSearchCategory !== "function") {
218
+ runtime.logger?.warn?.(
219
+ "Birdeye search category registry is unavailable; token search metadata was not registered",
220
+ );
221
+ return;
222
+ }
223
+
224
+ const enabled = options.enabled ?? true;
225
+ for (const category of BIRDEYE_SEARCH_CATEGORIES) {
226
+ if (hasSearchCategory(runtime, category.category)) {
227
+ continue;
228
+ }
229
+ runtime.registerSearchCategory({
230
+ ...category,
231
+ enabled,
232
+ disabledReason: enabled ? undefined : options.disabledReason,
233
+ });
234
+ }
235
+ }
236
+
237
+ function normalizeMode(value: unknown): BirdeyeTokenSearchMode {
238
+ const mode = typeof value === "string" ? value.toLowerCase() : "auto";
239
+ return mode === "symbol" || mode === "address" ? mode : "auto";
240
+ }
241
+
242
+ function normalizeLimit(value: unknown): number {
243
+ const limit =
244
+ typeof value === "number" && Number.isFinite(value)
245
+ ? Math.floor(value)
246
+ : DEFAULT_LIMIT;
247
+ return Math.max(1, Math.min(25, limit));
248
+ }
249
+
250
+ function filterBool(
251
+ filters: Record<string, unknown>,
252
+ key: string,
253
+ fallback: boolean,
254
+ ): boolean {
255
+ return typeof filters[key] === "boolean" ? filters[key] : fallback;
256
+ }
257
+
258
+ function normalizeSymbolQuery(query: string): string[] {
259
+ const strict = extractSymbols(query, "strict");
260
+ if (strict.length > 0) {
261
+ return strict;
262
+ }
263
+ const loose = extractSymbols(query, "loose");
264
+ if (loose.length > 0) {
265
+ return loose;
266
+ }
267
+ const symbol = query.trim().replace(/^\$/, "").toUpperCase();
268
+ return symbol ? [symbol] : [];
269
+ }
270
+
271
+ function inferMode(
272
+ query: string,
273
+ requestedMode: BirdeyeTokenSearchMode,
274
+ ): Exclude<BirdeyeTokenSearchMode, "auto"> {
275
+ if (requestedMode === "symbol" || requestedMode === "address") {
276
+ return requestedMode;
277
+ }
278
+ return extractAddresses(query).length > 0 ? "address" : "symbol";
279
+ }
280
+
281
+ function chainForAddress(
282
+ address: BaseAddress,
283
+ filters: Record<string, unknown>,
284
+ ): string {
285
+ const chain = typeof filters.chain === "string" ? filters.chain : "";
286
+ if (chain && chain !== "all") {
287
+ return chain;
288
+ }
289
+ return address.chain === "evm" ? "ethereum" : address.chain;
290
+ }
291
+
292
+ async function searchTokensBySymbol(
293
+ provider: BirdeyeTokenSearchProvider,
294
+ query: string,
295
+ filters: Record<string, unknown>,
296
+ limit: number,
297
+ ): Promise<BirdeyeTokenSymbolSearchResult[]> {
298
+ const symbols = normalizeSymbolQuery(query);
299
+ const chain = typeof filters.chain === "string" ? filters.chain : "all";
300
+ const target = typeof filters.target === "string" ? filters.target : "token";
301
+ const sortBy =
302
+ typeof filters.sort_by === "string" ? filters.sort_by : "volume_24h_usd";
303
+ const sortType =
304
+ typeof filters.sort_type === "string" ? filters.sort_type : "desc";
305
+
306
+ const results = await Promise.all(
307
+ symbols.map(async (symbol) => {
308
+ const response = await provider.fetchSearchTokenMarketData({
309
+ keyword: symbol,
310
+ chain,
311
+ target,
312
+ sort_by: sortBy,
313
+ sort_type: sortType,
314
+ limit,
315
+ verify_token:
316
+ typeof filters.verify_token === "boolean"
317
+ ? filters.verify_token
318
+ : undefined,
319
+ markets:
320
+ typeof filters.markets === "string" ? filters.markets : undefined,
321
+ });
322
+ const tokens = (response?.data?.items ?? [])
323
+ .filter((item) => item.type === "token" && item.result)
324
+ .flatMap((item) => item.result)
325
+ .filter(
326
+ (token) => token?.symbol?.toLowerCase?.() === symbol.toLowerCase(),
327
+ )
328
+ .slice(0, limit);
329
+ return { symbol, tokens };
330
+ }),
331
+ );
332
+
333
+ return results;
334
+ }
335
+
336
+ async function searchTokensByAddress(
337
+ provider: BirdeyeTokenSearchProvider,
338
+ query: string,
339
+ filters: Record<string, unknown>,
340
+ ): Promise<BirdeyeTokenAddressSearchResult[]> {
341
+ const addresses = extractAddresses(query);
342
+ const includeMarketData = filterBool(filters, "includeMarketData", true);
343
+ const includeSecurity = filterBool(filters, "includeSecurity", true);
344
+ const includeTradeData = filterBool(filters, "includeTradeData", true);
345
+
346
+ return Promise.all(
347
+ addresses.map(async (address) => {
348
+ const chain = chainForAddress(address, filters);
349
+ const request = { address: address.address };
350
+ const options = { headers: { "x-chain": chain } };
351
+ const [overview, marketData, security, tradeData] = await Promise.all([
352
+ provider.fetchTokenOverview(request, options),
353
+ includeMarketData
354
+ ? provider.fetchTokenMarketData(request, options)
355
+ : Promise.resolve(undefined),
356
+ includeSecurity
357
+ ? provider.fetchTokenSecurityByAddress(request, options)
358
+ : Promise.resolve(undefined),
359
+ includeTradeData
360
+ ? provider.fetchTokenTradeDataSingle(request, options)
361
+ : Promise.resolve(undefined),
362
+ ]);
363
+
364
+ return {
365
+ address,
366
+ chain,
367
+ overview,
368
+ marketData,
369
+ security,
370
+ tradeData,
371
+ };
372
+ }),
373
+ );
374
+ }
375
+
376
+ function formatSymbolSearchJson(
377
+ query: string,
378
+ results: BirdeyeTokenSymbolSearchResult[],
379
+ ): string {
380
+ const rows = results.flatMap((result) =>
381
+ result.tokens.map((token) => ({
382
+ querySymbol: result.symbol,
383
+ symbol: token.symbol?.toUpperCase?.() ?? result.symbol,
384
+ address: token.address,
385
+ network: token.network ?? "unknown",
386
+ price: formatPrice(token.price),
387
+ change24h: formatPercentChange(token.price_change_24h_percent),
388
+ volume24hUsd: formatValue(token.volume_24h_usd),
389
+ marketCap: token.market_cap ? formatValue(token.market_cap) : "N/A",
390
+ fdv: token.fdv ? formatValue(token.fdv) : "N/A",
391
+ })),
392
+ );
393
+
394
+ return [
395
+ "birdeye_token_search:",
396
+ " mode: symbol",
397
+ ` query: ${formatJsonScalar(query)}`,
398
+ ` resultCount: ${rows.length}`,
399
+ formatJsonTable(" results", rows, [
400
+ "querySymbol",
401
+ "symbol",
402
+ "address",
403
+ "network",
404
+ "price",
405
+ "change24h",
406
+ "volume24hUsd",
407
+ "marketCap",
408
+ "fdv",
409
+ ]),
410
+ ]
411
+ .filter(Boolean)
412
+ .join("\n");
413
+ }
414
+
415
+ function formatAddressSearchJson(
416
+ query: string,
417
+ results: BirdeyeTokenAddressSearchResult[],
418
+ ): string {
419
+ const rows = results.map((result) => ({
420
+ address: result.address.address,
421
+ chain: result.chain,
422
+ name: result.overview?.data?.name ?? "unknown",
423
+ symbol: result.overview?.data?.symbol?.toUpperCase?.() ?? "unknown",
424
+ decimals: result.overview?.data?.decimals ?? "unknown",
425
+ price: formatPrice(
426
+ result.marketData?.data?.price ?? result.overview?.data?.price,
427
+ ),
428
+ liquidity: formatValue(
429
+ result.marketData?.data?.liquidity ?? result.overview?.data?.liquidity,
430
+ ),
431
+ marketCap: formatValue(result.marketData?.data?.marketcap),
432
+ holders: result.tradeData?.data?.holder ?? "unknown",
433
+ change24h: formatPercentChange(
434
+ result.tradeData?.data?.price_change_24h_percent,
435
+ ),
436
+ owner: result.security?.data?.ownerAddress ?? "unknown",
437
+ }));
438
+
439
+ return [
440
+ "birdeye_token_search:",
441
+ " mode: address",
442
+ ` query: ${formatJsonScalar(query)}`,
443
+ ` resultCount: ${rows.length}`,
444
+ formatJsonTable(" results", rows, [
445
+ "address",
446
+ "chain",
447
+ "name",
448
+ "symbol",
449
+ "decimals",
450
+ "price",
451
+ "liquidity",
452
+ "marketCap",
453
+ "holders",
454
+ "change24h",
455
+ "owner",
456
+ ]),
457
+ ]
458
+ .filter(Boolean)
459
+ .join("\n");
460
+ }
461
+
462
+ export async function searchBirdeyeTokens(
463
+ runtime: IAgentRuntime,
464
+ request: BirdeyeTokenSearchRequest,
465
+ provider: BirdeyeTokenSearchProvider = new BirdeyeProvider(runtime),
466
+ ) {
467
+ const query = String(request.query ?? "").trim();
468
+ const filters = request.filters ?? {};
469
+ const requestedMode = normalizeMode(request.mode ?? filters.mode);
470
+ const mode = inferMode(query, requestedMode);
471
+ const limit = normalizeLimit(request.limit ?? filters.limit);
472
+
473
+ if (!query) {
474
+ return {
475
+ query,
476
+ mode,
477
+ resultCount: 0,
478
+ results: [],
479
+ text: "birdeye_token_search:\n status: error\n reason: missing_query",
480
+ };
481
+ }
482
+
483
+ if (mode === "address") {
484
+ const results = await searchTokensByAddress(provider, query, filters);
485
+ return {
486
+ query,
487
+ mode,
488
+ resultCount: results.length,
489
+ results,
490
+ text: formatAddressSearchJson(query, results),
491
+ };
492
+ }
493
+
494
+ const results = await searchTokensBySymbol(provider, query, filters, limit);
495
+ const resultCount = results.reduce(
496
+ (count, result) => count + result.tokens.length,
497
+ 0,
498
+ );
499
+ return {
500
+ query,
501
+ mode,
502
+ resultCount,
503
+ results,
504
+ text: formatSymbolSearchJson(query, results),
505
+ };
506
+ }