Skip to main content

Generalized Wrappers

Generalized wrappers are smart contracts that enable custom logic to execute before and after CoW Protocol settlement operations. This reference documents the contract interfaces, implementation patterns, and on-chain behavior of the wrapper system.

Architectureโ€‹

Execution Flowโ€‹

Key Design Principlesโ€‹

  1. Abstract: There are very few limitations on what a wrapper can/can't do around a settlement transaction
  2. Efficient Encoding: Wrapper-specific data appended to minimize gas overhead
  3. Nested Support: Multiple wrappers chain by encoding addresses sequentially, allowing CoW orders
  4. Authentication: Only allowlisted wrappers can call settlement contract

Implementationโ€‹

Developers looking to integrate using wrappers should copy this all-in-one solidity file into their project as vendored dependency.

It provides a few base contracts which can serve as the foundation for integration:

  • ICowWrapper: Core interface all wrappers must implement
  • CowWrapper: Abstract base contract providing security and utilities that all wrappers should use
// SPDX-License-Identifier: MIT OR Apache-2.0
pragma solidity ^0.8;

/**
* Title: CoW Wrapper all-in-one integration file
* Author: CoW DAO
* This file is completely self-contained (ie no dependencies) and can be portably copied to whatever projects it is needed.
* It contains:
* * CowWrapper -- an abstract base contract which should be inherited by all wrappers
* * ICowWrapper -- the required interface for all wrappers
* * ICowSettlement -- A minimized interface and base structures for CoW Protocol settlement contract. From https://github.com/cowprotocol/contracts/blob/main/src/contracts/GPv2Settlement.sol
* * ICowAuthentication -- The authentication interface used by ICowSettlement. From https://github.com/cowprotocol/contracts/blob/main/src/contracts/interfaces/GPv2Authentication.sol
*/

/// @title CoW Protocol Authentication Interface
/// @author CoW DAO developers
interface ICowAuthentication {
/// @dev determines whether the provided address is an authenticated solver.
/// @param prospectiveSolver the address of prospective solver.
/// @return true when prospectiveSolver is an authenticated solver, otherwise false.
function isSolver(address prospectiveSolver) external view returns (bool);
}

/// @title CoW Protocol Settlement Interface
/// @notice Minimal interface for CoW Protocol's settlement contract
/// @dev Used for type-safe calls to the settlement contract's settle function
interface ICowSettlement {
/// @notice Trade data structure matching GPv2Settlement
struct Trade {
uint256 sellTokenIndex;
uint256 buyTokenIndex;
address receiver;
uint256 sellAmount;
uint256 buyAmount;
uint32 validTo;
bytes32 appData;
uint256 feeAmount;
uint256 flags;
uint256 executedAmount;
bytes signature;
}

/// @notice Interaction data structure for pre/intra/post-settlement actions which are supplied by the solver to complete the user request
struct Interaction {
address target;
uint256 value;
bytes callData;
}

/// @notice Returns the authentication contract used by the settlement contract.
function authenticator() external view returns (ICowAuthentication);

/// @notice Returns the address of the vaultRelayer, the target for approvals for funds entering the settlement contract.
function vaultRelayer() external view returns (address);

/// @notice Returns the domain separator for EIP-712 signing
function domainSeparator() external view returns (bytes32);

/// @notice Allows for approval of orders by submitting an authorized hash on-chain prior to order execution.
function setPreSignature(bytes calldata orderUid, bool signed) external;

/// @notice Settles a batch of trades atomically
/// @param tokens Array of token addresses involved in the settlement
/// @param clearingPrices Array of clearing prices for each token
/// @param trades Array of trades to execute
/// @param interactions Array of three interaction arrays (pre, intra, post-settlement)
function settle(
address[] calldata tokens,
uint256[] calldata clearingPrices,
Trade[] calldata trades,
Interaction[][3] calldata interactions
) external;
}

