本指南仅供学习目的

本指南仅供教育目的,不应被视为财务建议。交易机器人可能存在风险,可能导致经济损失。请务必自行研究,并考虑在使用交易机器人或参与交易活动之前咨询财务顾问。确保在使用真实资金之前,你运行的所有代码都是安全且经过彻底测试的。

概述

在本指南中,我们将学习如何创建一个 Solana 交易机器人,该机器人使用QuickNode 的 Metis 插件 和Yellowstone gRPC 来复制指定钱包在 Pump.fun DEX 上的交易。此指南面向具备 JavaScript、Solana 和基本 DeFi 概念知识的开发者。

你将要做的事情

  1. 了解 Pump.fun 和 Yellowstone 的概述
  2. 创建一个 JavaScript 交易机器人,它监控 Pump.fun 上钱包的交易,并根据预定义策略复制其购买交易
  3. 模拟目标钱包交易并测试机器人的功能

更喜欢使用 Solana Web3.js 2.0 或 TypeScript?

如果你更喜欢使用 TypeScript 或新的 Solana Web3.js 2.0 库,请查看我们关于使用 Yellowstone 监控程序和如何使用 Solana Web3.js 2.0 构建 Pump.fun API 的指南。

你将需要的东西

  • 对 Solana 开发 和 DeFi 概念 的中级知识
  • 具备 JavaScript 和 Node.js 的经验
  • 一个具有 SOL 余额的 Solana 文件系统钱包(运行 solana-keygen -h 获取支持以创建新钱包的帮助)
  • 已启用 Yellowstone gRPC 插件的 QuickNode 账户

Solana 主要网 RPC 端点

使用你的 QuickNode 端点连接到 Solana 集群

要在 Solana 上进行构建,你需要一个 API 端点以连接到网络。你可以使用公共节点或自行部署和管理基础架构;但是,如果你希望享受 8 倍的更快响应时间,可以将繁重的工作交给我们。

了解为什么超过 50% 的 Solana 项目选择 QuickNode,并在这里 注册免费账户。我们将使用一个 SolanaMainnet 端点。

复制 HTTP 提供者链接:

什么是 Metis?

Metis 是一个强大的工具,帮助开发者访问 Solana 上的流动性。通过集成 Jupiter 的 V6 交换 API、限制订单 API、Pump.fun 交易、交易 WebSocket 等,你可以访问构建强大交易工具所需的工具,以访问 Solana 上的多个 DeFi 协议。Metis 可作为 QuickNode 市场上的附加组件, 在这里 或者可以通过 JupiterAPI.com 访问公共端点。

使用 Pump.fun API

在本指南中,我们将重点关注 /pump-fun/swap 端点(文档),该端点允许我们获取用于在 Pump.fun 上执行交换的序列化交易。此端点需要以下参数:

  • wallet:执行交易的钱包的公钥
  • type:交易类型("BUY" 或 "SELL")
  • mint:被交易的代币的 mint 地址
  • inAmount:输入代币的数量(以原始单位表示)
  • priorityFeeLevel(可选):交易的优先费用级别("low"、"medium"、"high" 或 "auto")
  • slippageBps(可选):允许的最大滑点(以基点表示)

来自该端点的响应包含一个 base64 编码的 Solana 交易,可以签名并发送到网络以执行交易。

什么是 Yellowstone?

Yellowstone 是一个市场附加组件,提供基于 gRPC 的 API,使开发者能够创建自定义订阅并在 Solana 网络上实时接收事件更新。这使其成为构建需要实时监控区块链活动的应用程序(例如交易机器人、分析平台和去中心化应用程序 dApps)的优秀工具。

要使用 Yellowstone,我们需要创建一个订阅请求,指定我们希望监控的账户、交易和其他事件。Yellowstone 将流式传输我们指定事件的实时更新。

有关 Yellowstone 的更多信息,请查看:

  • 文档
  • 入门指南

设置项目

