CoW Protocol supports order hooks - custom smart contract calls that execute before (pre-hooks) or after (post-hooks) your order settlement. This enables advanced use cases like token approvals, flash loans, and complex DeFi interactions.
Overview
Hooks allow you to:
Execute pre-order logic : Approve tokens, wrap/unwrap assets, or setup flash loans
Execute post-order logic : Stake tokens, compound yields, or trigger other contracts
Chain complex operations : Combine multiple DeFi protocols in a single transaction
Integrate with dApps : Enable seamless integration with other protocols
Hook Structure
Hooks are defined in the order’s app data and consist of:
interface CoWHook {
target : string // Contract address to call
callData : string // Encoded function call data
gasLimit : string // Gas limit for the hook execution
dappId ?: string // Optional identifier for the dApp that built the hook
}
interface OrderInteractionHooks {
version ?: string // Hooks schema version
pre ?: CoWHook [] // Hooks executed before the order
post ?: CoWHook [] // Hooks executed after the order
}
Adding Hooks to Orders
Basic Hook Setup
import { generateAppDataDoc } from '@cowprotocol/sdk-app-data'
import { OrderKind } from '@cowprotocol/sdk-common'
const appData = await generateAppDataDoc ({
appCode: 'YOUR_APP_CODE' ,
metadata: {
hooks: {
version: '1.0.0' ,
pre: [
{
target: '0xTokenAddress' ,
callData: '0x095ea7b3...' , // approve(spender, amount)
gasLimit: '50000' ,
dappId: 'my-dapp' ,
},
],
post: [
{
target: '0xStakingContract' ,
callData: '0xa694fc3a...' , // stake(amount)
gasLimit: '100000' ,
dappId: 'my-dapp' ,
},
],
},
},
})
const orderParams = {
kind: OrderKind . SELL ,
sellToken: '0xTokenAddress' ,
buyToken: '0xAnotherTokenAddress' ,
sellAmount: '1000000000000000000' ,
// ... other parameters
appData: appData . fullAppData ,
}
Using TradingSdk with Hooks
import { CowSdk } from '@cowprotocol/cow-sdk'
import { encodeFunctionData } from 'viem'
// Initialize SDK
const sdk = new CowSdk ({ chainId , adapter })
// Create hook calldata
const approveCallData = encodeFunctionData ({
abi: ERC20_ABI ,
functionName: 'approve' ,
args: [ spenderAddress , maxAmount ],
})
// Get quote with hooks in app data
const { quote } = await sdk . trading . getQuote ({
kind: OrderKind . SELL ,
sellToken: WETH_ADDRESS ,
buyToken: USDC_ADDRESS ,
sellAmount: parseEther ( '1' ),
appDataOverride: {
metadata: {
hooks: {
pre: [{
target: WETH_ADDRESS ,
callData: approveCallData ,
gasLimit: '50000' ,
}],
},
},
},
})
Pre-Hooks
Pre-hooks execute before the order settlement.
Token Approval
import { encodeFunctionData } from 'viem'
const approveHook = {
target: '0xTokenAddress' ,
callData: encodeFunctionData ({
abi: [{
name: 'approve' ,
type: 'function' ,
inputs: [
{ name: 'spender' , type: 'address' },
{ name: 'amount' , type: 'uint256' },
],
outputs: [{ type: 'bool' }],
}],
functionName: 'approve' ,
args: [ '0xSpenderAddress' , BigInt ( '1000000000000000000' )],
}),
gasLimit: '50000' ,
dappId: 'token-approval' ,
}
Wrap Native Token
const wrapETHHook = {
target: WETH_ADDRESS ,
callData: encodeFunctionData ({
abi: [{
name: 'deposit' ,
type: 'function' ,
inputs: [],
outputs: [],
payable: true ,
}],
functionName: 'deposit' ,
args: [],
}),
gasLimit: '30000' ,
dappId: 'wrap-eth' ,
}
Flash Loan Setup
const flashLoanHook = {
target: '0xFlashLoanProvider' ,
callData: encodeFunctionData ({
abi: FLASH_LOAN_ABI ,
functionName: 'flashLoan' ,
args: [
receiverAddress ,
tokenAddress ,
amount ,
encodedParams ,
],
}),
gasLimit: '300000' ,
dappId: 'flash-loan' ,
}
Post-Hooks
Post-hooks execute after the order settlement.
Stake Tokens
const stakeHook = {
target: '0xStakingContract' ,
callData: encodeFunctionData ({
abi: [{
name: 'stake' ,
type: 'function' ,
inputs: [{ name: 'amount' , type: 'uint256' }],
outputs: [],
}],
functionName: 'stake' ,
args: [ stakeAmount ],
}),
gasLimit: '100000' ,
dappId: 'staking-protocol' ,
}
Deposit to Yield Protocol
const depositHook = {
target: '0xYieldProtocol' ,
callData: encodeFunctionData ({
abi: YIELD_PROTOCOL_ABI ,
functionName: 'deposit' ,
args: [ tokenAddress , depositAmount , userAddress ],
}),
gasLimit: '150000' ,
dappId: 'yield-protocol' ,
}
Unwrap Wrapped Token
const unwrapHook = {
target: WETH_ADDRESS ,
callData: encodeFunctionData ({
abi: [{
name: 'withdraw' ,
type: 'function' ,
inputs: [{ name: 'amount' , type: 'uint256' }],
outputs: [],
}],
functionName: 'withdraw' ,
args: [ withdrawAmount ],
}),
gasLimit: '30000' ,
dappId: 'unwrap-eth' ,
}
Complete Example
Here’s a complete example with pre and post hooks:
import { CowSdk , OrderKind } from '@cowprotocol/cow-sdk'
import { generateAppDataDoc } from '@cowprotocol/sdk-app-data'
import { encodeFunctionData , parseEther } from 'viem'
// Initialize SDK
const sdk = new CowSdk ({ chainId: 1 , adapter })
// Define hooks
const hooks = {
version: '1.0.0' ,
pre: [
// Approve WETH for settlement
{
target: WETH_ADDRESS ,
callData: encodeFunctionData ({
abi: ERC20_ABI ,
functionName: 'approve' ,
args: [ SETTLEMENT_ADDRESS , parseEther ( '10' )],
}),
gasLimit: '50000' ,
dappId: 'cow-swap' ,
},
],
post: [
// Stake received tokens
{
target: STAKING_CONTRACT ,
callData: encodeFunctionData ({
abi: STAKING_ABI ,
functionName: 'stake' ,
args: [ parseEther ( '10000' )],
}),
gasLimit: '100000' ,
dappId: 'staking-protocol' ,
},
],
}
// Generate app data with hooks
const appDataDoc = await generateAppDataDoc ({
appCode: 'my-app' ,
metadata: { hooks },
})
// Create order with hooks
const { quote } = await sdk . trading . getQuote ({
kind: OrderKind . SELL ,
sellToken: WETH_ADDRESS ,
buyToken: REWARD_TOKEN ,
sellAmount: parseEther ( '10' ),
appData: appDataDoc . fullAppData ,
})
// Post order
const orderId = await sdk . trading . postSwapOrder ({
quote: quote . quote ,
appData: appDataDoc . fullAppData ,
})
console . log ( 'Order with hooks created:' , orderId )
Gas Limit Considerations
Set appropriate gas limits for hooks. If a hook runs out of gas, the entire order transaction will fail.
Recommended Gas Limits
Operation Typical Gas Recommended Limit ERC20 Approve 45,000 50,000 WETH Wrap/Unwrap 25,000 30,000 Token Transfer 50,000 65,000 Staking 80,000 100,000 Flash Loan 250,000 300,000 Complex DeFi 200,000+ 250,000+
Estimating Gas
import { estimateGas } from 'viem'
// Estimate gas for your hook call
const estimatedGas = await publicClient . estimateGas ({
account: userAddress ,
to: hookTarget ,
data: hookCallData ,
})
// Add 20% buffer for safety
const gasLimit = ( estimatedGas * BigInt ( 120 )) / BigInt ( 100 )
const hook = {
target: hookTarget ,
callData: hookCallData ,
gasLimit: gasLimit . toString (),
}
Hook Best Practices
Always test your hooks on testnets before production: // Test on Sepolia first
const sdk = new CowSdk ({
chainId: SupportedChainId . SEPOLIA ,
adapter
})
// Test with small amounts
const testAmount = parseEther ( '0.01' )
Handle failures gracefully
Consider whether your hook should fail the entire order:
Critical hooks (approvals): Must succeed
Optional hooks (staking): Can fail without breaking order
Keep hooks simple to reduce gas costs and failure risk: // Good: Simple single-purpose hook
const hook = { target: token , callData: approve , gasLimit: '50000' }
// Avoid: Complex multi-step operations in hooks
Always include a dappId to identify your hooks: const hook = {
target: '0x...' ,
callData: '0x...' ,
gasLimit: '50000' ,
dappId: 'my-protocol-v1' , // Helps with debugging and analytics
}
Use Cases
1. Automated Yield Farming
Swap and immediately deposit into yield protocol:
const hooks = {
post: [{
target: YIELD_VAULT ,
callData: encodeDeposit ( receivedTokens ),
gasLimit: '120000' ,
dappId: 'yield-farmer' ,
}],
}
2. Leveraged Trading
Flash loan, swap, and repay:
const hooks = {
pre: [{
target: FLASH_LOAN_PROVIDER ,
callData: encodeFlashLoan ( params ),
gasLimit: '300000' ,
dappId: 'leverage-trader' ,
}],
post: [{
target: FLASH_LOAN_PROVIDER ,
callData: encodeRepay ( amount ),
gasLimit: '150000' ,
dappId: 'leverage-trader' ,
}],
}
3. Cross-Chain Preparation
Prepare tokens for bridging:
const hooks = {
post: [{
target: BRIDGE_CONTRACT ,
callData: encodeInitiateBridge ( destChain , destAddress ),
gasLimit: '200000' ,
dappId: 'bridge-protocol' ,
}],
}
Debugging Hooks
Check Hook Execution
// Get order details including hook execution
const order = await sdk . orderBook . getOrder ( orderId )
// Check if hooks were included in app data
const appData = await fetchDocFromAppData ( order . appData )
if ( appData . metadata . hooks ) {
console . log ( 'Pre-hooks:' , appData . metadata . hooks . pre )
console . log ( 'Post-hooks:' , appData . metadata . hooks . post )
}
// Check transaction logs for hook execution
const tx = await provider . getTransaction ( order . executionTx )
const receipt = await tx . wait ()
console . log ( 'Gas used:' , receipt . gasUsed )
Limitations
Hooks must complete within their specified gas limit
Hooks are executed in order (pre-hooks → order → post-hooks)
Failed hooks will cause the entire transaction to revert
Hooks cannot be updated after order submission
Maximum gas for all hooks combined is subject to block gas limit
Next Steps
Partner Fee Configure partner fee collection
Cross-Chain Bridging Execute cross-chain swaps