/// @title CoW Protocol Wrapper Interface
/// @notice Interface for wrapper contracts that add custom logic around CoW settlements
/// @dev Wrappers can be chained together to compose multiple settlement operations
interface ICowWrapper {
/// @notice A human readable label for this wrapper. Used for display in explorer/analysis UIs
function name() external view returns (string memory);

/// @notice The settlement contract used by this wrapper
/// @return The CowSettlement contract address
function SETTLEMENT() external view returns (ICowSettlement);

/// @notice Initiates a wrapped settlement call
/// @dev This is the entry point for wrapped settlements. The wrapper will execute custom logic
/// before calling the next wrapper or settlement contract in the chain.
/// @dev SECURITY: `settleData` is NOT guaranteed to remain unchanged through the wrapper chain.
/// Intermediate wrappers could modify it before passing it along. Do not rely on
/// `settleData` validation for security-critical checks.
/// @param settleData ABI-encoded call to ICowSettlement.settle() containing trade data
/// @param chainedWrapperData Encoded wrapper chain with the following format:
/// Structure: [uint16 len1][bytes data1][address wrapper2][uint16 len2][bytes data2][address wrapper3]...
///
/// Each wrapper in the chain consists of:
/// - 2 bytes: uint16 length of wrapper-specific data
/// - `length` bytes: wrapper-specific data for this wrapper
/// - 20 bytes: address of next wrapper (omitted for the final wrapper)
///
/// The final wrapper in the chain omits the next wrapper address and calls SETTLEMENT directly.
///
/// Example: [0x0005][0xAABBCCDDEE][0x1234...ABCD][0x0003][0x112233]
/// โ†‘len โ†‘data โ†‘next wrapper โ†‘len โ†‘data (final, no next address)
///
function wrappedSettle(bytes calldata settleData, bytes calldata chainedWrapperData) external;

/// @notice Confirms validity of wrapper-specific data
/// @dev Used by CowWrapperHelpers to validate wrapper data before execution. Reverts if the wrapper data is not valid for some reason.
/// @param wrapperData The wrapper-specific data to parse
function validateWrapperData(bytes calldata wrapperData) external view;
}

/// @title CoW Protocol Wrapper Base Contract
/// @notice Abstract base contract for creating wrapper contracts around CoW Protocol settlements
/// @dev A wrapper enables custom pre/post-settlement and context-setting logic and can be chained with other wrappers.
/// Wrappers must:
/// - Be approved by the ICowAuthentication contract
/// - Verify the caller is an authenticated solver
/// - Eventually call settle() on the approved ICowSettlement contract
/// - Implement _wrap() for custom logic
abstract contract CowWrapper is ICowWrapper {
/// @notice Thrown when the caller is not an authenticated solver
/// @param unauthorized The address that attempted to call wrappedSettle
error NotASolver(address unauthorized);

/// @notice Thrown when settle data doesn't contain the correct function selector
/// @param invalidSettleData The invalid settle data that was provided
error InvalidSettleData(bytes invalidSettleData);

/// @notice The settlement contract
ICowSettlement public immutable SETTLEMENT;

/// @notice The authentication contract used to verify solvers
/// @dev This is derived from `SETTLEMENT.authenticator()`.
ICowAuthentication public immutable AUTHENTICATOR;

/// @notice Constructs a new CowWrapper
/// @param settlement_ The ICowSettlement contract to use at the end of the wrapper chain. Also used for wrapper authentication.
constructor(ICowSettlement settlement_) {
SETTLEMENT = settlement_;
AUTHENTICATOR = settlement_.authenticator();
}

/// @inheritdoc ICowWrapper
function wrappedSettle(bytes calldata settleData, bytes calldata chainedWrapperData) external {
// Revert if not a valid solver
require(AUTHENTICATOR.isSolver(msg.sender), NotASolver(msg.sender));

// Find out how long the next wrapper data is supposed to be
// We use 2 bytes to decode the length of the wrapper data because it allows for up to 64KB of data for each wrapper.
// This should be plenty of length for all identified use-cases of wrappers in the forseeable future.
uint256 nextWrapperDataLen = uint256(uint16(bytes2(chainedWrapperData[0:2])));

// Delegate to the wrapper's custom logic
uint256 remainingWrapperDataStart = 2 + nextWrapperDataLen;
_wrap(
settleData, chainedWrapperData[2:remainingWrapperDataStart], chainedWrapperData[remainingWrapperDataStart:]
);
}

/// @inheritdoc ICowWrapper
function validateWrapperData(bytes calldata wrapperData) external view virtual;

/// @notice Internal function containing the wrapper's custom logic
/// @dev Must be implemented by concrete wrapper contracts. Should execute custom logic
/// then eventually call _next() to continue the wrapped settlement chain.
/// @param settleData ABI-encoded call to ICowSettlement.settle()
/// @param wrapperData The wrapper data which should be consumed by this wrapper
/// @param remainingWrapperData The reminder bytes resulting from consuming the current's wrapper data from the original `chainedWrapperData` in the `wrappedSettle` call. This should be passed unaltered to `_next` that will call the settlement function if this remainder is empty, or delegate the settlement to the next wrapper
function _wrap(bytes calldata settleData, bytes calldata wrapperData, bytes calldata remainingWrapperData)
internal
virtual;

/// @notice Continues the wrapped settlement chain by calling the next wrapper or settlement contract
/// @dev Extracts the next target address from wrapperData and either:
/// - Calls ICowSettlement.settle() directly if no more wrappers remain, or
/// - Calls the next CowWrapper.wrappedSettle() to continue the chain
/// @param settleData ABI-encoded call to ICowSettlement.settle()
/// @param remainingWrapperData Remaining wrapper data starting with the next target address (20 bytes)
function _next(bytes calldata settleData, bytes calldata remainingWrapperData) internal {
if (remainingWrapperData.length == 0) {
// No more wrapper data - we're calling the final settlement contract
// Verify the settle data has the correct function selector
require(bytes4(settleData[:4]) == ICowSettlement.settle.selector, InvalidSettleData(settleData));

// Call the settlement contract directly with the settle data
(bool success, bytes memory returnData) = address(SETTLEMENT).call(settleData);

if (!success) {
// Bubble up the revert reason from the settlement contract
assembly ("memory-safe") {
revert(add(returnData, 0x20), mload(returnData))
}
}
} else {
// Extract the next wrapper address from the first 20 bytes of wrapperData
address nextWrapper = address(bytes20(remainingWrapperData[:20]));

// Skip past the address we just read
remainingWrapperData = remainingWrapperData[20:];

// More wrapper data remains - call the next wrapper in the chain
CowWrapper(nextWrapper).wrappedSettle(settleData, remainingWrapperData);
}
}
}

