使用Metis和Solana Web3.js 2.0自动化Pump.fun交易

本文介绍了如何使用Solana Web3.js 2.0与Metis Pump.fun API进行交互,包括如何获取报价、执行交易以及处理交易的签名和发送。提供了详细的TypeScript实现步骤,并附有相关代码示例。

概述

Pump.fun 是一个在 Solana 上允许无权限创建和交易代币的平台,利用公平发售的概念(例如,没有预售或团队分配)。在本指南中,你将学习如何使用 Solana Web3.js 2.0 通过 Metis Pump.fun API 程序化获取报价和执行交换。我们将创建一个处理 API 请求、交易签名和发送交易到 Solana 网络的 TypeScript 实现。

使用 Solana Web3.js Legacy (v1.x) 构建

本指南将带你逐步了解如何使用 Metis Pump.fun API 以及 Solana Web3.js 2.0。

如果你希望使用 Solana Web3.js Legacy (v1.x) 构建,请查看我们在 GitHub 上的示例代码。

你将做什么

  • 创建 Pump.fun API 请求的自定义 RPC 传输
  • 实现报价获取和交换执行
  • 使用 Web3.js 2.0 处理交易签名和发送
  • 使用示例脚本测试实现

你需要什么

  • 一个启用了 Metis Jupiter Swap API 附加的 QuickNode 帐户(你可以使用我们来自 JupiterAPI.com 的公共端点,但是公共端点仅支持某些方法,并且可能具有更严格的速率限制和交易费用)。
  • Node.js(推荐版本 21.0 或更高)
  • 对 TypeScript 和 Solana 开发概念的基本了解
  • Solana Web3.js 2.0 经验者优先

Metis 概述

Metis 是一个强大的工具,帮助开发者访问 Solana 的流动性。通过整合 Jupiter 的 V6 Swap API、Jupiter 的限价单 API、Pump.fun 交易、交易 websocket 等,你可以访问构建强开发交易工具所需的工具,这些工具可以访问 Solana 上的许多 DeFi 协议。

开始

1. 设置你的项目

首先,创建一个新的项目目录:

mkdir pumpfun-integration && cd pumpfun-integration

初始化你的 Node.js 项目:

npm init -y

安装所需的依赖:

npm install @solana/web3.js@2 dotenv

如果你未全局安装 TypeScript,可以将其作为开发依赖添加到项目中:

npm install --save-dev typescript ts-node @types/node

初始化 TypeScript 配置(tsconfig.json):

tsc --init

创建你将使用的项目文件:

echo > index.ts && echo > types.ts && echo > .env

更新 package.json 脚本以运行我们的 TypeScript 文件:

"scripts": {
    "start": "ts-node index.ts",
}

2. 创建类型定义

如果你之前使用过 Solana Web3.js 2.0,你就知道项目前端的小部分类型定义可以为你节省很多时间和精力。我们已经为你创建了 Pump.fun API 的类型定义。你可以在 这里 找到完整的类型定义。

将文件的全部内容复制并粘贴到你的 types.ts 文件中。这些类型定义了 Pump.fun API 请求和响应的结构,可以在我们的 Metis 文档 中找到。

3. 设置环境变量

在本指南中,你将需要一个启用了 Metis API 的 QuickNode 帐户,带有一个 Solana 端点。你可以在 这里 注册 QuickNode 帐户。

打开你的 .env 文件,添加你的钱包私钥(如果你没有,可以通过运行 solana-keygen new 生成),你的 Metis 端点,以及你的 Solana 主网 HTTP 和 WSS URL:

WALLET_SECRET_KEY=[1, 1, 1, 1, 1, 1 ...etc.]
METIS_URL='https://jupiter-swap-api.quiknode.pro/YOUR_ENDPOINT'
HTTP_ENDPOINT='https://example.solana-mainnet.quiknode.pro/123456/'
WSS_ENDPOINT='wss://example.solana-mainnet.quiknode.pro/123456/'

确保用你 QuickNode 控制面板中的 Solana 端点替换你的 HTTP 和 WSS 端点,将 https://jupiter-swap-api.quiknode.pro/YOUR_ENDPOINT 替换为你自己的 Metis 端点。你可以从 QuickNode 控制面板的附加页面 (https://dashboard.quicknode.com/endpoints/YOUR_ENDPOINT/add-ons) 找到你的 Metis 地址:

Solana 端点

Metis 地址

JupiterAPI.com 公共端点

JupiterAPI.com 提供了一个 Metis API 的公共端点。公共端点仅支持的 Pump.fun 方法是 /pump-fun/swap,并且使用中包括交换费用。请查看 JupiterAPI.com 获取最新的费用和速率限制。

实现

现在,让我们创建我们的实现和测试脚本以与 Pump.fun API 交互。打开 index.ts,并一步步实现每部分。

