跳转至

虚拟流动性

网络与兼容性

资源 值
API 基础 URL https://api.testnet.sera.cx/api/v1
链 Sepolia(chainId 11155111)
Sera 合约 0x83475A1bD98a8DC2DCd507A747e4DC85da241D6e
Vault 合约 0x3c7945840bAE0d7e7f3824Ebccef1962629250F0
SOR 合约 0x83c1368110B640A729f3810De5FBe94b99aa5668

签名原语。 所有交易型变更都是针对 Sera 域的 EIP-712 类型化数据签名。走 permit 路径的充值使用 ERC-2612 Permit 扩展 — USDC、EURC、EURT 以及多数现代稳定币支持,但并非所有 ERC-20 都支持;签名前请调用 GET /permit/metadata 检查。API key 管理使用 EIP-712 ManageApiKey 载荷。

已验证客户端。 Python eth_account >= 0.10 + requests;TypeScript ethers v6(signer.signTypedData)。已验证支持 EIP-712 类型化数据的浏览器钱包:MetaMask、Rabby、Frame、Coinbase Wallet、Trust、Rainbow。Safe 多签通过 EIP-1271 支持(消息在链上验证,而非通过 ecrecover)。

地址大小写。 读取类端点(/balances、/orders、/fills)将 owner_address 视为大小写敏感 — 请传入小写形式。EIP-712 签名载荷接受 EIP-55 校验和地址。

虚拟流动性(Virtual Liquidity, VL)允许您在多个不同交易对上同时挂出限价单,并让这些订单共享同一笔预算。系统冻结的是单个订单中的最大成本,而不是所有订单成本之和,因此更适合做市和多市场报价。

下面的交易对标签和计算采用假设的基础/报价方向。实际下单必须使用 GET /markets 返回的地址和方向,并相应调整 side、数量与价格;后面的提交示例按返回方向构造请求。

为什么选择虚拟流动性?

设想一位做市商希望同时在 EURC/USDC、GBPC/USDC 和 XSGD/USDC 上挂买单。如果没有 VL,每个订单都需要独立锁定抵押品,3 个订单就意味着 3 倍资金占用。使用 VL 时,一笔 USDC 预算即可为这三个订单提供支持。当其中一个订单成交后,其余订单会自动调整,以确保总花费始终在预算内。

方式 所需资金 风险
3 个独立订单 所有订单成本之和 每个订单独立锁定
1 个 VL 批次 单个订单中的最大成本 共享预算,自动调整

它如何运作

一个 VL 批次包含:

  • 一个 VL 批次包含 2 到 50 个兄弟订单(运行时通过 GET /config 的 limits.vl_batch 获取当前上限)
  • 所有兄弟订单必须属于不同交易对
  • 整个批次必须共享同一个 spent token(实际被花掉的代币)
  • 当其中一个订单成交后,其余订单会自动缩量或取消,确保总花费不超过共享预算
flowchart TD
  A[提交 VL 批次] --> B[冻结共享预算]
  B --> C1[兄弟订单 1: EURC/USDC 买单]
  B --> C2[兄弟订单 2: GBPC/USDC 买单]
  B --> C3[兄弟订单 3: XSGD/USDC 买单]
  C1 -->|成交| D[扣减预算]
  D --> E{还有剩余预算吗?}
  E -->|有| F[调整兄弟订单 2 和 3]
  E -->|没有| G[取消兄弟订单 2 和 3]

共享预算与抵押品

共享抵押品依赖撮合引擎在同一预算下核算消耗,并随着成交调整兄弟订单。不同市场本身不会阻止多条腿成交。账务预留单腿最大成本,并执行共享剩余预算约束。

花费代币(fromToken)规则

VL 批次中的所有兄弟订单必须解析为相同的 fromToken——即实际被花掉的 ERC-20 代币。fromToken 由 side 与交易对定义共同决定,其中 from_address 是基础代币、to_address 是报价代币:

Side fromToken(花费) toToken(收到) 每次成交的成本
Bid (买入) to_address(报价代币) from_address(基础代币) quantity x fill_price
Ask (卖出) from_address(基础代币) to_address(报价代币) quantity

