Router Integration Guide
The Router contract serves as the main entry point for all user interactions with Boros.
Router follows a modular architecture:
- AuthModule: agent authentication and delegated trading (irrelevant for external parties)
- AMMModule: AMM-specific operations (add/remove liquidity, swaps)
- TradeModule: core trading operations (enter/exit markets, place/cancel orders, cash transfer)
- MiscModule: utility functions
Account Authorizationβ
Currently, you can only operate on the main account (subaccount 0). Support for other subaccounts may be added in future versions.
Most Router functions require two key parameters:
bool cross
: Specifies margin mode (true = cross-margin, false = isolated-margin)MarketId marketId
: The market identifier
These parameters construct the MarketAcc
(Market Account) identifier:
// Cross-margin account (can trade multiple markets)
bool cross = true;
MarketId marketId = MarketIdLib.CROSS; // Special cross-margin value
// Isolated-margin account (single market)
bool cross = false;
MarketId marketId = MarketId.wrap(12345); // Specific market ID
Vault Operationsβ
Vault operations manage the underlying collateral tokens.
Deposit cashβ
router.vaultDeposit(
0, // accountId (0 = main account)
tokenId, // TokenId of collateral token
marketId, // marketId (2^24-1 for cross account)
1 ether // Raw token amount
);
Withdraw cashβ
Withdrawals follow a two-step process: request β cooldown period β finalize.
Checking Withdrawal Status: You can monitor your withdrawal request status and cooldown period through MarketHub's getUserWithdrawalStatus function. This returns detailed information including the pending amount and exact timestamp when withdrawal becomes available.
// Step 1: Request withdrawal
router.requestVaultWithdrawal(tokenId, 1 ether);
// Step 2: After cooldown period, finalize withdrawal on MarketHub
IMarketHub(marketHub).finalizeVaultWithdrawal(
address(this), // root address
tokenId // TokenId of collateral token
);
// Can cancel pending withdrawal
router.cancelVaultWithdrawal(tokenId);
Cash Transferβ
Transfer cash between cross-margin and isolated-margin accounts using the same collateral token.
// Transfer from cross-margin to isolated-margin account
CashTransferReq memory req = CashTransferReq({
marketId: marketId, // Target isolated market
signedAmount: 1000e18 // Positive = from cross to isolated
});
router.cashTransfer(req);
// Transfer from isolated-margin back to cross-margin account
req.signedAmount = -500e18; // Negative = from isolated to cross
router.cashTransfer(req);
Market Entry and Exitβ
Enter Marketsβ
Users must enter markets before trading.
MarketId[] memory marketIds = new MarketId[](2);
marketIds[0] = marketId0;
marketIds[1] = marketId1;
// Enter multiple markets
EnterExitMarketsReq memory req = EnterExitMarketsReq({
cross: true,
isEnter: true,
marketIds: marketIds
});
router.enterExitMarkets(req);
Market Entry Limits: Each account can enter a maximum of 10 markets simultaneously. Exceeding this limit will cause transactions to revert.
Market Entrance Fees: A one-time entrance fee is charged when you first interact with any market:
- BTC markets: 0.000008 BTC (β$1 USD)
- ETH markets: 0.00027 ETH (β$1 USD)
Minimum Cash Requirements: You must maintain minimum cash balances in your account to participate in markets. Check the specific requirements for cross vs isolated margin modes.
Exit Marketsβ
Exit Requirements: To exit a market, you must:
- Close all positions (zero position size)
- Cancel all open orders
- Have no pending settlements
Market Maturity: When markets reach maturity, you should exit them to free up market slots.
MarketId[] memory marketIds = new MarketId[](2);
marketIds[0] = marketId0;
marketIds[1] = marketId1;
// Exit multiple markets
EnterExitMarketsReq memory req = EnterExitMarketsReq({
cross: true,
isEnter: false,
marketIds: marketIds
});
router.enterExitMarkets(req);
Order Book Tradingβ
Place Single Orderβ
Compound Function: placeSingleOrder
is a compound function that can:
- Automatically enter markets if
enterMarket
is true - Cancel existing orders via
idToStrictCancel
- Place the new order
- Transfer cash for isolated margin positions
- Automatically exit markets if
exitMarket
is true
This reduces the number of transactions needed for complex operations.
// Basic limit order
SingleOrderReq memory req = SingleOrderReq({
order: OrderReq({
cross: true, // Cross-margin
marketId: marketId,
ammId: AMMIdLib.ZERO, // Order book only (not AMM)
side: Side.LONG, // Buy interest rate swap
tif: TimeInForce.GTC, // Good till cancelled
size: 1000e18, // Position size
tick: 125 // Price tick (β5% rate)
}),
enterMarket: false, // Don't auto-enter market
idToStrictCancel: OrderIdLib.ZERO, // No order to cancel
exitMarket: false, // Don't auto-exit
isolated_cashIn: 0, // For isolated margin only
isolated_cashTransferAll: false,
desiredMatchRate: 0 // Accept any match rate
});
router.placeSingleOrder(req);
Bulk Ordersβ
Optimal for Market Makers: bulkOrders
is designed for professional market makers and offers:
- Place multiple orders in a single transaction
- Operate across multiple markets simultaneously
- Cancel existing orders before placing new ones
- Significant gas savings compared to individual order transactions
Cancel Parameters:
isAll: true
- Cancel all existing orders in the market before placing new ordersisAll: false
- Cancel only the specific order IDs listed in theids
arrayisStrict: true
- Transaction reverts if any specified order ID doesn't existisStrict: false
- Silently skip non-existent order IDs without reverting
// Place multiple orders across different markets
BulkOrder[] memory bulks = new BulkOrder[](2);
uint256[] memory sizes0 = new uint256[](2);
sizes0[0] = 500e18;
sizes0[1] = 300e18;
int16[] memory limitTicks0 = new int16[](2);
limitTicks0[0] = 120;
limitTicks0[1] = 125;
bulks[0] = BulkOrder({
marketId: marketId1,
orders: LongShort({
tif: TimeInForce.GTC,
side: Side.LONG,
sizes: sizes0,
limitTicks: limitTicks0
}),
cancelData: CancelData({
ids: new OrderId[](0),
isAll: false,
isStrict: false
})
});
uint256[] memory sizes1 = new uint256[](1);
sizes1[0] = 1900e18;
int16[] memory limitTicks1 = new int16[](1);
limitTicks1[0] = 140;
bulks[1] = BulkOrder({
marketId: marketId2,
orders: LongShort({
tif: TimeInForce.GTC,
side: Side.SHORT,
sizes: [1000e18],
limitTicks: [140]
}),
cancelData: CancelData({
ids: new OrderId[](0),
isAll: false,
isStrict: false
})
});
int128[] memory desiredMatchRates = new int128[](2);
desiredMatchRates[0] = 0;
desiredMatchRates[1] = 0;
BulkOrdersReq memory req = BulkOrdersReq({
cross: true,
bulks: bulks,
desiredMatchRates: desiredMatchRates
});
BulkOrderResult[] memory results = router.bulkOrders(req);
Cancel Ordersβ
OrderId[] memory orderIds = new OrderId[](2);
orderIds[0] = orderId1;
orderIds[1] = orderId2;
// Cancel specific orders
BulkCancels memory cancels = BulkCancels({
cross: true,
marketId: marketId,
cancelAll: false,
orderIds: orderIds
});
router.bulkCancels(cancels);
// Cancel all orders in a market
cancels.cancelAll = true;
cancels.orderIds = new OrderId[](0);
router.bulkCancels(cancels);
AMMβ
Add Liquidity Dualβ
Adding liquidity requires providing both cash and position sizes in proportion to the AMM's current state. The AMM automatically calculates the required ratio based on its current cash-to-position balance.
AddLiquidityDualToAmmReq memory req = AddLiquidityDualToAmmReq({
cross: true,
ammId: ammId,
maxCashIn: 10000e18,
exactSizeIn: 1000e18,
minLpOut: 950e18
});
(uint256 lpOut, int256 cashIn, uint256 fee) = router.addLiquidityDualToAmm(req);
Add Liquidity Single Cashβ
This function simplifies liquidity provision by:
- Using part of your cash to trade for the required position size
- Adding the remaining cash + acquired position as dual liquidity
AddLiquiditySingleCashToAmmReq memory req = AddLiquiditySingleCashToAmmReq({
cross: true,
ammId: ammId,
enterMarket: true,
netCashIn: 5000e18,
minLpOut: 450e18
});
(uint256 lpOut, int256 cashUsed, uint256 fee, int256 swapSize) =
router.addLiquiditySingleCashToAmm(req);
Remove Liquidity Dualβ
Removes liquidity and receives both cash and position sizes proportionally. You specify the LP tokens to burn and receive the underlying assets based on the AMM's current composition.
// Remove liquidity and receive both cash and size
RemoveLiquidityDualFromAmmReq memory req = RemoveLiquidityDualFromAmmReq({
cross: true,
ammId: ammId,
lpToRemove: 1000e18,
minCashOut: 4500e18,
minSizeOut: -1200e18,
maxSizeOut: -800e18
});
(int256 cashOut, int256 sizeOut, uint256 fee) = router.removeLiquidityDualFromAmm(req);
Remove Liquidity Single Cashβ
Removes liquidity and converts everything to cash by:
- Withdrawing proportional cash and position sizes
- Trading the position sizes back to cash
- Returning the total cash amount to you
RemoveLiquiditySingleCashFromAmmReq memory req = RemoveLiquiditySingleCashFromAmmReq({
cross: true,
ammId: ammId,
lpToRemove: 500e18,
minCashOut: 4800e18
});
(int256 cashOut, uint256 fee, int256 swapSize) =
router.removeLiquiditySingleCashFromAmm(req);
Simulationβ
The MiscModule provides simulation capabilities.
// Simulate multiple operations
SimulateData[] memory simulations = new SimulateData[](2);
simulations[0] = SimulateData({
account: mainAccount,
target: address(router),
data: abi.encodeCall(ITradeModule.placeSingleOrder, orderRequest)
});
simulations[1] = SimulateData({
account: mainAccount,
target: address(router),
data: abi.encodeCall(IAMMModule.swapWithAmm, swapRequest)
});
// Get simulation results without changing state
(bytes[] memory results, uint256[] memory gasUsed) = router.batchSimulate(simulations);