# Customization Learn how to configure your application to use IBC and send data packets to other chains. {synopsis} This document serves as a guide for developers who want to write their own Inter-blockchain Communication Protocol (IBC) applications for custom [use-cases](https://github.com/cosmos/ics/blob/master/ibc/4_IBC_USECASES.md). Due to the modular design of the IBC protocol, IBC application developers do not need to concern themselves with the low-level details of clients, connections, and proof verification. Nevertheless a brief explanation of the lower levels of the stack is given so that application developers may have a high-level understanding of the IBC protocol. Then the document goes into detail on the abstraction layer most relevant for application developers (channels and ports), and describes how to define your own custom packets, and `IBCModule` callbacks. To have your module interact over IBC you must: bind to a port(s), define your own packet data and acknolwedgement structs as well as how to encode/decode them, and implement the `IBCModule` interface. Below is a more detailed explanation of how to write an IBC application module correctly. ## Pre-requisites Readings - [IBC Overview](./overview.md)) {prereq} - [IBC default integration](./integration.md) {prereq} ## Create a custom IBC application module ### Implement `IBCModule` Interface and callbacks The Cosmos SDK expects all IBC modules to implement the [`IBCModule` interface](https://github.com/cosmos/cosmos-sdk/tree/master/x/ibc/05-port/types/module.go). This interface contains all of the callbacks IBC expects modules to implement. This section will describe the callbacks that are called during channel handshake execution. Here are the channel handshake callbacks that modules are expected to implement: ```go // Called by IBC Handler on MsgOpenInit func (k Keeper) OnChanOpenInit(ctx sdk.Context, order channeltypes.Order, connectionHops []string, portID string, channelID string, channelCap *capabilitytypes.Capability, counterParty channeltypes.Counterparty, version string, ) error { // OpenInit must claim the channelCapability that IBC passes into the callback k.scopedKeeper.ClaimCapability(ctx, channelCap) // ... do custom initialization logic // Use above arguments to determine if we want to abort handshake // Examples: Abort if order == UNORDERED, // Abort if version is unsupported err := checkArguments(args) return err } // Called by IBC Handler on MsgOpenTry OnChanOpenTry( ctx sdk.Context, order channeltypes.Order, connectionHops []string, portID, channelID string, channelCap *capabilitytypes.Capability, counterparty channeltypes.Counterparty, version, counterpartyVersion string, ) error { // OpenInit must claim the channelCapability that IBC passes into the callback k.scopedKeeper.ClaimCapability(ctx, channelCap) // ... do custom initialization logic // Use above arguments to determine if we want to abort handshake err := checkArguments(args) return err } // Called by IBC Handler on MsgOpenAck OnChanOpenAck( ctx sdk.Context, portID, channelID string, counterpartyVersion string, ) error { // ... do custom initialization logic // Use above arguments to determine if we want to abort handshake err := checkArguments(args) return err } // Called by IBC Handler on MsgOpenConfirm OnChanOpenConfirm( ctx sdk.Context, portID, channelID string, ) error { // ... do custom initialization logic // Use above arguments to determine if we want to abort handshake err := checkArguments(args) return err } ``` The channel closing handshake will also invoke module callbacks that can return errors to abort the closing handshake. Closing a channel is a 2-step handshake, the initiating chain calls `ChanCloseInit` and the finalizing chain calls `ChanCloseConfirm`. ```go // Called by IBC Handler on MsgCloseInit OnChanCloseInit( ctx sdk.Context, portID, channelID string, ) error { // ... do custom finalization logic // Use above arguments to determine if we want to abort handshake err := checkArguments(args) return err } // Called by IBC Handler on MsgCloseConfirm OnChanCloseConfirm( ctx sdk.Context, portID, channelID string, ) error { // ... do custom finalization logic // Use above arguments to determine if we want to abort handshake err := checkArguments(args) return err } ``` #### Channel Handshake Version Negotiation Application modules are expected to verify versioning used during the channel handshake procedure. * `ChanOpenInit` callback should verify that the `MsgChanOpenInit.Version` is valid * `ChanOpenTry` callback should verify that the `MsgChanOpenTry.Version` is valid and that `MsgChanOpenTry.CounterpartyVersion` is valid. * `ChanOpenAck` callback should verify that the `MsgChanOpenAck.CounterpartyVersion` is valid and supported. Versions must be strings but can implement any versioning structure. If your application plans to have linear releases then semantic versioning is recommended. If your application plans to release various features in between major releases then it is advised to use the same versioning scheme as IBC. This versioning scheme specifies a version identifier and compatible feature set with that identifier. Valid version selection includes selecting a compatible version identifier with a subset of features supported by your application for that version. The struct is used for this scheme can be found in `03-connection/types`. Since the version type is a string, applications have the ability to do simple version verification via string matching or they can use the already impelemented versioning system and pass the proto encoded version into each handhshake call as necessary. ICS20 currently implements basic string matching with a single supported version. ### Bind Ports Currently, ports must be bound on app initialization. A module may bind to ports in `InitGenesis` like so: ```go func InitGenesis(ctx sdk.Context, keeper keeper.Keeper, state types.GenesisState) { // ... other initialization logic // Only try to bind to port if it is not already bound, since we may already own // port capability from capability InitGenesis if !isBound(ctx, state.PortID) { // module binds to desired ports on InitChain // and claims returned capabilities cap1 := keeper.IBCPortKeeper.BindPort(ctx, port1) cap2 := keeper.IBCPortKeeper.BindPort(ctx, port2) cap3 := keeper.IBCPortKeeper.BindPort(ctx, port3) // NOTE: The module's scoped capability keeper must be private keeper.scopedKeeper.ClaimCapability(cap1) keeper.scopedKeeper.ClaimCapability(cap2) keeper.scopedKeeper.ClaimCapability(cap3) } // ... more initialization logic } ``` ### Custom Packets Modules connected by a channel must agree on what application data they are sending over the channel, as well as how they will encode/decode it. This process is not specified by IBC as it is up to each application module to determine how to implement this agreement. However, for most applications this will happen as a version negotiation during the channel handshake. While more complex version negotiation is possible to implement inside the channel opening handshake, a very simple version negotation is implemented in the [ibc-transfer module](https://github.com/cosmos/cosmos-sdk/tree/master/x/ibc-transfer/module.go). Thus, a module must define its a custom packet data structure, along with a well-defined way to encode and decode it to and from `[]byte`. ```go // Custom packet data defined in application module type CustomPacketData struct { // Custom fields ... } EncodePacketData(packetData CustomPacketData) []byte { // encode packetData to bytes } DecodePacketData(encoded []byte) (CustomPacketData) { // decode from bytes to packet data } ``` Then a module must encode its packet data before sending it through IBC. ```go // Sending custom application packet data data := EncodePacketData(customPacketData) packet.Data = data IBCChannelKeeper.SendPacket(ctx, packet) ``` A module receiving a packet must decode the `PacketData` into a structure it expects so that it can act on it. ```go // Receiving custom application packet data (in OnRecvPacket) packetData := DecodePacketData(packet.Data) // handle received custom packet data ``` #### Packet Flow Handling Just as IBC expected modules to implement callbacks for channel handshakes, IBC also expects modules to implement callbacks for handling the packet flow through a channel. Once a module A and module B are connected to each other, relayers can start relaying packets and acknowledgements back and forth on the channel. ![IBC packet flow diagram](https://media.githubusercontent.com/media/cosmos/ics/master/spec/ics-004-channel-and-packet-semantics/packet-state-machine.png) Briefly, a successful packet flow works as follows: 1. module A sends a packet through the IBC module 2. the packet is received by module B 3. if module B writes an acknowledgement of the packet then module A will process the acknowledgement 4. if the packet is not successfully received before the timeout, then module A processes the packet's timeout. ##### Sending Packets Modules do not send packets through callbacks, since the modules initiate the action of sending packets to the IBC module, as opposed to other parts of the packet flow where msgs sent to the IBC module must trigger execution on the port-bound module through the use of callbacks. Thus, to send a packet a module simply needs to call `SendPacket` on the `IBCChannelKeeper`. ```go // retrieve the dynamic capability for this channel channelCap := scopedKeeper.GetCapability(ctx, channelCapName) // Sending custom application packet data data := EncodePacketData(customPacketData) packet.Data = data // Send packet to IBC, authenticating with channelCap IBCChannelKeeper.SendPacket(ctx, channelCap, packet) ``` ::: warning In order to prevent modules from sending packets on channels they do not own, IBC expects modules to pass in the correct channel capability for the packet's source channel. ::: ##### Receiving Packets To handle receiving packets, the module must implement the `OnRecvPacket` callback. This gets invoked by the IBC module after the packet has been proved valid and correctly processed by the IBC keepers. Thus, the `OnRecvPacket` callback only needs to worry about making the appropriate state changes given the packet data without worrying about whether the packet is valid or not. Modules must return an acknowledgement as a byte string and return it to the IBC handler. The IBC handler will then commit this acknowledgment of the packet so that a relayer may relay the acknowledgement back to the sender module. ```go OnRecvPacket( ctx sdk.Context, packet channeltypes.Packet, ) (res *sdk.Result, ack []byte, abort error) { // Decode the packet data packetData := DecodePacketData(packet.Data) // do application state changes based on packet data // and return result, acknowledgement and abortErr // Note: abortErr is only not nil if we need to abort the entire receive packet, and allow a replay of the receive. // If the application state change failed but we do not want to replay the packet, // simply encode this failure with relevant information in ack and return nil error res, ack, abortErr := processPacket(ctx, packet, packetData) // if we need to abort the entire receive packet, return error if abortErr != nil { return nil, nil, abortErr } // Encode the ack since IBC expects acknowledgement bytes ackBytes := EncodeAcknowledgement(ack) return res, ackBytes, nil } ``` ::: warning `OnRecvPacket` should **only** return an error if we want the entire receive packet execution (including the IBC handling) to be reverted. This will allow the packet to be replayed in the case that some mistake in the relaying caused the packet processing to fail. If some application-level error happened while processing the packet data, in most cases, we will not want the packet processing to revert. Instead, we may want to encode this failure into the acknowledgement and finish processing the packet. This will ensure the packet cannot be replayed, and will also allow the sender module to potentially remediate the situation upon receiving the acknowledgement. An example of this technique is in the `ibc-transfer` module's [`OnRecvPacket`](https://github.com/cosmos/cosmos-sdk/tree/master/x/ibc-transfer/module.go). ::: ### Acknowledgements Modules must commit an acknowledgement upon receiving and processing a packet. This acknowledgement can then be relayed back to the original sender chain, which can take action depending on the contents of the acknowledgement. Just as packet data was opaque to IBC, acknowledgements are similarly opaque. Modules must pass and receive acknowledegments with the IBC modules as byte strings. Thus, modules must agree on how to encode/decode acknowledgements. The process of creating an acknowledgement struct along with encoding and decoding it, is very similar to the packet data example above. #### Acknowledging Packets After a module writes an acknowledgement while receiving a packet. a relayer can relay back the acknowledgement to the sender module. The sender module can then process the acknowledgement using the `OnAcknowledgementPacket` callback. The contents of the acknowledgement is entirely upto the modules on the channel (just like the packet data); however, it may often contain information on whether the packet was successfully received and processed along with some additional data that could be useful for remediation if the packet processing failed. Since the modules are responsible for agreeing on an encoding/decoding standard for packet data and acknowledgements, IBC will pass in the acknowledgements as `[]byte` to this callback. The callback is responsible for decoding the acknowledgment and processing it. ```go OnAcknowledgementPacket( ctx sdk.Context, packet channeltypes.Packet, acknowledgement []byte, ) (*sdk.Result, error) { // Decode acknowledgement ack := DecodeAcknowledgement(acknowledgement) // process ack res, err := processAck(ack) return res, err } ``` #### Timeout Packets If the timout for a packet is reached before the packet is successfully received, the receiving chain can no longer process it. Thus, the sending chain must process the timout using `OnTimeoutPacket` to handle this situation. Again the IBC module will verify that the timeout is indeed valid, so our module only needs to implement the state machine logic for what to do once a timeout is reached and the packet can no longer be received. ```go OnTimeoutPacket( ctx sdk.Context, packet channeltypes.Packet, ) (*sdk.Result, error) { // do custom timeout logic } ``` ### Routing As mentioned above, modules must implement the IBC module interface (which contains both channel handshake callbacks and packet handling callbacks). The concrete implementation of this interface must be registered with the module name as a route on the IBC `Router`. ```go // app.go func NewApp(...args) *App { // ... // Create static IBC router, add module routes, then set and seal it ibcRouter := port.NewRouter() ibcRouter.AddRoute(ibctransfertypes.ModuleName, transferModule) // Note: moduleCallbacks must implement IBCModule interface ibcRouter.AddRoute(moduleName, moduleCallbacks) // Setting Router will finalize all routes by sealing router // No more routes can be added app.IBCKeeper.SetRouter(ibcRouter) ``` ## Working Example For a real working example of an IBC application, you can look through the `ibc-transfer` module which implements everything discussed above. Here are the useful parts of the module to look at: [Binding to transfer port](https://github.com/cosmos/cosmos-sdk/blob/master/x/ibc-transfer/genesis.go) [Sending transfer packets](https://github.com/cosmos/cosmos-sdk/blob/master/x/ibc-transfer/keeper/relay.go) [Implementing IBC callbacks](https://github.com/cosmos/cosmos-sdk/blob/master/x/ibc-transfer/module.go) ## Next {hide} Learn about [building modules](../building-modules/intro.md) {hide}