1. 导入依赖并定义类型

首先,导入所需的依赖并定义 Metis PumpFun API 的类型:

import {
    Rpc,
    createRpc,
    RpcTransport,
    createJsonRpcApi,
    address,
    getBase64Encoder,
    FullySignedTransaction,
    TransactionMessageBytes,
    getTransactionDecoder,
    signTransaction,
    createKeyPairFromBytes,
    TransactionWithBlockhashLifetime,
    getSignatureFromTransaction,
    createSolanaRpcSubscriptions,
    sendAndConfirmTransactionFactory,
    createSolanaRpc,
    SolanaRpcApi,
    RpcSubscriptions,
    SolanaRpcSubscriptionsApi,
    getAddressDecoder,
    getAddressFromPublicKey
} from "@solana/web3.js";
import {
    HttpRequestMethod,
    PumpFunEndpoint,
    PumpFunQuoteParams,
    PumpFunQuoteResponse,
    PumpFunRequest,
    PumpFunSwapInstructionsResponse,
    PumpFunSwapParams,
    PumpFunSwapResponse,
    SignAndSendTransactionParams
} from "./types";
import dotenv from 'dotenv';

dotenv.config();

type MetisPumpFunApi = {
    pumpfun_quote(params: PumpFunQuoteParams): Promise<PumpFunQuoteResponse>;
    pumpfun_swap(params: PumpFunSwapParams): Promise<PumpFunSwapResponse>;
    pumpfun_swap_instructions(params: PumpFunSwapParams): Promise<PumpFunSwapInstructionsResponse>;
}

const METHOD_TO_ENDPOINT: Record<string, PumpFunEndpoint> = {
    pumpfun_quote: {
        path: 'pump-fun/quote',
        method: 'GET'
    },
    pumpfun_swap: {
        path: 'pump-fun/swap',
        method: 'POST'
    },
    pumpfun_swap_instructions: {
        path: 'pump-fun/swap-instructions',
        method: 'POST'
    }
};

const PUBLIC_ENDPOINT_URL = 'https://public.jupiterapi.com';

除了我们在之前步骤中定义的类型外,我们还导入了所需的 Solana Web3.js 2.0 依赖并定义了 MetisPumpFunApi 类型,该类型表示我们将实现的 Pump.fun API 方法。这是我们为本指南使用的 Metis API 的一个子集。如果这看起来很陌生或新奇,我们建议你查看我们的指导 使用 Solana Web3.js 2.0 构建自定义 API。我们还创建了一个方法名称与 API 端点的映射,以便在我们的传输层处理每个方法时可以确保正确处理。

2. 实现 API 配置

接下来,让我们添加一些帮助功能以配置 PumpFun API 并处理请求。将以下代码添加到你的 index.ts

function createPumpFunUrl(metisEndpoint: string, method: string): URL {
    const baseUrl = metisEndpoint.replace(/\/$/, ''); // 移除末尾的斜杠(如果存在)
    const endpointPath = METHOD_TO_ENDPOINT[method].path;
    return new URL(`${baseUrl}/${endpointPath}`);
}

function createPumpFunTransport(metisEndpoint: string): RpcTransport {
    return async <TResponse>(...args: Parameters<RpcTransport>): Promise<TResponse> => {
        const { method, params } = args[0].payload as { method: string; params: PumpFunRequest };
        const url = createPumpFunUrl(metisEndpoint, method);
        const normalizedParams = Array.isArray(params) ? params[0] : params;
        switch (METHOD_TO_ENDPOINT[method].method) {
            case 'GET':
                return handlePumpFunGET<PumpFunRequest, TResponse>(url, normalizedParams);
            case 'POST':
                return handlePumpFunPOST<PumpFunRequest, TResponse>(url, normalizedParams);
            default:
                throw new Error(`未知的 PumpFun 方法 HTTP 方法: ${method}`);
        }
    };
}

function createPumpFunApi(metisEndpoint: string): Rpc<MetisPumpFunApi> {
    const api = createJsonRpcApi<MetisPumpFunApi>();
    const transport = createPumpFunTransport(metisEndpoint);
    return createRpc({ api, transport });
}

这里简要说明每个函数的作用:

  • createPumpFunUrl: 创建一个 Pump.fun API 端点的 URL 对象,基于 Metis 端点和方法名称(并移除任何尾随的斜杠)。
  • createPumpFunTransport: 创建一个自定义的 RPC 传输函数,该函数验证我们的端点/方法,规范化我们的参数,并根据 HTTP 方法(GET/POST)将请求路由到适当的处理函数 - 我们将下一步定义这些处理函数。
  • createPumpFunApi: 创建 PumpFun API 的 RPC 实例,使用自定义传输函数。

3. 实现请求处理程序

