Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.polynode.dev/llms.txt

Use this file to discover all available pages before exploring further.

Dome was acquired by Polymarket in February 2026 and is shutting down. polynode provides a drop-in replacement for both Dome’s WebSocket feed (same format, 3-5 seconds faster) and Dome’s trading SDK (same CLOB signing, but your credentials stay local instead of on Dome’s server).

What you get

  • Identical event format to Dome — same 16 fields, same field names, same types
  • Pre-chain delivery: events arrive 3-5 seconds before on-chain confirmation (and before Dome)
  • All existing filters work: condition_ids, wallets, tokens, slugs, side, min_size
  • One subscription type change: "dome" instead of Dome’s "orders"

Quick start

1. Get an API key

Sign up at polynode.dev to get your API key.

2. Connect

Replace your Dome WebSocket URL with polynode’s:
- const ws = new WebSocket(`wss://ws.domeapi.io/${DOME_KEY}`);
+ const ws = new WebSocket(`wss://ws.polynode.dev/ws?key=${POLYNODE_KEY}`);

3. Subscribe

The subscribe message changes slightly. Dome used platform and version fields that polynode doesn’t need:
- ws.send(JSON.stringify({
-   action: "subscribe",
-   platform: "polymarket",
-   version: 1,
-   type: "orders",
-   filters: { condition_ids: ["0xabc..."] }
- }));

+ ws.send(JSON.stringify({
+   action: "subscribe",
+   type: "dome",
+   filters: { condition_ids: ["0xabc..."] }
+ }));

4. Process events

No changes needed. The event format is identical:
ws.on("message", (data) => {
  const msg = JSON.parse(data);

  if (msg.type === "event") {
    const fill = msg.data;
    // These fields work exactly like Dome:
    console.log(fill.order_hash);  // EIP-712 order hash
    console.log(fill.user);        // order placer address
    console.log(fill.taker);       // counterparty address
    console.log(fill.tx_hash);     // on-chain transaction hash
    console.log(fill.side);        // "BUY" or "SELL"
    console.log(fill.price);       // fill price
    console.log(fill.shares);      // raw token amount (6 decimals)
  }
});

Event format

Every event is a flat object with 16 fields, matching Dome’s format exactly.

Wrapper

{
  "type": "event",
  "data": { ... }
}

Data fields

FieldTypeDescription
order_hashstringEIP-712 order hash. Persistent across partial fills of the same limit order. Use this to track order fill status.
userstringAddress of the order placer. The order_hash belongs to this address.
takerstringCounterparty address. On the taker’s own fill event, this is the exchange contract address (see Order perspective below).
tx_hashstringOn-chain transaction hash.
sidestring"BUY" or "SELL"
pricenumberFill price as a decimal (e.g. 0.65).
sharesnumberRaw token amount with 6 decimal precision (e.g. 25000000 = 25 tokens).
shares_normalizednumberHuman-readable token amount (e.g. 25.0).
token_idstringPolymarket conditional token ID.
token_labelstringOutcome name (e.g. "Yes", "No", "Up", "Down").
condition_idstringPolymarket condition ID for the market.
market_slugstringURL slug for the market.
titlestringHuman-readable market title.
timestampnumberUNIX timestamp in seconds when the event was detected.
block_numbernullAlways null for pre-chain events. polynode detects settlements from the mempool before they confirm on-chain.
log_indexnullAlways null for pre-chain events.

Example event

{
  "type": "event",
  "data": {
    "order_hash": "0x717a21e38d22024cac8e9f0a041aded587f8ae5d1ca3407a0ee75467be8dd20e",
    "user": "0x2b19beaaae9eeaa1898cf2910be04fda27eb8521",
    "taker": "0x7d4ed386d4c32a012739cfff96d0a8426eaeb700",
    "tx_hash": "0x1be7333ad265ea04146f4adb7e5f3f8881f5c551e2a638a2de91518fc74a3f3f",
    "side": "BUY",
    "price": 0.66,
    "shares": 14700000,
    "shares_normalized": 14.7,
    "token_id": "70473897434054355274348699520137654868233070870789130122447984108273533379399",
    "token_label": "Up",
    "condition_id": "0x9b4921efc74c8a208a31917c19a71782c4376d2647396aab5b6332125631f2b7",
    "market_slug": "btc-updown-5m-1774723200",
    "title": "Bitcoin Up or Down - March 28, 2:40PM-2:45PM ET",
    "timestamp": 1774723227,
    "block_number": null,
    "log_index": null
  }
}

