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.

The solver rewards pipeline divides time into discrete accounting periods — fixed-width windows over which all batch settlements, quote rewards, and slippage are aggregated before a single weekly payout is computed and posted.

Definition

An accounting period is a half-open interval of the form:
StartTime <= block_time < EndTime
Periods are always 7 days long by default. The pipeline aggregates every settlement whose block_time falls within this range, then produces a single set of transfers (COW + native token) to be executed via multisend. The string representation of a period uses the format YYYY-MM-DD-to-YYYY-MM-DD:
>>> str(AccountingPeriod("2024-01-09"))
'2024-01-09-to-2024-01-16'
This string is used as a filename suffix for output CSV files, for example:
transfers-mainnet-2024-01-09-to-2024-01-16-COW.csv
transfers-mainnet-2024-01-09-to-2024-01-16-NATIVE.csv

The AccountingPeriod class

The class lives in src/models/accounting_period.py and handles all date arithmetic and string conversions:
DATE_FORMAT = "%Y-%m-%d"

class AccountingPeriod:
    """Class handling the date arithmetic and string conversions for date intervals"""

    def __init__(self, start: str, length_days: int = 7):
        self.start = datetime.strptime(start, DATE_FORMAT)
        self.end = self.start + timedelta(days=length_days)

    def __str__(self) -> str:
        return "-to-".join(
            [self.start.strftime(DATE_FORMAT), self.end.strftime(DATE_FORMAT)]
        )

    def __hash__(self) -> int:
        """Turns (1985-03-10, 1994-04-05) into only the digits 1985031019940405"""
        return int(
            "".join([self.start.strftime("%Y%m%d"), self.end.strftime("%Y%m%d")])
        )

    def as_query_params(self) -> list[QueryParameter]:
        """Returns commonly used (start_time, end_time) query parameters"""
        return [
            QueryParameter.date_type("start_time", self.start),
            QueryParameter.date_type("end_time", self.end),
        ]
AccountingPeriod(start, length_days=7) accepts a date string in YYYY-MM-DD format and an optional length_days integer (default 7). It uses datetime.strptime to parse the start date and timedelta to compute the end date.
Produces a human-readable, filename-safe string like 2024-01-09-to-2024-01-16 by joining the formatted start and end dates with the literal -to- separator.
Converts the period into an integer by concatenating the %Y%m%d-formatted digits of both dates. For example, (1985-03-10, 1994-04-05) becomes 1985031019940405. This allows AccountingPeriod instances to be used as dictionary keys or in sets.
Returns a two-element list of QueryParameter objects (start_time and end_time) that are passed directly to Dune Analytics queries. This is the primary integration point between the accounting period and every parameterized Dune query.

Default period vs. custom periods

The pipeline is designed to run every Tuesday. The default accounting period is always the previous 7 days ending at today’s date:
# from src/utils/script_args.py
parser.add_argument(
    "--start",
    type=str,
    help="Accounting Period Start. Defaults to previous Tuesday",
    default=str(date.today() - timedelta(days=7)),
)
When the pipeline cannot run on its scheduled Tuesday, or when you need to reprocess a historical window, pass --start explicitly:
# Run for the period March 14–21, 2023
python -m src.fetch.transfer_file --start 2023-03-14
If you omit --start, the period start defaults to exactly 7 days before today. Make sure you are running the script on the correct day, or the period boundaries will be off by one or more days.

Block interval mapping

Block timestamps on EVM chains do not map 1:1 to wall-clock dates. The pipeline resolves this by querying Dune for the block numbers that correspond to the period’s start_time and end_time. This happens in DuneFetcher.__init__ immediately after the period is set:
# src/fetch/dune.py
class DuneFetcher:
    def __init__(
        self,
        dune: DuneClient,
        blockchain: str,
        period: AccountingPeriod,
    ):
        self.dune = dune
        self.blockchain = blockchain
        self.period = period
        # Resolve block numbers eagerly at construction time
        self.start_block, self.end_block = self.get_block_interval()

    def get_block_interval(self) -> tuple[str, str]:
        """Returns block numbers corresponding to date interval"""
        results = self._get_query_results(
            self._parameterized_query(
                QUERIES["PERIOD_BLOCK_INTERVAL"], self._network_and_period_params()
            )
        )
        assert len(results) == 1, "Block Interval Query should return only 1 result!"
        return str(results[0]["start_block"]), str(results[0]["end_block"])
The resolved block numbers are logged at startup:
Blockrange for accounting period 2024-01-09-to-2024-01-16 is from 18977000 to 19080000.
All subsequent Dune queries that operate on raw blockchain data use the resolved start_block/end_block rather than timestamps, ensuring consistency across chains with different block times.

Timing considerations

Several upstream data sources do not finalize immediately after a block is produced. The following data must settle before the pipeline produces accurate results:

prices.usd

Dune’s prices.usd table is populated from off-chain price feeds with a lag. Slippage values evaluated in ETH will be incorrect if the pipeline runs too soon after period end.

Transaction data

The ethereum.transactions table and on-chain event logs may not yet index the final blocks of the period.

Orderbook analytics

The analytics database (ANALYTICS_DB_URL) is populated by a separate dbt pipeline that runs on its own schedule.

Tuesday cadence

Payouts are executed each Tuesday. Running the script on Tuesday (one week after the period opened) gives all data sources time to finalize.
Do not run the payout script immediately after the accounting period ends. Wait for the data sources listed above to finalize. The README states: “we must wait some time after the period has ended for some data to finalize (e.g. prices.usd, ethereum.transactions, our event data, etc.).”