feat(detection): implement multi-page token detection system

- Add extractTwitterTokens() to detect $TOKEN mentions (e.g., $BONK, $SOL)
- Add extractContractAddresses() for Solana (base58) and Ethereum (0x) addresses
- Add extractTradingPairs() to detect TOKEN/SOL, TOKEN/USDT patterns
- Update extractPageContext() to use new detection functions
- Add detectedTokens field to PageContext interface
- Create DetectedTokensList component to display detected tokens
- Integrate DetectedTokensList into ChatInterface
- Add handleDetectedTokenClick to analyze selected tokens
- Support auto-detection on Twitter, generic pages, and DexScreener

Implements Task 2: Multi-Page Token Detection
Part of hybrid token detection system (manual search + auto-detect)
This commit is contained in:
API Test Bot 2026-02-04 10:55:49 +07:00
parent cb879fca37
commit e89824db0f
4 changed files with 251 additions and 0 deletions

View file

@ -82,6 +82,122 @@ function extractDexScreenerData(): TokenData | undefined {
};
}
/**
* Extract token mentions from Twitter/X
* Detects $TOKEN format (e.g., $BONK, $SOL)
*/
function extractTwitterTokens(): TokenData[] {
const tokens: TokenData[] = [];
const pageText = document.body.innerText;
// Match $TOKEN pattern (e.g., $BONK, $SOL, $PEPE)
const tokenPattern = /\$([A-Z]{2,10})\b/g;
const matches = pageText.matchAll(tokenPattern);
const uniqueTokens = new Set<string>();
for (const match of matches) {
const symbol = match[1];
if (!uniqueTokens.has(symbol)) {
uniqueTokens.add(symbol);
tokens.push({
chain: "solana", // Default to Solana, can be enhanced
pairAddress: "", // Will be resolved via API
tokenSymbol: symbol,
});
}
}
return tokens;
}
/**
* Extract contract addresses from page content
* Supports Solana and Ethereum address formats
*/
function extractContractAddresses(): TokenData[] {
const tokens: TokenData[] = [];
const pageText = document.body.innerText;
// Solana address pattern (base58, 32-44 characters)
const solanaPattern = /\b([1-9A-HJ-NP-Za-km-z]{32,44})\b/g;
// Ethereum address pattern (0x followed by 40 hex characters)
const ethPattern = /\b(0x[a-fA-F0-9]{40})\b/g;
// Extract Ethereum addresses
const ethMatches = pageText.matchAll(ethPattern);
for (const match of ethMatches) {
const address = match[1];
tokens.push({
chain: "ethereum",
pairAddress: address,
tokenSymbol: undefined,
});
}
// Extract Solana addresses (more selective to avoid false positives)
const solanaMatches = pageText.matchAll(solanaPattern);
const uniqueSolanaAddresses = new Set<string>();
for (const match of solanaMatches) {
const address = match[1];
// Basic validation: should not be all same character, should have variety
if (address.length >= 32 &&
address.length <= 44 &&
new Set(address).size > 10 &&
!uniqueSolanaAddresses.has(address)) {
uniqueSolanaAddresses.add(address);
tokens.push({
chain: "solana",
pairAddress: address,
tokenSymbol: undefined,
});
}
}
return tokens.slice(0, 5); // Limit to first 5 to avoid spam
}
/**
* Extract trading pairs from page content
* Detects patterns like TOKEN/SOL, TOKEN/USDT, etc.
*/
function extractTradingPairs(): TokenData[] {
const tokens: TokenData[] = [];
const pageText = document.body.innerText;
// Match trading pair patterns (e.g., BONK/SOL, PEPE/USDT)
const pairPattern = /\b([A-Z]{2,10})\/([A-Z]{2,10})\b/g;
const matches = pageText.matchAll(pairPattern);
const uniquePairs = new Set<string>();
for (const match of matches) {
const baseToken = match[1];
const quoteToken = match[2];
const pairKey = `${baseToken}/${quoteToken}`;
if (!uniquePairs.has(pairKey)) {
uniquePairs.add(pairKey);
tokens.push({
chain: "solana", // Default to Solana
pairAddress: "", // Will be resolved via API
tokenSymbol: baseToken,
});
}
}
return tokens.slice(0, 3); // Limit to first 3 pairs
}
interface PageContext {
url: string;
title: string;
pageType: PageType;
tokenData?: TokenData;
/** Detected tokens from page content (Twitter mentions, addresses, pairs) */
detectedTokens?: TokenData[];
}
/**
* Extract page context based on page type
*/
@ -99,6 +215,37 @@ function extractPageContext(): PageContext {
// Add page-specific data
if (pageType === "dexscreener") {
context.tokenData = extractDexScreenerData();
} else if (pageType === "twitter") {
// Extract Twitter token mentions
const twitterTokens = extractTwitterTokens();
const contractAddresses = extractContractAddresses();
const tradingPairs = extractTradingPairs();
// Combine all detected tokens
context.detectedTokens = [
...twitterTokens,
...contractAddresses,
...tradingPairs,
];
// Set primary token if available
if (context.detectedTokens.length > 0) {
context.tokenData = context.detectedTokens[0];
}
} else if (pageType === "generic") {
// For generic pages, try to detect contract addresses and trading pairs
const contractAddresses = extractContractAddresses();
const tradingPairs = extractTradingPairs();
context.detectedTokens = [
...contractAddresses,
...tradingPairs,
];
// Set primary token if available
if (context.detectedTokens.length > 0) {
context.tokenData = context.detectedTokens[0];
}
}
return context;