Diffusal

Quick Start

Get started with the Diffusal API in minutes

This guide walks you through integrating with the Diffusal API, from fetching market data to placing your first order.


Prerequisites

  • Runtime: Node.js 18+ or Bun
  • Wallet: MetaMask, WalletConnect, or any EIP-1193 compatible wallet
  • Dependencies: viem for Ethereum interactions
npm install viem
# or
bun add viem

Base URLs

EnvironmentREST APIWebSocket
Localhttp://localhost:8080ws://localhost:8080/ws
Productionhttps://api.diffusal.xyzwss://api.diffusal.xyz/ws
const API_URL =
  process.env.NODE_ENV === "production"
    ? "https://api.diffusal.xyz"
    : "http://localhost:8080";

const WS_URL =
  process.env.NODE_ENV === "production"
    ? "wss://api.diffusal.xyz/ws"
    : "ws://localhost:8080/ws";

Step 1: Verify Connection

Check that the API is reachable:

const healthCheck = await fetch(`${API_URL}/health`);
const { status, timestamp } = await healthCheck.json();

console.log(`API Status: ${status}`); // "ok"
console.log(`Server Time: ${new Date(timestamp)}`);

Step 2: Fetch Market Data (No Auth Required)

List Available Markets

// Get all active options markets
const marketsRes = await fetch(`${API_URL}/markets?limit=50`);
const { markets, total, hasMore, nextCursor } = await marketsRes.json();

console.log(`Found ${total} active markets`);

// Example market object:
// {
//   seriesId: "0x...",
//   symbol: "BTC-82000-C-1736409600",
//   pairId: "0x...",
//   pairSymbol: "BTC-USDC",
//   strike: "82000000000000000000000",  // WAD format
//   expiry: 1736409600,
//   isCall: true,
//   isSettled: false
// }

Get Order Book

const symbol = "BTC-82000-C-1736409600";
const orderbookRes = await fetch(
  `${API_URL}/markets/orderbook/${symbol}?depth=20`
);
const { bids, asks, timestamp } = await orderbookRes.json();

// Prices and sizes are in WAD format (18 decimals)
// Convert to human-readable:
const topBid = bids[0];
if (topBid) {
  const price = Number(topBid.price) / 1e18;
  const size = Number(topBid.size) / 1e18;
  console.log(`Best Bid: ${price} USDC for ${size} contracts`);
}

Get Ticker with Greeks

const tickerRes = await fetch(`${API_URL}/markets/ticker/${symbol}`);
const ticker = await tickerRes.json();

// All values in WAD format (18 decimals)
console.log(`Mark Price: ${Number(ticker.markPrice) / 1e18}`);
console.log(`Delta: ${Number(ticker.delta) / 1e18}`);
console.log(`IV: ${((Number(ticker.iv) / 1e18) * 100).toFixed(1)}%`);
console.log(`24h Volume: ${Number(ticker.volume24h) / 1e18} USDC`);

Step 3: Authenticate with SIWE

Authentication uses Sign-In with Ethereum (SIWE). Here's the complete flow:

3.1 Request Nonce

const walletAddress = "0x..."; // User's connected wallet

const nonceRes = await fetch(
  `${API_URL}/api/auth/siwe/nonce?walletAddress=${walletAddress}`
);
const { nonce } = await nonceRes.json();

3.2 Create SIWE Message

import { createSiweMessage } from "viem/siwe";

const message = createSiweMessage({
  domain: "localhost", // Use 'diffusal.xyz' in production
  address: walletAddress,
  statement: "Sign in to Diffusal",
  uri: API_URL,
  version: "1",
  chainId: 1, // Or your connected chain
  nonce: nonce,
  issuedAt: new Date(),
  expirationTime: new Date(Date.now() + 15 * 60 * 1000), // 15 minutes
});

3.3 Sign with Wallet

import { createWalletClient, custom } from "viem";
import { mainnet } from "viem/chains";

const walletClient = createWalletClient({
  chain: mainnet,
  transport: custom(window.ethereum),
});

