第 8 章 · 02 Jupiter Ultra Swap API


文档摘要

第 8 章 · 02 Jupiter Ultra Swap API 本节摘要:本节精读 的 函数——它把上一节的账户/密钥模型变成一次真实的兑换请求。Jupiter 是 Solana 上最大的 DEX 聚合器,Ultra Swap API 是它的一站式接口:你给它「用 inputmint 兑 outputmint、数量 amount、谁是 taker/receiver/payer」,它返回一笔已组装好但未签名的交易(base64)+ 。本节逐行拆解 getorder:从私钥派生钱包地址、把 taker/receiver/payer 全设成自己的钱包、GET 拿报价、返回含 (路由)、 (手续费基点)、 的 JSON。

第 8 章 · 02 Jupiter Ultra Swap API

本节摘要:本节精读 ultra_tools.pyget_order 函数——它把上一节的账户/密钥模型变成一次真实的兑换请求。Jupiter 是 Solana 上最大的 DEX 聚合器,Ultra Swap API 是它的一站式接口:你给它「用 input_mint 兑 output_mint、数量 amount、谁是 taker/receiver/payer」,它返回一笔已组装好但未签名的交易(base64)+ requestId。本节逐行拆解 get_order:从私钥派生钱包地址、把 taker/receiver/payer 全设成自己的钱包、GET /ultra/v1/order 拿报价、返回含 routePlan(路由)、feeBps(手续费基点)、inAmount/outAmount 的 JSON。重点指出一个本项目特有的坑:代码里读 SOLANA_PRIVATE_KEY,但 .env.example 写的却是 WALLET_PRIVATE_KEY——照着示例配,代码会报「私钥缺失」。

内容来源:原项目源码 autohedge/tools/ultra_tools.pyget_order_headers,逐行精读并套用体系化模板。

⚠️ 风险提示:get_order 本身不花你的钱(它只取报价、组装未签名交易),真正动钱的是下一节的 execute_trade。但报价有时效(Jupiter 锁价一小段时间),签名太晚提交可能因价格变化被拒。

学习目标

阅读完本节,你应当能够:

  1. 逐行读懂 get_order 函数。
  2. 说清 Jupiter Ultra Swap API 的一站式设计(输入 mint 对 → 输出未签名交易 + requestId)。
  3. 解释 taker / receiver / payer 三个角色,以及为什么本项目都设成同一个钱包。
  4. 理解返回的 routePlan / feeBps / inAmount / outAmount 各自含义。
  5. 指出 SOLANA_PRIVATE_KEY.env.exampleWALLET_PRIVATE_KEY 不一致坑
  6. 知道 JUPITER_API_KEY可选鉴权(提升额度),与签名密钥无关。

一、Jupiter 是什么:DEX 聚合器

Solana 上有几十个去中心化交易所(DEX):Orca、Raydium、Meteora......每个 DEX 有自己的流动性和价格。Jupiter 是其中的聚合器( aggregator)——它不自己托管流动性,而是:

  1. 同时查多个 DEX 的报价;
  2. 计算最优路径(可能跨多个 DEX 拆单,如「SOL → USDC → USDT」比直兑更划算);
  3. 把整条路径打包成一笔交易(用户只签一次名,链上自动完成多跳)。

所以用 Jupiter 的好处是:拿到全链最优价 + 一笔交易搞定多跳路由。代价是要信任 Jupiter 的路由算法和 API。

Ultra Swap API 是 Jupiter 较新的一站式接口,把「报价 + 路由 + 交易组装」合并成一个端点 /ultra/v1/order。对比旧的 Quote + Swap 两步接口,Ultra 更简单:一次调用拿到的就是可直接签名的完整交易

二、get_order 全貌

完整代码(ultra_tools.py 第 85-155 行):

def get_order(input_mint: str, output_mint: str, amount: str) -> str: """ Request a base64-encoded unsigned swap transaction for use with execute_trade (Jupiter Ultra). Taker, receiver, and payer are always the wallet from SOLANA_PRIVATE_KEY in .env. ... """ if not input_mint or not input_mint.strip(): raise ValueError("input_mint is required") if not output_mint or not output_mint.strip(): raise ValueError("output_mint is required") if not amount or not amount.strip(): raise ValueError("amount is required") wallet_pubkey = _get_wallet_pubkey() # ① 从私钥派生地址 params = { "inputMint": input_mint.strip(), "outputMint": output_mint.strip(), "amount": amount.strip(), "taker": wallet_pubkey, # ② 三角色全是自己 "receiver": wallet_pubkey, "payer": wallet_pubkey, } url = f"{JUPITER_ULTRA_BASE}/order" # ③ /ultra/v1/order try: with httpx.Client(timeout=15) as client: resp = client.get( # ④ GET 请求 url, params=params, headers=_headers() or None, ) resp.raise_for_status() return json.dumps(resp.json()) # ⑤ 原样返回 JSON 字符串 except httpx.HTTPError as e: logger.error(f"Jupiter Ultra order request failed: {e}") raise

