使用Solana Kit集成QuickNode附加组件(第二部分)

  • QuickNode
  • 发布于 2026-05-11 18:36
  • 阅读 44

本文是系列教程的第二部分,展示如何使用Solana Kit(前Solana Web3.js 2.0)集成多个QuickNode附加组件,包括优先级费用API、Metis Jupiter Swap API和IPFS网关。通过自定义RPC传输和类型定义,实现REST API调用,并最终将自定义附加组件与原生Solana方法(如getAccountInfo)结合使用。文章提供了详细的代码示例和步骤,帮助开发者在Solana Kit框架中灵活集成QuickNode服务。

🛠️ 更新通知

本指南已更新,以反映 Solana Web3.js 2.0 的新名称 —— Solana Kit。我们遵循最新的最佳实践,帮助你保持与时俱进。在此了解 Solana Kit 更多信息

概述

在本指南的第一部分中,我们探讨了如何将 Quicknode 的 Priority Fees 插件与 Solana Kit 集成。在第二部分,我们将在此基础上进一步扩展,整合多个 Quicknode 插件(包括 REST API),并演示如何将它们与原生 Solana 方法结合使用。

让我们开始吧!

你将完成什么

在本指南中,你将:

  • 将多个 Quicknode 插件集成到你的 Solana Kit 应用中
  • 在 Solana Kit 框架内实现 REST API 调用
  • 将 Quicknode 插件与原生 Solana 方法结合使用

你需要准备

  • 完成本系列指南的第一部分(我们将以该指南中的代码为起点)
  • 已安装 Node.js(版本 19 或更高)
  • 具备 TypeScript 经验并已安装 ts-node
  • 拥有一个 Quicknode 账户,并能够访问已启用 Priority Fees API、Metis Jupiter Swap API 和 IPFS 网关Solana 节点(其他插件也可用,但你的代码可能会有所不同)。如果你没有这些插件,可以从 Quicknode 控制面板 添加。如果你还没有 Quicknode 账户,可以在此免费创建

本指南使用的依赖

依赖 版本
@solana/kit ^2.0.0

扩展项目

首先,复制我们在第一部分创建的 example.ts 文件,并将其命名为 example2.ts。我们将使用这个新文件来实现高级功能。

cp example.ts example2.ts

现在,更新 example2.ts 文件,为新插件添加必要的导入和类型定义。

定义类型

首先,为 Metis Jupiter Swap API(这些类型来自 Metis Jupiter API 文档)和 IPFS API(我们使用了 IPFS API 文档 的修改版本,因为我们将在 Transport 函数中操作请求)定义类型。

将以下代码添加到 types.ts 文件中:

// 保留你现有的类型...
declare const QuoteGetSwapModeEnum: {
    readonly ExactIn: "ExactIn";
    readonly ExactOut: "ExactOut";
};
type QuoteGetSwapModeEnum = typeof QuoteGetSwapModeEnum[keyof typeof QuoteGetSwapModeEnum];

declare const SwapMode: {
    readonly ExactIn: "ExactIn";
    readonly ExactOut: "ExactOut";
};
type SwapMode = typeof SwapMode[keyof typeof SwapMode];

interface QuoteGetRequest {
    inputMint: string;
    outputMint: string;
    amount: number;
    slippageBps?: number;
    swapMode?: QuoteGetSwapModeEnum;
    dexes?: Array<string>;
    excludeDexes?: Array<string>;
    restrictIntermediateTokens?: boolean;
    onlyDirectRoutes?: boolean;
    asLegacyTransaction?: boolean;
    platformFeeBps?: number;
    maxAccounts?: number;
}

interface PlatformFee {
    amount?: string;
    feeBps?: number;
}

interface RoutePlanStep {
    swapInfo: SwapInfo;
    percent: number;
}

interface SwapInfo {
    ammKey: string;
    label?: string;
    inputMint: string;
    outputMint: string;
    inAmount: string;
    outAmount: string;
    feeAmount: string;
    feeMint: string;
}

interface QuoteResponse {
    inputMint: string;
    inAmount: string;
    outputMint: string;
    outAmount: string;
    otherAmountThreshold: string;
    swapMode: SwapMode;
    slippageBps: number;
    platformFee?: PlatformFee;
    priceImpactPct: string;
    routePlan: Array<RoutePlanStep>;
    contextSlot?: number;
    timeTaken?: number;
}

