807 lines
29 KiB
Markdown
807 lines
29 KiB
Markdown
# CoW Protocol + MycoFi Integration: Architecture Diagrams
|
|
|
|
## 1. Complete Order Lifecycle
|
|
|
|
```
|
|
┌─────────────────────────────────────────────────────────────────────────────┐
|
|
│ ORDER LIFECYCLE: USER INTENT → EXECUTION │
|
|
└─────────────────────────────────────────────────────────────────────────────┘
|
|
|
|
PHASE 1: USER CREATES ORDER (Off-chain, Private)
|
|
────────────────────────────────────────────────────────
|
|
|
|
User Wallet
|
|
│
|
|
├─ Signs intent message:
|
|
│ {
|
|
│ "buy": 1000 MYCO
|
|
│ "sell": 500 USDC
|
|
│ "minOut": 900 MYCO
|
|
│ "validTo": block.timestamp + 24h
|
|
│ }
|
|
│
|
|
└─ Message stays PRIVATE (not in mempool)
|
|
↓
|
|
CoW API (off-chain orderbook)
|
|
|
|
|
|
PHASE 2: BATCH COLLECTION (~30 seconds)
|
|
────────────────────────────────────────
|
|
|
|
Watch Tower Service
|
|
│
|
|
├─ Collects all pending intents (private)
|
|
├─ Polls MycoConditionalOrder.getTradeableOrder()
|
|
│ ├─ Static input: {direction, amount, minOut, receiver}
|
|
│ ├─ Calls BondingCurveAdapter.quoteBuy() for live quote
|
|
│ └─ Returns GPv2Order.Data ready for solver
|
|
│
|
|
└─ Broadcasts order batch to solvers
|
|
|
|
|
|
PHASE 3: SOLVER AUCTION (Typically <5 seconds)
|
|
───────────────────────────────────────────────
|
|
|
|
Solver 1 Solver 2 Solver 3
|
|
(Uniswap routing) (Direct CoW matching) (1inch aggregation)
|
|
│ │ │
|
|
└─ Computes solution ├─ Finds Alice ↔ Bob ├─ Finds best paths
|
|
(all orders) │ direct swap │ through DEXs
|
|
Surplus = $5 │ Surplus = $8 │ Surplus = $3
|
|
│ WINNER! ✓
|
|
│
|
|
|
|
GPv2Settlement.settle()
|
|
receives solution from winning solver
|
|
|
|
|
|
PHASE 4: ON-CHAIN SETTLEMENT
|
|
───────────────────────────
|
|
|
|
GPv2Settlement.settle()
|
|
│
|
|
├─ PRE-INTERACTION HOOKS
|
|
│ └─ None for basic orders
|
|
│
|
|
├─ INPUT TRANSFERS (via VaultRelayer)
|
|
│ └─ User's USDC transferred from Balancer Vault to adapter
|
|
│ VaultRelayer (authorized) pulls 500 USDC
|
|
│
|
|
├─ CUSTOM LOGIC (via pre-interaction)
|
|
│ └─ BondingCurveAdapter.executeBuyForCoW(500 USDC, 900 MYCO min)
|
|
│ ├─ Calls MycoBondingCurve.buy(500 USDC)
|
|
│ ├─ MYCO minted to adapter (1000 tokens)
|
|
│ └─ Returns 1000 MYCO to settlement
|
|
│
|
|
├─ OUTPUT TRANSFERS (via VaultRelayer)
|
|
│ └─ Settlement pulls 1000 MYCO from adapter to user
|
|
│
|
|
└─ POST-INTERACTION HOOKS
|
|
└─ None for basic orders
|
|
|
|
|
|
FINAL STATE
|
|
───────────
|
|
✓ User: -500 USDC, +1000 MYCO
|
|
✓ BondingCurve: +500 USDC reserve, -1000 MYCO supply
|
|
✓ Solver: Received 8 USDC surplus (profit for execution)
|
|
✓ Gas: Paid by solver (deducted from surplus)
|
|
```
|
|
|
|
---
|
|
|
|
## 2. Contract Interaction Flow
|
|
|
|
```
|
|
┌─────────────────────────────────────────────────────────────────┐
|
|
│ CONTRACT INTERACTION ARCHITECTURE │
|
|
└─────────────────────────────────────────────────────────────────┘
|
|
|
|
USER/WALLET
|
|
│
|
|
├─ Approves Balancer Vault (not Settlement!)
|
|
│ └─ IERC20(USDC).approve(balancerVault, uint256.max)
|
|
│
|
|
└─ Signs intent (off-chain)
|
|
|
|
|
|
Watch Tower (off-chain bot)
|
|
│
|
|
└─ Calls MycoConditionalOrder.getTradeableOrder()
|
|
│
|
|
├─ Decodes staticInput (OrderData)
|
|
├─ Decodes offchainInput (OffchainData from Watch Tower)
|
|
├─ Calls BondingCurveAdapter.quoteBuy() for live quote
|
|
│ └─ Returns expected MYCO output
|
|
│
|
|
└─ Generates GPv2Order.Data struct
|
|
├─ sellToken: USDC
|
|
├─ buyToken: MYCO
|
|
├─ sellAmount: 500 USDC
|
|
├─ buyAmount: 1000 MYCO (from quote)
|
|
├─ receiver: user address
|
|
└─ validTo: quote.validTo
|
|
|
|
|
|
Solver (winning solver)
|
|
│
|
|
└─ Calls GPv2Settlement.settle()
|
|
│
|
|
├─ Verifies order signature (ERC-1271 via Authenticator if Safe)
|
|
│
|
|
├─ Calls VaultRelayer.transferFromAccounts()
|
|
│ └─ Transfers 500 USDC from user's Vault allowance
|
|
│ → Sends to BondingCurveAdapter
|
|
│
|
|
├─ INTERACTION: Calls BondingCurveAdapter.executeBuyForCoW()
|
|
│ │
|
|
│ ├─ Checks only called from Settlement (modifier)
|
|
│ ├─ Checks USDC balance (already received)
|
|
│ ├─ Calls BondingCurve.buy(500 USDC, 900 MYCO min)
|
|
│ │ │
|
|
│ │ ├─ Calculates price: P(supply) = basePrice + coeff*(supply^exp)
|
|
│ │ ├─ Mints 1000 MYCO to adapter
|
|
│ │ └─ Returns amount minted
|
|
│ │
|
|
│ └─ Returns 1000 MYCO to Settlement
|
|
│
|
|
├─ Calls Vault.transferToAccounts()
|
|
│ └─ Transfers 1000 MYCO from adapter → user wallet
|
|
│
|
|
└─ Emits OrderFulfilled event
|
|
|
|
|
|
TOKENS HELD DURING EXECUTION
|
|
─────────────────────────────
|
|
Settlement: Temporarily holds order tokens (atomic)
|
|
Adapter: Receives input, produces output (pre/post interaction logic)
|
|
VaultRelayer: Only pulls/pushes via Settlement call
|
|
```
|
|
|
|
---
|
|
|
|
## 3. MEV Attack Prevention Visualization
|
|
|
|
```
|
|
┌──────────────────────────────────────────────────────────────┐
|
|
│ MEV PROTECTION: CoW vs Traditional AMM │
|
|
└──────────────────────────────────────────────────────────────┘
|
|
|
|
TRADITIONAL AMM (Uniswap-style):
|
|
──────────────────────────────────
|
|
|
|
Mempool (public visibility)
|
|
│
|
|
├─ Alice's TX: Buy 1000 MYCO for 500 USDC
|
|
│ (broadcast in mempool, visible to all)
|
|
│
|
|
├─ MEV Bot observes:
|
|
│ "Alice buying MYCO, will push price up"
|
|
│
|
|
├─ Bot's sandwich attack:
|
|
│ 1. Bot front-runs: Buy MYCO first
|
|
│ ├─ Price moves: 0.5 → 0.51 MYCO/USDC
|
|
│ └─ Bot pays: 490 USDC for 961 MYCO
|
|
│
|
|
│ 2. Alice's TX executes
|
|
│ ├─ Price higher: 0.51 MYCO/USDC
|
|
│ └─ Alice pays: 500 USDC for 980 MYCO (loss vs no MEV!)
|
|
│
|
|
│ 3. Bot back-runs: Sell MYCO
|
|
│ ├─ Sells 961 MYCO for 490 USDC
|
|
│ └─ Bot profit: ~10 USDC (extracted from Alice)
|
|
│
|
|
└─ Alice LOSES ~20 MYCO due to MEV extraction!
|
|
|
|
|
|
CoW PROTOCOL (Batch Auction):
|
|
──────────────────────────────
|
|
|
|
Private Order Collection
|
|
│
|
|
├─ Alice's intent: "Buy 1000 MYCO for 500 USDC" [SIGNED, PRIVATE]
|
|
│
|
|
├─ Bob's intent: "Buy 600 MYCO for 300 USDC" [SIGNED, PRIVATE]
|
|
│
|
|
└─ Both intents stay PRIVATE (not in mempool)
|
|
MEV bots can't see them!
|
|
|
|
|
|
Batch Closure (30 seconds)
|
|
│
|
|
├─ CoW collects orders: {Alice, Bob, Charlie, Dave, ...}
|
|
│
|
|
└─ Solvers compete to find best settlement
|
|
|
|
|
|
Solver Auction:
|
|
│
|
|
├─ Solver A (DEX routing):
|
|
│ └─ Route through Uniswap → 950 MYCO for Alice
|
|
│
|
|
├─ Solver B (Direct CoW matching):
|
|
│ ├─ Alice wants: 1000 MYCO for 500 USDC
|
|
│ ├─ Bob has MYCO but wants stables
|
|
│ ├─ Direct swap: Alice ↔ Bob
|
|
│ │ └─ Alice: 500 USDC → 1000 MYCO (NO AMM, no slippage!)
|
|
│ │ └─ Bob: 1000 MYCO → 500 USDC
|
|
│ └─ Surplus: $0 MEV extracted (perfect execution!)
|
|
│
|
|
└─ Solver B WINS (best price for users)
|
|
|
|
|
|
On-chain Settlement:
|
|
│
|
|
└─ Alice pays: 500 USDC
|
|
Alice receives: 1000 MYCO
|
|
No sandwich. No MEV extraction. MEV-protected!
|
|
|
|
|
|
KEY PROTECTIONS:
|
|
─────────────────
|
|
✓ Private Order Flow: Intent never in public mempool
|
|
✓ Uniform Clearing Price: All trades at same price in batch
|
|
✓ Coincidence of Wants: Direct peer-to-peer when possible
|
|
(No AMM slippage, no liquidity provider fees)
|
|
```
|
|
|
|
---
|
|
|
|
## 4. BondingCurveAdapter Token Flow
|
|
|
|
```
|
|
┌───────────────────────────────────────────────────────────────┐
|
|
│ TOKEN FLOW: BondingCurveAdapter in Settlement │
|
|
└───────────────────────────────────────────────────────────────┘
|
|
|
|
SCENARIO: User buys 1000 MYCO with 500 USDC via CoW Protocol
|
|
|
|
|
|
INITIAL STATE:
|
|
──────────────
|
|
User Wallet:
|
|
├─ 5000 USDC
|
|
└─ 100 MYCO
|
|
└─ Approval: Balancer Vault can spend 5000 USDC
|
|
|
|
BondingCurveAdapter:
|
|
└─ 0 USDC, 0 MYCO (fresh contract)
|
|
|
|
Balancer Vault:
|
|
└─ Holds user's approved allowances
|
|
|
|
|
|
STEP 1: Solver calls GPv2Settlement.settle()
|
|
─────────────────────────────────────────────
|
|
Settlement.settle({
|
|
orders: [order_from_user],
|
|
inTransfers: [500 USDC],
|
|
outTransfers: [1000 MYCO],
|
|
interactions: [call BondingCurveAdapter.executeBuyForCoW()]
|
|
})
|
|
|
|
|
|
STEP 2: Settlement pulls input tokens
|
|
──────────────────────────────────────
|
|
VaultRelayer.transferFromAccounts(
|
|
[User],
|
|
[USDC],
|
|
[500e6], // 500 USDC (6 decimals)
|
|
[BondingCurveAdapter]
|
|
)
|
|
|
|
User Wallet: BondingCurveAdapter: VaultRelayer:
|
|
5000 USDC → 0 USDC → (transfer)
|
|
4500 USDC ←─ pull ──┘
|
|
|
|
|
|
STEP 3: Settlement calls pre-interaction hook
|
|
──────────────────────────────────────────────
|
|
BondingCurveAdapter.executeBuyForCoW(
|
|
500e6, // usdcAmount
|
|
900e18 // minMycoOut (slippage protection)
|
|
)
|
|
│
|
|
├─ Checks: "Only called from Settlement" ✓
|
|
├─ Checks: "USDC balance >= 500e6" ✓ (we have 500 USDC)
|
|
│
|
|
├─ Calls BondingCurve.buy(500e6, 900e18)
|
|
│ │
|
|
│ ├─ Current supply: 10,000,000 MYCO
|
|
│ ├─ Price calc: P = basePrice + coeff * supply^exponent
|
|
│ ├─ Calculates output: ~1000 MYCO
|
|
│ ├─ Checks min output: 1000 >= 900 ✓
|
|
│ │
|
|
│ ├─ Burns tokens: bondingCurve.buy() path:
|
|
│ │ ├─ Adapter already approved to spend curve's USDC
|
|
│ │ ├─ USDC.transferFrom(adapter, curve, 500e6) ✓
|
|
│ │ └─ Curve updates: reserveBalance += 500 USDC
|
|
│ │
|
|
│ └─ Mints to adapter:
|
|
│ └─ MycoToken.mint(adapter, 1000e18) ✓
|
|
│
|
|
└─ Returns: 1000 MYCO minted
|
|
|
|
State after hook:
|
|
User: 4500 USDC, 100 MYCO
|
|
Adapter: 0 USDC, 1000 MYCO (minted by curve)
|
|
Curve: 500 USDC reserve, supply-1000
|
|
|
|
|
|
STEP 4: Settlement pulls output tokens (to user)
|
|
────────────────────────────────────────────────
|
|
Settlement registers: "Adapter owes 1000 MYCO"
|
|
Vault.transferToAccounts(
|
|
[BondingCurveAdapter],
|
|
[MYCO],
|
|
[1000e18],
|
|
[User]
|
|
)
|
|
|
|
VaultRelayer pulls from adapter (via Vault):
|
|
Adapter: Vault: User:
|
|
1000 MYCO → (pull) → 100 MYCO
|
|
0 MYCO ←───────────────────────── 1100 MYCO
|
|
|
|
|
|
FINAL STATE:
|
|
────────────
|
|
User Wallet:
|
|
├─ 4500 USDC (spent 500)
|
|
├─ 1100 MYCO (gained 1000) ✓
|
|
└─ Approval reset to available balance
|
|
|
|
BondingCurveAdapter:
|
|
└─ 0 USDC, 0 MYCO (drained, ready for next order)
|
|
|
|
BondingCurve Contract:
|
|
├─ reserveBalance: +500 USDC
|
|
├─ totalSupply: -1000 MYCO
|
|
└─ Ready for next user
|
|
|
|
|
|
KEY SECURITY POINTS:
|
|
─────────────────────
|
|
✓ Adapter doesn't hold tokens permanently
|
|
✓ All transfers atomic (via Settlement)
|
|
✓ VaultRelayer prevents unauthorized fund access
|
|
✓ Reentrancy guard protects during curve execution
|
|
✓ Quote validation ensures minimum output respected
|
|
```
|
|
|
|
---
|
|
|
|
## 5. Conditional Order State Machine
|
|
|
|
```
|
|
┌─────────────────────────────────────────────────────────────┐
|
|
│ IConditionalOrder State & Polling Lifecycle │
|
|
└─────────────────────────────────────────────────────────────┘
|
|
|
|
CONDITIONAL ORDER LIFECYCLE:
|
|
────────────────────────────
|
|
|
|
[CREATED]
|
|
│
|
|
└─ ComposableCoW.create({
|
|
handler: MycoConditionalOrder,
|
|
salt: unique_identifier,
|
|
staticInput: abi.encode({
|
|
direction: BUY,
|
|
inputAmount: 500e6, // 500 USDC
|
|
minOutputAmount: 900e18, // 900 MYCO minimum
|
|
receiver: user,
|
|
validityDuration: 86400 // 24 hours
|
|
})
|
|
})
|
|
|
|
└─ Stored in ComposableCoW registry
|
|
└─ Hashmap: owner → conditionalOrders[]
|
|
|
|
|
|
[POLLING] (every block, ~12 seconds on Ethereum, ~2 sec on Base)
|
|
│
|
|
└─ Watch Tower calls:
|
|
MycoConditionalOrder.getTradeableOrder(
|
|
owner: user_address,
|
|
sender: watchtower,
|
|
staticInput: above_encoded_data,
|
|
offchainInput: abi.encode({
|
|
quotedOutput: 1000e18, // Current quote from adapter
|
|
validTo: now + 30 min // Quote expiry
|
|
})
|
|
)
|
|
|
|
└─ Handler validates:
|
|
├─ Quote not expired? ✓
|
|
├─ quotedOutput >= minOutputAmount? ✓
|
|
├─ User has sufficient balance? ✓
|
|
└─ Returns GPv2Order.Data ready for solver
|
|
|
|
|
|
[SUBMITTED]
|
|
│
|
|
└─ Watch Tower submits to solver network
|
|
├─ Submits valid orders
|
|
└─ Retries failed orders next block
|
|
|
|
|
|
[AWAITING_SOLVER]
|
|
│
|
|
├─ Solvers receive order batch
|
|
├─ Compete to solve efficiently
|
|
└─ Winning solver selected
|
|
|
|
|
|
[EXECUTED / FULFILLED]
|
|
│
|
|
└─ Winning solver calls GPv2Settlement.settle()
|
|
├─ Order confirmed on-chain
|
|
└─ User receives MYCO
|
|
|
|
|
|
[REVERTED / CANCELLED]
|
|
│
|
|
├─ User calls ComposableCoW.cancel(orderHash)
|
|
│ └─ Order removed from registry
|
|
│
|
|
└─ Or if:
|
|
├─ Quote expires
|
|
├─ Balance insufficient
|
|
└─ Validity duration exceeded
|
|
|
|
|
|
WATCH TOWER ERROR HANDLING:
|
|
───────────────────────────
|
|
|
|
getTradeableOrder() returns error? What to do?
|
|
|
|
PollTryNextBlock("reason")
|
|
└─ Temporary issue (likely)
|
|
└─ Retry next block (~12 sec)
|
|
└─ Examples:
|
|
├─ User balance just transferred out
|
|
├─ Price quote slightly stale
|
|
└─ Network congestion
|
|
|
|
|
|
OrderNotValid("reason")
|
|
└─ Order invalid right now
|
|
└─ Will check again next block
|
|
└─ Examples:
|
|
├─ Quote expired
|
|
├─ Output below minimum
|
|
└─ Order not yet valid
|
|
|
|
|
|
PollNever("reason")
|
|
└─ STOP polling this order permanently
|
|
└─ Remove from active orders
|
|
└─ Examples:
|
|
├─ User disabled notifications
|
|
├─ Permanent configuration error
|
|
└─ Handler contract error
|
|
|
|
|
|
POLLING FREQUENCY (Base):
|
|
─────────────────────────
|
|
Block time: ~2 seconds
|
|
Watch Tower poll: every block
|
|
Batch window: ~30 seconds
|
|
Typical orders in batch: 100-1000+
|
|
|
|
Expected time from intent → execution: 5-60 seconds
|
|
(depending on batch phase and solver competition)
|
|
```
|
|
|
|
---
|
|
|
|
## 6. Security Architecture: Fund Protection
|
|
|
|
```
|
|
┌──────────────────────────────────────────────────────────────┐
|
|
│ SECURITY: How Funds Are Protected │
|
|
└──────────────────────────────────────────────────────────────┘
|
|
|
|
THE PROBLEM: Direct Settlement Authorization
|
|
───────────────────────────────────────────────
|
|
|
|
If user approved Settlement directly:
|
|
USDC.approve(GPv2Settlement, 5000 USDC)
|
|
|
|
Malicious solver could:
|
|
└─ In pre-interaction hook:
|
|
├─ Call Settlement.transferFrom(user, attacker, 5000 USDC)
|
|
└─ Steal all approved funds!
|
|
|
|
|
|
THE SOLUTION: Three-Layer Authorization
|
|
──────────────────────────────────────────
|
|
|
|
LAYER 1: Balancer Vault (Token Holder)
|
|
│
|
|
└─ User approves Vault (not Settlement):
|
|
USDC.approve(BalancerVault, uint256.max)
|
|
└─ Vault stores user allowances separately
|
|
|
|
|
|
LAYER 2: VaultRelayer (Authorized to Pull)
|
|
│
|
|
└─ VaultRelayer is ONLY authorized address
|
|
└─ VaultRelayer.transferFromAccounts(users, tokens, amounts, recipients)
|
|
└─ Can ONLY transfer TO Settlement
|
|
└─ No other transfers allowed
|
|
|
|
|
|
LAYER 3: Settlement (Atomic Execution)
|
|
│
|
|
└─ Settlement.settle() orchestrates:
|
|
├─ Calls VaultRelayer to pull input tokens
|
|
│ └─ VaultRelayer pulls from Vault (user's allowance)
|
|
│ └─ Transfers to specified recipient (adapter/settlement)
|
|
│
|
|
├─ Executes interactions (pre, during, post)
|
|
│ └─ Can't make arbitrary token calls (can call whitelisted)
|
|
│
|
|
└─ Calls VaultRelayer to return output tokens
|
|
└─ VaultRelayer pulls from adapter/settlement
|
|
└─ Returns to user
|
|
|
|
|
|
ATTACK SURFACE: What Malicious Solver CAN'T Do
|
|
────────────────────────────────────────────────
|
|
|
|
❌ Drain USDC allowance
|
|
└─ Settlement doesn't have direct access
|
|
└─ VaultRelayer only transfers to Settlement
|
|
└─ Settlement immediately re-transfers to user
|
|
|
|
❌ Call arbitrary token functions
|
|
└─ Settlement.settle() params validated
|
|
└─ Only authorized interactions executed
|
|
└─ Adapter is whitelisted
|
|
|
|
❌ Reenter settlement
|
|
└─ ReentrancyGuard on Settlement
|
|
└─ ReentrancyGuard on Adapter
|
|
|
|
❌ Access Vault directly
|
|
└─ VaultRelayer is the ONLY authorized puller
|
|
└─ Solver can't call Vault directly
|
|
|
|
|
|
SAFETY VERIFICATION CHECKLIST:
|
|
────────────────────────────────
|
|
|
|
Before deployment:
|
|
☑ Users approve BalancerVault, NOT Settlement
|
|
☑ Adapter approved for finite amounts (not uint256.max? Or whitelisted handler?)
|
|
☑ Adapter onlySettlement modifier on execution functions
|
|
☑ Settlement has ReentrancyGuard
|
|
☑ Adapter has ReentrancyGuard
|
|
☑ VaultRelayer address set correctly
|
|
☑ Quote expiry enforced in handler
|
|
☑ Slippage protection (minOutputAmount) enforced
|
|
☑ No direct token transfers without Settlement mediation
|
|
```
|
|
|
|
---
|
|
|
|
## 7. Watch Tower Polling Sequence
|
|
|
|
```
|
|
┌──────────────────────────────────────────────────────────┐
|
|
│ Watch Tower Polling Sequence (Every Block) │
|
|
└──────────────────────────────────────────────────────────┘
|
|
|
|
TIME: Every ~2 seconds (Base block time)
|
|
|
|
Watch Tower Service (background job)
|
|
│
|
|
├─ FOR EACH registered conditional order handler:
|
|
│
|
|
│ 1. GET all orders in ComposableCoW registry
|
|
│ └─ Orders: owner → handler → [order1, order2, ...]
|
|
│
|
|
│ 2. FOR EACH order in registry:
|
|
│
|
|
│ ┌─ Fetch staticInput (from registry)
|
|
│ │ {direction, inputAmount, minOutputAmount, receiver, validityDuration}
|
|
│ │
|
|
│ ├─ Fetch current quote from adapter
|
|
│ │ offchainData = {
|
|
│ │ quotedOutput: BondingCurveAdapter.quoteBuy(inputAmount),
|
|
│ │ validTo: now + 30min
|
|
│ │ }
|
|
│ │
|
|
│ ├─ CALL: MycoConditionalOrder.getTradeableOrder(
|
|
│ │ owner,
|
|
│ │ watchTowerAddress,
|
|
│ │ staticInput,
|
|
│ │ offchainData
|
|
│ │ )
|
|
│ │
|
|
│ ├─ Handler validates & returns GPv2Order.Data
|
|
│ │ └─ May throw:
|
|
│ │ ├─ PollTryNextBlock → Skip this block, retry next
|
|
│ │ ├─ OrderNotValid → Skip, retry later
|
|
│ │ ├─ PollNever → Remove from polling
|
|
│ │
|
|
│ ├─ Order is valid → Register in active orders
|
|
│ │ └─ Add to "orders to submit to solvers"
|
|
│ │
|
|
│ └─ Submit to solver network
|
|
│ └─ POST /orders with signed order
|
|
│
|
|
└─ LOOP: Wait for next block (in parallel, other handlers)
|
|
|
|
|
|
TIMELINE EXAMPLE (Real-world MycoFi order):
|
|
────────────────────────────────────────────
|
|
|
|
T+0s: User signs intent (private, off-chain)
|
|
└─ Creates conditional order in ComposableCoW
|
|
|
|
T+2s: Watch Tower polls
|
|
├─ Fetches static order params
|
|
├─ Calls adapter.quoteBuy() for live price
|
|
├─ Calls getTradeableOrder() → returns valid GPv2Order
|
|
└─ Submits to solver network
|
|
|
|
T+4s: (Poll cycle 2) ← Order already submitted, will appear in next batch
|
|
|
|
T+30s: Batch closes
|
|
└─ CoW collects all orders submitted in this batch window
|
|
|
|
T+32s: Solvers receive batch
|
|
├─ Solver A begins solving
|
|
├─ Solver B begins solving
|
|
└─ Solver C begins solving
|
|
|
|
T+35s: Solver B finds best solution
|
|
├─ Surplus: $8 profit for execution
|
|
└─ Submits GPv2Settlement.settle() tx
|
|
|
|
T+37s: Transaction included in block
|
|
├─ VaultRelayer pulls 500 USDC
|
|
├─ BondingCurveAdapter.executeBuyForCoW() executes
|
|
├─ MYCO minted, transferred to user
|
|
└─ USER RECEIVES 1000 MYCO! ✓
|
|
|
|
Total latency: ~37 seconds (quite reasonable!)
|
|
|
|
|
|
WHAT IF QUOTE EXPIRES?
|
|
──────────────────────
|
|
|
|
T+0s: Order created, quote valid until T+30s
|
|
|
|
T+28s: Watch Tower polls
|
|
├─ quotedOutput fetched
|
|
└─ Order submitted to solver
|
|
|
|
T+30s: Quote expires (validTo timestamp)
|
|
|
|
T+35s: Solver tries to execute
|
|
├─ Settlement calls getTradeableOrder()
|
|
├─ Handler checks: block.timestamp > quote.validTo?
|
|
│ └─ YES! Expired
|
|
├─ Throws OrderNotValid("Quote expired")
|
|
└─ Settlement fails, order not executed
|
|
|
|
T+40s: Watch Tower polls again
|
|
├─ Calls adapter for NEW quote
|
|
├─ Returns fresh GPv2Order with new validTo
|
|
└─ Resubmits to solvers
|
|
|
|
|
|
RATE LIMITING:
|
|
───────────────
|
|
Watch Tower polling: 1 per block (limited by block time)
|
|
BondingCurveAdapter.quoteBuy(): Call-per-poll (cheap, view function)
|
|
CoW API submission: Subject to API rate limits (5 reqs/sec)
|
|
|
|
For 1000 active conditional orders:
|
|
└─ Polls all 1000 orders per block
|
|
└─ ~1000 quote calls per block
|
|
└─ Adapter quotes must be fast (view function, no state change)
|
|
```
|
|
|
|
---
|
|
|
|
## 8. Custom Order Type Decision Tree
|
|
|
|
```
|
|
┌────────────────────────────────────────────────────────────┐
|
|
│ Which Custom Order Type Should You Use? │
|
|
└────────────────────────────────────────────────────────────┘
|
|
|
|
Need to execute a bonding curve trade?
|
|
│
|
|
├─ YES: Is it a simple "buy now" or "sell now" order?
|
|
│ │
|
|
│ ├─ YES → Use MycoConditionalOrder
|
|
│ │ ├─ Static input: amount, min output
|
|
│ │ ├─ Offchain input: live quote
|
|
│ │ └─ Executes immediately when quote valid
|
|
│ │
|
|
│ └─ NO: Do you need additional logic?
|
|
│ │
|
|
│ ├─ Need to trade at specific price level?
|
|
│ │ └─ Use MycoLimitOrder
|
|
│ │ └─ Only execute if price >= limit
|
|
│ │
|
|
│ ├─ Need to split trade over time?
|
|
│ │ └─ Use MycoTWAPOrder
|
|
│ │ └─ Divide into N parts, execute every T minutes
|
|
│ │
|
|
│ ├─ Need recurring purchases (e.g., daily)?
|
|
│ │ └─ Use MycoDCAOrder
|
|
│ │ └─ Buy $X every interval
|
|
│ │
|
|
│ └─ Need complex conditions?
|
|
│ └─ Combine handlers with BaseConditionalOrder
|
|
│ ├─ Pre-hook: Check oracle price
|
|
│ ├─ Handler: Generate GPv2Order
|
|
│ └─ Post-hook: Update tracking
|
|
|
|
|
|
┌─────────────────────┐
|
|
│ DECISION TABLE │
|
|
└─────────────────────┘
|
|
|
|
┌──────────────────┬──────────────────┬─────────────────┐
|
|
│ Use Case │ Handler │ Key Features │
|
|
├──────────────────┼──────────────────┼─────────────────┤
|
|
│ Buy now │ MycoConditional │ Live quote, │
|
|
│ Sell now │ Order │ slippage prot. │
|
|
├──────────────────┼──────────────────┼─────────────────┤
|
|
│ Buy at $0.50 │ MycoLimitOrder │ Price trigger, │
|
|
│ (no higher) │ │ post-only opt │
|
|
├──────────────────┼──────────────────┼─────────────────┤
|
|
│ Break buy into │ MycoTWAPOrder │ N parts, │
|
|
│ 10x over 1 hr │ │ T minute gaps │
|
|
├──────────────────┼──────────────────┼─────────────────┤
|
|
│ $100 daily for │ MycoDCAOrder │ Recurring, │
|
|
│ 30 days │ │ date-based │
|
|
├──────────────────┼──────────────────┼─────────────────┤
|
|
│ Buy if oracle │ Custom │ Compose │
|
|
│ says bullish │ + Pre-hook │ multiple │
|
|
└──────────────────┴──────────────────┴─────────────────┘
|
|
|
|
|
|
IMPLEMENTATION EFFORT:
|
|
──────────────────────
|
|
|
|
MycoConditionalOrder:
|
|
├─ Effort: 1-2 days
|
|
├─ Complexity: Medium
|
|
└─ Dependencies: BondingCurveAdapter, GPv2Order interfaces
|
|
|
|
MycoLimitOrder:
|
|
├─ Effort: 2-3 days
|
|
├─ Complexity: Medium-High
|
|
└─ Dependencies: Oracle/price feed, limit order tracking
|
|
|
|
MycoTWAPOrder:
|
|
├─ Effort: 3-4 days
|
|
├─ Complexity: High
|
|
└─ Dependencies: Timing logic, part completion tracking
|
|
|
|
MycoDCAOrder:
|
|
├─ Effort: 2-3 days
|
|
├─ Complexity: Medium
|
|
└─ Dependencies: Cron-like execution (Watch Tower polling)
|
|
|
|
Custom + Hooks:
|
|
├─ Effort: 4+ days
|
|
├─ Complexity: Very High
|
|
└─ Dependencies: Oracle, pre/post logic, state management
|
|
```
|
|
|
|
---
|
|
|
|
**Document Version:** 1.0
|
|
**Last Updated:** 2026-04-03
|
|
**Status:** Visual Reference Complete
|