Overview
Value Factories provide a mechanism for storing on-chain values in the ComposableCoW “cabinet” at the time of order creation or root setting. These values can be used by conditional order handlers to make decisions based on chain state captured at a specific moment.
Value Factories are typically used with the createWithContext or setRootWithContext functions to store snapshots of on-chain data alongside conditional orders.
IValueFactory Interface
The IValueFactory interface is simple and flexible:
interface IValueFactory {
/**
* Return a value at the time of the call
* @param data Implementation specific off-chain data
* @return value The value at the time of the call
*/
function getValue ( bytes calldata data ) external view returns ( bytes32 value );
}
The factory reads on-chain state and returns a bytes32 value that gets stored in the cabinet. The data parameter allows for implementation-specific configuration.
The Cabinet Storage
ComposableCoW maintains a “cabinet” - a nested mapping that stores contextual values:
// From ComposableCoW.sol
mapping ( address => mapping ( bytes32 => bytes32 )) public cabinet;
Values are stored with:
First key : Owner address (your Safe)
Second key : Context identifier (order hash or bytes32(0) for merkle roots)
Value : The bytes32 returned by the value factory
CurrentBlockTimestampFactory
The most common value factory implementation stores the current block timestamp:
contract CurrentBlockTimestampFactory is IValueFactory {
function getValue ( bytes calldata ) external view override returns ( bytes32 ) {
return bytes32 ( block .timestamp);
}
}
This factory ignores the data parameter and simply returns the current block.timestamp as a bytes32.
Use Case: Time-based Orders
Storing the creation timestamp allows conditional orders to:
Calculate time elapsed since order creation
Implement vesting schedules
Create time-locked trading strategies
Set expiration times relative to creation
Creating Orders with Context
Deploy a Value Factory
Deploy a value factory contract (or use an existing one like CurrentBlockTimestampFactory).
Call createWithContext
Create your conditional order with a stored value: IConditionalOrder.ConditionalOrderParams memory params = IConditionalOrder. ConditionalOrderParams ({
handler : IConditionalOrder (handlerAddress),
salt : bytes32 ( uint256 ( 1 )),
staticInput : abi . encode (orderData)
});
composableCow. createWithContext (
params,
IValueFactory (timestampFactoryAddress),
"" , // empty data for CurrentBlockTimestampFactory
true // dispatch event
);
Access the value in your handler
Your conditional order handler can read the stored value: bytes32 creationTime = composableCow. cabinet (owner, ctx);
Setting Roots with Context
For merkle tree-based order management, use setRootWithContext:
composableCow. setRootWithContext (
merkleRoot,
proof,
IValueFactory (timestampFactoryAddress),
"" // factory data
);
This stores the value at cabinet[msg.sender][bytes32(0)] - the default slot for merkle root contexts.
For merkle roots, the cabinet slot defaults to bytes32(0) to save gas on calldata and enable a common pattern for root-level context.
Custom Value Factory Examples
Token Price Factory
Capture a token price at order creation time using a Chainlink oracle:
import { IAggregatorV3Interface } from "./interfaces/IAggregatorV3Interface.sol" ;
import { IValueFactory } from "./interfaces/IValueFactory.sol" ;
contract ChainlinkPriceFactory is IValueFactory {
function getValue ( bytes calldata data ) external view override returns ( bytes32 ) {
// Decode the oracle address from data
address oracleAddress = abi . decode (data, ( address ));
// Get the latest price
IAggregatorV3Interface oracle = IAggregatorV3Interface (oracleAddress);
(, int256 price,,,) = oracle. latestRoundData ();
// Return as bytes32
return bytes32 ( uint256 (price));
}
}
Usage:
composableCow. createWithContext (
params,
IValueFactory (priceFactoryAddress),
abi . encode (chainlinkOracleAddress),
true
);
Token Balance Factory
Capture the current balance of a token:
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol" ;
import { IValueFactory } from "./interfaces/IValueFactory.sol" ;
contract TokenBalanceFactory is IValueFactory {
function getValue ( bytes calldata data ) external view override returns ( bytes32 ) {
// Decode token and account addresses
( address token, address account) = abi . decode (data, ( address , address ));
// Get current balance
uint256 balance = IERC20 (token). balanceOf (account);
return bytes32 (balance);
}
}
Composite Value Factory
Store multiple values packed into a single bytes32:
contract CompositeFactory is IValueFactory {
function getValue ( bytes calldata data ) external view override returns ( bytes32 ) {
// Example: Store timestamp (32 bits) + block number (32 bits) + custom data (192 bits)
uint256 timestamp = block .timestamp & 0xFFFFFFFF ;
uint256 blockNumber = block .number & 0xFFFFFFFF ;
// Decode custom data
uint256 customData = abi . decode (data, ( uint256 ));
// Pack into bytes32
bytes32 packed = bytes32 (
(timestamp << 224 ) |
(blockNumber << 192 ) |
(customData & 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF )
);
return packed;
}
}
Reading Cabinet Values in Handlers
Your conditional order handler can access cabinet values during order generation:
contract TimeLockOrder is BaseConditionalOrder {
struct Data {
IERC20 sellToken;
IERC20 buyToken;
uint256 sellAmount;
uint256 buyAmount;
uint256 lockDuration; // seconds
bytes32 appData;
}
function getTradeableOrder (
address owner ,
address ,
bytes32 ctx ,
bytes calldata staticInput ,
bytes calldata
) public view override returns ( GPv2Order . Data memory ) {
Data memory data = abi . decode (staticInput, (Data));
// Read the creation timestamp from cabinet
bytes32 creationTime = ComposableCoW ( msg.sender ). cabinet (owner, ctx);
uint256 unlockTime = uint256 (creationTime) + data.lockDuration;
// Revert if still locked
if ( block .timestamp < unlockTime) {
revert IConditionalOrder. PollTryAtEpoch (
unlockTime,
"Order still locked"
);
}
// Order is unlocked, return tradeable order
return GPv2Order. Data ({
sellToken : data.sellToken,
buyToken : data.buyToken,
receiver : address ( 0 ),
sellAmount : data.sellAmount,
buyAmount : data.buyAmount,
validTo : uint32 ( block .timestamp + 600 ),
appData : data.appData,
feeAmount : 0 ,
kind : GPv2Order.KIND_SELL,
partiallyFillable : false ,
sellTokenBalance : GPv2Order.BALANCE_ERC20,
buyTokenBalance : GPv2Order.BALANCE_ERC20
});
}
}
Best Practices
Gas Considerations : Value factory getValue calls happen during transaction execution (not view calls). Keep computations lightweight.
Design Tips
Use the data parameter for flexible, reusable factories
Return deterministic values based on block state
Consider packing multiple values into a single bytes32 when needed
Document what your factory returns and how to decode it
Context Keys
The context key determines where values are stored in the cabinet:
Order Type Context Key Description Merkle root orders bytes32(0)Default slot for root-level context Single orders H(params)Hash of the conditional order params
When creating a single order with createWithContext, the context is tied to that specific order. When setting a root with setRootWithContext, all orders in the tree share the same context at bytes32(0).
Advanced: Dynamic Context Updates
While cabinet values are typically set at creation time, you can update them:
// Remove an order and its context
composableCow. remove (orderHash); // Clears both singleOrders and cabinet
// Recreate with new context
composableCow. createWithContext (params, newFactory, newData, true );
For merkle roots, set a new root with updated context:
composableCow. setRootWithContext (newRoot, proof, factory, data);
Next Steps
Custom Orders Learn how to implement conditional order handlers that use cabinet values
Swap Guards Add restrictions to your conditional orders