使用Pump.fun创建一个Solana跟单交易机器人

  • QuickNode
  • 发布于 2024-03-22 12:29
  • 阅读 18

本文介绍了如何使用Pump.fun API和Yellowstone gRPC创建Solana交易机器人,以复制指定钱包的交易。内容包括配置项目、构建交易机器人、测试机器人等步骤。

本指南仅供学习目的

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

概述

在本指南中,我们将学习如何创建一个 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 主要网 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 教程,请查看我们的 指南文档。祝你好运,安全交易!

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

我们 ❤️ 反馈!

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

资源

  • 原文链接: quicknode.com/guides/sol...
  • 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
点赞 0
收藏 0
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论
QuickNode
QuickNode
江湖只有他的大名,没有他的介绍。