Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/cowprotocol/solver-rewards/llms.txt

Use this file to discover all available pages before exploring further.

Every solver that participated in the network during an accounting period receives a payout composed of up to three components: a batch reward (for settling trades), a quote reward (for providing accurate price quotes), and a reimbursement or penalty based on their net slippage. A service fee is deducted from all positive rewards as a cut for CoW DAO.

Payout columns

All solver-level inputs are carried in a DataFrame whose columns are exactly SOLVER_PAYOUTS_COLUMNS:
# src/fetch/payouts.py
SOLVER_PAYOUTS_COLUMNS = [
    "solver",
    "solver_name",
    "primary_reward_eth",
    "primary_reward_cow",
    "quote_reward_cow",
    "protocol_fee_eth",
    "network_fee_eth",
    "slippage_eth",
    "reward_target",
    "buffer_accounting_target",
    "reward_token_address",
    "service_fee",
]
All monetary values are in wei (integer atoms of the respective token). The pipeline asserts that the DataFrame has exactly these columns before processing.

Batch rewards

Batch rewards are earned by settling user trades on-chain. They are stored as two parallel values:
ColumnTypeMeaning
primary_reward_ethint (wei)Reward denominated in the chain’s native token
primary_reward_cowint (atoms)Reward denominated in COW tokens
Both values originate from the orderbook analytics database and reflect the value the solver added during the period.
On Mainnet, both columns are populated. On chains where COW is not natively distributed, primary_reward_cow may be zero or use a dummy token address.

Quote rewards

Quote rewards incentivize solvers to provide accurate price quotes to users browsing the CoW Protocol UI. Each valid quote earns a flat reward:
# src/config.py — RewardConfig.from_network()
quote_reward_cow = 6 * 10**18  # 6 COW per valid quote (all networks)
This is stored in SOLVER_PAYOUTS_COLUMNS as quote_reward_cow. Quote rewards are always paid in COW tokens and are always non-negative (asserted in RewardAndPenaltyDatum.__init__).

Per-network caps

To prevent runaway costs, each network enforces a quote_reward_cap_native — the maximum native-token value that can be paid out per quote. If the COW equivalent of 6 COW exceeds this cap at the current exchange rate, the reward is capped:
# src/config.py
case Network.MAINNET:
    quote_reward_cow = 6 * 10**18
    quote_reward_cap_native = 7 * 10**14       # 0.0007 ETH
case Network.GNOSIS:
    quote_reward_cow = 6 * 10**18
    quote_reward_cap_native = 15 * 10**16      # 0.15 xDAI
case Network.ARBITRUM_ONE:
    quote_reward_cow = 6 * 10**18
    quote_reward_cap_native = 24 * 10**13      # 0.00024 ETH
case Network.BASE:
    quote_reward_cow = 6 * 10**18
    quote_reward_cap_native = 24 * 10**13      # 0.00024 ETH
case Network.AVALANCHE:
    quote_reward_cow = 6 * 10**18
    quote_reward_cap_native = 6 * 10**15       # 0.006 AVAX

Service fee

CoW DAO retains a 15% cut of all positive rewards as a service fee:
# src/config.py
service_fee_factor = Fraction(15, 100)  # 0.15
The factor is stored per-solver in the service_fee column of SOLVER_PAYOUTS_COLUMNS. Solvers that have service_fee_enabled = 0 in the analytics database receive a service_fee of 0 (no cut taken).

reward_scaling()

The scaling multiplier applied to all positive rewards is 1 - service_fee:
# src/fetch/payouts.py
def reward_scaling(self) -> Fraction:
    """Scaling factor for service fee
    The reward is multiplied by this factor"""
    return 1 - self.service_fee
With the default factor, reward_scaling() returns Fraction(85, 100) — meaning solvers keep 85% of earned rewards and CoW DAO retains 15%.

total_service_fee()

The total service fee charged from a solver’s rewards (in COW atoms) is:
def total_service_fee(self) -> Fraction:
    """Total service fee charged from rewards"""
    return self.service_fee * (
        max(self.primary_reward_cow, 0) + self.quote_reward_cow
    )
Note that service fee is only applied to positive primary_reward_cow. Negative primary rewards (penalties) are not amplified by the service fee.

Total reward calculations

total_cow_reward()

def total_cow_reward(self) -> int:
    """Total outgoing COW token reward"""
    return (
        int(self.reward_scaling() * self.primary_reward_cow)
        if self.primary_reward_cow > 0
        else self.primary_reward_cow
    )