interface IpfsUploadRequest {
    filePath: string;
    fileName: string;
    fileType: string;
}

interface Pin {
    cid: string;
    name: string;
    origins: string[];
    meta: Record<string, any>;
}

interface Info {
    size: string;
}

interface IpfsUploadResponse {
    requestid: string;
    status: string;
    created: string;
    pin: Pin;
    info: Info;
    delegates: string[];
}

export type {
    // 现有的导出...
    FeeEstimates,
    EstimatePriorityFeesResponse,
    EstimatePriorityFeesParams,

    // 新导出...
    QuoteGetRequest,
    QuoteResponse,
    IpfsUploadRequest,
    IpfsUploadResponse
};

将新类型导入到 example2.ts 文件中:

import { QuoteGetRequest, QuoteResponse, IpfsUploadRequest, IpfsUploadResponse } from "./types";

另外,从 Node.js 标准库中导入 fs 以读取文件内容(上传文件到 IPFS 时需要):

import * as fs from "fs";

更新 API 类型

现在,更新 API 类型以包含 Metis 和 IPFS 方法。由于我们有多种类型的插件,让我们为每种类型创建一个新类型,然后将它们合并为一个类型,以便在 API 中使用。在 example2.ts 文件中的 PriorityFeeApi 类型后添加以下代码:

type PriorityFeeApi = {
    qn_estimatePriorityFees(params: EstimatePriorityFeesParams): Promise<EstimatePriorityFeesResponse>;
}

type MetisApi = {
    metis_quote(params: QuoteGetRequest): Promise<QuoteResponse>;
    // 在此添加其他 Metis 方法,例如:
}

type IpfsApi = {
    ipfs_upload(params: IpfsUploadRequest): Promise<IpfsUploadResponse>;
    // 在此添加其他 IPFS 方法,例如:
}

type QuickNodeAddons = PriorityFeeApi & MetisApi & IpfsApi;

注意,我们使用 & 运算符合并类型。这允许我们创建一个单一类型,包含所有插件的方法。根据你的用例,你可以选择使用单独的类型,或创建一个仅包含所需方法的统一类型。该库在如何组织 API 方面提供了很大的灵活性。

另外注意,我们为每个接口预留了添加额外方法的空间。本指南的目的是展示如何集成一个插件方法,而不是提供所有可用方法的详尽列表。欢迎探索 Quicknode Marketplace 和我们的文档,寻找其他可以集成到应用中的插件和方法。

实现 REST API 调用

在之前的指南中,我们使用了 createDefaultRpcTransport 函数来创建一个使用默认 JSON RPC 传输的 transport。虽然这是一个好的起点,但它不允许我们处理 REST API 调用(而 Metis API 和 Quicknode IPFS API 都需要 REST 调用)。为了处理 REST API 调用,我们需要创建一个自定义 transport,能够处理我们插件的特定要求(例如,端点、认证、头部等)。

为了在 Solana Kit 框架内处理 REST API 调用,我们需要修改 createQuickNodeTransport 函数,首先检查传递给 transport 函数的方法,然后相应地处理请求。如果方法未被识别,我们可以默认使用 JSON RPC 传输。在 example2.ts 文件中更新它:

interface CreateAddonsApiParams {
    endpoint: string;
    metisEndpoint?: string;
    ipfsApiKey?: string;
}

function createQuickNodeTransport({ endpoint, metisEndpoint, ipfsApiKey }: CreateAddonsApiParams): RpcTransport {
    const jsonRpcTransport = createDefaultRpcTransport({ url: endpoint });

    return async <TResponse>(...args: Parameters<RpcTransport>): Promise<TResponse> => {
        const { method, params } = args[0].payload as { method: string; params: unknown };
        switch (true) {
            case method.startsWith('metis_'):
                return handleMetisRequest<unknown, TResponse>(method, params, metisEndpoint);

            case method === 'ipfs_upload':
                return handleIpfsUpload<TResponse>(params as IpfsUploadRequest, ipfsApiKey);

            default:
                return jsonRpcTransport(...args);
        }
    };
}

