Diffusal

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

ContractPurposeKey Functions
CollateralVaultCollateral managementdeposit, withdraw
OrderBookLimit order tradingregisterOrder, cancelOrder
RFQRequest-for-quote tradingfillQuote
PortfolioManagerPortfolio managementcreatePortfolio, 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)

ParameterTypeDescription
portfolioIduint16Target portfolio (0 = default)
amountuint256USDC amount (6 decimals)

Prerequisites:

  1. Approve USDC spending: USDC.approve(collateralVault, amount)
  2. Portfolio must exist (or use portfolio 0 for auto-creation)

Events Emitted:

EventDescription
DepositedToPortfolio(user, portfolioId, amount)Deposit confirmed

Withdraw from Portfolio

Withdraw USDC collateral from a portfolio.

Contract: CollateralVault

Function: withdrawFromPortfolio(uint16 portfolioId, uint256 amount)

ParameterTypeDescription
portfolioIduint16Source portfolio
amountuint256USDC amount (6 decimals)

Constraints:

  • Post-withdrawal equity must be >= initial margin
  • Cannot withdraw more than maxWithdraw (see /account/portfolios/:id)

Events Emitted:

EventDescription
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 /markets to list available series and find the seriesId
  • 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/:id

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

FieldTypeDescription
seriesIdbytes32Option series ID
portfolioIduint16Portfolio for the trade
isBuyboolTrue for buy, false for sell
tickint24Price tick (from step 3)
sizeuint128Order size (WAD)
expiryuint64Order 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:

EventDescription
OrderRegistered(user, orderId, seriesId, ...)Order registered

Cancel Order

Cancel an existing limit order.

Contract: OrderBook

Function: cancelOrder(bytes32 orderId)

ParameterTypeDescription
orderIdbytes32Order identifier

Events Emitted:

EventDescription
OrderCancelled(user, orderId)Order cancelled

Increment Nonce (Bulk Cancel)

Cancel all orders below the new nonce.

Contract: OrderBook

Function: incrementNonce()

Events Emitted:

EventDescription
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):

FieldTypeDescription
mmmaddressMarket maker address
seriesIdbytes32Option series ID
mmmPortfolioIduint16MMM's portfolio ID
isMmmBuyingboolDirection from MMM perspective
tickint24Quote price tick
sizeuint128Maximum fill size
nonceuint64Quote nonce
quoteExpiryuint64Quote expiry timestamp
takeraddressSpecific taker (0x0 = anyone)
ParameterTypeDescription
quoteQuoteThe market maker's quote
signaturebytesEIP-712 signature
fillSizeuint128Amount to fill
takerPortfolioIduint16Taker's portfolio ID

Events Emitted:

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

EventDescription
PortfolioCreated(user, portfolioId)Portfolio created

Delete Portfolio

Delete an empty portfolio.

Contract: PortfolioManager

Function: deletePortfolio(uint16 portfolioId)

ParameterTypeDescription
portfolioIduint16Portfolio to delete

Constraints:

  • Portfolio must have no positions
  • Portfolio must have no collateral

Events Emitted:

EventDescription
PortfolioDeleted(user, portfolioId)Portfolio deleted

Transfer Collateral Between Portfolios

Move collateral between your portfolios.

Contract: PortfolioManager

Function: transferCollateral(uint16 fromPortfolioId, uint16 toPortfolioId, uint256 amount)

ParameterTypeDescription
fromPortfolioIduint16Source portfolio
toPortfolioIduint16Destination portfolio
amountuint256USDC amount

Constraints:

  • Source portfolio must remain healthy (equity >= MM) after transfer

Events Emitted:

EventDescription
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)

ParameterTypeDescription
fromPortfolioIduint16Source portfolio
toPortfolioIduint16Destination portfolio
seriesIdbytes32Option series
amountint128Position amount (can be partial)

Constraints:

  • Both portfolios must remain healthy after transfer
  • Destination must have room (< 16 positions)

Events Emitted:

EventDescription
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 RangeTick DecimalsMin TickExample
0.010.01 - 0.10120.000000000001Micro-cap tokens
11 - 1080.00000001Small tokens
100100 - 1,00060.000001Mid-cap
10,00010,000 - 100,00040.0001BTC 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

  1. Check margin: GET /account/portfolios/:id (ensure enough equity)
  2. Get tick decimals: GET /helpers/tick-decimals/:pairId
  3. Convert price to tick: GET /helpers/price-to-tick?price=...&tickDecimals=...
  4. Register order: OrderBook.registerOrder(...) or RFQ.fillQuote(...)

Closing a Position

  1. Get current positions: GET /account/portfolios/:id/positions
  2. Register opposite order or fill RFQ quote
  3. Positions automatically net (long + short = 0)

Depositing Collateral

  1. Check USDC balance
  2. Approve USDC: USDC.approve(collateralVault, amount)
  3. Deposit: CollateralVault.depositToPortfolio(0, amount)

Withdrawing Collateral

  1. Check max withdraw: GET /account/portfolios/:id
  2. Withdraw: CollateralVault.withdrawFromPortfolio(portfolioId, amount)

Gas Optimization

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

ErrorCauseSolution
InsufficientMarginPost-trade equity < MMDeposit more collateral
InsufficientBalanceWithdraw > maxWithdrawReduce withdrawal amount
OrderExpiredOrder past expiryCreate new order
InvalidTickTick out of rangeCheck tick decimals
PortfolioFull> 16 positionsCreate new portfolio
UnauthorizedCallerWrong senderCheck msg.sender

On this page