跳轉至

兌換端點

請求兌換報價

POST /swap/quote

身份驗證:

請求主體

{
  "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_lesspay_more;預設值為 receive_less

from_tokento_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_supportedfalse,請改用 GET /config 中的 sor_address 呼叫 POST /approve,本地簽名後透過 POST /tx/send 廣播,再提交 POST /swap

批次請求兌換報價

POST /swap/quote/batch

鑑權:

在一次往返請求中取得最多 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_MINNO_LIQUIDITYSLIPPAGE_EXCEEDEDINTENT_DEADLINE_EXPIREDQUOTE_STALE)時,會原樣轉發,讓客戶端的同一段類型化錯誤分派邏輯可同時服務兩個端點。批次執行階段的關注以自身類別呈現:

rejectionCategory 觸發條件
公開類型化錯誤碼(如 AMOUNT_BELOW_MINNO_LIQUIDITYSLIPPAGE_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

執行兌換

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 重試