现在,让我们实现核心请求处理功能。GET 方法将利用 URL 查询参数传递请求参数,而 POST 方法将把参数发送到请求体中。将以下代码添加到你的 index.ts

async function handlePumpFunGET<TParams, TResponse>(
    url: URL,
    params: TParams
): Promise<TResponse> {
    if (typeof params === 'object' && params !== null) {
        Object.entries(params as Record<string, unknown>).forEach(([key, value]) => {
            url.searchParams.append(key, String(value));
        });
    }

    const response = await fetch(url.toString(), {
        method: 'GET',
        redirect: 'follow',
        headers: {
            'Content-Type': 'application/json',
        }
    });

    if (!response.ok) {
        throw new Error(`请求 ${url} 时出错: ${response.statusText}`);
    }

    return await response.json() as TResponse;
}

async function handlePumpFunPOST<TParams, TResponse>(
    url: URL,
    params: TParams
): Promise<TResponse> {
    try {
        const response = await fetch(url.toString(), {
            method: 'POST',
            redirect: 'follow',
            headers: {
                'Content-Type': 'application/json',
            },
            body: JSON.stringify(params),
        });

        if (!response.ok) {
            throw new Error(`请求 ${url} 时出错: ${response.statusText}`);
        }

        return await response.json() as TResponse;
    } catch (error) {
        console.error('发送 POST 请求时出错:', error);
        throw error;
    }
}

以下是每个函数的作用:

  • handlePumpFunGET: 将请求参数附加到 URL 查询参数,并向 Pump.fun API 端点发送 GET 请求。它返回 JSON 响应。
  • handlePumpFunPOST: 使用请求参数在请求体中向 Pump.fun API 端点发送 POST 请求。它返回 JSON 响应。

4. 实现交易处理

让我们添加交易签名和发送功能:

async function signAndSendTransaction({
    transactionBase64,
    signerSecretKey,
    solanaRpc,
    solanaRpcSubscriptions,
    commitment = 'confirmed'
}: SignAndSendTransactionParams): Promise<string> {
    // 从私钥创建签名者密钥对
    const signerKeypair = await createKeyPairFromBytes(
        new Uint8Array(signerSecretKey)
    );

    // 解码 base64 交易
    const transactionBytes = getBase64Encoder().encode(transactionBase64) as TransactionMessageBytes;
    const transactionDecoder = getTransactionDecoder();
    const decodedTransaction = transactionDecoder.decode(transactionBytes);

    // 签名交易
    const signedTransaction = await signTransaction(
        [signerKeypair],
        decodedTransaction
    );

    // 获取最新的 blockhash,并准备具有生命周期的交易
    const { value: { lastValidBlockHeight, blockhash } } = await solanaRpc.getLatestBlockhash().send();
    const signedTransactionWithLifetime: FullySignedTransaction & TransactionWithBlockhashLifetime = {
        ...signedTransaction,
        lifetimeConstraint: {
            blockhash,
            lastValidBlockHeight,
        },
    };

    // 获取交易签名
    const transactionSignature = getSignatureFromTransaction(signedTransactionWithLifetime);

    // 创建 sendAndConfirm 函数
    const sendAndConfirmTransaction = sendAndConfirmTransactionFactory({
        rpc: solanaRpc,
        rpcSubscriptions: solanaRpcSubscriptions,
    });

    // 发送并确认交易
    await sendAndConfirmTransaction(signedTransactionWithLifetime, {
        commitment,
    });

    return transactionSignature;
}

这个函数将使我们的 base64 编码交易的执行变得轻而易举。signAndSendTransaction 函数处理交易的完整生命周期:

  1. 从提供的私钥创建密钥对
  2. 解码 base64 编码的交易
  3. 签署交易
  4. 获取最新的 blockhash
  5. 向交易添加生命周期约束,以确保其“新鲜”
  6. 使用 Web3.js 2.0 的 sendAndConfirmTransaction 工厂发送和确认交易
  7. 返回交易签名以进行跟踪

5. 环境变量验证

最后,让我们写一个简单的环境变量验证函数,以确保所有必需变量都已设置:

function validateEnv() {
    const envVars = ['WALLET_SECRET_KEY','HTTP_ENDPOINT','WSS_ENDPOINT']; // 不需要验证 'METIS_URL',因为我们有公共端点的后备
    envVars.forEach((envVar) => {
        if (!process.env[envVar]) {
            throw new Error(`${envVar} 环境变量是必需的`);
        }
    });
}

我们只是检查每个预期的环境变量,并在未设置时抛出错误。提醒:从不在客户端代码中暴露你的 QuickNode API 密钥。始终在后端保持其安全。请查看我们的 指南:如何保护你的端点 - 前端最佳实践 以获取有关端点安全的更多信息。

测试脚本

获取报价

