跳轉至

兌換端點

請求兌換報價

POST /swap/quote

身份驗證: 無

請求主體

{
  "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。

批次請求兌換報價

POST /swap/quote/batch

鑑權: 無

在一次往返請求中取得最多 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。

執行兌換

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。