# BYOS — full documentation corpus
Generated from https://bleu.github.io/byos-docs. This specification is normative: where an implementation disagrees with it, the implementation is wrong.
---
# FILE: design-document.md
# BYOS design document
The normative specification for BYOS. Where an implementation disagrees with this document, the implementation is wrong — unless this document carries a dated revision note saying otherwise.
Vocabulary is defined once, in [the glossary](glossary). CoW protocol mechanics this design rests on are under [fee collection](reference/cow-fee-collection), [slashing policy](reference/cow-solver-slashing-policy), [auctions](reference/solver-auctions), and [CIPs](reference/solver-cips). Rationale for individual decisions lives with the code, in the ADRs of [`byos-contracts`](https://github.com/bleu/byos-contracts/tree/main/docs/adr) and [`byos-service`](https://github.com/bleu/byos-service/tree/main/docs/adr); those ADRs cite the sections below and do not restate them.
## Citable sections
These anchors are the interface between this document and every ADR that cites it. Treat them as stable: heading text may be reworded only in ways that preserve the anchor, and removing or renaming one is a breaking change that requires updating the citations in all three implementation repos.
| Anchor | Covers |
|---|---|
| [`#overview`](#overview) | What BYOS is, its components, how the protocol sees it |
| [`#order-flow`](#order-flow) | Value flow through a settlement, all three outcomes |
| [`#trampoline`](#trampoline) | The sandbox contract as a whole |
| [`#topology`](#topology) | One instance per sub-solver, CREATE2, deployment timing |
| [`#execution-authority`](#execution-authority) | Who may call `execute`, and what it verifies |
| [`#escrow`](#escrow) | Collateral ledger as a whole |
| [`#escrow-roles`](#escrow-roles) | Owner, operator, submitter |
| [`#withdrawal-and-freeze`](#withdrawal-and-freeze) | Cooldown, all-or-nothing exit, freeze, pause |
| [`#proposal-schema`](#proposal-schema) | The EIP-712 signed struct and domain |
| [`#proposal-api`](#proposal-api) | HTTP surface, authentication, rate limiting |
| [`#proposal-lifecycle`](#proposal-lifecycle) | State machine, simulation, retention |
| [`#solver-engine`](#solver-engine) | Selection, scoring, settlement crafting |
| [`#single-order-solutions`](#single-order-solutions) | One order per proposal, per settlement |
| [`#gas`](#gas) | The gas cut and how CoW fees actually work |
| [`#penalties`](#penalties) | The penalty schedule as a whole |
| [`#track-a`](#track-a) | Revert, deadline, and non-settlement debits |
| [`#track-b`](#track-b) | EBBO and fairness passthrough |
| [`#attribution`](#attribution) | Mapping a settlement back to a sub-solver |
| [`#residue`](#residue) | Surplus custody and stray tokens |
## Implementation status
What is specified here versus what exists today. `n/a` means the section does not constrain that repo.
| Section | byos-contracts | byos-service (Rust) | byos-service-ts |
|---|---|---|---|
| [`#order-flow`](#order-flow) | implemented | implemented | implemented |
| [`#topology`](#topology) | implemented | implemented | implemented |
| [`#execution-authority`](#execution-authority) | implemented | implemented | implemented |
| [`#escrow`](#escrow) | implemented | partial | partial |
| [`#proposal-schema`](#proposal-schema) | implemented | implemented | implemented |
| [`#proposal-api`](#proposal-api) | n/a | implemented | implemented |
| [`#proposal-lifecycle`](#proposal-lifecycle) | n/a | implemented | implemented |
| [`#solver-engine`](#solver-engine) | n/a | implemented | implemented |
| [`#gas`](#gas) | n/a | implemented | implemented |
| [`#penalties`](#penalties) | implemented | partial | partial |
| [`#residue`](#residue) | implemented | n/a | n/a |
Both services are `partial` on escrow and penalties because Track B operations (freeze, unfreeze) are triggered by hand in v1 rather than by an automated flow.
## Overview
BYOS is a bonded CoW solver that does not compute routes. It sells its solver seat as a service: any external party may submit a signed route for a specific order, backed by collateral, and BYOS bids the best one it holds. From the protocol's side nothing is unusual — BYOS is one ordinary bonded solver, and the sub-solver relationship is entirely internal.
### Why BYOS exists
Becoming a CoW solver today is a gated process:
| Requirement | Standard pool (CIP-7) | Reduced pool (CIP-44) |
|---|---|---|
| Capital | $500,000 in stablecoins + 1,500,000 COW | $50,000–$100,000 + 500,000–1,000,000 COW |
| Governance | Deploy a Gnosis Safe with CoW DAO as sole signer | Same Safe requirement |
| Vouching | Vouched by an existing solver or the DAO | Core-team approval required |
| Onboarding | Shadow competition and testing on Sepolia before mainnet access | Same requirement |
| Compliance | KYC through the vouching solver's pool | Same |
An external router that can find good routes has no way to participate without a bonding pool willing to vouch for it and significant locked capital. BYOS drops the barrier to a collateral deposit sized to cover one worst-case revert penalty (`gas + c_l`) and the ability to sign an EIP-712 message and return a route.
### Responsibility split
| | Sub-solver | BYOS |
|---|---|---|
| **Route computation** | Responsible | Not involved |
| **Transaction submission** | Not involved | Responsible (via CoW driver) |
| **Scoring and auction bidding** | Not involved | Responsible |
| **Revenue from own venue fees** | Keeps any fees their route earns at the DEX level (e.g., LP fees on a pool they operate) | Not involved |
| **In-route surplus capture** | May capture surplus inside the route before the sweep ([details](#residue)) | Keeps uncaptured surplus as settlement slippage |
| **Gas cut** | Not charged directly | Retains the estimated gas cost on every settled trade ([details](#gas)) |
| **CoW solver rewards** | None in v1 — no reward pass-through | Retains 100% of CoW rewards earned under its bonded solver seat |
| **Escrow risk** | Bears Track A (revert) and Track B (EBBO) penalties | Absorbs shortfall when escrow is insufficient |
### Components
Three components carry that:
1. **Contracts** — an **Escrow** holding sub-solver collateral, and a per-sub-solver **Trampoline** that executes routes in a fund-less sandbox. Immutable, no proxies, no upgrade keys.
2. **Service** — a proposal API where sub-solvers submit signed routes, a solver engine answering the CoW driver's `/solve`, and background workers for validation, settlement outcomes, and escrow operations.
3. **Policy** — the penalty schedule and attribution model that lets BYOS recover from a sub-solver what CoW charges BYOS.
BYOS requires **no changes to the CoW auction or competition**. It is a black box to the protocol and a vanilla solver engine to the driver, which CoW itself runs under the bonding pool arrangement.
The design problem is that CoW's safety model does not fit. `settle` is `onlySolver`, gated by a manager-curated allowlist; vouched solvers post a bond; a circuit breaker slashes or jails misbehavior. CoW trusts a permissioned, bonded set and punishes them rather than constraining what interactions may do. Sub-solvers are permissionless and unbonded — exactly the actor that model refuses to let near `settle`.
So BYOS rebuilds the boundary structurally rather than socially. The Trampoline replaces the `onlySolver` allowlist with a sandbox. Escrow replaces the DAO bond. Debit and slash replace circuit-breaker slashing.
## Order flow
How a single order moves through `GPv2Settlement` and a sub-solver's Trampoline instance, and how the outcomes differ.
Actors:
- **BYOS driver** — builds and submits the settlement, authoring the funding transfer and the `execute` call. Run by the CoW core team.
- **`GPv2Settlement`** — CoW's settlement contract. Holds funds; runs intra-interactions as itself.
- **Trampoline** — the sub-solver's instance. Fund-less at rest, with no allowance over the settlement.
- **Route venues** — the DEXes the route hits.
- **Sub-solver** — signs the route offline (EIP-712), never executes on-chain.
The funding transfer and `execute` are separate interactions because they run in different `msg.sender` contexts. The transfer-in runs as the settlement, which owns the funds; the route runs as the Trampoline. That split keeps the route from ever holding the settlement's spend authority.
Inside `execute`, the instance records the settlement's buy-token balance, runs the sub-solver's route, sweeps its own full remaining balance of both trade tokens to the settlement, and reverts unless the settlement's buy-token balance grew by at least `buyAmount` — the signed floor. The sweep and the check are Trampoline contract code; the sub-solver supplies only the route.
### Happy path
The route produces at least `buyAmount` of buy token. The sweep pushes everything the instance holds back to the settlement, the delta check passes, the settlement pays the user, and BYOS's buffer nets to zero. Anything above the floor is not stranded and not sub-solver property: it sits in the settlement as BYOS-owned slippage, returned by CoW's weekly accounting.
```mermaid
sequenceDiagram
autonumber
participant D as BYOS Driver
participant S as GPv2Settlement
participant T as Trampoline
participant R as Route venues
Note over D: Route signed offline (EIP-712)
D->>S: settle(batch)
S->>S: pull user sellAmount into Settlement
S->>T: sellToken.transfer(trampoline, sellAmount)
S->>T: execute(proposal, route, sellToken, buyToken, signature)
T->>T: onlySettlement + submitter + validUntil + signature checks
T->>T: record Settlement's buyToken balance
T->>R: run route interactions
R-->>T: buyToken produced (>= buyAmount)
T->>S: sweep full buyToken + sellToken balances
T->>T: assert Settlement buyToken delta >= buyAmount
S->>S: transferToAccounts pays the user
S-->>D: settle() succeeds
Note over S: anything above the floor stays here as
BYOS-owned slippage, returned weekly
```
A route may also deliver output to the settlement directly instead of to the instance. The delta check measures what the settlement actually received, so both shapes pass.
### Shortfall
The delta check fails and reverts the whole settlement. No trade, and BYOS's buffer is untouched. Below `buyAmount` nothing settles: the guard is an explicit assertion on the settlement's balance growth, so it also catches routes that deliver output somewhere other than the settlement.
```mermaid
sequenceDiagram
autonumber
participant D as BYOS Driver
participant S as GPv2Settlement
participant T as Trampoline
participant R as Route venues
D->>S: settle(batch)
S->>T: sellToken.transfer(trampoline, sellAmount)
S->>T: execute(proposal, route, sellToken, buyToken, signature)
T->>T: record Settlement's buyToken balance
T->>R: run route interactions
R-->>T: buyToken produced (< buyAmount)
T->>S: sweep full buyToken + sellToken balances
T--xT: delta check fails: balance grew less than buyAmount
S--xD: settle() reverts, no state change
Note over D: buffer never net-drained,
sub-solver eats the Track A debit
```
### Buy orders
Same mechanism, different slack. A buy order fixes the user's output, so the input is over-provisioned: the full signed `sellAmount` is pushed in, the route consumes only what it needs, and the sweep returns the unconsumed sell token to the settlement along with the output. The delta check is identical — the settlement's buy-token balance must grow by at least the floor, which for a buy order covers the user's exact `buyAmount`.
```mermaid
sequenceDiagram
autonumber
participant D as BYOS Driver
participant S as GPv2Settlement
participant T as Trampoline
participant R as Route venues
D->>S: settle(batch)
S->>S: pull user's executed sell amount
(fee wedge included, stays in Settlement)
S->>T: sellToken.transfer(trampoline, sellAmount) — raw signed input
S->>T: execute(proposal, route, sellToken, buyToken, signature)
T->>R: run route: consumes part of the input
R-->>T: buyToken produced (>= buyAmount)
T->>S: sweep: all buyToken + unconsumed sellToken
T->>T: assert Settlement buyToken delta >= buyAmount
S->>S: transferToAccounts pays the user exactly buyAmount
S-->>D: settle() succeeds
```
Nothing above is specific to an order kind. For either kind the instance receives the signed `sellAmount`, runs the route, sweeps both trade tokens, and `execute` asserts the same buy-token delta floor. What changes is which amount the user fixed, and therefore where the slack shows up:
| | Sell order | Buy order |
|---|---|---|
| User fixes | `sellAmount`; the route normally consumes all of it | `buyAmount`, the exact amount owed to the user |
| Floor means | the minimum output the sub-solver commits to deliver | at least the user's exact `buyAmount` |
| Typical leftover | buy-token over-delivery above the floor | unconsumed sell token, returned by the sweep |
The mechanism also covers same-token hook orders (`sellToken == buyToken`, always with `sellAmount > buyAmount`), where the user submits the order mainly to run hooks and the difference funds them. The delta check stays sound because the snapshot is taken after the funding transfer has already left `GPv2Settlement`: the sweep returning the unconsumed input is the delivery it measures, and the floor still guarantees the settlement is never net-drained. The shared token is swept once, and `execute` must not reject equal addresses.
### The outcomes at a glance
| Route delivery vs floor | Delta check | Settlement | Extras (surplus, unconsumed input) |
| --- | --- | --- | --- |
| Exactly the floor | passes | succeeds | none |
| Above the floor | passes | succeeds | swept to the settlement; BYOS-owned slippage, returned weekly |
| Below the floor | reverts | reverts | none — no trade |
Where the fee wedge sits for each order kind, with worked numbers, is in [`reference/cow-fee-collection`](reference/cow-fee-collection) and summarized under [`#gas`](#gas).
## Trampoline
### Topology
**One Trampoline instance per sub-solver address.** Instances live at a deterministic CREATE2 address keyed by the sub-solver address recovered from the proposal's EIP-712 signature. Counterfactual, no registry, no governance step: the address is computed, not tracked.
A Trampoline is needed at all because in `GPv2Settlement.settle`, every interaction executes as a bare `call` from the settlement contract. `msg.sender` is `GPv2Settlement`, which holds all buffers and can be made to grant any approval; the only target it hard-blocks is the vault relayer. Permissionless sub-solver code must never run in that context. The Trampoline re-runs the interactions as itself, in a fund-less context.
Containment is structural, not filtered. A recognize-and-block approve filter cannot carry the boundary, because "grant an allowance" has shapes a filter misses — `Permit2.approve` uses a different target and selector yet still grants a drainable allowance on a real token. What a sub-solver cannot get around is a contract that holds no funds and where each sub-solver reaches only its own instance.
Topology governs what happens to persistent state. Because the Trampoline runs sub-solver-authored calls as itself, it grants ERC-20 approvals to sub-solver-chosen targets and may retain dust. An exploit needs both a planted approval and a resting balance, and an approval over an empty contract drains nothing.
**The instance ends every settlement holding none of the trade tokens.** That post-condition is the leak-prevention control, and it is enforced by contract code:
1. `GPv2Settlement` transfers exactly `sellAmount` of `sellToken` into the instance.
2. The instance runs the sub-solver interactions.
3. The instance sweeps its full remaining balance of both trade tokens back to `GPv2Settlement`.
4. `execute` reverts unless the settlement's buy-token balance grew by at least `buyAmount`.
Approvals are not reset to zero. The enforced invariant is zero balance at rest rather than zero approvals, because approvals are per-`(token, spender)` over an unbounded, sub-solver-authored set and cannot be generically enumerated to reset, whereas balance is directly assertable. With the instance fund-less at rest and isolated per sub-solver, a standing or over-broad approval drains nothing belonging to the protocol or another sub-solver. BYOS-encoded approvals to known routers may be left standing and reused across that sub-solver's future settlements. Failed settlements revert atomically, rolling back any approval set in the attempt.
As defense in depth, BYOS authors the approvals itself: exact `sellAmount`, route-derived, granted only to the venues the route uses, and it rejects obvious sub-solver-authored approve-like calls at gatekeeping. This is best-effort by design, since "approve-like" is not one selector, and per-instance isolation plus the sweep remains the backstop.
Native ETH follows the same rule. The instance performs any required WETH wrap or unwrap internally, within the single settlement, and any ETH balance remaining afterwards is swept back or the settlement reverts.
**Deployment happens at escrow-deposit time, paid by the sub-solver.** `Escrow.deposit()` triggers the factory's idempotent `ensureDeployed` for the credited sub-solver. Settlements assume the instance exists; there is no on-chain existence guard in the hot path. Since the API is permissionless but collateral-gated, no escrow deposit means no valid proposal, so a valid proposal implies a deployed trampoline. The only residual is a reorg of the deposit transaction, handled as an infra failure ([`#track-a`](#track-a)).
Per-instance isolation earns its keep on three things a shared trampoline cannot offer: it confines any un-sweepable residual to its originating sub-solver, it permits safe approval reuse for gas, and it gives on-chain attribution ([`#attribution`](#attribution)).
### Execution authority
`execute` is callable only when all of the following hold:
- `msg.sender == GPv2Settlement` — a settlement context.
- `tx.origin` holds the Escrow's `SUBMITTER_ROLE` — a settlement submitted by BYOS.
- The sub-solver's EIP-712 signature over the route verifies, and `validUntil` has not passed.
**Signature-gating** exists so a reverted settlement self-evidences exactly what the sub-solver authorized: the signed data is in the calldata, recoverable from the transaction. This makes Track A debits verifiable by any third party rather than only by BYOS. Without it, BYOS could substitute different interactions, submit a settlement that reverts, and debit the sub-solver for a fault it manufactured.
**The submitter gate** exists because once BYOS settles a proposal, its signature and route are public calldata. While `validUntil` is live, any other allow-listed CoW solver could replay or front-run the `execute` in its own settlement, rerunning the signed route outside BYOS's control and muddying attribution. `SUBMITTER_ROLE` is granted by the Owner on the Escrow, which therefore acts as the submitter registry for its contract generation. It covers both the allow-listed solver EOA and, for CoW's `Solver7702Delegate` parallel path, each approved auxiliary account — there the auxiliary account, not the solver EOA, is `tx.origin`.
Rotation is a `grantRole` or `revokeRole` call on the Escrow, not a redeploy. The submitter set must stay in sync with the 7702 delegate's approved callers: those are immutable constructor arguments, so rotating an auxiliary key means a new delegate deploy, a fresh EIP-7702 authorization, and matching role changes. An auxiliary account missing its grant fails settlements at the Trampoline; it does not create risk.
The instances are therefore not dependency-free: `execute` performs two staticcalls into the Escrow per settlement. A compromised Owner could block settlements by revoking all submitters, which is no worse than the pre-existing Owner trust.
## Escrow
A per-chain, native-token **ERC20 contract** holding sub-solver collateral keyed by sub-solver address. Tokens are minted 1:1 with deposited ETH and burned on withdrawal or debit. `balanceOf` is the single source of truth, with the invariant `totalSupply() + accumulatedDebits == address(this).balance`.
Anyone may deposit for a sub-solver. The sub-solver withdraws subject to a cooldown. BYOS holds an exclusive debit function. This collateral is the *only* sub-solver capital BYOS touches — trade capital flows atomically through `GPv2Settlement` into the Trampoline and back.
**Authorization is blanket, not per-proposal.** Depositing grants BYOS standing debit authority up to the sub-solver's balance. On-chain EIP-712 verification per debit was rejected: it adds gas and complexity for marginal benefit, since the operator is already a trusted role and sub-solvers have an off-chain relationship with BYOS.
The contract is a **dumb ledger**. It enforces bounds — who may debit, cooldown, pause, freeze, transfer restrictions — but never the correctness of a debit's reason. Reserve calculations, proposal eligibility, and transfer-chain debit caps live in the service.
**Deployment is immutable.** No proxy, no upgrade key. A v2 means a new deployment. The Escrow's constructor deploys the Trampoline factory itself, taking the `GPv2Settlement` address rather than a factory address: instances bind to the Escrow as their submitter registry, and the factory needs the Escrow address before the Escrow could otherwise exist. Escrow, factory, and EIP-712 domain therefore form one deployment generation.
### Escrow roles
- **Owner** — a secure wallet, multisig or Safe. Sets the operator, configures the cooldown, grants and revokes `SUBMITTER_ROLE`, transfers ownership, and receives all debited funds. Ownership transfer is two-step (`transferOwnership` then `acceptOwnership` by the nominee) so an address typo cannot brick the contract.
- **Operator** — an EOA living in the BYOS service, for automated operation: `debit`, `freeze`, `unfreeze`, `pause`, `unpause`. Cannot withdraw funds or change configuration.
- **Submitter** (`SUBMITTER_ROLE`) — the EOAs the service submits settlements from. Holds no escrow authority at all; the role exists only because `Trampoline.execute` gates on `tx.origin` ([`#execution-authority`](#execution-authority)).
The operator's key is the exposed one, since it lives in the service. If it is compromised, the attacker can debit sub-solver balances, but those funds go to the Owner, not the attacker, and the Owner can replace the operator immediately. That bounds a key compromise to griefing rather than theft. Granting submitters is deliberately Owner-only: giving the operator that power would let a compromised operator authorize a rogue submitter, pass the Trampoline's gate, and replay signed routes.
`withdrawDebits()` is callable by anyone, and funds always go to the Owner address. That allows automated sweeping by keepers or the service without the Owner's cold wallet signing.
### Withdrawal and freeze
Withdrawal is **all-or-nothing with a cooldown**:
- `requestWithdrawal()` — the sub-solver signals intent to withdraw the entire balance. Effective balance drops to zero immediately, so the sub-solver is offline for new proposals. The cooldown clock starts.
- `executeWithdrawal()` — after the cooldown expires, the full remaining balance is withdrawn. No partial withdrawals.
- `cancelWithdrawal()` — aborts the request, effective balance restores. Callable regardless of freeze state, since funds staying in the contract is always safe.
All-or-nothing eliminates balance fragmentation and the withdraw-after-known-revert race. A sub-solver reducing its position does a full cycle.
**Freeze** is per-address, operator-controlled, and blocks withdrawal execution and ERC20 transfers in both directions while a Track B investigation is open. It does not affect effective balance — reserve logic is a service concern. Deposits to a frozen address are allowed, so collateral can be topped up during an investigation. A pending withdrawal request survives a freeze: after unfreeze the sub-solver can execute immediately, with the cooldown already served. There is no on-chain freeze timeout or dispute mechanism; the Owner can replace an unresponsive operator.
**Pause** is global and operator-triggered, blocking all transfers and withdrawal executions. Every token movement flows through the ERC20 `_update` hook:
| | Paused | Sender frozen | Receiver frozen | Sender withdrawing | Receiver withdrawing |
|---|---|---|---|---|---|
| Transfer | blocked | blocked | blocked | blocked | blocked |
| Mint | allowed | n/a | allowed | n/a | blocked |
| Burn | no restriction | no restriction | n/a | no restriction | n/a |
Burns carry no `_update` restriction because the calling function enforces its own access control: `debit` must work during a pause and against frozen addresses so the operator can act during an incident, while `executeWithdrawal` independently checks not-frozen, not-paused, and cooldown-elapsed.
The incident response flow is: pause, trace tainted addresses through `Transfer` event history, freeze each identified address, unpause so legitimate sub-solvers resume, then debit the frozen addresses at leisure. The pause window should be minutes. Deposits stay open throughout.
**Transfers exist for key rotation.** A sub-solver calls `transfer(newAddress, fullBalance)`; BYOS detects it, updates its mapping, and a new Trampoline is deployed for the recipient (both transfer functions call `ensureDeployed`). This avoids the uncollateralized gap a withdraw-and-redeposit cycle would open. The security cost is that transfers enable debit evasion, mitigated by pause, freeze, and off-chain tracing: when `debit(A, amount)` hits an insufficient balance, the service traces A's outbound `Transfer` events and debits recipients up to what they received from A. That cap is enforced off-chain; the operator's blanket authority is unchanged. A consequence worth naming: a malicious sub-solver can send tokens to an innocent address and make it a debit target.
The token is deliberately transfer-restricted and will not integrate with DeFi protocols. It represents escrowed collateral, not a tradeable asset.
**Reserve and FX policy is off-chain.** There is no on-chain reserve multiplier. For Track B, the service converts the claim amount to a native-token equivalent via CoW's quote API, the operator debits that amount, and the service tracks a 5× reserve off-chain against pending claims, reducing the sub-solver's service-level effective balance. That buffer covers token appreciation over an investigation window of up to three months; beyond 5×, BYOS absorbs the tail. The multiplier is a service parameter, tunable without a contract change.
There are **no on-chain dispute mechanisms** — no per-debit caps, no freeze timeouts, no challenge windows. Disputes are handled off-chain ([`#penalties`](#penalties)).
### Escrow interface
The authoritative signatures and natspec live in [`src/interfaces/`](https://github.com/bleu/byos-contracts/tree/main/src/interfaces) in the contracts repo. The shape:
```solidity
// Owner-only
function setOperator(address newOperator) external;
function setCooldownPeriod(uint256 period) external;
function transferOwnership(address newOwner) external;
function acceptOwnership() external; // only the pendingOwner
// Operator-only
function debit(address subSolver, uint256 amount, bytes32 reason) external;
function freeze(address subSolver) external;
function unfreeze(address subSolver) external;
function pause() external;
function unpause() external;
// Sub-solver
function requestWithdrawal() external;
function executeWithdrawal() external;
function cancelWithdrawal() external;
// Anyone
function deposit(address subSolver) external payable; // also ensures the Trampoline exists
function withdrawDebits() external; // always pays the Owner
// Views
function balanceOf(address subSolver) external view returns (uint256);
function effectiveBalance(address subSolver) external view returns (uint256);
function withdrawableBalance() external view returns (uint256);
```
`effectiveBalance(S)` is zero when a withdrawal is pending, otherwise `balanceOf(S)`. Freeze does not affect it. `withdrawableBalance()` is the accumulated debit pool available to the Owner.
Events are the audit trail and the public record of every penalty action: `Deposited`, `Debited`, `Withdrawn`, `Frozen`, `Unfrozen`, `OperatorUpdated`, `DebitsWithdrawn`, `WithdrawalRequested`, `WithdrawalCancelled`, `CooldownPeriodUpdated`, plus the two ownership-transfer events. On-chain state is kept minimal by design; cumulative history comes from events.
## Proposal schema
The EIP-712 typed data a sub-solver signs. This struct is verified twice: by the service at ingestion, and on-chain by the Trampoline at settlement. What the service accepts is therefore exactly what the sub-solver consented to execute.
```solidity
struct ProposalData {
bytes32 orderUidHash; // keccak256(order_uid) — ties to a specific order
uint256 sellAmount; // route consumption the instance receives (raw, pre-fee)
uint256 buyAmount; // floor the route must deliver to the settlement (raw, pre-fee)
bytes32 interactionsHash; // keccak256(abi.encode(interactions)) — the route
uint256 validUntil; // expiry timestamp
uint256 nonce; // unique salt for signature uniqueness
}
```
```solidity
Eip712Domain {
name: "BYOS",
version: "0.1",
chainId: ,
verifyingContract:
}
```
**Amounts are raw pre-fee quotes.** `sellAmount` is the route's consumption; the fee wedge the user pays on top stays in the settlement and is never forwarded. `buyAmount` is a floor, enforced by the balance-delta check ([`#order-flow`](#order-flow)), not an exact amount. Disputes compare on-chain outcomes against the signed amounts after applying the driver's deterministic fee shift ([`#gas`](#gas)).
**`interactionsHash` is required.** Without it, BYOS could substitute different interactions while presenting the same signed amounts, then blame the sub-solver for the resulting revert. The Trampoline verifies `keccak256(abi.encode(interactions)) == interactionsHash` before executing, so substituted interactions fail signature verification. This differs from CoW order signatures, which do not sign interactions, because the threat model is inverted: sub-solvers need protection against the operator, not against the execution path.
**There is no `escrow_account` field.** The recovered signer address *is* the escrow key, and the Trampoline CREATE2 salt. One address is load-bearing three ways. Delegation — sign with key K, collateral from account E — would complicate the Escrow's dumb-ledger design and is a v2 concern. A sub-solver running multiple strategies deposits separately per address. Key rotation moves collateral by ERC20 transfer ([`#withdrawal-and-freeze`](#withdrawal-and-freeze)) and gets a new Trampoline instance.
**The nonce is a unique salt with no enforcement**, on-chain or off-chain. It makes each proposal's EIP-712 hash distinct; there is no ordering or uniqueness rule. Fill tracking alone would not prevent replay of `execute`, since a settlement need not include the order at all, so a third party could rerun a live proposal in a tradeless settlement. Third-party replay is blocked by the submitter gate instead ([`#execution-authority`](#execution-authority)). Replay by BYOS's own submitter remains possible by design: BYOS is trusted not to resubmit, `validUntil` bounds the window and is enforced on-chain, and a filled order cannot be settled again. Keeping the Trampoline storage-free is worth more than an on-chain nonce mapping.
**The payload is raw interactions**, `Vec<{target, value, calldata}>` — arbitrary calls against any DEX or protocol, executed as-is. Structured routes would let BYOS author every call and forbid sub-solver approvals outright, but they would kill any-DEX generality and require BYOS to maintain a venue registry. Containment is the Trampoline's job, structurally. The sub-solver is fully responsible for the complete route, including required hooks and approvals; BYOS can accept or reject at gatekeeping, never patch.
The **factory is a domain anchor**. Binding `verifyingContract` to the TrampolineFactory cleanly separates contract generations: v1 signatures do not verify against a v2 factory. A factory redeployment invalidates all outstanding signatures, so sub-solver clients must update their domain configuration.
## Proposal API
The public HTTP surface by which sub-solvers submit signed proposals. Field-level types, status codes, and error shapes are specified in [`crates/byos/openapi.yml`](https://github.com/bleu/byos-service/blob/main/crates/byos/openapi.yml) in the service repo, which is the authority for the wire contract; this section specifies its semantics.
| Endpoint | Purpose |
|---|---|
| `POST /proposals` | Submit a signed proposal. Answers `202 Accepted` with an id. |
| `GET /proposal/{id}` | The caller's own proposal, including status and any rejection reason. |
| `GET /proposals/{order_uid}` | The caller's own proposals on that order. |
| `GET /proposals/by-sub-solver` | All of the caller's proposals. |
| `DELETE /proposal/{id}` | Cancellation by the original signer. |
`POST` does not carry token addresses. The orderbook order is the single source of truth for them, which removes a lying-client hazard.
**The recovered signer is the identity.** There are no API keys, sessions, or accounts. Callers are sub-solver servers over TLS, not browsers.
**Every read is authenticated and owner-scoped.** GET endpoints require an EIP-712 signature in the `X-Signature` header, and the recovered signer scopes the response — competitors' proposals are invisible, and even "which addresses are competing on which order" does not leak. The signed message is a dedicated type owned by the service and never verified on-chain:
```solidity
struct ReadAuth {
uint256 version; // pinned to 1
}
```
It is a bearer signature: signed once, sent on every request, with no timestamp, nonce, or path binding. The blast radius of a leak is read access to the signer's own proposals — no writes, no cancellation, no funds — and a distinct typehash prevents replaying it as a submission or cancellation. A timestamp window would make external teams' clock drift a support burden; a nonce set would be the first per-signer auth state in the service. `version` exists because EIP-712 structs need at least one field, and bumping it invalidates all outstanding read tokens.
**Non-owners get 404, not 403**, on both `GET /proposal/{id}` and `DELETE`. A 403 would be an existence oracle: anyone could probe ids and learn how many proposals are live. The ownership check runs before the liveness check, so `DELETE`'s 409 for an already-terminal proposal is only ever seen by the owner.
**Cancellation is signed, by server-assigned id**, using another service-owned type in the same domain:
```solidity
struct CancelProposal {
uint256 proposalId;
}
```
Proposals are immutable, so there is no update operation and no `PUT`. Replacement is a new `POST`, optionally preceded by a `DELETE`.
**Ingestion is asynchronous.** The request path does three things inline: parse, `ecrecover`, and check the expiry window. On success the proposal is stored as `Submitted` and answered `202` — meaning "accepted for validation", not "accepted". Signature and expiry-window failures reject synchronously with a typed 4xx, since there is no point storing and auditing a proposal that is dead on arrival. All on-chain work, the escrow balance check and simulation, runs in a background validator loop ([`#proposal-lifecycle`](#proposal-lifecycle)). Sub-solvers poll for the verdict, and a rejection carries a machine-readable typed reason.
That means a `2xx` from `POST` is not acceptance. Integration code that treats it as acceptance is wrong. Verdict latency is bounded by the validator tick interval, not by the request round-trip.
**Rate limiting is two-layer and escrow-tiered.** A coarse per-IP limit plus a service-wide ceiling sheds floods before any cryptography; a per-signer limit applies after `ecrecover`, scaled by escrow balance tier, and signers below the minimum escrow are rejected outright. The escrow balance behind the second layer is cached with a short TTL so the request path does no RPC. The two limits are operational tuning parameters; what this document fixes is the two-layer structure. Well-capitalized sub-solvers get higher throughput, which is consistent with the collateral-gated permission model.
The reject-early pipeline, split across the sync and async boundary:
| # | Stage | Where |
|---|---|---|
| 1 | IP filter | request path |
| 2 | Parse + `ecrecover` | request path |
| 3 | Expiry-window check | request path |
| 4 | Signer rate limit | request path |
| 5 | Cached escrow tier check (in-memory) | request path |
| 6 | Authoritative escrow balance check (RPC) | background validator |
| 7 | Gatekeeping + simulation | background validator |
**Two listeners, one process.** A public port serves `/proposals`; a firewalled internal port serves `/solve` and `/notify`. They never share a socket, because their trust boundaries are opposite: the proposal API must be internet-reachable, while a `/solve` response is the full standing proposal book for an auction — amounts, routes, and signatures, all MEV-relevant. Origin is enforced by network topology rather than path obscurity; an optional bearer token on `/solve` is defense in depth, not a replacement. The split also prevents public traffic from starving the latency-critical path.
**Persistence is Postgres, in two tables with different jobs.** The `proposals` table holds what *is* — current state, single source of truth, read and written by `GET`, `/solve`, `/notify`, and the validator. The append-only `audit_events` table holds what *happened*, written behind an unbounded channel by a separate task, and is the dispute evidence for Track B claims that arrive up to three months later. Emission is in the store by construction, so a new mutation path cannot forget to leave evidence. The service refuses to start without a reachable database and applied migrations, retries forever during a runtime outage, and drains the queue on shutdown. There is no deletion path for the audit log.
The write-behind leaves a small crash window: an audit event is emitted only after its proposal write commits, so a crash between the two leaves a durable state change with no matching event. Closing it would couple every store write to the audit codec and remove the writer's retry isolation. Accepted, at one event per crash.
## Proposal lifecycle
### States
```mermaid
stateDiagram-v2
[*] --> Submitted: POST /proposals
Submitted --> Active: first simulation passes,\nscore > 0
Submitted --> Rejected: gatekeeping fails\n(escrow, envelope, unprofitable, ...)
Submitted --> SimFailed: simulation reverts
Active --> Active: re-simulation each tick\n(updates gas)
Active --> SimFailed: re-simulation reverts
Active --> Rejected: escrow re-check fails
Active --> Executing: driver SettlementStarted
Submitted --> Expired: validUntil passed
Active --> Expired: validUntil passed
Submitted --> Cancelled: DELETE /proposals/{id}
Active --> Cancelled: DELETE /proposals/{id}
Executing --> Settled: driver Success
Executing --> SettleFailed: driver Revert
Executing --> Active: driver Cancelled/Expired/Fail,\nor executing timeout
SettleFailed --> Penalized: Track A escrow debit lands
Settled --> [*]
Penalized --> [*]
```
A state answers exactly one question: what does the service do with this proposal right now?
| State | Simulated | Offered to `/solve` | Expiry sweep | Cancellable | Retention |
|---|---|---|---|---|---|
| `Submitted` | first pass | no | yes | yes | live |
| `Active` | every tick | yes | yes | yes | live |
| `Executing` | no | no | no | no | live |
| `Rejected` | no | no | — | no | 1 hour |
| `SimFailed` | no | no | — | no | 1 hour |
| `Expired` | no | no | — | no | 1 hour |
| `Cancelled` | no | no | — | no | 1 hour |
| `Settled` | no | no | — | no | indefinite |
| `SettleFailed` | no | no | — | no | indefinite |
| `Penalized` | no | no | — | no | indefinite |
Every transition:
| From | To | Trigger |
|---|---|---|
| — | `Submitted` | `POST /proposals`: signature verified, expiry window OK. |
| `Submitted` | `Active` | First validation passes: escrow check, envelope check, simulation succeeds, score > 0. Writes gas, trampoline address, token addresses. |
| `Submitted` | `Rejected` | A gatekeeping rule fails: insufficient escrow, unsupported order, amount mismatch, order not found, or unprofitable. Carries the typed reason. |
| `Submitted` | `SimFailed` | First simulation reverts. |
| `Active` | `Active` | Re-validation tick refreshes the gas estimate. No status change, no audit event. |
| `Active` | `Rejected` | Escrow re-check fails; the balance dropped below the threshold. |
| `Active` | `SimFailed` | Re-simulation reverts: the order filled or expired on-chain, the route broke, balances moved. |
| `Submitted`, `Active` | `Expired` | `validUntil` is behind the clock. |
| `Submitted`, `Active` | `Cancelled` | Signed `DELETE` by the owner. `DELETE` against any other state is a 409. |
| `Active` | `Executing` | Driver `SettlementStarted`: our solution won and the transaction is being submitted. |
| `Executing` | `Settled` | Driver `Success`. Transaction hash recorded. |
| `Executing` | `SettleFailed` | Driver `Revert`. Transaction hash recorded; the Track A debit follows. |
| `Executing` | `Active` | Driver `Cancelled`, `Expired`, or `Fail` (submission abandoned, no transaction landed), or the executing timeout elapsed. Queues the non-settlement debit. |
| `SettleFailed` | `Penalized` | The Track A escrow debit lands on-chain. Penalty transaction hash recorded. |
**Losing an auction is not a state.** A proposal outscored internally, or whose solution lost the external competition, is still valid and keeps competing. Participation is recorded as data, so "which auctions did this compete in and lose" is a query, not a status. Winning *is* a state change, because it changes what the service does: it must stop offering the proposal and stop re-simulating it.
`Executing` is entered on `SettlementStarted`, not at `/solve` time, because at `/solve` time we do not yet know we won. It is exempt from the expiry sweep on purpose — the chain enforces the order's real deadline. Two safety properties make the state recoverable: `Executing` to `Active` is always safe, because if the order was actually consumed the next re-simulation reverts and the proposal dies; and an executing timeout returns a stuck proposal to `Active`, covering lost notifications and restarts mid-settlement. Re-simulation is the truth-teller.
Transitions are compare-and-swap. Zero rows affected means the caller's verdict was stale, because a cancellation or a notification won the race.
**Terminal retention has one knob.** Rejected, sim-failed, expired, and cancelled rows are deleted an hour after reaching the state; consumers are polling loops that observe a terminal state within one interval, and after that the proposal is a 404. The money states — settled, settle-failed, penalized — are kept indefinitely, with no sweep code at all. `audit_events` has no deletion path.
### Settlement outcomes
Outcomes come from the stock CoW driver's `/notify` protocol. There is no chain watcher and no driver fork.
| Notification | Effect |
|---|---|
| `SettlementStarted` | `Active` to `Executing` |
| `Success { transaction }` | `Executing` to `Settled` |
| `Revert { transaction }` | `Executing` to `SettleFailed`; Track A trigger |
| `Cancelled`, `Expired`, `Fail` | `Executing` to `Active`; queues the non-settlement debit |
| Pre-submission kinds | no transition; recorded as audit events |
Notifications carry auction and solution ids, not proposals, so `/solve` records the `(auction_id, solution_id, proposal_id)` mapping synchronously before returning a solution — if it cannot be recorded, it is not bid. `/notify` joins through that mapping, which doubles as the per-auction participation record. The ids are optional on the wire, so the handler must tolerate notifications it cannot join, but an *outcome* notification that cannot be attributed is an alert-worthy bug.
The driver knows the transaction hash and whether it reverted, but not what it cost. BYOS makes one `eth_getTransactionReceipt` call on a reverted hash to read the real gas used and gas price. That read is how the debit amount is obtained, not a double-check.
This covers cases a block scanner would miss, including private submissions and dropped transactions, and missed-deadline detection comes free from `Expired` and `Cancelled`. A lost `Revert` notification costs that settlement's Track A debit unless recovered by hand from the audit trail and the chain.
### Simulation
Each proposal is simulated as the transaction the driver would actually submit: a real `settle()` on `GPv2Settlement` carrying the real order, via `eth_estimateGas` so the success verdict and the gas figure come from one RPC call.
```
eth_estimateGas:
from: 0x1111...1111 (dummy submitter)
to: GPv2Settlement
data: settle(
tokens = [sellToken, buyToken],
clearingPrices = [proposal.buyAmount, proposal.sellAmount],
trades = [the real order: fields and signature from the orderbook],
interactions = [[], [sellToken.transfer(trampoline, sellAmount),
trampoline.execute(...)], []]
)
state overrides:
authenticator -> code: AnyoneAuthenticator
escrow -> state_diff: hasRole(SUBMITTER_ROLE, dummy) = true
```
Because the order is real, the user has genuinely approved the vault relayer and holds the sell tokens. No balance faking, no allowance faking, no per-token storage-slot detection. Everything runs at real addresses, so the floor-and-sweep semantics behave exactly as in production and GPv2's own checks — order signature, limit price, `validTo`, filled amount — come along for free. The two overrides stand in only for permissions the dummy sender lacks, and both become unnecessary once a production submitter address holds the role on-chain.
The simulation does not model three calldata words: the encoder fixes the executed amount at the full order amount and the clearing prices at the raw proposal amounts, while the real transaction subtracts the gas cut and substitutes the driver's own per-trade prices, then applies protocol fees. The gas is the same — same tokens, same interactions, same trade, same storage touched — and the divergence is one-directional, since the real transaction pays the user less than the simulated one, never more. A proposal that simulates successfully therefore cannot fail the settlement's limit check because of the cut.
Order data is fetched once from the CoW orderbook and cached for the process lifetime, since orders are immutable after placement. An off-chain soft-cancel is invisible to the service; the proposal's own `validUntil` bounds the window and the driver re-validates at settlement time, so nothing wrong can land on-chain.
Before simulating, the order and proposal pair must pass a cheap envelope check with no RPC:
- Fill-or-kill only. Partially fillable orders are rejected.
- No bridging orders.
- `erc20` balance flavors only; external and internal balance orders are rejected.
- Amounts consistent: a sell fill-or-kill needs `proposal.sellAmount == order.sellAmount`, a buy needs `proposal.buyAmount == order.buyAmount`. Fill-or-kill executes the order amount in full, so a proposal quoting anything else would simulate a different trade than the one settled.
All four signature schemes are supported, since the scheme is encoded in the trade flags and GPv2 verifies it for real during simulation. Sell and buy orders are both supported, including native-ETH buys. Order hooks are included in the simulation for accurate gas, using the order's pre-encoded interactions from the orderbook; the `/solve` response does not include hooks, because the driver appends the order's own hooks itself.
**A revert is terminal on the first occurrence.** No strikes, no retry. A proposal that reverted once is not offered to `/solve` — if it won and then reverted on-chain, the sub-solver takes a Track A penalty. Transport errors are different: an RPC timeout or DNS failure defers to the next tick rather than punishing the sub-solver, and orderbook 404s reject while transient orderbook errors defer.
**The profitability gate runs on the first simulation only.** A score of zero or less rejects as unprofitable, matching `/solve`'s own inclusion rule, so one invariant holds: an `Active` proposal is one that could win an auction right now. It is not re-applied on re-validation, because gas prices wobble and rejecting on a spike would churn proposals that are profitable again two blocks later.
**Proposal lifetime is capped at ingestion.** `validUntil` more than the configured maximum in the future is rejected, which bounds worst-case simulation cost per proposal and guarantees the expiry sweep arrives. Sub-solvers already run polling loops, and a route priced longer ago than that is stale anyway. The order's own `validTo` needs no separate handling: once the order expires or fills, simulation reverts and first-revert-terminal cleans up within a tick.
Re-simulation runs every tick for `Submitted` and `Active` proposals, at an interval targeting about one block. `Executing` proposals are not simulated. This is deliberately not every-block simulation of everything; the driver's own post-encoding re-simulation catches proposals that go stale in between.
## Solver engine
BYOS is the **solver engine** half of a standard CoW driver and solver pair. The driver — unmodified, run by CoW — handles encoding, gas simulation, scoring, and submission. The engine's job is narrower: answer `/solve` with candidate solutions from the proposal store.
### Single-order solutions
**A solution contains exactly one order.** One proposal commits to one order, and one settlement carries one proposal and one sub-solver. Batch proposals are out of scope.
CoW's original single-winner batch auction rewarded batching, because netting opposing orders peer-to-peer was the winning edge. CIP-67 replaced it with the fair combinatorial auction: reference bids are computed per directed token pair, and a batched bid is filtered out if it underperforms the reference on any pair it covers. Coincidence of wants in a single auction is small, most directed pairs carry one order, and a batch usually has nothing to net. Meanwhile sub-solvers are mainly DEXes and routing APIs that want to quote one order and sign one proposal, not build netting logic.
One invariant follows for everything downstream: every BYOS settlement has exactly one order, one trampoline call, one sub-solver. Relaxing it later is a signed-schema change, needing a domain-version bump.
Settlement overhead is therefore paid per order and never amortized, and netting surplus is out of reach. Both are accepted: bids are scored per solution, and BYOS's niche is per-pair routing bids.
### Scoring
`score = surplus - gas`, in native-token units.
- **Surplus** is the improvement beyond the order's limit price — extra buy tokens on a sell order, sell tokens kept back on a buy order — converted at the auction's reference price.
- **Gas** is the simulated `eth_estimateGas` result plus a 30k buffer, cached on the proposal, times the auction's effective gas price. The buffer is small because the full-settle estimate already covers intrinsic gas and the whole settlement path, so it only absorbs warm and cold storage differences and driver batching variance.
**There is no fee term.** CoW's score is surplus plus protocol fees and nothing else; gas never appears as a subtraction there. It reaches the score only because a solver declares gas as its own fee, which lowers what the user receives, which lowers surplus. The protocol fee then cancels out of any ranking — it is carved out of surplus and added straight back — so `score = route surplus − our own cut`. Once the cut equals the gas cost ([`#gas`](#gas)), `surplus − gas` is the score the autopilot will compute for the bid.
**BYOS does not estimate protocol fees either.** The driver applies them itself, then encodes and simulates before bidding; a solution that cannot absorb the fee fails that simulation and is dropped, which costs the round but produces no revert, no penalty, and no escrow debit. It is also impossible to estimate before `/solve`, since fee policies are built per auction by the autopilot and delivered only in the `/solve` payload.
BYOS's score is a **pre-ranking**: it decides which proposals deserve the driver's encoding budget. The driver re-scores after encoding and simulation. Returning everything and letting the driver decide was rejected, because each solution costs a gas simulation and flooding the encoding budget with obviously worse proposals risks the deadline.
### Selection
One winner per order UID, filtered and ranked at `/solve` time with local computation only — no RPC, no simulation:
1. Expiry: `validUntil > now`.
2. Order liveness: the order UID is present in the auction.
3. Amount matching against the auction's order state.
4. Score rank by `surplus - gas`, using cached gas and the auction's prices.
5. Gas cut sizing; drop the proposal if taking the cut would breach the user's signed limit.
6. Select the single highest-scoring proposal per order UID.
A winner with a non-positive score is not returned: settling a trade expected to cost more in gas than it earns in surplus is worse than skipping the order. The escrow re-check is not on this path — the background validator owns it.
**Amount matching is strict, with no clamping.** Fill-or-kill proposals must satisfy the order's limit price; partially fillable proposals must not exceed the remaining fillable amount. BYOS never adapts proposal amounts, because the sub-solver computed a route for specific amounts and changing them would invalidate it. Sub-solvers resubmit through their polling loops when order state moves.
**EBBO baseline is not re-checked at `/solve`.** The ingestion-time check is the primary gatekeeping layer, and re-running it on the hot path would add a price lookup for marginal safety.
Because BYOS returns one proposal per order, there is no fallback if the selected proposal fails the driver's post-encoding re-simulation; BYOS loses that order for that round. Accepted — the divergence between BYOS's cached-gas score and the driver's fresh one is marginal, and sub-solvers resubmit naturally.
### Settlement crafting
Each selected proposal becomes exactly two intra-settlement interactions:
1. `sellToken.transfer(trampoline, sellAmount)` — BYOS-authored. Pushes trade capital from `GPv2Settlement` into the instance. The Trampoline cannot reach settlement funds itself, so this is mandatory.
2. `trampoline.execute(proposal, interactions, sellToken, buyToken, signature)` — runs the signed route inside the sandbox. Everything inside that call is contract behaviour ([`#trampoline`](#trampoline)). Token addresses are BYOS-supplied call parameters taken from the order, not signed proposal fields.
The engine computes the CREATE2 address from the recovered sub-solver address and ABI-encodes both calls. That is keccak256 and ABI encoding — pure local computation, no RPC on the hot path.
The driver's `SolutionMerging` is set to **`Forbidden`**, because the driver merges blindly by token pair with no sub-solver awareness and would otherwise silently break the one-sub-solver-per-settlement rule.
### Solution shape
| Field | Value |
|---|---|
| `id` | index within this response, 1-based; recorded against the proposal id so `/notify` can be attributed |
| `prices` | cross-multiplied from the proposal amounts; unaffected by the cut, which is a declared fee rather than a price shade |
| `trades` | exactly one fulfillment |
| `trades[0].fee` | the gas cut, in sell-token atoms — never absent |
| `trades[0].executed_amount` | sell order: `order.sellAmount - fee`. Buy order: `order.buyAmount` |
| `interactions` | the two custom entries above, not internalized |
| `pre_interactions`, `post_interactions`, `wrappers` | empty; the driver appends the order's own hooks |
| `gas` | simulated gas plus the 30k buffer — the same number the cut is priced from |
| `flashloans` | none |
The fee field is never absent because **every real order is limit class**: order validation assigns `Limit` when the signed `feeAmount` is zero, and every order has signed zero since the 2023 fee-model change. A fulfillment accepts a static fee only for market-class orders, so a missing fee is rejected on every order BYOS bids — and DTO conversion collects into a result, so one invalid trade discards *every* solution in the response. Quote requests invert this, since the driver's synthetic quote order is market class; that path is unreachable today but is a trap for anyone adding quoting deliberately.
## Gas
There is no fee logic on-chain in CoW. A fee is a **price wedge**: the solver pays the user slightly less than the trade produced, the difference stays in `GPv2Settlement`'s balance, and once a week the protocol computes off-chain who owes what and settles up.
| Fee | Covers | Who receives it | Who sets it |
|---|---|---|---|
| Network fee | gas of the settlement | the solver, via the weekly payout in native token | the solver's own cut; the protocol does **not** reimburse gas |
| Protocol fee | CoW DAO revenue | CoW DAO | fee policies attached per order by the autopilot |
| Partner fee | integrator revenue | the partner | declared in the order's appData, capped by the protocol |
Full mechanics, with worked numbers for both order kinds, are in [`reference/cow-fee-collection`](reference/cow-fee-collection).
### The gas cut
**BYOS charges exactly the gas the settlement is estimated to cost, in sell-token units, always on.** No multiplier, no configuration knob. It is *declared* as the fulfillment's fee while the route still carries the full sell amount.
Declaring and routing less are separable, and only the first is available: the sub-solver signed for the full `sellAmount`. The wedge lands anyway, because the driver rebuilds clearing prices from the declared execution, so the user receives proportionally less and the difference stays in the settlement's buffers. Nothing reimburses gas — what returns weekly is money BYOS declined to pass on. Using the fee field rather than shading prices keeps `encode_settle` producing the transaction that was simulated, and books the cut as a declared solver fee instead of as slippage.
**The limit check belongs to BYOS, because the price does.** A proposal is skipped when the cut would drop the user below what they signed for. This check needs no fee policies, and it can reject a proposal the score accepts: the score converts surplus at the auction's price while the limit is enforced on the route's own amounts, so a stale price makes the two disagree.
**The cut is not padded.** A larger cut lowers BYOS's score, which lowers CIP-85 consistency rewards; those come from a shared bucket allocated by closeness to the winner, so BYOS does not recapture what it adds to that bucket. Revenue margin above gas recovery is deliberately left open. BYOS retains 100% of CoW rewards earned under its bonded solver address; pass-through to sub-solvers is out of scope for v1.
The cut recovers gas **approximately, not exactly**. It is sized from the auction's native price, while the weekly payout converts at an average observed over roughly an hour around the trade. Padding to cover the gap costs more in consistency rewards than it recovers, so the gap is accepted and monitored through CoW's per-solver dashboard of gas paid against gas collected.
Say "gas cut", not "fee". The order's signed `feeAmount` is a different field, zero on every live order. CoW's protocol fee and network fee are applied by the driver. The percentage-of-`sellAmount` "BYOS fee" of early drafts never shipped: it took the cut by routing less than the user sold, which a fixed signed route does not allow.
### What this means for a sub-solver
Amounts in a proposal are **raw pre-fee route amounts**, the same convention a solver engine uses toward its driver. The driver's wedge is created after those amounts, so it lands in `GPv2Settlement`, never in the instance.
Two consequences follow. First, gatekeeping must ensure each proposal leaves room for the gas cut *and* the driver's fee shift above the user's limit price — a sub-solver quoting exactly at the limit produces an infeasible solution. Second, on-chain `settle()` calldata deviates from the signed raw tuples by exactly the driver's fee transform, which is deterministic because policies are public per auction, so a dispute must apply that transform to the signed tuples before comparing.
CIP-74 caps a solver's per-auction reward at a share of the protocol fees its solutions collected, so a settlement collecting no protocol fee earns nothing while BYOS still pays gas. That is why the gas cut is always on.
## Penalties
When CoW imposes a cost on BYOS, BYOS must attribute it to the responsible sub-solver and recover it from escrow — without being able to fabricate a penalty against an honest one.
CoW's own framework has four enforcement layers ([`reference/cow-solver-slashing-policy`](reference/cow-solver-slashing-policy)), and BYOS maps onto them rather than replicating them:
1. **Smart contract** (limit price reverts, allowlist) — architecturally prevented by the Trampoline; sub-solvers never call `settle`.
2. **Automated off-chain** (participation guards, banning) — subsumed by gatekeeping, Track A debits, and the collateral gate. No separate replication.
3. **DAO governance** (EBBO, score inflation, surplus shifts, overbidding, hooks, catch-all) — only EBBO/unfair pricing and the catch-all apply to sub-solvers. Score inflation, illegal buffer usage, surplus shifting, and overbidding are either architecturally prevented or are BYOS's own responsibility, since BYOS controls score construction, buffer access, and settlement composition.
4. **Economic penalties** (reward formula, `c_l` cap) — mirrored by the Track A `gas + c_l` debit. Sub-solvers receive no rewards in v1, so the escrow debit is the only lever.
### The schedule
| Scenario | Track | Amount | Timing | Dispute | Arbiter |
|---|---|---|---|---|---|
| Settlement reverts on-chain | A | `gas + c_l` | immediate debit | 72h | BYOS |
| Settlement misses block deadline | A | `gas + c_l` | immediate debit | 72h | BYOS |
| Won auction, BYOS chose not to settle | A (non-settlement) | 10% of `c_l` | immediate debit | 72h | BYOS |
| EBBO / unfair pricing | B | CoW certificate amount | freeze on receipt | 36h | CoW core team |
| Catch-all malicious behavior | B | CoW-determined amount | freeze on receipt | 36h | CoW core team |
Track A and Track B penalties for the same settlement **stack**. There is no crediting of one against the other: if a settlement causes both a revert and an EBBO ruling, the sub-solver pays both, because the proposal caused both problems.
`c_l` is read from CoW's reward mechanism at debit time, with a hardcoded fallback for v1. Current values: **0.010 ETH** on Ethereum, **10 xDAI** on Gnosis.
**Minimum escrow balance** is sized to cover worst-case Track A for a single settlement, `gas + c_l`.
**On shortfall**, BYOS drains the remaining balance and absorbs the difference. The sub-solver is suspended (zero collateral means ineligible). There is no permanent ban and no debt tracking.
The **policy is immutable for v1**. No unilateral updates; a change requires a v2 policy with a new escrow deployment or a migration.
### Track A
Routine, fast, provable.
| Stage | Actor | What happens | Timing |
|---|---|---|---|
| Trigger | chain | settlement reverts, misses its deadline, or BYOS abandons it after winning | T₀ |
| Debit | BYOS | operator calls `debit(S, amount, reason)` for `gas + c_l`, or `0.1 × c_l` for non-settlement | T₀ + seconds |
| Dispute | sub-solver | 72h window on narrow grounds: wrong attribution, the transaction did not revert, the amount exceeds `gas + c_l` | 72h |
| Resolution | BYOS | reviews and decides, unilaterally | after the window |
Track A is BYOS-unilateral because for reverts and deadline misses everything is on-chain verifiable: the receipt, the gas cost, and the Trampoline CREATE2 address that identifies the sub-solver.
**Non-settlement is detected from driver notifications**: a `Cancelled`, `Expired`, or `Fail` for an `Executing` proposal means the driver confirmed it began submitting and then abandoned the settlement with no transaction landing. That covers both submission failures and the driver's own block deadline. An executing *timeout* is deliberately not charged — a lost notification is not proof of non-settlement. This sub-category rests on BYOS's internal auction records and is not independently verifiable by the sub-solver, which is an accepted trust assumption.
**Infra failures are excluded.** A settlement that reverts because of BYOS's own orchestration — a trampoline missing after a deposit-transaction reorg, for instance — is BYOS's cost. The engine must distinguish "sub-solver route reverted" from "BYOS orchestration failed" before debiting.
### Track B
Rare, slow, a nested mirror of CoW's own process against BYOS.
```
CoW core team ──EBBO certificate──▶ BYOS ──slash claim──▶ sub-solver S
(72h for BYOS to comply/challenge) (36h window inside BYOS's 72h)
```
| Stage | Actor | What happens | Timing |
|---|---|---|---|
| Trigger | CoW core team | EBBO certificate against a BYOS settlement | T_c, days to 3 months post-trade |
| Identify | BYOS | maps the cited settlement to a proposal and sub-solver | T_c + minutes |
| Freeze + notify | escrow operator | `freeze(S)` blocks withdrawal; BYOS notifies S with the certificate, settlement reference, and amount | T_c + minutes |
| Challenge | S to BYOS to CoW | S supplies a refutation within 36h; BYOS relays it into its own CoW challenge | 36h |
| Resolution | CoW | upholds or overturns | within BYOS's 72h |
| Settle | BYOS + escrow | upheld: `debit(S, amount, reason)`, BYOS reimburses CoW, shortfall absorbed. Overturned: `unfreeze(S)` | after resolution |
The arbiter is the **CoW core team**, not BYOS. They already adjudicate EBBO, and routing Track B to them means BYOS cannot fabricate a certificate. Sub-solvers get the same evidence standard, challenge window, and appeal rights that CoW gives BYOS.
Track B stays out of the proposal state machine: a ruling months later is an account-level event against the sub-solver, not a transition of one proposal.
The 36h sub-solver window is tight, and permissionless participants without responsive operations may struggle. It is what remains after BYOS reserves the other 36h of its own 72h CoW window to process and relay.
**Track B has an unrecoverable gap.** If the sub-solver has withdrawn, or the escrow is smaller than the claim, BYOS absorbs the difference.
### Attribution
**One sub-solver per settlement transaction.** The per-sub-solver Trampoline CREATE2 address in the settlement calldata self-evidences which sub-solver's route ran, with no reliance on BYOS's private records. That is what makes Track A debits indisputable and Track B attribution clean.
The cost is less batching efficiency.
Off-chain, notifications carry auction and solution ids rather than proposals, so attribution to a proposal is a join through the `solutions` mapping the engine writes before bidding ([`#proposal-lifecycle`](#proposal-lifecycle)). The Trampoline address in calldata remains the on-chain proof, checked when debiting.
### Gatekeeping
Preventive, best-effort, and **non-exculpatory**. Before settling, BYOS validates that the proposal simulates without reverting and that the route is not obviously worse than reference AMM prices. BYOS includes the order's pre- and post-hooks in the simulation for accurate gas estimation; the driver appends them to the settlement separately ([`#solver-engine`](#solver-engine)).
Sub-solvers do not include hooks in their signed interactions — those contain only the routing calls. However, some hooks change the token balances a route depends on — withdrawing DEX liquidity before a swap, for instance — so the sub-solver must account for hook effects when computing a correct route. Passing gatekeeping does not absolve anyone: the EIP-712 signature is the sub-solver accepting responsibility for the route it signed.
Simulation failures cost the sub-solver **nothing** beyond a rate-limit slot. Only on-chain failures debit escrow. Simulation failures are not debited.
### Transparency
The Escrow's on-chain events are the public record of every penalty action. There is no additional public reporting or dashboard, because one would leak competitive intelligence about sub-solver routing quality; on-chain events are enough for a sub-solver to audit its own history. BYOS notifies the affected sub-solver privately with full evidence.
The `reason` field on `debit` carries the settlement transaction hash for a Track A revert, the order UID hash for non-settlement where no transaction exists, and the claim id for Track B.
## Residue
> Decision inverted 2026-07-22. Previously, route output above the signed floor and unconsumed sell tokens stranded in the instance as sub-solver-reclaimable **residue**, behind `claimToken`/`claimTokens`. Three premises fell: fees and slippage are price wedges, so surplus parked in the settlement returns to the solver weekly rather than being lost; the sub-solver persona is a DEX or routing API compensated by its own venue fees inside the route, not by leftovers; and the replay exposure that made parked balances unsafe was closed by the submitter gate.
**There is no residue.** `execute` sweeps the instance's full remaining balance of both trade tokens to `GPv2Settlement` and enforces `buyAmount` as a floor via the balance-delta check. The instance ends every settlement holding none of the trade tokens. Over-delivery and unconsumed sell tokens are BYOS-owned settlement slippage, returned weekly by CoW's accounting. The claim functions are removed, and the Trampoline keeps zero privileged keys — with nothing resting in the instance, nobody needs one.
**Strays are written off.** Tokens landing on an instance outside the settlement flow — mistaken transfers, airdrops, intermediate-token dust — are nobody's problem by design. A sub-solver with a standing route-planted approval can take them; preventing that is the un-enumerable approval-fighting problem the topology decision already rejected, and the amounts are donations and dust. Never user funds, trade capital, buffers, or escrow, all of which are protected by settlement atomicity and the floor check. If a sub-solver skims strays, the response is off-chain — gatekeeping, eviction — not a contract mechanism.
**In-route capture is tolerated.** A sub-solver can keep surplus by capturing it in-route before the sweep. That is bid-neutral: it touches only value above its own signed floor, which it could have kept by signing a higher floor. Guarding against it would reopen the filtered-approval arms race. The floor is the bid. A sub-solver signs the minimum it is sure to deliver, below its simulated route output, and margin sizing is its own tradeoff — too thin reverts and lands Track A debits, too thick loses auctions.
The instance is empty at rest; a planted approval over an empty contract drains nothing.
---
# FILE: glossary.md
# Glossary
The stable domain language for BYOS. Every term below is defined here and nowhere else — implementation repos use this vocabulary in issues, ADRs, tests, metric names, and code, and their own `CONTEXT.md` files define only what is local to them.
If a concept you need is not here, that is a signal. Either you are inventing language the project does not use, or there is a real gap worth flagging.
Source RFP: [Bring Your Own Solver (BYOS)](https://forum.cow.fi/t/rfp-bring-your-own-solver-byos/3469) · [accepted grant application](https://forum.cow.fi/t/grant-application-cow-byos-bring-your-own-solver/3476). CoW protocol background: [fee collection](reference/cow-fee-collection), [slashing policy](reference/cow-solver-slashing-policy), [auctions](reference/solver-auctions), [CIPs](reference/solver-cips).
## What BYOS is
A **bonded CoW solver** whose proposed solutions are sourced from a permissionless set of **external sub-solvers**. Sub-solvers submit signed routing proposals against specific order UIDs, collateralized by an escrow balance held by BYOS. BYOS retains exclusive control over on-chain settlement submission. From the protocol's perspective BYOS is a single, ordinary bonded solver — the sub-solver relationship is entirely internal to BYOS.
v1 targets **Ethereum mainnet + Gnosis**. Out of scope: a BYOS-operated orderbook, reward pass-through to sub-solvers, cross-chain escrow accounting, and BYOS's own bonding capital.
## Two risk classes
The core economic framing. Everything about escrow, penalties, and gatekeeping follows from this split.
| | Track A — gas + revert penalty | Track B — EBBO / fairness slash |
|---|---|---|
| Determined by | On-chain fact (tx reverted) | Off-chain CIP-52 certificate + DAO |
| Timing | Seconds to about one accounting week | Days, up to 3 months |
| Attributable cleanly? | Yes (tx to proposal) | Murky; BYOS *chose* to settle it |
| Recoverable from escrow? | Yes | Only if funds are still present; otherwise BYOS eats it |
| Primary defense | Escrow debit | BYOS pre-settlement **gatekeeping** |
## Terms
- **Sub-solver** — an external, permissionless party that computes a route for a specific order and submits a signed proposal to BYOS. Never holds submission keys; never calls `settle`. Identified by its address, recovered from its EIP-712 signature; that same address is its escrow key and its Trampoline CREATE2 salt. It is `sub_solver`, never plain `solver` — in CoW's vocabulary `solver` means BYOS itself.
- **Proposal** — an EIP-712-signed message authorizing BYOS to attempt a settlement of a specific route, and consenting to the associated escrow risk. Immutable: amounts, interactions, expiry, nonce, and signature form one signed unit, so there is no update operation. One proposal commits to exactly one order. Field-level definition is in [the design document](design-document#proposal-schema); the wire shape is in `byos-service`'s [`crates/byos/openapi.yml`](https://github.com/bleu/byos-service/blob/main/crates/byos/openapi.yml).
- **Trampoline** — the contract that receives a route's `sellAmount`, executes the sub-solver's interactions as itself, sweeps both trade tokens back to `GPv2Settlement`, and enforces `buyAmount` as a floor. Confines sub-solver code to a fund-less context so it cannot reach settlement buffers or plant an exploitable approval. One immutable instance per sub-solver, at a deterministic CREATE2 address, deployed at escrow-deposit time. See [the design document](design-document#trampoline).
- **Escrow** — a per-chain, native-token ERC20 contract holding sub-solver collateral keyed by sub-solver address. Tokens are minted 1:1 with deposited ETH and burned on withdrawal or debit; `balanceOf` is the single source of truth. The collateral at risk is the *only* sub-solver capital BYOS ever touches — trade capital flows atomically through `GPv2Settlement` into the Trampoline and back. See [the design document](design-document#escrow).
- **Owner** — the secure wallet (multisig or Safe) that owns the Escrow. Receives debited funds, sets the operator, grants and revokes submitters, configures the cooldown. Ownership transfer is two-step.
- **Operator** — an EOA held by the BYOS service for automated operations: debit, freeze, unfreeze, pause, unpause. Cannot withdraw funds or change configuration. A compromised operator can grief but not steal.
- **Submitter** — an EOA the BYOS service submits settlements from. Holds the Escrow's `SUBMITTER_ROLE`; `Trampoline.execute` requires `tx.origin` to be one. Covers both the allow-listed solver EOA and the auxiliary accounts of CoW's `Solver7702Delegate` parallel path, since there the auxiliary account is `tx.origin`. Rotation is a role change by the Owner, not a redeploy.
- **Cooldown** — the waiting period between requesting and executing an escrow withdrawal. Withdrawal is all-or-nothing: requesting drops effective balance to zero immediately, so a sub-solver is offline for new proposals for the duration.
- **Pause** — an operator-triggered global emergency brake blocking all ERC20 transfers and withdrawal executions. Deposits, debits, withdrawal requests and cancellations, and debit sweeps stay operational. The first response to detected malicious transfer activity; should be short-lived, minutes rather than hours.
- **Freeze** — the operator blocking withdrawal execution and ERC20 transfers, in both directions, for one sub-solver address while a Track B investigation is open. Does not affect effective balance. Deposits to a frozen address are allowed.
- **Debit (Track A)** — routine, provable recovery of `gas + c_l` from escrow when a winning settlement carrying a proposal reverts on-chain, misses its deadline, or is abandoned after winning. See [the design document](design-document#track-a).
- **Slash / clawback (Track B)** — rare passthrough of a CoW EBBO or fairness penalty (CIP-52) to the responsible sub-solver's escrow, mirroring the process CoW runs against BYOS. The service tracks a 5× off-chain reserve against pending claims. See [the design document](design-document#track-b).
- **Attribution** — mapping a settlement transaction back to the sub-solver whose proposal it contained. Enforced by settling one sub-solver per settlement transaction; the per-sub-solver Trampoline CREATE2 address in the calldata self-evidences which sub-solver's route ran.
- **Gatekeeping** — BYOS's *preventive* control: validating each proposal (simulation, hook presence, EBBO baseline price) before settling. Distinct from escrow, which is *recovery*. Best-effort and non-exculpatory — passing gatekeeping does not absolve a sub-solver.
- **Gas cut** — what BYOS keeps back to cover submitting a settlement: exactly the estimated gas cost, in the order's sell token, on every solution it bids. Kept rather than reimbursed. Always on, no rate to configure. Say "gas cut", not "fee": the order's signed `feeAmount` is a different field and is zero on every live order, CoW's **protocol fee** and **network fee** are applied by the driver rather than by BYOS, and the percentage-of-`sellAmount` "BYOS fee" of early drafts never shipped. See [the design document](design-document#gas).
- **`c_l`** — CoW's per-auction lower reward cap, which is the maximum revert penalty: 0.010 ETH on mainnet, 10 xDAI on Gnosis. A BYOS debit per reverted auction is bounded by `gas + c_l`. See [`reference/cow-solver-slashing-policy`](reference/cow-solver-slashing-policy).
- **Residue** — a retired category. Until 2026-07-22, route output above the signed floor and unconsumed sell tokens stranded in the Trampoline instance and were reclaimable by the sub-solver. `execute` now sweeps both trade tokens to `GPv2Settlement`, so that value is BYOS-owned settlement slippage. The term survives only in superseded ADR revisions. See [the design document](design-document#residue).
---
# FILE: guides/sub-solver-integration.md
# Sub-solver integration
This guide tells you how to go from zero to a settled proposal.
All normative facts (field names, amounts, signature formats, penalty amounts) are in [the design document](../design-document) or the [OpenAPI document](https://github.com/bleu/byos-service/blob/main/crates/byos/openapi.yml). This guide links to them and does not repeat them. If this guide and one of those disagree, the source document is correct.
You do not need a CoW solver seat, an allowlist entry, or a relationship with CoW DAO. You need an address, collateral in the Escrow, and the ability to sign EIP-712 messages.
## Your role as a sub-solver
**You are responsible for:**
- **Collateral.** Deposit funds into the Escrow. Your balance must be more than one worst-case Track A debit (`gas + c_l`).
- **Order selection.** Find orders in CoW's public orderbook. Compute any route that delivers buy tokens to the GPv2Settlement contract. Assume execution from Trampoline with sell tokens on it.
- **Floor margin.** Set the `buyAmount` floor in your proposal. If the floor is too close to the route output, your route can revert on-chain (Track A debit). If the floor is too far below, you lose auctions.
- **Venue-level fees.** If your route goes through a pool you operate, you keep those fees. To capture surplus above your floor, do it inside your route before the sweep. Any remaining tokens in the Trampoline belong to you to claim or use in future trades.
- **Responding to Track B claims** within the 36-hour challenge window. Claims can arrive months after a trade.
**You are NOT responsible for:**
- **Transaction submission.** BYOS builds and submits the settlement through the CoW driver. You never call `settle`.
- **Scoring.** BYOS scores proposals (`surplus - gas`), selects the best one per order, and bids it into CoW's auction.
- **Gas estimation or fee calculation.** BYOS sizes the gas cut. The driver applies protocol and partner fees. Your amounts are raw, pre-fee route amounts.
- **Trampoline contract logic.** The sweep, the floor check, and the sandbox isolation are in the contract code. You cannot change them.
- **CoW protocol compliance.** BYOS manages the relationship with CoW DAO, the bonding pool, and the reward accounting. But gatekeeping is non-exculpatory. Your signed route is your responsibility.
## 1. Understand the risks
You compute routes. BYOS bids them into CoW's auction under its own bonded solver seat. BYOS submits the settlement and takes the consequences from the protocol.
When a settlement that carries your route fails on-chain, BYOS debits the cost from your escrow balance. **BYOS debits this amount without prior approval.** Read the terms in [`#penalties`](../design-document#penalties).
**The `buyAmount` is a floor, not a quote.** The contract enforces it as a minimum. If the route delivers less than this amount, the settlement reverts. You set the margin between the floor and the expected route output. A [Track A](../design-document#track-a) debit is the penalty for a revert. A floor that is too far below the output loses auctions.
## 2. Deposit collateral
Deposit native token into the [Escrow](../design-document#escrow) for your address. Any address can fund a sub-solver address. Only the sub-solver address can withdraw.
The deposit causes three effects:
1. **You can submit proposals.** The deposit is the only requirement. The minimum balance must be enough for a single worst-case Track A debit ([`#penalties`](../design-document#penalties)).
2. **BYOS deploys your [Trampoline](../design-document#topology) instance.** The instance has a deterministic CREATE2 address that is based on your address. You pay this one-time gas cost. All your routes execute in this instance.
3. **BYOS sets your rate limit.** The rate limit scales with your balance ([`#proposal-api`](../design-document#proposal-api)). A larger deposit gives more throughput.
### Withdrawal
Withdrawal is not instant. It is all-or-nothing with a cooldown period. When you request a withdrawal, your effective balance drops to zero immediately. You cannot submit proposals during the cooldown. See [`#withdrawal-and-freeze`](../design-document#withdrawal-and-freeze).
### Key rotation
To rotate keys, use the ERC20 `transfer` function to move your escrow balance to the new address. Do not withdraw and redeposit. A transfer prevents the gap where you have no collateral.
The new address gets its own Trampoline instance. Your old proposals do not follow you. Your address serves three roles: proposal signer, escrow key, and CREATE2 salt ([`#proposal-schema`](../design-document#proposal-schema)).
## 3. Find orders to route
BYOS does not operate an orderbook. Orders come from CoW's public orderbook API.
One proposal covers one order ([`#single-order-solutions`](../design-document#single-order-solutions)). There is no batch format.
## 4. Build a route
A route is a list of raw calls. Each call has a target, a value, and calldata. You can use any DEX or protocol. BYOS does not maintain a venue registry. The calls execute as-is inside your Trampoline instance.
### Sandbox constraints
Your Trampoline instance holds only the sell amount that BYOS pushes in for this settlement. The instance has no allowance over `GPv2Settlement`. It has no access to other sub-solver instances. The instance is empty between settlements ([`#topology`](../design-document#topology)).
A route that tries to access protocol buffers gets nothing. A route that plants an approval for future use against a funded contract gets nothing.
### Headroom
Leave headroom above the user's limit price. BYOS takes a [gas cut](../design-document#gas) from the trade. The CoW driver applies protocol and partner fees on top. If a route quotes exactly at the limit, BYOS skips it because the solution is not feasible.
Sign **raw, pre-fee route amounts**. Do not pre-subtract fees. The driver creates the fee wedge after your amounts. The wedge stays in the settlement, not in your instance.
## 5. Sign the proposal
Sign the EIP-712 typed data described in [`#proposal-schema`](../design-document#proposal-schema). Get the struct, domain, and typehash from [`bleu/byos-contracts`](https://github.com/bleu/byos-contracts). Test your signatures against the contract's own test vectors. Do not derive the typehash yourself.
The API verifies your signature at submission. The Trampoline verifies the same signature on-chain at settlement. If the two do not match, the settlement fails.
### Domain binding
The EIP-712 domain binds to the TrampolineFactory address. The domain is specific to a chain **and** a deployment generation. A contracts v2 deployment invalidates all outstanding signatures. Update your domain configuration when contracts change.
### Route commitment
The `interactionsHash` field in the signed struct commits to your route. BYOS cannot substitute different interactions. A third party can verify that the signed data matches the settlement calldata. This property makes Track A debits verifiable.
### Expiry
Keep `validUntil` short. BYOS caps it at ingestion ([`#proposal-lifecycle`](../design-document#proposal-lifecycle)). A route that is more than a few minutes old is stale.
## 6. Submit and poll
Send a `POST` request with the proposal. See the [OpenAPI document](https://github.com/bleu/byos-service/blob/main/crates/byos/openapi.yml) for the payload format, status codes, and rejection reasons.
### API endpoints
All endpoints are on the public listener (default port 9585):
| Method | Path | Auth | Purpose |
|---|---|---|---|
| `POST` | `/proposals` | Proposal signature (in body) | Submit a signed proposal. Returns `202` with an id. This is **not** acceptance. |
| `GET` | `/proposal/{id}` | `X-Signature` (EIP-712 `ReadAuth`) | Get your proposal status, rejection reason, and settlement/penalty tx hashes. |
| `GET` | `/proposals/{order_uid}` | `X-Signature` | List your proposals on one order. |
| `GET` | `/proposals/by-sub-solver` | `X-Signature` | List all your proposals. |
| `DELETE` | `/proposal/{id}` | `X-Signature` (EIP-712 `CancelProposal`) | Cancel a proposal. Works only on `Submitted` or `Active` proposals. |
### Read authentication
Sign an EIP-712 `ReadAuth { version: 1 }` message once. Send it in the `X-Signature` header with every `GET` request. This signature has no timestamp or nonce. If it leaks, the risk is limited to read access to your own proposals. The signature does not grant write or cancellation access.
If you query a proposal that is not yours, you get `404` (not `403`). You cannot check if a proposal id exists.
### The response to POST is not acceptance
A `2xx` response means "accepted for validation". BYOS stores the proposal as `Submitted` and returns an id. Escrow checks and simulation run in a background loop ([`#proposal-api`](../design-document#proposal-api)). Do not treat a `2xx` as "my proposal is live".
### Poll for the verdict
After you submit, poll for the verdict with `GET /proposal/{id}`. You can see only your own proposals. Expect a verdict within approximately one block. The validator tick interval determines the latency, not the request round-trip. See [SLO targets](../operations/slo-targets).
Continue to poll after the first verdict. A live proposal is re-simulated every tick. It can fail at any time because chain state changed.
Run a loop: quote, sign, submit, poll, resubmit. This loop is the intended operating mode.
### Cancel a proposal
To cancel a proposal before it settles, send a signed `DELETE` request. Proposals are immutable. There is no update operation. To replace a proposal, cancel it and submit a new one.
## 7. Error handling
| What happened | Cost | Action |
|---|---|---|
| Rejected at gatekeeping | None | Read the typed rejection reason. Fix the route or the amounts. |
| Simulation reverted | None (one rate-limit slot used) | The proposal is dropped on the first revert. There are no retries. Resubmit if the route is still valid. |
| Expired | None | Your `validUntil` passed. Use a shorter interval. |
| Lost the auction | None | Your proposal stays live and competes in the next auction. |
| Settlement reverted on-chain | [Track A](../design-document#track-a) debit | BYOS debits your escrow immediately. You have a 72-hour dispute window. |
| BYOS won but did not settle | Smaller Track A debit | Same dispute window and grounds. |
| CoW raised an EBBO or fairness claim | [Track B](../design-document#track-b) passthrough | BYOS freezes your balance and sends you the certificate and evidence. |
### Simulation failures
Simulation failures do not cost escrow. Only on-chain failures cause escrow debits. If a revert is caused by BYOS's own orchestration (not your route), BYOS pays.
### Track B operational readiness
Track B claims need operational readiness. Claims can arrive up to three months after the trade. Your refutation window is 36 hours. The CoW core team arbitrates (not BYOS). BYOS cannot fabricate a claim against you, but it also cannot waive one.
If you cannot respond to evidence requests within 36 hours, this is a risk you must plan for.
Every penalty action emits an on-chain Escrow event. You can use these events to audit your own history.
## 8. Reference implementations
Two baseline sub-solver examples exist. Both do the full loop: fetch orders, compute a Uniswap V2 route, sign an EIP-712 proposal, submit, poll, and resubmit.
| Language | Location | Notes |
|---|---|---|
| **Rust** | [`crates/subsolver`](https://github.com/bleu/byos-service/tree/main/crates/subsolver) in `byos-service` | Used in the Rust service's end-to-end test suite. |
| **TypeScript** | [`apps/subsolver`](https://github.com/bleu/byos-service-ts/tree/main/apps/subsolver) in `byos-service-ts` | Uses viem for EIP-712 signing. Uses multicall for reserve fetching. |
The protocol is language-neutral. Use either example for the sequence, the EIP-712 construction, and the polling behavior. The [OpenAPI document](https://github.com/bleu/byos-service/blob/main/crates/byos/openapi.yml) specifies all wire-level details.
## Pre-launch checklist
Before you go live, make sure that:
- [ ] You funded the Escrow above the minimum. A deposit also deploys your Trampoline.
- [ ] You verified your EIP-712 hashes against the contract's test vectors (not your own derivation).
- [ ] Your domain configuration points to the correct chain and contracts generation.
- [ ] Your `validUntil` value is within the ingestion cap.
- [ ] Your route leaves headroom above the user's limit for the gas cut and the driver's fee shift.
- [ ] You have a polling loop that resubmits (not fire-and-forget).
- [ ] You have an operational process to respond to a Track B claim within 36 hours.
---
# FILE: operations/slo-targets.md
# SLO targets
Latency targets for the BYOS service, and the reasoning behind each number.
## `POST /solve` p99 < 100ms
The hot path, called by the CoW driver during auctions.
The driver gives solvers a 15-second deadline, configurable via `solve_deadline` in the autopilot. BYOS does no simulation and no RPC on this path: an indexed read of the live proposal rows per auction order, one `solutions` insert per returned bid, and scoring and encoding in memory ([`#solver-engine`](../design-document#solver-engine)).
100ms is conservative against a 15s deadline — BYOS should never be the bottleneck in the auction cycle.
## `GET /proposals/by-sub-solver` p99 < 50ms
Lists a sub-solver's live proposals, submitted and active. One indexed read scoped to the caller's own address ([`#proposal-api`](../design-document#proposal-api)). Even with hundreds of active proposals this is a single query.
## Proposal ingestion p99 < 1s
The asynchronous pipeline that runs *after* `POST /proposals` has already answered with a proposal id. This is time-to-verdict, not request latency — the request path itself does signature recovery and an expiry check and nothing else ([`#proposal-api`](../design-document#proposal-api)).
| Step | Estimated latency |
|---|---|
| EIP-712 signature recovery and validation | 10-20ms |
| Escrow balance check (cached, or RPC) | 50-100ms |
| Interactions hash verification | ~5ms |
| Simulation (`eth_estimateGas` over RPC) | ~500ms |
| Scoring and the row insert | ~10ms |
| **Total expected** | **600-650ms** |
Simulation dominates. The 1s target leaves roughly 50% headroom over the expected 650ms for slow RPC responses, retries, and GC pauses.
Note that the validator tick interval, not this budget, is what bounds how long a sub-solver waits for a verdict in practice: a proposal submitted just after a tick waits for the next one. The tick targets about one block.
---
# FILE: security/trampoline-settlement-isolation.md
# Trampoline isolation from GPv2Settlement funds
Status: proven (COW-1152)
## What this guarantees
A sub-solver authors an arbitrary route — a list of `(target, value, callData)`
interactions — that the Trampoline runs during a settlement. This document states, and
backs with tests, what such a route can and cannot reach.
The guarantee is structural rather than filtered. Routes execute as the Trampoline
instance (`msg.sender` is the instance), never as `GPv2Settlement`, so they inherit none
of the settlement's buffer-spend or approval-granting authority ([`#topology`](../design-document#topology)).
`execute` sweeps the instance's full remaining balance of both trade tokens to the
settlement ([`#residue`](../design-document#residue)), so the instance is empty of
trade tokens at rest, and each sub-solver has a distinct instance. The blast radius of
any route is the trade capital in flight during its own settlement.
The tests run against the **real deployed `GPv2Settlement`** on a mainnet fork, exercising
CoW's actual semantics (allowance checks, `onlySolver`, the reentrancy guard,
owner-scoped order state). A controlled ERC-20 buffer is seeded into the settlement in
`setUp`, so every "no value moved" assertion runs against non-zero value.
The invariant asserted is that value does not move — buffer balances, allowances, and
order state are unchanged after the route runs. A revert is one mechanism that enforces
this, but it is not the bar: several attacks are also proven inside a settlement that
**succeeds** (the failed attack swallowed so the transaction finalizes), because a real
adversary wants the settlement to complete unattributed rather than self-abort.
## Reachability
| Target | Reachable by a route | Why | Backing |
| --- | --- | --- | --- |
| Own instance balance, in flight | **yes** | The route runs as the instance, so during its own settlement it moves the instance's balance freely. This is the boundary's positive edge, and isolation is instance-scoped, not token-scoped: a route reaches the capital passing through its own instance while the settlement's buffer of the same token stays put. At rest there is nothing left to reach — the sweep empties the instance of trade tokens, so a planted approval drains nothing. | Cited: `test_execute_sweeps_full_route_output_and_emits_executed`, `test_execute_buy_order_sweeps_unconsumed_sell_token_to_settlement`, `test_planted_approval_cannot_reach_other_instances_residue` (`test/Trampoline/Trampoline.t.sol`) |
| Settlement token buffers | no | A `transferFrom` from the settlement needs an allowance the settlement never granted the instance. Proven inside a *successful* settlement where the failed attempt is swallowed, so the guarantee holds even when the transaction finalizes rather than aborting. | `test_settlement_succeeds_but_buffer_transferFrom_moves_nothing` |
| Settlement via re-entering `settle()` | no | `settle` is `nonReentrant onlySolver`. A route always runs inside a live `settle`, so the reentrancy guard (the first modifier) reverts before `onlySolver` is even reached. `onlySolver` is the backstop that applies if the guard weren't engaged — the instance is not an allow-listed solver. | `test_route_cannot_reenter_settle` (guard), `test_route_settle_call_is_rejected_by_onlySolver` (backstop) |
| Another party's order state | no | `setPreSignature` and `invalidateOrder` require the order's encoded owner to equal `msg.sender`. A route is the instance, so it cannot pre-sign or cancel an order owned by anyone else; the victim's state is unchanged. A route can pre-sign an order it *owns*, but nobody places orders naming a Trampoline, so that capability is inert. | `test_route_cannot_presign_another_owners_order`, `test_route_cannot_invalidate_another_owners_order` |
| Vault-relayer allowances (user funds) | no | The vault relayer pulls users' sell tokens and is `onlyCreator` — only the settlement may call it. A route calling it is rejected at the gate even against a user who really approved the relayer. | `test_route_cannot_pull_through_vault_relayer` |
| Other instances' balances | no | Cross-instance isolation is a property of per-instance EVM storage; an approval or call from one instance grants nothing over another's balance (instances end settlements swept empty, but a stray token could still land outside the flow). | Cited: `test_route_cannot_call_another_instances_execute`, `test_planted_approval_cannot_reach_other_instances_residue`, `test_signature_from_other_factory_generation_fails` (`test/Trampoline/Trampoline.t.sol`) |
| Escrow collateral | no | Collateral lives in the `Escrow` contract, which never routes funds through a Trampoline; payouts are gated to escrow's own access-controlled roles, unreachable from a route. | Cited: `test/Escrow/AccessControl.t.sol`, `test/Escrow/SubSolverActions.t.sol` |
Two directions are deliberately inert rather than blocked, because they move value
*toward* the settlement:
| Action | Effect | Backing |
| --- | --- | --- |
| Approving the settlement | Grants the settlement an allowance over the *instance's* funds, not the reverse; the instance holds nothing for it to reach. | `test_route_approving_settlement_is_inert` |
| Sending native value at the settlement | A one-way donation; the settlement ends richer, the instance poorer, nothing extracted. | `test_route_sending_value_at_settlement_is_inert` |
## Running the proofs
The suite (`test/fork/SettlementIsolation.t.sol`) is fork-gated. It uses a public RPC by
default, so it runs in CI without extra configuration; override with `MAINNET_RPC_URL`,
or set it empty to skip when offline.
```
MAINNET_RPC_URL= forge test --match-path test/fork/SettlementIsolation.t.sol
```
---
# FILE: reference/cow-fee-collection.md
# CoW Protocol — Fee Collection & Enforcement (reference)
> Consolidated from the official docs, the settlement contract source, and the autopilot code.
> Captured 2026-07-21 while analyzing fee handling for BYOS. Sources:
> [settlement contract docs](https://docs.cow.fi/cow-protocol/reference/contracts/core/settlement),
> [accounting](https://docs.cow.fi/cow-protocol/reference/core/auctions/accounting),
> [rewards](https://docs.cow.fi/cow-protocol/reference/core/auctions/rewards),
> [governance fees](https://docs.cow.fi/governance/fees),
> [`GPv2Settlement.sol`](https://github.com/cowprotocol/contracts/blob/main/src/contracts/GPv2Settlement.sol),
> [`autopilot/src/domain/fee`](https://github.com/cowprotocol/services/blob/main/crates/autopilot/src/domain/fee/mod.rs),
> [`autopilot/src/domain/settlement`](https://github.com/cowprotocol/services/blob/main/crates/autopilot/src/domain/settlement/mod.rs).
>
> Why this matters for BYOS: separating the fee is the **solver's job**, done by pricing, not by
> the contract. In BYOS the executed amounts are signed by the sub-solver, so the fee wedge is
> under sub-solver control — see the BYOS section at the end, and
> [`#gas`](../design-document#gas) for what BYOS actually does with it.
## The one-sentence model
There is no fee logic on-chain. A fee is a **price wedge**: the solver pays the user slightly
less than the trade produced, the difference stays in `GPv2Settlement`'s balance, and once a week
the protocol computes off-chain who owes what and settles up.
## Fee types
| Fee | What it covers | Who receives it | How the amount is set |
|---|---|---|---|
| Network fee | Gas of the settlement | The solver — the cut parks in the settlement's buffers and reaches the solver through the weekly payout, in native token | Solver's own cut, typically in the sell token; the protocol does **not** reimburse gas |
| Protocol fee | CoW DAO revenue | CoW DAO | Fee policies attached to each order by the autopilot (surplus %, volume %, price improvement) |
| Partner fee | Integrator revenue | The partner | Declared in the order's appData, capped by the protocol |
Orders sign `feeAmount = 0` since the 2023 fee-model change. The `feeAmount` field and its
proportional-scaling math still exist in `GPv2Settlement.sol` but are a dead path for new orders.
All three fee types above travel the same physical route: a wedge in the executed prices.
## One order, end to end (a sell order)
One concrete example threaded through every component. The user sells 1 WETH for USDC with a
limit price of 2,400. The best route delivers 2,500 USDC. Total fees come to 20 USDC: 1 USDC
as the solver's gas cut, 19 USDC of protocol and partner fees.
1. The user signs the order — sell 1 WETH, receive at least 2,400 USDC, `feeAmount = 0` — and
posts it to the orderbook API.
2. The autopilot puts the order into the next auction, attaching the fee policies (protocol
surplus/volume fee, any partner fee from appData) and a native ETH price per token, and
broadcasts the auction to all solvers.
3. The solver engine finds the route: 1 WETH in, 2,500 USDC out. It can ignore protocol and
partner fees — the driver handles those next — but the gas cut is the engine's own job; the
driver never inserts one. With gas estimated at about 1 USDC worth of ETH, the engine quotes
2,499 to its driver while the route still delivers 2,500, keeping the difference. (The
solver-driver API also has a legacy `fee` field for taking the cut in sell token instead.)
Note the engine thinks in amounts and profitability only; it never builds clearing prices.
4. The driver applies the fee policies on top, by shifting only the clearing prices: 19 USDC
of protocol and partner fees move the user's executed amount from 2,499 down to 2,480. The
route calldata is untouched. It bids with score = user surplus + protocol fees.
5. The autopilot picks the winning bid and the driver submits `settle()`.
6. `GPv2Settlement` executes: pull 1 WETH from the user via the vault relayer, run the route
interactions (2,500 USDC arrive in the contract), pay the user 2,480 USDC. The contract
checks exactly two things — the user got at least their limit price, and the caller is an
allow-listed solver. The 20 USDC difference just stays in the contract's ERC20 balance;
that residual **is** the fee. No transfer, no recipient, no event. It sits commingled with
everything else (the "buffers"), and solvers may even spend buffer balances as liquidity in
later settlements.
7. The autopilot observes the settlement on-chain, decodes the calldata, matches it to the
promised solution, and recomputes surplus and the fee breakdown from the executed amounts
plus the auction's fee policies. Solver reporting is not trusted. The per-trade result is
stored and public on Dune.
8. The weekly accounting (Tuesday 00:00 UTC to Tuesday 00:00 UTC) nets per solver: rewards,
minus penalties, minus protocol and partner fees, plus the solver-owned imbalances its
settlements created (gas cuts, slippage) converted to native token. Net positive pays out;
net negative is recorded as an overdraft, backed by the bond.
```mermaid
sequenceDiagram
autonumber
participant U as User
participant AP as Orderbook / Autopilot
participant D as Driver
participant E as Solver engine
participant S as GPv2Settlement
participant W as Weekly accounting
U->>AP: sign + post order: sell 1 WETH,
min 2,400 USDC, feeAmount = 0
AP->>D: auction: order + fee policies + native prices
D->>E: order
E->>D: solution: route delivers 2,500 USDC,
quote 2,499 (1 USDC kept as gas cut)
D->>D: apply fee policies: shift clearing prices
from 2,499 down to 2,480 (route untouched)
D->>AP: bid, score = surplus + protocol fees
AP-->>D: auction won
D->>S: settle()
S->>U: pull 1 WETH (vault relayer)
S->>S: run route interactions: 2,500 USDC arrive
S->>U: pay 2,480 USDC (limit price checked on-chain)
Note over S: 20 USDC remain in the contract.
That residual balance IS the fee.
No transfer, no recipient, no event.
AP->>AP: decode tx, recompute surplus + fee breakdown
AP->>W: week's settlements (Tue 00:00 UTC to Tue 00:00 UTC)
W->>W: net per solver: rewards − penalties − fees
+ solver-owned imbalances, in native token
W->>D: solver payout in native token
(or overdraft if negative)
```
Two consequences of the score formula:
- Ranking is fee-neutral. Score counts protocol fees *as if collected*, computed by the autopilot
from the executed amounts. A solver that skips the fee gives the user more surplus but the score
is the same — skipping buys no competitive edge.
- The fee debt is independent of collection. The autopilot derives what the solver owes from the
executed prices and the fee policies. Whether the solver actually kept a wedge only decides
whether the debt is covered by retained balance or comes out of the solver's own rewards.
## The same trade as a buy order
The mechanism does not change with the order kind — only the side the wedge sits on. Suppose the
user instead signs a buy order: receive exactly 2,400 USDC, pay at most 1 WETH. The route needs
0.96 WETH to produce 2,400 USDC, and the same 20 USDC of fees is now 0.008 WETH.
The driver shifts the sell side of the price instead: the user pays 0.968 WETH, the route
consumes 0.96, and 0.008 WETH never leaves the contract. The user receives exactly the 2,400
USDC they signed for.
```mermaid
sequenceDiagram
autonumber
participant U as User
participant S as GPv2Settlement
participant R as Route (AMM/DEX)
Note over S: clearing prices shifted so
the user pays 0.968 WETH
S->>U: pull 0.968 WETH (vault relayer)
S->>R: interaction: swap 0.96 WETH
R-->>S: 2,400 USDC
S->>U: pay exactly 2,400 USDC
Note over S: 0.008 WETH remain in the contract.
The wedge is in the sell token.
```
Side by side:
| | Sell order | Buy order |
|---|---|---|
| User fixes | the input: 1 WETH | the output: 2,400 USDC |
| Driver's price shift | proceeds down: route delivers 2,500, user receives 2,480 | payment up: route consumes 0.96 WETH, user pays 0.968 |
| Wedge sits in | buy token (USDC) | sell token (WETH) |
| Contract's limit check | received ≥ 2,400 USDC | paid ≤ 1 WETH |
Everything after `settle()` — recompute, weekly netting — is identical for both kinds.
## Enforcement layers
Nothing about fees is enforced by the contract. Enforcement is ex-post accounting (steps 7 and 8
above) against money the solver is owed, with the bond and the allow-list behind it. The layers,
inside-out:
```mermaid
flowchart TD
L1["Layer 1 — contract:
limit prices + solver allow-list only.
Zero fee logic."]
L2["Layer 2 — autopilot:
recomputes the fee debt from chain data.
Solver reporting is not trusted."]
L3["Layer 3 — weekly netting:
fees withheld from payouts.
Negative net → overdraft, no payout."]
L4["Layer 4 — bond + membership:
CIP-52 slashing of the solver bond;
removal from the allow-list ends the business."]
L1 --> L2 --> L3 --> L4
```
The model is trust-minimized, not trustless: it works because the fee debt is deterministically
computable from on-chain data, and because a bonded solver has more at stake (bond + future
revenue) than any single week's shortfall. A solver that walks away is recoverable only up to its bond.
## Who does what — summary
| Step | Actor | On-chain? |
|---|---|---|
| Decide the fee amount per order | Autopilot (fee policies) + solver (network fee) | No |
| Separate the fee from the user's proceeds | The driver, by shifting clearing prices — the settlement contract has no fee logic and never checks the split | Encoded in calldata; not checked |
| Hold the fee | `GPv2Settlement` buffers, commingled | Yes, passively |
| Compute what each solver owes | Autopilot, from decoded calldata | No |
| Collect | Weekly accounting: withheld from payouts, transferred to DAO/partners | One accounting tx per week |
| Punish shortfalls | Overdraft → bond slashing → allow-list removal | Governance |
## Refinements from the CoW solvers team (meeting 2026-07-22, Haris Angelidakis)
Validated in a call with CoW's solver team; these sharpen or correct the docs-derived picture
above.
- The default driver does the protocol/partner fee separation itself, by post-processing the
solver engine's solution. The engine reports raw route output ("route delivers 100 USDC"); the
driver knows the fee policies and shifts only the clearing-price vector so the user receives
98 and 2 stays in the settlement contract. Interactions calldata is never touched. A solver
engine behind the default driver can be completely oblivious to protocol fees.
- Gas is never reimbursed. The solver takes its own cut from the trade (the solver-driver API has
a legacy `fee` field for a sell-token cut; the driver will not insert one for you). After
protocol/partner fees are accounted, all remaining imbalances a settlement created are solver
property: positive gets paid out weekly, negative is owed.
- Solver-owned imbalances are converted to native token using observed exchange rates in roughly
a one-hour window around the trade — not the auction's native prices (changed recently). The
auction JSON's native prices are a sizing heuristic for the gas cut, occasionally bogus. An
order cannot enter an auction without a native price. On L2s the protocol withdraws settlement
contract fees roughly hourly; payouts stay weekly (Tuesday to Tuesday).
- CIP-74 (late 2025): per auction, a solver's reward is capped at the protocol fees its solutions
collected — 50% of them on mainnet, Arbitrum, BNB (revenue split). Zero protocol fee collected
means zero reward, even on a win. Unspent cap budget plus penalties fund CIP-85 consistency
rewards (closeness-to-winner across all orders), computable only after the accounting week
closes.
- Penalties are capped per auction (0.010 ETH on mainnet — the `c_l` in
[the glossary](../glossary)). Negative weekly totals are recovered first from the solver's
own collected native-token imbalances, then via the on-chain overdraft contract (event emitted;
solver repays partially or fully).
- Real-time tracking: the competition endpoint exposes score and reference score per auction
seconds after it closes (reward = score − reference); Dune lags ~2 hours. A driver callback
reporting settle outcome / reward is a requested feature, not yet built.
- Batching context: with the fair combinatorial auction's multiple winners, single-order bids
stay competitive; batch solutions matter in maybe 3–5% of auctions. CoW considers batch support
a nice-to-have for a v0 BYOS, not a requirement.
## Why this matters for BYOS
Updated 2026-07-22 after the meeting above; the original 2026-07-21 analysis assumed BYOS applies
fee policies itself, which the CoW-run driver setup makes wrong.
BYOS joins the CoW bonding pool, so the CoW core team runs the driver (key custody requirement;
reduced pool $50k + 500k COW, full pool $500k + 1.5M COW, KYB for mainnet). That driver inserts
the protocol/partner fee wedge by price shift, downstream of anything the sub-solver signs.
Consequences:
1. Protocol-fee separation is not a BYOS job in the default setup. Sub-solver executions and
outflows should be defined as **raw, pre-fee route amounts** — the same convention the
solver engine uses toward the driver. The
earlier concern that a sub-solver could size flows to leak the fee wedge into the trampoline
([`#residue`](../design-document#residue)) dissolves under this convention: the
wedge is created by the driver's price shift after the raw amounts, so it lands in
`GPv2Settlement`, not the instance.
2. The dispute model needs one amendment: on-chain `settle()` calldata will deviate from signed
raw tuples by exactly the driver's fee transform. The transform is deterministic (policies are
public per auction), so disputes must apply it to the signed tuples before comparing —
see [`#proposal-schema`](../design-document#proposal-schema).
3. The gas cut is the piece BYOS truly owns. The driver won't take it, the protocol won't
reimburse it, and CIP-74 caps the reward at 50% of protocol fees collected — so a settlement
collecting no protocol fee earns nothing while BYOS pays gas. Gatekeeping must ensure each
proposal leaves room for the gas cut *and* the driver's fee shift above the user's limit price;
a sub-solver quoting exactly at the limit produces an infeasible solution.
4. Penalty passthrough is trackable per auction in near real time via the competition endpoint,
which supports the planned per-sub-solver running balance with a cutoff at the known worst
case (`c_l`).
The enforcement-layer mapping, revised: autopilot recompute → CoW-run driver (protocol fees) plus
BYOS gatekeeper (gas + feasibility); weekly netting → per-sub-solver running balance off the
competition endpoint; solver bond → escrow (sized for Track A); allow-list → proposal API access.
---
# FILE: reference/cow-solver-slashing-policy.md
# CoW Protocol Solver Penalty & Slashing Framework
There are **three enforcement layers**: smart contract (on-chain), automated off-chain (autopilot/driver code), and governance (DAO social consensus).
---
## Layer 1: Smart Contract Enforcement (On-Chain, Automatic)
Hard-coded in the settlement contract and cannot be bypassed.
| Rule | What Happens |
|------|-------------|
| **Limit price violation** | Transaction reverts — orders cannot execute at prices worse than the user's limit |
| **Solver not whitelisted** | Transaction reverts — only bonded, approved solvers can submit |
---
## Layer 2: Automated Off-Chain Enforcement (Autopilot + Driver Code)
Enforced programmatically by the autopilot and driver services.
### 2a. Participation Guard — Solver Banning (Autopilot)
| Policy | Trigger | Default Threshold | Penalty | Duration |
|--------|---------|-------------------|---------|----------|
| **Non-settling** | Won N consecutive auctions, settled none | 3 consecutive unsettled wins | Auction ban | 5 min |
| **Low-settling** | Settlement failure rate too high across window | >90% failure over 100 auctions (min 3 wins) | Auction ban | 5 min |
Both are configurable and enabled by default. Banned solvers receive HTTP notifications (`Banned { reason, until }`).
**Configuration parameters:**
| Parameter | Default | Purpose |
|-----------|---------|---------|
| `db_enabled` | true | Master switch for participation guard |
| `solver_blacklist_cache_ttl` | 5m | How long bans last |
| `non_settling_solvers_blacklisting_enabled` | true | Enable non-settling policy |
| `non_settling_last_auctions_participation_count` | 3 | Auction window for non-settling policy |
| `low_settling_solvers_blacklisting_enabled` | true | Enable low-settling policy |
| `low_settling_last_auctions_participation_count` | 100 | Auction window for low-settling policy |
| `low_settling_min_wins_threshold` | 3 | Min wins before evaluation |
| `solver_max_settlement_failure_rate` | 0.9 | Max acceptable failure rate (90%) |
### 2b. Settlement Validation (Driver)
Before any settlement hits the chain, the driver validates:
- **Trusted token check** — internalized interactions can only use trusted tokens; violation produces `NonBufferableTokensUsed` error and settlement is blocked
- **Simulation** — settlement must not revert in simulation
- **Gas safety** — sufficient gas parameters required
### 2c. Settlement Deadline Enforcement
Settlements must land within chain-specific block deadlines:
| Chain | Deadline |
|-------|----------|
| Ethereum | 3 blocks |
| Gnosis/Polygon/BNB/Avalanche | 10-20 blocks |
| Arbitrum/Base/Linea/Ink | 20-40 blocks |
Missing the deadline can trigger **immediate denylisting** pending manual inspection.
---
## Layer 3: Governance / Social Consensus (DAO-Enforced, CIP-11+)
These rules are **not automatically enforced** by code. The core team monitors settlements with tooling, flags suspicious behavior, and the DAO votes on slashing via CIPs.
### 3a. Unfair Solutions (CIP-11)
- **Violation**: Providing clearing prices worse than what users would get on reference AMMs (Uniswap, Balancer, Curve, etc.), also called the **EBBO rule** (Ethereum Best Bid/Offer)
- **Consequence**: Monitoring flag, potential slashing at DAO discretion
### 3b. Score Inflation (CIP-11)
- **Violation**: Creating fake tokens or wash-trading to artificially inflate solution scores
- **Consequence**: Slashing
### 3c. Illegal Buffer Usage (CIP-11)
- **Violation**: Using internal buffers beyond legitimate AMM replacement; systematic trading with unsafe tokens; creating buffer attack vectors
- **Consequence**: Flagging, slashing
- **Real-world example**: CIP-22 — Barter Solver failed to revoke approvals on an old contract, a hacker drained ~$166K from the settlement contract. Bond was slashed to reimburse losses.
### 3d. Illegal Surplus Shifts / Local Token Conservation (CIP-11)
- **Violation**: Intentionally transferring surplus between orders that share common tokens (one user's surplus subsidizes another)
- **Consequence**: Slashing
### 3e. Overbidding / Pennying (CIP-13)
- **Violation**: Systematically inflating reported scores beyond `surplus + fees - gas`, expecting rewards to cover losses
- **Detection formula**: `avg(score) > avg(surplus + fees - gas) + epsilon`
- **Consequence**: Slashing (currently retroactive/manual, not automated)
### 3f. Pre/Post Hook Non-Execution (CIP-11)
- **Violation**: Intentionally excluding hooks specified in order app data
- **Consequence**: Slashing
### 3g. Catch-All: Other Malicious Behavior (CIP-11)
- **Violation**: Any intentional harm to users or the protocol not covered above
- **Consequence**: Slashing at DAO discretion
- **Real-world example**: CIP-55 — GlueX solver slashing (passed vote)
---
## Layer 4: Economic Penalties (Reward Mechanism, CIP-38+)
Even without explicit "slashing," the reward formula itself penalizes poor behavior.
### Performance Reward Formula
```
performanceReward_i = cap(totalScore - referenceScore_i - missingScore_i)
```
Where:
- `totalScore` = sum of all winning solutions' scores
- `referenceScore_i` = counterfactual total score if solver i hadn't participated
- `missingScore_i` = scores of solver i's solutions that **reverted** (failed settlements)
Failed settlements directly reduce rewards — and can make them negative.
### Capping (Bounds)
```
cap(x) = max(-c_l, min(c_u, x))
```
| Chain | Lower Bound (max loss per auction) | Upper Bound |
|-------|------------------------------------|-------------|
| Ethereum/Arbitrum/Base | 0.010 ETH | beta x protocol fees earned |
| Gnosis | 10 xDAI | beta x protocol fees earned |
| Polygon | 30 POL | beta x protocol fees earned |
| Avalanche | 0.3 AVAX | beta x protocol fees earned |
| BNB | 0.04 BNB | beta x protocol fees earned |
| Linea/Ink | 0.0015 ETH | beta x protocol fees earned |
| Plasma | 30 XPL | beta x protocol fees earned |
A solver can owe the protocol money if their reverted settlements drag down the total score.
### Buffer Slippage Accounting
Positive/negative slippage from buffer usage is settled **weekly**. Negative slippage = solver pays.
---
## Bonding Requirements (The Stake at Risk)
All DAO-enforced slashing ultimately hits the solver's **bonding pool**.
| Pool Type | Stablecoins | COW Tokens | Notes |
|-----------|-------------|------------|-------|
| **Standard (CoW DAO pool)** | $500,000 | 1,500,000 COW | CoW DAO safe is sole signer |
| **Reduced pool (CIP-44)** | $50,000 to $100,000 over 1 year | 500,000 to 1,000,000 COW over 1 year | Must already be vouched under main pool |
Solvers in the CoW DAO pool also pay a **15% service fee** on weekly COW rewards (starting 6 months after joining).
---
## Summary: What Can Go Wrong for a Solver
| Risk | Enforcement | Automated? | Financial Impact |
|------|------------|------------|------------------|
| Limit price violation | Smart contract | Yes | Tx reverts (gas wasted) |
| Not whitelisted | Smart contract | Yes | Tx reverts |
| Consecutive non-settlement | Autopilot | Yes | Temp ban (5 min) |
| High failure rate | Autopilot | Yes | Temp ban (5 min) |
| Untrusted token internalization | Driver | Yes | Settlement blocked |
| Settlement revert on-chain | Reward formula | Yes | Negative reward (pay protocol) |
| Missed deadline | Autopilot | Yes | Immediate denylist |
| Unfair prices (EBBO) | DAO governance | No | Bond slashing |
| Score inflation | DAO governance | No | Bond slashing |
| Illegal buffer usage | DAO governance | No | Bond slashing |
| Surplus shifting | DAO governance | No | Bond slashing |
| Overbidding | DAO governance | No | Bond slashing |
| Hook non-execution | DAO governance | No | Bond slashing |
| Security negligence | DAO governance | No | Bond slashing (e.g. $166K in CIP-22) |
---
## Sources
- [Solver competition rules — CoW Docs](https://docs.cow.fi/cow-protocol/reference/core/auctions/competition-rules)
- [Solver rewards — CoW Docs](https://docs.cow.fi/cow-protocol/reference/core/auctions/rewards)
- [Bonding pools — CoW Docs](https://docs.cow.fi/cow-protocol/reference/core/auctions/bonding-pools)
- [CIP-11: Rules of the Solver Competition](https://forum.cow.fi/t/cip-11-rules-of-the-solver-competition-status-quo-and-an-update-proposal/1016)
- [CIP-22: Slashing of the Barter Solver](https://forum.cow.fi/t/cip-22-slashing-of-the-barter-solver-responsible-for-a-hack-causing-cow-dao-a-loss-of-1-week-fee-accrual/1440)
- [CIP-38: Solver Computed Fees & Rank by Surplus](https://forum.cow.fi/t/cip-38-solver-computed-fees-rank-by-surplus/2061)
- [CIP-55: Slashing of the GlueX Solver](https://forum.cow.fi/t/cip-55-slashing-of-the-gluex-solver/2649/3)
- [Measuring and banning overbidding — Forum](https://forum.cow.fi/t/measuring-and-banning-overbidding/1874)
---
# FILE: reference/solver-auctions.md
# CoW Protocol — Auctions & Solver Competition (reference)
> Consolidated from the official docs under .
> Captured 2026-06-18 for offline consultation while exploring the BYOS RFP ([BYOS RFP](https://forum.cow.fi/t/rfp-bring-your-own-solver-byos/3469)). For authoritative/current text, follow the source link in each section.
>
> Why this matters for BYOS: BYOS is a bonded solver that must win the standard CoW auction. Sub-solver proposals must produce a valid, competitive CoW solution under these rules.
CoW Protocol uses an implementation of the [Fair Combinatorial Auction](https://arxiv.org/abs/2408.12225) (FCA) to execute trades. A **solver** is an algorithm that takes an auction instance (valid orders, liquidity state, protocol rules/fees) and outputs one or more **solutions** selecting order subsets and feasible amounts.
---
## 1. What is solving (the problem)
Source:
**Inputs:** orders valid for the auction, state of liquidity sources, protocol rules including fees.
**Output:** one or multiple solutions selecting a subset of orders and specifying feasible amounts for each.
**Orders are modeled as acceptance sets** (the set of trades a user will accept):
- **Sell orders** — max sell amount, buy token, limit price (worst acceptable rate). *Fill-or-kill* (all or nothing) or *partially-fillable* (any amount up to max). Surplus = extra buy tokens received vs. the limit price.
- **Buy orders** — max buy amount, limit price. Fill-or-kill or partial. Surplus = savings vs. worst-case pricing.
- **CoW AMM orders** — always valid across auctions; solver specifies buy and sell amounts, priced from the AMM's reserves (e.g. constant product).
**Protocol fees** map accepted trades to non-negative token vectors (costs charged to users). Solver fees (gas/execution) are handled separately during optimal bidding.
**A valid solution must satisfy:**
1. **Incentive compatibility** — respect order acceptance sets (limit prices).
2. **Uniform directional clearing prices (UDCP)** — identical pricing for the same token pair in the same direction.
3. **Competition rules** — the protocol-mandated principles in §2.
**Scoring:** solutions are ranked by **total surplus + protocol fees**, denominated in a common unit (native token) via external price feeds. Buy and sell orders use distinct surplus formulas based on their limit prices and asset valuations.
---
## 2. Solver competition rules
Source:
Rules are enforced across three layers: **smart contracts**, **off-chain protocol infrastructure**, and **governance / social consensus**.
### Smart-contract enforcement
1. **Limit price constraint** — orders cannot execute if limit prices are violated.
2. **Solver whitelisting** — only whitelisted solvers (via bonding pools, §6) can submit settlements.
### Off-chain protocol rules
- **Scoring & validity:** a valid solution must have a **positive score** and respect **UDCP** (orders on the same directed token pair get identical prices; exceptions for orders with hooks, to account for gas).
- **Fair Combinatorial Auction (winner selection):**
- Find the highest-scoring bid for each **directed token pair** (pair + direction).
- These best bids are **reference outcomes** (optimal execution vs. external liquidity).
- **Batched bids** (covering multiple directed pairs) are **filtered out** if they underperform the reference outcome on *any* pair.
- Winners are chosen from surviving batched bids plus best single-pair bids, ensuring all orders on the same directed pair belong to one winning bid.
- Rewards follow a **second-price auction** model (see §3).
- **Settlement validity:** execution must match the winning solution (solver, score, amounts); pre-hooks before fund transfers, post-hooks after distribution; partially-fillable orders run pre-hooks once but post-hooks per fill; settlement must land before network deadlines (~3 blocks mainnet → ~40 blocks Arbitrum/BNB).
- **Buffer usage:** solvers may use settlement-contract funds for protocol/partner fee storage, network fee coverage, slippage offsets, and internal trades with "trusted" tokens marked in auction data.
### Governance / social-consensus rules
Monitored for systematic violation; penalties include denylisting or slashing.
- **EBBO** (Ethereum Best Bid and Offer) — execution must be at least as good as baseline liquidity (e.g. on mainnet: Uniswap v2/v3, Sushiswap, Swapr, Balancer v2, Pancakeswap) against base tokens (WETH, DAI, USDC, USDT, COMP, MKR, WBTC, GNO). Details in §5.
- **Prohibited behaviors:** score inflation (fake tokens / wash trading), illegal buffer usage, surplus shifting between orders sharing tokens, pennying/overbidding, hook violations. The protocol reserves discretion to slash other malicious conduct.
---
## 3. Solver rewards
Source:
Governed by CIPs 20, 27, 36, 38, 48, 57, 67, 72, 74, 85 (see [`solver-cips.md`](./solver-cips.md)). Tracking: [Dune dashboard](https://dune.com/cowprotocol/cow-solver-rewards). Rewards paid weekly in COW.
### Performance reward
```
performanceReward_i = cap( totalScore − referenceScore_i − missingScore_i )
```
- **totalScore** — sum of all winning solutions' scores in the auction.
- **referenceScore_i** — score of a counterfactual auction excluding solver *i*'s bids.
- **missingScore_i** — scores from solver *i*'s solutions that reverted.
- **cap(x) = max(−c_l, min(c_u, x))**. Upper cap `c_u` = β (chain fraction) of protocol fees earned by that solver; lower cap `c_l` is chain-specific.
| Chain | β | Lower cap c_l |
|---|---|---|
| Ethereum, Arbitrum, Base | 50% | 0.010 ETH |
| Gnosis Chain | 100% | 10 xDAI |
| Avalanche | 100% | 0.3 AVAX |
| Polygon | 100% | 30 POL |
| BNB | 100% | 0.04 BNB |
| Linea, Ink | 100% | 0.0015 ETH |
| Plasma | 100% | 30 XPL |
### Consistency reward (CIP-85)
Incentivizes consistent participation. Each auction contributes `β · protocolFee_i − performanceReward_i`, distributed proportionally to the number of executed orders for which the solver submitted a solution.
### Price-estimation (quote) rewards
For solvers that quote fill-or-kill market orders that then execute. Eligibility: fill-or-kill market order, verified quote (calldata simulation succeeds), order executed, and the proposed execution at least matches the quote and passes fairness filtering. Reward = native amount or 6 COW, whichever is less (e.g. Ethereum 0.0007 ETH, Arbitrum/Base 0.00024 ETH, Gnosis 0.15 xDAI).
### Slippage & buffers
Slippage between bidding and execution is tracked weekly: positive slippage accrues in the settlement contract; negative slippage is covered via buffer usage. Net slippage is paid to or collected from the solver.
### Strategic note
Solvers report a **cost-adjusted score** (they bear gas + revert penalties). Recommended approach: group orders by directed token pair, route optimally per group, and additionally submit batched solutions where combining pairs yields efficiencies.
---
## 4. Accounting process
Source:
- **Weekly, Tuesday→Tuesday UTC.** Auctions are bucketed into a week by block deadline; quote rewards by execution block.
- **Rewards/penalties:** successful on-time submissions earn native-token rewards; reverts/late submissions incur penalties. Dune's `capped_payment` column tracks per-auction amounts.
- **COW conversion:** performance + quote rewards denominated in COW, converted using the average COW/USD price over the final 24h of the period (manipulation resistance).
- **Protocol & partner fees:** denominated in the order's surplus token, converted to native token via auction prices. Protocol fees → CoW DAO; partner fees → designated recipient. DAO amount = `protocol_fee − partner_fee`.
- **Buffer accounting:** protocol/partner fees collected in the settlement contract, network fees (sell-token, converted to native and paid weekly), and per-tx slippage (raw imbalance minus expected fees, converted via price feed). Network fee derived from the difference between actual amounts and amounts implied by the fee-free UCP vector.
- **Payout adjustments:** **service fee** on positive COW rewards (default 15%, per CIP-48); **minimum transfer thresholds**; **overdraft handling** via the overdrafts manager contract `0x8fd67ea651329fd142d7cfd8e90406f133f26e8a` (`solverOverdraftBalance`, `payOverdraft`); curated **auction price corrections**.
---
## 5. EBBO violations
Source: — framework from **CIP-52**.
- **Certificate of violation:** a reference routing on a block (and log index) between auction start and on-chain settlement, using only base liquidity + base tokens, establishes a baseline surplus. Violation magnitude = reference surplus − actual user surplus.
- **Challenge:** accused solver has 72h to propose an alternative block/index; the core team may set a new (final) certificate.
- **Reimbursement:** detected by core team or third parties; must be reported within 3 months. Solver gets a reimbursement demand in the surplus token and 72h to comply. Compliance closes the case.
- **Escalation/slashing:** non-compliance → auto deny-listing, forum statement, 3-day review, then a Snapshot vote. Successful CIP → bond slashed by the refund amount, proceeds to affected users. Bond replenishment allows reinstatement (DAO may replenish from treasury).
---
## 6. Bonding pools
Source:
- **Standard pool (CIP-7):** deploy a Mainnet Gnosis Safe with the CoW DAO safe as sole signer; once confirmed, fund with **$500,000** in yield-bearing stablecoins + **1,500,000 COW**. The pool can then vouch for solvers.
- **Reduced pool (CIP-44):** available to already-vouched solvers; grants full control over calldata and on-chain submission. Requires core-team approval. Lower requirements: **$50,000** stablecoins/ETH + **500,000 COW** initially, scaling to **$100,000** + **1,000,000 COW** over the following year. Still formally vouched under the CoW DAO pool.
- **Vouching:** call `Vouch` on the `VouchRegister` contracts (multi-chain) with the pool owner's signature; registers submission address, pool address, and rewards address in one tx.
- **Exit/dissolution:** `invalidateVouching` to leave; to dissolve, unvouch all solvers, post on the forum (≥6 days), then submit a CIP Snapshot proposal with tx simulations.
> **BYOS relevance:** BYOS must be a bonded solver. The RFP places the bonding capital out of scope, but BYOS will operate under a bonding pool (standard or reduced) and is liable for slashing — making the safety guarantees around sub-solver execution (Trampoline + escrow) directly load-bearing.
---
# FILE: reference/solver-cips.md
# CoW DAO CIPs — Solver Competition (reference index)
> CoW Improvement Proposals (CIPs) that **establish, modify, or discuss** the solver competition: who can solve, how solutions are scored and ranked, how winners are chosen, rewards, fees, bonding, and enforcement/slashing.
> Captured 2026-06-18 for the BYOS exploration ([BYOS RFP](https://forum.cow.fi/t/rfp-bring-your-own-solver-byos/3469)). Status/details summarized from forum search — open each link for authoritative text. The consolidated mechanics live in [`solver-auctions.md`](./solver-auctions.md).
## Auction & winner-selection mechanism
| CIP | Title | What it changes / discusses |
|---|---|---|
| **CIP-67** | [Moving from batch auction to the fair combinatorial auction](https://forum.cow.fi/t/cip-67-moving-from-batch-auction-to-the-fair-combinatorial-auction/2967) | The current core mechanism. Replaces single-winner batch auction with the FCA: per-directed-pair reference bids, filtering of underperforming batched bids, multiple winners. Higher throughput + stronger per-pair fairness guarantees. **Most relevant to how BYOS bids win.** |
| **CIP-11** | [Rules of the Solver Competition — status quo and update](https://forum.cow.fi/t/cip-11-rules-of-the-solver-competition-status-quo-and-an-update-proposal/1016) | Foundational competition rules: social-consensus (implicit) rules, global + local token-conservation constraints. |
| **CIP-13** | [Rules update: ban pennying](https://forum.cow.fi/t/cip-13-rules-of-the-solver-competition-update-proposal-to-ban-pennying/1119) | Prohibits pennying (deliberately inflating reported scores expecting rewards to cover losses). |
| **CIP-38** | [Solver Computed Fees & Rank by Surplus](https://forum.cow.fi/t/cip-38-solver-computed-fees-rank-by-surplus/2061) | Solvers compute their own "network fee" to cover gas; ranking moves to surplus-based. |
| **CIP-72** | [Aligning quoting and solving behavior of solvers](https://forum.cow.fi/t/cip-72-aligning-quoting-and-solving-behavior-of-solvers/3079) | Addresses solvers giving over-optimistic quotes but not matching bids at solve time. **Relevant to BYOS quote/solve consistency.** |
## Rewards & fees
| CIP | Title | What it changes / discusses |
|---|---|---|
| **CIP-20** | [Auction model for solver rewards](https://forum.cow.fi/t/cip-20-auction-model-for-solver-rewards/1405) | Establishes the auction-based (second-price) COW reward model for the competition winner. |
| **CIP-34** | [Testing Fee Models for CoW Protocol](https://forum.cow.fi/t/cip-34-testing-fee-models-for-cow-protocol/1984) | Early experimentation with protocol fee models. |
| **CIP-36** | [Adjusting and renewing solver rewards budget](https://forum.cow.fi/t/cip-36-adjusting-and-renewing-solver-rewards-budget/2244) | Renews the rewards budget (committed 8M COW for the competition). |
| **CIP-48** | [Solver rewards budget renewal & update of bonding pool operations](https://forum.cow.fi/t/cip-48-solver-rewards-budget-renewal-and-update-of-cow-dao-bonding-pool-operations/2493) | Budget renewal + introduces the **15% service fee** on positive COW rewards for designated/bonding-pool solvers. |
| **CIP-57** | [Solver rewards on all chains](https://forum.cow.fi/t/cip-57-solver-rewards-on-all-chains/2634) | Extends rewards across all operating chains (mainnet, Gnosis, Arbitrum, …). |
| **CIP-74** | [Align Solver Rewards with Protocol Revenue + volume-based fee](https://forum.cow.fi/t/cip-74-align-solver-rewards-with-protocol-revenue-and-introduce-a-volume-based-fee/3234) | Replaces fixed reward cap with a **dynamic cap tied to protocol fees** of the winning solution; adds a 2 bps unconditional volume fee. **Directly shapes BYOS economics** (the RFP's fee defaults to 0). See also the [retrospective](https://forum.cow.fi/t/cip-74-retrospective-aligning-rewards-with-revenue/3358) and the [small-order second-price issue](https://forum.cow.fi/t/second-price-auction-is-broken-for-small-orders-since-cip-74/3317). |
| **CIP-85** | [Performance and Consistency Rewards](https://forum.cow.fi/t/cip-85-performance-and-consistency-rewards/3377) | Current reward shape: fixes reward budget at 50% of protocol revenue; adds **consistency rewards** for reliable participation. |
### Related drafts
- [CIP-Draft: Align Solver Rewards with Protocol Revenue](https://forum.cow.fi/t/cip-draft-align-solver-rewards-with-protocol-revenue/3174) — hybrid model (solvers 25% of batch surplus when the protocol collects fees; users 50%, protocol 25%). Predecessor discussion to CIP-74.
- [CIP-Draft: Distributing COW rewards on mainnet for all chains](https://forum.cow.fi/t/cip-draft-distributing-cow-rewards-on-mainnet-for-all-chains/3042)
## Bonding, eligibility & enforcement
| CIP | Title | What it changes / discusses |
|---|---|---|
| **CIP-7** | [Allowing External Solvers](https://forum.cow.fi/t/cip-7-allowing-external-solvers/923) | Establishes bonding pools ($500k + 1.5M COW Safe owned by CoW DAO); pool creators signal liability to allow-list solver addresses. **The mechanism BYOS itself is bonded under.** |
| **CIP-44** | [Reduced bonding requirements](https://forum.cow.fi/t/cip-44-reduced-bonding-requirements/2424) | Reduced bonding pool ($50k + 500k COW, scaling up) granting full calldata/submission control; still vouched under the DAO pool. **A likely path for BYOS.** |
| **CIP-52** | [EBBO (fairness) specs, reimbursement & escalation](https://forum.cow.fi/t/cip-52-ebbo-fairness-specifications-reimbursement-procedures-and-escalation-mechanisms/2579) | Defines EBBO violation certificates, reimbursement, and slashing escalation. **Defines BYOS's liability surface.** |
| **CIP-55** | [Slashing of the GlueX solver](https://forum.cow.fi/t/cip-55-slashing-of-the-gluex-solver/2649) | Concrete precedent: a solver slashed for misbehavior — illustrates real enforcement. |
| **CIP-78** | [Dissolve Sprinter Bonding Pool](https://forum.cow.fi/t/cip-78-dissolve-sprinter-bonding-pool/3241) | Example of the pool-dissolution process in practice. |
| **CIP-Draft** | [Simplifying the operations of the CoW DAO bonding pool](https://forum.cow.fi/t/cip-draft-simplifying-the-operations-of-the-cow-dao-bonding-pool/3455) | Ongoing discussion on bonding-pool operations. |
## Most load-bearing for BYOS
1. **CIP-67** — the FCA mechanism BYOS must win under.
2. **CIP-85** + **CIP-74** — current reward/fee economics (the RFP says BYOS keeps 100% of rewards, fee defaults to 0).
3. **CIP-7 / CIP-44** — bonding pool BYOS operates under.
4. **CIP-52** — EBBO/slashing, i.e. BYOS's liability if a sub-solver's proposal harms users.
5. **CIP-72** — quote/solve consistency, relevant to proposal re-simulation.
> Note: CIP numbers/status evolve. Re-check the forum's [Governance](https://forum.cow.fi/c/governance) and Closed Proposals categories for anything newer than the capture date.