Service fee scaling is only applied when the primary reward is positive. If primary_reward_cow is zero or negative, it passes through unchanged.

total_eth_reward()

def total_eth_reward(self) -> int:
    """Total outgoing ETH reward"""
    return (
        int(self.reward_scaling() * self.primary_reward_eth)
        if self.primary_reward_eth > 0
        else self.primary_reward_eth
    )
The same logic applies to native-token rewards.

Dual-token payout logic

The as_payouts() method on RewardAndPenaltyDatum implements the full decision tree for which token(s) to use when constructing Transfer objects. The key insight is that the total outgoing value must remain consistent even when rewards and reimbursements have opposite signs.
def as_payouts(self) -> list[Transfer]:
    quote_reward_cow = int(self.reward_scaling() * self.quote_reward_cow)
    result = []
    # Quote rewards are always paid in COW, regardless of other signs
    if quote_reward_cow > 0:
        result.append(Transfer(token=Token(self.reward_token_address, 18),
                               recipient=self.reward_target,
                               amount_wei=quote_reward_cow))
    # Overdrafts are excluded from transfer generation
    if self.is_overdraft():
        return result
    ...
When both slippage_eth and total_cow_reward are in their expected ranges (reimbursement ≥ 0, reward ≥ 0), two separate transfers are generated:
# ETH reimbursement → buffer_accounting_target
Transfer(token=None, recipient=self.buffer_accounting_target,
         amount_wei=reimbursement_eth)

# COW reward → reward_target
Transfer(token=Token(self.reward_token_address, 18),
         recipient=self.reward_target,
         amount_wei=total_cow_reward)
The two recipients can differ: buffer_accounting_target receives the slippage reimbursement and reward_target receives the COW reward.

Partner fees

Protocol fees collected from trades are split between CoW DAO and registered partner integrators. The fee flow is:
# src/fetch/payouts.py — prepare_payouts()
total_protocol_fee = int(solver_payouts["protocol_fee_eth"].sum())
total_partner_fee  = int(partner_payouts["partner_fee_eth"].sum())

# Partner fees are subject to a tax (partner_fee_tax)
total_partner_fee_taxed = sum(
    int(row["partner_fee_eth"] * (1 - row["partner_fee_tax"]))
    for _, row in partner_payouts.iterrows()
)
total_partner_fee_tax = total_partner_fee - total_partner_fee_taxed

# Remaining protocol fee goes to CoW DAO
net_protocol_fee = total_protocol_fee - total_partner_fee
Three transfer types are created from this:
  1. net_protocol_fee → sent to protocol_fee_safe (0x22af3D38E50ddedeb7C47f36faB321eC3Bb72A76) if positive
  2. total_partner_fee_tax → also sent to protocol_fee_safe (the CoW DAO cut of partner fees)
  3. Per-partner transferspartner_fee_eth * (1 - partner_fee_tax) sent to each partner’s address
for _, row in partner_payouts.iterrows():
    partner = row["partner"]
    partner_fee = int(row["partner_fee_eth"] * (1 - row["partner_fee_tax"]))
    assert partner_fee >= 0
    if partner_fee > 0:
        transfers.append(
            Transfer(token=None, recipient=Address(partner), amount_wei=partner_fee)
        )
All partner fee transfers are in the chain’s native token, not COW. The PARTNER_PAYOUTS_COLUMNS list is ["partner", "partner_fee_eth", "partner_fee_tax"].

The RewardAndPenaltyDatum class

This class is the central unit of computation for a single solver’s payout:
class RewardAndPenaltyDatum:
    def __init__(
        self,
        solver: Address,
        solver_name: str,
        reward_target: Address,          # recipient of COW rewards
        buffer_accounting_target: Address, # recipient of ETH reimbursements
        primary_reward_eth: int,
        slippage_eth: int,               # combined slippage + network_fee
        primary_reward_cow: int,
        quote_reward_cow: int,           # always >= 0
        service_fee: Fraction,           # e.g. Fraction(15, 100)
        reward_token_address: Address,
    ):
Instances are constructed from a DataFrame row via RewardAndPenaltyDatum.from_series(frame), which also merges slippage_eth and network_fee_eth into a single slippage_eth field:
@classmethod
def from_series(cls, frame: Series) -> RewardAndPenaltyDatum:
    slippage = int(frame["slippage_eth"]) + int(frame["network_fee_eth"])
    ...

Build docs developers (and LLMs) love