订单模型

单向订单模型

与多数中心化交易所的订单模型不同,路印采用的是单向订单模型(Uni-Directional Order Model,简称UDOM)。也就是说,无论买单还是卖单,都统一用一种数据结构表示。我们先通过一个简化过的模型举几个路印限价单的例子(路印目前不支持市价单)。

在LRC-ETH交易对,一个用0.03价格卖出500个LRC的卖单可以这样表示:

{   // LRC-ETH市场:0.03价格卖出500个LRC的卖单
    "tokenS": "LRC",
    "tokenB": "ETH",
    "amountS": 500,
    "amountB": 15 // = 500 * 0.03
}

订单数据项中的的字母S代表Sell,B代表Buy。

用0.03价格买入出500个LRC的买单这样表示:

{   // LRC-ETH市场:0.03价格买入500个LRC的买单
    "tokenS": "ETH",
    "tokenB": "LRC",
    "amountS": 15, // = 500 * 0.03
    "amountB": 500 
}

单向订单模型中不显性表达交易对和价格。

不过上面的模型有个小问题:对完全成交的判断条件没有做说明。或者说,一个订单完全成交,是按照amountS的实际交易额达到了指定的值做标准,还是按照amountB的实际交易额达到了指定的值做标准。因此我们还需引入了另一个参数buy来指明完全成交的判断条件。如果buy==true,就按照amountB的实际交易判断是否完全成交;否则按照amountS的实际交易额判断。因此这上面的卖单和买单就需要这样修改:

{   // LRC-ETH市场:0.03价格卖出500个LRC的卖单
    "tokenS": "LRC",
    "tokenB": "ETH",
    "amountS": 500,
    "amountB": 15 // = 500 * 0.03,
    "buy": false  // 完全成交用amountS实际交易额判断
}
{   // LRC-ETH市场:0.03价格买入500个LRC的买单
    "tokenS": "ETH",
    "tokenB": "LRC",
    "amountS": 15, // = 500 * 0.03
    "amountB": 500,
    "buy": true // 完全成交用amountB实际交易额判断
}

注意:上面的卖单如果完全成交,实际上获得的ETH可能大于15ETH;而上面的买单如果完全成交,实际上支付的ETH可能少于15ETH。这就是buy这个参数对撮合引擎行为影响的结果。

将上面两个订单的buy值反转,会有什么效果呢?答案是:LRC-ETH交易对的卖单就变成了ETH-LRC交易对的买单;而LRC-ETH交易对的买单就变成了ETH-LRC交易对的卖单。也就是说,路印协议的一个交易对,实际上等同于多数中心化交易所的LRC-ETH和ETH-LRC两个交易对,并且可以表达这个两个交易对各自的买卖单,并将其放在一起撮合。

除了优雅和简单之外,路印协议的单向订单模型还使得在零知识证明电路中实现更简单的结算逻辑成为可能。

订单数据

路印实际的订单格式要更加复杂一些。您可以通过下面的JSON来表达一个路印的限价单。具体参数细节详见提交订单

newOrder = {
    "tokenSId": 2,  // LRC
    "tokenBId": 0,  // ETH
    "amountS": "500000000000000000000",
    "amountB": "15000000000000000000",
    "buy": "false",
    "exchangeId": 2,
    "accountId": 1234,
    "allOrNone": "false", // 目前值必须为"false"
    "maxFeeBips": 50,
    "label": 211,
    "validSince": 1582094327,
    "validUntil": 1587278341,
    "orderId": 5,
    "hash": "14504358714580556901944011952143357684927684879578923674101657902115012783290",
    "signatureRx": "15179969700843231746888635151106024191752286977677731880613780154804077177446",
    "signatureRy": "8103765835373541952843207933665617916816772340145691265012430975846006955894",
    "signatureS" : "4462707474665244243174020779004308974607763640730341744048308145656189589982",
    "clientOrderId": "Test01",
    "channelId": "channel1::maker1"
}

接下来我们为您对其中的一些数据项做进一步说明。

通证和数量

与简化模型不同,实际订单中通证不用其名字或ERC20地址表达,而是使用该通证在路印交易所的合约中注册的序号(Token ID)表达。上面的例子中,我们假设LRC和ETH的ID分别是2和0。 实际通证配置信息可以通过交易所支持的通证信息查询。

