On-Chain Guide
Contract interaction patterns for frontend integration
All write operations in Diffusal go directly to smart contracts. This guide covers the primary user interactions.
Contract Overview
| Contract | Purpose | Key Functions |
|---|---|---|
| CollateralVault | Collateral management | deposit, withdraw |
| OrderBook | Limit order trading | registerOrder, cancelOrder |
| RFQ | Request-for-quote trading | fillQuote |
| PortfolioManager | Portfolio management | createPortfolio, transferPosition |
Use the /contracts API endpoint to get current addresses and ABIs.
Collateral Operations
Deposit to Portfolio
Deposit USDC collateral to a portfolio.
Contract: CollateralVault
Function: depositToPortfolio(uint16 portfolioId, uint256 amount)
| Parameter | Type | Description |
|---|---|---|
portfolioId | uint16 | Target portfolio (0 = default) |
amount | uint256 | USDC amount (6 decimals) |
Prerequisites:
- Approve USDC spending:
USDC.approve(collateralVault, amount) - Portfolio must exist (or use portfolio 0 for auto-creation)
Events Emitted:
| Event | Description |
|---|---|
DepositedToPortfolio(user, portfolioId, amount) | Deposit confirmed |
Withdraw from Portfolio
Withdraw USDC collateral from a portfolio.
Contract: CollateralVault
Function: withdrawFromPortfolio(uint16 portfolioId, uint256 amount)
| Parameter | Type | Description |
|---|---|---|
portfolioId | uint16 | Source portfolio |
amount | uint256 | USDC amount (6 decimals) |
Constraints:
- Post-withdrawal equity must be >= initial margin
- Cannot withdraw more than
maxWithdraw(see/account/portfolios/:id)
Events Emitted:
| Event | Description |
|---|---|
WithdrawnFromPortfolio(user, portfolioId, amount) | Withdrawal confirmed |
Order Placement Preparation
Before placing an order, prepare the required parameters using the API:
1. Identify the Series
Get the series identifier for the option you want to trade:
- Option A: Use
GET /marketsto list available series and find theseriesId - Option B: Compute series ID via
GET /helpers/series-id?pairId=...&strike=...&expiry=...&isCall=...
2. Get Tick Decimals
Fetch current tick decimals for the pair:
GET /helpers/tick-decimals/:pairId
Response: { "tickDecimals": 4, "spotPrice": "..." }Tick decimals determine the price precision for the pair.
3. Convert Price to Tick
Convert your desired price to a tick value:
GET /helpers/price-to-tick?price=...&tickDecimals=...&roundUp=...
Response: { "tick": 50000 }Use roundUp=true for buy orders, false for sell orders.
4. Verify Margin Requirements
Check that your portfolio has sufficient collateral:
GET /account/portfolios/:idEnsure equity >= initialMargin after the proposed trade.
5. Submit Transaction
Use the computed parameters to call the OrderBook contract.
Complete Trade Flow
This diagram shows the end-to-end flow for placing a limit order:
┌─────────────────────────────────────────────────────────────────────────────┐
│ CLIENT / FRONTEND │
└─────────────────────────────────────────────────────────────────────────────┘
│
┌───────────────────────────────┼───────────────────────────────┐
│ │ │
▼ ▼ ▼
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ 1. GET /markets │ │ 2. GET /helpers │ │ 3. GET /account │
│ Find series │ │ /tick-decimals│ │ /portfolios │
│ to trade │ │ /:pairId │ │ /:id │
│ │ │ │ │ Check margin │
│ Response: │ │ Response: │ │ Response: │
│ • seriesId │ │ • tickDecimals │ │ • equity │
│ • pairId │ │ • spotPrice │ │ • initialMargin │
└────────┬────────┘ └────────┬────────┘ └────────┬────────┘
│ │ │
└───────────────────────┼───────────────────────┘
│
▼
┌─────────────────────────────┐
│ 4. GET /helpers/price-to-tick│
│ Convert desired price │
│ │
│ Query: │
│ • price (WAD format) │
│ • tickDecimals │
│ • roundUp (true for buys) │
│ │
│ Response: { tick: 50000 } │
└──────────────┬──────────────┘
│
▼
┌─────────────────────────────┐
│ 5. Build & Submit Tx │
│ OrderBook.registerOrder │
│ │
│ Params: │
│ • seriesId (from step 1) │
│ • portfolioId │
│ • isBuy (true/false) │
│ • tick (from step 4) │
│ • size (WAD) │
│ • expiry (timestamp) │
└──────────────┬──────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────────────┐
│ BLOCKCHAIN │
│ │
│ OrderBook.registerOrder() → emits OrderRegistered event │
│ │
└──────────────────────────────────┬──────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────────────┐
│ INDEXER │
│ │
│ Indexes OrderRegistered event → Updates order book state │
│ │
└──────────────────────────────────┬──────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────────────┐
│ API SERVER │
│ │
│ Broadcasts via WebSocket: │
│ • orderbook:{symbol} → New level appears in book │
│ • orders (private) → Order status update for user │
│ │
└─────────────────────────────────────────────────────────────────────────────┘Trade Flow Code Example
import {
createWalletClient,
http,
parseUnits,
formatUnits,
custom,
} from "viem";
const API_URL = "https://api.diffusal.xyz";
// Assume walletClient is configured with user's wallet
async function placeOrder(
symbol: string,
desiredPrice: number, // e.g., 1.5 USDC
size: number, // e.g., 1 contract
isBuy: boolean,
portfolioId = 0
) {
// 1. Find the series
const marketsRes = await fetch(`${API_URL}/markets?limit=100`);
const { markets } = await marketsRes.json();
const series = markets.find((m: any) => m.symbol === symbol);
if (!series) throw new Error(`Series not found: ${symbol}`);
// 2. Get tick decimals for the pair
const tickRes = await fetch(
`${API_URL}/helpers/tick-decimals/${series.pairId}`
);
const { tickDecimals } = await tickRes.json();
// 3. Check portfolio margin
const portfolioRes = await fetch(
`${API_URL}/account/portfolios/${portfolioId}`,
{
credentials: "include",
}
);
const portfolio = await portfolioRes.json();
const equityUsdc = Number(portfolio.equity) / 1e6;
console.log(`Portfolio equity: $${equityUsdc.toFixed(2)}`);
// 4. Convert price to tick
const priceWad = parseUnits(desiredPrice.toString(), 18);
const tickConvertRes = await fetch(
`${API_URL}/helpers/price-to-tick?price=${priceWad}&tickDecimals=${tickDecimals}&roundUp=${isBuy}`
);
const { tick } = await tickConvertRes.json();
// 5. Get contract address
const contractsRes = await fetch(`${API_URL}/markets/contracts`);
const { contracts } = await contractsRes.json();
// 6. Build and submit transaction
const orderParams = {
seriesId: series.seriesId as `0x${string}`,
portfolioId: portfolioId,
isBuy: isBuy,
tick: tick,
size: parseUnits(size.toString(), 18),
expiry: BigInt(Math.floor(Date.now() / 1000) + 3600), // 1 hour
};
const tx = await walletClient.writeContract({
address: contracts.DiffusalOptionsOrderBook as `0x${string}`,
abi: orderBookAbi, // Import from contract artifacts
functionName: "registerOrder",
args: [orderParams],
});
console.log("Order submitted:", tx);
return tx;
}
// Usage
await placeOrder("BTC-82000-C-1736409600", 1.5, 1, true);Order Book Operations
Register Limit Order
Register a new limit order on the order book.
Prerequisites: Complete Order Placement Preparation steps.
Contract: OrderBook
Function: registerOrder(OrderParams calldata params)
OrderParams Structure:
| Field | Type | Description |
|---|---|---|
seriesId | bytes32 | Option series ID |
portfolioId | uint16 | Portfolio for the trade |
isBuy | bool | True for buy, false for sell |
tick | int24 | Price tick (from step 3) |
size | uint128 | Order size (WAD) |
expiry | uint64 | Order expiry timestamp |
Computing Tick:
Use the /helpers/price-to-tick API endpoint (step 3 above):
GET /helpers/price-to-tick?price=5000000000000000000&tickDecimals=4
Response: { "tick": 500 }Events Emitted:
| Event | Description |
|---|---|
OrderRegistered(user, orderId, seriesId, ...) | Order registered |
Cancel Order
Cancel an existing limit order.
Contract: OrderBook
Function: cancelOrder(bytes32 orderId)
| Parameter | Type | Description |
|---|---|---|
orderId | bytes32 | Order identifier |
Events Emitted:
| Event | Description |
|---|---|
OrderCancelled(user, orderId) | Order cancelled |
Increment Nonce (Bulk Cancel)
Cancel all orders below the new nonce.
Contract: OrderBook
Function: incrementNonce()
Events Emitted:
| Event | Description |
|---|---|
NonceIncremented(user, newNonce) | All old orders invalidated |
RFQ Operations
Fill RFQ Quote
Fill a quote received from a market maker.
Contract: RFQ
Function: fillQuote(Quote calldata quote, bytes calldata signature, uint128 fillSize, uint16 takerPortfolioId)
Quote Structure (from market maker):
| Field | Type | Description |
|---|---|---|
mmm | address | Market maker address |
seriesId | bytes32 | Option series ID |
mmmPortfolioId | uint16 | MMM's portfolio ID |
isMmmBuying | bool | Direction from MMM perspective |
tick | int24 | Quote price tick |
size | uint128 | Maximum fill size |
nonce | uint64 | Quote nonce |
quoteExpiry | uint64 | Quote expiry timestamp |
taker | address | Specific taker (0x0 = anyone) |
| Parameter | Type | Description |
|---|---|---|
quote | Quote | The market maker's quote |
signature | bytes | EIP-712 signature |
fillSize | uint128 | Amount to fill |
takerPortfolioId | uint16 | Taker's portfolio ID |
Events Emitted:
| Event | Description |
|---|---|
QuoteFilled(mmm, taker, seriesId, price, size, ...) | Quote filled |
Portfolio Operations
Create Portfolio
Create a new portfolio for isolated margin.
Contract: PortfolioManager
Function: createPortfolio()
Returns: uint16 portfolioId
Notes:
- Portfolio 0 is auto-created on first deposit
- Maximum 256 portfolios per user
- Each portfolio can hold up to 16 positions
Events Emitted:
| Event | Description |
|---|---|
PortfolioCreated(user, portfolioId) | Portfolio created |
Delete Portfolio
Delete an empty portfolio.
Contract: PortfolioManager
Function: deletePortfolio(uint16 portfolioId)
| Parameter | Type | Description |
|---|---|---|
portfolioId | uint16 | Portfolio to delete |
Constraints:
- Portfolio must have no positions
- Portfolio must have no collateral
Events Emitted:
| Event | Description |
|---|---|
PortfolioDeleted(user, portfolioId) | Portfolio deleted |
Transfer Collateral Between Portfolios
Move collateral between your portfolios.
Contract: PortfolioManager
Function: transferCollateral(uint16 fromPortfolioId, uint16 toPortfolioId, uint256 amount)
| Parameter | Type | Description |
|---|---|---|
fromPortfolioId | uint16 | Source portfolio |
toPortfolioId | uint16 | Destination portfolio |
amount | uint256 | USDC amount |
Constraints:
- Source portfolio must remain healthy (equity >= MM) after transfer
Events Emitted:
| Event | Description |
|---|---|
CollateralTransferred(user, from, to, amount) | Transfer complete |
Transfer Position Between Portfolios
Move a position between your portfolios.
Contract: PortfolioManager
Function: transferPositionBetweenPortfolios(uint16 fromPortfolioId, uint16 toPortfolioId, bytes32 seriesId, int128 amount)
| Parameter | Type | Description |
|---|---|---|
fromPortfolioId | uint16 | Source portfolio |
toPortfolioId | uint16 | Destination portfolio |
seriesId | bytes32 | Option series |
amount | int128 | Position amount (can be partial) |
Constraints:
- Both portfolios must remain healthy after transfer
- Destination must have room (< 16 positions)
Events Emitted:
| Event | Description |
|---|---|
PositionTransferred(user, from, to, seriesId, amount) | Transfer complete |
Series ID Computation
Series IDs are deterministically computed:
seriesId = keccak256(abi.encodePacked(pairId, strike, expiry, isCall))Use the /helpers/series-id API endpoint:
GET /helpers/series-id?pairId=0x...&strike=100000000000000000000000&expiry=1735257600&isCall=true
Response: { "seriesId": "0x..." }Tick System
Prices are represented as ticks for gas efficiency.
Tick Decimals
Tick decimals vary by spot price magnitude (2-12 decimals):
| Spot Price Range | Tick Decimals | Min Tick | Example |
|---|---|---|---|
| 0.10 | 12 | 0.000000000001 | Micro-cap tokens |
| 10 | 8 | 0.00000001 | Small tokens |
| 1,000 | 6 | 0.000001 | Mid-cap |
| 100,000 | 4 | 0.0001 | BTC range |
Price ↔ Tick Conversion
Price to Tick:
tick = floor(price / 10^(18 - tickDecimals))Tick to Price:
price = tick * 10^(18 - tickDecimals)Use /helpers/price-to-tick and /helpers/tick-to-price for conversions.
Transaction Patterns
Opening a Long Position
- Check margin:
GET /account/portfolios/:id(ensure enough equity) - Get tick decimals:
GET /helpers/tick-decimals/:pairId - Convert price to tick:
GET /helpers/price-to-tick?price=...&tickDecimals=... - Register order:
OrderBook.registerOrder(...)orRFQ.fillQuote(...)
Closing a Position
- Get current positions:
GET /account/portfolios/:id/positions - Register opposite order or fill RFQ quote
- Positions automatically net (long + short = 0)
Depositing Collateral
- Check USDC balance
- Approve USDC:
USDC.approve(collateralVault, amount) - Deposit:
CollateralVault.depositToPortfolio(0, amount)
Withdrawing Collateral
- Check max withdraw:
GET /account/portfolios/:id - Withdraw:
CollateralVault.withdrawFromPortfolio(portfolioId, amount)
Gas Optimization
| Operation | Approximate Gas |
|---|---|
| Deposit (first time) | ~150,000 |
| Deposit (existing portfolio) | ~80,000 |
| Withdraw | ~70,000 |
| Register order | ~100,000 |
| Cancel order | ~50,000 |
| Fill RFQ quote | ~200,000 |
| Create portfolio | ~50,000 |
Error Handling
Common revert reasons:
| Error | Cause | Solution |
|---|---|---|
InsufficientMargin | Post-trade equity < MM | Deposit more collateral |
InsufficientBalance | Withdraw > maxWithdraw | Reduce withdrawal amount |
OrderExpired | Order past expiry | Create new order |
InvalidTick | Tick out of range | Check tick decimals |
PortfolioFull | > 16 positions | Create new portfolio |
UnauthorizedCaller | Wrong sender | Check msg.sender |
Related
- API Reference - Helper endpoints
- Margin System - Margin calculations
- Order Book - Order book mechanics
- Contracts - Full contract documentation