Router Integration Guide
Interacting with Boros contracts directly is not recommended for most use cases. The REST API and SDK handle authentication, calldata encoding, and transaction submission for you. Direct contract interaction is error-prone and may break if contract addresses or ABIs change. The information below is provided as a reference for advanced integrators only.
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)
- ConditionalModule: conditional order execution (used by stop-order system)
- DepositModule: deposit box operations (managed by Pendle backend)
- MiscModule: utility functions (simulation, batch operations)
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);
Subaccount Transferβ
Transfer cash between subaccounts under the same root. This is distinct from cross/isolated cash transfer β it moves funds between different subaccount IDs (e.g., from subaccount 0 to subaccount 1).
router.subaccountTransfer(
1, // target accountId
tokenId, // TokenId of collateral token
marketId, // marketId (CROSS for cross-margin)
500e18, // amount to transfer
true // isDeposit: true = transfer TO target, false = transfer FROM target
);
Currently only subaccount 0 is available for external users. Subaccount transfers are primarily used internally.
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 market account (marketAcc) can enter a maximum of 10 markets simultaneously. This limit is per combination of (address, accountId, tokenId) β i.e., per collateral type. Exceeding this limit will cause transactions to revert. You must exit expired or matured markets to free up slots before entering new ones.
Market Entrance Fees: A one-time entrance fee is charged when you first interact with any market. See Fees β Market Entrance Fees for amounts.
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
enterMarketis true - Cancel existing orders via
idToStrictCancel - Place the new order
- Transfer cash for isolated margin positions
- Automatically exit markets if
exitMarketis 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β
Orders placed via bulkOrders are placed directly into the order book and cannot match with AMM liquidity. If you need to take liquidity from both the order book and the AMM (e.g., for taker orders), use placeSingleOrder instead. For taker-only execution without any residual maker orders, set tif to IOC (Immediate or Cancel) or FOK (Fill or Kill).
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 theidsarrayisStrict: 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);
Conditional Ordersβ
The ConditionalModule enables orders with off-chain trigger conditions. This is the on-chain mechanism underlying the Stop Order system.
How it works:
- The user (agent) signs a
ConditionalOrderspecifying the trade parameters and a hashed off-chain condition - When the condition is met (e.g., market rate crosses a threshold), a permissioned validator signs an execution message
- The validator submits both signatures to
executeConditionalOrder(), which executes the trade on-chain
struct ConditionalOrder {
Account account; // The trading account
bool cross; // Cross or isolated margin
MarketId marketId; // Target market
Side side; // LONG or SHORT
TimeInForce tif; // Order type (GTC, IOC, etc.)
uint256 size; // Order size
int16 tick; // Price tick
bool reduceOnly; // Only reduce existing position
uint256 salt; // Unique salt for replay protection
uint64 expiry; // Order expiration timestamp
bytes32 hashedOffchainCondition; // Hash of the trigger condition
}
Conditional orders are not placed directly by users. They are managed through the Stop Order Service API, which handles the signing, condition monitoring, and execution flow.