Introducing the Callbacks Middleware: Compose Smart Contracts and Modules with IBC

IBC Protocol
The Interchain Foundation

--

We at Interchain GmbH are excited to announce the release of the callbacks middleware. Now, smart contracts and modules can seamlessly integrate with IBC applications like ICS-20 token transfers or ICS-27 interchain accounts (ICA). For the first time, application developers can compose their smart contracts/modules on any of the 100+ IBC-enabled chains that use an execution environment like Wasm or EVM, to leverage use cases of the form ‘send X, do Y programatically’.

In this blog post, we’ll dive into the concept of the callbacks middleware, discussing its significance and the diverse range of use cases it caters to.

TL;DR

  • The callbacks middleware will be supported in ibc-go v7.3.0 onwards and will have its own go.mod. The Confio team is aiming to add support for this feature within x/wasm during Q4 of this year.
  • An implementation of ADR-008, the callbacks middleware enables IBC apps like transfer and ICA to pass IBC callbacks to smart contracts/modules at every stage of the packet lifecycle - SendPacket, onRecvPacket, onAcknowledgementPacket, and onTimeoutPacket.
  • Smart contracts deployed on execution environments like Wasm, EVM, etc., can retrieve these callbacks to execute further user actions. For example: send tokens, if the transfer was successful then automatically send an ICA message to stake those tokens.
  • Refer to our docs to learn how to integrate the callbacks middleware on your chain.

What problem does the callbacks middleware solve?

In its initial design, IBC was limited to callbacks between core IBC and application modules. Core IBC executed a callback to the application every time a packet was sent or received.

Figure 1: Callback architecture between core IBC and application modules

As shown in Figure 1, when a packet is sent, core IBC executes the callback to the sending module onAcknowledgementPacket or onTimeoutPacket. Conversely, upon receiving a packet, the callback onRecvPacket is called.

Over time, smart contract developers expressed their interest in integrating smart contracts with IBC applications like ICS-20 token transfers or ICS-27 interchain accounts (ICA). The main challenge with doing so was that smart contracts had no means of obtaining information about the outcome of a packet’s lifecycle. This meant that if a contract sent a packet using transfer, there was no functionality within ibc-go to allow callbacks to the contract to confirm whether a packet was successful. The absence of callbacks hampered the execution of follow-up actions, such as sending an ICA packet to swap tokens that were transferred in the previous step.

To address this challenge, we’ve introduced the callbacks middleware that sits between core IBC and the execution environment, facilitating callbacks between these two parts of the stack.

What is the Callbacks Middleware?

An implementation of ADR-008, the callbacks middleware, is an IBC module that allows core IBC to execute callbacks to any execution environment. It enables an underlying application, such as transfer or ICA, to execute callbacks to secondary applications, like the Wasm or EVM execution frameworks.

The callbacks middleware functions as an extension of the core IBC codebase to smart contracts that live on different execution environments like x/wasm or ethermint, allowing smart contracts to receive callbacks that happen during the processing of a regular packet lifecycle.

The callbacks middleware will have its own go.mod file and is supported from ibc-go v7.3.0 onwards. Support for this feature within x/wasm is planned for Q4 this year. Evmos will also be supporting the callbacks middleware for their EVM use cases.

Use Cases and Benefits

The callbacks middleware satisfies all use cases of the form: ‘send X, do Y programmatically’. The main benefit for smart contract developers is the ability to leverage existing IBC apps, to build new and unique apps for use cases of the form ‘transfer + action’, or ‘ICA + action’. This streamlines the end-user experience and can improve user retention for chains.

Below are some example use cases. Note that all of these are executed in a single user flow.

  • Send tokens from chain A to B. If the transfer was successful, then send an ICA packet to stake/LP/swap tokens.
  • Execute arbitrary smart contract logic upon receiving an ICS-20 packet.

As depicted in Figure 2, before this feature was introduced, a user needed to manually confirm the outcome of action 1 in order to decide whether to proceed with action 2, as the latter was dependent on the former’s result (packet success/failure). This introduces needless complexity and friction for end users of a product.

By enabling smart contracts to compose with one another over IBC, the callbacks middleware automates this flow by allowing application modules to pass on the result of a packet lifecycle, and execute actions programmatically based on the result.

Figure 2: User flows with and without using the callbacks middleware

How the Callbacks Middleware Works

Here’s a quick walkthrough of how the middleware functions. In our examples, an IBC actor refers to a smart contract/module/off-chain user that initiates a packet using an underlying app like transfer or ICA. A callback actor is an on-chain smart contract/module registered to receive callbacks from the secondary app (execution environment).

  • An IBC actor sends a transfer packet. If the packet succeeds or fails, the callback actor receives the onAcknowledgementPacket callback.
  • An IBC actor sends a transfer packet. If the packet times out, the callback actor receives the onTimeoutPacket callback.