在开始构建交易机器人之前,让我们设置项目并安装必要的依赖项。

  1. 为你的项目创建一个新目录,并在终端中导航到该目录。

  2. 通过运行以下命令初始化一个新的 Node.js 项目:

npm init -y
  1. 通过运行以下命令安装所需的依赖项:
npm install @solana/web3.js@1 bs58 dotenv @triton-one/yellowstone-grpc
  • @solana/web3.js:与 Solana 区块链交互的 Solana Web3.js 库的旧版本
  • bs58:用于处理 Base58 编码/解码的库
  • dotenv:用于从 .env 文件加载环境变量的库
  • @triton-one/yellowstone-grpc:Yellowstone gRPC 客户端库
  1. 在你的项目目录中创建一个名为 bot.js 的新文件。

  2. 在你的项目目录中创建一个 .env 文件,并添加以下环境变量:

SOLANA_RPC=<your_solana_rpc_endpoint> # https://example.quiknode.pro/replace-me-123/ SECRET_KEY=<your_wallet_secret_key> # [0, 0, ..., 0] METIS_ENDPOINT=<your_metis_endpoint> # https://jupiter-swap-api.quiknode.pro/REPLACE_ME YELLOWSTONE_ENDPOINT=<your_yellowstone_endpoint> # https://example.solana-mainnet.quiknode.pro:10000 YELLOWSTONE_TOKEN=<your_yellowstone_token> # abc...xyz

