Partner Fee
CoW Protocol allows partners and integrators to collect fees on orders placed through their applications. Partner fees can be configured as a percentage of volume, surplus, or price improvement.
Overview
Partner fees enable:
- Revenue sharing: Collect fees from orders placed through your app
- Flexible fee structures: Choose from volume-based, surplus-based, or price improvement fees
- Transparent fee collection: Fees are automatically calculated and deducted
- Multiple fee tiers: Support different fee levels for different user segments
Fee Types
Volume-Based Fee (volumeBps)
A fixed percentage of the order’s sell or buy amount:
const partnerFee = {
volumeBps: 50, // 0.5% of order volume
recipient: '0xPartnerAddress',
}
Volume-based fees are capped at 100 BPS (1%) at the protocol level.
Surplus-Based Fee (surplusBps)
A percentage of the order’s surplus (the difference between limit price and execution price):
const partnerFee = {
surplusBps: 5000, // 50% of surplus
maxVolumeBps: 100, // Maximum 1% of volume
recipient: '0xPartnerAddress',
}
Price Improvement Fee (priceImprovementBps)
A percentage of the price improvement over the best available market price:
const partnerFee = {
priceImprovementBps: 5000, // 50% of price improvement
maxVolumeBps: 100, // Maximum 1% of volume
recipient: '0xPartnerAddress',
}
Configuration
Single Fee Structure
import { CowSdk, OrderKind } from '@cowprotocol/cow-sdk'
const sdk = new CowSdk({ chainId, adapter })
// Create order with partner fee
const { quote } = await sdk.trading.getQuote({
kind: OrderKind.SELL,
sellToken: '0xTokenAddress',
buyToken: '0xAnotherTokenAddress',
sellAmount: '1000000000000000000',
partnerFee: {
volumeBps: 50, // 0.5%
recipient: '0xPartnerAddress',
},
})
Multiple Fee Tiers
You can specify multiple fee structures, and the protocol will choose the most beneficial one:
const partnerFee = [
// Primary: Take 50% of surplus, capped at 1% volume
{
surplusBps: 5000,
maxVolumeBps: 100,
recipient: '0xPartnerAddress',
},
// Fallback: If no surplus, take 0.5% of volume
{
volumeBps: 50,
recipient: '0xPartnerAddress',
},
]
const { quote } = await sdk.trading.getQuote({
kind: OrderKind.SELL,
sellToken: WETH_ADDRESS,
buyToken: USDC_ADDRESS,
sellAmount: parseEther('1'),
partnerFee,
})
Usage with TradingSdk
Get Quote with Partner Fee
import { CowSdk, OrderKind } from '@cowprotocol/cow-sdk'
import { parseEther } from 'viem'
const sdk = new CowSdk({ chainId: 1, adapter })
// Request quote with partner fee
const { quote } = await sdk.trading.getQuote({
kind: OrderKind.SELL,
sellToken: WETH_ADDRESS,
buyToken: USDC_ADDRESS,
sellAmount: parseEther('10'),
partnerFee: {
volumeBps: 50, // 0.5% fee
recipient: '0xYourPartnerAddress',
},
})
// The quote already includes the partner fee deduction
console.log('Buy amount after fees:', quote.buyAmount)
console.log('Partner fee amount:', quote.feeAmount)
Post Order with Partner Fee
// Partner fee is included in the quote
const orderId = await sdk.trading.postSwapOrder({
quote: quote.quote,
// Partner fee is automatically included from the quote
})
console.log('Order placed with partner fee:', orderId)
Limit Orders with Partner Fee
const orderId = await sdk.trading.postLimitOrder({
chainId: 1,
sellToken: WETH_ADDRESS,
buyToken: USDC_ADDRESS,
sellAmount: parseEther('5'),
buyAmount: parseUnits('10000', 6),
partnerFee: {
volumeBps: 50,
recipient: '0xPartnerAddress',
},
})
Partner Fee in App Data
Partner fees are stored in the order’s app data:
import { generateAppDataDoc } from '@cowprotocol/sdk-app-data'
const appDataDoc = await generateAppDataDoc({
appCode: 'my-app',
metadata: {
partnerFee: {
volumeBps: 50,
recipient: '0xPartnerAddress',
},
},
})
// Use in order
const { quote } = await sdk.trading.getQuote({
kind: OrderKind.SELL,
sellToken: WETH_ADDRESS,
buyToken: USDC_ADDRESS,
sellAmount: parseEther('1'),
appData: appDataDoc.fullAppData,
})
Fee Calculation
For Sell Orders
Partner fee is deducted from the buy amount:
// Before partner fee
const buyAmountBeforeFee = quote.buyAmount
// Partner fee calculation (0.5% of volume)
const volumeBps = 50
const partnerFeeAmount = (buyAmountBeforeFee * BigInt(volumeBps)) / BigInt(10000)
// After partner fee
const buyAmountAfterFee = buyAmountBeforeFee - partnerFeeAmount
console.log('Buy amount (before fee):', buyAmountBeforeFee)
console.log('Partner fee:', partnerFeeAmount)
console.log('Buy amount (after fee):', buyAmountAfterFee)
For Buy Orders
Partner fee is added to the sell amount:
// Before partner fee
const sellAmountBeforeFee = quote.sellAmount
// Partner fee calculation (0.5% of volume)
const volumeBps = 50
const partnerFeeAmount = (sellAmountBeforeFee * BigInt(volumeBps)) / BigInt(10000)
// After partner fee
const sellAmountAfterFee = sellAmountBeforeFee + partnerFeeAmount
console.log('Sell amount (before fee):', sellAmountBeforeFee)
console.log('Partner fee:', partnerFeeAmount)
console.log('Sell amount (after fee):', sellAmountAfterFee)
Use the utility function to get partner fee BPS:
import { getPartnerFeeBps } from '@cowprotocol/sdk-trading'
const partnerFee = {
volumeBps: 50,
recipient: '0xPartnerAddress',
}
const feeBps = getPartnerFeeBps(partnerFee)
console.log('Partner fee in BPS:', feeBps) // 50
// For multiple fee structures, it returns the first volumeBps found
const multiPartnerFee = [
{ surplusBps: 5000, maxVolumeBps: 100, recipient: '0x...' },
{ volumeBps: 50, recipient: '0x...' },
]
const feeBps2 = getPartnerFeeBps(multiPartnerFee)
console.log('Partner fee in BPS:', feeBps2) // 50
Complete Example
import { CowSdk, OrderKind } from '@cowprotocol/cow-sdk'
import { EthersV6Adapter } from '@cowprotocol/sdk-ethers-v6-adapter'
import { parseEther, parseUnits } from 'viem'
import { JsonRpcProvider, Wallet } from 'ethers'
// Initialize
const provider = new JsonRpcProvider('YOUR_RPC_URL')
const wallet = new Wallet('YOUR_PRIVATE_KEY', provider)
const adapter = new EthersV6Adapter({ provider, signer: wallet })
const sdk = new CowSdk({
chainId: 1,
adapter,
})
// Define partner fee structure
const partnerFee = {
volumeBps: 50, // 0.5% fee
recipient: '0xYourPartnerFeeRecipient',
}
// Get quote with partner fee
const { quote } = await sdk.trading.getQuote({
kind: OrderKind.SELL,
sellToken: '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2', // WETH
buyToken: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48', // USDC
sellAmount: parseEther('10'),
partnerFee,
})
console.log('Quote buy amount (after partner fee):', quote.buyAmount)
console.log('Partner fee amount:', quote.feeAmount)
// Post order (partner fee automatically included)
const orderId = await sdk.trading.postSwapOrder({
quote: quote.quote,
})
console.log('Order with partner fee placed:', orderId)
// Track fee collection
const order = await sdk.orderBook.getOrder(orderId)
console.log('Order status:', order.status)
console.log('Executed buy amount:', order.executedBuyAmount)
Best Practices
Choose appropriate fee type
- Use volumeBps for predictable, transparent fees
- Use surplusBps to share in the value created
- Use priceImprovementBps to incentivize better execution
- Combine multiple types for optimal fee capture
Set reasonable fee rates
// Good: Competitive rates
const partnerFee = { volumeBps: 25, recipient } // 0.25%
// Avoid: Excessive fees that hurt user experience
const partnerFee = { volumeBps: 100, recipient } // 1% (maximum)
Display fees to users
Always show users the fee they’re paying:
const feeBps = getPartnerFeeBps(partnerFee)
const feePercent = feeBps / 100
console.log(`Partner fee: ${feePercent}%`)
// Calculate fee amount
const feeAmount = (sellAmount * BigInt(feeBps)) / BigInt(10000)
console.log(`Fee amount: ${formatUnits(feeAmount, decimals)}`)
Use surplus-based fees wisely
Surplus-based fees can be zero if there’s no positive slippage:
// Provide fallback for guaranteed minimum fee
const partnerFee = [
{ surplusBps: 5000, maxVolumeBps: 100, recipient },
{ volumeBps: 25, recipient }, // Fallback
]
Fee Recipient Management
Multiple Recipients
Support different fee recipients for different user tiers:
const getFeeRecipient = (userTier: string) => {
switch (userTier) {
case 'premium':
return '0xPremiumFeeRecipient'
case 'standard':
return '0xStandardFeeRecipient'
default:
return '0xDefaultFeeRecipient'
}
}
const partnerFee = {
volumeBps: 50,
recipient: getFeeRecipient(user.tier),
}
Fee Splitter Contract
Use a fee splitter to distribute fees:
// Deploy or use existing fee splitter
const FEE_SPLITTER = '0xFeeSplitterContractAddress'
const partnerFee = {
volumeBps: 50,
recipient: FEE_SPLITTER, // Automatically splits among recipients
}
Monitoring Fee Collection
// Track total fees collected
let totalFeesCollected = 0n
const orders = await sdk.orderBook.getOrders({
owner: userAddress,
})
for (const order of orders) {
if (order.status === 'fulfilled') {
const appData = await fetchDocFromAppData(order.appData)
if (appData.metadata.partnerFee) {
const feeBps = getPartnerFeeBps(appData.metadata.partnerFee)
const feeAmount = (BigInt(order.executedBuyAmount) * BigInt(feeBps)) / BigInt(10000)
totalFeesCollected += feeAmount
}
}
}
console.log('Total fees collected:', formatUnits(totalFeesCollected, 6), 'USDC')
Next Steps
- Hooks - Execute custom logic before and after orders
- TWAP Orders - Split large orders over time