A virtual machine for programmable token swaps. Execute complex trading strategies from bytecode programs without deploying contracts.
- Overview
- Deployment
- How It Works
- For Makers (Liquidity Providers)
- For Takers (Swap Executors)
- For Developers
- Security Model
- Advanced Topics
SwapVM is a computation engine that executes token swap strategies from bytecode programs. Instead of deploying smart contracts, you compose instructions into programs that are signed off-chain and executed on-demand.
Key Features:
- Static Balances - Fixed exchange rates for single-direction trades (limit orders, auctions, TWAP, DCA, RFQ)
- Dynamic Balances - Persistent, isolated AMM-style orders (each maker's liquidity is separate)
- Composable Instructions - Mix and match building blocks for complex strategies (combining pricing, fees, MEV protection)
- πΎ Makers - Provide liquidity through limit orders, AMM-style orders, or complex strategies
- π Takers - Execute swaps to arbitrage or fulfill trades
- π Developers - Build custom instructions and integrate SwapVM
SwapVM is deployed across multiple chains with a unified address for seamless cross-chain integration.
Contract Address: 0x8fdd04dbf6111437b44bbca99c28882434e0958f
Supported Networks:
- Ethereum Mainnet
- Base
- Optimism
- Polygon
- Arbitrum
- Avalanche
- Binance Smart Chain
- Linea
- Sonic
- Unichain
- Gnosis
- zkSync
SwapVM uses 4 registers to compute token swaps:
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β SwapRegisters β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β balanceIn: Maker's available input token balance β
β balanceOut: Maker's available output token balance β
β amountIn: Input amount (taker provides OR VM computes) β
β amountOut: Output amount (taker provides OR VM computes) β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
The Core Principle:
- Taker specifies ONE amount (either
amountInoramountOut) - VM computes the OTHER amount using the 4 registers
- Instructions modify registers to apply fees, adjust rates, etc.
The execution flow shows all available instructions and strategies for each balance type:
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β 1D STRATEGY (Static Balances, Single Direction) β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β BYTECODE COMPOSITION (Off-chain) β
β β
β 1. Balance Setup (Required) β
β ββ _staticBalancesXD β Fixed exchange rate β
β β
β 2. Core Swap Logic (Choose One) β
β ββ _limitSwap1D β Partial fills allowed β
β ββ _limitSwapOnlyFull1D β All-or-nothing β
β β
β 3. Order Invalidation (Required for Partial Fills) β
β ββ _invalidateBit1D β One-time order β
β ββ _invalidateTokenIn1D β Track input consumed β
β ββ _invalidateTokenOut1D β Track output distributed β
β β
β 4. Dynamic Pricing (Optional, Combinable) β
β ββ _dutchAuctionBalanceIn1D β Decreasing input amount β
β ββ _dutchAuctionBalanceOut1D β Increasing output amountβ
β ββ _oraclePriceAdjuster1D β External price feed β
β ββ _baseFeeAdjuster1D β Gas-responsive pricing β
β β
β 5. Fee Mechanisms (Optional, Combinable) β
β ββ _flatFeeAmountInXD β Fee from input amount β
β ββ _flatFeeAmountOutXD β Fee from output amount β
β ββ _progressiveFeeInXD β Size-based dynamic fee (input)β
β ββ _progressiveFeeOutXD β Size-based dynamic fee (output)β
β ββ _protocolFeeAmountOutXD β Protocol revenue (ERC20) β
β ββ _aquaProtocolFeeAmountOutXD β Protocol revenue (Aqua)β
β β
β 6. Advanced Strategies (Optional) β
β ββ _requireMinRate1D β Enforce minimum exchange rate β
β ββ _adjustMinRate1D β Adjust amounts to meet min rate β
β ββ _twap β Time-weighted average price execution β
β ββ _extruction β Extract and execute custom logic β
β β
β 7. Control Flow (Optional) β
β ββ _jump β Skip instructions β
β ββ _jumpIfTokenIn β Conditional on exact input β
β ββ _jumpIfTokenOut β Conditional on exact output β
β ββ _deadline β Expiration check β
β ββ _onlyTakerTokenBalanceNonZero β Require balance > 0β
β ββ _onlyTakerTokenBalanceGte β Minimum balance check β
β ββ _onlyTakerTokenSupplyShareGte β Min % of supply β
β ββ _salt β Order uniqueness (hash modifier) β
β β
β EXECUTION (On-chain) β
β ββ Verify signature & expiration β
β ββ Load static balances into 4 registers β
β ββ Execute bytecode instructions sequentially β
β ββ Update invalidator state (prevent replay/overfill) β
β ββ Transfer tokens (single direction only) β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β AMM STRATEGIES (2D/XD Bidirectional, Two Balance Options) β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β BALANCE MANAGEMENT OPTIONS β
β β
β Option A: Dynamic Balances (SwapVM Internal) β
β ββ Setup: Sign order with EIP-712 β
β ββ Balance Instruction: _dynamicBalancesXD β
β ββ Storage: SwapVM contract (self-managed) β
β β
β Option B: Aqua Protocol (External) β
β ββ Setup: Deposit via Aqua.ship() (on-chain) β
β ββ Balance Instruction: None (Aqua manages) β
β ββ Configuration: useAquaInsteadOfSignature = true β
β ββ Storage: Aqua protocol (shared liquidity) β
β β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β BYTECODE COMPOSITION (Same for Both) β
β β
β 1. Balance Setup β
β ββ Dynamic: _dynamicBalancesXD (required) β
β ββ Aqua: Skip (balances in Aqua) β
β β
β 2. AMM Logic (Choose Primary Strategy) β
β ββ _xycSwapXD β Classic x*y=k constant product β
β ββ _xycConcentrateGrowLiquidityXD/2D β CLMM ranges β
β β
β 3. Fee Mechanisms (Optional, Combinable) β
β ββ _flatFeeAmountInXD β Fee from input amount β
β ββ _flatFeeAmountOutXD β Fee from output amount β
β ββ _progressiveFeeInXD β Size-based dynamic fee (input) β
β ββ _progressiveFeeOutXD β Size-based dynamic fee (output)β
β ββ _protocolFeeAmountOutXD β Protocol revenue (ERC20) β
β ββ _aquaProtocolFeeAmountOutXD β Protocol revenue (Aqua)β
β β
β 4. MEV Protection (Optional) β
β ββ _decayXD β Virtual reserves (Mooniswap-style) β
β β
β 5. Advanced Features (Optional) β
β ββ _twap β Time-weighted average price trading β
β ββ _extruction β Extract and execute custom logic β
β β
β 6. Control Flow (Optional) β
β ββ _jump β Skip instructions β
β ββ _jumpIfTokenIn β Conditional jump on exact input β
β ββ _jumpIfTokenOut β Conditional jump on exact output β
β ββ _deadline β Expiration check β
β ββ _onlyTakerTokenBalanceNonZero β Require balance > 0 β
β ββ _onlyTakerTokenBalanceGte β Minimum balance check β
β ββ _onlyTakerTokenSupplyShareGte β Min % of supply β
β ββ _salt β Order uniqueness (hash modifier) β
β β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β EXECUTION (On-chain) β
β β
β Dynamic Balances Flow: β
β ββ Verify EIP-712 signature β
β ββ Load maker's isolated reserves from SwapVM β
β ββ Execute AMM calculations β
β ββ Update maker's state in SwapVM storage β
β ββ Transfer tokens (bidirectional) β
β β
β Aqua Protocol Flow: β
β ββ Verify Aqua balance (no signature) β
β ββ Load reserves from Aqua protocol β
β ββ Execute AMM calculations (same logic!) β
β ββ Aqua updates balance accounting β
β ββ Transfer tokens via Aqua settlement β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β COMMON TAKER FLOW (All Strategies) β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β 1. Discovery (Off-chain) β
β ββ Find orders via indexer/API β
β ββ Filter by tokens, rates, liquidity β
β ββ Simulate profitability β
β β
β 2. Quote (On-chain View) β
β ββ Call quote() to preview exact amounts β
β ββ Check slippage and fees β
β ββ Verify execution conditions β
β β
β 3. Execution Parameters β
β ββ isExactIn β Specify input or output amount β
β ββ threshold β Minimum/maximum acceptable amount β
β ββ to β Recipient address β
β ββ hooks β Pre/post swap callbacks β
β β
β 4. Settlement β
β ββ Maker β Taker (output token) β
β ββ Taker β Maker (input token) β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Programs are sequences of instructions, each encoded as:
[opcode_index][args_length][args_data]
β β β
1 byte 1 byte N bytes
Example: A limit order might compile to:
[17][4A][balance_args][26][01][swap_args]
β β
staticBalances limitSwap
SwapVM offers two primary balance management approaches:
Use Case: Limit orders, Dutch auctions, TWAP, DCA, RFQ, range orders, stop-loss
- Fixed Rate: Exchange rate remains constant
- Partial Fills: Supports partial execution with amount invalidators
- No Storage: Pure function, no state persistence
- Direction: Single-direction trades (e.g., only sell ETH for USDC)
// Example: Sell 1 ETH for 2000 USDC
p.build(Balances._staticBalancesXD,
BalancesArgsBuilder.build(
dynamic([WETH, USDC]),
dynamic([1e18, 2000e6]) // Fixed rate
))Use Case: Constant product AMMs, CLMMs
- Self-Rebalancing: Balances update after each trade
- State Persistence: Order state stored in SwapVM
- Isolated Liquidity: Each maker's funds are separate (no pooling)
- Bidirectional: Supports trading in both directions
- Price Discovery: Price adjusts based on reserves
// Example: Initialize AMM-style order with 10 ETH and 20,000 USDC
p.build(Balances._dynamicBalancesXD,
BalancesArgsBuilder.build(
dynamic([WETH, USDC]),
dynamic([10e18, 20_000e6]) // Initial reserves
))SwapVM maintains fundamental invariants that ensure economic security and predictable behavior across all instructions:
Every instruction MUST maintain symmetry between exactIn and exactOut swaps:
- If
exactIn(X) β Y, thenexactOut(Y) β X(within rounding tolerance) - Critical for price consistency and preventing internal arbitrage
- Validated by test suite across all swap instructions
Splitting swaps must not provide better rates:
swap(A+B)should equalswap(A) + swap(B)for output amounts- Ensures no gaming through order splitting
- Larger trades cannot be improved by breaking into smaller ones
Quote and swap functions must return identical amounts:
quote()is a view function that previews swap resultsswap()execution must match the quoted amounts exactly- Essential for MEV protection and predictable execution
Larger trades receive equal or worse prices:
- Price defined as
amountOut/amountIn - Must decrease (or stay constant) as trade size increases
- Natural consequence of liquidity curves and market impact
All rounding operations must favor the liquidity provider:
- Small trades (few wei) shouldn't exceed theoretical spot price
amountInalways rounds UP (ceil)amountOutalways rounds DOWN (floor)- Protects makers from rounding-based value extraction
Trades cannot exceed available liquidity:
- Must revert if computed
amountOut > balanceOut - Prevents impossible trades and protects order integrity
- Enforced at the VM level before token transfers
These invariants are validated through comprehensive test suites and must be maintained by any new instruction implementations.
SwapVM provides a reusable CoreInvariants base contract for testing:
import { CoreInvariants } from "test/invariants/CoreInvariants.t.sol";
contract MyInstructionTest is Test, OpcodesDebug, CoreInvariants {
function test_MyInstruction_MaintainsInvariants() public {
// Create order with your instruction
ISwapVM.Order memory order = createOrderWithMyInstruction();
// Test all invariants at once
assertAllInvariants(swapVM, order, tokenIn, tokenOut);
// Or test specific invariants
assertSymmetryInvariant(swapVM, order, tokenIn, tokenOut,
amount, tolerance, exactInData, exactOutData);
assertMonotonicityInvariant(swapVM, order, tokenIn, tokenOut,
amounts, takerData);
}
}Configuration options for complex scenarios:
InvariantConfig memory config = createInvariantConfig(testAmounts, tolerance);
config.skipAdditivity = true; // For stateless orders
config.skipMonotonicity = true; // For fixed-rate orders
assertAllInvariantsWithConfig(swapVM, order, tokenIn, tokenOut, config);See test/invariants/ExampleInvariantUsage.t.sol for complete examples.
Makers provide liquidity by creating orders with custom swap logic.
- Define swap logic via bytecode programs (includes setting balances/exchange rate)
- Configure order parameters (expiration, fees, hooks)
- Sign orders off-chain (gasless)
// 1. Build your swap program
Program memory p = ProgramBuilder.init(_opcodes());
bytes memory program = bytes.concat(
// Set your exchange rate: 1000 USDC for 0.5 WETH
p.build(Balances._staticBalancesXD,
BalancesArgsBuilder.build(
dynamic([USDC, WETH]),
dynamic([1000e6, 0.5e18]) // Your offered rate
)),
// Execute the swap
p.build(LimitSwap._limitSwap1D,
LimitSwapArgsBuilder.build(USDC, WETH)),
// Track partial fills (prevents overfilling)
p.build(Invalidators._invalidateTokenOut1D,
InvalidatorsArgsBuilder.buildInvalidateByTokenOut(WETH))
);
// 2. Configure order parameters
MakerTraits makerTraits = MakerTraitsLib.build(MakerTraitsLib.Args({
shouldUnwrapWeth: false, // Keep WETH (don't unwrap to ETH)
expiration: block.timestamp + 1 days, // Order expires in 24h
receiver: address(0), // You receive the tokens
useAquaInsteadOfSignature: false // Use standard EIP-712 signing
}));
// 3. Create order (completely off-chain)
ISwapVM.Order memory order = ISwapVM.Order({
maker: yourAddress,
traits: makerTraits,
program: program
});
// 4. Sign order off-chain (gasless)
bytes32 orderHash = swapVM.hash(order);
bytes memory signature = signEIP712(orderHash);Create a persistent, isolated AMM-style order (your liquidity only):
// Constant product AMM with 0.3% fee
bytes memory program = bytes.concat(
// Load/initialize balances
p.build(Balances._dynamicBalancesXD,
BalancesArgsBuilder.build(
dynamic([USDC, WETH]),
dynamic([100_000e6, 50e18]) // Initial liquidity
)),
// Apply trading fee
p.build(Fee._flatFeeAmountInXD,
FeeArgsBuilder.buildFlatFee(0.003e9)), // 0.3%
// Execute constant product swap (x*y=k)
p.build(XYCSwap._xycSwapXD)
);// Fixed exchange rate for 1D strategies (limit orders, auctions)
p.build(Balances._staticBalancesXD, ...)Characteristics:
- Fixed exchange rate throughout order lifetime
- Supports partial fills with amount invalidators
- No state storage (pure function)
- Single-direction trades only
- Ideal for: Limit orders, Dutch auctions, TWAP, DCA, RFQ, range orders, stop-loss
Both options use the same AMM logic and support identical features. The only difference is where balances are stored:
// Persistent AMM-style order with isolated liquidity
p.build(Balances._dynamicBalancesXD, ...)
// Sign with EIP-712Storage: SwapVM contract (per-maker isolation)
Setup: Sign order off-chain (gasless)
Use Case: Individual AMM strategies, custom curves
Key Point: Replicates Aqua-like functionality but with signature-based orders (no deposits)
Note: Each maker's liquidity is isolated - no pooling with others
// Use Aqua's shared liquidity layer
MakerTraits makerTraits = MakerTraitsLib.build({
useAquaInsteadOfSignature: true
});
// Requires prior: aqua.ship(token, amount)Storage: Aqua protocol (external)
Setup: Deposit to Aqua via ship()
Use Case: Share liquidity across multiple strategies
Key Difference: Unlike isolated dynamic balances, Aqua enables shared liquidity
See Aqua Protocol for details
Your orders are protected by:
- EIP-712 Signatures - Orders cannot be modified
- Expiration Control - Orders expire when you want
- Balance Limits - Cannot trade more than specified
- Custom Receivers - Send tokens where you want
- Hooks - Custom validation logic
- Order Invalidation - One-time execution via bitmaps
Best Practices:
- Always set expiration dates
- Use
_invalidateBit1Dfor one-time orders - Validate rates match market conditions
- Consider MEV protection (
_decayXD)
Takers execute swaps against maker orders to arbitrage or fulfill trades.
- Find profitable orders to execute
- Specify swap amount (either input or output)
- Provide dynamic data for adaptive instructions
- Execute swaps on-chain
// 1. Find an order to execute
ISwapVM.Order memory order = findProfitableOrder();
// 2. Preview the swap (free call)
(uint256 amountIn, uint256 amountOut) = swapVM.asView().quote(
order,
USDC, // Token you're trading
WETH, // Token you're receiving
1000e6, // Amount (input if isExactIn=true)
takerTraitsData // Your execution parameters
);
// 3. Prepare taker parameters
bytes memory takerTraits = TakerTraitsLib.build(TakerTraitsLib.Args({
isExactIn: true, // You specify input amount
threshold: minAmountOut, // Minimum output (slippage protection)
to: yourAddress, // Where to receive tokens
shouldUnwrapWeth: false, // Keep as WETH
// Optional features:
hasPreTransferInHook: false,
isFirstTransferFromTaker: false
}));
// 4. Execute the swap
(uint256 actualIn, uint256 actualOut, bytes32 orderHash) = swapVM.swap(
order,
USDC,
WETH,
1000e6, // Your input amount
abi.encodePacked(signature, takerTraits, customData)
);Some instructions read data from takers at execution time:
// Pack custom data for instructions
bytes memory customData = abi.encode(
oraclePrice, // For oracle-based adjustments
maxGasPrice, // For gas-sensitive orders
userPreference // Any custom parameters
);
// Instructions access via:
// ctx.tryChopTakerArgs(32) - extracts 32 bytesThe isExactIn flag determines which amount you control:
| isExactIn | You Specify | VM Computes | Use Case |
|---|---|---|---|
| true | Input amount | Output amount | "I want to sell exactly 1000 USDC" |
| false | Output amount | Input amount | "I want to buy exactly 0.5 WETH" |
Your swaps are protected by:
- Threshold Validation - Minimum output / maximum input
- Slippage Protection - Via threshold amounts
- Custom Recipients - Send tokens anywhere
- Pre-hooks - Validate before execution
- Quote Preview - Check amounts before executing
Best Practices:
- Always use
quote()beforeswap() - Set appropriate thresholds for slippage
- Verify order hasn't expired
- Check for MEV opportunities
- Consider gas costs vs profit
SwapVM creates MEV opportunities:
- Arbitrage - Price differences between orders
- Liquidations - Execute against distressed positions
- JIT Liquidity - Provide liquidity just-in-time
- Sandwich Protection - Some orders use
_decayXDfor protection
Build custom instructions and integrate SwapVM into your protocols.
Every instruction receives a Context with three components:
Context
βββ VM (Execution State)
β βββ nextPC βββββββββββββββββββββ Program counter (MUTABLE - for jumps)
β βββ programPtr βββββββββββββββββ Bytecode being executed
β βββ takerArgsPtr βββββββββββββββ Taker's dynamic data (MUTABLE - via tryChopTakerArgs)
β βββ opcodes ββββββββββββββββββββ Available instructions array
β
βββ SwapQuery (READ-ONLY)
β βββ orderHash ββββββββββββββββββ Unique order identifier
β βββ maker ββββββββββββββββββββββ Liquidity provider address
β βββ taker ββββββββββββββββββββββ Swap executor address
β βββ tokenIn ββββββββββββββββββββ Input token address
β βββ tokenOut βββββββββββββββββββ Output token address
β βββ isExactIn ββββββββββββββββββ Taker's swap direction (true = exact in, false = exact out)
β
βββ SwapRegisters (MUTABLE)
βββ balanceIn ββββββββββββββββββ Maker's available input token balance
βββ balanceOut βββββββββββββββββ Maker's available output token balance
βββ amountIn βββββββββββββββββββ Input amount (taker provides OR VM computes)
βββ amountOut ββββββββββββββββββ Output amount (taker provides OR VM computes)
MakerTraits (256-bit packed)
βββ Token Handling
β βββ shouldUnwrapWeth ββββββββββ Unwrap WETH to ETH on output
β
βββ Order Lifecycle
β βββ expiration (40 bits) ββββββ Unix timestamp when order expires
β
βββ Balance Management
β βββ useAquaInsteadOfSignature β Use Aqua balance instead of signature
β βββ allowZeroAmountIn βββ Skip Aqua for input transfers
β
βββ Recipient Control
β βββ receiver βββββββββββββββββββ Custom recipient (0 = maker)
β
βββ Hooks (Callbacks)
βββ hasPreTransferOutHook ββββββ Call maker before output transfer
βββ hasPostTransferInHook ββββββ Call maker after input transfer
βββ preTransferOutData ββββββββββ Data for pre-transfer hook
βββ postTransferInData ββββββββββ Data for post-transfer hook
TakerTraits (Variable-length)
βββ Swap Direction
β βββ isExactIn ββββββββββββββββββ true = specify input, false = output
β
βββ Amount Validation
β βββ threshold ββββββββββββββββββ Min output or max input
β βββ isStrictThresholdAmount βββ true = exact, false = min/max
β
βββ Token Handling
β βββ shouldUnwrapWeth βββββββββββ Unwrap WETH to ETH on output
β βββ to βββββββββββββββββββββββββ Custom recipient (0 = taker)
β
βββ Transfer Mechanics
β βββ isFirstTransferFromTaker ββ Who transfers first
β βββ useTransferFromAndAquaPush β SwapVM does transferFrom + Aqua push (vs taker pushes in callback)
β
βββ Hooks (Callbacks)
βββ hasPreTransferInHook βββββββ Call taker before input transfer
Instructions compute swap amounts only - they do NOT execute the actual token transfers (except protocol fee instructions which can transfer fees). The swap itself happens after all instructions complete.
Instructions can only modify three aspects of the Context:
All four registers can be modified to calculate swap amounts:
balanceIn/balanceOut- Set or adjust available balances for calculationsamountIn/amountOut- Compute the missing swap amount
Control execution flow between instructions:
- Skip instructions (jump forward)
- Loop back to previous instructions
- Conditional branching based on computation state
Consume data provided by taker at execution time:
- Read dynamic parameters for calculations
- Process variable-length data
- Advance the taker data pointer
Instructions can invoke ctx.runLoop() to execute remaining instructions and then continue:
- Apply pre-processing, let other instructions compute amounts, then post-processing
- Wrap amount computations with fee calculations
- Wait for amount computation before validation
- Implement complex multi-phase amount calculations
Instructions operate within SwapVM's execution framework:
What Instructions CAN Do:
- β Read all context data (query, VM state, registers)
- β Modify the 4 swap registers
- β Change program counter for control flow
- β Consume taker-provided data
- β Read and write to their own storage mappings
- β
Make external calls (via
_extruction) - β Execute fee transfers (protocol fee instructions)
What Instructions CANNOT Do:
- β Modify query data (maker, taker, tokens, etc. - immutable)
- β Transfer swap tokens directly (except protocol fees)
- β Bypass SwapVM's validation (thresholds, signatures, etc.)
- β Modify core SwapVM protocol state
- β Execute after swap is complete
Security Considerations:
- Reentrancy protection only for Aqua settlement (via transient storage when taker pushes)
- Gas limited by block and transaction
- External calls risk managed by maker's instruction choice
- Deterministic execution
Routers define available instructions:
contract MyRouter is SwapVM, Opcodes {
constructor(address aqua)
SwapVM(aqua, "MyRouter", "1.0")
Opcodes(aqua)
{}
function _instructions() internal pure override
returns (function(Context memory, bytes calldata) internal[] memory)
{
// Return your instruction set
return _opcodes();
}
}Use the provided CoreInvariants base contract to ensure your instructions maintain all invariants:
contract MyInstructionTest is Test, OpcodesDebug, CoreInvariants {
function test_MyInstruction() public {
// Build program with your instruction
bytes memory program = buildProgramWithMyInstruction();
ISwapVM.Order memory order = createOrder(program);
// Validate all core invariants are maintained
assertAllInvariants(swapVM, order, tokenA, tokenB);
}
}For manual testing:
function testMyInstructionManually() public {
// Create test context
Context memory ctx = Context({
vm: VM({
isStaticContext: false,
nextPC: 0,
programPtr: CalldataPtrLib.from(program),
takerArgsPtr: CalldataPtrLib.from(takerData),
opcodes: _opcodes()
}),
query: SwapQuery({
orderHash: bytes32(0),
maker: makeAddr("maker"),
taker: makeAddr("taker"),
tokenIn: address(tokenA),
tokenOut: address(tokenB),
isExactIn: true
}),
swap: SwapRegisters({
balanceIn: 1000e18,
balanceOut: 2000e18,
amountIn: 100e18,
amountOut: 0
})
});
// Execute instruction
bytes memory args = abi.encode(0.003e9); // 0.3% fee
MyInstruction._myInstruction(ctx, args);
// Verify results
assertGt(ctx.swap.amountOut, 0);
}SwapVM's security is built on maintaining fundamental invariants that ensure economic correctness:
- Exact In/Out Symmetry - Prevents internal arbitrage opportunities
- Swap Additivity - Ensures no gaming through order splitting
- Quote/Swap Consistency - Guarantees predictable execution
- Price Monotonicity - Natural market dynamics are preserved
- Rounding Favors Maker - Protects liquidity providers from value extraction
- Balance Sufficiency - Prevents impossible trades
These invariants are enforced at the VM level and validated through comprehensive test suites.
Core Security Features:
- EIP-712 Typed Signatures - Prevents signature malleability
- Order Hash Uniqueness - Each order has unique identifier
- Reentrancy Protection - Transient storage locks (EIP-1153)
- Overflow Protection - Solidity 0.8+ automatic checks
- Gas Limits - Block gas limit prevents infinite loops
- Invariant Validation - All instructions must maintain core invariants
Signature Verification:
// Standard EIP-712
orderHash = keccak256(abi.encode(
ORDER_TYPEHASH,
order.maker,
order.traits,
keccak256(order.program)
));
// Or Aqua Protocol (no signature needed)
if (useAquaInsteadOfSignature) {
require(AQUA.balances(maker, orderHash, token) >= amount);
}Protection Mechanisms:
| Feature | Description | Implementation |
|---|---|---|
| Signature Control | Orders cannot be modified | EIP-712 signatures |
| Expiration | Time-limited orders | expiration in MakerTraits |
| Balance Limits | Cannot exceed specified amounts | Register bounds checking |
| One-time Execution | Prevent replay | _invalidateBit1D instruction |
| Custom Logic | Hooks for validation | Pre/post transfer hooks |
| Receiver Control | Specify token recipient | receiver in MakerTraits |
Risk Mitigations:
// Limit order exposure
p.build(Invalidators._invalidateBit1D, bitIndex);
// Add expiration
traits.expiration = block.timestamp + 1 hours;
// MEV protection
p.build(Decay._decayXD, DecayArgsBuilder.build(30));Protection Mechanisms:
| Feature | Description | Implementation |
|---|---|---|
| Slippage Protection | Min output/max input | threshold in TakerTraits |
| Amount Validation | Exact amounts enforced | isStrictThresholdAmount flag |
| Preview Execution | Check before swap | quote() function |
| Custom Recipients | Control token destination | to in TakerTraits |
| Hook Validation | Pre-execution checks | hasPreTransferInHook |
Risk Mitigations:
// Set minimum output
takerTraits.threshold = minAcceptableOutput;
// Preview first
(amountIn, amountOut) = swapVM.asView().quote(...);
require(amountOut >= minRequired, "Insufficient output");
// Then execute
swapVM.swap(...);Sandboxed Execution:
βββββββββββββββββββββββββββββββββββββββββββ
β Instruction Sandbox β
βββββββββββββββββββββββββββββββββββββββββββ€
β β
Allowed: β
β β’ Read context data β
β β’ Modify swap registers β
β β’ Control flow (jumps) β
β β’ Pure computations β
βββββββββββββββββββββββββββββββββββββββββββ€
β β Restricted: β
β β’ External calls β
β β’ Storage modification β
β β’ Query data modification β
β β’ Infinite loops β
βββββββββββββββββββββββββββββββββββββββββββ
Validation Example:
function _safeInstruction(Context memory ctx, bytes calldata args) internal {
// β
Can read and modify swap registers
ctx.swap.amountIn = ctx.swap.amountIn * 99 / 100;
// β
Can read query data (read-only)
address maker = ctx.query.maker;
// β
Can modify VM state for control flow
ctx.vm.nextPC = newPC;
// β
Can consume taker data
bytes calldata data = ctx.tryChopTakerArgs(32);
// β Cannot do:
// IERC20(token).transfer(...); // No external calls
// ctx.query.maker = newMaker; // Query is read-only
// selfdestruct(); // No destructive operations
}Makers define programs that trade assets on their behalf and are responsible for correctness:
Logic Errors
- Risk: Incorrect instruction sequence or arguments
- Mitigation: Test thoroughly, use proven patterns, audit critical strategies
Replay Attacks
- Risk: Order executed multiple times or overfilled
- Mitigation:
- Include
_invalidateBit1Dfor one-time execution - Use
_invalidateTokenIn/Out1Dfor partial fills - Set appropriate expiration
- Include
Price Exposure
- Risk: Trades at unfavorable market conditions
- Mitigation:
- Add
_requireMinRate1Dchecks - Set expiration timestamps
- Use oracle price bounds
- Add
Order Uniqueness
- Risk: Cannot create multiple identical orders
- Mitigation: Use
_saltinstruction to differentiate, vary parameters slightly
Takers control execution parameters and must verify rates:
Rate Slippage
- Risk: Receive worse exchange rate than expected
- Mitigation Options:
- Threshold Protection:
- Exact:
isStrictThresholdAmount = true - Min output:
isExactIn = true, threshold = minOut - Max input:
isExactIn = false, threshold = maxIn
- Exact:
- Callback Validation:
- Pre-transfer hook:
hasPreTransferInHook = true - Custom logic via
ITakerCallbacks
- Pre-transfer hook:
- Return Data Verification:
- Check returned
(amountIn, amountOut) - Compare with
quote()results
- Check returned
- Threshold Protection:
MEV Attacks
- Risk: Front-running or sandwich attacks
- Mitigation: Use private mempools (Flashbots), set tight thresholds, use commit-reveal patterns
Failed Transactions
- Risk: Wasted gas from reverts
- Mitigation: Always call
quote()first, verify token balances, check order expiration
The protocol provides these built-in protections:
Parameter Integrity
- Never violates maker/taker constraints through strict trait enforcement
Balance Isolation
- Each maker's liquidity is separate using per-maker storage slots
Instruction Sandboxing
- No external calls from instructions (pure/view functions only)
Reentrancy Protection
- Prevents recursive calls using transient locks (EIP-1153)
Overflow Protection
- Safe arithmetic operations with Solidity 0.8+ checks
Deterministic Execution
- Same inputs always produce same outputs (no external dependencies in core logic)
Provide liquidity within specific price ranges:
// Calculate concentration parameters
(uint256 deltaA, uint256 deltaB) = XYCConcentrateArgsBuilder.computeDeltas(
1000e6, // balanceA
0.5e18, // balanceB
2000e18, // current price
1900e18, // lower bound
2100e18 // upper bound
);
// Build CLMM strategy
bytes memory program = bytes.concat(
p.build(Balances._dynamicBalancesXD, balances),
p.build(XYCConcentrate._xycConcentrateGrowLiquidity2D,
XYCConcentrateArgsBuilder.build2D(tokenA, tokenB, deltaA, deltaB)),
p.build(Fee._flatFeeAmountInXD, fee),
p.build(XYCSwap._xycSwapXD)
);Complex multi-instruction strategies:
// Dutch auction + gas adjustment + oracle + rate limit
bytes memory program = bytes.concat(
p.build(Balances._staticBalancesXD, ...),
p.build(DutchAuction._dutchAuctionBalanceOut1D, ...),
p.build(BaseFeeAdjuster._baseFeeAdjuster1D, ...),
p.build(OraclePriceAdjuster._oraclePriceAdjuster1D, ...),
p.build(MinRate._adjustMinRate1D, ...),
p.build(LimitSwap._limitSwap1D, ...)
);SwapVM offers two protocol fee instructions with different settlement mechanisms:
1. _protocolFeeAmountOutXD - Direct ERC20 Transfer
- Uses standard
transferFromto collect fees - Requires maker to have approved SwapVM contract
- Fee is transferred directly from maker to recipient
- Suitable for standard ERC20 tokens
2. _aquaProtocolFeeAmountOutXD - Aqua Protocol Integration
- Uses Aqua's
pullfunction for fee collection - Works with orders using Aqua balance management
- No separate approval needed (uses Aqua's existing permissions)
- Enables batched fee collection and gas optimization
Usage Example:
// Direct ERC20 protocol fee
p.build(Fee._protocolFeeAmountOutXD,
FeeArgsBuilder.buildProtocolFee(10, treasury)); // 0.1% to treasury
// Aqua protocol fee (for Aqua-managed orders)
p.build(Fee._aquaProtocolFeeAmountOutXD,
FeeArgsBuilder.buildProtocolFee(10, treasury)); // 0.1% via AquaBoth calculate fees identically but differ in the transfer mechanism.
// Virtual balance decay
p.build(Decay._decayXD, DecayArgsBuilder.build(30)); // 30s decay
// Progressive fees (larger swaps pay more)
p.build(Fee._progressiveFeeInXD, ...); // or _progressiveFeeOutXD
/* Progressive Fee Improvements:
* New formula: dx_eff = dx / (1 + Ξ» * dx / x)
* - Maintains near-perfect exact in/out symmetry
* - Only ~1 gwei asymmetry from safety ceiling operations
* - Mathematically reversible for consistent pricing
*/
// Time-based pricing
p.build(DutchAuction._dutchAuctionBalanceOut1D, ...);The _twap instruction implements a sophisticated selling strategy with:
- Linear liquidity unlocking over time
- Exponential price decay (Dutch auction) for price discovery
- Automatic price bumps after illiquidity periods
- Minimum trade size enforcement
Set minTradeAmountOut 1000x+ larger than expected gas costs:
| Network | Gas Cost | Recommended Min Trade |
|---|---|---|
| Ethereum | $50 | $50,000+ |
| Arbitrum/Optimism | $0.50 | $500+ |
| BSC/Polygon | $0.05 | $50+ |
This ensures gas costs remain <0.1% of trade value.
The priceBumpAfterIlliquidity compensates for mandatory waiting periods:
| Min Trade % of Balance | Unlock Time | Recommended Bump |
|---|---|---|
| 0.1% | 14.4 min | 5-10% (1.05e18 - 1.10e18) |
| 1% | 14.4 min | 10-20% (1.10e18 - 1.20e18) |
| 5% | 1.2 hours | 30-50% (1.30e18 - 1.50e18) |
| 10% | 2.4 hours | 50-100% (1.50e18 - 2.00e18) |
Additional factors:
- Network gas costs: Higher gas β larger bumps
- Pair volatility: Volatile pairs β larger bumps
- Market depth: Thin markets β higher bumps
SwapVM reserves opcodes 1-10 for debugging utilities, available only in debug routers:
Available Debug Instructions:
_printSwapRegisters- Logs all 4 swap registers (balances and amounts)_printSwapQuery- Logs query data (orderHash, maker, taker, tokens, isExactIn)_printContext- Logs complete execution context_printFreeMemoryPointer- Logs current memory usage_printGasLeft- Logs remaining gas
Usage:
// Deploy debug router
SwapVMRouterDebug debugRouter = new SwapVMRouterDebug(aquaAddress);
// Include debug instructions in program
bytes memory program = bytes.concat(
p.build(Balances._staticBalancesXD, ...),
p.build(Debug._printSwapRegisters), // Debug output
p.build(LimitSwap._limitSwap1D, ...),
p.build(Debug._printContext) // Final state
);Note: Debug instructions are no-ops in production routers and should only be used for development and testing.
Architecture Benefits:
- Transient storage (EIP-1153) for reentrancy guards
- Zero deployment cost for makers
- Compact bytecode encoding (8-bit opcodes)
Tips for Makers:
- Use
_staticBalancesXDfor single-direction trades with fixed rates - Use
_dynamicBalancesXDfor AMM strategies with automatic rebalancing - Pack multiple operations in single program
- Minimize argument sizes
Tips for Takers:
- Batch multiple swaps
- Use
quote()to avoid failed transactions - Consider gas costs in profit calculations
The AquaAMM contract provides a helper for building AMM programs with Aqua integration:
import { AquaAMM } from "@1inch/swap-vm/contracts/strategies/AquaAMM.sol";
// Build a concentrated liquidity AMM with fees
ISwapVM.Order memory order = AquaAMM(aquaAMM).buildProgram(
maker, // Your address
expiration, // Order expiration
token0, // First token
token1, // Second token
feeBpsIn, // Trading fee (e.g., 30 for 0.3%)
delta0, // Concentration parameter for token0
delta1, // Concentration parameter for token1
decayPeriod, // MEV protection decay period
protocolFeeBps, // Protocol fee share
feeReceiver, // Protocol fee recipient
salt // Order uniqueness salt
);Features:
- Automatically constructs bytecode with proper instruction ordering
- Integrates concentrated liquidity, fees, and MEV protection
- Uses Aqua protocol for balance management (no signatures needed)
- Includes debug output in development mode
Example: 0.3% Fee Concentrated AMM:
// Calculate concentration deltas for price range
(uint256 delta0, uint256 delta1) = XYCConcentrateArgsBuilder.computeDeltas(
1000e6, // 1000 USDC
0.5e18, // 0.5 ETH
2000e18, // Current price: 2000 USDC/ETH
1900e18, // Lower bound
2100e18 // Upper bound
);
// Build order
ISwapVM.Order memory order = aquaAMM.buildProgram(
msg.sender, // maker
block.timestamp + 30 days, // expiration
USDC, // token0
WETH, // token1
30, // 0.3% fee
delta0, // USDC concentration
delta1, // ETH concentration
30, // 30s decay period
10, // 0.1% protocol fee
treasury, // fee receiver
1 // salt
);npm install @1inch/swap-vm
# or
yarn add @1inch/swap-vmimport { SwapVMRouter } from "@1inch/swap-vm/contracts/SwapVMRouter.sol";
import { Program, ProgramBuilder } from "@1inch/swap-vm/test/utils/ProgramBuilder.sol";
// Deploy router
SwapVMRouter router = new SwapVMRouter(aquaAddress, "MyDEX", "1.0");
// Create and execute orders...- GitHub: github.com/1inch/swap-vm
- Documentation: See
/docsdirectory - Tests: Comprehensive examples in
/test - Audits: Security review reports in
/audits
This project is licensed under the LicenseRef-Degensoft-SwapVM-1.1
See the LICENSE file for details. See the THIRD_PARTY_NOTICES file for information about third-party software, libraries, and dependencies used in this project.
Contact for licensing inquiries:
- π§ [email protected]
- π§ [email protected]