不会自动纠正

系统不会自动翻转 side 或交换交易对方向来让各兄弟订单的 fromToken 一致。fromToken 会根据提交的 side、from_address 与 to_address 推导;如果推导出的支出代币不一致,整个批次会直接返回 422。

示例 1:同方向批次(全部 bid,花费 USDC)

  • Bid from_address: EURC, to_address: USDC @ 1.08 — fromToken = USDC
  • Bid from_address: GBPC, to_address: USDC @ 1.27 — fromToken = USDC
  • Bid from_address: XSGD, to_address: USDC @ 0.75 — fromToken = USDC

冻结金额:1,500 USDC(最大单个订单成本),而非 3,215 USDC。

示例 2:混合方向批次(均花费 USDC)

当 USDC 在一个交易对中是报价币、在另一个交易对中是基础币时,只要推导出的 fromToken 都是 USDC,仍可组成批次:

  • Bid from_address: MYRC, to_address: USDC @ 4.50 — fromToken = USDC ✅
  • Ask from_address: USDC, to_address: GBPC @ 0.79 — fromToken = USDC ✅

此批次有效,因为两个订单都花费 USDC。

示例 3:无效的混合方向批次(fromToken 不匹配)

  • Ask from_address: MYRC, to_address: USDC @ 4.50 — fromToken = MYRC ❌
  • Ask from_address: GBPC, to_address: USDC @ 1.27 — fromToken = GBPC ❌

尽管这两个市场都以 USDC 计价,但一个兄弟订单实际支出 MYRC,另一个实际支出 GBPC。该批次将被拒绝并返回 422。

下单

先加载做市商指南的配置及 VL 帮助函数。该函数逐腿预览,使用规范消息和共享组 UUID 签名,在发送前保存完整请求并返回完整响应。选择批量大小前读取 GET /config → limits.vl_batch。

下面按两个市场各自返回的方向,让两条腿都支出 USDC。数量单位为 base,价格单位为 quote/base;1000 和 1 仅演示请求流程,应替换为你在这些单位下的目标数量/价格。上方预算示例采用假设的市场方向,不代表实际部署注册表。

response = requests.get(f"{API}/markets", timeout=10)
response.raise_for_status()
markets = response.json()["markets"]
selected = [m for m in markets if "USDC" in (m["base_symbol"], m["quote_symbol"])][:2]
if len(selected) < 2:
    raise RuntimeError("Two distinct active markets spending USDC are required")
expiry = int(time.time()) + 3600
legs = [{
    "side": "ask" if m["base_symbol"] == "USDC" else "bid",
    "base": {"address": m["base_address"], "decimals": m["base_decimals"]},
    "quote": {"address": m["quote_address"], "decimals": m["quote_decimals"]},
    "amount": Decimal("1000"), "price": Decimal("1"), "expiration": expiry,
} for m in selected]
batch = place_vl_batch(legs)
print(batch)  # Retain outcomes and budget status, not only order_ids.
const marketsResponse = await fetch(`${API}/markets`);
if (!marketsResponse.ok) throw new Error(`markets: ${marketsResponse.status}`);
const selected = (await marketsResponse.json()).markets.filter((m: any) =>
  [m.base_symbol, m.quote_symbol].includes("USDC")).slice(0, 2);
if (selected.length < 2) throw new Error("Two distinct active markets spending USDC are required");
const expiry = Math.floor(Date.now() / 1000) + 3600;
const legs = selected.map((m: any) => ({
  side: (m.base_symbol === "USDC" ? "ask" : "bid") as "ask" | "bid",
  base: { address: m.base_address, decimals: m.base_decimals },
  quote: { address: m.quote_address, decimals: m.quote_decimals },
  amount: "1000", price: "1", expiration: expiry,
}));
const batch = await placeVlBatch(legs);
console.log(batch); // Retain outcomes and budget status, not only order_ids.

下单结果