Quick Start Exampleโ€‹

Here's a minimal wrapper implementation to use as a starting point:

// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.0;

import { CowWrapper } from "./CowWrapper.sol";

contract MyWrapper is CowWrapper {
constructor(address settlement) CowWrapper(settlement) {}

function _wrap(bytes calldata wrapperData) internal override {
// Your custom pre-settlement logic here
// Example: pull tokens, initiate flash loan, etc.

// Continue the wrapper chain (calls settlement or next wrapper)
_next(wrapperData);

// Your custom post-settlement logic here
// Example: repay flash loan, stake tokens, etc.
}

function parseWrapperData(bytes calldata wrapperData)
external
view
override
returns (bytes memory)
{
// Validate and parse wrapper-specific data
// This must be deterministic - same input always returns same output
// Revert if data is invalid
return wrapperData;
}
}

Wrapper Public Functionsโ€‹

wrappedSettleโ€‹

Entry point for wrapper execution. Validates caller authentication and delegates to _wrap()--where integrators place their custom logic.

function wrappedSettle(
bytes calldata settleData,
bytes calldata chainedWrapperData
) external override {
// Verify caller is an authenticated solver
require(
GPv2Authentication(SETTLEMENT).isSolver(msg.sender),
"Not authorized"
);

_wrap(wrapperData);
}

Parameters:

  • settleData: Original GPv2Settlement.settle(...) calldata
  • chainedWrapperData:

Chained Wrapper Data Encodingโ€‹

Each wrapper in the chain consists of:

  • 2 bytes: uint16 length of wrapper-specific data
  • length bytes: wrapper-specific data for this wrapper
  • 20 bytes: address of next wrapper (omitted for the final wrapper) The final wrapper in the chain omits the next wrapper address and calls SETTLEMENT directly.

Example:

[0x0005][0xAABBCCDDEE][0x1234...ABCD][0x0003][0x112233]
โ†‘len โ†‘data โ†‘next wrapper โ†‘len โ†‘data

Each wrapper in the chain will successively pull off their own data and then call the next wrapper address (or the settlement address if no further wrapper data is supplied).

Virtual Functions for integratorsโ€‹

_wrapโ€‹

Contains custom surrounding settlement logic. Must call _next() to continue the chain to the settlement contract.

function _wrap(bytes calldata wrapperData) internal virtual;

Implementation Requirements:

  • Parse wrapper-specific data from wrapperData as required
  • Execute pre-settlement logic
  • Call _next(remainingWrapperData) to continue chain
  • Execute post-settlement logic

Example:

function _wrap(bytes calldata wrapperData) internal override {
// 1. Parse data (first 2 bytes = length)
uint16 myDataLength = uint16(bytes2(wrapperData[:2]));
bytes calldata myData = wrapperData[2:2+myDataLength];

(address token, uint256 amount) = abi.decode(myData, (address, uint256));

// 2. Pre-settlement logic. Example, receive tokens from user
IERC20(token).transferFrom(msg.sender, address(this), amount);

// 3. Continue chain (REQUIRED)
_next(wrapperData[2+myDataLength:]);

// 4. Post-settlement logic. Example: stake tokens to a contract (for swap and stake)
stakeTokens(token, amount);
}