让我们拆解一下这里做了什么:

  1. 首先,我们将参数改为不仅接受一个端点,还接受可选的 metisEndpointipfsApiKey 参数。
  2. 接着,像之前一样,我们定义了一个默认的 JSON RPC 传输。
  3. 然后,我们使用 switch 语句检查传递给 transport 函数的方法。如果方法以 metis_ 开头,我们调用一个新函数 handleMetisRequest 来处理 Metis API 请求(我们将使用这个处理程序来处理所有 Metis 请求,无论具体方法是什么)。此外,如果方法是 ipfs_upload,我们调用一个新函数 handleIpfsUpload 来处理 IPFS API 请求(我们只使用这个处理程序处理特定方法)。如果方法未被识别,我们返回默认的 JSON RPC 传输。

这里需要注意的是,这个逻辑是完全可定制的。我们只是提供了一个示例,展示如何使用 switch 语句来包含不同的 API 或方法,但你可以使用任何其他适合你应用的方法。

接下来让我们定义这些 API 处理函数。

定义 Metis API 处理函数

现在,定义一个函数来处理 Metis API 请求。我们将使用这个函数来处理所有 Metis API 请求,无论具体方法是什么。更新 example2.ts 文件:

async function handleMetisRequest<TParams, TResponse>(method: string, params: TParams, metisEndpoint?: string): Promise<TResponse> {
    const DEFAULT_METIS_ENDPOINT = 'https://public.jupiterapi.com';
    const jupiterMethod = method.replace('metis_', '');
    const url = new URL(`${metisEndpoint || DEFAULT_METIS_ENDPOINT}/${jupiterMethod}`);
    const paramsToUse = Array.isArray(params) ? params[0] : params;

    if (typeof paramsToUse === 'object' && paramsToUse !== null) {
        Object.entries(paramsToUse as Record<string, unknown>).forEach(([key, value]) => {
            url.searchParams.append(key, String(value));
        });
    }

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

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

    const data = await response.json();
    return { result: data } as TResponse;
}

这个函数中发生了什么:

  1. 如果没有提供 Metis 端点,我们定义一个默认的 Metis 端点。
  2. 我们从传递给 transport 函数的方法中提取具体方法。
  3. 我们创建一个 URL 对象,包含 Metis 端点和具体方法。
  4. 我们遍历对象的条目,并将每个键值对作为查询参数追加到 URL 中。
  5. 我们向 URL 发起一个 fetch 请求,并将响应作为 TResponse 返回。

值得注意,这里我们硬编码了请求为 GET 请求。虽然这对我们的演示有效,但某些方法需要不同的请求类型(例如,swap 方法是 POST 请求)。你可以选择修改此函数以根据具体方法处理不同的请求类型,或者根据请求类型创建单独的处理函数(例如,handleMetisGetRequesthandleMetisPostRequest 等)。

定义 IPFS API 处理函数

现在,定义一个函数来处理 IPFS API 请求。我们将使用这个函数来处理 ipfs_upload 方法。更新 example2.ts 文件:

async function handleIpfsUpload<T>(params: IpfsUploadRequest, ipfsApiKey?: string): Promise<T> {
    if (!ipfsApiKey) {
        throw new Error('未提供 IPFS API 密钥');
    }

    const { filePath, fileName, fileType } = params;
    const fileContent = fs.readFileSync(filePath);
    const file = new File([fileContent], fileName, { type: fileType });
    const formData = new FormData();
    formData.append("Body", file);
    formData.append("Key", file.name);
    formData.append("ContentType", file.type);

    const url = new URL('https://api.quicknode.com/ipfs/rest/v1/s3/put-object');
    const response = await fetch(url, {
        method: 'POST',
        headers: {
            'x-api-key': ipfsApiKey,
        },
        body: formData,
        redirect: "follow",
    });

    if (!response.ok) {
        throw new Error(`向 ${url} 发起 fetch 请求时出错:${response.statusText}`);
    }
    const data = await response.json();

    return { result: data } as T;
}

这个函数中发生了什么:

  1. 我们检查是否提供了 IPFS API 密钥(与 Metis API 不同,IPFS API 没有公共端点,因此如果没有提供密钥,我们抛出错误)。
  2. 我们使用文件内容、文件名和文件类型创建一个 File 对象。
  3. 我们根据 IPFS 文档 创建一个 FormData 对象并将文件附加到其中。
  4. 我们为特定方法创建一个指向 IPFS API 端点的 URL 对象(与我们创建的 Metis 处理程序不同,这个处理程序是特定于单个方法的)。
  5. 我们向 URL 发起一个 fetch 请求,并将响应作为 TResponse 返回。

