在Solana上使用Titan元聚合API兑换代币

QuickNode 发布于 2026-06-30 阅读 22

本教程指导你使用Titan Meta-Aggregation API(通过Quicknode提供)构建Solana兑换界面。

概述

在 Solana 上获取最佳兑换价格意味着要比较分散流动性的路径,而每个聚合器只会从其内部的黑盒路由器返回单一路径。自行操作则需要集成多个聚合器并在每次兑换时比较它们的报价。

Titan 元聚合 API 通过一次请求即可完成上述工作。Titan 是一个元聚合器:它会一次性将一笔兑换请求分发到多个流动性提供商,针对链上实时状态模拟每条路径,并返回所有竞争报价以及预期的胜出者。该 API 可通过你的 Quicknode Solana 端点使用,因此无需单独维护集成。

完成本指南后,你将拥有一个由 Titan 通过 Quicknode 驱动的 Solana 兑换交互界面,用户可以选择代币、观察各提供商竞争最佳路径,并端到端地执行一笔兑换。

TLDR

  • 使用通过 Quicknode 端点提供的 Titan 元聚合 API 构建一个 Solana 兑换交互界面
  • 通过 /quote/price 获取轻量级指示性价格用于实时显示,然后通过 /quote/swap 运行完整的提供商竞争
  • 使用 metadata.ExpectedWinner 选择胜出路径,并显示每个竞争提供商的报价
  • 根据 Titan 提供的可组合指令和地址查找表自行构建 VersionedTransaction
  • 使用已连接的钱包签名,并通过 Quicknode RPC 提交交易

你将完成的任务

  • 克隆并运行配套示例应用
  • 为未连接钱包的用户获取指示性价格,为已连接用户获取完整的提供商竞争报价
  • 根据 Titan 的指令和地址查找表构建 VersionedTransaction
  • 通过 Quicknode RPC 签名、发送并确认兑换交易

你需要具备的条件

本指南假设你具备构建 TypeScript dApp、DeFi 以及使用 Solana 钱包的基本知识。

如需复习,请参考:

你还需要:

  • 少量 mainnet-beta 的 SOL 用于支付费用
  • 主网上少量用于兑换的代币(建议使用 USDC)
  • 一个 Quicknode Solana 主网端点。通过注册 Quicknode 获取你的端点
  • 在你的端点上启用 Titan 元聚合 API

危险

Titan 元聚合 API 仅在 Solana 主网测试版(mainnet-beta)上可用。这意味着如果你按照本指南操作并执行兑换,你将交易真实代币并支付真实网络费用,并且可能因价格变动、滑点或选择错误的代币对而损失价值。

Titan 元聚合 API 是如何工作的?

Titan 元聚合 API 是一个 Solana 兑换 API,它会一次性将一笔请求分发到多个流动性提供商,包括 Titan 自己的路由器、第三方 DEX 聚合器以及询价(RFQ)场所,然后针对链上实时状态模拟每条返回的路径,并返回所有竞争报价以及预期胜出的路径。

它与普通聚合器的实际区别在于返回的内容。单引擎聚合器为一个代币对返回一条路径;Titan 则返回每个提供商的独立路径,因此你的应用可以看到完整的竞争情况并进行显示或选择,而不是一个黑盒答案。

有三个特性使得集成变得实用:

  • 竞争提供商。 一次 /quote/swap 调用会返回一个以提供商为键的 quotes 映射。每个条目都是该提供商的一条完整、可执行的路径。metadata.ExpectedWinner 字段命名了 Titan 预期提供最佳执行的提供商,因此你可以自动选择一个胜出者,或向用户展示完整的竞争情况。
  • 模拟验证的路径。 在返回路径之前,Titan 会针对最新的链上状态对其进行模拟。你显示的输出金额与实际执行结果非常接近,这减少了因池数据过时而导致的交易失败。
  • 可组合指令。 Titan 不会返回一个已密封、可直接发送的交易。每条路径都包含原始的兑换指令以及它们引用的地址查找表(ALT)。你需要自行组装、签名和发送交易,这为你将兑换包装到自己的逻辑中留下了空间。

以下是你的兑换交互界面在本指南中遵循的生命周期:

预览价格报价

在钱包连接之前,你只需要一个指示性汇率来在用户输入时显示。GET /api/v1/quote/price 端点非常轻量:它返回预期输出和价格影响,而无需构建任何指令或运行完整的提供商竞争。这是实时价格更新的正确选择。