Order perspective

Each on-chain settlement produces multiple fill events, one per OrderFilled log. This is identical to how Dome works. Maker fill events: user is the maker’s address, taker is the actual taker’s wallet address, order_hash is the maker’s EIP-712 order hash. Taker fill events: user is the taker’s address, taker is the exchange contract address (not a real wallet). The order_hash is the taker’s EIP-712 order hash. The exchange contract address depends on the market type:
Market typeExchange contract
Standard (neg_risk = false)0x4bfb41d5b3570defd03c39a9a4d8de6bd8b8982e
Neg-risk (neg_risk = true)0xc5d563a36ae78145c45a50134d48a1215220f80a
This happens because the Polymarket CTF Exchange settles the taker’s order against itself as an intermediary. The on-chain OrderFilled event reflects this, and both Dome and polynode pass it through as-is. To find your order’s fills: Check if event.user === yourWallet. When it matches, event.order_hash is your order’s hash. If you’re using polynode’s standard trade or settlement event subscriptions instead of the Dome-format feed, the same logic applies but the field is called maker instead of user. See Tracking a specific wallet’s trades on the Trade Event reference or the equivalent section on the Settlement Event page. If you’re using polynode’s standard trade event subscription instead of the Dome-format feed, the same logic applies but the field is called maker instead of user. See Tracking a specific wallet’s trades on the Trade Event reference for the equivalent explanation.

Copy trading example

If you’re building a copy trading system that tracks orders by hash:
const WebSocket = require("ws");

const ws = new WebSocket(
  "wss://ws.polynode.dev/ws?key=YOUR_KEY"
);

// Track a specific wallet
const TRACKED_WALLET = "0x1234...";
const orderBook = new Map(); // order_hash -> order state

ws.on("open", () => {
  ws.send(JSON.stringify({
    action: "subscribe",
    type: "dome",
    filters: { wallets: [TRACKED_WALLET] }
  }));
});

ws.on("message", (raw) => {
  const msg = JSON.parse(raw);
  if (msg.type !== "event") return;

  const fill = msg.data;

  // This fill involves our tracked wallet.
  // Check if the tracked wallet is the order placer:
  if (fill.user === TRACKED_WALLET.toLowerCase()) {
    // This is the tracked wallet's own order being filled.
    // fill.order_hash is their order's hash.
    if (orderBook.has(fill.order_hash)) {
      // Update existing order
      console.log("Order filled:", fill.order_hash, fill.shares_normalized, "shares at", fill.price);
    } else {
      // New order detected
      orderBook.set(fill.order_hash, {
        side: fill.side,
        price: fill.price,
        totalFilled: fill.shares_normalized,
        market: fill.title,
      });
      console.log("New order:", fill.order_hash, fill.side, fill.price);
    }
  }
});

Full migration example

Complete before/after showing the Dome to polynode migration:
const WebSocket = require("ws");

const DOME_KEY = "your_dome_key";
const ws = new WebSocket(`wss://ws.domeapi.io/${DOME_KEY}`);

ws.on("open", () => {
  ws.send(JSON.stringify({
    action: "subscribe",
    platform: "polymarket",
    version: 1,
    type: "orders",
    filters: {
      condition_ids: ["0xabc123..."]
    }
  }));
});

ws.on("message", (raw) => {
  const msg = JSON.parse(raw);

  if (msg.type === "ack") {
    console.log("Subscribed");
    return;
  }

  if (msg.type === "event") {
    const fill = msg.data;
    console.log(fill.order_hash, fill.user, fill.side, fill.price);
  }
});

What changes

Domepolynode
WebSocket URLwss://ws.domeapi.io/{key}wss://ws.polynode.dev/ws?key={key}
Subscribe type"orders""dome"
Subscribe fieldsplatform, version requiredNot needed
Ack message{"type": "ack"}{"type": "subscribed"}
Event format16 fieldsSame 16 fields
block_numberBlock number (integer)null (pre-chain)
log_indexLog index (integer)null (pre-chain)
Delivery speedOn-chain confirmation (~2s block time)Pre-chain mempool detection (3-5s earlier)

