- 从各种来源收集用户意图。
- 实时向已连接的求解器广播这些意图。
- 跟踪所有交易的链上状态。
我们推荐使用订单服务器,因为它内置了安全检查,但_对于链上订单_,它完全是可选的。不过,并非所有意图都会上链——例如 lintent.org 就不会在链上发出意图。而任何你自己从链上读取的意图,都必须经过补全并进行非常彻底的验证。
订单订阅
有两种方式可以收集订单:一种是会向你推送订单的 WebSocket 订阅,另一种是你自行轮询的 GET 端点。我们推荐使用 WebSocket 订阅。这两种方式都可以在 lintent.org 演示实现中找到。 如果您过于频繁地使用 GET 端点,您可能会被限流或暂时超时。您可以在 https://order-dev.li.fi/docs 上找到关于 api 接口的 swagger 文档。您可能会通过 WebSocket 接口多次收到同一个订单。每当从一个真实来源摄取意图时,都可能发生这种情况,而后续的副本可能携带额外的上下文。请使用
orderId 来识别一个意图,并在后续副本携带额外上下文时保留它。订单服务器事件
WebSocket 主机地址为wss://order.li.fi(生产环境)或 wss://order-dev.li.fi(开发环境)。消息是形如 { "event": string, "data": ... } 的 JSON 帧:
user:vm-order-submit—— 有新订单可用。返回的数据格式与提交时类似:完整订单加上元数据(订单 id、状态、时间戳、报价 id)。intent:transaction-observed—— 当订单变为Delivered、Finalised或Refunded时的生命周期更新。- 心跳 —— 服务器每 5 秒发送一次
{"event": "ping"};请在 10 秒内回复{"event": "pong"},否则连接会被关闭。这用于断开行为异常的客户端,并用于调试目的。您可以在此处找到示例实现。
订单验证
LI.FI Intents 允许高度可定制的订单,因此您必须添加自己的验证,以确认您支持转发给您的每一笔订单。 下文中,“白名单”是指由您信任并验证过的。当某个代币_必须_被列入白名单时,意味着您已经审核过该代币。如果某个验证层被列入白名单,意味着您已经审核过该验证层,以此类推。白名单并不意味着由某个中心化实体授权。 以下清单旨在涵盖求解器必须实现的每一项验证。它可能看起来很长,但您很可能已经实现了其中的大部分检查。通用验证
fillDeadline:确保您有足够的时间填充订单:- 在目标链上填充的时间,包括潜在的源链最终性。
expires:确保您有足够的时间填充、转发和领取订单:- 在目标链上填充的时间,包括潜在的源链最终性。
- 将消息验证证明发送到输入链的时间。
- 验证送达后领取订单的时间。
- 验证层:确保您支持向验证层提交证明(如果不是自动的,还需转发)。此外,
inputOracle和output.oracle必须属于同一个验证层。 - 确保您已将每个
input代币列入白名单。如果某个输入代币是恶意的,订单可能无法领取。此外,对于像 USDC 这样可被列入黑名单的代币,请确保您不在黑名单上。- 如果订单的输入或输出中包含 0 数量的某个您未列入白名单的代币,该订单可能无法填充。在填充包含不熟悉代币的订单时请务必小心。
- 对于每个输出:
output.chainId已列入白名单。output.oracle和inputOracle就originChainId和output.chainId而言配置正确。链上配置是不可变的,因此这对每个链对只需做一次。output.settler已列入白名单。output.context可解码,且订单类型受支持并与output.settler兼容。output.token已列入白名单。此外,对于像 USDC 这样可被列入黑名单的代币,请确保您和接收方都不在黑名单上。- 如果订单的输入或输出中包含 0 数量的某个您未列入白名单的代币,该订单可能无法填充。在填充包含不熟悉代币的订单时请务必小心。
- 您有足够的代币用于
output.amount。 - 如果输出包含
calldata,请确保您能够原子性地执行它以及其他输出。对于不同链上的输出,如果存在calldata,您可能必须将接收方列入白名单。- 在 OP 链上,CrossL2Inbox 需要在整个调用树中被列入黑名单。如果其他链上存在类似的合约,它们也需要被列入黑名单。
call.length和context.length都不超过 65,535 字节长。- 根据订单类型验证上下文。具体针对 Bitcoin,请确保编码的乘数是相对于 Bitcoin 值的。
- 如果订单有多个输出,请确保您能够填充所有输出,且第一个输出设置为您的求解器标识符。如果所有输出都在同一条链上,作为一种保护措施,单次
fillOrderOutputs调用即可全部填充。 - 如果 InputSettler 有任何费用,请检查是否有即将发生的费用变更。
在 EVM 和 Tron 上,输入代币以
uint256 编码;前 12 字节可能存放一个 lock tag,地址是最后 20 字节。而 Solana 订单携带的是单个 MandateInput { token: Pubkey, amount: u64 }。在 Solana 的信息流消息中,顶层地址以 base58 渲染,而输出的 oracle、settler、token 和 recipient 值仍然是 32 字节字段。Escrow 验证
- 确保没有任何输入代币是转账时收费(fee on transfer)的。
- 确保每个
inputs代币的高 12 字节中没有设置任何位。 - 确保订单已经被打开,可以通过
event Open或function orderStatus,或者您在填充订单之前通过function openFor打开订单。
Solana 验证
Solana 订单仅支持 Escrow,因此下方的 Compact 检查不适用。除了通用验证之外,还需检查:- 身份字段。 从已发布的程序 id 和种子重新推导订单中的每一个 32 字节身份字段——
outputs[].settler、outputs[].oracle、inputOracle(或者将已发布的地址钉住作为标准值),并与身份矩阵进行核对。只要有任何不匹配,就拒绝该订单。 u64边界。 超过u64::MAX的MandateOutput.amount和荷兰式拍卖slope值会使某个输出在 Solana 上无法被填充(见拍卖)。- 回调。 默认的 Solana 输出结算器要求
callback_data为空,否则会拒绝填充。 - 接收方账户。 对于 SPL 输出,
recipient_token_account.owner必须等于recipient;如果接收方还没有代币账户,你可能需要支付 ATA 的租金。 - 订单大小。 一个没有 calldata、使用标准 context 的单输出订单,始终可以安全地完结。超出这个范围的任何情况,都需要针对完结交易的限制进行彻底验证——精确的界限见订单大小。
Compact 验证
- 确保每个
input代币使用相同的allocatorId。如果lockId不同,则应检查每个lockId的锁过期时间;如果它们都相等,则只需检查一次过期时间。 - 确保资源锁的潜在
reset period延伸到expiry之后,并且没有活跃的提款。 - 确保
allocatorID已列入白名单。分配器可以阻止领取的处理(通过撤回签名或重用 nonce)。- allocatorID 是
inputs的lock tag(前 12 字节)的一部分。 - 可选地,确保用户有足够的代币。不过这应该已经由分配器验证过了。
- allocatorID 是
- 确保用户提供了 ECDSA 签名或者他们注册了列入白名单的 emissary或者您在填充领取之前在链上注册了该领取。
- 注意:如果采取了这些操作中的任何一个,则可以假定签名是可信的。
- emissary 签名无法在链上注册。必须信任 emissary 不会撤回签名。
- 验证
allocatorData。您可能需要进行链上调用。 - 验证分配器
nonce之前未被任何用户使用过。订单 nonce 不是用户 nonce。
签名验证
StandardOrder 是您用来对接输入链上所有函数的对象。此外,一旦填充了签名,它也让您可以核实一笔订单是否有效。
StandardOrder 结构体将被签名并作为见证存储在相应的锁/领取结构中。对于 TheCompact,这是:
链上订单广播
当意图在链上注册时,可以通过输入结算合约上的IntentRegistered 事件进行广播:
在收集无需许可的订单时,请确保它们经过正确验证和共同签名。订单服务器有助于验证,但在链上,任何人都可能发出欺诈性消息。