订单中的通证数量使用通证的最小单元,通过字符串类型表达。以LRC为例,LRC的ERC20合约中decimals为18,因此1.0LRC应该表示为"1000000000000000000"(1后面跟18个0)。每个通证的decimals都是由其智能合约决定;ETH的decimals是18。

请注意:订单中的buyallOrNone的类型是字符串而不是布尔。

交易手续费

maxFeeBips=50代表该订单愿意支付给交易所的最高手续费比例是0.5%(maxFeeBips的单位是0.01%)。路印的交易手续费都是用成交获得的tokenB支付的。假设上面订单某次成交买入了"10000000000000000000"ETH(10ETH),那么实际支付的手续费不会超过0.05ETH"10000000000000000000" * 0.5%)。

实际支付的手续费比例是由路印中继决定的。中继会根据不同的VIP等级,给不同的用户相应的交易手续费折扣。路印协议不允许实际手续费比例大于用户订单中指定的最高手续费比例。

用户下单的时候,必须将maxFeeBips设置为不小于该用户在指定交易对的默认交易手续费比例。该信息可以通过/api/v2/user/feeRates查询获得。如果您信任路印交易所,也可以将这个maxFeeBips设置为协议允许的最大值63

生效和过期时间

validSince代表订单生效时间,validUntil代表订单过期时间,其单位均为秒。

中继服务器收到订单时会验证订单中的这两个时间戳;路印协议的零知识证明电路代码在清算时候也需要判断这两个时间戳。由于zkRollup批处理延迟,以及以太坊上时间与服务器时间可能存在的偏差,我们强烈建议validSince设置为当前时间,且validSincevalidUntil之间的时间窗口不小于一个星期,否则您的订单可能不会被撮合。

您可以通过使用validUntil时间戳来让订单自动过期,避免不必要的主动取消订单操作。

成交量与订单号

路印协议3.1.1为支持的每个通证预留了16384(2142^{14})个槽位来记录卖出该通证的订单的成交量。如果订单ID是N,那么使用的槽位编号就是N % 16384。换言之,如果槽位编号是m,该槽位就可以被用来记录具有下列ID的订单:mm + 16384m + 16384 * 2,... 以此类推。

每个槽位都记录了当前在追踪的订单的ID(初始值就是槽位编号),并且后续不接受订单ID比当前订单ID更小的订单。假设槽位1记录的是ID为327691 + 16384 * 2)的订单的订单状态,当用户下一个订单ID为116385的订单的时候,下单就会失败。当您在一个市场的活跃订单达到16384后,您需要先取消部分订单释放槽位,才可以继续下新的订单。

订单ID的最大值是1048576,即2202^{20}。到达这个ID上限后,对应的通证就无法再下任何卖单。对于普通用户,这不是大问题;但对于程序化交易,您可能需要注册一个新账号继续交易。

路印协议3.5会去除订单ID最大值的限制,但依然保留槽位的设计和数量。

值得注意的是,同一用户在基础通证相同的多个交易对(如LRC-ETH和LRC-USDT)的所有卖单共享上面的16384槽位的。如果您不想在客户端维护交易对间订单ID和槽位的分配,您可以注册多个账号:一个账号参与LRC-ETH市场的交易,另一个账号参与LRC-USDT市场的交易。

我们知道这种设计带来的不便利。不过这是路印协议设计时候做的取舍。希望后续技术的进步可以将这个限制去除。

其它数据项

  • exchangeId:路印交易所在路印协议体系中的交易所序号。后续路印交易所升级智能合约后,这个exchangeId的值会变化。路印交易所beta1对应的exchangeId是2。
  • accountId:用户的账号ID。
  • allOrNone:如果是"true",要求订单要么不成交,要么就要完全成交。目前这个参数还不被撮合引擎支持,因此请先设置为"false"
  • label: 用于在协议层标记订单。该项的值对于交易清算没有任何影响。用户会对这个值做签名,因此该值对于不同实体间的分润根据可信度。
  • clientOrderId: 用户客户端在协议层外标记订单,可以是任意长度小于66的字符串。该项的值对于交易清算没有任何影响。用户不会对该项做签名。
  • channelId:订单渠道号, 用来标示订单从哪个渠道提交。

更多细节请参考提交订单

results matching ""

    No results matching ""