304 lines
8.4 KiB
Markdown
304 lines
8.4 KiB
Markdown
# CoW Protocol + MycoFi: Quick Reference Card
|
|
|
|
## One-Page Overview
|
|
|
|
### What is CoW?
|
|
**Intent-based DEX with batch auctions.** Users sign intents (not transactions). Solvers compete to execute batches. MEV-protected. Solvers pay gas.
|
|
|
|
### Your Integration
|
|
```
|
|
User Intent (private)
|
|
↓ Watch Tower polls
|
|
↓ MycoConditionalOrder.getTradeableOrder()
|
|
↓ BondingCurveAdapter.quoteBuy/quoteSell
|
|
↓ Solver competition
|
|
↓ GPv2Settlement.settle() executes
|
|
↓ User receives tokens (MEV-safe)
|
|
```
|
|
|
|
---
|
|
|
|
## Contract Reference
|
|
|
|
| Contract | Address (Base) | Role |
|
|
|----------|---|---|
|
|
| GPv2Settlement | `0x9008D19f...` | Settlement executor |
|
|
| GPv2VaultRelayer | `0xc92e8bdf...` | Token pull intermediary |
|
|
| MycoBondingCurve | `0x...` (deploy) | Pricing logic |
|
|
| BondingCurveAdapter | `0x...` (deploy) | Pre-interaction hook |
|
|
| MycoConditionalOrder | `0x...` (deploy) | Order type handler |
|
|
|
|
---
|
|
|
|
## Key Interfaces
|
|
|
|
### IConditionalOrder (implement this)
|
|
```solidity
|
|
function getTradeableOrder(
|
|
address owner, // Safe wallet
|
|
address sender, // Usually Watch Tower
|
|
bytes calldata staticInput, // Encoded params
|
|
bytes calldata offchainInput // Encoded quote + expiry
|
|
) external view returns (GPv2Order.Data);
|
|
|
|
function verify(...) external view; // Validation
|
|
```
|
|
|
|
### GPv2Order.Data (return this)
|
|
```solidity
|
|
struct Data {
|
|
address sellToken; // USDC for buy
|
|
address buyToken; // MYCO for buy
|
|
address receiver; // Order recipient
|
|
uint256 sellAmount; // Exact input
|
|
uint256 buyAmount; // Minimum output
|
|
uint32 validTo; // Expiry timestamp
|
|
bytes32 appData; // Metadata
|
|
uint256 feeAmount; // Usually 0
|
|
bytes32 kind; // KIND_SELL or KIND_BUY
|
|
bool partiallyFillable; // Usually false
|
|
bytes32 sellTokenBalance; // BALANCE_ERC20
|
|
bytes32 buyTokenBalance; // BALANCE_ERC20
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## Error Handling
|
|
|
|
```solidity
|
|
// In getTradeableOrder():
|
|
|
|
// Temporary (retry next block)
|
|
if (quote_expired) revert PollTryNextBlock("Quote expired");
|
|
if (insufficient_balance) revert PollTryNextBlock("Balance changed");
|
|
|
|
// Current state invalid (try later)
|
|
if (quote_below_minimum) revert OrderNotValid("Quote below min");
|
|
|
|
// Stop polling (permanent issue)
|
|
if (handler_misconfigured) revert PollNever("Config error");
|
|
```
|
|
|
|
---
|
|
|
|
## Stateless Handler Pattern
|
|
|
|
```solidity
|
|
contract MycoConditionalOrder is IConditionalOrder {
|
|
// NO state variables (except immutable refs)
|
|
// All logic is view-only in getTradeableOrder()
|
|
|
|
function getTradeableOrder(
|
|
address owner,
|
|
address,
|
|
bytes calldata staticInput, // Order params
|
|
bytes calldata offchainInput // Live quote from Watch Tower
|
|
) external view returns (GPv2Order.Data memory) {
|
|
// 1. Decode staticInput (set once at order creation)
|
|
OrderData memory params = abi.decode(staticInput, (OrderData));
|
|
|
|
// 2. Decode offchainInput (fresh quote from Watch Tower)
|
|
OffchainData memory quote = abi.decode(offchainInput, (OffchainData));
|
|
|
|
// 3. Validate
|
|
if (block.timestamp > quote.validTo) revert OrderNotValid("Expired");
|
|
if (quote.quotedOutput < params.minOutputAmount) revert OrderNotValid("Below min");
|
|
|
|
// 4. Generate order
|
|
return GPv2Order.Data({
|
|
sellToken: params.direction == BUY ? USDC : MYCO,
|
|
buyToken: params.direction == BUY ? MYCO : USDC,
|
|
sellAmount: params.inputAmount,
|
|
buyAmount: quote.quotedOutput, // From Watch Tower
|
|
receiver: params.receiver,
|
|
validTo: quote.validTo,
|
|
// ... other fields
|
|
});
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## Watch Tower Polling Lifecycle
|
|
|
|
```
|
|
Every block (every 2 sec on Base):
|
|
FOR each handler in registry:
|
|
FOR each order:
|
|
staticInput = stored params
|
|
offchainInput = adapter.quoteBuy() + timestamp
|
|
|
|
TRY getTradeableOrder(owner, sender, static, offchain)
|
|
✓ Valid? → Submit to solvers
|
|
✗ PollTryNextBlock? → Retry next block
|
|
✗ OrderNotValid? → Skip, retry later
|
|
✗ PollNever? → Remove from polling
|
|
|
|
offchainInput = BondingCurveAdapter.quoteBuy()
|
|
Submit to solver network
|
|
```
|
|
|
|
---
|
|
|
|
## Token Flow During Settlement
|
|
|
|
```
|
|
Settlement.settle() called by solver:
|
|
|
|
1. BEFORE INTERACTIONS
|
|
└─ Nothing (for basic buy/sell)
|
|
|
|
2. INPUT TRANSFERS (via VaultRelayer)
|
|
└─ VaultRelayer pulls 500 USDC from user's Vault
|
|
└─ Transfers to BondingCurveAdapter
|
|
|
|
3. INTERACTIONS
|
|
└─ BondingCurveAdapter.executeBuyForCoW(500 USDC, 900 MYCO min)
|
|
├─ Calls curve.buy(500 USDC)
|
|
├─ Curve mints 1000 MYCO to adapter
|
|
└─ Returns 1000 to settlement
|
|
|
|
4. OUTPUT TRANSFERS (via VaultRelayer)
|
|
└─ VaultRelayer pulls 1000 MYCO from adapter
|
|
└─ Transfers to user
|
|
|
|
5. AFTER INTERACTIONS
|
|
└─ Nothing (for basic buy/sell)
|
|
|
|
Result: User -500 USDC, +1000 MYCO
|
|
```
|
|
|
|
---
|
|
|
|
## Critical Security Checklist
|
|
|
|
- [ ] Users approve **Balancer Vault** (NOT Settlement)
|
|
- [ ] Adapter has `onlySettlement` modifier
|
|
- [ ] Quote expiry validated (block.timestamp <= quote.validTo)
|
|
- [ ] Min output enforced (quote.output >= params.minOutputAmount)
|
|
- [ ] Reentrancy guarded on hook functions
|
|
- [ ] No permanent state in handler (stateless)
|
|
- [ ] VaultRelayer can only transfer TO Settlement
|
|
|
|
---
|
|
|
|
## MEV Protection Summary
|
|
|
|
| Mechanism | How it Works |
|
|
|-----------|-------------|
|
|
| **Private Order Flow** | Intents never in public mempool → Bots can't see you |
|
|
| **Uniform Clearing Prices** | All USDC→MYCO trades in batch at same price → Order can't be reordered for profit |
|
|
| **Coincidence of Wants** | Alice ↔ Bob direct match → No AMM slippage → No MEV possible |
|
|
|
|
---
|
|
|
|
## Testing Quick Start
|
|
|
|
```solidity
|
|
// Test handler returns valid GPv2Order
|
|
GPv2Order.Data memory order = handler.getTradeableOrder(
|
|
owner, watchtower, staticInput, offchainInput
|
|
);
|
|
assert(order.buyAmount >= minOutput);
|
|
assert(order.validTo > block.timestamp);
|
|
|
|
// Test adapter executes correctly
|
|
usdc.transfer(adapter, 500e6);
|
|
uint256 mycoOut = adapter.executeBuyForCoW(500e6, 900e18);
|
|
assert(mycoToken.balanceOf(adapter) >= 900e18);
|
|
|
|
// Test quote functions
|
|
uint256 quote = adapter.quoteBuy(500e6);
|
|
assert(quote == curve.calculateBuyReturn(500e6));
|
|
```
|
|
|
|
---
|
|
|
|
## Deployment Checklist
|
|
|
|
Base Mainnet Addresses:
|
|
```
|
|
✓ USDC: 0x833589fC4D06F649c466dB920d0135aa6Df1cDEA
|
|
✓ GPv2Settlement: 0x9008D19f58AAbD9eD0D60971565AA8510560ab41
|
|
✓ VaultRelayer: 0xc92e8bdf79f0507f65a392b0ab4667716bfe0110
|
|
|
|
Deploy (in order):
|
|
1. MycoToken
|
|
2. MycoBondingCurve (set basePrice, coeff, exponent, fee, treasury)
|
|
3. BondingCurveAdapter
|
|
4. MycoConditionalOrder
|
|
5. Register in ComposableCoW (request Watch Tower access)
|
|
```
|
|
|
|
---
|
|
|
|
## API Rate Limits
|
|
|
|
```
|
|
Quote requests: 10/second
|
|
Order submission: 5/second
|
|
General endpoints: 100/minute
|
|
```
|
|
|
|
---
|
|
|
|
## Common Errors & Solutions
|
|
|
|
| Error | Cause | Fix |
|
|
|-------|-------|-----|
|
|
| `OrderNotValid("Quote expired")` | Watch Tower quote >30min old | Fetch fresh quote |
|
|
| `PollTryNextBlock("Insufficient balance")` | User moved tokens | Wait for balance recovery |
|
|
| `OnlySettlement()` revert | Called adapter directly | Only Settlement can call |
|
|
| `SlippageExceeded` | Quote < minOutputAmount | Increase minOut or retry |
|
|
|
|
---
|
|
|
|
## Links
|
|
|
|
- Docs: https://docs.cow.fi/
|
|
- GitHub: https://github.com/cowprotocol/
|
|
- SDK: `@cowprotocol/sdk` (npm)
|
|
- Forum: https://forum.cow.fi/
|
|
- API Base: https://api.cow.fi/base/
|
|
|
|
---
|
|
|
|
## Key Metrics to Track
|
|
|
|
```
|
|
Per order:
|
|
- Quote latency (target <100ms)
|
|
- Time to execution (target <60s)
|
|
- Actual price vs quote (should be >=)
|
|
- Gas paid by solver (should be <fees collected)
|
|
|
|
Per batch (daily):
|
|
- Orders submitted / executed / failed
|
|
- Average surplus per order
|
|
- MEV extraction (should be ~0)
|
|
- Watch Tower polling rate
|
|
|
|
Monthly:
|
|
- Total volume
|
|
- User count
|
|
- Protocol revenue (accumulated fees)
|
|
```
|
|
|
|
---
|
|
|
|
## Golden Rules
|
|
|
|
1. **Handlers are stateless** — All logic in `getTradeableOrder()` view function
|
|
2. **Quotes are ephemeral** — Each poll gets fresh quote from adapter
|
|
3. **Errors must be clear** — Use correct error type (PollTryNextBlock vs OrderNotValid)
|
|
4. **Fund access is protected** — Users approve Vault, not Settlement
|
|
5. **Solvers are untrusted** — But can't break anything (settlement atomic)
|
|
|
|
---
|
|
|
|
**Version:** 1.0
|
|
**Updated:** 2026-04-03
|
|
**Status:** Ready to reference during implementation
|