npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@jaxonchenjc/fomo-sdk

v0.1.0

Published

Independent, read-only Fomo SDK with native transport and explicit session management

Readme

Fomo TypeScript SDK

Independent, unofficial, read-only Fomo SDK. Native Node.js HTTP via Impit, explicit session management, exact decimal text and experimental streaming.

Pre-1.0 SDK: Privy refresh and WebSocket APIs remain experimental. No browser, CLI process, credential discovery, Keychain access or network request starts merely from importing/constructing it.

Install

Node.js 22+; ESM only.

npm install @jaxonchenjc/fomo-sdk

For a tested local artifact when working from the source repository:

npm ci --ignore-scripts
npm run check
node scripts/check-package.mjs --artifact

# In your consumer project:
npm install /path/to/fomo-sdk-ts/artifacts/jaxonchenjc-fomo-sdk-0.1.0.tgz

Read data

Activity may have userId: null: the provider has not associated that event with a Fomo user. The event is retained, and consumers must handle the nullable actor. Actual token rotation and automatic WS gap recovery are not qualified; do not treat the experimental stream as a lossless collector.

import { FomoClient, StaticSession } from "@jaxonchenjc/fomo-sdk";

const accessToken = process.env.FOMO_ACCESS_TOKEN;
if (!accessToken) throw new Error("Supply a Fomo access token");

const fomo = new FomoClient({
  session: new StaticSession({ accessToken }),
  timeoutMs: 10_000,
});

const me = await fomo.users.getCurrent();
const leaders = await fomo.leaderboards.list({ window: "24h", limit: 10 });
const swaps = await fomo.swaps.list({ userId: me.data.id });
const activity = await fomo.activity.list({ limit: 25 });
const feed = await fomo.tokens.feed({
  token: { networkId: 56, address: tokenAddress },
});
const holders = await fomo.tokens.holders({
  token: { networkId: 56, address: tokenAddress },
});

tokenAddress must be provided by the application. networkId is Fomo's provider identifier, not a universal EVM chain ID. No address case conversion or ownership inference is performed. Profile address/evmAddress are provider-reported fields.

Every result contains data and meta (operation, observation timestamp, contract revision and opaque session generation). These methods use GET only. There are no swap, signing, transfer, profile-upsert, follow or watchlist-write methods.

Numbers and unknown values

Financial values are decimal strings. The lossless JSON decoder preserves numeric lexemes before they can become IEEE754 doubles. Known count/network fields are converted only after safe-integer validation. Unknown extension fields are preserved, with unknown numeric literals also represented as decimal strings.

This is deliberate normalization: it does not claim the server sends strings. Missing values remain missing; null stays null; neither becomes zero. No decimals, price, USD rate, PnL or fee is invented. Unknown activity variants retain their original type and use kind: "unknown"; transfers preserve direction.

Thesis comment remains structured. Holder rows are open objects until a stronger row-level contract is qualified. See PROTOCOL.md.

Pagination and cancellation

const controller = new AbortController();
for await (const page of fomo.swaps.pages(
  { userId },
  { maxPages: 10, maxItems: 250, signal: controller.signal },
)) {
  await savePage(page); // application's own storage, not an SDK side effect
  console.log(page.pageInfo.stopReason);
}

Stop reasons distinguish server end from a client page/item cap. Repeated cursors and empty nonterminal pages fail explicitly. A clipped swap page's resume cursor names the last delivered item, not the last undispatched server item.

Activity exposes the source-derived lastId cursor through cursor; only swaps currently have an automatic page iterator. Token-feed continuation is not implemented: a hasNextPage flag is not a verified cursor contract. Reaching a server end marker does not prove global history completeness. Cross-page deduplication and persistent checkpoints belong to the collector.

Sessions

StaticSession is useful for short-lived reads. It does not refresh. For renewal:

import { RefreshableSession } from "@jaxonchenjc/fomo-sdk";

const session = new RefreshableSession({
  credentials, // explicitly loaded by your application
  refresh: async (current, { signal }) => {
    return yourApprovedRefresher(current, signal);
    // { action: "ignore" }
    // { action: "set", credentials: updatedCredentials }
    // { action: "clear" }
  },
  // Optional: persist atomically to your application's secure credential store.
  persist: async (updated) => secureStore.save(updated),
});

Share one session instance across connections. Refresh is single-flight within that instance. A cancelled caller stops waiting without cancelling another caller's renewal. A late401 cannot invalidate a newer generation.

  • ignore reuses an existing valid, non-rejected token.
  • Expired/rejected token plus ignore fails authentication.
  • set retains new material in memory before optional persistence. If persistence fails, the next acquisition retries persistence without spending the old refresh token again.
  • clear prevents releasing credentials; reauthorization requires a new session.
  • JWT expiry is a scheduling hint, not local proof of identity or authorization.

