Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 6 additions & 4 deletions docs/cow-protocol/tutorials/arbitrate/solver/driver.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,9 +53,10 @@ The auctions sent to the driver by the autopilot only contain the bare minimum o
But usually a solver engine requires more information than that so the driver pre-processes the auction before forwarding it to the solver engine.
We also want to reduce the overall workload of a solver engine, since it's usually expensive to match an order (in terms of time, RPC requests, API calls, etc.).
That process includes:
* fetching additional metadata (e.g. token decimals)
* discarding orders that can definitely not be settled (e.g. user is missing balances)
* very basic prioritization of remaining orders (e.g. orders below or close to the market price are most likely to settle)

- fetching additional metadata (e.g. token decimals)
- discarding orders that cannot be settled (e.g. user is missing balances)
- very basic prioritization of remaining orders (e.g. orders below or close to the market price are most likely to settle)

### Fetching liquidity

Expand Down Expand Up @@ -92,11 +93,12 @@ The driver can be configured to use different submission strategies which it dyn
If the settlement does not expose any MEV (e.g. it executes all trades without AMMs) it's safe and most efficient to directly submit to the public mempool.
However, if a settlement exposes MEV the driver would submit to an MEV-protected RPC like [MEVBlocker](https://mevblocker.io).

Solvers that need parallel settlement submission can use [Solver7702Delegate](../../solvers/solver-7702-delegate.md). It keeps the existing solver EOA as the settlement caller while auxiliary EOAs provide additional nonce lanes if the main solver EOA has pending transactions.

### Flash Loans

The user is able to create a flash loan order's hint by attaching to the `appData` the specified metadata. The autopilot reads the order and cuts it into a [fair combinatorial batch auction](../../../concepts/introduction/fair-combinatorial-auction). Then the driver fetches the `appData` by calling the orderbook with `GET /v1/app_data/<app_data_hash>` for every order and caches them in memory. The driver should include the flash loan information into the batch auction's order before sending it to the solver(s).


```mermaid
sequenceDiagram
actor User
Expand Down
2 changes: 2 additions & 0 deletions docs/cow-protocol/tutorials/solvers/onboard.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@ The first step to joining the solver competition is to set up a solver. We have

API specification: <https://github.com/cowprotocol/services/blob/main/crates/solvers/openapi.yml>

Solvers that need parallel settlement submission can use [Solver7702Delegate](./solver-7702-delegate.md) to keep their existing solver EOA while adding extra submission nonce lanes.

## 3. Joining the shadow (test) competition

Once you have a solver running locally you can join the shadow competition to start testing your solver using CoW Protocol's order flow.
Expand Down
295 changes: 295 additions & 0 deletions docs/cow-protocol/tutorials/solvers/solver-7702-delegate.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,295 @@
---
sidebar_position: 11
---

# Using Solver7702Delegate for parallel settlement submission

`Solver7702Delegate` is an ERC-7702 delegation target for CoW Protocol solvers. It lets a solver keep using its existing allowlisted solver EOA, while extra submission EOAs send settlement transactions through that solver EOA.

The main use case is parallel settlement submission. A single EOA has one nonce lane. If settlement `B` uses nonce `101`, it has to wait for the nonce of settlement `A` with nonce `100` to be mined. With `Solver7702Delegate`, each auxiliary EOA has its own nonce lane, but the settlement contract still sees the solver EOA as `msg.sender`.

## Why use it?

Without ERC-7702, a solver that wants parallel submission would need one of these options:

- allowlist several solver EOAs;
- migrate to a smart contract solver account;
- keep using one EOA and accept one nonce lane.

`Solver7702Delegate` keeps the current solver identity. The solver EOA delegates execution to the delegate contract, and a fixed set of auxiliary EOAs can call through it.

```text
auxiliary EOA 1 -> solver EOA running Solver7702Delegate -> GPv2Settlement
auxiliary EOA 2 -> solver EOA running Solver7702Delegate -> GPv2Settlement
auxiliary EOA 3 -> solver EOA running Solver7702Delegate -> GPv2Settlement
```

Inside the delegate:

```text
msg.sender = auxiliary EOA
address(this) = solver EOA
```

Inside `GPv2Settlement`:

```text
msg.sender = solver EOA
```

This keeps settlement authorization tied to the solver EOA.

## Call shape

Normal settlement submission:

```text
from = solver EOA
to = GPv2Settlement
data = GPv2Settlement.settle(...)
```

ERC-7702 settlement submission:

```text
from = auxiliary EOA
to = solver EOA
data = bytes20(target) || targetCalldata
```

For a settlement:

```text
target = GPv2Settlement
targetCalldata = GPv2Settlement.settle(...)
```

The calldata format is packed on purpose:

```solidity
abi.encodePacked(bytes20(target), targetCalldata)
```

Do not use this format:

```solidity
abi.encode(target, targetCalldata)
```

The first 20 bytes are parsed as the target address. The remaining bytes are forwarded as calldata. Return data and revert data are bubbled back as-is.

## Contract model

`Solver7702Delegate` has no normal public methods. Its interface is the constructor and `fallback()`:

```solidity
constructor(address[5] memory approvedCallers);
fallback() external payable;
```

A fixed number of approved callers is set in the constructor. They are embedded into the deployed bytecode, not stored in contract storage. This keeps the hot path small and avoids mutable admin logic.

If calldata is shorter than 20 bytes, the fallback accepts ETH and returns. Otherwise, it checks that `msg.sender` is one of the approved callers, parses the target address from the first 20 bytes, and calls the target from the solver EOA context.

Only `CALL` is supported. `DELEGATECALL` is not supported, because the delegate already runs in the solver EOA context through ERC-7702.

There are no events. This keeps gas lower for the submission path.

## Setup

The exact setup depends on whether you use the reference driver or your own driver.

At a high level:

1. Choose a number of auxiliary EOAs up to the limit defined in `Solver7702Delegate`.
2. Deploy `Solver7702Delegate` with those auxiliary EOAs as approved callers.
3. Have the solver EOA sign an ERC-7702 authorization pointing to the delegate.
4. Submit a transaction that includes that authorization.
5. Verify that the solver EOA now delegates to the expected delegate.
6. Configure the driver to use the auxiliary EOAs for delegated submission.

If you use the reference driver, configure extra submission accounts for the solver. The driver treats these as EIP-7702 submission accounts and sets up the delegate and delegation during startup when the accounts are available as signers.

```toml
[[solver]]
name = "my-solver"
endpoint = "https://solver.example"
account = "<solver-private-key-or-signer-config>"
max-solutions-to-propose = 2
submission-accounts = [
"<auxiliary-private-key-or-signer-config-1>",
"<auxiliary-private-key-or-signer-config-2>",
...
]
```

The solver `account` must be able to sign the ERC-7702 authorization. Submission accounts must also be signer-backed, not address-only config.

When `submission-accounts` is set, the reference driver:

- requires every submission account to be a signer;
- supports up to the number of submission accounts defined in `Solver7702Delegate`;
- pads unused approved caller slots with the zero address;
- deploys the delegate through [Arachnid's deterministic CREATE2 deployer](https://github.com/Arachnid/deterministic-deployment-proxy) with zero salt;
- reuses the same delegate deployment if the expected code is already present (using CREATE2 and the same constructor arguments);

If `max-solutions-to-propose` is greater than `1`, `submission-accounts` must be configured. The driver refuses to start otherwise, because proposing multiple solutions needs parallel submission lanes.

If you run a custom driver, it must:

- deploy the delegate with the expected caller set;
- create the ERC-7702 authorization for the solver EOA;
- submit the authorization transaction;
- sign the authorization for the next nonce if the solver EOA also sends the authorization transaction;
- route delegated settlements with `to = solver EOA`;
- encode delegated calldata as `bytes20(target) || targetCalldata`;
- simulate the exact transaction shape before sending it.

The reference driver sends the delegation setup as an inert zero-value transaction from the solver EOA to the zero address with the ERC-7702 authorization attached. It does not combine delegation setup with delegate deployment, because a reverted transaction can still leave the ERC-7702 authorization applied.

With Foundry, this means using `cast wallet sign-auth --self-broadcast` when the solver EOA submits its own authorization transaction. Do not use `--self-broadcast` when a different funded EOA submits that transaction.

## Verification

Before using delegated submission, verify both pieces:

1. The solver EOA delegates to the expected delegate.
2. The delegate bytecode matches the expected caller set.

For ERC-7702, delegated account code has this shape:

```text
0xef0100 || delegateAddress
```

So the solver EOA code should be 23 bytes:

```text
0xef0100<20-byte delegate address>
```

The delegate bytecode must also be checked. Because approved callers are immutable constructor values, each solver's deployed runtime bytecode can be different. Do not only check that the delegate address has code. Compute the expected runtime bytecode from:

- the official `Solver7702Delegate` artifact;
- the compiler settings;
- the approved caller addresses.

Then compare that expected runtime bytecode, or its hash, with `eth_getCode(delegateAddress)`.

## How the driver should submit

Prefer direct submission when the solver EOA is idle:

```text
from = solver EOA
to = GPv2Settlement
data = settle(...)
```

Use delegated submission when the solver EOA already has a pending transaction and parallel submission is useful:

```text
from = auxiliary EOA
to = solver EOA
data = bytes20(GPv2Settlement) || settle(...)
```

Each auxiliary EOA has its own nonce lane. Do not send more than one transaction from the same auxiliary EOA unless you intentionally want nonce ordering for that account.

The driver should simulate the same transaction shape it will submit. For delegated submission, that means simulating with:

- `from = auxiliary EOA`;
- `to = solver EOA`;
- `data = bytes20(target) || targetCalldata`;
- the solver EOA already delegated to the expected `Solver7702Delegate`.

## Funding

Auxiliary EOAs pay gas. The delegate does not fund them.

If an auxiliary EOA is underfunded:

- skip it;
- alert the operator;
- use another idle funded account;
- fall back to direct submission if that is safe;
- otherwise wait.

## Replacing callers

Approved callers cannot be changed on an existing delegate. To replace any auxiliary EOA:

1. Deploy a new `Solver7702Delegate` with the new caller set.
2. Have the solver EOA sign a new ERC-7702 authorization to the new delegate.
3. Submit the replacement authorization transaction.
4. Verify the solver EOA code is `0xef0100 || newDelegate`.
5. Verify the new delegate runtime bytecode matches the new caller set.
6. Update driver config and monitoring.
7. Treat the old delegate as deprecated.

The old delegate remains on-chain, but it has no power unless the solver EOA delegates to it.

## Revoking delegation

To clear ERC-7702 delegation:

1. Have the solver EOA sign an ERC-7702 authorization to the zero address.
2. Submit a transaction with that authorization.
3. Verify that the solver EOA no longer has delegated code.
4. Disable delegated submission in the driver.
5. Fall back to direct solver EOA submission.

Revocation is useful when auxiliary submission is no longer needed, or when the caller set cannot be safely replaced immediately.

## Compromised auxiliary key response

Treat a compromised auxiliary EOA as severe. An approved auxiliary EOA can trigger arbitrary calls from the solver EOA context until it is removed.

Recommended response:

1. Disable ERC-7702 submission for that solver.
2. Consider disabling the solver while you assess the risk.
3. Deploy a new `Solver7702Delegate` without the compromised caller.
4. Have the solver EOA authorize the new delegate.
5. Verify delegation and delegate bytecode.
6. Move funds and revoke approvals from the solver EOA if needed.
7. Mark the old delegate/config as deprecated in monitoring.

If replacement cannot be done quickly, clear the solver EOA delegation and use direct submission only.

## Warnings

Approved auxiliary EOAs are trusted hot keys. They can cause arbitrary calls from the solver EOA context. That includes token transfers, approvals, protocol interactions, and ETH transfers if the solver EOA holds funds.

Use these safeguards:

- do not share auxiliary EOAs across solvers;
- keep solver EOA funds and approvals minimal;
- monitor auxiliary keys and delegated calls;
- rotate callers immediately if a key is compromised;
- only enable this on chains and infrastructure that support ERC-7702 transaction authorization handling;
- fall back to direct solver EOA submission when ERC-7702 is not supported.

`Solver7702Delegate` does not implement ERC-721 or ERC-1155 receiver functions. It is meant for settlement submission, not for receiving NFTs.

## FAQ

### How many auxiliary EOAs are supported?

Version 1 supports up to five immutable approved caller slots.

### Can the delegate call only `GPv2Settlement`?

No. It supports arbitrary targets. The driver should normally use `GPv2Settlement` as the target for settlement submissions.

### Can an auxiliary EOA drain funds?

The contract does not prevent that. Approved auxiliary EOAs are trusted execution keys. Keep funds and approvals on the solver EOA as small as possible.

### What happens when all auxiliary EOAs are busy?

The driver should wait or queue until one lane is idle. It should not reuse the same auxiliary EOA unless nonce ordering is intended.

### Should direct submission still be used?

Yes. Direct submission is cheaper and simpler when the solver EOA is idle. Delegated submission is useful when parallel settlement submission adds value.
Loading