What stays the same

  • All 16 data fields: order_hash, user, taker, tx_hash, side, price, shares, shares_normalized, token_id, token_label, condition_id, market_slug, title, timestamp, block_number, log_index
  • One event per order fill (not bundled)
  • order_hash belongs to user
  • Taker fills show exchange contract as taker
  • All filter types: condition_ids, wallets, tokens, slugs, side, min_size

Filters

The dome subscription supports all standard polynode filters:
{
  "action": "subscribe",
  "type": "dome",
  "filters": {
    "condition_ids": ["0xabc..."],
    "wallets": ["0x1234..."],
    "tokens": ["21742633..."],
    "slugs": ["bitcoin-100k-2026"],
    "side": "BUY",
    "min_size": 100,
    "max_size": 10000
  }
}

Differences from Dome

block_number and log_index are always null. polynode detects settlements from the Polygon mempool before they are included in a block. This is why events arrive 3-5 seconds before Dome. The tradeoff is that block-level fields aren’t available at detection time. Minor floating-point precision differences on price and shares. polynode computes fill amounts from transaction calldata, while Dome reads from on-chain OrderFilled event logs. In rare cases (~1% of fills), this produces sub-penny price differences (e.g. 0.76 vs 0.7599999821) or shares off by 1 unit in millions. All other fields are byte-identical. No subscription_id in event wrapper. Dome includes a subscription_id field in every event message. polynode does not include this field, consistent with all other polynode subscription types. If your code reads msg.subscription_id, it will be undefined but this should not affect event processing.

Order Placement

If you used Dome’s trading SDK to place orders on Polymarket, the polynode SDK is a drop-in replacement with two key improvements: your credentials stay on your machine (not on a third-party server), and wallet type is auto-detected so you don’t need to know if you’re EOA, Proxy, Safe, or deposit wallet.
npm install polynode-sdk@0.5.5 viem better-sqlite3 @polymarket/clob-client @polymarket/builder-relayer-client @polymarket/builder-signing-sdk
Requires Node.js 18 or later.

Quick comparison

Domepolynode
Order signing@polymarket/clob-clientSame @polymarket/clob-client
Credential storageDome’s serverLocal SQLite (your machine)
Credential exportNot possibleexportWallet() / exportAll()
Wallet typeYou must specifyAuto-detected from on-chain state
Gasless onboardingVia relayerSame relayer, one call (ensureReady())
Works without their serverNoYes (fallbackDirect: true)
Builder attributionDome’s builder keypolynode’s builder key (via relay)
Order formatorderToJson()Same orderToJson()
Balance/approval checksManualBuilt-in methods
Local order historyNoneSQLite-backed

Migration

import { DomeTrader } from '@dome/sdk';

const trader = new DomeTrader({
  apiKey: 'dome_...',
  privateKey: '0x...',
  walletType: 'safe', // you had to know this
});

await trader.connect();

const result = await trader.placeOrder({
  tokenId: '...',
  side: 'BUY',
  price: 0.55,
  size: 100,
});

await trader.cancelOrder(result.orderId);

Importing Dome credentials

If you already have CLOB credentials from Dome, import them directly without re-deriving:
const trader = new PolyNodeTrader({ polynodeKey: 'pn_live_...' });

// Import your existing credentials
trader.linkCredentials({
  wallet: '0xYOUR_EOA',
  apiKey: 'your-dome-api-key',
  apiSecret: 'your-dome-api-secret',
  apiPassphrase: 'your-dome-passphrase',
  signatureType: 2,  // Safe, or whatever Dome was using
  funderAddress: '0xYOUR_SAFE',
});

// Link your signer for order signing
await trader.linkWallet('0xYOUR_PRIVATE_KEY');

// Done. Same credentials, same orders, different relay.

Exporting from Polymarket directly

If you exported your private key from Polymarket’s settings, you don’t need to know anything else:
const trader = new PolyNodeTrader({ polynodeKey: 'pn_live_...' });

// Auto-detects your wallet type (Safe, Proxy, or deposit wallet),
// derives CLOB credentials. You don't need to know your wallet type.
const status = await trader.ensureReady('0xEXPORTED_KEY_FROM_POLYMARKET');

console.log(`Detected: type ${status.signatureType}`);
console.log(`USDC.e lives at: ${status.funderAddress}`);
See the full Trading documentation for all methods and configuration options.