替换占位符为你的实际值:

  • SOLANA_RPC:你的 QuickNode Solana 主要网 RPC 端点(你可以在 QuickNode 仪表板 中找到此内容)
  • SECRET_KEY:你的 Solana 钱包的秘密密钥(JSON 数组格式,例如 [0, 0, ..., 0])。确保此钱包充满 SOL 以便机器人能执行交易。
  • METIS_ENDPOINT:你的 QuickNode Metis 端点用于 Pump.fun API(例如 https://jupiter-swap-api.quiknode.pro/...)。如果没有 Metis 附加组件,你可以使用公共端点:https://public.jupiterapi.com (注意:公共端点可能会产生交易费用——请查看 jupiterapi.com 获取详细信息)。
  • YELLOWSTONE_ENDPOINT:你的 Yellowstone 端点(注意:这应是以 gRPC 端点,结尾需为 :10000,有关更多信息,请查看 这里)
  • YELLOWSTONE_TOKEN:你的 Yellowstone API Token(在 这里 查找你的Token)

构建交易机器人

现在我们已经设置好了项目,让我们开始构建交易机器人。🤖

从高层来看,我们将要做的事情如下:

  • 创建一个交易机器人类,初始化必要的配置和依赖项。
  • 创建一个监控功能,使用 Yellowstone 监听目标钱包在 Pump.fun 上的购买交易。
  • 实现一个方法,这个方法响应来自目标交易的特定交易,利用 Pump.fun API 获取复制的交换交易并在 Solana 上执行交易。
  • 记录成功的交易日志文件以进行跟踪和分析。

配置机器人

打开 bot.js 文件并添加以下代码:

require("dotenv").config(); const fs = require("fs"); const fetch = require("node-fetch"); const bs58 = require("bs58").default; const { Connection, Keypair, VersionedTransaction, LAMPORTS_PER_SOL, PublicKey, } = require("@solana/web3.js"); const Client = require("@triton-one/yellowstone-grpc").default; const { CommitmentLevel } = require("@triton-one/yellowstone-grpc"); class CopyTradeBot { config = { WATCH_LIST: [\ "WALLET_TO_TRACK_1",\ "WALLET_TO_TRACK_2",\ "WALLET_TO_TRACK_3",\ //...\ ], PUMP_FUN: { PROGRAM_ID: "6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P", FEE_ACCOUNT: "CebN5WGQ4jvEPvsVU4EoHEpgzq1VV7AbicfhtW4xC9iM", BUY_DISCRIMINATOR: Buffer.from([102, 6, 61, 18, 1, 218, 235, 234]), SELL_DISCRIMINATOR: Buffer.from([51, 230, 133, 164, 1, 127, 131, 173]), TOKEN_DECIMALS: 6, TARGET_ACCOUNTS: { BUY: [\ { name: "mint", index: 2 },\ { name: "user", index: 6 },\ ], SELL: [\ { name: "mint", index: 2 },\ { name: "user", index: 6 },\ ], }, }, MIN_TX_AMOUNT: LAMPORTS_PER_SOL / 1000, BUY_AMOUNT: LAMPORTS_PER_SOL / 1000, LOG_FILE: "pump_fun_swaps.json", COMMITMENT: CommitmentLevel.CONFIRMED, TEST_MODE: true }; constructor() { this.validateEnv(); this.connection = new Connection(process.env.SOLANA_RPC); this.wallet = Keypair.fromSecretKey( Uint8Array.from(JSON.parse(process.env.SECRET_KEY)) ); console.log("🤖 机器人钱包:", this.wallet.publicKey.toBase58()); console.log("监控地址:"); this.config.WATCH_LIST.forEach((address) => console.log(" -", address)); } // ... (其他方法将在这里添加) } 

这段代码设置了交易机器人的初始配置。配置每个部分的作用如下:

  • WATCH_LIST:一个监控交易的Wallet地址数组。为了本示例,请从你的浏览器钱包中使用一个钱包地址(例如 Phantom、SolFlare、Backpack 等)。我们将在本指南的后面使用该钱包在 Pump.fun 上执行交易。
  • PUMP_FUN:一个包含 Pump.fun 程序配置详细信息的对象,包括程序 ID、费用账户、购买和出售指令的识别符、代币小数位数以及指令中目标账户(mint 用户)索引的配置。这些都是从 Pump.fun 的程序 IDL 和指令中得到的已知常量。在我们的 Solana 程序 IDL 指南 中获取更多信息。
  • MIN_TX_AMOUNT:机器人考虑交易所需的最低 SOL 数量(如果目标钱包消费的金额小于该金额,我们将直接忽略该交易)。
  • BUY_AMOUNT:机器人用于执行购买交易的 SOL 数量。为了演示,我们将只设置一个静态购买金额——如果我们看到一个想要复制的交易,我们将固定金额的 SOL 进行复制交易。完成指南后,你可以根据自己的用例自行尝试不同策略。
  • LOG_FILE:机器人将成功交易记录的文件路径。
  • COMMITMENT:Yellowstone 订阅的承诺级别。
  • TEST_MODE:启用/禁用测试模式的标志。当设置为 true 时,机器人将仿真交易,而不是在 Solana 网络上实际执行。这对于测试机器人的功能而不冒用真实资金是有用的。

constructor 方法验证所需的环境变量,创建了一个 Solana 连接,并使用提供的秘密密钥初始化机器人的钱包。

让我们添加我们的其他方法!

验证环境变量

我们在上面的构造函数中已经添加了一个 validateEnv() 方法,用于确保所需的环境变量已设置。该方法在 CopyTradeBot 类实例化时被触发。如果缺少任何必需的变量,机器人将抛出错误并退出。

在你的 CopyTradeBot 类中添加以下方法以验证所需的环境变量:

 validateEnv = () => { const requiredEnvs = [\ "SOLANA_RPC",\ "SECRET_KEY",\ "METIS_ENDPOINT",\ "YELLOWSTONE_ENDPOINT",\ "YELLOWSTONE_TOKEN",\ ]; requiredEnvs.forEach((env) => { if (!process.env[env]) { throw new Error(`缺少必需的环境变量: ${env}`); } }); }; 

这将检查所有必需的环境变量在继续之前是否已设置。

获取交换交易

让我们创建一个 fetchSwapTransaction() 方法,该方法将负责与 Pump.fun API(通过你的 Metis 端点)通信,以获取执行交换所需的序列化交易。你提供的信息包括钱包地址、交换类型(例如 "BUY")、代币 mint 和交换金额。

 fetchSwapTransaction = async ({ wallet, type, mint, inAmount, priorityFeeLevel = "high", slippageBps = "100", }) => { const body = JSON.stringify({ wallet, type, mint, inAmount, priorityFeeLevel, slippageBps, }); const res = await fetch(`${process.env.METIS_ENDPOINT}/pump-fun/swap`, { method: "POST", headers: { "Content-Type": "application/json" }, body, }); if (!res.ok) { throw new Error(`交换指令获取错误: ${await res.text()}`); } return res.json(); }; 

该方法的两个主要组件如下:

  1. body:包含 Pump.fun API 生成交换交易所需的参数。
  2. fetch:向 Pump.fun 的 /swap 端点发送 POST 请求,并提供参数。如果成功,你将获得一个 JSON 响应,其中包含一个 base64 编码的交易。

有关此方法的更多信息,请查看我们 Pump.fun API 文档 在这里。

签署交易

一旦 Pump.fun API 返回一个 base64 编码的交易,你需要使用机器人的 Keypair 在本地进行签名。让我们在 CopyTradeBot 类中添加一个负责此操作的 signTransaction() 方法:

 signTransaction = async (swapTransaction) => { const transaction = VersionedTransaction.deserialize( Buffer.from(swapTransaction, "base64") ); const latestBlockHash = await this.connection.getLatestBlockhash(); transaction.message.recentBlockhash = latestBlockHash.blockhash; transaction.sign([this.wallet]); const txBuffer = Buffer.from(transaction.serialize()); const txBase64 = txBuffer.toString("base64"); return txBase64; }; 

因为交易已经序列化,所以我们需要将其反序列化为一个 VersionedTransaction 对象,用机器人的钱包进行签名,然后重新序列化为原始字节以供广播:

  1. 反序列化:将 base64 编码的交易转换为一个 VersionedTransaction 对象。
  2. 获取最新区块哈希:从 Solana 网络中获取最新的区块哈希并将其应用于交易。
  3. 签名:将机器人的钱包签名添加到交易中。
  4. 序列化:返回完全签名的交易的 base64 编码字符串,准备广播。

发送和确认交易

下一步是将签名后的交易广播到 Solana 网络。在你的机器人类中添加以下 sendAndConfirmTransaction() 方法:

 sendAndConfirmTransaction = async (signedTxBase64) => { try { const txid = await this.connection.sendEncodedTransaction(signedTxBase64, { skipPreflight: false, encoding: 'base64' }); const timeout = 30 * 1000; const pollInterval = 3 * 1000; const start = Date.now(); while (Date.now() - start < timeout) { const response = await this.connection.getSignatureStatuses([txid]); if (!response) { await new Promise(resolve => setTimeout(resolve, pollInterval)); continue; } const statuses = response.value; if (!statuses || statuses.length === 0) { await new Promise(resolve => setTimeout(resolve, pollInterval)); continue; } const status = statuses[0]; if (status === null) { await new Promise(resolve => setTimeout(resolve, pollInterval)); continue; } if (status.err) { throw new Error(`交易失败: ${JSON.stringify(status.err)}`); } if (status.confirmationStatus && (status.confirmationStatus === 'confirmed' || status.confirmationStatus === 'finalized')) { return txid; } await new Promise(resolve => setTimeout(resolve, pollInterval)); } throw new Error(`交易确认超时,超时后 ${timeout}ms`); } catch (error) { throw {error, base64: Buffer.from(rawTransaction).toString("base64")}; } }; 

该方法将签名后的交易发送到 Solana 网络,并等待其确认。使用以下步骤:

  1. sendEncodedTransaction():将签名交易作为 base64 字符串发送到集群,并返回交易签名(txid)——请注意,由于我们在上一步中序列化了交易,因此可以使用 sendEncodedTransaction() 方法。创建该交易的 API 已经模拟了交易以计算计算单元,因此可以跳过预检查。
  2. 最后,我们创建一个简单的轮询函数,使用 getSignatureStatuses() 检查交易状态,直到确认或超时。有关最佳实践的更多信息,请查看我们的文档 在这里。

记录交换

为了记录和调试,logSwap() 方法将每个交换的 JSON 记录写入日志文件:

 logSwap = (swapLog) => { const logs = fs.existsSync(this.config.LOG_FILE) ? JSON.parse(fs.readFileSync(this.config.LOG_FILE, "utf-8")) : []; logs.push(swapLog); fs.writeFileSync(this.config.LOG_FILE, JSON.stringify(logs, null, 2)); }; 

这里的工作原理如下:

  1. 现有日志:如果文件 pump_fun_swaps.json 存在,将其读取并解析为数组。
  2. 附加新日志:将 swapLog 对象添加到数组中。
  3. 回写:将更新后的数组写回磁盘。

处理鲸鱼购买

每当我们检测到来自 WATCH_LIST 中“鲸鱼”的购买交易超过某个阈值(MIN_TX_AMOUNT)时,我们需要执行某些逻辑。让我们在 CopyTradeBot 类中创建一个方法 handleWhaleBuy(),在知道“鲸鱼”完成达成我们标准的购买交易后执行复制交易。将以下方法添加到你的 CopyTradeBot 类中:

 handleWhaleBuy = async (whalePubkey, tokenMint, lamportsSpent, copiedTxid) => { if (lamportsSpent < this.config.MIN_TX_AMOUNT) return; try { const inAmount = this.config.BUY_AMOUNT; const response = await this.fetchSwapTransaction({ wallet: this.wallet.publicKey.toBase58(), type: "BUY", mint: tokenMint, inAmount, slippageBps: "300", }); if (!response.tx) { throw new Error(`意外的响应格式: ${JSON.stringify(response)}`); } const { tx } = response; const signedTransaction = await this.signTransaction(tx); let txid = '模拟-TxID'; if (!this.config.TEST_MODE) { txid = await this.sendAndConfirmTransaction(signedTransaction); } console.log("🎯 - 复制 - TxID:", txid); this.logSwap({ event: "COPY_BUY", txid, copiedTxid, tokenMint, lamportsSpent, whalePubkey, timestamp: new Date().toISOString(), }); } catch (err) { this.logSwap({ event: "COPY_BUY_ERROR", error: typeof err === "string" ? err : err && typeof err.message === "string" ? err.message : JSON.stringify(err, null, 2) || "未知错误", copiedTxid, timestamp: new Date().toISOString(), }); } }; 

让我们查看此方法的关键组件:

  1. lamportsSpent:目标钱包在原始交易中花费的总 SOL。这必须超过 MIN_TX_AMOUNT,我们才会去复制该交易。你当然可以在这里实现自己的逻辑。
  2. inAmount:我们机器人将花费多少 SOL 来复制交易。它设置为我们配置中的 BUY_AMOUNT
  3. fetchSwapTransaction():我们请求 Pump.fun 进行我们的购买交易的交易。
  4. signTransaction():使用我们的钱包对其进行签名。
  5. sendAndConfirmTransaction():将签名后的交易发送到 Solana 网络并等待确认。(如果启用 TEST_MODE 则跳过)
  6. logSwap():将复制交易尝试记录在日志文件中。

构建 Yellowstone 订阅

现在我们已经在执行方法中处理完了交易,那么我们需要设置一个 Yellowstone 订阅,以便监控目标钱包在 Pump.fun 上的购买交易。Yellowstone 将允许我们监听符合特定条件的交易,然后我们可以解析交易指令数据,以确保我们做出适当的响应。

创建订阅请求

我们需要告诉 Yellowstone 我们关心监测哪些地址或程序。让我们在 CopyTradeBot 类中添加几个方法:

 createSubscribeRequest = () => { const { WATCH_LIST, PUMP_FUN, COMMITMENT } = this.config; return { accounts: {}, slots: {}, transactions: { pumpFun: { accountInclude: WATCH_LIST, accountExclude: [], accountRequired: [PUMP_FUN.FEE_ACCOUNT, PUMP_FUN.PROGRAM_ID], }, }, transactionsStatus: {}, entry: {}, blocks: {}, blocksMeta: {}, commitment: COMMITMENT, accountsDataSlice: [], ping: undefined, }; }; sendSubscribeRequest = (stream, request) => { return new Promise((resolve, reject) => { stream.write(request, (err) => { if (err) reject(err); else resolve(); }); }); }; handleStreamEvents = (stream) => { return new Promise((resolve, reject) => { stream.on("data", this.handleData); stream.on("error", (error) => { console.error("流错误:", error); reject(error); stream.end(); }); stream.on("end", () => { console.log("流结束"); resolve(); }); stream.on("close", () => { console.log("流关闭"); resolve(); }); }); }; 

在这里我们定义了三个函数:

  1. createSubscribeRequest():构建带有我们关心的账户/过滤器的订阅请求对象。accountsRequired 将确保我们只看到涉及 Pump.fun 的费用账户和程序 ID 的交易——费用账户帮助我们标识 Pump.fun 交易与程序上的其他类型的交易。accountInclude 过滤器确保我们只看到来自 WATCH_LIST 钱包的交易(你可以将它们视为“或者”连接,因此只要一笔交易包含这些账户中的任何一笔,就会被显示)。
  2. sendSubscribeRequest():将订阅发送到网络。
  3. handleStreamEvents():勾勒出我们将如何处理传入的数据、错误和流关闭。重要的是,在此,我们接收到新数据时调用 this.handleData。换句话说,当 Yellowstone 返回符合我们过滤条件的交易时,我们将使用 handleData() 方法处理它。

处理传入交易

handleData() 方法是我们解析 Pump.fun 交易详细信息的地方。这对于我们的用例极为重要,因为这是我们将确定发生了哪种类型的交易(例如购买或出售)以及交易规模的地方。将以下方法添加到你的机器人类中,我们将对此进行详细讲解:

 handleData = (data) => { if ( !this.isSubscribeUpdateTransaction(data) || !data.filters.includes("pumpFun") ) { return; } const transaction = data.transaction?.transaction; const message = transaction?.transaction?.message; const innerInstructions = transaction?.meta?.innerInstructions; const flattenedInnerInstructions = innerInstructions?.flatMap((ix) => ix.instructions || []) || []; const allInstructions = [\ ...message.instructions,\ ...flattenedInnerInstructions,\ ]; if (!transaction || !message || transaction?.meta?.err) return; const formattedSignature = this.convertSignature(transaction.signature); const matching = allInstructions.find(this.matchesInstructionDiscriminator); if (!matching) { console.log(`❓ - 未知 - TxID: ${formattedSignature.base58}`); return; } const { amount, solAmount } = this.getInstructionData(matching.data); if (solAmount < this.config.MIN_TX_AMOUNT) return; const txType = this.getTransactionType(matching.data); const icon = txType === "SELL" ? "📉" : txType === "BUY" ? "🎯" : "❓"; console.log(`${icon} - ${txType} - TxID: ${formattedSignature.base58}`); const accountKeys = message.accountKeys; const accountsToInclude = this.config.PUMP_FUN.TARGET_ACCOUNTS[txType]; const includedAccounts = accountsToInclude.reduce((acc, { name, index }) => { const accountIndex = matching.accounts[index]; const publicKey = accountKeys[accountIndex]; acc[name] = new PublicKey(publicKey).toBase58(); return acc; }, {}); if (includedAccounts.mint) { console.log(" Mint:", includedAccounts.mint); } if (includedAccounts.user) { console.log(" User:", includedAccounts.user); } console.log( " 代币数量:", amount / Math.pow(10, this.config.PUMP_FUN.TOKEN_DECIMALS) ); console.log(" SOL 数量:", solAmount / LAMPORTS_PER_SOL); if (txType === "BUY") { (async () => { try { await this.handleWhaleBuy( includedAccounts.user, includedAccounts.mint, solAmount, formattedSignature.base58 ); } catch (error) { console.error("处理(handleWhaleBuy)中的错误:", error); } })(); } }; 

这里我们做的工作包括:

  • 首先,我们确保传入的数据是来自 Yellowstone 的交易更新,且涉及 Pump.fun 程序。如果由于某种原因不满足这两个条件,则我们将其忽略。
  • 接下来,我们从传入数据中提取交易数据和指令。我们将所有指令展平为一个数组,以便更方便地处理内部和外部指令。
  • 我们检查交易是 Pump.fun 上的买入还是卖出 —— 我们将稍后定义该方法。
  • 我们然后从交易数据中提取金额和花费的 SOL。
  • 如果交易包括 Pump.fun 的购买:我们调用 handleWhaleBuy() 函数,并传递相关交易数据。

你将注意到我们在这里使用了一些尚未定义的辅助方法。让我们现在添加这些。将以下剩余的辅助方法添加到你的机器人类中。

 isSubscribeUpdateTransaction = (data) => { return ( "transaction" in data && typeof data.transaction === "object" && data.transaction !== null && "slot" in data.transaction && "transaction" in data.transaction ); }; convertSignature = (signature) => { return { base58: bs58.encode(Buffer.from(signature)) }; }; parseU64 = (data, offset) => { const slice = data.slice(offset, offset + 8); const dataView = new DataView( slice.buffer, slice.byteOffset, slice.byteLength ); return Number(dataView.getBigUint64(0, true)); }; getInstructionData = (instructionData) => { const amount = this.parseU64(instructionData, 8); const solAmount = this.parseU64(instructionData, 16); return { amount, solAmount }; }; getTransactionType = (instructionData) => { if (!instructionData) return "Unknown"; if ( this.config.PUMP_FUN.SELL_DISCRIMINATOR.equals( instructionData.slice(0, 8) ) ) { return "SELL"; } else if ( this.config.PUMP_FUN.BUY_DISCRIMINATOR.equals( instructionData.slice(0, 8) ) ) { return "BUY"; } return "Unknown"; }; matchesInstructionDiscriminator = (ix) => { if (!ix?.data) return false; return ( this.config.PUMP_FUN.SELL_DISCRIMINATOR.equals(ix.data.slice(0, 8)) || this.config.PUMP_FUN.BUY_DISCRIMINATOR.equals(ix.data.slice(0, 8)) ); }; 

让我们解释其中每个的作用:

  • isSubscribeUpdateTransaction():检查传入的数据是否是来自 Yellowstone 的有效交易对象。
  • convertSignature():将交易签名转换为 base58 编码字符串,以便更容易进行日志记录。
  • parseU64():从指令数据中以给定偏移量解析 64 位无符号整数。这样我们就可以获取像代币数量和 SOL 数量的指令数据。
  • getInstructionData():根据程序 IDL 中已知的偏移量从指令数据中提取金额和 SOL 数量。
  • getTransactionType():根据指令数据的标志符确定交易是买入还是卖出,这些标志符包含在我们的配置中(来自程序 IDL 的已知值)。
  • matchesInstructionDiscriminator():检查指令是否匹配买入或卖出的标志符。

初始化我们的机器人

最后,让我们创建一个方法来初始化我们的 Yellowstone 实例,以及一个方法来启动我们的机器人。将以下方法添加到你的 CopyTradeBot 类中:

 monitorWhales = async () => { console.log("监控鲸鱼..."); const client = new Client( process.env.YELLOWSTONE_ENDPOINT, process.env.YELLOWSTONE_TOKEN, {} ); const stream = await client.subscribe(); const request = this.createSubscribeRequest(); try { await this.sendSubscribeRequest(stream, request); console.log( "Geyser 连接已建立 - 正在监视鲸鱼 Pump.fun 活动。" ); await this.handleStreamEvents(stream); } catch (error) { console.error("订阅过程中的错误:", error); stream.end(); } }; start = async () => { console.log("🤖 Pump.fun 复制交易机器人正在启动..."); this.monitorWhales(); }; 

这些方法做的事情如下:

  • monitorWhales():使用你在 .env 中配置的端点和Token建立一个 Yellowstone 客户端。然后创建我们关心的 Pump.fun 交易的订阅,并监听传入数据。最终流式传输数据将传递给 handleStreamEvents 进行处理。
  • start():通过调用 monitorWhales() 启动机器人。这是我们的机器人的入口点。

让我们在 bot.js 文件中添加一行最后的代码,以便运行脚本时启动我们的机器人。在 CopyTradeBot 类外部添加以下主函数:

async function main() { const bot = new CopyTradeBot(); await bot.start(); } main().catch(console.error);

就是这样!如果你遇到任何问题,你可以在我们的 GitHub 示例库中查看此项目的完整代码, 在这里。让我们测试一下吧!

测试机器人

此时,你的 bot.js 文件包含监控目标钱包或钱包(WATCH_LIST)并在 SOL 数量超过 MIN_TX_AMOUNT 时复制其 Pump.fun 购买的完整逻辑。为了演示/测试,请确保你的 WATCH_LIST 只是你控制的单个钱包地址。我们将在下一步中使用此地址复制自己的交易。

请小心真实资金

此示例在主要网上进行,如果将 TEST_MODE 设置为 false,将执行真实交易。请小心真实资金,并考虑在 本地网络 上测试或使用少量 SOL。链上交易是不可逆的,如果未正确执行,可能会导致资金损失。

  1. 为你的机器人钱包注资:确保你的机器人钱包(.env 文件中的钱包)拥有足够的 SOL 来支付所有兑换费用和交易费用。
  2. 运行机器人:
node bot.js
  1. 触发交易:使用来自 WATCH_LIST 的相同钱包,访问 Pump.fun 并执行一次购买交易。
  2. 检查机器人输出:

-你的控制台应打印类似 🎯 - BUY - TxID: ... 的消息 -如果交易被检测到,你应在文件 pump_fun_swaps.json 中看到 COPY_BUY 日志条目。 -如果你以 TEST_MODE 设置为 false 运行机器人,你可以在 Solana Explorer 上查看你的 txid 以获取交易详细信息。

Bot Output

干得好!

继续构建!

恭喜你!你已经构建了一个简单而强大的 Solana 复制交易机器人,使用 Pump.fun API 进行交换和 Yellowstone gRPC 进行实时交易流。这种设置演示了如何订阅链上事件并通过使用 Pump.fun API 获取和发送自己的交换交易来以编程方式做出响应。

随意定制此机器人以实现更高级的策略:- 变量购买金额:而不是固定的 BUY_AMOUNT,你可以跟踪鲸鱼的购买规模与你可用的总 SOL 之间的比例。

  • 止损/卖出信号:实施逻辑以检测大量抛售或设置自动卖出代币的阈值。
  • 多程序监控:扩展你的 Yellowstone 订阅,以监视 Solana 上的其他 DeFi 协议。考虑利用 Metis API 用于其他 DEX 或 AMM,和/或设置限价单。

感谢你的关注,祝你构建愉快!如果你有任何问题或想探索更多 Solana 和 DeFi 教程,请查看我们的 指南 和 文档。祝你好运,安全交易!

让我们知道你在做什么或是否有任何问题!你可以在 Discord 或 Twitter 与我们联系。

我们 ❤️ 反馈!

让我们知道 如果你有任何反馈或对新主题的请求。我们很想听到你的声音。

资源

  • 本指南的完整代码
  • Metis - Jupiter V6 Swap API
  • Yellowstone gRPC
  • JupiterAPI.com(公众 Pump.fun API 端点)
  • Solana 指南
  • 指南:构建 Jupiter 交易机器人
  • 🎥 视频:构建 Jupiter 交易机器人
  • 原文链接: quicknode.com/guides/sol...
  • 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~