本文介绍了如何使用Helius的Geyser增强WebSockets监控Solana交易,包括代码示例和过滤逻辑的实现。文章提供了多个实例,包括Raydium流动性池监控、pump.fun代币监控及Jupiter DCA监控,展示了如何提取和处理交易数据,同时对JSON信息结构进行了详细分析,是实现实时交易监控的实用指南。
很长一段时间以来,我一直在想这些监控应用和机器人是如何工作的。在一次艰难的搜索后,我遇到了 Helius 的 Geyser 增强版 WebSocket。虽然使用它们并不是免费的(你需要一个商业或专业计划),但它们是一个非常强大的工具。
使用 Geyser 增强版 WebSocket 结合 Helius 非常简单——粘贴你想要监控的地址并运行一些代码。你可以监控任何东西:NFT、钱包、程序、平台,字面意义上的任何东西。你可以制作钱包追踪器、代币追踪器、买卖监控器、交易量监控器等。
这种类型的 API 和工具有时会花费数千美元,但这篇文章将展示几个可以用极少的成本制作的示例。用 $499(商业计划),制作自己的工具的投资回报率是无限的;与你人共享工具或自己使用将赋予你在实时 Solana 数据上工作的优势和无尽的方式。
如果你是第一次使用 Helius Geyser WebSocket,请阅读这篇 博客文章 和 文档。你可以跟着这个示例,因为这个第一个示例应该相当容易理解。
const WebSocket = require('ws');
// 创建 WebSocket 连接
const ws = new WebSocket('wss://atlas-mainnet.helius-rpc.com?api-key=YOUR_API_KEY');
// 向 WebSocket 服务器发送请求的函数
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));
}
这段代码的第一部分非常简单。我们设置了我们的 API 密钥并发送了一个请求,其中监视的账户是 675kPX9… 也就是 Raydium 链上程序。通过这个请求,我们将获得所有与 Raydium 交互的确认交易,且没有失败。发送这个请求通常会导致每秒返回数千笔交易,因此我们来看看一种简单的过滤噪声的方法。
以下代码片段是主要的事件处理过滤逻辑。on message 函数根据日志解析从交易中返回的数据。在这个实例中,我们正在查看所有带有 “ initialize2: InitializeInstruction2
” 日志的交易——它告诉我们用户何时在 Raydium 上创建新的流动性池。你也可以使用任何其他日志,具体取决于你想监控什么。我建议进行一次测试交易,查看要监控的内容。例如,你可以为池添加流动性,并查看该交易的日志是什么样的,然后过滤这些日志,以获取所有添加流动性的交易。
ws.on('open', function open() {
console.log('WebSocket 已开启');
sendRequest(ws); // 一旦 WebSocket 开启,发送请求
});
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; // 提取签名
const accountKeys = result.transaction.transaction.message.accountKeys.map(ak => ak.pubkey); // 仅提取公钥
if (logs && logs.some(log => log.includes("initialize2: InitializeInstruction2"))) {
// 记录签名和 AMM ID 的公钥
console.log('交易签名:', signature);
console.log('AMM ID:', accountKeys[2]); // 更正为 AMM ID 的第三个账户
}
} catch (e) {
}
});
ws.on('error', function error(err) {
console.error('WebSocket 错误:', err);
});
ws.on('close', function close() {
console.log('WebSocket 已关闭');
});
知道交易有日志后,我们提取了两个东西:签名,以便我们可以比较并验证程序的准确性,以及账户密钥,这是 AMM ID(即上述池的 AMM 地址),因为许多机器人/狙击手使用 AMM ID 来发起交易。
示例输出
如果你想要,你还可以提取更多数据。例如,你可以获取创建者(通常是 accountKeys
中的第 17 个公钥),创建池所用的代币(来自于预-/后代币余额或内部指令),代币的数量,甚至是创建者收到的 LP 代币。创建者收到的 LP 代币的数量可以用来制作流动性销毁监视器,因为当池的创建者销毁他们的 LP 代币时,基本上是他们在池中的份额的收据,他们撤销了去除流动性的能力(如果铸造权限仍在他们手中,他们可能会铸造更多代币并将其出售到池中,要小心)。
为了获取更多数据,你可以查看交易的 JSON 结构,该结构可以在官方的 Solana 文档 中找到。另外,你也可以保存 JSON 响应并使用这个 格式化工具 来查看结构。这一点非常重要,因为一旦你了解了交易的一般结构,你就可以提取使用区块浏览器时看到的任何数据。
以下是一般的 JSON,其中包含两个重要的、深度嵌套的对象:Transaction
和 Meta
。在 Transaction
里面,我们有消息对象,存储最近的块哈希、accountKeys
和 instructions
。在 Meta
中,我们有预-/后- Balances
(lamport 余额)、innerInstructions
、logMessages
和预-/后- token balances
。
JSON 结构
看到 Solana 上当前的 Meme 代币,我制作了一个非常简单的 pump.fun 监视器。只需将我们监控的地址交换为 “ 6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P
”,这是 pump.fun 链上程序,并使用这个 on message 函数:
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; // 提取签名
const accountKeys = result.transaction.transaction.message.accountKeys.map(ak => ak.pubkey);
if (logs && logs.some(log => log.includes('Program log: Instruction: InitializeMint2'))) {
console.log('新的 pump.fun 代币!');
console.log('tx:', signature);
console.log('创建者:', accountKeys[0]);
console.log('代币:', accountKeys[1]);
// 如果存在,记录第一个和第二个账户密钥
}
} catch (e) {
}
});
类似于我们与 Raydium 的方法,我们查看所有与 pump.fun 程序交互的交易,并根据所需日志进行过滤(我们在这里有一个模式:日志、日志、日志)。一旦我们得到了我们想要的日志的交易,我们就提取签名、创建者和代币本身!
你可以看到,accountKeys
和签名在 JSON 中的相同位置嵌套,像 Raydium 示例一样,因此很容易获取创建者、代币和签名。大多数交易的 JSON 结构基本相同,但某些值可能位于不同的位置或顺序不同——你将在下一个示例中看到这一点。
无论如何,这都是一个相当简单的新 pump.fun 代币监视器。使用这些信息,你可以购买代币或仅知道它存在以便进行监控。你还可以将其与不同的 Helius DAS API 结合使用,制作一个更强大的工具,例如获取元数据或有关创建者的信息。
示例输出
这个示例相对小众,但它展示了你可以通过日志看到所有内容。通过以下请求,我们将查看 Jupiter 的 DCA(Dollar-Cost Average(美元成本平均法))程序,具体来说,查看使用 Jupiter 进行的每一笔 DCA 购买。这示例可用于套利交易计算或查看潜在市场影响。查看 DCA 购买的潜在市场影响是非常未被利用的数据。
正如你所见,到目前为止,并没有做复杂的事情。我们在顶部添加了一个 base58 导入,并将地址更改为 Jupiter DCA 程序。接下来的部分可能看起来相当复杂,实际上确实是,但我觉得有必要提供一些稍微复杂的示例。
const WebSocket = require("ws");
const bs58 = require("bs58");
// 创建 WebSocket 连接
const ws = new WebSocket(
"wss://atlas-mainnet.helius-rpc.com?api-key=YOUR_API_KEY"
);
// 向 WebSocket 服务器发送请求的函数
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));
}
我们在这里做的是:
User
、Input Mint
和 Output Mint
。Instruction Data Raw
并将字节转换为输入参数,如用户输入的金额、他们的出售频率和每次出售的金额。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;
// 仅提取公钥
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("用户:", instruction.accounts[2]);
console.log("输入铸币:", instruction.accounts[3]);
console.log("输出铸币:", 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);
// 反转字节顺序以进行小端解释
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("每", cycleFrequency.toString() + "秒的周期频率");
console.log("输入金额:", inAmount.toString());
console.log("每周期的金额:", inAmountPerCycle.toString());
}
}
});
}
} catch (e) {}
});
预期输出
当你看到原始指令数据时,它通常对应于输入参数。通过这种方式获取数据并不难,我们不需要反序列化任何东西,这对大多数人来说可能是个障碍。
例如,让我们以这个交易中的数据为例:交易链接:
8e772b6da2340bb12e783a66000000006d9415754e00000037ca8a3a270000003c00000000000000010000000000000000010000000000000000010000000000000000
现在,访问 hexed.it 并粘贴它:
Hexed 粘贴界面, 现在,让我们找到这些输入参数:
交易中的输入参数在右侧,我们输入 336971797613
进行搜索。单击查找下一个,向我们显示 inAmount
值在字节中的位置。
Hexed UI 显示对应于我们输入参数 336971797613 的字节
如你所见,它显示字节从 6D
到最后的 00
之前的 37
包含了 inAmount
的值。我们监控的每个交易的 DCA 格式都有 inAmount
值在相同的字节位置。接下来的 8 个字节对保存着 inAmountPerCycle
的值。这是一种很好的方法,可以找到在交易 JSON 中通常不清晰的值,因为 JSON 大多数情况下有地址和余额,而不是输入参数。
如果你能看到这里,那么你应该已经有了一个很好的基础来开始使用 Helius Geyser WebSocket。最难的部分是开始。现在,你可以监控钱包、程序、池以及任何你想要的东西。没有必要使用价格高昂的 API 或其他任何东西。
- 原文链接: helius.dev/blog/how-to-m...
- 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!