更新 RPC 创建函数

在之前的指南中,我们创建了一个函数来创建我们的 PriorityFeeApicreatePriorityFeeApi。由于我们的新接口包含多个插件,让我们创建一个更通用的函数来支持我们的 QuickNodeApi 接口。在 example2.ts 文件中添加一个 createAddonsApi 函数:

function createAddonsApi(params: CreateAddonsApiParams): Rpc<QuickNodeAddons> {
    const api = createJsonRpcApi<QuickNodeAddons>({
        requestTransformer: (request: RpcRequest<any>) => request.params[0],
        responseTransformer: (response: any) => response.result,
    });

    const transport = createQuickNodeTransport(params);

    return createRpc({ api, transport });
}

这个函数与我们之前在指南中创建的 createPriorityFeeApi 函数非常相似。唯一的区别是:

  1. 我们传入的是 CreateAddonsApiParams 接口,而不是 CreatePriorityFeeApiParams 接口(它包括我们的 Metis 和 IPFS 授权密钥)。
  2. 我们使用新的 createQuickNodeTransport 函数来创建 transport。
  3. 我们使用 QuickNodeAddons 接口而不是 PriorityFeeApi 接口。

如果我们正确地完成了所有操作,这个函数应该返回一个包含 PriorityFeeApiMetisApiIpfsApi 接口中所有方法的 Rpc 接口。让我们测试一下!

使用组合功能

现在,让我们测试新的组合功能。将 example2.ts 文件中的 main 函数替换为以下代码:

async function main() {
    const quickNodeRpc = createAddonsApi({
        endpoint: 'https://example.solana-mainnet.quiknode.pro/123456/', // 👈 替换为你的 Quicknode Solana 主网端点
        ipfsApiKey: 'QN_REPLACE_WITH_YOUR_IPFS_API_KEY',                 // 👈 替换为你的 Quicknode IPFS API 密钥
        // metisEndpoint: ''                                             // 👈 (可选) 取消注释并替换为你的 Metis 端点
    });

    try {
        const priorityFees = await quickNodeRpc.qn_estimatePriorityFees({
            account: 'JUP6LkbZbjS1jKKwapdHNy74zcZ3tLUZoi5QNyVTaV4',
            last_n_blocks: 100,
            api_version: 2
        }).send();
        console.log(`Priority Fees (Med Per CU): ${priorityFees.per_compute_unit.medium}`);
    } catch (error) {
        console.error('估算优先费用时出错:', error);
    }

    try {
        const metisQuote = await quickNodeRpc.metis_quote({
            inputMint: "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
            outputMint: "So11111111111111111111111111111111111111112",
            amount: 10 * 1e6,
        }).send();
        console.log(`Metis Quote (lamports): ${metisQuote.outAmount}`);
    } catch (error) {
        console.error('获取 Metis 报价时出错:', error);
    }

    try {
        const result = await quickNodeRpc.ipfs_upload({
            filePath: 'test.png',   // 👈 替换为你的文件路径
            fileName: 'test.png',   // 👈 替换为你的文件名
            fileType: 'image/png',  // 👈 替换为你的文件类型
        }).send();
        console.log('文件上传成功!CID:', result.pin.cid);
    } catch (error) {
        console.error('上传文件时出错:', error);
    }
}

main().catch(console.error);

确保将端点 URL、IPFS API 密钥和文件详情替换为实际值。你可以在 Quicknode 控制面板 中找到你的 Quicknode 端点和 IPFS API 密钥。

运行脚本

要运行我们的高级示例,请使用以下命令:

ts-node example2.ts

你应该会看到输出包含估算的优先费用、报价金额以及上传文件的 CID,例如:

Priority Fees (Med Per CU): 29767
Metis Quote (lamports): 77848587
File uploaded successfully! CID: QmcPq5iyjgpuW1RdGKw55fV4RvWMNZYPHCmhPGXbCfbEC3

干得不错!

那 Solana 方法呢?

