pump-analyzer-solana
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChinesePumpAnalyzer — Solana Token Monitoring Platform
PumpAnalyzer — Solana代币监控平台
Skill by ara.so — Daily 2026 Skills collection.
PumpAnalyzer is a static front-end platform (pure HTML/CSS/JS, no build tools) that provides real-time monitoring, analytics, and alerts for tokens launched on Pump.fun on the Solana blockchain. It connects to Pump.fun's WebSocket API for sub-second updates, displays price/volume charts, supports custom alert criteria, and includes non-custodial Solana wallet connection.
来自ara.so的技能 — 2026每日技能合集。
PumpAnalyzer是一个静态前端平台(纯HTML/CSS/JS,无需构建工具),为Solana区块链上Pump.fun平台发行的代币提供实时监控、分析与告警功能。它通过连接Pump.fun的WebSocket API实现亚秒级更新,展示价格/交易量图表,支持自定义告警规则,还集成了非托管Solana钱包连接功能。
Installation
安装
bash
git clone https://github.com/happyboy4ty25/pump-analyzer.git
cd pump-analyzer
open index.html # or use a local dev serverNo npm, no bundler, no dependencies — open directly in a browser or serve with any static file server:
index.htmlbash
undefinedbash
git clone https://github.com/happyboy4ty25/pump-analyzer.git
cd pump-analyzer
open index.html # 或使用本地开发服务器无需npm、无需打包工具、无依赖项 — 直接在浏览器中打开,或使用任何静态文件服务器运行:
index.htmlbash
undefinedPython
Python
python3 -m http.server 8080
python3 -m http.server 8080
Node (npx)
Node (npx)
npx serve .
npx serve .
VS Code
VS Code
Use the "Live Server" extension
使用「Live Server」扩展
---
---Project Structure
项目结构
pump-analyzer/
├── index.html # Main landing page & app shell
├── css/
│ └── style.css # All styles, animations, responsive layout
├── js/
│ ├── main.js # App init, UI interactions, animations
│ ├── websocket.js # Pump.fun WebSocket connection & event handling
│ ├── wallet.js # Solana wallet adapter (Phantom, Solflare, etc.)
│ ├── alerts.js # Custom alert criteria logic
│ └── charts.js # Price/volume chart rendering
└── assets/
└── ... # Icons, imagespump-analyzer/
├── index.html # 主页面与应用外壳
├── css/
│ └── style.css # 所有样式、动画、响应式布局
├── js/
│ ├── main.js # 应用初始化、UI交互、动画
│ ├── websocket.js # Pump.fun WebSocket连接与事件处理
│ ├── wallet.js # Solana钱包适配器(Phantom、Solflare等)
│ ├── alerts.js # 自定义告警规则逻辑
│ └── charts.js # 价格/交易量图表渲染
└── assets/
└── ... # 图标、图片Key Concepts & Architecture
核心概念与架构
1. Pump.fun WebSocket Connection
1. Pump.fun WebSocket连接
PumpAnalyzer subscribes to Pump.fun's real-time data stream. The core pattern:
javascript
// js/websocket.js
const PUMP_FUN_WS_URL = 'wss://pumpportal.fun/api/data';
class PumpWebSocket {
constructor(onToken, onTrade) {
this.onToken = onToken; // callback for new token launches
this.onTrade = onTrade; // callback for trade events
this.ws = null;
this.reconnectDelay = 1000;
}
connect() {
this.ws = new WebSocket(PUMP_FUN_WS_URL);
this.ws.addEventListener('open', () => {
console.log('[PumpWS] Connected');
this.reconnectDelay = 1000;
// Subscribe to new token creation events
this.ws.send(JSON.stringify({
method: 'subscribeNewToken'
}));
// Subscribe to all trades on new tokens
this.ws.send(JSON.stringify({
method: 'subscribeTokenTrade',
keys: [] // empty = all tokens
}));
});
this.ws.addEventListener('message', (event) => {
const data = JSON.parse(event.data);
if (data.txType === 'create') {
this.onToken(data);
} else if (data.txType === 'buy' || data.txType === 'sell') {
this.onTrade(data);
}
});
this.ws.addEventListener('close', () => {
console.warn('[PumpWS] Disconnected — reconnecting in', this.reconnectDelay, 'ms');
setTimeout(() => this.connect(), this.reconnectDelay);
this.reconnectDelay = Math.min(this.reconnectDelay * 2, 30000);
});
this.ws.addEventListener('error', (err) => {
console.error('[PumpWS] Error:', err);
this.ws.close();
});
}
// Subscribe to trades for a specific token mint
subscribeToken(mintAddress) {
if (this.ws?.readyState === WebSocket.OPEN) {
this.ws.send(JSON.stringify({
method: 'subscribeTokenTrade',
keys: [mintAddress]
}));
}
}
disconnect() {
this.ws?.close();
}
}
export default PumpWebSocket;PumpAnalyzer订阅Pump.fun的实时数据流,核心实现模式如下:
javascript
// js/websocket.js
const PUMP_FUN_WS_URL = 'wss://pumpportal.fun/api/data';
class PumpWebSocket {
constructor(onToken, onTrade) {
this.onToken = onToken; // 新代币发行的回调函数
this.onTrade = onTrade; // 交易事件的回调函数
this.ws = null;
this.reconnectDelay = 1000;
}
connect() {
this.ws = new WebSocket(PUMP_FUN_WS_URL);
this.ws.addEventListener('open', () => {
console.log('[PumpWS] Connected');
this.reconnectDelay = 1000;
// 订阅新代币创建事件
this.ws.send(JSON.stringify({
method: 'subscribeNewToken'
}));
// 订阅所有新代币的交易事件
this.ws.send(JSON.stringify({
method: 'subscribeTokenTrade',
keys: [] // 空数组表示订阅所有代币
}));
});
this.ws.addEventListener('message', (event) => {
const data = JSON.parse(event.data);
if (data.txType === 'create') {
this.onToken(data);
} else if (data.txType === 'buy' || data.txType === 'sell') {
this.onTrade(data);
}
});
this.ws.addEventListener('close', () => {
console.warn('[PumpWS] Disconnected — reconnecting in', this.reconnectDelay, 'ms');
setTimeout(() => this.connect(), this.reconnectDelay);
this.reconnectDelay = Math.min(this.reconnectDelay * 2, 30000);
});
this.ws.addEventListener('error', (err) => {
console.error('[PumpWS] Error:', err);
this.ws.close();
});
}
// 订阅特定代币铸币地址的交易
subscribeToken(mintAddress) {
if (this.ws?.readyState === WebSocket.OPEN) {
this.ws.send(JSON.stringify({
method: 'subscribeTokenTrade',
keys: [mintAddress]
}));
}
}
disconnect() {
this.ws?.close();
}
}
export default PumpWebSocket;2. Handling New Token Events
2. 处理新代币事件
javascript
// js/main.js
import PumpWebSocket from './websocket.js';
const tokenList = [];
function onNewToken(tokenData) {
// tokenData shape from Pump.fun:
// {
// signature: string,
// mint: string, // token mint address
// traderPublicKey: string,
// txType: 'create',
// name: string,
// symbol: string,
// description: string,
// imageUri: string,
// initialBuy: number, // SOL amount
// marketCapSol: number,
// uri: string,
// timestamp: number
// }
tokenList.unshift(tokenData);
renderTokenCard(tokenData);
checkAlerts(tokenData);
}
function onTrade(tradeData) {
// tradeData shape:
// {
// signature: string,
// mint: string,
// traderPublicKey: string,
// txType: 'buy' | 'sell',
// tokenAmount: number,
// solAmount: number,
// newTokenBalance: number,
// bondingCurveKey: string,
// vTokensInBondingCurve: number,
// vSolInBondingCurve: number,
// marketCapSol: number,
// timestamp: number
// }
updateTokenMetrics(tradeData.mint, tradeData);
}
const pumpWS = new PumpWebSocket(onNewToken, onTrade);
pumpWS.connect();javascript
// js/main.js
import PumpWebSocket from './websocket.js';
const tokenList = [];
function onNewToken(tokenData) {
// Pump.fun返回的tokenData结构:
// {
// signature: string,
// mint: string, // 代币铸币地址
// traderPublicKey: string,
// txType: 'create',
// name: string,
// symbol: string,
// description: string,
// imageUri: string,
// initialBuy: number, // SOL数量
// marketCapSol: number,
// uri: string,
// timestamp: number
// }
tokenList.unshift(tokenData);
renderTokenCard(tokenData);
checkAlerts(tokenData);
}
function onTrade(tradeData) {
// tradeData结构:
// {
// signature: string,
// mint: string,
// traderPublicKey: string,
// txType: 'buy' | 'sell',
// tokenAmount: number,
// solAmount: number,
// newTokenBalance: number,
// bondingCurveKey: string,
// vTokensInBondingCurve: number,
// vSolInBondingCurve: number,
// marketCapSol: number,
// timestamp: number
// }
updateTokenMetrics(tradeData.mint, tradeData);
}
const pumpWS = new PumpWebSocket(onNewToken, onTrade);
pumpWS.connect();3. Rendering Token Cards
3. 渲染代币卡片
javascript
// js/main.js
function renderTokenCard(token) {
const container = document.getElementById('token-feed');
const card = document.createElement('div');
card.className = 'token-card';
card.dataset.mint = token.mint;
card.innerHTML = `
<div class="token-header">
<img src="${token.imageUri || 'assets/placeholder.png'}"
alt="${token.symbol}"
class="token-image"
onerror="this.src='assets/placeholder.png'">
<div class="token-info">
<span class="token-name">${escapeHtml(token.name)}</span>
<span class="token-symbol">$${escapeHtml(token.symbol)}</span>
</div>
<span class="token-time">${formatTimestamp(token.timestamp)}</span>
</div>
<div class="token-metrics">
<div class="metric">
<label>Market Cap</label>
<span class="market-cap">${formatSol(token.marketCapSol)} SOL</span>
</div>
<div class="metric">
<label>Initial Buy</label>
<span>${formatSol(token.initialBuy)} SOL</span>
</div>
</div>
<div class="token-actions">
<a href="https://pump.fun/${token.mint}" target="_blank" rel="noopener"
class="btn btn-small">View on Pump.fun</a>
<button class="btn btn-small btn-outline"
onclick="setAlert('${token.mint}')">Set Alert</button>
</div>
`;
// Animate in
card.style.opacity = '0';
card.style.transform = 'translateY(-10px)';
container.prepend(card);
requestAnimationFrame(() => {
card.style.transition = 'opacity 0.3s, transform 0.3s';
card.style.opacity = '1';
card.style.transform = 'translateY(0)';
});
// Cap the list at 50 cards
while (container.children.length > 50) {
container.removeChild(container.lastChild);
}
}
function escapeHtml(str) {
const div = document.createElement('div');
div.textContent = str;
return div.innerHTML;
}
function formatSol(amount) {
return amount ? Number(amount).toFixed(2) : '0.00';
}
function formatTimestamp(ts) {
return new Date(ts * 1000).toLocaleTimeString();
}javascript
// js/main.js
function renderTokenCard(token) {
const container = document.getElementById('token-feed');
const card = document.createElement('div');
card.className = 'token-card';
card.dataset.mint = token.mint;
card.innerHTML = `
<div class="token-header">
<img src="${token.imageUri || 'assets/placeholder.png'}"
alt="${token.symbol}"
class="token-image"
onerror="this.src='assets/placeholder.png'">
<div class="token-info">
<span class="token-name">${escapeHtml(token.name)}</span>
<span class="token-symbol">$${escapeHtml(token.symbol)}</span>
</div>
<span class="token-time">${formatTimestamp(token.timestamp)}</span>
</div>
<div class="token-metrics">
<div class="metric">
<label>市值</label>
<span class="market-cap">${formatSol(token.marketCapSol)} SOL</span>
</div>
<div class="metric">
<label>初始买入额</label>
<span>${formatSol(token.initialBuy)} SOL</span>
</div>
</div>
<div class="token-actions">
<a href="https://pump.fun/${token.mint}" target="_blank" rel="noopener"
class="btn btn-small">在Pump.fun查看</a>
<button class="btn btn-small btn-outline"
onclick="setAlert('${token.mint}')">设置告警</button>
</div>
`;
// 入场动画
card.style.opacity = '0';
card.style.transform = 'translateY(-10px)';
container.prepend(card);
requestAnimationFrame(() => {
card.style.transition = 'opacity 0.3s, transform 0.3s';
card.style.opacity = '1';
card.style.transform = 'translateY(0)';
});
// 限制列表最多显示50张卡片
while (container.children.length > 50) {
container.removeChild(container.lastChild);
}
}
function escapeHtml(str) {
const div = document.createElement('div');
div.textContent = str;
return div.innerHTML;
}
function formatSol(amount) {
return amount ? Number(amount).toFixed(2) : '0.00';
}
function formatTimestamp(ts) {
return new Date(ts * 1000).toLocaleTimeString();
}4. Custom Alerts System
4. 自定义告警系统
javascript
// js/alerts.js
const MAX_FREE_ALERTS = 5;
class AlertManager {
constructor() {
this.alerts = JSON.parse(localStorage.getItem('pump_alerts') || '[]');
this.dailyCount = parseInt(localStorage.getItem('pump_alert_count') || '0');
this.plan = localStorage.getItem('pump_plan') || 'free';
}
canAddAlert() {
if (this.plan !== 'free') return true;
return this.dailyCount < MAX_FREE_ALERTS;
}
addAlert({ mint, criteria }) {
// criteria: { minMarketCap, maxMarketCap, minVolume, keywords }
if (!this.canAddAlert()) {
showUpgradeModal('You've reached the free plan limit of 5 alerts/day.');
return false;
}
const alert = { id: Date.now(), mint, criteria, active: true };
this.alerts.push(alert);
this._save();
return alert;
}
checkToken(tokenData) {
for (const alert of this.alerts) {
if (!alert.active) continue;
if (this._matches(tokenData, alert.criteria)) {
this._trigger(alert, tokenData);
}
}
}
_matches(token, criteria) {
if (criteria.minMarketCap && token.marketCapSol < criteria.minMarketCap) return false;
if (criteria.maxMarketCap && token.marketCapSol > criteria.maxMarketCap) return false;
if (criteria.keywords?.length) {
const text = `${token.name} ${token.symbol} ${token.description}`.toLowerCase();
if (!criteria.keywords.some(k => text.includes(k.toLowerCase()))) return false;
}
return true;
}
_trigger(alert, token) {
// Browser notification
if (Notification.permission === 'granted') {
new Notification(`🚨 Alert: ${token.name} ($${token.symbol})`, {
body: `Market cap: ${token.marketCapSol.toFixed(2)} SOL`,
icon: token.imageUri || 'assets/icon.png'
});
}
// In-app notification
showInAppAlert(token);
this.dailyCount++;
localStorage.setItem('pump_alert_count', this.dailyCount);
}
_save() {
localStorage.setItem('pump_alerts', JSON.stringify(this.alerts));
}
}
export const alertManager = new AlertManager();
// Request notification permission on load
if ('Notification' in window && Notification.permission === 'default') {
Notification.requestPermission();
}javascript
// js/alerts.js
const MAX_FREE_ALERTS = 5;
class AlertManager {
constructor() {
this.alerts = JSON.parse(localStorage.getItem('pump_alerts') || '[]');
this.dailyCount = parseInt(localStorage.getItem('pump_alert_count') || '0');
this.plan = localStorage.getItem('pump_plan') || 'free';
}
canAddAlert() {
if (this.plan !== 'free') return true;
return this.dailyCount < MAX_FREE_ALERTS;
}
addAlert({ mint, criteria }) {
// criteria: { minMarketCap, maxMarketCap, minVolume, keywords }
if (!this.canAddAlert()) {
showUpgradeModal('您已达到免费计划每日5条告警的上限。');
return false;
}
const alert = { id: Date.now(), mint, criteria, active: true };
this.alerts.push(alert);
this._save();
return alert;
}
checkToken(tokenData) {
for (const alert of this.alerts) {
if (!alert.active) continue;
if (this._matches(tokenData, alert.criteria)) {
this._trigger(alert, tokenData);
}
}
}
_matches(token, criteria) {
if (criteria.minMarketCap && token.marketCapSol < criteria.minMarketCap) return false;
if (criteria.maxMarketCap && token.marketCapSol > criteria.maxMarketCap) return false;
if (criteria.keywords?.length) {
const text = `${token.name} ${token.symbol} ${token.description}`.toLowerCase();
if (!criteria.keywords.some(k => text.includes(k.toLowerCase()))) return false;
}
return true;
}
_trigger(alert, token) {
// 浏览器通知
if (Notification.permission === 'granted') {
new Notification(`🚨 告警: ${token.name} ($${token.symbol})`, {
body: `市值: ${token.marketCapSol.toFixed(2)} SOL`,
icon: token.imageUri || 'assets/icon.png'
});
}
// 应用内通知
showInAppAlert(token);
this.dailyCount++;
localStorage.setItem('pump_alert_count', this.dailyCount);
}
_save() {
localStorage.setItem('pump_alerts', JSON.stringify(this.alerts));
}
}
export const alertManager = new AlertManager();
// 页面加载时请求通知权限
if ('Notification' in window && Notification.permission === 'default') {
Notification.requestPermission();
}5. Solana Wallet Connection (Non-Custodial)
5. Solana非托管钱包连接
javascript
// js/wallet.js
class SolanaWalletConnect {
constructor() {
this.publicKey = null;
this.provider = null;
}
getProvider() {
// Phantom
if ('phantom' in window && window.phantom?.solana?.isPhantom) {
return window.phantom.solana;
}
// Solflare
if ('solflare' in window && window.solflare?.isSolflare) {
return window.solflare;
}
return null;
}
async connect() {
this.provider = this.getProvider();
if (!this.provider) {
window.open('https://phantom.app/', '_blank');
throw new Error('No Solana wallet found. Please install Phantom.');
}
try {
const resp = await this.provider.connect();
this.publicKey = resp.publicKey.toString();
this._onConnected();
return this.publicKey;
} catch (err) {
if (err.code === 4001) {
throw new Error('Connection rejected by user.');
}
throw err;
}
}
async disconnect() {
await this.provider?.disconnect();
this.publicKey = null;
this._onDisconnected();
}
_onConnected() {
const btn = document.getElementById('wallet-btn');
if (btn) {
btn.textContent = `${this.publicKey.slice(0, 4)}...${this.publicKey.slice(-4)}`;
btn.classList.add('connected');
}
// Unlock plan features based on on-chain subscription (check via RPC)
this.checkSubscription();
}
_onDisconnected() {
const btn = document.getElementById('wallet-btn');
if (btn) {
btn.textContent = 'Connect Wallet';
btn.classList.remove('connected');
}
}
async checkSubscription() {
// Query your backend or on-chain program to verify subscription tier
const RPC = 'https://api.mainnet-beta.solana.com';
// ... implement based on your subscription contract
}
}
export const wallet = new SolanaWalletConnect();
// Wire up button
document.getElementById('wallet-btn')?.addEventListener('click', async () => {
try {
if (wallet.publicKey) {
await wallet.disconnect();
} else {
await wallet.connect();
}
} catch (err) {
console.error('Wallet error:', err.message);
showToast(err.message, 'error');
}
});javascript
// js/wallet.js
class SolanaWalletConnect {
constructor() {
this.publicKey = null;
this.provider = null;
}
getProvider() {
// Phantom钱包
if ('phantom' in window && window.phantom?.solana?.isPhantom) {
return window.phantom.solana;
}
// Solflare钱包
if ('solflare' in window && window.solflare?.isSolflare) {
return window.solflare;
}
return null;
}
async connect() {
this.provider = this.getProvider();
if (!this.provider) {
window.open('https://phantom.app/', '_blank');
throw new Error('未找到Solana钱包,请安装Phantom。');
}
try {
const resp = await this.provider.connect();
this.publicKey = resp.publicKey.toString();
this._onConnected();
return this.publicKey;
} catch (err) {
if (err.code === 4001) {
throw new Error('用户拒绝连接。');
}
throw err;
}
}
async disconnect() {
await this.provider?.disconnect();
this.publicKey = null;
this._onDisconnected();
}
_onConnected() {
const btn = document.getElementById('wallet-btn');
if (btn) {
btn.textContent = `${this.publicKey.slice(0, 4)}...${this.publicKey.slice(-4)}`;
btn.classList.add('connected');
}
// 根据链上订阅解锁高级功能(通过RPC查询)
this.checkSubscription();
}
_onDisconnected() {
const btn = document.getElementById('wallet-btn');
if (btn) {
btn.textContent = '连接钱包';
btn.classList.remove('connected');
}
}
async checkSubscription() {
// 查询后端或链上程序以验证订阅等级
const RPC = 'https://api.mainnet-beta.solana.com';
// ... 根据您的订阅合约实现逻辑
}
}
export const wallet = new SolanaWalletConnect();
// 绑定按钮事件
document.getElementById('wallet-btn')?.addEventListener('click', async () => {
try {
if (wallet.publicKey) {
await wallet.disconnect();
} else {
await wallet.connect();
}
} catch (err) {
console.error('Wallet error:', err.message);
showToast(err.message, 'error');
}
});6. Simple Price Chart (Canvas API)
6. 简易价格图表(Canvas API)
javascript
// js/charts.js
class PriceChart {
constructor(canvasId) {
this.canvas = document.getElementById(canvasId);
this.ctx = this.canvas.getContext('2d');
this.dataPoints = [];
this.maxPoints = 60;
}
addPoint(marketCapSol, timestamp) {
this.dataPoints.push({ value: marketCapSol, time: timestamp });
if (this.dataPoints.length > this.maxPoints) {
this.dataPoints.shift();
}
this.render();
}
render() {
const { ctx, canvas, dataPoints } = this;
const { width, height } = canvas;
ctx.clearRect(0, 0, width, height);
if (dataPoints.length < 2) return;
const values = dataPoints.map(p => p.value);
const min = Math.min(...values);
const max = Math.max(...values);
const range = max - min || 1;
const xStep = width / (dataPoints.length - 1);
// Draw gradient fill
const gradient = ctx.createLinearGradient(0, 0, 0, height);
gradient.addColorStop(0, 'rgba(20, 241, 149, 0.3)');
gradient.addColorStop(1, 'rgba(20, 241, 149, 0)');
ctx.beginPath();
ctx.moveTo(0, height - ((dataPoints[0].value - min) / range) * height);
dataPoints.forEach((point, i) => {
const x = i * xStep;
const y = height - ((point.value - min) / range) * height;
ctx.lineTo(x, y);
});
ctx.lineTo(width, height);
ctx.lineTo(0, height);
ctx.closePath();
ctx.fillStyle = gradient;
ctx.fill();
// Draw line
ctx.beginPath();
ctx.strokeStyle = '#14F195';
ctx.lineWidth = 2;
dataPoints.forEach((point, i) => {
const x = i * xStep;
const y = height - ((point.value - min) / range) * height;
i === 0 ? ctx.moveTo(x, y) : ctx.lineTo(x, y);
});
ctx.stroke();
}
}
export default PriceChart;javascript
// js/charts.js
class PriceChart {
constructor(canvasId) {
this.canvas = document.getElementById(canvasId);
this.ctx = this.canvas.getContext('2d');
this.dataPoints = [];
this.maxPoints = 60;
}
addPoint(marketCapSol, timestamp) {
this.dataPoints.push({ value: marketCapSol, time: timestamp });
if (this.dataPoints.length > this.maxPoints) {
this.dataPoints.shift();
}
this.render();
}
render() {
const { ctx, canvas, dataPoints } = this;
const { width, height } = canvas;
ctx.clearRect(0, 0, width, height);
if (dataPoints.length < 2) return;
const values = dataPoints.map(p => p.value);
const min = Math.min(...values);
const max = Math.max(...values);
const range = max - min || 1;
const xStep = width / (dataPoints.length - 1);
// 绘制渐变填充
const gradient = ctx.createLinearGradient(0, 0, 0, height);
gradient.addColorStop(0, 'rgba(20, 241, 149, 0.3)');
gradient.addColorStop(1, 'rgba(20, 241, 149, 0)');
ctx.beginPath();
ctx.moveTo(0, height - ((dataPoints[0].value - min) / range) * height);
dataPoints.forEach((point, i) => {
const x = i * xStep;
const y = height - ((point.value - min) / range) * height;
ctx.lineTo(x, y);
});
ctx.lineTo(width, height);
ctx.lineTo(0, height);
ctx.closePath();
ctx.fillStyle = gradient;
ctx.fill();
// 绘制线条
ctx.beginPath();
ctx.strokeStyle = '#14F195';
ctx.lineWidth = 2;
dataPoints.forEach((point, i) => {
const x = i * xStep;
const y = height - ((point.value - min) / range) * height;
i === 0 ? ctx.moveTo(x, y) : ctx.lineTo(x, y);
});
ctx.stroke();
}
}
export default PriceChart;Configuration
配置
All configuration is done via constants at the top of each JS file. No file needed for the front-end — but if you add a backend:
.envjavascript
// js/config.js
const CONFIG = {
WS_URL: 'wss://pumpportal.fun/api/data',
RPC_URL: process.env.SOLANA_RPC_URL || 'https://api.mainnet-beta.solana.com',
API_BASE: process.env.API_BASE_URL || 'https://pump-analyzer.com/api',
PLANS: {
free: { alertsPerDay: 5, price: 0 },
pro: { alertsPerDay: Infinity, price: 29, sol: 0.5 },
elite: { alertsPerDay: Infinity, price: 99, sol: 1.5 }
}
};所有配置通过每个JS文件顶部的常量完成。前端无需文件 — 若添加后端则可使用:
.envjavascript
// js/config.js
const CONFIG = {
WS_URL: 'wss://pumpportal.fun/api/data',
RPC_URL: process.env.SOLANA_RPC_URL || 'https://api.mainnet-beta.solana.com',
API_BASE: process.env.API_BASE_URL || 'https://pump-analyzer.com/api',
PLANS: {
free: { alertsPerDay: 5, price: 0 },
pro: { alertsPerDay: Infinity, price: 29, sol: 0.5 },
elite: { alertsPerDay: Infinity, price: 99, sol: 1.5 }
}
};Common Patterns
常用模式
Filter tokens by keyword on arrival
按关键词过滤新代币
javascript
function onNewToken(token) {
const keyword = document.getElementById('filter-input').value.toLowerCase();
if (keyword && !`${token.name} ${token.symbol}`.toLowerCase().includes(keyword)) return;
renderTokenCard(token);
}javascript
function onNewToken(token) {
const keyword = document.getElementById('filter-input').value.toLowerCase();
if (keyword && !`${token.name} ${token.symbol}`.toLowerCase().includes(keyword)) return;
renderTokenCard(token);
}Debounce rapid trade updates
防抖处理频繁交易更新
javascript
const updateQueue = new Map();
function onTrade(trade) {
clearTimeout(updateQueue.get(trade.mint));
updateQueue.set(trade.mint, setTimeout(() => {
updateTokenMetrics(trade.mint, trade);
updateQueue.delete(trade.mint);
}, 200));
}javascript
const updateQueue = new Map();
function onTrade(trade) {
clearTimeout(updateQueue.get(trade.mint));
updateQueue.set(trade.mint, setTimeout(() => {
updateTokenMetrics(trade.mint, trade);
updateQueue.delete(trade.mint);
}, 200));
}Show upgrade modal for free plan limits
免费计划限额时展示升级弹窗
javascript
function showUpgradeModal(reason) {
document.getElementById('upgrade-reason').textContent = reason;
document.getElementById('upgrade-modal').classList.add('visible');
}javascript
function showUpgradeModal(reason) {
document.getElementById('upgrade-reason').textContent = reason;
document.getElementById('upgrade-modal').classList.add('visible');
}Troubleshooting
故障排查
| Issue | Cause | Fix |
|---|---|---|
| WebSocket won't connect | Browser blocks WSS or wrong URL | Check |
| No tokens appearing | Subscription message not sent on | Ensure |
| Wallet button does nothing | Wallet extension not installed | Detect |
| Notifications not firing | Permission not granted | Call |
| Cards not updating market cap | | Normalize mint addresses to strings before comparison |
| Page flickers on new token | DOM prepend causes reflow | Use |
| 问题 | 原因 | 解决方法 |
|---|---|---|
| WebSocket无法连接 | 浏览器阻止WSS或URL错误 | 检查 |
| 无代币显示 | 连接成功时未发送订阅消息 | 确保 |
| 钱包按钮无响应 | 未安装钱包扩展 | 调用 |
| 通知不触发 | 未获取权限 | 在用户交互后调用 |
| 卡片未更新市值 | 代币与交易事件的 | 比较前将铸币地址统一转换为字符串 |
| 新代币出现时页面闪烁 | DOM前置操作导致重排 | 使用 |
Pricing / Plan Gating Pattern
定价/计划权限控制模式
javascript
// Check plan before unlocking features
function requirePlan(minimumPlan, action) {
const planRank = { free: 0, pro: 1, elite: 2 };
const userPlan = localStorage.getItem('pump_plan') || 'free';
if (planRank[userPlan] >= planRank[minimumPlan]) {
action();
} else {
showUpgradeModal(`This feature requires the ${minimumPlan} plan.`);
}
}
// Usage
requirePlan('pro', () => enableUnlimitedAlerts());
requirePlan('elite', () => enableAIInsights());javascript
// 解锁功能前检查用户计划
function requirePlan(minimumPlan, action) {
const planRank = { free: 0, pro: 1, elite: 2 };
const userPlan = localStorage.getItem('pump_plan') || 'free';
if (planRank[userPlan] >= planRank[minimumPlan]) {
action();
} else {
showUpgradeModal(`此功能需要${minimumPlan}计划。`);
}
}
// 使用示例
requirePlan('pro', () => enableUnlimitedAlerts());
requirePlan('elite', () => enableAIInsights());