逻辑链:input/output mint + amount派生钱包地址GET /order返回含未签名交易 + requestId 的 JSON。注意 get_order 不签名、不广播,它只「拿货」;签名和广播是下一节 execute_trade 的事。下面拆开讲。

三、入参校验:三个必填

if not input_mint or not input_mint.strip(): raise ValueError("input_mint is required") if not output_mint or not output_mint.strip(): raise ValueError("output_mint is required") if not amount or not amount.strip(): raise ValueError("amount is required")

三个入参缺一不可,空值直接 raise ValueError(不是返回安全字符串)——和 yahoo_api 的「失败返回 {}」不同。设计取舍:get_order 失败通常意味着调用方写错了(没传 mint/amount),应该立刻暴露而不是悄悄返回空让下游困惑。

回顾上一节:

  • input_mint / output_mint:完整 mint 地址,不能写 SOL/USDC
  • amount:input_mint 的最小单位数(SOL 是 lamports,USDC 是 10^-6)。

四、三个角色:taker / receiver / payer

wallet_pubkey = _get_wallet_pubkey() params = { "inputMint": input_mint.strip(), "outputMint": output_mint.strip(), "amount": amount.strip(), "taker": wallet_pubkey, # 发起兑换的人 "receiver": wallet_pubkey, # 收到 output_mint 的人 "payer": wallet_pubkey, # 付 gas 和 input_mint 的人 }

三个角色在 Jupiter Ultra API 里本可分开:

  • taker:发起这笔兑换的主体(有些场景是代别人兑换)。
  • receiver:兑换后 output_mint 收到哪个地址(可以转给别人的钱包)。
  • payer:谁付 gas 和被扣 input_mint(签名人必须是 payer 或其授权)。

本项目把三者全设成同一个钱包(_get_wallet_pubkey())——自己兑、自己收、自己付。这是最简单的「自兑自用」模式。如果想做「帮别人兑换并打到对方地址」,把 receiver 改成对方地址即可(但 taker/payer 仍是自己)。

💡 关键心法:这三个角色都是地址(公钥),不是私钥。get_order 只需要地址(用来组装交易里的账户列表),不需要私钥——私钥只在签名(execute_trade)时才用。所以「拿报价」和「签名授权」是严格分离的两步,这是 DeFi 安全的基础(下一节详讲)。

五、请求细节:_headers 与超时

_headers(第 20-36 行):

def _headers() -> Dict[str, str]: headers = {} key = os.getenv("JUPITER_API_KEY") if key: headers["x-api-key"] = key return headers
  • JUPITER_API_KEY,有就加 x-api-key header,没有也能用(公共额度)。和第 7 章 jupiter_price/search 一样的可选鉴权设计。
  • 注意:JUPITER_API_KEY调用 Jupiter API 的鉴权 key(在 portal.jup.ag 申请),与 SOLANA_PRIVATE_KEY(签名密钥)是完全不同的两样东西。前者是「能不能调 Jupiter」,后者是「能不能动你的钱包」。千万别混淆。

请求超时 15 秒(第 145 行 timeout=15),介于 price 工具的 10 秒和 execute_trade 的 30 秒之间——取报价要等 Jupiter 算路由,比纯查价慢,但比签名广播快。

六、返回结构:未签名交易 + 路由信息

get_order 返回的 JSON(第 109-111 行 docstring 列了字段)大致长这样:

{ "mode": "...", "inputMint": "So11111111111111111111111111111111111111112", "outputMint": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", "inAmount": "10000000", "outAmount": "...", "routePlan": [ {"swapInfo": {...}, "swapAmount": "..."}, {"swapInfo": {...}, "swapAmount": "..."} ], "transaction": "AQAAAAAAAAA...(base64 未签名交易)", "requestId": "b5e5f3a7-8c4d-4e2f-9a1b-3c6d8e0f2a4b", "feeBps": 0, "platformFee": null, "priorityLevel": "...", "computeUnitLimitMeta": ..., "gas": {...} }

关键字段:

  • transaction:核心!这是 Jupiter 组装好的未签名交易(base64 编码的字节)。下一节 execute_trade 就是拿它来签名。此时它对链上无效——没你的签名,广播了也会被拒。
  • requestId:这笔报价的唯一 ID。execute_trade 时必须回传同一个 requestId,Jupiter 用它核对「你签名的交易对应哪个报价」,防止拿旧报价套新价。
  • routePlan:路由计划,每一段是一个 DEX 兑换(swapInfo 含 DEX 名、地址),按顺序执行。多段说明 Jupiter 拆单跨多个 DEX 找最优价。
  • inAmount / outAmount:输入量(你传的 amount 原样返回)和预估输出量。outAmount 是按当前流动性的估算,实际成交可能因滑点略变。
  • feeBps:手续费基点(basis points,1 bps = 0.01%)。feeBps: 0 表示这笔不收平台费;非 0 时是 Jupiter 平台费比例。
  • gas / priorityLevel:gas 和优先级,影响交易被打包的速度。

💡 routePlan 的意义:routePlan 让你能在签名前审计这笔交易要走哪些 DEX。生产风控应该检查:这些 DEX 是不是可信的?有没有路由到刚上线、流动性可疑的池子(防夹子)?本项目完全没做这类检查——拿到 routePlan 直接就签了(下一节详述),是风控缺口。

七、坑:SOLANA_PRIVATE_KEY 与 WALLET_PRIVATE_KEY 不一致

这是本项目一个实打实的配置坑,务必记下:

位置 变量名
ultra_tools.py_get_keypair(第 57 行) SOLANA_PRIVATE_KEY
.env.example(第 12 行) WALLET_PRIVATE_KEY=""
README.md 的环境变量示例 WALLET_PRIVATE_KEY=""

代码读 SOLANA_PRIVATE_KEY,配置示例写 WALLET_PRIVATE_KEY——照着 .env.example 把私钥填到 WALLET_PRIVATE_KEY 里,运行时 _get_keypairSOLANA_PRIVATE_KEY 拿不到,直接报:

ValueError: SOLANA_PRIVATE_KEY is required in .env to sign transactions

也就是说,即使你「正确地」照着官方示例配了 .env,只要没读源码、没发现这个不一致,代码就跑不起来。这是项目「半成品」的又一证据——文档和代码各走各的路。

⚠️ 现实澄清:正确的做法是 .env 里写 SOLANA_PRIVATE_KEY="你的base58私钥"(忽略 .env.example 误导的 WALLET_PRIVATE_KEY)。或者更稳妥:修源码改成统一读 WALLET_PRIVATE_KEY,或让 _get_keypair 两个名字都认(os.getenv("SOLANA_PRIVATE_KEY") or os.getenv("WALLET_PRIVATE_KEY"))。第 10 章会把这个不一致纳入落差盘点。

八、鉴权 key 与签名 key:别混淆

小结两类「key」的区别,这是本节最容易混的点:

名称 来源 作用 存哪 能干什么
JUPITER_API_KEY portal.jup.ag 申请 调 Jupiter API 的鉴权(提额度) .env 提升 API 调用额度;泄露只是被蹭额度
SOLANA_PRIVATE_KEY 钱包生成 签名交易的私钥 .env(明文,有隐患) 动钱包里所有资产;泄露=资产清零

JUPITER_API_KEY 泄露最多被别人蹭你的 API 额度;SOLANA_PRIVATE_KEY 泄露则钱包归零。两者风险等级完全不同,绝不能一样对待。生产环境,签名密钥要用 HSM/KMS/硬件钱包隔离,不能明文躺 .env(第 10 章详讲)。

九、get_order 不花你的钱

重要的事实再强调一次:get_order 返回的 transaction 是未签名的,它对链上无效。任何人拿到这串 base64,没有你的私钥都无法让它生效。所以你可以放心地:

  • 反复调用 get_order 比价(取多个 routePlan 对比);
  • 打印 transaction 字段研究结构;
  • 把它传给第三方审计路由——

只要你不签(execute_trade),它就只是「一个报价单」,不是「一笔已发生的交易」。这就是「先报价、后签名」两步分离的安全价值。

本节要点回顾

  1. Jupiter 是聚合器:一次查多个 DEX,算最优多跳路由,打包成一笔交易;Ultra Swap API 把报价+组装合并到 /ultra/v1/order
  2. get_order 全貌:校验三参数 → 派生钱包地址 → 设 taker/receiver/payer 全为自己 → GET /order → 返回含未签名交易+requestId+routePlan 的 JSON;不签名不广播,只取报价。
  3. 三个角色:taker(发起)/receiver(收款)/payer(付款+签名人);本项目全设同一钱包,是「自兑自用」最简模式;三者都是地址不是私钥。
  4. 返回字段:transaction(未签名 base64,核心)、requestId(签名时回传配对)、routePlan(路由,可审计)、inAmount/outAmountfeeBps(平台费基点)。
  5. 配置坑:代码读 SOLANA_PRIVATE_KEY,但 .env.example/README 写 WALLET_PRIVATE_KEY——照示例配会报「私钥缺失」,必须改 .env 用 SOLANA_PRIVATE_KEY 或修源码统一。
  6. 两类 key 区别:JUPITER_API_KEY(API 鉴权,泄露=蹭额度)vs SOLANA_PRIVATE_KEY(签名私钥,泄露=资产清零),风险等级天差地别。
  7. 未签名即无效:get_order 的 transaction 没你的签名对链上无效,可放心比价/审计;只有 execute_trade 签名后才动钱。

下一节(已存在)逐行精读 execute_trade 的 VersionedTransaction 签名五步流程。本教程的下一节我们补 04——把 get_order + execute_trade + get_holdings 串成完整实盘链路,看整条链如何被 Agent 触发、以及无前置风控的现实。


发布者: 作者: 灏天文库 转发
评论区 (0)
U