# Create a Custom IBC Middleware
When developing a custom IBC application, one of the first things to do is to implement the IBCModule
interface, as seen previously.
The interface can be found here (opens new window).
Likewise, when developing IBC middleware, the Middleware
interface should be implemented. It can be found in the same file as the IBCModule
and appears as follows:
Middleware must implement IBCModule
to wrap communication from core IBC to the underlying application, and must implement ICS4Wrapper
to wrap communication from the underlying application to core IBC.
You know IBCModule
from the IBC custom app section. ICS4Wrapper
is an interface like so:
When developing custom middleware, you can implement these interfaces for a new IBCMiddleware
that has access to its keeper and an underlying application. In the ibc_middleware.go
file:
For simplification, in the snippet above, keeper.Keeper
is supposed to implement the ICS4Wrapper
interface. See here (opens new window) for an example implementation of this interface for the ICS29 Fee Middleware module.
Below you will take a closer look at how to implement the handshake callbacks and the packet callbacks, to satisfy the IBCModule
interface, and also the SendPacket
, WriteAcknowledgement
and GetAppVersion
methods, to satisfy the ICS4Wrapper
interface.
As a reminder, review once more the diagram representing the information flow between core IBC and the base application with a middleware (stack) applied:
# Channel handshake callbacks
The first type of callbacks from core IBC to the application are the channel handshake callbacks. When a middleware (stack) is applied, every piece of middleware will have access to the underlying application (base application or downstream middleware). For the handshake callbacks, function calls happen from core IBC through the middleware and then to the base application. Each middleware will call the underlying application's callback but it can execute custom application logic before doing so.
Check out the code snippets below to see this in action, or use the links to see how it is implemented for fee middleware (ICS-29).
Middleware may choose not to call the underlying application's callback at all. However, these should generally be limited to error cases.
# Middleware version negotiation
In the case where the IBC middleware expects to speak to a compatible IBC middleware on the counterparty chain, they must use the channel handshake to negotiate the middleware version without interfering in the version negotiation of the underlying application.
Middleware accomplishes this by formatting the version in a JSON-encoded string containing the 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 middlewares wrapping a base application. The format of the version is specified in ICS-30 as follows:
The <middleware_version_key>
key in the JSON struct should be replaced by the actual name of the key for the corresponding middleware (e.g. fee_version
).
During the handshake callbacks, the middleware can unmarshal the version string and retrieve the middleware and application versions. It can do its negotiation logic on <middleware_version_value>
, and pass the <application_version_value>
to the underlying application.
Middleware that does not need to negotiate with a counterparty middleware on the remote stack will not implement the version unmarshalling and negotiation, and may simply perform its own custom logic on the callbacks without relying on the counterparty behaving similarly.
# Capabilities
The middleware should simply pass the capability in the callback arguments along to the underlying application so that it may be claimed by the base application. The base application will then pass the capability up the stack in order to authenticate an outgoing packet/acknowledgement.
# Code snippets
Note that the code snippets below contain pseudo code, like doCustomLogic(args)
. Every code snippet is accompanied by a reference to the respective function in the fee middleware IBC application (opens new window) from the official ibc-go
repository.
See here (opens new window) for an example implementation of this callback for the ICS29 Fee Middleware module.
See here (opens new window) for an example implementation of this callback for the ICS29 Fee Middleware module.
See here (opens new window) for an example implementation of this callback for the ICS29 Fee Middleware module.
See here (opens new window) for an example implementation of this callback for the ICS29 Fee Middleware module.
Similarly, for the channel closing:
See here (opens new window) for an example implementation of this callback for the ICS29 Fee Middleware module.
See here (opens new window) for an example implementation of this callback for the ICS29 Fee Middleware module.
# Packet callbacks
The middleware's packet callbacks wrap the application's packet callbacks, just like the middleware's handshake callbacks wrapped the application's handshake callbacks.
The packet callbacks are where the middleware performs most of its custom logic. The middleware may read the packet flow data and perform some additional packet handling, or it may modify the incoming data before it reaches the underlying application. This enables a wide degree of use cases, as a simple base application like token transfer can be transformed for a variety of use cases by combining it with custom middleware.
# Code snippets
Note that the code snippets below contain pseudo code, like doCustomLogic(args)
. Every code snippet is accompanied by a reference to the respective function in the fee middleware IBC application (opens new window) from the official ibc-go
repository.
See here (opens new window) for an example implementation of this callback for the ICS29 Fee Middleware module.
See here (opens new window) for an example implementation of this callback for the ICS29 Fee Middleware module.
See here (opens new window) for an example implementation of this callback for the ICS29 Fee Middleware module.
# ICS-4 wrappers
Middleware must also implement the ICS4Wrapper
interface so that any communication from the application to the channelKeeper
goes through the middleware first. Similar to the packet callbacks, the middleware may modify outgoing acknowledgements and packets in any way it wishes.
# Capabilities
Earlier you saw that the handshake callbacks passed the capability in the callback arguments along to the underlying application so that it may be claimed by the base application. In the ICS4Wrapper
methods, the base application will then pass the capability up the stack in order to authenticate an outgoing packet or acknowledgment.
If the middleware wishes to send a packet or acknowledgement without the involvement of the underlying application, it should be given access to the same scopedKeeper
as the base application so that it can retrieve the capabilities by itself.
# Code snippets
Note that the code snippets below contain pseudo code, like doCustomLogic(args)
. Every code snippet is accompanied by a reference to the respective function in the fee middleware IBC application (opens new window) from the official ibc-go
repository.
See here (opens new window) for an example implementation of this function for the ICS29 Fee Middleware module.
See here (opens new window) for an example implementation of this function for the ICS29 Fee Middleware module.
See here (opens new window) for an example implementation of this function for the ICS29 Fee Middleware module.
To summarize, this section has explored:
- How to integrate middleware (or a middleware stack) in
app.go
. - The importance of middleware order in the middleware stack.
- How only the top-layer middleware of any stack should be connected to the IBC router.