如果你注意类型守卫,可能会发现 quicknodeRpc 对象有一些意料之外的情况。你可能已经注意到 Intellisense 没有检测到任何原生 Solana 方法:

可用方法

这是由于我们构建 QuickNodeAddons 接口的方式。目前,我们只限于插件中定义的方法。

一种解决方法是使用 Proxy 对象来拦截方法调用并在执行前修改它们。这可以通过 JavaScript 中的 Proxy 类来实现。另一种方法是创建一个新的接口,包含所有插件的方法和所有原生 Solana 方法。让我们试试看!

example2.ts 文件中添加以下导入:

import {
    // ... 现有导入
    SolanaRpcApi,
    address,
} from "@solana/kit";

现在,创建一个新的、更广泛的类型,包含所有插件的方法和所有原生 Solana 方法。在 example2.ts 文件中添加以下代码:

type QuickNodeSolana = SolanaRpcApi & QuickNodeAddons;

SolanaRpcApi 接口是一个类型,它包含一个类似我们插件接口的接口,用于每个原生 Solana 方法。现在,更新 createAddonsApi 函数,使其返回一个 QuickNodeSolana 接口而不是 QuickNodeAddons 接口:

export function createQuickNodeApi(params: CreateAddonsApiParams): Rpc<QuickNodeSolana> {
    const METHODS_TO_TRANSFORM_PARAMS = [`qn_estimatePriorityFees`, `metis_quote`, `ipfs_upload`];

    const quickNodeApi = createJsonRpcApi<QuickNodeSolana>({
        requestTransformer: (request: RpcRequest<any>) => {
            if (METHODS_TO_TRANSFORM_PARAMS.includes(request.methodName)) {
                return Array.isArray(request.params) ? request.params[0] : request.params;
            } else {
                return request.params;
            }
        },
        responseTransformer: (response: any) => response.result,
    });

    const transport = createQuickNodeTransport(params);

    return createRpc({ api: quickNodeApi, transport });
}

这与我们之前所写的基本完全相同,但我们现在使用了 QuickNodeSolana 接口而不是 QuickNodeAddons 接口,并且在执行参数转换之前对方法进行了检查。这允许我们拦截并转换自定义方法的参数,同时允许原生 Solana 方法的参数通过。确保更新 main 函数以调用 createQuickNodeApi 函数,就这么简单!你现在应该会看到 Intellisense 对所有原生 Solana 方法以及自定义方法都有效:

Intellisense

继续在 example2.ts 文件的 main 函数中添加以下代码:

    try {
        const info = await quickNodeRpc.getAccountInfo(address('JUP6LkbZbjS1jKKwapdHNy74zcZ3tLUZoi5QNyVTaV4')).send();
        console.log(info.value);
    } catch (error) {
        console.error('获取账户信息时出错:', error);
    }

如果你不想再上传另一个文件,请确保在 main 函数中删除或注释掉 ipfs_upload 调用。再次运行脚本,你应该会看到 getAccountInfo 调用的输出紧挨着自定义方法的输出!🎉

干得漂亮!

总结

在本指南中,我们在之前知识的基础上进行了扩展,将多个 Quicknode 插件(包括 REST API)集成到了 Solana Kit 应用中。我们演示了如何:

  1. 集成多个 Quicknode 插件
  2. 在 Solana Kit 框架内实现 REST API 调用
  3. 将 Quicknode 插件与原生 Solana 方法结合使用

这种方法允许高度自定义,以无缝集成各种 Quicknode 插件和 API。我们希望你已经看到这个过程提供了很大的灵活性,所以发挥你的创造力,构建一些令人惊叹的东西吧!我们很期待看到你的成果——在 Quicknode DiscordTwitter 上给我们留言,告诉我们你构建了什么!

继续构建

为了进一步提升你的 Solana 开发技能:

  1. 向你的 Metis 和 IPFS API 接口添加更多方法
  2. 探索其他 Quicknode 插件 并将其集成到你的应用中
  3. 创建你自己的 Quicknode 函数 来将你的插件集成到应用中
  4. 在你的 transport 函数中实现错误处理和重试

额外资源

我们 ❤️ 反馈!

告诉我们 如果你有任何反馈或对新主题的请求。我们期待你的来信。

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

0 条评论

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