Note: In certain cases, the IBC actor and the callback actor can be the same entity — meaning the sender of the packet is also the entity receiving the callback and performing a subsequent action.

The callbacks defined by the middleware can be divided into two categories:

  1. Source chain callbacks:
  • SendPacket
  • onAcknowledgementPacket
  • onTimeoutPacket

2. Destination chain callbacks:

  • onRecvPacket

Walkthrough of the Callbacks Middleware interfaces

The callbacks middleware exposes all parts of the packet lifecycle to the secondary app/execution environment. It is up to the different execution environments to define their own APIs with smart contracts deployed on their platform.

In order to achieve this, we’ve added three new interfaces to core IBC:

  1. PacketDataUnmarshaller: To unmarshal the packet data bytes into the appropriate packet data type.
  2. PacketDataProvider: To retrieve the custom data from the packet. In the case of transfer and ICA, the custom packet data is encoded within the memo field.
  3. PacketData: To retrieve the packet sender address from the packet data.

You can see the different callback types that are exposed by the middleware here in the keys.go file, namely send_packet, receive_packet, acknowledgement_packet, and timeout_packet.

src_callback and dest_callback are the keys from the memo field that are used for source and destination callbacks, respectively. address denotes the smart contract address to which you want to make the callback. And gas_limit denotes the user-defined gas limit in order to execute a callback. The gas_limit will be equal to or less than the maxCallbackGas.

Another file within the callbacks middleware is ibc_middleware.go. There you can see the source and destination chain callbacks for all stages of the packet lifecycle. For the purposes of this blog post, we’ll go through OnAcknowledgementPacket, but note that the logic for all the callbacks (SendPacket, onTimeoutPacket, onRecvPacket) is exactly the same.

The OnAcknowledgementPacket method calls into the underlying application for the result of the packet. If the underlying application returns an error, then the callback is not executed. Otherwise, the callback data is retrieved using the GetSourceCallbackData function. The execution of the contract callback is performed by processCallback.

The callbacks middleware requires the secondary application (execution environment such as x/wasm) to implement the contractKeeper interface. This interface defines the entry points exposed to the secondary app that invokes a smart contract. As shown in Figure 3, the callbacks middleware communicates with the contractKeeperimplemented by the secondary app, and the secondary app will have its own custom API with the callback actor i.e. smart contracts/modules.

Figure 3: An IBC application stack using transfer/ICA and the callbacks middleware

The contractKeeper interface outlines the required methods for a contract keeper to handle various source and destination chain callbacks. These include IBCSendCallback, IBCOnAcknowledgementPacketCallback, IBCOnTimeoutCallback and IBCReceivePacketCallback.The first three methods are source chain callbacks while the last one is a destination callback.

Check out our documentation to learn how to integrate the callbacks middleware with your app.

Figure 4 below shows a full transaction flow for ICS-20 transfer + ICA using the callbacks middleware for OnAcknowledgementPacket callbacks.

Figure 4: Transaction flow for transfer + ICA using the callbacks middleware for OnAcknowledgementPacket callbacks

Conclusion

The release of the callbacks middleware establishes the foundation for smart contracts and modules to tap into existing IBC apps for a wide variety of use cases. Application and smart contract developers can now condense multiple different end-user actions into a single one, greatly improving the end-user experience on-chain.

In addition to enabling smart contracts to interact with IBC, the callbacks middleware broadens IBC’s developer community to encompass smart contract developers. Rather than creating new handlers to make your application IBC-compatible, you can now make use of pre-existing message types. This approach transforms IBC components into Legos, allowing developers to easily stack different applications on top of each other.

If you’re an application or smart contract developer, we highly encourage you to explore the capabilities of the callbacks middleware, which you can find here. Check out our documentation to learn how to integrate the middleware with your app. If you have any questions or feedback about this feature, feel free to reach out to us on our discord!

Acknowledgements

Many thanks to the Confio team for their support, with a special acknowledgement to Alex Peters for his invaluable contributions to the development of this feature. Also, a shoutout to Osmosis for pioneering IBC hooks which served as a point of inspiration for the callbacks middleware implementation.

About IBC:

The inter-blockchain communication protocol (IBC) is the most widely adopted trust-minimized, permissionless, and general messaging-passing protocol. IBC connects 100+ chains for cross-chain activities such as token transfers, inter-chain account control, shared security, data queries, and much more. Build using IBC or join us on Discord.

About the author:

Adi Ravi Raj works at Interchain GmbH and is the protocol analyst for the IBC team.

Thank you to Mary McGilvray, Susannah Evans, Carlos Rodriguez, and Syed Choudhury for the feedback and review.

--

--

IBC Protocol
The Interchain Foundation

IBC is a blockchain interoperability protocol used by 100+ chains. It enables secure, permissionless, feature-rich cross-chain interactions.