const signature = await walletClient.signMessage({
  account: walletAddress,
  message: message,
});

3.4 Verify and Get Session

const verifyRes = await fetch(`${API_URL}/api/auth/siwe/verify`, {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({ message, signature }),
  credentials: "include", // Important: allows cookies to be set
});

const { user, session } = await verifyRes.json();
console.log(`Authenticated as: ${user.address}`);

3.5 Extract JWT for WebSocket (Non-Browser)

For server-side or mobile applications that need the JWT token directly:

// The JWT is in the Set-Cookie header
const setCookie = verifyRes.headers.get("set-cookie");
const sessionToken = setCookie?.match(/session=([^;]+)/)?.[1];

// Store this token for WebSocket authentication
console.log("JWT Token:", sessionToken);

Step 4: Access Account Data

With authentication, you can access user-specific endpoints.

Get User Profile

const meRes = await fetch(`${API_URL}/account/me`, {
  credentials: "include", // Include session cookie
});
const profile = await meRes.json();

// Values in USDC format (6 decimals)
console.log(`Total Equity: $${Number(profile.totalEquity) / 1e6}`);
console.log(`Portfolios: ${profile.portfolioCount}`);
console.log(`Positions: ${profile.positionCount}`);
console.log(`Healthy: ${profile.isHealthy}`);

Get Portfolio Details

const portfolioId = 0; // Default portfolio

const portfolioRes = await fetch(
  `${API_URL}/account/portfolios/${portfolioId}`,
  {
    credentials: "include",
  }
);
const portfolio = await portfolioRes.json();

// USDC fields (6 decimals)
console.log(`Deposit: $${Number(portfolio.deposit) / 1e6}`);
console.log(`Equity: $${Number(portfolio.equity) / 1e6}`);
console.log(`Max Withdraw: $${Number(portfolio.maxWithdraw) / 1e6}`);

// WAD fields (18 decimals)
console.log(`Initial Margin: ${Number(portfolio.initialMargin) / 1e18}`);
console.log(`Health Ratio: ${Number(portfolio.healthRatio) / 1e18}`);

Get Positions

const positionsRes = await fetch(
  `${API_URL}/account/portfolios/${portfolioId}/positions`,
  { credentials: "include" }
);
const { positions } = await positionsRes.json();

for (const pos of positions) {
  const balance = Number(pos.optionBalance) / 1e18;
  const side = balance > 0 ? "LONG" : "SHORT";
  console.log(`${pos.symbol}: ${side} ${Math.abs(balance)} contracts`);
}

Step 5: Connect WebSocket

Public Channels (No Auth)

const ws = new WebSocket(WS_URL);

ws.onopen = () => {
  console.log("Connected");

  // Subscribe to public channels
  ws.send(
    JSON.stringify({
      type: "subscribe",
      id: "sub-1",
      channels: [
        "orderbook:BTC-82000-C-1736409600",
        "trades:BTC-82000-C-1736409600",
        "ticker:BTC-82000-C-1736409600",
      ],
    })
  );
};

ws.onmessage = (event) => {
  const msg = JSON.parse(event.data);

  switch (msg.type) {
    case "connected":
      console.log("Connection acknowledged");
      break;

    case "subscribed":
      console.log("Subscribed to:", msg.channels);
      break;

    case "snapshot":
      console.log(`Initial ${msg.channel} data:`, msg.data);
      break;

    case "update":
      console.log(`${msg.channel} update:`, msg.data);
      break;

    case "heartbeat":
      ws.send(JSON.stringify({ type: "pong" }));
      break;

    case "error":
      console.error(`Error: ${msg.code} - ${msg.message}`);
      break;
  }
};

Private Channels (With Auth)

const ws = new WebSocket(WS_URL);

ws.onopen = () => {
  // Authenticate first
  ws.send(
    JSON.stringify({
      type: "auth",
      token: sessionToken, // JWT from SIWE verification
    })
  );
};

