App Data
Overview
App Data is a flexible metadata system enabling developers to attach additional information to CoW Protocol orders. It supports developer attribution, analytics tracking, custom hooks, and more.
What is App Data?
App Data is a 32-byte hash (bytes32) stored in every order that points to a JSON document containing metadata. The document can include slippage tolerance, order classification, UTM tracking, partner fees, CoW Hooks, and custom application data.
const order = {
appData: '0x8e4f5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e',
}
App Data Structure
interface AppDataDoc {
version: string
appCode: string
environment?: string
metadata: {
quote?: {
slippageBips: number
}
orderClass?: {
orderClass: 'market' | 'limit' | 'liquidity' | 'twap'
}
utm?: {
utmSource?: string
utmMedium?: string
utmCampaign?: string
utmContent?: string
utmTerm?: string
}
hooks?: {
pre?: CoWHook[]
post?: CoWHook[]
}
partnerFee?: {
bps: number
recipient: string
}
}
}
Creating App Data
Basic Usage
import { buildAppData } from '@cowprotocol/cow-sdk'
const appDataInfo = await buildAppData({
appCode: 'MyTradingApp',
slippageBps: 50,
orderClass: 'market',
})
// Returns:
// {
// doc: { /* full app data document */ },
// fullAppData: '{ ... }',
// appDataKeccak256: '0x...'
// }
Using in Orders
const { appDataKeccak256 } = await buildAppData({
appCode: 'MyApp',
slippageBps: 50,
orderClass: 'market',
})
const order = {
sellToken: '0x...',
buyToken: '0x...',
appData: appDataKeccak256,
}
Default UTM Parameters
The SDK automatically includes UTM parameters:
const defaultUtm = {
utmCampaign: 'developer-cohort',
utmContent: '',
utmMedium: 'cow-sdk@X.Y.Z',
utmSource: 'cowmunity',
utmTerm: 'js',
}
Overriding UTM Parameters
const appDataInfo = await buildAppData(
{
appCode: 'MyApp',
slippageBps: 50,
orderClass: 'market',
},
{
metadata: {
utm: {
utmSource: 'my-platform',
utmMedium: 'web-app',
utmCampaign: 'launch-week',
},
},
}
)
Custom UTM parameters completely replace the defaults when provided.
Partner Fees
const appDataInfo = await buildAppData({
appCode: 'MyApp',
slippageBps: 50,
orderClass: 'market',
partnerFee: {
bps: 25,
recipient: '0x1234...',
},
})
Partner fees are deducted from order surplus and don’t affect minimum user output.
CoW Hooks
CoW Hooks execute custom contract calls before and/or after order execution, enabling token approvals, reward claiming, staking, and arbitrary DeFi operations.
Hook Structure
interface CoWHook {
target: string
callData: string
gasLimit: string
}
Pre-Hooks Example
import { encodeFunctionData } from 'viem'
const approveCallData = encodeFunctionData({
abi: ERC20_ABI,
functionName: 'approve',
args: [SPENDER_ADDRESS, MAX_UINT256],
})
const appDataInfo = await buildAppData(
{
appCode: 'MyApp',
slippageBps: 50,
orderClass: 'market',
},
{
metadata: {
hooks: {
pre: [
{
target: TOKEN_ADDRESS,
callData: approveCallData,
gasLimit: '50000',
},
],
},
},
}
)
Post-Hooks Example
const stakeCallData = encodeFunctionData({
abi: STAKING_ABI,
functionName: 'stake',
args: [AMOUNT],
})
const appDataInfo = await buildAppData(
{
appCode: 'MyApp',
slippageBps: 50,
orderClass: 'market',
},
{
metadata: {
hooks: {
post: [
{
target: STAKING_CONTRACT,
callData: stakeCallData,
gasLimit: '100000',
},
],
},
},
}
)
Hook Limitations:
- Hooks execute in the same transaction as the order
- Failed hooks cause the entire transaction to revert
- Set appropriate gas limits to avoid out-of-gas errors
- Only use trusted contracts as hook targets
Advanced Usage
Merging App Data
import { mergeAppDataDoc } from '@cowprotocol/cow-sdk'
const existingDoc = await buildAppData({
appCode: 'MyApp',
slippageBps: 50,
orderClass: 'market',
})
const mergedAppData = await mergeAppDataDoc(
existingDoc.doc,
{
metadata: {
referrer: {
address: '0x...',
},
},
}
)
const appDataInfo = await buildAppData(
{
appCode: 'MyApp',
slippageBps: 50,
orderClass: 'market',
},
{
environment: 'production',
metadata: {
signer: '0x...',
referrer: {
address: '0x...',
},
},
}
)
IPFS Integration
const { fullAppData, appDataKeccak256 } = await buildAppData({
appCode: 'MyApp',
slippageBps: 50,
orderClass: 'market',
})
import { keccak256, toUtf8Bytes } from 'ethers'
const computedHash = keccak256(toUtf8Bytes(fullAppData))
// computedHash === appDataKeccak256
The CoW Protocol API automatically uploads app data to IPFS when you submit orders.
Retrieving App Data
import { MetadataApi } from '@cowprotocol/sdk-app-data'
import { getGlobalAdapter } from '@cowprotocol/cow-sdk'
const metadataApi = new MetadataApi(getGlobalAdapter())
const appDataDoc = await metadataApi.fetchDocFromAppData(
'0x8e4f5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e'
)
console.log(appDataDoc.metadata.quote?.slippageBips)
Common Patterns
Market Order:
const appData = await buildAppData({
appCode: 'MyTradingApp',
slippageBps: 50,
orderClass: 'market',
})
Limit Order:
const appData = await buildAppData({
appCode: 'MyTradingApp',
slippageBps: 0,
orderClass: 'limit',
})
With Partner Fee:
const appData = await buildAppData({
appCode: 'MyTradingApp',
slippageBps: 50,
orderClass: 'market',
partnerFee: {
bps: 10,
recipient: '0x...',
},
})
With Custom UTM:
const appData = await buildAppData(
{
appCode: 'MyApp',
slippageBps: 50,
orderClass: 'market',
},
{
metadata: {
utm: {
utmSource: 'twitter',
utmMedium: 'social',
utmCampaign: 'launch',
},
},
}
)
Best Practices
- Use Descriptive App Codes: Choose clear, unique identifiers for analytics
- Set Appropriate Slippage: Market orders 0.5-1%, limit orders 0%, volatile tokens higher
- Test Hooks Thoroughly: Always test on testnets before production
- Cache App Data Hashes: Reuse identical metadata hashes to reduce IPFS uploads
Troubleshooting
App data hash mismatch: Ensure the exact fullAppData JSON string with deterministic formatting is used. The SDK handles this automatically.
Hook execution failed: Verify target contract address, callData encoding matches function signature, gasLimit is sufficient, and test hook separately.
Custom UTM not appearing: When providing metadata.utm, it replaces all defaults. Set all desired UTM fields explicitly.
Next Steps
Last modified on March 4, 2026