虚拟流动性¶
网络与兼容性
| 资源 | 值 |
|---|---|
| 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 方向:
- EURC/USDC bid 1000 @ 1.08 成交 500,消耗 540 USDC。
- 剩余预算为 960 USDC。
- GBPC/USDC bid 500 @ 1.27 需要 635 USDC,所以剩余数量保持 500,不会增加为 755。
- XSGD/USDC bid 2000 @ 0.75 缩减为 1280,前提是该数量满足市场精度和已签名结算量子。
调整只在需要时减少当前剩余数量。计算使用代币 raw 单位和已签名结算量子,不能一律向下取整为整数个代币。
取消 VL 批次¶
通过 POST /orders/vl/cancel 取消批次中的所有兄弟订单:
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)