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.
Hooks are defined in the order’s app data and consist of:
Copy
Ask AI
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}
Always test your hooks on testnets before production:
Copy
Ask AI
// Test on Sepolia firstconst sdk = new CowSdk({ chainId: SupportedChainId.SEPOLIA, adapter})// Test with small amountsconst 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
Minimize hook complexity
Keep hooks simple to reduce gas costs and failure risk:
// Get order details including hook executionconst order = await sdk.orderBook.getOrder(orderId)// Check if hooks were included in app dataconst 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 executionconst tx = await provider.getTransaction(order.executionTx)const receipt = await tx.wait()console.log('Gas used:', receipt.gasUsed)
Post-hooks that need to act on the tokens received from a swap require special handling. By default, bought tokens are sent to the user’s wallet — but the post-hook executes in the context of the HooksTrampoline contract, not the user’s wallet. This means a post-hook cannot directly access the user’s received tokens.The solution is CoW Shed — a deterministic smart account (proxy) that acts as an intermediary:
Set the order’s receiver to the user’s CoW Shed account
Tokens land in the CoW Shed proxy after the swap
The post-hook calls the CoW Shed proxy to execute pre-signed operations (stake, bridge, deposit, etc.)
Copy
Ask AI
import { TradingSdk } from '@cowprotocol/sdk-trading'import { CowShedSdk } from '@cowprotocol/sdk-cow-shed'const tradingSdk = new TradingSdk()const cowShedSdk = new CowShedSdk()// 1. Prepare the post-hook calls (e.g., stake received tokens)const hookResult = await cowShedSdk.signCalls({ calls: [ { target: STAKING_CONTRACT, callData: encodeFunctionData({ abi: STAKING_ABI, functionName: 'stake', args: [stakeAmount], }), value: 0n, allowFailure: false, isDelegateCall: false, }, ], signer, chainId: SupportedChainId.MAINNET, deadline: BigInt(Math.floor(Date.now() / 1000) + 3600),})// 2. Create order with receiver set to CoW Shed accountconst order = await tradingSdk.postOrder({ kind: OrderKind.SELL, sellToken: WETH_ADDRESS, buyToken: REWARD_TOKEN, sellAmount: parseEther('10'), receiver: hookResult.cowShedAccount, // Tokens go to CoW Shed signer, postHooks: [{ target: hookResult.signedMulticall.to, callData: hookResult.signedMulticall.data, gasLimit: hookResult.gasLimit, }],})
If your post-hook needs to act on received tokens (staking, bridging, depositing into a protocol), you must use CoW Shed. Without it, the tokens will be in the user’s wallet but the hook will execute without access to them.