兌換端點¶
請求兌換報價¶
身份驗證: 無
請求主體¶
{
"from_token": "0xDcAEcdd8Db64f4316A11917Ad0162DEBD935285b",
"to_token": "0xd3BdB2CE9cD98566EFc2e2977448c40578371779",
"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": "0xDcAEcdd8Db64f4316A11917Ad0162DEBD935285b",
"outputToken": "0xd3BdB2CE9cD98566EFc2e2977448c40578371779",
"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": "0xDcAEcdd8Db64f4316A11917Ad0162DEBD935285b",
"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": "0xDcAEcdd8Db64f4316A11917Ad0162DEBD935285b"
},
"eip712": {
"domain": {
"name": "USD Coin", "version": "2",
"chainId": 11155111,
"verifyingContract": "0xDcAEcdd8Db64f4316A11917Ad0162DEBD935285b"
},
"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 成本;底層定價輸入並不屬於回應契約的一部分。permit在所有需要從錢包扣款的兌換報價中(即route_params.initialDepositAmount > 0)都會非空。它包含一個eip712子物件,結構與signTypedData(domain, types, message)完全一致,錢包收到報價後可直接簽名,無需自行組裝結構。permit.nonce是錢包下一次 EIP-2612 簽名的預期 nonce — 它會把同一錢包已被伺服器接受但尚未上鏈的兌換計入,以保證連續兩筆兌換在中繼器中按序排隊,而非爭搶同一個鏈上 nonce。僅initialDepositAmount = 0的純權益兌換會讓permit保持null。permit_supported = false表示輸入代幣不支援 EIP-2612 — 改為POST /approve流程(該分支不會帶eip712欄位)。
Gas 模式¶
| 模式 | 行為 |
|---|---|
receive_less | 保持輸入金額不變,由 gas 減少輸出 |
pay_more | 保持輸出目標不變,由輸入預算吸收 gas |
Note
只有完整簽名通過驗證的提交才會消耗報價。簽名不匹配、缺少必填 permit 欄位或 minOutputAmount = 0 都會直接回傳 400 而不消耗報價,避免良性錯誤強制重新報價。如果有並發的有效請求先消耗成功,您會收到 410。
Note
某些報價會故意回傳 route_params.minOutputAmount = "0",代表在該請求規模下沒有可執行路徑。這類報價僅供參考,POST /swap 會拒絕它們。
Note
如果 permit.permit_supported 為 false,請改用 GET /config 中的 sor_address 呼叫 POST /approve,本地簽名後透過 POST /tx/send 廣播,再提交 POST /swap。
批次請求兌換報價¶
鑑權: 無
在一次往返請求中取得最多 50 筆兌換報價。適合造市商或需要同時為多個交易對定價的交易者 —— 把 N 次串列 /swap/quote 呼叫合併為單一 HTTP 請求,伺服端以受限並發量處理。
請求主體¶
{
"quotes": [
{
"from_token": "0xDcAEcdd8Db64f4316A11917Ad0162DEBD935285b",
"to_token": "0xd3BdB2CE9cD98566EFc2e2977448c40578371779",
"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 為字串。當底層錯誤帶有類型化錯誤碼(與 POST /swap 共用的 code 欄位:如 AMOUNT_BELOW_MIN、NO_LIQUIDITY、SLIPPAGE_EXCEEDED、INTENT_DEADLINE_EXPIRED、QUOTE_STALE)時,會原樣轉發,讓客戶端的同一段類型化錯誤分派邏輯可同時服務兩個端點。批次執行階段的關注以自身類別呈現:
rejectionCategory | 觸發條件 |
|---|---|
公開類型化錯誤碼(如 AMOUNT_BELOW_MIN、NO_LIQUIDITY、SLIPPAGE_EXCEEDED) | 由定價引擎透傳,與 POST /swap 的類型化 error_code 同詞彙表。詳見上方 Swap 錯誤包裝 中的對應動作表 |
no_liquidity | 定價回傳舊版字串形式的無流動性(小寫變體,等價於類型化的 NO_LIQUIDITY) |
invalid_quote | 定價引擎其他未帶類型化錯誤碼的 4xx |
upstream_unavailable | 訂單核算不可達 / 5xx |
quote_timeout | 單筆報價計算超過伺服端預算(預設 10 秒) |
internal_error | 未處理的伺服端錯誤 |
運維須知¶
- 上限: 每次請求 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 != null 時必填 | 把 quote.permit.eip712.{domain, types, message} 直接傳給 wallet.signTypedData(...) 取得的 EIP-2612 簽名;支援 65 位元組與緊湊 64 位元組兩種十六進位編碼。僅在 quote.permit == null(純權益兌換)時可省略 |
permit_deadline | integer | permit != null 時必填 | 簽名時使用的 deadline(即 quote.permit.eip712.message.deadline)。需與 permit_signature 成對出現 |
回應¶
{
"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 stale; re-quote required" — 報價產生與提交之間,鏈上 nonce 或伺服器端佇列發生變動(例如同錢包另一筆兌換先一步上鏈)。報價未被消耗;客戶端應靜默重新報價並重新提交。 |
410 | 報價已過期、不存在,或已被消耗 |
429 | 觸發錢包級速率限制 |
503 | 服務暫時不可用;請依 Retry-After 重試 |