概述
本文档标准规定了在任意 ICS 应用协议之上处理手续费支付所需的数据包数据结构、状态机处理逻辑以及编码细节。它需要对确认消息作出一些修改,但任何应用都可以采用,而无需强制其他应用使用该实现。动机
关于面向中继者的通用激励机制,已经有过大量讨论。最初曾提出一个简单方案,试图扩展 ICS-20 以激励中继,在目标链上提供激励。然而,该方案过于针对 ICS-20,无法适用于其他协议。随后,这一思路被扩展为更通用的手续费支付设计,可供任意 ICS 应用协议采用。 总体而言,如果没有明确的方法来激励中继者,跨链互操作的愿景就无法扩展。我们的目标是定义一个清晰、易于被任何应用采用的接口,同时也不排斥那些不使用代币的链。期望属性
- 激励及时传递数据包(调用
recvPacket) - 激励为这些数据包中继确认消息(调用
acknowledgePacket) - 当超时时间已过且数据包尚未送达时,激励为这些数据包中继超时消息(例如接收费过低时)(调用
timeoutPacket) - 不产生额外的 IBC 数据包
- 即使目标链不支持同质化代币概念,单向流程也能工作
- 每条实现该机制的链都可自行选择是否启用。例如,链 A 上支持手续费的 ICS27 可以连接到链 B 上不支持手续费的 ICS27。
- 为每条实现该扩展的链提供标准化接口
- 在同一框架内支持自定义的手续费处理逻辑
- 中继者地址不应可被伪造
- 支持无许可或许可式中继
定义
forward relayer:为给定数据包提交 recvPacket 消息的中继者
reverse relayer:为给定数据包提交 acknowledgePacket 消息的中继者
timeout relayer:为给定数据包提交 timeoutPacket 或 timeoutOnClose 消息的中继者
receive fee:为给定数据包提交 recvPacket 消息所支付的手续费
ack fee:为给定数据包提交 acknowledgePacket 消息所支付的手续费
timeout fee:为给定数据包提交 timeoutPacket 或 timeoutOnClose 消息所支付的手续费
source address:中继者在发送该数据包的链上选择的收款地址
destination address:在接收该数据包的链上的中继者地址
技术规范
总体设计
为了避免产生数量级与应用数据包数相同的额外手续费数据包,并提供可选启用的方式,我们只在源链上存储所有手续费支付信息。源链是发送方能够提供代币来激励该数据包的唯一位置。手续费分配方式可以是具体实现相关的,因此无需写入 IBC 规范中(本文档只需要给出高层要求)。 我们要求向应用模块暴露中继者地址,以便所有与数据包相关的消息都能让模块激励数据包中继者。因此,acknowledgePacket、timeoutPacket 和 timeoutOnClose 消息将携带中继者地址,并能够将托管的代币发送到该地址。
不过,我们还需要一种可靠的方法,将在目标链上提交 recvPacket 的中继者地址传回源链。实际上,我们需要的是该中继者对应的 source address 用于支付,而不是对数据包签名的 destination address。
手续费支付机制将作为 IBC Middleware(见 ICS-30)实现,以便为应用开发者和区块链提供最大的灵活性。
基于此,流程如下:
- 中继者在目标链的手续费中间件中注册其 destination address 到 source address 的映射。
- 用户或模块在
source链上提交发送数据包,同时向手续费中间件模块提交一条消息,附带一些代币以及如何分配这些代币的手续费信息。所有手续费代币都由手续费模块托管。 - RelayerA 在
destination链上提交RecvPacket。 - 目标链的手续费中间件会根据该中继者的 destination address 取回对应的 source address(该映射已预先注册),并将其写入确认消息中。
- RelayerB 提交
AcknowledgePacket,消息发送者中会提供 reverse relayer 在源链上的地址,同时确认消息中还嵌入了 forward relayer 的 source address。 - 源链的手续费中间件可以将步骤 (1) 中托管的代币分配给 forward 和 reverse 两个中继者,并将剩余代币退还给原始手续费支付方。
- 用户或模块在
source链上提交发送数据包,同时附带一些代币以及如何分配这些代币的手续费信息 - 中继者提交
OnTimeout,其中提供其在源链上的地址 - 源链应用可以将步骤 (1) 中托管的代币分配给该中继者,并可将剩余代币退还给原始手续费支付方
手续费细节
以 Cosmos SDK 中的一个实现为例,我们考虑 3 种可定义的潜在手续费支付。每一种都可以用不同的代币支付。设想 IrisNet 与 Cosmos Hub 之间建立了一条连接。为了激励从 IrisNet 发往 Cosmos Hub 的数据包,他们可以定义:- ReceiveFee: 0.003 channel-7/ATOM 凭证(已通过 ICS20 存在于 IrisNet 上的 ATOM)
- AckFee: 0.001 IRIS
- TimeoutFee: 0.002 IRIS
recvPacket,并由反向中继者提交 ackPacket,那么前向中继者将获得 0.003 channel-7/ATOM,反向中继者将获得 0.001 IRIS,而 0.002 IRIS 会退还给原始手续费支付方。如果数据包发生超时,则超时中继者将获得 0.002 IRIS,而 0.003 channel-7/ATOM 会退还给原始手续费支付方。
向用户收取手续费并将其支付给相应中继者的逻辑,由独立的手续费模块封装,不同实现之间可以有所差异。然而,所有手续费模块都必须实现统一接口,以便 ICS-4 处理程序能够将手续费正确支付给对应的中继者,并使中继者自身能够方便地确定其在中继某个数据包时可预期获得的手续费。
数据结构
写入目标链的激励型确认消息包含:- 底层应用确认消息的原始字节,
- 前向中继者的 source address,
- 以及一个布尔值,用于指示底层应用的接收操作是否成功。
存储路径
异步确认路径的中继者地址
前向中继者地址存储在一个以端口标识符、通道标识符和序列号组合唯一确定的存储路径前缀下。该信息可以存储在私有存储中。费用中间件合约
尽管不同费用模块的实现细节可能有所不同,但所有费用模块必须确保做到以下几点:- 必须允许中继器注册其对手方收款地址(即源地址)。
- 必须托管所有未完成数据包可能支付的最大费用(或者必须具备铸造所需代币数量的能力)。
- 必须将某个数据包的接收费支付给
PayFee回调中指定的正向中继器(如果未指定,则必须将正向费用退还给原始费用支付者)。 - 必须将某个数据包的确认费支付给
PayFee回调中指定的反向中继器。 - 必须将某个数据包的超时费支付给
PayTimeoutFee回调中指定的超时中继器。 - 如适用,必须将托管中的剩余费用退还给原始费用支付者。
Fee 指定特定表示。每条链都可以选择自己的表示方式,中继器有责任正确解释 Fee。
默认表示将具有以下结构:
IBC 模块包装器
费用中间件将实现其自己的 ICS-26 回调,这些回调会包装特定于应用的模块回调,以及由底层应用调用的 ICS-4 处理器函数。该费用中间件将确保对手方模块支持激励机制,并实现所有费用相关逻辑。随后,它会将请求传递给嵌入的应用模块,以进行进一步的回调处理。 通过这种方式,可以将自定义费用处理逻辑挂接到 IBC 数据包流转逻辑中,而无需将代码放入 ICS-4 处理器或应用代码中。这一点很有价值,因为 ICS-4 处理器应只关注 IBC 核心部分的正确性(传输、认证和排序),而应用处理器不应处理所有受激励应用通用的费用逻辑。事实上,一个给定的应用模块应当能够接入任何费用模块,而无需对应用本身做进一步修改。费用协议协商
费用中间件将把自己的版本与应用版本一起包含在内,以此与对手方模块协商费用协议版本。通道版本将是一个 JSON 结构体的字符串,其中包含费用中间件版本和应用版本。如果应用栈由多个中间件包装一个基础应用组成,那么应用版本本身也可以是一个 JSON 编码字符串,并可能进一步包含更多中间件和应用版本。 通道版本:握手回调
数据包回调
调用 ICS-4 的嵌入式应用
请注意,如果嵌入式应用使用异步 ack,那么应用中的WriteAcknowledgement 调用必须调用费用中间件的 WriteAcknowledgement,而不是直接调用 ICS-4 处理器的 WriteAcknowledgement 函数。
用户与费用中间件的交互
用户发送数据包 用户可以在提交数据包时指定一笔费用,以激励中继。具体做法是在应用特定的“发送数据包”消息(例如 ICS-20MsgTransfer)之外,原子性地一并提交一条费用支付消息。费用中间件会为与该托管操作原子创建的数据包托管这笔费用。费用支付消息本身不在本文档中规定,因为它在不同实现之间可能差异很大。在某些中间件中,如果费用来自一个利他资金池,甚至可能根本不存在费用支付消息。
由于费用中间件不需要修改出站数据包,费用支付消息既可以放在发送数据包消息之前,也可以放在之后。不过,为了与其他中间件消息保持一致,建议费用中间件要求其消息放在发送数据包消息之前,并为给定通道上的下一个序列号托管费用。这样,当这些消息被原子提交时,通道上的下一个序列号对应的就是用户发送的数据包消息,用户也就为新创建的数据包托管了费用。
如果用户希望在数据包已经创建之后再支付费用,费用中间件 SHOULD 提供一条消息,允许用户按指定的序列号、通道标识符和端口标识符为某个数据包支付费用。这样用户就可以唯一标识一个已经创建的数据包,从而使费用中间件能够在事后为该数据包托管费用。
中继器发送 RecvPacket
在中继器开始在某个通道上进行中继之前,它应当使用标准化消息注册其对手方消息:
relayer 的所有者发送。接收链必须为给定通道存储如下映射:relayer -> counterpartyPayee。随后,目标费用中间件的 onRecvPacket 就可以查询 recvPacket 消息发送者的对手方收款地址,从而获取正向中继器的源地址。这个源地址会被嵌入到确认中。
如果中继器没有注册其对手方收款地址(或者注册了无效地址),确认仍然会被接收并处理,但正向费用将退还给原始费用支付者。
向后兼容性
如果要在费用模块内部直接维护与非激励链的向后兼容性,则需要顶层费用模块协商不包含费用版本的版本,并同时与激励模块和非激励模块通信。随着嵌套应用层级增加,这种模式会带来不必要的复杂性。 因此,费用模块只会连接到对手方费用模块。这样可以简化费用模块逻辑,也不需要它去模拟底层嵌套应用。 为了让激励链在某个特定应用(例如 ICS-20)上与非激励链保持向后兼容,激励链应同时托管一个顶层 ICS-20 模块和一个嵌套了 ICS-20 应用的顶层费用模块,并且两者都应绑定到各自唯一的端口。论证
该提案满足所需属性。数据包流的所有部分(接收、确认、超时)都可以被正确激励和奖励。协议不会预先指定中继器,因此激励机制既可以是无许可的,也可以是许可制的。资金的托管和分发完全在源链上处理,因此费用协议不需要额外的 IBC 数据包,也不需要使用 ICS-20。费用协议只假设源链上存在同质化代币。通过为同一个基础应用创建应用栈(一个带费用中间件,一个不带),我们可以获得向后兼容性。正确性
费用模块负责正确地托管资金,并将资金分发给指定的中继器。ack 和 timeout 中继器很容易获取,因为它们分别就是确认消息和超时消息的发送者。正向中继器负责在发送recvPacket 消息之前注册其源地址,这样目标费用中间件就可以把该地址嵌入确认中。随后,源链上的费用中间件会使用确认中的地址,在源链上向正向中继器支付费用。
对于正向中继器地址,源链会采用“尽力而为”的处理方式。由于该地址不会被对手方直接验证,而只是被当作一个字符串回传到确认中,因此已注册的正向中继器源地址可能不是一个有效的源链地址。在这种情况下,无效地址会被丢弃,接收费会被退还,确认处理会继续进行。中继器有责任正确地向对手方链注册自己的源地址。
如果对手方链本身错误地发送了正向中继器地址,这会导致中继器无法因中继数据包而在源链上获得费用。受激励驱动的中继器会停止为该链中继,直到确认逻辑被修复,但通道本身仍然可用。
我们不能在源地址无效时返回错误,因为这会永久阻止源链处理一个本来已经在对手方链上被正确接收、处理并确认的数据包的确认。IBC 协议要求,不正确或恶意的中继器最多只能影响用户数据包的活性。如果在这种情况下阻止成功确认,数据包流将永久处于未完成状态,这对于某些 IBC 应用(例如 ICS-20)可能带来非常严重的后果。
因此,正向中继器是否能获得奖励,取决于它在发送 receive_packet 消息时是否提供了正确的 payOnSender 地址。即使费用支付失败,数据包流仍会继续成功处理。
当确认中正确嵌入了正向中继器,而反向与超时中继器又可直接从消息中获得时,费用中间件就能够准确地托管并分发费用给相关中继器。
可选附录
向前兼容性
不适用。示例实现
- Go 语言的 ICS 29 实现可见于 ibc-go repository。
历史
2021 年 6 月 8 日 - 从直接在 ICS-4 中实现回调切换为中间件方案。 2021 年 6 月 1 日 - 完成草案撰写 2022 年 7 月 6 日 - 根据实现中的最新变更更新版权
本文所有内容均依据 Apache 2.0 许可。Synopsis
This standard document specifies packet data structure, state machine handling logic, and encoding details for handling fee payments on top of any ICS application protocol. It requires some changes to the acknowledgement, but can be adopted by any application, without forcing other applications to use this implementation.Motivation
There has been much discussion on a general incentivization mechanism for relayers. A simple proposal was created to extend ICS-20 to incentivize relaying on the destination chain. However, it was very specific to ICS-20 and would not work for other protocols. This was then extended to a more general fee payment design that could be adopted by any ICS application protocol. In general, the Interchain dream will never scale unless there is a clear way to incentivize relayers. We seek to define a clear interface that can be easily adopted by any application, but not preclude chains that don’t use tokens.Desired Properties
- Incentivize timely delivery of the packet (
recvPacketcalled) - Incentivize relaying acks for these packets (
acknowledgePacketcalled) - Incentivize relaying timeouts for these packets when the timeout has expired before packet is delivered (for example as receive fee was too low) (
timeoutPacketcalled) - Produces no extra IBC packets
- One direction works, even when destination chain does not support concept of fungible tokens
- Opt-in for each chain implementing this. e.g. ICS27 with fee support on chain A could connect to ICS27 without fee support on chain B.
- Standardized interface for each chain implementing this extension
- Support custom fee-handling logic within the same framework
- Relayer addresses should not be forgeable
- Enable permissionless or permissioned relaying
Definitions
forward relayer: The relayer that submits the recvPacket message for a given packet
reverse relayer: The relayer that submits the acknowledgePacket message for a given packet
timeout relayer: The relayer that submits the timeoutPacket or timeoutOnClose message for a given packet
receive fee: The fee paid for submitting the recvPacket message for a given packet
ack fee: The fee paid for submitting the acknowledgePacket message for a given packet
timeout fee: The fee paid for submitting the timeoutPacket or timeoutOnClose message for a given packet
source address: The payee address selected by a relayer on the chain that sent the packet
destination address: The address of a relayer on the chain that receives the packet
Technical Specification
General Design
In order to avoid extra fee packets on the order of the number of application packets, as well as provide an opt-in approach, we store all fee payment info only on the source chain. The source chain is the one location where the sender can provide tokens to incentivize the packet. The fee distribution may be implementation specific and thus does not need to be in the IBC spec (just high-level requirements are needed in this doc). We require that the relayer address is exposed to application modules for all packet-related messages, so the modules are able to incentivize the packet relayer.acknowledgePacket, timeoutPacket,
and timeoutOnClose messages will therefore have the relayer address and be capable of sending escrowed tokens to such address.
However, we need a way to reliably get the address of the relayer that submitted recvPacket on the destination chain to
the source chain. In fact, we need a source address for this relayer to pay out to, not the destination address that signed
the packet.
The fee payment mechanism will be implemented as IBC Middleware (see ICS-30) in order to provide maximum flexibility for application developers and blockchains.
Given this, the flow would be:
- Relayer registers their destination address to source address mapping on the destination chain’s fee middleware.
- User/module submits a send packet on the
sourcechain, along with a message to the fee middleware module with some tokens and fee information on how to distribute them. The fee tokens are all escrowed by the fee module. - RelayerA submits
RecvPacketon thedestinationchain. - Destination fee middleware will retrieve the source address for the given relayer’s destination address (this mapping is already registered) and include it in the acknowledgement.
- RelayerB submits
AcknowledgePacketwhich provides the reverse relayer address on the source chain in the message sender, along with the source address of the forward relayer embedded in the acknowledgement. - Source fee middleware can distribute the tokens escrowed in (1) to both the forward and the reverse relayers and refund remainder tokens to original fee payer(s).
- User/module submits a send packet on the
sourcechain, along with some tokens and fee information on how to distribute them - Relayer submits
OnTimeoutwhich provides its address on the source chain - Source application can distribute the tokens escrowed in (1) to this relayer, and potentially return remainder tokens to the original fee payer(s).
Fee details
For an example implementation in the Cosmos SDK, we consider 3 potential fee payments, which may be defined. Each one may be paid out in a different token. Imagine a connection between IrisNet and the Cosmos Hub. To incentivize a packet from IrisNet to the Cosmos Hub, they may define:- ReceiveFee: 0.003 channel-7/ATOM vouchers (ATOMs already on IrisNet via ICS20)
- AckFee: 0.001 IRIS
- TimeoutFee: 0.002 IRIS
recvPacket and a reverse relayer submits the ackPacket, the forward relayer is rewarded 0.003 channel-7/ATOM and the reverse relayer is rewarded 0.001 IRIS while 0.002 IRIS is refunded to the original fee payer. In the case where the packet times out, the timeout relayer receives 0.002 IRIS and 0.003 channel-7/ATOM is refunded to the original fee payer.
The logic involved in collecting fees from users and then paying it out to the relevant relayers is encapsulated by a separate fee module and may vary between implementations. However, all fee modules must implement a uniform interface such that the ICS-4 handlers can correctly pay out fees to the right relayers, and so that relayers themselves can easily determine the fees they can expect for relaying a packet.
Data Structures
The incentivized acknowledgment written on the destination chain includes:- raw bytes of the acknowledgement from the underlying application,
- the source address of the forward relayer,
- and a boolean indicative of receive operation success on the underlying application.
Store Paths
Relayer Address for Async Ack Path
The forward relayer addresses are stored under a store path prefix unique to a combination of port identifier, channel identifier and sequence. This may be stored in the private store.Fee Middleware Contract
While the details may vary between fee modules, all fee modules must ensure they does the following:- It must allow relayers to register their counterparty payee address (i.e. source address).
- It must have in escrow the maximum fees that all outstanding packets may pay out (or it must have ability to mint required amount of tokens)
- It must pay the receive fee for a packet to the forward relayer specified in
PayFeecallback (if unspecified, it must refund forward fee to original fee payer(s)) - It must pay the ack fee for a packet to the reverse relayer specified in
PayFeecallback - It must pay the timeout fee for a packet to the timeout relayer specified in
PayTimeoutFeecallback - It must refund any remainder fees in escrow to the original fee payer(s) if applicable
Fee. Each chain may choose its own representation, it is incumbent on relayers to interpret the Fee correctly.
A default representation will have the following structure:
IBC Module Wrapper
The fee middleware will implement its own ICS-26 callbacks that wrap the application-specific module callbacks as well as the ICS-4 handler functions called by the underlying application. This fee middleware will ensure that the counterparty module supports incentivization and will implement all fee-specific logic. It will then pass on the request to the embedded application module for further callback processing. In this way, custom fee-handling logic can be hooked up to the IBC packet flow logic without placing the code in the ICS-4 handlers or the application code. This is valuable since the ICS-4 handlers should only be concerned with correctness of core IBC (transport, authentication, and ordering), and the application handlers should not be handling fee logic that is universal amongst all other incentivized applications. In fact, a given application module should be able to be hooked up to any fee module with no further changes to the application itself.Fee Protocol Negotiation
The fee middleware will negotiate its fee protocol version with the counterparty module by including its own version next to the application version. The channel version will be a string of a JSON struct containing the fee middleware version and the application version. The application version may as well be a JSON-encoded string, possibly including further middleware and app versions, if the application stack consists of multiple milddlewares wrapping a base application. Channel Version:Handshake Callbacks
Packet Callbacks
Embedded applications calling into ICS-4
Note that if the embedded application uses asynchronous acks then, theWriteAcknowledgement call in the application must call the fee middleware’s WriteAcknowledgement rather than calling the ICS-4 handler’s WriteAcknowledgement function directly.
User Interaction with Fee Middleware
User sending Packets A user may specify a fee to incentivize the relaying during packet submission, by submitting a fee payment message atomically with the application-specific “send packet” message (e.g. ICS-20MsgTransfer). The fee middleware will escrow the fee for the packet that is created atomically with the escrow. The fee payment message itself is not specified in this document as it may vary greatly across implementations. In some middleware, there may be no fee payment message at all if the fees are being paid out from an altruistic pool.
Since the fee middleware does not need to modify the outgoing packet, the fee payment message may be placed before or after the send packet message. However in order to maintain consistency with other middleware messages, it is recommended that fee middleware require their messages to be placed before the send packet message and escrow fees for the next sequence on the given channel. This way when the messages are atomically committed, the next sequence on the channel is the send packet message sent by the user, and the user escrows their fee for the created packet.
In case a user wants to pay fees on a packet after it has already been created, the fee middleware SHOULD provide a message that allows users to pay fees on a packet with the specified sequence, channel and port identifiers. This allows the user to uniquely identify a packet that has already been created, so that the fee middleware can escrow fees for that packet after the fact.
Relayers sending RecvPacket
Before a relayer starts relaying on a channel, they should register their counterparty message using the standardized message:
relayer. The receiving chain must store the mapping from: relayer -> counterpartyPayee for the given channel. Then, onRecvPacket of the destination fee middleware can query for the counterparty payee address of the recvPacket message sender in order to get the source address of the forward relayer. This source address is what will get embedded in the acknowledgement.
If the relayer does not register their counterparty payee address (or registers an invalid address), then the acknowledgment will still be received and processed but the forward fee will be refunded to the original fee payer(s).
Backwards Compatibility
Maintaining backwards compatibility with an unincentivized chain directly in the fee module, would require the top-level fee module to negotiate versions that do not contain a fee version and communicate with both incentivized and unincentivized modules. This pattern causes unnecessary complexity as the layers of nested applications increase. Instead, the fee module will only connect to a counterparty fee module. This simplifies the fee module logic, and doesn’t require it to mimic the underlying nested application(s). In order for an incentivized chain to maintain backwards compatibility with an unincentivized chain for a given application (e.g. ICS-20), the incentivized chain should host both a top-level ICS-20 module and a top-level fee module that nests an ICS-20 application each of which should bind to unique ports.Reasoning
This proposal satisfies the desired properties. All parts of the packet flow (receive/acknowledge/timeout) can be properly incentivized and rewarded. The protocol does not specify the relayer beforehand, thus the incentivization can be permissionless or permissioned. The escrowing and distribution of funds is completely handled on source chain, thus there is no need for additional IBC packets or the use of ICS-20 in the fee protocol. The fee protocol only assumes existence of fungible tokens on the source chain. By creating application stacks for the same base application (one with fee middleware, one without), we can get backwards compatibility.Correctness
The fee module is responsible for correctly escrowing and distributing funds to the provided relayers. The ack and timeout relayers are trivially retrievable since they are the senders of the acknowledgment and timeout message. The forward relayer is responsible for registering their source address before sendingrecvPacket messages, so that the destination fee middleware can embed this address in the acknowledgement. The fee middleware on source will then use the address in acknowledgement to pay the forward relayer on the source chain.
The source chain will use a “best efforts” approach with regard to the forward relayer address. Since it is not verified directly by the counterparty and is instead just treated as a string to be passed back in the acknowledgement, the registered forward relayer source address may not be a valid source chain address. In this case, the invalid address is discarded, the receive fee is refunded, and the acknowledgement processing continues. It is incumbent on relayers to register their source addresses to the counterparty chain correctly.
In the event that the counterparty chain itself incorrectly sends the forward relayer address, this will cause relayers to not collect fees on source chain for relaying packets. The incentivize-driven relayers will stop relaying for the chain until the acknowledgement logic is fixed, however the channel remains functional.
We cannot return an error on an invalid source address as this would permanently prevent the source chain from processing the acknowledgment of a packet that was otherwise correctly received, processed and acknowledged on the counterparty chain. The IBC protocol requires that incorrect or malicious relayers may at best affect the liveness of a user’s packets. Preventing successful acknowledgement in this case would leave the packet flow at a permanently incomplete state, which may be very consequential for certain IBC applications like ICS-20.
Thus, the forward relayer reward is contingent on it providing the correct payOnSender address when it sends the receive_packet message. The packet flow will continue processing successfully even if the fee payment is unsuccessful.
With the forward relayer correctly embedded in the acknowledgement, and the reverse and timeout relayers available directly in the message; the fee middleware will accurately escrow and distribute fee payments to the relevant relayers.
Optional addenda
Forwards Compatibility
Not applicable.Example Implementations
- Implementation of ICS 29 in Go can be found in ibc-go repository.