
Cách giám sát các giao dịch Solana bằng Geyser Enhanced Websockets
Giới thiệu
Trong một thời gian dài, tôi đã tự hỏi tất cả các ứng dụng và bot giám sát này hoạt động như thế nào. Sau một quá trình tìm kiếm vất vả, tôi đã biết đến Geyser Enhanced Websockets của Helius. Mặc dù dịch vụ này không miễn phí (bạn cần gói Business hoặc Professional), đây là một công cụ rất mạnh mẽ.
Sử dụng Geyser Enhanced Websockets với Helius rất đơn giản — dán địa chỉ bạn muốn giám sát rồi chạy một đoạn mã. Bạn có thể giám sát mọi thứ: NFT, ví, chương trình, nền tảng, theo đúng nghĩa đen là bất cứ thứ gì. Bạn có thể tạo trình theo dõi ví, trình theo dõi token, công cụ giám sát mua bán, công cụ giám sát khối lượng, v.v.
API và các công cụ kiểu này đôi khi có thể tốn đến hàng nghìn đô la, nhưng bài viết này sẽ giới thiệu một vài ví dụ về những gì có thể xây dựng với chi phí chỉ bằng một phần nhỏ. Với 499 USD (gói Business), lợi tức đầu tư khi tự xây dựng công cụ là vô hạn; chia sẻ công cụ với người khác hoặc tự mình sử dụng sẽ mang lại lợi thế cùng vô số cách khai thác dữ liệu Solana trực tiếp.
Nếu bạn mới bắt đầu làm việc với Helius Geyser Websockets, hãy đọc bài viết blog này và tài liệu. Bạn có thể làm theo vì ví dụ đầu tiên này khá dễ tiếp cận.
Công cụ giám sát pool mới trên Raydium
const WebSocket = require('ws');
// Create a WebSocket connection
const ws = new WebSocket('wss://atlas-mainnet.helius-rpc.com?api-key=YOUR_API_KEY');
// Function to send a request to the WebSocket server
function sendRequest(ws) {
const request = {
jsonrpc: "2.0",
id: 420,
method: "transactionSubscribe",
params: [
{ failed: false,
accountInclude: ["675kPX9MHTjS2zt1qfr1NYHuzeLXfQM9H24wFSUt1Mp8"]
},
{
commitment: "confirmed",
encoding: "jsonParsed",
transactionDetails: "full",
maxSupportedTransactionVersion: 0
}
]
};
ws.send(JSON.stringify(request));
}Phần đầu tiên của đoạn mã này rất đơn giản. Chúng ta thiết lập khóa API và gửi một yêu cầu kèm theo tài khoản muốn giám sát, ở đây là 675kPX9…, tức chương trình on-chain của Raydium. Với yêu cầu này, chúng ta sẽ nhận được tất cả giao dịch đã xác nhận, không bị lỗi và có tương tác với Raydium. Thông thường, việc gửi yêu cầu này sẽ trả về hàng nghìn giao dịch mỗi giây, vì vậy hãy tập trung vào một cách đơn giản để lọc bỏ dữ liệu nhiễu.
Lọc bỏ dữ liệu nhiễu
Đoạn mã dưới đây chứa logic lọc chính để xử lý sự kiện. Hàm on message phân tích dữ liệu trả về từ các giao dịch dựa trên log. Trong trường hợp này, chúng ta xem xét tất cả giao dịch có log “initialize2: InitializeInstruction2” — log này cho biết khi nào người dùng tạo một pool thanh khoản mới trên Raydium. Bạn cũng có thể sử dụng bất kỳ log nào khác, tùy thuộc vào nội dung muốn giám sát. Tôi khuyên bạn nên thực hiện một giao dịch thử nghiệm cho hoạt động muốn giám sát. Ví dụ: bạn có thể thêm thanh khoản vào một pool và xem log của giao dịch đó trông như thế nào, sau đó lọc các log này để lấy tất cả giao dịch có hoạt động thêm thanh khoản.
ws.on('open', function open() {
console.log('WebSocket is open');
sendRequest(ws); // Send a request once the WebSocket is open
});
ws.on('message', async function incoming(data) {
const messageStr = data.toString('utf8');
try {
const messageObj = JSON.parse(messageStr);
const result = messageObj.params.result;
const logs = result.transaction.meta.logMessages;
const signature = result.signature; // Extract the signature
const accountKeys = result.transaction.transaction.message.accountKeys.map(ak => ak.pubkey); // Extract only pubkeys
if (logs && logs.some(log => log.includes("initialize2: InitializeInstruction2"))) {
// Log the signature, and the public keys of the AMM ID
console.log('Transaction signature:', signature);
console.log('AMM ID:', accountKeys[2]); // Corrected to the third account for AMM ID
}
} catch (e) {
}
});
ws.on('error', function error(err) {
console.error('WebSocket error:', err);
});
ws.on('close', function close() {
console.log('WebSocket is closed');
});Khi biết giao dịch có chứa log đó, chúng ta trích xuất hai thứ: chữ ký để có thể so sánh và xác minh độ chính xác của chương trình, cùng khóa tài khoản, tức AMM ID (địa chỉ AMM của pool đó), vì nhiều bot/sniper sử dụng AMM ID để bắt đầu giao dịch.
Bạn cũng có thể trích xuất thêm dữ liệu nếu muốn. Ví dụ: bạn có thể lấy thông tin người tạo (thường là khóa công khai thứ 17 trong accountKeys), các token được dùng để tạo pool (từ số dư token trước/sau hoặc các chỉ thị bên trong), số lượng token và thậm chí cả các token LP mà người tạo nhận được. Số lượng token LP mà người tạo nhận được có thể dùng để xây dựng công cụ giám sát đốt thanh khoản, vì khi người tạo pool đốt token LP của họ — về cơ bản là token biên nhận đại diện cho phần sở hữu của họ trong pool — họ sẽ từ bỏ khả năng rút thanh khoản (hãy thận trọng nếu quyền mint vẫn nằm trong tay họ, vì họ có thể mint thêm token rồi bán vào pool).
Cấu trúc JSON
Để lấy thêm dữ liệu, bạn có thể xem cấu trúc JSON của giao dịch trong tài liệu chính thức của Solana. Ngoài ra, bạn có thể lưu phản hồi JSON và dùng trình định dạng này để xem cấu trúc. Điều này khá quan trọng vì một khi hiểu cấu trúc tổng quát của giao dịch, bạn có thể trích xuất mọi dữ liệu nhìn thấy khi sử dụng trình khám phá khối.
Dưới đây là JSON tổng quát, chứa hai đối tượng quan trọng được lồng sâu: Transaction và Meta. Bên trong Transaction là đối tượng message, chứa blockhash gần nhất, accountKeys và instructions. Bên trong Meta là Balances trước/sau (số dư lamport), innerInstructions, logMessages và token balances trước/sau.
Ví dụ về công cụ giám sát Pump.Fun
Nhận thấy xu hướng meme coin hiện tại trên Solana, tôi đã tạo một công cụ giám sát pump.fun rất đơn giản. Chỉ cần đổi địa chỉ đang giám sát thành “6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P”, đây là chương trình on-chain của pump.fun, rồi sử dụng hàm on message sau:
ws.on('message', function incoming(data) {
const messageStr = data.toString('utf8');
try {
const messageObj = JSON.parse(messageStr);
const result = messageObj.params.result;
const logs = result.transaction.meta.logMessages;
const signature = result.signature; // Extract the signature
const accountKeys = result.transaction.transaction.message.accountKeys.map(ak => ak.pubkey);
if (logs && logs.some(log => log.includes('Program log: Instruction: InitializeMint2'))) {
console.log('New pump.fun token!');
console.log('tx:', signature);
console.log('Creator:', accountKeys[0]);
console.log('Token:', accountKeys[1]);
// Log the first and second account keys if they exist
}
} catch (e) {
}
});Tương tự cách tiếp cận với Raydium, chúng ta xem xét tất cả giao dịch tương tác với chương trình pump.fun và lọc chúng dựa trên log mong muốn (có thể thấy một mô-típ ở đây: log, log và log). Khi có một giao dịch chứa các log cần tìm, chúng ta trích xuất chữ ký, người tạo và chính token đó!
Bạn có thể thấy accountKeys và chữ ký được lồng tại cùng một vị trí trong JSON như ở ví dụ Raydium, vì vậy rất dễ lấy thông tin người tạo, token và chữ ký. Về cơ bản, cấu trúc JSON giống nhau đối với hầu hết giao dịch, nhưng một số giá trị có thể nằm ở vị trí khác hoặc được sắp xếp theo thứ tự khác — bạn sẽ thấy điều này trong ví dụ tiếp theo.
Dù sao thì đây cũng là một công cụ giám sát khá đơn giản cho các token pump.fun mới. Với thông tin này, bạn có thể mua token hoặc chỉ cần biết token đó tồn tại để phục vụ mục đích giám sát. Bạn cũng có thể kết hợp công cụ này với các Helius DAS API khác để tạo ra một công cụ mạnh mẽ hơn nữa, chẳng hạn như lấy siêu dữ liệu hoặc thông tin về người tạo.
Công cụ giám sát Jupiter DCA
Ví dụ này khá chuyên biệt, nhưng cho thấy bạn có thể quan sát mọi thứ bằng log. Với yêu cầu sau, chúng ta sẽ xem xét chương trình DCA (đầu tư bình quân theo định kỳ) của Jupiter và cụ thể hơn là theo dõi từng giao dịch mua DCA được thực hiện qua Jupiter. Ví dụ này có thể dùng trong các phép tính giao dịch chênh lệch giá hoặc để đánh giá tác động tiềm tàng lên thị trường. Dữ liệu về các giao dịch mua DCA vẫn chưa được khai thác đúng mức để đánh giá tác động tiềm tàng lên thị trường.
Như bạn có thể thấy, cho đến lúc này chưa có gì phức tạp. Chúng ta đã thêm lệnh import base58 ở đầu và đổi địa chỉ thành chương trình Jupiter DCA. Phần tiếp theo có thể trông khá phức tạp — và đúng là có phần như vậy — nhưng tôi cho rằng việc đưa ra một ví dụ phức tạp hơn một chút là rất quan trọng.
const WebSocket = require("ws");
const bs58 = require("bs58");
// Create a WebSocket connection
const ws = new WebSocket(
"wss://atlas-mainnet.helius-rpc.com?api-key=YOUR_API_KEY"
);
// Function to send a request to the WebSocket server
function sendRequest(ws) {
const request = {
jsonrpc: "2.0",
id: 420,
method: "transactionSubscribe",
params: [
{
failed: false,
accountInclude: ["DCA265Vj8a9CEuX1eb1LWRnDT7uK6q1xMipnNyatn23M"],
},
{
commitment: "confirmed",
encoding: "jsonParsed",
transactionDetails: "full",
maxSupportedTransactionVersion: 0,
},
],
};
ws.send(JSON.stringify(request));
}Ở đây, chúng ta đang:
- Sử dụng log DCA để chỉ lọc các loại giao dịch này.
- Trích xuất
User,Input MintvàOutput Minttừ giao dịch. - Lấy
Instruction Data Rawvà chuyển đổi các byte thành tham số đầu vào, chẳng hạn như số lượng người dùng nhập, tần suất họ bán và số lượng bán mỗi lần.
ws.on("message", async function incoming(data) {
const messageStr = data.toString("utf8");
try {
const messageObj = JSON.parse(messageStr);
const instructions = messageObj.params.result.transaction.transaction.message.instructions;
const result = messageObj.params.result;
const logs = result.transaction.meta.logMessages;
// Extract only pubkeys
if (
logs &&
logs.some((log) => log.includes("Program log: Instruction: OpenDcaV2"))
) {
instructions.forEach((instruction) => {
if (instruction.programId.includes("DCA265")) {
if (instruction.accounts.length === 13) {
console.log("User:", instruction.accounts[2]);
console.log("Input Mint:", instruction.accounts[3]);
console.log("Output Mint:", instruction.accounts[4]);
const data = instruction.data;
const bytedata = bs58.decode(data);
const hexString = bytedata.toString("hex");
const inAmountbytes = hexString.substring(16 * 2, 24 * 2);
const cycleFrequencyBytes = hexString.substring(32 * 2, (32 + 8) * 2);
const inAmountPerCycleBytes = hexString.substring(24 * 2, 32 * 2);
// Reverse the byte order for little-endian interpretation
const reversedCycleFrequencyBytes = cycleFrequencyBytes
.match(/.{1,2}/g)
.reverse()
.join("");
const reversedInAmountBytes = inAmountbytes
.match(/.{1,2}/g)
.reverse()
.join("");
const reversedInAmountPerCycleBytes = inAmountPerCycleBytes
.match(/.{1,2}/g)
.reverse()
.join("");
const cycleFrequency = BigInt("0x" + reversedCycleFrequencyBytes);
const inAmount = BigInt("0x" + reversedInAmountBytes);
const inAmountPerCycle = BigInt("0x" + reversedInAmountPerCycleBytes);
console.log("Cycle Frequency every", cycleFrequency.toString() + " seconds");
console.log("Amount input:", inAmount.toString());
console.log("Amount per cycle:", inAmountPerCycle.toString());
}
}
});
}
} catch (e) {}
});*Giải mã dữ liệu chỉ thị thô
Khi nhìn thấy dữ liệu chỉ thị thô, dữ liệu đó thường tương ứng với các tham số đầu vào. Không khó để lấy dữ liệu theo cách này và chúng ta không phải giải tuần tự hóa bất cứ thứ gì, một công việc có thể khiến nhiều người nản lòng.
Ví dụ, hãy lấy dữ liệu này từ giao dịch sau:
8e772b6da2340bb12e783a66000000006d9415754e00000037ca8a3a270000003c00000000000000010000000000000000010000000000000000010000000000000000
Bây giờ, hãy truy cập hexed.it và dán dữ liệu vào:
Bây giờ, hãy tìm các tham số đầu vào sau:
Ở phía bên phải, hãy nhập 336971797613 vào ô tìm kiếm. Nhấp vào tìm tiếp theo để xem giá trị inAmount nằm ở đâu trong các byte.
Như bạn có thể thấy, công cụ cho biết các byte từ 6D đến 00 cuối cùng trước 37 chứa giá trị inAmount. Mỗi giao dịch DCA có định dạng mà chúng ta giám sát đều có inAmount tại cùng một vị trí byte. 8 cặp byte tiếp theo chứa giá trị inAmountPerCycle. Đây là một cách hữu ích để tìm các giá trị thường không rõ ràng trong JSON của giao dịch, vì JSON chủ yếu chứa địa chỉ và số dư chứ không phải tham số đầu vào.
Bạn đã làm được!
Nếu đã đọc đến đây, bạn hẳn đã có nền tảng vững chắc để bắt đầu sử dụng Helius Geyser WebSockets. Khó nhất là bước khởi đầu. Giờ đây, bạn có thể giám sát ví, chương trình, pool và bất cứ thứ gì mình muốn. Không cần đến các API có mức giá trên trời hay bất cứ thứ gì tương tự. Nếu gặp vấn đề hoặc có câu hỏi, bạn luôn có thể đặt câu hỏi trong Discord của Helius!
Tài nguyên
Bài viết liên quan
Đăng ký nhận tin từ Helius
Luôn cập nhật những thông tin mới nhất về phát triển Solana và nhận thông báo khi chúng tôi đăng bài


