第 7 章 · 02 Jupiter 代币价格与搜索 本节摘要:本节精读两个 Solana 代币数据工具—— (约 75 行)和 (约 68 行)。它们都走 Jupiter 官方 API( ),一个按 mint 地址查 USD 价格(Price API V3),一个按 symbol/名称/mint 搜代币信息(Tokens API V2)。两个文件结构高度对称:用 发 GET、读 加 header、返回 字符串。本节重点讲清三个工程要点:为什么价格按 mint 地址而非 symbol 查(Solana 上同名代币泛滥)、Jupiter 的 API key 是可选的(有 key 提高额度,没 key 也能用公共额度)、以及两个工具的对称设计如何降低心智负担。
本节摘要:本节精读两个 Solana 代币数据工具——
jupiter_price.py(约 75 行)和jupiter_search.py(约 68 行)。它们都走 Jupiter 官方 API(api.jup.ag),一个按 mint 地址查 USD 价格(Price API V3),一个按 symbol/名称/mint 搜代币信息(Tokens API V2)。两个文件结构高度对称:用httpx.Client发 GET、读JUPITER_API_KEY加x-api-keyheader、返回json.dumps(resp.json())字符串。本节重点讲清三个工程要点:为什么价格按 mint 地址而非 symbol 查(Solana 上同名代币泛滥)、Jupiter 的 API key 是可选的(有 key 提高额度,没 key 也能用公共额度)、以及两个工具的对称设计如何降低心智负担。读完本节,你理解 Solana 链上「认物」和「认价」的标准做法。
内容来源:原项目源码
autohedge/tools/jupiter_price.py与autohedge/tools/jupiter_search.py,逐行精读并套用体系化模板。
⚠️ 现实澄清:Jupiter 是 Solana 上最大的 DEX 聚合器,API 相对稳定,但免费额度有限。生产环境建议申请
JUPITER_API_KEY(在 portal.jup.ag),否则高频调用会被限流。
阅读完本节,你应当能够:
get_token_price 和 search_tokens 两个函数。JUPITER_API_KEY 是可选鉴权的含义(公共额度 + 提升额度)。Solana 链上有成千上万种代币,要拿到「某代币当前值多少美元」和「某名字对应哪个代币」,是两个不同的问题:
| 需求 | 工具 | 端点 | 入参 | 出参 |
|---|---|---|---|---|
| 已知 mint,查 USD 价格 | get_token_price |
/price/v3 |
mint 地址(可多个) | {mint: {usdPrice, decimals, ...}} |
| 只知道名字/symbol,找代币 | search_tokens |
/tokens/v2/search |
symbol/名称/mint | [{id, name, symbol, mcap, liquidity, ...}] |
典型工作流是先 search 拿 mint,再 price 查价:用户输入「USDC」,先 search_tokens("USDC") 拿到 USDC 的 mint 地址 EPjF...Dt1v,再 get_token_price("EPjF...Dt1v") 拿到当前价格。两步缺一不可——直接拿 symbol 查价不行,因为 Jupiter 价格端点只认 mint。
这是 Solana 与传统交易所的根本差异。在 A 股,「600519」唯一对应贵州茅台;在 CEX,「BTC」基本唯一指比特币。但在 Solana 这种人人可发币的链上:
USDC、名字也叫 USD Coin 的假币。EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v)唯一确定。所以 Jupiter Price API 只认 mint 地址,这是防伪造的根本设计。search_tokens 返回结果里也带 isVerified、organicScore、liquidity 等字段,帮用户分辨真假。
💡 核心心法:Solana 的世界,mint 地址是代币的身份证号,symbol 只是花名。花名可以重名,身份证号不会。任何链上操作(查价、转账、兑换)都必须最终落到 mint 地址。
完整代码:
import json import os from typing import List, Union import httpx from loguru import logger JUPITER_PRICE_BASE = "https://api.jup.ag" def get_token_price(ids: Union[str, List[str]]) -> str: """ Get USD prices for one or more Solana token mint addresses via Jupiter Price API V3. ... """ if isinstance(ids, list): # ① 列表→逗号拼接 ids_param = ",".join(ids) else: ids_param = ids if not ids_param or not ids_param.strip(): # ② 空入参兜底 logger.warning("get_token_price: ids is empty") return "{}" url = f"{JUPITER_PRICE_BASE}/price/v3" params = {"ids": ids_param} headers = {} key = os.getenv("JUPITER_API_KEY") # ③ 可选鉴权 if key: headers["x-api-key"] = key try: with httpx.Client(timeout=10) as client: resp = client.get( # ④ GET /price/v3 url, params=params, headers=headers or None ) resp.raise_for_status() return json.dumps(resp.json()) # ⑤ 原样转字符串返回 except httpx.HTTPError as e: logger.error(f"Jupiter price API request failed: {e}") raise # ⑥ 失败抛异常
逐段:
"So111...11112,EPjF...Dt1v"),作为 ids 查询参数。一次请求查多个价,省往返。"{}",不发空请求。和 yahoo_api 的约定一致——工具失败/空输入返回安全字符串。JUPITER_API_KEY 有就加 x-api-key header,没有就用公共额度。headers or None 这一句很妙——空 dict 在 Python 里是 falsy,{} or None 等于 None,httpx 收到 None 就不发 header,避免发个空 header 惹麻烦。json.dumps(resp.json()) 把 Jupiter 返回的 JSON 原样序列化回字符串。注意这里不做任何字段筛选,把完整响应交给 LLM,让它自己挑 usdPrice、priceChange24h 等字段。raise 把 HTTP 错误往上抛。设计取舍:价格查不到是硬错误(可能是 mint 写错、网络断了),让上层知道比吞掉好。Price V3 返回结构(以 wrapped SOL 为例):
{ "So11111111111111111111111111111111111111112": { "decimals": 9, "usdPrice": 145.32, "blockId": 123456789, "priceChange24h": 2.15 } }
字段含义:
decimals:精度(用于链上最小单位↔人类单位的换算。SOL 的 decimals=9 意味着 1 SOL = 10^9 lamports)。usdPrice:当前 USD 价格。blockId:这个价格对应的链上区块号(可用于判断价格新鲜度)。priceChange24h:24 小时涨跌幅(百分比)。💡 decimals 的重要性:
get_order的 amount 参数用的是最小单位(lamports),不是人类单位(SOL)。要换算必须知道 decimals。所以查价时一并拿到 decimals,是后续构造交易金额的必要信息——第 8 章会用到。
完整代码:
import json import os import httpx from loguru import logger JUPITER_TOKENS_BASE = "https://api.jup.ag/tokens/v2" def search_tokens(query: str) -> str: """ Search for Solana tokens by symbol, name, or mint address via Jupiter Tokens API V2. ... """ if not query or not query.strip(): # ① 空入参兜底 logger.warning("search_tokens: query is empty") return "[]" url = f"{JUPITER_TOKENS_BASE}/search" params = {"query": query.strip()} headers = {} key = os.getenv("JUPITER_API_KEY") # ② 可选鉴权(同 price) if key: headers["x-api-key"] = key try: with httpx.Client(timeout=10) as client: resp = client.get( # ③ GET /tokens/v2/search url, params=params, headers=headers or None ) resp.raise_for_status() return json.dumps(resp.json()) # ④ 原样返回 except httpx.HTTPError as e: logger.error(f"Jupiter tokens search request failed: {e}") raise
结构和 get_token_price 几乎一模一样:同样的空兜底、同样的可选鉴权、同样的 GET + raise_for_status、同样的 json.dumps 返回。差别只有三处:
/tokens/v2 vs /price/v3。query(symbol/名称/mint 字符串)vs ids(mint 地址)。"[]"(数组空)vs "{}"(对象空)——因为 search 返回的是数组,price 返回的是 map,空值形态要对应。💡 对称设计的红利:两个工具结构一致,学会一个就会用另一个。读者维护时心智负担小,LLM 调用时也容易触类旁通。这是「同构 API」应保持「同构客户端」的好实践。
search 返回一个数组,每个元素是一个代币信息对象:
[ { "id": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", "name": "USD Coin", "symbol": "USDC", "decimals": 6, "icon": "https://...", "holderCount": 1234567, "fdv": 3.4e9, "mcap": 3.3e9, "usdPrice": 1.0, "liquidity": 1.2e7, "organicScore": 0.95, "isVerified": true, "tags": ["stablecoin", "verified"] } ]
几个关键字段:
id:就是 mint 地址(Price API 用的就是它)。organicScore:「有机评分」,衡量这个代币的交易/持仓是不是真实的(防刷量)。isVerified:是否经过 Jupiter 验证。liquidity:流动性(美元),流动性太低的代币很难成交、容易被夹。fdv/mcap:完全稀释估值 / 流通市值。💡 真假代币分辨:搜
USDC可能返回好几个结果(真 USDC + 各种假币)。要靠isVerified=true、organicScore高、liquidity大、holderCount多这几个指标综合判断,锁定真的那个,拿到它的id(mint)再去查价。
把 search 和 price 串起来,从用户输入到拿到价格的完整链路:
注意中间「筛选」这一步是 LLM 或上层逻辑做的——search 返回多个候选,工具本身不替你决定哪个是真 USDC。这正契合工具设计的边界:工具提供信息,判断留给 Agent。
把 Jupiter 两个工具和 yahoo_api 放一起对比,体会「链上数据」与「股票数据」工具的差异:
| 维度 | yahoo_api | jupiter_price/search |
|---|---|---|
| 标识符 | symbol(如 AAPL,基本唯一) | mint 地址(唯一),symbol 易重名 |
| 数据源稳定性 | 非官方抓取,常被 429 | Jupiter 官方 API,较稳 |
| 鉴权 | 无(免费) | 可选 JUPITER_API_KEY |
| 失败处理 | 部分数据返回 + _warning |
抛 HTTPError |
| 返回 | JSON 字符串(经过 _df_to_json_serializable 加工) |
JSON 字符串(原样 json.dumps) |
⚠️ 现实澄清:Jupiter 工具的失败处理是直接
raise,意味着如果网络抖动或被限流,工具会抛异常给上层 Agent。swarms 框架默认不一定会优雅处理工具异常——这可能让 Agent 整个 run 失败。对比 yahoo_api 的「部分返回」,这里的容错更粗放,是可改进点。
get_token_price 按 mint 查价(Price V3),search_tokens 按 symbol/名称/mint 搜代币(Tokens V2);先 search 拿 mint,再 price 查价。usdPrice/decimals/priceChange24h 的对象;失败 raise。isVerified/organicScore/liquidity 用于分辨真假;结构和 price 工具高度对称。JUPITER_API_KEY 有就加 x-api-key 提升额度,没有用公共额度;headers or None 处理空 dict。下一节,我们直面本项目一个尴尬真相——
polygon_api.py文件名叫 polygon,代码里指向的却是 massive.com 的占位实现,这背后是项目成熟度的一个缩影。