Local Development Environment
The full local stack runs via docker-compose. All services connect to a local Anvil chain with deployed contracts and mock oracles.
Start the stack with cd infra && docker compose --profile m2 up -d. For system-level sequence diagrams, see System Flows.
Forking Sepolia
To test against the live Sepolia deployment without deploying your own contracts, fork the testnet locally:
anvil --fork-url $SEPOLIA_RPC_URL --fork-block-number <LATEST_BLOCK>
This creates a local chain with all deployed contracts at their current state. You can interact with them using the same addresses as on Sepolia, while having full control over block production and account balances.
Use cast rpc anvil_setBalance <address> <amount> to give test accounts ETH for gas, and interact with the MockUSDC contract to mint test collateral.
Test Vectors
The following test vectors can be used to verify your integration calculations match the protocol’s onchain behavior.
PnL Calculation
| Input | Value |
|---|
| Notional | 1,000 USDC (1,000,000,000 raw, 6 decimals) |
| Entry Price | 1.08 EUR/USD (1,080,000,000,000,000,000 raw, 18 decimals) |
| Current Price | 1.10 EUR/USD (1,100,000,000,000,000,000 raw, 18 decimals) |
| Expected PnL (LONG) | +20 USDC |
| Expected PnL (SHORT) | -20 USDC |
PnL formula: notional × (currentPrice − entryPrice) / entryPrice. For SHORT positions, the sign is inverted. See PnL Calculation for details.
Margin Requirements
| Input | Value |
|---|
| Notional | 1,000 USDC |
| IM Factor | 200 bps (2%) |
| Required Initial Margin | 20 USDC |
| MM Factor | 100 bps (1%) |
| Required Maintenance Margin | 10 USDC |
Common Errors
When integrating with the protocol, you may encounter these revert errors:
| Error | Cause | Resolution |
|---|
OracleInvalid() | The publisher has not published forward prices yet, or oracle data has gone stale | Start the publisher service and wait for the first price update |
ProtocolPaused() | The protocol is in PAUSED mode | Check the ModeController state. If auto-paused due to oracle, restore oracle data and have admin de-escalate |
InsufficientAvailableBalance() | The account does not have enough free collateral for the requested operation | Deposit more USDC collateral or reduce existing position sizes to free up margin |
ReduceOnlyMode() | The protocol is in DEGRADED or REDUCE_ONLY mode | Only closing operations (close, settle, liquidate) are permitted. Wait for admin to restore NORMAL mode |
Debugging Tips
Reading Contract State
Use Foundry’s cast tool to query onchain state directly:
# Read the current protocol mode
cast call <ModeController> "currentMode()(uint8)" --rpc-url $RPC_URL
# Read an account's collateral balance
cast call <MarginAccounts> "collateralBalance(address)(uint256)" <account> --rpc-url $RPC_URL
# Read a position's details
cast call <PositionManager> "getPosition(uint256)((address,uint8,uint8,uint8,uint256,uint256,int256,uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint8))" <positionId> --rpc-url $RPC_URL
Sending Transactions
Use cast send to interact with contracts from the command line:
# Deposit collateral (after approving USDC)
cast send <MarginAccounts> "deposit(uint256)" <amount> --private-key $PRIVATE_KEY --rpc-url $RPC_URL
# Open a position
cast send <PositionManager> "openPosition(bytes32,uint8,uint8,uint256,uint256)" \
<pairId> <side> <tenor> <notional> <margin> \
--private-key $PRIVATE_KEY --rpc-url $RPC_URL
Always start the publisher before attempting to open positions. Without published forward prices, the oracle will return invalid data and all position operations will revert with OracleInvalid().