Internal Functions (Provided)โ€‹

_nextโ€‹

Continues the wrapper chain or calls settlement. Handles all parsing and routing automatically.

function _next(bytes calldata remainingWrapperData) internal;

Behavior:

  • Reads next wrapper address from remainingWrapperData
  • Determines if more wrappers exist or if settlement is next
  • Makes appropriate call with correct data
  • Handles wrapper nesting automatically

Implementation Details:

  • Extracts next target address (last 20 bytes)
  • Separates settle data from remaining wrapper data
  • Calls next wrapper via wrappedSettle() or settlement via settle()

Calldata Encoding Specificationโ€‹

Wrapper data uses an optimized encoding to minimize gas overhead:

Structureโ€‹

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚ Settle Calldata โ”‚ Lenโ‚ โ”‚ Dataโ‚ โ”‚ Addrโ‚‚ โ”‚ Lenโ‚‚ โ”‚ Dataโ‚‚ โ”‚ Settlement โ”‚
โ”‚ (to settlement) โ”‚(2 B) โ”‚ (wrap1) โ”‚ (20 B) โ”‚(2 B) โ”‚ (wrap2) โ”‚ (20 B) โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
โ”‚<โ”€โ”€โ”€ settleData โ”€โ”€โ”€>โ”‚<โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ wrapperData โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€>โ”‚

Components:

  • Settle Calldata: GPv2Settlement.settle(...) calldata (function selector + args)
  • Lenโ‚: 2-byte (uint16) length of wrapper 1's data
  • Dataโ‚: Wrapper 1's custom data
  • Addrโ‚‚: 20-byte address of wrapper 2
  • Lenโ‚‚: 2-byte (uint16) length of wrapper 2's data
  • Dataโ‚‚: Wrapper 2's custom data
  • Settlement: 20-byte address of settlement contract (final target)

Data Consumptionโ€‹

Each wrapper in the chain:

  1. Reads length prefix (2 bytes)
  2. Extracts its data (next N bytes)
  3. Passes remaining bytes to _next()

The _next() function:

  1. Reads next address (from appropriate offset)
  2. Calls next wrapper or settlement
  3. Passes remaining data

CowWrapperHelperโ€‹

Utility contract for on-chain validation and encoding.

Interfaceโ€‹

contract CowWrapperHelper {
/// @notice Validate and build wrapper data for multiple wrappers
/// @param wrapperAddresses Array of wrapper contract addresses
/// @param wrapperDatas Array of wrapper-specific data (parallel to addresses)
/// @return wrapperData Encoded wrapper data ready for wrappedSettle call
function verifyAndBuildWrapperData(
address[] calldata wrapperAddresses,
bytes[] calldata wrapperDatas
) external view returns (bytes memory wrapperData);
}

CowWrapperHelpersโ€‹

To aid external integrators in encoding and verifying wrapper data, an additional view-only contract is provided.

parseWrapperDataโ€‹

Validates wrapper-specific data. Must be deterministic and revert on invalid input.

function parseWrapperData(
bytes calldata wrapperData
) external view override returns (bytes memory);

Requirements:

  • Deterministic: Same input must always produce same output
  • No time-based logic: Cannot use block.timestamp, block.number, etc.
  • No mutable state: Cannot read external mutable state
  • View function: Cannot modify state
  • Revert on invalid: Revert if data is malformed

Implementation Guidelines for Integratorsโ€‹

Security Requirementsโ€‹

Critical Requirements

1. Use CowWrapper Abstract Contractโ€‹

It is strongly recommended to NOT implement ICowWrapper directly. The CowWrapper abstract contract provides:

  • Solver authentication checks
  • Correct calldata parsing and decoding
  • Safe wrapper chain continuation
  • Sanity checks for the settlement call

2. Intermediate Contracts for User Callsโ€‹

If allowing user-defined calls, route through intermediate contracts:

// โŒ DANGEROUS
function _wrap(bytes calldata wrapperData) internal override {
(address target, bytes memory data) = abi.decode(wrapperData, (address, bytes));
target.call(data); // User could call anything they want, including the settlement contract, using the wrapper's authenticated context
}

// โœ… SAFE
function _wrap(bytes calldata wrapperData) internal override {
(address target, bytes memory data) = abi.decode(wrapperData, (address, bytes));
HooksTrampoline(TRAMPOLINE).execute(target, data); // Isolated execution
}

3. Assume All Parameters Are Untrustedโ€‹

Settlement data can be modified by nested wrappers, and solvers can supply arbitrary calldata. If it is important for your wrapper to be able to validate the wrapper it is receiving, only trust signature-protected or on-chain validated parameters.

