API Reference
Overview
This document specifies the API endpoints required for frontend integration with the Ollo system. The backend service should expose these endpoints, which will interact with the on-chain smart contracts.
Write Endpoints (Transactions)
These endpoints initiate blockchain transactions. Responses should include transaction hash and status.
POST /accounts
Create a new trading account for a user.
Description: Creates an FxAccount contract for the user. One account per address.
Request Body:
{
"userAddress": "0x..." // User's wallet address (required for tx signing)
}
Contract Call:
FxEngine.createAccount()
Response (Success - 201):
{
"success": true,
"data": {
"accountId": 1,
"accountAddress": "0x...", // FxAccount contract address
"owner": "0x...",
"transactionHash": "0x..."
}
}
Response (Error - 400):
{
"success": false,
"error": {
"code": "ACCOUNT_EXISTS",
"message": "Account already exists for this address"
}
}
Errors:
| Code | Description |
|---|---|
ACCOUNT_EXISTS | User already has an account |
POST /accounts/{accountId}/deposit
Deposit USDC margin into an account.
Description: Transfers USDC from user's wallet to their FxAccount. Requires prior USDC approval.
Path Parameters:
| Parameter | Type | Description |
|---|---|---|
accountId | uint256 | Account ID |
Request Body:
{
"amount": "1000000000" // Amount in USDC base units (6 decimals)
}
Contract Call:
// Prerequisite: User must approve FxEngine to spend USDC
USDC.approve(fxEngineAddress, amount)
// Then:
FxEngine.depositMargin(accountId, amount)
Response (Success - 200):
{
"success": true,
"data": {
"accountId": 1,
"depositedAmount": "1000000000",
"depositedAmountFormatted": "1000.00",
"newAvailableMargin": "1000000000000000000000",
"transactionHash": "0x..."
}
}
Response (Error - 400):
{
"success": false,
"error": {
"code": "INVALID_AMOUNT",
"message": "Amount must be greater than 0"
}
}
Errors:
| Code | Description |
|---|---|
INVALID_AMOUNT | Amount is 0 |
INSUFFICIENT_BALANCE | User doesn't have enough USDC |
INSUFFICIENT_ALLOWANCE | USDC approval insufficient |
POST /accounts/{accountId}/withdraw
Withdraw available margin from an account.
Description: Withdraws USDC from FxAccount to the account owner. Only available (uncommitted) margin can be withdrawn.
Path Parameters:
| Parameter | Type | Description |
|---|---|---|
accountId | uint256 | Account ID |
Request Body:
{
"amount": "500000000" // Amount in USDC base units (6 decimals)
}
Contract Call:
FxEngine.withdrawMargin(accountId, amount)
Response (Success - 200):
{
"success": true,
"data": {
"accountId": 1,
"withdrawnAmount": "500000000",
"withdrawnAmountFormatted": "500.00",
"newAvailableMargin": "500000000000000000000",
"transactionHash": "0x..."
}
}
Errors:
| Code | Description |
|---|---|
INVALID_AMOUNT | Amount is 0 |
INSUFFICIENT_MARGIN | Not enough available margin |
UNAUTHORIZED | Caller is not account owner |
POST /accounts/{accountId}/orders
Place a new order (open, add to, or close position).
Description: Places an order on the order book. For opening/adding positions, margin is committed. For closing, use collateralAmount: 0 with opposite side.
Path Parameters:
| Parameter | Type | Description |
|---|---|---|
accountId | uint256 | Account ID |
Request Body:
{
"marketId": "0x35b8bafff3570683af968b8d36b91b1a19465141d9712425e9f76c68ff8cb152",
"collateralAmount": "1000000000", // USDC (6 decimals), 0 for close
"side": "LONG", // "LONG" or "SHORT"
"leverage": 10, // 1-50 (market dependent)
"orderType": "MARKET", // "MARKET" or "LIMIT"
"limitPrice": "0" // Price in 18 decimals, ignored for MARKET
}
Side Enum:
| Value | Description |
|---|---|
LONG (0) | Profit when price increases |
SHORT (1) | Profit when price decreases |
Order Type Enum:
| Value | Description |
|---|---|
MARKET (0) | Execute at best available price immediately |
LIMIT (1) | Execute at specified price or better |
Contract Call:
FxEngine.placeOrder(
accountId,
marketId,
collateralAmount, // uint128
side, // 0 = LONG, 1 = SHORT
leverage, // uint16
orderType, // 0 = MARKET, 1 = LIMIT
limitPrice // uint128, 18 decimals
)
Response (Success - 201):
{
"success": true,
"data": {
"clientKey": "0x1181bbc736bb03fb4c87bf3f88518de202ec190c1ead906d71da89b2022e5491",
"orderId": 1,
"marketId": "0x35b8bafff...",
"side": "LONG",
"size": "10000000000000000000000", // Position size (18 decimals)
"sizeFormatted": "10000.00",
"orderType": "MARKET",
"status": "PLACED",
"transactionHash": "0x..."
}
}
Errors:
| Code | Description |
|---|---|
MARKET_NOT_FOUND | Invalid marketId |
MARKET_NOT_ACTIVE | Market is paused |
EXCEEDS_MAX_LEVERAGE | Leverage > market max or 0 |
INSUFFICIENT_MARGIN | Not enough available margin |
UNAUTHORIZED | Caller is not account owner |
DELETE /accounts/{accountId}/orders/{clientKey}
Cancel an open order.
Description: Cancels an unfilled or partially filled order. Returns uncommitted margin to available balance.
Path Parameters:
| Parameter | Type | Description |
|---|---|---|
accountId | uint256 | Account ID |
clientKey | bytes32 | Order client key |
Query Parameters:
| Parameter | Type | Description |
|---|---|---|
marketId | bytes32 | Market identifier (required) |
Contract Call:
FxEngine.cancelOrder(accountId, marketId, clientKey)
Response (Success - 200):
{
"success": true,
"data": {
"clientKey": "0x1181bbc736bb03fb4c87bf3f88518de202ec190c1ead906d71da89b2022e5491",
"orderId": 1,
"marginReturned": "1000000000000000000000",
"marginReturnedFormatted": "1000.00",
"transactionHash": "0x..."
}
}
Errors:
| Code | Description |
|---|---|
ORDER_NOT_FOUND | Order doesn't exist or already filled/cancelled |
MARKET_NOT_FOUND | Invalid marketId |
UNAUTHORIZED | Caller is not account owner |
POST /accounts/{accountId}/positions/{marketId}/close
Close an existing position.
Description: Convenience endpoint that places a closing order. Internally calls placeOrder with opposite side and zero collateral.
Path Parameters:
| Parameter | Type | Description |
|---|---|---|
accountId | uint256 | Account ID |
marketId | bytes32 | Market identifier |
Request Body:
{
"orderType": "MARKET", // "MARKET" or "LIMIT"
"limitPrice": "0" // Required for LIMIT orders (18 decimals)
}
Contract Call:
// Get current position to determine close side
Position pos = FxAccount.getPosition(marketId);
SIDE closeSide = (pos.side == LONG) ? SHORT : LONG;
FxEngine.placeOrder(
accountId,
marketId,
0, // Zero collateral for close
closeSide,
1, // Leverage ignored for close
orderType,
limitPrice
)
Response (Success - 201):
{
"success": true,
"data": {
"clientKey": "0x...",
"orderId": 2,
"marketId": "0x35b8bafff...",
"closingSide": "SHORT",
"size": "10000000000000000000000",
"orderType": "MARKET",
"transactionHash": "0x..."
}
}
Errors:
| Code | Description |
|---|---|
NO_POSITION | No open position in this market |
MARKET_NOT_FOUND | Invalid marketId |
UNAUTHORIZED | Caller is not account owner |
Read Endpoints (Queries)
These endpoints query blockchain state. No transactions required.
GET /accounts/{accountId}
Get account information.
Path Parameters:
| Parameter | Type | Description |
|---|---|---|
accountId | uint256 | Account ID |
Contract Calls:
FxEngine.getAccount(accountId)
FxEngine.getAccountMargin(accountId)
Response (Success - 200):
{
"success": true,
"data": {
"accountId": 1,
"accountAddress": "0x...",
"owner": "0x...",
"margin": {
"available": "500000000000000000000",
"availableFormatted": "500.00",
"committed": "1000000000000000000000",
"committedFormatted": "1000.00",
"total": "1500000000000000000000",
"totalFormatted": "1500.00"
}
}
}
Errors:
| Code | Description |
|---|---|
ACCOUNT_NOT_FOUND | Account doesn't exist |
GET /accounts/by-address/{userAddress}
Get account by user wallet address.
Path Parameters:
| Parameter | Type | Description |
|---|---|---|
userAddress | address | User's wallet address |
Contract Call:
uint256 accountId = FxEngine.getAccountId(userAddress)
// If accountId > 0, fetch full account info
Response (Success - 200):
{
"success": true,
"data": {
"accountId": 1,
"accountAddress": "0x...",
"owner": "0x...",
"exists": true
}
}
Response (No Account - 200):
{
"success": true,
"data": {
"accountId": 0,
"exists": false
}
}
GET /accounts/{accountId}/positions/{marketId}
Get position details for a specific market.
Path Parameters:
| Parameter | Type | Description |
|---|---|---|
accountId | uint256 | Account ID |
marketId | bytes32 | Market identifier |
Contract Calls:
FxAccount account = FxEngine.getAccount(accountId).accountAddress;
Position pos = account.getPosition(marketId);
PositionHealth health = FxEngine.getPositionHealth(accountId, marketId);
(uint128 liqPrice, uint128 insolvencyPrice) = FxEngine.getLiquidationPrices(accountId, marketId);
Response (Success - 200):
{
"success": true,
"data": {
"isOpen": true,
"side": "LONG",
"size": "10000000000000000000000",
"sizeFormatted": "10000.00",
"entryPrice": "150000000000000000000",
"entryPriceFormatted": "150.00",
"margin": "1000000000000000000000",
"marginFormatted": "1000.00",
"leverage": 10,
"openBlock": 12345678,
"health": {
"markPrice": "151000000000000000000",
"markPriceFormatted": "151.00",
"positionValue": "10066666666666666666666",
"positionValueFormatted": "10066.67",
"unrealizedPnL": "66666666666666666666",
"unrealizedPnLFormatted": "66.67",
"marginRatioBps": 1060,
"marginRatioPercent": "10.60",
"liquidationPrice": "136500000000000000000",
"liquidationPriceFormatted": "136.50",
"insolvencyPrice": "135000000000000000000",
"insolvencyPriceFormatted": "135.00",
"isLiquidatable": false
}
}
}
Response (No Position - 200):
{
"success": true,
"data": {
"isOpen": false,
"side": null,
"size": "0",
"health": null
}
}
GET /accounts/{accountId}/orders
Get all orders for an account.
Path Parameters:
| Parameter | Type | Description |
|---|---|---|
accountId | uint256 | Account ID |
Query Parameters:
| Parameter | Type | Description |
|---|---|---|
marketId | bytes32 | Filter by market (optional) |
status | string | Filter: "active", "filled", "cancelled" (optional) |
Contract Calls:
FxAccount account = FxEngine.getAccount(accountId).accountAddress;
bytes32[] keys = account.marketOrderKeys(marketId);
for each key:
OrderInfo info = account.getOrderInfoByKey(key);
Response (Success - 200):
{
"success": true,
"data": {
"orders": [
{
"clientKey": "0x...",
"orderId": 1,
"marketId": "0x35b8bafff...",
"side": "LONG",
"orderType": "LIMIT",
"price": "149000000000000000000",
"priceFormatted": "149.00",
"originalQuantity": "10000000000000000000000",
"originalQuantityFormatted": "10000.00",
"filledQuantity": "5000000000000000000000",
"filledQuantityFormatted": "5000.00",
"remainingQuantity": "5000000000000000000000",
"remainingQuantityFormatted": "5000.00",
"marginAllocated": "1000000000000000000000",
"isActive": true,
"fillPercent": 50
}
],
"totalCount": 1
}
}
GET /markets
Get all registered markets.
Contract Call:
// Iterate through known marketIds or emit events to index
FxEngine.getMarket(marketId)
Response (Success - 200):
{
"success": true,
"data": {
"markets": [
{
"marketId": "0x35b8bafff3570683af968b8d36b91b1a19465141d9712425e9f76c68ff8cb152",
"symbol": "USD/JPY",
"marketAddress": "0x...",
"baseFxToken": "0x...",
"baseFxTokenSymbol": "fxUSD",
"quoteFxToken": "0x...",
"quoteFxTokenSymbol": "fxJPY",
"maxLeverage": 50,
"maintenanceMarginBps": 100,
"maintenanceMarginPercent": "1.00",
"liquidationFeeBps": 50,
"liquidationFeePercent": "0.50",
"isActive": true,
"openInterest": {
"long": "1000000000000000000000000",
"longFormatted": "1000000.00",
"short": "950000000000000000000000",
"shortFormatted": "950000.00"
}
}
]
}
}
GET /markets/{marketId}
Get specific market details.
Path Parameters:
| Parameter | Type | Description |
|---|---|---|
marketId | bytes32 | Market identifier |
Contract Call:
FxEngine.getMarket(marketId)
Response: Same structure as single market in /markets response.
GET /markets/{marketId}/funding
Get funding rate information for a market.
Path Parameters:
| Parameter | Type | Description |
|---|---|---|
marketId | bytes32 | Market identifier |
Contract Calls:
int256 currentRate = FxEngine.getCurrentFundingRate(marketId);
int256 cumulativeFunding = FxEngine.getCumulativeFunding(marketId);
int256 pendingFunding = FxEngine.getPendingFunding(marketId);
MarketConfig config = FxEngine.getMarket(marketId);
// From FundingRateOracle (if available):
(uint128 perpPrice, uint128 indexPrice, uint256 lastUpdate) = oracle.getPrices(marketId);
(int256 rate, int256 premium, int256 skew) = oracle.previewFundingRate(marketId);
Response (Success - 200):
{
"success": true,
"data": {
"marketId": "0x35b8bafff...",
"currentRate": "100000000000000",
"currentRateFormatted": "0.0001",
"currentRatePercent": "0.01",
"currentRateAnnualized": "87.60",
"cumulativeFunding": "500000000000000000",
"cumulativeFundingFormatted": "0.50",
"pendingFunding": "10000000000000000",
"pendingFundingFormatted": "0.01",
"lastUpdate": 1702234567,
"lastUpdateISO": "2024-12-10T15:42:47Z",
"prices": {
"perpPrice": "150100000000000000000",
"perpPriceFormatted": "150.10",
"indexPrice": "150000000000000000000",
"indexPriceFormatted": "150.00",
"premium": "666666666666666",
"premiumPercent": "0.067"
},
"components": {
"premiumComponent": "66666666666666",
"skewComponent": "33333333333333",
"skew": "26315789473684210526",
"skewPercent": "2.63"
},
"direction": "LONGS_PAY"
}
}
Direction Values:
| Value | Description |
|---|---|
LONGS_PAY | Positive rate, longs pay shorts |
SHORTS_PAY | Negative rate, shorts pay longs |
NEUTRAL | Zero rate |
GET /markets/{marketId}/orderbook
Get order book for a market.
Path Parameters:
| Parameter | Type | Description |
|---|---|---|
marketId | bytes32 | Market identifier |
Query Parameters:
| Parameter | Type | Default | Description |
|---|---|---|---|
depth | int | 10 | Number of price levels per side |
Contract Calls:
Market market = FxEngine.getMarket(marketId).market;
// Aggregate orders at each price level from Market contract
Response (Success - 200):
{
"success": true,
"data": {
"marketId": "0x35b8bafff...",
"timestamp": 1702234567890,
"bids": [
{
"price": "149900000000000000000",
"priceFormatted": "149.90",
"quantity": "50000000000000000000000",
"quantityFormatted": "50000.00",
"orderCount": 3
},
{
"price": "149800000000000000000",
"priceFormatted": "149.80",
"quantity": "75000000000000000000000",
"quantityFormatted": "75000.00",
"orderCount": 5
}
],
"asks": [
{
"price": "150100000000000000000",
"priceFormatted": "150.10",
"quantity": "45000000000000000000000",
"quantityFormatted": "45000.00",
"orderCount": 2
},
{
"price": "150200000000000000000",
"priceFormatted": "150.20",
"quantity": "60000000000000000000000",
"quantityFormatted": "60000.00",
"orderCount": 4
}
],
"spread": {
"absolute": "200000000000000000",
"absoluteFormatted": "0.20",
"percent": "0.133"
},
"midPrice": "150000000000000000000",
"midPriceFormatted": "150.00"
}
}
GET /markets/{marketId}/price
Get current mark price for a market.
Path Parameters:
| Parameter | Type | Description |
|---|---|---|
marketId | bytes32 | Market identifier |
Contract Call:
uint128 markPrice = FxEngine.getMarkPrice(marketId);
Response (Success - 200):
{
"success": true,
"data": {
"marketId": "0x35b8bafff...",
"markPrice": "150000000000000000000",
"markPriceFormatted": "150.00",
"timestamp": 1702234567
}
}
GET /accounts/{accountId}/positions/{marketId}/funding
Get pending funding for a specific position.
Path Parameters:
| Parameter | Type | Description |
|---|---|---|
accountId | uint256 | Account ID |
marketId | bytes32 | Market identifier |
Contract Calls:
FxAccount account = FxEngine.getAccount(accountId).accountAddress;
int256 cumulativeFunding = FxEngine.getCumulativeFunding(marketId);
int256 pendingFunding = account.getPendingFunding(marketId, cumulativeFunding);
Response (Success - 200):
{
"success": true,
"data": {
"accountId": 1,
"marketId": "0x35b8bafff...",
"pendingFunding": "-50000000000000000000",
"pendingFundingFormatted": "-50.00",
"direction": "RECEIVE",
"lastSettled": 1702230000,
"lastSettledISO": "2024-12-10T14:20:00Z"
}
}
Direction Values:
| Value | Description |
|---|---|
PAY | Position owes funding (positive value) |
RECEIVE | Position receives funding (negative value) |
NONE | No pending funding |
Data Types Reference
Units
| Type | Decimals | Example | Description |
|---|---|---|---|
| USDC Amount | 6 | 1000000000 = 1000 USDC | On-chain USDC |
| WAD Amount | 18 | 1000000000000000000000 = 1000 | Internal margin |
| Price | 18 | 150000000000000000000 = 150.00 | All prices |
| Funding Rate | 18 | 100000000000000 = 0.0001 | Per hour |
| Basis Points | 0 | 100 = 1% | Ratios |
Enum Mappings
Side:
| String | Contract Value |
|---|---|
LONG | 0 |
SHORT | 1 |
Order Type:
| String | Contract Value |
|---|---|
MARKET | 0 |
LIMIT | 1 |