> For the complete documentation index, see [llms.txt](https://sm-fund.gitbook.io/sm-fund-docs/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://sm-fund.gitbook.io/sm-fund-docs/examples/nodejs.md).

# Node.js

This example uses Node.js 22 or later and [`ws` 8](https://github.com/websockets/ws). It requests a fresh Ticket, receives three messages, and closes. It does not automatically reconnect.

## Install and configure

In a new local directory, run:

```bash
npm init -y
npm install ws@8
```

Save your key in a local `.env` file and exclude that file from Git:

```dotenv
SMFUND_API_KEY=YOUR_API_KEY
SMFUND_FEED=polymarket
```

Set `SMFUND_FEED=raw` to select raw pending notifications. This example opens a connection for your account, replacing any other active client.

## Receive messages

Save as `smfund.mjs`:

```js
import WebSocket from "ws";

const apiKey = process.env.SMFUND_API_KEY;
const requestedFeed = process.env.SMFUND_FEED || "polymarket";
if (!apiKey) throw new Error("Set SMFUND_API_KEY first.");
if (!["polymarket", "raw"].includes(requestedFeed)) {
  throw new Error("SMFUND_FEED must be polymarket or raw.");
}

async function main() {
  const response = await fetch("https://sm.fund/v1/wss/tickets", {
    method: "POST",
    headers: { "X-API-Key": apiKey, "Content-Type": "application/json" },
    body: JSON.stringify({ feed: requestedFeed }),
    signal: AbortSignal.timeout(15_000),
  });
  if (!response.ok) {
    const wait = response.headers.get("Retry-After");
    console.error(`Ticket request failed: HTTP ${response.status}`);
    if (wait) console.error(`Retry after ${wait} seconds.`);
    process.exitCode = 1;
    return;
  }

  const { ticket, wss_url, feed, subprotocol } = await response.json();
  if (!["polymarket", "raw"].includes(feed) || feed !== requestedFeed) {
    throw new Error("Ticket response selected an unexpected feed.");
  }
  const socket = new WebSocket(wss_url, subprotocol, {
    headers: { Authorization: `Bearer ${ticket}` },
    handshakeTimeout: 15_000,
  });
  let received = 0;
  const timer = setTimeout(() => {
    console.error("No three-message sample within 60 seconds.");
    process.exitCode = 1;
    socket.terminate();
  }, 60_000);

  socket.on("open", () => console.log(`Connected: ${feed}`));
  socket.on("message", (data) => {
    if (received >= 3) return;
    received += 1;
    const text = data.toString("utf8");
    if (feed === "raw") {
      // 原始流按收到的文本处理；本示例只打印大小。
      console.log(`Raw message ${received}: ${Buffer.byteLength(text)} bytes`);
    } else {
      const batch = JSON.parse(text);
      console.log(batch.tx_hash, `${batch.fills.length} fills`);
    }
    if (received === 3) socket.close(1000, "sample complete");
  });
  socket.on("error", () => {
    console.error("WebSocket connection failed; check the handshake and network.");
    process.exitCode = 1;
  });
  socket.on("close", (code) => {
    clearTimeout(timer);
    console.log(`Closed: ${code}`);
    if (received < 3) process.exitCode = 1;
    if (code === 4001) console.error("Replaced by another client; stop reconnecting.");
    if (code === 4003) console.error("Access changed; check access and request a fresh Ticket.");
  });
}

main().catch(() => {
  console.error("Ticket request failed; check your network and configuration.");
  process.exitCode = 1;
});
```

Run:

```bash
node --env-file=.env smfund.mjs
```

Expected output starts with `Connected: polymarket`, followed by transaction hashes and fill counts, then `Closed: 1000`. Raw mode prints message byte lengths without parsing or reserializing the notifications. The timeout indicates an incomplete sample, not necessarily a service outage.

`ws` responds to protocol ping frames automatically. Do not send JSON-RPC subscriptions or application heartbeat messages. Use the [connection lifecycle guide](/sm-fund-docs/guides/connection-lifecycle.md) when adapting this sample to a continuous consumer.