运行提供商竞争

一旦钱包连接且用户准备行动,你可以使用他们的 userPublicKey 调用 GET /api/v1/quote/swap。这将运行完整的元聚合过程:每个活跃的提供商都会参与竞争,响应中包含每个提供商的可执行指令、ALT、路径步骤以及预期胜出者。示例应用按输出金额从高到低排序报价,并将预期胜出者保持在顶部。

发送交易

由于 Titan 返回的是指令而非完整的交易,你需要从 RPC 解析路径的 ALT,编译一个 v0 版本的 VersionedTransaction,让钱包签名,并通过 Quicknode RPC 提交。你完全拥有交易生命周期。

发现提供商和场所

Titan 还公开了描述聚合广度的元数据端点。GET /api/v1/providers 列出了当前竞争的提供商,GET /api/v1/venues 列出了可路由的链上场所以及其程序 ID)。这些信息在会话期间很少更改,并为示例交互界面中的“提供商竞争”和“涉及场所”显示提供支持。

运行示例应用

首先,克隆示例应用仓库并打开 solana/titan-swap 文件夹。

git clone https://github.com/quiknode-labs/qn-guide-examples.git
cd qn-guide-examples/solana/titan-swap
npm install
npm run dev

该应用从 .env.local 文件中读取两个仅服务端使用的环境变量,它们都来自同一个 Quicknode Solana 端点。QUICKNODE_RPC_URL 是你的端点 URL 本身,你可以从 Quicknode 仪表板 的“端点”页面复制。TITAN_GATEWAY_URL 是该 URL 后附加 /addon/1147/ 的结果(1147 是 Titan 元聚合 API 的插件 ID),一旦你在该端点上启用了该插件,它就会生效。

.env.local

## Quicknode Solana RPC 端点(仅服务端使用,绝不暴露给浏览器)。
QUICKNODE_RPC_URL=https://your-endpoint.solana-mainnet.quiknode.pro/YOUR_TOKEN/

## Titan 元聚合 API 基础 URL:上述 RPC 端点后附加
## /addon/1147/。代理会自动为你附加尾部的 /api/v1。
TITAN_GATEWAY_URL=https://your-endpoint.solana-mainnet.quiknode.pro/YOUR_TOKEN/addon/1147/

在本地运行后,快速浏览一下代码库以熟悉结构,然后返回本指南,逐步完成每个集成步骤。

示例兑换交互界面

这个示例应用是一个兑换交互界面,围绕价格预览、多提供商兑换竞争以及构建 → 签名 → 发送 → 确认流程构建。UI 组件和状态已经就位;以下各节将介绍与 Titan 通信并组装交易的模块。

在服务端调用 Titan API

每个 Titan 请求都通过一个仅服务端的模块发出,这样你的 API 凭据永远不会到达浏览器。该 API 将响应编码为 MessagePack 二进制格式而非 JSON,因此客户端请求 application/vnd.msgpack,在配置了 bearer token 时附加它,并在将干净数据返回之前解码响应。这个单一的辅助函数是应用中使用的每个 Titan 端点的入口点。

lib/titan-server.ts

import "server-only";
import { decode } from "@msgpack/msgpack";

// TITAN_GATEWAY_URL — 你的 Titan 元聚合 API 基础 URL(带或不带尾部的 /api/v1)
// TITAN_GATEWAY_AUTH — 可选的 bearer token(如果尚未包含在 URL 中)
const RAW_BASE = process.env.TITAN_GATEWAY_URL;
const AUTH = process.env.TITAN_GATEWAY_AUTH;

function baseUrl(): string {
  if (!RAW_BASE) {
    throw new Error("TITAN_GATEWAY_URL 未设置。请将你的 API URL 添加到 .env.local。");
  }
  let b = RAW_BASE.replace(/\/+$/, "");
  if (!b.endsWith("/api/v1")) b = `${b}/api/v1`;
  return b;
}

type QueryValue = string | number | boolean | undefined;