字段 解释
order_ids 已提交身份,仅凭存在不能确认挂单数量或结算
amendments 查看 quantity_semantics:placement_total_quantity 是下单总量,remaining_quantity 是剩余量;不能把所有 actual_amount 当成当前剩余挂单量
cancelled 下单期间被移除的订单及原因
fills 即时撮合证据,remaining 是成交后剩余量;仍需确认链上结算
vl_group status=confirmed 时,max_budget - budget_consumed 是下单时的预算快照,之后的成交仍可改变它

预算状态为 pending、budget_consumed 为 null 或 group 缺失时,不要推断零消耗或已确认挂单量。结果空数组和旧响应缺字段都不能证明按完整签名数量挂单。应查询订单/成交核对,保留已保存请求;核对后如需重试,使用原 body,不为结果未知的批次生成新 ID。

成交与调整流程

当某个兄弟订单成交时,系统自动调整批次中的其余订单:

sequenceDiagram
  participant Sera
  participant Accounting

  Sera->>Sera: 兄弟订单 1 成交(消耗 500 USD)
  Sera->>Sera: 预算:1500 -> 1000 USD
  alt 预算充足
    Sera->>Sera: 调整剩余兄弟订单数量以适配 1000 USD
  else 预算耗尽
    Sera->>Sera: 取消剩余兄弟订单
  end
  Sera->>Accounting: 更新冻结余额

调整示例

起始预算:1,500 USDC。以下使用假设的 base/quote 方向:

  1. EURC/USDC bid 1000 @ 1.08 成交 500,消耗 540 USDC。
  2. 剩余预算为 960 USDC。
  3. GBPC/USDC bid 500 @ 1.27 需要 635 USDC,所以剩余数量保持 500,不会增加为 755。
  4. XSGD/USDC bid 2000 @ 0.75 缩减为 1280,前提是该数量满足市场精度和已签名结算量子。

调整只在需要时减少当前剩余数量。计算使用代币 raw 单位和已签名结算量子,不能一律向下取整为整数个代币。

取消 VL 批次

通过 POST /orders/vl/cancel 取消批次中的所有兄弟订单:

cancel_res = requests.post(
    "https://api.testnet.sera.cx/api/v1/orders/vl/cancel",
    json={
        "owner_address": wallet_address,
        "vl_batch_id":   batch["order_ids"][0],   # 批次的主订单 ID
        "signature":     cancel_vl_sig,           # EIP-712 CancelVLBatch 签名
    },
    timeout=10,
)
const cancelRes = await fetch("https://api.testnet.sera.cx/api/v1/orders/vl/cancel", {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({
    owner_address: walletAddress,
    vl_batch_id:   batch.order_ids[0],   // 批次的主订单 ID
    signature:     cancelVlSig,          // EIP-712 CancelVLBatch 签名
  }),
});

取消行为:

  • 取消单个兄弟订单(通过 POST /orders/cancel)— 预算不会解冻(其余兄弟订单仍在挂单中)
  • 取消整个批次(通过 POST /orders/vl/cancel)— 所有兄弟订单被取消,剩余预算解冻
  • 所有兄弟订单成交或取消 — 一旦没有活跃的兄弟订单,剩余预算自动解冻

验证规则

VL 批次在下单时进行验证,必须满足以下约束:

规则 原因
所有兄弟订单属于不同交易对 防止同一订单簿上的竞态条件
所有兄弟订单共享相同的花费代币 确保单一预算可以支撑所有订单
每个批次 2 到 50 个兄弟订单 通过 GET /config 的 limits.vl_batch 读取当前值
每个兄弟订单是有效的限价单 适用标准订单验证

有效:

  • Bid EURC/USDC + Bid GBPC/USDC + Bid XSGD/USDC(全部 fromToken = USDC)
  • Ask USDC/GBPC + Bid MYRC/USDC(方向混合,但 fromToken 均为 USDC)

无效:

  • Bid EURC/USDC + Bid EURC/USDC(重复交易对)
  • Bid XSGD/USDC + Ask USDC/XSGD(正反向互逆市场)
  • Ask MYRC/USDC + Ask GBPC/USDC(fromToken = MYRC vs GBPC —— 不匹配,返回 422)

后续步骤