兌換端點¶
請求兌換報價¶
身份驗證: 無
請求主體¶
{
"from_token": "0x965d4b4546716e416e950bc30467d128455d2d0e",
"to_token": "0xef64d15ed6c371545eb6dcd6c026c17dfb6c440f",
"from_amount": "1000000",
"owner_address": "0x...",
"recipient": "0x...",
"expiration": 1713254400,
"gas_mode": "receive_less"
}
| 欄位 | 類型 | 必填 | 說明 |
|---|---|---|---|
from_token | address | 是 | ERC-20 輸入代幣地址 |
to_token | address | 是 | ERC-20 輸出代幣地址 |
from_amount | string | 是 | 以正 uint256 字串表示的原始輸入金額 |
owner_address | address | 是 | 負責簽署並支付兌換的地址 |
recipient | address | 是 | 最終接收輸出代幣的地址。可以與 owner_address 不同——設定為任何地址即可將輸出代幣傳送至第三方錢包。該值會被鎖定在已簽署的 Intent 中,合約會強制要求最後一段腿恰好將輸出付給此地址。 |
expiration | integer | 是 | 作為 Intent 截止時間使用的 Unix 時間戳,必須滿足 now < expiration <= now + 365 天 - 300 秒 |
gas_mode | string | 否 | receive_less 或 pay_more;預設值為 receive_less |
from_token 與 to_token 不能相同。
如果 from_amount 低於 GET /tokens 給出的代幣最低成交門檻,介面會回傳 HTTP 400,並在結構化 detail.code 中給出 AMOUNT_BELOW_MIN,同時回傳必須滿足的原始值與十進位顯示值。
回應¶
{
"uuid": "6d0ad60d-c5d5-4b71-b0ca-9e8d2ae1bca4",
"route_params": {
"taker": "0x...",
"inputToken": "0x965d4b4546716e416e950bc30467d128455d2d0e",
"outputToken": "0xef64d15ed6c371545eb6dcd6c026c17dfb6c440f",
"maxInputAmount": "1000000",
"minOutputAmount": "923450",
"recipient": "0x...",
"initialDepositAmount": "1000000",
"uuid": "6431994229952211760403847975151123456789012345678901234567",
"deadline": 1713254400
},
"fee_breakdown": {
"gas_cost_usd": "0.12",
"gas_cost_from_token": "0.120000"
},
"expires_at": 1713250830,
"permit": {
"permit_supported": true,
"permit_required": true,
"token": "0x965d4b4546716e416e950bc30467d128455d2d0e",
"spender": "0x...",
"owner": "0x...",
"value_raw": "1000000",
"current_allowance_raw": "1000000",
"nonce": 4,
"suggested_deadline": 1713254400,
"domain": {
"name": "USD Coin",
"version": "2",
"chainId": 11155111,
"verifyingContract": "0x965d4b4546716e416e950bc30467d128455d2d0e"
},
"eip712": {
"domain": {
"name": "USD Coin", "version": "2",
"chainId": 11155111,
"verifyingContract": "0x965d4b4546716e416e950bc30467d128455d2d0e"
},
"primaryType": "Permit",
"types": {
"Permit": [
{ "name": "owner", "type": "address" },
{ "name": "spender", "type": "address" },
{ "name": "value", "type": "uint256" },
{ "name": "nonce", "type": "uint256" },
{ "name": "deadline", "type": "uint256" }
]
},
"message": {
"owner": "0x...",
"spender": "0x...",
"value": "1000000",
"nonce": 4,
"deadline": 1713254400
}
}
}
}
回應說明¶
uuid是傳給POST /swap的報價記錄 ID。route_params.uuid是綁定到簽署 Intent 的組合 uint256。expires_at生命週期很短;目前實作中報價大約只保存 30 秒。fee_breakdown僅涵蓋 gas 成本;底層定價輸入並不屬於回應契約的一部分。initialDepositAmount > 0時permit非空。若permit_supported=true,簽署回傳的eip712消息。nonce 讀取鏈上值,報價不會預留一組未來 nonce;提交時重新檢查鏈上 nonce,並原子認領該簽名 nonce。nonce 已改變或已被認領時可能需要重新報價。純權益報價(initialDepositAmount=0)的permit=null。permit_supported = false表示輸入代幣不支援 EIP-2612 — 改為POST /approve流程(該分支不會帶eip712欄位)。
Gas 模式¶
| 模式 | 行為 |
|---|---|
receive_less | 保持輸入金額不變,由 gas 減少輸出 |
pay_more | 保持輸出目標不變,由輸入預算吸收 gas |
Note
Intent 簽名無效或缺少必填 permit 欄位會在認領報價之前失敗。並行或重複的有效提交按下方身份規則處理,不會統一回傳 410。
Note
某些報價會故意回傳 route_params.minOutputAmount = "0",代表在該請求規模下沒有可執行路徑。這類報價僅供參考,POST /swap 會拒絕它們。
Note
不支持 permit 且 allowance 不足時,使用交易輔助接口向報價指定的 spender 授權,本地簽名後通過 POST /tx/send 廣播。等待確認並獲取新報價後再簽名/提交 Swap。已有足夠 allowance 時無需再次 approve。
批次請求兌換報價¶
鑑權: 無
在一次往返請求中取得最多 50 筆兌換報價。適合造市商或需要同時為多個交易對定價的交易者 —— 把 N 次串列 /swap/quote 呼叫合併為單一 HTTP 請求,伺服端以受限並發量處理。
請求主體¶
{
"quotes": [
{
"from_token": "0x965d4b4546716e416e950bc30467d128455d2d0e",
"to_token": "0xef64d15ed6c371545eb6dcd6c026c17dfb6c440f",
"from_amount": "1000000",
"owner_address": "0x...",
"recipient": "0x...",
"expiration": 1713254400,
"gas_mode": "receive_less"
}
]
}
| 欄位 | 型別 | 必填 | 說明 |
|---|---|---|---|
quotes | array | 是 | 1–50 筆報價請求,每筆與 POST /swap/quote 請求主體格式一致 |
每筆報價的欄位驗證與 POST /swap/quote 完全一致。任一筆未通過 Pydantic 驗證(無效位址、自交易、錯誤的 gas_mode 等)時,整體請求回傳 422 且不會進入業務處理。執行階段錯誤(預言機、訂單核算、逾時)以單筆 error 包裝回傳 —— 整體請求仍回傳 200。
回應¶
{
"items": [
{
"ok": true,
"quote": {
"uuid": "6d0ad60d-c5d5-4b71-b0ca-9e8d2ae1bca4",
"route_params": { "...": "..." },
"fee_breakdown": { "gas_cost_usd": "0.12", "gas_cost_from_token": "0.120000" },
"expires_at": 1713250830,
"permit": null
},
"error": null
},
{
"ok": false,
"quote": null,
"error": {
"rejectionCategory": "no_liquidity",
"message": "No liquidity"
}
}
]
}
items[i] 與 quotes[i] 一一對應 —— 順序相同,長度相同。每筆要麼是成功報價 (ok: true),要麼是帶型別的錯誤包裝 (ok: false)。
錯誤包裝¶
rejectionCategory 是字串。上游型別化錯誤碼原樣轉發,包括報價專用錯誤。與執行共用的 AMOUNT_BELOW_MIN、NO_LIQUIDITY 等可共用處理邏輯,同時必須相容未知錯誤碼。
rejectionCategory | 觸發條件 |
|---|---|
| 型別化錯誤碼 | 原樣轉發;報價專用錯誤碼不一定屬於執行介面的錯誤集合。 |
SWAP_GAS_PRICE_UNAVAILABLE | 缺少用於 gas 成本換算的有效美元價格。批量外層仍為 HTTP 200,該項 ok:false,message 可為空;稍後重試。 |
no_liquidity | 定價回傳舊版字串形式的無流動性(小寫變體,等價於類型化的 NO_LIQUIDITY) |
invalid_quote | 定價引擎其他未帶類型化錯誤碼的 4xx |
upstream_unavailable | 無型別碼的上游 5xx / 帳務服務不可達;型別碼優先。 |
quote_timeout | 單筆報價計算超過伺服端預算(預設 10 秒) |
internal_error | 未處理的伺服端錯誤 |
單條報價或 Exact Output 報價在 gas 定價不可用時可回傳通用 HTTP 503,不能要求清洗後的回應一定帶 SWAP_GAS_PRICE_UNAVAILABLE。批量和簽名還須符合整體請求體上限。
運維須知¶
- 上限: 每次請求 50 筆。超出會在請求驗證階段回傳
422。剩餘交易對請分批提交。 - 並發: 伺服端將單筆計算的並發量限制在固定值(預設 20),即使一次提交 50 筆,也最多有 20 筆同時與訂單核算互動。
- 單筆逾時: 每筆報價的計算時間獨立受限,單一慢交易對不會拖累整批次。慢筆回傳
quote_timeout,其他仍可成功。 - 報價快照: 各交易對獨立計算,不存在跨交易對的統一快照。若需跨對原子性定價,本端點不適用。
- 無批次冪等: 每筆成功結果都會產生新的報價
uuid。重試相同批次會產出新的 UUID。 - 每筆回傳的報價 UUID 相互獨立 —— 與
POST /swap/quote一樣,各自附上簽名 Intent 提交到POST /swap。
執行兌換¶
身份驗證: 請求主體中攜帶的 EIP-712 Intent 簽名
請求主體¶
{
"uuid": "6d0ad60d-c5d5-4b71-b0ca-9e8d2ae1bca4",
"signature": "0x...",
"permit_signature": "0x...",
"permit_deadline": 1713254400
}
| 欄位 | 類型 | 必填 | 說明 |
|---|---|---|---|
uuid | string | 是 | 由 POST /swap/quote 回傳的報價 ID |
signature | hex string | 是 | 對 quote.route_params 的簽名 |
permit_signature | hex string | permit_required && permit_supported 時 | 簽署回傳的 permit.eip712,支援 64/65 字節簽名。不支援 permit 時完成 approve 並省略此欄位。 |
permit_deadline | integer | 與 permit_signature 配套 | permit 簽名中的截止時間;使用報價建議值,兩個欄位一起提交。 |
回應¶
{
"success": true,
"trade_id": "85c92fcb-21b9-43ba-bb36-01d7b21eaa8d",
"status": "pending",
"fee_breakdown": {
"gas_cost_usd": "0.12",
"gas_cost_from_token": "0.120000"
}
}
trade_id 是後續可透過 GET /orders/{order_id} 查詢,或以 GET /orders?type=swap 過濾的訂單 ID。
fee_breakdown 為可選欄位,在部分失敗或降級回應路徑中可能省略。
要簽署的 Intent 類型¶
const types = {
Intent: [
{ name: 'taker', type: 'address' },
{ name: 'inputToken', type: 'address' },
{ name: 'outputToken', type: 'address' },
{ name: 'maxInputAmount', type: 'uint256' },
{ name: 'minOutputAmount', type: 'uint256' },
{ name: 'recipient', type: 'address' },
{ name: 'initialDepositAmount', type: 'uint256' },
{ name: 'uuid', type: 'uint256' },
{ name: 'deadline', type: 'uint48' }
]
};
請嚴格依照回傳值原樣簽署 quote.route_params,不要在客戶端重新生成或標準化欄位。
錯誤情況¶
| 狀態碼 | 原因 |
|---|---|
400 | 請求無效、簽名不匹配、缺少必填 permit 欄位,或報價不可執行(minOutputAmount = 0) |
409 | 准入前 permit nonce 過時可能回傳字串 detail,且尚未消費報價。其他報價衝突可能使用類型化錯誤。請檢查回應與當前訂單狀態,不能假定所有 409 的原因或報價消費狀態相同。 |
410 | 報價不存在、未認領報價過期或結果已不再保留;已保留的相同成功請求仍可回傳緩存結果 |
429 | 觸發錢包級速率限制 |
503 | 服務暫時不可用;請依 Retry-After 重試 |
報價展示欄位¶
單條和批量成功報價還可能包含 route_metadata.leg_count 和 quote_breakdown:
quote_breakdown 欄位 | 說明 |
|---|---|
gas_mode、input_token、output_token | gas 選擇與代幣身份 |
input_amount_before_gas、input_amount_after_gas | gas 前後輸入數量,輸入代幣 raw 單位 |
output_amount_before_gas、output_amount_after_gas | gas 前後輸出數量,輸出代幣 raw 單位 |
gas_cost_input_token、gas_cost_output_token | 以對應代幣 raw 單位表示的 gas 成本 |
gas_cost_usd | 美元成本,十進制字串 |
這些是展示估計值,不是確認結算金額。請原樣簽名 route_params。批量報價按請求中的報價條數計費;強制公共限流開啓時,quote 組預算爲每秒 50 條。
指定必須收到的輸出金額時,請使用獨立的 Exact Output API。在現有輸入金額報價上設置 pay_more,不等於使用該接口契約。
Swap 錯誤包裝¶
類型化提交失敗使用 { "detail": { "detail": "Swap execution failed", "error_code": "NO_LIQUIDITY" } }。錯誤碼包括 ALLOWANCE_INSUFFICIENT、INTENT_DEADLINE_EXPIRED、SLIPPAGE_EXCEEDED、NO_LIQUIDITY、QUOTE_STALE、AMOUNT_BELOW_MIN、STP_BLOCKED、COMPLIANCE_BLOCKED、TRANSIENT_SETTLEMENT_FAILURE。簽名缺失、請求模型無效等預檢失敗也可能使用字串 detail。批量報價的 rejectionCategory 是單項報價錯誤,不證明兌換已提交。
客戶端應按結構化 error_code 判斷錯誤類別,不要按展示文本 detail 分支。各碼含義參見訂單錯誤說明;兌換重試必須遵循下方的請求身份與結果核對規則。
提交身份與重試¶
首次提交前持久化 uuid、signature、permit_signature、permit_deadline。這些值共同標識請求;重試時保留原簽名編碼與 permit 值。
| 已有報價狀態 | 相同請求 | 不同請求 |
|---|---|---|
| 成功結果仍被保留 | HTTP 200 回傳原結果,不執行第二次 | 409 衝突 |
| 正在提交 | 已知結果可恢復時回傳結果,否則回傳 409 進行中 | 409 衝突 |
| 已拒絕 | 保留期內回傳 409 與拒絕碼,合規拒絕爲 403 | 不會成爲新報價 |
| 不存在或認領前過期 | 410 | 410 |
超時或 503 後,重試完全相同的簽名請求以恢復已保存結果。回應給出 trade_id 後,使用該 ID 和所有者 API Key 查詢 GET /orders/{order_id}。“正在提交”的 409 不表示終態失敗。只有確認前次已拒絕或不能執行後,才重新報價。保留期有限,清理後不保證無限期重放。Exact Output 有獨立的狀態接口,以 quote_uuid 查詢。
區分三種 Swap 標識¶
| 標識 | 用途 |
|---|---|
報價回應的 uuid | 作爲 POST /swap 請求的 uuid,標識已保存報價與重試請求 |
route_params.uuid | 在 Intent 中籤名的複合 uint256 |
執行回應的 trade_id | 已持久化訂單 ID,用於 GET /orders/{order_id} 與 /fills/{order_id} |
持久化訂單 ID 會另外生成。普通 Swap 沒有 Exact Output 那樣按報價查詢狀態的獨立接口;首次回應丟失時,應通過相同的 POST /swap 請求恢復,不能將報價 ID 填入訂單查詢 URL。