The persistence hook must settle and perform an atomic store operation. A hung hook blocks that shared session's update; request deadlines still bound individual callers. This SDK does not implement cross-process token-rotation locks: use a single broker/owner or a suitably coordinated store integration.

Experimental Privy adapter

import { createPrivySession } from "@jaxonchenjc/fomo-sdk/experimental/privy";

const session = createPrivySession({
  credentials: {
    accessToken,
    privyAccessToken,
    refreshToken,
    clientAuthId,
  },
  persist: async (updated) => secureStore.save(updated),
});
const fomo = new FomoClient({ session });

Variables above are caller-supplied secrets, not SDK-generated keys. This adapter uses the investigated Fomo/Privy integration, is unofficial, and can break when that integration changes. Only an ignore response has been live-observed in our preflight. True expiry, rotation, revocation and persistence remain live gates. Unit tests of set/clear are synthetic; they do not qualify autonomous cloud refresh.

No Google login automation, browser-token scraping, wallet-key export or automatic credential-file writes are included.

Native transport and errors

Impit 0.14.3 is lazily loaded by default, with Chrome-compatible networking and HTTP/3 disabled, matching the successful preflight. It is a native HTTP client, not a browser process. createFetchTransport() is available for explicitly chosen environments. There is no automatic fallback/impersonation escalation on403/430.

Connection options:

| Option | Default | | ------------------ | ----------------------------------- | | timeoutMs | 15000, total request budget | | maxRetries | 1 additional transient read attempt | | maxRetryDelayMs | 5000 | | maxResponseBytes | 4 MiB | | maxConcurrency | 4 | | maxQueueSize | 128 |

These are SDK resource bounds, not claimed Fomo quotas. Deadlines include credential waits, queueing, body reads and retry waits. Retry-After is not shortened to fit the budget, and cooldown is shared by a connection. 401 permits one session invalidation/reacquisition;403/430/431 do not retry. Native adapters reject redirects. Custom base origins/fetch/session callbacks are trusted input; HTTP is allowed only for explicitly enabled literal loopback test origins.

FomoError contains safe kind/operation/reason/status/attempt/retry-delay metadata, not raw response bodies, tokens or reflected server errors. Kinds distinguish authentication, access denial, rate limiting, protocol, network, cancellation, timeout, persistence, pagination and stream backpressure.

Experimental stream

import { FomoStreamClient } from "@jaxonchenjc/fomo-sdk/experimental/stream";

const stream = new FomoStreamClient(fomo);
for await (const message of stream.activity({ signal: controller.signal })) {
  if (message.kind === "activity") await recordObservation(message);
  if (message.kind === "gap") await recordPotentialGap(message);
}

The stream obtains and verifies the current account through the same HTTP client and session; arbitrary other-account subscriptions are not an API feature. Ready means a matching account/topic ACK, not socket-open.

  • Each reconnect revalidates the account and emits a potential-gap record.
  • Default reconnection budget is three reconnects total, not an infinite loop.
  • Queue overflow terminates with backpressure; it does not silently discard trades while claiming a healthy stream.
  • Cancellation/break closes the SDK's socket and listeners.
  • Built-in session providers notify credential changes; custom providers should implement subscribe or applications must abort their streams on logout.
  • Data envelopes are checked, but the activity payload is an open object until real WS data shapes are qualified. Numeric literals remain decimal strings.
  • No server resume cursor, backfill, application heartbeat or unsubscribe contract is invented. Quiet connections are not treated as dead just for lacking trades.
  • maxMessageBytes bounds parsing after delivery; the native socket implementation may buffer a complete frame first. Use a reviewed custom socket factory if a transport-level payload bound is required.

Development and release

npm run check runs formatting, strict typecheck, tests, and fresh tarball-consumer installation/type validation. Raw accounts, credentials and request captures are never committed; sanitized HTTP cassettes and WS traces run offline in CI. See record/replay development guide. The coverage matrix separates offline cases, live successes, known failures and unqualified combinations. CI defines Linux Node22/24/26 and macOS Node24. The manual package workflow produces a tested tarball and SHA256; it does not publish npm.

The core is implemented; production qualification is incomplete. See PROTOCOL.md, SECURITY.md, and CHANGELOG.md. This is not a lossless collector, PnL engine, backtester, wallet resolver or trading strategy.

An explicit bounded live read probe is available with npm run test:smoke. It requires both FOMO_LIVE_READS=1 and a caller-injected FOMO_ACCESS_TOKEN, makes two GET calls without retries, and prints only parsing/count metadata. Use a secret manager or an existing secure environment; do not put the token in shell history. CI never runs this probe.