Skip to main content
This guide wires the rate limiting middleware into a chain’s app.go, using simapp as the reference. It assumes an ibc-go application that already has the transfer module wired. Wire the middleware for IBC v1, IBC v2, or both. The middleware only understands ICS-20 transfer packets, so it must wrap the transfer application. It cannot sit on a non-transfer route.

Before you begin

Confirm the application already has the middleware’s dependencies:
  • The transfer keeper and, for IBC v2, the transfer v2 module.
  • The IBC keeper, including its ChannelKeeper, ClientKeeper, and for v2 its ChannelKeeperV2.
  • The bank keeper.
  • A governance authority address, the only account allowed to change limits.
The module needs no manual genesis. It defaults to empty state: no limits, no whitelist, and no blacklist. To preload a whitelist or blacklist, see configure rate limits.

Wire rate limiting for IBC v1

These changes give a complete v1 integration. They add the keeper, register the middleware at the top of the transfer stack, and add the module to the manager.
Key details:
  • The last keeper argument is the governance authority. The keeper stores it and enforces it on every message. A chain that uses the standard governance module passes authtypes.NewModuleAddress(govtypes.ModuleName).String().
  • The module runs a begin-block hook and has genesis state, so it must appear in the begin-blocker order and the init and export genesis orderings.
  • If the chain already wires other transfer middleware, add rate limiting to the same stack with another Next call rather than creating a new stack. See the packet forward example below.

With packet forward middleware

If the chain runs packet forward middleware, place rate limiting above it in the same stack, so limits apply to the final inbound transfer before it is forwarded:

Wire rate limiting for IBC v2

These changes give a complete v2 integration. IBC v2 uses a separate router, and the middleware wraps the transfer v2 module directly.
The keeper, store key, module registration, and ordering are shared across versions. If the chain already wired v1 rate limiting above, those parts are done. Only the v2 router wiring is new, so skip to the step that builds transferModuleV2.
The v2 constructor takes four arguments: the keeper by value, the module being wrapped, a write-acknowledgement wrapper, and the v2 channel keeper. In simapp the last two are both app.IBCKeeper.ChannelKeeperV2. Both are required, and the constructor panics if either is nil. The v2 middleware enforces the same limits as v1. It converts each v2 payload to the internal packet form and calls the same keeper logic, so a denom’s limit applies identically regardless of which IBC version carried the transfer.
The transfer v2 route through rate limiting is added by pull request #8984. On an ibc-go version from before that change, only the v1 stack is wired. Confirm the version in use includes it before wiring v2.

What can go wrong

  • The application panics on start. The store key is likely unregistered, or a nil dependency reached the v2 constructor.
  • Limits never trigger. Confirm the middleware is on the route the transfers use, and that the module is in the begin-blocker order so windows advance.

Next steps