Swap & Intent System
Swap & Intent System
Technical architecture of VaubanSwap — Starknet's CoW (Coincidence of Wants) intent settlement system.
System Overview
Off-chain On-chain
┌──────────┐ ┌────────────┐ ┌──────────────┐ ┌──────────────┐
│ User │────>│ Backend │────>│ CoW Matcher │────>│ Settlement │
│ Wallet │ │ (API) │ │ (bipartite) │ │ Contract │
│ │<────│ │<────│ │ │ │
│ SNIP-12 │ │ swap_intents│ │ cow-matcher │ │ VaubanSettle │
│ Signing │ │ table │ │ .ts (318L) │ │ ment.cairo │
└──────────┘ └────────────┘ └──────┬───────┘ └──────────────┘
│
┌──────┴───────┐
│ AVNU Fallback │
│ (unmatched) │
└──────────────┘Smart Contracts
VaubanSettlement
File: contracts/src/VaubanSettlement.cairo (571 LOC)
The atomic batch settlement contract. All intents in a batch either succeed together or revert together.
Constructor:
VaubanSettlement(owner, deposit_token, min_solver_deposit)Core function — settle_batch():
settle_batch(
users: Span<ContractAddress>, // Intent signers
sell_tokens: Span<ContractAddress>, // Tokens being sold
buy_tokens: Span<ContractAddress>, // Tokens being bought
sell_amounts: Span<u256>, // Amounts sold
min_buy_amounts: Span<u256>, // Minimum acceptable output
deadlines: Span<u64>, // Expiration timestamps
nonces: Span<u256>, // Replay protection
signatures: Span<Span<felt252>>, // SNIP-12 signatures
fill_buy_amounts: Span<u256>, // Actual fill amounts (>= min)
)Invariant: For every intent i, fill_buy_amounts[i] >= min_buy_amounts[i]. The contract hard-reverts the entire batch if any intent receives less than its minimum.
Security features:
- OpenZeppelin: Ownable, Pausable, ReentrancyGuard
- Nonce-based replay protection (per-user nonce tracking)
- On-chain intent cancellation (
cancel_intent,cancel_intents) - Deadline enforcement (expired intents revert)
Solver Registration
Two paths to become a solver:
| Path | Mechanism | Requirements |
|---|---|---|
| Admin-authorized | authorize_solver(address) | Owner approval |
| Permissionless | register_solver() | Deposit bond (100 STRK default) |
Permissionless solvers deposit a configurable bond (min_solver_deposit) in the deposit token. The bond can be slashed by the admin for misbehavior via slash_solver(). Deregistration returns the remaining bond.
IntentVault
File: contracts/src/IntentVault.cairo (476 LOC)
Optional pre-deposit vault for faster execution. Users deposit tokens in advance; the solver pulls directly from the vault during settlement.
Key features:
- Supports multiple ERC20 tokens
- Auto-staking: output tokens can be auto-staked into VaubanVault via
supported_vaultsmapping - Instant withdrawal (no unbonding period)
- Per-user balance tracking
YieldIntentVault
File: contracts/src/YieldIntentVault.cairo (612 LOC)
Extended IntentVault that earns yield on idle DCA funds via Nostra lending integration.
Architecture:
User deposits ──> YieldIntentVault ──> Nostra ILendingPool
│ │
│ 10% liquid buffer │ supply()
│ for execution │ withdraw()
│ │
DCA executor ◄───── yield + principalKey parameters:
| Parameter | Value | Purpose |
|---|---|---|
| Liquid buffer | 10% | Ensures execution reliability |
| Per-user cap | 100K STRK | Risk management |
| Min stake | 10 STRK | Gas efficiency threshold |
Functions:
stake_idle_funds()— Move idle balance to lending poolunstake_for_execution()— Pull funds back for DCA executionharvest_yield()— Collect and redistribute earned yield
Off-Chain Components
Solver Service
File: apps/defi/bastion/api/src/services/solver.service.ts (523 LOC)
Orchestrates the batch settlement cycle:
- Collect: Accumulate signed intents from API submissions
- Match: Run CoW bipartite matching (
cow-matcher.ts) - Settle: Submit matched batch to
VaubanSettlement - Fallback: Route unmatched intents through AVNU aggregator
The batch window is adaptive — higher volume triggers more frequent settlements.
CoW Matcher
File: apps/defi/bastion/api/src/services/cow-matcher.ts (318 LOC)
Bipartite matching engine that finds opposing intent pairs:
Intent A: Sell 1000 STRK, Buy ETH
Intent B: Sell 0.5 ETH, Buy STRK
──────────────────────────────────
Match: A ←→ B (Coincidence of Wants)
Surplus: Any price improvement beyond both minimumsThe matcher maximizes total matched volume across all token pairs in a single pass.
SNIP-12 Intent Signing
Intents are signed off-chain using the SNIP-12 typed data standard (Starknet's equivalent of EIP-712).
Type hash structure:
SwapIntent {
inputToken: ContractAddress,
outputToken: ContractAddress,
inputAmount: u256,
minOutputAmount: u256,
deadline: u64,
nonce: felt252,
}Signing spec: docs/specs/SNIP-12-SWAP-INTENT.md
Settlement Flow
1. User signs SNIP-12 intent off-chain (zero gas)
│
2. Backend stores intent in swap_intents table
│
3. Solver triggers batch window
│
4. CoW matcher finds opposing pairs
│
5a. Matched pairs ──> VaubanSettlement.settle_batch()
│ │
│ ├── Verify all signatures
│ ├── Check all nonces unused
│ ├── Check all deadlines valid
│ ├── Transfer sell tokens from users
│ ├── Transfer buy tokens to users
│ ├── Assert fill >= minBuyAmount
│ └── Emit IntentSettled + BatchSettled
│
5b. Unmatched intents ──> AVNU aggregator
│ │
│ └── Price floor: minOutputAmount
│
6. WebSocket notifies user of settlement statusEvents
| Event | Indexed Keys | Data |
|---|---|---|
IntentSettled | user, nonce | sell_token, buy_token, sell_amount, buy_amount, settlement_id |
BatchSettled | settlement_id | num_intents, solver, timestamp |
IntentCancelled | user, nonce | — |
SolverRegistered | solver | deposit |
SolverDeregistered | solver | refund |
SolverSlashed | solver | amount, remaining |
Deployed Contracts
| Contract | Sepolia | Mainnet |
|---|---|---|
| VaubanSettlement | 0x003ea562... | 0x070347e0... |
| IntentVault | See .sepolia_addresses | 0x03450709... |
| YieldIntentVault | Pending | — |
Security Considerations
- No custody: Users sign intents; tokens are transferred atomically during settlement
- Atomic batches: All-or-nothing execution prevents partial fills
- Solver bonds: Permissionless solvers stake collateral that can be slashed
- AVNU price floor: Unmatched intents always get at least
minOutputAmount - Nonce replay protection: Each nonce can only be used once per user
- Pausable: Admin can halt settlements during incidents