Hướng dẫn bắt đầu nhanh
1
Get Your API Key
Lấy khóa API Helius từ bảng điều khiển. Bạn sẽ cần khóa này để xác thực. Các tác nhân có thể lấy khóa bằng Helius CLI.
2
Choose Your Network
Chọn điểm cuối phù hợp:
- Mainnet:
wss://mainnet.helius-rpc.com/?api-key=YOUR_API_KEY - Mainnet (Gatekeeper Beta):
wss://beta.helius-rpc.com/?api-key=YOUR_API_KEY - Devnet:
wss://devnet.helius-rpc.com/?api-key=YOUR_API_KEY
3
Create Your First Connection
const ws = new WebSocket('wss://mainnet.helius-rpc.com/?api-key=YOUR_API_KEY');
ws.onopen = () => {
console.log('Connected to Solana!');
};
ws.onmessage = (event) => {
const data = JSON.parse(event.data);
console.log('Received:', data);
};
4
Subscribe to Updates
// Subscribe to account changes
const subscribeMessage = {
"jsonrpc": "2.0",
"id": 1,
"method": "accountSubscribe",
"params": [
"ACCOUNT_ADDRESS_HERE",
{ "commitment": "confirmed" }
]
};
ws.send(JSON.stringify(subscribeMessage));
Các phương thức đăng ký
Giám sát tài khoản
Giám sát các tài khoản cụ thể để phát hiện thay đổi số dư, cập nhật dữ liệu hoặc chuyển quyền sở hữu.const ws = new WebSocket('wss://mainnet.helius-rpc.com/?api-key=YOUR_API_KEY');
const subscribeToAccount = (accountAddress) => {
const request = {
"jsonrpc": "2.0",
"id": 1,
"method": "accountSubscribe",
"params": [
accountAddress,
{
"encoding": "jsonParsed",
"commitment": "confirmed"
}
]
};
ws.send(JSON.stringify(request));
};
ws.onmessage = (event) => {
const data = JSON.parse(event.data);
if (data.method === "accountNotification") {
console.log("Account updated:", data.params.result.value);
// Handle the account change
const accountInfo = data.params.result.value;
console.log(`New balance: ${accountInfo.lamports} lamports`);
}
};
// Subscribe to a wallet address
subscribeToAccount("9PejEmViKHgUkVFWN57cNEZnFS4Qo6SzsLj5UPAXfDTF");
const ws = new WebSocket('wss://mainnet.helius-rpc.com/?api-key=YOUR_API_KEY');
// Monitor a token account for balance changes
const monitorTokenAccount = (tokenAccountAddress) => {
const request = {
"jsonrpc": "2.0",
"id": 2,
"method": "accountSubscribe",
"params": [
tokenAccountAddress,
{
"encoding": "jsonParsed",
"commitment": "confirmed"
}
]
};
ws.send(JSON.stringify(request));
};
ws.onmessage = (event) => {
const data = JSON.parse(event.data);
if (data.method === "accountNotification") {
const accountData = data.params.result.value.data;
if (accountData.program === "spl-token") {
const parsed = accountData.parsed;
console.log(`Token balance: ${parsed.info.tokenAmount.uiAmount}`);
console.log(`Owner: ${parsed.info.owner}`);
}
}
};
Giám sát hoạt động của chương trình
Theo dõi tất cả tài khoản thuộc sở hữu của một chương trình — lý tưởng để giám sát giao dịch DEX, chuyển NFT hoặc hoạt động DeFi.// Monitor Raydium AMM for all trade activity
const RAYDIUM_AMM_PROGRAM = "675kPX9MHTjS2zt1qfr1NYHuzeLXfQM9H24wFSUt1Mp8";
const subscribeToProgram = (programId) => {
const request = {
"jsonrpc": "2.0",
"id": 3,
"method": "programSubscribe",
"params": [
programId,
{
"encoding": "jsonParsed",
"commitment": "confirmed",
"filters": [
{
"dataSize": 752 // Raydium pool account size
}
]
}
]
};
ws.send(JSON.stringify(request));
};
ws.onmessage = (event) => {
const data = JSON.parse(event.data);
if (data.method === "programNotification") {
console.log("Raydium pool updated:", data.params.result.value.pubkey);
// Process the pool data change
const accountData = data.params.result.value.account;
// Parse and handle the pool state change
}
};
subscribeToProgram(RAYDIUM_AMM_PROGRAM);
// Monitor Magic Eden program for NFT activity
const MAGIC_EDEN_PROGRAM = "M2mx93ekt1fmXSVkTrUL9xVFHkmME8HTUi5Cyc5aF7K";
const subscribeToNFTActivity = () => {
const request = {
"jsonrpc": "2.0",
"id": 4,
"method": "programSubscribe",
"params": [
MAGIC_EDEN_PROGRAM,
{
"encoding": "base64",
"commitment": "confirmed"
}
]
};
ws.send(JSON.stringify(request));
};
ws.onmessage = (event) => {
const data = JSON.parse(event.data);
if (data.method === "programNotification") {
console.log("NFT marketplace activity detected");
// Decode and process the marketplace instruction
const accountInfo = data.params.result.value;
// Handle NFT listing, purchase, or cancellation
}
};
Giám sát giao dịch
Theo dõi các giao dịch cụ thể hoặc giám sát những giao dịch đề cập đến một số tài khoản nhất định.// Monitor a specific transaction signature
const trackTransaction = (signature) => {
const request = {
"jsonrpc": "2.0",
"id": 5,
"method": "signatureSubscribe",
"params": [
signature,
{
"commitment": "confirmed"
}
]
};
ws.send(JSON.stringify(request));
};
ws.onmessage = (event) => {
const data = JSON.parse(event.data);
if (data.method === "signatureNotification") {
const result = data.params.result;
if (result.value.err) {
console.log("Transaction failed:", result.value.err);
} else {
console.log("Transaction confirmed successfully!");
}
// Subscription automatically ends after notification
}
};
// Track a payment or swap transaction
trackTransaction("YOUR_TRANSACTION_SIGNATURE_HERE");
// Monitor all transactions mentioning a specific address
const monitorAddress = (address) => {
const request = {
"jsonrpc": "2.0",
"id": 6,
"method": "logsSubscribe",
"params": [
{
"mentions": [address]
},
{
"commitment": "confirmed"
}
]
};
ws.send(JSON.stringify(request));
};
ws.onmessage = (event) => {
const data = JSON.parse(event.data);
if (data.method === "logsNotification") {
const logs = data.params.result.value.logs;
const signature = data.params.result.value.signature;
console.log(`Transaction ${signature} mentioned our address`);
console.log("Logs:", logs);
// Parse logs for specific events or errors
if (logs.some(log => log.includes("Program failed"))) {
console.log("Transaction failed!");
}
}
};
// Monitor a token mint for all activity
monitorAddress("EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v");
Triển khai sẵn sàng cho môi trường production
Dưới đây là một cách triển khai WebSocket ổn định với logic kết nối lại, xử lý lỗi và quản lý trạng thái phù hợp:class WebSocketManager {
constructor(endpoint) {
this.endpoint = endpoint;
this.ws = null;
this.subscriptions = new Map();
this.isConnected = false;
}
async connect() {
this.ws = new WebSocket(this.endpoint);
this.ws.onopen = () => {
console.log('Connected');
this.isConnected = true;
this.resubscribeAll();
};
this.ws.onmessage = (event) => {
const data = JSON.parse(event.data);
// Handle subscription confirmations
if (data.result && typeof data.result === 'number') {
const sub = Array.from(this.subscriptions.values())
.find(s => s.requestId === data.id);
if (sub) sub.subscriptionId = data.result;
return;
}
// Handle notifications
if (data.method?.endsWith('Notification')) {
const sub = Array.from(this.subscriptions.values())
.find(s => s.subscriptionId === data.params.subscription);
if (sub?.callback) sub.callback(data.params.result);
}
};
this.ws.onclose = () => {
console.log('Disconnected');
this.isConnected = false;
// Implement reconnection logic here
};
}
subscribe(method, params, callback) {
const requestId = Date.now();
const subscription = { requestId, method, params, callback, subscriptionId: null };
this.subscriptions.set(requestId, subscription);
if (this.isConnected) {
this.ws.send(JSON.stringify({
jsonrpc: '2.0',
id: requestId,
method,
params
}));
}
return requestId;
}
resubscribeAll() {
for (const [id, sub] of this.subscriptions) {
sub.subscriptionId = null;
this.ws.send(JSON.stringify({
jsonrpc: '2.0',
id,
method: sub.method,
params: sub.params
}));
}
}
}
// Usage
const wsManager = new WebSocketManager('wss://mainnet.helius-rpc.com/?api-key=YOUR_API_KEY');
await wsManager.connect();
// Subscribe to account changes
wsManager.subscribe(
'accountSubscribe',
['account-address-here', { commitment: 'confirmed' }],
(data) => console.log('Account updated:', data)
);
Khám phá thêm các phương thức WebSocket
Các ví dụ trên đề cập đến những loại đăng ký được sử dụng phổ biến nhất, nhưng API WebSocket của Solana còn cung cấp nhiều phương thức khác cho các nhu cầu giám sát chuyên biệt.Complete WebSocket API Reference
Khám phá hơn 18 phương thức WebSocket cùng tài liệu chi tiết, tham số và định dạng phản hồi.
Các trường hợp sử dụng thực tế
Bảng điều khiển giao dịch DeFi
class LiquidityPoolMonitor {
constructor(wsManager) {
this.wsManager = wsManager;
this.pools = new Map();
}
monitorPool(poolAddress, tokenAMint, tokenBMint) {
// Monitor the pool account itself
const poolSubscription = this.wsManager.subscribe(
'accountSubscribe',
[poolAddress, { encoding: 'base64', commitment: 'confirmed' }],
(data) => this.handlePoolUpdate(poolAddress, data)
);
// Monitor token reserves
const tokenASubscription = this.wsManager.subscribe(
'accountSubscribe',
[tokenAMint, { encoding: 'jsonParsed', commitment: 'confirmed' }],
(data) => this.handleReserveUpdate(poolAddress, 'tokenA', data)
);
const tokenBSubscription = this.wsManager.subscribe(
'accountSubscribe',
[tokenBMint, { encoding: 'jsonParsed', commitment: 'confirmed' }],
(data) => this.handleReserveUpdate(poolAddress, 'tokenB', data)
);
this.pools.set(poolAddress, {
poolSubscription,
tokenASubscription,
tokenBSubscription,
lastUpdate: Date.now()
});
}
handlePoolUpdate(poolAddress, data) {
// Decode pool state and calculate price
const poolState = this.decodePoolState(data.value.data);
console.log(`Pool ${poolAddress} updated:`);
console.log(`- Price: ${poolState.price}`);
console.log(`- Liquidity: ${poolState.liquidity}`);
console.log(`- Volume 24h: ${poolState.volume24h}`);
// Emit price update event
this.emitPriceUpdate(poolAddress, poolState);
}
handleReserveUpdate(poolAddress, tokenType, data) {
const tokenAmount = data.value.data.parsed.info.tokenAmount;
console.log(`${tokenType} reserve updated: ${tokenAmount.uiAmount}`);
// Update internal state and recalculate price
this.updatePoolPrice(poolAddress, tokenType, tokenAmount);
}
decodePoolState(data) {
// Implement pool state decoding based on DEX
// This is pseudo-code - actual implementation depends on the DEX
return {
price: 0,
liquidity: 0,
volume24h: 0
};
}
emitPriceUpdate(poolAddress, poolState) {
// Emit custom events for price updates
window.dispatchEvent(new CustomEvent('poolPriceUpdate', {
detail: { poolAddress, ...poolState }
}));
}
}
// Usage
const poolMonitor = new LiquidityPoolMonitor(wsManager);
poolMonitor.monitorPool(
'POOL_ADDRESS',
'TOKEN_A_MINT',
'TOKEN_B_MINT'
);
Công cụ theo dõi danh mục đầu tư
class PortfolioTracker {
constructor(wsManager) {
this.wsManager = wsManager;
this.watchedAddresses = new Set();
this.balances = new Map();
}
addWallet(walletAddress) {
if (this.watchedAddresses.has(walletAddress)) return;
this.watchedAddresses.add(walletAddress);
// Monitor SOL balance
const solSubscription = this.wsManager.subscribe(
'accountSubscribe',
[walletAddress, { encoding: 'jsonParsed', commitment: 'confirmed' }],
(data) => this.handleSolBalanceUpdate(walletAddress, data)
);
// Monitor all token accounts owned by this wallet
const tokenSubscription = this.wsManager.subscribe(
'programSubscribe',
[
'TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA',
{
encoding: 'jsonParsed',
commitment: 'confirmed',
filters: [
{ memcmp: { offset: 32, bytes: walletAddress } }
]
}
],
(data) => this.handleTokenBalanceUpdate(walletAddress, data)
);
console.log(`Now tracking portfolio for ${walletAddress}`);
}
handleSolBalanceUpdate(walletAddress, data) {
const lamports = data.value.lamports;
const solBalance = lamports / 1e9;
this.balances.set(`${walletAddress}:SOL`, solBalance);
console.log(`${walletAddress} SOL balance: ${solBalance}`);
this.emitBalanceUpdate(walletAddress, 'SOL', solBalance);
}
handleTokenBalanceUpdate(walletAddress, data) {
const tokenData = data.value.account.data.parsed.info;
const tokenMint = tokenData.mint;
const balance = parseFloat(tokenData.tokenAmount.uiAmount);
this.balances.set(`${walletAddress}:${tokenMint}`, balance);
console.log(`${walletAddress} token ${tokenMint} balance: ${balance}`);
this.emitBalanceUpdate(walletAddress, tokenMint, balance);
}
emitBalanceUpdate(walletAddress, asset, balance) {
window.dispatchEvent(new CustomEvent('balanceUpdate', {
detail: { walletAddress, asset, balance, timestamp: Date.now() }
}));
}
getPortfolioValue(walletAddress) {
// Calculate total portfolio value in USD
// This would integrate with price feeds
let totalValue = 0;
for (const [key, balance] of this.balances) {
if (key.startsWith(walletAddress)) {
const asset = key.split(':')[1];
const price = this.getAssetPrice(asset); // Implement price lookup
totalValue += balance * price;
}
}
return totalValue;
}
}
Tích hợp sàn giao dịch NFT
class NFTActivityMonitor {
constructor(wsManager) {
this.wsManager = wsManager;
this.watchedCollections = new Map();
}
monitorCollection(collectionAddress, options = {}) {
const { includeListings = true, includeSales = true, includeCancellations = true } = options;
// Monitor the collection's update authority or creator address
const collectionSubscription = this.wsManager.subscribe(
'logsSubscribe',
[
{ mentions: [collectionAddress] },
{ commitment: 'confirmed' }
],
(data) => this.handleCollectionActivity(collectionAddress, data)
);
this.watchedCollections.set(collectionAddress, {
subscription: collectionSubscription,
options,
stats: {
listings: 0,
sales: 0,
volume: 0
}
});
console.log(`Monitoring NFT collection: ${collectionAddress}`);
}
handleCollectionActivity(collectionAddress, data) {
const logs = data.value.logs;
const signature = data.value.signature;
// Parse marketplace-specific logs
const activity = this.parseMarketplaceActivity(logs);
if (activity) {
console.log(`NFT Activity in ${collectionAddress}:`);
console.log(`- Type: ${activity.type}`);
console.log(`- Price: ${activity.price} SOL`);
console.log(`- Signature: ${signature}`);
// Update collection stats
this.updateCollectionStats(collectionAddress, activity);
// Emit activity event
this.emitNFTActivity(collectionAddress, activity, signature);
}
}
parseMarketplaceActivity(logs) {
// Parse Magic Eden, OpenSea, or other marketplace logs
for (const log of logs) {
// Magic Eden listing
if (log.includes('Instruction: List')) {
const price = this.extractPriceFromLog(log);
return { type: 'listing', price };
}
// Magic Eden sale
if (log.includes('Instruction: ExecuteSale')) {
const price = this.extractPriceFromLog(log);
return { type: 'sale', price };
}
// Listing cancellation
if (log.includes('Instruction: CancelBuy') || log.includes('Instruction: CancelSell')) {
return { type: 'cancellation', price: 0 };
}
}
return null;
}
extractPriceFromLog(log) {
// Extract price from marketplace logs - implementation depends on marketplace
// This is pseudo-code
const priceMatch = log.match(/price:\s*(\d+)/);
return priceMatch ? parseInt(priceMatch[1]) / 1e9 : 0;
}
updateCollectionStats(collectionAddress, activity) {
const collection = this.watchedCollections.get(collectionAddress);
if (!collection) return;
switch (activity.type) {
case 'listing':
collection.stats.listings++;
break;
case 'sale':
collection.stats.sales++;
collection.stats.volume += activity.price;
break;
}
}
emitNFTActivity(collectionAddress, activity, signature) {
window.dispatchEvent(new CustomEvent('nftActivity', {
detail: {
collection: collectionAddress,
activity,
signature,
timestamp: Date.now()
}
}));
}
}
Ví dụ tích hợp React
import React, { useEffect, useState } from 'react';
function WebSocketComponent() {
const [accountData, setAccountData] = useState(null);
const [wsManager, setWsManager] = useState(null);
useEffect(() => {
// Initialize WebSocket
const manager = new WebSocketManager('wss://mainnet.helius-rpc.com/?api-key=YOUR_API_KEY');
manager.connect().then(() => {
// Subscribe to account changes
manager.subscribe(
'accountSubscribe',
['account-address-here', { commitment: 'confirmed' }],
(data) => setAccountData(data)
);
});
setWsManager(manager);
// Cleanup on unmount
return () => {
if (manager) manager.disconnect();
};
}, []);
return (
<div>
<h3>Account Monitor</h3>
{accountData ? (
<div>
<p>Balance: {accountData.value.lamports} lamports</p>
<p>Owner: {accountData.value.owner}</p>
</div>
) : (
<p>Waiting for account updates...</p>
)}
</div>
);
}
Tối ưu hóa hiệu suất
- Connection Management
- Memory Management
// Use connection pooling for multiple subscriptions
class WebSocketPool {
constructor(endpoint, maxConnections = 3) {
this.endpoint = endpoint;
this.connections = [];
this.maxConnections = maxConnections;
this.currentConnectionIndex = 0;
}
async getConnection() {
if (this.connections.length < this.maxConnections) {
const wsManager = new SolanaWebSocketManager(this.endpoint);
await wsManager.connect();
this.connections.push(wsManager);
return wsManager;
}
// Round-robin selection
const connection = this.connections[this.currentConnectionIndex];
this.currentConnectionIndex = (this.currentConnectionIndex + 1) % this.connections.length;
return connection;
}
}
// Implement subscription cleanup and memory management
class ManagedWebSocket extends SolanaWebSocketManager {
constructor(endpoint, options = {}) {
super(endpoint, options);
this.maxSubscriptions = options.maxSubscriptions || 100;
this.subscriptionHistory = [];
}
subscribe(method, params, callback) {
// Check subscription limit
if (this.subscriptions.size >= this.maxSubscriptions) {
this.cleanupOldSubscriptions();
}
const requestId = super.subscribe(method, params, callback);
// Track subscription for cleanup
this.subscriptionHistory.push({
requestId,
timestamp: Date.now(),
method
});
return requestId;
}
cleanupOldSubscriptions() {
// Remove oldest 10% of subscriptions
const toRemove = Math.floor(this.subscriptions.size * 0.1);
const sortedHistory = this.subscriptionHistory
.sort((a, b) => a.timestamp - b.timestamp);
for (let i = 0; i < toRemove && i < sortedHistory.length; i++) {
const sub = sortedHistory[i];
this.unsubscribe(sub.requestId);
// Remove from history
this.subscriptionHistory = this.subscriptionHistory
.filter(s => s.requestId !== sub.requestId);
}
}
}
Xử lý lỗi và gỡ lỗi
Xử lý lỗi ổn định là yếu tố thiết yếu đối với các ứng dụng WebSocket trong môi trường production. Hãy triển khai những mẫu này để xử lý sự cố mạng, giới hạn tốc độ và lỗi API một cách phù hợp.
class ErrorHandlingWebSocket extends SolanaWebSocketManager {
constructor(endpoint, options = {}) {
super(endpoint, options);
this.errorCounts = new Map();
this.maxErrorsPerMinute = options.maxErrorsPerMinute || 10;
}
handleMessage(data) {
try {
// Handle rate limiting
if (data.error && data.error.code === 429) {
console.warn('Rate limited, backing off...');
this.handleRateLimit();
return;
}
// Handle subscription errors
if (data.error && data.error.code === -32602) {
console.error('Invalid subscription parameters:', data.error.message);
this.handleInvalidSubscription(data.id);
return;
}
// Handle network errors
if (data.error && data.error.code === -32603) {
console.error('Internal server error:', data.error.message);
this.handleServerError();
return;
}
super.handleMessage(data);
} catch (error) {
console.error('Error processing WebSocket message:', error);
this.incrementErrorCount('message_processing');
}
}
handleRateLimit() {
// Exponential backoff for rate limiting
const backoffDelay = Math.min(1000 * Math.pow(2, this.reconnectAttempts), 30000);
setTimeout(() => {
if (this.subscriptions.size > 0) {
console.log('Retrying after rate limit...');
this.resubscribeAll();
}
}, backoffDelay);
}
handleInvalidSubscription(requestId) {
// Remove invalid subscription
const subscription = Array.from(this.subscriptions.entries())
.find(([id, sub]) => sub.requestId === requestId);
if (subscription) {
console.warn(`Removing invalid subscription: ${subscription[1].method}`);
this.subscriptions.delete(subscription[0]);
}
}
handleServerError() {
// Log server error and potentially switch endpoints
this.incrementErrorCount('server_error');
if (this.getErrorCount('server_error') > 5) {
console.error('Too many server errors, consider switching endpoints');
this.eventEmitter.dispatchEvent(new CustomEvent('tooManyServerErrors'));
}
}
incrementErrorCount(errorType) {
const now = Date.now();
const errors = this.errorCounts.get(errorType) || [];
// Add current error
errors.push(now);
// Remove errors older than 1 minute
const oneMinuteAgo = now - 60000;
const recentErrors = errors.filter(timestamp => timestamp > oneMinuteAgo);
this.errorCounts.set(errorType, recentErrors);
// Check if we're exceeding error threshold
if (recentErrors.length > this.maxErrorsPerMinute) {
console.error(`Too many ${errorType} errors in the last minute`);
this.eventEmitter.dispatchEvent(new CustomEvent('errorThresholdExceeded', {
detail: { errorType, count: recentErrors.length }
}));
}
}
getErrorCount(errorType) {
const errors = this.errorCounts.get(errorType) || [];
const oneMinuteAgo = Date.now() - 60000;
return errors.filter(timestamp => timestamp > oneMinuteAgo).length;
}
}
Các bước tiếp theo
API Reference
Tài liệu đầy đủ cho tất cả phương thức đăng ký WebSocket
WebSocket Overview
Khám phá các đăng ký Helius WebSocket, bao gồm
transactionSubscribe và các bộ lọc accountSubscribe nâng caoLaserStream
Nâng cấp lên LaserStream để truyền phát dữ liệu siêu nhanh với khả năng phát lại dữ liệu lịch sử
LaserStream Guide
Tìm hiểu cách truyền phát các bản cập nhật tài khoản theo thời gian thực bằng LaserStream