第 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。
本节摘要:本节精读
ultra_tools.py的get_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.py的get_order与_headers,逐行精读并套用体系化模板。
⚠️ 风险提示:get_order 本身不花你的钱(它只取报价、组装未签名交易),真正动钱的是下一节的 execute_trade。但报价有时效(Jupiter 锁价一小段时间),签名太晚提交可能因价格变化被拒。
阅读完本节,你应当能够:
get_order 函数。SOLANA_PRIVATE_KEY 与 .env.example 的 WALLET_PRIVATE_KEY 不一致坑。JUPITER_API_KEY 是可选鉴权(提升额度),与签名密钥无关。Solana 上有几十个去中心化交易所(DEX):Orca、Raydium、Meteora......每个 DEX 有自己的流动性和价格。Jupiter 是其中的聚合器( aggregator)——它不自己托管流动性,而是:
所以用 Jupiter 的好处是:拿到全链最优价 + 一笔交易搞定多跳路由。代价是要信任 Jupiter 的路由算法和 API。
Ultra Swap API 是 Jupiter 较新的一站式接口,把「报价 + 路由 + 交易组装」合并成一个端点 /ultra/v1/order。对比旧的 Quote + Swap 两步接口,Ultra 更简单:一次调用拿到的就是可直接签名的完整交易。
完整代码(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)。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 里本可分开:
本项目把三者全设成同一个钱包(_get_wallet_pubkey())——自己兑、自己收、自己付。这是最简单的「自兑自用」模式。如果想做「帮别人兑换并打到对方地址」,把 receiver 改成对方地址即可(但 taker/payer 仍是自己)。
💡 关键心法:这三个角色都是地址(公钥),不是私钥。get_order 只需要地址(用来组装交易里的账户列表),不需要私钥——私钥只在签名(execute_trade)时才用。所以「拿报价」和「签名授权」是严格分离的两步,这是 DeFi 安全的基础(下一节详讲)。
_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 直接就签了(下一节详述),是风控缺口。
这是本项目一个实打实的配置坑,务必记下:
| 位置 | 变量名 |
|---|---|
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_keypair 读 SOLANA_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」的区别,这是本节最容易混的点:
| 名称 | 来源 | 作用 | 存哪 | 能干什么 |
|---|---|---|---|---|
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 返回的 transaction 是未签名的,它对链上无效。任何人拿到这串 base64,没有你的私钥都无法让它生效。所以你可以放心地:
只要你不签(execute_trade),它就只是「一个报价单」,不是「一笔已发生的交易」。这就是「先报价、后签名」两步分离的安全价值。
/ultra/v1/order。transaction(未签名 base64,核心)、requestId(签名时回传配对)、routePlan(路由,可审计)、inAmount/outAmount、feeBps(平台费基点)。SOLANA_PRIVATE_KEY,但 .env.example/README 写 WALLET_PRIVATE_KEY——照示例配会报「私钥缺失」,必须改 .env 用 SOLANA_PRIVATE_KEY 或修源码统一。JUPITER_API_KEY(API 鉴权,泄露=蹭额度)vs SOLANA_PRIVATE_KEY(签名私钥,泄露=资产清零),风险等级天差地别。下一节(已存在)逐行精读 execute_trade 的 VersionedTransaction 签名五步流程。本教程的下一节我们补 04——把 get_order + execute_trade + get_holdings 串成完整实盘链路,看整条链如何被 Agent 触发、以及无前置风控的现实。