Skip to main content

Hooks

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.
OperationTypical GasRecommended Limit
ERC20 Approve45,00050,000
WETH Wrap/Unwrap25,00030,000
Token Transfer50,00065,000
Staking80,000100,000
Flash Loan250,000300,000
Complex DeFi200,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')
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

Last modified on March 4, 2026