Events & Callbacks
Moolah hands control back to you in two ways:
Callbacks — synchronous, in-transaction hooks that Moolah invokes on the caller mid-execution (before it pulls the tokens it is owed), enabling atomic flows such as leverage loops, flash-liquidations, and position migrations.
Events — the logs Moolah, the IRM, and the vault layer emit for off-chain indexers, subgraphs, and monitoring.
For function signatures, structs, and the Id/MarketParams types referenced below, see the Contract & Interface Reference. For the higher-level provider/broker model, see Integration Patterns.
Callbacks
Moolah follows the Morpho-style callback pattern: several core entry points optionally call back into msg.sender after updating internal accounting but before transferring in the assets the caller owes. This lets an integrator source those assets inside the same transaction — for example, minting/swapping collateral, or repaying a loan out of seized collateral — so the whole operation is atomic and reverts as a unit if any leg fails.
Firing rule
Each entry point takes a trailing bytes data argument. For supply, repay, supplyCollateral, and liquidate, the callback fires only when data is non-empty (data.length > 0); pass empty data to skip it. flashLoan is the exception: its callback is always invoked, because the flash loan has no purpose without it.
The callback is always made on msg.sender, so the contract that calls Moolah must be the contract that implements the interface.
Callback interfaces
All five interfaces are declared in moolah/interfaces/IMoolahCallbacks.sol.
IMoolahSupplyCallback
onMoolahSupply(uint256 assets, bytes data)
supply
data non-empty
IMoolahRepayCallback
onMoolahRepay(uint256 assets, bytes data)
repay
data non-empty
IMoolahSupplyCollateralCallback
onMoolahSupplyCollateral(uint256 assets, bytes data)
supplyCollateral
data non-empty
IMoolahLiquidateCallback
onMoolahLiquidate(uint256 repaidAssets, bytes data)
liquidate
data non-empty
IMoolahFlashLoanCallback
onMoolahFlashLoan(uint256 assets, bytes data)
flashLoan
always
The first argument is the settled amount for that action: assets supplied/repaid/supplied-as-collateral, repaidAssets for a liquidation, or the flash-loaned amount. The data argument is the same opaque payload you passed into the originating call, so you can encode a route, a target market, or step parameters and decode them inside the callback.
Execution order (the atomic window)
Within the originating call, Moolah:
Updates its own accounting.
Emits the corresponding event (
Supply,Repay,SupplyCollateral,Liquidate,FlashLoan).For the actions where Moolah owes you assets, transfers them out now — the seized collateral for
liquidate, the loaned token forflashLoan. (supply,repay, andsupplyCollateralhave no outbound transfer; you are the one paying in.)Calls back into
msg.sender(subject to the firing rule above), handing you control while the position is mid-update.On return, pulls in the assets you owe via
transferFrom(the supplied/repaid/collateral token, or the flash-loaned amount plus zero fee). If your balance or allowance is short, the whole transaction reverts.
Your callback body runs in step 4, so it must leave msg.sender holding enough of the inbound token (and allowance to Moolah) for step 5 to succeed.
Typical atomic flows
Read this before designing a flow. Moolah inherits a single global reentrancy guard, and eight entry points carry it —
supply,withdraw,borrow,repay,supplyCollateral,withdrawCollateral,liquidateandliquidateBrokerPosition.flashLoanis the only callback entry point without it. So from insideonMoolahSupply,onMoolahRepay,onMoolahSupplyCollateraloronMoolahLiquidateyou cannot re-enter any of those eight — they revertReentrancyGuardReentrantCall(). Views,accrueInterestandflashLoancarry no guard and remain callable, but no position-mutating composition is possible. Multi-step Moolah composition is possible only fromonMoolahFlashLoan. This is a deliberate divergence from upstream Morpho Blue, where the equivalent flows do work, so do not carry a Morpho design over unchanged.
Leverage loop
onMoolahFlashLoan
Flash-loan the loan token; in the callback swap it to collateral, supplyCollateral, then borrow enough to repay the flash loan. Must be built on flashLoan — the supplyCollateral callback cannot borrow.
Flash-liquidation
onMoolahLiquidate
Call liquidate with data; Moolah sends you the seized collateral first, then calls back. Swap that collateral on an external venue to raise the repayment. No Moolah call is needed inside the callback, which is why this one works.
Debt / position migration
onMoolahFlashLoan
Flash-loan the loan token, use it to repay a position elsewhere, withdraw the freed collateral, move it, and re-borrow to repay the flash loan.
Zero-capital deleverage
onMoolahFlashLoan
Flash-loan the loan token, repay with it, withdrawCollateral, swap part of the collateral back to the loan token and repay the flash loan. Not available via onMoolahRepay, which cannot call withdrawCollateral.
Callbacks are a low-level primitive. If you are integrating in TypeScript and do not need custom atomic routing, the Moolah Lending SDK builds standard supply/borrow/repay transactions for you.
Events
Core market events (Moolah)
Signatures and indexed parameters are in moolah/libraries/EventsLib.sol — read them from there or from the ABI. Id is the market identifier (the keccak hash of MarketParams). Five things will catch you out when reconstructing state from these logs:
Position reconstruction. A supplier's balance evolves through
Supply/Withdraw; a borrower's throughBorrow/Repay/Liquidate; collateral throughSupplyCollateral/WithdrawCollateral. All are keyed by the indexedIdand the indexedonBehalf(orborrower) address.Interest.
AccrueInterestcarries theprevBorrowRateused for the elapsed period, theinterestadded to the market, and thefeeSharesminted to the fee recipient. Note that the fee recipient can receive shares during accrual without aSupplyevent, so reconstruct fee-recipient balances fromAccrueInterest, notSupply.Repay/Liquidate rounding. The repaid amount —
assetsonRepay,repaidAssetsonLiquidate— may exceed the market'stotalBorrowAssetsby 1 due to rounding. Account for this when reconciling.Bad debt. On
Liquidate, non-zerobadDebtAssets/badDebtSharesmean the position was underwater and the loss was socialized to suppliers of that market.Authorization. Watch
SetAuthorization(andIncrementNoncefor signature-based authorizations) to track which managers can act on a position via theauthorized/onBehalfmodel.
Lista-specific administrative events (provider/broker wiring, whitelists, blacklists, min-loan, default fee) are also declared in the same EventsLib and documented alongside the functions that emit them in the Contract & Interface Reference.
Interest-rate model events (InterestRateModel)
The IRMs emit their own events, and three of them are emitted by both IRMs with byte-identical signatures, so they share a topic0 — BorrowRateCapUpdate, BorrowRateFloorUpdate and MinCapUpdate come from either InterestRateModel or FixedRateIrm. Filter by the emitting address, not by topic alone, or you will file fixed-rate broker-market logs as adaptive-curve events. Only BorrowRateUpdate is exclusive to the adaptive curve (FixedRateIrm declares it but never emits it, since its borrowRate is view). BorrowRateUpdate is emitted each time Moolah accrues interest against a market that uses this IRM (Moolah calls IIrm.borrowRate during _accrueInterest), so it is the finest-grained rate signal available to indexers.
avgBorrowRate is the average per-second rate (scaled by WAD) applied over the accrual interval; rateAtTarget is the model's rate at target utilization after the update. Fixed-rate broker markets use a separate FixedRateIrm, which emits SetBorrowRate(Id indexed id, int256 newBorrowRate) when its rate is set.
Vault events (MoolahVault)
The ERC-4626 curator vault layer emits the standard Deposit/Withdraw/Transfer events plus its own lifecycle and configuration set, declared in moolah-vault/libraries/EventsLib.sol. Two declared event groups require special handling:
Do not index the vault's Submit* / Revoke* events. SubmitCap, SubmitTimelock, SetTimelock, SubmitGuardian, SetGuardian, SubmitMarketRemoval and the RevokePending* family are all declared in EventsLib but emitted nowhere. They are inherited declarations; Lista uses two external OpenZeppelin TimelockControllers per vault instead of the in-contract pending/timelock mechanism. Their addresses appear in CreateMoolahVault as managerTimeLock and curatorTimeLock; watch those for CallScheduled / CallExecuted / Cancelled. SetCap is emitted directly by the vault, with no preceding SubmitCap.
SetCurator and SetIsAllocator are declared in the same library and likewise never emitted. The vault uses OpenZeppelin AccessControl, so role changes surface as RoleGranted / RoleRevoked filtered on the CURATOR / ALLOCATOR role hashes.
To reconstruct how a vault allocates deposits across underlying Moolah markets, track SetSupplyQueue/SetWithdrawQueue for ordering, SetCap for per-market limits, and ReallocateSupply/ReallocateWithdraw for actual moves.
Vault allocator events (VaultAllocator)
The public reallocation helper (src/vault-allocator/VaultAllocator.sol) declares its events in vault-allocator/libraries/EventsLib.sol.
A public reallocation emits one PublicWithdrawal per market it pulls from and a PublicReallocateTo for the market it supplies into, all correlated by the indexed vault.
See also
Smart Contract — deployed contract addresses.
Last updated
Was this helpful?