Skip to main content

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:

CodeDescription
ACCOUNT_EXISTSUser 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:

ParameterTypeDescription
accountIduint256Account 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:

CodeDescription
INVALID_AMOUNTAmount is 0
INSUFFICIENT_BALANCEUser doesn't have enough USDC
INSUFFICIENT_ALLOWANCEUSDC 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:

ParameterTypeDescription
accountIduint256Account 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:

CodeDescription
INVALID_AMOUNTAmount is 0
INSUFFICIENT_MARGINNot enough available margin
UNAUTHORIZEDCaller 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:

ParameterTypeDescription
accountIduint256Account 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:

ValueDescription
LONG (0)Profit when price increases
SHORT (1)Profit when price decreases

Order Type Enum:

ValueDescription
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:

CodeDescription
MARKET_NOT_FOUNDInvalid marketId
MARKET_NOT_ACTIVEMarket is paused
EXCEEDS_MAX_LEVERAGELeverage > market max or 0
INSUFFICIENT_MARGINNot enough available margin
UNAUTHORIZEDCaller 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:

ParameterTypeDescription
accountIduint256Account ID
clientKeybytes32Order client key

Query Parameters:

ParameterTypeDescription
marketIdbytes32Market 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:

CodeDescription
ORDER_NOT_FOUNDOrder doesn't exist or already filled/cancelled
MARKET_NOT_FOUNDInvalid marketId
UNAUTHORIZEDCaller 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:

ParameterTypeDescription
accountIduint256Account ID
marketIdbytes32Market 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:

CodeDescription
NO_POSITIONNo open position in this market
MARKET_NOT_FOUNDInvalid marketId
UNAUTHORIZEDCaller is not account owner

Read Endpoints (Queries)

These endpoints query blockchain state. No transactions required.


GET /accounts/{accountId}

Get account information.

Path Parameters:

ParameterTypeDescription
accountIduint256Account 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:

CodeDescription
ACCOUNT_NOT_FOUNDAccount doesn't exist

GET /accounts/by-address/{userAddress}

Get account by user wallet address.

Path Parameters:

ParameterTypeDescription
userAddressaddressUser'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:

ParameterTypeDescription
accountIduint256Account ID
marketIdbytes32Market 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:

ParameterTypeDescription
accountIduint256Account ID

Query Parameters:

ParameterTypeDescription
marketIdbytes32Filter by market (optional)
statusstringFilter: "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:

ParameterTypeDescription
marketIdbytes32Market 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:

ParameterTypeDescription
marketIdbytes32Market 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:

ValueDescription
LONGS_PAYPositive rate, longs pay shorts
SHORTS_PAYNegative rate, shorts pay longs
NEUTRALZero rate

GET /markets/{marketId}/orderbook

Get order book for a market.

Path Parameters:

ParameterTypeDescription
marketIdbytes32Market identifier

Query Parameters:

ParameterTypeDefaultDescription
depthint10Number 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:

ParameterTypeDescription
marketIdbytes32Market 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:

ParameterTypeDescription
accountIduint256Account ID
marketIdbytes32Market 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:

ValueDescription
PAYPosition owes funding (positive value)
RECEIVEPosition receives funding (negative value)
NONENo pending funding

Data Types Reference

Units

TypeDecimalsExampleDescription
USDC Amount61000000000 = 1000 USDCOn-chain USDC
WAD Amount181000000000000000000000 = 1000Internal margin
Price18150000000000000000000 = 150.00All prices
Funding Rate18100000000000000 = 0.0001Per hour
Basis Points0100 = 1%Ratios

Enum Mappings

Side:

StringContract Value
LONG0
SHORT1

Order Type:

StringContract Value
MARKET0
LIMIT1