让我们创建一个新的函数 main,来测试我们的 API。注意:如果你使用的是公共端点,此方法将无法工作。 将以下代码添加到你的 index.ts

async function main() {
    validateEnv();
    const metisUrl = process.env.METIS_URL ?? PUBLIC_ENDPOINT_URL;
    const rpcUrl = process.env.HTTP_ENDPOINT as string;
    const rpcSubscriptionsUrl = process.env.WSS_ENDPOINT as string;
    const signerSecretKey = JSON.parse(process.env.WALLET_SECRET_KEY as string) as number[];
    const signerKeypair = await createKeyPairFromBytes(new Uint8Array(signerSecretKey));
    const wallet = await getAddressFromPublicKey(signerKeypair.publicKey);

    const targetMint = address("8gXN67Nmw9FZQjunJZzRoi2Qf1ykZtN9Q3BqxhCypump");
    const pumpFunApi = createPumpFunApi(metisUrl);
    const solanaRpc: Rpc<SolanaRpcApi> = createSolanaRpc(rpcUrl);
    const solanaRpcSubscriptions: RpcSubscriptions<SolanaRpcSubscriptionsApi>
        = createSolanaRpcSubscriptions(rpcSubscriptionsUrl);

    try {
        const pumpFunQuote = await pumpFunApi.pumpfun_quote({
            type: 'BUY',
            mint: targetMint,
            amount: 1_000_000,
        }).send();
        console.log(`PumpFun 报价:\n ${JSON.stringify(pumpFunQuote.quote, null, 2)}`);
    } catch (error) {
        console.error('获取 PumpFun 报价时出错:', error);
    }
}

main().catch(console.error);

在这里,我们声明了我们的环境变量,创建了 PumpFun API 实例,并从 PumpFun API 获取报价。我们使用 pumpfun_quote 方法获取指定代币的购买报价 0.001 SOL。你可以调整参数以满足你的需求。

在终端中,运行以下命令以启动脚本:

npm start

如果一切设置正确,你应该会看到报价响应记录到控制台,如下所示:

PumpFun 报价:
 {
  "mint": "8gXN67Nmw9FZQjunJZzRoi2Qf1ykZtN9Q3BqxhCypump",
  "bondingCurve": "6TWadAgufwkQqZfKm1fqLgSQBUGKoMup7x5uC8fHUtnZ",
  "type": "BUY",
  "inAmount": "1000000",
  "inAmountUi": 0.001,
  "inTokenAddress": "So11111111111111111111111111111111111111112",
  "outAmount": "33339932484",
  "outAmountUi": 33339.932484,
  "outTokenAddress": "8gXN67Nmw9FZQjunJZzRoi2Qf1ykZtN9Q3BqxhCypump",
  "meta": {
    "isCompleted": false,
    "outDecimals": 6,
    "inDecimals": 9,
    "totalSupply": "1000000000000000",
    "currentMarketCapInSol": 29.993096666
  }
}

干得好!

执行交换

如果你准备开始交易,可以通过将以下代码添加到 main 函数来构建交易并将其发送到网络:

    try {
        const pumpFunQuote = await pumpFunApi.pumpfun_swap({
            wallet,
            type: 'BUY',
            mint: targetMint,
            inAmount: 1_000_000,
            priorityFeeLevel: 'high', // 可选设置优先费用级别
        }).send();

        const sig = await signAndSendTransaction({
            transactionBase64: pumpFunQuote.tx,
            signerSecretKey: JSON.parse(process.env.WALLET_SECRET_KEY as string) as number[],
            solanaRpc,
            solanaRpcSubscriptions,
        });

        console.log(`交易签名: ${sig}`);
    } catch (error) {
        console.error('获取 PumpFun 报价时出错:', error);
    }

警告: 此代码将在 Solana 网络上执行真实交易,并导致不可逆转的资金转移。在运行之前,请确保你理解其影响。

结论

你现在已经拥有了一个强大的实现,用于使用 Solana Web3.js 2.0 与 Metis Pump.fun API 进行交互。该实现提供了一种类型安全、抗错误的方式,来获取报价并在 Pump.fun 平台上执行交换。如果你想继续构建,请查看我们的 指南:如何在 Solana 上构建 Jupiter 交易机器人,获取更多有关如何将该项目提升到新水平的创意。

请记住,在部署到生产之前,请始终在开发环境中充分测试,并确保在处理私钥和 QuickNode 凭据方面采取适当的安全措施。

要了解有关 Solana 开发的更多信息,请查看我们关于 QuickNode 指南 的其他指南。如需支持或有疑问,请加入我们的 Discord 社区 或通过 Twitter 联系我们。

我们 ❤️ 你的反馈!

请告诉我们 如果你有任何反馈或新的话题请求。我们非常希望听到你的声音。

资源

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

0 条评论

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