ws.onmessage = (event) => {
  const msg = JSON.parse(event.data);

  if (msg.type === "auth_success") {
    console.log("Authenticated as:", msg.address);

    // Now subscribe to private channels
    ws.send(
      JSON.stringify({
        type: "subscribe",
        id: "sub-private",
        channels: ["positions", "collateral", "orders", "fills"],
      })
    );
  }

  if (msg.type === "auth_error") {
    console.error("Auth failed:", msg.error);
  }

  // Handle position updates
  if (msg.channel === "positions") {
    console.log("Position changed:", msg.data);
  }

  // Handle fill notifications
  if (msg.channel === "fills") {
    console.log("Trade filled:", msg.data);
  }
};

Step 6: Place an Order (On-Chain)

Orders are placed directly on-chain. Here's the complete flow:

6.1 Get Contract Addresses

const contractsRes = await fetch(`${API_URL}/markets/contracts`);
const { chainId, contracts } = await contractsRes.json();

const ORDERBOOK_ADDRESS = contracts.DiffusalOptionsOrderBook;
const COLLATERAL_VAULT = contracts.DiffusalCollateralVault;

6.2 Prepare Order Parameters

// 1. Get the series you want to trade
const symbol = "BTC-82000-C-1736409600";
const marketsRes = await fetch(`${API_URL}/markets?limit=100`);
const { markets } = await marketsRes.json();
const series = markets.find((m) => m.symbol === 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. Convert your desired price to a tick
const desiredPriceWad = BigInt(1.5e18).toString(); // 1.5 USDC premium
const tickConvertRes = await fetch(
  `${API_URL}/helpers/price-to-tick?price=${desiredPriceWad}&tickDecimals=${tickDecimals}&roundUp=true`
);
const { tick } = await tickConvertRes.json();

// 4. Check margin requirements
const portfolioRes = await fetch(`${API_URL}/account/portfolios/0`, {
  credentials: "include",
});
const portfolio = await portfolioRes.json();
console.log(`Available equity: $${Number(portfolio.equity) / 1e6}`);

6.3 Submit Order Transaction

import { parseUnits } from "viem";

// Order parameters
const orderParams = {
  seriesId: series.seriesId,
  portfolioId: 0, // Default portfolio
  isBuy: true,
  tick: tick,
  size: parseUnits("1", 18), // 1 contract
  expiry: BigInt(Math.floor(Date.now() / 1000) + 3600), // 1 hour
};

// Submit transaction
const tx = await walletClient.writeContract({
  address: ORDERBOOK_ADDRESS,
  abi: orderBookAbi, // Import from contract artifacts
  functionName: "registerOrder",
  args: [orderParams],
});

console.log("Order submitted:", tx);

Common Patterns

Error Handling

async function fetchWithErrorHandling(url: string, options?: RequestInit) {
  const res = await fetch(url, options);

  if (!res.ok) {
    const error = await res.json();
    throw new Error(`${error.error}: ${error.message}`);
  }

  return res.json();
}

// Usage
try {
  const data = await fetchWithErrorHandling(`${API_URL}/markets`);
} catch (err) {
  if (err.message.includes("rate_limited")) {
    // Wait and retry
  }
}

Rate Limit Handling

// Check rate limit headers (when available)
const res = await fetch(`${API_URL}/markets`);

// Rate limits by category (requests per minute):
// - Market data: 60 (unauth) / 300 (auth)
// - Helpers: 100 (unauth) / 300 (auth)
// - Account: 120 (auth only)

if (res.status === 429) {
  const error = await res.json();
  const retryAfter = error.details?.resetAt;
  console.log(`Rate limited. Retry after: ${new Date(retryAfter)}`);
}

Converting WAD Values

// WAD = 18 decimal fixed-point (used for prices, Greeks, sizes)
function fromWad(wadString: string): number {
  return Number(wadString) / 1e18;
}

function toWad(value: number): bigint {
  return BigInt(Math.floor(value * 1e18));
}

// USDC = 6 decimal fixed-point (used for collateral, equity)
function fromUsdc(usdcString: string): number {
  return Number(usdcString) / 1e6;
}

function toUsdc(value: number): bigint {
  return BigInt(Math.floor(value * 1e6));
}

Next Steps

On this page