async function titanGet<T = unknown>(
  path: string,
  params?: Record<string, QueryValue>
): Promise<T> {
  const url = new URL(`${baseUrl()}${path}`);
  if (params) {
    for (const [k, v] of Object.entries(params)) {
      if (v !== undefined && v !== "") url.searchParams.set(k, String(v));
    }
  }

  const headers: Record<string, string> = { Accept: "application/vnd.msgpack" };
  if (AUTH) headers.Authorization = `Bearer ${AUTH}`;

  const res = await fetch(url.toString(), { headers, cache: "no-store" });
  if (!res.ok) {
    const text = await res.text().catch(() => "");
    throw new Error(`Titan ${path} 失败: ${res.status} ${text.slice(0, 300)}`);
  }

  const buf = new Uint8Array(await res.arrayBuffer());
  return decode(buf) as T;
}

位于 /api/titan/* 下的薄层 Next.js 路由处理器、将 Solana JSON-RPC 转发到你的 Quicknode 端点的 /api/rpc 代理,以及客户端的 fetch 封装,都是标准的框架基础设施,此处省略。

加载代币列表

Titan 元聚合 API 是一个交换聚合器,而不是代币目录,因此它不附带代币列表。示例应用仅用于显示和获取小数位,从公共注册表中提取经过验证的代币元数据(符号、名称、小数位、图标),并以常见代币(SOL、USDC、USDT)的简短列表作为后备。任何路由或定价都不来源于此列表;一切通过 Titan 完成。由于这不涉及 Titan API,代币列表加载器作为标准应用代码保留在示例仓库中。

获取钱包余额

当钱包连接时,应用通过 Quicknode RPC 获取其 SOL 和 SPL 代币余额。这些余额用于填充“来源”选择器(持有最多的排在前),验证用户是否有足够的代币进行兑换,并驱动“最大”按钮。这是普通的 RPC 基础设施,不是 Titan 调用,因此余额 Hook 作为标准应用代码保留在示例仓库中。

获取价格报价

当没有连接钱包时,应用调用 /quote/price 获取上述指示性价格,并在用户输入时进行防抖处理,以使实时显示保持响应,同时不耗尽速率限制。

lib/titan-server.ts

export async function fetchPrice(params: {
  inputMint: string;
  outputMint: string;
  amount: string;
  slippageBps?: number;
}): Promise<TitanPriceResponse> {
  const raw = await titanGet<any>("/quote/price", {
    inputMint: params.inputMint,
    outputMint: params.outputMint,
    amount: params.amount,
    slippageBps: params.slippageBps,
  });
  return {
    inputAmount: String(raw?.inputAmount ?? params.amount),
    outputAmount: String(raw?.outputAmount ?? raw?.outAmount ?? "0"),
    priceImpact: raw?.priceImpact != null ? Number(raw.priceImpact) : undefined,
  };
}

获取交换报价

当钱包连接时,应用使用用户的公钥和 simulate 标志调用 /quote/swap。这就是元聚合发生的地方:响应包含一个以提供商为键的 quotes 映射,以及一个 metadata.ExpectedWinner 字段。应用向每个提供商请求报价(网关限制为 10 个),读取胜出者,标准化每条路径,丢弃未能生成可用路径的提供商,并按输出金额从高到低排序,同时将预期胜出者保持在顶部。

lib/titan-server.ts

export async function fetchSwap(params: {
  inputMint: string;
  outputMint: string;
  amount: string;
  userPublicKey: string;
  slippageBps?: number;
  simulate?: boolean;
}): Promise<TitanSwapResponse> {
  const raw = await titanGet<any>("/quote/swap", {
    inputMint: params.inputMint,
    outputMint: params.outputMint,
    amount: params.amount,
    userPublicKey: params.userPublicKey,
    slippageBps: params.slippageBps,
    simulate: params.simulate,
    // 要求每个提供商报价,以填充竞争(服务器最大值为 10)。
    numQuotes: 10,
  });

  const quotesMap = raw?.quotes ?? {};
  const expectedWinner: string | null = raw?.metadata?.ExpectedWinner ?? null;

  const quotes: TitanSwapRoute[] = Object.entries(quotesMap)
    .map(([provider, route]) => normRoute(provider, route))
    // 丢弃未能生成可用路径的提供商。
    .filter((q) => Number(q.outAmount) > 0);

  // 按最佳输出优先排序;如果存在预期胜出者,则将其保持在顶部。
  quotes.sort((a, b) => {
    if (expectedWinner) {
      if (a.provider === expectedWinner) return -1;
      if (b.provider === expectedWinner) return 1;
    }
    return Number(b.outAmount) - Number(a.outAmount);
  });

  return { quotes, expectedWinner };
}

simulate 参数映射到 UI 的“精确”与“快速”切换。针对链上实时状态模拟每条路径更精确,但会增加延迟;关闭它将返回更快、但验证程度较低的报价。然后 UI 并排显示竞争报价,显示每个报价落后领先者的基点数量,标记预期胜出者,并将胜出路径传递给交换步骤。

响应中的每条路径以 MessagePack 格式到达,使用紧凑键名,公钥以原始字节缓冲区形式出现,因此需要通过标准化过程将所有内容转换为交易构建器期望的 base58/base64 格式。

lib/titan-server.ts

function toBase58(v: unknown): string {
  if (typeof v === "string") return v;
  if (v instanceof Uint8Array) return bs58.encode(v);
  if (Array.isArray(v)) return bs58.encode(Uint8Array.from(v as number[]));
  return String(v ?? "");
}

function toBase64(v: unknown): string {
  if (typeof v === "string") return v;
  if (v instanceof Uint8Array) return Buffer.from(v).toString("base64");
  if (Array.isArray(v)) return Buffer.from(Uint8Array.from(v as number[])).toString("base64");
  return "";
}

function normStep(s: any) {
  return {
    label: String(s.label ?? s.amm ?? "Unknown"),
    ammKey: toBase58(s.ammKey),
    inputMint: toBase58(s.inputMint),
    outputMint: toBase58(s.outputMint),
    inAmount: String(s.inAmount ?? ""),
    outAmount: String(s.outAmount ?? ""),
    // allocPpb 是十亿分之一;转换为百分比。
    allocPct: s.allocPpb != null ? Number(s.allocPpb) / 1e7 : 0,
  };
}

// API 使用紧凑键名(p/a/d, p/s/w),但我们也读取长格式,因此上游的模式调整不会静默破坏构建器。
function normInstruction(ix: any): TitanInstruction {
  return {
    programId: toBase58(ix.p ?? ix.programId),
    accounts: (ix.a ?? ix.accounts ?? []).map((a: any) => ({
      pubkey: toBase58(a.p ?? a.pubkey),
      isSigner: Boolean(a.s ?? a.isSigner),
      isWritable: Boolean(a.w ?? a.isWritable),
    })),
    data: toBase64(ix.d ?? ix.data),
  };
}

function normRoute(provider: string, r: any): TitanSwapRoute {
  return {
    provider,
    inputAmount: String(r.inputAmount ?? r.inAmount ?? ""),
    outAmount: String(r.outAmount ?? r.outputAmount ?? ""),
    slippageBps: Number(r.slippageBps ?? 0),
    priceImpact: r.priceImpact != null ? Number(r.priceImpact) : undefined,
    computeUnitsSafe:
      r.computeUnitsSafe != null ? Number(r.computeUnitsSafe) : undefined,
    steps: (r.steps ?? []).map(normStep),
    instructions: (r.instructions ?? []).map(normInstruction),
    addressLookupTables: (r.addressLookupTables ?? []).map(toBase58),
    expiresAtMs: r.expiresAtMs != null ? Number(r.expiresAtMs) : undefined,
    expiresAfterSlot:
      r.expiresAfterSlot != null ? Number(r.expiresAfterSlot) : undefined,
  };
}

构建 VersionedTransaction

这是与“为你执行”式聚合器的主要集成区别。Titan 返回指令以及它们引用的 ALT,因此你需要自行构建交易。构建器从 Quicknode RPC 解析每个 ALT,根据标准化的 base58 程序 ID 和 base64 数据重新构建每条指令,获取最新的区块哈希,并编译一个包含查找表的 v0 消息。此步骤是标准的 Solana 交易组装,而非 Titan API 调用,因此完整的构建器(lib/build-swap-tx.ts,使用 @solana/kit 构建)保留在示例仓库中供你阅读。

注意

addressLookupTables 中的地址查找表必须从 RPC 获取并在编译消息时传递。跳过此步骤会导致交易编译失败,因为路径指令中的压缩账户引用无法在没有其查找表的情况下解析。

签名并发送交易

交易构建完成后,交换 Hook 提示已连接的钱包对其签名,通过 Quicknode RPC 提交原始交易,并确认它。由于 RPC 代理仅承载 HTTP JSON-RPC(无 WebSocket),应用通过轮询 getSignatureStatuses 来确认,直到交易被确认、出错或其区块哈希过期。状态流经 building → signing → sending → confirming → success,UI 显示签名并附带一个链接,用于在链上验证兑换。

hooks/useSwap.ts

const executeSwap = async (route: TitanSwapRoute, toToken: Token) => {
  if (!signer) throw new Error("钱包未连接");

  const rpc = createRpc();

  // 根据 Titan 的指令 + 查找表构建交易。
  setStatus("building");
  const { message, lastValidBlockHeight } = await buildSwapTransaction(
    route,
    signer,
    rpc
  );

  // 通过附加到消息上的 Kit 签名者在钱包中签名。
  setStatus("signing");
  const signedTransaction = await signTransactionMessageWithSigners(message);

  // 通过 Quicknode RPC 提交。
  setStatus("sending");
  const signature = getSignatureFromTransaction(signedTransaction);
  await rpc
    .sendTransaction(getBase64EncodedWireTransaction(signedTransaction), {
      encoding: "base64",
      skipPreflight: false,
      preflightCommitment: "confirmed",
    })
    .send();
  setTxSignature(signature);

  // 通过 HTTP 轮询确认(RPC 代理不承载 WebSocket)。
  setStatus("confirming");
  await confirmBySignature(rpc, signature, lastValidBlockHeight);

  setStatus("success");
};

签名、发送和确认是标准的 Solana 交易基础设施,而非 Titan 调用,因此相关的 Hook 状态和 confirmBySignature 轮询器作为标准应用代码保留在示例仓库中。

交易签名、发送并确认后,兑换完成。这完成了完整的 Titan 流程:价格预览 → 兑换竞争 → 构建 → 签名 → 发送。

总结

你已经构建了一个兑换交互界面,它由 Titan 元聚合 API 驱动,将代币发现、多提供商路由、滑点处理和交易管理整合为一个统一的体验。由于 Titan 返回的是竞争报价和可组合指令,而非密封的交易,你确切地看到了哪些提供商在竞标、哪条路径获胜,以及如何自行组装和发送交易。这种对交易生命周期的掌控为你提供了坚实的基础来进行扩展,无论是添加优先费用、自定义指令,还是你自己的着陆策略。

常见问题

什么是 Titan 元聚合 API?

Titan 元聚合 API 是一个 Solana 兑换 API,它在单次请求中从多个流动性提供商获取可执行的报价。它不是通过一个引擎路由,而是将请求分发到多个提供商,针对链上实时状态模拟每条路径,并返回所有竞争报价以及预期胜出的报价。

元聚合与常规 DEX 聚合器有何不同?

常规聚合器只返回其自身的最佳路径。元聚合器则同时查询多个提供商,包括其自身的路由器、其他聚合器以及 RFQ 场所,并返回所有路径,以便你的应用进行比较或选择预期胜出者。你获得的是完整的竞争情况,而不是一个黑盒答案。

为什么我必须自己构建交易?

Titan 返回的是可组合指令以及它们引用的地址查找表,而不是密封的交易。你需要从 RPC 解析查找表,编译 v0 版本的 VersionedTransaction,用已连接的钱包签名,然后发送。这让你完全控制将兑换包装到自定义逻辑中,但意味着你必须在编译之前加载查找表,否则交易将无法构建。

我应该何时使用价格端点与交换端点?

在用户输入时,使用 /quote/price 进行轻量级的实时显示,因为它返回预期输出,无需构建指令或运行完整的提供商竞争。仅在钱包连接且用户准备行动时调用 /quote/swap,因为它会运行完整的元聚合并返回可执行指令和地址查找表。

Titan 元聚合 API 在 Solana 测试网上可用吗?还是仅限主网?

它仅在 Solana 主网测试版(mainnet-beta)上可用。你执行的任何兑换都将交易真实代币并产生真实网络费用,可能因价格变动、滑点或选择错误的代币对而导致价值损失。

集成 Titan 元聚合 API 需要什么?

你需要一个启用了 Titan 元聚合 API 的 Quicknode Solana 主网端点、基本的 TypeScript 和 Solana dApp 经验,以及少量主网 SOL 和代币用于测试。Quicknode RPC Token 和 API 凭据都应保持在服务端,位于代理路由之后。

资源

我们 ❤️ 反馈!

如果你有任何反馈或新主题的请求,请告诉我们。我们很乐意听到你的声音。

  • 原文链接: quicknode.com/guides/sol...
  • 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~

相关文章

0 条评论