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:
viemfor Ethereum interactions
npm install viem
# or
bun add viemBase URLs
| Environment | REST API | WebSocket |
|---|---|---|
| Local | http://localhost:8080 | ws://localhost:8080/ws |
| Production | https://api.diffusal.xyz | wss://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
- API Reference - Complete endpoint documentation
- WebSocket API - Real-time data streams
- On-Chain Guide - Contract interaction details
- Authentication - SIWE flow details
- Reference - Symbol formats and data types