本文介绍了如何将多个QuickNode Add-ons集成到Solana Web3.js 2.0应用程序中,包括REST API的调用和与原生Solana方法的结合。
在本指南的 第一部分 中,我们探讨了如何将 QuickNode 的优先费用附加组件与 Solana Web3.js 2.0 集成。在第二部分中,我们将基于此基础扩展,结合多个 QuickNode 附加组件,包括 REST APIs,并演示如何与原生 Solana 方法一起使用它们。
让我们开始吧!
在本指南中,你将:
依赖项 | 版本 |
---|---|
@solana/web3.js | ^2.0.0 |
首先,复制我们在第一部分中创建的 example.ts
文件,并命名为 example2.ts
。我们将使用这个新文件来实现我们更高级的功能。
cp example.ts example2.ts
现在,让我们更新我们的 example2.ts
文件,以包括我们新附加组件所需的导入和类型。
首先,让我们为 Metis Jupiter V6 交换 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 类型,以包括 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 上提供了很大的灵活性。
同样,注意我们留出了空间以添加每个接口的其他方法。本指南的目的是展示如何集成附加组件方法,而不是 exhaustively 列出所有可用方法。可以探索 QuickNode 市场 及我们的 文档 来查找其他附加组件和方法以集成到你的应用程序中。
在之前的指南中,我们使用 createDefaultRpcTransport
函数创建了一个使用默认 JSON RPC 传输的运输方式。尽管这是一个良好的起点,但它不允许我们处理 REST API 调用(Metis API 和 QuickNode IPFS API 都需要 REST 调用)。要处理 REST API 调用,我们需要创建一个自定义传输,可以处理附加组件的特定要求(例如,端点、身份验证、请求头等)。
要在我们的 Solana Web3.js 2.0 框架内处理 REST API 调用,我们需要修改我们的 createQuickNodeTransport
函数,首先检查传递给传输函数的方法,然后相应地处理请求。如果方法未识别,我们可以默认为 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);
}
};
}
分解一下我们在这里所做的事情:
params
更改为接受不仅是一个端点,还是一个可选的 metisEndpoint
和 ipfsApiKey
参数。metis_
开头,我们调用一个名为 handleMetisRequest
的新函数来处理 Metis API 请求(我们将使用这个处理器来处理所有 Metis 请求,不论具体的方法)。此外,如果方法是 ipfs_upload
,我们调用一个新的函数 handleIpfsUpload
来处理 IPFS API 请求(我们仅使用这个处理器处理特定的方法)。如果方法未识别,我们返回默认的 JSON RPC 传输。在这里需要注意的是,这个逻辑是完全可定制的。我们仅举例说明你如何使用 switch 语句来包含不同的 API 或方法,但你可以使用其他任何适合你应用程序的方法。
接下来让我们定义那些 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} 发起获取请求时出错: ${response.statusText}`);
}
const data = await response.json();
return { result: data } as TResponse;
}
在这个函数中,我们正在做的事情:
值得注意的是,我们将请求硬编码为 GET
请求。虽然这对我们的演示有效,但一些方法需要不同的请求类型(例如 swap
方法是一个 POST
请求)。你可以选择根据特定方法修改此函数以处理不同的请求类型,或者你可以根据请求类型创建单独的处理器(例如,handleMetisGetRequest
、handleMetisPostRequest
等)。
现在,让我们定义一个函数来处理 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} 发起获取请求时出错: ${response.statusText}`);
}
const data = await response.json();
return { result: data } as T;
}
在这个函数中,我们正在做的事情:
在之前的指南中,我们创建了一个用于创建我们的 PriorityFeeApi 的函数 createPriorityFeeApi
。由于我们的新接口包含多个附加组件,让我们创建一个更通用的函数,以支持我们的 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
函数非常相似。唯一的区别是:
createQuickNodeTransport
函数来创建传输。如果我们一切都正确,此函数应返回一个 Rpc 接口,其中包括来自我们的 PriorityFeeApi、MetisApi 和 IpfsApi 接口的所有方法。让我们来测试一下!
现在,让我们测试我们的新组合功能。将你的 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(`优先费用(每 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 报价(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 的输出,例如:
优先费用(每 CU 中等):29767
Metis 报价(Lamports):77848587
文件上传成功!CID: QmcPq5iyjgpuW1RdGKw55fV4RvWMNZYPHCmhPGXbCfbEC3
做得不错!
如果你注意到类型保护,你可能会对你的 quicknodeRpc
对象感到意外。你可能注意到 Intellisense 没有检测到任何原生 Solana 方法:
这是由于我们构建 QuickNodeAddons 接口的方式。如前所述,我们排除了你附加组件定义的方法。
解决这个问题的一种方法是使用 Proxy 对象来拦截方法调用,并在它们执行之前修改它们。这可以通过 JavaScript 中的 Proxy
类完成。另一种方法是创建一个新的接口,该接口包括你附加组件的所有方法以及所有原生 Solana 方法。让我们试一下!
将以下导入添加到你的 example2.ts
文件:
import {
// ... 现有导入
SolanaRpcApi,
address,
} from "@solana/web3.js";
现在,创建一个新的、更广泛的类型,包含你附加组件的所有方法以及所有原生 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 方法以及我们的自定义方法都有效:
继续在你的 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 APIs 到我们的 Solana Web3.js 2.0 应用程序中。我们演示了如何:
这种方法允许在集成各种 QuickNode 附加组件和 API 时实现高度的定制化。我们希望你意识到这个过程允许很多灵活性,所以带上你的创意,构建一些惊人的东西!我们期待看到你的作品—在 QuickNode Discord 或 Twitter 上告诉我们你所构建的内容!
要进一步提高你的 Solana 开发技能:
告诉我们 如果你有任何反馈或希望的新主题。我们很乐意听取你的意见。
- 原文链接: quicknode.com/guides/sol...
- 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!