第 8 章 · 04 实盘执行链路 本节摘要:本节把第 02 节的 、第 03 节的 ,加上查询持仓的 ,串成一条完整的实盘执行链路——从「想知道钱包有什么」到「真的完成一笔链上兑换」。整条链是:Agent 或脚本调用 看持仓 → 决定兑什么 → 拿未签名交易+报价 → 签名广播 → 拿到链上成交回执。本节还会讲清一个现实问题:这条链路虽然代码正确,却没有被任何 Agent 真正接入—— 的四个专家没一个绑定 ultratools, 收录了它们但没人调;而且整条链没有任何前置风控(不查金额上限、不审计路由、不防夹子),Agent 一旦决定交易就直奔签名。读完本节,你既会跑通这条链,也清楚它的危险缺口。 内容来源:原项目源码 的 + 整链串接,精读并套用体系化模板。
本节摘要:本节把第 02 节的
get_order、第 03 节的execute_trade,加上查询持仓的get_holdings,串成一条完整的实盘执行链路——从「想知道钱包有什么」到「真的完成一笔链上兑换」。整条链是:Agent 或脚本调用get_holdings看持仓 → 决定兑什么 →get_order拿未签名交易+报价 →execute_trade签名广播 → 拿到链上成交回执。本节还会讲清一个现实问题:这条链路虽然代码正确,却没有被任何 Agent 真正接入——workers.py的四个专家没一个绑定 ultra_tools,get_tools()收录了它们但没人调;而且整条链没有任何前置风控(不查金额上限、不审计路由、不防夹子),Agent 一旦决定交易就直奔签名。读完本节,你既会跑通这条链,也清楚它的危险缺口。
内容来源:原项目源码
autohedge/tools/ultra_tools.py的get_holdings+ 整链串接,精读并套用体系化模板。
⚠️ 风险提示:这是全项目唯一真实动钱的链路。务必用测试钱包、小额、在测试网或主网丢点零钱试。没有任何风控闸门,Agent/脚本给什么参数就签什么,填错金额或 mint 直接成交,无法撤销。
阅读完本节,你应当能够:
get_holdings(查持仓)。workers.py 里没绑定)。get_holdings(ultra_tools.py 第 241-282 行)是这条链路的起点——先看钱包里有什么,才能决定兑什么:
def get_holdings(address: str) -> str: """ Get token balances and SOL balance for a wallet address (Jupiter Ultra). ... """ if not address or not address.strip(): raise ValueError("address is required") url = f"{JUPITER_ULTRA_BASE}/holdings/{address.strip()}" try: with httpx.Client(timeout=10) as client: resp = client.get(url, headers=_headers() or None) resp.raise_for_status() return json.dumps(resp.json()) except httpx.HTTPError as e: logger.error(f"Jupiter Ultra holdings request failed: {e}") raise
逐行:
address:要查的钱包地址(Solana 公钥 base58)。注意这里不需要私钥——查别人的持仓也可以(链上数据公开),只要地址。所以 get_holdings 和 get_order/execute_trade 不同,它不调 _get_wallet_pubkey(),直接吃外部传的地址。GET /ultra/v1/holdings/{address}——Jupiter 把「这个地址有多少 SOL、有哪些代币、各多少」一次返回。_headers() 可选加 JUPITER_API_KEY。{}」不同)。返回结构大致:
{ "amount": "1500000000", "uiAmount": 1.5, "uiAmountString": "1.5", "tokens": { "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v": [ { "account": "...", "amount": "2000000", "uiAmount": 2.0, "uiAmountString": "2.0", "isFrozen": false, "decimals": 6 } ] } }
amount 是 lamports(1.5 SOL = 1.5e9 lamports),uiAmount 是人类单位。tokens:代币账户 map,key 是 mint 地址,value 是该 mint 下所有代币账户的数组(一个钱包可能有多个 USDC 账户)。isFrozen:代币账户是否被冻结(冻结状态下不能转)。💡 查 vs 动的分离:
get_holdings只读,任何人查任何地址都行;get_order/execute_trade要动钱,必须私钥。这条链路上「查」和「动」用不同的权限模型,是 Solana 公开账本的安全设计。
把三个工具放在链路里看分工:
| 工具 | 动作 | 要私钥吗 | 副作用 |
|---|---|---|---|
get_holdings |
查持仓 | ❌(只要地址) | 无(只读) |
get_order |
拿未签名交易+报价 | ❌(只要地址) | 无(只是报价单) |
execute_trade |
签名+广播 | ✅(要 SOLANA_PRIVATE_KEY) | 真实动钱 |
这条链的风险完全集中在 execute_trade——前两步怎么调用都安全(最多浪费 API 额度),只有 execute_trade 一签,链上就动了。所以风控闸门必须加在 execute_trade 之前(本节末详述)。
把三步串起来,一笔「用 0.01 SOL 换 USDC」的完整链路:
每一步的代码片段(精简,真实可跑):
import json from autohedge.tools.ultra_tools import get_holdings, get_order, execute_trade MY_ADDR = "你的钱包地址" SOL = "So11111111111111111111111111111111111111112" USDC = "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v" # ① 查持仓 print(json.loads(get_holdings(MY_ADDR))) # ② 取报价(0.01 SOL = 10_000_000 lamports) order = json.loads(get_order(SOL, USDC, "10000000")) print("预计得 USDC:", order.get("outAmount"), "路由:", order.get("routePlan")) # ③ 签名广播 result = execute_trade(order["transaction"], order["requestId"]) print("成交:", json.loads(result))
这段代码在配置好 SOLANA_PRIVATE_KEY 和 JUPITER_API_KEY、且钱包有 0.01 SOL + gas 的情况下,真的会成交。务必先在测试网/小额验证。
execute_trade 返回的 JSON(第 177-179 行 docstring 列了字段):
{ "status": "Success", "signature": "5KJ2...链上交易哈希...", "slot": 234567890, "totalInputAmount": "10000000", "totalOutputAmount": "...", "inputAmountResult": "...", "outputAmountResult": "...", "swapEvents": [...] }
核对要点:
status:Success 或 Failed。Failed 时看 error/code 字段。signature:链上交易哈希,可以用 Solana 浏览器(如 solscan.io)查这笔交易的完整链上记录。这是「这笔交易真的上链了」的证据。slot:出块高度,可用于判断确认深度。totalInputAmount / totalOutputAmount:实际成交的输入/输出量。和 get_order 的预估 outAmount 比,差额就是滑点。swapEvents:链上各跳兑换事件的明细。生产环境必须做「成交核对」:status==Success 且 totalOutputAmount 在可接受滑点范围内,才算这单成功。本项目完全没做这个核对,execute_trade 返回即结束。
现在讲本节最重要的现实:这条链路代码正确,但在 AutoHedge 里没有被任何 Agent 触发。证据来自 workers.py(第 26-69 行):
sentiment_agent = Agent(..., tools=[exa_search]) # 只有 exa_search risk_agent = Agent(..., model_name="gpt-4.1") # 无 tools execution_agent = Agent(..., model_name="gpt-4.1") # 无 tools ← 本该下单的 Agent! quant_agent = Agent(..., model_name="gpt-4.1") # 无 tools
观察:
execution_agent(执行 Agent)本应是下单的专家,但它没有 tools 参数——既没绑 execute_trade,也没绑 get_order/get_holdings。它只会用 LLM 写一段订单结构的文字(订单类型/数量/入场价/止损/止盈),不会真的下单。get_tools() 收录了 ultra_tools 三件套,但(第 6 章已指出)没有任何 Agent 调用 get_tools()。EXECUTION_PROMPT 让它「生成订单」,不是「调用工具执行」。结论:AutoHedge 的 Solana 实盘链路只能由外部脚本显式调用(像上面的最小脚本),不会在「Director → 专家」的多 Agent 流程里自动发生。README 宣称的「Full autonomous trading on Solana」在主流程里并未实现——链路存在,但没接到 Agent 大脑上。
⚠️ 现实澄清:这正是第 1 章的判断在链路层面的印证:工具存在、签名正确,但「手」(工具)和「脑」(Agent)之间没有神经连上。要让它自主交易,得给 execution_agent 绑
tools=[get_order, execute_trade, get_holdings],并在 prompt 里明确「当你决定交易时,先 get_holdings、再 get_order、最后 execute_trade」——但这些都没做。
假设我们补上 Agent 绑定,让 execution_agent 能调 execute_trade——下一个致命问题是:整条链没有任何前置风控。代码里:
amount="99999999999999" 也会照签。execute_trade 本身只检查「transaction 和 request_id 非空」「私钥有效」——和金额、对手、路由完全无关。这是个干净的签名机器,但不带任何判断。
生产级风控应在这条链上加的闸门:
get_order 之后、execute_trade 之前: ├─ 金额 ≤ 配置上限? ├─ mint 在白名单? ├─ routePlan 的 DEX 都可信? ├─ outAmount 滑点 ≤ 阈值? ├─ 钱包余量足够(别把全部 SOL 兑光,留 gas)? ├─ 频率未超限? └─ (大额)人工二次确认?
任何一条不过,拒绝 execute_trade。本项目这些都没有——这是它不能直接实盘的根本原因之一(第 10 章详讲)。
整条链的错误处理较粗:
| 工具 | 失败行为 |
|---|---|
get_holdings |
raise httpx.HTTPError(网络/HTTP 错) |
get_order |
raise ValueError(参数错)或 raise httpx.HTTPError(网络) |
execute_trade |
raise ValueError(参数/格式错)或 raise httpx.HTTPError(广播失败) |
注意没有任何自动重试——网络抖动一次就抛异常。对于查持仓/取报价,失败重试是安全的;对于 execute_trade,重试要小心(可能重复下单),应基于 requestId 幂等判断。本项目两者都没做重试,留给调用方自行处理。
链上交易失败(如滑点保护触发)时,Jupiter 的 /execute 返回 status: "Failed" 而非 HTTP 错误——所以 execute_trade 不会 raise,会返回带 Failed 的 JSON。调用方必须自己检查 status 字段,不能光看有没有异常。本项目示例脚本没有这个检查。
给一个带基本核对和风控雏形的最小脚本,供学习:
import json, os from autohedge.tools.ultra_tools import get_holdings, get_order, execute_trade MY_ADDR = os.getenv("MY_WALLET_ADDR") SOL = "So11111111111111111111111111111111111111112" USDC = "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v" AMOUNT_LAMPORTS = "10000000" # 0.01 SOL MAX_SLIPPAGE_BPS = 100 # 1% 滑点上限(自行核对用) # ① 查持仓 holdings = json.loads(get_holdings(MY_ADDR)) if int(holdings.get("amount", 0)) < int(AMOUNT_LAMPORTS) + 5000000: # 留 0.005 SOL 当 gas raise SystemExit("SOL 不足或没留 gas") # ② 取报价 order = json.loads(get_order(SOL, USDC, AMOUNT_LAMPORTS)) expected_out = int(order.get("outAmount", 0)) print("预计得 USDC 最小单位:", expected_out) # ③ [风控闸门示例] 这里应加白名单/滑点/金额上限检查,本项目没有 # ④ 签名广播 result = json.loads(execute_trade(order["transaction"], order["requestId"])) if result.get("status") != "Success": raise SystemExit(f"链上失败: {result}") print("成交,signature:", result["signature"])
这个脚本至少加了「余额/gas 检查」和「成交 status 核对」两个本项目缺失的环节,但仍远不够生产级(没白名单、没滑点核对、没人工确认)。
GET /ultra/v1/holdings/{address},返回 SOL 余额(lamports)+ 代币账户 map;只要地址不要私钥,任何人查任何地址(链上公开)。至此第 8 章结束——你彻底理解了 AutoHedge 唯一真实动钱的链路:账户模型、Jupiter API、签名流程、整链串接。下一章我们看 experimental/ 目录下三段实验性代码:做市策略、BTC 地址监控、外部 Agent 封装。