Transfer hook
An external call on the transfer path: what it can and cannot do, how much gas to hold, and why a failed transfer no longer means insufficient funds.
All docs
The token calls a policy contract after every transfer between two real accounts. It is the most expensive thing a token in this framework can do to you, and the flag exists to warn you before you commit rather than after.
What the hook can and cannot do
- It runs last
- After balances have settled. It cannot change amounts and cannot observe a half-applied transfer — only accept the result or reject it.
- A revert reverts
- Transfers can fail for reasons having nothing to do with balances or allowances. Do not assume a failed transfer means insufficient funds.
- It cannot re-enter
- The token holds a transient-storage reentrancy guard for the whole pipeline, so a hook calling back into any balance-moving path reverts.
- It is inspectable
- transferHook() names the contract, so you can read its code before integrating rather than discovering it in a trace.
- Supply is exempt
- Mint and burn do not fire the hook; phase four only runs for transfers between two non-zero addresses.
Budgeting the gas
The hook receives exactly the published limit. The EVM withholds one sixty-fourth of the remaining gas from any call, so a caller holding exactly that number will watch the hook receive slightly less and run out. Hold roughly the budget times sixty-four over sixty-three, on top of what a plain transfer costs.
if (flags & TRANSFER_HOOK != 0) {
uint32 budget = IERC20TransferHook(token)
.transferHookGasLimit();
require(
gasleft() > baseCost + (uint256(budget) * 64) / 63,
"insufficient gas for hook"
);
}The budget is bounded on both sides: never below thirty thousand, because a hook with less cannot do anything useful, and never above a million, because past that the published number stops being a bound anyone would treat as one. A token with the module installed but no hook configured reports a budget of zero.
A hook that returns without the expected acknowledgement selector reverts the transfer too. Swallowing hook failures would turn a policy contract into decoration, and the flag exists precisely to say that transfers here can fail for external reasons.