@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.
- package/LICENSE +21 -0
- package/README.md +64 -0
- package/auto-enable.ts +76 -0
- package/dist/LpManagementService-BWrQ5-cO.mjs +353 -0
- package/dist/MockLpService-D_Apn4Fd.mjs +99 -0
- package/dist/aerodrome-CfnESC32.mjs +890 -0
- package/dist/chunk-hT5z_Zn9.mjs +35 -0
- package/dist/index.d.mts +34727 -0
- package/dist/index.mjs +21590 -0
- package/dist/lib/server-wallet-trade.d.mts +34 -0
- package/dist/lib/server-wallet-trade.mjs +306 -0
- package/dist/meteora-BPX39hZo.mjs +22640 -0
- package/dist/orca-Bybp1HXO.mjs +249 -0
- package/dist/pancakeswp-CkEXlXti.mjs +604 -0
- package/dist/plugin-ZO_MTyd0.mjs +529 -0
- package/dist/raydium-rfaM9yEf.mjs +539 -0
- package/dist/sdk/index.d.mts +32492 -0
- package/dist/sdk/index.mjs +6415 -0
- package/dist/types-D5252NZk.mjs +487 -0
- package/dist/uniswap-CReXgXVN.mjs +573 -0
- package/dist/wallet-action.d.mts +6 -0
- package/dist/wallet-action.mjs +820 -0
- package/package.json +152 -0
- package/src/actions/failure-codes.ts +79 -0
- package/src/actions/index.ts +1 -0
- package/src/analytics/birdeye/actions/wallet-search-address.ts +9 -0
- package/src/analytics/birdeye/birdeye-task.ts +175 -0
- package/src/analytics/birdeye/birdeye.ts +813 -0
- package/src/analytics/birdeye/constants.ts +74 -0
- package/src/analytics/birdeye/providers/agent-portfolio-provider.ts +18 -0
- package/src/analytics/birdeye/providers/market.ts +227 -0
- package/src/analytics/birdeye/providers/portfolio-factory.test.ts +138 -0
- package/src/analytics/birdeye/providers/portfolio-factory.ts +252 -0
- package/src/analytics/birdeye/providers/trending.ts +365 -0
- package/src/analytics/birdeye/providers/wallet.ts +14 -0
- package/src/analytics/birdeye/search-category.test.ts +207 -0
- package/src/analytics/birdeye/search-category.ts +506 -0
- package/src/analytics/birdeye/service.ts +992 -0
- package/src/analytics/birdeye/tasks/birdeye.ts +232 -0
- package/src/analytics/birdeye/types/api/common.ts +305 -0
- package/src/analytics/birdeye/types/api/defi.ts +220 -0
- package/src/analytics/birdeye/types/api/pair.ts +200 -0
- package/src/analytics/birdeye/types/api/search.ts +86 -0
- package/src/analytics/birdeye/types/api/token.ts +635 -0
- package/src/analytics/birdeye/types/api/trader.ts +76 -0
- package/src/analytics/birdeye/types/api/wallet.ts +181 -0
- package/src/analytics/birdeye/types/shared.ts +106 -0
- package/src/analytics/birdeye/utils.ts +700 -0
- package/src/analytics/dexscreener/errors.ts +28 -0
- package/src/analytics/dexscreener/index.ts +3 -0
- package/src/analytics/dexscreener/search-category.test.ts +49 -0
- package/src/analytics/dexscreener/search-category.ts +42 -0
- package/src/analytics/dexscreener/service.ts +595 -0
- package/src/analytics/dexscreener/types.ts +128 -0
- package/src/analytics/lpinfo/index.d.ts +7 -0
- package/src/analytics/lpinfo/index.ts +52 -0
- package/src/analytics/lpinfo/kamino/README.md +102 -0
- package/src/analytics/lpinfo/kamino/index.ts +24 -0
- package/src/analytics/lpinfo/kamino/providers/kaminoLiquidityProvider.ts +422 -0
- package/src/analytics/lpinfo/kamino/providers/kaminoPoolProvider.ts +365 -0
- package/src/analytics/lpinfo/kamino/providers/kaminoProvider.ts +496 -0
- package/src/analytics/lpinfo/kamino/services/kaminoLiquidityService.ts +1123 -0
- package/src/analytics/lpinfo/kamino/services/kaminoService.ts +758 -0
- package/src/analytics/lpinfo/steer/README.md +169 -0
- package/src/analytics/lpinfo/steer/index.ts +23 -0
- package/src/analytics/lpinfo/steer/providers/steerLiquidityProvider.ts +544 -0
- package/src/analytics/lpinfo/steer/services/steerLiquidityService.ts +1690 -0
- package/src/analytics/lpinfo/steer/steer-display-types.ts +99 -0
- package/src/analytics/news/index.ts +52 -0
- package/src/analytics/news/interfaces/types.ts +222 -0
- package/src/analytics/news/providers/defiNewsProvider.ts +734 -0
- package/src/analytics/news/services/newsDataService.ts +332 -0
- package/src/analytics/news/utils/formatters.ts +151 -0
- package/src/analytics/token-info/action.ts +240 -0
- package/src/analytics/token-info/index.ts +3 -0
- package/src/analytics/token-info/params.ts +215 -0
- package/src/analytics/token-info/providers.ts +681 -0
- package/src/analytics/token-info/service.ts +168 -0
- package/src/analytics/token-info/types.ts +74 -0
- package/src/audit/audit-log.ts +45 -0
- package/src/browser-shim/build-shim.ts +123 -0
- package/src/browser-shim/index.ts +5 -0
- package/src/browser-shim/shim.template.js +563 -0
- package/src/chains/evm/.github/workflows/npm-deploy.yml +112 -0
- package/src/chains/evm/LICENSE +21 -0
- package/src/chains/evm/README.md +106 -0
- package/src/chains/evm/actions/helpers.ts +147 -0
- package/src/chains/evm/actions/swap.ts +839 -0
- package/src/chains/evm/actions/transfer.ts +254 -0
- package/src/chains/evm/biome.json +61 -0
- package/src/chains/evm/bridge-router.ts +660 -0
- package/src/chains/evm/build.ts +89 -0
- package/src/chains/evm/chain-handler.ts +416 -0
- package/src/chains/evm/constants.ts +23 -0
- package/src/chains/evm/contracts/artifacts/OZGovernor.json +1707 -0
- package/src/chains/evm/contracts/artifacts/TimelockController.json +1007 -0
- package/src/chains/evm/contracts/artifacts/VoteToken.json +895 -0
- package/src/chains/evm/dex/aerodrome/index.ts +34 -0
- package/src/chains/evm/dex/aerodrome/services/AerodromeLpService.ts +558 -0
- package/src/chains/evm/dex/aerodrome/types.ts +318 -0
- package/src/chains/evm/dex/pancakeswp/index.ts +35 -0
- package/src/chains/evm/dex/pancakeswp/services/PancakeSwapV3LpService.ts +743 -0
- package/src/chains/evm/dex/pancakeswp/types.ts +65 -0
- package/src/chains/evm/dex/uniswap/index.ts +35 -0
- package/src/chains/evm/dex/uniswap/services/UniswapV3LpService.ts +759 -0
- package/src/chains/evm/dex/uniswap/types.ts +390 -0
- package/src/chains/evm/generated/specs/spec-helpers.ts +73 -0
- package/src/chains/evm/generated/specs/specs.ts +151 -0
- package/src/chains/evm/gov-router.ts +250 -0
- package/src/chains/evm/index.browser.ts +16 -0
- package/src/chains/evm/index.ts +31 -0
- package/src/chains/evm/prompts.ts +193 -0
- package/src/chains/evm/providers/get-balance.ts +123 -0
- package/src/chains/evm/providers/wallet.ts +715 -0
- package/src/chains/evm/routes/sign.ts +333 -0
- package/src/chains/evm/rpc-providers.ts +410 -0
- package/src/chains/evm/service.ts +140 -0
- package/src/chains/evm/templates/index.ts +10 -0
- package/src/chains/evm/types/index.ts +432 -0
- package/src/chains/evm/vitest.config.ts +18 -0
- package/src/chains/registry.ts +668 -0
- package/src/chains/solana/README.md +367 -0
- package/src/chains/wallet-action.ts +533 -0
- package/src/chains/wallet-router.test.ts +296 -0
- package/src/contracts.ts +65 -0
- package/src/core-augmentation.ts +10 -0
- package/src/index.ts +71 -0
- package/src/lib/server-wallet-trade.ts +192 -0
- package/src/lib/wallet-export-guard.ts +330 -0
- package/src/lp/actions/liquidity.ts +827 -0
- package/src/lp/e2e/real-token-tests.ts +428 -0
- package/src/lp/e2e/scenarios.ts +470 -0
- package/src/lp/e2e/test-utils.ts +145 -0
- package/src/lp/lp-manager-entry.ts +303 -0
- package/src/lp/services/ConcentratedLiquidityService.ts +120 -0
- package/src/lp/services/DexInteractionService.ts +226 -0
- package/src/lp/services/LpManagementService.test.ts +148 -0
- package/src/lp/services/LpManagementService.ts +632 -0
- package/src/lp/services/UserLpProfileService.ts +163 -0
- package/src/lp/services/VaultService.ts +153 -0
- package/src/lp/services/YieldOptimizationService.ts +344 -0
- package/src/lp/services/__tests__/MockLpService.ts +146 -0
- package/src/lp/tasks/LpAutoRebalanceTask.ts +117 -0
- package/src/lp/tasks/__tests__/LpAutoRebalanceTask.test.ts +370 -0
- package/src/lp/types.ts +582 -0
- package/src/lp/utils/solanaClient.ts +143 -0
- package/src/plugin.ts +125 -0
- package/src/policy/policy.ts +19 -0
- package/src/providers/canonical-provider.ts +27 -0
- package/src/providers/unified-wallet-provider.ts +79 -0
- package/src/register-routes.ts +11 -0
- package/src/routes/plugin.ts +47 -0
- package/src/routes/wallet-market-overview-route.ts +869 -0
- package/src/sdk/abi.ts +258 -0
- package/src/sdk/bridge/abis.ts +126 -0
- package/src/sdk/bridge/client.ts +518 -0
- package/src/sdk/bridge/index.ts +56 -0
- package/src/sdk/bridge/solana.ts +604 -0
- package/src/sdk/bridge/types.ts +202 -0
- package/src/sdk/convenience.ts +347 -0
- package/src/sdk/escrow/MutualStakeEscrow.ts +480 -0
- package/src/sdk/escrow/types.ts +64 -0
- package/src/sdk/escrow/verifiers.ts +73 -0
- package/src/sdk/identity/erc8004.ts +692 -0
- package/src/sdk/identity/reputation.ts +449 -0
- package/src/sdk/identity/uaid.ts +497 -0
- package/src/sdk/identity/validation.ts +372 -0
- package/src/sdk/index.ts +763 -0
- package/src/sdk/policy/SpendingPolicy.ts +260 -0
- package/src/sdk/policy/UptoBillingPolicy.ts +320 -0
- package/src/sdk/router/PaymentRouter.ts +215 -0
- package/src/sdk/router/index.ts +8 -0
- package/src/sdk/swap/SwapModule.ts +310 -0
- package/src/sdk/swap/abi.ts +117 -0
- package/src/sdk/swap/index.ts +34 -0
- package/src/sdk/swap/types.ts +135 -0
- package/src/sdk/tokens/decimals.ts +140 -0
- package/src/sdk/tokens/registry.ts +911 -0
- package/src/sdk/tokens/solana.ts +419 -0
- package/src/sdk/tokens/transfers.ts +327 -0
- package/src/sdk/types.ts +158 -0
- package/src/sdk/wallet-core.ts +115 -0
- package/src/sdk/x402/budget.ts +168 -0
- package/src/sdk/x402/chains/abstract/index.ts +280 -0
- package/src/sdk/x402/client.ts +320 -0
- package/src/sdk/x402/index.ts +46 -0
- package/src/sdk/x402/middleware.ts +92 -0
- package/src/sdk/x402/multi-asset.ts +144 -0
- package/src/sdk/x402/types.ts +156 -0
- package/src/services/wallet-backend-service.ts +328 -0
- package/src/types/wallet-router.ts +227 -0
- package/src/utils/intent-trajectory.ts +106 -0
- package/src/wallet/backend.ts +62 -0
- package/src/wallet/errors.ts +49 -0
- package/src/wallet/index.ts +27 -0
- package/src/wallet/local-eoa-backend.ts +201 -0
- package/src/wallet/pending.ts +60 -0
- package/src/wallet/select-backend.ts +47 -0
- package/src/wallet/steward-backend.ts +161 -0
- package/src/wallet-action.ts +1 -0
|
@@ -0,0 +1,700 @@
|
|
|
1
|
+
// @ts-nocheck ā legacy code from absorbed plugins (lp-manager, lpinfo, dexscreener, defi-news, birdeye); strict types pending cleanup
|
|
2
|
+
import { logger } from "@elizaos/core";
|
|
3
|
+
import type { BirdeyeApiParams } from "./types/api/common";
|
|
4
|
+
import type {
|
|
5
|
+
TokenMarketSearchResponse,
|
|
6
|
+
TokenResult,
|
|
7
|
+
} from "./types/api/search";
|
|
8
|
+
import type { TokenMetadataSingleResponse } from "./types/api/token";
|
|
9
|
+
import type { BaseAddress, BirdeyeSupportedChain } from "./types/shared";
|
|
10
|
+
|
|
11
|
+
// Constants
|
|
12
|
+
export const BASE_URL = "https://public-api.birdeye.so";
|
|
13
|
+
|
|
14
|
+
export const BIRDEYE_SUPPORTED_CHAINS = [
|
|
15
|
+
"solana",
|
|
16
|
+
"ethereum",
|
|
17
|
+
"arbitrum",
|
|
18
|
+
"avalanche",
|
|
19
|
+
"bsc",
|
|
20
|
+
"optimism",
|
|
21
|
+
"polygon",
|
|
22
|
+
"base",
|
|
23
|
+
"zksync",
|
|
24
|
+
"sui",
|
|
25
|
+
"evm", // EVM-compatible chains but we don't know the chain
|
|
26
|
+
] as const;
|
|
27
|
+
|
|
28
|
+
// Chain abbreviations and alternative names mapping
|
|
29
|
+
export const CHAIN_ALIASES: Record<string, BirdeyeSupportedChain> = {
|
|
30
|
+
// Solana
|
|
31
|
+
sol: "solana",
|
|
32
|
+
|
|
33
|
+
// Ethereum
|
|
34
|
+
eth: "ethereum",
|
|
35
|
+
ether: "ethereum",
|
|
36
|
+
|
|
37
|
+
// Arbitrum
|
|
38
|
+
arb: "arbitrum",
|
|
39
|
+
arbitrumone: "arbitrum",
|
|
40
|
+
|
|
41
|
+
// Avalanche
|
|
42
|
+
avax: "avalanche",
|
|
43
|
+
|
|
44
|
+
// BSC
|
|
45
|
+
bnb: "bsc",
|
|
46
|
+
binance: "bsc",
|
|
47
|
+
"binance smart chain": "bsc",
|
|
48
|
+
|
|
49
|
+
// Optimism
|
|
50
|
+
op: "optimism",
|
|
51
|
+
opti: "optimism",
|
|
52
|
+
|
|
53
|
+
// Polygon
|
|
54
|
+
matic: "polygon",
|
|
55
|
+
poly: "polygon",
|
|
56
|
+
|
|
57
|
+
// Base
|
|
58
|
+
// no common abbreviations
|
|
59
|
+
|
|
60
|
+
// zkSync
|
|
61
|
+
zks: "zksync",
|
|
62
|
+
zk: "zksync",
|
|
63
|
+
|
|
64
|
+
// Sui
|
|
65
|
+
// no common abbreviations
|
|
66
|
+
} as const;
|
|
67
|
+
|
|
68
|
+
export class BirdeyeApiError extends Error {
|
|
69
|
+
constructor(
|
|
70
|
+
public status: number,
|
|
71
|
+
message: string,
|
|
72
|
+
) {
|
|
73
|
+
super(message);
|
|
74
|
+
this.name = "BirdeyeApiError";
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export interface ApiResponse<T> {
|
|
79
|
+
success: boolean;
|
|
80
|
+
data: T;
|
|
81
|
+
error?: string;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Time-related types and constants
|
|
85
|
+
export const TIME_UNITS = {
|
|
86
|
+
second: 1,
|
|
87
|
+
minute: 60,
|
|
88
|
+
hour: 3600,
|
|
89
|
+
day: 86400,
|
|
90
|
+
week: 604800,
|
|
91
|
+
month: 2592000,
|
|
92
|
+
} as const;
|
|
93
|
+
|
|
94
|
+
export const TIMEFRAME_KEYWORDS = {
|
|
95
|
+
"1m": 60,
|
|
96
|
+
"3m": 180,
|
|
97
|
+
"5m": 300,
|
|
98
|
+
"15m": 900,
|
|
99
|
+
"30m": 1800,
|
|
100
|
+
"1h": 3600,
|
|
101
|
+
"2h": 7200,
|
|
102
|
+
"4h": 14400,
|
|
103
|
+
"6h": 21600,
|
|
104
|
+
"12h": 43200,
|
|
105
|
+
"1d": 86400,
|
|
106
|
+
"1w": 604800,
|
|
107
|
+
} as const;
|
|
108
|
+
|
|
109
|
+
export type TimeUnit = keyof typeof TIME_UNITS;
|
|
110
|
+
export type Timeframe = keyof typeof TIMEFRAME_KEYWORDS;
|
|
111
|
+
|
|
112
|
+
// Helper functions
|
|
113
|
+
/**
|
|
114
|
+
* Extract blockchain chain from address with optional explicit chain setting
|
|
115
|
+
* @param text - The wallet address to analyze (optional if explicitChain is provided)
|
|
116
|
+
* @param explicitChain - Optional explicit chain specification (from BIRDEYE_CHAIN setting)
|
|
117
|
+
* @returns The detected or specified chain
|
|
118
|
+
* @throws Error if address format is invalid or EVM address without explicit chain
|
|
119
|
+
*/
|
|
120
|
+
export const extractChain = (
|
|
121
|
+
text?: string,
|
|
122
|
+
explicitChain?: string,
|
|
123
|
+
): BirdeyeSupportedChain => {
|
|
124
|
+
// First, check for explicit chain setting
|
|
125
|
+
if (explicitChain) {
|
|
126
|
+
const normalizedChain = explicitChain.toLowerCase();
|
|
127
|
+
// Check if it's a valid chain or alias
|
|
128
|
+
if (
|
|
129
|
+
BIRDEYE_SUPPORTED_CHAINS.includes(
|
|
130
|
+
normalizedChain as BirdeyeSupportedChain,
|
|
131
|
+
)
|
|
132
|
+
) {
|
|
133
|
+
return normalizedChain as BirdeyeSupportedChain;
|
|
134
|
+
}
|
|
135
|
+
// Check aliases
|
|
136
|
+
if (CHAIN_ALIASES[normalizedChain]) {
|
|
137
|
+
return CHAIN_ALIASES[normalizedChain];
|
|
138
|
+
}
|
|
139
|
+
throw new Error(
|
|
140
|
+
`Invalid chain: "${explicitChain}". Must be one of: ${BIRDEYE_SUPPORTED_CHAINS.join(", ")}`,
|
|
141
|
+
);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// Validate input
|
|
145
|
+
if (!text || typeof text !== "string" || text.trim().length === 0) {
|
|
146
|
+
throw new Error("Invalid address: empty or non-string value provided");
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
const trimmedText = text.trim();
|
|
150
|
+
|
|
151
|
+
// Check for SUI address (0x followed by 64 hex chars)
|
|
152
|
+
if (trimmedText.match(/^0x[a-fA-F0-9]{64}$/)) {
|
|
153
|
+
return "sui";
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// Check for EVM address (0x followed by 40 hex chars)
|
|
157
|
+
if (trimmedText.match(/^0x[a-fA-F0-9]{40}$/)) {
|
|
158
|
+
// Build EVM chain list dynamically from supported chains (exclude solana and sui)
|
|
159
|
+
const evmChains = BIRDEYE_SUPPORTED_CHAINS.filter(
|
|
160
|
+
(chain) => chain !== "solana" && chain !== "sui",
|
|
161
|
+
).join(", ");
|
|
162
|
+
throw new Error(
|
|
163
|
+
`EVM address detected but specific chain unknown. Please set BIRDEYE_CHAIN environment variable to one of: ${evmChains}`,
|
|
164
|
+
);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// Check for Solana address (base58, typically 32-44 chars, no 0x prefix)
|
|
168
|
+
if (trimmedText.match(/^[1-9A-HJ-NP-Za-km-z]{32,44}$/)) {
|
|
169
|
+
return "solana";
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// Invalid address format
|
|
173
|
+
throw new Error(
|
|
174
|
+
`Invalid address format: "${trimmedText}". Expected Solana (base58), EVM (0x + 40 hex), or Sui (0x + 64 hex) address.`,
|
|
175
|
+
);
|
|
176
|
+
};
|
|
177
|
+
|
|
178
|
+
export const extractAddresses = (text: string): BaseAddress[] => {
|
|
179
|
+
if (!text?.match) return [];
|
|
180
|
+
const addresses: BaseAddress[] = [];
|
|
181
|
+
|
|
182
|
+
// Sui addresses (0x followed by 64 hex chars). Extract first so the EVM
|
|
183
|
+
// matcher does not take the 40-char prefix of a Sui address.
|
|
184
|
+
const suiAddresses = text.match(/0x[a-fA-F0-9]{64}(?![a-fA-F0-9])/g);
|
|
185
|
+
if (suiAddresses) {
|
|
186
|
+
addresses.push(
|
|
187
|
+
...suiAddresses.map((address) => ({
|
|
188
|
+
address,
|
|
189
|
+
chain: "sui" as BirdeyeSupportedChain,
|
|
190
|
+
})),
|
|
191
|
+
);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// EVM-compatible chains (Ethereum, Arbitrum, Avalanche, BSC, Optimism, Polygon, Base, zkSync)
|
|
195
|
+
const evmAddresses = text.match(/0x[a-fA-F0-9]{40}(?![a-fA-F0-9])/g);
|
|
196
|
+
if (evmAddresses) {
|
|
197
|
+
addresses.push(
|
|
198
|
+
...evmAddresses
|
|
199
|
+
.filter(
|
|
200
|
+
(address) =>
|
|
201
|
+
!addresses.some(
|
|
202
|
+
(existing) =>
|
|
203
|
+
existing.chain === "sui" &&
|
|
204
|
+
existing.address.startsWith(address),
|
|
205
|
+
),
|
|
206
|
+
)
|
|
207
|
+
.map((address) => ({
|
|
208
|
+
address,
|
|
209
|
+
chain: "evm" as BirdeyeSupportedChain, // we don't yet know the chain but can assume it's EVM-compatible
|
|
210
|
+
})),
|
|
211
|
+
);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// Solana addresses (base58 strings)
|
|
215
|
+
const solAddresses = Array.from(text.matchAll(/[1-9A-HJ-NP-Za-km-z]{32,44}/g))
|
|
216
|
+
.filter((match) => {
|
|
217
|
+
const start = match.index ?? 0;
|
|
218
|
+
const end = start + match[0].length;
|
|
219
|
+
return (
|
|
220
|
+
!/[A-Za-z0-9]/.test(text[start - 1] ?? "") &&
|
|
221
|
+
!/[A-Za-z0-9]/.test(text[end] ?? "")
|
|
222
|
+
);
|
|
223
|
+
})
|
|
224
|
+
.map((match) => match[0]);
|
|
225
|
+
if (solAddresses) {
|
|
226
|
+
addresses.push(
|
|
227
|
+
...solAddresses.map((address) => ({
|
|
228
|
+
address,
|
|
229
|
+
chain: "solana" as BirdeyeSupportedChain,
|
|
230
|
+
})),
|
|
231
|
+
);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
return addresses;
|
|
235
|
+
};
|
|
236
|
+
|
|
237
|
+
// Time extraction and analysis
|
|
238
|
+
export const extractTimeframe = (text: string): Timeframe => {
|
|
239
|
+
// First, check for explicit timeframe mentions
|
|
240
|
+
const timeframe = Object.keys(TIMEFRAME_KEYWORDS).find((tf) =>
|
|
241
|
+
text.toLowerCase().includes(tf.toLowerCase()),
|
|
242
|
+
);
|
|
243
|
+
if (timeframe) return timeframe as Timeframe;
|
|
244
|
+
|
|
245
|
+
// Check for semantic timeframe hints
|
|
246
|
+
const semanticMap = {
|
|
247
|
+
"short term": "15m",
|
|
248
|
+
"medium term": "1h",
|
|
249
|
+
"long term": "1d",
|
|
250
|
+
intraday: "1h",
|
|
251
|
+
daily: "1d",
|
|
252
|
+
weekly: "1w",
|
|
253
|
+
detailed: "5m",
|
|
254
|
+
quick: "15m",
|
|
255
|
+
overview: "1d",
|
|
256
|
+
} as const;
|
|
257
|
+
|
|
258
|
+
for (const [hint, tf] of Object.entries(semanticMap)) {
|
|
259
|
+
if (text.toLowerCase().includes(hint)) {
|
|
260
|
+
return tf as Timeframe;
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// Analyze for time-related words
|
|
265
|
+
if (text.match(/minute|min|minutes/i)) return "15m";
|
|
266
|
+
if (text.match(/hour|hourly|hours/i)) return "1h";
|
|
267
|
+
if (text.match(/day|daily|24h/i)) return "1d";
|
|
268
|
+
if (text.match(/week|weekly/i)) return "1w";
|
|
269
|
+
|
|
270
|
+
// Default based on context
|
|
271
|
+
if (text.match(/trade|trades|trading|recent/i)) return "15m";
|
|
272
|
+
if (text.match(/trend|analysis|analyze/i)) return "1h";
|
|
273
|
+
if (text.match(/history|historical|long|performance/i)) return "1d";
|
|
274
|
+
|
|
275
|
+
return "1h"; // Default timeframe
|
|
276
|
+
};
|
|
277
|
+
|
|
278
|
+
export const extractTimeRange = (
|
|
279
|
+
text: string,
|
|
280
|
+
): { start: number; end: number } => {
|
|
281
|
+
const now = Math.floor(Date.now() / 1000);
|
|
282
|
+
|
|
283
|
+
// Check for specific date ranges
|
|
284
|
+
const dateRangeMatch = text.match(
|
|
285
|
+
/from\s+(\d{4}-\d{2}-\d{2})\s+to\s+(\d{4}-\d{2}-\d{2})/i,
|
|
286
|
+
);
|
|
287
|
+
if (dateRangeMatch) {
|
|
288
|
+
const start = new Date(dateRangeMatch[1]).getTime() / 1000;
|
|
289
|
+
const end = new Date(dateRangeMatch[2]).getTime() / 1000;
|
|
290
|
+
return { start, end };
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
// Check for relative time expressions
|
|
294
|
+
const timeRegex = /(\d+)\s*(second|minute|hour|day|week|month)s?\s*ago/i;
|
|
295
|
+
const match = text.match(timeRegex);
|
|
296
|
+
if (match) {
|
|
297
|
+
const amount = Number.parseInt(match[1], 10);
|
|
298
|
+
const unit = match[2].toLowerCase() as TimeUnit;
|
|
299
|
+
const start = now - amount * TIME_UNITS[unit];
|
|
300
|
+
return { start, end: now };
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
// Check for semantic time ranges
|
|
304
|
+
const semanticRanges: Record<string, number> = {
|
|
305
|
+
today: TIME_UNITS.day,
|
|
306
|
+
"this week": TIME_UNITS.week,
|
|
307
|
+
"this month": TIME_UNITS.month,
|
|
308
|
+
recent: TIME_UNITS.hour * 4,
|
|
309
|
+
latest: TIME_UNITS.hour,
|
|
310
|
+
"last hour": TIME_UNITS.hour,
|
|
311
|
+
"last day": TIME_UNITS.day,
|
|
312
|
+
"last week": TIME_UNITS.week,
|
|
313
|
+
"last month": TIME_UNITS.month,
|
|
314
|
+
};
|
|
315
|
+
|
|
316
|
+
for (const [range, duration] of Object.entries(semanticRanges)) {
|
|
317
|
+
if (text.toLowerCase().includes(range)) {
|
|
318
|
+
return { start: now - duration, end: now };
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
// Analyze context for appropriate default range
|
|
323
|
+
if (text.match(/trend|analysis|performance/i)) {
|
|
324
|
+
return { start: now - TIME_UNITS.week, end: now }; // 1 week for analysis
|
|
325
|
+
}
|
|
326
|
+
if (text.match(/trade|trades|trading|recent/i)) {
|
|
327
|
+
return { start: now - TIME_UNITS.day, end: now }; // 1 day for trading
|
|
328
|
+
}
|
|
329
|
+
if (text.match(/history|historical|long term/i)) {
|
|
330
|
+
return { start: now - TIME_UNITS.month, end: now }; // 1 month for history
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
// Default to last 24 hours
|
|
334
|
+
return { start: now - TIME_UNITS.day, end: now };
|
|
335
|
+
};
|
|
336
|
+
|
|
337
|
+
export const extractLimit = (text: string): number => {
|
|
338
|
+
// Check for explicit limit mentions
|
|
339
|
+
const limitMatch = text.match(/\b(show|display|get|fetch|limit)\s+(\d+)\b/i);
|
|
340
|
+
if (limitMatch) {
|
|
341
|
+
const limit = Number.parseInt(limitMatch[2], 10);
|
|
342
|
+
return Math.min(Math.max(limit, 1), 100); // Clamp between 1 and 100
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
// Check for semantic limit hints
|
|
346
|
+
if (text.match(/\b(all|everything|full|complete)\b/i)) return 100;
|
|
347
|
+
if (text.match(/\b(brief|quick|summary|overview)\b/i)) return 5;
|
|
348
|
+
if (text.match(/\b(detailed|comprehensive)\b/i)) return 50;
|
|
349
|
+
|
|
350
|
+
// Default based on context
|
|
351
|
+
if (text.match(/\b(trade|trades|trading)\b/i)) return 10;
|
|
352
|
+
if (text.match(/\b(analysis|analyze|trend)\b/i)) return 24;
|
|
353
|
+
if (text.match(/\b(history|historical)\b/i)) return 50;
|
|
354
|
+
|
|
355
|
+
return 10; // Default limit
|
|
356
|
+
};
|
|
357
|
+
|
|
358
|
+
// Formatting helpers
|
|
359
|
+
export const formatValue = (value?: number): string => {
|
|
360
|
+
if (!value) return "N/A";
|
|
361
|
+
if (value && value >= 1_000_000_000) {
|
|
362
|
+
return `$${(value / 1_000_000_000).toFixed(2)}B`;
|
|
363
|
+
}
|
|
364
|
+
if (value >= 1_000_000) {
|
|
365
|
+
return `$${(value / 1_000_000).toFixed(2)}M`;
|
|
366
|
+
}
|
|
367
|
+
if (value >= 1_000) {
|
|
368
|
+
return `$${(value / 1_000).toFixed(2)}K`;
|
|
369
|
+
}
|
|
370
|
+
return `$${value.toFixed(2)}`;
|
|
371
|
+
};
|
|
372
|
+
|
|
373
|
+
export const formatPercentChange = (change?: number): string => {
|
|
374
|
+
if (change === undefined) return "N/A";
|
|
375
|
+
const symbol = change >= 0 ? "ā" : "ā";
|
|
376
|
+
return `${symbol} ${Math.abs(change).toFixed(2)}%`;
|
|
377
|
+
};
|
|
378
|
+
|
|
379
|
+
export const shortenAddress = (address?: string): string => {
|
|
380
|
+
if (!address || address.length <= 12) return address || "Unknown";
|
|
381
|
+
return `${address.slice(0, 6)}...${address.slice(-4)}`;
|
|
382
|
+
};
|
|
383
|
+
|
|
384
|
+
export const formatTimestamp = (timestamp?: number): string => {
|
|
385
|
+
return timestamp ? new Date(timestamp * 1000).toLocaleString() : "N/A";
|
|
386
|
+
};
|
|
387
|
+
|
|
388
|
+
export const formatPrice = (price?: number): string => {
|
|
389
|
+
return price
|
|
390
|
+
? price < 0.01
|
|
391
|
+
? price.toExponential(2)
|
|
392
|
+
: price.toFixed(2)
|
|
393
|
+
: "N/A";
|
|
394
|
+
};
|
|
395
|
+
|
|
396
|
+
export const formatJsonScalar = (value: unknown): string => {
|
|
397
|
+
if (value === undefined || value === null || value === "") {
|
|
398
|
+
return "null";
|
|
399
|
+
}
|
|
400
|
+
if (typeof value === "number" || typeof value === "boolean") {
|
|
401
|
+
return String(value);
|
|
402
|
+
}
|
|
403
|
+
const normalized = String(value).replace(/\s+/g, " ").trim();
|
|
404
|
+
if (/^[A-Za-z0-9._/@:+-]+$/.test(normalized)) {
|
|
405
|
+
return normalized;
|
|
406
|
+
}
|
|
407
|
+
return `"${normalized.replace(/"/g, '\\"')}"`;
|
|
408
|
+
};
|
|
409
|
+
|
|
410
|
+
export const formatJsonTable = (
|
|
411
|
+
label: string,
|
|
412
|
+
rows: Array<Record<string, unknown>>,
|
|
413
|
+
fields: string[],
|
|
414
|
+
): string => {
|
|
415
|
+
const indent = label.match(/^\s*/)?.[0] ?? "";
|
|
416
|
+
if (!rows.length) {
|
|
417
|
+
return `${label}[0]: []`;
|
|
418
|
+
}
|
|
419
|
+
const lines = [`${label}[${rows.length}]{${fields.join(",")}}:`];
|
|
420
|
+
for (const row of rows) {
|
|
421
|
+
lines.push(
|
|
422
|
+
`${indent} - ${fields.map((field) => formatJsonScalar(row[field])).join(",")}`,
|
|
423
|
+
);
|
|
424
|
+
}
|
|
425
|
+
return lines.join("\n");
|
|
426
|
+
};
|
|
427
|
+
|
|
428
|
+
// API helpers
|
|
429
|
+
export async function makeApiRequest<T>(
|
|
430
|
+
url: string,
|
|
431
|
+
options: {
|
|
432
|
+
apiKey: string;
|
|
433
|
+
chain?: BirdeyeSupportedChain;
|
|
434
|
+
method?: "GET" | "POST";
|
|
435
|
+
body?: unknown;
|
|
436
|
+
},
|
|
437
|
+
): Promise<T> {
|
|
438
|
+
const { apiKey, chain = "solana", method = "GET", body } = options;
|
|
439
|
+
|
|
440
|
+
try {
|
|
441
|
+
const response = await fetch(url, {
|
|
442
|
+
method,
|
|
443
|
+
headers: {
|
|
444
|
+
"X-API-KEY": apiKey,
|
|
445
|
+
"x-chain": chain,
|
|
446
|
+
...(body && { "Content-Type": "application/json" }),
|
|
447
|
+
},
|
|
448
|
+
...(body && { body: JSON.stringify(body) }),
|
|
449
|
+
});
|
|
450
|
+
|
|
451
|
+
if (!response.ok) {
|
|
452
|
+
if (response.status === 404) {
|
|
453
|
+
throw new BirdeyeApiError(404, "Resource not found");
|
|
454
|
+
}
|
|
455
|
+
if (response.status === 429) {
|
|
456
|
+
throw new BirdeyeApiError(429, "Rate limit exceeded");
|
|
457
|
+
}
|
|
458
|
+
throw new BirdeyeApiError(
|
|
459
|
+
response.status,
|
|
460
|
+
`HTTP error! status: ${response.status}`,
|
|
461
|
+
);
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
const responseJson: T = await response.json();
|
|
465
|
+
|
|
466
|
+
return responseJson;
|
|
467
|
+
} catch (error) {
|
|
468
|
+
if (error instanceof BirdeyeApiError) {
|
|
469
|
+
logger.error(`API Error (${error.status}):`, error.message);
|
|
470
|
+
} else {
|
|
471
|
+
logger.error({ error }, "Error making API request:");
|
|
472
|
+
}
|
|
473
|
+
throw error;
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
// Formatting helpers
|
|
478
|
+
export const formatTokenInfo = (
|
|
479
|
+
token: TokenResult,
|
|
480
|
+
metadata?: TokenMetadataSingleResponse,
|
|
481
|
+
): string => {
|
|
482
|
+
const priceFormatted =
|
|
483
|
+
token.price != null
|
|
484
|
+
? token.price < 0.01
|
|
485
|
+
? token.price.toExponential(2)
|
|
486
|
+
: token.price.toFixed(2)
|
|
487
|
+
: "N/A";
|
|
488
|
+
|
|
489
|
+
const volume =
|
|
490
|
+
token.volume_24h_usd != null
|
|
491
|
+
? `$${(token.volume_24h_usd / 1_000_000).toFixed(2)}M`
|
|
492
|
+
: "N/A";
|
|
493
|
+
|
|
494
|
+
const liquidity =
|
|
495
|
+
token.liquidity != null
|
|
496
|
+
? `$${(token.liquidity / 1_000_000).toFixed(2)}M`
|
|
497
|
+
: "N/A";
|
|
498
|
+
|
|
499
|
+
const fdv =
|
|
500
|
+
token.fdv != null ? `$${(token.fdv / 1_000_000).toFixed(2)}M` : "N/A";
|
|
501
|
+
|
|
502
|
+
const priceChange =
|
|
503
|
+
token.price_change_24h_percent != null
|
|
504
|
+
? `${token.price_change_24h_percent > 0 ? "+" : ""}${token.price_change_24h_percent.toFixed(2)}%`
|
|
505
|
+
: "N/A";
|
|
506
|
+
|
|
507
|
+
const trades = token.trade_24h != null ? token.trade_24h.toString() : "N/A";
|
|
508
|
+
|
|
509
|
+
const age = token.creation_time
|
|
510
|
+
? `${Math.floor((Date.now() - new Date(token.creation_time).getTime()) / (1000 * 60 * 60 * 24))}d`
|
|
511
|
+
: "N/A";
|
|
512
|
+
|
|
513
|
+
let output =
|
|
514
|
+
`šŖ ${token.name} @ ${token.symbol}\n` +
|
|
515
|
+
`š° USD: $${priceFormatted} (${priceChange})\n` +
|
|
516
|
+
`š FDV: ${fdv}\n` +
|
|
517
|
+
`š¦ MCap: ${token.market_cap ? `$${(token.market_cap / 1_000_000).toFixed(2)}M` : "N/A"}\n` +
|
|
518
|
+
`š¦ Liq: ${liquidity}\n` +
|
|
519
|
+
`š Vol: ${volume}\n` +
|
|
520
|
+
`š°ļø Age: ${age}\n` +
|
|
521
|
+
`š Trades: ${trades}\n` +
|
|
522
|
+
`š Address: ${token.address}`;
|
|
523
|
+
|
|
524
|
+
// Add metadata if available
|
|
525
|
+
if (metadata?.success) {
|
|
526
|
+
const { extensions } = metadata.data;
|
|
527
|
+
const links: string[] = [];
|
|
528
|
+
|
|
529
|
+
if (extensions) {
|
|
530
|
+
if (extensions.website) links.push(`š [Website](${extensions.website})`);
|
|
531
|
+
if (extensions.twitter) links.push(`š¦ [Twitter](${extensions.twitter})`);
|
|
532
|
+
if (extensions.discord) links.push(`š¬ [Discord](${extensions.discord})`);
|
|
533
|
+
if (extensions.medium) links.push(`š [Medium](${extensions.medium})`);
|
|
534
|
+
if (extensions.coingecko_id)
|
|
535
|
+
links.push(
|
|
536
|
+
`š¦ [CoinGecko](https://www.coingecko.com/en/coins/${extensions.coingecko_id})`,
|
|
537
|
+
);
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
if (links.length > 0) {
|
|
541
|
+
output += `\n\nš± Social Links:\n${links.join("\n")}`;
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
return output;
|
|
546
|
+
};
|
|
547
|
+
|
|
548
|
+
// Extract symbols from text
|
|
549
|
+
export const extractSymbols = (
|
|
550
|
+
text: string,
|
|
551
|
+
// loose mode will try to extract more symbols but may include false positives
|
|
552
|
+
// strict mode will only extract symbols that are clearly formatted as a symbol using $SOL format
|
|
553
|
+
mode: "strict" | "loose" = "loose",
|
|
554
|
+
): string[] => {
|
|
555
|
+
if (!text?.matchAll) return [];
|
|
556
|
+
const symbols = new Set<string>();
|
|
557
|
+
|
|
558
|
+
// Match patterns
|
|
559
|
+
const patterns =
|
|
560
|
+
mode === "strict"
|
|
561
|
+
? [
|
|
562
|
+
// $SYMBOL format (case-insensitive due to 'i' flag)
|
|
563
|
+
/\$([A-Z0-9]{2,10})\b/gi,
|
|
564
|
+
]
|
|
565
|
+
: [
|
|
566
|
+
// $SYMBOL format
|
|
567
|
+
/\$([A-Z0-9]{2,10})\b/gi,
|
|
568
|
+
// After articles (a/an)
|
|
569
|
+
/\b(?:a|an)\s+([A-Z0-9]{2,10})\b/gi,
|
|
570
|
+
// // Standalone caps
|
|
571
|
+
/\b[A-Z0-9]{2,10}\b/g,
|
|
572
|
+
// // Quoted symbols
|
|
573
|
+
/["']([A-Z0-9]{2,10})["']/gi,
|
|
574
|
+
// // Common price patterns
|
|
575
|
+
/\b([A-Z0-9]{2,10})\/USD\b/gi,
|
|
576
|
+
/\b([A-Z0-9]{2,10})-USD\b/gi,
|
|
577
|
+
];
|
|
578
|
+
|
|
579
|
+
// Extract all matches
|
|
580
|
+
patterns.forEach((pattern) => {
|
|
581
|
+
const matches = Array.from(text.matchAll(pattern));
|
|
582
|
+
for (const match of matches) {
|
|
583
|
+
const symbol = (match[1] || match[0]).toUpperCase();
|
|
584
|
+
symbols.add(symbol);
|
|
585
|
+
}
|
|
586
|
+
});
|
|
587
|
+
|
|
588
|
+
return Array.from(symbols);
|
|
589
|
+
};
|
|
590
|
+
|
|
591
|
+
export const formatMetadataResponse = (
|
|
592
|
+
data: TokenMetadataSingleResponse,
|
|
593
|
+
chain: BirdeyeSupportedChain,
|
|
594
|
+
): string => {
|
|
595
|
+
const tokenData = data.data;
|
|
596
|
+
const chainName = chain.charAt(0).toUpperCase() + chain.slice(1);
|
|
597
|
+
const chainExplorer = (() => {
|
|
598
|
+
switch (chain) {
|
|
599
|
+
case "solana":
|
|
600
|
+
return `https://solscan.io/token/${tokenData.address}`;
|
|
601
|
+
case "ethereum":
|
|
602
|
+
return `https://etherscan.io/token/${tokenData.address}`;
|
|
603
|
+
case "arbitrum":
|
|
604
|
+
return `https://arbiscan.io/token/${tokenData.address}`;
|
|
605
|
+
case "avalanche":
|
|
606
|
+
return `https://snowtrace.io/token/${tokenData.address}`;
|
|
607
|
+
case "bsc":
|
|
608
|
+
return `https://bscscan.com/token/${tokenData.address}`;
|
|
609
|
+
case "optimism":
|
|
610
|
+
return `https://optimistic.etherscan.io/token/${tokenData.address}`;
|
|
611
|
+
case "polygon":
|
|
612
|
+
return `https://polygonscan.com/token/${tokenData.address}`;
|
|
613
|
+
case "base":
|
|
614
|
+
return `https://basescan.org/token/${tokenData.address}`;
|
|
615
|
+
case "zksync":
|
|
616
|
+
return `https://explorer.zksync.io/address/${tokenData.address}`;
|
|
617
|
+
case "sui":
|
|
618
|
+
return `https://suiscan.xyz/mainnet/object/${tokenData.address}`;
|
|
619
|
+
default:
|
|
620
|
+
return null;
|
|
621
|
+
}
|
|
622
|
+
})();
|
|
623
|
+
|
|
624
|
+
let response = `Token Metadata for ${tokenData.name} (${tokenData.symbol}) on ${chainName}\n\n`;
|
|
625
|
+
|
|
626
|
+
// Basic Information
|
|
627
|
+
response += "š Basic Information\n";
|
|
628
|
+
response += `⢠Name: ${tokenData.name}\n`;
|
|
629
|
+
response += `⢠Symbol: ${tokenData.symbol}\n`;
|
|
630
|
+
response += `⢠Address: ${tokenData.address}\n`;
|
|
631
|
+
response += `⢠Decimals: ${tokenData.decimals}\n`;
|
|
632
|
+
if (chainExplorer) {
|
|
633
|
+
response += `⢠Explorer: [View on ${chainName} Explorer](${chainExplorer})\n`;
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
// Social Links
|
|
637
|
+
response += "\nš Social Links & Extensions\n";
|
|
638
|
+
response += `${formatSocialLinks(tokenData)}\n`;
|
|
639
|
+
|
|
640
|
+
// Logo
|
|
641
|
+
if (tokenData.logo_uri) {
|
|
642
|
+
response += "\nš¼ļø Logo\n";
|
|
643
|
+
response += tokenData.logo_uri;
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
return response;
|
|
647
|
+
};
|
|
648
|
+
|
|
649
|
+
const formatSocialLinks = (
|
|
650
|
+
data: TokenMetadataSingleResponse["data"],
|
|
651
|
+
): string => {
|
|
652
|
+
const links: string[] = [];
|
|
653
|
+
const { extensions } = data;
|
|
654
|
+
|
|
655
|
+
if (!extensions) {
|
|
656
|
+
return "No social links available";
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
if (extensions.website) {
|
|
660
|
+
links.push(`š [Website](${extensions.website})`);
|
|
661
|
+
}
|
|
662
|
+
if (extensions.twitter) {
|
|
663
|
+
links.push(`š¦ [Twitter](${extensions.twitter})`);
|
|
664
|
+
}
|
|
665
|
+
if (extensions.discord) {
|
|
666
|
+
links.push(`š¬ [Discord](${extensions.discord})`);
|
|
667
|
+
}
|
|
668
|
+
if (extensions.medium) {
|
|
669
|
+
links.push(`š [Medium](${extensions.medium})`);
|
|
670
|
+
}
|
|
671
|
+
if (extensions.coingecko_id) {
|
|
672
|
+
links.push(
|
|
673
|
+
`š¦ [CoinGecko](https://www.coingecko.com/en/coins/${extensions.coingecko_id})`,
|
|
674
|
+
);
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
return links.length > 0 ? links.join("\n") : "No social links available";
|
|
678
|
+
};
|
|
679
|
+
|
|
680
|
+
export const waitFor = (ms: number) =>
|
|
681
|
+
new Promise((resolve) => setTimeout(resolve, ms));
|
|
682
|
+
|
|
683
|
+
export const convertToStringParams = (
|
|
684
|
+
params: BirdeyeApiParams,
|
|
685
|
+
): Record<string, string> => {
|
|
686
|
+
const result: Record<string, string> = {};
|
|
687
|
+
for (const [key, value] of Object.entries(params || {})) {
|
|
688
|
+
result[key] = value?.toString() || "";
|
|
689
|
+
}
|
|
690
|
+
return result;
|
|
691
|
+
};
|
|
692
|
+
|
|
693
|
+
export const getTokenResultFromSearchResponse = (
|
|
694
|
+
response: TokenMarketSearchResponse,
|
|
695
|
+
): TokenResult[] | undefined => {
|
|
696
|
+
return response.data.items
|
|
697
|
+
.filter((item) => item.type === "token")
|
|
698
|
+
.flatMap((item) => item.result)
|
|
699
|
+
.filter((result): result is TokenResult => result !== undefined);
|
|
700
|
+
};
|