Overview
TransferDepositHook is a deposit hook implementing the IHook interface. It forwards every processed deposit of one asset into a fixed subvault, then makes that subvault transfer the same amount to a fixed destination address.
Use it when deposits must leave the vault right away for a known external destination, such as a custody account or an external strategy wallet, instead of staying idle in the vault.
A single hook instance is bound to one vault, one asset, one subvault and one destination. All four are set in the constructor and cannot be changed.
Constructor parameters
The values are exposed as public immutables:
asset(), vault(), subvault() and to().
Key function
callHook(address asset_, uint256 assets_)
The vault calls the hook through delegatecall from ShareModule.callHook, so the code runs in the vault’s context.
Execution logic:
- Reverts with
OnlyDelegateCallif called directly instead of throughdelegatecall. - Reverts with
InvalidVaultif the calling context is not the configuredvault. - Reverts with
InvalidAssetifasset_is not the configuredasset. - Reverts with
UnsupportedSubvaultif the configured subvault is no longer connected to the vault (vault.hasSubvault(subvault)). - Returns early if
assets_is zero. - Calls
vault.hookPushAssets(subvault, asset, assets_). This moves the assets into the subvault and increases the subvault balance in theRiskManager. - Calls
ICallModule(subvault).call(asset, 0, transfer(to, assets_), payload)with an emptyONCHAIN_COMPACTverification payload. The subvault transfersassets_toto. - Reverts with
InvalidResponseif the token returns data other thantrue. Tokens that return no data, such as USDT, are accepted.
When the hook runs
DepositQueue: when an oracle report processes pending deposits, for the assets moved into the vault in that report.SyncDepositQueue: inside the user’sdeposittransaction.
Configuration requirements
The hook only works if the vault is configured for it:- Assign it to the deposit queue for
assetwithsetCustomHook(queue, hook), or as the default deposit hook withsetDefaultDepositHook(hook). Both requireSET_HOOK_ROLE. If you set it as the default, every deposit queue in the vault must useasset, because any other asset reverts withInvalidAsset. - The subvault’s
RiskManagerconfiguration must allowassetfor that subvault and leave enough limit for incoming deposits. OtherwisehookPushAssetsreverts. - The vault address must hold the verifier
CALLER_ROLE, because the vault itself callssubvault.call(...). - The subvault’s verifier must allow the compact call
(who = vault, where = asset, selector = transfer)throughallowCalls. The hook always sends anONCHAIN_COMPACTpayload, so a Merkle-only permission is not enough.
Accounting note
hookPushAssets records the deposit as subvault balance in the RiskManager. The transfer from the subvault to to is a plain token transfer, so it does not reduce that recorded balance. The oracle report for the vault must account for the assets held at to so that the share price stays correct.
Errors
Audit
TransferDepositHook was reviewed in the Nethermind custom hook audit (NM_0956, June 2026). See Security.