4. Deterministic Parsing Requiredโ€‹

parseWrapperData() must always return same result for same input:

// โŒ NOT DETERMINISTIC
function parseWrapperData(bytes calldata wrapperData)
external view override returns (bytes memory)
{
uint256 deadline = abi.decode(wrapperData, (uint256));
require(block.timestamp < deadline, "Expired"); // Changes over time!
return wrapperData;
}

// โœ… DETERMINISTIC
function parseWrapperData(bytes calldata wrapperData)
external view override returns (bytes memory)
{
uint256 deadline = abi.decode(wrapperData, (uint256));
require(deadline > 0, "Invalid deadline"); // Always same result
return wrapperData;
}

In the example above, your _wrap code can always reject deadline past expired instead.

5. Defensive Designโ€‹

Though a solver would be slashed for doing so, there is no hard guarantee wrapper executes even if user specifies it. If wrapper is critical:

Option A: Make Order Fail Without Wrapper

// Pull required funds in wrapper
function _wrap(bytes calldata wrapperData) internal override {
vault.withdraw(user, token, amount); // Funds now in wrapper
_next(wrapperData);
// Settlement uses these funds - fails if wrapper didn't execute
}

Option B: EIP-1271 Authentication

// Order signature only valid if wrapper executed
function isValidSignature(bytes32 hash, bytes memory signature)
external view returns (bytes4)
{
require(wrapperExecuted[hash], "Wrapper required");
return EIP1271_MAGIC;
}

There may be other similar ways to enforce execution, but these patterns serve as a starting point.

Gas Overheadโ€‹

Wrapper execution adds gas overhead to settlements.

Benchmark (EmptyWrapper on Ethereum mainnet):

MetricValue
With EmptyWrapper217,033 gas
Without wrapper194,761 gas
Overhead22,272 gas (11.4%)

Scaling Factors:

  • Settlement data size (calldata copying)
  • Wrapper logic complexity
  • Number of nested wrappers

Methodology: Single Uniswap V3 WETHโ†’USDC trade. See services PR #3700.

Example Implementationsโ€‹

EmptyWrapperโ€‹

Minimal wrapper demonstrating the pattern:

contract EmptyWrapper is CowWrapper {
constructor(address settlement) CowWrapper(settlement) {}

function _wrap(bytes calldata wrapperData) internal override {
// No pre-settlement logic
_next(wrapperData); // Pass through
// No post-settlement logic
}

function parseWrapperData(bytes calldata wrapperData)
external
view
override
returns (bytes memory)
{
// No validation needed
return wrapperData;
}
}

Flash Loan Wrapperโ€‹

Wrapper coordinating with lending protocol:

contract FlashLoanWrapper is CowWrapper {
ILendingPool public immutable lendingPool;

constructor(address settlement, address _lendingPool)
CowWrapper(settlement)
{
lendingPool = ILendingPool(_lendingPool);
}

function _wrap(bytes calldata wrapperData) internal override {
(address asset, uint256 amount, bytes memory params) =
abi.decode(wrapperData, (address, uint256, bytes));

// Initiate flash loan (calls executeOperation)
lendingPool.flashLoan(address(this), asset, amount, params);
}

function executeOperation(
address asset,
uint256 amount,
uint256 premium,
address,
bytes calldata params
) external returns (bool) {
require(msg.sender == address(lendingPool), "Unauthorized");

// Execute settlement with borrowed funds
_next(params);

// Approve repayment
IERC20(asset).approve(address(lendingPool), amount + premium);
return true;
}

function parseWrapperData(bytes calldata wrapperData)
external
view
override
returns (bytes memory)
{
require(wrapperData.length >= 96, "Invalid data length");
(address asset, uint256 amount,) = abi.decode(
wrapperData,
(address, uint256, bytes)
);
require(asset != address(0) && amount > 0, "Invalid parameters");
return wrapperData;
}
}

Known Limitationsโ€‹

Authentication Requiredโ€‹

Wrappers need protocol approval, and are not fully permissionless.

Settlement Upgradesโ€‹

When settlement contract upgrades, all wrappers must be redeployed. It is recommended to use Cannon to make it easy to redeploy your wrappers.

Eventsโ€‹

No wrapper-specific events are emitted. Wrapper usage can be identified by:

  1. Transaction target is wrapper address (not settlement)
  2. Function signature is wrappedSettle(bytes,bytes) (not settle(...))
  3. Analyzing transaction trace for wrapper contract calls

Resourcesโ€‹

Documentationโ€‹

Codeโ€‹